(Partially) implement net::http::do
This commit is contained in:
parent
8cde44d639
commit
26b21689b5
|
@ -1,5 +1,7 @@
|
||||||
|
use fmt;
|
||||||
|
use io;
|
||||||
|
use net::dial;
|
||||||
use net::http;
|
use net::http;
|
||||||
use net::ip;
|
|
||||||
use net::uri;
|
use net::uri;
|
||||||
use os;
|
use os;
|
||||||
|
|
||||||
|
@ -7,12 +9,23 @@ export fn main() void = {
|
||||||
const client = http::newclient("Hare test client");
|
const client = http::newclient("Hare test client");
|
||||||
defer http::client_finish(&client);
|
defer http::client_finish(&client);
|
||||||
|
|
||||||
const req = http::get(&client, &uri::uri {
|
const target = match (uri::parse(os::args[1])) {
|
||||||
scheme = "http",
|
case let u: uri::uri =>
|
||||||
host = ip::LOCAL_V6,
|
yield u;
|
||||||
path = "/",
|
case uri::invalid =>
|
||||||
port = 8080,
|
fmt::fatal("Invalid URI");
|
||||||
...
|
};
|
||||||
});
|
defer uri::finish(&target);
|
||||||
http::write_header(os::stdout, &req.header)!;
|
|
||||||
|
const req = http::get(&client, &target);
|
||||||
|
const resp = match (http::do(&client, &req)) {
|
||||||
|
case let err: http::error =>
|
||||||
|
fmt::fatal("HTTP error:", http::strerror(err));
|
||||||
|
case let resp: http::response =>
|
||||||
|
yield resp;
|
||||||
|
};
|
||||||
|
|
||||||
|
// XXX TEMP
|
||||||
|
const body = resp.body as io::handle;
|
||||||
|
io::copy(os::stdout, body)!;
|
||||||
};
|
};
|
||||||
|
|
|
@ -20,10 +20,7 @@ export fn newclient(ua: str) client = {
|
||||||
|
|
||||||
// Frees resources associated with an HTTP [[client]].
|
// Frees resources associated with an HTTP [[client]].
|
||||||
export fn client_finish(client: *client) void = {
|
export fn client_finish(client: *client) void = {
|
||||||
for (let i = 0z; i < len(client.default_header); i += 1) {
|
header_free(&client.default_header);
|
||||||
free(client.default_header[i].1);
|
|
||||||
};
|
|
||||||
free(client.default_header);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Adds a default header which is included on all HTTP requests using a given
|
// Adds a default header which is included on all HTTP requests using a given
|
||||||
|
@ -95,6 +92,19 @@ fn request_set_content_length(req: *request, body: io::handle) void = {
|
||||||
add_header(&req.header, "Content-Length", strconv::ztos(ln: size));
|
add_header(&req.header, "Content-Length", strconv::ztos(ln: size));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fn uri_origin_form(target: *uri::uri) uri::uri = {
|
||||||
|
let target = *target;
|
||||||
|
target.scheme = "";
|
||||||
|
target.host = "";
|
||||||
|
target.fragment = "";
|
||||||
|
target.userinfo = "";
|
||||||
|
target.port = 0;
|
||||||
|
if (target.path == "") {
|
||||||
|
target.path = "/";
|
||||||
|
};
|
||||||
|
return target;
|
||||||
|
};
|
||||||
|
|
||||||
// Prepares a new HTTP GET request for the given client and fills in the default
|
// Prepares a new HTTP GET request for the given client and fills in the default
|
||||||
// headers, such as User-Agent and Host. Provide the return value to [[do]] to
|
// headers, such as User-Agent and Host. Provide the return value to [[do]] to
|
||||||
// execute the request and free associated resources, or use [[request_finish]]
|
// execute the request and free associated resources, or use [[request_finish]]
|
||||||
|
@ -123,7 +133,7 @@ export fn head(client: *client, target: *uri::uri) request = {
|
||||||
// [[request_finish]] to free resources without executing the request.
|
// [[request_finish]] to free resources without executing the request.
|
||||||
//
|
//
|
||||||
// If the provided I/O handle is seekable, the Content-Length header is added
|
// If the provided I/O handle is seekable, the Content-Length header is added
|
||||||
// automatically.
|
// automatically. Otherwise, Transfer-Encoding: chunked will be used.
|
||||||
//
|
//
|
||||||
// The URI parameter is borrowed from the caller for the lifetime of the request
|
// The URI parameter is borrowed from the caller for the lifetime of the request
|
||||||
// object.
|
// object.
|
||||||
|
@ -137,7 +147,7 @@ export fn post(client: *client, target: *uri::uri, body: io::handle) request = {
|
||||||
// [[request_finish]] to free resources without executing the request.
|
// [[request_finish]] to free resources without executing the request.
|
||||||
//
|
//
|
||||||
// If the provided I/O handle is seekable, the Content-Length header is added
|
// If the provided I/O handle is seekable, the Content-Length header is added
|
||||||
// automatically.
|
// automatically. Otherwise, Transfer-Encoding: chunked will be used.
|
||||||
//
|
//
|
||||||
// The URI parameter is borrowed from the caller for the lifetime of the request
|
// The URI parameter is borrowed from the caller for the lifetime of the request
|
||||||
// object.
|
// object.
|
||||||
|
|
57
net/http/do.ha
Normal file
57
net/http/do.ha
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
use bufio;
|
||||||
|
use fmt;
|
||||||
|
use io;
|
||||||
|
use net::dial;
|
||||||
|
use net::uri;
|
||||||
|
use net;
|
||||||
|
use os;
|
||||||
|
|
||||||
|
type context = struct {
|
||||||
|
conn: io::handle,
|
||||||
|
file: bufio::bufstream,
|
||||||
|
buf: []u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Performs an HTTP [[request]] with the given [[client]]. The request is
|
||||||
|
// performed synchronously; this function blocks until the server has returned
|
||||||
|
// the response status and all HTTP headers associated with the response.
|
||||||
|
export fn do(client: *client, req: *request) (response | error) = {
|
||||||
|
let ctx = context {
|
||||||
|
buf = alloc([0...], os::BUFSIZ),
|
||||||
|
...
|
||||||
|
};
|
||||||
|
defer free(ctx.buf);
|
||||||
|
|
||||||
|
const conn = dial::dial_uri("tcp", req.target)?;
|
||||||
|
ctx.file = bufio::buffered(conn, [], ctx.buf);
|
||||||
|
bufio::setflush(&ctx.file, []);
|
||||||
|
|
||||||
|
fmt::fprintf(&ctx.file, "{} ", req.method)?;
|
||||||
|
|
||||||
|
// TODO: Support other request-targets than origin-form
|
||||||
|
let target = uri_origin_form(req.target);
|
||||||
|
uri::fmt(&ctx.file, &target)?;
|
||||||
|
fmt::fprintf(&ctx.file, " HTTP/1.1\r\n")?;
|
||||||
|
|
||||||
|
// TODO: Handle Content-Length and Transfer-Encoding chunked/gzip
|
||||||
|
// properly
|
||||||
|
|
||||||
|
write_header(&ctx.file, &req.header)?;
|
||||||
|
fmt::fprintf(&ctx.file, "\r\n")?;
|
||||||
|
bufio::flush(&ctx.file)?;
|
||||||
|
|
||||||
|
match (req.body) {
|
||||||
|
case void =>
|
||||||
|
yield;
|
||||||
|
case let body: io::handle =>
|
||||||
|
// Copy to conn directly so we can use sendfile(2) if
|
||||||
|
// appropriate
|
||||||
|
io::copy(conn, body)?;
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: Parse resposne
|
||||||
|
return response {
|
||||||
|
body = conn,
|
||||||
|
...
|
||||||
|
};
|
||||||
|
};
|
17
net/http/error.ha
Normal file
17
net/http/error.ha
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
use io;
|
||||||
|
use net::dial;
|
||||||
|
|
||||||
|
// Errors possible while servicing HTTP requests. Note that these errors are for
|
||||||
|
// errors related to the processing of the HTTP connection; semantic HTTP errors
|
||||||
|
// such as [[STATUS_NOTFOUND]] are not handled by this type.
|
||||||
|
export type error = !(dial::error | io::error);
|
||||||
|
|
||||||
|
// Converts an [[error]] to a string.
|
||||||
|
export fn strerror(err: error) const str = {
|
||||||
|
match (err) {
|
||||||
|
case let err: dial::error =>
|
||||||
|
return dial::strerror(err);
|
||||||
|
case let err: io::error =>
|
||||||
|
return io::strerror(err);
|
||||||
|
};
|
||||||
|
};
|
|
@ -42,3 +42,11 @@ export fn write_header(sink: io::handle, head: *header) (size | io::error) = {
|
||||||
};
|
};
|
||||||
return z;
|
return z;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Frees state associated with an HTTP [[header]].
|
||||||
|
export fn header_free(head: *header) void = {
|
||||||
|
for (let i = 0z; i < len(head); i += 1) {
|
||||||
|
free(head[i].1);
|
||||||
|
};
|
||||||
|
free(*head);
|
||||||
|
};
|
||||||
|
|
|
@ -21,11 +21,7 @@ export type request = struct {
|
||||||
|
|
||||||
// Frees state associated with an HTTP [[request]].
|
// Frees state associated with an HTTP [[request]].
|
||||||
export fn request_finish(req: *request) void = {
|
export fn request_finish(req: *request) void = {
|
||||||
for (let i = 0z; i < len(req.header); i += 1) {
|
header_free(&req.header);
|
||||||
free(req.header[i].1);
|
|
||||||
};
|
|
||||||
free(req.header);
|
|
||||||
|
|
||||||
uri::finish(req.target);
|
uri::finish(req.target);
|
||||||
free(req.target);
|
free(req.target);
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
use io;
|
||||||
|
|
||||||
|
// Stores state related to an HTTP response.
|
||||||
|
export type response = struct {
|
||||||
|
// The HTTP status for this request as an integer.
|
||||||
|
status: uint,
|
||||||
|
// The HTTP status reason phrase.
|
||||||
|
reason: str,
|
||||||
|
// The HTTP headers provided by the server.
|
||||||
|
header: header,
|
||||||
|
// The response body, if any.
|
||||||
|
body: (io::handle | void),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Frees state associated with an HTTP [[response]].
|
||||||
|
export fn response_finish(resp: *response) void = {
|
||||||
|
header_free(&resp.header);
|
||||||
|
};
|
Loading…
Reference in a new issue