From b449d18bbb384cc1aa3aaa7c53a8e48bb9ba8760 Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Mon, 30 Jan 2012 22:10:24 -0500 Subject: [PATCH] fix symlink --- httpd.d | 303 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 302 insertions(+), 1 deletion(-) mode change 120000 => 100644 httpd.d diff --git a/httpd.d b/httpd.d deleted file mode 120000 index 1560a2b..0000000 --- a/httpd.d +++ /dev/null @@ -1 +0,0 @@ -../../httpd/httpd.d \ No newline at end of file diff --git a/httpd.d b/httpd.d new file mode 100644 index 0000000..7f2b598 --- /dev/null +++ b/httpd.d @@ -0,0 +1,302 @@ +// This thing sucks. It's primarily to prove that cgi.d *can* work +// with an embedded http server without much difficulty, so you aren't +// tied to CGI if you don't want to be, even if you have a huge codebase +// built on cgi.d already. + +// But this particular module is no where near ready for serious use. +// (it does do reasonably well under controlled conditions though) + +module arsd.httpd; + +public import arsd.cgi; + +import arsd.netman; + +import std.range; + +/* + +import arsd.curl; +void handler(Cgi cgi) { + cgi.write("hello world!"); + cgi.close(); +} + +void main() { + serveHttp(&handler, 5000); +} + +*/ + +void serveHttp(CustomCgi)(void function(Cgi) requestHandler, ushort port) if(is(CustomCgi: Cgi)) { + auto netman = new NetMan!(CustomCgi)(requestHandler); + netman.listen(port); + for(;;) + try + netman.proceed(); + catch(ConnectionException e) + e.c.disconnectNow(); + catch(Exception e) + writefln("Exception: %s", e.toString()); +} + +class NetMan(CustomCgi) : NetworkManager if(is(CustomCgi : Cgi)) { + void function(Cgi) requestHandler; + + this(void function(Cgi) requestHandler) { + this.requestHandler = requestHandler; + } + + override Connection allocConnection(int port) { + return new HttpdConnection!CustomCgi(requestHandler); + } +} + +class HttpdConnection(CustomCgi) : Connection if(is(CustomCgi : Cgi)) { + // The way this rolls is to get the whole thing in memory, then pass it off to Cgi to do the rest + + this(void function(Cgi) requestHandler) { + handler = requestHandler; + } + + void function(Cgi) handler; + + int state; + string[] headers; + immutable(ubyte)[] data; + int contentLength; + bool chunked; + int chunkSize = 0; + string separator = "\r\n"; + bool closeConnection; + + void log(in ubyte[] a) { + data ~= a; + } + + void finishRequest() { + state = 0; + separator = "\r\n"; + + scope(exit) { + if(closeConnection) + disconnect(); + closeConnection = false; + } + + Cgi cgi; + + try { + cgi = new CustomCgi(headers, data, peerAddress(), + cast(void delegate(const(ubyte)[])) &this.write); + } catch(Throwable t) { + write("HTTP/1.1 400 Bad Request\r\n"); + write("Content-Type: text/plain\r\n"); + write("Connection: close\r\n"); + write("\r\n"); + write(t.toString()); + + return; + } + + try { + handler(cgi); + cgi.close(); + cgi.dispose(); + } catch(Throwable e) { + cgi.setResponseStatus("500 Internal Server Error"); + cgi.write(e.toString()); + + return; + } + } + + override void onDataReceived(){ + auto a = read(); + + more: + switch(state) { + default: assert(0); + case 0: // reading the headers + // while it's supposed to be \r\n, we want + // to be permissive here to avoid hanging if + // the wrong thing comes. + try_again: + int l = locationOf(a, separator ~ separator); + if(l == -1) { + if(separator.length > 1) { + separator = "\n"; + goto try_again; + } else { + separator = "\r\n"; + } + + return; // not enough data + } + changeReadPosition(l+separator.length * 2); // we're now at the beginning of the data + + data.length = 0; + contentLength = 0; + + string hdrs = cast(string) a[0..l].idup; + a = read(); // advance ourselves + headers = hdrs.split(separator); + + chunked = false; + + if(headers.length == 0) { + disconnect(); + return; + } + + if(headers[0].indexOf("HTTP/1.0") != -1) + closeConnection = true; // always one request per connection with 1.0 + + foreach(ref h; headers[1..$]) { + int colon = h.indexOf(":"); + if(colon == -1) + throw new Exception("Http headers need colons"); + string name = h[0..colon].tolower; + string value = h[colon+2..$]; // FIXME? + + switch(name) { + case "transfer-encoding": + if(value == "chunked") + chunked = true; + break; + case "content-length": + contentLength = to!int(value); + break; + case "connection": + if(value == "close") + closeConnection = true; + break; + default: + // leave it unmolested for passthrough + } + } + + // forward the header and advance our state + state = 1; + // break; // fall through to read some more data if we have any + case 1: // reading Content-Length type data + // We need to read up the data we have, and write it out as a chunk. + if(!chunked) { + if(a.length <= contentLength) { + log(a); + contentLength -= a.length; + resetRead(); + // we just finished it off, terminate the chunks + if(contentLength == 0) { + finishRequest(); + } + } else { + // we actually have *more* here than we need.... + log(a[0..contentLength]); + contentLength = 0; + finishRequest(); + + changeReadPosition(contentLength); + a = read(); + // we're done + goto more; // see if we can make use of the rest of the data + } + } else { + // decode it, modify it, then reencode it + // If here, we are at the beginning of a chunk. + int loc = locationOf(a, "\r\n"); + if(loc == -1) { + return; // don't have the length + } + + string hex; + hex = ""; + for(int i = 0; i < loc; i++) { + char c = a[i]; + if(c >= 'A' && c <= 'Z') + c += 0x20; + if((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z')) { + hex ~= c; + } else { + break; + } + } + + assert(hex.length); + + int power = 1; + int size = 0; + foreach(cc1; retro(hex)) { + dchar cc = cc1; + if(cc >= 'a' && cc <= 'z') + cc -= 0x20; + int val = 0; + if(cc >= '0' && cc <= '9') + val = cc - '0'; + else + val = cc - 'A' + 10; + + size += power * val; + power *= 16; + } + + chunkSize = size; + assert(size >= 0); + + if(loc + 2 > a.length) { + return; // need more data + } + changeReadPosition(loc+2); // skips the chunk header + a = read(); + + if(chunkSize == 0) { // we're done with the response + state = 3; + goto more; + } else { + state = 2; + goto more; + } + + resetRead(); + } + break; + case 2: // reading a chunk + // if we got here, will change must be true.... + if(a.length < chunkSize + 2) { + return; // we want to handle the whole chunk at once + } + + log(a[0..chunkSize]); + + state = 1; + + if(a.length > chunkSize + 2) { + assert(a[chunkSize] == 13); + assert(a[chunkSize+1] == 10); + changeReadPosition(chunkSize + 2); // skip the \r\n + a = read(); + chunkSize = 0; + state = 1; + goto more; + } else { + chunkSize = 0; + resetRead(); + } + break; + case 3: // reading footers + // if we got here, will change must be true.... + int loc = locationOf(a, "\r\n"); + if(loc == -1) { + return; // not done yet + } else { + assert(loc == 0); + changeReadPosition(loc+2); // FIXME: should handle footers properly + finishRequest(); + a = read(); + + goto more; + } + break; + } + } +}