quicsilver 0.1.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 +7 -0
- data/.gitignore +12 -0
- data/.gitmodules +3 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +22 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +28 -0
- data/README.md +105 -0
- data/Rakefile +31 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/examples/README.md +105 -0
- data/examples/minimal_http3_client.rb +89 -0
- data/examples/minimal_http3_server.rb +32 -0
- data/examples/rack_http3_server.rb +60 -0
- data/examples/setup_certs.sh +57 -0
- data/ext/quicsilver/extconf.rb +25 -0
- data/ext/quicsilver/quicsilver.c +807 -0
- data/lib/quicsilver/client.rb +191 -0
- data/lib/quicsilver/http3/request_encoder.rb +112 -0
- data/lib/quicsilver/http3/request_parser.rb +158 -0
- data/lib/quicsilver/http3/response_encoder.rb +73 -0
- data/lib/quicsilver/http3.rb +68 -0
- data/lib/quicsilver/listener_data.rb +29 -0
- data/lib/quicsilver/server.rb +258 -0
- data/lib/quicsilver/server_configuration.rb +49 -0
- data/lib/quicsilver/version.rb +3 -0
- data/lib/quicsilver.rb +22 -0
- data/quicsilver.gemspec +44 -0
- metadata +143 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Quicsilver
|
|
4
|
+
class Server
|
|
5
|
+
attr_reader :address, :port, :server_configuration, :running
|
|
6
|
+
|
|
7
|
+
STREAM_EVENT_RECEIVE = "RECEIVE"
|
|
8
|
+
STREAM_EVENT_RECEIVE_FIN = "RECEIVE_FIN"
|
|
9
|
+
STREAM_EVENT_CONNECTION_ESTABLISHED = "CONNECTION_ESTABLISHED"
|
|
10
|
+
STREAM_EVENT_SEND_COMPLETE = "SEND_COMPLETE"
|
|
11
|
+
|
|
12
|
+
class << self
|
|
13
|
+
def stream_buffers
|
|
14
|
+
@stream_buffers ||= {}
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def stream_handles
|
|
18
|
+
@stream_handles ||= {}
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def rack_app
|
|
22
|
+
@rack_app
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def rack_app=(app)
|
|
26
|
+
@rack_app = app
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def handle_stream(stream_id, event, data)
|
|
30
|
+
case event
|
|
31
|
+
when STREAM_EVENT_CONNECTION_ESTABLISHED
|
|
32
|
+
puts "🔧 Ruby: Connection established with client"
|
|
33
|
+
connection_handle = data.unpack1('Q') # Unpack 64-bit pointer
|
|
34
|
+
stream = Quicsilver.open_stream(connection_handle, true) # unidirectional
|
|
35
|
+
control_data = Quicsilver::HTTP3.build_control_stream
|
|
36
|
+
Quicsilver.send_stream(stream, control_data, false) # no FIN
|
|
37
|
+
when STREAM_EVENT_SEND_COMPLETE
|
|
38
|
+
puts "🔧 Ruby: Control stream sent to client"
|
|
39
|
+
when STREAM_EVENT_RECEIVE
|
|
40
|
+
# Accumulate data
|
|
41
|
+
stream_buffers[stream_id] ||= ""
|
|
42
|
+
stream_buffers[stream_id] += data
|
|
43
|
+
puts "🔧 Ruby: Stream #{stream_id}: Buffering #{data.bytesize} bytes (total: #{stream_buffers[stream_id].bytesize})"
|
|
44
|
+
when STREAM_EVENT_RECEIVE_FIN
|
|
45
|
+
# Extract stream handle from data (first 8 bytes)
|
|
46
|
+
stream_handle = data[0, 8].unpack1('Q')
|
|
47
|
+
actual_data = data[8..-1] || ""
|
|
48
|
+
|
|
49
|
+
# Store stream handle for later use
|
|
50
|
+
stream_handles[stream_id] = stream_handle
|
|
51
|
+
|
|
52
|
+
# Final chunk - process complete message
|
|
53
|
+
stream_buffers[stream_id] ||= ""
|
|
54
|
+
stream_buffers[stream_id] += actual_data
|
|
55
|
+
complete_data = stream_buffers[stream_id]
|
|
56
|
+
|
|
57
|
+
# Handle bidirectional streams (client requests)
|
|
58
|
+
if bidirectional?(stream_id)
|
|
59
|
+
handle_http3_request(stream_id, complete_data)
|
|
60
|
+
else
|
|
61
|
+
# Unidirectional stream (control/QPACK)
|
|
62
|
+
puts "✅ Ruby: Stream #{stream_id}: Control/QPACK stream (#{complete_data.bytesize} bytes)"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Clean up buffers
|
|
66
|
+
stream_buffers.delete(stream_id)
|
|
67
|
+
stream_handles.delete(stream_id)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
def bidirectional?(stream_id)
|
|
74
|
+
# Client-initiated bidirectional streams have bit 0x02 clear
|
|
75
|
+
(stream_id & 0x02) == 0
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def handle_http3_request(stream_id, data)
|
|
79
|
+
parser = HTTP3::RequestParser.new(data)
|
|
80
|
+
parser.parse
|
|
81
|
+
env = parser.to_rack_env
|
|
82
|
+
|
|
83
|
+
if env && rack_app
|
|
84
|
+
puts "✅ Ruby: #{env['REQUEST_METHOD']} #{env['PATH_INFO']}"
|
|
85
|
+
|
|
86
|
+
# Call Rack app
|
|
87
|
+
status, headers, body = rack_app.call(env)
|
|
88
|
+
|
|
89
|
+
# Encode response
|
|
90
|
+
encoder = HTTP3::ResponseEncoder.new(status, headers, body)
|
|
91
|
+
response_data = encoder.encode
|
|
92
|
+
|
|
93
|
+
# Get stream handle from stored handles
|
|
94
|
+
stream_handle = stream_handles[stream_id]
|
|
95
|
+
if stream_handle
|
|
96
|
+
# Send response
|
|
97
|
+
Quicsilver.send_stream(stream_handle, response_data, true)
|
|
98
|
+
puts "✅ Ruby: Response sent: #{status}"
|
|
99
|
+
else
|
|
100
|
+
puts "❌ Ruby: Stream handle not found for stream #{stream_id}"
|
|
101
|
+
end
|
|
102
|
+
else
|
|
103
|
+
puts "❌ Ruby: Failed to parse request"
|
|
104
|
+
end
|
|
105
|
+
rescue => e
|
|
106
|
+
puts "❌ Ruby: Error handling request: #{e.class} - #{e.message}"
|
|
107
|
+
puts e.backtrace.first(5)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def initialize(port = 4433, address: "0.0.0.0", app: nil, server_configuration: nil)
|
|
112
|
+
@port = port
|
|
113
|
+
@address = address
|
|
114
|
+
@app = app || default_rack_app
|
|
115
|
+
@server_configuration = server_configuration || ServerConfiguration.new
|
|
116
|
+
@running = false
|
|
117
|
+
@listener_data = nil
|
|
118
|
+
|
|
119
|
+
# Set class-level rack app so handle_stream can access it
|
|
120
|
+
self.class.rack_app = @app
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def start
|
|
124
|
+
raise ServerIsRunningError, "Server is already running" if @running
|
|
125
|
+
|
|
126
|
+
# Initialize MSQUIC if not already done
|
|
127
|
+
Quicsilver.open_connection
|
|
128
|
+
|
|
129
|
+
config = Quicsilver.create_server_configuration(@server_configuration.to_h)
|
|
130
|
+
unless config
|
|
131
|
+
raise ServerConfigurationError, "Failed to create server configuration"
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Create and start the listener
|
|
135
|
+
@listener_data = start_listener(config)
|
|
136
|
+
start_server(config)
|
|
137
|
+
|
|
138
|
+
@running = true
|
|
139
|
+
|
|
140
|
+
puts "✅ QUIC server started successfully on #{@address}:#{@port}"
|
|
141
|
+
rescue ServerConfigurationError, ServerListenerError => e
|
|
142
|
+
cleanup_failed_server
|
|
143
|
+
@running = false
|
|
144
|
+
raise e
|
|
145
|
+
rescue => e
|
|
146
|
+
cleanup_failed_server
|
|
147
|
+
@running = false
|
|
148
|
+
|
|
149
|
+
error_msg = case e.message
|
|
150
|
+
when /0x16/
|
|
151
|
+
"Invalid parameter error - check certificate files and network configuration"
|
|
152
|
+
when /0x30/
|
|
153
|
+
"Address already in use - port #{@port} may be occupied"
|
|
154
|
+
else
|
|
155
|
+
e.message
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
raise ServerError, "Server start failed: #{error_msg}"
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def stop
|
|
162
|
+
return unless @running
|
|
163
|
+
|
|
164
|
+
puts "🛑 Stopping QUIC server..."
|
|
165
|
+
|
|
166
|
+
if @listener_data
|
|
167
|
+
listener_handle = @listener_data[0]
|
|
168
|
+
Quicsilver.stop_listener(listener_handle)
|
|
169
|
+
Quicsilver.close_listener(@listener_data)
|
|
170
|
+
@listener_data = nil
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
@running = false
|
|
174
|
+
puts "👋 Server stopped"
|
|
175
|
+
rescue
|
|
176
|
+
puts "⚠️ Error during server shutdown"
|
|
177
|
+
# Continue with cleanup even if there are errors
|
|
178
|
+
@listener_data = nil
|
|
179
|
+
@running = false
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def running?
|
|
183
|
+
@running
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def server_info
|
|
187
|
+
{
|
|
188
|
+
address: @address,
|
|
189
|
+
port: @port,
|
|
190
|
+
running: @running,
|
|
191
|
+
cert_file: @cert_file,
|
|
192
|
+
key_file: @key_file
|
|
193
|
+
}
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def wait_for_connections(timeout: nil)
|
|
197
|
+
if timeout
|
|
198
|
+
end_time = Time.now + timeout
|
|
199
|
+
while Time.now < end_time && @running
|
|
200
|
+
Quicsilver.process_events
|
|
201
|
+
sleep(0.01) # Poll every 10ms
|
|
202
|
+
end
|
|
203
|
+
else
|
|
204
|
+
# Keep the server running indefinitely
|
|
205
|
+
# Process events from MSQUIC callbacks
|
|
206
|
+
loop do
|
|
207
|
+
Quicsilver.process_events
|
|
208
|
+
sleep(0.01) # Poll every 10ms
|
|
209
|
+
break unless @running
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
private
|
|
215
|
+
|
|
216
|
+
def default_rack_app
|
|
217
|
+
->(env) {
|
|
218
|
+
[200,
|
|
219
|
+
{'Content-Type' => 'text/plain'},
|
|
220
|
+
["Hello from Quicsilver!\nMethod: #{env['REQUEST_METHOD']}\nPath: #{env['PATH_INFO']}\n"]]
|
|
221
|
+
}
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def start_server(config)
|
|
225
|
+
result = Quicsilver.start_listener(@listener_data.listener_handle, @address, @port)
|
|
226
|
+
unless result
|
|
227
|
+
Quicsilver.close_configuration(config)
|
|
228
|
+
cleanup_failed_server
|
|
229
|
+
raise ServerListenerError, "Failed to start listener on #{@address}:#{@port}"
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def start_listener(config)
|
|
234
|
+
result = Quicsilver.create_listener(config)
|
|
235
|
+
listener_data = ListenerData.new(result[0], result[1])
|
|
236
|
+
|
|
237
|
+
unless listener_data
|
|
238
|
+
Quicsilver.close_configuration(config)
|
|
239
|
+
raise ServerListenerError, "Failed to create listener on #{@address}:#{@port}"
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
listener_data
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def cleanup_failed_server
|
|
246
|
+
if @listener_data
|
|
247
|
+
begin
|
|
248
|
+
Quicsilver.stop_listener(@listener_data)
|
|
249
|
+
Quicsilver.close_listener(@listener_data)
|
|
250
|
+
rescue
|
|
251
|
+
# Ignore cleanup errors
|
|
252
|
+
ensure
|
|
253
|
+
@listener_data = nil
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Quicsilver
|
|
4
|
+
class ServerConfiguration
|
|
5
|
+
attr_reader :cert_file, :key_file, :idle_timeout, :server_resumption_level, :peer_bidi_stream_count,
|
|
6
|
+
:peer_unidi_stream_count
|
|
7
|
+
|
|
8
|
+
QUIC_SERVER_RESUME_AND_ZERORTT = 1
|
|
9
|
+
QUIC_SERVER_RESUME_ONLY = 2
|
|
10
|
+
QUIC_SERVER_RESUME_AND_REUSE = 3
|
|
11
|
+
QUIC_SERVER_RESUME_AND_REUSE_ZERORTT = 4
|
|
12
|
+
|
|
13
|
+
def initialize(cert_file = nil, key_file = nil, options = {})
|
|
14
|
+
@cert_file = cert_file.nil? ? "certs/server.crt" : cert_file
|
|
15
|
+
@key_file = key_file.nil? ? "certs/server.key" : key_file
|
|
16
|
+
@idle_timeout = options[:idle_timeout].nil? ? 10000 : options[:idle_timeout]
|
|
17
|
+
@server_resumption_level = options[:server_resumption_level].nil? ? QUIC_SERVER_RESUME_AND_ZERORTT : options[:server_resumption_level]
|
|
18
|
+
@peer_bidi_stream_count = options[:peer_bidi_stream_count].nil? ? 10 : options[:peer_bidi_stream_count]
|
|
19
|
+
@peer_unidi_stream_count = options[:peer_unidi_stream_count].nil? ? 10 : options[:peer_unidi_stream_count]
|
|
20
|
+
@alpn = options[:alpn].nil? ? "h3" : options[:alpn]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Common HTTP/3 ALPN Values:
|
|
24
|
+
# "h3" - HTTP/3 (most common)
|
|
25
|
+
# "h3-29" - HTTP/3 draft version 29
|
|
26
|
+
# "h3-28" - HTTP/3 draft version 28
|
|
27
|
+
# "h3-27" - HTTP/3 draft version 27
|
|
28
|
+
# Other QUIC ALPN Values:
|
|
29
|
+
# "hq-interop" - HTTP/0.9 over QUIC (testing)
|
|
30
|
+
# "hq-29" - HTTP/0.9 over QUIC draft 29
|
|
31
|
+
# "doq" - DNS over QUIC
|
|
32
|
+
# "doq-i03" - DNS over QUIC draft
|
|
33
|
+
def alpn
|
|
34
|
+
@alpn
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def to_h
|
|
38
|
+
{
|
|
39
|
+
cert_file: @cert_file,
|
|
40
|
+
key_file: @key_file,
|
|
41
|
+
idle_timeout: @idle_timeout,
|
|
42
|
+
server_resumption_level: @server_resumption_level,
|
|
43
|
+
peer_bidi_stream_count: @peer_bidi_stream_count,
|
|
44
|
+
peer_unidi_stream_count: @peer_unidi_stream_count,
|
|
45
|
+
alpn: alpn
|
|
46
|
+
}
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
data/lib/quicsilver.rb
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "quicsilver/version"
|
|
4
|
+
require_relative "quicsilver/client"
|
|
5
|
+
require_relative "quicsilver/listener_data"
|
|
6
|
+
require_relative "quicsilver/server"
|
|
7
|
+
require_relative "quicsilver/server_configuration"
|
|
8
|
+
require_relative "quicsilver/http3"
|
|
9
|
+
require_relative "quicsilver/http3/request_parser"
|
|
10
|
+
require_relative "quicsilver/http3/request_encoder"
|
|
11
|
+
require_relative "quicsilver/http3/response_encoder"
|
|
12
|
+
require_relative "quicsilver/quicsilver"
|
|
13
|
+
|
|
14
|
+
module Quicsilver
|
|
15
|
+
class Error < StandardError; end
|
|
16
|
+
class ServerIsRunningError < Error; end
|
|
17
|
+
class ServerConfigurationError < Error; end
|
|
18
|
+
class ServerListenerError < Error; end
|
|
19
|
+
class ServerError < Error; end
|
|
20
|
+
class ConnectionError < Error; end
|
|
21
|
+
class TimeoutError < Error; end
|
|
22
|
+
end
|
data/quicsilver.gemspec
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
lib = File.expand_path("../lib", __FILE__)
|
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
3
|
+
require "quicsilver/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "quicsilver"
|
|
7
|
+
spec.version = Quicsilver::VERSION
|
|
8
|
+
spec.authors = ["Haroon Ahmed"]
|
|
9
|
+
spec.email = ["haroon.ahmed25@gmail.com"]
|
|
10
|
+
|
|
11
|
+
spec.summary = %q{Minimal HTTP/3 server implementation for Ruby}
|
|
12
|
+
spec.description = %q{A minimal HTTP/3 server implementation for Ruby using Microsoft's MSQUIC library.}
|
|
13
|
+
spec.homepage = "https://github.com/hahmed/quicsilver"
|
|
14
|
+
|
|
15
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
|
16
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
|
17
|
+
if spec.respond_to?(:metadata)
|
|
18
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
|
19
|
+
|
|
20
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
21
|
+
spec.metadata["source_code_uri"] = "https://github.com/hahmed/quicsilver"
|
|
22
|
+
spec.metadata["changelog_uri"] = "https://github.com/hahmed/quicsilver/blob/main/CHANGELOG.md"
|
|
23
|
+
else
|
|
24
|
+
raise "RubyGems 2.0 or newer is required to protect against " \
|
|
25
|
+
"public gem pushes."
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Specify which files should be added to the gem when it is released.
|
|
29
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
30
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
|
31
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
32
|
+
end
|
|
33
|
+
spec.bindir = "exe"
|
|
34
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
35
|
+
spec.require_paths = ["lib"]
|
|
36
|
+
|
|
37
|
+
spec.extensions = ['ext/quicsilver/extconf.rb']
|
|
38
|
+
|
|
39
|
+
spec.add_development_dependency "bundler", "~> 2.0"
|
|
40
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
|
41
|
+
spec.add_development_dependency 'rake-compiler', '~> 1.2'
|
|
42
|
+
spec.add_development_dependency 'rake-compiler-dock', '~> 1.3'
|
|
43
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
|
44
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: quicsilver
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Haroon Ahmed
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 2025-10-28 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: bundler
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '2.0'
|
|
19
|
+
type: :development
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '2.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: rake
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '10.0'
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '10.0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: rake-compiler
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '1.2'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '1.2'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: rake-compiler-dock
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '1.3'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '1.3'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: minitest
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - "~>"
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '5.0'
|
|
75
|
+
type: :development
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - "~>"
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '5.0'
|
|
82
|
+
description: A minimal HTTP/3 server implementation for Ruby using Microsoft's MSQUIC
|
|
83
|
+
library.
|
|
84
|
+
email:
|
|
85
|
+
- haroon.ahmed25@gmail.com
|
|
86
|
+
executables: []
|
|
87
|
+
extensions:
|
|
88
|
+
- ext/quicsilver/extconf.rb
|
|
89
|
+
extra_rdoc_files: []
|
|
90
|
+
files:
|
|
91
|
+
- ".gitignore"
|
|
92
|
+
- ".gitmodules"
|
|
93
|
+
- ".ruby-version"
|
|
94
|
+
- CHANGELOG.md
|
|
95
|
+
- Gemfile
|
|
96
|
+
- Gemfile.lock
|
|
97
|
+
- README.md
|
|
98
|
+
- Rakefile
|
|
99
|
+
- bin/console
|
|
100
|
+
- bin/setup
|
|
101
|
+
- examples/README.md
|
|
102
|
+
- examples/minimal_http3_client.rb
|
|
103
|
+
- examples/minimal_http3_server.rb
|
|
104
|
+
- examples/rack_http3_server.rb
|
|
105
|
+
- examples/setup_certs.sh
|
|
106
|
+
- ext/quicsilver/extconf.rb
|
|
107
|
+
- ext/quicsilver/quicsilver.c
|
|
108
|
+
- lib/quicsilver.rb
|
|
109
|
+
- lib/quicsilver/client.rb
|
|
110
|
+
- lib/quicsilver/http3.rb
|
|
111
|
+
- lib/quicsilver/http3/request_encoder.rb
|
|
112
|
+
- lib/quicsilver/http3/request_parser.rb
|
|
113
|
+
- lib/quicsilver/http3/response_encoder.rb
|
|
114
|
+
- lib/quicsilver/listener_data.rb
|
|
115
|
+
- lib/quicsilver/server.rb
|
|
116
|
+
- lib/quicsilver/server_configuration.rb
|
|
117
|
+
- lib/quicsilver/version.rb
|
|
118
|
+
- quicsilver.gemspec
|
|
119
|
+
homepage: https://github.com/hahmed/quicsilver
|
|
120
|
+
licenses: []
|
|
121
|
+
metadata:
|
|
122
|
+
allowed_push_host: https://rubygems.org
|
|
123
|
+
homepage_uri: https://github.com/hahmed/quicsilver
|
|
124
|
+
source_code_uri: https://github.com/hahmed/quicsilver
|
|
125
|
+
changelog_uri: https://github.com/hahmed/quicsilver/blob/main/CHANGELOG.md
|
|
126
|
+
rdoc_options: []
|
|
127
|
+
require_paths:
|
|
128
|
+
- lib
|
|
129
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
130
|
+
requirements:
|
|
131
|
+
- - ">="
|
|
132
|
+
- !ruby/object:Gem::Version
|
|
133
|
+
version: '0'
|
|
134
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
135
|
+
requirements:
|
|
136
|
+
- - ">="
|
|
137
|
+
- !ruby/object:Gem::Version
|
|
138
|
+
version: '0'
|
|
139
|
+
requirements: []
|
|
140
|
+
rubygems_version: 3.6.2
|
|
141
|
+
specification_version: 4
|
|
142
|
+
summary: Minimal HTTP/3 server implementation for Ruby
|
|
143
|
+
test_files: []
|