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.
Files changed (102) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +9 -1
  3. data/.github/workflows/cibuildgem.yaml +117 -0
  4. data/.gitignore +8 -1
  5. data/CHANGELOG.md +82 -0
  6. data/Gemfile.lock +27 -2
  7. data/README.md +205 -29
  8. data/Rakefile +75 -3
  9. data/benchmarks/baseline.rb +175 -0
  10. data/benchmarks/compare.rb +398 -0
  11. data/benchmarks/helpers.rb +138 -64
  12. data/benchmarks/latency.rb +128 -0
  13. data/benchmarks/loss.rb +193 -0
  14. data/benchmarks/mobile.rb +125 -0
  15. data/docs/connection_identity.md +141 -0
  16. data/examples/README.md +44 -91
  17. data/examples/benchmark.rb +191 -0
  18. data/examples/connection_pool_demo.rb +47 -0
  19. data/examples/example_helper.rb +69 -0
  20. data/examples/falcon_middleware.rb +44 -0
  21. data/examples/feature_demo.rb +125 -0
  22. data/examples/grpc_style.rb +97 -0
  23. data/examples/latency_aware_routing.rb +382 -0
  24. data/examples/lifecycle_drain.rb +264 -0
  25. data/examples/minimal_http3_server.rb +6 -18
  26. data/examples/priorities.rb +60 -0
  27. data/examples/protocol_http_server.rb +31 -0
  28. data/examples/rack_http3_server.rb +8 -20
  29. data/examples/rails_feature_test.rb +280 -0
  30. data/examples/server_stats.rb +117 -0
  31. data/examples/simple_client_test.rb +5 -5
  32. data/examples/streaming_sse.rb +33 -0
  33. data/examples/trailers.rb +69 -0
  34. data/examples/webtransport_smoke.rb +546 -0
  35. data/examples/worker_load_balancing.rb +425 -0
  36. data/ext/quicsilver/extconf.rb +111 -12
  37. data/ext/quicsilver/include/msquic.h +2226 -0
  38. data/ext/quicsilver/include/msquic_posix.h +696 -0
  39. data/ext/quicsilver/include/quic_sal_stub.h +314 -0
  40. data/ext/quicsilver/msquic_patch.rb +68 -0
  41. data/ext/quicsilver/patches/reliable-reset.patch +723 -0
  42. data/ext/quicsilver/patches/stream-final-size.patch +47 -0
  43. data/ext/quicsilver/quicsilver.c +1646 -189
  44. data/lib/quicsilver/client/client.rb +546 -88
  45. data/lib/quicsilver/client/connection_pool.rb +202 -0
  46. data/lib/quicsilver/client/request.rb +106 -14
  47. data/lib/quicsilver/errors.rb +39 -0
  48. data/lib/quicsilver/libmsquic.2.dylib +0 -0
  49. data/lib/quicsilver/protocol/adapter.rb +196 -0
  50. data/lib/quicsilver/protocol/capsule.rb +38 -0
  51. data/lib/quicsilver/protocol/connect_stream_decoder.rb +75 -0
  52. data/lib/quicsilver/protocol/control_stream_parser.rb +117 -0
  53. data/lib/quicsilver/protocol/datagram.rb +40 -0
  54. data/lib/quicsilver/protocol/frame_parser.rb +141 -0
  55. data/lib/quicsilver/protocol/frame_reader.rb +84 -0
  56. data/lib/quicsilver/protocol/frames.rb +137 -10
  57. data/lib/quicsilver/protocol/priority.rb +65 -0
  58. data/lib/quicsilver/protocol/qpack/decoder.rb +4 -42
  59. data/lib/quicsilver/protocol/qpack/encoder.rb +49 -21
  60. data/lib/quicsilver/protocol/qpack/header_block_decoder.rb +16 -1
  61. data/lib/quicsilver/protocol/qpack/huffman.rb +4 -4
  62. data/lib/quicsilver/protocol/request_encoder.rb +6 -3
  63. data/lib/quicsilver/protocol/request_parser.rb +43 -141
  64. data/lib/quicsilver/protocol/response_encoder.rb +40 -5
  65. data/lib/quicsilver/protocol/response_parser.rb +26 -128
  66. data/lib/quicsilver/protocol/stream_input.rb +98 -0
  67. data/lib/quicsilver/protocol/stream_output.rb +59 -0
  68. data/lib/quicsilver/protocol/unidirectional_stream.rb +12 -0
  69. data/lib/quicsilver/protocol/web_transport.rb +214 -0
  70. data/lib/quicsilver/rack/context.rb +33 -0
  71. data/lib/quicsilver/response.rb +40 -0
  72. data/lib/quicsilver/server/rack_adapter.rb +77 -0
  73. data/lib/quicsilver/server/receive_queue.rb +106 -0
  74. data/lib/quicsilver/server/request_handler.rb +111 -48
  75. data/lib/quicsilver/server/request_registry.rb +18 -5
  76. data/lib/quicsilver/server/scheduler.rb +50 -0
  77. data/lib/quicsilver/server/schedulers/fiber_scheduler.rb +79 -0
  78. data/lib/quicsilver/server/schedulers/thread_scheduler.rb +76 -0
  79. data/lib/quicsilver/server/server.rb +848 -104
  80. data/lib/quicsilver/server/web_transport_flow_control.rb +270 -0
  81. data/lib/quicsilver/server/web_transport_manager.rb +281 -0
  82. data/lib/quicsilver/server/web_transport_registry.rb +40 -0
  83. data/lib/quicsilver/server/web_transport_session.rb +812 -0
  84. data/lib/quicsilver/server/web_transport_stream.rb +326 -0
  85. data/lib/quicsilver/transport/configuration.rb +113 -11
  86. data/lib/quicsilver/transport/connection.rb +376 -132
  87. data/lib/quicsilver/transport/connection_stats.rb +53 -0
  88. data/lib/quicsilver/transport/event_loop.rb +36 -0
  89. data/lib/quicsilver/transport/inbound_stream.rb +21 -1
  90. data/lib/quicsilver/transport/stream.rb +18 -3
  91. data/lib/quicsilver/transport/stream_event.rb +25 -2
  92. data/lib/quicsilver/transport/stream_id.rb +26 -0
  93. data/lib/quicsilver/version.rb +1 -1
  94. data/lib/quicsilver.rb +45 -11
  95. data/lib/rackup/handler/quicsilver.rb +57 -38
  96. data/quicsilver.gemspec +17 -2
  97. metadata +120 -10
  98. data/benchmarks/concurrent.rb +0 -110
  99. data/benchmarks/quicsilver_server.rb +0 -46
  100. data/benchmarks/rails.rb +0 -170
  101. data/benchmarks/throughput.rb +0 -113
  102. data/examples/setup_certs.sh +0 -57
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quicsilver
4
+ module Transport
5
+ # Immutable snapshot of QUIC transport statistics from MsQuic's QUIC_STATISTICS_V2.
6
+ #
7
+ # stats = client.stats
8
+ # stats.rtt # => 1234 (microseconds)
9
+ # stats.send_total_packets # => 42
10
+ # stats.recv_total_bytes # => 98765
11
+ #
12
+ ConnectionStats = Data.define(
13
+ # RTT (microseconds)
14
+ :rtt, :min_rtt, :max_rtt,
15
+
16
+ # Handshake
17
+ :resumption_attempted, :resumption_succeeded,
18
+
19
+ # Send
20
+ :send_path_mtu,
21
+ :send_total_packets, :send_retransmittable_packets,
22
+ :send_suspected_lost_packets, :send_spurious_lost_packets,
23
+ :send_total_bytes, :send_total_stream_bytes,
24
+ :send_congestion_count, :send_persistent_congestion_count,
25
+ :send_congestion_window,
26
+
27
+ # Recv
28
+ :recv_total_packets, :recv_reordered_packets,
29
+ :recv_dropped_packets, :recv_duplicate_packets,
30
+ :recv_total_bytes, :recv_total_stream_bytes,
31
+ :recv_decryption_failures, :recv_valid_ack_frames,
32
+
33
+ # Misc
34
+ :key_update_count
35
+ ) do
36
+ # Build from the hash returned by the C extension.
37
+ def self.from_hash(hash)
38
+ return nil unless hash
39
+
40
+ new(**hash.transform_keys(&:to_sym))
41
+ end
42
+
43
+ def resumed?
44
+ resumption_succeeded
45
+ end
46
+
47
+ def packet_loss_rate
48
+ return 0.0 if send_total_packets == 0
49
+ send_suspected_lost_packets.to_f / send_total_packets
50
+ end
51
+ end
52
+ end
53
+ end
@@ -2,10 +2,33 @@
2
2
 
3
3
  module Quicsilver
4
4
  module Transport
5
+ # Two threads: one drives MsQuic, one delivers its events to Ruby.
6
+ #
7
+ # The poll thread runs Quicsilver.poll, which releases the GVL for the
8
+ # whole MsQuic iteration: decryption, loss recovery, stream reassembly and
9
+ # the callbacks that queue events for Ruby all happen without the lock.
10
+ # The dispatcher thread runs Quicsilver.dispatch_events, which waits on
11
+ # that queue without the GVL, then takes it to call Server.handle_stream
12
+ # (or Client#handle_stream_event) for each event in order.
13
+ #
14
+ # Before, a single thread did both, and every byte MsQuic processed was
15
+ # processed while holding the GVL, because the callback at the end of it
16
+ # called into Ruby. Splitting them lets the datapath run in parallel with
17
+ # the dispatcher and the worker pool.
18
+ #
19
+ # Event order is preserved: the queue is FIFO and there is one consumer.
5
20
  class EventLoop
21
+ # Events delivered per GVL hold. Bounds how long a batch runs; the
22
+ # dispatcher yields early only when the server's admission queue is
23
+ # under pressure (Server#enqueue_work). QUICSILVER_DISPATCH_BATCH
24
+ # overrides it for measurement.
25
+ DISPATCH_BATCH = Integer(ENV.fetch("QUICSILVER_DISPATCH_BATCH", "64"))
26
+ DISPATCH_WAIT_MS = 1000
27
+
6
28
  def initialize
7
29
  @running = false
8
30
  @thread = nil
31
+ @dispatcher = nil
9
32
  @mutex = Mutex.new
10
33
  end
11
34
 
@@ -14,16 +37,29 @@ module Quicsilver
14
37
  return if @running
15
38
 
16
39
  @running = true
40
+ Quicsilver.open_event_queue
41
+ @dispatcher = Thread.new do
42
+ Thread.current.name = "quicsilver-dispatch"
43
+ loop do
44
+ delivered = Quicsilver.dispatch_events(DISPATCH_BATCH, DISPATCH_WAIT_MS)
45
+ break if delivered.nil? # queue closed and drained
46
+ end
47
+ end
17
48
  @thread = Thread.new do
49
+ Thread.current.name = "quicsilver-poll"
18
50
  Quicsilver.poll while @running
19
51
  end
20
52
  end
21
53
  end
22
54
 
55
+ # Stop the poll thread first so nothing else is queued, then close the
56
+ # queue so the dispatcher exits once it has delivered what is there.
23
57
  def stop
24
58
  @running = false
25
59
  Quicsilver.wake
26
60
  @thread&.join(2)
61
+ Quicsilver.close_event_queue
62
+ @dispatcher&.join(2)
27
63
  end
28
64
 
29
65
  def join
@@ -14,7 +14,7 @@ module Quicsilver
14
14
  end
15
15
 
16
16
  def bidirectional?
17
- (stream_id & 0x02) == 0
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
- # Wraps a QUIC stream opened by Ruby code (client requests, server control streams).
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
@@ -1,3 +1,3 @@
1
1
  module Quicsilver
2
- VERSION = "0.3.0"
2
+ VERSION = "0.5.0"
3
3
  end
data/lib/quicsilver.rb CHANGED
@@ -2,51 +2,75 @@
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
- # Protocol layer (pure HTTP/3 codec)
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"
14
+ require_relative "quicsilver/protocol/priority"
8
15
  require_relative "quicsilver/protocol/qpack/encoder"
9
16
  require_relative "quicsilver/protocol/request_parser"
10
17
  require_relative "quicsilver/protocol/request_encoder"
11
18
  require_relative "quicsilver/protocol/response_parser"
12
19
  require_relative "quicsilver/protocol/response_encoder"
20
+ require_relative "quicsilver/protocol/stream_input"
21
+ require_relative "quicsilver/protocol/stream_output"
22
+ require_relative "quicsilver/protocol/adapter"
23
+ require_relative "quicsilver/protocol/control_stream_parser"
24
+ require "protocol/rack"
13
25
 
14
- # Transport layer (QUIC primitives)
26
+ # Rack integration
27
+ require_relative "quicsilver/rack/context"
28
+
29
+ # Transport layer
30
+ require_relative "quicsilver/transport/stream_id"
15
31
  require_relative "quicsilver/transport/stream"
16
32
  require_relative "quicsilver/transport/stream_event"
17
33
  require_relative "quicsilver/transport/inbound_stream"
18
34
  require_relative "quicsilver/transport/event_loop"
19
35
  require_relative "quicsilver/transport/configuration"
20
36
  require_relative "quicsilver/transport/connection"
37
+ require_relative "quicsilver/transport/connection_stats"
21
38
 
22
39
  # Server
23
40
  require_relative "quicsilver/server/listener_data"
24
41
  require_relative "quicsilver/server/request_registry"
25
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"
26
46
  require_relative "quicsilver/server/server"
27
47
 
28
48
  # Client
29
49
  require_relative "quicsilver/client/request"
50
+ require_relative "quicsilver/client/connection_pool"
30
51
  require_relative "quicsilver/client/client"
31
52
 
32
53
  # C extension
33
- require_relative "quicsilver/quicsilver"
54
+ begin
55
+ ruby_version = /(\d+\.\d+)/.match(RUBY_VERSION)
56
+ require "quicsilver/#{ruby_version}/quicsilver"
57
+ rescue LoadError
58
+ require "quicsilver/quicsilver"
59
+ end
34
60
 
35
61
  # Rackup handler
36
62
  require_relative "rackup/handler/quicsilver"
37
63
 
38
64
  module Quicsilver
39
- class Error < StandardError; end
40
- class ServerIsRunningError < Error; end
41
- class ServerConfigurationError < Error; end
42
- class ServerListenerError < Error; end
43
- class ServerError < Error; end
44
- class ConnectionError < Error; end
45
- class TimeoutError < Error; end
46
-
47
65
  class << self
48
66
  attr_writer :logger
49
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
+
50
74
  def logger
51
75
  @logger ||= default_logger
52
76
  end
@@ -59,4 +83,14 @@ module Quicsilver
59
83
  end
60
84
  end
61
85
  end
86
+
87
+ # Release pooled client connections on process exit.
88
+ # Closes connection handles so the OS doesn't leak UDP sockets.
89
+ # MsQuic itself is cleaned up by the OS when the process exits.
90
+ at_exit do
91
+ begin
92
+ Client.close_pool
93
+ rescue StandardError # rubocop:disable Lint/SuppressedException
94
+ end
95
+ end
62
96
  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
- def self.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
- }
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
- cert_file = options[:cert_file]
20
- key_file = options[:key_file]
19
+ cert_file, key_file = certificate_paths(options)
20
+ config = ::Quicsilver::Transport::Configuration.new(cert_file, key_file)
21
21
 
22
- if cert_file.nil? && key_file.nil?
23
- env = options[:environment] || ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'development'
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
- if env == 'production'
26
- raise ArgumentError, "cert_file and key_file are required in production"
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
- require 'localhost/authority'
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
- config = ::Quicsilver::Transport::Configuration.new(cert_file, key_file)
39
-
40
- server = ::Quicsilver::Server.new(
41
- normalized_options[:port],
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
- yield server if block_given?
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
- server.start
50
- end
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
- def self.valid_options
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
@@ -11,6 +11,7 @@ Gem::Specification.new do |spec|
11
11
  spec.summary = %q{HTTP/3 server implementation for Ruby}
12
12
  spec.description = %q{HTTP/3 server implementation for Ruby}
13
13
  spec.homepage = "https://github.com/hahmed/quicsilver"
14
+ spec.license = "MIT"
14
15
 
15
16
  # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
16
17
  # to allow pushing to a single host or delete this section to allow pushing to any host.
@@ -28,12 +29,22 @@ Gem::Specification.new do |spec|
28
29
  # Specify which files should be added to the gem when it is released.
29
30
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
30
31
  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
+ files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|vendor)/}) }
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
+
42
+ files
32
43
  end
33
44
  spec.bindir = "exe"
34
45
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
35
46
  spec.require_paths = ["lib"]
36
- spec.required_ruby_version = ">= 3.2.0"
47
+ spec.required_ruby_version = ">= 3.4.0"
37
48
 
38
49
  spec.extensions = ['ext/quicsilver/extconf.rb']
39
50
 
@@ -41,9 +52,13 @@ Gem::Specification.new do |spec|
41
52
  spec.add_development_dependency "rake", "~> 13.0"
42
53
  spec.add_development_dependency 'rake-compiler', '~> 1.2'
43
54
  spec.add_development_dependency 'rake-compiler-dock', '~> 1.3'
55
+ spec.add_development_dependency "cibuildgem"
44
56
  spec.add_development_dependency "minitest", "~> 5.0"
45
57
  spec.add_development_dependency "minitest-focus", "~> 1.3"
46
58
  spec.add_development_dependency "benchmark-ips", "~> 2.12"
59
+ spec.add_dependency "protocol-http", "~> 0.49"
60
+ spec.add_dependency "protocol-rack", "~> 0.22"
61
+ spec.add_dependency "console" # TODO: drop when protocol-rack declares this in its gemspec
47
62
  spec.add_dependency "logger"
48
63
  spec.add_dependency "localhost", "~> 1.6"
49
64
  spec.add_dependency "rack", "~> 3.0"