AnyEvent-YACurl

 view release on metacpan or  search on metacpan

YACurl.xs  view on Meta::CPAN

#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)) {

YACurl.xs  view on Meta::CPAN

                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]);
                        }

YACurl.xs  view on Meta::CPAN

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 )