quicsilver 0.3.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 +117 -0
- data/.gitignore +8 -1
- data/CHANGELOG.md +82 -0
- data/Gemfile.lock +27 -2
- data/README.md +205 -29
- data/Rakefile +75 -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/README.md +44 -91
- 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 +6 -18
- data/examples/priorities.rb +60 -0
- data/examples/protocol_http_server.rb +31 -0
- data/examples/rack_http3_server.rb +8 -20
- data/examples/rails_feature_test.rb +280 -0
- data/examples/server_stats.rb +117 -0
- data/examples/simple_client_test.rb +5 -5
- 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 +111 -12
- 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 +1646 -189
- data/lib/quicsilver/client/client.rb +546 -88
- data/lib/quicsilver/client/connection_pool.rb +202 -0
- 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 +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 +137 -10
- data/lib/quicsilver/protocol/priority.rb +65 -0
- data/lib/quicsilver/protocol/qpack/decoder.rb +4 -42
- data/lib/quicsilver/protocol/qpack/encoder.rb +49 -21
- data/lib/quicsilver/protocol/qpack/header_block_decoder.rb +16 -1
- 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 +43 -141
- data/lib/quicsilver/protocol/response_encoder.rb +40 -5
- data/lib/quicsilver/protocol/response_parser.rb +26 -128
- 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/rack_adapter.rb +77 -0
- data/lib/quicsilver/server/receive_queue.rb +106 -0
- data/lib/quicsilver/server/request_handler.rb +111 -48
- 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 +848 -104
- 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 +113 -11
- data/lib/quicsilver/transport/connection.rb +376 -132
- 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 +45 -11
- data/lib/rackup/handler/quicsilver.rb +57 -38
- data/quicsilver.gemspec +17 -2
- metadata +120 -10
- 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/examples/setup_certs.sh +0 -57
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Example 1: graceful drain as an HTTP/3 lifecycle control loop.
|
|
5
|
+
#
|
|
6
|
+
# bundle exec ruby examples/lifecycle_drain.rb
|
|
7
|
+
#
|
|
8
|
+
# This keeps the policy deliberately small and in one file:
|
|
9
|
+
#
|
|
10
|
+
# Quicsilver::Server#stats -> LifecycleSignal -> readiness / drain / safe-to-exit
|
|
11
|
+
#
|
|
12
|
+
# A real quicsilver-health/quicsilver-lifecycle package could extract the
|
|
13
|
+
# LifecycleSignal, but the core idea should stay this boring:
|
|
14
|
+
#
|
|
15
|
+
# 1. fail readiness
|
|
16
|
+
# 2. send HTTP/3 GOAWAY via Server#shutdown
|
|
17
|
+
# 3. refuse/avoid new work
|
|
18
|
+
# 4. finish in-flight streams
|
|
19
|
+
# 5. exit when active work reaches zero or the deadline expires
|
|
20
|
+
|
|
21
|
+
require_relative "example_helper"
|
|
22
|
+
|
|
23
|
+
$stdout.sync = true
|
|
24
|
+
|
|
25
|
+
HOST = ENV.fetch("HOST", "127.0.0.1")
|
|
26
|
+
PORT = Integer(ENV.fetch("PORT", "0"))
|
|
27
|
+
SLOW_REQUESTS = Integer(ENV.fetch("SLOW_REQUESTS", "3"))
|
|
28
|
+
SLOW_MS = Integer(ENV.fetch("SLOW_MS", "2_000"))
|
|
29
|
+
SHUTDOWN_TIMEOUT = Integer(ENV.fetch("SHUTDOWN_TIMEOUT", "10"))
|
|
30
|
+
|
|
31
|
+
class LifecycleSignal
|
|
32
|
+
def self.for(server)
|
|
33
|
+
new(server&.stats || {})
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def initialize(stats)
|
|
37
|
+
@stats = stats || {}
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def ready?
|
|
41
|
+
running? && !draining? && !queue_full?
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def accepting_requests?
|
|
45
|
+
ready?
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def safe_to_exit?
|
|
49
|
+
draining? && active_requests.zero? && pending_requests.zero?
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def state
|
|
53
|
+
if !running?
|
|
54
|
+
"stopped"
|
|
55
|
+
elsif draining?
|
|
56
|
+
safe_to_exit? ? "drained" : "draining"
|
|
57
|
+
elsif queue_full?
|
|
58
|
+
"overloaded"
|
|
59
|
+
else
|
|
60
|
+
"ready"
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def to_h
|
|
65
|
+
{
|
|
66
|
+
"state" => state,
|
|
67
|
+
"ready" => ready?,
|
|
68
|
+
"accepting_requests" => accepting_requests?,
|
|
69
|
+
"safe_to_exit" => safe_to_exit?,
|
|
70
|
+
"active_connections" => active_connections,
|
|
71
|
+
"active_requests" => active_requests,
|
|
72
|
+
"pending_requests" => pending_requests,
|
|
73
|
+
"queue_max" => @stats.dig("scheduler", "max_queue_size"),
|
|
74
|
+
"goaway_or_drain" => draining?
|
|
75
|
+
}
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
private
|
|
79
|
+
def running?
|
|
80
|
+
@stats["running"] == true
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def draining?
|
|
84
|
+
@stats["shutting_down"] == true
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def queue_full?
|
|
88
|
+
@stats.dig("scheduler", "full") == true
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def active_connections
|
|
92
|
+
@stats.dig("connections", "active").to_i
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def active_requests
|
|
96
|
+
@stats.dig("requests", "active").to_i
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def pending_requests
|
|
100
|
+
if running? || draining?
|
|
101
|
+
@stats.dig("scheduler", "pending").to_i
|
|
102
|
+
else
|
|
103
|
+
0
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def banner(title)
|
|
109
|
+
Example.heading(title, level: 1)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def section(number, title)
|
|
113
|
+
Example.heading("#{number}. #{title}")
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def yes_no(value)
|
|
117
|
+
value ? "yes" : "no"
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def lifecycle_header
|
|
121
|
+
Example.say
|
|
122
|
+
Example.say format(
|
|
123
|
+
" %-22s %-10s %-5s %-7s %-5s %5s %6s %5s",
|
|
124
|
+
"sample", "state", "ready", "accept", "safe", "conns", "active", "queue"
|
|
125
|
+
)
|
|
126
|
+
Example.say " #{'-' * 75}"
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def lifecycle_row(label, server)
|
|
130
|
+
data = LifecycleSignal.for(server).to_h
|
|
131
|
+
|
|
132
|
+
Example.say format(
|
|
133
|
+
" %-22s %-10s %-5s %-7s %-5s %5d %6d %5d",
|
|
134
|
+
label,
|
|
135
|
+
data["state"],
|
|
136
|
+
yes_no(data["ready"]),
|
|
137
|
+
yes_no(data["accepting_requests"]),
|
|
138
|
+
yes_no(data["safe_to_exit"]),
|
|
139
|
+
data["active_connections"],
|
|
140
|
+
data["active_requests"],
|
|
141
|
+
data["pending_requests"]
|
|
142
|
+
)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def elapsed_label(started_at)
|
|
146
|
+
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started_at
|
|
147
|
+
"t+#{format('%.1f', elapsed)}s"
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def attempt_new_request(client)
|
|
151
|
+
response = client.get("/hello", timeout: 2)
|
|
152
|
+
"#{response.status} #{response.body.inspect}"
|
|
153
|
+
rescue => error
|
|
154
|
+
"#{error.class}: #{error.message}"
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
Quicsilver.logger = Logger.new(File::NULL)
|
|
158
|
+
|
|
159
|
+
port = PORT.zero? ? Example.available_udp_port(HOST) : PORT
|
|
160
|
+
server = nil
|
|
161
|
+
|
|
162
|
+
app = ->(env) {
|
|
163
|
+
signal = LifecycleSignal.for(server)
|
|
164
|
+
|
|
165
|
+
case env["PATH_INFO"]
|
|
166
|
+
when "/_quicsilver/ready"
|
|
167
|
+
Example.json_response(signal.ready? ? 200 : 503, signal.to_h)
|
|
168
|
+
when "/_quicsilver/lifecycle"
|
|
169
|
+
Example.json_response(200, signal.to_h)
|
|
170
|
+
when %r{\A/slow/(\d+)}
|
|
171
|
+
request_id = Regexp.last_match(1)
|
|
172
|
+
sleep SLOW_MS / 1000.0
|
|
173
|
+
Example.text_response(200, "slow request #{request_id} completed while #{signal.state}\n")
|
|
174
|
+
when "/hello"
|
|
175
|
+
if signal.accepting_requests?
|
|
176
|
+
Example.text_response(200, "hello before drain\n")
|
|
177
|
+
else
|
|
178
|
+
Example.json_response(503, "error" => "server draining", "lifecycle" => signal.to_h)
|
|
179
|
+
end
|
|
180
|
+
else
|
|
181
|
+
Example.text_response(404, "not found\n")
|
|
182
|
+
end
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
server = Quicsilver::Server.new(
|
|
186
|
+
port,
|
|
187
|
+
app: app,
|
|
188
|
+
server_configuration: EXAMPLE_TLS_CONFIG,
|
|
189
|
+
threads: SLOW_REQUESTS + 2
|
|
190
|
+
)
|
|
191
|
+
server_thread = Thread.new { server.start }
|
|
192
|
+
client = Quicsilver::Client.new(HOST, port, unsecure: true)
|
|
193
|
+
shutdown_thread = nil
|
|
194
|
+
monitor = nil
|
|
195
|
+
slow_threads = []
|
|
196
|
+
monitor_done = false
|
|
197
|
+
|
|
198
|
+
begin
|
|
199
|
+
Example.wait_until { server.running? }
|
|
200
|
+
|
|
201
|
+
banner "HTTP/3 Lifecycle Drain Demo"
|
|
202
|
+
Example.detail "server", "https://#{HOST}:#{port}"
|
|
203
|
+
Example.detail "slow streams", "#{SLOW_REQUESTS} × #{SLOW_MS}ms"
|
|
204
|
+
Example.detail "shutdown timeout", "#{SHUTDOWN_TIMEOUT}s"
|
|
205
|
+
Example.detail "control loop", "readiness=false -> GOAWAY/drain -> active=0 -> safe exit"
|
|
206
|
+
|
|
207
|
+
section 1, "Warm up the connection"
|
|
208
|
+
Example.detail "GET /hello", attempt_new_request(client)
|
|
209
|
+
lifecycle_header
|
|
210
|
+
lifecycle_row "before drain", server
|
|
211
|
+
|
|
212
|
+
section 2, "Start long-running streams"
|
|
213
|
+
slow_threads = SLOW_REQUESTS.times.map do |index|
|
|
214
|
+
Thread.new do
|
|
215
|
+
started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
216
|
+
response = client.get("/slow/#{index + 1}", timeout: SHUTDOWN_TIMEOUT + 5)
|
|
217
|
+
elapsed_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - started_at) * 1000).round(1)
|
|
218
|
+
Example.say format(" stream #%-2d completed status=%d elapsed=%7.1fms", index + 1, response.status, elapsed_ms)
|
|
219
|
+
rescue => error
|
|
220
|
+
Example.say format(" stream #%-2d failed %s: %s", index + 1, error.class, error.message)
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
Example.wait_until { server.stats.dig("requests", "active") >= SLOW_REQUESTS }
|
|
225
|
+
lifecycle_header
|
|
226
|
+
lifecycle_row "streams in flight", server
|
|
227
|
+
|
|
228
|
+
section 3, "Begin drain"
|
|
229
|
+
Example.detail "action", "Server#shutdown(timeout: #{SHUTDOWN_TIMEOUT})"
|
|
230
|
+
shutdown_thread = Thread.new { server.shutdown(timeout: SHUTDOWN_TIMEOUT) }
|
|
231
|
+
Example.wait_until { server.shutting_down }
|
|
232
|
+
sleep 0.05 # give GOAWAY/readiness state a moment to propagate, but keep streams in flight
|
|
233
|
+
|
|
234
|
+
Example.detail "readiness", "not ready"
|
|
235
|
+
Example.detail "routing", "new work should move to other endpoints"
|
|
236
|
+
Example.detail "in-flight", "existing streams continue until complete"
|
|
237
|
+
lifecycle_header
|
|
238
|
+
lifecycle_row "after GOAWAY", server
|
|
239
|
+
|
|
240
|
+
monitor_started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
241
|
+
monitor = Thread.new do
|
|
242
|
+
until monitor_done
|
|
243
|
+
lifecycle_row elapsed_label(monitor_started_at), server
|
|
244
|
+
sleep 0.5
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
slow_threads.each(&:join)
|
|
249
|
+
shutdown_thread.join
|
|
250
|
+
monitor_done = true
|
|
251
|
+
monitor.join
|
|
252
|
+
|
|
253
|
+
section 4, "Shutdown complete"
|
|
254
|
+
lifecycle_header
|
|
255
|
+
lifecycle_row "after shutdown", server
|
|
256
|
+
Example.detail "takeaway", "routing changes immediately; in-flight HTTP/3 streams drain cleanly"
|
|
257
|
+
ensure
|
|
258
|
+
monitor_done = true
|
|
259
|
+
monitor&.join(1)
|
|
260
|
+
client&.disconnect rescue nil
|
|
261
|
+
Quicsilver::Client.close_pool rescue nil
|
|
262
|
+
server&.stop if server&.running? rescue nil
|
|
263
|
+
server_thread&.join(2)
|
|
264
|
+
end
|
|
@@ -1,26 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
require "quicsilver"
|
|
3
|
+
require_relative "example_helper"
|
|
5
4
|
|
|
6
5
|
puts "🚀 Minimal HTTP/3 Server Example"
|
|
7
6
|
puts "=" * 40
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
server = Quicsilver::Server.new(4433)
|
|
8
|
+
server = Quicsilver::Server.new(4433, server_configuration: EXAMPLE_TLS_CONFIG)
|
|
11
9
|
|
|
12
|
-
puts "
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
puts "✅ Server is running on port 4433"
|
|
16
|
-
puts "📋 Server info: #{server.server_info}"
|
|
10
|
+
puts "✅ Listening on https://localhost:4433"
|
|
11
|
+
puts "⏳ Press Ctrl+C to stop."
|
|
12
|
+
puts
|
|
17
13
|
|
|
18
|
-
|
|
19
|
-
puts "⏳ Server is running. Press Ctrl+C to stop..."
|
|
20
|
-
begin
|
|
21
|
-
server.wait_for_connections
|
|
22
|
-
rescue Interrupt
|
|
23
|
-
puts "\n🛑 Stopping server..."
|
|
24
|
-
server.stop
|
|
25
|
-
puts "👋 Server stopped"
|
|
26
|
-
end
|
|
14
|
+
server.start
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
# HTTP/3 Extensible Priorities (RFC 9218).
|
|
4
|
+
#
|
|
5
|
+
# Browsers send priority hints: CSS is urgency 0 (highest),
|
|
6
|
+
# images are urgency 5 (low). Quicsilver parses these and tells
|
|
7
|
+
# MsQuic to send high-priority data first.
|
|
8
|
+
#
|
|
9
|
+
# ruby examples/priorities.rb
|
|
10
|
+
#
|
|
11
|
+
# Then from another terminal, the client fires CSS and image
|
|
12
|
+
# requests and shows the priority was parsed.
|
|
13
|
+
|
|
14
|
+
require_relative "example_helper"
|
|
15
|
+
|
|
16
|
+
PORT = 4433
|
|
17
|
+
HOST = "localhost"
|
|
18
|
+
|
|
19
|
+
app = ->(env) {
|
|
20
|
+
path = env["PATH_INFO"]
|
|
21
|
+
|
|
22
|
+
case path
|
|
23
|
+
when "/style.css"
|
|
24
|
+
# Browsers send: priority: u=0 (highest urgency)
|
|
25
|
+
[200, { "content-type" => "text/css" }, ["body { margin: 0; }\n" * 50]]
|
|
26
|
+
when "/image.png"
|
|
27
|
+
# Browsers send: priority: u=5 (low urgency)
|
|
28
|
+
[200, { "content-type" => "image/png" }, ["x" * 10_000]]
|
|
29
|
+
else
|
|
30
|
+
[200, { "content-type" => "text/html" }, [
|
|
31
|
+
"<link rel='stylesheet' href='/style.css'>\n<img src='/image.png'>\n"
|
|
32
|
+
]]
|
|
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
|
+
sleep 0.3
|
|
39
|
+
|
|
40
|
+
puts "🎯 HTTP/3 Priorities Demo"
|
|
41
|
+
puts "=" * 50
|
|
42
|
+
|
|
43
|
+
client = Quicsilver::Client.new(HOST, PORT, unsecure: true)
|
|
44
|
+
|
|
45
|
+
# Request with priority header (what a browser would send)
|
|
46
|
+
puts "\n CSS request (high priority):"
|
|
47
|
+
response = client.get("/style.css", headers: { "priority" => "u=0, i" })
|
|
48
|
+
puts " Status: #{response.status}, Size: #{response.body.bytesize} bytes"
|
|
49
|
+
|
|
50
|
+
puts "\n Image request (low priority):"
|
|
51
|
+
response = client.get("/image.png", headers: { "priority" => "u=5" })
|
|
52
|
+
puts " Status: #{response.status}, Size: #{response.body.bytesize} bytes"
|
|
53
|
+
|
|
54
|
+
puts "\n MsQuic schedules CSS data before image data"
|
|
55
|
+
puts " when both are in flight on the same connection."
|
|
56
|
+
|
|
57
|
+
client.disconnect
|
|
58
|
+
server.stop
|
|
59
|
+
server_thread.join(2)
|
|
60
|
+
puts "\n✅ Done"
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Example: Running a Rack app with protocol-http as the internal layer
|
|
5
|
+
#
|
|
6
|
+
# quicsilver uses protocol-http Request/Response internally.
|
|
7
|
+
# Rack apps are automatically wrapped — no code changes needed.
|
|
8
|
+
#
|
|
9
|
+
# Modes:
|
|
10
|
+
# :rack (default) — Rack app, wrapped with Protocol::Rack::Adapter
|
|
11
|
+
# :falcon — native protocol-http app, no wrapping
|
|
12
|
+
|
|
13
|
+
require_relative "example_helper"
|
|
14
|
+
|
|
15
|
+
# Any standard Rack app works
|
|
16
|
+
app = ->(env) {
|
|
17
|
+
puts "#{env['REQUEST_METHOD']} #{env['PATH_INFO']} #{env['SERVER_PROTOCOL']}"
|
|
18
|
+
|
|
19
|
+
body = "Hello from Quicsilver!\n" \
|
|
20
|
+
"Method: #{env['REQUEST_METHOD']}\n" \
|
|
21
|
+
"Path: #{env['PATH_INFO']}\n" \
|
|
22
|
+
"Protocol: #{env['SERVER_PROTOCOL']}\n"
|
|
23
|
+
|
|
24
|
+
[200, { "content-type" => "text/plain" }, [body]]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
server = Quicsilver::Server.new(4433, app: app, server_configuration: EXAMPLE_TLS_CONFIG)
|
|
28
|
+
|
|
29
|
+
puts "Starting Quicsilver on https://localhost:4433"
|
|
30
|
+
puts "Test with: curl --http3 -k https://localhost:4433/"
|
|
31
|
+
server.start
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
require "quicsilver"
|
|
3
|
+
require_relative "example_helper"
|
|
5
4
|
|
|
6
5
|
puts "🚀 Rack HTTP/3 Server Example"
|
|
7
6
|
puts "=" * 40
|
|
@@ -31,24 +30,13 @@ app = ->(env) {
|
|
|
31
30
|
end
|
|
32
31
|
}
|
|
33
32
|
|
|
34
|
-
|
|
35
|
-
server = Quicsilver::Server.new(4433, app: app)
|
|
33
|
+
server = Quicsilver::Server.new(4433, app: app, server_configuration: EXAMPLE_TLS_CONFIG)
|
|
36
34
|
|
|
37
|
-
puts "🔧 Starting server..."
|
|
38
|
-
server.start
|
|
39
|
-
|
|
40
|
-
puts "✅ Server is running on port 4433"
|
|
41
35
|
puts "📋 Try these requests:"
|
|
42
|
-
puts " curl --http3 -k https://
|
|
43
|
-
puts " curl --http3 -k https://
|
|
44
|
-
puts " curl --http3 -k https://
|
|
36
|
+
puts " curl --http3 -k https://localhost:4433/"
|
|
37
|
+
puts " curl --http3 -k https://localhost:4433/api/users"
|
|
38
|
+
puts " curl --http3 -k https://localhost:4433/api/status"
|
|
39
|
+
puts "⏳ Press Ctrl+C to stop."
|
|
40
|
+
puts
|
|
45
41
|
|
|
46
|
-
|
|
47
|
-
puts "⏳ Server is running. Press Ctrl+C to stop..."
|
|
48
|
-
begin
|
|
49
|
-
server.wait_for_connections
|
|
50
|
-
rescue Interrupt
|
|
51
|
-
puts "\n🛑 Stopping server..."
|
|
52
|
-
server.stop
|
|
53
|
-
puts "👋 Server stopped"
|
|
54
|
-
end
|
|
42
|
+
server.start
|