This thread contains a patchset. You're looking at the original emails,
but you may wish to use the patch review UI.
Review patch
5
[PATCH hare-http 0/5] http/server: hare-ev compatible
The first patch is a squashed commit to bring back the reviewed changed
we missed while merging the initial http server code (plus some
additional changes I did afterward).
The later patches refactorize the code to hare-ev.
[PATCH hare-http 1/5] http/server: fixes
We changed lot of things from this initial version.
Signed-off-by: Willow Barraco <contact@willowbarraco.fr>
---
cmd/http/main.ha | 9 + --
cmd/httpd/main.ha | 95 +++++++++++++ -----------------
net/http/do.ha | 17 +++ ---
net/http/header.ha | 13 +++ --
net/http/request.ha | 120 +++++++++++++++++++++++ ---------------
net/http/response.ha | 53 ++++++ -----------
net/http/server.ha | 130 ++++++++++++++++++++++++++++++++++ --------
net/http/status.ha | 4 + -
net/http/transport.ha | 24 ++++++ --
9 files changed, 278 insertions(+), 187 deletions(-)
diff --git a/cmd/http/main.ha b/cmd/http/main.ha
index e266c29..81bd7c3 100644
--- a/cmd/http/main.ha
+++ b/cmd/http/main.ha
@@ -64,12 +64,5 @@ export fn main() void = {
log::printfln("{}: {}", name, val);
};
- const body = match (resp.body) {
- case let st: *io::stream =>
- yield st;
- case null =>
- return;
- };
- io::copy(os::stdout, body)!;
- io::close(body)!;
+ io::copy(os::stdout, resp.body)!;
};
diff --git a/cmd/httpd/main.ha b/cmd/httpd/main.ha
index 819a2f5..7ecc85b 100644
--- a/cmd/httpd/main.ha
+++ b/cmd/httpd/main.ha
@@ -1,13 +1,14 @@
- use getopt;
- use net;
- use net::ip;
- use net::http;
- use net::dial;
- use os;
- use memio;
- use io;
- use fmt;
use bufio;
+ use fmt;
+ use getopt;
+ use io;
+ use log;
+ use memio;
+ use net::dial;
+ use net::http;
+ use net::ip;
+ use net;
+ use os;
use strings;
const usage: [_]getopt::help = [
@@ -20,7 +21,7 @@ export fn main() void = {
defer getopt::finish(&cmd);
let port: u16 = 8080;
- let ip_addr: ip::addr4 = [127, 0, 0, 1];
+ let ip_addr = ip::LOCAL_V4;
for (let i = 0z; i < len(cmd.opts); i += 1) {
const opt = cmd.opts[i];
@@ -37,56 +38,42 @@ export fn main() void = {
};
};
- const server = match (http::listen(ip_addr, port)) {
- case let this: *http::server =>
- yield this;
- case net::error => abort("failure while listening");
+ const serv = match (http::listen(ip_addr, port)) {
+ case let serv: http::server =>
+ yield serv;
+ case let err: net::error =>
+ log::fatalf("http::listen: {}", net::strerror(err));
};
- defer http::server_finish(server);
+ defer http::server_finish(&serv);
for (true) {
- const serv_req = match (http::serve(server)) {
- case let this: *http::server_request =>
- yield this;
- case net::error => abort("failure while serving");
- };
- defer http::serve_finish(serv_req);
+ const (req, rw) = http::serve(&serv)!;
+ defer http::parsed_request_finish(&req);
+ defer io::close(rw.sock)!;
- let buf = memio::dynamic();
- defer io::close(&buf)!;
- handlereq(&buf, &serv_req.request);
-
- http::response_write(
- serv_req.socket,
- http::STATUS_OK,
- &buf,
- ("Content-Type", "text/plain")
- )!;
+ const (ip, port) = http::peeraddr(&rw);
+ log::printfln("{}:{}: {} {}", ip::string(ip), port,
+ req.method, req.target.path);
+ handle(&req, &rw);
};
};
- export fn handlereq(buf: *io::stream, request: *http::request) void = {
- fmt::fprintfln(buf, "Method: {}", request.method)!;
- fmt::fprintfln(buf, "Path: {}", request.target.path)!;
- fmt::fprintfln(buf, "Fragment: {}", request.target.fragment)!;
- fmt::fprintfln(buf, "Query: {}", request.target.query)!;
- fmt::fprintfln(buf, "Headers: <<EOF")!;
- http::write_header(buf, &request.header)!;
- fmt::fprintfln(buf, "EOF")!;
+ fn handle(req: *http::request, rw: *http::response_writer) void = {
+ const buf = memio::dynamic();
+ defer io::close(&buf)!;
- match (request.body) {
- case void => void;
- case let body: io::handle =>
- fmt::fprintfln(buf, "Body: <<EOF")!;
- for (true) {
- match (bufio::read_line(body)!) {
- case let line: []u8 =>
- fmt::fprintln(buf, strings::fromutf8(line)!)!;
- break;
- case io::EOF =>
- break;
- };
- };
- fmt::fprintfln(buf, "EOF")!;
- };
+ fmt::fprintfln(&buf, "Method: {}", req.method)!;
+ fmt::fprintfln(&buf, "Path: {}", req.target.path)!;
+ fmt::fprintfln(&buf, "Fragment: {}", req.target.fragment)!;
+ fmt::fprintfln(&buf, "Query: {}", req.target.query)!;
+ fmt::fprintfln(&buf, "Headers:")!;
+ http::write_header(&buf, &req.header)!;
+ fmt::fprintln(&buf)!;
+ io::copy(&buf, req.body)!;
+
+ io::seek(&buf, 0, io::whence::SET)!;
+
+ http::response_add_header(rw, "Content-Type", "text/plain");
+ http::response_set_body(rw, &buf);
+ http::response_write(rw)!;
};
diff --git a/net/http/do.ha b/net/http/do.ha
index 9b5905b..99786eb 100644
--- a/net/http/do.ha
+++ b/net/http/do.ha
@@ -48,17 +48,18 @@ export fn do(client: *client, req: *request) (response | error) = {
assert(trans.request_content == content_mode::AUTO);
assert(trans.response_content == content_mode::AUTO);
- match (req.body) {
- case let body: io::handle =>
- io::copy(conn, body)?;
- case void =>
- yield;
- };
+ io::copy(conn, req.body)?;
- let resp = response { ... };
+ let resp = response {
+ body = io::empty,
+ ...
+ };
const scan = bufio::newscanner(conn, 512);
read_statusline(&resp, &scan)?;
- read_header(&resp.header, &scan)?;
+ match (read_header(&resp.header, &scan)?) {
+ case void => void;
+ case io::EOF => return protoerr;
+ };
const response_complete =
req.method == "HEAD" ||
diff --git a/net/http/header.ha b/net/http/header.ha
index c615484..ee9e40a 100644
--- a/net/http/header.ha
@@ -47,12 +47,17 @@ export fn header_get(head: *header, name: str) str = {
return "";
};
- // Frees state associated with an HTTP [[header]].
- export fn header_free(head: *header) void = {
+ // Finish state associated with an HTTP [[header]].
+ export fn header_finish(head: *header) void = {
for (let i = 0z; i < len(head); i += 1) {
free(head[i].0);
free(head[i].1);
};
+ };
+
+ // Frees state associated with an HTTP [[header]].
+ export fn header_free(head: *header) void = {
+ header_finish(head);
free(*head);
};
@@ -77,13 +82,13 @@ export fn write_header(sink: io::handle, head: *header) (size | io::error) = {
return z;
};
- fn read_header(head: *header, scan: *bufio::scanner) (void | io::error | protoerr) = {
+ fn read_header(head: *header, scan: *bufio::scanner) (void | io::EOF | io::error | protoerr) = {
for (true) {
const item = match (bufio::scan_string(scan, "\r\n")) {
case let line: const str =>
yield line;
case io::EOF =>
- break;
+ return io::EOF;
case let err: io::error =>
return err;
case utf8::invalid =>
diff --git a/net/http/request.ha b/net/http/request.ha
index cb85a8c..604c61a 100644
--- a/net/http/request.ha
+++ b/net/http/request.ha
@@ -1,13 +1,13 @@
+ use bufio;
+ use encoding::utf8;
use errors;
use fmt;
use io;
+ use memio;
use net::ip;
use net::uri;
use strconv;
use strings;
- use bufio;
- use memio;
- use encoding::utf8;
use types;
// Stores state related to an HTTP request.
@@ -19,6 +19,7 @@ use types;
export type request = struct {
// HTTP request method, e.g. GET
method: str,
+
// Request target URI.
//
// Note that the normal constraints for [[uri::parse]] are not upheld in
@@ -32,8 +33,9 @@ export type request = struct {
// Transport configuration, or null to use the [[client]] default.
transport: nullable *transport,
- // I/O reader for the request body, or void if there is no body.
- body: (io::handle | void),
+ // I/O reader for the request body, or [[io::empty]] if there is no
+ // body.
+ body: io::handle,
};
// Frees state associated with an HTTP [[request]].
@@ -54,7 +56,7 @@ export fn new_request(
target = alloc(uri::dup(target)),
header = header_dup(&client.default_header),
transport = null,
- body = void,
+ body = io::empty,
};
switch (req.target.scheme) {
case "http" =>
@@ -134,47 +136,79 @@ export type request_line = struct {
version: version,
};
- export fn request_parse(file: io::handle) (request | protoerr | io::error) = {
+ export fn request_parse(file: io::handle) (request | protoerr | errors::unsupported | io::error) = {
const scan = bufio::newscanner(file, types::SIZE_MAX);
defer bufio::finish(&scan);
+ match (request_scan(&scan)?) {
+ case io::EOF => return protoerr;
+ case let req: request => return req;
+ };
+ };
- const req_line = request_line_parse(&scan)?;
+ export fn request_scan(scan: *bufio::scanner) (request | io::EOF | protoerr | errors::unsupported | io::error) = {
+ const req_line = match (request_line_parse(scan)?) {
+ case let req_line: request_line => yield req_line;
+ case io::EOF => return io::EOF;
+ };
defer request_line_finish(&req_line);
let header: header = [];
- read_header(&header, &scan)?;
+ match (read_header(&header, scan)?) {
+ case void => void;
+ case io::EOF =>
+ header_finish(&header);
+ return io::EOF;
+ };
+
+ let body = io::empty;
+ const cl = header_get(&header, "Content-Length");
+ const te = header_get(&header, "Transfer-Encoding");
+
+ if (cl != "" && te == "") {
+ const cl = match(strconv::stou(cl)) {
+ case let l: uint => yield l;
+ case (strconv::invalid | strconv::overflow) => return protoerr;
+ };
+
+
+ let buf: []u8 = alloc([0...], cl);
+
+ match (io::readall(scan, buf)) {
+ case let n: size => void;
+ case io::EOF =>
+ free(buf);
+ return io::EOF;
+ case let err: io::error =>
+ free(buf);
+ if (err is io::underread) {
+ return io::EOF;
+ } else {
+ return err;
+ };
+ };
+
+ body = alloc(memio::dynamic_from(buf));
+ } else if (cl != "" || te != "") {
+ return errors::unsupported;
+ };
const target = match (req_line.uri) {
case let uri: request_server => return errors::unsupported;
case let uri: authority => return errors::unsupported;
- case let uri: *uri::uri => yield uri;
+ case let uri: *uri::uri => yield &uri::dup(uri);
case let path: str =>
- const uri = fmt::asprintf("http:{}", path);
- defer free(uri);
- yield alloc(uri::parse(uri)!);
+ const u = fmt::asprintf("http://{}", path);
+ defer free(u);
+ yield alloc(uri::parse(u)!);
};
- const length: (void | size) = void;
- const head_length = header_get(&header, "Content-Length");
- if ("" != head_length) {
- match (strconv::stoz(head_length)) {
- case let s: size => length = s;
- case strconv::invalid => return protoerr;
- case strconv::overflow => return protoerr;
- };
- };
-
- let body: (io::handle | void) = void;
- if (length is size) {
- const limit = io::limitreader(&scan, length as size);
- let _body = alloc(memio::dynamic());
- io::copy(_body, &limit)!;
- io::seek(_body, 0, io::whence::SET)!;
- body = _body;
+ const host = header_get(&header, "Host");
+ if (host != "") {
+ target.host = strings::dup(host);
};
return request {
- method = req_line.method,
+ method = strings::dup(req_line.method),
target = target,
header = header,
body = body,
@@ -182,21 +216,14 @@ export fn request_parse(file: io::handle) (request | protoerr | io::error) = {
};
};
- export fn parsed_request_finish(request: *request) void = {
- uri::finish(request.target);
- free(request.target);
-
- match (request.body) {
- case void => yield;
- case let body: io::handle =>
- io::close(body)!;
- free(body: *memio::stream);
- };
-
- header_free(&request.header);
+ export fn parsed_request_finish(req: *request) void = {
+ free(req.method);
+ uri::finish(req.target);
+ header_finish(&req.header);
+ io::close(req.body)!;
};
- export fn request_line_parse(scan: *bufio::scanner) (request_line | protoerr | io::error) = {
+ fn request_line_parse(scan: *bufio::scanner) (request_line | io::EOF | protoerr | io::error) = {
const line = match (bufio::scan_string(scan, "\r\n")) {
case let line: const str =>
yield line;
@@ -205,7 +232,7 @@ export fn request_line_parse(scan: *bufio::scanner) (request_line | protoerr | i
case utf8::invalid =>
return protoerr;
case io::EOF =>
- return protoerr;
+ return io::EOF;
};
const tok = strings::tokenize(line, " ");
@@ -269,9 +296,10 @@ export fn request_line_parse(scan: *bufio::scanner) (request_line | protoerr | i
};
};
- export fn request_line_finish(line: *request_line) void = {
+ fn request_line_finish(line: *request_line) void = {
match (line.uri) {
case let path: str => free(path);
+ case let uri: *uri::uri => uri::finish(uri);
case => yield;
};
};
diff --git a/net/http/response.ha b/net/http/response.ha
index 84f0c81..37e15aa 100644
--- a/net/http/response.ha
+++ b/net/http/response.ha
@@ -15,49 +15,28 @@ export type response = struct {
reason: str,
// The HTTP headers provided by the server.
header: header,
- // The response body, if any.
- body: nullable *io::stream,
+ // The response body, if any, or [[io::empty]].
+ body: io::handle,
};
- // 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.
+ // Frees state associated with an HTTP [[response]] and closes the response
+ // body.
export fn response_finish(resp: *response) void = {
+ // Ignore errors in case the caller closed it themselves
+ io::close(resp.body): void;
header_free(&resp.header);
free(resp.reason);
- free(resp.body);
};
- export fn response_write(
- rw: io::handle,
- status: uint,
- body: (void | io::handle),
- header: (str, str)...
+ // Formats an HTTP [[response]] and writes it to the given [[io::handle]].
+ export fn response_write_internal(
+ out: io::handle,
+ resp: *response,
) (void | io::error) = {
- fmt::fprintfln(rw, "HTTP/1.1 {} {}", status, status_reason(status))?;
-
- let header = header_dup(&header);
- defer header_free(&header);
-
- match (body) {
- case void => void;
- case let body: io::handle =>
- match (io::tell(body)) {
- case io::error => void;
- case let off: io::off =>
- header_add(&header, "Content-Length", strconv::i64tos(off));
- io::seek(body, 0, io::whence::SET)!;
- body = &io::limitreader(body, off: size);
- };
- };
-
- write_header(rw, &header)?;
-
- fmt::fprintln(rw)!;
-
- match (body) {
- case void => void;
- case let body: io::handle =>
- io::copy(rw, body)!;
- };
+ fmt::fprintf(out, "HTTP/{}.{} {} {}\r\n",
+ resp.version.0, resp.version.1,
+ resp.status, resp.reason)?;
+ write_header(out, &resp.header)?;
+ fmt::fprintf(out, "\r\n")?;
+ io::copy(out, resp.body)?;
};
diff --git a/net/http/server.ha b/net/http/server.ha
index 44e76ac..c709524 100644
--- a/net/http/server.ha
+++ b/net/http/server.ha
@@ -1,43 +1,125 @@
+ use io;
use net;
use net::ip;
use net::tcp;
+ use strconv;
+ use strings;
export type server = struct {
- socket: net::socket,
+ sock: net::socket,
};
- export type server_request = struct {
- socket: net::socket,
- request: request,
+ // Stores state for a pending HTTP [[response]] to be sent by a [[server]].
+ export type response_writer = struct {
+ // The pending response
+ resp: response,
+ // The remote client socket
+ sock: net::socket,
+ // Where to write the response
+ writeto: io::handle,
};
- export fn listen(ip: ip::addr, port: u16) (*server | net::error) = {
- return alloc(server {
- socket = tcp::listen(ip, port)?,
+ // Creates a [[server]] which listens for HTTP traffic on the given IP address
+ // and port, binding the socket as appropriate.
+ export fn listen(ip: ip::addr, port: u16) (server | net::error) = {
+ return server {
+ sock = tcp::listen(ip, port, tcp::reuseaddr)?,
+ };
+ };
+
+ // Frees resources associated with a [[server]] and closes its socket.
+ export fn server_finish(serv: *server) void = {
+ net::close(serv.sock)!;
+ };
+
+ // Listens for an incoming request on a [[server]], blocking until one is
+ // available and returning the ([[request]], [[response_writer]]) pair. The
+ // caller should configure the response as necessary (by populating
+ // [[response_writer]].resp directly, or with convenience functions like
+ // [[response_set_body]]) and pass it [[response_write]] before calling
+ // [[serve]] again.
+ //
+ // The response is initialized with a 200 OK status, an empty header, and an
+ // empty response body.
+ export fn serve(serv: *server) ((request, response_writer) | error) = {
+ // TODO: connection pooling
+ const sock = net::accept(serv.sock)?;
+
+ const req = request_parse(sock)?;
+ const resp = response {
+ version = (1, 1),
+ status = STATUS_OK,
+ reason = strings::dup(status_reason(STATUS_OK)),
+ header = [],
+ body = io::empty,
+ };
+ return (req, response_writer {
+ sock = sock,
+ resp = resp,
+ writeto = sock, // Same socket
});
};
- export fn server_finish(server: *server) void = {
- net::close(server.socket)!;
- free(server);
+ // Returns the remote peer address associated with this connection.
+ export fn peeraddr(rw: *response_writer) (ip::addr, u16) = {
+ return tcp::peeraddr(rw.sock) as (ip::addr, u16);
};
- export fn accept(server: *server) (net::socket | net::error) = {
- return net::accept(server.socket)?;
+ // Convenience function to set the response body of a [[response_writer]].
+ export fn response_set_body(
+ rw: *response_writer,
+ body: io::handle,
+ ) void = {
+ rw.resp.body = body;
};
- export fn serve(server: *server) (*server_request | net::error) = {
- const socket = accept(server)?;
- const request = request_parse(socket)!;
-
- return alloc(server_request {
- request = request,
- socket = socket,
- });
+ // Convenience function to set the response status of a [[response_writer]].
+ export fn response_set_status(
+ rw: *response_writer,
+ status: uint,
+ reason: str,
+ ) void = {
+ free(rw.resp.reason);
+ rw.resp.status = status;
+ rw.resp.reason = strings::dup(reason);
};
- export fn serve_finish(serv_req: *server_request) void = {
- parsed_request_finish(&serv_req.request);
- net::close(serv_req.socket)!;
- free(serv_req);
+ // Convenience function to add a header to a [[response_writer]].
+ export fn response_add_header(
+ rw: *response_writer,
+ name: str,
+ val: str,
+ ) void = {
+ header_add(&rw.resp.header, name, val);
+ };
+
+ // Sends a completed HTTP response to the connected client and frees resources
+ // associated with the response. The response body is not closed; the caller
+ // must do so themselves if appropriate.
+ //
+ // If the response does not have a Content-Length header and the response body
+ // is seekable, a Content-Length header will be added automatically.
+ export fn response_write(rw: *response_writer) (void | error) = {
+ defer response_finish(&rw.resp);
+
+ if (header_get(&rw.resp.header, "Content-Length") == "") {
+ add_content_length(rw);
+ };
+
+ response_write_internal(rw.writeto, &rw.resp)?;
+ };
+
+ fn add_content_length(rw: *response_writer) void = {
+ const body = rw.resp.body;
+ const orig = match (io::tell(body)) {
+ case let off: io::off =>
+ yield off;
+ case io::error => return;
+ };
+
+ let length = io::seek(body, 0, io::whence::END)!;
+ io::seek(body, orig, io::whence::SET)!;
+ length -= orig;
+
+ header_add(&rw.resp.header, "Content-Length", strconv::i64tos(length));
};
diff --git a/net/http/status.ha b/net/http/status.ha
index bbb418a..920b70a 100644
--- a/net/http/status.ha
+++ b/net/http/status.ha
@@ -20,9 +20,9 @@ export fn status_reason(status: uint) const str = {
case STATUS_SWITCHING_PROTOCOLS =>
return "Switching Protocols";
case STATUS_OK =>
- return "Continue";
+ return "OK";
case STATUS_CREATED =>
- return "Continue";
+ return "Created";
case STATUS_ACCEPTED =>
return "Accepted";
case STATUS_NONAUTHORITATIVE_INFO =>
diff --git a/net/http/transport.ha b/net/http/transport.ha
index 6343ccf..6832602 100644
--- a/net/http/transport.ha
+++ b/net/http/transport.ha
@@ -53,7 +53,7 @@ fn new_reader(
conn: io::file,
resp: *response,
scan: *bufio::scanner,
- ) (*io::stream | errors::unsupported | protoerr) = {
+ ) (io::handle | errors::unsupported | protoerr) = {
// TODO: Content-Encoding support
const cl = header_get(&resp.header, "Content-Length");
const te = header_get(&resp.header, "Transfer-Encoding");
@@ -77,11 +77,17 @@ fn new_reader(
// And it should not close the actual connection if it's still in the
// connection pool
// Unless it isn't in the pool, then it should!
+ // And this leaks, fix that too
let stream: io::handle = conn;
let buffer: []u8 = bufio::scan_buffer(scan);
const iter = strings::tokenize(te, ",");
- for (const tok => strings::next_token(&iter)) {
- const te = strings::trim(tok);
+ for (true) {
+ const te = match (strings::next_token(&iter)) {
+ case let tok: str =>
+ yield strings::trim(tok);
+ case done =>
+ break;
+ };
// XXX: We could add lzw support if someone added it to
// hare-compress
@@ -104,7 +110,7 @@ fn new_reader(
// Empty Transfer-Encoding header
return protoerr;
};
- return stream as *io::stream;
+ return stream;
};
type identity_reader = struct {
@@ -156,7 +162,9 @@ fn identity_close(s: *io::stream) (void | io::error) = {
bufio::finish(rd.scan);
free(rd.scan);
+ // TODO connection pool
io::close(rd.conn)?;
+ free(rd);
};
type chunk_state = enum {
@@ -192,6 +200,7 @@ fn new_chunked_reader(
const chunked_reader_vtable = io::vtable {
reader = &chunked_read,
+ closer = &chunked_close,
...
};
@@ -294,3 +303,10 @@ fn chunked_read(
rd.state = chunk_state::HEADER;
};
};
+
+ fn chunked_close(s: *io::stream) (void | io::error) = {
+ let rd = s: *chunked_reader;
+ // TODO connection pool
+ io::close(rd.conn)?;
+ free(rd);
+ };
--
2.44.0
[PATCH hare-http 2/5] Use add_content_length while doing requests
Signed-off-by: Willow Barraco <contact@willowbarraco.fr>
---
net/http/do.ha | 2 ++
net/http/header.ha | 15 +++++++++++++++
net/http/server.ha | 18 + -----------------
3 files changed, 18 insertions(+), 17 deletions(-)
diff --git a/net/http/do.ha b/net/http/do.ha
index 99786eb..a0020c6 100644
--- a/net/http/do.ha
+++ b/net/http/do.ha
@@ -32,6 +32,8 @@ export fn do(client: *client, req: *request) (response | error) = {
uri::fmt(&file, &target)?;
fmt::fprintf(&file, " HTTP/1.1\r\n")?;
+ add_content_length(req.body, &req.header);
+
write_header(&file, &req.header)?;
fmt::fprintf(&file, "\r\n")?;
bufio::flush(&file)?;
diff --git a/net/http/header.ha b/net/http/header.ha
index ee9e40a..88e57f1 100644
--- a/net/http/header.ha
@@ -2,6 +2,7 @@ use bufio;
use encoding::utf8;
use fmt;
use io;
+ use strconv;
use strings;
// List of HTTP headers.
@@ -108,3 +109,17 @@ fn read_header(head: *header, scan: *bufio::scanner) (void | io::EOF | io::error
header_add(head, name, val);
};
};
+
+ export fn add_content_length(body: io::handle, header: *header) void = {
+ const orig = match (io::tell(body)) {
+ case let off: io::off =>
+ yield off;
+ case io::error => return;
+ };
+
+ let length = io::seek(body, 0, io::whence::END)!;
+ io::seek(body, orig, io::whence::SET)!;
+ length -= orig;
+
+ header_add(header, "Content-Length", strconv::i64tos(length));
+ };
diff --git a/net/http/server.ha b/net/http/server.ha
index c709524..b42bfb8 100644
--- a/net/http/server.ha
+++ b/net/http/server.ha
@@ -2,7 +2,6 @@ use io;
use net;
use net::ip;
use net::tcp;
- use strconv;
use strings;
export type server = struct {
@@ -103,23 +102,8 @@ export fn response_write(rw: *response_writer) (void | error) = {
defer response_finish(&rw.resp);
if (header_get(&rw.resp.header, "Content-Length") == "") {
- add_content_length(rw);
+ add_content_length(rw.resp.body, &rw.resp.header);
};
response_write_internal(rw.writeto, &rw.resp)?;
};
-
- fn add_content_length(rw: *response_writer) void = {
- const body = rw.resp.body;
- const orig = match (io::tell(body)) {
- case let off: io::off =>
- yield off;
- case io::error => return;
- };
-
- let length = io::seek(body, 0, io::whence::END)!;
- io::seek(body, orig, io::whence::SET)!;
- length -= orig;
-
- header_add(&rw.resp.header, "Content-Length", strconv::i64tos(length));
- };
--
2.44.0
[PATCH hare-http 3/5] do: move to an external request_write_internal
Signed-off-by: Willow Barraco <contact@willowbarraco.fr>
---
net/http/do.ha | 26 + -------------------------
net/http/request.ha | 34 ++++++++++++++++++++++++++++++++++
2 files changed, 35 insertions(+), 25 deletions(-)
diff --git a/net/http/do.ha b/net/http/do.ha
index a0020c6..33a8f71 100644
--- a/net/http/do.ha
+++ b/net/http/do.ha
@@ -25,33 +25,9 @@ export fn do(client: *client, req: *request) (response | error) = {
let file = bufio::init(conn, [], buf);
bufio::setflush(&file, []);
- fmt::fprintf(&file, "{} ", req.method)?;
-
- // TODO: Support other request-targets than origin-form
- const target = uri_origin_form(req.target);
- uri::fmt(&file, &target)?;
- fmt::fprintf(&file, " HTTP/1.1\r\n")?;
-
- add_content_length(req.body, &req.header);
-
- write_header(&file, &req.header)?;
- fmt::fprintf(&file, "\r\n")?;
+ request_write_internal(&file, req, client)?;
bufio::flush(&file)?;
- const trans = match (req.transport) {
- case let t: *transport =>
- yield t;
- case =>
- yield &client.default_transport;
- };
- // TODO: Implement None
- assert(trans.request_transport == transport_mode::AUTO);
- assert(trans.response_transport == transport_mode::AUTO);
- assert(trans.request_content == content_mode::AUTO);
- assert(trans.response_content == content_mode::AUTO);
-
- io::copy(conn, req.body)?;
-
let resp = response {
body = io::empty,
...
diff --git a/net/http/request.ha b/net/http/request.ha
index 604c61a..efc07f1 100644
--- a/net/http/request.ha
+++ b/net/http/request.ha
@@ -303,3 +303,37 @@ fn request_line_finish(line: *request_line) void = {
case => yield;
};
};
+
+ // Formats an HTTP [[request]] and writes it to the given [[io::handle]].
+ export fn request_write_internal(
+ out: io::handle,
+ req: *request,
+ cli: *client,
+ ) (void | io::error) = {
+ fmt::fprintf(out, "{} ", req.method)?;
+
+ // TODO: Support other request-targets than origin-form
+ const target = uri_origin_form(req.target);
+ uri::fmt(out, &target)?;
+ fmt::fprintf(out, " HTTP/1.1\r\n")?;
+
+ add_content_length(req.body, &req.header);
+
+ write_header(out, &req.header)?;
+ fmt::fprintf(out, "\r\n")?;
+ bufio::flush(out)?;
+
+ const trans = match (req.transport) {
+ case let t: *transport =>
+ yield t;
+ case =>
+ yield &cli.default_transport;
+ };
+ // TODO: Implement None
+ assert(trans.request_transport == transport_mode::AUTO);
+ assert(trans.response_transport == transport_mode::AUTO);
+ assert(trans.request_content == content_mode::AUTO);
+ assert(trans.response_content == content_mode::AUTO);
+
+ io::copy(out, req.body)?;
+ };
--
2.44.0
[PATCH hare-http 4/5] Refact do to export parse responses methods
Signed-off-by: Willow Barraco <contact@willowbarraco.fr>
---
net/http/do.ha | 102 + ---------------------------
net/http/request.ha | 1 -
net/http/response.ha | 156 +++++++++++++++++++++++++++++++++++++++++++
3 files changed, 158 insertions(+), 101 deletions(-)
diff --git a/net/http/do.ha b/net/http/do.ha
index 33a8f71..b43970a 100644
--- a/net/http/do.ha
+++ b/net/http/do.ha
@@ -1,15 +1,7 @@
use bufio;
- use encoding::utf8;
- use errors;
- use fmt;
use io;
use net::dial;
- use net::uri;
- 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
@@ -20,6 +12,7 @@ use types;
export fn do(client: *client, req: *request) (response | error) = {
assert(req.target.scheme == "http"); // TODO: https
const conn = dial::dial_uri("tcp", req.target)?;
+ defer io::close(conn)!;
let buf: [os::BUFSZ]u8 = [0...];
let file = bufio::init(conn, [], buf);
@@ -28,96 +21,5 @@ export fn do(client: *client, req: *request) (response | error) = {
request_write_internal(&file, req, client)?;
bufio::flush(&file)?;
- let resp = response {
- body = io::empty,
- ...
- };
- const scan = bufio::newscanner(conn, 512);
- read_statusline(&resp, &scan)?;
- match (read_header(&resp.header, &scan)?) {
- case void => void;
- case io::EOF => return protoerr;
- };
-
- const response_complete =
- req.method == "HEAD" ||
- resp.status == STATUS_NO_CONTENT ||
- resp.status == STATUS_NOT_MODIFIED ||
- (resp.status >= 100 && resp.status < 200) ||
- (req.method == "CONNECT" && resp.status >= 200 && resp.status < 300);
- if (!response_complete) {
- resp.body = new_reader(conn, &resp, &scan)?;
- } else if (req.method != "CONNECT") {
- io::close(conn)!;
- };
- return resp;
- };
-
- fn read_statusline(
- resp: *response,
- scan: *bufio::scanner,
- ) (void | error) = {
- const status = match (bufio::scan_string(scan, "\r\n")) {
- case let line: const str =>
- yield line;
- case let err: io::error =>
- return err;
- case utf8::invalid =>
- return protoerr;
- case io::EOF =>
- return protoerr;
- };
-
- const tok = strings::tokenize(status, " ");
-
- const version = match (strings::next_token(&tok)) {
- case let ver: str =>
- yield ver;
- case done =>
- return protoerr;
- };
-
- const status = match (strings::next_token(&tok)) {
- case let status: str =>
- yield status;
- case done =>
- return protoerr;
- };
-
- const reason = match (strings::next_token(&tok)) {
- case let reason: str =>
- yield reason;
- case done =>
- return protoerr;
- };
-
- const (_, version) = strings::cut(version, "/");
- const (major, minor) = strings::cut(version, ".");
-
- const major = match (strconv::stou(major)) {
- case let u: uint =>
- yield u;
- case =>
- return protoerr;
- };
- const minor = match (strconv::stou(minor)) {
- case let u: uint =>
- yield u;
- case =>
- return protoerr;
- };
- resp.version = (major, minor);
-
- if (resp.version.0 > 1) {
- return errors::unsupported;
- };
-
- resp.status = match (strconv::stou(status)) {
- case let u: uint =>
- yield u;
- case =>
- return protoerr;
- };
-
- resp.reason = strings::dup(reason);
+ return response_parse(conn)?;
};
diff --git a/net/http/request.ha b/net/http/request.ha
index efc07f1..0933669 100644
--- a/net/http/request.ha
+++ b/net/http/request.ha
@@ -321,7 +321,6 @@ export fn request_write_internal(
write_header(out, &req.header)?;
fmt::fprintf(out, "\r\n")?;
- bufio::flush(out)?;
const trans = match (req.transport) {
case let t: *transport =>
diff --git a/net/http/response.ha b/net/http/response.ha
index 37e15aa..e3ece35 100644
--- a/net/http/response.ha
+++ b/net/http/response.ha
@@ -1,7 +1,13 @@
+ use bufio;
use io;
use os;
use fmt;
use strconv;
+ use errors;
+ use strings;
+ use types;
+ use encoding::utf8;
+ use memio;
export type version = (uint, uint);
@@ -40,3 +46,153 @@ export fn response_write_internal(
fmt::fprintf(out, "\r\n")?;
io::copy(out, resp.body)?;
};
+
+ export fn response_parse(file: io::handle) (response | protoerr | errors::unsupported | io::error) = {
+ const scan = bufio::newscanner(file, types::SIZE_MAX);
+ defer bufio::finish(&scan);
+ match (response_scan(&scan)?) {
+ case io::EOF => return protoerr;
+ case let res: response => return res;
+ };
+ };
+
+ export fn response_scan(scan: *bufio::scanner) (response | io::EOF | protoerr | errors::unsupported | io::error) = {
+ const resp_line = match (response_line_parse(scan)?) {
+ case let resp_line: response_line => yield resp_line;
+ case io::EOF => return io::EOF;
+ };
+ defer response_line_finish(&resp_line);
+
+ let header: header = [];
+ match (read_header(&header, scan)?) {
+ case void => void;
+ case io::EOF =>
+ header_finish(&header);
+ return io::EOF;
+ };
+
+ let body = io::empty;
+ const cl = header_get(&header, "Content-Length");
+ const te = header_get(&header, "Transfer-Encoding");
+
+ if (cl != "" && te == "") {
+ const cl = match(strconv::stou(cl)) {
+ case let l: uint => yield l;
+ case (strconv::invalid | strconv::overflow) => return protoerr;
+ };
+
+ let buf: []u8 = alloc([0...], cl);
+
+ match (io::readall(scan, buf)) {
+ case let n: size => void;
+ case io::EOF =>
+ free(buf);
+ return io::EOF;
+ case let err: io::error =>
+ free(buf);
+ if (err is io::underread) {
+ return io::EOF;
+ } else {
+ return err;
+ };
+ };
+
+ body = alloc(memio::dynamic_from(buf));
+ } else if (cl != "" || te != "") {
+ return errors::unsupported;
+ };
+
+ return response {
+ version = resp_line.version,
+ status = resp_line.status,
+ reason = strings::dup(resp_line.reason),
+ header = header,
+ body = body,
+ };
+ };
+
+ export fn parsed_response_finish(resp: *response) void = {
+ free(resp.reason);
+ header_finish(&resp.header);
+ };
+
+ export type response_line = struct {
+ version: version,
+ status: uint,
+ reason: str,
+ };
+
+ fn response_line_parse(scan: *bufio::scanner) (response_line | io::EOF | protoerr | io::error) = {
+ const status = match (bufio::scan_string(scan, "\r\n")) {
+ case let line: const str =>
+ yield line;
+ case let err: io::error =>
+ return err;
+ case utf8::invalid =>
+ return protoerr;
+ case io::EOF =>
+ return io::EOF;
+ };
+
+ const tok = strings::tokenize(status, " ");
+
+ const version = match (strings::next_token(&tok)) {
+ case let ver: str =>
+ yield ver;
+ case done =>
+ return protoerr;
+ };
+
+ const status = match (strings::next_token(&tok)) {
+ case let status: str =>
+ yield status;
+ case done =>
+ return protoerr;
+ };
+
+ const reason = match (strings::next_token(&tok)) {
+ case let reason: str =>
+ yield reason;
+ case done =>
+ return protoerr;
+ };
+
+ const (_, version) = strings::cut(version, "/");
+ const (major, minor) = strings::cut(version, ".");
+
+ const major = match (strconv::stou(major)) {
+ case let u: uint =>
+ yield u;
+ case =>
+ return protoerr;
+ };
+ const minor = match (strconv::stou(minor)) {
+ case let u: uint =>
+ yield u;
+ case =>
+ return protoerr;
+ };
+
+ if (major > 1) {
+ return errors::unsupported;
+ };
+
+ const status = match (strconv::stou(status)) {
+ case let u: uint =>
+ yield u;
+ case =>
+ return protoerr;
+ };
+
+ const reason = strings::dup(reason);
+
+ return response_line{
+ version = (major, minor),
+ status = status,
+ reason = reason,
+ };
+ };
+
+ fn response_line_finish(line: *response_line) void = {
+ free(line.reason);
+ };
--
2.44.0
[PATCH hare-http 5/5] use back transport for request/response scanning
We use an io::handle rather than an io::stream now that we check if the body is
complete while parsing the message.
Signed-off-by: Willow Barraco <contact@willowbarraco.fr>
---
cmd/http/main.ha | 2 + -
net/http/request.ha | 50 +++ -----
net/http/response.ha | 48 +++ -----
net/http/transport.ha | 277 ++++++++++ --------------------------------
4 files changed, 106 insertions(+), 271 deletions(-)
diff --git a/cmd/http/main.ha b/cmd/http/main.ha
index 81bd7c3..938e09f 100644
--- a/cmd/http/main.ha
+++ b/cmd/http/main.ha
@@ -53,7 +53,7 @@ export fn main() void = {
case let resp: http::response =>
yield resp;
};
- defer http::response_finish(&resp);
+ defer http::parsed_response_finish(&resp);
log::printfln("HTTP/{}.{}: {} {}",
resp.version.0, resp.version.1,
diff --git a/net/http/request.ha b/net/http/request.ha
index 0933669..e1aa4b5 100644
--- a/net/http/request.ha
+++ b/net/http/request.ha
@@ -160,36 +160,17 @@ export fn request_scan(scan: *bufio::scanner) (request | io::EOF | protoerr | er
return io::EOF;
};
- let body = io::empty;
- const cl = header_get(&header, "Content-Length");
- const te = header_get(&header, "Transfer-Encoding");
-
- if (cl != "" && te == "") {
- const cl = match(strconv::stou(cl)) {
- case let l: uint => yield l;
- case (strconv::invalid | strconv::overflow) => return protoerr;
- };
-
-
- let buf: []u8 = alloc([0...], cl);
-
- match (io::readall(scan, buf)) {
- case let n: size => void;
- case io::EOF =>
- free(buf);
- return io::EOF;
- case let err: io::error =>
- free(buf);
- if (err is io::underread) {
- return io::EOF;
- } else {
- return err;
- };
- };
-
- body = alloc(memio::dynamic_from(buf));
- } else if (cl != "" || te != "") {
- return errors::unsupported;
+ let body = match (body_scan(scan, &header)) {
+ case io::EOF => return io::EOF;
+ case let body: io::handle => yield body;
+ case let err: protoerr =>
+ return err;
+ case let err: errors::unsupported =>
+ return err;
+ case let err: utf8::invalid =>
+ return protoerr;
+ case let err: io::error =>
+ return err;
};
const target = match (req_line.uri) {
@@ -220,7 +201,14 @@ export fn parsed_request_finish(req: *request) void = {
free(req.method);
uri::finish(req.target);
header_finish(&req.header);
- io::close(req.body)!;
+ match (req.body) {
+ case let body: *io::stream =>
+ if (body != io::empty) {
+ io::close(body)!;
+ free(body);
+ };
+ case => void;
+ };
};
fn request_line_parse(scan: *bufio::scanner) (request_line | io::EOF | protoerr | io::error) = {
diff --git a/net/http/response.ha b/net/http/response.ha
index e3ece35..4369cf4 100644
--- a/net/http/response.ha
+++ b/net/http/response.ha
@@ -71,35 +71,17 @@ export fn response_scan(scan: *bufio::scanner) (response | io::EOF | protoerr |
return io::EOF;
};
- let body = io::empty;
- const cl = header_get(&header, "Content-Length");
- const te = header_get(&header, "Transfer-Encoding");
-
- if (cl != "" && te == "") {
- const cl = match(strconv::stou(cl)) {
- case let l: uint => yield l;
- case (strconv::invalid | strconv::overflow) => return protoerr;
- };
-
- let buf: []u8 = alloc([0...], cl);
-
- match (io::readall(scan, buf)) {
- case let n: size => void;
- case io::EOF =>
- free(buf);
- return io::EOF;
- case let err: io::error =>
- free(buf);
- if (err is io::underread) {
- return io::EOF;
- } else {
- return err;
- };
- };
-
- body = alloc(memio::dynamic_from(buf));
- } else if (cl != "" || te != "") {
- return errors::unsupported;
+ let body = match (body_scan(scan, &header)) {
+ case io::EOF => return io::EOF;
+ case let body: io::handle => yield body;
+ case let err: protoerr =>
+ return err;
+ case let err: errors::unsupported =>
+ return err;
+ case let err: utf8::invalid =>
+ return protoerr;
+ case let err: io::error =>
+ return err;
};
return response {
@@ -114,6 +96,14 @@ export fn response_scan(scan: *bufio::scanner) (response | io::EOF | protoerr |
export fn parsed_response_finish(resp: *response) void = {
free(resp.reason);
header_finish(&resp.header);
+ match (resp.body) {
+ case let body: *io::stream =>
+ if (body != io::empty) {
+ io::close(body)!;
+ free(body);
+ };
+ case => void;
+ };
};
export type response_line = struct {
diff --git a/net/http/transport.ha b/net/http/transport.ha
index 6832602..2fb015c 100644
--- a/net/http/transport.ha
+++ b/net/http/transport.ha
@@ -1,7 +1,9 @@
- use errors;
use bufio;
use bytes;
+ use encoding::utf8;
+ use errors;
use io;
+ use memio;
use os;
use strconv;
use strings;
@@ -49,26 +51,27 @@ export type transport = struct {
response_content: content_mode,
};
- fn new_reader(
- conn: io::file,
- resp: *response,
+ fn body_scan(
scan: *bufio::scanner,
- ) (io::handle | errors::unsupported | protoerr) = {
+ header: *header,
+ ) (io::handle | io::EOF | io::error | utf8::invalid | errors::unsupported | protoerr) = {
// TODO: Content-Encoding support
- const cl = header_get(&resp.header, "Content-Length");
- const te = header_get(&resp.header, "Transfer-Encoding");
+ const cl = header_get(header, "Content-Length");
+ const te = header_get(header, "Transfer-Encoding");
+
+ if (cl == "" && te == "") {
+ return io::empty;
+ };
if (cl != "" || te == "") {
- let length = types::SIZE_MAX;
if (cl != "") {
- length = match (strconv::stoz(cl)) {
+ match (strconv::stoz(cl)) {
case let z: size =>
- yield z;
+ return identity_body_scan(scan, z)?;
case =>
return protoerr;
};
};
- return new_identity_reader(conn, scan, length);
};
// TODO: Figure out the semantics for closing the stream
@@ -77,236 +80,90 @@ fn new_reader(
// And it should not close the actual connection if it's still in the
// connection pool
// Unless it isn't in the pool, then it should!
- // And this leaks, fix that too
- let stream: io::handle = conn;
- let buffer: []u8 = bufio::scan_buffer(scan);
const iter = strings::tokenize(te, ",");
for (true) {
const te = match (strings::next_token(&iter)) {
case let tok: str =>
yield strings::trim(tok);
case done =>
- break;
+ return errors::unsupported;
};
// XXX: We could add lzw support if someone added it to
// hare-compress
- const next = switch (te) {
+ switch (te) {
case "chunked" =>
- yield new_chunked_reader(stream, buffer);
+ return chunked_body_scan(scan)?;
case "deflate" =>
abort(); // TODO
case "gzip" =>
abort(); // TODO
case =>
- return errors::unsupported;
+ continue;
};
- stream = next;
-
- buffer = [];
};
-
- if (!(stream is *io::stream)) {
- // Empty Transfer-Encoding header
- return protoerr;
- };
- return stream;
};
- type identity_reader = struct {
- vtable: io::stream,
- 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
+ // Creates a new body handle that contains the full body until Content-Length is
// reached; i.e. the null Transport-Encoding.
- fn new_identity_reader(
- conn: io::file,
+ fn identity_body_scan(
scan: *bufio::scanner,
content_length: size,
- ) *io::stream = {
- const scan = alloc(*scan);
- return alloc(identity_reader {
- vtable = &identity_reader_vtable,
- conn = conn,
- scan = scan,
- src = io::limitreader(scan, content_length),
- ...
- });
- };
+ ) (io::handle | io::EOF | io::error) = {
+ let buf: []u8 = alloc([0...], content_length);
- fn identity_read(
- s: *io::stream,
- buf: []u8,
- ) (size | io::EOF | io::error) = {
- let rd = s: *identity_reader;
- assert(rd.vtable == &identity_reader_vtable);
- return io::read(&rd.src, buf)?;
- };
-
- fn identity_close(s: *io::stream) (void | io::error) = {
- let rd = s: *identity_reader;
- assert(rd.vtable == &identity_reader_vtable);
-
- // Flush the remainder of the response in case the caller did not read
- // it out entirely
- io::copy(io::empty, &rd.src)?;
-
- bufio::finish(rd.scan);
- free(rd.scan);
- // TODO connection pool
- io::close(rd.conn)?;
- free(rd);
- };
-
- type chunk_state = enum {
- HEADER,
- DATA,
- FOOTER,
- };
-
- type chunked_reader = struct {
- vtable: io::stream,
- conn: io::handle,
- buffer: [os::BUFSZ]u8,
- state: chunk_state,
- // Amount of read-ahead data in buffer
- pending: size,
- // Length of current chunk
- length: size,
- };
-
- fn new_chunked_reader(
- conn: io::handle,
- buffer: []u8,
- ) *io::stream = {
- let rd = alloc(chunked_reader {
- vtable = &chunked_reader_vtable,
- conn = conn,
- ...
- });
- rd.buffer[..len(buffer)] = buffer[..];
- rd.pending = len(buffer);
- return rd;
- };
-
- const chunked_reader_vtable = io::vtable {
- reader = &chunked_read,
- closer = &chunked_close,
- ...
- };
-
- fn chunked_read(
- s: *io::stream,
- buf: []u8,
- ) (size | io::EOF | io::error) = {
- // XXX: I am not satisfied with this code
- let rd = s: *chunked_reader;
- assert(rd.vtable == &chunked_reader_vtable);
-
- for (true) switch (rd.state) {
- case chunk_state::HEADER =>
- let crlf = 0z;
- for (true) {
- const n = rd.pending;
- match (bytes::index(rd.buffer[..n], ['\r', '\n'])) {
- case let z: size =>
- crlf = z;
- break;
- case void =>
- yield;
- };
- if (rd.pending >= len(rd.buffer)) {
- // Chunk header exceeds buffer size
- return errors::overflow;
- };
-
- match (io::read(rd.conn, rd.buffer[rd.pending..])?) {
- case let n: size =>
- rd.pending += n;
- case io::EOF =>
- if (rd.pending > 0) {
- return errors::invalid;
- };
- return io::EOF;
- };
+ match (io::readall(scan, buf)) {
+ case let n: size => void;
+ case io::EOF =>
+ free(buf);
+ return io::EOF;
+ case let err: io::error =>
+ free(buf);
+ if (err is io::underread) {
+ return io::EOF;
+ } else {
+ return err;
};
+ };
- // XXX: Should we do anything with chunk-ext?
- const header = rd.buffer[..crlf];
- const (ln, _) = bytes::cut(header, ';');
- const ln = match (strings::fromutf8(ln)) {
- case let s: str =>
- yield s;
- case =>
- return errors::invalid;
- };
+ return alloc(memio::dynamic_from(buf));
+ };
- match (strconv::stoz(ln, strconv::base::HEX)) {
- case let z: size =>
- rd.length = z;
- case =>
- return errors::invalid;
- };
- if (rd.length == 0) {
+ fn chunked_body_scan(
+ scan: *bufio::scanner,
+ ) (io::handle | io::EOF | io::error | utf8::invalid | protoerr) = {
+ let buf = alloc(memio::dynamic());
+
+ for (true) {
+ const header = match (bufio::scan_string(scan, "\r\n")?) {
+ case let line: const str =>
+ yield line;
+ case io::EOF =>
+ io::close(buf)!;
+ free(buf);
return io::EOF;
};
+ const length = match (strconv::stoz(header, strconv::base::HEX)) {
+ case let z: size =>
+ yield z;
+ case =>
+ return protoerr;
+ };
+ if (length == 0) {
+ io::seek(buf, 0, io::whence::SET)!;
+ return buf;
+ };
- const n = crlf + 2;
- rd.buffer[..rd.pending - n] = rd.buffer[n..rd.pending];
- rd.pending -= n;
- rd.state = chunk_state::DATA;
- case chunk_state::DATA =>
- if (rd.pending == 0) {
- match (io::read(rd.conn, rd.buffer)?) {
- case let n: size =>
- rd.pending += n;
- case io::EOF =>
- return io::EOF;
+ match (bufio::scan_string(scan, "\r\n")?) {
+ case let line: const str =>
+ if (len(line) != length) {
+ return protoerr;
};
+ io::write(buf, strings::toutf8(line))?;
+ case io::EOF =>
+ io::close(buf)!;
+ free(buf);
+ return io::EOF;
};
- let n = len(buf);
- if (n > rd.pending) {
- n = rd.pending;
- };
- if (n > rd.length) {
- n = rd.length;
- };
- buf[..n] = rd.buffer[..n];
- rd.buffer[..rd.pending - n] = rd.buffer[n..rd.pending];
- rd.pending -= n;
- rd.length -= n;
- rd.state = chunk_state::FOOTER;
- return n;
- case chunk_state::FOOTER =>
- for (rd.pending < 2) {
- match (io::read(rd.conn, rd.buffer[rd.pending..])?) {
- case let n: size =>
- rd.pending += n;
- case io::EOF =>
- return io::EOF;
- };
- };
- if (!bytes::equal(rd.buffer[..2], ['\r', '\n'])) {
- return errors::invalid;
- };
- rd.buffer[..rd.pending - 2] = rd.buffer[2..rd.pending];
- rd.pending -= 2;
- rd.state = chunk_state::HEADER;
};
};
-
- fn chunked_close(s: *io::stream) (void | io::error) = {
- let rd = s: *chunked_reader;
- // TODO connection pool
- io::close(rd.conn)?;
- free(rd);
- };
--
2.44.0