httpd/httpd.d

204 lines
3.8 KiB
D

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;
}