This extends read_statusline to support both request and response types. This
will be needed when there is a server implementation to read incoming requests.
One thing that is rather awkward is setting the request.target with empty
values since uri::parse() fails to parse scheme, host, and port-less URIs.
Signed-off-by: Blain Smith <rebelgeek@blainsmith.com>
---
net/http/client.ha | 1 +
net/http/do.ha | 136 +++++++++++++++++++++++++++++++++---------
net/http/request.ha | 3 +
net/http/transport.ha | 6 +-
4 files changed, 116 insertions(+), 30 deletions(-)
diff --git a/net/http/client.ha b/net/http/client.ha
index 1e2edbd..7f57dfa 100644
--- a/net/http/client.ha
+++ b/net/http/client.ha
@@ -47,6 +47,7 @@ fn new_request(client: *client, method: str, target: *uri::uri) request = {
let req = request {
method = method,
target = alloc(uri::dup(target)),
+ version = (1, 1),
header = header_dup(&client.default_header),
transport = null,
body = void,
diff --git a/net/http/do.ha b/net/http/do.ha
index 4631c71..41cb12b 100644
--- a/net/http/do.ha
+++ b/net/http/do.ha
@@ -3,6 +3,7 @@ use encoding::utf8;
use errors;
use fmt;
use io;
+use memio;
use net::dial;
use net::uri;
use net;
@@ -56,12 +57,12 @@ export fn do(client: *client, req: *request) (response | error) = {
const scan = bufio::newscanner_static(conn, buf);
read_statusline(&resp, &scan)?;
read_header(&resp.header, &scan)?;
- resp.body = new_reader(conn, &resp, &scan)?;
+ resp.body = new_reader(conn, &resp.header, &scan)?;
return resp;
};
fn read_statusline(
- resp: *response,
+ r: (*request | *response),
scan: *bufio::scanner,
) (void | error) = {
const status = match (bufio::scan_string(scan, "\r\n")) {
@@ -77,38 +78,119 @@ fn read_statusline(
const tok = strings::tokenize(status, " ");
- const version = match (strings::next_token(&tok)) {
- case let ver: str =>
- yield ver;
- case void =>
- return protoerr;
- };
-
- const status = match (strings::next_token(&tok)) {
- case let status: str =>
- yield status;
- case void =>
- return protoerr;
- };
-
- const reason = match (strings::next_token(&tok)) {
- case let reason: str =>
- yield reason;
- case void =>
- return protoerr;
+ match (r) {
+ case let req: *request =>
+ const method = match (strings::next_token(&tok)) {
+ case let m: str =>
+ yield m;
+ case void =>
+ return protoerr;
+ };
+
+ const target = match (strings::next_token(&tok)) {
+ case let path: str =>
+ const (path, remain) = strings::cut(path, "?");
+ const (query, frag) = strings::cut(remain, "#");
+
+ yield alloc(uri::uri {
+ scheme = "",
+ host = "",
+ port = 0,
+ userinfo = "",
+ path = strings::dup(path),
+ query = strings::dup(query),
+ fragment = strings::dup(frag),
+ });
+ case void =>
+ return protoerr;
+ };
+
+ const version = match (strings::next_token(&tok)) {
+ case let ver: str =>
+ yield ver;
+ case void =>
+ return protoerr;
+ };
+
+ req.version = parse_version(version);
+ if (req.version.0 > 1) {
+ return errors::unsupported;
+ };
+
+ req.method = strings::dup(method);
+ req.target = target;
+ case let resp: *response =>
+ const version = match (strings::next_token(&tok)) {
+ case let ver: str =>
+ yield ver;
+ case void =>
+ return protoerr;
+ };
+
+ const status = match (strings::next_token(&tok)) {
+ case let status: str =>
+ yield status;
+ case void =>
+ return protoerr;
+ };
+
+ const reason = match (strings::next_token(&tok)) {
+ case let reason: str =>
+ yield reason;
+ case void =>
+ return protoerr;
+ };
+
+ resp.version = parse_version(version);
+ if (resp.version.0 > 1) {
+ return errors::unsupported;
+ };
+
+ resp.status = strconv::stou(status)!;
+ resp.reason = strings::dup(reason);
};
+};
+fn parse_version(version: str) (uint, uint) = {
const (_, version) = strings::cut(version, "/");
const (major, minor) = strings::cut(version, ".");
- resp.version = (
+ return (
strconv::stou(major)!,
strconv::stou(minor)!,
);
+};
- if (resp.version.0 > 1) {
- return errors::unsupported;
- };
+@test fn read_statusline_request() void = {
+ const raw = "GET /blog/post.html?session=1234#middle HTTP/1.1\r\n";
+ const conn = memio::fixed(strings::toutf8(raw));
+ const scan = bufio::newscanner(&conn: io::handle, 1024);
- resp.status = strconv::stou(status)!;
- resp.reason = strings::dup(reason);
+ const target = uri::parse("http://example.com") as uri::uri;
+ const req = request {
+ target = &target,
+ body = void,
+ ...
+ };
+ assert(read_statusline(&req, &scan) is void);
+
+ assert(req.method == GET);
+ assert(req.target.path == "/blog/post.html");
+ assert(req.target.query == "session=1234");
+ assert(req.target.fragment == "middle");
+ assert(req.version.0 == 1);
+ assert(req.version.1 == 1);
};
+
+@test fn read_statusline_response() void = {
+ const raw = "HTTP/1.1 200 OK\r\n";
+ const conn = memio::fixed(strings::toutf8(raw));
+ const scan = bufio::newscanner(&conn: io::handle, 1024);
+
+ const resp = response { ... };
+ assert(read_statusline(&resp, &scan) is void);
+
+ assert(resp.status == STATUS_OK);
+ assert(resp.reason == "OK");
+ assert(resp.version.0 == 1);
+ assert(resp.version.1 == 1);
+};
\ No newline at end of file
diff --git a/net/http/request.ha b/net/http/request.ha
index ec982bb..3d29bf2 100644
--- a/net/http/request.ha
+++ b/net/http/request.ha
@@ -17,6 +17,9 @@ export type request = struct {
// the scheme field may be empty.
target: *uri::uri,
+ // HTTP protocol version (major, minor)
+ version: (uint, uint),
+
// List of HTTP request headers.
header: header,
diff --git a/net/http/transport.ha b/net/http/transport.ha
index 7c59307..5b44fd5 100644
--- a/net/http/transport.ha
+++ b/net/http/transport.ha
@@ -51,12 +51,12 @@ export type transport = struct {
fn new_reader(
conn: io::handle,
- resp: *response,
+ head: *header,
scan: *bufio::scanner,
) (*io::stream | 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(head, "Content-Length");
+ const te = header_get(head, "Transfer-Encoding");
if (cl != "" || te == "") {
let length = types::SIZE_MAX;
--
2.34.1