protocol-http3 0.0.1 → 0.0.2
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
- checksums.yaml.gz.sig +0 -0
- data/ext/rakefile.rb +7 -2
- data/ext/ruby-protocol-http3-lock.yml +11 -2
- data/ext/source/Ruby/Protocol/HTTP3/Client.cpp +165 -17
- data/ext/source/Ruby/Protocol/HTTP3/Server.cpp +238 -12
- data/ext/source/Ruby/Protocol/HTTP3/Stream.cpp +155 -0
- data/ext/source/Ruby/Protocol/HTTP3/Stream.hpp +47 -0
- data/lib/protocol/http3/stream.rb +58 -0
- data/lib/protocol/http3/version.rb +1 -1
- data/lib/protocol/http3.rb +36 -0
- data.tar.gz.sig +0 -0
- metadata +20 -3
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2026dd714a98de999332e2aa5eb59944d2f42ee40ec34f1e90ce320c124a41b0
|
|
4
|
+
data.tar.gz: 25bd20d83b78acd2c7d9eb00436630a6b25e67928fa609e2df5659949a2f6c1e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 10a05532e4bc989f3a4750e1d27eadf1cc745e3b3e6177b3f5cd35de598b096dc869058c62878b8ff45d526d3d4917bf1648d23ae1210b2df8b280c9a32418c7
|
|
7
|
+
data.tar.gz: 0ce529f6b7f18644c4b4ed655c682ca0aea12c5e482bed759f8745b922861f86da3300b162946fe42263bee4aa1bb0b7c6ed15a806cda69c709db9400e49e574
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/ext/rakefile.rb
CHANGED
|
@@ -3,10 +3,15 @@
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
4
|
# Copyright, 2023-2026, by Samuel Williams.
|
|
5
5
|
|
|
6
|
+
require "rbconfig"
|
|
7
|
+
require "rubygems"
|
|
8
|
+
|
|
9
|
+
TEAPOT = [RbConfig.ruby, Gem.bin_path("teapot", "teapot")]
|
|
10
|
+
|
|
6
11
|
task :default do
|
|
7
12
|
ruby_library_directory = ENV.fetch("RUBYLIBDIR"){ENV.fetch("RUBYARCHDIR")}
|
|
8
13
|
build_environment = {"RUBYLIBDIR" => ruby_library_directory}
|
|
9
14
|
|
|
10
|
-
sh build_environment,
|
|
11
|
-
sh build_environment,
|
|
15
|
+
sh build_environment, *TEAPOT, "fetch"
|
|
16
|
+
sh build_environment, *TEAPOT, "scheduler-ruby-library", "Ruby/Protocol/HTTP3"
|
|
12
17
|
end
|
|
@@ -9,7 +9,7 @@ build-cmake:
|
|
|
9
9
|
:commit: 6558fdad5bbb475f0fa5584ff773de8f12ff232e
|
|
10
10
|
:branch: master
|
|
11
11
|
protocol-http3:
|
|
12
|
-
:commit:
|
|
12
|
+
:commit: b7d3d4d1b1d6f6c089d2ec9986631bcfe2c373cf
|
|
13
13
|
:branch: main
|
|
14
14
|
ruby:
|
|
15
15
|
:commit: 685cdd5a421dae7749f368d08add9aa9709ad043
|
|
@@ -48,7 +48,7 @@ executor-lldb:
|
|
|
48
48
|
:commit: 193ba0b15c532d2745b0f0bfb05cd9581e869555
|
|
49
49
|
:branch: master
|
|
50
50
|
protocol-quic:
|
|
51
|
-
:commit:
|
|
51
|
+
:commit: 5d069ac54f446f24dc95003543a2acaeef807e70
|
|
52
52
|
:branch: main
|
|
53
53
|
scheduler:
|
|
54
54
|
:commit: 4d05a1631ac749106d268bda07425bf6ce0476dc
|
|
@@ -80,3 +80,12 @@ coroutine-arm64:
|
|
|
80
80
|
scheduler-ruby:
|
|
81
81
|
:commit: 94e524e64912081aaa5a8cb54bad7ad73f01a51a
|
|
82
82
|
:branch: main
|
|
83
|
+
platform-linux:
|
|
84
|
+
:commit: c3c5cbf90d26d23c6137def781cd997ddca5e51a
|
|
85
|
+
:branch: master
|
|
86
|
+
build-linux:
|
|
87
|
+
:commit: cbc8c52c2ca2fc75761e545c83429ef34e4cd4fe
|
|
88
|
+
:branch: master
|
|
89
|
+
coroutine-amd64:
|
|
90
|
+
:commit: 27aee56aa1414094584e7b2d8b74e289ecad4c86
|
|
91
|
+
:branch: master
|
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
#include "Client.hpp"
|
|
2
|
+
#include "Stream.hpp"
|
|
2
3
|
|
|
3
4
|
#include "../QUIC/Bindings.hpp"
|
|
4
5
|
|
|
5
|
-
#include <Protocol/HTTP3/Stream.hpp>
|
|
6
|
-
|
|
7
6
|
#include <array>
|
|
8
|
-
#include <
|
|
7
|
+
#include <stdexcept>
|
|
9
8
|
#include <vector>
|
|
10
9
|
|
|
11
10
|
VALUE Ruby_Protocol_HTTP3_Client = Qnil;
|
|
12
11
|
|
|
13
12
|
namespace Ruby::Protocol::HTTP3 {
|
|
14
|
-
|
|
15
13
|
class Client final : public ::Protocol::QUIC::Client, public ::Protocol::HTTP3::Session {
|
|
16
14
|
public:
|
|
17
15
|
VALUE self;
|
|
@@ -21,7 +19,6 @@ namespace Ruby::Protocol::HTTP3 {
|
|
|
21
19
|
VALUE _tls_context;
|
|
22
20
|
VALUE _socket;
|
|
23
21
|
VALUE _remote_address;
|
|
24
|
-
std::unordered_map<::Protocol::QUIC::StreamID, VALUE> _streams;
|
|
25
22
|
|
|
26
23
|
public:
|
|
27
24
|
Client(VALUE self, VALUE configuration, VALUE tls_context, VALUE socket, VALUE remote_address, VALUE chosen_version) :
|
|
@@ -53,7 +50,19 @@ namespace Ruby::Protocol::HTTP3 {
|
|
|
53
50
|
|
|
54
51
|
::Protocol::QUIC::Stream * create_stream(::Protocol::QUIC::StreamID stream_id) override
|
|
55
52
|
{
|
|
56
|
-
return new
|
|
53
|
+
return new Stream(*this, *this, stream_id);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
Stream * stream_for(::Protocol::QUIC::StreamID stream_id)
|
|
57
|
+
{
|
|
58
|
+
auto stream = reinterpret_cast<::Protocol::QUIC::Stream *>(ngtcp2_conn_get_stream_user_data(::Protocol::QUIC::Client::native_handle(), stream_id));
|
|
59
|
+
auto ruby_stream = dynamic_cast<Stream *>(stream);
|
|
60
|
+
|
|
61
|
+
if (!ruby_stream) {
|
|
62
|
+
throw std::runtime_error("Could not find HTTP/3 stream.");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return ruby_stream;
|
|
57
66
|
}
|
|
58
67
|
|
|
59
68
|
::Protocol::QUIC::Connection::Status send_stream_data() override
|
|
@@ -65,7 +74,14 @@ namespace Ruby::Protocol::HTTP3 {
|
|
|
65
74
|
::Protocol::QUIC::StreamID stream_id = -1;
|
|
66
75
|
bool is_final = false;
|
|
67
76
|
|
|
68
|
-
|
|
77
|
+
nghttp3_ssize vector_count = 0;
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
vector_count = write_stream_data(stream_id, is_final, http_vectors.data(), http_vectors.size());
|
|
81
|
+
} catch (const std::system_error &) {
|
|
82
|
+
close_pending_streams();
|
|
83
|
+
return Status(NGTCP2_ERR_CALLBACK_FAILURE);
|
|
84
|
+
}
|
|
69
85
|
|
|
70
86
|
if (stream_id < 0) {
|
|
71
87
|
break;
|
|
@@ -98,12 +114,16 @@ namespace Ruby::Protocol::HTTP3 {
|
|
|
98
114
|
return Status(result);
|
|
99
115
|
}
|
|
100
116
|
|
|
101
|
-
add_write_offset(stream_id, static_cast<std::size_t>(written_length));
|
|
102
|
-
|
|
103
117
|
if (result > 0) {
|
|
104
118
|
send_packet(path_storage.path, packet_info, packet.data(), result);
|
|
105
119
|
}
|
|
106
120
|
|
|
121
|
+
if (written_length < 0) {
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
add_write_offset(stream_id, static_cast<std::size_t>(written_length));
|
|
126
|
+
|
|
107
127
|
if (result == 0 && written_length == 0) {
|
|
108
128
|
break;
|
|
109
129
|
}
|
|
@@ -112,6 +132,10 @@ namespace Ruby::Protocol::HTTP3 {
|
|
|
112
132
|
return Status::OK;
|
|
113
133
|
}
|
|
114
134
|
|
|
135
|
+
void close_pending_streams()
|
|
136
|
+
{
|
|
137
|
+
}
|
|
138
|
+
|
|
115
139
|
void header_received(::Protocol::QUIC::StreamID stream_id, std::int32_t token, nghttp3_rcbuf *name, nghttp3_rcbuf *value, std::uint8_t flags, void *stream_data) override
|
|
116
140
|
{
|
|
117
141
|
(void)token;
|
|
@@ -144,6 +168,31 @@ namespace Ruby::Protocol::HTTP3 {
|
|
|
144
168
|
}
|
|
145
169
|
}
|
|
146
170
|
|
|
171
|
+
void stream_data_received(::Protocol::QUIC::StreamID stream_id, const std::uint8_t *data, std::size_t size, void *stream_data) override
|
|
172
|
+
{
|
|
173
|
+
(void)stream_data;
|
|
174
|
+
|
|
175
|
+
auto stream = stream_for(stream_id);
|
|
176
|
+
stream->receive_input(data, size);
|
|
177
|
+
|
|
178
|
+
if (rb_respond_to(self, rb_intern("data_received"))) {
|
|
179
|
+
rb_funcall(self, rb_intern("data_received"), 2, RB_LL2NUM(stream_id), stream->shift_input());
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
void stream_data_acknowledged(::Protocol::QUIC::StreamID stream_id, std::uint64_t size, void *stream_data) override
|
|
184
|
+
{
|
|
185
|
+
(void)stream_data;
|
|
186
|
+
|
|
187
|
+
stream_for(stream_id)->acknowledge_output(size);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
void stream_closed(::Protocol::QUIC::StreamID stream_id, std::uint64_t error_code, void *stream_data) override
|
|
191
|
+
{
|
|
192
|
+
(void)error_code;
|
|
193
|
+
(void)stream_data;
|
|
194
|
+
}
|
|
195
|
+
|
|
147
196
|
void settings_received(const nghttp3_proto_settings *settings) override
|
|
148
197
|
{
|
|
149
198
|
(void)settings;
|
|
@@ -157,6 +206,8 @@ namespace Ruby::Protocol::HTTP3 {
|
|
|
157
206
|
{
|
|
158
207
|
(void)stream_data;
|
|
159
208
|
|
|
209
|
+
stream_for(stream_id)->finish_input();
|
|
210
|
+
|
|
160
211
|
if (rb_respond_to(self, rb_intern("stream_finished"))) {
|
|
161
212
|
rb_funcall(self, rb_intern("stream_finished"), 1, RB_LL2NUM(stream_id));
|
|
162
213
|
}
|
|
@@ -165,7 +216,6 @@ namespace Ruby::Protocol::HTTP3 {
|
|
|
165
216
|
void disconnect() override
|
|
166
217
|
{
|
|
167
218
|
::Protocol::QUIC::Client::disconnect();
|
|
168
|
-
_streams.clear();
|
|
169
219
|
}
|
|
170
220
|
|
|
171
221
|
void mark()
|
|
@@ -176,9 +226,12 @@ namespace Ruby::Protocol::HTTP3 {
|
|
|
176
226
|
rb_gc_mark_movable(_socket);
|
|
177
227
|
rb_gc_mark_movable(_remote_address);
|
|
178
228
|
|
|
179
|
-
for (auto & [stream_id, stream] : _streams) {
|
|
229
|
+
for (auto & [stream_id, stream] : ::Protocol::QUIC::Connection::_streams) {
|
|
180
230
|
(void)stream_id;
|
|
181
|
-
|
|
231
|
+
|
|
232
|
+
if (auto ruby_stream = dynamic_cast<Stream *>(stream)) {
|
|
233
|
+
ruby_stream->mark();
|
|
234
|
+
}
|
|
182
235
|
}
|
|
183
236
|
}
|
|
184
237
|
|
|
@@ -190,11 +243,57 @@ namespace Ruby::Protocol::HTTP3 {
|
|
|
190
243
|
_socket = rb_gc_location(_socket);
|
|
191
244
|
_remote_address = rb_gc_location(_remote_address);
|
|
192
245
|
|
|
193
|
-
for (auto & [stream_id, stream] : _streams) {
|
|
246
|
+
for (auto & [stream_id, stream] : ::Protocol::QUIC::Connection::_streams) {
|
|
194
247
|
(void)stream_id;
|
|
195
|
-
|
|
248
|
+
|
|
249
|
+
if (auto ruby_stream = dynamic_cast<Stream *>(stream)) {
|
|
250
|
+
ruby_stream->compact();
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
void submit_request_with_body(::Protocol::QUIC::StreamID stream_id, const nghttp3_nv *headers, std::size_t count, VALUE body)
|
|
256
|
+
{
|
|
257
|
+
auto stream = stream_for(stream_id);
|
|
258
|
+
|
|
259
|
+
if (NIL_P(body)) {
|
|
260
|
+
submit_request(stream_id, headers, count, nullptr, stream);
|
|
261
|
+
} else {
|
|
262
|
+
submit_request(stream_id, headers, count, stream->reader(), stream);
|
|
196
263
|
}
|
|
197
264
|
}
|
|
265
|
+
|
|
266
|
+
void append_body(::Protocol::QUIC::StreamID stream_id, VALUE chunk)
|
|
267
|
+
{
|
|
268
|
+
auto stream = stream_for(stream_id);
|
|
269
|
+
auto was_empty = !stream->output_pending();
|
|
270
|
+
|
|
271
|
+
stream->append_output(chunk);
|
|
272
|
+
|
|
273
|
+
if (was_empty) {
|
|
274
|
+
resume_stream(stream_id);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
void finish_body(::Protocol::QUIC::StreamID stream_id)
|
|
279
|
+
{
|
|
280
|
+
auto stream = stream_for(stream_id);
|
|
281
|
+
auto was_empty = !stream->output_pending();
|
|
282
|
+
|
|
283
|
+
stream->finish_output();
|
|
284
|
+
|
|
285
|
+
if (was_empty) {
|
|
286
|
+
resume_stream(stream_id);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
void reset_body(::Protocol::QUIC::StreamID stream_id, std::uint64_t error_code)
|
|
291
|
+
{
|
|
292
|
+
stream_for(stream_id)->reset_output();
|
|
293
|
+
shutdown_stream_write(stream_id);
|
|
294
|
+
close_stream(stream_id, error_code);
|
|
295
|
+
ngtcp2_conn_shutdown_stream_write(::Protocol::QUIC::Client::native_handle(), 0, stream_id, error_code);
|
|
296
|
+
}
|
|
198
297
|
};
|
|
199
298
|
|
|
200
299
|
}
|
|
@@ -307,6 +406,7 @@ static VALUE Ruby_Protocol_HTTP3_Client_receive(VALUE self, VALUE ruby_socket)
|
|
|
307
406
|
switch (status) {
|
|
308
407
|
case Protocol::QUIC::Connection::Status::OK:
|
|
309
408
|
client->send_packets();
|
|
409
|
+
|
|
310
410
|
return Qtrue;
|
|
311
411
|
case Protocol::QUIC::Connection::Status::CLOSING:
|
|
312
412
|
case Protocol::QUIC::Connection::Status::DRAINING:
|
|
@@ -316,8 +416,13 @@ static VALUE Ruby_Protocol_HTTP3_Client_receive(VALUE self, VALUE ruby_socket)
|
|
|
316
416
|
}
|
|
317
417
|
}
|
|
318
418
|
|
|
319
|
-
static VALUE Ruby_Protocol_HTTP3_Client_submit_request(VALUE
|
|
419
|
+
static VALUE Ruby_Protocol_HTTP3_Client_submit_request(int argc, VALUE *argv, VALUE self)
|
|
320
420
|
{
|
|
421
|
+
VALUE headers;
|
|
422
|
+
VALUE body = Qnil;
|
|
423
|
+
|
|
424
|
+
rb_scan_args(argc, argv, "11", &headers, &body);
|
|
425
|
+
|
|
321
426
|
auto client = Ruby_Protocol_HTTP3_Client_native_get(self);
|
|
322
427
|
auto stream = client->open_bidirectional_stream();
|
|
323
428
|
auto stream_id = stream->stream_id();
|
|
@@ -342,12 +447,51 @@ static VALUE Ruby_Protocol_HTTP3_Client_submit_request(VALUE self, VALUE headers
|
|
|
342
447
|
});
|
|
343
448
|
}
|
|
344
449
|
|
|
345
|
-
client->
|
|
450
|
+
client->submit_request_with_body(stream_id, native_headers.data(), native_headers.size(), body);
|
|
346
451
|
client->send_packets();
|
|
347
452
|
|
|
348
453
|
return RB_LL2NUM(stream_id);
|
|
349
454
|
}
|
|
350
455
|
|
|
456
|
+
static VALUE Ruby_Protocol_HTTP3_Client_write_body_chunk(VALUE self, VALUE stream_id, VALUE chunk)
|
|
457
|
+
{
|
|
458
|
+
auto client = Ruby_Protocol_HTTP3_Client_native_get(self);
|
|
459
|
+
|
|
460
|
+
client->append_body(RB_NUM2LL(stream_id), chunk);
|
|
461
|
+
|
|
462
|
+
return Qnil;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
static VALUE Ruby_Protocol_HTTP3_Client_read_body_chunk(VALUE self, VALUE stream_id)
|
|
466
|
+
{
|
|
467
|
+
auto client = Ruby_Protocol_HTTP3_Client_native_get(self);
|
|
468
|
+
|
|
469
|
+
return client->stream_for(RB_NUM2LL(stream_id))->shift_input();
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
static VALUE Ruby_Protocol_HTTP3_Client_finish_body(VALUE self, VALUE stream_id)
|
|
473
|
+
{
|
|
474
|
+
auto client = Ruby_Protocol_HTTP3_Client_native_get(self);
|
|
475
|
+
|
|
476
|
+
client->finish_body(RB_NUM2LL(stream_id));
|
|
477
|
+
|
|
478
|
+
return Qnil;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
static VALUE Ruby_Protocol_HTTP3_Client_reset_body(int argc, VALUE *argv, VALUE self)
|
|
482
|
+
{
|
|
483
|
+
VALUE stream_id;
|
|
484
|
+
VALUE error_code;
|
|
485
|
+
rb_scan_args(argc, argv, "11", &stream_id, &error_code);
|
|
486
|
+
|
|
487
|
+
auto client = Ruby_Protocol_HTTP3_Client_native_get(self);
|
|
488
|
+
auto native_error_code = NIL_P(error_code) ? NGHTTP3_H3_INTERNAL_ERROR : RB_NUM2ULL(error_code);
|
|
489
|
+
|
|
490
|
+
client->reset_body(RB_NUM2LL(stream_id), native_error_code);
|
|
491
|
+
|
|
492
|
+
return Qnil;
|
|
493
|
+
}
|
|
494
|
+
|
|
351
495
|
void Init_Ruby_Protocol_HTTP3_Client(VALUE Protocol_HTTP3)
|
|
352
496
|
{
|
|
353
497
|
Ruby_Protocol_HTTP3_Client = rb_define_class_under(Protocol_HTTP3, "Client", rb_cObject);
|
|
@@ -359,5 +503,9 @@ void Init_Ruby_Protocol_HTTP3_Client(VALUE Protocol_HTTP3)
|
|
|
359
503
|
rb_define_method(Ruby_Protocol_HTTP3_Client, "close", Ruby_Protocol_HTTP3_Client_close, 0);
|
|
360
504
|
rb_define_method(Ruby_Protocol_HTTP3_Client, "send_packets", Ruby_Protocol_HTTP3_Client_send_packets, 0);
|
|
361
505
|
rb_define_method(Ruby_Protocol_HTTP3_Client, "receive", Ruby_Protocol_HTTP3_Client_receive, 1);
|
|
362
|
-
rb_define_method(Ruby_Protocol_HTTP3_Client, "submit_request", Ruby_Protocol_HTTP3_Client_submit_request, 1);
|
|
506
|
+
rb_define_method(Ruby_Protocol_HTTP3_Client, "submit_request", RUBY_METHOD_FUNC(Ruby_Protocol_HTTP3_Client_submit_request), -1);
|
|
507
|
+
rb_define_method(Ruby_Protocol_HTTP3_Client, "write_body_chunk", Ruby_Protocol_HTTP3_Client_write_body_chunk, 2);
|
|
508
|
+
rb_define_method(Ruby_Protocol_HTTP3_Client, "read_body_chunk", Ruby_Protocol_HTTP3_Client_read_body_chunk, 1);
|
|
509
|
+
rb_define_method(Ruby_Protocol_HTTP3_Client, "finish_body", Ruby_Protocol_HTTP3_Client_finish_body, 1);
|
|
510
|
+
rb_define_method(Ruby_Protocol_HTTP3_Client, "reset_body", RUBY_METHOD_FUNC(Ruby_Protocol_HTTP3_Client_reset_body), -1);
|
|
363
511
|
}
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
#include "Server.hpp"
|
|
2
2
|
|
|
3
3
|
#include "Dispatcher.hpp"
|
|
4
|
+
#include "Stream.hpp"
|
|
4
5
|
|
|
5
6
|
#include "../QUIC/Bindings.hpp"
|
|
6
7
|
|
|
7
|
-
#include <
|
|
8
|
+
#include <array>
|
|
9
|
+
#include <stdexcept>
|
|
8
10
|
#include <vector>
|
|
9
11
|
|
|
10
12
|
VALUE Ruby_Protocol_HTTP3_Server = Qnil;
|
|
11
13
|
|
|
12
14
|
namespace Ruby::Protocol::HTTP3 {
|
|
13
|
-
|
|
14
15
|
class Server : public ::Protocol::HTTP3::Server {
|
|
15
16
|
public:
|
|
16
17
|
VALUE self;
|
|
@@ -21,7 +22,6 @@ namespace Ruby::Protocol::HTTP3 {
|
|
|
21
22
|
VALUE _tls_context;
|
|
22
23
|
VALUE _socket;
|
|
23
24
|
VALUE _remote_address;
|
|
24
|
-
std::unordered_map<::Protocol::QUIC::StreamID, VALUE> _streams;
|
|
25
25
|
|
|
26
26
|
public:
|
|
27
27
|
Server(VALUE self, VALUE dispatcher, VALUE configuration, VALUE tls_context, VALUE socket, VALUE remote_address, VALUE packet_header, VALUE original_connection_id) :
|
|
@@ -40,6 +40,93 @@ namespace Ruby::Protocol::HTTP3 {
|
|
|
40
40
|
{
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
::Protocol::QUIC::Stream * create_stream(::Protocol::QUIC::StreamID stream_id) override
|
|
44
|
+
{
|
|
45
|
+
return new Stream(*this, *this, stream_id);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
Stream * stream_for(::Protocol::QUIC::StreamID stream_id)
|
|
49
|
+
{
|
|
50
|
+
auto stream = reinterpret_cast<::Protocol::QUIC::Stream *>(ngtcp2_conn_get_stream_user_data(::Protocol::QUIC::Server::native_handle(), stream_id));
|
|
51
|
+
auto ruby_stream = dynamic_cast<Stream *>(stream);
|
|
52
|
+
|
|
53
|
+
if (!ruby_stream) {
|
|
54
|
+
throw std::runtime_error("Could not find HTTP/3 stream.");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return ruby_stream;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
::Protocol::QUIC::Connection::Status send_stream_data() override
|
|
61
|
+
{
|
|
62
|
+
std::array<::Protocol::QUIC::Byte, 1024*64> packet;
|
|
63
|
+
std::array<nghttp3_vec, 16> http_vectors;
|
|
64
|
+
|
|
65
|
+
while (true) {
|
|
66
|
+
::Protocol::QUIC::StreamID stream_id = -1;
|
|
67
|
+
bool is_final = false;
|
|
68
|
+
nghttp3_ssize vector_count = 0;
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
vector_count = write_stream_data(stream_id, is_final, http_vectors.data(), http_vectors.size());
|
|
72
|
+
} catch (const std::system_error &) {
|
|
73
|
+
close_pending_streams();
|
|
74
|
+
return Status(NGTCP2_ERR_CALLBACK_FAILURE);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (stream_id < 0) {
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
std::vector<ngtcp2_vec> stream_vectors;
|
|
82
|
+
stream_vectors.reserve(static_cast<std::size_t>(vector_count));
|
|
83
|
+
|
|
84
|
+
for (nghttp3_ssize index = 0; index < vector_count; ++index) {
|
|
85
|
+
stream_vectors.push_back(ngtcp2_vec{
|
|
86
|
+
.base = http_vectors[index].base,
|
|
87
|
+
.len = http_vectors[index].len,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
ngtcp2_path_storage path_storage;
|
|
92
|
+
ngtcp2_path_storage_zero(&path_storage);
|
|
93
|
+
ngtcp2_pkt_info packet_info;
|
|
94
|
+
ngtcp2_ssize written_length = 0;
|
|
95
|
+
::Protocol::QUIC::StreamDataFlags flags = is_final ? NGTCP2_WRITE_STREAM_FLAG_FIN : 0;
|
|
96
|
+
|
|
97
|
+
auto result = ngtcp2_conn_writev_stream(::Protocol::QUIC::Server::native_handle(), &path_storage.path, &packet_info, packet.data(), packet.size(), &written_length, flags, stream_id, stream_vectors.data(), stream_vectors.size(), ::Protocol::QUIC::timestamp());
|
|
98
|
+
|
|
99
|
+
if (result == NGTCP2_ERR_STREAM_DATA_BLOCKED || result == NGTCP2_ERR_STREAM_SHUT_WR) {
|
|
100
|
+
block_stream(stream_id);
|
|
101
|
+
return Status(result);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (result < 0) {
|
|
105
|
+
return Status(result);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (result > 0) {
|
|
109
|
+
send_packet(path_storage.path, packet_info, packet.data(), result);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (written_length < 0) {
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
add_write_offset(stream_id, static_cast<std::size_t>(written_length));
|
|
117
|
+
|
|
118
|
+
if (result == 0 && written_length == 0) {
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return Status::OK;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
void close_pending_streams()
|
|
127
|
+
{
|
|
128
|
+
}
|
|
129
|
+
|
|
43
130
|
void header_received(::Protocol::QUIC::StreamID stream_id, std::int32_t token, nghttp3_rcbuf *name, nghttp3_rcbuf *value, std::uint8_t flags, void *stream_data) override
|
|
44
131
|
{
|
|
45
132
|
(void)token;
|
|
@@ -72,6 +159,26 @@ namespace Ruby::Protocol::HTTP3 {
|
|
|
72
159
|
}
|
|
73
160
|
}
|
|
74
161
|
|
|
162
|
+
void stream_data_received(::Protocol::QUIC::StreamID stream_id, const std::uint8_t *data, std::size_t size, void *stream_data) override
|
|
163
|
+
{
|
|
164
|
+
::Protocol::HTTP3::Server::stream_data_received(stream_id, data, size, stream_data);
|
|
165
|
+
|
|
166
|
+
if (rb_respond_to(self, rb_intern("data_received"))) {
|
|
167
|
+
rb_funcall(self, rb_intern("data_received"), 2, RB_LL2NUM(stream_id), stream_for(stream_id)->shift_input());
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
void stream_data_acknowledged(::Protocol::QUIC::StreamID stream_id, std::uint64_t size, void *stream_data) override
|
|
172
|
+
{
|
|
173
|
+
::Protocol::HTTP3::Server::stream_data_acknowledged(stream_id, size, stream_data);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
void stream_closed(::Protocol::QUIC::StreamID stream_id, std::uint64_t error_code, void *stream_data) override
|
|
177
|
+
{
|
|
178
|
+
(void)error_code;
|
|
179
|
+
(void)stream_data;
|
|
180
|
+
}
|
|
181
|
+
|
|
75
182
|
void settings_received(const nghttp3_proto_settings *settings) override
|
|
76
183
|
{
|
|
77
184
|
(void)settings;
|
|
@@ -83,7 +190,7 @@ namespace Ruby::Protocol::HTTP3 {
|
|
|
83
190
|
|
|
84
191
|
void stream_finished(::Protocol::QUIC::StreamID stream_id, void *stream_data) override
|
|
85
192
|
{
|
|
86
|
-
(
|
|
193
|
+
::Protocol::HTTP3::Server::stream_finished(stream_id, stream_data);
|
|
87
194
|
|
|
88
195
|
if (rb_respond_to(self, rb_intern("stream_finished"))) {
|
|
89
196
|
rb_funcall(self, rb_intern("stream_finished"), 1, RB_LL2NUM(stream_id));
|
|
@@ -93,7 +200,6 @@ namespace Ruby::Protocol::HTTP3 {
|
|
|
93
200
|
void disconnect() override
|
|
94
201
|
{
|
|
95
202
|
::Protocol::HTTP3::Server::disconnect();
|
|
96
|
-
_streams.clear();
|
|
97
203
|
}
|
|
98
204
|
|
|
99
205
|
void mark()
|
|
@@ -105,9 +211,12 @@ namespace Ruby::Protocol::HTTP3 {
|
|
|
105
211
|
rb_gc_mark_movable(_socket);
|
|
106
212
|
rb_gc_mark_movable(_remote_address);
|
|
107
213
|
|
|
108
|
-
for (auto & [stream_id, stream] : _streams) {
|
|
214
|
+
for (auto & [stream_id, stream] : ::Protocol::QUIC::Connection::_streams) {
|
|
109
215
|
(void)stream_id;
|
|
110
|
-
|
|
216
|
+
|
|
217
|
+
if (auto ruby_stream = dynamic_cast<Stream *>(stream)) {
|
|
218
|
+
ruby_stream->mark();
|
|
219
|
+
}
|
|
111
220
|
}
|
|
112
221
|
}
|
|
113
222
|
|
|
@@ -120,11 +229,56 @@ namespace Ruby::Protocol::HTTP3 {
|
|
|
120
229
|
_socket = rb_gc_location(_socket);
|
|
121
230
|
_remote_address = rb_gc_location(_remote_address);
|
|
122
231
|
|
|
123
|
-
for (auto & [stream_id, stream] : _streams) {
|
|
232
|
+
for (auto & [stream_id, stream] : ::Protocol::QUIC::Connection::_streams) {
|
|
124
233
|
(void)stream_id;
|
|
125
|
-
|
|
234
|
+
|
|
235
|
+
if (auto ruby_stream = dynamic_cast<Stream *>(stream)) {
|
|
236
|
+
ruby_stream->compact();
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
void submit_response_with_body(::Protocol::QUIC::StreamID stream_id, const nghttp3_nv *headers, std::size_t count, VALUE body)
|
|
242
|
+
{
|
|
243
|
+
if (NIL_P(body)) {
|
|
244
|
+
submit_response(stream_id, headers, count);
|
|
245
|
+
} else {
|
|
246
|
+
auto stream = stream_for(stream_id);
|
|
247
|
+
submit_response(stream_id, headers, count, stream->reader(), stream);
|
|
126
248
|
}
|
|
127
249
|
}
|
|
250
|
+
|
|
251
|
+
void append_body(::Protocol::QUIC::StreamID stream_id, VALUE chunk)
|
|
252
|
+
{
|
|
253
|
+
auto stream = stream_for(stream_id);
|
|
254
|
+
auto was_empty = !stream->output_pending();
|
|
255
|
+
|
|
256
|
+
stream->append_output(chunk);
|
|
257
|
+
|
|
258
|
+
if (was_empty) {
|
|
259
|
+
resume_stream(stream_id);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
void finish_body(::Protocol::QUIC::StreamID stream_id)
|
|
264
|
+
{
|
|
265
|
+
auto stream = stream_for(stream_id);
|
|
266
|
+
auto was_empty = !stream->output_pending();
|
|
267
|
+
|
|
268
|
+
stream->finish_output();
|
|
269
|
+
|
|
270
|
+
if (was_empty) {
|
|
271
|
+
resume_stream(stream_id);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
void reset_body(::Protocol::QUIC::StreamID stream_id, std::uint64_t error_code)
|
|
276
|
+
{
|
|
277
|
+
stream_for(stream_id)->reset_output();
|
|
278
|
+
shutdown_stream_write(stream_id);
|
|
279
|
+
close_stream(stream_id, error_code);
|
|
280
|
+
ngtcp2_conn_shutdown_stream_write(::Protocol::QUIC::Server::native_handle(), 0, stream_id, error_code);
|
|
281
|
+
}
|
|
128
282
|
};
|
|
129
283
|
|
|
130
284
|
}
|
|
@@ -197,8 +351,14 @@ static VALUE Ruby_Protocol_HTTP3_Server_send_packets(VALUE self)
|
|
|
197
351
|
return Qnil;
|
|
198
352
|
}
|
|
199
353
|
|
|
200
|
-
static VALUE Ruby_Protocol_HTTP3_Server_submit_response(
|
|
354
|
+
static VALUE Ruby_Protocol_HTTP3_Server_submit_response(int argc, VALUE *argv, VALUE self)
|
|
201
355
|
{
|
|
356
|
+
VALUE stream_id;
|
|
357
|
+
VALUE headers;
|
|
358
|
+
VALUE body = Qnil;
|
|
359
|
+
|
|
360
|
+
rb_scan_args(argc, argv, "21", &stream_id, &headers, &body);
|
|
361
|
+
|
|
202
362
|
auto server = Ruby_Protocol_HTTP3_Server_get(self);
|
|
203
363
|
auto count = RARRAY_LEN(headers);
|
|
204
364
|
std::vector<nghttp3_nv> native_headers;
|
|
@@ -221,12 +381,74 @@ static VALUE Ruby_Protocol_HTTP3_Server_submit_response(VALUE self, VALUE stream
|
|
|
221
381
|
});
|
|
222
382
|
}
|
|
223
383
|
|
|
224
|
-
|
|
384
|
+
auto ruby_server = dynamic_cast<Ruby::Protocol::HTTP3::Server *>(server);
|
|
385
|
+
|
|
386
|
+
if (!ruby_server) {
|
|
387
|
+
rb_raise(rb_eRuntimeError, "Could not get HTTP/3 server.");
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
ruby_server->submit_response_with_body(RB_NUM2LL(stream_id), native_headers.data(), native_headers.size(), body);
|
|
225
391
|
server->send_packets();
|
|
226
392
|
|
|
227
393
|
return Qnil;
|
|
228
394
|
}
|
|
229
395
|
|
|
396
|
+
static VALUE Ruby_Protocol_HTTP3_Server_write_body_chunk(VALUE self, VALUE stream_id, VALUE chunk)
|
|
397
|
+
{
|
|
398
|
+
auto server = dynamic_cast<Ruby::Protocol::HTTP3::Server *>(Ruby_Protocol_HTTP3_Server_get(self));
|
|
399
|
+
|
|
400
|
+
if (!server) {
|
|
401
|
+
rb_raise(rb_eRuntimeError, "Could not get HTTP/3 server.");
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
server->append_body(RB_NUM2LL(stream_id), chunk);
|
|
405
|
+
|
|
406
|
+
return Qnil;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
static VALUE Ruby_Protocol_HTTP3_Server_read_body_chunk(VALUE self, VALUE stream_id)
|
|
410
|
+
{
|
|
411
|
+
auto server = dynamic_cast<Ruby::Protocol::HTTP3::Server *>(Ruby_Protocol_HTTP3_Server_get(self));
|
|
412
|
+
|
|
413
|
+
if (!server) {
|
|
414
|
+
rb_raise(rb_eRuntimeError, "Could not get HTTP/3 server.");
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return server->stream_for(RB_NUM2LL(stream_id))->shift_input();
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
static VALUE Ruby_Protocol_HTTP3_Server_finish_body(VALUE self, VALUE stream_id)
|
|
421
|
+
{
|
|
422
|
+
auto server = dynamic_cast<Ruby::Protocol::HTTP3::Server *>(Ruby_Protocol_HTTP3_Server_get(self));
|
|
423
|
+
|
|
424
|
+
if (!server) {
|
|
425
|
+
rb_raise(rb_eRuntimeError, "Could not get HTTP/3 server.");
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
server->finish_body(RB_NUM2LL(stream_id));
|
|
429
|
+
|
|
430
|
+
return Qnil;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
static VALUE Ruby_Protocol_HTTP3_Server_reset_body(int argc, VALUE *argv, VALUE self)
|
|
434
|
+
{
|
|
435
|
+
VALUE stream_id;
|
|
436
|
+
VALUE error_code;
|
|
437
|
+
rb_scan_args(argc, argv, "11", &stream_id, &error_code);
|
|
438
|
+
|
|
439
|
+
auto server = dynamic_cast<Ruby::Protocol::HTTP3::Server *>(Ruby_Protocol_HTTP3_Server_get(self));
|
|
440
|
+
|
|
441
|
+
if (!server) {
|
|
442
|
+
rb_raise(rb_eRuntimeError, "Could not get HTTP/3 server.");
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
auto native_error_code = NIL_P(error_code) ? NGHTTP3_H3_INTERNAL_ERROR : RB_NUM2ULL(error_code);
|
|
446
|
+
|
|
447
|
+
server->reset_body(RB_NUM2LL(stream_id), native_error_code);
|
|
448
|
+
|
|
449
|
+
return Qnil;
|
|
450
|
+
}
|
|
451
|
+
|
|
230
452
|
void Init_Ruby_Protocol_HTTP3_Server(VALUE Protocol_HTTP3)
|
|
231
453
|
{
|
|
232
454
|
Ruby_Protocol_HTTP3_Server = rb_define_class_under(Protocol_HTTP3, "Server", rb_cObject);
|
|
@@ -235,5 +457,9 @@ void Init_Ruby_Protocol_HTTP3_Server(VALUE Protocol_HTTP3)
|
|
|
235
457
|
rb_define_method(Ruby_Protocol_HTTP3_Server, "initialize", Ruby_Protocol_HTTP3_Server_initialize, 7);
|
|
236
458
|
|
|
237
459
|
rb_define_method(Ruby_Protocol_HTTP3_Server, "send_packets", Ruby_Protocol_HTTP3_Server_send_packets, 0);
|
|
238
|
-
rb_define_method(Ruby_Protocol_HTTP3_Server, "submit_response", Ruby_Protocol_HTTP3_Server_submit_response,
|
|
460
|
+
rb_define_method(Ruby_Protocol_HTTP3_Server, "submit_response", RUBY_METHOD_FUNC(Ruby_Protocol_HTTP3_Server_submit_response), -1);
|
|
461
|
+
rb_define_method(Ruby_Protocol_HTTP3_Server, "write_body_chunk", Ruby_Protocol_HTTP3_Server_write_body_chunk, 2);
|
|
462
|
+
rb_define_method(Ruby_Protocol_HTTP3_Server, "read_body_chunk", Ruby_Protocol_HTTP3_Server_read_body_chunk, 1);
|
|
463
|
+
rb_define_method(Ruby_Protocol_HTTP3_Server, "finish_body", Ruby_Protocol_HTTP3_Server_finish_body, 1);
|
|
464
|
+
rb_define_method(Ruby_Protocol_HTTP3_Server, "reset_body", RUBY_METHOD_FUNC(Ruby_Protocol_HTTP3_Server_reset_body), -1);
|
|
239
465
|
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
#include "Stream.hpp"
|
|
2
|
+
|
|
3
|
+
#include <stdexcept>
|
|
4
|
+
|
|
5
|
+
namespace Ruby::Protocol::HTTP3 {
|
|
6
|
+
Stream::Stream(::Protocol::HTTP3::Session & session, ::Protocol::QUIC::Connection & connection, ::Protocol::QUIC::StreamID stream_id) :
|
|
7
|
+
::Protocol::HTTP3::Stream(session, connection, stream_id),
|
|
8
|
+
_reader{read_data}
|
|
9
|
+
{
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
Stream::~Stream()
|
|
13
|
+
{
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
void Stream::receive_input(const void *data, std::size_t size)
|
|
17
|
+
{
|
|
18
|
+
if (_input_finished) {
|
|
19
|
+
throw std::runtime_error("Cannot receive input after stream input has finished.");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
_input_chunks.push_back(rb_str_new(static_cast<const char *>(data), size));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
void Stream::finish_input()
|
|
26
|
+
{
|
|
27
|
+
_input_finished = true;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
void Stream::acknowledge_output(std::size_t size)
|
|
31
|
+
{
|
|
32
|
+
_acknowledged_output += size;
|
|
33
|
+
|
|
34
|
+
while (!_retained_output_chunks.empty() && _acknowledged_output >= static_cast<std::size_t>(RSTRING_LEN(_retained_output_chunks.front()))) {
|
|
35
|
+
_acknowledged_output -= static_cast<std::size_t>(RSTRING_LEN(_retained_output_chunks.front()));
|
|
36
|
+
_retained_output_chunks.pop_front();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
bool Stream::output_pending() const noexcept
|
|
41
|
+
{
|
|
42
|
+
return !_output_chunks.empty();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
void Stream::append_output(VALUE chunk)
|
|
46
|
+
{
|
|
47
|
+
if (_output_finished) {
|
|
48
|
+
throw std::runtime_error("Cannot append output after stream output has finished.");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
chunk = rb_str_to_str(chunk);
|
|
52
|
+
_output_chunks.push_back(rb_str_dup_frozen(chunk));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
void Stream::finish_output()
|
|
56
|
+
{
|
|
57
|
+
_output_finished = true;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
void Stream::reset_output()
|
|
61
|
+
{
|
|
62
|
+
_output_finished = true;
|
|
63
|
+
_output_chunks.clear();
|
|
64
|
+
clear_retained_output();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
VALUE Stream::shift_input()
|
|
68
|
+
{
|
|
69
|
+
if (_input_chunks.empty()) {
|
|
70
|
+
return Qnil;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
auto chunk = _input_chunks.front();
|
|
74
|
+
_input_chunks.pop_front();
|
|
75
|
+
|
|
76
|
+
return chunk;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
void Stream::mark()
|
|
80
|
+
{
|
|
81
|
+
for (auto chunk : _input_chunks) {
|
|
82
|
+
rb_gc_mark_movable(chunk);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
for (auto chunk : _output_chunks) {
|
|
86
|
+
rb_gc_mark_movable(chunk);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
for (auto chunk : _retained_output_chunks) {
|
|
90
|
+
rb_gc_mark_movable(chunk);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
void Stream::compact()
|
|
95
|
+
{
|
|
96
|
+
for (auto & chunk : _input_chunks) {
|
|
97
|
+
chunk = rb_gc_location(chunk);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
for (auto & chunk : _output_chunks) {
|
|
101
|
+
chunk = rb_gc_location(chunk);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
for (auto & chunk : _retained_output_chunks) {
|
|
105
|
+
chunk = rb_gc_location(chunk);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
nghttp3_ssize Stream::read_data(nghttp3_conn *connection, std::int64_t stream_id, nghttp3_vec *vectors, std::size_t vector_count, std::uint32_t *flags, void *connection_data, void *stream_data)
|
|
110
|
+
{
|
|
111
|
+
(void)connection;
|
|
112
|
+
(void)stream_id;
|
|
113
|
+
(void)connection_data;
|
|
114
|
+
|
|
115
|
+
if (vector_count == 0) {
|
|
116
|
+
return 0;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
auto stream = reinterpret_cast<Stream *>(stream_data);
|
|
120
|
+
|
|
121
|
+
if (!stream) {
|
|
122
|
+
*flags |= NGHTTP3_DATA_FLAG_EOF;
|
|
123
|
+
return 0;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (stream->_output_chunks.empty()) {
|
|
127
|
+
if (!stream->_output_finished) {
|
|
128
|
+
return NGHTTP3_ERR_WOULDBLOCK;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
*flags |= NGHTTP3_DATA_FLAG_EOF;
|
|
132
|
+
return 0;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
stream->_retained_output_chunks.push_back(stream->_output_chunks.front());
|
|
136
|
+
stream->_output_chunks.pop_front();
|
|
137
|
+
|
|
138
|
+
auto chunk = stream->_retained_output_chunks.back();
|
|
139
|
+
|
|
140
|
+
vectors[0].base = reinterpret_cast<std::uint8_t *>(RSTRING_PTR(chunk));
|
|
141
|
+
vectors[0].len = RSTRING_LEN(chunk);
|
|
142
|
+
|
|
143
|
+
if (stream->_output_finished && stream->_output_chunks.empty()) {
|
|
144
|
+
*flags |= NGHTTP3_DATA_FLAG_EOF;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return 1;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
void Stream::clear_retained_output()
|
|
151
|
+
{
|
|
152
|
+
_retained_output_chunks.clear();
|
|
153
|
+
_acknowledged_output = 0;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <ruby.h>
|
|
4
|
+
|
|
5
|
+
#include <Protocol/HTTP3/Stream.hpp>
|
|
6
|
+
|
|
7
|
+
#include <deque>
|
|
8
|
+
|
|
9
|
+
namespace Ruby::Protocol::HTTP3 {
|
|
10
|
+
class Stream : public ::Protocol::HTTP3::Stream {
|
|
11
|
+
public:
|
|
12
|
+
Stream(::Protocol::HTTP3::Session & session, ::Protocol::QUIC::Connection & connection, ::Protocol::QUIC::StreamID stream_id);
|
|
13
|
+
virtual ~Stream();
|
|
14
|
+
|
|
15
|
+
nghttp3_data_reader * reader() noexcept {return &_reader;}
|
|
16
|
+
|
|
17
|
+
void receive_input(const void *data, std::size_t size) override;
|
|
18
|
+
void finish_input() override;
|
|
19
|
+
|
|
20
|
+
void acknowledge_output(std::size_t size) override;
|
|
21
|
+
|
|
22
|
+
bool output_pending() const noexcept;
|
|
23
|
+
void append_output(VALUE chunk);
|
|
24
|
+
void finish_output();
|
|
25
|
+
void reset_output();
|
|
26
|
+
|
|
27
|
+
VALUE shift_input();
|
|
28
|
+
|
|
29
|
+
void mark();
|
|
30
|
+
void compact();
|
|
31
|
+
|
|
32
|
+
static nghttp3_ssize read_data(nghttp3_conn *connection, std::int64_t stream_id, nghttp3_vec *vectors, std::size_t vector_count, std::uint32_t *flags, void *connection_data, void *stream_data);
|
|
33
|
+
|
|
34
|
+
private:
|
|
35
|
+
nghttp3_data_reader _reader;
|
|
36
|
+
|
|
37
|
+
std::deque<VALUE> _input_chunks;
|
|
38
|
+
std::deque<VALUE> _output_chunks;
|
|
39
|
+
std::deque<VALUE> _retained_output_chunks;
|
|
40
|
+
|
|
41
|
+
std::size_t _acknowledged_output = 0;
|
|
42
|
+
bool _input_finished = false;
|
|
43
|
+
bool _output_finished = false;
|
|
44
|
+
|
|
45
|
+
void clear_retained_output();
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2026, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
module Protocol::HTTP3
|
|
7
|
+
# A single HTTP/3 request or response body stream.
|
|
8
|
+
class Stream
|
|
9
|
+
# Initialize the stream wrapper for the given connection and native stream identifier.
|
|
10
|
+
def initialize(connection, stream_id)
|
|
11
|
+
@connection = connection
|
|
12
|
+
@stream_id = stream_id
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
attr :connection
|
|
16
|
+
attr :stream_id
|
|
17
|
+
|
|
18
|
+
# Read the next available body chunk from the stream.
|
|
19
|
+
def read_chunk
|
|
20
|
+
@connection.__send__(:read_body_chunk, @stream_id)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Write a body chunk to the stream.
|
|
24
|
+
def write_chunk(chunk)
|
|
25
|
+
@connection.__send__(:write_body_chunk, @stream_id, chunk)
|
|
26
|
+
@connection.send_packets
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Finish the stream after all body chunks have been written.
|
|
30
|
+
def finish
|
|
31
|
+
@connection.__send__(:finish_body, @stream_id)
|
|
32
|
+
@connection.send_packets
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Reset the stream with the optional HTTP/3 error code.
|
|
36
|
+
def reset(error_code = nil)
|
|
37
|
+
@connection.__send__(:reset_body, @stream_id, error_code)
|
|
38
|
+
@connection.send_packets
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Write all chunks from the given body to the stream.
|
|
42
|
+
def write_body(body)
|
|
43
|
+
error = nil
|
|
44
|
+
|
|
45
|
+
begin
|
|
46
|
+
while chunk = body.read
|
|
47
|
+
write_chunk(chunk)
|
|
48
|
+
end
|
|
49
|
+
rescue => error
|
|
50
|
+
reset
|
|
51
|
+
raise
|
|
52
|
+
ensure
|
|
53
|
+
body.close(error)
|
|
54
|
+
finish unless error
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
data/lib/protocol/http3.rb
CHANGED
|
@@ -9,3 +9,39 @@ require_relative "http3/version"
|
|
|
9
9
|
|
|
10
10
|
# Native extension:
|
|
11
11
|
require "Ruby_Protocol_HTTP3"
|
|
12
|
+
|
|
13
|
+
require_relative "http3/stream"
|
|
14
|
+
|
|
15
|
+
module Protocol::HTTP3
|
|
16
|
+
# An HTTP/3 client connection.
|
|
17
|
+
class Client
|
|
18
|
+
alias native_submit_request submit_request
|
|
19
|
+
|
|
20
|
+
# Submit a request and return the stream used for the response body.
|
|
21
|
+
def submit_request(headers, body = nil)
|
|
22
|
+
Stream.new(self, native_submit_request(headers, body))
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private :read_body_chunk
|
|
26
|
+
private :write_body_chunk
|
|
27
|
+
private :finish_body
|
|
28
|
+
private :reset_body
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# An HTTP/3 server connection.
|
|
32
|
+
class Server
|
|
33
|
+
alias native_submit_response submit_response
|
|
34
|
+
|
|
35
|
+
# Submit a response for the given stream and return the stream used for the response body.
|
|
36
|
+
def submit_response(stream_id, headers, body = nil)
|
|
37
|
+
Stream.new(self, stream_id).tap do
|
|
38
|
+
native_submit_response(stream_id, headers, body)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private :read_body_chunk
|
|
43
|
+
private :write_body_chunk
|
|
44
|
+
private :finish_body
|
|
45
|
+
private :reset_body
|
|
46
|
+
end
|
|
47
|
+
end
|
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: protocol-http3
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Samuel Williams
|
|
@@ -38,20 +38,34 @@ cert_chain:
|
|
|
38
38
|
-----END CERTIFICATE-----
|
|
39
39
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
40
40
|
dependencies:
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: protocol-http
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '0.62'
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '0.62'
|
|
41
55
|
- !ruby/object:Gem::Dependency
|
|
42
56
|
name: protocol-quic
|
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
|
44
58
|
requirements:
|
|
45
59
|
- - "~>"
|
|
46
60
|
- !ruby/object:Gem::Version
|
|
47
|
-
version: 0.0.
|
|
61
|
+
version: 0.0.9
|
|
48
62
|
type: :runtime
|
|
49
63
|
prerelease: false
|
|
50
64
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
65
|
requirements:
|
|
52
66
|
- - "~>"
|
|
53
67
|
- !ruby/object:Gem::Version
|
|
54
|
-
version: 0.0.
|
|
68
|
+
version: 0.0.9
|
|
55
69
|
- !ruby/object:Gem::Dependency
|
|
56
70
|
name: teapot
|
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -95,9 +109,12 @@ files:
|
|
|
95
109
|
- ext/source/Ruby/Protocol/HTTP3/Dispatcher.hpp
|
|
96
110
|
- ext/source/Ruby/Protocol/HTTP3/Server.cpp
|
|
97
111
|
- ext/source/Ruby/Protocol/HTTP3/Server.hpp
|
|
112
|
+
- ext/source/Ruby/Protocol/HTTP3/Stream.cpp
|
|
113
|
+
- ext/source/Ruby/Protocol/HTTP3/Stream.hpp
|
|
98
114
|
- ext/source/Ruby/Protocol/QUIC/Bindings.hpp
|
|
99
115
|
- ext/teapot.rb
|
|
100
116
|
- lib/protocol/http3.rb
|
|
117
|
+
- lib/protocol/http3/stream.rb
|
|
101
118
|
- lib/protocol/http3/version.rb
|
|
102
119
|
- license.md
|
|
103
120
|
- readme.md
|
metadata.gz.sig
CHANGED
|
Binary file
|