|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#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(); |
} |
|
qr = s->async_do_method_url(http_method, uri, |
args->variables, |
args->data, |
args->headers, |
0, |
lambda (Result ok) { |
res = ok; |
q && q->write("@"); |
if (async) { |
if (args->on_success) { |
args->on_success(res); |
} |
qr = 0; |
s = 0; |
} |
}, |
lambda (Result fail) { |
res = fail; |
q && q->write("@"); |
if (async) { |
if (args->on_failure) { |
args->on_failure(res); |
} |
qr = 0; |
s = 0; |
} |
}, |
({})); |
|
if (!query_has_maxtime()) { |
TRACE("External timeout\n"); |
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; |
} |
|
|
class Arguments |
{ |
|
int timeout; |
|
|
int maxtime; |
|
|
Protocols.HTTP.Session.URL url; |
|
|
mapping(string:string) headers; |
|
|
mapping(string:mixed) variables; |
|
|
void|string|mapping data; |
|
|
function(Result:void) on_success; |
|
|
function(Result:void) on_failure; |
|
|
|
|
protected void create(void|mapping(string:mixed) args) |
{ |
if (args) { |
foreach (args; string key; mixed val) { |
if (has_index(this, key)) { |
this[key] = val; |
} |
} |
} |
} |
} |
|
|
|
|
|
|
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; |
} |
|
|
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) |
{ |
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 async_fail(object q) |
{ |
TRACE("fail q: %O\n", q); |
|
mapping ret = ([ |
"status" : q->status, |
"status_desc" : q->status_desc, |
"host" : q->host, |
"headers" : copy_value(q->headers), |
"url" : ::url_requested |
]); |
|
|
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(object 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\n", loc); |
|
if (loc->scheme == "http" || loc->scheme == "https") { |
destroy(); |
follow_redirects--; |
do_async(prepare_method("GET", loc)); |
return; |
} |
} |
|
|
con->set_callbacks(0, 0); |
|
if (data_callback) { |
con->timed_async_fetch(async_data, async_fail); |
} |
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 |
]); |
|
|
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, 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; |
} |
} |
} |
} |
|
|