module httpd; import http; import httpmodules; import std.algorithm; import std.array; import std.conv; import std.format; import std.socket; import std.stdio; import std.string; int main(string[] args) { char[1024] buffer; Socket[] clients; SocketSet readSet; Socket listener; listener = new Socket(AddressFamily.INET, SocketType.STREAM); listener.bind(new InternetAddress("127.0.0.1", 80)); listener.listen(32); scope (exit) { listener.shutdown(SocketShutdown.BOTH); listener.close(); } readSet = new SocketSet(); while (true) { readSet.reset(); readSet.add(listener); foreach (Socket client; clients) { readSet.add(client); } if (Socket.select(readSet, null, null)) { foreach (ulong i, Socket client; clients) { if (readSet.isSet(client)) { client.send(tryRequest(buffer[0 .. client.receive(buffer)])); clients.remove(i); } } if (readSet.isSet(listener)) { clients ~= listener.accept(); } } } return 0; } string tryRequest(char[] requestBody) { HTTPRequest rq; string lastErr; try { rq = parseHTTPRequest(requestBody); foreach (mod; httpModules) { try return makeHTTPResponse(mod(rq)); catch (HTTPException e) lastErr = e.msg; } /* jump to catch block */ throw new HTTPException(lastErr); } catch (HTTPException e) return failurePage(e.msg); } string failurePage(string status) { return respondWithPage( import("fail.html") .replace("{status}", status), status); } string respondWithPage(string pageBody, string status = "200 OK", string contentType = "text/html") { return makeHTTPResponse( HTTPResponse( status, ["Content-Type": contentType], pageBody)); } string makeHTTPResponse(in HTTPResponse resp) { string[string] defaultHeaders, headers; char[] result; defaultHeaders = [ "Content-Length": resp.responseBody.length.to!string(), "Server": "Jeremy's httpd" ]; result = (resp.httpVersion ~ ' ' ~ resp.status).dup(); headers = mergeHeaders(defaultHeaders, resp.headers); foreach (string header; headers.byKey()) { result ~= "\r\n" ~ header ~ ": " ~ headers[header]; } finishResponse: return result.idup() ~ "\r\n\r\n" ~ resp.responseBody; } string[string] mergeHeaders(const(string[string]) a1, const(string[string]) a2) { string[string] result = cast(string[string])a1.dup(); foreach (string key; a2.byKey()) { result[key] = a2[key]; } return result; } HTTPRequest parseHTTPRequest(in char[] request) { HTTPRequest result; result = parseHTTPRequestHeading(request .findSplitBefore("\r\n")[0] .idup()); return result; } HTTPRequest parseHTTPRequestHeading(string heading) { HTTPRequest result; string methodString, path, params; heading = heading.dup(); auto tup = heading.findSplit(" "); methodString = tup[0]; heading = tup[2]; switch (methodString) { case "GET": result.method = HTTPMethod.GET; break; case "HEAD": result.method = HTTPMethod.HEAD; break; case "POST": result.method = HTTPMethod.POST; break; case "PUT": result.method = HTTPMethod.PUT; break; case "DELETE": result.method = HTTPMethod.DELETE; break; case "CONNECT": result.method = HTTPMethod.CONNECT; break; case "OPTIONS": result.method = HTTPMethod.OPTIONS; break; case "TRACE": result.method = HTTPMethod.TRACE; break; default: throw new HTTPException("400 Bad Request"); } tup = heading.findSplit(" "); path = tup[0]; params = path.findSplitAfter("?")[1]; heading = tup[2]; params = params.length == path.length ? "" : params; if (path.length == 0 || path[0] != '/') throw new HTTPException("400 Bad Request"); result.path = path; if (params.length == 0) return result; foreach (string param; params.split("&")) { auto pair = param.findSplit("="); result.parameters[pair[0]] = pair[2]; } result.httpVersion = heading.strip(); return result; }