Skip to content

Commit e4d2724

Browse files
Add agent context.
1 parent c1a2fd6 commit e4d2724

File tree

11 files changed

+650
-3
lines changed

11 files changed

+650
-3
lines changed

bake.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@
88
# @parameter version [String] The new version number.
99
def after_gem_release_version_increment(version)
1010
context["releases:update"].call(version)
11-
context["utopia:project:readme:update"].call
11+
context["utopia:project:update"].call
1212
end

context/getting-started.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Getting Started
2+
3+
This guide explains how to get started with `protocol-rack` and integrate Rack applications with `Protocol::HTTP` servers.
4+
5+
## Installation
6+
7+
Add the gem to your project:
8+
9+
```bash
10+
$ bundle add protocol-rack
11+
```
12+
13+
## Core Concepts
14+
15+
`protocol-rack` provides a bridge between two HTTP ecosystems:
16+
17+
- **Rack**: The standard Ruby web server interface used by frameworks like Rails, Sinatra, and Roda.
18+
- **`Protocol::HTTP`**: A modern, asynchronous HTTP protocol implementation used by servers like Falcon and Async.
19+
20+
The library enables bidirectional integration:
21+
22+
- **Application Adapter**: Run existing Rack applications on `Protocol::HTTP` servers (like Falcon).
23+
- **Server Adapter**: Run `Protocol::HTTP` applications on Rack-compatible servers (like Puma).
24+
25+
## Usage
26+
27+
The most common use case is running a Rack application on an asynchronous `Protocol::HTTP` server like [falcon](https://github.com/socketry/falcon). This allows you to leverage the performance benefits of async I/O while using your existing Rack-based application code.
28+
29+
### Running a Rack Application
30+
31+
When you have an existing Rack application (like a Rails app, Sinatra app, or any app that follows the Rack specification), you can adapt it to run on `Protocol::HTTP` servers:
32+
33+
```ruby
34+
require "async"
35+
require "async/http/server"
36+
require "async/http/endpoint"
37+
require "protocol/rack/adapter"
38+
39+
# Your existing Rack application:
40+
app = proc do |env|
41+
[200, {"content-type" => "text/plain"}, ["Hello World"]]
42+
end
43+
44+
# Create an adapter:
45+
middleware = Protocol::Rack::Adapter.new(app)
46+
47+
# Run on an async server:
48+
Async do
49+
endpoint = Async::HTTP::Endpoint.parse("http://localhost:9292")
50+
server = Async::HTTP::Server.new(middleware, endpoint)
51+
server.run
52+
end
53+
```
54+
55+
The adapter automatically detects your Rack version (v2, v3, or v3.1+) and uses the appropriate implementation, ensuring compatibility without any configuration.

context/index.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Automatically generated context index for Utopia::Project guides.
2+
# Do not edit then files in this directory directly, instead edit the guides and then run `bake utopia:project:agent:context:update`.
3+
---
4+
description: An implementation of the Rack protocol/specification.
5+
metadata:
6+
documentation_uri: https://socketry.github.io/protocol-rack/
7+
source_code_uri: https://github.com/socketry/protocol-rack.git
8+
files:
9+
- path: getting-started.md
10+
title: Getting Started
11+
description: This guide explains how to get started with `protocol-rack` and integrate
12+
Rack applications with `Protocol::HTTP` servers.
13+
- path: request-response.md
14+
title: Request and Response Handling
15+
description: This guide explains how to work with requests and responses when bridging
16+
between Rack and `Protocol::HTTP`, covering advanced use cases and edge cases.

context/request-response.md

Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
# Request and Response Handling
2+
3+
This guide explains how to work with requests and responses when bridging between Rack and `Protocol::HTTP`, covering advanced use cases and edge cases.
4+
5+
## Request Conversion
6+
7+
The {ruby Protocol::Rack::Request} class converts Rack environment hashes into rich `Protocol::HTTP` request objects, providing access to modern HTTP features while maintaining compatibility with Rack.
8+
9+
### Basic Request Access
10+
11+
```ruby
12+
require "protocol/rack/request"
13+
14+
run do |env|
15+
request = Protocol::Rack::Request[env]
16+
17+
# Access request properties:
18+
puts request.method # "GET", "POST", etc.
19+
puts request.path # "/users/123"
20+
puts request.url_scheme # "http" or "https"
21+
puts request.authority # "example.com:80"
22+
end
23+
```
24+
25+
### Headers
26+
27+
Headers are automatically extracted from Rack's `HTTP_*` environment variables:
28+
29+
```ruby
30+
run do |env|
31+
request = Protocol::Rack::Request[env]
32+
33+
# Headers are available as a `Protocol::HTTP::Headers` object:
34+
user_agent = request.headers["user-agent"]
35+
content_type = request.headers["content-type"]
36+
37+
# Headers are case-insensitive:
38+
user_agent = request.headers["User-Agent"] # Same as above
39+
end
40+
```
41+
42+
The adapter converts Rack's `HTTP_ACCEPT_ENCODING` format to standard HTTP header names (`accept-encoding`).
43+
44+
### Request Body
45+
46+
The request body is wrapped in a `Protocol::HTTP`-compatible interface:
47+
48+
```ruby
49+
run do |env|
50+
request = Protocol::Rack::Request[env]
51+
52+
# Read the entire body:
53+
body = request.body.read
54+
55+
# Or stream it:
56+
request.body.each do |chunk|
57+
process_chunk(chunk)
58+
end
59+
60+
# The body supports rewind if the underlying Rack input supports it:
61+
request.body.rewind
62+
end
63+
```
64+
65+
The body wrapper handles Rack's `rack.input` interface, which may or may not support `rewind` depending on the server.
66+
67+
### Query Parameters
68+
69+
Query parameters are parsed from the request path:
70+
71+
```ruby
72+
run do |env|
73+
request = Protocol::Rack::Request[env]
74+
75+
# Access query string:
76+
query = request.query # "name=value&other=123"
77+
78+
# Parse query parameters (if using a helper):
79+
params = URI.decode_www_form(query).to_h
80+
end
81+
```
82+
83+
### Protocol Upgrades
84+
85+
The adapter handles protocol upgrade requests (like WebSockets):
86+
87+
```ruby
88+
run do |env|
89+
request = Protocol::Rack::Request[env]
90+
91+
# Check for upgrade protocols:
92+
if protocols = request.protocol
93+
# protocols is an array: ["websocket"]:
94+
if protocols.include?("websocket")
95+
# Handle WebSocket upgrade.
96+
end
97+
end
98+
end
99+
```
100+
101+
Protocols are extracted from either `rack.protocol` or the `HTTP_UPGRADE` header.
102+
103+
## Response Conversion
104+
105+
The {ruby Protocol::Rack::Response} class and {ruby Protocol::Rack::Adapter.make_response} handle converting `Protocol::HTTP` responses back to Rack format.
106+
107+
### Basic Response
108+
109+
```ruby
110+
require "protocol/rack/adapter"
111+
112+
run do |env|
113+
request = Protocol::Rack::Request[env]
114+
115+
# Create a `Protocol::HTTP` response:
116+
response = Protocol::HTTP::Response[
117+
200,
118+
{"content-type" => "text/html"},
119+
["<h1>Hello</h1>"]
120+
]
121+
122+
# Convert to Rack format:
123+
Protocol::Rack::Adapter.make_response(env, response)
124+
end
125+
```
126+
127+
### Response Bodies
128+
129+
The adapter handles different types of response bodies:
130+
131+
#### Enumerable Bodies
132+
133+
```ruby
134+
# Array bodies:
135+
response = Protocol::HTTP::Response[
136+
200,
137+
{"content-type" => "text/plain"},
138+
["Hello", " ", "World"]
139+
]
140+
141+
# Enumerable bodies:
142+
response = Protocol::HTTP::Response[
143+
200,
144+
{"content-type" => "text/plain"},
145+
Enumerator.new do |yielder|
146+
yielder << "Chunk 1\n"
147+
yielder << "Chunk 2\n"
148+
end
149+
]
150+
```
151+
152+
#### Streaming Bodies
153+
154+
```ruby
155+
# Streaming response body:
156+
body = Protocol::HTTP::Body::Buffered.new(["Streaming content"])
157+
158+
response = Protocol::HTTP::Response[
159+
200,
160+
{"content-type" => "text/plain"},
161+
body
162+
]
163+
```
164+
165+
#### File Bodies
166+
167+
```ruby
168+
# File-based responses:
169+
body = Protocol::HTTP::Body::File.open("path/to/file.txt")
170+
171+
response = Protocol::HTTP::Response[
172+
200,
173+
{"content-type" => "text/plain"},
174+
body
175+
]
176+
```
177+
178+
### HEAD Requests
179+
180+
The adapter automatically handles HEAD requests by removing response bodies:
181+
182+
```ruby
183+
run do |env|
184+
request = Protocol::Rack::Request[env]
185+
186+
# Create a response with a body:
187+
response = Protocol::HTTP::Response[
188+
200,
189+
{"content-type" => "text/html"},
190+
["<h1>Full Response</h1>"]
191+
]
192+
193+
# For HEAD requests, the body is automatically removed:
194+
Protocol::Rack::Adapter.make_response(env, response)
195+
end
196+
```
197+
198+
### Status Codes Without Bodies
199+
200+
Certain status codes (204 No Content, 205 Reset Content, 304 Not Modified) should not include response bodies. The adapter handles this automatically:
201+
202+
```ruby
203+
response = Protocol::HTTP::Response[
204+
204, # No Content
205+
{},
206+
["This body will be removed"]
207+
]
208+
209+
# The adapter automatically removes the body for 204 responses.
210+
```
211+
212+
### Rack-Specific Features
213+
214+
#### Hijacking
215+
216+
Rack supports response hijacking, which allows taking over the connection:
217+
218+
```ruby
219+
# In a Rack application:
220+
[200, {"rack.hijack" => proc{|io| io.write("Hijacked!")}}, []]
221+
222+
# The adapter handles hijacking automatically using streaming responses.
223+
```
224+
225+
#### Response Finished Callbacks
226+
227+
Rack 2+ supports `rack.response_finished` callbacks:
228+
229+
```ruby
230+
env["rack.response_finished"] ||= []
231+
env["rack.response_finished"] << proc do |env, status, headers, error|
232+
# Cleanup or logging after response is sent
233+
puts "Response finished: #{status}"
234+
end
235+
```
236+
237+
The adapter invokes these callbacks in reverse order of registration, as specified by the Rack specification.
238+
239+
### Hop Headers
240+
241+
HTTP hop-by-hop headers (like `Connection`, `Transfer-Encoding`) are automatically removed from responses, as they should not be forwarded through proxies:
242+
243+
```ruby
244+
response = Protocol::HTTP::Response[
245+
200,
246+
{
247+
"content-type" => "text/plain",
248+
"connection" => "close", # This will be removed
249+
"transfer-encoding" => "chunked" # This will be removed
250+
},
251+
["Body"]
252+
]
253+
```

gems.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
gem "bake-gem"
1313
gem "bake-releases"
1414

15+
gem "agent-context"
16+
1517
gem "utopia-project"
1618
end
1719

guides/getting-started/readme.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Getting Started
2+
3+
This guide explains how to get started with `protocol-rack` and integrate Rack applications with `Protocol::HTTP` servers.
4+
5+
## Installation
6+
7+
Add the gem to your project:
8+
9+
```bash
10+
$ bundle add protocol-rack
11+
```
12+
13+
## Core Concepts
14+
15+
`protocol-rack` provides a bridge between two HTTP ecosystems:
16+
17+
- **Rack**: The standard Ruby web server interface used by frameworks like Rails, Sinatra, and Roda.
18+
- **`Protocol::HTTP`**: A modern, asynchronous HTTP protocol implementation used by servers like Falcon and Async.
19+
20+
The library enables bidirectional integration:
21+
22+
- **Application Adapter**: Run existing Rack applications on `Protocol::HTTP` servers (like Falcon).
23+
- **Server Adapter**: Run `Protocol::HTTP` applications on Rack-compatible servers (like Puma).
24+
25+
## Usage
26+
27+
The most common use case is running a Rack application on an asynchronous `Protocol::HTTP` server like [falcon](https://github.com/socketry/falcon). This allows you to leverage the performance benefits of async I/O while using your existing Rack-based application code.
28+
29+
### Running a Rack Application
30+
31+
When you have an existing Rack application (like a Rails app, Sinatra app, or any app that follows the Rack specification), you can adapt it to run on `Protocol::HTTP` servers:
32+
33+
```ruby
34+
require "async"
35+
require "async/http/server"
36+
require "async/http/endpoint"
37+
require "protocol/rack/adapter"
38+
39+
# Your existing Rack application:
40+
app = proc do |env|
41+
[200, {"content-type" => "text/plain"}, ["Hello World"]]
42+
end
43+
44+
# Create an adapter:
45+
middleware = Protocol::Rack::Adapter.new(app)
46+
47+
# Run on an async server:
48+
Async do
49+
endpoint = Async::HTTP::Endpoint.parse("http://localhost:9292")
50+
server = Async::HTTP::Server.new(middleware, endpoint)
51+
server.run
52+
end
53+
```
54+
55+
The adapter automatically detects your Rack version (v2, v3, or v3.1+) and uses the appropriate implementation, ensuring compatibility without any configuration.

0 commit comments

Comments
 (0)