@gjsify/http2-native 0.4.0 → 0.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/types/index.d.ts +2 -0
- package/lib/types/session-bridge.gjs.spec.d.ts +1 -0
- package/package.json +60 -55
- package/prebuilds/linux-aarch64/GjsifyHttp2-1.0.gir +419 -0
- package/prebuilds/linux-aarch64/GjsifyHttp2-1.0.typelib +0 -0
- package/prebuilds/linux-aarch64/libgjsifyhttp2.so +0 -0
- package/prebuilds/linux-ppc64/GjsifyHttp2-1.0.gir +419 -0
- package/prebuilds/linux-ppc64/GjsifyHttp2-1.0.typelib +0 -0
- package/prebuilds/linux-ppc64/libgjsifyhttp2.so +0 -0
- package/prebuilds/linux-riscv64/GjsifyHttp2-1.0.gir +419 -0
- package/prebuilds/linux-riscv64/GjsifyHttp2-1.0.typelib +0 -0
- package/prebuilds/linux-riscv64/libgjsifyhttp2.so +0 -0
- package/prebuilds/linux-s390x/GjsifyHttp2-1.0.gir +419 -0
- package/prebuilds/linux-s390x/GjsifyHttp2-1.0.typelib +0 -0
- package/prebuilds/linux-s390x/libgjsifyhttp2.so +0 -0
- package/prebuilds/linux-x86_64/GjsifyHttp2-1.0.gir +419 -0
- package/prebuilds/linux-x86_64/GjsifyHttp2-1.0.typelib +0 -0
- package/prebuilds/linux-x86_64/libgjsifyhttp2.so +0 -0
- package/src/vala/frame-encoder.vala +241 -0
- package/src/vala/nghttp2-helpers.c +842 -0
- package/src/vala/nghttp2-helpers.h +327 -0
- package/src/vala/session-bridge.vala +468 -0
- package/src/vala/stream-id-allocator.vala +62 -0
|
@@ -0,0 +1,842 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Tiny C shim around libnghttp2's HPACK encoder + frame helpers + session API.
|
|
3
|
+
* See nghttp2-helpers.h for the rationale + per-function docs.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
#include "nghttp2-helpers.h"
|
|
7
|
+
|
|
8
|
+
#include <string.h>
|
|
9
|
+
#include <stdlib.h>
|
|
10
|
+
|
|
11
|
+
/* ────────────────────────────────────────────────────────────────────── *
|
|
12
|
+
* HPACK + standalone frame builders *
|
|
13
|
+
* ────────────────────────────────────────────────────────────────────── */
|
|
14
|
+
|
|
15
|
+
GBytes *
|
|
16
|
+
gjsify_http2_hpack_encode (char **names,
|
|
17
|
+
char **values,
|
|
18
|
+
gsize n_pairs)
|
|
19
|
+
{
|
|
20
|
+
if (n_pairs == 0) {
|
|
21
|
+
return g_bytes_new (NULL, 0);
|
|
22
|
+
}
|
|
23
|
+
if (names == NULL || values == NULL) {
|
|
24
|
+
return NULL;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
nghttp2_nv *nva = g_new0 (nghttp2_nv, n_pairs);
|
|
28
|
+
for (gsize i = 0; i < n_pairs; i++) {
|
|
29
|
+
const char *n = names[i] ? names[i] : "";
|
|
30
|
+
const char *v = values[i] ? values[i] : "";
|
|
31
|
+
nva[i].name = (uint8_t *) n;
|
|
32
|
+
nva[i].value = (uint8_t *) v;
|
|
33
|
+
nva[i].namelen = strlen (n);
|
|
34
|
+
nva[i].valuelen = strlen (v);
|
|
35
|
+
nva[i].flags = NGHTTP2_NV_FLAG_NONE;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
nghttp2_hd_deflater *def = NULL;
|
|
39
|
+
int rv = nghttp2_hd_deflate_new (&def, 4096);
|
|
40
|
+
if (rv != 0 || def == NULL) {
|
|
41
|
+
g_free (nva);
|
|
42
|
+
return NULL;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
size_t bound = nghttp2_hd_deflate_bound (def, nva, n_pairs);
|
|
46
|
+
guint8 *buf = g_malloc (bound);
|
|
47
|
+
ssize_t produced = nghttp2_hd_deflate_hd (def, buf, bound, nva, n_pairs);
|
|
48
|
+
|
|
49
|
+
nghttp2_hd_deflate_del (def);
|
|
50
|
+
g_free (nva);
|
|
51
|
+
|
|
52
|
+
if (produced < 0) {
|
|
53
|
+
g_free (buf);
|
|
54
|
+
return NULL;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* GBytes takes ownership of buf via g_free destroy notify. */
|
|
58
|
+
return g_bytes_new_with_free_func (buf, (gsize) produced, g_free, buf);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
GBytes *
|
|
62
|
+
gjsify_http2_pack_frame (guint8 type,
|
|
63
|
+
guint8 flags,
|
|
64
|
+
guint32 stream_id,
|
|
65
|
+
GBytes *payload)
|
|
66
|
+
{
|
|
67
|
+
gsize plen = 0;
|
|
68
|
+
const guint8 *pdata = payload ? g_bytes_get_data (payload, &plen) : NULL;
|
|
69
|
+
|
|
70
|
+
/* Frame header is 9 bytes:
|
|
71
|
+
* length : 24-bit big-endian
|
|
72
|
+
* type : 8-bit
|
|
73
|
+
* flags : 8-bit
|
|
74
|
+
* stream_id : 31-bit big-endian (high bit reserved, must be 0)
|
|
75
|
+
*/
|
|
76
|
+
gsize total = 9 + plen;
|
|
77
|
+
guint8 *buf = g_malloc (total);
|
|
78
|
+
|
|
79
|
+
buf[0] = (guint8) ((plen >> 16) & 0xff);
|
|
80
|
+
buf[1] = (guint8) ((plen >> 8) & 0xff);
|
|
81
|
+
buf[2] = (guint8) ((plen ) & 0xff);
|
|
82
|
+
buf[3] = type;
|
|
83
|
+
buf[4] = flags;
|
|
84
|
+
|
|
85
|
+
guint32 sid = stream_id & 0x7fffffffu;
|
|
86
|
+
buf[5] = (guint8) ((sid >> 24) & 0xff);
|
|
87
|
+
buf[6] = (guint8) ((sid >> 16) & 0xff);
|
|
88
|
+
buf[7] = (guint8) ((sid >> 8) & 0xff);
|
|
89
|
+
buf[8] = (guint8) ((sid ) & 0xff);
|
|
90
|
+
|
|
91
|
+
if (plen > 0 && pdata != NULL) {
|
|
92
|
+
memcpy (buf + 9, pdata, plen);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return g_bytes_new_with_free_func (buf, total, g_free, buf);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
GBytes *
|
|
99
|
+
gjsify_http2_pack_push_promise (guint32 associated_stream_id,
|
|
100
|
+
guint32 promised_stream_id,
|
|
101
|
+
GBytes *header_block)
|
|
102
|
+
{
|
|
103
|
+
gsize hlen = 0;
|
|
104
|
+
const guint8 *hdata = header_block ? g_bytes_get_data (header_block, &hlen) : NULL;
|
|
105
|
+
|
|
106
|
+
/* PUSH_PROMISE payload = 4-byte promised-stream-id (R + 31-bit id)
|
|
107
|
+
* || HPACK header block. */
|
|
108
|
+
gsize plen = 4 + hlen;
|
|
109
|
+
guint8 *payload = g_malloc (plen);
|
|
110
|
+
|
|
111
|
+
guint32 pid = promised_stream_id & 0x7fffffffu;
|
|
112
|
+
payload[0] = (guint8) ((pid >> 24) & 0xff);
|
|
113
|
+
payload[1] = (guint8) ((pid >> 16) & 0xff);
|
|
114
|
+
payload[2] = (guint8) ((pid >> 8) & 0xff);
|
|
115
|
+
payload[3] = (guint8) ((pid ) & 0xff);
|
|
116
|
+
|
|
117
|
+
if (hlen > 0 && hdata != NULL) {
|
|
118
|
+
memcpy (payload + 4, hdata, hlen);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
GBytes *payload_bytes = g_bytes_new_with_free_func (payload, plen, g_free, payload);
|
|
122
|
+
GBytes *frame = gjsify_http2_pack_frame (
|
|
123
|
+
NGHTTP2_PUSH_PROMISE,
|
|
124
|
+
NGHTTP2_FLAG_END_HEADERS,
|
|
125
|
+
associated_stream_id,
|
|
126
|
+
payload_bytes
|
|
127
|
+
);
|
|
128
|
+
g_bytes_unref (payload_bytes);
|
|
129
|
+
return frame;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const char *
|
|
133
|
+
gjsify_http2_nghttp2_version (void)
|
|
134
|
+
{
|
|
135
|
+
nghttp2_info *info = nghttp2_version (0);
|
|
136
|
+
return info ? info->version_str : "unknown";
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/* ────────────────────────────────────────────────────────────────────── *
|
|
140
|
+
* Session API *
|
|
141
|
+
* ────────────────────────────────────────────────────────────────────── */
|
|
142
|
+
|
|
143
|
+
/* Full struct layout for the opaque GjsifyHttp2Event (forward-declared
|
|
144
|
+
* in nghttp2-helpers.h). Vala emits its own forward declaration with
|
|
145
|
+
* the matching tag (`struct _GjsifyHttp2Event`) and never tries to
|
|
146
|
+
* inspect the fields — every access goes through the getter functions
|
|
147
|
+
* below. */
|
|
148
|
+
struct _GjsifyHttp2Event {
|
|
149
|
+
GjsifyHttp2EventKind kind;
|
|
150
|
+
guint32 stream_id;
|
|
151
|
+
guint32 error_code;
|
|
152
|
+
guint32 last_stream_id;
|
|
153
|
+
gboolean end_stream;
|
|
154
|
+
char **headers_names;
|
|
155
|
+
char **headers_values;
|
|
156
|
+
gsize headers_count;
|
|
157
|
+
GBytes *data;
|
|
158
|
+
guint32 promised_stream_id;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
/* Per-stream HEADERS accumulator. We hold names/values in two GPtrArrays
|
|
162
|
+
* (auto-NULL-terminated, plain `char*` ownership) until the HEADERS frame
|
|
163
|
+
* finishes (on_frame_recv with type==HEADERS) — only then do we emit the
|
|
164
|
+
* EVENT_HEADERS event so JS sees the complete pseudo-header set in one
|
|
165
|
+
* call. Without this, partial header arrival could leak through on_header
|
|
166
|
+
* if nghttp2 ever spreads HEADERS+CONTINUATION across multiple recv()s. */
|
|
167
|
+
typedef struct {
|
|
168
|
+
GPtrArray *names; /* owns each entry via g_free */
|
|
169
|
+
GPtrArray *values; /* owns each entry via g_free */
|
|
170
|
+
} StreamHeaderAccum;
|
|
171
|
+
|
|
172
|
+
static StreamHeaderAccum *
|
|
173
|
+
stream_header_accum_new (void)
|
|
174
|
+
{
|
|
175
|
+
StreamHeaderAccum *a = g_new0 (StreamHeaderAccum, 1);
|
|
176
|
+
a->names = g_ptr_array_new_with_free_func (g_free);
|
|
177
|
+
a->values = g_ptr_array_new_with_free_func (g_free);
|
|
178
|
+
return a;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
static void
|
|
182
|
+
stream_header_accum_free (gpointer p)
|
|
183
|
+
{
|
|
184
|
+
StreamHeaderAccum *a = p;
|
|
185
|
+
if (!a) return;
|
|
186
|
+
g_ptr_array_unref (a->names);
|
|
187
|
+
g_ptr_array_unref (a->values);
|
|
188
|
+
g_free (a);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/* Outgoing-DATA carrier — backs nghttp2_data_provider.
|
|
192
|
+
* We hold a GBytes per pending DATA submit and feed it through the
|
|
193
|
+
* data_source_read_callback. The carrier is destroyed once nghttp2
|
|
194
|
+
* signals the frame's full transmission via on_frame_send. */
|
|
195
|
+
typedef struct {
|
|
196
|
+
GBytes *bytes;
|
|
197
|
+
gsize offset;
|
|
198
|
+
gboolean end_stream;
|
|
199
|
+
} DataSourceCarrier;
|
|
200
|
+
|
|
201
|
+
static DataSourceCarrier *
|
|
202
|
+
data_source_carrier_new (GBytes *bytes, gboolean end_stream)
|
|
203
|
+
{
|
|
204
|
+
DataSourceCarrier *c = g_new0 (DataSourceCarrier, 1);
|
|
205
|
+
c->bytes = bytes ? g_bytes_ref (bytes) : g_bytes_new (NULL, 0);
|
|
206
|
+
c->offset = 0;
|
|
207
|
+
c->end_stream = end_stream;
|
|
208
|
+
return c;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
static void
|
|
212
|
+
data_source_carrier_free (gpointer p)
|
|
213
|
+
{
|
|
214
|
+
DataSourceCarrier *c = p;
|
|
215
|
+
if (!c) return;
|
|
216
|
+
if (c->bytes) g_bytes_unref (c->bytes);
|
|
217
|
+
g_free (c);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
struct _GjsifyHttp2Session {
|
|
221
|
+
nghttp2_session *session;
|
|
222
|
+
GjsifyHttp2SessionMode mode;
|
|
223
|
+
|
|
224
|
+
/* stream_id (gpointer key) -> StreamHeaderAccum */
|
|
225
|
+
GHashTable *header_accumulators;
|
|
226
|
+
|
|
227
|
+
/* stream_id (gpointer key) -> DataSourceCarrier (only while a DATA
|
|
228
|
+
* provider is active for that stream). */
|
|
229
|
+
GHashTable *data_carriers;
|
|
230
|
+
|
|
231
|
+
/* GQueue<GjsifyHttp2Event*> — drained on demand. */
|
|
232
|
+
GQueue *events;
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
/* ── helpers ─────────────────────────────────────────────────────────── */
|
|
236
|
+
|
|
237
|
+
static void
|
|
238
|
+
push_event (GjsifyHttp2Session *self, GjsifyHttp2Event *ev)
|
|
239
|
+
{
|
|
240
|
+
g_queue_push_tail (self->events, ev);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
static StreamHeaderAccum *
|
|
244
|
+
accum_get_or_create (GjsifyHttp2Session *self, guint32 stream_id)
|
|
245
|
+
{
|
|
246
|
+
StreamHeaderAccum *a = g_hash_table_lookup (self->header_accumulators,
|
|
247
|
+
GUINT_TO_POINTER (stream_id));
|
|
248
|
+
if (!a) {
|
|
249
|
+
a = stream_header_accum_new ();
|
|
250
|
+
g_hash_table_insert (self->header_accumulators,
|
|
251
|
+
GUINT_TO_POINTER (stream_id), a);
|
|
252
|
+
}
|
|
253
|
+
return a;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/* Move accumulated headers into a freshly-allocated NULL-terminated
|
|
257
|
+
* C-string array. Caller owns the returned array AND each string
|
|
258
|
+
* (g_strfreev frees both correctly). */
|
|
259
|
+
static void
|
|
260
|
+
accum_take_headers (StreamHeaderAccum *a,
|
|
261
|
+
char ***out_names,
|
|
262
|
+
char ***out_values,
|
|
263
|
+
gsize *out_n)
|
|
264
|
+
{
|
|
265
|
+
gsize n = a->names->len;
|
|
266
|
+
char **names = g_new0 (char *, n + 1);
|
|
267
|
+
char **values = g_new0 (char *, n + 1);
|
|
268
|
+
for (gsize i = 0; i < n; i++) {
|
|
269
|
+
names[i] = g_strdup ((const char *) g_ptr_array_index (a->names, i));
|
|
270
|
+
values[i] = g_strdup ((const char *) g_ptr_array_index (a->values, i));
|
|
271
|
+
}
|
|
272
|
+
*out_names = names;
|
|
273
|
+
*out_values = values;
|
|
274
|
+
*out_n = n;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/* ── nghttp2 callbacks ───────────────────────────────────────────────── */
|
|
278
|
+
|
|
279
|
+
/* For HEADERS, the headers belong to frame->hd.stream_id. For PUSH_PROMISE,
|
|
280
|
+
* they describe the resource on the PROMISED stream id (the parent stream
|
|
281
|
+
* id is the request the push is associated with). Keep one accumulator per
|
|
282
|
+
* logical stream so PUSH_PROMISE-on-the-client surfaces a complete header
|
|
283
|
+
* set keyed by the promised id. */
|
|
284
|
+
static guint32
|
|
285
|
+
accum_key_for_frame (const nghttp2_frame *frame)
|
|
286
|
+
{
|
|
287
|
+
if (frame->hd.type == NGHTTP2_PUSH_PROMISE) {
|
|
288
|
+
return (guint32) frame->push_promise.promised_stream_id;
|
|
289
|
+
}
|
|
290
|
+
return (guint32) frame->hd.stream_id;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
static int
|
|
294
|
+
on_begin_headers_cb (nghttp2_session *session,
|
|
295
|
+
const nghttp2_frame *frame,
|
|
296
|
+
void *user_data)
|
|
297
|
+
{
|
|
298
|
+
GjsifyHttp2Session *self = user_data;
|
|
299
|
+
(void) session;
|
|
300
|
+
guint32 key = accum_key_for_frame (frame);
|
|
301
|
+
/* Reset/create accumulator for the logical stream. Closing+reopening
|
|
302
|
+
* the same id is illegal in HTTP/2, so destroy-then-create is safe. */
|
|
303
|
+
g_hash_table_remove (self->header_accumulators, GUINT_TO_POINTER (key));
|
|
304
|
+
accum_get_or_create (self, key);
|
|
305
|
+
return 0;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
static int
|
|
309
|
+
on_header_cb (nghttp2_session *session,
|
|
310
|
+
const nghttp2_frame *frame,
|
|
311
|
+
const uint8_t *name, size_t namelen,
|
|
312
|
+
const uint8_t *value, size_t valuelen,
|
|
313
|
+
uint8_t flags,
|
|
314
|
+
void *user_data)
|
|
315
|
+
{
|
|
316
|
+
GjsifyHttp2Session *self = user_data;
|
|
317
|
+
(void) session;
|
|
318
|
+
(void) flags;
|
|
319
|
+
guint32 key = accum_key_for_frame (frame);
|
|
320
|
+
StreamHeaderAccum *a = accum_get_or_create (self, key);
|
|
321
|
+
/* g_ptr_array owns via g_free — store NUL-terminated copies. */
|
|
322
|
+
g_ptr_array_add (a->names, g_strndup ((const char *) name, namelen));
|
|
323
|
+
g_ptr_array_add (a->values, g_strndup ((const char *) value, valuelen));
|
|
324
|
+
return 0;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
static int
|
|
328
|
+
on_frame_recv_cb (nghttp2_session *session,
|
|
329
|
+
const nghttp2_frame *frame,
|
|
330
|
+
void *user_data)
|
|
331
|
+
{
|
|
332
|
+
GjsifyHttp2Session *self = user_data;
|
|
333
|
+
(void) session;
|
|
334
|
+
switch (frame->hd.type) {
|
|
335
|
+
case NGHTTP2_HEADERS: {
|
|
336
|
+
StreamHeaderAccum *a = g_hash_table_lookup (self->header_accumulators,
|
|
337
|
+
GUINT_TO_POINTER (frame->hd.stream_id));
|
|
338
|
+
if (!a) return 0;
|
|
339
|
+
GjsifyHttp2Event *ev = g_new0 (GjsifyHttp2Event, 1);
|
|
340
|
+
ev->kind = GJSIFY_HTTP2_EVENT_HEADERS;
|
|
341
|
+
ev->stream_id = frame->hd.stream_id;
|
|
342
|
+
ev->end_stream = (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) != 0;
|
|
343
|
+
accum_take_headers (a, &ev->headers_names, &ev->headers_values, &ev->headers_count);
|
|
344
|
+
/* Once consumed, drop the accumulator. */
|
|
345
|
+
g_hash_table_remove (self->header_accumulators,
|
|
346
|
+
GUINT_TO_POINTER (frame->hd.stream_id));
|
|
347
|
+
push_event (self, ev);
|
|
348
|
+
break;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
case NGHTTP2_DATA: {
|
|
352
|
+
/* DATA chunks are surfaced via on_data_chunk_recv; here we only
|
|
353
|
+
* need to notice END_STREAM on a frame carrying no chunks. */
|
|
354
|
+
if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
|
|
355
|
+
GjsifyHttp2Event *ev = g_new0 (GjsifyHttp2Event, 1);
|
|
356
|
+
ev->kind = GJSIFY_HTTP2_EVENT_DATA;
|
|
357
|
+
ev->stream_id = frame->hd.stream_id;
|
|
358
|
+
ev->end_stream = TRUE;
|
|
359
|
+
ev->data = g_bytes_new (NULL, 0);
|
|
360
|
+
push_event (self, ev);
|
|
361
|
+
}
|
|
362
|
+
break;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
case NGHTTP2_SETTINGS: {
|
|
366
|
+
if (!(frame->hd.flags & NGHTTP2_FLAG_ACK)) {
|
|
367
|
+
GjsifyHttp2Event *ev = g_new0 (GjsifyHttp2Event, 1);
|
|
368
|
+
ev->kind = GJSIFY_HTTP2_EVENT_SETTINGS;
|
|
369
|
+
push_event (self, ev);
|
|
370
|
+
}
|
|
371
|
+
break;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
case NGHTTP2_GOAWAY: {
|
|
375
|
+
GjsifyHttp2Event *ev = g_new0 (GjsifyHttp2Event, 1);
|
|
376
|
+
ev->kind = GJSIFY_HTTP2_EVENT_GOAWAY;
|
|
377
|
+
ev->error_code = frame->goaway.error_code;
|
|
378
|
+
ev->last_stream_id = frame->goaway.last_stream_id;
|
|
379
|
+
push_event (self, ev);
|
|
380
|
+
break;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
case NGHTTP2_PUSH_PROMISE: {
|
|
384
|
+
/* Client-side: a push promise arrived. Headers were already
|
|
385
|
+
* accumulated by the begin/on_header callbacks for the PROMISED
|
|
386
|
+
* stream id (frame->push_promise.promised_stream_id). */
|
|
387
|
+
guint32 promised = frame->push_promise.promised_stream_id;
|
|
388
|
+
StreamHeaderAccum *a = g_hash_table_lookup (self->header_accumulators,
|
|
389
|
+
GUINT_TO_POINTER (promised));
|
|
390
|
+
if (a) {
|
|
391
|
+
GjsifyHttp2Event *ev = g_new0 (GjsifyHttp2Event, 1);
|
|
392
|
+
ev->kind = GJSIFY_HTTP2_EVENT_PUSH_PROMISE;
|
|
393
|
+
ev->stream_id = frame->hd.stream_id;
|
|
394
|
+
ev->promised_stream_id = promised;
|
|
395
|
+
accum_take_headers (a, &ev->headers_names, &ev->headers_values, &ev->headers_count);
|
|
396
|
+
g_hash_table_remove (self->header_accumulators,
|
|
397
|
+
GUINT_TO_POINTER (promised));
|
|
398
|
+
push_event (self, ev);
|
|
399
|
+
}
|
|
400
|
+
break;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
default:
|
|
404
|
+
break;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
return 0;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
static int
|
|
411
|
+
on_data_chunk_recv_cb (nghttp2_session *session,
|
|
412
|
+
uint8_t flags,
|
|
413
|
+
int32_t stream_id,
|
|
414
|
+
const uint8_t *data, size_t len,
|
|
415
|
+
void *user_data)
|
|
416
|
+
{
|
|
417
|
+
GjsifyHttp2Session *self = user_data;
|
|
418
|
+
(void) session;
|
|
419
|
+
(void) flags;
|
|
420
|
+
|
|
421
|
+
GjsifyHttp2Event *ev = g_new0 (GjsifyHttp2Event, 1);
|
|
422
|
+
ev->kind = GJSIFY_HTTP2_EVENT_DATA;
|
|
423
|
+
ev->stream_id = (guint32) stream_id;
|
|
424
|
+
ev->end_stream = FALSE; /* END_STREAM is reported via on_frame_recv */
|
|
425
|
+
ev->data = g_bytes_new (data, len);
|
|
426
|
+
push_event (self, ev);
|
|
427
|
+
return 0;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
static int
|
|
431
|
+
on_stream_close_cb (nghttp2_session *session,
|
|
432
|
+
int32_t stream_id,
|
|
433
|
+
uint32_t error_code,
|
|
434
|
+
void *user_data)
|
|
435
|
+
{
|
|
436
|
+
GjsifyHttp2Session *self = user_data;
|
|
437
|
+
(void) session;
|
|
438
|
+
|
|
439
|
+
GjsifyHttp2Event *ev = g_new0 (GjsifyHttp2Event, 1);
|
|
440
|
+
ev->kind = GJSIFY_HTTP2_EVENT_STREAM_CLOSED;
|
|
441
|
+
ev->stream_id = (guint32) stream_id;
|
|
442
|
+
ev->error_code = error_code;
|
|
443
|
+
push_event (self, ev);
|
|
444
|
+
|
|
445
|
+
/* Drop any leftover state for this stream. */
|
|
446
|
+
g_hash_table_remove (self->header_accumulators,
|
|
447
|
+
GUINT_TO_POINTER ((guint32) stream_id));
|
|
448
|
+
g_hash_table_remove (self->data_carriers,
|
|
449
|
+
GUINT_TO_POINTER ((guint32) stream_id));
|
|
450
|
+
return 0;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/* DATA provider — feeds bytes from the carrier we attached for this
|
|
454
|
+
* stream id. We look the carrier up by stream_id in self->data_carriers
|
|
455
|
+
* (not via source.ptr) so submit_data() can replace the carrier mid-
|
|
456
|
+
* stream without touching nghttp2's data_provider struct. */
|
|
457
|
+
static ssize_t
|
|
458
|
+
data_source_read_cb (nghttp2_session *session,
|
|
459
|
+
int32_t stream_id,
|
|
460
|
+
uint8_t *buf, size_t length,
|
|
461
|
+
uint32_t *data_flags,
|
|
462
|
+
nghttp2_data_source *source,
|
|
463
|
+
void *user_data)
|
|
464
|
+
{
|
|
465
|
+
(void) session;
|
|
466
|
+
(void) source;
|
|
467
|
+
GjsifyHttp2Session *self = user_data;
|
|
468
|
+
|
|
469
|
+
DataSourceCarrier *c = g_hash_table_lookup (self->data_carriers,
|
|
470
|
+
GUINT_TO_POINTER ((guint32) stream_id));
|
|
471
|
+
if (!c || !c->bytes) {
|
|
472
|
+
/* No data queued yet — defer. nghttp2 will pause this stream's
|
|
473
|
+
* DATA emission and resume on nghttp2_session_resume_data(). */
|
|
474
|
+
return NGHTTP2_ERR_DEFERRED;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
gsize total = 0;
|
|
478
|
+
const guint8 *base = g_bytes_get_data (c->bytes, &total);
|
|
479
|
+
gsize remaining = total - c->offset;
|
|
480
|
+
gsize chunk = remaining < length ? remaining : length;
|
|
481
|
+
if (chunk > 0) memcpy (buf, base + c->offset, chunk);
|
|
482
|
+
c->offset += chunk;
|
|
483
|
+
|
|
484
|
+
if (c->offset >= total) {
|
|
485
|
+
if (c->end_stream) {
|
|
486
|
+
*data_flags |= NGHTTP2_DATA_FLAG_EOF;
|
|
487
|
+
} else {
|
|
488
|
+
/* Buffer drained but not the last chunk — defer until the
|
|
489
|
+
* next submit_data() replaces the carrier. */
|
|
490
|
+
return chunk > 0 ? (ssize_t) chunk : NGHTTP2_ERR_DEFERRED;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
return (ssize_t) chunk;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/* When nghttp2 finishes transmitting a DATA frame whose source is one of
|
|
497
|
+
* our carriers AND it was the last (EOF reached), drop the carrier. */
|
|
498
|
+
static int
|
|
499
|
+
on_frame_send_cb (nghttp2_session *session,
|
|
500
|
+
const nghttp2_frame *frame,
|
|
501
|
+
void *user_data)
|
|
502
|
+
{
|
|
503
|
+
GjsifyHttp2Session *self = user_data;
|
|
504
|
+
(void) session;
|
|
505
|
+
if (frame->hd.type != NGHTTP2_DATA) return 0;
|
|
506
|
+
DataSourceCarrier *c = g_hash_table_lookup (self->data_carriers,
|
|
507
|
+
GUINT_TO_POINTER (frame->hd.stream_id));
|
|
508
|
+
if (!c) return 0;
|
|
509
|
+
gsize total = 0;
|
|
510
|
+
g_bytes_get_data (c->bytes, &total);
|
|
511
|
+
if (c->offset >= total) {
|
|
512
|
+
g_hash_table_remove (self->data_carriers,
|
|
513
|
+
GUINT_TO_POINTER (frame->hd.stream_id));
|
|
514
|
+
}
|
|
515
|
+
return 0;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/* ── construction / destruction ──────────────────────────────────────── */
|
|
519
|
+
|
|
520
|
+
static int
|
|
521
|
+
configure_callbacks (nghttp2_session_callbacks *cb)
|
|
522
|
+
{
|
|
523
|
+
nghttp2_session_callbacks_set_on_frame_recv_callback (cb, on_frame_recv_cb);
|
|
524
|
+
nghttp2_session_callbacks_set_on_data_chunk_recv_callback (cb, on_data_chunk_recv_cb);
|
|
525
|
+
nghttp2_session_callbacks_set_on_stream_close_callback (cb, on_stream_close_cb);
|
|
526
|
+
nghttp2_session_callbacks_set_on_header_callback (cb, on_header_cb);
|
|
527
|
+
nghttp2_session_callbacks_set_on_begin_headers_callback (cb, on_begin_headers_cb);
|
|
528
|
+
nghttp2_session_callbacks_set_on_frame_send_callback (cb, on_frame_send_cb);
|
|
529
|
+
return 0;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
GjsifyHttp2Session *
|
|
533
|
+
gjsify_http2_session_new (GjsifyHttp2SessionMode mode)
|
|
534
|
+
{
|
|
535
|
+
GjsifyHttp2Session *self = g_new0 (GjsifyHttp2Session, 1);
|
|
536
|
+
self->mode = mode;
|
|
537
|
+
self->header_accumulators = g_hash_table_new_full (
|
|
538
|
+
g_direct_hash, g_direct_equal, NULL, stream_header_accum_free);
|
|
539
|
+
self->data_carriers = g_hash_table_new_full (
|
|
540
|
+
g_direct_hash, g_direct_equal, NULL, data_source_carrier_free);
|
|
541
|
+
self->events = g_queue_new ();
|
|
542
|
+
|
|
543
|
+
nghttp2_session_callbacks *cb = NULL;
|
|
544
|
+
if (nghttp2_session_callbacks_new (&cb) != 0 || cb == NULL) {
|
|
545
|
+
gjsify_http2_session_free (self);
|
|
546
|
+
return NULL;
|
|
547
|
+
}
|
|
548
|
+
configure_callbacks (cb);
|
|
549
|
+
|
|
550
|
+
int rv;
|
|
551
|
+
if (mode == GJSIFY_HTTP2_SESSION_MODE_SERVER) {
|
|
552
|
+
rv = nghttp2_session_server_new (&self->session, cb, self);
|
|
553
|
+
} else {
|
|
554
|
+
rv = nghttp2_session_client_new (&self->session, cb, self);
|
|
555
|
+
}
|
|
556
|
+
nghttp2_session_callbacks_del (cb);
|
|
557
|
+
|
|
558
|
+
if (rv != 0 || self->session == NULL) {
|
|
559
|
+
gjsify_http2_session_free (self);
|
|
560
|
+
return NULL;
|
|
561
|
+
}
|
|
562
|
+
return self;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
void
|
|
566
|
+
gjsify_http2_session_free (GjsifyHttp2Session *self)
|
|
567
|
+
{
|
|
568
|
+
if (!self) return;
|
|
569
|
+
if (self->session) nghttp2_session_del (self->session);
|
|
570
|
+
if (self->header_accumulators) g_hash_table_destroy (self->header_accumulators);
|
|
571
|
+
if (self->data_carriers) g_hash_table_destroy (self->data_carriers);
|
|
572
|
+
if (self->events) {
|
|
573
|
+
GjsifyHttp2Event *ev;
|
|
574
|
+
while ((ev = g_queue_pop_head (self->events)) != NULL) {
|
|
575
|
+
gjsify_http2_event_free (ev);
|
|
576
|
+
}
|
|
577
|
+
g_queue_free (self->events);
|
|
578
|
+
}
|
|
579
|
+
g_free (self);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
void
|
|
583
|
+
gjsify_http2_event_free (GjsifyHttp2Event *event)
|
|
584
|
+
{
|
|
585
|
+
if (!event) return;
|
|
586
|
+
if (event->headers_names) g_strfreev (event->headers_names);
|
|
587
|
+
if (event->headers_values) g_strfreev (event->headers_values);
|
|
588
|
+
if (event->data) g_bytes_unref (event->data);
|
|
589
|
+
g_free (event);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/* ── Event getters (see header for ownership rules) ─────────────────── */
|
|
593
|
+
|
|
594
|
+
guint32 gjsify_http2_event_get_kind (GjsifyHttp2Event *e) { return e ? (guint32) e->kind : 0; }
|
|
595
|
+
guint32 gjsify_http2_event_get_stream_id (GjsifyHttp2Event *e) { return e ? e->stream_id : 0; }
|
|
596
|
+
guint32 gjsify_http2_event_get_error_code (GjsifyHttp2Event *e) { return e ? e->error_code : 0; }
|
|
597
|
+
guint32 gjsify_http2_event_get_last_stream_id (GjsifyHttp2Event *e) { return e ? e->last_stream_id : 0; }
|
|
598
|
+
gboolean gjsify_http2_event_get_end_stream (GjsifyHttp2Event *e) { return e ? e->end_stream : FALSE; }
|
|
599
|
+
guint32 gjsify_http2_event_get_promised_stream_id (GjsifyHttp2Event *e) { return e ? e->promised_stream_id : 0; }
|
|
600
|
+
gsize gjsify_http2_event_get_header_count (GjsifyHttp2Event *e) { return e ? e->headers_count : 0; }
|
|
601
|
+
|
|
602
|
+
const char *
|
|
603
|
+
gjsify_http2_event_get_header_name (GjsifyHttp2Event *e, gsize index)
|
|
604
|
+
{
|
|
605
|
+
if (!e || !e->headers_names || index >= e->headers_count) return NULL;
|
|
606
|
+
return e->headers_names[index];
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
const char *
|
|
610
|
+
gjsify_http2_event_get_header_value (GjsifyHttp2Event *e, gsize index)
|
|
611
|
+
{
|
|
612
|
+
if (!e || !e->headers_values || index >= e->headers_count) return NULL;
|
|
613
|
+
return e->headers_values[index];
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
GBytes *
|
|
617
|
+
gjsify_http2_event_get_data (GjsifyHttp2Event *e)
|
|
618
|
+
{
|
|
619
|
+
return e ? e->data : NULL;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/* ── I/O ─────────────────────────────────────────────────────────────── */
|
|
623
|
+
|
|
624
|
+
gssize
|
|
625
|
+
gjsify_http2_session_feed (GjsifyHttp2Session *self, GBytes *input)
|
|
626
|
+
{
|
|
627
|
+
if (!self || !self->session) return -1;
|
|
628
|
+
gsize len = 0;
|
|
629
|
+
const guint8 *data = input ? g_bytes_get_data (input, &len) : NULL;
|
|
630
|
+
if (len == 0) return 0;
|
|
631
|
+
ssize_t rv = nghttp2_session_mem_recv (self->session, data, len);
|
|
632
|
+
return (gssize) rv;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
GBytes *
|
|
636
|
+
gjsify_http2_session_drain_output (GjsifyHttp2Session *self)
|
|
637
|
+
{
|
|
638
|
+
if (!self || !self->session) return g_bytes_new (NULL, 0);
|
|
639
|
+
|
|
640
|
+
GByteArray *accum = g_byte_array_new ();
|
|
641
|
+
const uint8_t *chunk = NULL;
|
|
642
|
+
ssize_t rv;
|
|
643
|
+
while ((rv = nghttp2_session_mem_send (self->session, &chunk)) > 0) {
|
|
644
|
+
g_byte_array_append (accum, chunk, (guint) rv);
|
|
645
|
+
}
|
|
646
|
+
/* rv < 0 indicates an error; we still return whatever we collected
|
|
647
|
+
* so the caller can flush before tearing down. */
|
|
648
|
+
|
|
649
|
+
return g_byte_array_free_to_bytes (accum);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
GjsifyHttp2Event **
|
|
653
|
+
gjsify_http2_session_drain_events (GjsifyHttp2Session *self, gsize *out_n)
|
|
654
|
+
{
|
|
655
|
+
if (!self) {
|
|
656
|
+
if (out_n) *out_n = 0;
|
|
657
|
+
return NULL;
|
|
658
|
+
}
|
|
659
|
+
gsize n = g_queue_get_length (self->events);
|
|
660
|
+
if (out_n) *out_n = n;
|
|
661
|
+
if (n == 0) return NULL;
|
|
662
|
+
|
|
663
|
+
GjsifyHttp2Event **arr = g_new0 (GjsifyHttp2Event *, n + 1);
|
|
664
|
+
for (gsize i = 0; i < n; i++) {
|
|
665
|
+
arr[i] = g_queue_pop_head (self->events);
|
|
666
|
+
}
|
|
667
|
+
arr[n] = NULL;
|
|
668
|
+
return arr;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/* ── submits ─────────────────────────────────────────────────────────── */
|
|
672
|
+
|
|
673
|
+
int
|
|
674
|
+
gjsify_http2_session_submit_settings (GjsifyHttp2Session *self)
|
|
675
|
+
{
|
|
676
|
+
if (!self || !self->session) return -1;
|
|
677
|
+
/* Default-shaped SETTINGS: no entries. nghttp2 sends the empty frame
|
|
678
|
+
* which the peer ACKs; that's sufficient for handshake completion. */
|
|
679
|
+
return nghttp2_submit_settings (self->session, NGHTTP2_FLAG_NONE, NULL, 0);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
static void
|
|
683
|
+
fill_nva (nghttp2_nv *nva,
|
|
684
|
+
char **names,
|
|
685
|
+
char **values,
|
|
686
|
+
gsize n_pairs)
|
|
687
|
+
{
|
|
688
|
+
for (gsize i = 0; i < n_pairs; i++) {
|
|
689
|
+
const char *n = names[i] ? names[i] : "";
|
|
690
|
+
const char *v = values[i] ? values[i] : "";
|
|
691
|
+
nva[i].name = (uint8_t *) n;
|
|
692
|
+
nva[i].value = (uint8_t *) v;
|
|
693
|
+
nva[i].namelen = strlen (n);
|
|
694
|
+
nva[i].valuelen = strlen (v);
|
|
695
|
+
nva[i].flags = NGHTTP2_NV_FLAG_NONE;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
int
|
|
700
|
+
gjsify_http2_session_submit_response (GjsifyHttp2Session *self,
|
|
701
|
+
guint32 stream_id,
|
|
702
|
+
char **names,
|
|
703
|
+
char **values,
|
|
704
|
+
gsize n_pairs,
|
|
705
|
+
gboolean end_stream)
|
|
706
|
+
{
|
|
707
|
+
if (!self || !self->session) return -1;
|
|
708
|
+
if (!names || !values) return -1;
|
|
709
|
+
|
|
710
|
+
nghttp2_nv *nva = g_new0 (nghttp2_nv, n_pairs);
|
|
711
|
+
fill_nva (nva, names, values, n_pairs);
|
|
712
|
+
|
|
713
|
+
nghttp2_data_provider *prdp = NULL;
|
|
714
|
+
nghttp2_data_provider prd;
|
|
715
|
+
if (!end_stream) {
|
|
716
|
+
/* Caller will follow up with submit_data(). We do NOT pre-create
|
|
717
|
+
* a carrier — the data_source_read_cb returns NGHTTP2_ERR_DEFERRED
|
|
718
|
+
* until submit_data() replaces the entry, at which point
|
|
719
|
+
* nghttp2_session_resume_data() unblocks the stream. */
|
|
720
|
+
memset (&prd, 0, sizeof (prd));
|
|
721
|
+
prd.source.ptr = NULL;
|
|
722
|
+
prd.read_callback = data_source_read_cb;
|
|
723
|
+
prdp = &prd;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
int rv = nghttp2_submit_response (self->session, (int32_t) stream_id,
|
|
727
|
+
nva, n_pairs, prdp);
|
|
728
|
+
g_free (nva);
|
|
729
|
+
return rv;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
guint32
|
|
733
|
+
gjsify_http2_session_submit_request (GjsifyHttp2Session *self,
|
|
734
|
+
char **names,
|
|
735
|
+
char **values,
|
|
736
|
+
gsize n_pairs,
|
|
737
|
+
gboolean end_stream)
|
|
738
|
+
{
|
|
739
|
+
if (!self || !self->session) return 0;
|
|
740
|
+
if (!names || !values) return 0;
|
|
741
|
+
|
|
742
|
+
nghttp2_nv *nva = g_new0 (nghttp2_nv, n_pairs);
|
|
743
|
+
fill_nva (nva, names, values, n_pairs);
|
|
744
|
+
|
|
745
|
+
nghttp2_data_provider *prdp = NULL;
|
|
746
|
+
nghttp2_data_provider prd;
|
|
747
|
+
if (!end_stream) {
|
|
748
|
+
memset (&prd, 0, sizeof (prd));
|
|
749
|
+
prd.source.ptr = NULL;
|
|
750
|
+
prd.read_callback = data_source_read_cb;
|
|
751
|
+
prdp = &prd;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
int32_t sid = nghttp2_submit_request (self->session, NULL,
|
|
755
|
+
nva, n_pairs, prdp, NULL);
|
|
756
|
+
g_free (nva);
|
|
757
|
+
if (sid < 0) return 0;
|
|
758
|
+
/* No pre-created carrier — submit_data() will lazily attach. */
|
|
759
|
+
return (guint32) sid;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
int
|
|
763
|
+
gjsify_http2_session_submit_data (GjsifyHttp2Session *self,
|
|
764
|
+
guint32 stream_id,
|
|
765
|
+
GBytes *data,
|
|
766
|
+
gboolean end_stream)
|
|
767
|
+
{
|
|
768
|
+
if (!self || !self->session) return -1;
|
|
769
|
+
|
|
770
|
+
DataSourceCarrier *c = data_source_carrier_new (data, end_stream);
|
|
771
|
+
/* Replace any existing (empty) carrier from submit_response. */
|
|
772
|
+
g_hash_table_replace (self->data_carriers,
|
|
773
|
+
GUINT_TO_POINTER (stream_id), c);
|
|
774
|
+
|
|
775
|
+
/* Resume sending after replacing the data source. nghttp2 supports
|
|
776
|
+
* resume_data only AFTER a previous submit_response set a deferred
|
|
777
|
+
* provider; calling it on a stream with no provider is harmless. */
|
|
778
|
+
nghttp2_session_resume_data (self->session, (int32_t) stream_id);
|
|
779
|
+
return 0;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
guint32
|
|
783
|
+
gjsify_http2_session_submit_push_promise (GjsifyHttp2Session *self,
|
|
784
|
+
guint32 parent_id,
|
|
785
|
+
char **names,
|
|
786
|
+
char **values,
|
|
787
|
+
gsize n_pairs)
|
|
788
|
+
{
|
|
789
|
+
if (!self || !self->session) return 0;
|
|
790
|
+
if (!names || !values) return 0;
|
|
791
|
+
|
|
792
|
+
nghttp2_nv *nva = g_new0 (nghttp2_nv, n_pairs);
|
|
793
|
+
fill_nva (nva, names, values, n_pairs);
|
|
794
|
+
|
|
795
|
+
int rv = nghttp2_submit_push_promise (self->session,
|
|
796
|
+
NGHTTP2_FLAG_NONE,
|
|
797
|
+
(int32_t) parent_id,
|
|
798
|
+
nva, n_pairs,
|
|
799
|
+
NULL);
|
|
800
|
+
g_free (nva);
|
|
801
|
+
if (rv < 0) return 0;
|
|
802
|
+
return (guint32) rv;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
int
|
|
806
|
+
gjsify_http2_session_submit_goaway (GjsifyHttp2Session *self,
|
|
807
|
+
guint32 last_stream_id,
|
|
808
|
+
guint32 error_code)
|
|
809
|
+
{
|
|
810
|
+
if (!self || !self->session) return -1;
|
|
811
|
+
return nghttp2_submit_goaway (self->session,
|
|
812
|
+
NGHTTP2_FLAG_NONE,
|
|
813
|
+
(int32_t) last_stream_id,
|
|
814
|
+
error_code,
|
|
815
|
+
NULL, 0);
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
int
|
|
819
|
+
gjsify_http2_session_submit_rst_stream (GjsifyHttp2Session *self,
|
|
820
|
+
guint32 stream_id,
|
|
821
|
+
guint32 error_code)
|
|
822
|
+
{
|
|
823
|
+
if (!self || !self->session) return -1;
|
|
824
|
+
return nghttp2_submit_rst_stream (self->session,
|
|
825
|
+
NGHTTP2_FLAG_NONE,
|
|
826
|
+
(int32_t) stream_id,
|
|
827
|
+
error_code);
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
gboolean
|
|
831
|
+
gjsify_http2_session_want_read (GjsifyHttp2Session *self)
|
|
832
|
+
{
|
|
833
|
+
if (!self || !self->session) return FALSE;
|
|
834
|
+
return nghttp2_session_want_read (self->session) != 0;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
gboolean
|
|
838
|
+
gjsify_http2_session_want_write (GjsifyHttp2Session *self)
|
|
839
|
+
{
|
|
840
|
+
if (!self || !self->session) return FALSE;
|
|
841
|
+
return nghttp2_session_want_write (self->session) != 0;
|
|
842
|
+
}
|