hyperion-rb 1.0.0.rc17

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: bcb08723cb65420d073fe0e239c039946649ed4b2d5ec913391edaa9f9022f5c
4
+ data.tar.gz: 87f88b3956f6879defe23ba4f9e4b6a5b41bcf4db787b553ed7a3608ef9f6760
5
+ SHA512:
6
+ metadata.gz: b0fecfb34f4cd2a2b272858305fc43a46a9f09d0ac9a77e13b2977c0793bd92a5eac024d8d6f080eaaa565f99f87e092d919a1ff4662eb3a205a29cefa9826fd
7
+ data.tar.gz: 4a86ffdf69d7ba24e3dbac045c26c26aed00ee07f43db3eaabe725c3ce5a52b9b3af7f94cfefa80ca743a75db0c882d931e1ae4953131acc70a20b5104ccdbf0
data/CHANGELOG.md ADDED
@@ -0,0 +1,133 @@
1
+ # Changelog
2
+
3
+ ## [1.0.0.rc17] - 2026-04-26
4
+
5
+ ### Performance
6
+ - **C-extension response head builder** (`Hyperion::CParser.build_response_head`).
7
+ Per-response status-line, normalized hash, and per-header String#<< allocations now happen in C — same wire output, ~44% faster on the synthetic head-build microbench (430 k vs 298 k writes/sec on macOS).
8
+ - `ResponseWriter` keeps a pure-Ruby `build_head_ruby` fallback for JRuby / TruffleRuby / build failures; behaviour is identical.
9
+ - Ships alongside the frozen `HTTP_KEY_CACHE` Rack-adapter optimisation already on the rc16 tip.
10
+
11
+ ## [1.0.0.rc16] - 2026-04-26
12
+
13
+ ### Changed
14
+ - **Logger split (12-factor)**: `info` / `debug` route to stdout; `warn` / `error` / `fatal` route to stderr. Previously everything went to stderr.
15
+ - `Logger.new` accepts `out:` and `err:` kwargs (default `$stdout` / `$stderr`); `io:` kwarg still works as a back-compat alias for tests.
16
+ - Per-request access logs go to **stdout** (info level).
17
+
18
+ All notable changes to Hyperion are documented here.
19
+
20
+ ## [1.0.0.rc15] - 2026-04-26
21
+
22
+ ### Added
23
+ - **Per-OS worker model**: master auto-detects host OS and picks the right multi-worker strategy.
24
+ - **macOS / BSD**: master binds the listening socket once, forks workers that inherit the FD (Puma's pattern). Single accept queue, race-fair across workers.
25
+ - **Linux**: each worker independently binds with `SO_REUSEPORT` (kernel-fair distribution, no thundering herd).
26
+ - Override via `HYPERION_WORKER_MODEL=share|reuseport`.
27
+ - macOS `-w 4` now scales correctly: hello-world bench at `-w 4 -t 10` serves 44 k r/s vs Puma's 38 k r/s (1.17× throughput, 15× lower p99).
28
+
29
+ ### Fixed
30
+ - macOS `SO_REUSEPORT` distribution issue (Darwin's kernel funnels connections to one socket, never load-balancing siblings). The share-model fix sidesteps it entirely.
31
+
32
+ ## [1.0.0.rc14] - 2026-04-26
33
+
34
+ ### Added
35
+ - **Ruby DSL config file** (`config/hyperion.rb`) with the same shape as Puma's `config/puma.rb`. Auto-loaded if present in cwd; explicit path via `-C / --config PATH`.
36
+ - **Lifecycle hooks**: `before_fork`, `on_worker_boot`, `on_worker_shutdown`. Same API as Puma's hooks.
37
+ - DSL settings: `bind`, `port`, `workers`, `thread_count`, `tls_cert_path`, `tls_key_path`, `read_timeout`, `idle_keepalive`, `graceful_timeout`, `max_header_bytes`, `max_body_bytes`, `log_level`, `log_format`, `log_requests`, `fiber_local_shim`.
38
+ - `config/hyperion.example.rb` with documented sample config.
39
+ - Strict DSL: unknown methods raise `NoMethodError` at boot — typos surface immediately.
40
+
41
+ ### Changed
42
+ - Precedence: explicit CLI flag > env var > config file > built-in default.
43
+
44
+ ## [1.0.0.rc13] - 2026-04-26
45
+
46
+ ### Changed
47
+ - **Per-request access logs default ON** (matches Puma + Rails operator expectations). Disable with `--no-log-requests` or `HYPERION_LOG_REQUESTS=0`.
48
+ - Logger emit is now lock-free: dropped the per-write mutex (POSIX `write(2)` is atomic for writes ≤ PIPE_BUF).
49
+ - Access-log hot path: per-thread cached iso8601 timestamp, hand-rolled single-interpolation line builder, per-thread 4 KiB write buffer flushed in Connection's `ensure`.
50
+
51
+ ### Performance
52
+ - Default-ON Hyperion still beats Puma: hello-world at `-t 16` serves 20 k r/s vs Puma's 19 k r/s, with 24× lower p99 — and Hyperion emits 200 k+ access lines that Puma doesn't.
53
+
54
+ ## [1.0.0.rc12] - 2026-04-26
55
+
56
+ ### Added
57
+ - `--log-requests` flag (initially default OFF) emitting one structured INFO line per response.
58
+
59
+ ## [1.0.0.rc11] - 2026-04-26
60
+
61
+ ### Added
62
+ - Smart log format auto-detect: `--log-format auto` (default).
63
+ - `RAILS_ENV` / `RACK_ENV` / `HYPERION_ENV` ∈ `{production, staging}` → JSON.
64
+ - Stderr is a TTY → colored text (ANSI level colors).
65
+ - Otherwise (piped output, no env hint) → JSON.
66
+
67
+ ## [1.0.0.rc10] - 2026-04-26
68
+
69
+ ### Changed
70
+ - **Lock-free per-thread metrics counters**, no global mutex. Hot-path cost: one TLS lookup + one Hash op per increment.
71
+ - Connection caches `Hyperion.logger` and `Hyperion.metrics` once at init so the request loop skips method dispatch.
72
+
73
+ ## [1.0.0.rc9] - 2026-04-26
74
+
75
+ ### Added
76
+ - `Hyperion::Logger` — structured logger with levels (`debug`, `info`, `warn`, `error`, `fatal`), text + JSON formats. Hash-based payload: `logger.info { { message: 'foo', key: val } }`.
77
+ - `Hyperion::Metrics` — `Hyperion.stats` returns a hash of counters: `connections_accepted`, `connections_active`, `requests_total`, `requests_in_flight`, `responses_<code>`, `parse_errors`, `app_errors`, `read_timeouts`.
78
+
79
+ ## [1.0.0.rc8] - 2026-04-26
80
+
81
+ ### Changed
82
+ - **Worker thread owns the HTTP/1.1 connection** (Puma's model). The `app.call(env)` per-request hop through a cross-thread Queue is gone.
83
+ - `ThreadPool#submit_connection(socket, app)` hands the entire connection to a worker thread; workers run `Connection#serve` directly with no pool indirection.
84
+ - HTTP/2 path keeps fiber-per-stream (correct because h2 multiplexes streams onto one socket).
85
+
86
+ ### Performance
87
+ - Microbench: per-connection serve goes from 12 k r/s (rc7 hop-per-request) to 29 k r/s (rc8 worker-owns).
88
+ - Hello-world `-t 16`: 1.27× Puma throughput, 30× lower p99.
89
+
90
+ ## [1.0.0.rc7] - 2026-04-26
91
+
92
+ ### Performance
93
+ - Pooled the per-call reply `Queue` in `ThreadPool` (was `Queue.new` on every dispatch).
94
+ - Inlined `Connection#read_chunk`'s hot path.
95
+
96
+ ## [1.0.0.rc6] - 2026-04-26
97
+
98
+ ### Added
99
+ - Production-mode Rails benchmark suite. At Puma's default 3-thread config Hyperion `-t 16` serves 3.18× Puma's throughput with 2.10× lower p99.
100
+
101
+ ## [1.0.0.rc5] - 2026-04-26
102
+
103
+ ### Added
104
+ - **Hybrid async/thread-pool architecture**: accept/parse/write stays on fibers; `app.call(env)` dispatched to OS-thread pool (default 5, `-t/--threads N`).
105
+
106
+ ## [1.0.0.rc4] - 2026-04-26
107
+
108
+ ### Fixed
109
+ - Adapter sets `REMOTE_ADDR` from socket peer (was missing — broke Rack::Attack and any IP-based middleware).
110
+ - Pinned `openssl < 4.0` so apps mutating `OpenSSL::SSL::SSLContext::DEFAULT_PARAMS` (e.g. AWS SDK initializers) don't crash on boot.
111
+
112
+ ## [1.0.0.rc3] - 2026-04-26
113
+
114
+ ### Added
115
+ - HTTP/2 dispatch via `protocol-http2` with per-stream fiber multiplexing and WINDOW_UPDATE-aware flow control.
116
+ - rake-compiler integration: `bundle exec rake compile` builds the C ext.
117
+
118
+ ## [1.0.0.rc1] - 2026-04-26
119
+
120
+ ### Added
121
+ - TLS + ALPN (HTTPS over HTTP/1.1; HTTP/2 via ALPN handshake).
122
+ - FiberLocal compatibility shim (`Hyperion::FiberLocal.install!`) for Rails apps using `Thread.current.thread_variable_*`.
123
+ - Object pooling for Rack `env` Hash and `rack.input` StringIO.
124
+ - Vendored llhttp 9.3.0 C parser (`Hyperion::CParser`); falls back to pure-Ruby `Hyperion::Parser`.
125
+ - Pre-fork cluster (`-w N`); graceful shutdown with 30 s drain.
126
+ - `Async::Scheduler` integration; fiber per connection.
127
+ - HTTP/1.1 keep-alive, pipelining, chunked Transfer-Encoding.
128
+ - Smuggling defenses: `Content-Length` + `Transfer-Encoding` together → 400; non-chunked TE → 501.
129
+
130
+ ## v0.0.1 - 2026-04-26
131
+
132
+ ### Added
133
+ - Phase 1 skeleton: pure-Ruby HTTP/1.1 parser, Rack 3 adapter, Connection state machine, Server accept loop, CLI.
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Andrey Lobanov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,260 @@
1
+ # Hyperion
2
+
3
+ High-performance Ruby HTTP server. Falcon-class fiber concurrency, Puma-class compatibility.
4
+
5
+ [![Specs](https://img.shields.io/badge/specs-114%20passing-brightgreen)]()
6
+ [![Ruby](https://img.shields.io/badge/ruby-%3E%3D%203.2-red)]()
7
+ [![License](https://img.shields.io/badge/license-MIT-blue)]()
8
+
9
+ ```sh
10
+ gem install hyperion
11
+ bundle exec hyperion config.ru
12
+ ```
13
+
14
+ ## Highlights
15
+
16
+ - **HTTP/1.1 + HTTP/2 + TLS** out of the box (HTTP/2 with per-stream fiber multiplexing, WINDOW_UPDATE-aware flow control, ALPN auto-negotiation).
17
+ - **Pre-fork cluster** with per-OS worker model: `SO_REUSEPORT` on Linux, master-bind + worker-fd-share on macOS/BSD (Darwin's `SO_REUSEPORT` doesn't load-balance).
18
+ - **Hybrid concurrency**: fiber-per-connection for I/O, OS-thread pool for `app.call(env)` — synchronous Rack handlers (Rails, ActiveRecord, anything holding a global mutex) get true OS-thread concurrency.
19
+ - **Vendored llhttp 9.3.0** C parser; pure-Ruby fallback for non-MRI runtimes.
20
+ - **Default-ON structured access logs** (one JSON or text line per request) with hot-path optimisations: per-thread cached timestamp, hand-rolled line builder, lock-free per-thread write buffer.
21
+ - **12-factor logger split**: info/debug → stdout, warn/error/fatal → stderr.
22
+ - **Ruby DSL config file** (`config/hyperion.rb`) with lifecycle hooks (`before_fork`, `on_worker_boot`, `on_worker_shutdown`).
23
+ - **Object pooling** for the Rack `env` hash and `rack.input` IO — amortizes per-request allocations across the worker's lifetime.
24
+ - **`Hyperion::FiberLocal`** opt-in shim for older Rails idioms that store request-scoped data via `Thread.current.thread_variable_*`.
25
+
26
+ ## Benchmarks
27
+
28
+ All numbers are real wrk runs against published Hyperion configs. Hyperion ships **with default-ON structured access logs**; Puma comparisons use Puma defaults (no per-request log emission).
29
+
30
+ ### Hello-world Rack app
31
+
32
+ `bench/hello.ru`, single worker, parity threads (`-t 16` vs Puma `-t 16:16`), 4 wrk threads / 50 connections / 10s, macOS arm64 / Ruby 3.3.3:
33
+
34
+ | | r/s | p99 |
35
+ |---|---:|---:|
36
+ | **Hyperion default (logs ON)** | **23,885** | **1.05 ms** |
37
+ | Hyperion `--no-log-requests` | 24,222 | 1.00 ms |
38
+ | Puma `-t 16:16` | 18,794 | 30.89 ms |
39
+
40
+ **1.27× Puma throughput, ~30× lower p99 — while emitting structured JSON access logs Puma doesn't.**
41
+
42
+ ### Production cluster config (`-w 4`)
43
+
44
+ Same bench app, `-w 4` cluster, parity threads. macOS arm64:
45
+
46
+ | | r/s | p99 |
47
+ |---|---:|---:|
48
+ | **Hyperion `-w 4 -t 10`** | **44,221** | **1.15 ms** |
49
+ | Puma `-w 4 -t 10:10` | 37,929 | 17.06 ms |
50
+
51
+ **1.17× Puma throughput, ~15× lower p99.**
52
+
53
+ ### Linux production-config (DB-backed Rack)
54
+
55
+ `-w 4 -t 10` on Ubuntu 24.04 / Ruby 3.3.3. Rack app does one Postgres `SELECT 1` + one Redis `GET` per request, real network round-trip. wrk `-t4 -c50 -d10s` × 3 runs (median):
56
+
57
+ | | r/s (median) | vs Puma default |
58
+ |---|---:|---:|
59
+ | **Hyperion default (rc17, logs ON)** | **5,786** | **1.012×** |
60
+ | Hyperion `--no-log-requests` | 6,364 | 1.114× |
61
+ | Puma `-w 4 -t 10:10` (no per-req logs) | 5,715 | 1.000× |
62
+
63
+ Bench is network-bound (~3-4 ms median is the PG + Redis round-trip). Hyperion's lead comes from cheaper per-request CPU: lock-free per-thread metrics, per-thread cached iso8601 timestamps in the access log, hand-rolled single-interpolation log line builder, no logger mutex (POSIX `write(2)` atomicity), C-extension response-head builder.
64
+
65
+ ### Real Rails 8.1 app (single worker, parity threads `-t 16`)
66
+
67
+ Health endpoint that traverses the full middleware chain (rack-attack, locale redirect, structured tagger, geo-location, etc.). Plus a Grape API endpoint reading cached data, and a Rails controller doing a Redis GET + an ActiveRecord query.
68
+
69
+ | endpoint | server | r/s | p99 | wrk timeouts |
70
+ |---|---|---:|---:|---:|
71
+ | `/up` (health) | **Hyperion** | **19.03** | **1.12 s** | **0** |
72
+ | `/up` (health) | Puma `-t 16:16` | 16.64 | 1.95 s | **138** |
73
+ | Grape `/api/v1/cached_data` | **Hyperion** | **16.15** | **779 ms** | 16 |
74
+ | Grape `/api/v1/cached_data` | Puma `-t 16:16` | 10.90 | (>2 s, censored) | **110** |
75
+ | Rails `/api/v1/health` | **Hyperion** | **15.95** | **992 ms** | 16 |
76
+ | Rails `/api/v1/health` | Puma `-t 16:16` | 11.29 | (>2 s, censored) | **114** |
77
+
78
+ On Grape and Rails-controller workloads Puma hits wrk's 2 s timeout cap on ~⅔ of requests — its real p99 is censored above 2 s. Hyperion serves all of its requests under 1.2 s with 0 to 16 timeouts. **1.14–1.48× Puma throughput** depending on endpoint.
79
+
80
+ ### Reproduce
81
+
82
+ ```sh
83
+ # hello-world
84
+ bundle exec ruby bench/compare.rb
85
+ HYPERION_WORKERS=4 PUMA_WORKERS=4 FALCON_COUNT=4 bundle exec ruby bench/compare.rb
86
+
87
+ # Real Rails / Grape: see bench/db.ru for the schema
88
+ ```
89
+
90
+ ## Quick start
91
+
92
+ ```sh
93
+ bundle install
94
+ bundle exec rake compile # build the llhttp C ext
95
+ bundle exec hyperion config.ru # single-process default
96
+ bundle exec hyperion -w 4 -t 10 config.ru # 4-worker cluster, 10 threads each
97
+ bundle exec hyperion -w 0 config.ru # 1 worker per CPU
98
+ bundle exec hyperion --tls-cert cert.pem --tls-key key.pem -p 9443 config.ru # HTTPS
99
+ curl http://127.0.0.1:9292/ # => hello
100
+
101
+ # Chunked POST works:
102
+ curl -X POST -H "Transfer-Encoding: chunked" --data-binary @file http://127.0.0.1:9292/
103
+
104
+ # HTTP/2 (over TLS, ALPN-negotiated):
105
+ curl --http2 -k https://127.0.0.1:9443/
106
+ ```
107
+
108
+ `bundle exec rake spec` (and the `default` task) auto-invoke `compile`, so a fresh checkout just needs `bundle install && bundle exec rake` to get a green run.
109
+
110
+ ## Configuration
111
+
112
+ Three layers, in precedence order: explicit CLI flag > environment variable > `config/hyperion.rb` > built-in default.
113
+
114
+ ### CLI flags
115
+
116
+ | Flag | Default | Notes |
117
+ |---|---|---|
118
+ | `-b, --bind HOST` | `127.0.0.1` | |
119
+ | `-p, --port PORT` | `9292` | |
120
+ | `-w, --workers N` | `1` | `0` → `Etc.nprocessors` |
121
+ | `-t, --threads N` | `5` | OS-thread Rack handler pool per worker. `0` → run inline (no pool, debugging only). |
122
+ | `-C, --config PATH` | `config/hyperion.rb` if present | Ruby DSL file. |
123
+ | `--tls-cert PATH` | nil | PEM certificate. |
124
+ | `--tls-key PATH` | nil | PEM private key. |
125
+ | `--log-level LEVEL` | `info` | `debug` / `info` / `warn` / `error` / `fatal`. |
126
+ | `--log-format FORMAT` | `auto` | `text` / `json` / `auto`. Auto: JSON when `RAILS_ENV`/`RACK_ENV` is `production`/`staging`, colored text on TTY, JSON otherwise. |
127
+ | `--[no-]log-requests` | ON | Per-request access log. |
128
+ | `--fiber-local-shim` | off | Patches `Thread#thread_variable_*` to fiber storage for older Rails idioms. |
129
+
130
+ ### Environment variables
131
+
132
+ `HYPERION_LOG_LEVEL`, `HYPERION_LOG_FORMAT`, `HYPERION_LOG_REQUESTS` (`0|1|true|false|yes|no|on|off`), `HYPERION_ENV`, `HYPERION_WORKER_MODEL` (`share|reuseport`).
133
+
134
+ ### Config file
135
+
136
+ `config/hyperion.rb` — same shape as Puma's `puma.rb`. Auto-loaded if present.
137
+
138
+ ```ruby
139
+ # config/hyperion.rb
140
+ bind '0.0.0.0'
141
+ port 9292
142
+
143
+ workers 4
144
+ thread_count 10
145
+
146
+ # tls_cert_path 'config/cert.pem'
147
+ # tls_key_path 'config/key.pem'
148
+
149
+ read_timeout 30
150
+ idle_keepalive 5
151
+ graceful_timeout 30
152
+
153
+ max_header_bytes 64 * 1024
154
+ max_body_bytes 16 * 1024 * 1024
155
+
156
+ log_level :info
157
+ log_format :auto
158
+ log_requests true
159
+
160
+ fiber_local_shim false
161
+
162
+ before_fork do
163
+ ActiveRecord::Base.connection_handler.clear_all_connections! if defined?(ActiveRecord)
164
+ end
165
+
166
+ on_worker_boot do |worker_index|
167
+ ActiveRecord::Base.establish_connection if defined?(ActiveRecord)
168
+ end
169
+
170
+ on_worker_shutdown do |worker_index|
171
+ ActiveRecord::Base.connection_handler.clear_all_connections! if defined?(ActiveRecord)
172
+ end
173
+ ```
174
+
175
+ Strict DSL: unknown methods raise `NoMethodError` at boot — typos surface immediately rather than getting silently ignored.
176
+
177
+ A documented sample lives at [`config/hyperion.example.rb`](config/hyperion.example.rb).
178
+
179
+ ## Logging
180
+
181
+ Default behaviour (rc16+):
182
+
183
+ - **`info` / `debug` → stdout**, **`warn` / `error` / `fatal` → stderr** (12-factor).
184
+ - **One structured access-log line per response**, info level, on stdout. Disable with `--no-log-requests` or `HYPERION_LOG_REQUESTS=0`.
185
+ - **Format auto-selects**: production envs → JSON (line-delimited, parseable by every log aggregator); TTY → coloured text; piped output without env hint → JSON.
186
+
187
+ ### Sample access log lines
188
+
189
+ Text format (TTY default):
190
+
191
+ ```
192
+ 2026-04-26T18:40:04.112Z INFO [hyperion] message=request method=GET path=/api/v1/health status=200 duration_ms=46.63 remote_addr=127.0.0.1 http_version=HTTP/1.1
193
+ 2026-04-26T18:40:04.123Z INFO [hyperion] message=request method=GET path=/api/v1/cached_data query="currency=USD" status=200 duration_ms=43.87 remote_addr=127.0.0.1 http_version=HTTP/1.1
194
+ ```
195
+
196
+ JSON format (auto-selected on `RAILS_ENV=production`/`staging` or piped output):
197
+
198
+ ```json
199
+ {"ts":"2026-04-26T18:38:49.405Z","level":"info","source":"hyperion","message":"request","method":"GET","path":"/api/v1/health","status":200,"duration_ms":46.63,"remote_addr":"127.0.0.1","http_version":"HTTP/1.1"}
200
+ {"ts":"2026-04-26T18:38:49.411Z","level":"info","source":"hyperion","message":"request","method":"GET","path":"/api/v1/cached_data","query":"currency=USD","status":200,"duration_ms":40.64,"remote_addr":"127.0.0.1","http_version":"HTTP/1.1"}
201
+ ```
202
+
203
+ ### Hot-path optimisations
204
+
205
+ The default-ON access log path is engineered to stay near-zero cost:
206
+
207
+ - **Per-thread cached `iso8601(3)` timestamp** — one allocation per millisecond per thread, reused across all requests in that millisecond.
208
+ - **Hand-rolled single-interpolation line builder** — bypasses generic `Hash#map.join`.
209
+ - **Per-thread 4 KiB write buffer** — flushes to stdout when full or on connection close. Cuts ~32× the syscalls under load.
210
+ - **Lock-free emit** — POSIX `write(2)` is atomic for writes ≤ PIPE_BUF (4096 B); a log line is ~200 B. No logger mutex.
211
+
212
+ ## Metrics
213
+
214
+ `Hyperion.stats` returns a snapshot Hash with the following counters (lock-free per-thread aggregation):
215
+
216
+ | Counter | Meaning |
217
+ |---|---|
218
+ | `connections_accepted` | Lifetime accept count. |
219
+ | `connections_active` | Currently in-flight connections. |
220
+ | `requests_total` | Lifetime request count. |
221
+ | `requests_in_flight` | Currently in-flight requests. |
222
+ | `responses_<code>` | One counter per status code emitted (`responses_200`, `responses_400`, …). |
223
+ | `parse_errors` | HTTP parse failures → 400. |
224
+ | `app_errors` | Rack app raised → 500. |
225
+ | `read_timeouts` | Per-connection read deadline hit. |
226
+
227
+ ```ruby
228
+ require 'hyperion'
229
+ Hyperion.stats
230
+ # => {connections_accepted: 1234, connections_active: 7, requests_total: 8910, …}
231
+ ```
232
+
233
+ ## TLS + HTTP/2
234
+
235
+ Provide a PEM cert + key:
236
+
237
+ ```sh
238
+ bundle exec hyperion --tls-cert config/cert.pem --tls-key config/key.pem -p 9443 config.ru
239
+ ```
240
+
241
+ ALPN auto-negotiates `h2` (HTTP/2) or `http/1.1` per connection. HTTP/2 multiplexes streams onto fibers within a single connection — slow handlers don't head-of-line-block other streams. Cluster-mode TLS works (`-w N` + `--tls-cert` / `--tls-key`).
242
+
243
+ Smuggling defenses for HTTP/1.1: `Content-Length` + `Transfer-Encoding` together → 400; non-chunked `Transfer-Encoding` → 501; CRLF in response header values → `ArgumentError` (response-splitting guard).
244
+
245
+ ## Compatibility
246
+
247
+ - **Ruby 3.2+** required.
248
+ - **Rack 3** (auto-sets `SERVER_SOFTWARE`, `rack.version`, `REMOTE_ADDR`, IPv6-safe `Host` parsing, CRLF guard).
249
+ - **`Hyperion::FiberLocal.install!`** opt-in shim for older Rails apps that store request-scoped data via `Thread.current.thread_variable_*` (modern Rails 7.1+ already uses Fiber storage natively; the shim handles the residual footgun).
250
+ - **`Hyperion::FiberLocal.verify_environment!`** runtime check that `Thread.current[:k]` is fiber-local on the current Ruby (it is on 3.2+).
251
+
252
+ ## Credits
253
+
254
+ - Vendored [llhttp](https://github.com/nodejs/llhttp) (Node.js's HTTP parser, MIT) under `ext/hyperion_http/llhttp/`.
255
+ - HTTP/2 framing and HPACK via [`protocol-http2`](https://github.com/socketry/protocol-http2).
256
+ - Fiber scheduler via [`async`](https://github.com/socketry/async).
257
+
258
+ ## License
259
+
260
+ MIT.
data/bin/hyperion ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ $LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
5
+ require 'hyperion/cli'
6
+ Hyperion::CLI.run(ARGV)
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'mkmf'
4
+
5
+ # llhttp source files vendored under ext/hyperion_http/llhttp/.
6
+ # We compile them in-tree (single static unit) rather than linking a
7
+ # system library, so the gem builds standalone on any host with a C
8
+ # compiler + Ruby headers.
9
+ $srcs = %w[
10
+ parser.c
11
+ llhttp.c
12
+ api.c
13
+ http.c
14
+ ]
15
+ $VPATH << '$(srcdir)/llhttp'
16
+ $INCFLAGS << ' -I$(srcdir)/llhttp'
17
+ $CFLAGS << ' -O2 -fno-strict-aliasing'
18
+
19
+ create_makefile('hyperion_http/hyperion_http')