Commit Diff


commit - b28be21f0f8cb1d884d3831c67115e34ee16c793
commit + 94eeacc5a3d331d18f064fe93df5209168a3d444
blob - 7faeb6ca6a4b1e410be10ebe39e6c64bd47f88df
blob + 6e5a7417185ee781c80bc0a32e61db0ffe0436f1
--- http/http.ha
+++ http/http.ha
@@ -61,16 +61,14 @@ export type request = struct {
 	chunked: bool,
 };
 
-export type routefn = *fn(*client) (void | error);
+export type routefn = *fn(*request, *reswriter) (void | error);
 
-export type client = struct {
+type client = struct {
 	fp: io::file,
 	in: bufio::scanner,
 	first_line: bool,
 	request: request,
-	chunked: bool,		// whether to chunk the reply
-	hdrdone: bool,
-	done: bool,
+	response: reswriter,
 	// routefn: (void | routefn),
 	mux: *mux,
 };
@@ -115,6 +113,7 @@ fn acceptclt(f: io::file, ev: ev::event, data: nullabl
 		// routefn = void,
 		in = bufio::newscanner(sock, types::SIZE_MAX),
 		first_line = true,
+		response = new_reswriter(sock),
 		...
 	});
 
@@ -152,10 +151,10 @@ fn parse_req(c: *client) (bool | error | encoding::utf
 
 			switch (version) {
 			case "HTTP/1.0" =>
-				c.request.version = version::HTTP_1_1;
-			case "HTTP/1.1" =>
 				c.request.version = version::HTTP_1_0;
-				c.chunked = true;
+			case "HTTP/1.1" =>
+				c.request.version = version::HTTP_1_1;
+				c.response.chunked = true;
 			case => return badrequest;
 			};
 
@@ -179,6 +178,10 @@ fn delclient(c: *client) void = {
 	case => yield;
 	};
 
+	match (io::close(&c.response)) {
+	case => yield;
+	};
+
 	free(c);
 };
 
@@ -191,8 +194,10 @@ fn handleclt(f: io::file, ev: ev::event, data: nullabl
 		delclient(clt);
 		return;
 	case =>
-		if (clt.done) {
+		// XXX wait if there is still data pending.
+		if (clt.response.done) {
 			delclient(clt);
+			return;
 		};
 	};
 };
@@ -205,8 +210,9 @@ fn do_request(clt: *client) (void | error) = {
 		yield;
 	case errors::again => return;	// try again later
 	case badrequest =>
-		route_badrequest(clt)?;
-		clt.done = true;
+		route_badrequest(&clt.request, &clt.response)?;
+		clt.response.done = true;
+		reswriter_flush(&clt.response)?;
 		return;
 	case let err: io::error =>
 		log::printfln("parse_req: {}", io::strerror(err));
@@ -220,59 +226,27 @@ fn do_request(clt: *client) (void | error) = {
 	for (let i = 0z; i < len(clt.mux); i += 1) {
 		if (fnmatch::fnmatch(clt.mux[i].pattern, path)) {
 			found = true;
-			clt.mux[i].routefn(clt)?;
+			clt.mux[i].routefn(&clt.request, &clt.response)?;
 			break;
 		};
 	};
 
 	if (!found) {
-		route_notfound(clt)?;
-	};
-
-	write(clt, "")?; // flush
-
-	clt.done = true;
-};
-
-export fn reply(clt: *client, code: int, status: str) (void | error) = {
-	log::printfln("> {} {}", code, status);
-
-	let version = if (clt.request.version == version::HTTP_1_0)
-		"HTTP/1.0" else "HTTP/1.1";
-
-	fmt::fprintf(clt.fp, "{} {} {}\r\n", version, code, status)?;
-};
-
-export fn header(clt: *client, header: str, value: str) (void | error) = {
-	fmt::fprintf(clt.fp, "{}: {}\r\n", header, value)?;
-};
-
-export fn write(clt: *client, body: str) (void | error) = {
-	if (!clt.hdrdone) {
-		if (clt.chunked) {
-			header(clt, "Transfer-Encoding", "chunked")?;
-		};
-		header(clt, "Connection", "closed")?;
-
-		clt.hdrdone = true;
-		fmt::fprint(clt.fp, "\r\n")?;
+		route_notfound(&clt.request, &clt.response)?;
 	};
 
-	if (clt.chunked) {
-		fmt::fprintf(clt.fp, "{:x}\r\n{}\r\n", len(body), body)?;
-	} else {
-		fmt::fprintf(clt.fp, "{}", body)?;
-	};
+	clt.response.done = true;
+	reswriter_flush(&clt.response)?;
 };
 
-fn route_notfound(clt: *client) (void | error) = {
-	reply(clt, 404, "Not Found")?;
-	header(clt, "Content-Type", "text/plain")?;
-	write(clt, "Page not found.\n")?;
+fn route_notfound(req: *request, res: *reswriter) (void | error) = {
+	reply(req, res, 404, "Not Found")?;
+	header(res, "Content-Type", "text/plain")?;
+	fmt::fprintln(res, "Page not found.")?;
 };
 
-fn route_badrequest(clt: *client) (void | error) = {
-	reply(clt, 400, "Bad Request")?;
-	header(clt, "Content-Type", "text/plain")?;
-	write(clt, "Bad Request")?;
+fn route_badrequest(req: *request, res: *reswriter) (void | error) = {
+	reply(req, res, 400, "Bad Request")?;
+	header(res, "Content-Type", "text/plain")?;
+	fmt::fprintln(res, "Bad Request")?;
 };
blob - /dev/null
blob + cd59096f2c893f258fd125e72bc37ffc808c2dc7 (mode 644)
--- /dev/null
+++ http/reswriter.ha
@@ -0,0 +1,120 @@
+// This is free and unencumbered software released into the public domain.
+//
+// Anyone is free to copy, modify, publish, use, compile, sell, or
+// distribute this software, either in source code form or as a compiled
+// binary, for any purpose, commercial or non-commercial, and by any
+// means.
+//
+// In jurisdictions that recognize copyright laws, the author or authors
+// of this software dedicate any and all copyright interest in the
+// software to the public domain. We make this dedication for the benefit
+// of the public at large and to the detriment of our heirs and
+// successors. We intend this dedication to be an overt act of
+// relinquishment in perpetuity of all present and future rights to this
+// software under copyright law.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+// OTHER DEALINGS IN THE SOFTWARE.
+
+use bufio;
+use fmt;
+use io;
+use log;
+
+export type reswriter = struct {
+	vtable: io::stream,
+	dst: bufio::stream,
+	hdrdone: bool,		// done with the headers
+	done: bool,		// done with the response
+	wrotedone: bool,	// written the done chunk
+	chunked: bool,		// chunk the reply
+
+	// XXX
+	buf: []u8,
+};
+
+const reswriter_vtable: io::vtable = io::vtable {
+	writer = &reswriter_write,
+	closer = &reswriter_close,
+	...
+};
+
+fn new_reswriter(s: io::handle) reswriter = {
+	let buf: []u8 = alloc([0...], 1024);
+
+	// XXX using a bufio::stream means we can't append more than
+	// len(buf) data...
+	let w = reswriter {
+		vtable = &reswriter_vtable,
+		dst = bufio::init(s, [], buf),
+		buf = buf,
+		...
+	};
+
+	bufio::setflush(&w.dst, []);
+
+	return w;
+};
+
+fn reswriter_write(s: *io::stream, buf: const []u8) (size | io::error) = {
+	let res = s: *reswriter;
+
+	if (!res.hdrdone) {
+		if (res.chunked) {
+			header(res, "Transfer-Encoding", "chunked")?;
+		};
+		header(res, "Connection", "closed")?;
+
+		fmt::fprint(&res.dst, "\r\n")?;
+		res.hdrdone = true;
+	};
+
+	if (res.chunked) {
+		let ret = 0z;
+
+		ret += fmt::fprintf(&res.dst, "{:x}\r\n", len(buf))?;
+		ret += io::write(&res.dst, buf)?;
+		ret += fmt::fprint(&res.dst, "\r\n")?;
+		return ret;
+	} else {
+		return io::write(&res.dst, buf)?;
+	};
+};
+
+fn reswriter_flush(res: *reswriter) (void | io::error) = {
+	if (res.done && !res.wrotedone) {
+		fmt::fprint(&res.dst, "0\r\n\r\n")?;
+		res.wrotedone = true;
+	};
+	bufio::flush(&res.dst)?;
+};
+
+fn reswriter_close(s: *io::stream) (void | io::error) = {
+	let w = s: *reswriter;
+
+	io::close(&w.dst)?;
+	free(w.buf);
+};
+
+export fn reply(
+	req: *request,
+	res: *reswriter,
+	code: int,
+	status: str
+) (void | io::error) = {
+	log::printfln("> {} {}", code, status);
+
+	let version = if (req.version == version::HTTP_1_0)
+		"HTTP/1.0" else "HTTP/1.1";
+
+	fmt::fprintf(&res.dst, "{} {} {}\r\n", version, code, status)?;
+};
+
+export fn header(res: *reswriter, header: str, value: str) (void | io::error) = {
+	fmt::fprintf(&res.dst, "{}: {}\r\n", header, value)?;
+};