quicsilver 0.5.0-x86_64-linux
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.github/workflows/ci.yml +49 -0
- data/.github/workflows/cibuildgem.yaml +117 -0
- data/.gitignore +21 -0
- data/.gitmodules +3 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +175 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +67 -0
- data/LICENSE +21 -0
- data/README.md +261 -0
- data/Rakefile +130 -0
- data/benchmarks/baseline.rb +175 -0
- data/benchmarks/compare.rb +398 -0
- data/benchmarks/components.rb +191 -0
- data/benchmarks/helpers.rb +162 -0
- data/benchmarks/latency.rb +128 -0
- data/benchmarks/loss.rb +193 -0
- data/benchmarks/mobile.rb +125 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/docs/connection_identity.md +141 -0
- data/examples/README.md +58 -0
- data/examples/benchmark.rb +191 -0
- data/examples/connection_pool_demo.rb +47 -0
- data/examples/example_helper.rb +69 -0
- data/examples/falcon_middleware.rb +44 -0
- data/examples/feature_demo.rb +125 -0
- data/examples/grpc_style.rb +97 -0
- data/examples/latency_aware_routing.rb +382 -0
- data/examples/lifecycle_drain.rb +264 -0
- data/examples/minimal_http3_server.rb +14 -0
- data/examples/priorities.rb +60 -0
- data/examples/protocol_http_server.rb +31 -0
- data/examples/rack_http3_server.rb +42 -0
- data/examples/rails_feature_test.rb +280 -0
- data/examples/server_stats.rb +117 -0
- data/examples/simple_client_test.rb +26 -0
- data/examples/streaming_sse.rb +33 -0
- data/examples/trailers.rb +69 -0
- data/examples/webtransport_smoke.rb +546 -0
- data/examples/worker_load_balancing.rb +425 -0
- data/ext/quicsilver/extconf.rb +124 -0
- 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 +2741 -0
- data/lib/quicsilver/3.4/quicsilver.so +0 -0
- data/lib/quicsilver/4.0/quicsilver.so +0 -0
- data/lib/quicsilver/client/client.rb +708 -0
- data/lib/quicsilver/client/connection_pool.rb +202 -0
- data/lib/quicsilver/client/request.rb +190 -0
- data/lib/quicsilver/errors.rb +39 -0
- data/lib/quicsilver/libmsquic.so.2 +0 -0
- data/lib/quicsilver/protocol/adapter.rb +196 -0
- 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 +117 -0
- data/lib/quicsilver/protocol/datagram.rb +40 -0
- data/lib/quicsilver/protocol/frame_parser.rb +141 -0
- data/lib/quicsilver/protocol/frame_reader.rb +84 -0
- data/lib/quicsilver/protocol/frames.rb +454 -0
- data/lib/quicsilver/protocol/priority.rb +65 -0
- data/lib/quicsilver/protocol/qpack/decoder.rb +127 -0
- data/lib/quicsilver/protocol/qpack/encoder.rb +217 -0
- data/lib/quicsilver/protocol/qpack/header_block_decoder.rb +140 -0
- data/lib/quicsilver/protocol/qpack/huffman.rb +459 -0
- data/lib/quicsilver/protocol/request_encoder.rb +50 -0
- data/lib/quicsilver/protocol/request_parser.rb +289 -0
- data/lib/quicsilver/protocol/response_encoder.rb +107 -0
- data/lib/quicsilver/protocol/response_parser.rb +147 -0
- data/lib/quicsilver/protocol/stream_input.rb +98 -0
- data/lib/quicsilver/protocol/stream_output.rb +59 -0
- 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/listener_data.rb +14 -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 +149 -0
- data/lib/quicsilver/server/request_registry.rb +63 -0
- 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 +1080 -0
- 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 +234 -0
- data/lib/quicsilver/transport/connection.rb +594 -0
- data/lib/quicsilver/transport/connection_stats.rb +53 -0
- data/lib/quicsilver/transport/event_loop.rb +74 -0
- data/lib/quicsilver/transport/inbound_stream.rb +53 -0
- data/lib/quicsilver/transport/stream.rb +43 -0
- data/lib/quicsilver/transport/stream_event.rb +49 -0
- data/lib/quicsilver/transport/stream_id.rb +26 -0
- data/lib/quicsilver/version.rb +3 -0
- data/lib/quicsilver.rb +96 -0
- data/lib/rackup/handler/quicsilver.rb +96 -0
- data/quicsilver.gemspec +66 -0
- metadata +362 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Demonstrates Quicsilver::Server#stats.
|
|
5
|
+
#
|
|
6
|
+
# ruby examples/server_stats.rb
|
|
7
|
+
# PORT=4444 ruby examples/server_stats.rb
|
|
8
|
+
#
|
|
9
|
+
# The stats snapshot combines:
|
|
10
|
+
# - Quicsilver app-server state: running, connections, active requests, queue.
|
|
11
|
+
# - QUIC transport counters: process-wide connection/stream/packet/worker state.
|
|
12
|
+
|
|
13
|
+
require_relative "example_helper"
|
|
14
|
+
require "json"
|
|
15
|
+
|
|
16
|
+
HOST = ENV.fetch("HOST", "localhost")
|
|
17
|
+
PORT = Integer(ENV.fetch("PORT", "4433"))
|
|
18
|
+
|
|
19
|
+
server = nil
|
|
20
|
+
server_thread = nil
|
|
21
|
+
clients = []
|
|
22
|
+
|
|
23
|
+
begin
|
|
24
|
+
app = ->(env) {
|
|
25
|
+
case env["PATH_INFO"]
|
|
26
|
+
when "/hello"
|
|
27
|
+
[200, { "content-type" => "text/plain" }, ["hello\n"]]
|
|
28
|
+
when %r{\A/slow}
|
|
29
|
+
sleep 0.25
|
|
30
|
+
[200, { "content-type" => "text/plain" }, ["slow response\n"]]
|
|
31
|
+
else
|
|
32
|
+
[404, { "content-type" => "text/plain" }, ["not found\n"]]
|
|
33
|
+
end
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
server = Quicsilver::Server.new(PORT, app: app, server_configuration: EXAMPLE_TLS_CONFIG)
|
|
37
|
+
server_thread = Thread.new { server.start }
|
|
38
|
+
|
|
39
|
+
# Keep examples self-contained without requiring test helpers.
|
|
40
|
+
def wait_until(timeout: 3)
|
|
41
|
+
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout
|
|
42
|
+
until yield
|
|
43
|
+
raise "timed out waiting" if Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline
|
|
44
|
+
|
|
45
|
+
sleep 0.01
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
wait_until { server.running? }
|
|
50
|
+
|
|
51
|
+
# Print the useful subset first, then include the whole transport hash so it is
|
|
52
|
+
# easy to see every counter name currently exposed by Quicsilver.transport_counters.
|
|
53
|
+
def print_stats(title, server)
|
|
54
|
+
stats = server.stats
|
|
55
|
+
transport = stats["transport"] || {}
|
|
56
|
+
|
|
57
|
+
puts "\n#{title}"
|
|
58
|
+
puts "-" * title.length
|
|
59
|
+
puts JSON.pretty_generate(
|
|
60
|
+
"server" => {
|
|
61
|
+
"running" => stats["running"],
|
|
62
|
+
"shutting_down" => stats["shutting_down"],
|
|
63
|
+
"connections" => stats["connections"],
|
|
64
|
+
"requests" => stats["requests"],
|
|
65
|
+
"scheduler" => stats["scheduler"]
|
|
66
|
+
},
|
|
67
|
+
"transport_summary" => {
|
|
68
|
+
"connections_active" => transport["connections_active"],
|
|
69
|
+
"connections_connected" => transport["connections_connected"],
|
|
70
|
+
"streams_active" => transport["streams_active"],
|
|
71
|
+
"connection_queue_depth" => transport["connection_queue_depth"],
|
|
72
|
+
"connection_operations_queue_depth" => transport["connection_operations_queue_depth"],
|
|
73
|
+
"worker_operations_queue_depth" => transport["worker_operations_queue_depth"],
|
|
74
|
+
"connections_load_rejected" => transport["connections_load_rejected"],
|
|
75
|
+
"packets_dropped" => transport["packets_dropped"],
|
|
76
|
+
"app_bytes_received" => transport["app_bytes_received"],
|
|
77
|
+
"app_bytes_sent" => transport["app_bytes_sent"]
|
|
78
|
+
},
|
|
79
|
+
"transport" => transport
|
|
80
|
+
)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
puts "Quicsilver Server Stats Demo"
|
|
84
|
+
puts "Listening on https://#{HOST}:#{PORT}"
|
|
85
|
+
puts "Note: transport counters are process-wide, so they include this example's clients too."
|
|
86
|
+
|
|
87
|
+
print_stats("1. Fresh server", server)
|
|
88
|
+
|
|
89
|
+
clients = 2.times.map { Quicsilver::Client.new(HOST, PORT, unsecure: true) }
|
|
90
|
+
clients.each_with_index do |client, index|
|
|
91
|
+
response = client.get("/hello")
|
|
92
|
+
puts "client #{index + 1}: GET /hello -> #{response.status}"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
wait_until { server.stats.dig("connections", "active") >= 2 }
|
|
96
|
+
print_stats("2. After two client connections", server)
|
|
97
|
+
|
|
98
|
+
slow_requests = clients.each_with_index.map do |client, index|
|
|
99
|
+
Thread.new do
|
|
100
|
+
response = client.get("/slow/#{index + 1}")
|
|
101
|
+
puts "client #{index + 1}: GET /slow/#{index + 1} -> #{response.status}"
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
wait_until { server.stats.dig("requests", "active") >= 2 }
|
|
106
|
+
print_stats("3. While slow requests are in flight", server)
|
|
107
|
+
|
|
108
|
+
slow_requests.each(&:join)
|
|
109
|
+
print_stats("4. After slow requests complete", server)
|
|
110
|
+
|
|
111
|
+
puts "\nDone."
|
|
112
|
+
ensure
|
|
113
|
+
clients.each { |client| client.disconnect rescue nil }
|
|
114
|
+
Quicsilver::Client.close_pool rescue nil
|
|
115
|
+
server&.stop rescue nil
|
|
116
|
+
server_thread&.join(2)
|
|
117
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require "bundler/setup"
|
|
4
|
+
require "quicsilver"
|
|
5
|
+
|
|
6
|
+
puts "🔌 Simple HTTP/3 Client Test"
|
|
7
|
+
puts "=" * 40
|
|
8
|
+
|
|
9
|
+
begin
|
|
10
|
+
client = Quicsilver::Client.new("localhost", 4433, unsecure: true)
|
|
11
|
+
|
|
12
|
+
client.connect
|
|
13
|
+
|
|
14
|
+
response = client.get("/")
|
|
15
|
+
|
|
16
|
+
puts "Status: #{response.status}"
|
|
17
|
+
puts "Headers: #{response.headers.inspect}"
|
|
18
|
+
puts "Body: #{response.body}"
|
|
19
|
+
|
|
20
|
+
rescue => e
|
|
21
|
+
puts "❌ Error: #{e.class} - #{e.message}"
|
|
22
|
+
puts e.backtrace.first(10)
|
|
23
|
+
ensure
|
|
24
|
+
client&.disconnect
|
|
25
|
+
puts "👋 Done"
|
|
26
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
# Server-Sent Events over HTTP/3.
|
|
4
|
+
#
|
|
5
|
+
# Demonstrates streaming responses — data arrives chunk by chunk,
|
|
6
|
+
# not buffered. Each chunk is sent as a separate HTTP/3 DATA frame.
|
|
7
|
+
#
|
|
8
|
+
# ruby examples/streaming_sse.rb
|
|
9
|
+
# curl --http3-only -k https://localhost:4433/
|
|
10
|
+
|
|
11
|
+
require_relative "example_helper"
|
|
12
|
+
|
|
13
|
+
app = ->(env) {
|
|
14
|
+
body = Enumerator.new do |y|
|
|
15
|
+
10.times do |i|
|
|
16
|
+
y << "data: {\"count\":#{i},\"time\":\"#{Time.now.iso8601}\"}\n\n"
|
|
17
|
+
sleep 0.2
|
|
18
|
+
end
|
|
19
|
+
y << "data: {\"done\":true}\n\n"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
[200, { "content-type" => "text/event-stream", "cache-control" => "no-cache" }, body]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
server = Quicsilver::Server.new(4433, app: app, server_configuration: EXAMPLE_TLS_CONFIG)
|
|
26
|
+
|
|
27
|
+
puts "📡 Streaming SSE over HTTP/3"
|
|
28
|
+
puts " https://localhost:4433"
|
|
29
|
+
puts " curl --http3-only -k https://localhost:4433/"
|
|
30
|
+
puts " Events arrive every 200ms — no buffering."
|
|
31
|
+
puts
|
|
32
|
+
|
|
33
|
+
server.start
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
# HTTP/3 Trailers — send headers after the body.
|
|
4
|
+
#
|
|
5
|
+
# Trailers let the server report status after streaming is complete.
|
|
6
|
+
# Useful for checksums, streaming error status, and gRPC.
|
|
7
|
+
#
|
|
8
|
+
# ruby examples/trailers.rb
|
|
9
|
+
|
|
10
|
+
require_relative "example_helper"
|
|
11
|
+
require "digest"
|
|
12
|
+
|
|
13
|
+
PORT = 4433
|
|
14
|
+
HOST = "localhost"
|
|
15
|
+
|
|
16
|
+
app = ->(env) {
|
|
17
|
+
path = env["PATH_INFO"]
|
|
18
|
+
|
|
19
|
+
case path
|
|
20
|
+
when "/stream-with-checksum"
|
|
21
|
+
body = ["chunk1\n", "chunk2\n", "chunk3\n"]
|
|
22
|
+
checksum = Digest::SHA256.hexdigest(body.join)[0..7]
|
|
23
|
+
env["rack.trailers"] = { "x-checksum" => checksum }
|
|
24
|
+
[200, { "content-type" => "text/plain" }, body]
|
|
25
|
+
|
|
26
|
+
when "/grpc-style"
|
|
27
|
+
env["rack.trailers"] = { "grpc-status" => "0", "grpc-message" => "OK" }
|
|
28
|
+
[200, { "content-type" => "application/grpc" }, ['{"result":"processed"}']]
|
|
29
|
+
|
|
30
|
+
when "/grpc-error"
|
|
31
|
+
env["rack.trailers"] = { "grpc-status" => "13", "grpc-message" => "INTERNAL" }
|
|
32
|
+
[200, { "content-type" => "application/grpc" }, ['{"error":"something broke"}']]
|
|
33
|
+
|
|
34
|
+
else
|
|
35
|
+
[200, { "content-type" => "text/plain" }, ["Try /stream-with-checksum, /grpc-style, or /grpc-error"]]
|
|
36
|
+
end
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
server = Quicsilver::Server.new(PORT, app: app, server_configuration: EXAMPLE_TLS_CONFIG)
|
|
40
|
+
server_thread = Thread.new { server.start }
|
|
41
|
+
sleep 0.3
|
|
42
|
+
|
|
43
|
+
puts "HTTP/3 Trailers Demo"
|
|
44
|
+
puts "=" * 50
|
|
45
|
+
|
|
46
|
+
client = Quicsilver::Client.new(HOST, PORT, unsecure: true)
|
|
47
|
+
|
|
48
|
+
puts "\n Streaming with checksum trailer:"
|
|
49
|
+
response = client.get("/stream-with-checksum")
|
|
50
|
+
puts " Status: #{response.status}"
|
|
51
|
+
puts " Body: #{response.body.inspect}"
|
|
52
|
+
puts " Trailers: #{response.trailers}"
|
|
53
|
+
|
|
54
|
+
puts "\n gRPC-style success:"
|
|
55
|
+
response = client.get("/grpc-style")
|
|
56
|
+
puts " Status: #{response.status}"
|
|
57
|
+
puts " Body: #{response.body}"
|
|
58
|
+
puts " Trailers: #{response.trailers}"
|
|
59
|
+
|
|
60
|
+
puts "\n gRPC-style error:"
|
|
61
|
+
response = client.get("/grpc-error")
|
|
62
|
+
puts " Status: #{response.status}"
|
|
63
|
+
puts " Body: #{response.body}"
|
|
64
|
+
puts " Trailers: #{response.trailers}"
|
|
65
|
+
|
|
66
|
+
client.disconnect
|
|
67
|
+
server.stop
|
|
68
|
+
server_thread.join(2)
|
|
69
|
+
puts "\nDone"
|