Blob


1 // This is free and unencumbered software released into the public domain.
2 //
3 // Anyone is free to copy, modify, publish, use, compile, sell, or
4 // distribute this software, either in source code form or as a compiled
5 // binary, for any purpose, commercial or non-commercial, and by any
6 // means.
7 //
8 // In jurisdictions that recognize copyright laws, the author or authors
9 // of this software dedicate any and all copyright interest in the
10 // software to the public domain. We make this dedication for the benefit
11 // of the public at large and to the detriment of our heirs and
12 // successors. We intend this dedication to be an overt act of
13 // relinquishment in perpetuity of all present and future rights to this
14 // software under copyright law.
15 //
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 // OTHER DEALINGS IN THE SOFTWARE.
24 use bufio;
25 use encoding::utf8;
26 use errors;
27 use fmt;
28 use io;
29 use log;
30 use net;
31 use strings;
32 use types;
34 use ev;
36 type badrequest = !void;
38 export type version = enum {
39 HTTP_1_0,
40 HTTP_1_1,
41 };
43 export type method = enum {
44 GET,
45 HEAD,
46 POST,
47 PUT,
48 DELETE,
49 CONNECT,
50 OPTIONS,
51 TRACE,
52 PATCH,
53 };
55 export type request = struct {
56 path: str,
57 method: method,
58 version: version,
59 ctype: str,
60 csize: size,
61 chunked: bool,
62 };
64 export type routefn = *fn(*request, *reswriter) (void | io::error);
66 type connection = struct {
67 fp: io::file,
68 in: bufio::scanner,
69 first_line: bool,
70 request_done: bool,
71 request: request,
72 response: reswriter,
73 routefn: routefn,
74 mux: *mux,
75 };
77 fn acceptconn(f: io::file, ev: ev::event, data: nullable *opaque) void = {
78 let sock = match(net::accept(f, net::sockflag::NONBLOCK)) {
79 case let err: net::error =>
80 log::printfln("accept: {}", net::strerror(err));
81 return;
82 case let sock: net::socket =>
83 yield sock;
84 };
86 let mux = data: *mux;
87 let conn = alloc(connection {
88 fp = sock,
89 mux = mux,
90 in = bufio::newscanner(sock, types::SIZE_MAX),
91 first_line = true,
92 response = new_reswriter(sock),
93 routefn = &route_notfound,
94 ...
95 });
97 ev::add(sock, ev::READ, &handleconn, conn);
98 };
100 fn parse_req(
101 conn: *connection
102 ) (bool | badrequest | io::error | encoding::utf8::invalid) = {
103 let req = &conn.request;
104 let res = &conn.response;
106 for (true) {
107 let line = match(bufio::scan_string(&conn.in, "\r\n")) {
108 case io::EOF => return badrequest;
109 case let err: io::error => return err;
110 case let line: const str => yield line;
111 };
113 if (conn.first_line) {
114 conn.first_line = false;
116 let (method, rest) = strings::cut(line, " ");
118 switch (method) {
119 case "GET" => req.method = method::GET;
120 case "HEAD" => req.method = method::HEAD;
121 case "POST" => req.method = method::POST;
122 case "PUT" => req.method = method::PUT;
123 case "DELETE" => req.method = method::DELETE;
124 case "CONNECT" => req.method = method::CONNECT;
125 case "OPTIONS" => req.method = method::OPTIONS;
126 case "TRACE" => req.method = method::TRACE;
127 case "PATCH" => req.method = method::PATCH;
128 case => return badrequest;
129 };
131 let (path, version) = strings::cut(rest, " ");
132 req.path = strings::dup(path);
134 switch (version) {
135 case "HTTP/1.0" =>
136 req.version = version::HTTP_1_0;
137 case "HTTP/1.1" =>
138 req.version = version::HTTP_1_1;
139 res.chunked = true;
140 case => return badrequest;
141 };
143 continue;
144 };
146 // ignore everything for the time being.
147 if (line == "") {
148 return true;
149 };
151 continue;
152 };
153 };
155 fn close_conn(conn: *connection) void = {
156 ev::del(conn.fp);
158 bufio::finish(&conn.in);
159 match (io::close(conn.fp)) {
160 case => yield;
161 };
163 match (io::close(&conn.response)) {
164 case => yield;
165 };
167 free(conn);
168 };
170 fn handleconn(f: io::file, ev: ev::event, data: nullable *opaque) void = {
171 let conn = data: *connection;
173 match (respond(conn)) {
174 case let err: io::error =>
175 log::printfln("failure: {}", io::strerror(err));
176 close_conn(conn);
177 return;
178 case =>
179 // XXX wait if there is still data pending.
180 if (conn.response.done) {
181 close_conn(conn);
182 return;
183 };
185 ev::add(conn.fp, ev::READ | ev::WRITE, &handleconn, conn);
186 };
187 };
189 fn handle_request(conn: *connection) (void | io::error) = {
190 match (parse_req(conn)) {
191 case let done: bool =>
192 if (!done)
193 return; // try again later
194 yield;
195 case errors::again => return; // try again later
196 case badrequest =>
197 route_badrequest(&conn.request, &conn.response)?;
198 conn.response.done = true;
199 reswriter_flush(&conn.response)?;
200 return;
201 case let err: io::error =>
202 log::printfln("parse_req: {}", io::strerror(err));
203 return;
204 };
206 log::printfln("< GET {}", conn.request.path);
208 conn.routefn = mux_match(conn.mux, &conn.request, &route_notfound);
209 conn.request_done = true;
210 };
212 fn respond(conn: *connection) (void | io::error) = {
213 if (!conn.request_done)
214 handle_request(conn)?;
215 if (!conn.request_done)
216 return;
218 if (!conn.response.done) {
219 conn.response.done = true;
220 conn.routefn(&conn.request, &conn.response)?;
221 };
223 reswriter_flush(&conn.response)?;
224 };
226 fn route_notfound(req: *request, res: *reswriter) (void | io::error) = {
227 reply(req, res, 404, "Not Found")?;
228 header(res, "Content-Type", "text/plain")?;
229 fmt::fprintln(res, "Page not found.")?;
230 };
232 fn route_badrequest(req: *request, res: *reswriter) (void | io::error) = {
233 reply(req, res, 400, "Bad Request")?;
234 header(res, "Content-Type", "text/plain")?;
235 fmt::fprintln(res, "Bad Request")?;
236 };