single-serve option

This commit is contained in:
Adam D. Ruppe 2020-10-10 15:25:44 -04:00
parent a53ea697bd
commit 19f6dd989c
1 changed files with 101 additions and 41 deletions

142
cgi.d
View File

@ -1621,14 +1621,22 @@ class Cgi {
} }
auto ira = ir.source.remoteAddress(); auto ira = ir.source.remoteAddress();
auto irLocalAddress = ir.source.localAddress();
ushort port = 80;
if(auto ia = cast(InternetAddress) irLocalAddress) {
port = ia.port;
} else if(auto ia = cast(Internet6Address) irLocalAddress) {
port = ia.port;
}
// that check for UnixAddress is to work around a Phobos bug // that check for UnixAddress is to work around a Phobos bug
// see: https://github.com/dlang/phobos/pull/7383 // see: https://github.com/dlang/phobos/pull/7383
// but this might be more useful anyway tbh for this case // but this might be more useful anyway tbh for this case
version(Posix) version(Posix)
this(ir, cast(UnixAddress) ira ? "unix:" : ira.toString(), 80 /* FIXME */, 0, false, &rdo, null, closeConnection); this(ir, cast(UnixAddress) ira ? "unix:" : ira.toString(), port, 0, false, &rdo, null, closeConnection);
else else
this(ir, ira.toString(), 80 /* FIXME */, 0, false, &rdo, null, closeConnection); this(ir, ira.toString(), port, 0, false, &rdo, null, closeConnection);
} }
/** /**
@ -3296,6 +3304,12 @@ struct RequestServer {
/// ///
ushort listeningPort = defaultListeningPort(); ushort listeningPort = defaultListeningPort();
///
this(string defaultHost, ushort defaultPort) {
this.listeningHost = defaultHost;
this.listeningPort = defaultPort;
}
/// ///
this(ushort defaultPort) { this(ushort defaultPort) {
listeningPort = defaultPort; listeningPort = defaultPort;
@ -3321,6 +3335,41 @@ struct RequestServer {
} }
} }
/++
Serves a single request on this thread, with an embedded server, then stops. Designed for cases like embedded oauth responders
History:
Added Oct 10, 2020.
Example:
---
import arsd.cgi;
void main() {
RequestServer server = RequestServer("127.0.0.1", 6789);
string oauthCode;
string oauthScope;
server.serveOnce!((cgi) {
oauthCode = cgi.request("code");
oauthScope = cgi.request("scope");
cgi.write("Thank you, please return to the application.");
});
// use the code and scope given
}
---
+/
void serveOnce(alias fun, CustomCgi = Cgi, long maxContentLength = defaultMaxContentLength)() {
import std.socket;
bool tcp;
void delegate() cleanup;
auto socket = startListening(listeningHost, listeningPort, tcp, cleanup, 1);
auto connection = socket.accept();
doThreadHttpConnectionGuts!(CustomCgi, fun, true)(connection);
if(cleanup)
cleanup();
}
/++ /++
Starts serving requests according to the current configuration. Starts serving requests according to the current configuration.
+/ +/
@ -3858,7 +3907,6 @@ class CgiFiber : Fiber {
} }
} }
version(embedded_httpd_threads)
void doThreadHttpConnection(CustomCgi, alias fun)(Socket connection) { void doThreadHttpConnection(CustomCgi, alias fun)(Socket connection) {
assert(connection !is null); assert(connection !is null);
version(cgi_use_fiber) { version(cgi_use_fiber) {
@ -3872,15 +3920,14 @@ void doThreadHttpConnection(CustomCgi, alias fun)(Socket connection) {
} }
} }
version(embedded_httpd_threads) void doThreadHttpConnectionGuts(CustomCgi, alias fun, bool alwaysCloseConnection = false)(Socket connection) {
void doThreadHttpConnectionGuts(CustomCgi, alias fun)(Socket connection) {
scope(failure) { scope(failure) {
// catch all for other errors // catch all for other errors
sendAll(connection, plainHttpError(false, "500 Internal Server Error", null)); sendAll(connection, plainHttpError(false, "500 Internal Server Error", null));
connection.close(); connection.close();
} }
bool closeConnection; bool closeConnection = alwaysCloseConnection;
/+ /+
ubyte[4096] inputBuffer = void; ubyte[4096] inputBuffer = void;
@ -3900,6 +3947,7 @@ void doThreadHttpConnectionGuts(CustomCgi, alias fun)(Socket connection) {
ir.popFront(); ir.popFront();
if(ir.sourceClosed) { if(ir.sourceClosed) {
connection.close(); connection.close();
closeConnection = true;
break; break;
} }
} }
@ -3945,9 +3993,10 @@ void doThreadHttpConnectionGuts(CustomCgi, alias fun)(Socket connection) {
closeConnection = true; closeConnection = true;
} }
if(closeConnection) { if(closeConnection || alwaysCloseConnection) {
connection.close(); connection.close();
ir.dispose(); ir.dispose();
closeConnection = false; // don't reclose after loop
break; break;
} else { } else {
if(ir.front.length) { if(ir.front.length) {
@ -3955,6 +4004,7 @@ void doThreadHttpConnectionGuts(CustomCgi, alias fun)(Socket connection) {
} else if(ir.sourceClosed) { } else if(ir.sourceClosed) {
ir.source.close(); ir.source.close();
ir.dispose(); ir.dispose();
closeConnection = false;
} else { } else {
continue; continue;
// break; // this was for a keepalive experiment // break; // this was for a keepalive experiment
@ -4587,40 +4637,7 @@ class ListeningConnectionManager {
this(string host, ushort port, void function(Socket) handler) { this(string host, ushort port, void function(Socket) handler) {
this.handler = handler; this.handler = handler;
if(host.startsWith("unix:")) { listener = startListening(host, port, tcp, cleanup, 128);
version(Posix) {
listener = new Socket(AddressFamily.UNIX, SocketType.STREAM);
cloexec(listener);
string filename = host["unix:".length .. $].idup;
listener.bind(new UnixAddress(filename));
cleanup = delegate() {
import std.file;
remove(filename);
};
tcp = false;
} else {
throw new Exception("unix sockets not supported on this system");
}
} else if(host.startsWith("abstract:")) {
version(linux) {
listener = new Socket(AddressFamily.UNIX, SocketType.STREAM);
cloexec(listener);
string filename = "\0" ~ host["abstract:".length .. $];
import std.stdio; stderr.writeln("Listening to abstract unix domain socket: ", host["abstract:".length .. $]);
listener.bind(new UnixAddress(filename));
tcp = false;
} else {
throw new Exception("abstract unix sockets not supported on this system");
}
} else {
listener = new TcpSocket();
cloexec(listener);
listener.setOption(SocketOptionLevel.SOCKET, SocketOption.REUSEADDR, true);
listener.bind(host.length ? parseAddress(host, port) : new InternetAddress(port));
tcp = true;
}
listener.listen(128);
version(cgi_use_fiber) version(cgi_use_fork) version(cgi_use_fiber) version(cgi_use_fork)
listener.blocking = false; listener.blocking = false;
@ -4638,6 +4655,49 @@ class ListeningConnectionManager {
} }
} }
Socket startListening(string host, ushort port, ref bool tcp, ref void delegate() cleanup, int backQueue) {
Socket listener;
if(host.startsWith("unix:")) {
version(Posix) {
listener = new Socket(AddressFamily.UNIX, SocketType.STREAM);
cloexec(listener);
string filename = host["unix:".length .. $].idup;
listener.bind(new UnixAddress(filename));
cleanup = delegate() {
listener.close();
import std.file;
remove(filename);
};
tcp = false;
} else {
throw new Exception("unix sockets not supported on this system");
}
} else if(host.startsWith("abstract:")) {
version(linux) {
listener = new Socket(AddressFamily.UNIX, SocketType.STREAM);
cloexec(listener);
string filename = "\0" ~ host["abstract:".length .. $];
import std.stdio; stderr.writeln("Listening to abstract unix domain socket: ", host["abstract:".length .. $]);
listener.bind(new UnixAddress(filename));
tcp = false;
} else {
throw new Exception("abstract unix sockets not supported on this system");
}
} else {
listener = new TcpSocket();
cloexec(listener);
listener.setOption(SocketOptionLevel.SOCKET, SocketOption.REUSEADDR, true);
listener.bind(host.length ? parseAddress(host, port) : new InternetAddress(port));
cleanup = delegate() {
listener.close();
};
tcp = true;
}
listener.listen(backQueue);
return listener;
}
// helper function to send a lot to a socket. Since this blocks for the buffer (possibly several times), you should probably call it in a separate thread or something. // helper function to send a lot to a socket. Since this blocks for the buffer (possibly several times), you should probably call it in a separate thread or something.
void sendAll(Socket s, const(void)[] data, string file = __FILE__, size_t line = __LINE__) { void sendAll(Socket s, const(void)[] data, string file = __FILE__, size_t line = __LINE__) {
if(data.length == 0) return; if(data.length == 0) return;