quicsilver 0.3.0 → 0.4.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 +1 -1
- data/.github/workflows/cibuildgem.yaml +93 -0
- data/.gitignore +3 -1
- data/CHANGELOG.md +32 -0
- data/Gemfile.lock +20 -2
- data/README.md +92 -29
- data/Rakefile +67 -2
- data/benchmarks/concurrent.rb +2 -2
- data/benchmarks/rails.rb +3 -3
- data/benchmarks/throughput.rb +2 -2
- data/examples/README.md +44 -91
- data/examples/benchmark.rb +111 -0
- data/examples/connection_pool_demo.rb +47 -0
- data/examples/example_helper.rb +18 -0
- data/examples/falcon_middleware.rb +44 -0
- data/examples/feature_demo.rb +125 -0
- data/examples/grpc_style.rb +97 -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 +260 -0
- data/examples/simple_client_test.rb +2 -2
- data/examples/streaming_sse.rb +33 -0
- data/examples/trailers.rb +69 -0
- data/ext/quicsilver/extconf.rb +14 -0
- data/ext/quicsilver/quicsilver.c +39 -0
- data/lib/quicsilver/client/client.rb +138 -39
- data/lib/quicsilver/client/connection_pool.rb +106 -0
- data/lib/quicsilver/libmsquic.2.dylib +0 -0
- data/lib/quicsilver/protocol/adapter.rb +176 -0
- data/lib/quicsilver/protocol/control_stream_parser.rb +106 -0
- data/lib/quicsilver/protocol/frame_parser.rb +142 -0
- data/lib/quicsilver/protocol/frame_reader.rb +55 -0
- data/lib/quicsilver/protocol/frames.rb +18 -7
- data/lib/quicsilver/protocol/priority.rb +56 -0
- data/lib/quicsilver/protocol/qpack/encoder.rb +39 -1
- data/lib/quicsilver/protocol/qpack/header_block_decoder.rb +16 -1
- data/lib/quicsilver/protocol/request_parser.rb +28 -140
- data/lib/quicsilver/protocol/response_encoder.rb +27 -2
- data/lib/quicsilver/protocol/response_parser.rb +22 -130
- data/lib/quicsilver/protocol/stream_input.rb +98 -0
- data/lib/quicsilver/protocol/stream_output.rb +59 -0
- data/lib/quicsilver/quicsilver.bundle +0 -0
- data/lib/quicsilver/server/request_handler.rb +96 -44
- data/lib/quicsilver/server/server.rb +316 -42
- data/lib/quicsilver/transport/configuration.rb +10 -1
- data/lib/quicsilver/transport/connection.rb +92 -63
- data/lib/quicsilver/version.rb +1 -1
- data/lib/quicsilver.rb +26 -3
- data/quicsilver.gemspec +10 -2
- metadata +69 -5
- data/examples/setup_certs.sh +0 -57
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d4926d24fbce141791505bb14d59e543a30a6c5cbf8ca3c3fd36c9123b2941db
|
|
4
|
+
data.tar.gz: 45c128c6bf9fc48b8267ba2927414f8429607e8f99e4169235d12ebe26863ccc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ad6f92657375909bac67ce88bfa64d47c53a2dd69bb424a36f297927778389247714c092b004287cc278c5ecdf94c0f186a4c15f4fa4cfff62dbef9fcfc155f6
|
|
7
|
+
data.tar.gz: 1f5d2812bfb18a15b1821c8afcb18f779663ec3066495ef69dfd9a1c79f8d83f13f7c6287b8ce56be318476ea69fd6c520c1dc4298ceb807c64f6de0747b3035
|
data/.github/workflows/ci.yml
CHANGED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
name: "Package and release gems with precompiled binaries"
|
|
2
|
+
on:
|
|
3
|
+
workflow_dispatch:
|
|
4
|
+
inputs:
|
|
5
|
+
release:
|
|
6
|
+
description: "If the whole build passes on all platforms, release the gems on RubyGems.org"
|
|
7
|
+
required: false
|
|
8
|
+
type: boolean
|
|
9
|
+
default: false
|
|
10
|
+
env:
|
|
11
|
+
CIBUILDGEM: 1
|
|
12
|
+
jobs:
|
|
13
|
+
compile:
|
|
14
|
+
timeout-minutes: 20
|
|
15
|
+
name: "Cross compile the gem on different ruby versions"
|
|
16
|
+
strategy:
|
|
17
|
+
matrix:
|
|
18
|
+
os: ["macos-latest", "ubuntu-22.04"]
|
|
19
|
+
runs-on: "${{ matrix.os }}"
|
|
20
|
+
steps:
|
|
21
|
+
- name: "Checkout code"
|
|
22
|
+
uses: "actions/checkout@v5"
|
|
23
|
+
with:
|
|
24
|
+
submodules: recursive
|
|
25
|
+
- name: "Setup Ruby"
|
|
26
|
+
uses: "ruby/setup-ruby@v1"
|
|
27
|
+
with:
|
|
28
|
+
ruby-version: "3.4"
|
|
29
|
+
bundler-cache: true
|
|
30
|
+
- name: "Build MsQuic"
|
|
31
|
+
run: bundle exec rake build_msquic
|
|
32
|
+
- name: "Run cibuildgem"
|
|
33
|
+
uses: "shopify/cibuildgem/.github/actions/cibuildgem@main"
|
|
34
|
+
with:
|
|
35
|
+
step: "compile"
|
|
36
|
+
test:
|
|
37
|
+
timeout-minutes: 20
|
|
38
|
+
name: "Run the test suite"
|
|
39
|
+
needs: compile
|
|
40
|
+
strategy:
|
|
41
|
+
matrix:
|
|
42
|
+
os: ["macos-latest", "ubuntu-22.04"]
|
|
43
|
+
rubies: ["3.4", "4.0"]
|
|
44
|
+
type: ["cross", "native"]
|
|
45
|
+
runs-on: "${{ matrix.os }}"
|
|
46
|
+
steps:
|
|
47
|
+
- name: "Checkout code"
|
|
48
|
+
uses: "actions/checkout@v5"
|
|
49
|
+
- name: "Setup Ruby"
|
|
50
|
+
uses: "ruby/setup-ruby@v1"
|
|
51
|
+
with:
|
|
52
|
+
ruby-version: "${{ matrix.rubies }}"
|
|
53
|
+
bundler-cache: true
|
|
54
|
+
- name: "Run cibuildgem"
|
|
55
|
+
uses: "shopify/cibuildgem/.github/actions/cibuildgem@main"
|
|
56
|
+
with:
|
|
57
|
+
step: "test_${{ matrix.type }}"
|
|
58
|
+
install:
|
|
59
|
+
timeout-minutes: 5
|
|
60
|
+
name: "Verify the gem can be installed"
|
|
61
|
+
needs: test
|
|
62
|
+
strategy:
|
|
63
|
+
matrix:
|
|
64
|
+
os: ["macos-latest", "ubuntu-22.04"]
|
|
65
|
+
runs-on: "${{ matrix.os }}"
|
|
66
|
+
steps:
|
|
67
|
+
- name: "Setup Ruby"
|
|
68
|
+
uses: "ruby/setup-ruby@v1"
|
|
69
|
+
with:
|
|
70
|
+
ruby-version: "4.0.0"
|
|
71
|
+
- name: "Run cibuildgem"
|
|
72
|
+
uses: "shopify/cibuildgem/.github/actions/cibuildgem@main"
|
|
73
|
+
with:
|
|
74
|
+
step: "install"
|
|
75
|
+
release:
|
|
76
|
+
environment: release
|
|
77
|
+
permissions:
|
|
78
|
+
id-token: write
|
|
79
|
+
contents: read
|
|
80
|
+
timeout-minutes: 5
|
|
81
|
+
if: ${{ inputs.release }}
|
|
82
|
+
name: "Release all gems with RubyGems"
|
|
83
|
+
needs: install
|
|
84
|
+
runs-on: "ubuntu-latest"
|
|
85
|
+
steps:
|
|
86
|
+
- name: "Setup Ruby"
|
|
87
|
+
uses: "ruby/setup-ruby@v1"
|
|
88
|
+
with:
|
|
89
|
+
ruby-version: "4.0.0"
|
|
90
|
+
- name: "Run cibuildgem"
|
|
91
|
+
uses: "shopify/cibuildgem/.github/actions/cibuildgem@main"
|
|
92
|
+
with:
|
|
93
|
+
step: "release"
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,38 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.4.0] - 2026-04-25
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Client connection pool with automatic reuse (`Quicsilver::Client.get/post` class-level API)
|
|
12
|
+
- GREASE support (RFC 9297) — settings, frames, and unidirectional streams
|
|
13
|
+
- GOAWAY validation (RFC 9114 §7.2.6) — monotonically decreasing IDs, stream ID validation
|
|
14
|
+
- Trailer support (RFC 9114 §4.1) — parse and send trailing HEADERS frames
|
|
15
|
+
- Extensible Priorities (RFC 9218) — parse `priority` header, PRIORITY_UPDATE frames on control stream, MsQuic stream priority mapping
|
|
16
|
+
- FrameParser base class — unified frame walking, ordering, body accumulation, size limits
|
|
17
|
+
- FrameReader module — shared byte-level frame extraction for request/response/control streams
|
|
18
|
+
- Trailer wiring in Adapter and StreamOutput for protocol-http integration
|
|
19
|
+
- Informational 1xx responses (§4.1) — 103 Early Hints with `rack.early_hints` support for Rails
|
|
20
|
+
- Two-phase GOAWAY shutdown (§5.2) — server sends decreasing GOAWAY IDs during graceful shutdown
|
|
21
|
+
- Client processes server SETTINGS (§7.2.4) — parses peer's SETTINGS including MAX_FIELD_SECTION_SIZE
|
|
22
|
+
- Client processes server GOAWAY (§5.2) — tracks peer_goaway_id, blocks new requests, connection pool evicts draining connections
|
|
23
|
+
- MIT license in gemspec
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
- QPACK prefix decoding — decode Required Insert Count and Delta Base as varints instead of hardcoded `offset = 2`
|
|
27
|
+
- Default decoder rejects payloads referencing the dynamic table
|
|
28
|
+
- Response parser now enforces `max_frame_payload_size` (was missing)
|
|
29
|
+
- Duplicate `frames` method in FrameParser
|
|
30
|
+
- Consistent `@headers` and `@trailers` initialization (`{}` not `nil`)
|
|
31
|
+
- extconf.rb — force Apple clang on macOS (Homebrew clang produces broken MsQuic binaries)
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
- RequestParser and ResponseParser inherit from FrameParser (reduced ~230 lines of duplication)
|
|
35
|
+
- `store_header`, `body`, `DEFAULT_DECODER`, `EMPTY_BODY`, `parse!` moved to FrameParser base class
|
|
36
|
+
- `@body_io` renamed to `@body` in ResponseParser for consistency
|
|
37
|
+
- ResponseEncoder accepts optional `trailers:` hash
|
|
38
|
+
- StreamOutput accepts `send_fin:` parameter for trailer support
|
|
39
|
+
|
|
8
40
|
## [0.3.0] - 2026-03-23
|
|
9
41
|
|
|
10
42
|
### Added
|
data/Gemfile.lock
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
quicsilver (0.
|
|
4
|
+
quicsilver (0.4.0)
|
|
5
|
+
console
|
|
5
6
|
localhost (~> 1.6)
|
|
6
7
|
logger
|
|
8
|
+
protocol-http (~> 0.49)
|
|
9
|
+
protocol-rack (~> 0.22)
|
|
7
10
|
rack (~> 3.0)
|
|
8
11
|
rackup (~> 2.0)
|
|
9
12
|
|
|
@@ -11,15 +14,30 @@ GEM
|
|
|
11
14
|
remote: https://rubygems.org/
|
|
12
15
|
specs:
|
|
13
16
|
benchmark-ips (2.14.0)
|
|
17
|
+
console (1.34.3)
|
|
18
|
+
fiber-annotation
|
|
19
|
+
fiber-local (~> 1.1)
|
|
20
|
+
json
|
|
21
|
+
fiber-annotation (0.2.0)
|
|
22
|
+
fiber-local (1.1.0)
|
|
23
|
+
fiber-storage
|
|
24
|
+
fiber-storage (1.0.1)
|
|
25
|
+
io-stream (0.11.1)
|
|
26
|
+
json (2.15.2)
|
|
14
27
|
localhost (1.6.0)
|
|
15
28
|
logger (1.7.0)
|
|
16
29
|
minitest (5.27.0)
|
|
17
30
|
minitest-focus (1.4.0)
|
|
18
31
|
minitest (>= 4, < 6)
|
|
32
|
+
protocol-http (0.60.0)
|
|
33
|
+
protocol-rack (0.22.1)
|
|
34
|
+
io-stream (>= 0.10)
|
|
35
|
+
protocol-http (~> 0.58)
|
|
36
|
+
rack (>= 1.0)
|
|
19
37
|
rack (3.2.4)
|
|
20
38
|
rackup (2.2.1)
|
|
21
39
|
rack (>= 3)
|
|
22
|
-
rake (13.
|
|
40
|
+
rake (13.4.2)
|
|
23
41
|
rake-compiler (1.3.0)
|
|
24
42
|
rake
|
|
25
43
|
rake-compiler-dock (1.9.1)
|
data/README.md
CHANGED
|
@@ -1,13 +1,25 @@
|
|
|
1
1
|
# Quicsilver
|
|
2
2
|
|
|
3
|
-
HTTP/3 server for Ruby with Rack support.
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
HTTP/3 server and client for Ruby with Rack support.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **HTTP/3 server** — serve any Rack app over QUIC/HTTP/3
|
|
8
|
+
- **HTTP/3 client** — make requests with automatic connection pooling
|
|
9
|
+
- **Rack integration** — `rackup -s quicsilver` works with Rails, Sinatra, any Rack app
|
|
10
|
+
- **Streaming** — dispatch on HEADERS, stream body chunks as they arrive
|
|
11
|
+
- **Extensible Priorities** (RFC 9218) — CSS before images, server respects client priority hints
|
|
12
|
+
- **Trailers** (RFC 9114 §4.1) — send/receive trailing headers after the body
|
|
13
|
+
- **GREASE** (RFC 9297) — extensibility testing on settings, frames, and streams
|
|
14
|
+
- **GOAWAY** (RFC 9114 §7.2.6) — graceful connection draining with validation
|
|
15
|
+
- **0-RTT** — fast reconnection with replay protection
|
|
16
|
+
- **Connection pooling** — client reuses connections automatically
|
|
17
|
+
- **protocol-http integration** — works with Falcon and protocol-http ecosystem
|
|
6
18
|
|
|
7
19
|
## Installation
|
|
8
20
|
|
|
9
21
|
```bash
|
|
10
|
-
git clone
|
|
22
|
+
git clone https://github.com/hahmed/quicsilver
|
|
11
23
|
cd quicsilver
|
|
12
24
|
bundle install
|
|
13
25
|
rake compile
|
|
@@ -21,18 +33,11 @@ rake compile
|
|
|
21
33
|
require "quicsilver"
|
|
22
34
|
|
|
23
35
|
app = ->(env) {
|
|
24
|
-
|
|
25
|
-
when '/'
|
|
26
|
-
[200, {'content-type' => 'text/plain'}, ["Hello HTTP/3!"]]
|
|
27
|
-
when '/api/users'
|
|
28
|
-
[200, {'content-type' => 'application/json'}, ['{"users": ["alice", "bob"]}']]
|
|
29
|
-
else
|
|
30
|
-
[404, {'content-type' => 'text/plain'}, ["Not Found"]]
|
|
31
|
-
end
|
|
36
|
+
[200, {"content-type" => "text/plain"}, ["Hello HTTP/3!"]]
|
|
32
37
|
}
|
|
33
38
|
|
|
34
39
|
server = Quicsilver::Server.new(4433, app: app)
|
|
35
|
-
server.start
|
|
40
|
+
server.start
|
|
36
41
|
```
|
|
37
42
|
|
|
38
43
|
### Client
|
|
@@ -40,44 +45,102 @@ server.start # Blocks until shutdown
|
|
|
40
45
|
```ruby
|
|
41
46
|
require "quicsilver"
|
|
42
47
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
response
|
|
47
|
-
puts response[:body]
|
|
48
|
+
# Class-level API with automatic connection pooling
|
|
49
|
+
response = Quicsilver::Client.get("127.0.0.1", 4433, "/")
|
|
50
|
+
puts response[:status] # => 200
|
|
51
|
+
puts response[:body] # => "Hello HTTP/3!"
|
|
48
52
|
|
|
49
|
-
|
|
53
|
+
# POST with body
|
|
54
|
+
response = Quicsilver::Client.post("127.0.0.1", 4433, "/api/users",
|
|
55
|
+
body: '{"name": "alice"}',
|
|
56
|
+
headers: { "content-type" => "application/json" })
|
|
50
57
|
|
|
58
|
+
# Instance-level for more control
|
|
59
|
+
client = Quicsilver::Client.new("127.0.0.1", 4433, unsecure: true)
|
|
60
|
+
response = client.get("/")
|
|
51
61
|
client.disconnect
|
|
52
62
|
```
|
|
53
63
|
|
|
54
|
-
|
|
64
|
+
### Rails
|
|
55
65
|
|
|
56
66
|
```bash
|
|
57
67
|
rackup -s quicsilver -p 4433
|
|
58
68
|
```
|
|
59
69
|
|
|
70
|
+
### curl
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
curl --http3-only https://localhost:4433/
|
|
74
|
+
```
|
|
75
|
+
|
|
60
76
|
## Configuration
|
|
61
77
|
|
|
62
78
|
```ruby
|
|
63
|
-
config = Quicsilver::
|
|
64
|
-
|
|
65
|
-
|
|
79
|
+
config = Quicsilver::Transport::Configuration.new(
|
|
80
|
+
"certificates/server.crt",
|
|
81
|
+
"certificates/server.key",
|
|
82
|
+
idle_timeout_ms: 10_000,
|
|
83
|
+
max_concurrent_requests: 100,
|
|
84
|
+
max_body_size: 10 * 1024 * 1024, # 10MB body limit (optional)
|
|
85
|
+
max_header_size: 64 * 1024, # 64KB header limit (optional)
|
|
86
|
+
max_header_count: 128, # Header count limit (optional)
|
|
87
|
+
stream_receive_window: 262_144, # 256KB per stream
|
|
88
|
+
connection_flow_control_window: 16_777_216 # 16MB per connection
|
|
66
89
|
)
|
|
67
90
|
|
|
68
|
-
server = Quicsilver::Server.new(4433,
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
91
|
+
server = Quicsilver::Server.new(4433, app: app, server_configuration: config)
|
|
92
|
+
server.start
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Priorities
|
|
96
|
+
|
|
97
|
+
Browsers send priority hints on requests. Quicsilver parses them and tells MsQuic to schedule high-priority streams first.
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
GET /style.css → priority: u=0 → sent first (highest urgency)
|
|
101
|
+
GET /app.js → priority: u=1 → sent second
|
|
102
|
+
GET /hero.png → priority: u=5 → sent later
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
No configuration needed — it works automatically.
|
|
106
|
+
|
|
107
|
+
## Trailers
|
|
108
|
+
|
|
109
|
+
Send headers after the body — useful for checksums, streaming status, and gRPC.
|
|
110
|
+
|
|
111
|
+
```ruby
|
|
112
|
+
# Trailers work with protocol-http's Headers#trailer! API
|
|
113
|
+
headers = Protocol::HTTP::Headers.new
|
|
114
|
+
headers.add("content-type", "text/plain")
|
|
115
|
+
headers.trailer!
|
|
116
|
+
headers.add("x-checksum", "abc123")
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Protocol-HTTP Mode
|
|
120
|
+
|
|
121
|
+
For integration with [Falcon](https://github.com/socketry/falcon) and the protocol-http ecosystem:
|
|
122
|
+
|
|
123
|
+
```ruby
|
|
124
|
+
config = Quicsilver::Transport::Configuration.new(
|
|
125
|
+
"certificates/server.crt",
|
|
126
|
+
"certificates/server.key",
|
|
127
|
+
mode: :protocol_http
|
|
72
128
|
)
|
|
129
|
+
|
|
130
|
+
server = Quicsilver::Server.new(4433, app: app, server_configuration: config)
|
|
131
|
+
server.start
|
|
73
132
|
```
|
|
74
133
|
|
|
134
|
+
| Mode | Body Handling | Use Case |
|
|
135
|
+
|------|---------------|----------|
|
|
136
|
+
| `:rack` (default) | Buffered | Standard Rack apps |
|
|
137
|
+
| `:protocol_http` | Streaming | Falcon, protocol-http apps |
|
|
138
|
+
|
|
75
139
|
## Development
|
|
76
140
|
|
|
77
141
|
```bash
|
|
78
|
-
rake compile # Build C extension
|
|
142
|
+
rake compile # Build C extension (macOS: uses Apple clang automatically)
|
|
79
143
|
rake test # Run tests
|
|
80
|
-
rake clean # Clean build artifacts
|
|
81
144
|
```
|
|
82
145
|
|
|
83
146
|
## License
|
data/Rakefile
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
require "bundler/setup"
|
|
1
2
|
require "bundler/gem_tasks"
|
|
2
3
|
require "rake/testtask"
|
|
3
4
|
require "rake/extensiontask"
|
|
@@ -6,6 +7,27 @@ Rake::ExtensionTask.new('quicsilver') do |ext|
|
|
|
6
7
|
ext.lib_dir = 'lib/quicsilver'
|
|
7
8
|
end
|
|
8
9
|
|
|
10
|
+
# Ensure MsQuic is built before compiling the C extension
|
|
11
|
+
task :compile => :build_msquic
|
|
12
|
+
|
|
13
|
+
# Copy MsQuic dylib next to the compiled extension for gem packaging
|
|
14
|
+
task :bundle_msquic do
|
|
15
|
+
lib_dir = 'lib/quicsilver'
|
|
16
|
+
if RUBY_PLATFORM =~ /darwin/
|
|
17
|
+
dylib = 'vendor/msquic/build/bin/Release/libmsquic.2.dylib'
|
|
18
|
+
if File.exist?(dylib)
|
|
19
|
+
cp dylib, "#{lib_dir}/libmsquic.2.dylib"
|
|
20
|
+
# Update rpath so the bundle finds the dylib in the same directory
|
|
21
|
+
sh "install_name_tool -add_rpath @loader_path #{lib_dir}/quicsilver.bundle 2>/dev/null || true"
|
|
22
|
+
end
|
|
23
|
+
elsif RUBY_PLATFORM =~ /linux/
|
|
24
|
+
so = 'vendor/msquic/build/bin/Release/libmsquic.so.2'
|
|
25
|
+
cp so, "#{lib_dir}/libmsquic.so.2" if File.exist?(so)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
task :build => :bundle_msquic
|
|
30
|
+
|
|
9
31
|
task :setup do
|
|
10
32
|
# Initialize git submodule if it doesn't exist
|
|
11
33
|
unless File.exist?('vendor/msquic')
|
|
@@ -19,9 +41,14 @@ task :build_msquic => :setup do
|
|
|
19
41
|
if RUBY_PLATFORM =~ /darwin/
|
|
20
42
|
cmake_args << '-DCMAKE_EXE_LINKER_FLAGS="-framework CoreServices"'
|
|
21
43
|
cmake_args << '-DCMAKE_SHARED_LINKER_FLAGS="-framework CoreServices"'
|
|
44
|
+
# Ensure QuicTLS uses Xcode SDK, not Homebrew OpenSSL
|
|
45
|
+
sdk_path = `xcrun --show-sdk-path 2>/dev/null`.strip
|
|
46
|
+
cmake_args << "-DCMAKE_OSX_SYSROOT=#{sdk_path}" unless sdk_path.empty?
|
|
22
47
|
end
|
|
23
|
-
|
|
24
|
-
|
|
48
|
+
# Override PATH so QuicTLS openssldir detection finds system openssl, not Homebrew
|
|
49
|
+
env = { 'PATH' => "/usr/bin:#{ENV['PATH']}" }
|
|
50
|
+
sh env, "cd vendor/msquic && cmake #{cmake_args.join(' ')}"
|
|
51
|
+
sh env, 'cd vendor/msquic && cmake --build build --config Release'
|
|
25
52
|
end
|
|
26
53
|
|
|
27
54
|
task :build => [:build_msquic, :compile]
|
|
@@ -32,6 +59,44 @@ Rake::TestTask.new(:test) do |t|
|
|
|
32
59
|
t.test_files = FileList["test/**/*_test.rb"]
|
|
33
60
|
end
|
|
34
61
|
|
|
62
|
+
Rake::TestTask.new(:test_unit) do |t|
|
|
63
|
+
t.libs << "test"
|
|
64
|
+
t.libs << "lib"
|
|
65
|
+
t.test_files = FileList["test/**/*_test.rb"].reject { |f|
|
|
66
|
+
f.include?("integration") || f.include?("stream_control") ||
|
|
67
|
+
f =~ /quicsilver_test|event_loop_test/
|
|
68
|
+
}
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
Rake::TestTask.new(:test_integration) do |t|
|
|
72
|
+
t.libs << "test"
|
|
73
|
+
t.libs << "lib"
|
|
74
|
+
t.test_files = FileList[
|
|
75
|
+
"test/stream_control_integration_test.rb",
|
|
76
|
+
"test/integration/**/*_test.rb",
|
|
77
|
+
"test/quicsilver_test.rb",
|
|
78
|
+
"test/event_loop_test.rb"
|
|
79
|
+
]
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
desc "Run unit and integration tests in parallel"
|
|
83
|
+
task :test_parallel do
|
|
84
|
+
threads = []
|
|
85
|
+
results = {}
|
|
86
|
+
|
|
87
|
+
threads << Thread.new {
|
|
88
|
+
results[:unit] = system("bundle exec rake test_unit 2>&1 > /dev/null")
|
|
89
|
+
}
|
|
90
|
+
threads << Thread.new {
|
|
91
|
+
results[:integration] = system("bundle exec rake test_integration 2>&1 > /dev/null")
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
threads.each(&:join)
|
|
95
|
+
unless results.values.all?
|
|
96
|
+
abort "Tests failed: #{results.inspect}"
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
35
100
|
namespace :benchmark do
|
|
36
101
|
desc "Run throughput benchmark"
|
|
37
102
|
task :throughput do
|
data/benchmarks/concurrent.rb
CHANGED
|
@@ -26,7 +26,7 @@ def test_multiplexing(host, port)
|
|
|
26
26
|
mutex = Mutex.new
|
|
27
27
|
|
|
28
28
|
client = Quicsilver::Client.new(host, port, unsecure: true)
|
|
29
|
-
client.
|
|
29
|
+
client.open_connection
|
|
30
30
|
|
|
31
31
|
elapsed = Benchmark.realtime do
|
|
32
32
|
threads = MULTIPLEX_REQUESTS.times.map do |i|
|
|
@@ -65,7 +65,7 @@ def test_concurrent_clients(host, port)
|
|
|
65
65
|
threads = NUM_CLIENTS.times.map do |i|
|
|
66
66
|
Thread.new do
|
|
67
67
|
client = Quicsilver::Client.new(host, port, unsecure: true)
|
|
68
|
-
client.
|
|
68
|
+
client.open_connection
|
|
69
69
|
|
|
70
70
|
REQUESTS_PER_CLIENT.times do |req|
|
|
71
71
|
start = Time.now
|
data/benchmarks/rails.rb
CHANGED
|
@@ -59,7 +59,7 @@ post_elapsed = Benchmark.realtime do
|
|
|
59
59
|
conn_threads = CONNECTIONS.times.map do |conn_id|
|
|
60
60
|
Thread.new do
|
|
61
61
|
client = Quicsilver::Client.new(HOST, PORT, unsecure: true)
|
|
62
|
-
client.
|
|
62
|
+
client.open_connection
|
|
63
63
|
|
|
64
64
|
local_times = []
|
|
65
65
|
local_ids = []
|
|
@@ -99,7 +99,7 @@ get_elapsed = Benchmark.realtime do
|
|
|
99
99
|
conn_threads = CONNECTIONS.times.map do
|
|
100
100
|
Thread.new do
|
|
101
101
|
client = Quicsilver::Client.new(HOST, PORT, unsecure: true)
|
|
102
|
-
client.
|
|
102
|
+
client.open_connection
|
|
103
103
|
|
|
104
104
|
local_times = []
|
|
105
105
|
|
|
@@ -132,7 +132,7 @@ delete_elapsed = Benchmark.realtime do
|
|
|
132
132
|
next if ids.empty?
|
|
133
133
|
|
|
134
134
|
client = Quicsilver::Client.new(HOST, PORT, unsecure: true)
|
|
135
|
-
client.
|
|
135
|
+
client.open_connection
|
|
136
136
|
|
|
137
137
|
local_times = []
|
|
138
138
|
|
data/benchmarks/throughput.rb
CHANGED
|
@@ -29,7 +29,7 @@ def run_benchmark(host, port)
|
|
|
29
29
|
threads = CONNECTIONS.times.map do
|
|
30
30
|
Thread.new do
|
|
31
31
|
client = Quicsilver::Client.new(host, port, connection_timeout: 5000, request_timeout: 10)
|
|
32
|
-
client.
|
|
32
|
+
client.open_connection
|
|
33
33
|
|
|
34
34
|
local = []
|
|
35
35
|
per_conn.times do
|
|
@@ -61,7 +61,7 @@ def run_benchmark(host, port)
|
|
|
61
61
|
threads = CONNECTIONS.times.map do
|
|
62
62
|
Thread.new do
|
|
63
63
|
client = Quicsilver::Client.new(host, port, connection_timeout: 5000, request_timeout: 10)
|
|
64
|
-
client.
|
|
64
|
+
client.open_connection
|
|
65
65
|
|
|
66
66
|
local = []
|
|
67
67
|
queue = Queue.new
|