http3 0.0.3 → 0.0.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +46 -0
- data/README.md +66 -9
- data/ext/http3/nghttp3.c +20 -1
- data/ext/http3/nghttp3.h +35 -2
- data/ext/http3/nghttp3_connection.c +377 -205
- data/ext/http3/nghttp3_nv.c +53 -20
- data/ext/http3/nghttp3_qpack.c +64 -20
- data/ext/http3/session.c +197 -48
- data/lib/http3/rack_adapter.rb +141 -26
- data/lib/http3/server.rb +187 -17
- data/lib/http3/session.rb +211 -32
- data/lib/http3/version.rb +1 -1
- data/sig/http3/nghttp3/connection.rbs +43 -9
- data/sig/http3/nghttp3/nv.rbs +4 -0
- data/sig/http3/nghttp3/qpack.rbs +10 -3
- data/sig/http3/rack_adapter.rbs +17 -2
- data/sig/http3/server.rbs +6 -0
- data/sig/http3/session.rbs +52 -8
- data/sig/http3.rbs +3 -0
- metadata +3 -3
data/ext/http3/nghttp3_nv.c
CHANGED
|
@@ -2,27 +2,49 @@
|
|
|
2
2
|
|
|
3
3
|
VALUE rb_cNghttp3NV;
|
|
4
4
|
|
|
5
|
-
/*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
5
|
+
/* nghttp3 keeps the name and value of an NV it is told not to copy until
|
|
6
|
+
* writev_stream has sent them, and nothing keeps a Ruby String alive, or in
|
|
7
|
+
* place, that long: the NV may be dropped and its Strings freed, or moved by
|
|
8
|
+
* GC.compact. So these two flags are dropped, and nghttp3 always copies.
|
|
9
|
+
* They only spare a copy; the bytes sent stay the same, except that nghttp3
|
|
10
|
+
* lowercases a name it copies. */
|
|
11
|
+
#define NV_FLAGS_NO_COPY \
|
|
12
|
+
(NGHTTP3_NV_FLAG_NO_COPY_NAME | NGHTTP3_NV_FLAG_NO_COPY_VALUE)
|
|
13
|
+
|
|
14
|
+
static ID id_iv_name, id_iv_value, id_iv_flags;
|
|
15
|
+
|
|
16
|
+
/* The instance variables are read as they are, and to_str is not called:
|
|
17
|
+
* Ruby code could change the Strings already taken, or start a GC that
|
|
18
|
+
* moves them. rb_obj_is_kind_of and rb_ivar_get call no methods. */
|
|
19
|
+
long nghttp3_rb_nva_fill(VALUE headers, nghttp3_nv *nva, long nvlen) {
|
|
20
|
+
for (long i = 0; i < nvlen; i++) {
|
|
21
|
+
VALUE nv = RARRAY_AREF(headers, i);
|
|
22
|
+
if (!RTEST(rb_obj_is_kind_of(nv, rb_cNghttp3NV))) {
|
|
23
|
+
return i;
|
|
24
|
+
}
|
|
25
|
+
VALUE name = rb_ivar_get(nv, id_iv_name);
|
|
26
|
+
VALUE value = rb_ivar_get(nv, id_iv_value);
|
|
27
|
+
VALUE flags = rb_ivar_get(nv, id_iv_flags);
|
|
28
|
+
if (!RB_TYPE_P(name, T_STRING) || !RB_TYPE_P(value, T_STRING) ||
|
|
29
|
+
!FIXNUM_P(flags) || FIX2LONG(flags) < 0 || FIX2LONG(flags) > 255) {
|
|
30
|
+
return i;
|
|
31
|
+
}
|
|
32
|
+
nva[i] = (nghttp3_nv){
|
|
33
|
+
.name = (const uint8_t *)RSTRING_PTR(name),
|
|
34
|
+
.namelen = (size_t)RSTRING_LEN(name),
|
|
35
|
+
.value = (const uint8_t *)RSTRING_PTR(value),
|
|
36
|
+
.valuelen = (size_t)RSTRING_LEN(value),
|
|
37
|
+
.flags = (uint8_t)(FIX2LONG(flags) & ~NV_FLAGS_NO_COPY),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
return -1;
|
|
41
|
+
}
|
|
24
42
|
|
|
25
|
-
|
|
43
|
+
void nghttp3_rb_raise_bad_nv(long index) {
|
|
44
|
+
rb_raise(rb_eArgError,
|
|
45
|
+
"headers[%ld] is not an HTTP3::Nghttp3::NV with String name and "
|
|
46
|
+
"value",
|
|
47
|
+
index);
|
|
26
48
|
}
|
|
27
49
|
|
|
28
50
|
/*
|
|
@@ -30,6 +52,8 @@ nghttp3_nv nghttp3_rb_nv_to_c(VALUE rb_nv) {
|
|
|
30
52
|
* NV.new(name, value, flags: 0) -> NV
|
|
31
53
|
*
|
|
32
54
|
* Creates a new NV (name-value pair) object representing an HTTP header.
|
|
55
|
+
* flags is an Integer of 0..255, the NV_FLAG_* constants or-ed together;
|
|
56
|
+
* NV_FLAG_NO_COPY_NAME and NV_FLAG_NO_COPY_VALUE are ignored.
|
|
33
57
|
*/
|
|
34
58
|
static VALUE rb_nghttp3_nv_initialize(int argc, VALUE *argv, VALUE self) {
|
|
35
59
|
VALUE name, value, opts;
|
|
@@ -46,6 +70,11 @@ static VALUE rb_nghttp3_nv_initialize(int argc, VALUE *argv, VALUE self) {
|
|
|
46
70
|
flags = rb_flags;
|
|
47
71
|
}
|
|
48
72
|
}
|
|
73
|
+
/* nghttp3_nv.flags is a uint8_t. */
|
|
74
|
+
if (!FIXNUM_P(flags) || FIX2LONG(flags) < 0 || FIX2LONG(flags) > 255) {
|
|
75
|
+
rb_raise(rb_eArgError,
|
|
76
|
+
"flags must be an Integer of 0..255 (got %+" PRIsVALUE ")", flags);
|
|
77
|
+
}
|
|
49
78
|
|
|
50
79
|
rb_iv_set(self, "@name", rb_str_freeze(rb_str_dup(name)));
|
|
51
80
|
rb_iv_set(self, "@value", rb_str_freeze(rb_str_dup(value)));
|
|
@@ -67,6 +96,10 @@ static VALUE rb_nghttp3_nv_get_flags(VALUE self) {
|
|
|
67
96
|
}
|
|
68
97
|
|
|
69
98
|
void Init_nghttp3_nv(void) {
|
|
99
|
+
id_iv_name = rb_intern("@name");
|
|
100
|
+
id_iv_value = rb_intern("@value");
|
|
101
|
+
id_iv_flags = rb_intern("@flags");
|
|
102
|
+
|
|
70
103
|
/* Define NV flag constants */
|
|
71
104
|
rb_define_const(rb_mNghttp3, "NV_FLAG_NONE", UINT2NUM(NGHTTP3_NV_FLAG_NONE));
|
|
72
105
|
rb_define_const(rb_mNghttp3, "NV_FLAG_NEVER_INDEX",
|
data/ext/http3/nghttp3_qpack.c
CHANGED
|
@@ -46,26 +46,33 @@ static VALUE encoder_alloc(VALUE klass) {
|
|
|
46
46
|
* call-seq:
|
|
47
47
|
* Encoder.new(max_dtable_capacity) -> Encoder
|
|
48
48
|
*
|
|
49
|
-
* Creates a new QPACK encoder.
|
|
49
|
+
* Creates a new QPACK encoder. Called again, it starts over with a new
|
|
50
|
+
* encoder and frees the old one; if the new one cannot be made, it raises
|
|
51
|
+
* and the old one stays.
|
|
50
52
|
*/
|
|
51
53
|
static VALUE rb_nghttp3_qpack_encoder_initialize(VALUE self,
|
|
52
54
|
VALUE rb_max_capacity) {
|
|
53
55
|
EncoderObj *obj;
|
|
56
|
+
nghttp3_qpack_encoder *encoder;
|
|
54
57
|
size_t max_capacity;
|
|
55
58
|
int rv;
|
|
56
59
|
|
|
57
60
|
TypedData_Get_Struct(self, EncoderObj, &encoder_data_type, obj);
|
|
58
61
|
|
|
59
62
|
max_capacity = NUM2SIZET(rb_max_capacity);
|
|
60
|
-
obj->hard_max_dtable_capacity = max_capacity;
|
|
61
63
|
|
|
62
|
-
rv = nghttp3_qpack_encoder_new(&
|
|
63
|
-
nghttp3_mem_default());
|
|
64
|
+
rv = nghttp3_qpack_encoder_new(&encoder, max_capacity, nghttp3_mem_default());
|
|
64
65
|
|
|
65
66
|
if (rv != 0) {
|
|
66
67
|
nghttp3_rb_raise(rv, "Failed to create QPACK encoder");
|
|
67
68
|
}
|
|
68
69
|
|
|
70
|
+
if (obj->encoder != NULL) {
|
|
71
|
+
nghttp3_qpack_encoder_del(obj->encoder);
|
|
72
|
+
}
|
|
73
|
+
obj->encoder = encoder;
|
|
74
|
+
obj->hard_max_dtable_capacity = max_capacity;
|
|
75
|
+
|
|
69
76
|
return self;
|
|
70
77
|
}
|
|
71
78
|
|
|
@@ -91,26 +98,33 @@ static VALUE rb_nghttp3_qpack_encoder_encode(VALUE self, VALUE rb_stream_id,
|
|
|
91
98
|
EncoderObj *obj;
|
|
92
99
|
int64_t stream_id;
|
|
93
100
|
nghttp3_nv *nva;
|
|
94
|
-
|
|
101
|
+
VALUE nva_buf;
|
|
102
|
+
long nvlen, bad;
|
|
95
103
|
nghttp3_buf pbuf, rbuf, ebuf;
|
|
96
104
|
int rv;
|
|
97
105
|
VALUE result;
|
|
98
106
|
|
|
99
107
|
TypedData_Get_Struct(self, EncoderObj, &encoder_data_type, obj);
|
|
100
108
|
|
|
109
|
+
/* As Connection#submit_request does: the arguments first, since to_int
|
|
110
|
+
* may run Ruby code, then every allocation, and only then the pointers
|
|
111
|
+
* into the header Strings. */
|
|
112
|
+
stream_id = nghttp3_rb_stream_id(rb_stream_id);
|
|
113
|
+
Check_Type(rb_headers, T_ARRAY);
|
|
114
|
+
|
|
101
115
|
if (obj->encoder == NULL) {
|
|
102
116
|
rb_raise(rb_eNghttp3InvalidStateError, "Encoder is not initialized");
|
|
103
117
|
}
|
|
104
118
|
|
|
105
|
-
|
|
106
|
-
|
|
119
|
+
/* On the heap: a Ruby thread's machine stack is too small for a long
|
|
120
|
+
* list. */
|
|
107
121
|
nvlen = RARRAY_LEN(rb_headers);
|
|
122
|
+
nva = ALLOCV_N(nghttp3_nv, nva_buf, nvlen);
|
|
108
123
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
nva[i] = nghttp3_rb_nv_to_c(rb_nv);
|
|
124
|
+
bad = nghttp3_rb_nva_fill(rb_headers, nva, nvlen);
|
|
125
|
+
if (bad >= 0) {
|
|
126
|
+
ALLOCV_END(nva_buf);
|
|
127
|
+
nghttp3_rb_raise_bad_nv(bad);
|
|
114
128
|
}
|
|
115
129
|
|
|
116
130
|
/* Initialize buffers */
|
|
@@ -119,7 +133,9 @@ static VALUE rb_nghttp3_qpack_encoder_encode(VALUE self, VALUE rb_stream_id,
|
|
|
119
133
|
nghttp3_buf_init(&ebuf);
|
|
120
134
|
|
|
121
135
|
rv = nghttp3_qpack_encoder_encode(obj->encoder, &pbuf, &rbuf, &ebuf,
|
|
122
|
-
stream_id, nva, nvlen);
|
|
136
|
+
stream_id, nva, (size_t)nvlen);
|
|
137
|
+
ALLOCV_END(nva_buf);
|
|
138
|
+
RB_GC_GUARD(rb_headers);
|
|
123
139
|
|
|
124
140
|
if (rv != 0) {
|
|
125
141
|
nghttp3_buf_free(&pbuf, nghttp3_mem_default());
|
|
@@ -239,6 +255,12 @@ static VALUE rb_nghttp3_qpack_encoder_get_num_blocked_streams(VALUE self) {
|
|
|
239
255
|
|
|
240
256
|
/* ============== Decoder ============== */
|
|
241
257
|
|
|
258
|
+
/* The most streams a Decoder decodes at once, Decoder::MAX_STREAMS: each
|
|
259
|
+
* one not finished holds a stream context, and the peer chooses how many
|
|
260
|
+
* it leaves unfinished. nghttp3's encoder keeps the same number of streams
|
|
261
|
+
* (NGHTTP3_QPACK_MAX_QPACK_STREAMS). */
|
|
262
|
+
#define DECODER_MAX_STREAMS 2000
|
|
263
|
+
|
|
242
264
|
typedef struct {
|
|
243
265
|
nghttp3_qpack_decoder *decoder;
|
|
244
266
|
/* stream_id => nghttp3_qpack_stream_context *, in a C st_table: dfree
|
|
@@ -301,12 +323,15 @@ static VALUE decoder_alloc(VALUE klass) {
|
|
|
301
323
|
* call-seq:
|
|
302
324
|
* Decoder.new(max_dtable_capacity, max_blocked_streams) -> Decoder
|
|
303
325
|
*
|
|
304
|
-
* Creates a new QPACK decoder.
|
|
326
|
+
* Creates a new QPACK decoder. Called again, it starts over with a new
|
|
327
|
+
* decoder and frees the old one with the streams it was decoding; if the
|
|
328
|
+
* new one cannot be made, it raises and the old one stays.
|
|
305
329
|
*/
|
|
306
330
|
static VALUE rb_nghttp3_qpack_decoder_initialize(VALUE self,
|
|
307
331
|
VALUE rb_max_capacity,
|
|
308
332
|
VALUE rb_max_blocked) {
|
|
309
333
|
DecoderObj *obj;
|
|
334
|
+
nghttp3_qpack_decoder *decoder;
|
|
310
335
|
size_t max_capacity, max_blocked;
|
|
311
336
|
int rv;
|
|
312
337
|
|
|
@@ -314,21 +339,31 @@ static VALUE rb_nghttp3_qpack_decoder_initialize(VALUE self,
|
|
|
314
339
|
|
|
315
340
|
max_capacity = NUM2SIZET(rb_max_capacity);
|
|
316
341
|
max_blocked = NUM2SIZET(rb_max_blocked);
|
|
317
|
-
obj->hard_max_dtable_capacity = max_capacity;
|
|
318
|
-
obj->max_blocked_streams = max_blocked;
|
|
319
342
|
|
|
320
|
-
rv = nghttp3_qpack_decoder_new(&
|
|
343
|
+
rv = nghttp3_qpack_decoder_new(&decoder, max_capacity, max_blocked,
|
|
321
344
|
nghttp3_mem_default());
|
|
322
345
|
|
|
323
346
|
if (rv != 0) {
|
|
324
347
|
nghttp3_rb_raise(rv, "Failed to create QPACK decoder");
|
|
325
348
|
}
|
|
326
349
|
|
|
350
|
+
/* The stream contexts are of the old decoder's field sections. */
|
|
351
|
+
st_foreach(obj->stream_contexts, free_stream_context_i, 0);
|
|
352
|
+
st_clear(obj->stream_contexts);
|
|
353
|
+
if (obj->decoder != NULL) {
|
|
354
|
+
nghttp3_qpack_decoder_del(obj->decoder);
|
|
355
|
+
}
|
|
356
|
+
obj->decoder = decoder;
|
|
357
|
+
obj->hard_max_dtable_capacity = max_capacity;
|
|
358
|
+
obj->max_blocked_streams = max_blocked;
|
|
359
|
+
|
|
327
360
|
return self;
|
|
328
361
|
}
|
|
329
362
|
|
|
330
363
|
/*
|
|
331
|
-
* Get or create a stream context for the given stream_id
|
|
364
|
+
* Get or create a stream context for the given stream_id. Raises
|
|
365
|
+
* InvalidStateError when a new one would be one more than
|
|
366
|
+
* DECODER_MAX_STREAMS.
|
|
332
367
|
*/
|
|
333
368
|
static nghttp3_qpack_stream_context *
|
|
334
369
|
get_or_create_stream_context(DecoderObj *obj, int64_t stream_id) {
|
|
@@ -338,6 +373,13 @@ get_or_create_stream_context(DecoderObj *obj, int64_t stream_id) {
|
|
|
338
373
|
return (nghttp3_qpack_stream_context *)existing;
|
|
339
374
|
}
|
|
340
375
|
|
|
376
|
+
if (obj->stream_contexts->num_entries >= DECODER_MAX_STREAMS) {
|
|
377
|
+
rb_raise(rb_eNghttp3InvalidStateError,
|
|
378
|
+
"a decoder takes at most %d streams at once; finish or "
|
|
379
|
+
"cancel_stream one first",
|
|
380
|
+
DECODER_MAX_STREAMS);
|
|
381
|
+
}
|
|
382
|
+
|
|
341
383
|
/* Create new context */
|
|
342
384
|
nghttp3_qpack_stream_context *sctx;
|
|
343
385
|
int rv =
|
|
@@ -407,7 +449,7 @@ static VALUE rb_nghttp3_qpack_decoder_decode(int argc, VALUE *argv,
|
|
|
407
449
|
rb_raise(rb_eNghttp3InvalidStateError, "Decoder is not initialized");
|
|
408
450
|
}
|
|
409
451
|
|
|
410
|
-
stream_id =
|
|
452
|
+
stream_id = nghttp3_rb_stream_id(rb_stream_id);
|
|
411
453
|
Check_Type(rb_data, T_STRING);
|
|
412
454
|
src = (const uint8_t *)RSTRING_PTR(rb_data);
|
|
413
455
|
srclen = RSTRING_LEN(rb_data);
|
|
@@ -552,7 +594,7 @@ static VALUE rb_nghttp3_qpack_decoder_cancel_stream(VALUE self,
|
|
|
552
594
|
rb_raise(rb_eNghttp3InvalidStateError, "Decoder is not initialized");
|
|
553
595
|
}
|
|
554
596
|
|
|
555
|
-
stream_id =
|
|
597
|
+
stream_id = nghttp3_rb_stream_id(rb_stream_id);
|
|
556
598
|
|
|
557
599
|
delete_stream_context(obj, stream_id);
|
|
558
600
|
|
|
@@ -638,6 +680,8 @@ void Init_nghttp3_qpack(void) {
|
|
|
638
680
|
/* Define Decoder class */
|
|
639
681
|
rb_cNghttp3QPACKDecoder =
|
|
640
682
|
rb_define_class_under(rb_mNghttp3QPACK, "Decoder", rb_cObject);
|
|
683
|
+
rb_define_const(rb_cNghttp3QPACKDecoder, "MAX_STREAMS",
|
|
684
|
+
INT2NUM(DECODER_MAX_STREAMS));
|
|
641
685
|
rb_define_alloc_func(rb_cNghttp3QPACKDecoder, decoder_alloc);
|
|
642
686
|
rb_define_method(rb_cNghttp3QPACKDecoder, "initialize",
|
|
643
687
|
rb_nghttp3_qpack_decoder_initialize, 2);
|