Net-HTTP2-nghttp2
view release on metacpan or search on metacpan
#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];
}
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
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 )