http3 0.0.3
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 +7 -0
- data/.clang-format +1 -0
- data/.vscode/extensions.json +6 -0
- data/.vscode/settings.json +10 -0
- data/CHANGELOG.md +34 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE-DEPENDENCIES.txt +66 -0
- data/LICENSE.txt +21 -0
- data/README.md +127 -0
- data/Rakefile +20 -0
- data/ext/http3/extconf.rb +48 -0
- data/ext/http3/nghttp3.c +347 -0
- data/ext/http3/nghttp3.h +105 -0
- data/ext/http3/nghttp3_callbacks.c +621 -0
- data/ext/http3/nghttp3_connection.c +1303 -0
- data/ext/http3/nghttp3_nv.c +87 -0
- data/ext/http3/nghttp3_qpack.c +656 -0
- data/ext/http3/nghttp3_settings.c +188 -0
- data/ext/http3/session.c +1017 -0
- data/lib/http3/rack_adapter.rb +162 -0
- data/lib/http3/request.rb +9 -0
- data/lib/http3/response.rb +21 -0
- data/lib/http3/server.rb +357 -0
- data/lib/http3/session.rb +193 -0
- data/lib/http3/version.rb +5 -0
- data/lib/http3.rb +16 -0
- data/sig/http3/nghttp3/callbacks.rbs +32 -0
- data/sig/http3/nghttp3/connection.rbs +101 -0
- data/sig/http3/nghttp3/error.rbs +48 -0
- data/sig/http3/nghttp3/info.rbs +9 -0
- data/sig/http3/nghttp3/nv.rbs +11 -0
- data/sig/http3/nghttp3/qpack.rbs +48 -0
- data/sig/http3/nghttp3/settings.rbs +27 -0
- data/sig/http3/rack_adapter.rbs +18 -0
- data/sig/http3/request.rbs +11 -0
- data/sig/http3/response.rbs +30 -0
- data/sig/http3/server.rbs +32 -0
- data/sig/http3/session.rbs +52 -0
- data/sig/http3.rbs +66 -0
- metadata +140 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HTTP3
|
|
4
|
+
# HTTP/3 on one QUIC connection. The C part (ext/http3/session.c) runs
|
|
5
|
+
# nghttp3 inside the connection's packet I/O and calls the on_* methods
|
|
6
|
+
# below with the parts of each request. This part puts the requests
|
|
7
|
+
# together and keeps the bodies of the responses being sent.
|
|
8
|
+
#
|
|
9
|
+
# A Session belongs to the server's I/O loop: Server takes the finished
|
|
10
|
+
# requests, answers them, keeps the bodies flowing and lets go of what
|
|
11
|
+
# the streams no longer need.
|
|
12
|
+
class Session
|
|
13
|
+
# A request still coming in. reject is the status to answer with
|
|
14
|
+
# instead of calling the application (413, 431), or nil.
|
|
15
|
+
Incoming = Struct.new(:pseudo, :fields, :body, :reject)
|
|
16
|
+
|
|
17
|
+
# A response whose body is being sent.
|
|
18
|
+
Outgoing = Struct.new(:request, :body, :done)
|
|
19
|
+
|
|
20
|
+
# Take over quic_conn's streams. A request body over max_request_body
|
|
21
|
+
# bytes is answered with 413, a header section over
|
|
22
|
+
# max_field_section_size bytes (RFC 9114 section 4.2.2) with 431.
|
|
23
|
+
def self.attach(quic_conn, max_request_body:, max_field_section_size:)
|
|
24
|
+
session = _attach(quic_conn, max_field_section_size)
|
|
25
|
+
session.send(:setup, max_request_body)
|
|
26
|
+
session
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# The requests received in full since the last call, each as
|
|
30
|
+
# [request, status]: status is nil for the application to answer the
|
|
31
|
+
# request, or the status to answer it with directly.
|
|
32
|
+
def take_requests
|
|
33
|
+
done = @done
|
|
34
|
+
@done = []
|
|
35
|
+
done
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Send response to request. Its body, if any, is kept until the stream
|
|
39
|
+
# closes, but the IO of a Body::File is closed at once: the C part reads
|
|
40
|
+
# the file through a descriptor of its own. When that descriptor cannot
|
|
41
|
+
# be had (EMFILE, say), the stream is aborted with H3_INTERNAL_ERROR and
|
|
42
|
+
# the block gets the exception.
|
|
43
|
+
def respond(request, response)
|
|
44
|
+
headers = [[":status", response.status.to_s], *response.headers]
|
|
45
|
+
body = response.body
|
|
46
|
+
submit_response(request.stream_id, headers, !body.nil?)
|
|
47
|
+
return if body.nil?
|
|
48
|
+
|
|
49
|
+
@outgoing[request.stream_id] = Outgoing.new(request, body, false)
|
|
50
|
+
return unless body.is_a?(Body::File)
|
|
51
|
+
|
|
52
|
+
begin
|
|
53
|
+
attach_file(request.stream_id, body.io, body.offset, body.length)
|
|
54
|
+
rescue SystemCallError => e
|
|
55
|
+
abort_stream(request.stream_id, Nghttp3::H3_INTERNAL_ERROR)
|
|
56
|
+
yield e if block_given?
|
|
57
|
+
ensure
|
|
58
|
+
body.io.close
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Queue more of each Body::Chunks body until limit bytes wait unsent on
|
|
63
|
+
# its stream. A body whose enum raises is the application failing after
|
|
64
|
+
# its headers went out: the stream is aborted with H3_INTERNAL_ERROR and
|
|
65
|
+
# the block gets (request, exception).
|
|
66
|
+
def fill_bodies(limit)
|
|
67
|
+
@outgoing.each_value do |out|
|
|
68
|
+
next if out.done || !out.body.is_a?(Body::Chunks)
|
|
69
|
+
|
|
70
|
+
fill_body(out, limit) { |error| yield out.request, error if block_given? }
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# True when a Body::Chunks body could take more right now.
|
|
75
|
+
def wants_fill?(limit)
|
|
76
|
+
@outgoing.any? do |id, out|
|
|
77
|
+
!out.done && out.body.is_a?(Body::Chunks) && unsent_bytes(id) < limit
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# True while a request stream is open: received in part, waiting for
|
|
82
|
+
# its answer, or with a response still being sent.
|
|
83
|
+
def busy?
|
|
84
|
+
!@open.empty?
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Let go of the streams nghttp3 closed: their requests are dropped and
|
|
88
|
+
# their bodies closed. The block gets (stream_id, exception) for a body
|
|
89
|
+
# whose close raised.
|
|
90
|
+
def release_closed(&block)
|
|
91
|
+
take_closed.each { |id| release(id, &block) }
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# The connection is gone: close every body still held.
|
|
95
|
+
def release_all(&block)
|
|
96
|
+
(@open.keys | @outgoing.keys).each { |id| release(id, &block) }
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Called by the C part.
|
|
100
|
+
|
|
101
|
+
def on_begin_headers(id)
|
|
102
|
+
@open[id] = true
|
|
103
|
+
@incoming[id] = Incoming.new([], [], "".b, nil)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def on_header(id, name, value)
|
|
107
|
+
request = @incoming[id] or return
|
|
108
|
+
(name.start_with?(":") ? request.pseudo : request.fields) << [name, value]
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def on_fields_too_large(id)
|
|
112
|
+
request = @incoming[id] or return
|
|
113
|
+
request.reject = 431
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def on_end_headers(id)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def on_data(id, bytes)
|
|
120
|
+
request = @incoming[id] or return
|
|
121
|
+
return if request.reject
|
|
122
|
+
|
|
123
|
+
if request.body.bytesize + bytes.bytesize > @max_request_body
|
|
124
|
+
request.reject = 413
|
|
125
|
+
request.body = "".b
|
|
126
|
+
else
|
|
127
|
+
request.body << bytes
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def on_end_stream(id)
|
|
132
|
+
request = @incoming.delete(id) or return
|
|
133
|
+
headers = request.pseudo + request.fields
|
|
134
|
+
@done << [Request.new(stream_id: id, headers: headers, body: request.body), request.reject]
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
private
|
|
138
|
+
|
|
139
|
+
def setup(max_request_body)
|
|
140
|
+
@max_request_body = max_request_body
|
|
141
|
+
@incoming = {}
|
|
142
|
+
@done = []
|
|
143
|
+
@outgoing = {}
|
|
144
|
+
@open = {}
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def fill_body(out, limit)
|
|
148
|
+
id = out.request.stream_id
|
|
149
|
+
while unsent_bytes(id) < limit
|
|
150
|
+
begin
|
|
151
|
+
chunk = out.body.enum.next
|
|
152
|
+
rescue StopIteration
|
|
153
|
+
out.done = true
|
|
154
|
+
push_eof(id)
|
|
155
|
+
return
|
|
156
|
+
rescue => e
|
|
157
|
+
out.done = true
|
|
158
|
+
abort_stream(id, Nghttp3::H3_INTERNAL_ERROR)
|
|
159
|
+
yield e
|
|
160
|
+
return
|
|
161
|
+
end
|
|
162
|
+
unless chunk.is_a?(String)
|
|
163
|
+
out.done = true
|
|
164
|
+
abort_stream(id, Nghttp3::H3_INTERNAL_ERROR)
|
|
165
|
+
yield TypeError.new("body chunk must be a String (got #{chunk.class})")
|
|
166
|
+
return
|
|
167
|
+
end
|
|
168
|
+
push_chunk(id, chunk)
|
|
169
|
+
end
|
|
170
|
+
rescue Nghttp3::Error
|
|
171
|
+
# The stream went away under the body (the peer reset it); it is
|
|
172
|
+
# released when its close comes through.
|
|
173
|
+
out.done = true
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def release(id)
|
|
177
|
+
@open.delete(id)
|
|
178
|
+
@incoming.delete(id)
|
|
179
|
+
@done.reject! { |request, _| request.stream_id == id }
|
|
180
|
+
out = @outgoing.delete(id) or return
|
|
181
|
+
|
|
182
|
+
close_body(out.body)
|
|
183
|
+
rescue => e
|
|
184
|
+
yield id, e if block_given?
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def close_body(body)
|
|
188
|
+
body.io.close if body.is_a?(Body::File) && !body.io.closed?
|
|
189
|
+
ensure
|
|
190
|
+
body.rack_body.close if body.rack_body.respond_to?(:close)
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
data/lib/http3.rb
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "http3/version"
|
|
4
|
+
require_relative "http3/http3"
|
|
5
|
+
|
|
6
|
+
require_relative "http3/request"
|
|
7
|
+
require_relative "http3/response"
|
|
8
|
+
require_relative "http3/session"
|
|
9
|
+
require_relative "http3/rack_adapter"
|
|
10
|
+
require_relative "http3/server"
|
|
11
|
+
|
|
12
|
+
module HTTP3
|
|
13
|
+
module Nghttp3
|
|
14
|
+
class Error < StandardError; end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module HTTP3
|
|
2
|
+
module Nghttp3
|
|
3
|
+
class Callbacks
|
|
4
|
+
def initialize: () -> void
|
|
5
|
+
|
|
6
|
+
# Stream data callbacks
|
|
7
|
+
def on_acked_stream_data: () { (Integer stream_id, Integer datalen) -> void } -> self
|
|
8
|
+
def on_stream_close: () { (Integer stream_id, Integer app_error_code) -> void } -> self
|
|
9
|
+
def on_recv_data: () { (Integer stream_id, String data) -> void } -> self
|
|
10
|
+
def on_deferred_consume: () { (Integer stream_id, Integer consumed) -> void } -> self
|
|
11
|
+
|
|
12
|
+
# Header callbacks
|
|
13
|
+
def on_begin_headers: () { (Integer stream_id) -> void } -> self
|
|
14
|
+
def on_recv_header: () { (Integer stream_id, String name, String value, Integer flags) -> void } -> self
|
|
15
|
+
def on_end_headers: () { (Integer stream_id, bool fin) -> void } -> self
|
|
16
|
+
|
|
17
|
+
# Trailer callbacks
|
|
18
|
+
def on_begin_trailers: () { (Integer stream_id) -> void } -> self
|
|
19
|
+
def on_recv_trailer: () { (Integer stream_id, String name, String value, Integer flags) -> void } -> self
|
|
20
|
+
def on_end_trailers: () { (Integer stream_id, bool fin) -> void } -> self
|
|
21
|
+
|
|
22
|
+
# Stream control callbacks
|
|
23
|
+
def on_stop_sending: () { (Integer stream_id, Integer app_error_code) -> void } -> self
|
|
24
|
+
def on_end_stream: () { (Integer stream_id) -> void } -> self
|
|
25
|
+
def on_reset_stream: () { (Integer stream_id, Integer app_error_code) -> void } -> self
|
|
26
|
+
|
|
27
|
+
# Connection callbacks
|
|
28
|
+
def on_shutdown: () { (Integer id) -> void } -> self
|
|
29
|
+
def on_recv_settings: () { (Hash[Symbol, untyped] settings) -> void } -> self
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
module HTTP3
|
|
2
|
+
module Nghttp3
|
|
3
|
+
class Connection
|
|
4
|
+
# Creates a new client connection
|
|
5
|
+
def self.client_new: (?Settings? settings, ?Callbacks? callbacks) -> Connection
|
|
6
|
+
|
|
7
|
+
# Creates a new server connection
|
|
8
|
+
def self.server_new: (?Settings? settings, ?Callbacks? callbacks) -> Connection
|
|
9
|
+
|
|
10
|
+
# Binds the control stream
|
|
11
|
+
def bind_control_stream: (Integer stream_id) -> self
|
|
12
|
+
|
|
13
|
+
# Binds QPACK encoder and decoder streams
|
|
14
|
+
def bind_qpack_streams: (Integer encoder_stream_id, Integer decoder_stream_id) -> self
|
|
15
|
+
|
|
16
|
+
# Closes the connection
|
|
17
|
+
def close: () -> nil
|
|
18
|
+
|
|
19
|
+
# Returns true if the connection is closed
|
|
20
|
+
def closed?: () -> bool
|
|
21
|
+
|
|
22
|
+
# Returns true if this is a server connection
|
|
23
|
+
def server?: () -> bool
|
|
24
|
+
|
|
25
|
+
# Returns true if this is a client connection
|
|
26
|
+
def client?: () -> bool
|
|
27
|
+
|
|
28
|
+
# Stream operations
|
|
29
|
+
|
|
30
|
+
# Reads data on a stream from the QUIC layer. ts is the time in
|
|
31
|
+
# nanoseconds on a monotonic clock (CLOCK_MONOTONIC by default). An
|
|
32
|
+
# exception raised by a callback comes out of here.
|
|
33
|
+
def read_stream: (Integer stream_id, String data, ?fin: bool, ?ts: Integer?) -> Integer
|
|
34
|
+
|
|
35
|
+
# Gets stream data to send to the QUIC layer. An exception raised by a
|
|
36
|
+
# body block comes out of here.
|
|
37
|
+
def writev_stream: () -> { stream_id: Integer, fin: bool, data: String }?
|
|
38
|
+
|
|
39
|
+
# Tells the connection that n bytes have been accepted by the QUIC layer
|
|
40
|
+
def add_write_offset: (Integer stream_id, Integer n) -> self
|
|
41
|
+
|
|
42
|
+
# Tells the connection that n bytes have been acknowledged by the remote peer
|
|
43
|
+
def add_ack_offset: (Integer stream_id, Integer n) -> self
|
|
44
|
+
|
|
45
|
+
# Marks a stream as blocked due to QUIC flow control
|
|
46
|
+
def block_stream: (Integer stream_id) -> self
|
|
47
|
+
|
|
48
|
+
# Marks a stream as unblocked
|
|
49
|
+
def unblock_stream: (Integer stream_id) -> self
|
|
50
|
+
|
|
51
|
+
# Returns true if the stream is writable
|
|
52
|
+
def stream_writable?: (Integer stream_id) -> bool
|
|
53
|
+
|
|
54
|
+
# Closes the stream with the given error code
|
|
55
|
+
def close_stream: (Integer stream_id, Integer app_error_code) -> self
|
|
56
|
+
|
|
57
|
+
# Prevents any further write operations on the stream
|
|
58
|
+
def shutdown_stream_write: (Integer stream_id) -> self
|
|
59
|
+
|
|
60
|
+
# Resumes a stream that was blocked for input data
|
|
61
|
+
def resume_stream: (Integer stream_id) -> self
|
|
62
|
+
|
|
63
|
+
# Tells the connection that no more data will be read on the stream
|
|
64
|
+
def shutdown_stream_read: (Integer stream_id) -> self
|
|
65
|
+
|
|
66
|
+
# Tells a server connection how many bidirectional streams the client
|
|
67
|
+
# may open in total
|
|
68
|
+
def set_max_client_streams_bidi: (Integer max_streams) -> self
|
|
69
|
+
|
|
70
|
+
# The body Strings handed to nghttp3 that the peer has not acknowledged
|
|
71
|
+
# in full yet, oldest first (a copy)
|
|
72
|
+
def pending_data: (Integer stream_id) -> Array[String]
|
|
73
|
+
|
|
74
|
+
# HTTP operations
|
|
75
|
+
|
|
76
|
+
# Submits an HTTP request (client only)
|
|
77
|
+
def submit_request: (Integer stream_id, Array[NV] headers, ?body: String?) ?{ (Integer stream_id) -> (String | Symbol | nil) } -> self
|
|
78
|
+
|
|
79
|
+
# Submits an HTTP response (server only)
|
|
80
|
+
def submit_response: (Integer stream_id, Array[NV] headers, ?body: String?) ?{ (Integer stream_id) -> (String | Symbol | nil) } -> self
|
|
81
|
+
|
|
82
|
+
# Submits a 1xx informational response
|
|
83
|
+
def submit_info: (Integer stream_id, Array[NV] headers) -> self
|
|
84
|
+
|
|
85
|
+
# Submits trailer headers (implicitly ends the stream)
|
|
86
|
+
def submit_trailers: (Integer stream_id, Array[NV] trailers) -> self
|
|
87
|
+
|
|
88
|
+
# Signals the intention to shut down the connection gracefully
|
|
89
|
+
def submit_shutdown_notice: () -> self
|
|
90
|
+
|
|
91
|
+
# Initiates graceful shutdown
|
|
92
|
+
def shutdown: () -> self
|
|
93
|
+
|
|
94
|
+
# Associates arbitrary data with a stream
|
|
95
|
+
def set_stream_user_data: (Integer stream_id, untyped data) -> self
|
|
96
|
+
|
|
97
|
+
# Returns data associated with a stream
|
|
98
|
+
def get_stream_user_data: (Integer stream_id) -> untyped
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module HTTP3
|
|
2
|
+
module Nghttp3
|
|
3
|
+
class Error < StandardError
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
class InvalidArgumentError < Error
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
class InvalidStateError < Error
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class WouldBlockError < Error
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class StreamInUseError < Error
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class MalformedHTTPHeaderError < Error
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
class MalformedHTTPMessagingError < Error
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class QPACKFatalError < Error
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
class QPACKHeaderTooLargeError < Error
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
class StreamNotFoundError < Error
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
class ConnClosingError < Error
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
class StreamDataOverflowError < Error
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
class FatalError < Error
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
class NoMemError < Error
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
class CallbackFailureError < Error
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module HTTP3
|
|
2
|
+
module Nghttp3
|
|
3
|
+
module QPACK
|
|
4
|
+
class Encoder
|
|
5
|
+
# Creates a new QPACK encoder
|
|
6
|
+
def initialize: (Integer max_dtable_capacity) -> void
|
|
7
|
+
|
|
8
|
+
# Encodes headers for a given stream
|
|
9
|
+
def encode: (Integer stream_id, Array[NV] headers) -> { prefix: String, data: String, encoder_stream: String }
|
|
10
|
+
|
|
11
|
+
# Reads decoder stream data
|
|
12
|
+
def read_decoder: (String data) -> Integer
|
|
13
|
+
|
|
14
|
+
# Sets the maximum dynamic table capacity
|
|
15
|
+
def max_dtable_capacity=: (Integer capacity) -> Integer
|
|
16
|
+
|
|
17
|
+
# Sets the maximum number of blocked streams
|
|
18
|
+
def max_blocked_streams=: (Integer num) -> Integer
|
|
19
|
+
|
|
20
|
+
# Returns the number of currently blocked streams
|
|
21
|
+
def num_blocked_streams: () -> Integer
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class Decoder
|
|
25
|
+
# Creates a new QPACK decoder
|
|
26
|
+
def initialize: (Integer max_dtable_capacity, Integer max_blocked_streams) -> void
|
|
27
|
+
|
|
28
|
+
# Decodes headers from a stream
|
|
29
|
+
def decode: (Integer stream_id, String data, ?fin: bool) -> { headers: Array[{ name: String, value: String, token: Integer }]?, blocked: bool, consumed: Integer }
|
|
30
|
+
|
|
31
|
+
# Reads encoder stream data
|
|
32
|
+
def read_encoder: (String data) -> Integer
|
|
33
|
+
|
|
34
|
+
# Returns data to be sent on the decoder stream
|
|
35
|
+
def decoder_stream_data: () -> String
|
|
36
|
+
|
|
37
|
+
# Cancels decoding for the given stream
|
|
38
|
+
def cancel_stream: (Integer stream_id) -> void
|
|
39
|
+
|
|
40
|
+
# Sets the maximum dynamic table capacity
|
|
41
|
+
def max_dtable_capacity=: (Integer capacity) -> Integer
|
|
42
|
+
|
|
43
|
+
# Returns the current insert count
|
|
44
|
+
def insert_count: () -> Integer
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module HTTP3
|
|
2
|
+
module Nghttp3
|
|
3
|
+
class Settings
|
|
4
|
+
def self.default: () -> Settings
|
|
5
|
+
|
|
6
|
+
def initialize: () -> void
|
|
7
|
+
|
|
8
|
+
def max_field_section_size: () -> Integer
|
|
9
|
+
def max_field_section_size=: (Integer value) -> Integer
|
|
10
|
+
|
|
11
|
+
def qpack_max_dtable_capacity: () -> Integer
|
|
12
|
+
def qpack_max_dtable_capacity=: (Integer value) -> Integer
|
|
13
|
+
|
|
14
|
+
def qpack_encoder_max_dtable_capacity: () -> Integer
|
|
15
|
+
def qpack_encoder_max_dtable_capacity=: (Integer value) -> Integer
|
|
16
|
+
|
|
17
|
+
def qpack_blocked_streams: () -> Integer
|
|
18
|
+
def qpack_blocked_streams=: (Integer value) -> Integer
|
|
19
|
+
|
|
20
|
+
def enable_connect_protocol: () -> bool
|
|
21
|
+
def enable_connect_protocol=: (boolish value) -> boolish
|
|
22
|
+
|
|
23
|
+
def h3_datagram: () -> bool
|
|
24
|
+
def h3_datagram=: (boolish value) -> boolish
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module HTTP3
|
|
2
|
+
# Calls a Rack 3 application for a Request and turns its answer into a
|
|
3
|
+
# Response.
|
|
4
|
+
class RackAdapter
|
|
5
|
+
class InvalidResponse < StandardError
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
CONNECTION_FIELDS: Array[String]
|
|
9
|
+
FIELD_NAME: Regexp
|
|
10
|
+
FORBIDDEN_IN_VALUE: Regexp
|
|
11
|
+
|
|
12
|
+
def initialize: (untyped app) -> void
|
|
13
|
+
|
|
14
|
+
# Raises what the application raises, and InvalidResponse for an
|
|
15
|
+
# answer that cannot be sent; the Rack body is closed either way.
|
|
16
|
+
def call: (Request request, remote: Addrinfo) -> Response
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
module HTTP3
|
|
2
|
+
# A request as a Session put it together. headers holds [name, value]
|
|
3
|
+
# pairs with lowercase names, the pseudo-headers first.
|
|
4
|
+
class Request < Data
|
|
5
|
+
attr_reader stream_id: Integer
|
|
6
|
+
attr_reader headers: Array[[String, String]]
|
|
7
|
+
attr_reader body: String
|
|
8
|
+
|
|
9
|
+
def self.new: (stream_id: Integer, headers: Array[[String, String]], body: String) -> Request
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module HTTP3
|
|
2
|
+
# A response for a Session to send; headers exclude pseudo-headers.
|
|
3
|
+
class Response < Data
|
|
4
|
+
attr_reader status: Integer
|
|
5
|
+
attr_reader headers: Array[[String, String]]
|
|
6
|
+
attr_reader body: Body::File | Body::Chunks | nil
|
|
7
|
+
|
|
8
|
+
def self.new: (status: Integer, headers: Array[[String, String]], body: Body::File | Body::Chunks | nil) -> Response
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
module Body
|
|
12
|
+
# length bytes of io from offset, read by the C part of the Session.
|
|
13
|
+
class File < Data
|
|
14
|
+
attr_reader io: ::File
|
|
15
|
+
attr_reader offset: Integer
|
|
16
|
+
attr_reader length: Integer
|
|
17
|
+
attr_reader rack_body: untyped
|
|
18
|
+
|
|
19
|
+
def self.new: (io: ::File, offset: Integer, length: Integer, rack_body: untyped) -> File
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Chunks pulled with enum.next as the send buffer drains.
|
|
23
|
+
class Chunks < Data
|
|
24
|
+
attr_reader enum: Enumerator[String, untyped]
|
|
25
|
+
attr_reader rack_body: untyped
|
|
26
|
+
|
|
27
|
+
def self.new: (enum: Enumerator[String, untyped], rack_body: untyped) -> Chunks
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module HTTP3
|
|
2
|
+
# An HTTP/3 server for a Rack 3 application on one UDP address. #run
|
|
3
|
+
# serves until SIGTERM or SIGINT, then drains; SIGHUP reloads the
|
|
4
|
+
# certificate and key.
|
|
5
|
+
class Server
|
|
6
|
+
ALPN: Array[String]
|
|
7
|
+
SIGNALS: Hash[String, String]
|
|
8
|
+
RECV_SIZE: Integer
|
|
9
|
+
RECV_BATCH: Integer
|
|
10
|
+
|
|
11
|
+
# The address #run bound; nil until then.
|
|
12
|
+
attr_reader local_address: Addrinfo?
|
|
13
|
+
|
|
14
|
+
def initialize: (
|
|
15
|
+
app: untyped,
|
|
16
|
+
bind: String,
|
|
17
|
+
certificate_path: String | _ToPath,
|
|
18
|
+
private_key_path: String | _ToPath,
|
|
19
|
+
?transport_params: QUIC::TransportParams?,
|
|
20
|
+
?settings: QUIC::Settings?,
|
|
21
|
+
?qlog_dir: (String | _ToPath)?,
|
|
22
|
+
?max_connections: Integer,
|
|
23
|
+
?max_request_body: Integer,
|
|
24
|
+
?max_field_section_size: Integer,
|
|
25
|
+
?send_buffer_size: Integer,
|
|
26
|
+
?shutdown_timeout: Numeric,
|
|
27
|
+
?logger: Logger
|
|
28
|
+
) -> void
|
|
29
|
+
|
|
30
|
+
def run: () -> nil
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module HTTP3
|
|
2
|
+
# HTTP/3 on one QUIC connection, driven by HTTP3::Server's I/O loop. The
|
|
3
|
+
# on_* methods are called by the C part with the parts of each request.
|
|
4
|
+
class Session
|
|
5
|
+
# Take over quic_conn's streams. Requests over max_request_body bytes of
|
|
6
|
+
# body get 413, over max_field_section_size bytes of headers 431.
|
|
7
|
+
def self.attach: (QUIC::Connection::Server quic_conn, max_request_body: Integer, max_field_section_size: Integer) -> Session
|
|
8
|
+
|
|
9
|
+
def self._attach: (QUIC::Connection::Server quic_conn, Integer max_field_section_size) -> Session
|
|
10
|
+
|
|
11
|
+
# Finished requests, each with the status to answer it with directly
|
|
12
|
+
# (413, 431) or nil for the application to answer it.
|
|
13
|
+
def take_requests: () -> Array[[Request, Integer?]]
|
|
14
|
+
|
|
15
|
+
# Send response. Yields the exception of a file body whose descriptor
|
|
16
|
+
# could not be duplicated; its stream is aborted.
|
|
17
|
+
def respond: (Request request, Response response) ?{ (SystemCallError error) -> void } -> void
|
|
18
|
+
|
|
19
|
+
# Queue more of each Body::Chunks body, up to limit unsent bytes per
|
|
20
|
+
# stream. Yields the request and exception of a body that raised.
|
|
21
|
+
def fill_bodies: (Integer limit) ?{ (Request request, Exception error) -> void } -> void
|
|
22
|
+
|
|
23
|
+
def wants_fill?: (Integer limit) -> bool
|
|
24
|
+
|
|
25
|
+
# True while a request stream is open.
|
|
26
|
+
def busy?: () -> bool
|
|
27
|
+
|
|
28
|
+
# Let go of closed streams; yields a body close that raised.
|
|
29
|
+
def release_closed: () ?{ (Integer stream_id, Exception error) -> void } -> void
|
|
30
|
+
|
|
31
|
+
# Close every body still held.
|
|
32
|
+
def release_all: () ?{ (Integer stream_id, Exception error) -> void } -> void
|
|
33
|
+
|
|
34
|
+
def on_begin_headers: (Integer id) -> void
|
|
35
|
+
def on_header: (Integer id, String name, String value) -> void
|
|
36
|
+
def on_fields_too_large: (Integer id) -> void
|
|
37
|
+
def on_end_headers: (Integer id) -> void
|
|
38
|
+
def on_data: (Integer id, String bytes) -> void
|
|
39
|
+
def on_end_stream: (Integer id) -> void
|
|
40
|
+
|
|
41
|
+
# The C part.
|
|
42
|
+
def submit_response: (Integer stream_id, Array[[String, String]] headers, bool has_body) -> self
|
|
43
|
+
def push_chunk: (Integer stream_id, String str) -> self
|
|
44
|
+
def push_eof: (Integer stream_id) -> self
|
|
45
|
+
def attach_file: (Integer stream_id, IO io, Integer offset, Integer length) -> self
|
|
46
|
+
def unsent_bytes: (Integer stream_id) -> Integer
|
|
47
|
+
def take_closed: () -> Array[Integer]
|
|
48
|
+
def take_error: () -> Exception?
|
|
49
|
+
def shutdown_notice: () -> self
|
|
50
|
+
def abort_stream: (Integer stream_id, Integer app_error_code) -> self
|
|
51
|
+
end
|
|
52
|
+
end
|