Request and Response Deep Dive
Project Code:https://github.com/hyperlane-dev/hyperlane
At the heart of every HTTP server lies the processing of incoming requests and the generation of outgoing responses. Hyperlane provides a comprehensive, ergonomic API for both sides of this equation. This article takes a deep dive into how hyperlane handles requests and responses, covering everything from basic header access to JSON body parsing, cookies, and attribute macros.
Understanding the Request Lifecycle
When a client sends an HTTP request to a hyperlane server, the following sequence occurs:
- The TCP connection is accepted and a
Streamis created. - The request is parsed from the stream into a structured
Requestobject. - The request passes through any registered request middleware.
- The request is matched against registered routes.
- The matched route's
handlemethod is called. - The response passes through any registered response middleware.
- The response is serialized and sent back to the client.
Understanding this lifecycle is crucial for writing effective middleware and route handlers.
Accessing Request Data
Hyperlane provides multiple ways to access request data — through method calls and through attribute macros.
Method-Based Access
The Context object gives you access to all aspects of the incoming request:
let method = ctx.get_request().get_method();
let path = ctx.get_request().get_path();
let host = ctx.get_request().get_host();
let headers = ctx.get_request().get_headers();
let body = ctx.get_request().get_body_string();
let json_body: T = ctx.get_request().get_body_json::<T>();
let query = ctx.get_request().try_get_query("key");
Let's examine each of these:
-
get_method()— Returns the HTTP method (GET, POST, PUT, DELETE, etc.). -
get_path()— Returns the URL path portion of the request. -
get_host()— Returns the Host header value. -
get_headers()— Returns all request headers. -
get_body_string()— Returns the raw request body as a string. -
get_body_json::<T>()— Deserializes the request body as JSON into a typeT. This requiresTto implementserde::Deserialize. -
try_get_query("key")— Attempts to extract a query parameter by name.
Attribute Macro Access
Hyperlane also provides attribute macros that automatically extract request data and bind it to variables:
#[request_body(body)]
#[request_body_json(body: TestData)]
#[request_header(HOST => host_value)]
#[try_get_request_header(HOST => host_value)]
#[request_path(path)]
#[request_query("key" => query_value)]
#[is_get_method]
#[methods("GET", "POST")]
#[is_http1_1_version]
#[is_ws_upgrade_type]
These macros are applied to the handle method of your route handler. For example:
#[route("/test/{text}")]
struct Route;
impl ServerHook for Route {
async fn new(_: &mut Stream, _: &mut Context) -> Self { Self }
#[request_body_json(body: TestData)]
#[request_query("key" => query_value)]
async fn handle(self, stream: &mut Stream, ctx: &mut Context) -> Status {
// `body` is now a TestData instance deserialized from the request body
// `query_value` is the value of the "key" query parameter
Status::Continue
}
}
The attribute macros support:
-
#[request_body(body)]— Binds the raw request body string tobody. -
#[request_body_json(body: TestData)]— Deserializes the JSON body into aTestDatastruct. -
#[request_header(HOST => host_value)]— Extracts the HOST header intohost_value. -
#[try_get_request_header(HOST => host_value)]— Safely attempts to extract a header. -
#[request_path(path)]— Binds the request path topath. -
#[request_query("key" => query_value)]— Extracts a query parameter. -
#[is_get_method]— Checks if the request method is GET. -
#[methods("GET", "POST")]— Checks if the request method matches one of the specified methods. -
#[is_http1_1_version]— Checks if the request uses HTTP/1.1. -
#[is_ws_upgrade_type]— Checks if the request is a WebSocket upgrade request.
Route Parameters
Dynamic route parameters are a core feature of any web framework. Hyperlane uses the {param_name} syntax in route paths:
server.route::<Route>("/test/{text}");
server.route::<Route>("/test/{number:\\d+}");
Route parameters can be accessed through the Context:
let param: Option<String> = ctx.try_get_route_param("text");
let param: String = ctx.get_route_param("text");
let params = ctx.get_route_params();
-
try_get_route_param("text")— ReturnsOption<String>, safe for optional parameters. -
get_route_param("text")— ReturnsString, panics if the parameter doesn't exist. -
get_route_params()— Returns all route parameters as a collection.
For regex-constrained parameters like {number:\\d+}, the route will only match if the URL segment satisfies the regex pattern.
Route Filters
Hyperlane supports route-level filtering through attribute macros:
#[host("example.com")]
#[reject_host("blocked.com")]
#[referer("https://example.com")]
#[reject_referer("https://malicious.com")]
#[filter(ctx.get_request().get_method() == &RequestMethod::Get)]
#[reject(ctx.get_request().get_path().len() > 1000)]
These filters allow you to:
-
#[host("example.com")]— Only match requests to a specific host. - **
#[reject_host("blocked.com")]— Reject requests to a specific host. -
#[referer("https://example.com")]— Only match requests from a specific referer. -
#[reject_referer("https://malicious.com")]— Reject requests from a specific referer. -
#[filter(...)]— Apply a custom filter expression. -
#[reject(...)]— Apply a custom rejection expression.
Building Responses
Hyperlane's Context provides mutable access to the response object:
ctx.get_mut_response().set_version(HttpVersion::Http1_1).set_status_code(200);
ctx.get_mut_response().set_body("Hello World");
ctx.get_mut_response().set_header(CONTENT_TYPE, APPLICATION_JSON);
ctx.get_mut_response().add_header(SERVER, "hyperlane");
Key response methods:
-
set_version(HttpVersion::Http1_1)— Sets the HTTP version. -
set_status_code(200)— Sets the HTTP status code. -
set_body("Hello World")— Sets the response body. -
set_header(CONTENT_TYPE, APPLICATION_JSON)— Sets a response header (replaces existing value). -
add_header(SERVER, "hyperlane")— Adds a response header (allows multiple values).
Attribute Macro Responses
You can also configure responses using attribute macros:
#[response_status_code(200)]
#[response_version(HttpVersion::Http1_1)]
#[response_body("Hello World")]
#[response_header(SERVER => HYPERLANE)]
#[response_header(SET_COOKIE, "session_id=abc123")]
#[clear_response_headers]
These macros are applied to the handle method:
#[response_status_code(200)]
#[response_body("Hello World")]
async fn handle(self, stream: &mut Stream, ctx: &mut Context) -> Status {
// The response is automatically configured with status 200 and body "Hello World"
Status::Continue
}
Working with Cookies
Cookies are essential for session management and stateful web applications. Hyperlane provides comprehensive cookie support:
Reading Cookies
let cookies = ctx.get_request().try_get_cookies();
let cookie = ctx.get_request().try_get_cookie("session_id");
-
try_get_cookies()— Returns all cookies from the request. -
try_get_cookie("session_id")— Returns a specific cookie by name.
Setting Cookies with CookieBuilder
Hyperlane provides the CookieBuilder for constructing cookies with various attributes:
let cookie = CookieBuilder::new("session_id", "abc123").set_path("/").http_only().build();
ctx.get_mut_response().set_header(SET_COOKIE, &cookie);
The CookieBuilder supports:
let cookie = CookieBuilder::new("session", "token123")
.set_expires("Wed, 21 Oct 2025 07:28:00 GMT")
.set_domain("example.com")
.set_same_site("Strict")
.set_max_age(3600)
.set_path("/")
.secure()
.http_only()
.build();
Available builder methods:
-
set_expires(...)— Sets the cookie expiration date. -
set_domain(...)— Sets the cookie domain. -
set_same_site(...)— Sets the SameSite attribute (Strict, Lax, None). -
set_max_age(...)— Sets the cookie max age in seconds. -
set_path(...)— Sets the cookie path. -
secure()— Marks the cookie as Secure (HTTPS only). -
http_only()— Marks the cookie as HttpOnly (inaccessible to JavaScript).
Clearing Cookies
To clear a cookie, create one with an empty value and max age of 0:
let clear_cookie = CookieBuilder::new("session", "").set_max_age(0).build();
Sending Responses
Once you've built a response, you need to send it to the client:
let data = ctx.get_mut_response().build();
stream.try_send(data).await;
stream.send(data).await;
stream.try_send_list(&frame_list).await;
stream.try_flush().await;
-
build()— Serializes the response into bytes. -
stream.try_send(data).await— Attempts to send data, returns an error if the stream is closed. -
stream.send(data).await— Sends data, blocking until complete. -
stream.try_send_list(&frame_list).await— Sends multiple frames at once. -
stream.try_flush().await— Flushes any buffered data.
Attribute Macros for Sending
#[try_send]
#[send]
#[try_flush]
#[flush]
#[closed]
These macros handle the send/flush operations automatically:
#[try_send]
async fn handle(self, stream: &mut Stream, ctx: &mut Context) -> Status {
// The response is automatically built and sent
Status::Continue
}
Common Patterns
JSON API Response
ctx.get_mut_response()
.set_header(CONTENT_TYPE, APPLICATION_JSON)
.set_body(json_string);
let data = ctx.get_mut_response().build();
stream.try_send(data).await;
Authentication Middleware
impl ServerHook for AuthMiddleware {
async fn handle(self, stream: &mut Stream, ctx: &mut Context) -> Status {
let auth_str = ctx.get_request().try_get_header_back(AUTHORIZATION).unwrap_or_default();
if auth_str.is_empty() {
let data = ctx.get_mut_response().set_status_code(401).set_body("Unauthorized").build();
if stream.try_send(data).await.is_err() {
stream.set_closed(true);
}
return Status::Reject;
}
Status::Continue
}
}
CORS Middleware
ctx.get_mut_response()
.set_header(ACCESS_CONTROL_ALLOW_ORIGIN, WILDCARD_ANY)
.set_header(ACCESS_CONTROL_ALLOW_METHODS, ALL_METHODS)
.set_header(ACCESS_CONTROL_ALLOW_HEADERS, WILDCARD_ANY);
Conclusion
Hyperlane provides a rich, dual-mode API for handling requests and responses. Whether you prefer the explicit method-call style or the concise attribute macro style, you have full control over every aspect of the HTTP request/response cycle. The combination of route parameters, filters, cookie management, and flexible response building makes hyperlane a powerful tool for building web applications of any complexity.
Project Code:https://github.com/hyperlane-dev/hyperlane
Top comments (0)