|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef HTTP_CLIENT_DEBUG |
# define TRACE(X...)werror("%s:%d: %s",basename(__FILE__),__LINE__,sprintf(X)) |
#else |
# define TRACE(X...)0 |
#endif |
|
protected constant DEFAULT_MAXTIME = 60; |
|
|
|
public Result sync_get(Protocols.HTTP.Session.URL uri, void|Arguments args) |
{ |
return do_safe_method("GET", uri, args); |
} |
|
|
|
public Result sync_post(Protocols.HTTP.Session.URL uri, void|Arguments args) |
{ |
return do_safe_method("POST", uri, args); |
} |
|
|
|
public Result sync_put(Protocols.HTTP.Session.URL uri, void|Arguments args) |
{ |
return do_safe_method("PUT", uri, args); |
} |
|
|
|
public Result sync_delete(Protocols.HTTP.Session.URL uri, void|Arguments args) |
{ |
return do_safe_method("DELETE", uri, args); |
} |
|
|
|
public void async_get(Protocols.HTTP.Session.URL uri, void|Arguments args) |
{ |
do_safe_method("GET", uri, args, true); |
} |
|
|
|
public void async_post(Protocols.HTTP.Session.URL uri, void|Arguments args) |
{ |
do_safe_method("POST", uri, args, true); |
} |
|
|
|
public void async_put(Protocols.HTTP.Session.URL uri, void|Arguments args) |
{ |
do_safe_method("PUT", uri, args, true); |
} |
|
|
|
public void async_delete(Protocols.HTTP.Session.URL uri, void|Arguments args) |
{ |
do_safe_method("DELETE", uri, args, true); |
} |
|
|
|
public Result do_safe_method(string http_method, |
Protocols.HTTP.Session.URL uri, |
void|Arguments args, |
void|bool async) |
{ |
if (!args) { |
args = Arguments(); |
} |
|
Result res; |
Session s = Session(); |
object qr; |
Thread.Queue q; |
mixed co_maxtime; |
|
if (args->maxtime) s->maxtime = args->maxtime; |
if (args->timeout) s->timeout = args->timeout; |
|
if (!async) { |
q = Thread.Queue(); |
} |
|
function(Result:void) cb = lambda (Result r) { |
TRACE("Got callback: %O\n", r); |
res = r; |
q && q->write("@"); |
if (async) { |
if (r->ok && args->on_success) { |
args->on_success(res); |
} |
else if (!r->ok && args->on_failure) { |
args->on_failure(r); |
} |
qr = 0; |
s = 0; |
} |
}; |
|
qr = s->async_do_method_url(http_method, uri, |
args->variables, |
args->data, |
args->headers, |
0, |
cb, |
cb, |
args->extra_args || ({})); |
|
if (!query_has_maxtime()) { |
TRACE("No maxtime in Protocols.HTTP.Query. Set external max timeout: %O\n", |
s->maxtime || DEFAULT_MAXTIME); |
co_maxtime = call_out(lambda () { |
TRACE("Timeout callback: %O\n", qr); |
|
res = Failure(([ |
"status" : 504, |
"status_desc" : "Gateway timeout", |
"host" : qr->con->host, |
"headers" : qr->con->headers, |
"url" : qr->url_requested |
])); |
|
qr->set_callbacks(0, 0, 0, 0); |
qr->con->set_callbacks(0, 0, 0); |
destruct(qr); |
destruct(s); |
|
q && q->write("@"); |
|
if (async) { |
if (args->on_failure) { |
args->on_failure(res); |
} |
|
qr = 0; |
s = 0; |
} |
}, s->maxtime || DEFAULT_MAXTIME); |
} |
|
if (!async) { |
q->read(); |
} |
|
if (co_maxtime && co_maxtime[0]) { |
TRACE("Remove timeout callout\n"); |
remove_call_out(co_maxtime); |
} |
|
if (!async) { |
q = 0; |
qr = 0; |
s = 0; |
} |
|
return res; |
} |
|
|
|
|
protected int(-1..1) _query_has_maxtime = -1; |
protected bool query_has_maxtime() |
{ |
if (_query_has_maxtime != -1) { |
return _query_has_maxtime == 1; |
} |
|
Protocols.HTTP.Query q = Protocols.HTTP.Query(); |
_query_has_maxtime = (int) has_index(q, "maxtime"); |
destruct(q); |
return _query_has_maxtime == 1; |
} |
|
|
class Arguments |
{ |
|
int timeout; |
|
|
int maxtime; |
|
|
mapping(string:string) headers; |
|
|
mapping(string:mixed) variables; |
|
|
void|string|mapping data; |
|
|
function(Result:void) on_success; |
|
|
function(Result:void) on_failure; |
|
|
array(mixed) extra_args; |
|
|
|
|
protected void create(void|mapping(string:mixed) args) |
{ |
if (args) { |
foreach (args; string key; mixed value) { |
if (has_index(this, key)) { |
if ((< "variables", "headers" >)[key]) { |
|
value = mkmapping(indices(value), map(values(value), |
lambda (mixed s) { |
return (string) s; |
})); |
} |
|
this[key] = value; |
} |
else { |
error("Unknown argument %O!\n", key); |
} |
} |
} |
} |
} |
|
|
|
|
|
|
class Result |
{ |
|
protected mapping result; |
|
|
public bool `ok(); |
|
|
public mapping `headers() |
{ |
return result->headers; |
} |
|
|
public string `host() |
{ |
return result->host; |
} |
|
|
|
public int `status() |
{ |
return result->status; |
} |
|
|
public string `status_description() |
{ |
return result->status_desc; |
} |
|
|
public string `url() |
{ |
return result->url; |
} |
|
|
public array(mixed) `extra_args() |
{ |
return result->extra_args; |
} |
|
|
protected void create(mapping _result) |
{ |
|
result = _result; |
} |
|
} |
|
|
|
|
|
|
|
class Success |
{ |
inherit Result; |
|
public bool `ok() { return true; } |
|
|
public string `data() { return result->data; } |
|
|
public int `length() |
{ |
return headers && (int)headers["content-length"]; |
} |
|
|
public string `content_type() |
{ |
if (string ct = (headers && headers["content-type"])) { |
sscanf (ct, "%s;", ct); |
return ct; |
} |
} |
|
|
|
public string `content_encoding() |
{ |
if (string ce = (headers && headers["content-type"])) { |
if (sscanf (ce, "%*s;%*scharset=%s", ce) == 3) { |
if (ce[0] == '"' || ce[0] == '\'') { |
ce = ce[1..<1]; |
} |
return ce; |
} |
} |
} |
} |
|
|
|
|
|
|
|
class Failure |
{ |
inherit Result; |
public bool `ok() { return false; } |
} |
|
|
|
protected class Session |
{ |
inherit Protocols.HTTP.Session : parent; |
|
public int(0..) maxtime, timeout; |
public int maximum_connections_per_server = 20; |
|
Request async_do_method_url(string method, |
URL url, |
void|mapping query_variables, |
void|string|mapping data, |
void|mapping extra_headers, |
function callback_headers_ok, |
function callback_data_ok, |
function callback_fail, |
array callback_arguments) |
{ |
if (stringp(url)) { |
url = Standards.URI(url); |
} |
|
|
|
if (!extra_headers || !extra_headers->host || !extra_headers->Host) { |
extra_headers = extra_headers || ([]); |
|
TRACE("Set host in headers: %O\n", url); |
|
if (url->scheme == "http") { |
extra_headers->host = url->host; |
if (url->port != 80) { |
extra_headers->host += ":" + url->port; |
} |
} |
else if (url->scheme == "https") { |
extra_headers->host = url->host; |
if (url->port != 443) { |
extra_headers->host += ":" + url->port; |
} |
} |
|
if (!sizeof(extra_headers)) { |
extra_headers = 0; |
} |
|
TRACE("Host header set?: %O\n", extra_headers); |
} |
|
TRACE("Request: %O\n", url); |
|
return ::async_do_method_url(method, url, query_variables, data, |
extra_headers, callback_headers_ok, |
callback_data_ok, callback_fail, |
callback_arguments); |
} |
|
|
class Request |
{ |
inherit parent::Request; |
|
protected void set_extra_args_in_result(mapping(string:mixed) r) |
{ |
if (extra_callback_arguments && sizeof(extra_callback_arguments) > 1) { |
r->extra_args = extra_callback_arguments[1..]; |
} |
} |
|
protected void async_fail(SessionQuery q) |
{ |
TRACE("fail q: %O -> %O\n", q, ::url_requested); |
|
mapping ret = ([ |
"status" : q->status, |
"status_desc" : q->status_desc, |
"host" : q->host, |
"headers" : copy_value(q->headers), |
"url" : ::url_requested |
]); |
|
TRACE("Ret: %O\n", ret); |
|
set_extra_args_in_result(ret); |
|
|
con->set_callbacks(0, 0); |
|
function fc = fail_callback; |
set_callbacks(0, 0, 0); |
extra_callback_arguments = 0; |
|
if (fc) { |
fc(Failure(ret)); |
} |
} |
|
|
protected void async_ok(SessionQuery q) |
{ |
TRACE("async_ok: %O -> %s!\n", q->host, ::url_requested); |
|
::check_for_cookies(); |
|
if (con->status >= 300 && con->status < 400 && |
con->headers->location && follow_redirects) |
{ |
Standards.URI loc = Standards.URI(con->headers->location,url_requested); |
TRACE("New location: %O -> %O (%O)\n", url_requested, loc, con->headers); |
|
if (loc->scheme == "http" || loc->scheme == "https") { |
con->set_callbacks(0, 0); |
::destroy(); |
follow_redirects--; |
do_async(prepare_method("GET", loc)); |
return; |
} |
} |
|
|
con->set_callbacks(0, 0); |
|
if (data_callback) { |
con->async_fetch(async_data); |
} |
else { |
extra_callback_arguments = 0; |
} |
} |
|
|
protected void async_data() |
{ |
mapping ret = ([ |
"host" : con->host, |
"status" : con->status, |
"status_desc" : con->status_desc, |
"headers" : copy_value(con->headers), |
"data" : con->data(), |
"url" : ::url_requested |
]); |
|
set_extra_args_in_result(ret); |
|
|
con->set_callbacks(0, 0); |
|
if (data_callback) { |
data_callback(Success(ret)); |
} |
|
extra_callback_arguments = 0; |
} |
|
void destroy() |
{ |
TRACE("Destructor called in Request: %O\n", ::url_requested); |
::set_callbacks(0, 0, 0); |
::destroy(); |
} |
} |
|
|
class SessionQuery |
{ |
inherit parent::SessionQuery; |
|
protected void create() |
{ |
#if constant (this::maxtime) |
TRACE("# Query has maxtime\n"); |
if (Session::maxtime) { |
this::maxtime = Session::maxtime; |
} |
#endif |
|
if (Session::timeout) { |
this::timeout = Session::timeout; |
} |
} |
} |
} |
|
|