204 lines
3.8 KiB
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;
|
|
}
|