AnyEvent-YACurl
view release on metacpan or search on metacpan
#define PERL_NO_GET_CONTEXT
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include "ppport.h"
#define MY_CXT_KEY "AnyEvent::YACurl::_guts" XS_VERSION
#include <curl/curl.h>
#include "libcurl-symbols.h"
typedef struct {
SV *watchset_fn;
SV *timerset_fn;
HV *curlopt;
} my_cxt_t;
typedef struct {
CURLM *multi;
SV *weak_self_ref;
int needs_invoke_timeout;
int needs_read_info;
int last_running;
} AnyEvent__YACurl;
typedef struct {
SV *self_rv;
CURL *easy;
curl_mime *mimepost;
AV *held_references;
FILE *redirected_stderr;
int slists_count;
struct curl_slist **slists;
char errbuf[CURL_ERROR_SIZE];
SV *callback;
} AnyEvent__YACurl__Response;
START_MY_CXT
struct curl_slist *slist_from_av(pTHX_ struct curl_slist *list, AV *input);
void maybe_warn_eval(pTHX)
{
SV *error = ERRSV;
if (SvTRUE(error)) {
warn("Error in callback: %s", SvPV_nolen(error));
}
}
int mcurl_socket_callback(CURL* easy,
curl_socket_t s,
int what,
void* userp,
void* socketp)
{
dTHX;
dMY_CXT;
dSP;
ENTER;
SAVETMPS;
PUSHMARK(SP);
EXTEND(SP, 3);
PUSHs((SV*)userp); /* XXX This is a weakened reference, will it ever be undef? */
PUSHs(sv_2mortal(newSViv(s)));
PUSHs(sv_2mortal(newSViv(what)));
PUTBACK;
call_sv(MY_CXT.watchset_fn, G_DISCARD | G_VOID);
FREETMPS;
LEAVE;
return 0;
}
int mcurl_timer_callback(CURLM* multi,
long timeout_ms,
void *userp)
{
dTHX;
if (timeout_ms == 0) {
/* We short-circuit timeout_ms==0, as we're very likely to call do_post_work shortly
* after reaching this code path. A timer of 0sec in AnyEvent would almost always turn
* into a 1ms wait, which is unnecessary and slow. Same goes for AE::postpone. */
IV tmp = SvIV((SV*)SvRV((SV*)userp));
AnyEvent__YACurl *client = INT2PTR(AnyEvent__YACurl*, tmp);
client->needs_invoke_timeout = 1;
return 0;
}
dMY_CXT;
dSP;
ENTER;
SAVETMPS;
PUSHMARK(SP);
EXTEND(SP, 2);
PUSHs((SV*)userp); /* XXX This is a weakened reference, will it ever be undef? */
PUSHs(sv_2mortal(newSViv(timeout_ms)));
PUTBACK;
call_sv(MY_CXT.timerset_fn, G_DISCARD | G_VOID);
FREETMPS;
LEAVE;
return 0;
}
/* write callback: used for WRITEFUNCTION and HEADERFUNCTION */
size_t mcurl_write_callback(char *ptr,
size_t size,
size_t nmemb,
void *userdata)
{
dTHX;
dSP;
ENTER;
SAVETMPS;
PUSHMARK(SP);
EXTEND(SP, 1);
PUSHs(sv_2mortal(newSVpvn(ptr, size*nmemb)));
PUTBACK;
call_sv((SV*)userdata, G_DISCARD | G_VOID | G_EVAL);
SPAGAIN;
maybe_warn_eval(aTHX);
PUTBACK;
FREETMPS;
LEAVE;
return size * nmemb;
}
size_t mcurl_read_callback(char *buffer,
size_t size,
size_t nitems,
void *userdata)
{
size_t result;
dTHX;
dSP;
ENTER;
SAVETMPS;
PUSHMARK(SP);
EXTEND(SP, 1);
PUSHs(sv_2mortal(newSViv(size * nitems)));
PUTBACK;
call_sv((SV*)userdata, G_SCALAR | G_EVAL);
SPAGAIN;
maybe_warn_eval(aTHX);
SV *data = POPs;
if (!SvOK(data)) {
int have_value = 0;
{
SV **value_sv = hv_fetchs(entry_hv, "value", FALSE);
if (value_sv && SvOK(*value_sv)) {
if (have_value)
croak("MIMEPOST: at most one of 'value' or 'file' may be provided");
have_value = 1;
STRLEN valuelen;
char *value = SvPV(*value_sv, valuelen);
curl_mime_data(part, value, valuelen);
}
}
{
SV **file_sv = hv_fetchs(entry_hv, "file", FALSE);
if (file_sv && SvOK(*file_sv)) {
if (have_value)
croak("MIMEPOST: at most one of 'value' or 'file' may be provided");
have_value = 1;
STRLEN filelen;
char *filename = SvPV(*file_sv, filelen);
curl_mime_filedata(part, filename);
}
}
if (!have_value) {
croak("MIMEPOST: one of 'value' or 'file' is required, together with 'name'");
}
}
/* If this fails, we'll still free the mimepost properly later */
result = curl_easy_setopt(request->easy, CURLOPT_MIMEPOST, request->mimepost);
break;
}
/* Don't know... */
default:
{
croak("Not sure what to do with CURL option %d", option);
break;
}
}
return result;
}
MODULE = AnyEvent::YACurl PACKAGE = AnyEvent::YACurl
PROTOTYPES: DISABLE
BOOT:
{
/* XXX: Needs a CLONE */
MY_CXT_INIT;
MY_CXT.watchset_fn = NULL;
MY_CXT.timerset_fn = NULL;
MY_CXT.curlopt = newHV();
fill_hv_with_constants(aTHX_ MY_CXT.curlopt);
curl_global_init(CURL_GLOBAL_ALL);
}
void
new(class, args)
char *class
HV *args
PPCODE:
dMY_CXT;
(void)class;
AnyEvent__YACurl *client;
Newxz(client, 1, AnyEvent__YACurl);
ST(0) = sv_newmortal();
sv_setref_pv(ST(0), "AnyEvent::YACurl", (void*)client);
/* XXX When we destroy the client, do we pass undefs to the timer/watch functions? */
client->weak_self_ref = newSVsv(ST(0));
sv_rvweaken(client->weak_self_ref);
client->multi = curl_multi_init();
curl_multi_setopt(client->multi, CURLMOPT_SOCKETFUNCTION, mcurl_socket_callback);
curl_multi_setopt(client->multi, CURLMOPT_TIMERFUNCTION, mcurl_timer_callback);
curl_multi_setopt(client->multi, CURLMOPT_SOCKETDATA, (void*)client->weak_self_ref);
curl_multi_setopt(client->multi, CURLMOPT_TIMERDATA, (void*)client->weak_self_ref);
{
hv_iterinit(args);
HE *iterentry;
while ((iterentry = hv_iternext(args)) != NULL) {
long opt;
int opt_from_str;
SV *key = HeSVKEY_force(iterentry);
opt = option_from_sv_or_croak(aTHX_ aMY_CXT_ key, HeHASH(iterentry), &opt_from_str);
switch (opt) {
/* Longs */
case CURLMOPT_CHUNK_LENGTH_PENALTY_SIZE:
case CURLMOPT_CONTENT_LENGTH_PENALTY_SIZE:
case CURLMOPT_MAX_HOST_CONNECTIONS:
case CURLMOPT_MAX_PIPELINE_LENGTH:
case CURLMOPT_MAX_TOTAL_CONNECTIONS:
case CURLMOPT_MAXCONNECTS:
case CURLMOPT_PIPELINING:
{
long value = SvIV(HeVAL(iterentry));
CURLMcode mcode = curl_multi_setopt(client->multi, opt, value);
if (mcode != CURLM_OK) {
croak("Failed to set %d (%s): %s", opt, SvPV_nolen(key), curl_multi_strerror(mcode));
}
break;
}
/* String arrays */
case CURLMOPT_PIPELINING_SITE_BL:
case CURLMOPT_PIPELINING_SERVER_BL:
{
char **strings;
if (!SvROK(HeVAL(iterentry)) || SvTYPE(SvRV(HeVAL(iterentry))) != SVt_PVAV) {
croak("%d (%s): cannot convert value to ARRAYREF", opt, SvPV_nolen(key));
}
AV *array = (AV*)SvRV(HeVAL(iterentry));
int arraylen = av_len(array) + 1;
Newxz(strings, arraylen+1, char*);
int i;
for (i = 0; i < arraylen; i++) {
char *strcopy, *pv;
STRLEN pvlen;
pv = SvPV(*av_fetch(array, i, TRUE), pvlen);
Newxz(strcopy, pvlen+1, char);
Copy(pv, strcopy, pvlen, char);
strings[i] = strcopy;
}
CURLMcode mcode = curl_multi_setopt(client->multi, opt, strings);
for (i = 0; i < arraylen; i++) {
Safefree(strings[i]);
}
request(self, callback, options)
SV* self
SV* callback
HV* options
CODE:
dMY_CXT;
AnyEvent__YACurl *client = sv_to_client(aTHX_ self);
/* Bit of a memory juggle to avoid leaks and allow us to croak() */
AnyEvent__YACurl__Response *response_ctx;
Newxz(response_ctx, 1, AnyEvent__YACurl__Response);
response_ctx->self_rv = sv_newmortal();
sv_setref_pv(response_ctx->self_rv, "AnyEvent::YACurl::Response", (void*)response_ctx);
CURL* easy = curl_easy_init();
if (!easy) {
croak("Failed to instantiate CURL object");
}
response_ctx->easy = easy;
if (curl_easy_setopt(easy, CURLOPT_PRIVATE, response_ctx) != 0) {
croak("Failed to setup CURL object");
}
response_ctx->held_references = newAV();
response_ctx->callback = newSVsv(callback);
/* Avoid deallocating the client while the request is ongoing */
av_push(response_ctx->held_references, newSVsv(self));
hv_iterinit(options);
HE *iterentry;
while ((iterentry = hv_iternext(options)) != NULL) {
long opt;
int opt_from_str;
SV *key = HeSVKEY_force(iterentry);
opt = option_from_sv_or_croak(aTHX_ aMY_CXT_ key, HeHASH(iterentry), &opt_from_str);
CURLcode ccode = setopt_sv_or_croak(aTHX_ response_ctx, opt, HeVAL(iterentry));
if (ccode != CURLE_OK) {
croak("Failed to set %s: %s", SvPV_nolen(key), curl_easy_strerror(ccode));
}
}
CURLMcode error = curl_multi_add_handle(client->multi, easy);
if (error != CURLM_OK) {
croak("Failed to perform CURL request: %s", curl_multi_strerror(error));
}
/* At this point we succeeded, so we want to be sure we retain the structs until we're done */
SvREFCNT_inc(response_ctx->self_rv);
update_running(aTHX_ client, client->last_running + 1);
client->needs_invoke_timeout = 1;
do_post_work(aTHX_ client);
PROTOTYPES: ENABLE
void
_ae_set_helpers(watchset, timerset)
SV* watchset
SV* timerset
CODE:
dMY_CXT;
if (MY_CXT.watchset_fn != NULL)
croak("watchset already set");
MY_CXT.watchset_fn = newSVsv(watchset);
if (MY_CXT.timerset_fn != NULL)
croak("timerset already set");
MY_CXT.timerset_fn = newSVsv(timerset);
void
_ae_timer_fired(self)
SV* self
CODE:
AnyEvent__YACurl *client = sv_to_client(aTHX_ self);
client->needs_invoke_timeout = 1;
do_post_work(aTHX_ client);
void
_ae_event(self, sock, is_write)
SV* self
int sock
int is_write
CODE:
AnyEvent__YACurl *client = sv_to_client(aTHX_ self);
int running;
curl_multi_socket_action(client->multi, sock, (is_write ? CURL_CSELECT_OUT : CURL_CSELECT_IN), &running);
update_running(aTHX_ client, running);
do_post_work(aTHX_ client);
HV*
_get_known_constants()
CODE:
RETVAL = newHV();
sv_2mortal((SV*)RETVAL); /* hehe, perl bugs! */
fill_hv_with_constants(aTHX_ RETVAL);
OUTPUT:
RETVAL
void
DESTROY(self)
SV* self
CODE:
AnyEvent__YACurl *client = sv_to_client(aTHX_ self);
if (client->last_running)
warn("Destroying with %d requests active", client->last_running);
if (client->multi != NULL) {
curl_multi_cleanup(client->multi);
}
if (client->weak_self_ref != NULL) {
SvREFCNT_dec(client->weak_self_ref);
}
Safefree(client);
MODULE = AnyEvent::YACurl PACKAGE = AnyEvent::YACurl::Response
SV*
getinfo(self, option)
SV* self
SV* option
CODE:
dMY_CXT;
AnyEvent__YACurl__Response *response = sv_to_response(aTHX_ self);
int opt_from_str;
( run in 1.883 second using v1.01-cache-2.11-cpan-39bf76dae61 )