quicsilver 0.4.0 → 0.5.0
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/.github/workflows/ci.yml +9 -1
- data/.github/workflows/cibuildgem.yaml +29 -5
- data/.gitignore +5 -0
- data/CHANGELOG.md +50 -0
- data/Gemfile.lock +8 -1
- data/README.md +128 -15
- data/Rakefile +10 -3
- data/benchmarks/baseline.rb +175 -0
- data/benchmarks/compare.rb +398 -0
- data/benchmarks/helpers.rb +138 -64
- data/benchmarks/latency.rb +128 -0
- data/benchmarks/loss.rb +193 -0
- data/benchmarks/mobile.rb +125 -0
- data/docs/connection_identity.md +141 -0
- data/examples/benchmark.rb +80 -0
- data/examples/connection_pool_demo.rb +1 -1
- data/examples/example_helper.rb +51 -0
- data/examples/feature_demo.rb +7 -7
- data/examples/grpc_style.rb +2 -2
- data/examples/latency_aware_routing.rb +382 -0
- data/examples/lifecycle_drain.rb +264 -0
- data/examples/priorities.rb +2 -2
- data/examples/rails_feature_test.rb +92 -72
- data/examples/server_stats.rb +117 -0
- data/examples/simple_client_test.rb +3 -3
- data/examples/trailers.rb +23 -23
- data/examples/webtransport_smoke.rb +546 -0
- data/examples/worker_load_balancing.rb +425 -0
- data/ext/quicsilver/extconf.rb +100 -15
- data/ext/quicsilver/include/msquic.h +2226 -0
- data/ext/quicsilver/include/msquic_posix.h +696 -0
- data/ext/quicsilver/include/quic_sal_stub.h +314 -0
- data/ext/quicsilver/msquic_patch.rb +68 -0
- data/ext/quicsilver/patches/reliable-reset.patch +723 -0
- data/ext/quicsilver/patches/stream-final-size.patch +47 -0
- data/ext/quicsilver/quicsilver.c +1640 -222
- data/lib/quicsilver/client/client.rb +419 -60
- data/lib/quicsilver/client/connection_pool.rb +122 -26
- data/lib/quicsilver/client/request.rb +106 -14
- data/lib/quicsilver/errors.rb +39 -0
- data/lib/quicsilver/libmsquic.2.dylib +0 -0
- data/lib/quicsilver/protocol/adapter.rb +41 -21
- data/lib/quicsilver/protocol/capsule.rb +38 -0
- data/lib/quicsilver/protocol/connect_stream_decoder.rb +75 -0
- data/lib/quicsilver/protocol/control_stream_parser.rb +12 -1
- data/lib/quicsilver/protocol/datagram.rb +40 -0
- data/lib/quicsilver/protocol/frame_parser.rb +5 -6
- data/lib/quicsilver/protocol/frame_reader.rb +31 -2
- data/lib/quicsilver/protocol/frames.rb +128 -12
- data/lib/quicsilver/protocol/priority.rb +9 -0
- data/lib/quicsilver/protocol/qpack/decoder.rb +4 -42
- data/lib/quicsilver/protocol/qpack/encoder.rb +10 -20
- data/lib/quicsilver/protocol/qpack/huffman.rb +4 -4
- data/lib/quicsilver/protocol/request_encoder.rb +6 -3
- data/lib/quicsilver/protocol/request_parser.rb +16 -2
- data/lib/quicsilver/protocol/response_encoder.rb +18 -8
- data/lib/quicsilver/protocol/response_parser.rb +6 -0
- data/lib/quicsilver/protocol/stream_input.rb +3 -3
- data/lib/quicsilver/protocol/unidirectional_stream.rb +12 -0
- data/lib/quicsilver/protocol/web_transport.rb +214 -0
- data/lib/quicsilver/rack/context.rb +33 -0
- data/lib/quicsilver/response.rb +40 -0
- data/lib/quicsilver/server/rack_adapter.rb +77 -0
- data/lib/quicsilver/server/receive_queue.rb +106 -0
- data/lib/quicsilver/server/request_handler.rb +24 -13
- data/lib/quicsilver/server/request_registry.rb +18 -5
- data/lib/quicsilver/server/scheduler.rb +50 -0
- data/lib/quicsilver/server/schedulers/fiber_scheduler.rb +79 -0
- data/lib/quicsilver/server/schedulers/thread_scheduler.rb +76 -0
- data/lib/quicsilver/server/server.rb +655 -185
- data/lib/quicsilver/server/web_transport_flow_control.rb +270 -0
- data/lib/quicsilver/server/web_transport_manager.rb +281 -0
- data/lib/quicsilver/server/web_transport_registry.rb +40 -0
- data/lib/quicsilver/server/web_transport_session.rb +812 -0
- data/lib/quicsilver/server/web_transport_stream.rb +326 -0
- data/lib/quicsilver/transport/configuration.rb +103 -10
- data/lib/quicsilver/transport/connection.rb +302 -87
- data/lib/quicsilver/transport/connection_stats.rb +53 -0
- data/lib/quicsilver/transport/event_loop.rb +36 -0
- data/lib/quicsilver/transport/inbound_stream.rb +21 -1
- data/lib/quicsilver/transport/stream.rb +18 -3
- data/lib/quicsilver/transport/stream_event.rb +25 -2
- data/lib/quicsilver/transport/stream_id.rb +26 -0
- data/lib/quicsilver/version.rb +1 -1
- data/lib/quicsilver.rb +22 -11
- data/lib/rackup/handler/quicsilver.rb +57 -38
- data/quicsilver.gemspec +10 -3
- metadata +54 -8
- data/benchmarks/concurrent.rb +0 -110
- data/benchmarks/quicsilver_server.rb +0 -46
- data/benchmarks/rails.rb +0 -170
- data/benchmarks/throughput.rb +0 -113
- data/lib/quicsilver/quicsilver.bundle +0 -0
|
@@ -14,7 +14,7 @@ module Quicsilver
|
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def bidirectional?
|
|
17
|
-
(stream_id
|
|
17
|
+
StreamId.bidirectional?(stream_id)
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def writable?
|
|
@@ -28,6 +28,26 @@ module Quicsilver
|
|
|
28
28
|
def data
|
|
29
29
|
@buffer.string
|
|
30
30
|
end
|
|
31
|
+
|
|
32
|
+
def send(data, fin: false)
|
|
33
|
+
return unless writable?
|
|
34
|
+
Quicsilver.send_stream(@stream_handle, data, fin)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def reset(error_code = Protocol::H3_REQUEST_CANCELLED)
|
|
38
|
+
return unless writable?
|
|
39
|
+
Quicsilver.stream_reset(@stream_handle, error_code)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def stop_sending(error_code = Protocol::H3_REQUEST_CANCELLED)
|
|
43
|
+
return unless writable?
|
|
44
|
+
Quicsilver.stream_stop_sending(@stream_handle, error_code)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def abort(error_code = Protocol::H3_REQUEST_CANCELLED)
|
|
48
|
+
return unless writable?
|
|
49
|
+
Quicsilver.stream_abort(@stream_handle, error_code)
|
|
50
|
+
end
|
|
31
51
|
end
|
|
32
52
|
end
|
|
33
53
|
end
|
|
@@ -2,9 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module Quicsilver
|
|
4
4
|
module Transport
|
|
5
|
-
#
|
|
6
|
-
# Encapsulates the C handle — callers use send/reset/stop_sending methods instead
|
|
7
|
-
# of passing raw pointers to Quicsilver.send_stream etc.
|
|
5
|
+
# The handle is an opaque native registry token, not a pointer.
|
|
8
6
|
class Stream
|
|
9
7
|
attr_reader :handle
|
|
10
8
|
|
|
@@ -12,10 +10,23 @@ module Quicsilver
|
|
|
12
10
|
@handle = handle
|
|
13
11
|
end
|
|
14
12
|
|
|
13
|
+
# Returns nil until asynchronous stream startup completes, or after shutdown.
|
|
14
|
+
def stream_id
|
|
15
|
+
Quicsilver.get_stream_id(@handle)
|
|
16
|
+
end
|
|
17
|
+
|
|
15
18
|
def send(data, fin: false)
|
|
16
19
|
Quicsilver.send_stream(@handle, data, fin)
|
|
17
20
|
end
|
|
18
21
|
|
|
22
|
+
def reliable_offset=(offset)
|
|
23
|
+
Quicsilver.set_stream_reliable_offset(@handle, offset)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def grant_receive_credit(bytes, chunks)
|
|
27
|
+
Quicsilver.grant_stream_receive_credit(@handle, bytes, chunks)
|
|
28
|
+
end
|
|
29
|
+
|
|
19
30
|
def reset(error_code = Protocol::H3_REQUEST_CANCELLED)
|
|
20
31
|
Quicsilver.stream_reset(@handle, error_code)
|
|
21
32
|
end
|
|
@@ -23,6 +34,10 @@ module Quicsilver
|
|
|
23
34
|
def stop_sending(error_code = Protocol::H3_REQUEST_CANCELLED)
|
|
24
35
|
Quicsilver.stream_stop_sending(@handle, error_code)
|
|
25
36
|
end
|
|
37
|
+
|
|
38
|
+
def abort(error_code = Protocol::H3_REQUEST_CANCELLED)
|
|
39
|
+
Quicsilver.stream_abort(@handle, error_code)
|
|
40
|
+
end
|
|
26
41
|
end
|
|
27
42
|
end
|
|
28
43
|
end
|
|
@@ -5,22 +5,45 @@ module Quicsilver
|
|
|
5
5
|
# Parses the binary data packed by the C extension for stream completion events.
|
|
6
6
|
# C packs events as:
|
|
7
7
|
# RECEIVE_FIN: [stream_handle(8)][payload...]
|
|
8
|
-
# STREAM_RESET: [stream_handle(8)][error_code(8)]
|
|
8
|
+
# STREAM_RESET: [stream_handle(8)][error_code(8)][final_size(8)]
|
|
9
9
|
# STOP_SENDING: [stream_handle(8)][error_code(8)]
|
|
10
10
|
class StreamEvent
|
|
11
|
+
# A reset stream's final size is unknown to MsQuic until RESET_STREAM or
|
|
12
|
+
# FIN settles it; the C extension reports that as this sentinel so it
|
|
13
|
+
# never has to be confused with a real length.
|
|
14
|
+
FINAL_SIZE_UNKNOWN = (2**64) - 1
|
|
15
|
+
|
|
11
16
|
attr_reader :handle, :data, :error_code
|
|
12
17
|
|
|
18
|
+
# The total number of bytes the peer sent on this stream, from the
|
|
19
|
+
# RESET_STREAM frame (RFC 9000 4.5). nil when it is not settled or the
|
|
20
|
+
# event does not carry one. WebTransport session flow control needs it to
|
|
21
|
+
# charge the bytes a reset discarded (draft-ietf-webtrans-http3-16 5.4).
|
|
22
|
+
attr_reader :final_size
|
|
23
|
+
|
|
13
24
|
def initialize(raw_data, event_type)
|
|
14
25
|
@handle = raw_data[0, 8].unpack1("Q")
|
|
15
26
|
remaining = raw_data[8..] || "".b
|
|
16
27
|
|
|
17
28
|
case event_type
|
|
18
|
-
when "RECEIVE_FIN"
|
|
29
|
+
when "RECEIVE", "RECEIVE_FIN"
|
|
19
30
|
@data = remaining
|
|
20
31
|
when "STREAM_RESET", "STOP_SENDING"
|
|
21
32
|
@error_code = remaining.unpack1("Q")
|
|
33
|
+
@final_size = parse_final_size(remaining)
|
|
22
34
|
end
|
|
23
35
|
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
# Older packings carry only the error code, so a missing final size is
|
|
40
|
+
# not an error.
|
|
41
|
+
def parse_final_size(remaining)
|
|
42
|
+
return unless remaining.bytesize >= 16
|
|
43
|
+
|
|
44
|
+
value = remaining.unpack1("@8Q")
|
|
45
|
+
value unless value == FINAL_SIZE_UNKNOWN
|
|
46
|
+
end
|
|
24
47
|
end
|
|
25
48
|
end
|
|
26
49
|
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Quicsilver
|
|
4
|
+
module Transport
|
|
5
|
+
module StreamId
|
|
6
|
+
INITIATOR_MASK = 0x01
|
|
7
|
+
DIRECTION_MASK = 0x02
|
|
8
|
+
REQUEST_STREAM_MASK = INITIATOR_MASK | DIRECTION_MASK
|
|
9
|
+
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
def bidirectional?(stream_id)
|
|
13
|
+
(stream_id & DIRECTION_MASK).zero?
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def unidirectional?(stream_id)
|
|
17
|
+
!bidirectional?(stream_id)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# HTTP/3 requests run on client-initiated bidirectional QUIC streams.
|
|
21
|
+
def request?(stream_id)
|
|
22
|
+
(stream_id & REQUEST_STREAM_MASK).zero?
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
data/lib/quicsilver/version.rb
CHANGED
data/lib/quicsilver.rb
CHANGED
|
@@ -2,9 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
require "logger"
|
|
4
4
|
require_relative "quicsilver/version"
|
|
5
|
+
require_relative "quicsilver/errors"
|
|
6
|
+
require_relative "quicsilver/response"
|
|
5
7
|
|
|
6
8
|
# Protocol layer
|
|
7
9
|
require_relative "quicsilver/protocol/frames"
|
|
10
|
+
require_relative "quicsilver/protocol/datagram"
|
|
11
|
+
require_relative "quicsilver/protocol/capsule"
|
|
12
|
+
require_relative "quicsilver/protocol/web_transport"
|
|
13
|
+
require_relative "quicsilver/protocol/unidirectional_stream"
|
|
8
14
|
require_relative "quicsilver/protocol/priority"
|
|
9
15
|
require_relative "quicsilver/protocol/qpack/encoder"
|
|
10
16
|
require_relative "quicsilver/protocol/request_parser"
|
|
@@ -17,18 +23,26 @@ require_relative "quicsilver/protocol/adapter"
|
|
|
17
23
|
require_relative "quicsilver/protocol/control_stream_parser"
|
|
18
24
|
require "protocol/rack"
|
|
19
25
|
|
|
26
|
+
# Rack integration
|
|
27
|
+
require_relative "quicsilver/rack/context"
|
|
28
|
+
|
|
20
29
|
# Transport layer
|
|
30
|
+
require_relative "quicsilver/transport/stream_id"
|
|
21
31
|
require_relative "quicsilver/transport/stream"
|
|
22
32
|
require_relative "quicsilver/transport/stream_event"
|
|
23
33
|
require_relative "quicsilver/transport/inbound_stream"
|
|
24
34
|
require_relative "quicsilver/transport/event_loop"
|
|
25
35
|
require_relative "quicsilver/transport/configuration"
|
|
26
36
|
require_relative "quicsilver/transport/connection"
|
|
37
|
+
require_relative "quicsilver/transport/connection_stats"
|
|
27
38
|
|
|
28
39
|
# Server
|
|
29
40
|
require_relative "quicsilver/server/listener_data"
|
|
30
41
|
require_relative "quicsilver/server/request_registry"
|
|
31
42
|
require_relative "quicsilver/server/request_handler"
|
|
43
|
+
require_relative "quicsilver/server/rack_adapter"
|
|
44
|
+
require_relative "quicsilver/server/web_transport_flow_control"
|
|
45
|
+
require_relative "quicsilver/server/web_transport_manager"
|
|
32
46
|
require_relative "quicsilver/server/server"
|
|
33
47
|
|
|
34
48
|
# Client
|
|
@@ -37,29 +51,26 @@ require_relative "quicsilver/client/connection_pool"
|
|
|
37
51
|
require_relative "quicsilver/client/client"
|
|
38
52
|
|
|
39
53
|
# C extension
|
|
40
|
-
# Load precompiled binary if available, fall back to native extension
|
|
41
54
|
begin
|
|
42
55
|
ruby_version = /(\d+\.\d+)/.match(RUBY_VERSION)
|
|
43
|
-
|
|
56
|
+
require "quicsilver/#{ruby_version}/quicsilver"
|
|
44
57
|
rescue LoadError
|
|
45
|
-
|
|
58
|
+
require "quicsilver/quicsilver"
|
|
46
59
|
end
|
|
47
60
|
|
|
48
61
|
# Rackup handler
|
|
49
62
|
require_relative "rackup/handler/quicsilver"
|
|
50
63
|
|
|
51
64
|
module Quicsilver
|
|
52
|
-
class Error < StandardError; end
|
|
53
|
-
class ServerIsRunningError < Error; end
|
|
54
|
-
class ServerConfigurationError < Error; end
|
|
55
|
-
class ServerListenerError < Error; end
|
|
56
|
-
class ServerError < Error; end
|
|
57
|
-
class ConnectionError < Error; end
|
|
58
|
-
class TimeoutError < Error; end
|
|
59
|
-
|
|
60
65
|
class << self
|
|
61
66
|
attr_writer :logger
|
|
62
67
|
|
|
68
|
+
def stream_abort(handle, error_code)
|
|
69
|
+
receiving_stopped = stream_stop_sending(handle, error_code)
|
|
70
|
+
sending_reset = stream_reset(handle, error_code)
|
|
71
|
+
receiving_stopped || sending_reset
|
|
72
|
+
end
|
|
73
|
+
|
|
63
74
|
def logger
|
|
64
75
|
@logger ||= default_logger
|
|
65
76
|
end
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "rackup/handler"
|
|
4
|
-
require "localhost"
|
|
5
4
|
|
|
6
5
|
module Quicsilver
|
|
7
6
|
module RackHandler
|
|
@@ -10,54 +9,74 @@ module Quicsilver
|
|
|
10
9
|
Port: 4433,
|
|
11
10
|
}
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
class << self
|
|
13
|
+
def run(app, **options)
|
|
14
|
+
normalized_options = {
|
|
15
|
+
host: options[:Host] || options[:host] || DEFAULT_OPTIONS[:Host],
|
|
16
|
+
port: (options[:Port] || options[:port] || DEFAULT_OPTIONS[:Port]).to_i,
|
|
17
|
+
}
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
cert_file, key_file = certificate_paths(options)
|
|
20
|
+
config = ::Quicsilver::Transport::Configuration.new(cert_file, key_file)
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
server = ::Quicsilver::Server.new(
|
|
23
|
+
normalized_options[:port],
|
|
24
|
+
address: normalized_options[:host],
|
|
25
|
+
app: app,
|
|
26
|
+
server_configuration: config
|
|
27
|
+
)
|
|
24
28
|
|
|
25
|
-
|
|
26
|
-
|
|
29
|
+
yield server if block_given?
|
|
30
|
+
|
|
31
|
+
server.start
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def valid_options
|
|
35
|
+
{
|
|
36
|
+
"Host=HOST" => "Hostname to listen on (default: 0.0.0.0)",
|
|
37
|
+
"Port=PORT" => "Port to listen on (default: 4433)",
|
|
38
|
+
"cert_file=PATH" => "Path to TLS certificate file (required in production)",
|
|
39
|
+
"key_file=PATH" => "Path to TLS key file (required in production)"
|
|
40
|
+
}
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def certificate_paths(options)
|
|
46
|
+
cert_file = options[:cert_file]
|
|
47
|
+
key_file = options[:key_file]
|
|
48
|
+
|
|
49
|
+
if cert_file.nil? && key_file.nil?
|
|
50
|
+
if production?(options)
|
|
51
|
+
raise ArgumentError, "cert_file and key_file are required in production"
|
|
52
|
+
else
|
|
53
|
+
localhost_certificate_paths
|
|
54
|
+
end
|
|
55
|
+
elsif cert_file.nil? || key_file.nil?
|
|
56
|
+
raise ArgumentError, "cert_file and key_file must be provided together"
|
|
27
57
|
else
|
|
28
|
-
|
|
29
|
-
authority = Localhost::Authority.fetch
|
|
30
|
-
cert_file = authority.certificate_path
|
|
31
|
-
key_file = authority.key_path
|
|
32
|
-
Quicsilver.logger.info("Using auto-generated certificates for localhost")
|
|
33
|
-
Quicsilver.logger.info(" Cert: #{cert_file}")
|
|
34
|
-
Quicsilver.logger.info(" Key: #{key_file}")
|
|
58
|
+
[cert_file, key_file]
|
|
35
59
|
end
|
|
36
60
|
end
|
|
37
61
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
address: normalized_options[:host],
|
|
43
|
-
app: app,
|
|
44
|
-
server_configuration: config
|
|
45
|
-
)
|
|
62
|
+
def production?(options)
|
|
63
|
+
env = options[:environment] || ENV["RACK_ENV"] || ENV["RAILS_ENV"] || "development"
|
|
64
|
+
env == "production"
|
|
65
|
+
end
|
|
46
66
|
|
|
47
|
-
|
|
67
|
+
def localhost_certificate_paths
|
|
68
|
+
require "localhost/authority"
|
|
69
|
+
authority = Localhost::Authority.fetch
|
|
70
|
+
cert_file = authority.certificate_path
|
|
71
|
+
key_file = authority.key_path
|
|
48
72
|
|
|
49
|
-
|
|
50
|
-
|
|
73
|
+
Quicsilver.logger.info("Using auto-generated certificates for localhost")
|
|
74
|
+
Quicsilver.logger.info(" Cert: #{cert_file}")
|
|
75
|
+
Quicsilver.logger.info(" Key: #{key_file}")
|
|
51
76
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
"Host=HOST" => "Hostname to listen on (default: 0.0.0.0)",
|
|
55
|
-
"Port=PORT" => "Port to listen on (default: 4433)",
|
|
56
|
-
"cert_file=PATH" => "Path to TLS certificate file (required)",
|
|
57
|
-
"key_file=PATH" => "Path to TLS key file (required)"
|
|
58
|
-
}
|
|
77
|
+
[cert_file, key_file]
|
|
78
|
+
end
|
|
59
79
|
end
|
|
60
|
-
|
|
61
80
|
end
|
|
62
81
|
end
|
|
63
82
|
|
data/quicsilver.gemspec
CHANGED
|
@@ -30,9 +30,15 @@ Gem::Specification.new do |spec|
|
|
|
30
30
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
31
31
|
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
|
32
32
|
files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|vendor)/}) }
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
|
|
34
|
+
if ENV["RELEASE"]
|
|
35
|
+
if RUBY_PLATFORM.include?("darwin")
|
|
36
|
+
files += Dir["lib/quicsilver/libmsquic*.dylib"]
|
|
37
|
+
elsif RUBY_PLATFORM.include?("linux")
|
|
38
|
+
files += Dir["lib/quicsilver/libmsquic.so*"]
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
36
42
|
files
|
|
37
43
|
end
|
|
38
44
|
spec.bindir = "exe"
|
|
@@ -46,6 +52,7 @@ Gem::Specification.new do |spec|
|
|
|
46
52
|
spec.add_development_dependency "rake", "~> 13.0"
|
|
47
53
|
spec.add_development_dependency 'rake-compiler', '~> 1.2'
|
|
48
54
|
spec.add_development_dependency 'rake-compiler-dock', '~> 1.3'
|
|
55
|
+
spec.add_development_dependency "cibuildgem"
|
|
49
56
|
spec.add_development_dependency "minitest", "~> 5.0"
|
|
50
57
|
spec.add_development_dependency "minitest-focus", "~> 1.3"
|
|
51
58
|
spec.add_development_dependency "benchmark-ips", "~> 2.12"
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: quicsilver
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Haroon Ahmed
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: bundler
|
|
@@ -65,6 +65,20 @@ dependencies:
|
|
|
65
65
|
- - "~>"
|
|
66
66
|
- !ruby/object:Gem::Version
|
|
67
67
|
version: '1.3'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: cibuildgem
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - ">="
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '0'
|
|
75
|
+
type: :development
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '0'
|
|
68
82
|
- !ruby/object:Gem::Dependency
|
|
69
83
|
name: minitest
|
|
70
84
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -224,14 +238,16 @@ files:
|
|
|
224
238
|
- LICENSE
|
|
225
239
|
- README.md
|
|
226
240
|
- Rakefile
|
|
241
|
+
- benchmarks/baseline.rb
|
|
242
|
+
- benchmarks/compare.rb
|
|
227
243
|
- benchmarks/components.rb
|
|
228
|
-
- benchmarks/concurrent.rb
|
|
229
244
|
- benchmarks/helpers.rb
|
|
230
|
-
- benchmarks/
|
|
231
|
-
- benchmarks/
|
|
232
|
-
- benchmarks/
|
|
245
|
+
- benchmarks/latency.rb
|
|
246
|
+
- benchmarks/loss.rb
|
|
247
|
+
- benchmarks/mobile.rb
|
|
233
248
|
- bin/console
|
|
234
249
|
- bin/setup
|
|
250
|
+
- docs/connection_identity.md
|
|
235
251
|
- examples/README.md
|
|
236
252
|
- examples/benchmark.rb
|
|
237
253
|
- examples/connection_pool_demo.rb
|
|
@@ -239,23 +255,38 @@ files:
|
|
|
239
255
|
- examples/falcon_middleware.rb
|
|
240
256
|
- examples/feature_demo.rb
|
|
241
257
|
- examples/grpc_style.rb
|
|
258
|
+
- examples/latency_aware_routing.rb
|
|
259
|
+
- examples/lifecycle_drain.rb
|
|
242
260
|
- examples/minimal_http3_server.rb
|
|
243
261
|
- examples/priorities.rb
|
|
244
262
|
- examples/protocol_http_server.rb
|
|
245
263
|
- examples/rack_http3_server.rb
|
|
246
264
|
- examples/rails_feature_test.rb
|
|
265
|
+
- examples/server_stats.rb
|
|
247
266
|
- examples/simple_client_test.rb
|
|
248
267
|
- examples/streaming_sse.rb
|
|
249
268
|
- examples/trailers.rb
|
|
269
|
+
- examples/webtransport_smoke.rb
|
|
270
|
+
- examples/worker_load_balancing.rb
|
|
250
271
|
- ext/quicsilver/extconf.rb
|
|
272
|
+
- ext/quicsilver/include/msquic.h
|
|
273
|
+
- ext/quicsilver/include/msquic_posix.h
|
|
274
|
+
- ext/quicsilver/include/quic_sal_stub.h
|
|
275
|
+
- ext/quicsilver/msquic_patch.rb
|
|
276
|
+
- ext/quicsilver/patches/reliable-reset.patch
|
|
277
|
+
- ext/quicsilver/patches/stream-final-size.patch
|
|
251
278
|
- ext/quicsilver/quicsilver.c
|
|
252
279
|
- lib/quicsilver.rb
|
|
253
280
|
- lib/quicsilver/client/client.rb
|
|
254
281
|
- lib/quicsilver/client/connection_pool.rb
|
|
255
282
|
- lib/quicsilver/client/request.rb
|
|
283
|
+
- lib/quicsilver/errors.rb
|
|
256
284
|
- lib/quicsilver/libmsquic.2.dylib
|
|
257
285
|
- lib/quicsilver/protocol/adapter.rb
|
|
286
|
+
- lib/quicsilver/protocol/capsule.rb
|
|
287
|
+
- lib/quicsilver/protocol/connect_stream_decoder.rb
|
|
258
288
|
- lib/quicsilver/protocol/control_stream_parser.rb
|
|
289
|
+
- lib/quicsilver/protocol/datagram.rb
|
|
259
290
|
- lib/quicsilver/protocol/frame_parser.rb
|
|
260
291
|
- lib/quicsilver/protocol/frame_reader.rb
|
|
261
292
|
- lib/quicsilver/protocol/frames.rb
|
|
@@ -270,17 +301,32 @@ files:
|
|
|
270
301
|
- lib/quicsilver/protocol/response_parser.rb
|
|
271
302
|
- lib/quicsilver/protocol/stream_input.rb
|
|
272
303
|
- lib/quicsilver/protocol/stream_output.rb
|
|
273
|
-
- lib/quicsilver/
|
|
304
|
+
- lib/quicsilver/protocol/unidirectional_stream.rb
|
|
305
|
+
- lib/quicsilver/protocol/web_transport.rb
|
|
306
|
+
- lib/quicsilver/rack/context.rb
|
|
307
|
+
- lib/quicsilver/response.rb
|
|
274
308
|
- lib/quicsilver/server/listener_data.rb
|
|
309
|
+
- lib/quicsilver/server/rack_adapter.rb
|
|
310
|
+
- lib/quicsilver/server/receive_queue.rb
|
|
275
311
|
- lib/quicsilver/server/request_handler.rb
|
|
276
312
|
- lib/quicsilver/server/request_registry.rb
|
|
313
|
+
- lib/quicsilver/server/scheduler.rb
|
|
314
|
+
- lib/quicsilver/server/schedulers/fiber_scheduler.rb
|
|
315
|
+
- lib/quicsilver/server/schedulers/thread_scheduler.rb
|
|
277
316
|
- lib/quicsilver/server/server.rb
|
|
317
|
+
- lib/quicsilver/server/web_transport_flow_control.rb
|
|
318
|
+
- lib/quicsilver/server/web_transport_manager.rb
|
|
319
|
+
- lib/quicsilver/server/web_transport_registry.rb
|
|
320
|
+
- lib/quicsilver/server/web_transport_session.rb
|
|
321
|
+
- lib/quicsilver/server/web_transport_stream.rb
|
|
278
322
|
- lib/quicsilver/transport/configuration.rb
|
|
279
323
|
- lib/quicsilver/transport/connection.rb
|
|
324
|
+
- lib/quicsilver/transport/connection_stats.rb
|
|
280
325
|
- lib/quicsilver/transport/event_loop.rb
|
|
281
326
|
- lib/quicsilver/transport/inbound_stream.rb
|
|
282
327
|
- lib/quicsilver/transport/stream.rb
|
|
283
328
|
- lib/quicsilver/transport/stream_event.rb
|
|
329
|
+
- lib/quicsilver/transport/stream_id.rb
|
|
284
330
|
- lib/quicsilver/version.rb
|
|
285
331
|
- lib/rackup/handler/quicsilver.rb
|
|
286
332
|
- quicsilver.gemspec
|
|
@@ -306,7 +352,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
306
352
|
- !ruby/object:Gem::Version
|
|
307
353
|
version: '0'
|
|
308
354
|
requirements: []
|
|
309
|
-
rubygems_version: 3.6.
|
|
355
|
+
rubygems_version: 3.6.9
|
|
310
356
|
specification_version: 4
|
|
311
357
|
summary: HTTP/3 server implementation for Ruby
|
|
312
358
|
test_files: []
|
data/benchmarks/concurrent.rb
DELETED
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
# Concurrency benchmark: tests HTTP/3 multiplexing and multi-connection overhead.
|
|
3
|
-
#
|
|
4
|
-
# Self-contained:
|
|
5
|
-
# ruby benchmarks/concurrent.rb
|
|
6
|
-
#
|
|
7
|
-
# External server:
|
|
8
|
-
# HOST=127.0.0.1 PORT=4433 ruby benchmarks/concurrent.rb
|
|
9
|
-
|
|
10
|
-
require_relative "helpers"
|
|
11
|
-
require "benchmark"
|
|
12
|
-
|
|
13
|
-
MULTIPLEX_REQUESTS = ENV.fetch("MULTIPLEX_REQUESTS", "50").to_i
|
|
14
|
-
NUM_CLIENTS = ENV.fetch("NUM_CLIENTS", "20").to_i
|
|
15
|
-
REQUESTS_PER_CLIENT = ENV.fetch("REQUESTS_PER_CLIENT", "5").to_i
|
|
16
|
-
HOST = ENV["HOST"]
|
|
17
|
-
PORT = ENV["PORT"]&.to_i
|
|
18
|
-
|
|
19
|
-
def test_multiplexing(host, port)
|
|
20
|
-
Benchmarks::Helpers.print_header(
|
|
21
|
-
"Test 1: Single-connection multiplexing",
|
|
22
|
-
requests: MULTIPLEX_REQUESTS
|
|
23
|
-
)
|
|
24
|
-
|
|
25
|
-
request_times = []
|
|
26
|
-
mutex = Mutex.new
|
|
27
|
-
|
|
28
|
-
client = Quicsilver::Client.new(host, port, unsecure: true)
|
|
29
|
-
client.open_connection
|
|
30
|
-
|
|
31
|
-
elapsed = Benchmark.realtime do
|
|
32
|
-
threads = MULTIPLEX_REQUESTS.times.map do |i|
|
|
33
|
-
Thread.new do
|
|
34
|
-
start = Time.now
|
|
35
|
-
client.get("/multiplex/#{i}")
|
|
36
|
-
req_time = Time.now - start
|
|
37
|
-
mutex.synchronize { request_times << req_time }
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
threads.each(&:join)
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
client.disconnect
|
|
44
|
-
|
|
45
|
-
concurrency_factor = request_times.any? ? (request_times.sum / elapsed).round(2) : 0
|
|
46
|
-
puts " Wall clock: #{(elapsed * 1000).round(2)}ms"
|
|
47
|
-
puts " Avg request time: #{(request_times.sum / request_times.size * 1000).round(2)}ms"
|
|
48
|
-
puts " Throughput: #{(MULTIPLEX_REQUESTS / elapsed).round(2)} req/s"
|
|
49
|
-
puts " Concurrency factor: #{concurrency_factor}x"
|
|
50
|
-
Benchmarks::Helpers.print_stats("Latency", request_times)
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def test_concurrent_clients(host, port)
|
|
54
|
-
Benchmarks::Helpers.print_header(
|
|
55
|
-
"Test 2: Concurrent clients",
|
|
56
|
-
clients: NUM_CLIENTS,
|
|
57
|
-
requests_per_client: REQUESTS_PER_CLIENT
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
request_times = []
|
|
61
|
-
mutex = Mutex.new
|
|
62
|
-
successful = 0
|
|
63
|
-
|
|
64
|
-
elapsed = Benchmark.realtime do
|
|
65
|
-
threads = NUM_CLIENTS.times.map do |i|
|
|
66
|
-
Thread.new do
|
|
67
|
-
client = Quicsilver::Client.new(host, port, unsecure: true)
|
|
68
|
-
client.open_connection
|
|
69
|
-
|
|
70
|
-
REQUESTS_PER_CLIENT.times do |req|
|
|
71
|
-
start = Time.now
|
|
72
|
-
client.get("/client#{i}/request#{req}")
|
|
73
|
-
req_time = Time.now - start
|
|
74
|
-
mutex.synchronize { request_times << req_time }
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
client.disconnect
|
|
78
|
-
mutex.synchronize { successful += 1 }
|
|
79
|
-
rescue => e
|
|
80
|
-
$stderr.puts " Client #{i} error: #{e.message}"
|
|
81
|
-
end
|
|
82
|
-
end
|
|
83
|
-
threads.each(&:join)
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
total_requests = request_times.size
|
|
87
|
-
concurrency_factor = request_times.any? ? (request_times.sum / elapsed).round(2) : 0
|
|
88
|
-
|
|
89
|
-
puts " Successful clients: #{successful}/#{NUM_CLIENTS}"
|
|
90
|
-
puts " Total requests: #{total_requests}"
|
|
91
|
-
puts " Wall clock: #{(elapsed * 1000).round(2)}ms"
|
|
92
|
-
puts " Avg request time: #{request_times.any? ? (request_times.sum / request_times.size * 1000).round(2) : "N/A"}ms"
|
|
93
|
-
puts " Throughput: #{(total_requests / elapsed).round(2)} req/s"
|
|
94
|
-
puts " Concurrency factor: #{concurrency_factor}x"
|
|
95
|
-
Benchmarks::Helpers.print_stats("Latency", request_times)
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
def run_all(host, port)
|
|
99
|
-
test_multiplexing(host, port)
|
|
100
|
-
test_concurrent_clients(host, port)
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
if HOST && PORT
|
|
104
|
-
run_all(HOST, PORT)
|
|
105
|
-
else
|
|
106
|
-
puts "Booting inline server..."
|
|
107
|
-
Benchmarks::Helpers.with_server(Benchmarks::Helpers.benchmark_app) do |port|
|
|
108
|
-
run_all("127.0.0.1", port)
|
|
109
|
-
end
|
|
110
|
-
end
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
|
|
3
|
-
require "bundler/setup"
|
|
4
|
-
require "quicsilver"
|
|
5
|
-
require 'localhost/authority'
|
|
6
|
-
require 'json'
|
|
7
|
-
|
|
8
|
-
SIMPLE_APP = lambda do |env|
|
|
9
|
-
case env['PATH_INFO']
|
|
10
|
-
when '/'
|
|
11
|
-
[200, {'Content-Type' => 'text/plain'}, ['OK']]
|
|
12
|
-
when '/json'
|
|
13
|
-
body = JSON.generate({status: 'ok', timestamp: Time.now.to_i})
|
|
14
|
-
[200, {'Content-Type' => 'application/json'}, [body]]
|
|
15
|
-
when '/echo'
|
|
16
|
-
[200, {'Content-Type' => 'text/plain'}, [env['REQUEST_METHOD']]]
|
|
17
|
-
else
|
|
18
|
-
[404, {'Content-Type' => 'text/plain'}, ['Not Found']]
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
port = ENV["PORT"] || 4433
|
|
23
|
-
default_host = ENV["HOST"] || "0.0.0.0"
|
|
24
|
-
|
|
25
|
-
authority = Localhost::Authority.fetch
|
|
26
|
-
cert_file = authority.certificate_path
|
|
27
|
-
key_file = authority.key_path
|
|
28
|
-
|
|
29
|
-
config = ::Quicsilver::Transport::Configuration.new(cert_file, key_file)
|
|
30
|
-
|
|
31
|
-
server = ::Quicsilver::Server.new(
|
|
32
|
-
port.to_i,
|
|
33
|
-
address: default_host,
|
|
34
|
-
app: SIMPLE_APP,
|
|
35
|
-
server_configuration: config
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
puts "Starting Quicsilver on port #{port}..."
|
|
39
|
-
|
|
40
|
-
trap("INT") do
|
|
41
|
-
puts "\nStopping server..."
|
|
42
|
-
server.stop
|
|
43
|
-
exit
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
server.start # Blocks until shutdown
|