commit - b28be21f0f8cb1d884d3831c67115e34ee16c793
commit + 94eeacc5a3d331d18f064fe93df5209168a3d444
blob - 7faeb6ca6a4b1e410be10ebe39e6c64bd47f88df
blob + 6e5a7417185ee781c80bc0a32e61db0ffe0436f1
--- http/http.ha
+++ http/http.ha
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,
};
// routefn = void,
in = bufio::newscanner(sock, types::SIZE_MAX),
first_line = true,
+ response = new_reswriter(sock),
...
});
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;
};
case => yield;
};
+ match (io::close(&c.response)) {
+ case => yield;
+ };
+
free(c);
};
delclient(clt);
return;
case =>
- if (clt.done) {
+ // XXX wait if there is still data pending.
+ if (clt.response.done) {
delclient(clt);
+ return;
};
};
};
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));
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
+// 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)?;
+};