http3 0.0.3 → 0.0.5
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 +92 -0
- data/LICENSE-DEPENDENCIES.txt +2 -2
- data/README.md +124 -8
- data/ext/http3/extconf.rb +8 -5
- data/ext/http3/nghttp3.c +20 -1
- data/ext/http3/nghttp3.h +38 -3
- data/ext/http3/nghttp3_callbacks.c +8 -10
- data/ext/http3/nghttp3_connection.c +378 -206
- data/ext/http3/nghttp3_nv.c +53 -20
- data/ext/http3/nghttp3_qpack.c +66 -22
- data/ext/http3/session.c +429 -55
- data/lib/http3/body_pipe.rb +160 -0
- data/lib/http3/body_stream.rb +120 -0
- data/lib/http3/rack_adapter.rb +220 -38
- data/lib/http3/request.rb +4 -2
- data/lib/http3/server.rb +551 -41
- data/lib/http3/session.rb +614 -66
- data/lib/http3/version.rb +1 -1
- data/lib/http3/worker_pool.rb +71 -0
- data/lib/http3.rb +3 -0
- data/sig/http3/body_pipe.rbs +45 -0
- data/sig/http3/body_stream.rbs +41 -0
- 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 +29 -4
- data/sig/http3/request.rbs +5 -3
- data/sig/http3/response.rbs +2 -2
- data/sig/http3/server.rbs +14 -1
- data/sig/http3/session.rbs +136 -13
- data/sig/http3/worker_pool.rbs +19 -0
- data/sig/http3.rbs +3 -0
- metadata +9 -6
- data/.clang-format +0 -1
- data/.vscode/extensions.json +0 -6
- data/.vscode/settings.json +0 -10
data/lib/http3/version.rb
CHANGED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HTTP3
|
|
4
|
+
# Threads that run the jobs posted to them, one at a time each:
|
|
5
|
+
#
|
|
6
|
+
# pool = HTTP3::WorkerPool.new(3)
|
|
7
|
+
# pool.start
|
|
8
|
+
# pool.post { ... }
|
|
9
|
+
# pool.stop(1) # => the threads still running after a second
|
|
10
|
+
#
|
|
11
|
+
# The pool does not count the free threads: Server posts only as many
|
|
12
|
+
# jobs as there are, so none waits for long.
|
|
13
|
+
class WorkerPool
|
|
14
|
+
# The priority of the threads. MRI shortens a thread's time slice of
|
|
15
|
+
# 100 ms by its negative priority, halving it for each step, so at -3,
|
|
16
|
+
# the least, a worker busy on the CPU keeps the thread that drives the
|
|
17
|
+
# connections waiting for the GVL 12.5 ms at a time. Which thread gets
|
|
18
|
+
# the GVL next does not change.
|
|
19
|
+
WORKER_PRIORITY = -3
|
|
20
|
+
|
|
21
|
+
def initialize(size)
|
|
22
|
+
@size = size
|
|
23
|
+
@jobs = Thread::Queue.new
|
|
24
|
+
@threads = []
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Make the threads, named "http3-worker-1" and on.
|
|
28
|
+
def start
|
|
29
|
+
@threads = Array.new(@size) do |i|
|
|
30
|
+
Thread.new { work }.tap do |thread|
|
|
31
|
+
thread.name = "http3-worker-#{i + 1}"
|
|
32
|
+
thread.priority = WORKER_PRIORITY
|
|
33
|
+
thread.report_on_exception = false
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
self
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Queue job for the next free thread, without waiting.
|
|
40
|
+
def post(&job)
|
|
41
|
+
@jobs << job
|
|
42
|
+
self
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Drop the jobs not started, let each thread end once its job is done,
|
|
46
|
+
# and wait up to timeout seconds for them all. Returns the number of
|
|
47
|
+
# threads still running.
|
|
48
|
+
def stop(timeout)
|
|
49
|
+
@jobs.clear
|
|
50
|
+
@jobs.close
|
|
51
|
+
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout
|
|
52
|
+
@threads.count do |thread|
|
|
53
|
+
thread.join([deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC), 0].max).nil?
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def work
|
|
60
|
+
while (job = @jobs.pop)
|
|
61
|
+
begin
|
|
62
|
+
job.call
|
|
63
|
+
rescue Exception # rubocop:disable Lint/RescueException
|
|
64
|
+
# A job reports its own failures. Even exit in one leaves the
|
|
65
|
+
# thread for the next.
|
|
66
|
+
nil
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
data/lib/http3.rb
CHANGED
|
@@ -5,8 +5,11 @@ require_relative "http3/http3"
|
|
|
5
5
|
|
|
6
6
|
require_relative "http3/request"
|
|
7
7
|
require_relative "http3/response"
|
|
8
|
+
require_relative "http3/body_pipe"
|
|
9
|
+
require_relative "http3/body_stream"
|
|
8
10
|
require_relative "http3/session"
|
|
9
11
|
require_relative "http3/rack_adapter"
|
|
12
|
+
require_relative "http3/worker_pool"
|
|
10
13
|
require_relative "http3/server"
|
|
11
14
|
|
|
12
15
|
module HTTP3
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module HTTP3
|
|
2
|
+
# The stream a response was being sent on is gone (reset by the client,
|
|
3
|
+
# or asked by it for no more (STOP_SENDING), its connection ended, the
|
|
4
|
+
# server stopped, or the response was aborted after waiting on its client
|
|
5
|
+
# for response_timeout).
|
|
6
|
+
class StreamClosed < IOError
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
module Body
|
|
10
|
+
# A response body written by the thread that runs the application and
|
|
11
|
+
# taken by the server's I/O loop. wake is called, with no lock held,
|
|
12
|
+
# each time the body goes from having nothing to take to having
|
|
13
|
+
# something.
|
|
14
|
+
class Pipe
|
|
15
|
+
def initialize: (capacity: Integer, wake: ^() -> void) -> void
|
|
16
|
+
|
|
17
|
+
# Queue chunk, waiting while capacity bytes or more are queued.
|
|
18
|
+
# Returns its size. Raises StreamClosed once the body was given up on,
|
|
19
|
+
# and IOError once it has ended.
|
|
20
|
+
def write: (String chunk) -> Integer
|
|
21
|
+
|
|
22
|
+
# End the body and wait until all of it has been taken.
|
|
23
|
+
def finish: () -> nil
|
|
24
|
+
|
|
25
|
+
# End the body with error, once what is queued has been taken, and
|
|
26
|
+
# wait until it has. Returns true once error has been taken, and false
|
|
27
|
+
# when it never will: the body had ended or been given up on (fail
|
|
28
|
+
# does nothing then), or was given up on while fail waited.
|
|
29
|
+
def fail: (Exception error) -> bool
|
|
30
|
+
|
|
31
|
+
# Wait until every chunk queued has been taken.
|
|
32
|
+
def flush: () -> self
|
|
33
|
+
|
|
34
|
+
# A chunk; once they have all been taken, :eof, or the exception given
|
|
35
|
+
# to #fail; nil while there is nothing yet. Never waits.
|
|
36
|
+
def take: () -> (String | :eof | Exception | nil)
|
|
37
|
+
|
|
38
|
+
# True when #take has something other than nil to return.
|
|
39
|
+
def ready?: () -> bool
|
|
40
|
+
|
|
41
|
+
# Give up on the body: the writer's calls raise StreamClosed.
|
|
42
|
+
def cancel: () -> nil
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
module HTTP3
|
|
2
|
+
module Body
|
|
3
|
+
# The stream a streaming body (Rack 3's call(stream)) is called with:
|
|
4
|
+
# writes go to a Body::Pipe, reads come from the request body. Its
|
|
5
|
+
# methods behave as IO's do. Once the stream has gone away (the client
|
|
6
|
+
# reset it or sent STOP_SENDING, or it was aborted after
|
|
7
|
+
# response_timeout), write and flush raise StreamClosed, and so do
|
|
8
|
+
# close_write and close, once;
|
|
9
|
+
# writing past length, the content-length, fails the pipe and raises
|
|
10
|
+
# StreamClosed too.
|
|
11
|
+
class Stream
|
|
12
|
+
def initialize: (pipe: Pipe, input: StringIO | ::File, length: Integer?) -> void
|
|
13
|
+
|
|
14
|
+
# Read from the request body, as IO#read does. Raises IOError after
|
|
15
|
+
# close_read.
|
|
16
|
+
def read: (?Integer? length, ?String outbuf) -> String?
|
|
17
|
+
|
|
18
|
+
# Write each of objects, as a String, waiting while the pipe is full.
|
|
19
|
+
# Returns the bytes written. Raises IOError after close_write.
|
|
20
|
+
def write: (*_ToS objects) -> Integer
|
|
21
|
+
|
|
22
|
+
def <<: (_ToS object) -> self
|
|
23
|
+
|
|
24
|
+
# Wait until what has been written has been taken to be sent. Raises
|
|
25
|
+
# IOError after close_write.
|
|
26
|
+
def flush: () -> self
|
|
27
|
+
|
|
28
|
+
# Close the request body; may be called again.
|
|
29
|
+
def close_read: () -> nil
|
|
30
|
+
|
|
31
|
+
# End the body, and wait until all of it has been taken; fail the pipe
|
|
32
|
+
# instead if it is short of length. May be called again, and does
|
|
33
|
+
# nothing then.
|
|
34
|
+
def close_write: () -> nil
|
|
35
|
+
|
|
36
|
+
def close: () -> nil
|
|
37
|
+
|
|
38
|
+
def closed?: () -> bool
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
module HTTP3
|
|
2
2
|
module Nghttp3
|
|
3
|
+
# A callback, a Callbacks block or a body block, runs while nghttp3 is
|
|
4
|
+
# in the middle of a call on the connection, and may call back into it
|
|
5
|
+
# only as far as nghttp3 allows there. Inside the callbacks of
|
|
6
|
+
# #read_stream, it may call #submit_request, #submit_response,
|
|
7
|
+
# #submit_info, #submit_trailers, #resume_stream, #block_stream,
|
|
8
|
+
# #unblock_stream, #shutdown_stream_read, #shutdown_stream_write,
|
|
9
|
+
# #submit_shutdown_notice, #shutdown, #set_max_client_streams_bidi and
|
|
10
|
+
# #stream_writable?; inside any other callback, none of the methods that
|
|
11
|
+
# call into nghttp3. Anything else, #close among it, raises
|
|
12
|
+
# InvalidStateError. #closed?, #server?, #client?, #pending_data and the
|
|
13
|
+
# stream user data methods call no nghttp3 and can be called anywhere.
|
|
14
|
+
#
|
|
15
|
+
# Once #read_stream or #writev_stream has raised for an error nghttp3
|
|
16
|
+
# returned, or another method for a fatal one, nghttp3 allows nothing
|
|
17
|
+
# but deleting the connection: only #close and the methods that call no
|
|
18
|
+
# nghttp3 can be called, and the rest raise InvalidStateError.
|
|
19
|
+
#
|
|
20
|
+
# What nghttp3 asserts of its callers raises instead: a stream id out of
|
|
21
|
+
# 0..2**62-1 or of the wrong kind raises ArgumentError, and a call on a
|
|
22
|
+
# connection of the wrong role, or before or after the calls nghttp3
|
|
23
|
+
# needs, InvalidStateError.
|
|
3
24
|
class Connection
|
|
4
25
|
# Creates a new client connection
|
|
5
26
|
def self.client_new: (?Settings? settings, ?Callbacks? callbacks) -> Connection
|
|
@@ -7,10 +28,12 @@ module HTTP3
|
|
|
7
28
|
# Creates a new server connection
|
|
8
29
|
def self.server_new: (?Settings? settings, ?Callbacks? callbacks) -> Connection
|
|
9
30
|
|
|
10
|
-
# Binds the control stream
|
|
31
|
+
# Binds the control stream, a unidirectional stream of this end (id % 4
|
|
32
|
+
# == 3 for a server, 2 for a client)
|
|
11
33
|
def bind_control_stream: (Integer stream_id) -> self
|
|
12
34
|
|
|
13
|
-
# Binds QPACK encoder and decoder streams
|
|
35
|
+
# Binds QPACK encoder and decoder streams, unidirectional streams of
|
|
36
|
+
# this end as for bind_control_stream
|
|
14
37
|
def bind_qpack_streams: (Integer encoder_stream_id, Integer decoder_stream_id) -> self
|
|
15
38
|
|
|
16
39
|
# Closes the connection
|
|
@@ -29,7 +52,9 @@ module HTTP3
|
|
|
29
52
|
|
|
30
53
|
# Reads data on a stream from the QUIC layer. ts is the time in
|
|
31
54
|
# nanoseconds on a monotonic clock (CLOCK_MONOTONIC by default). An
|
|
32
|
-
# exception raised by a callback comes out of here.
|
|
55
|
+
# exception raised by a callback comes out of here. A server reads the
|
|
56
|
+
# streams the client opened (even ids), a client the request streams
|
|
57
|
+
# and the server's unidirectional streams (id % 4 of 0 or 3).
|
|
33
58
|
def read_stream: (Integer stream_id, String data, ?fin: bool, ?ts: Integer?) -> Integer
|
|
34
59
|
|
|
35
60
|
# Gets stream data to send to the QUIC layer. An exception raised by a
|
|
@@ -64,7 +89,7 @@ module HTTP3
|
|
|
64
89
|
def shutdown_stream_read: (Integer stream_id) -> self
|
|
65
90
|
|
|
66
91
|
# Tells a server connection how many bidirectional streams the client
|
|
67
|
-
# may open in total
|
|
92
|
+
# may open in total. It never goes down (ArgumentError).
|
|
68
93
|
def set_max_client_streams_bidi: (Integer max_streams) -> self
|
|
69
94
|
|
|
70
95
|
# The body Strings handed to nghttp3 that the peer has not acknowledged
|
|
@@ -72,23 +97,32 @@ module HTTP3
|
|
|
72
97
|
def pending_data: (Integer stream_id) -> Array[String]
|
|
73
98
|
|
|
74
99
|
# HTTP operations
|
|
75
|
-
|
|
76
|
-
#
|
|
100
|
+
#
|
|
101
|
+
# The submit_* methods read each NV's name, value and flags as they
|
|
102
|
+
# are, without to_str or to_int, and raise ArgumentError for an
|
|
103
|
+
# element that is not an NV with String name and value and flags of
|
|
104
|
+
# 0..255; nothing is submitted then. They need #bind_qpack_streams
|
|
105
|
+
# first.
|
|
106
|
+
|
|
107
|
+
# Submits an HTTP request (client only) on a client-initiated
|
|
108
|
+
# bidirectional stream (id % 4 == 0)
|
|
77
109
|
def submit_request: (Integer stream_id, Array[NV] headers, ?body: String?) ?{ (Integer stream_id) -> (String | Symbol | nil) } -> self
|
|
78
110
|
|
|
79
111
|
# Submits an HTTP response (server only)
|
|
80
112
|
def submit_response: (Integer stream_id, Array[NV] headers, ?body: String?) ?{ (Integer stream_id) -> (String | Symbol | nil) } -> self
|
|
81
113
|
|
|
82
|
-
# Submits a 1xx informational response
|
|
114
|
+
# Submits a 1xx informational response (server only)
|
|
83
115
|
def submit_info: (Integer stream_id, Array[NV] headers) -> self
|
|
84
116
|
|
|
85
117
|
# Submits trailer headers (implicitly ends the stream)
|
|
86
118
|
def submit_trailers: (Integer stream_id, Array[NV] trailers) -> self
|
|
87
119
|
|
|
88
|
-
# Signals the intention to shut down the connection gracefully
|
|
120
|
+
# Signals the intention to shut down the connection gracefully. Needs
|
|
121
|
+
# #bind_control_stream first, and cannot follow #shutdown.
|
|
89
122
|
def submit_shutdown_notice: () -> self
|
|
90
123
|
|
|
91
|
-
# Initiates graceful shutdown
|
|
124
|
+
# Initiates graceful shutdown. Needs #bind_control_stream first, and
|
|
125
|
+
# can be called once.
|
|
92
126
|
def shutdown: () -> self
|
|
93
127
|
|
|
94
128
|
# Associates arbitrary data with a stream
|
data/sig/http3/nghttp3/nv.rbs
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
module HTTP3
|
|
2
2
|
module Nghttp3
|
|
3
3
|
class NV
|
|
4
|
+
# flags is an Integer of 0..255, the NV_FLAG_* constants or-ed
|
|
5
|
+
# together; anything else raises ArgumentError. NV_FLAG_NO_COPY_NAME
|
|
6
|
+
# and NV_FLAG_NO_COPY_VALUE are ignored, and nghttp3 copies the name
|
|
7
|
+
# and value all the same.
|
|
4
8
|
def initialize: (String name, String value, ?flags: Integer) -> void
|
|
5
9
|
|
|
6
10
|
def name: () -> String
|
data/sig/http3/nghttp3/qpack.rbs
CHANGED
|
@@ -5,7 +5,9 @@ module HTTP3
|
|
|
5
5
|
# Creates a new QPACK encoder
|
|
6
6
|
def initialize: (Integer max_dtable_capacity) -> void
|
|
7
7
|
|
|
8
|
-
# Encodes headers for a given stream
|
|
8
|
+
# Encodes headers for a given stream. headers are checked as
|
|
9
|
+
# Connection#submit_request checks them, and a stream_id out of
|
|
10
|
+
# 0..2**62-1 raises ArgumentError.
|
|
9
11
|
def encode: (Integer stream_id, Array[NV] headers) -> { prefix: String, data: String, encoder_stream: String }
|
|
10
12
|
|
|
11
13
|
# Reads decoder stream data
|
|
@@ -22,10 +24,15 @@ module HTTP3
|
|
|
22
24
|
end
|
|
23
25
|
|
|
24
26
|
class Decoder
|
|
27
|
+
# The most streams decoded at once: decode on a new stream past it
|
|
28
|
+
# raises InvalidStateError, until one is finished or cancelled.
|
|
29
|
+
MAX_STREAMS: Integer
|
|
30
|
+
|
|
25
31
|
# Creates a new QPACK decoder
|
|
26
32
|
def initialize: (Integer max_dtable_capacity, Integer max_blocked_streams) -> void
|
|
27
33
|
|
|
28
|
-
# Decodes headers from a stream
|
|
34
|
+
# Decodes headers from a stream. A stream_id out of 0..2**62-1 raises
|
|
35
|
+
# ArgumentError.
|
|
29
36
|
def decode: (Integer stream_id, String data, ?fin: bool) -> { headers: Array[{ name: String, value: String, token: Integer }]?, blocked: bool, consumed: Integer }
|
|
30
37
|
|
|
31
38
|
# Reads encoder stream data
|
|
@@ -34,7 +41,7 @@ module HTTP3
|
|
|
34
41
|
# Returns data to be sent on the decoder stream
|
|
35
42
|
def decoder_stream_data: () -> String
|
|
36
43
|
|
|
37
|
-
# Cancels decoding for the given stream
|
|
44
|
+
# Cancels decoding for the given stream (stream_id 0..2**62-1)
|
|
38
45
|
def cancel_stream: (Integer stream_id) -> void
|
|
39
46
|
|
|
40
47
|
# Sets the maximum dynamic table capacity
|
data/sig/http3/rack_adapter.rbs
CHANGED
|
@@ -8,11 +8,36 @@ module HTTP3
|
|
|
8
8
|
CONNECTION_FIELDS: Array[String]
|
|
9
9
|
FIELD_NAME: Regexp
|
|
10
10
|
FORBIDDEN_IN_VALUE: Regexp
|
|
11
|
+
# The schemes a request may have.
|
|
12
|
+
SCHEMES: Array[String]
|
|
13
|
+
# What :authority, or host, must look like.
|
|
14
|
+
AUTHORITY: Regexp
|
|
11
15
|
|
|
12
|
-
|
|
16
|
+
# A body read with each, and a streaming body, go through a Body::Pipe
|
|
17
|
+
# of pipe_capacity bytes, whose wake is wake.
|
|
18
|
+
def initialize: (untyped app, pipe_capacity: Integer, wake: ^() -> void) -> void
|
|
13
19
|
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
|
|
20
|
+
# Returns [response, fill]. Answers, without calling the application,
|
|
21
|
+
# CONNECT with 501, and with 400 a request whose scheme is not http or
|
|
22
|
+
# https, whose path has a fragment, or whose authority is malformed or
|
|
23
|
+
# differs from its host field. Raises what the application raises, and
|
|
24
|
+
# InvalidResponse for an answer that cannot be sent (among them a
|
|
25
|
+
# status outside 200..599, and a content-length that is malformed or
|
|
26
|
+
# that a file or array body does not have); the Rack body is closed
|
|
27
|
+
# either way. fill is nil, the Rack body closed already, unless the
|
|
28
|
+
# response sends a body that is read with each or is a streaming body
|
|
29
|
+
# (call): then the response has a Body::Pipe, and fill writes the body
|
|
30
|
+
# to it, fails it once the body passes content-length, ends short of
|
|
31
|
+
# it or raises (or, with each, yields a chunk that is not a String),
|
|
32
|
+
# and closes the Rack body. A streaming body is called with a
|
|
33
|
+
# Body::Stream whose read reads the request body, and ends when call
|
|
34
|
+
# returns. fill returns nil, or an exception the body raised that did
|
|
35
|
+
# not abort its stream, the body having ended or the server having
|
|
36
|
+
# given up on it, for the caller to log.
|
|
37
|
+
def call: (Request request, remote: Addrinfo) -> [Response, (^() -> Exception?)?]
|
|
38
|
+
|
|
39
|
+
# The 400s and 501s #call answered since the last call, as
|
|
40
|
+
# {status => count}; the count starts over.
|
|
41
|
+
def take_refusals: () -> Hash[Integer, Integer]
|
|
17
42
|
end
|
|
18
43
|
end
|
data/sig/http3/request.rbs
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
module HTTP3
|
|
2
2
|
# A request as a Session put it together. headers holds [name, value]
|
|
3
|
-
# pairs with lowercase names, the pseudo-headers first.
|
|
3
|
+
# pairs with lowercase names, the pseudo-headers first. input is the
|
|
4
|
+
# body, a binary IO at its start, which becomes rack.input: a StringIO,
|
|
5
|
+
# or a temporary File once the body is past request_body_buffer_size.
|
|
4
6
|
class Request < Data
|
|
5
7
|
attr_reader stream_id: Integer
|
|
6
8
|
attr_reader headers: Array[[String, String]]
|
|
7
|
-
attr_reader
|
|
9
|
+
attr_reader input: StringIO | ::File
|
|
8
10
|
|
|
9
|
-
def self.new: (stream_id: Integer, headers: Array[[String, String]],
|
|
11
|
+
def self.new: (stream_id: Integer, headers: Array[[String, String]], input: StringIO | ::File) -> Request
|
|
10
12
|
end
|
|
11
13
|
end
|
data/sig/http3/response.rbs
CHANGED
|
@@ -3,9 +3,9 @@ module HTTP3
|
|
|
3
3
|
class Response < Data
|
|
4
4
|
attr_reader status: Integer
|
|
5
5
|
attr_reader headers: Array[[String, String]]
|
|
6
|
-
attr_reader body: Body::File | Body::Chunks | nil
|
|
6
|
+
attr_reader body: Body::File | Body::Chunks | Body::Pipe | nil
|
|
7
7
|
|
|
8
|
-
def self.new: (status: Integer, headers: Array[[String, String]], body: Body::File | Body::Chunks | nil) -> Response
|
|
8
|
+
def self.new: (status: Integer, headers: Array[[String, String]], body: Body::File | Body::Chunks | Body::Pipe | nil) -> Response
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
module Body
|
data/sig/http3/server.rbs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
module HTTP3
|
|
2
2
|
# An HTTP/3 server for a Rack 3 application on one UDP address. #run
|
|
3
3
|
# serves until SIGTERM or SIGINT, then drains; SIGHUP reloads the
|
|
4
|
-
# certificate and key.
|
|
4
|
+
# certificate and key. The application runs on threads worker threads.
|
|
5
5
|
class Server
|
|
6
6
|
ALPN: Array[String]
|
|
7
7
|
SIGNALS: Hash[String, String]
|
|
@@ -20,9 +20,22 @@ module HTTP3
|
|
|
20
20
|
?settings: QUIC::Settings?,
|
|
21
21
|
?qlog_dir: (String | _ToPath)?,
|
|
22
22
|
?max_connections: Integer,
|
|
23
|
+
?threads: Integer,
|
|
23
24
|
?max_request_body: Integer,
|
|
24
25
|
?max_field_section_size: Integer,
|
|
26
|
+
?max_connection_request_buffer: Integer,
|
|
27
|
+
?max_total_request_buffer: Integer,
|
|
28
|
+
?request_body_buffer_size: Integer,
|
|
29
|
+
?max_connection_request_spool: Integer,
|
|
30
|
+
?max_total_request_spool: Integer,
|
|
31
|
+
?max_connection_request_spool_files: Integer,
|
|
32
|
+
?max_total_request_spool_files: Integer,
|
|
25
33
|
?send_buffer_size: Integer,
|
|
34
|
+
?max_connection_send_buffer: Integer,
|
|
35
|
+
?max_total_send_buffer: Integer,
|
|
36
|
+
?request_timeout: (Integer | Float)?,
|
|
37
|
+
?response_timeout: (Integer | Float)?,
|
|
38
|
+
?refusal_log_interval: Integer | Float,
|
|
26
39
|
?shutdown_timeout: Numeric,
|
|
27
40
|
?logger: Logger
|
|
28
41
|
) -> void
|
data/sig/http3/session.rbs
CHANGED
|
@@ -2,51 +2,174 @@ module HTTP3
|
|
|
2
2
|
# HTTP/3 on one QUIC connection, driven by HTTP3::Server's I/O loop. The
|
|
3
3
|
# on_* methods are called by the C part with the parts of each request.
|
|
4
4
|
class Session
|
|
5
|
+
# What .shared_buffer returns, to be passed to .attach as it is.
|
|
6
|
+
type shared_buffer = Object
|
|
7
|
+
|
|
8
|
+
# What .shared_spool returns, to be passed to .attach as it is.
|
|
9
|
+
type shared_spool = Object
|
|
10
|
+
|
|
11
|
+
# What a field counts for besides its name and value (RFC 9114 section
|
|
12
|
+
# 4.2.2).
|
|
13
|
+
FIELD_OVERHEAD: Integer
|
|
14
|
+
|
|
5
15
|
# 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
|
-
|
|
16
|
+
# body get 413, over max_field_section_size bytes of headers 431, as
|
|
17
|
+
# soon as they pass the limit; the rest of them is not read. Requests
|
|
18
|
+
# may hold max_connection_request_buffer bytes, and those of every
|
|
19
|
+
# Session sharing shared_buffer its limit; more gets 503. One not in
|
|
20
|
+
# full within request_timeout seconds of its headers gets 408, and one
|
|
21
|
+
# not handed out within request_timeout seconds of its end 503
|
|
22
|
+
# (#expire_requests); nil is no limit. A body past
|
|
23
|
+
# request_body_buffer_size bytes goes to a temporary file, all of it
|
|
24
|
+
# (.spool_file). The files may hold max_connection_request_spool bytes
|
|
25
|
+
# and be max_connection_request_spool_files many; those of every
|
|
26
|
+
# Session sharing shared_spool count against its limits. More, or a
|
|
27
|
+
# file that cannot be made or written, gets 503. A request counts until
|
|
28
|
+
# its stream closes, or, once handed out, until #finish, though its
|
|
29
|
+
# stream or connection has gone. A response whose body has waited on
|
|
30
|
+
# its client for response_timeout seconds is aborted
|
|
31
|
+
# (#expire_responses): a limit on a stall, not on the whole response;
|
|
32
|
+
# nil is no limit. The caller calls #expire_requests and
|
|
33
|
+
# #expire_responses every turn, and waits no longer than
|
|
34
|
+
# #next_request_deadline and #next_response_deadline.
|
|
35
|
+
def self.attach: (QUIC::Connection::Server quic_conn, max_request_body: Integer, max_field_section_size: Integer,
|
|
36
|
+
?max_connection_request_buffer: Integer, ?shared_buffer: shared_buffer?,
|
|
37
|
+
?request_timeout: (Integer | Float)?, ?request_body_buffer_size: Integer,
|
|
38
|
+
?max_connection_request_spool: Integer, ?max_connection_request_spool_files: Integer,
|
|
39
|
+
?shared_spool: shared_spool?, ?response_timeout: (Integer | Float)?) -> Session
|
|
40
|
+
|
|
41
|
+
# A limit of limit bytes for the requests of every Session given it.
|
|
42
|
+
def self.shared_buffer: (Integer limit) -> shared_buffer
|
|
43
|
+
|
|
44
|
+
# Limits of limit bytes in temporary files, and of files open at once,
|
|
45
|
+
# for the requests of every Session given it.
|
|
46
|
+
def self.shared_spool: (Integer limit, files: Integer) -> shared_spool
|
|
47
|
+
|
|
48
|
+
# A temporary file without a name in Dir.tmpdir, in binary mode.
|
|
49
|
+
# Raises SystemCallError when none can be had.
|
|
50
|
+
def self.spool_file: () -> ::File
|
|
8
51
|
|
|
9
52
|
def self._attach: (QUIC::Connection::Server quic_conn, Integer max_field_section_size) -> Session
|
|
10
53
|
|
|
11
|
-
#
|
|
12
|
-
# (413, 431)
|
|
13
|
-
|
|
54
|
+
# The requests refused since the last call, each with the status to
|
|
55
|
+
# answer it with directly (408, 413, 431, 503). One comes out as soon
|
|
56
|
+
# as it is refused, with neither headers nor body.
|
|
57
|
+
def take_refusals: () -> Array[[Request, Integer]]
|
|
58
|
+
|
|
59
|
+
# The next request received in full, for the application, or nil. The
|
|
60
|
+
# caller closes its input, and calls #finish, once done with it; what
|
|
61
|
+
# it holds counts until then, even after its stream has closed.
|
|
62
|
+
def next_request: () -> Request?
|
|
63
|
+
|
|
64
|
+
# The caller is done with a request #next_request handed out: what it
|
|
65
|
+
# held no longer counts, though its stream or connection has gone.
|
|
66
|
+
# Calling it again does nothing.
|
|
67
|
+
def finish: (Integer stream_id) -> void
|
|
68
|
+
|
|
69
|
+
# True while the request on stream_id can still be answered: its stream
|
|
70
|
+
# is open, the client has neither reset it nor asked for no more of it
|
|
71
|
+
# (STOP_SENDING), it has not been refused, and it has not been answered.
|
|
72
|
+
def answerable?: (Integer stream_id) -> bool
|
|
73
|
+
|
|
74
|
+
# The first exception a temporary file raised since the last call (its
|
|
75
|
+
# request got 503), or nil.
|
|
76
|
+
def take_spool_error: () -> Exception?
|
|
77
|
+
|
|
78
|
+
# Refuse with 408 the requests still coming in whose request_timeout
|
|
79
|
+
# has passed at now (CLOCK_MONOTONIC), and with 503 those that have
|
|
80
|
+
# waited that long to be handed out.
|
|
81
|
+
def expire_requests: (Float now) -> void
|
|
82
|
+
|
|
83
|
+
# When the next request still coming in, or waiting to be handed out,
|
|
84
|
+
# runs out of time, or nil.
|
|
85
|
+
def next_request_deadline: () -> Float?
|
|
86
|
+
|
|
87
|
+
# Abort with H3_REQUEST_CANCELLED the responses whose body has waited
|
|
88
|
+
# on its client response_timeout seconds or more at now
|
|
89
|
+
# (CLOCK_MONOTONIC), cancelling a Body::Pipe, whose writer then gets
|
|
90
|
+
# StreamClosed. Returns how many.
|
|
91
|
+
def expire_responses: (Float now) -> Integer
|
|
92
|
+
|
|
93
|
+
# When the next response waiting on its client runs out of time, or nil.
|
|
94
|
+
def next_response_deadline: () -> Float?
|
|
14
95
|
|
|
15
96
|
# Send response. Yields the exception of a file body whose descriptor
|
|
16
|
-
# could not be duplicated; its stream is aborted.
|
|
97
|
+
# could not be duplicated; its stream is aborted. Raises
|
|
98
|
+
# Nghttp3::InvalidStateError for a request already answered.
|
|
17
99
|
def respond: (Request request, Response response) ?{ (SystemCallError error) -> void } -> void
|
|
18
100
|
|
|
19
|
-
# Queue more of each Body::Chunks body, up to limit
|
|
20
|
-
#
|
|
21
|
-
|
|
101
|
+
# Queue more of each Body::Chunks and Body::Pipe body, up to limit
|
|
102
|
+
# unsent bytes per stream, connection_limit on the connection, and room
|
|
103
|
+
# bytes queued now (nil for no limit); a chunk may pass a limit.
|
|
104
|
+
# Returns the bytes queued. Yields the request (its pseudo-headers
|
|
105
|
+
# only, without body) and exception of a body that raised or failed.
|
|
106
|
+
def fill_bodies: (Integer limit, ?connection_limit: Integer?, ?room: Integer?) ?{ (Request request, Exception error) -> void } -> Integer
|
|
107
|
+
|
|
108
|
+
# True when a Body::Chunks body, or a Body::Pipe with something to
|
|
109
|
+
# take, could take more.
|
|
110
|
+
def wants_fill?: (Integer limit, ?connection_limit: Integer?, ?room: Integer?) -> bool
|
|
22
111
|
|
|
23
|
-
|
|
112
|
+
# Bytes of the Body::Chunks and Body::Pipe bodies queued but not taken
|
|
113
|
+
# by nghttp3 yet.
|
|
114
|
+
def unsent_total: () -> Integer
|
|
24
115
|
|
|
25
|
-
# True while a request stream is open
|
|
116
|
+
# True while a request stream is open, or a request waits to be handed
|
|
117
|
+
# out.
|
|
26
118
|
def busy?: () -> bool
|
|
27
119
|
|
|
28
|
-
# Let go of closed streams; yields a body
|
|
120
|
+
# Let go of closed streams (a Body::Pipe is cancelled); yields a body
|
|
121
|
+
# close that raised. Resets our side of the requests the client reset
|
|
122
|
+
# before their end (H3_REQUEST_INCOMPLETE), and of those it reset once
|
|
123
|
+
# they had come in full, while they waited to be handed out or once
|
|
124
|
+
# handed out and not answered (H3_REQUEST_CANCELLED).
|
|
29
125
|
def release_closed: () ?{ (Integer stream_id, Exception error) -> void } -> void
|
|
30
126
|
|
|
31
|
-
#
|
|
127
|
+
# The connection is gone: let go of the nghttp3 connection and, of each
|
|
128
|
+
# body, of what nghttp3 has not taken (a file body's descriptor
|
|
129
|
+
# included), then close every body still held. Afterwards submit_response
|
|
130
|
+
# and stop_reading raise Nghttp3::InvalidStateError, as on a failed
|
|
131
|
+
# connection, and push_chunk, push_eof and attach_file as on an aborted
|
|
132
|
+
# stream. What the requests handed out hold counts until #finish.
|
|
32
133
|
def release_all: () ?{ (Integer stream_id, Exception error) -> void } -> void
|
|
33
134
|
|
|
135
|
+
# Called inside nghttp3, or inside the QUIC connection's packet
|
|
136
|
+
# handling, where submit_response, stop_reading, push_chunk, push_eof,
|
|
137
|
+
# attach_file, shutdown_notice and abort_stream (and so respond), and
|
|
138
|
+
# release_all, raise Nghttp3::InvalidStateError.
|
|
34
139
|
def on_begin_headers: (Integer id) -> void
|
|
35
140
|
def on_header: (Integer id, String name, String value) -> void
|
|
36
141
|
def on_fields_too_large: (Integer id) -> void
|
|
37
142
|
def on_end_headers: (Integer id) -> void
|
|
38
143
|
def on_data: (Integer id, String bytes) -> void
|
|
39
144
|
def on_end_stream: (Integer id) -> void
|
|
145
|
+
# The client reset its side of the stream.
|
|
146
|
+
def on_reset: (Integer id) -> void
|
|
147
|
+
# The client asked for no more of the response (STOP_SENDING).
|
|
148
|
+
def on_stop_sending: (Integer id) -> void
|
|
40
149
|
|
|
41
150
|
# The C part.
|
|
42
151
|
def submit_response: (Integer stream_id, Array[[String, String]] headers, bool has_body) -> self
|
|
152
|
+
# Read no more of the request on stream_id (STOP_SENDING with
|
|
153
|
+
# H3_NO_ERROR); for one answered before it has arrived in full.
|
|
154
|
+
def stop_reading: (Integer stream_id) -> self
|
|
155
|
+
# These three raise Nghttp3::InvalidStateError once the stream has been
|
|
156
|
+
# aborted.
|
|
43
157
|
def push_chunk: (Integer stream_id, String str) -> self
|
|
44
158
|
def push_eof: (Integer stream_id) -> self
|
|
45
159
|
def attach_file: (Integer stream_id, IO io, Integer offset, Integer length) -> self
|
|
46
160
|
def unsent_bytes: (Integer stream_id) -> Integer
|
|
161
|
+
# Since when (CLOCK_MONOTONIC seconds) the response body on stream_id
|
|
162
|
+
# has waited on the client with nothing moving: no acknowledgement of
|
|
163
|
+
# it, no wider flow control window for it, and, while that window does
|
|
164
|
+
# not hold it back, no acknowledgement of any body on the connection.
|
|
165
|
+
# nil while none of it waits, without a body, and once aborted.
|
|
166
|
+
def response_stalled_since: (Integer stream_id) -> Float?
|
|
47
167
|
def take_closed: () -> Array[Integer]
|
|
48
168
|
def take_error: () -> Exception?
|
|
49
169
|
def shutdown_notice: () -> self
|
|
170
|
+
# Stop the stream in both directions with app_error_code. What of the
|
|
171
|
+
# body nghttp3 has not taken is let go at once (unsent_bytes no longer
|
|
172
|
+
# counts it), and a file body's descriptor closed.
|
|
50
173
|
def abort_stream: (Integer stream_id, Integer app_error_code) -> self
|
|
51
174
|
end
|
|
52
175
|
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module HTTP3
|
|
2
|
+
# Threads that run the jobs posted to them, one at a time each.
|
|
3
|
+
class WorkerPool
|
|
4
|
+
# The priority of the threads, the least MRI has.
|
|
5
|
+
WORKER_PRIORITY: Integer
|
|
6
|
+
|
|
7
|
+
def initialize: (Integer size) -> void
|
|
8
|
+
|
|
9
|
+
# Make the threads, named "http3-worker-1" and on.
|
|
10
|
+
def start: () -> self
|
|
11
|
+
|
|
12
|
+
# Queue a job for the next free thread, without waiting.
|
|
13
|
+
def post: () { () -> void } -> self
|
|
14
|
+
|
|
15
|
+
# Drop the jobs not started, and wait up to timeout seconds for the
|
|
16
|
+
# threads to end. Returns the number still running.
|
|
17
|
+
def stop: (Numeric timeout) -> Integer
|
|
18
|
+
end
|
|
19
|
+
end
|
data/sig/http3.rbs
CHANGED
|
@@ -26,6 +26,9 @@ module HTTP3
|
|
|
26
26
|
# NV flag constants
|
|
27
27
|
NV_FLAG_NONE: Integer
|
|
28
28
|
NV_FLAG_NEVER_INDEX: Integer
|
|
29
|
+
# Ignored: nghttp3 always copies the name and value. It would otherwise
|
|
30
|
+
# read them when writev_stream sends them, and nothing keeps the Strings
|
|
31
|
+
# alive, or in place, until then.
|
|
29
32
|
NV_FLAG_NO_COPY_NAME: Integer
|
|
30
33
|
NV_FLAG_NO_COPY_VALUE: Integer
|
|
31
34
|
NV_FLAG_TRY_INDEX: Integer
|