51354e | 2016-05-13 | Pontus Östlund | |
#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)
{
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;
}
}
}
}
|