mirror of https://github.com/adamdruppe/arsd.git
command line processing for easier testing
This commit is contained in:
parent
79b2a25b92
commit
5ccf8b4ae0
217
cgi.d
217
cgi.d
|
@ -119,6 +119,8 @@ mixin template ForwardCgiConstructors() {
|
||||||
void delegate() _flush = null
|
void delegate() _flush = null
|
||||||
) { super(maxContentLength, env, readdata, _rawDataOutput, _flush); }
|
) { super(maxContentLength, env, readdata, _rawDataOutput, _flush); }
|
||||||
|
|
||||||
|
this(string[] args) { super(args); }
|
||||||
|
|
||||||
this(
|
this(
|
||||||
BufferedInputRange inputData,
|
BufferedInputRange inputData,
|
||||||
string address, ushort _port,
|
string address, ushort _port,
|
||||||
|
@ -209,6 +211,190 @@ class Cgi {
|
||||||
// this is an extension for when the method is not specified and you want to assume
|
// this is an extension for when the method is not specified and you want to assume
|
||||||
CommandLine }
|
CommandLine }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
import core.runtime;
|
||||||
|
auto args = Runtime.args();
|
||||||
|
|
||||||
|
we can call the app a few ways:
|
||||||
|
|
||||||
|
1) set up the environment variables and call the app (manually simulating CGI)
|
||||||
|
2) simulate a call automatically:
|
||||||
|
./app method 'uri'
|
||||||
|
|
||||||
|
for example:
|
||||||
|
./app get /path?arg arg2=something
|
||||||
|
|
||||||
|
Anything on the uri is treated as query string etc
|
||||||
|
|
||||||
|
on get method, further args are appended to the query string (encoded automatically)
|
||||||
|
on post method, further args are done as post
|
||||||
|
|
||||||
|
|
||||||
|
@name means import from file "name". if name == -, it uses stdin
|
||||||
|
(so info=@- means set info to the value of stdin)
|
||||||
|
|
||||||
|
|
||||||
|
Other arguments include:
|
||||||
|
--cookie name=value (these are all concated together)
|
||||||
|
--header 'X-Something: cool'
|
||||||
|
--referrer 'something'
|
||||||
|
--port 80
|
||||||
|
--remote-address some.ip.address.here
|
||||||
|
--https yes
|
||||||
|
--user-agent 'something'
|
||||||
|
--userpass 'user:pass'
|
||||||
|
--authorization 'Basic base64encoded_user:pass'
|
||||||
|
--accept 'content' // FIXME: better example
|
||||||
|
--last-event-id 'something'
|
||||||
|
--host 'something.com'
|
||||||
|
|
||||||
|
Non-simulation arguments:
|
||||||
|
--port xxx listening port for non-cgi things (valid for the cgi interfaces)
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** Initializes it with command line arguments (for easy testing) */
|
||||||
|
this(string[] args) {
|
||||||
|
bool lookingForMethod;
|
||||||
|
bool lookingForUri;
|
||||||
|
string nextArgIs;
|
||||||
|
|
||||||
|
string _cookie;
|
||||||
|
string _queryString;
|
||||||
|
string[][string] _post;
|
||||||
|
string[string] _headers;
|
||||||
|
|
||||||
|
string[] breakUp(string s) {
|
||||||
|
string k, v;
|
||||||
|
auto idx = s.indexOf("=");
|
||||||
|
if(idx == -1) {
|
||||||
|
k = s;
|
||||||
|
} else {
|
||||||
|
k = s[0 .. idx];
|
||||||
|
v = s[idx + 1 .. $];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [k, v];
|
||||||
|
}
|
||||||
|
|
||||||
|
lookingForMethod = true;
|
||||||
|
|
||||||
|
scriptName = args[0];
|
||||||
|
|
||||||
|
foreach(arg; args[1 .. $]) {
|
||||||
|
if(arg.startsWith("--")) {
|
||||||
|
nextArgIs = arg[2 .. $];
|
||||||
|
} else if(nextArgIs.length) {
|
||||||
|
switch(nextArgIs) {
|
||||||
|
case "cookie":
|
||||||
|
auto info = breakUp(arg);
|
||||||
|
if(_cookie.length)
|
||||||
|
_cookie ~= "; ";
|
||||||
|
_cookie ~= std.uri.encodeComponent(info[0]) ~ "=" ~ std.uri.encodeComponent(info[1]);
|
||||||
|
break;
|
||||||
|
case "port":
|
||||||
|
port = to!int(arg);
|
||||||
|
break;
|
||||||
|
case "referrer":
|
||||||
|
referrer = arg;
|
||||||
|
break;
|
||||||
|
case "remote-address":
|
||||||
|
remoteAddress = arg;
|
||||||
|
break;
|
||||||
|
case "user-agent":
|
||||||
|
userAgent = arg;
|
||||||
|
break;
|
||||||
|
case "authorization":
|
||||||
|
authorization = arg;
|
||||||
|
break;
|
||||||
|
case "userpass":
|
||||||
|
authorization = "Basic " ~ Base64.encode(cast(immutable(ubyte)[]) (arg)).idup;
|
||||||
|
break;
|
||||||
|
case "accept":
|
||||||
|
accept = arg;
|
||||||
|
break;
|
||||||
|
case "last-event-id":
|
||||||
|
lastEventId = arg;
|
||||||
|
break;
|
||||||
|
case "https":
|
||||||
|
if(arg == "yes")
|
||||||
|
https = true;
|
||||||
|
break;
|
||||||
|
case "header":
|
||||||
|
string thing, other;
|
||||||
|
auto idx = arg.indexOf(":");
|
||||||
|
if(idx == -1)
|
||||||
|
throw new Exception("need a colon in a http header");
|
||||||
|
thing = arg[0 .. idx];
|
||||||
|
other = arg[idx + 1.. $];
|
||||||
|
_headers[thing.strip.toLower()] = other.strip;
|
||||||
|
break;
|
||||||
|
case "host":
|
||||||
|
host = arg;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// skip, we don't know it but that's ok, it might be used elsewhere so no error
|
||||||
|
}
|
||||||
|
|
||||||
|
nextArgIs = null;
|
||||||
|
} else if(lookingForMethod) {
|
||||||
|
lookingForMethod = false;
|
||||||
|
lookingForUri = true;
|
||||||
|
|
||||||
|
if(arg.toLower() == "commandline")
|
||||||
|
requestMethod = RequestMethod.CommandLine;
|
||||||
|
else
|
||||||
|
requestMethod = to!RequestMethod(arg.toUpper());
|
||||||
|
} else if(lookingForUri) {
|
||||||
|
lookingForUri = false;
|
||||||
|
|
||||||
|
requestUri = arg;
|
||||||
|
|
||||||
|
auto idx = arg.indexOf("?");
|
||||||
|
if(idx == -1)
|
||||||
|
pathInfo = arg;
|
||||||
|
else {
|
||||||
|
pathInfo = arg[0 .. idx];
|
||||||
|
queryString = arg[idx + 1 .. $];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// it is an argument of some sort
|
||||||
|
if(requestMethod == Cgi.RequestMethod.POST) {
|
||||||
|
auto parts = breakUp(arg);
|
||||||
|
_post[parts[0]] ~= parts[1];
|
||||||
|
} else {
|
||||||
|
if(_queryString.length)
|
||||||
|
_queryString ~= "&";
|
||||||
|
auto parts = breakUp(arg);
|
||||||
|
_queryString ~= std.uri.encodeComponent(parts[0]) ~ "=" ~ std.uri.encodeComponent(parts[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
acceptsGzip = false;
|
||||||
|
keepAliveRequested = false;
|
||||||
|
requestHeaders = cast(immutable) _headers;
|
||||||
|
|
||||||
|
cookie = _cookie;
|
||||||
|
cookiesArray = getCookieArray();
|
||||||
|
cookies = keepLastOf(cookiesArray);
|
||||||
|
|
||||||
|
queryString = _queryString;
|
||||||
|
getArray = cast(immutable) decodeVariables(queryString);
|
||||||
|
get = keepLastOf(getArray);
|
||||||
|
|
||||||
|
postArray = cast(immutable) _post;
|
||||||
|
post = keepLastOf(_post);
|
||||||
|
|
||||||
|
// FIXME
|
||||||
|
filesArray = null;
|
||||||
|
files = null;
|
||||||
|
|
||||||
|
isCalledWithCommandLineArguments = true;
|
||||||
|
}
|
||||||
|
|
||||||
/** Initializes it using a CGI or CGI-like interface */
|
/** Initializes it using a CGI or CGI-like interface */
|
||||||
this(long maxContentLength = defaultMaxContentLength,
|
this(long maxContentLength = defaultMaxContentLength,
|
||||||
// use this to override the environment variable listing
|
// use this to override the environment variable listing
|
||||||
|
@ -221,6 +407,7 @@ class Cgi {
|
||||||
void delegate() _flush = null
|
void delegate() _flush = null
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
isCalledWithCommandLineArguments = false;
|
||||||
rawDataOutput = _rawDataOutput;
|
rawDataOutput = _rawDataOutput;
|
||||||
flushDelegate = _flush;
|
flushDelegate = _flush;
|
||||||
auto getenv = delegate string(string var) {
|
auto getenv = delegate string(string var) {
|
||||||
|
@ -860,6 +1047,7 @@ 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.
|
||||||
/// *closeConnection will be set to true if you should close the connection after handling this request
|
/// *closeConnection will be set to true if you should close the connection after handling this request
|
||||||
this(BufferedInputRange ir, bool* closeConnection) {
|
this(BufferedInputRange ir, bool* closeConnection) {
|
||||||
|
isCalledWithCommandLineArguments = false;
|
||||||
import al = std.algorithm;
|
import al = std.algorithm;
|
||||||
|
|
||||||
immutable(ubyte)[] data;
|
immutable(ubyte)[] data;
|
||||||
|
@ -901,6 +1089,7 @@ class Cgi {
|
||||||
bool* closeConnection = null)
|
bool* closeConnection = null)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
isCalledWithCommandLineArguments = false;
|
||||||
|
|
||||||
https = _https;
|
https = _https;
|
||||||
port = _port;
|
port = _port;
|
||||||
|
@ -1161,6 +1350,9 @@ class Cgi {
|
||||||
immutable bool acceptsGzip;
|
immutable bool acceptsGzip;
|
||||||
immutable bool keepAliveRequested;
|
immutable bool keepAliveRequested;
|
||||||
|
|
||||||
|
/// Set to true if and only if this was initialized with command line arguments
|
||||||
|
immutable bool isCalledWithCommandLineArguments;
|
||||||
|
|
||||||
/// This gets a full url for the current request, including port, protocol, host, path, and query
|
/// This gets a full url for the current request, including port, protocol, host, path, and query
|
||||||
string getCurrentCompleteUri() const {
|
string getCurrentCompleteUri() const {
|
||||||
ushort defaultPort = https ? 443 : 80;
|
ushort defaultPort = https ? 443 : 80;
|
||||||
|
@ -1954,17 +2146,40 @@ bool handleException(Cgi cgi, Throwable t) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isCgiRequestMethod(string s) {
|
||||||
|
s = s.toUpper();
|
||||||
|
if(s == "COMMANDLINE")
|
||||||
|
return true;
|
||||||
|
foreach(member; __traits(allMembers, Cgi.RequestMethod))
|
||||||
|
if(s == member)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/// If you want to use a subclass of Cgi with generic main, use this mixin.
|
/// If you want to use a subclass of Cgi with generic main, use this mixin.
|
||||||
mixin template CustomCgiMain(CustomCgi, alias fun, long maxContentLength = defaultMaxContentLength) if(is(CustomCgi : Cgi)) {
|
mixin template CustomCgiMain(CustomCgi, alias fun, long maxContentLength = defaultMaxContentLength) if(is(CustomCgi : Cgi)) {
|
||||||
// kinda hacky - the T... is passed to Cgi's constructor in standard cgi mode, and ignored elsewhere
|
// kinda hacky - the T... is passed to Cgi's constructor in standard cgi mode, and ignored elsewhere
|
||||||
|
|
||||||
void main(string[] args) {
|
void main(string[] args) {
|
||||||
|
|
||||||
|
|
||||||
|
// we support command line thing for easy testing everywhere
|
||||||
|
// it needs to be called ./app method uri [other args...]
|
||||||
|
if(args.length >= 3 && isCgiRequestMethod(args[1])) {
|
||||||
|
Cgi cgi = new CustomCgi(args);
|
||||||
|
scope(exit) cgi.dispose();
|
||||||
|
fun(cgi);
|
||||||
|
cgi.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
ushort listeningPort(ushort def) {
|
ushort listeningPort(ushort def) {
|
||||||
bool found = false;
|
bool found = false;
|
||||||
foreach(arg; args) {
|
foreach(arg; args) {
|
||||||
if(found)
|
if(found)
|
||||||
return to!ushort(arg);
|
return to!ushort(arg);
|
||||||
if(arg == "--port" || arg == "-p" || arg == "/port")
|
if(arg == "--port" || arg == "-p" || arg == "/port" || arg == "--listening-port")
|
||||||
found = true;
|
found = true;
|
||||||
}
|
}
|
||||||
return def;
|
return def;
|
||||||
|
|
Loading…
Reference in New Issue