diff --git a/cmd/http/main.ha b/cmd/http/main.ha index ea0edf7..72d8901 100644 --- a/cmd/http/main.ha +++ b/cmd/http/main.ha @@ -6,7 +6,7 @@ use net::uri; use os; export fn main() void = { - const client = http::newclient("Hare test client"); + const client = http::newclient("Hare net::http test client"); defer http::client_finish(&client); const target = match (uri::parse(os::args[1])) { @@ -36,4 +36,5 @@ export fn main() void = { const body = resp.body as *io::stream; io::copy(os::stdout, body)!; + io::close(body)!; }; diff --git a/net/http/do.ha b/net/http/do.ha index a9b2c16..3c01ceb 100644 --- a/net/http/do.ha +++ b/net/http/do.ha @@ -14,6 +14,9 @@ use types; // 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. +// +// If the provided [[response]] has a non-null body, the user must pass it to +// [[io::close]] before calling [[response_finish]]. export fn do(client: *client, req: *request) (response | error) = { assert(req.target.scheme == "http"); // TODO: https const conn = dial::dial_uri("tcp", req.target)?; @@ -53,7 +56,7 @@ export fn do(client: *client, req: *request) (response | error) = { }; let resp = response { ... }; - const scan = bufio::newscanner_static(conn, buf); + const scan = bufio::newscanner(conn, 512); read_statusline(&resp, &scan)?; read_header(&resp.header, &scan)?; resp.body = new_reader(conn, &resp, &scan)?; diff --git a/net/http/response.ha b/net/http/response.ha index 4052db7..36c0087 100644 --- a/net/http/response.ha +++ b/net/http/response.ha @@ -15,7 +15,9 @@ export type response = struct { body: nullable *io::stream, }; -// Frees state associated with an HTTP [[response]]. +// Frees state associated with an HTTP [[response]]. If the response has a +// non-null body, the user must call [[io::close]] prior to calling this +// function. export fn response_finish(resp: *response) void = { header_free(&resp.header); free(resp.reason); diff --git a/net/http/transport.ha b/net/http/transport.ha index 7c59307..55ac6b2 100644 --- a/net/http/transport.ha +++ b/net/http/transport.ha @@ -50,7 +50,7 @@ export type transport = struct { }; fn new_reader( - conn: io::handle, + conn: io::file, resp: *response, scan: *bufio::scanner, ) (*io::stream | errors::unsupported | protoerr) = { @@ -68,8 +68,7 @@ fn new_reader( return protoerr; }; }; - const remain = bufio::scan_buffer(scan); - return new_identity_reader(conn, remain, length); + return new_identity_reader(conn, scan, length); }; // TODO: Figure out the semantics for closing the stream @@ -115,33 +114,32 @@ fn new_reader( type identity_reader = struct { vtable: io::stream, - conn: io::handle, - buffer: [os::BUFSZ]u8, - pending: size, - length: size, + conn: io::file, + scan: *bufio::scanner, + src: io::limitstream, }; const identity_reader_vtable = io::vtable { reader = &identity_read, + closer = &identity_close, ... }; // Creates a new reader that reads data until the response's Content-Length is // reached; i.e. the null Transport-Encoding. fn new_identity_reader( - conn: io::handle, - buffer: []u8, + conn: io::file, + scan: *bufio::scanner, content_length: size, ) *io::stream = { - let rd = alloc(identity_reader { + const scan = alloc(*scan); + return alloc(identity_reader { vtable = &identity_reader_vtable, conn = conn, - length = content_length, + scan = scan, + src = io::limitreader(scan, content_length), ... }); - rd.buffer[..len(buffer)] = buffer[..]; - rd.pending = len(buffer); - return rd; }; fn identity_read( @@ -150,34 +148,20 @@ fn identity_read( ) (size | io::EOF | io::error) = { let rd = s: *identity_reader; assert(rd.vtable == &identity_reader_vtable); + return io::read(&rd.src, buf)?; +}; - if (rd.length <= 0) { - return io::EOF; - }; +fn identity_close(s: *io::stream) (void | io::error) = { + let rd = s: *identity_reader; + assert(rd.vtable == &identity_reader_vtable); - if (rd.pending == 0) { - let nread = rd.length; - if (nread > len(rd.buffer)) { - nread = len(rd.buffer); - }; + // Flush the remainder of the response in case the caller did not read + // it out entirely + io::copy(io::empty, &rd.src)?; - match (io::read(rd.conn, rd.buffer[..nread])?) { - case let n: size => - rd.pending = n; - case io::EOF => - return io::EOF; - }; - }; - - let n = len(buf); - if (n > rd.pending) { - n = rd.pending; - }; - buf[..n] = rd.buffer[..n]; - rd.buffer[..len(rd.buffer) - n] = rd.buffer[n..]; - rd.pending -= n; - rd.length -= n; - return n; + bufio::finish(rd.scan); + free(rd.scan); + io::close(rd.conn)?; }; type chunk_state = enum {