initial commit
This commit is contained in:
commit
a1d36338a1
5 changed files with 274 additions and 0 deletions
231
httpd.d
Normal file
231
httpd.d
Normal file
|
@ -0,0 +1,231 @@
|
|||
module httpd;
|
||||
|
||||
import std.algorithm;
|
||||
import std.array;
|
||||
import std.conv;
|
||||
import std.exception;
|
||||
import std.format;
|
||||
import std.socket;
|
||||
import std.stdio;
|
||||
import std.string;
|
||||
|
||||
enum HTTPMethod
|
||||
{
|
||||
GET,
|
||||
HEAD,
|
||||
POST,
|
||||
PUT,
|
||||
DELETE,
|
||||
CONNECT,
|
||||
OPTIONS,
|
||||
TRACE,
|
||||
invalid
|
||||
}
|
||||
|
||||
struct HTTPRequest
|
||||
{
|
||||
HTTPMethod method;
|
||||
string resource;
|
||||
string[string] parameters;
|
||||
string[string] headers;
|
||||
string httpVersion = "HTTP/1.1";
|
||||
|
||||
alias path = resource;
|
||||
}
|
||||
|
||||
struct HTTPResponse
|
||||
{
|
||||
string status;
|
||||
string[string] headers;
|
||||
string responseBody;
|
||||
string httpVersion = "HTTP/1.1";
|
||||
}
|
||||
|
||||
class HttpdException : Exception
|
||||
{
|
||||
mixin basicExceptionCtors;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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;
|
||||
|
||||
try {
|
||||
rq = parseHTTPRequest(requestBody);
|
||||
if (rq.method != HTTPMethod.GET)
|
||||
return failurePage("405 Method Not Allowed");
|
||||
return rq.resource == "/"
|
||||
|| rq.resource == "/index.html"
|
||||
? respondWithPage(import("index.html"))
|
||||
: failurePage("404 Not Found");
|
||||
} catch (HttpdException 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 HttpdException("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 HttpdException("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;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue