~sircmpwn/hare-dev

This thread contains a patchset. You're looking at the original emails, but you may wish to use the patch review UI. Review patch
2 2

[PATCH hare-http] read_statusline supports request and response

Details
Message ID
<20231022023108.443665-1-rebelgeek@blainsmith.com>
DKIM signature
missing
Download raw message
Patch: +116 -30
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
Details
Message ID
<CWLNSJ6RHEXK.1OWI2CWKVQVYT@taiga>
In-Reply-To
<20231022023108.443665-1-rebelgeek@blainsmith.com> (view parent)
DKIM signature
missing
Download raw message
Does not apply, rebase?
Details
Message ID
<CAO3qA0ktNoB5HcXU_YvXLQfjvXFSnqE2=T9FLL9jpYALsQSUXw@mail.gmail.com>
In-Reply-To
<CWLNSJ6RHEXK.1OWI2CWKVQVYT@taiga> (view parent)
DKIM signature
missing
Download raw message
Apologies, I will get that taken care of.

On Mon, Oct 30, 2023 at 5:16 AM Drew DeVault <sir@cmpwn.com> wrote:
>
> Does not apply, rebase?
Reply to thread Export thread (mbox)