diff --git a/cmd/http/main.ha b/cmd/http/main.ha index c7de44e..ea0edf7 100644 --- a/cmd/http/main.ha +++ b/cmd/http/main.ha @@ -34,6 +34,6 @@ export fn main() void = { log::printfln("{}: {}", name, val); }; - const body = resp.body as *http::reader; + const body = resp.body as *io::stream; io::copy(os::stdout, body)!; }; diff --git a/net/http/do.ha b/net/http/do.ha index 5fa21ca..9347fe7 100644 --- a/net/http/do.ha +++ b/net/http/do.ha @@ -9,6 +9,7 @@ use net; use os; use strconv; use strings; +use types; // Performs an HTTP [[request]] with the given [[client]]. The request is // performed synchronously; this function blocks until the server has returned @@ -49,15 +50,28 @@ export fn do(client: *client, req: *request) (response | error) = { read_statusline(&resp, &scan)?; read_header(&resp.header, &scan)?; - const remain = bufio::scan_buffer(&scan); - const rd = alloc(reader { - vtable = &reader_vtable, - conn = conn, - ... - }); - rd.buffer[..len(remain)] = remain[..]; - rd.pending = len(remain); - resp.body = rd; + const cl = header_get(&resp.header, "Content-Length"); + const te = header_get(&resp.header, "Transfer-Encoding"); + if (len(cl) > 1 || len(te) > 1) { + return protoerr; + }; + + if (len(te) == 1) { + abort(); // TODO: Assign transport encoding appropriately + } else { + let length = types::SIZE_MAX; + if (len(cl) == 1) { + length = match (strconv::stoz(cl[0])) { + case let z: size => + yield z; + case => + return protoerr; + }; + }; + const remain = bufio::scan_buffer(&scan); + resp.body = new_identity_reader(conn, remain, length); + }; + return resp; }; diff --git a/net/http/header.ha b/net/http/header.ha index 53c416c..7766e25 100644 --- a/net/http/header.ha +++ b/net/http/header.ha @@ -11,6 +11,7 @@ export type header = [](str, str); // Adds a given HTTP header, which may be added more than once. The name should // be canonicalized by the caller. export fn header_add(head: *header, name: str, val: str) void = { + assert(len(name) > 1 && len(val) > 1); append(head, (strings::dup(name), strings::dup(val))); }; @@ -34,6 +35,18 @@ export fn header_del(head: *header, name: str) void = { }; }; +// Retrieves a value, or values, from a header. The empty slice indicates the +// abscence of a header. +export fn header_get(head: *header, name: str) []str = { + for (let i = 0z; i < len(head); i += 1) { + const (key, val) = head[i]; + if (key == name) { + return [val]; + }; + }; + return []; +}; + // Frees state associated with an HTTP [[header]]. export fn header_free(head: *header) void = { for (let i = 0z; i < len(head); i += 1) { diff --git a/net/http/response.ha b/net/http/response.ha index 88a25b5..4052db7 100644 --- a/net/http/response.ha +++ b/net/http/response.ha @@ -12,44 +12,12 @@ export type response = struct { // The HTTP headers provided by the server. header: header, // The response body, if any. - body: nullable *reader, + body: nullable *io::stream, }; // Frees state associated with an HTTP [[response]]. export fn response_finish(resp: *response) void = { header_free(&resp.header); free(resp.reason); -}; - -export type reader = struct { - vtable: io::stream, - conn: io::handle, - buffer: [os::BUFSIZ]u8, - pending: size, -}; - -const reader_vtable = io::vtable { - reader = &reader_read, - ... -}; - -fn reader_read(s: *io::stream, buf: []u8) (size | io::EOF | io::error) = { - let rd = s: *reader; - if (rd.pending == 0) { - match (io::read(rd.conn, rd.buffer)?) { - 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; - return n; + free(resp.body); }; diff --git a/net/http/transport.ha b/net/http/transport.ha new file mode 100644 index 0000000..24a036a --- /dev/null +++ b/net/http/transport.ha @@ -0,0 +1,69 @@ +use io; +use os; + +export type identity_reader = struct { + vtable: io::stream, + conn: io::handle, + buffer: [os::BUFSIZ]u8, + pending: size, + length: size, +}; + +const identity_reader_vtable = io::vtable { + reader = &identity_read, + ... +}; + +// 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, + content_length: size, +) *io::stream = { + let rd = alloc(identity_reader { + vtable = &identity_reader_vtable, + conn = conn, + length = content_length, + ... + }); + rd.buffer[..len(buffer)] = buffer[..]; + rd.pending = len(buffer); + return rd; +}; + +fn identity_read( + s: *io::stream, + buf: []u8, +) (size | io::EOF | io::error) = { + let rd = s: *identity_reader; + assert(rd.vtable == &identity_reader_vtable); + + if (rd.length <= 0) { + return io::EOF; + }; + + if (rd.pending == 0) { + let nread = rd.length; + if (nread > len(rd.buffer)) { + nread = len(rd.buffer); + }; + + 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; +};