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.
data/lib/http3/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HTTP3
4
- VERSION = "0.0.3"
4
+ VERSION = "0.0.5"
5
5
  end
@@ -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
- # Submits an HTTP request (client only)
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
@@ -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
@@ -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
@@ -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
- def initialize: (untyped app) -> void
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
- # 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
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
@@ -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 body: String
9
+ attr_reader input: StringIO | ::File
8
10
 
9
- def self.new: (stream_id: Integer, headers: Array[[String, String]], body: String) -> Request
11
+ def self.new: (stream_id: Integer, headers: Array[[String, String]], input: StringIO | ::File) -> Request
10
12
  end
11
13
  end
@@ -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
@@ -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
- def self.attach: (QUIC::Connection::Server quic_conn, max_request_body: Integer, max_field_section_size: Integer) -> Session
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
- # 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?]]
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 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
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
- def wants_fill?: (Integer limit) -> bool
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 close that raised.
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
- # Close every body still held.
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