Adding net/http-client and using it underneath net/url
original commit: e8bafbd9b9
This commit is contained in:
parent
c447b25384
commit
6b5cd82d29
120
pkgs/racket-pkgs/racket-doc/net/scribblings/http-client.scrbl
Normal file
120
pkgs/racket-pkgs/racket-doc/net/scribblings/http-client.scrbl
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
#lang scribble/doc
|
||||||
|
@(require "common.rkt" scribble/bnf
|
||||||
|
(for-label net/http-client
|
||||||
|
racket/list
|
||||||
|
openssl))
|
||||||
|
|
||||||
|
@title[#:tag "http-client"]{HTTP Client}
|
||||||
|
|
||||||
|
@defmodule[net/http-client]{The @racketmodname[net/http-client] library provides
|
||||||
|
utilities to use the HTTP protocol.}
|
||||||
|
|
||||||
|
@defproc[(http-conn? [x any/c])
|
||||||
|
boolean?]{
|
||||||
|
|
||||||
|
Identifies an HTTP connection.
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@defproc[(http-conn-live? [x any/c])
|
||||||
|
boolean?]{
|
||||||
|
|
||||||
|
Identifies an HTTP connection that is "live", i.e. one for which
|
||||||
|
@racket[http-conn-send!] is valid.
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@defproc[(http-conn)
|
||||||
|
http-conn?]{
|
||||||
|
|
||||||
|
Returns a fresh HTTP connection.
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@defproc[(http-conn-open! [hc http-conn?] [host (or/c bytes? string?)]
|
||||||
|
[#:ssl? ssl? (or/c boolean? ssl-client-context? symbol?) #f]
|
||||||
|
[#:port port (between/c 1 65535) (if ssl? 443 80)])
|
||||||
|
void?]{
|
||||||
|
|
||||||
|
Uses @racket[hc] to connect to @racket[host] on port @racket[port]
|
||||||
|
using SSL if @racket[ssl?] is not @racket[#f] (using @racket[ssl?] as
|
||||||
|
an argument to @racket[ssl-connect] to, for example, check
|
||||||
|
certificates.)
|
||||||
|
|
||||||
|
If @racket[hc] is live, the connection is closed.
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@defproc[(http-conn-open [host (or/c bytes? string?)]
|
||||||
|
[#:ssl? ssl? (or/c boolean? ssl-client-context? symbol?) #f]
|
||||||
|
[#:port port (between/c 1 65535) (if ssl? 443 80)])
|
||||||
|
http-conn?]{
|
||||||
|
|
||||||
|
Calls @racket[http-conn-open!] with a fresh connection, which is returned.
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@defproc[(http-conn-close! [hc http-conn?])
|
||||||
|
void?]{
|
||||||
|
|
||||||
|
Closes @racket[hc] if it is live.
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@defproc[(http-conn-send! [hc http-conn-live?] [uri (or/c bytes? string?)]
|
||||||
|
[#:method method (or/c bytes? string? symbol?) #"GET"]
|
||||||
|
[#:headers headers (listof (or/c bytes? string?)) empty]
|
||||||
|
[#:data data (or/c false/c bytes? string?) #f])
|
||||||
|
void?]{
|
||||||
|
|
||||||
|
Sends an HTTP request to @racket[hc] to the URI @racket[uri] using the
|
||||||
|
method @racket[method] and the additional headers given in
|
||||||
|
@racket[headers] and the additional data @racket[data].
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@defproc[(http-conn-recv! [hc http-conn-live?]
|
||||||
|
[#:close? close? boolean? #f])
|
||||||
|
(values bytes? (listof bytes?) input-port?)]{
|
||||||
|
|
||||||
|
Parses an HTTP response from @racket[hc].
|
||||||
|
|
||||||
|
Returns the status line, a list of headers, and an port which contains
|
||||||
|
the contents of the response.
|
||||||
|
|
||||||
|
If @racket[close?] is @racket[#t], then the connection will be closed
|
||||||
|
following the response parsing. If @racket[close?] is @racket[#f],
|
||||||
|
then the connection is only closed if the server instructs the client
|
||||||
|
to do so.
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@defproc[(http-conn-sendrecv! [hc http-conn-live?] [uri (or/c bytes? string?)]
|
||||||
|
[#:method method (or/c bytes? string? symbol?) #"GET"]
|
||||||
|
[#:headers headers (listof (or/c bytes? string?)) empty]
|
||||||
|
[#:data data (or/c false/c bytes? string?) #f]
|
||||||
|
[#:close? close? boolean? #f])
|
||||||
|
(values bytes? (listof bytes?) input-port?)]{
|
||||||
|
|
||||||
|
Calls @racket[http-conn-send!] and @racket[http-conn-recv!] in sequence.
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@defproc[(http-sendrecv [host (or/c bytes? string?)] [uri (or/c bytes? string?)]
|
||||||
|
[#:ssl? ssl? (or/c boolean? ssl-client-context? symbol?) #f]
|
||||||
|
[#:port port (between/c 1 65535) (if ssl? 443 80)]
|
||||||
|
[#:method method (or/c bytes? string? symbol?) #"GET"]
|
||||||
|
[#:headers headers (listof (or/c bytes? string?)) empty]
|
||||||
|
[#:data data (or/c false/c bytes? string?) #f])
|
||||||
|
(values bytes? (listof bytes?) input-port?)]{
|
||||||
|
|
||||||
|
Calls @racket[http-conn-send!] and @racket[http-conn-recv!] in
|
||||||
|
sequence on a fresh HTTP connection produced by
|
||||||
|
@racket[http-conn-open].
|
||||||
|
|
||||||
|
The HTTP connection is not returned, so it is always closed after one
|
||||||
|
response, which is why there is no @racket[#:closed?] argument like
|
||||||
|
@racket[http-conn-recv!].
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
@table-of-contents[]
|
@table-of-contents[]
|
||||||
|
|
||||||
|
@include-section["http-client.scrbl"]
|
||||||
@include-section["url.scrbl"]
|
@include-section["url.scrbl"]
|
||||||
@include-section["uri-codec.scrbl"]
|
@include-section["uri-codec.scrbl"]
|
||||||
@include-section["websocket.scrbl"]
|
@include-section["websocket.scrbl"]
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
#lang scribble/doc
|
#lang scribble/doc
|
||||||
@(require "common.rkt" scribble/bnf
|
@(require "common.rkt" scribble/bnf
|
||||||
(for-label net/url net/url-unit net/url-sig
|
(for-label net/url net/url-unit net/url-sig
|
||||||
|
racket/list
|
||||||
net/head net/uri-codec net/tcp-sig
|
net/head net/uri-codec net/tcp-sig
|
||||||
|
net/http-client
|
||||||
(only-in net/url-connect current-https-protocol)
|
(only-in net/url-connect current-https-protocol)
|
||||||
openssl))
|
openssl))
|
||||||
|
|
||||||
|
@ -428,6 +430,18 @@ mapping is the empty list (i.e., no proxies).}
|
||||||
Identifies an error thrown by URL functions.
|
Identifies an error thrown by URL functions.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@defproc[(http-sendrecv/url [u url?]
|
||||||
|
[#:method method (or/c bytes? string? symbol?) #"GET"]
|
||||||
|
[#:headers headers (listof (or/c bytes? string?)) empty]
|
||||||
|
[#:data data (or/c false/c bytes? string?) #f])
|
||||||
|
(values bytes? (listof bytes?) input-port?)]{
|
||||||
|
|
||||||
|
Calls @racket[http-sendrecv] using @racket[u] to populate the host, URI, port, and SSL parameters.
|
||||||
|
|
||||||
|
This function does not support proxies.
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@section{URL HTTPS mode}
|
@section{URL HTTPS mode}
|
||||||
|
|
||||||
@defmodule[net/url-connect]
|
@defmodule[net/url-connect]
|
||||||
|
|
146
pkgs/racket-pkgs/racket-test/tests/net/http-client.rkt
Normal file
146
pkgs/racket-pkgs/racket-test/tests/net/http-client.rkt
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
#lang racket/base
|
||||||
|
(module+ test
|
||||||
|
(require rackunit
|
||||||
|
racket/tcp
|
||||||
|
racket/port
|
||||||
|
racket/list
|
||||||
|
(prefix-in hc: net/http-client)
|
||||||
|
(prefix-in u: net/url))
|
||||||
|
|
||||||
|
(define (port->bytes* in)
|
||||||
|
(define ob (open-output-bytes))
|
||||||
|
(let loop ()
|
||||||
|
(sleep)
|
||||||
|
(when (byte-ready? in)
|
||||||
|
(define b (read-byte in))
|
||||||
|
(unless (eof-object? b)
|
||||||
|
(write-byte b ob)
|
||||||
|
(loop))))
|
||||||
|
(get-output-bytes ob))
|
||||||
|
|
||||||
|
(define-syntax-rule (tests [t ...] ...)
|
||||||
|
(begin (test t ...) ...))
|
||||||
|
|
||||||
|
(define-syntax-rule (test-e the-port e raw ereq estatus eheaders econtent)
|
||||||
|
(let ()
|
||||||
|
(define l (tcp-listen 0 40 #t "127.0.0.1"))
|
||||||
|
(define-values (_1 the-port _2 _3)
|
||||||
|
(tcp-addresses l #t))
|
||||||
|
(define req #f)
|
||||||
|
(define lt
|
||||||
|
(thread
|
||||||
|
(λ ()
|
||||||
|
(define-values (in out) (tcp-accept l))
|
||||||
|
(tcp-close l)
|
||||||
|
(display raw out)
|
||||||
|
(flush-output out)
|
||||||
|
(tcp-abandon-port out)
|
||||||
|
(close-output-port out)
|
||||||
|
(set! req (port->bytes* in))
|
||||||
|
(tcp-abandon-port in)
|
||||||
|
(close-input-port in))))
|
||||||
|
(define-values (status headers content-port)
|
||||||
|
e)
|
||||||
|
(thread-wait lt)
|
||||||
|
(check-equal? req ereq)
|
||||||
|
(check-equal? status estatus)
|
||||||
|
(check-equal? headers eheaders)
|
||||||
|
(check-equal? (port->bytes content-port) econtent)))
|
||||||
|
|
||||||
|
(define-syntax-rule (test raw ereq estatus eheaders econtent)
|
||||||
|
(begin
|
||||||
|
(test-e the-port
|
||||||
|
(hc:http-sendrecv "localhost" "/"
|
||||||
|
#:ssl? #f
|
||||||
|
#:port the-port
|
||||||
|
#:method "GET"
|
||||||
|
#:headers empty
|
||||||
|
#:data #f)
|
||||||
|
raw ereq estatus eheaders econtent)
|
||||||
|
(test-e the-port
|
||||||
|
(u:http-sendrecv/url
|
||||||
|
(u:make-url "http" #f "localhost" the-port #t (list (u:path/param "" empty)) empty #f)
|
||||||
|
#:method "GET"
|
||||||
|
#:headers empty
|
||||||
|
#:data #f)
|
||||||
|
raw ereq estatus eheaders econtent)))
|
||||||
|
|
||||||
|
(tests
|
||||||
|
["HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nTransfer-Encoding: chunked\r\n\r\n24\r\nThis is the data in the first chunk \r\n1A\r\nand this is the second one\r\n0\r\n"
|
||||||
|
#"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n"
|
||||||
|
#"HTTP/1.1 200 OK"
|
||||||
|
'(#"Content-Type: text/plain" #"Transfer-Encoding: chunked")
|
||||||
|
#"This is the data in the first chunk and this is the second one"]
|
||||||
|
|
||||||
|
["HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\n\r\nThis is the data in the first chunk and this is the second one"
|
||||||
|
#"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n"
|
||||||
|
#"HTTP/1.0 200 OK"
|
||||||
|
'(#"Content-Type: text/plain")
|
||||||
|
#"This is the data in the first chunk and this is the second one"]
|
||||||
|
|
||||||
|
["HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nTransfer-Encoding: chunked\r\n\r\n20\r\nThis is the data in the first ch\r\n21\r\nand this is the second oneXXXXXXX\r\n0\r\n"
|
||||||
|
|
||||||
|
#"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n"
|
||||||
|
#"HTTP/1.1 200 OK"
|
||||||
|
'(#"Content-Type: text/plain" #"Transfer-Encoding: chunked")
|
||||||
|
#"This is the data in the first chand this is the second oneXXXXXXX"]
|
||||||
|
|
||||||
|
["HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nTransfer-Encoding: chunked\r\n\r\n24\r\nThis is the data in the first chunk \r\n1A\r\nand this is the second one\r\n0\r\n"
|
||||||
|
#"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n"
|
||||||
|
#"HTTP/1.1 200 OK"
|
||||||
|
'(#"Content-Type: text/plain" #"Transfer-Encoding: chunked")
|
||||||
|
#"This is the data in the first chunk and this is the second one"]
|
||||||
|
|
||||||
|
["HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\n\r\nThis is the data in the first chunk and this is the second one"
|
||||||
|
#"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n"
|
||||||
|
#"HTTP/1.0 200 OK"
|
||||||
|
'(#"Content-Type: text/plain")
|
||||||
|
#"This is the data in the first chunk and this is the second one"]
|
||||||
|
|
||||||
|
["HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nTransfer-Encoding: chunked\r\n\r\n20\r\nThis is the data in the first ch\r\n21\r\nand this is the second oneXXXXXXX\r\n0\r\n"
|
||||||
|
#"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n"
|
||||||
|
#"HTTP/1.1 200 OK"
|
||||||
|
'(#"Content-Type: text/plain" #"Transfer-Encoding: chunked")
|
||||||
|
#"This is the data in the first chand this is the second oneXXXXXXX"]
|
||||||
|
|
||||||
|
["HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\n\r\nThis is the data in the first chunk and this is the second one\r\n"
|
||||||
|
#"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n"
|
||||||
|
#"HTTP/1.0 200 OK"
|
||||||
|
'(#"Content-Type: text/plain")
|
||||||
|
#"This is the data in the first chunk and this is the second one\r\n"]
|
||||||
|
|
||||||
|
["HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nTransfer-Encoding: chunked\r\n\r\n24\r\nThis is the data in the first chunk \r\n1A\r\nand this is the second one\r\n0\r\n"
|
||||||
|
#"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n"
|
||||||
|
#"HTTP/1.1 200 OK"
|
||||||
|
'(#"Content-Type: text/plain" #"Transfer-Encoding: chunked")
|
||||||
|
#"This is the data in the first chunk and this is the second one"]
|
||||||
|
|
||||||
|
["HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\n\r\nThis is the data in the first chunk and this is the second one"
|
||||||
|
#"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n"
|
||||||
|
#"HTTP/1.0 200 OK"
|
||||||
|
'(#"Content-Type: text/plain")
|
||||||
|
#"This is the data in the first chunk and this is the second one"]
|
||||||
|
|
||||||
|
["HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nTransfer-Encoding: chunked\r\n\r\n20\r\nThis is the data in the first ch\r\n21\r\nand this is the second oneXXXXXXX\r\n0\r\n"
|
||||||
|
#"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n"
|
||||||
|
#"HTTP/1.1 200 OK"
|
||||||
|
'(#"Content-Type: text/plain" #"Transfer-Encoding: chunked")
|
||||||
|
#"This is the data in the first chand this is the second oneXXXXXXX"]
|
||||||
|
|
||||||
|
["HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nTransfer-Encoding: chunked\r\nAnother-Header: ta-daa\r\n\r\n20\r\nThis is the data in the first ch\r\n21\r\nand this is the second oneXXXXXXX\r\n0\r\n"
|
||||||
|
#"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n"
|
||||||
|
#"HTTP/1.1 200 OK"
|
||||||
|
'(#"Content-Type: text/plain" #"Transfer-Encoding: chunked" #"Another-Header: ta-daa")
|
||||||
|
#"This is the data in the first chand this is the second oneXXXXXXX"]
|
||||||
|
|
||||||
|
["HTTP/1.1 301 Moved Permanently\r\nLocation: http://localhost:9002/whatever\r\n\r\nstuff"
|
||||||
|
#"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n"
|
||||||
|
#"HTTP/1.1 301 Moved Permanently"
|
||||||
|
'(#"Location: http://localhost:9002/whatever")
|
||||||
|
#"stuff"]
|
||||||
|
|
||||||
|
["HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nTransfer-Encoding: chunked\r\nAnother-Header: ta-daa\r\n\r\n20\r\nThis is the data in the first ch\r\n21\r\nand this is the second oneXXXXXXX\r\n0\r\n"
|
||||||
|
#"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n"
|
||||||
|
#"HTTP/1.1 200 OK"
|
||||||
|
'(#"Content-Type: text/plain" #"Transfer-Encoding: chunked" #"Another-Header: ta-daa")
|
||||||
|
#"This is the data in the first chand this is the second oneXXXXXXX"]))
|
Loading…
Reference in New Issue
Block a user