mirror of https://github.com/adamdruppe/arsd.git
better embedded httpd, no longer requires external module
This commit is contained in:
parent
75891e0634
commit
053012024b
405
cgi.d
405
cgi.d
|
@ -53,12 +53,21 @@ mixin template ForwardCgiConstructors() {
|
||||||
void delegate(const(ubyte)[]) _rawDataOutput = null,
|
void delegate(const(ubyte)[]) _rawDataOutput = null,
|
||||||
void delegate() _flush = null
|
void delegate() _flush = null
|
||||||
) { super(maxContentLength, env, readdata, _rawDataOutput, _flush); }
|
) { super(maxContentLength, env, readdata, _rawDataOutput, _flush); }
|
||||||
|
|
||||||
this(string[] headers, immutable(ubyte)[] data, string address, void delegate(const(ubyte)[]) _rawDataOutput = null, int pathInfoStarts = 0, void delegate() _flush = null) {
|
this(
|
||||||
super(headers, data, address, _rawDataOutput, pathInfoStarts, _flush);
|
BufferedInputRange inputData,
|
||||||
|
string address, ushort _port,
|
||||||
|
int pathInfoStarts = 0,
|
||||||
|
bool _https = false,
|
||||||
|
void delegate(const(ubyte)[]) _rawDataOutput = null,
|
||||||
|
void delegate() _flush = null,
|
||||||
|
// this pointer tells if the connection is supposed to be closed after we handle this
|
||||||
|
bool* closeConnection = null)
|
||||||
|
{
|
||||||
|
super(inputData, address, _port, pathInfoStarts, _https, _rawDataOutput, _flush, closeConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
this(BufferedInputRange ir) { super(ir); }
|
this(BufferedInputRange ir, bool* closeConnection) { super(ir, closeConnection); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -732,93 +741,16 @@ class Cgi {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initializes the cgi from completely raw HTTP data. The ir must have a Socket source.
|
/// Initializes the cgi from completely raw HTTP data. The ir must have a Socket source.
|
||||||
this(BufferedInputRange ir) {
|
this(BufferedInputRange ir, bool* closeConnection) {
|
||||||
import al = std.algorithm;
|
import al = std.algorithm;
|
||||||
|
|
||||||
auto idx = al.indexOf(ir.front(), "\r\n\r\n");
|
|
||||||
while(idx == -1) {
|
|
||||||
ir.popFront(0);
|
|
||||||
idx = al.indexOf(ir.front(), "\r\n\r\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(idx != -1);
|
|
||||||
|
|
||||||
string[] headers;
|
|
||||||
foreach(line; al.splitter(ir.front()[0 .. idx], "\r\n"))
|
|
||||||
if(line.length)
|
|
||||||
headers ~= cast(string)(line.idup);
|
|
||||||
|
|
||||||
ir.consume(idx + 4);
|
|
||||||
|
|
||||||
|
|
||||||
bool chunked = false;
|
|
||||||
bool closeConnection = false;
|
|
||||||
int contentLength;
|
|
||||||
|
|
||||||
// FIXME: integrate all this below
|
|
||||||
|
|
||||||
if(headers[0].indexOf("HTTP/1.0") != -1)
|
|
||||||
closeConnection = true; // always one request per connection with 1.0
|
|
||||||
|
|
||||||
foreach(h; headers[1..$]) {
|
|
||||||
auto colon = h.indexOf(":");
|
|
||||||
if(colon == -1)
|
|
||||||
throw new Exception("Http headers need colons " ~ h);
|
|
||||||
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:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
immutable(ubyte)[] data;
|
immutable(ubyte)[] data;
|
||||||
void log(in ubyte[] d) {
|
|
||||||
data ~= d;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto a = ir.front();
|
|
||||||
// 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;
|
|
||||||
a = ir.consume(a.length);
|
|
||||||
// we just finished it off, terminate the chunks
|
|
||||||
if(contentLength == 0) {
|
|
||||||
// goto finish;
|
|
||||||
} else {
|
|
||||||
ir.popFront();
|
|
||||||
a = ir.front();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// we actually have *more* here than we need....
|
|
||||||
log(a[0..contentLength]);
|
|
||||||
contentLength = 0;
|
|
||||||
ir.consume(contentLength);
|
|
||||||
// goto finish;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dechunk(contentLength, &log, ir);
|
|
||||||
}
|
|
||||||
|
|
||||||
void rdo(const(ubyte)[] d) {
|
void rdo(const(ubyte)[] d) {
|
||||||
sendAll(ir.source, d);
|
sendAll(ir.source, d);
|
||||||
}
|
}
|
||||||
|
|
||||||
this(headers, data, ir.source.remoteAddress().toString(), &rdo);
|
this(ir, ir.source.remoteAddress().toString(), 80 /* FIXME */, 0, false, &rdo, null, closeConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Initializes it from some almost* raw HTTP request data
|
/** Initializes it from some almost* raw HTTP request data
|
||||||
|
@ -836,106 +768,177 @@ class Cgi {
|
||||||
If null, the data is sent to stdout.
|
If null, the data is sent to stdout.
|
||||||
|
|
||||||
|
|
||||||
|
If you are behind a reverse proxy, getting this right is tricky.... FIXME
|
||||||
|
|
||||||
|
|
||||||
FIXME: data should be able to be streaming, for large files
|
FIXME: data should be able to be streaming, for large files
|
||||||
indeed, it should probably just take a file descriptor
|
indeed, it should probably just take a file descriptor
|
||||||
or two and do all the work itself.
|
or two and do all the work itself.
|
||||||
*/
|
*/
|
||||||
this(string[] headers, immutable(ubyte)[] data, string address, void delegate(const(ubyte)[]) _rawDataOutput = null, int pathInfoStarts = 0, void delegate() _flush = null) {
|
this(
|
||||||
auto parts = headers[0].split(" ");
|
BufferedInputRange inputData,
|
||||||
|
// string[] headers, immutable(ubyte)[] data,
|
||||||
|
string address, ushort _port,
|
||||||
|
int pathInfoStarts = 0, // use this if you know the script name, like if this is in a folder in a bigger web environment
|
||||||
|
bool _https = false,
|
||||||
|
void delegate(const(ubyte)[]) _rawDataOutput = null,
|
||||||
|
void delegate() _flush = null,
|
||||||
|
// this pointer tells if the connection is supposed to be closed after we handle this
|
||||||
|
bool* closeConnection = null)
|
||||||
|
{
|
||||||
|
|
||||||
https = false;
|
|
||||||
port = 80; // FIXME
|
https = _https;
|
||||||
|
port = _port;
|
||||||
|
|
||||||
rawDataOutput = _rawDataOutput;
|
rawDataOutput = _rawDataOutput;
|
||||||
flushDelegate = _flush;
|
flushDelegate = _flush;
|
||||||
nph = true;
|
nph = true;
|
||||||
|
|
||||||
requestMethod = to!RequestMethod(parts[0]);
|
|
||||||
|
|
||||||
requestUri = parts[1];
|
|
||||||
|
|
||||||
scriptName = requestUri[0 .. pathInfoStarts];
|
|
||||||
|
|
||||||
auto question = requestUri.indexOf("?");
|
|
||||||
if(question == -1) {
|
|
||||||
queryString = "";
|
|
||||||
pathInfo = requestUri[pathInfoStarts..$];
|
|
||||||
} else {
|
|
||||||
queryString = requestUri[question+1..$];
|
|
||||||
pathInfo = requestUri[pathInfoStarts..question];
|
|
||||||
}
|
|
||||||
|
|
||||||
get = getGetVariables();
|
|
||||||
auto ugh = decodeVariables(queryString);
|
|
||||||
getArray = assumeUnique(ugh);
|
|
||||||
|
|
||||||
remoteAddress = address;
|
remoteAddress = address;
|
||||||
|
|
||||||
if(headers[0].indexOf("HTTP/1.0") != -1) {
|
// streaming parser
|
||||||
http10 = true;
|
import al = std.algorithm;
|
||||||
autoBuffer = true;
|
|
||||||
|
auto idx = al.indexOf(inputData.front(), "\r\n\r\n");
|
||||||
|
while(idx == -1) {
|
||||||
|
inputData.popFront(0);
|
||||||
|
idx = al.indexOf(inputData.front(), "\r\n\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert(idx != -1);
|
||||||
|
|
||||||
|
|
||||||
string contentType = "";
|
string contentType = "";
|
||||||
|
|
||||||
string[string] requestHeadersHere;
|
string[string] requestHeadersHere;
|
||||||
|
|
||||||
foreach(header; headers[1..$]) {
|
size_t contentLength;
|
||||||
auto colon = header.indexOf(":");
|
|
||||||
if(colon == -1)
|
|
||||||
throw new Exception("HTTP headers should have a colon!");
|
|
||||||
string name = header[0..colon].toLower;
|
|
||||||
string value = header[colon+2..$]; // skip the colon and the space
|
|
||||||
|
|
||||||
requestHeadersHere[name] = value;
|
bool isChunked;
|
||||||
|
|
||||||
|
|
||||||
|
int headerNumber = 0;
|
||||||
|
foreach(line; al.splitter(inputData.front()[0 .. idx], "\r\n"))
|
||||||
|
if(line.length) {
|
||||||
|
headerNumber++;
|
||||||
|
auto header = cast(string) line.idup;
|
||||||
|
if(headerNumber == 1) {
|
||||||
|
// request line
|
||||||
|
auto parts = header.split(" ");
|
||||||
|
requestMethod = to!RequestMethod(parts[0]);
|
||||||
|
requestUri = parts[1];
|
||||||
|
|
||||||
|
scriptName = requestUri[0 .. pathInfoStarts];
|
||||||
|
|
||||||
|
auto question = requestUri.indexOf("?");
|
||||||
|
if(question == -1) {
|
||||||
|
queryString = "";
|
||||||
|
pathInfo = requestUri[pathInfoStarts..$];
|
||||||
|
} else {
|
||||||
|
queryString = requestUri[question+1..$];
|
||||||
|
pathInfo = requestUri[pathInfoStarts..question];
|
||||||
|
}
|
||||||
|
|
||||||
|
get = getGetVariables();
|
||||||
|
auto ugh = decodeVariables(queryString);
|
||||||
|
getArray = assumeUnique(ugh);
|
||||||
|
|
||||||
|
if(header.indexOf("HTTP/1.0") != -1) {
|
||||||
|
http10 = true;
|
||||||
|
autoBuffer = true;
|
||||||
|
if(closeConnection)
|
||||||
|
*closeConnection = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// other header
|
||||||
|
auto colon = header.indexOf(":");
|
||||||
|
if(colon == -1)
|
||||||
|
throw new Exception("HTTP headers should have a colon!");
|
||||||
|
string name = header[0..colon].toLower;
|
||||||
|
string value = header[colon+2..$]; // skip the colon and the space
|
||||||
|
|
||||||
|
requestHeadersHere[name] = value;
|
||||||
|
|
||||||
|
switch(name) {
|
||||||
|
case "accept":
|
||||||
|
accept = value;
|
||||||
|
break;
|
||||||
|
case "connection":
|
||||||
|
if(value == "close" && closeConnection)
|
||||||
|
*closeConnection = true;
|
||||||
|
if(value.toLower().indexOf("keep-alive") != -1)
|
||||||
|
keepAliveRequested = true;
|
||||||
|
break;
|
||||||
|
case "transfer-encoding":
|
||||||
|
if(value == "chunked")
|
||||||
|
isChunked = true;
|
||||||
|
break;
|
||||||
|
case "last-event-id":
|
||||||
|
lastEventId = value;
|
||||||
|
break;
|
||||||
|
case "authorization":
|
||||||
|
authorization = value;
|
||||||
|
break;
|
||||||
|
case "content-type":
|
||||||
|
contentType = value;
|
||||||
|
break;
|
||||||
|
case "content-length":
|
||||||
|
contentLength = to!size_t(value);
|
||||||
|
break;
|
||||||
|
case "host":
|
||||||
|
host = value;
|
||||||
|
break;
|
||||||
|
case "accept-encoding":
|
||||||
|
if(value.indexOf("gzip") != -1)
|
||||||
|
acceptsGzip = true;
|
||||||
|
break;
|
||||||
|
case "user-agent":
|
||||||
|
userAgent = value;
|
||||||
|
break;
|
||||||
|
case "referer":
|
||||||
|
referrer = value;
|
||||||
|
break;
|
||||||
|
case "cookie":
|
||||||
|
cookie ~= value;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// ignore it
|
||||||
|
}
|
||||||
|
|
||||||
switch(name) {
|
|
||||||
case "accept":
|
|
||||||
accept = value;
|
|
||||||
break;
|
|
||||||
case "last-event-id":
|
|
||||||
lastEventId = value;
|
|
||||||
break;
|
|
||||||
case "authorization":
|
|
||||||
authorization = value;
|
|
||||||
break;
|
|
||||||
case "content-type":
|
|
||||||
contentType = value;
|
|
||||||
break;
|
|
||||||
case "connection":
|
|
||||||
if(value.toLower().indexOf("keep-alive") != -1)
|
|
||||||
keepAliveRequested = true;
|
|
||||||
break;
|
|
||||||
case "host":
|
|
||||||
host = value;
|
|
||||||
break;
|
|
||||||
case "accept-encoding":
|
|
||||||
if(value.indexOf("gzip") != -1)
|
|
||||||
acceptsGzip = true;
|
|
||||||
break;
|
|
||||||
case "user-agent":
|
|
||||||
userAgent = value;
|
|
||||||
break;
|
|
||||||
case "referer":
|
|
||||||
referrer = value;
|
|
||||||
break;
|
|
||||||
case "cookie":
|
|
||||||
cookie ~= value;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// ignore it
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inputData.consume(idx + 4);
|
||||||
|
// done
|
||||||
|
|
||||||
requestHeaders = assumeUnique(requestHeadersHere);
|
requestHeaders = assumeUnique(requestHeadersHere);
|
||||||
|
|
||||||
cookiesArray = getCookieArray();
|
cookiesArray = getCookieArray();
|
||||||
cookies = keepLastOf(cookiesArray);
|
cookies = keepLastOf(cookiesArray);
|
||||||
if(data.length) {
|
|
||||||
prepareForIncomingDataChunks(contentType, data.length);
|
ByChunkRange dataByChunk;
|
||||||
for(size_t block = 0; block < data.length; block += 4096)
|
|
||||||
handleIncomingDataChunk(data[block .. ((block+4096 > data.length) ? data.length : block + 4096)]);
|
// reading Content-Length type data
|
||||||
|
// We need to read up the data we have, and write it out as a chunk.
|
||||||
|
if(!isChunked) {
|
||||||
|
dataByChunk = byChunk(inputData, contentLength);
|
||||||
|
} else {
|
||||||
|
// chunked requests happen, but not every day. Since we need to know
|
||||||
|
// the content length (for now, maybe that should change), we'll buffer
|
||||||
|
// the whole thing here instead of parse streaming. (I think this is what Apache does anyway in cgi modes)
|
||||||
|
auto data = dechunk(inputData);
|
||||||
|
|
||||||
|
// set the range here
|
||||||
|
dataByChunk = byChunk(data);
|
||||||
|
contentLength = data.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(dataByChunk !is null);
|
||||||
|
|
||||||
|
if(contentLength) {
|
||||||
|
prepareForIncomingDataChunks(contentType, contentLength);
|
||||||
|
foreach(dataChunk; dataByChunk)
|
||||||
|
handleIncomingDataChunk(dataChunk);
|
||||||
postArray = assumeUnique(pps._post);
|
postArray = assumeUnique(pps._post);
|
||||||
files = assumeUnique(pps._files);
|
files = assumeUnique(pps._files);
|
||||||
post = keepLastOf(postArray);
|
post = keepLastOf(postArray);
|
||||||
|
@ -1672,12 +1675,16 @@ version(embedded_httpd)
|
||||||
foreach(connection; manager) {
|
foreach(connection; manager) {
|
||||||
scope(failure) {
|
scope(failure) {
|
||||||
// FIXME
|
// FIXME
|
||||||
sendAll(connection, "HTTP/1.0 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n");
|
|
||||||
|
// FIXME: an exception after cgi.write() can output bad stuff according to the http protocol
|
||||||
|
auto msg = "Something went wrong.";
|
||||||
|
sendAll(connection, format("HTTP/1.0 500 Internal Server Error\r\nContent-Length: %s\r\n\r\n%s", msg.length, msg));
|
||||||
connection.close();
|
connection.close();
|
||||||
}
|
}
|
||||||
|
bool closeConnection;
|
||||||
auto ir = new BufferedInputRange(connection);
|
auto ir = new BufferedInputRange(connection);
|
||||||
while(!ir.empty) {
|
while(!ir.empty) {
|
||||||
auto cgi = new Cgi(ir);
|
auto cgi = new Cgi(ir, &closeConnection);
|
||||||
fun(cgi);
|
fun(cgi);
|
||||||
cgi.close();
|
cgi.close();
|
||||||
cgi.dispose();
|
cgi.dispose();
|
||||||
|
@ -1685,6 +1692,9 @@ version(embedded_httpd)
|
||||||
if(!ir.empty)
|
if(!ir.empty)
|
||||||
ir.popFront(); // get the next????
|
ir.popFront(); // get the next????
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(closeConnection)
|
||||||
|
connection.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2126,16 +2136,16 @@ class ListeningConnectionManager {
|
||||||
Socket listener;
|
Socket listener;
|
||||||
|
|
||||||
int opApply(CMT dg) {
|
int opApply(CMT dg) {
|
||||||
shared(int) broken;
|
shared(int) loopBroken;
|
||||||
|
|
||||||
while(!broken) {
|
while(!loopBroken) {
|
||||||
auto sn = listener.accept();
|
auto sn = listener.accept();
|
||||||
auto thread = new ConnectionThread(sn, &broken, dg);
|
auto thread = new ConnectionThread(sn, &loopBroken, dg);
|
||||||
thread.start();
|
thread.start();
|
||||||
// broken = dg(sn);
|
// loopBroken = dg(sn);
|
||||||
}
|
}
|
||||||
|
|
||||||
return broken;
|
return loopBroken;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2236,7 +2246,9 @@ Distributed under the Boost Software License, Version 1.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
void dechunk(int contentLength, void delegate(in ubyte[]) log, BufferedInputRange ir) {
|
immutable(ubyte[]) dechunk(BufferedInputRange ir) {
|
||||||
|
immutable(ubyte)[] ret;
|
||||||
|
|
||||||
another_chunk:
|
another_chunk:
|
||||||
// If here, we are at the beginning of a chunk.
|
// If here, we are at the beginning of a chunk.
|
||||||
auto a = ir.front();
|
auto a = ir.front();
|
||||||
|
@ -2309,7 +2321,7 @@ void dechunk(int contentLength, void delegate(in ubyte[]) log, BufferedInputRang
|
||||||
a = ir.front();
|
a = ir.front();
|
||||||
}
|
}
|
||||||
|
|
||||||
log(a[0..chunkSize]);
|
ret ~= (a[0..chunkSize]);
|
||||||
|
|
||||||
if(!(a.length > chunkSize + 2)) {
|
if(!(a.length > chunkSize + 2)) {
|
||||||
ir.popFront(0, chunkSize + 2);
|
ir.popFront(0, chunkSize + 2);
|
||||||
|
@ -2323,5 +2335,66 @@ void dechunk(int contentLength, void delegate(in ubyte[]) log, BufferedInputRang
|
||||||
}
|
}
|
||||||
|
|
||||||
finish:
|
finish:
|
||||||
return;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ByChunkRange {
|
||||||
|
bool empty();
|
||||||
|
void popFront();
|
||||||
|
const(ubyte)[] front();
|
||||||
|
}
|
||||||
|
|
||||||
|
ByChunkRange byChunk(const(ubyte)[] data) {
|
||||||
|
return new class ByChunkRange {
|
||||||
|
override bool empty() {
|
||||||
|
return !data.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
override void popFront() {
|
||||||
|
if(data.length > 4096)
|
||||||
|
data = data[4096 .. $];
|
||||||
|
else
|
||||||
|
data = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
override const(ubyte)[] front() {
|
||||||
|
return data[0 .. $ > 4096 ? 4096 : $];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
ByChunkRange byChunk(BufferedInputRange ir, size_t atMost) {
|
||||||
|
const(ubyte)[] f;
|
||||||
|
|
||||||
|
f = ir.front;
|
||||||
|
if(f.length > atMost)
|
||||||
|
f = f[0 .. atMost];
|
||||||
|
|
||||||
|
return new class ByChunkRange {
|
||||||
|
override bool empty() {
|
||||||
|
return atMost == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
override const(ubyte)[] front() {
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
override void popFront() {
|
||||||
|
auto a = ir.front();
|
||||||
|
|
||||||
|
if(a.length <= atMost) {
|
||||||
|
f = a;
|
||||||
|
atMost -= a.length;
|
||||||
|
a = ir.consume(a.length);
|
||||||
|
if(atMost != 0)
|
||||||
|
ir.popFront();
|
||||||
|
} else {
|
||||||
|
// we actually have *more* here than we need....
|
||||||
|
f = a[0..atMost];
|
||||||
|
atMost = 0;
|
||||||
|
ir.consume(atMost);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue