Net-HTTP2-nghttp2

 view release on metacpan or  search on metacpan

nghttp2.xs  view on Meta::CPAN

#define PERL_NO_GET_CONTEXT
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#include <nghttp2/nghttp2.h>
#include <string.h>

/*
 * Net::HTTP2::nghttp2 - Perl XS bindings for nghttp2
 *
 * This module provides server-side HTTP/2 support via nghttp2.
 */

/* Per-stream data provider state for streaming responses */
typedef struct {
    SV *callback;           /* Perl callback to produce data */
    SV *user_data;          /* User data for callback */
    int32_t stream_id;      /* Stream ID */
    int eof;                /* End of data flag */
    int deferred;           /* Currently deferred */
} nghttp2_perl_data_provider;

/* Session wrapper structure */
typedef struct {
    nghttp2_session *session;
    SV *user_data;
    SV *cb_on_begin_headers;
    SV *cb_on_header;
    SV *cb_on_frame_recv;
    SV *cb_on_data_chunk_recv;
    SV *cb_on_stream_close;
    SV *cb_send;
    SV *cb_data_source_read;
    /* Output buffer for mem_send */
    char *send_buf;
    size_t send_buf_len;
    size_t send_buf_cap;
    /* Data providers for active streams (simple linear array) */
    nghttp2_perl_data_provider **data_providers;
    int data_providers_count;
    int data_providers_cap;
} nghttp2_perl_session;

/* Forward declarations */
static ssize_t perl_send_callback(nghttp2_session *session,
                                  const uint8_t *data, size_t length,
                                  int flags, void *user_data);
static int perl_on_begin_headers_callback(nghttp2_session *session,
                                          const nghttp2_frame *frame,
                                          void *user_data);
static int perl_on_header_callback(nghttp2_session *session,
                                   const nghttp2_frame *frame,
                                   const uint8_t *name, size_t namelen,
                                   const uint8_t *value, size_t valuelen,
                                   uint8_t flags, void *user_data);
static int perl_on_frame_recv_callback(nghttp2_session *session,
                                       const nghttp2_frame *frame,
                                       void *user_data);
static int perl_on_data_chunk_recv_callback(nghttp2_session *session,
                                            uint8_t flags, int32_t stream_id,
                                            const uint8_t *data, size_t len,
                                            void *user_data);
static int perl_on_stream_close_callback(nghttp2_session *session,
                                         int32_t stream_id,
                                         uint32_t error_code,
                                         void *user_data);

/* Data provider helper functions */
static nghttp2_perl_data_provider *find_data_provider(nghttp2_perl_session *ps, int32_t stream_id) {
    int i;
    for (i = 0; i < ps->data_providers_count; i++) {
        if (ps->data_providers[i] && ps->data_providers[i]->stream_id == stream_id) {
            return ps->data_providers[i];
        }

nghttp2.xs  view on Meta::CPAN

        rv = nghttp2_session_resume_data(ps->session, stream_id);
        if (rv != 0 && rv != NGHTTP2_ERR_INVALID_ARGUMENT) {
            croak("nghttp2_session_resume_data failed: %s", nghttp2_strerror(rv));
        }
        RETVAL = rv;
    OUTPUT:
        RETVAL

# Get stream user data
SV *
get_stream_user_data(self, stream_id)
        SV *self
        int stream_id
    PREINIT:
        nghttp2_perl_session *ps;
        void *data;
    CODE:
        ps = (nghttp2_perl_session *)SvIV(SvRV(self));
        data = nghttp2_session_get_stream_user_data(ps->session, stream_id);
        if (data) {
            RETVAL = newSVsv((SV *)data);
        } else {
            RETVAL = &PL_sv_undef;
        }
    OUTPUT:
        RETVAL

# Set stream user data
int
set_stream_user_data(self, stream_id, data)
        SV *self
        int stream_id
        SV *data
    PREINIT:
        nghttp2_perl_session *ps;
        int rv;
    CODE:
        ps = (nghttp2_perl_session *)SvIV(SvRV(self));
        /* Note: caller must ensure data SV survives */
        rv = nghttp2_session_set_stream_user_data(ps->session, stream_id,
                                                   SvOK(data) ? newSVsv(data) : NULL);
        RETVAL = rv;
    OUTPUT:
        RETVAL

# Terminate session with GOAWAY
int
terminate_session(self, error_code)
        SV *self
        int error_code
    PREINIT:
        nghttp2_perl_session *ps;
        int rv;
    CODE:
        ps = (nghttp2_perl_session *)SvIV(SvRV(self));
        rv = nghttp2_session_terminate_session(ps->session, error_code);
        RETVAL = rv;
    OUTPUT:
        RETVAL

# Submit response with streaming data callback
# Callback receives ($stream_id, $max_length, $user_data) and returns ($data, $eof)
# Return undef or empty list to defer (call resume_data later)
int
_submit_response_streaming(self, stream_id, headers_av, data_callback, cb_user_data)
        SV *self
        int stream_id
        AV *headers_av
        SV *data_callback
        SV *cb_user_data
    PREINIT:
        nghttp2_perl_session *ps;
        nghttp2_nv *nva;
        size_t nvlen;
        nghttp2_data_provider data_prd;
        nghttp2_perl_data_provider *dp;
        int rv;
        I32 i;
    CODE:
        ps = (nghttp2_perl_session *)SvIV(SvRV(self));

        /* Build name-value array from Perl array of arrayrefs */
        nvlen = av_len(headers_av) + 1;
        Newxz(nva, nvlen, nghttp2_nv);

        for (i = 0; i < (I32)nvlen; i++) {
            SV **pair = av_fetch(headers_av, i, 0);
            if (pair && SvROK(*pair) && SvTYPE(SvRV(*pair)) == SVt_PVAV) {
                AV *pair_av = (AV *)SvRV(*pair);
                SV **name_sv = av_fetch(pair_av, 0, 0);
                SV **value_sv = av_fetch(pair_av, 1, 0);

                if (name_sv && value_sv) {
                    STRLEN name_len, value_len;
                    nva[i].name = (uint8_t *)SvPVbyte(*name_sv, name_len);
                    nva[i].namelen = name_len;
                    nva[i].value = (uint8_t *)SvPVbyte(*value_sv, value_len);
                    nva[i].valuelen = value_len;
                    nva[i].flags = NGHTTP2_NV_FLAG_NONE;
                }
            }
        }

        /* Create data provider state */
        Newxz(dp, 1, nghttp2_perl_data_provider);
        dp->stream_id = stream_id;
        dp->callback = newSVsv(data_callback);
        if (SvOK(cb_user_data)) {
            dp->user_data = newSVsv(cb_user_data);
        }
        dp->eof = 0;
        dp->deferred = 0;

        /* Track the data provider */
        add_data_provider(ps, dp);

        /* Set up nghttp2 data provider */
        data_prd.source.ptr = dp;
        data_prd.read_callback = perl_data_source_read_callback;

        rv = nghttp2_submit_response(ps->session, stream_id, nva, nvlen, &data_prd);

        Safefree(nva);

        if (rv != 0) {
            remove_data_provider(ps, stream_id);
            croak("nghttp2_submit_response failed: %s", nghttp2_strerror(rv));
        }
        RETVAL = rv;
    OUTPUT:
        RETVAL

# Queue data to send on an existing stream.
# The stream must already have a data provider (from submit_request or
# submit_response with a streaming body callback).  This sets the data
# provider's user_data to the given data and eof flag, clears the deferred
# state, and calls nghttp2_session_resume_data so the next mem_send will
# invoke the read callback which returns this data.
int
submit_data(self, stream_id, data_sv, eof)
        SV *self
        int stream_id
        SV *data_sv
        int eof
    PREINIT:
        nghttp2_perl_session *ps;
        nghttp2_perl_data_provider *dp;
        int rv;
    CODE:
        ps = (nghttp2_perl_session *)SvIV(SvRV(self));

        dp = find_data_provider(ps, stream_id);
        if (!dp) {
            croak("submit_data: no data provider for stream %d "
                  "(submit_request or submit_response with body callback first)",
                  stream_id);
        }

        /* Replace the callback with NULL (one-shot static body mode) and
           store the data in user_data for the read callback to pick up */
        if (dp->callback) {
            SvREFCNT_dec(dp->callback);
            dp->callback = NULL;
        }
        if (dp->user_data) {
            SvREFCNT_dec(dp->user_data);
        }
        dp->user_data = SvOK(data_sv) ? newSVsv(data_sv) : NULL;
        dp->eof = eof ? 1 : 0;
        dp->deferred = 0;

        /* Resume the stream so nghttp2 calls the read callback */
        rv = nghttp2_session_resume_data(ps->session, stream_id);
        if (rv != 0 && rv != NGHTTP2_ERR_INVALID_ARGUMENT) {
            croak("submit_data: resume failed: %s", nghttp2_strerror(rv));
        }
        RETVAL = 0;
    OUTPUT:
        RETVAL

# Check if stream is deferred (waiting for data)
int
is_stream_deferred(self, stream_id)
        SV *self
        int stream_id
    PREINIT:
        nghttp2_perl_session *ps;
        nghttp2_perl_data_provider *dp;
    CODE:
        ps = (nghttp2_perl_session *)SvIV(SvRV(self));
        dp = find_data_provider(ps, stream_id);
        RETVAL = dp ? dp->deferred : 0;
    OUTPUT:
        RETVAL

nghttp2.xs  view on Meta::CPAN

            if (ps->cb_on_header) SvREFCNT_dec(ps->cb_on_header);
            if (ps->cb_on_frame_recv) SvREFCNT_dec(ps->cb_on_frame_recv);
            if (ps->cb_on_data_chunk_recv) SvREFCNT_dec(ps->cb_on_data_chunk_recv);
            if (ps->cb_on_stream_close) SvREFCNT_dec(ps->cb_on_stream_close);
            free(ps->send_buf);
            Safefree(ps);
            croak("nghttp2_session_client_new failed: %s", nghttp2_strerror(rv));
        }

        /* Bless and return */
        RETVAL = sv_newmortal();
        sv_setref_pv(RETVAL, class, (void *)ps);
        SvREFCNT_inc(RETVAL);
    OUTPUT:
        RETVAL

# Submit request (client-side)
# Returns stream ID on success
int
_submit_request_xs(self, headers_av, body_sv)
        SV *self
        AV *headers_av
        SV *body_sv
    PREINIT:
        nghttp2_perl_session *ps;
        nghttp2_nv *nva;
        size_t nvlen;
        nghttp2_data_provider data_prd;
        nghttp2_data_provider *data_prd_ptr = NULL;
        nghttp2_perl_data_provider *dp = NULL;
        int32_t stream_id;
        I32 i;
        STRLEN body_len = 0;
    CODE:
        ps = (nghttp2_perl_session *)SvIV(SvRV(self));

        /* Build name-value array from Perl array of arrayrefs */
        nvlen = av_len(headers_av) + 1;
        Newxz(nva, nvlen, nghttp2_nv);

        for (i = 0; i < (I32)nvlen; i++) {
            SV **pair = av_fetch(headers_av, i, 0);
            if (pair && SvROK(*pair) && SvTYPE(SvRV(*pair)) == SVt_PVAV) {
                AV *pair_av = (AV *)SvRV(*pair);
                SV **name_sv = av_fetch(pair_av, 0, 0);
                SV **value_sv = av_fetch(pair_av, 1, 0);

                if (name_sv && value_sv) {
                    STRLEN name_len, value_len;
                    nva[i].name = (uint8_t *)SvPVbyte(*name_sv, name_len);
                    nva[i].namelen = name_len;
                    nva[i].value = (uint8_t *)SvPVbyte(*value_sv, value_len);
                    nva[i].valuelen = value_len;
                    nva[i].flags = NGHTTP2_NV_FLAG_NONE;
                }
            }
        }

        /* Check if we have a body to send */
        if (SvOK(body_sv) && SvROK(body_sv) && SvTYPE(SvRV(body_sv)) == SVt_PVCV) {
            /* CODE ref body: streaming callback data provider */
            Newxz(dp, 1, nghttp2_perl_data_provider);
            dp->stream_id = 0;  /* Will be set after submit */
            dp->eof = 0;
            dp->deferred = 0;
            dp->callback = newSVsv(body_sv);

            data_prd.source.ptr = dp;
            data_prd.read_callback = perl_data_source_read_callback;
            data_prd_ptr = &data_prd;
        }
        else if (SvOK(body_sv) && SvPOK(body_sv)) {
            const char *body_ptr = SvPVbyte(body_sv, body_len);
            if (body_len > 0) {
                /* Static string body: one-shot data provider */
                Newxz(dp, 1, nghttp2_perl_data_provider);
                dp->stream_id = 0;  /* Will be set after submit */
                dp->eof = 1;       /* Static bodies always want EOF after sending */
                dp->deferred = 0;
                dp->user_data = newSVsv(body_sv);
                dp->callback = NULL;  /* Use user_data as body */

                data_prd.source.ptr = dp;
                data_prd.read_callback = perl_data_source_read_callback;
                data_prd_ptr = &data_prd;
            }
        }

        stream_id = nghttp2_submit_request(ps->session, NULL, nva, nvlen, data_prd_ptr, NULL);

        Safefree(nva);

        if (stream_id < 0) {
            if (dp) {
                if (dp->callback) SvREFCNT_dec(dp->callback);
                if (dp->user_data) SvREFCNT_dec(dp->user_data);
                Safefree(dp);
            }
            croak("nghttp2_submit_request failed: %s", nghttp2_strerror(stream_id));
        }

        /* Track data provider if we have one */
        if (dp) {
            dp->stream_id = stream_id;
            add_data_provider(ps, dp);
        }

        RETVAL = stream_id;
    OUTPUT:
        RETVAL

# Submit RST_STREAM (reset a stream)
int
submit_rst_stream(self, stream_id, error_code)
        SV *self
        int stream_id
        unsigned int error_code
    PREINIT:
        nghttp2_perl_session *ps;
        int rv;
    CODE:



( run in 0.967 second using v1.01-cache-2.11-cpan-140bd7fdf52 )