docs.rs failed to build hyperchad_renderer_html_http-0.2.0
Please check the
build logs for more information.
See
Builds for ideas on how to fix a failed build,
or
Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault,
open an issue.
HyperChad HTML HTTP Renderer
Generic HTTP server integration for HyperChad HTML renderer with framework-agnostic design.
Overview
The HyperChad HTML HTTP Renderer provides:
- Framework Agnostic: Works with any HTTP server implementation
- Request Processing: Process RouteRequest objects into HTTP responses
- Response Generation: Standards-compliant HTTP response generation
- Static Assets: Integrated static file serving
- Action Handling: Server-side action processing
- Error Handling: Comprehensive HTTP error handling
Features
HTTP Server Capabilities
- Generic HTTP: Works with any HTTP server framework
- Request Processing: Process RouteRequest objects into HTTP responses
- Response Building: Generate proper HTTP responses
- Status Codes: Full HTTP status code support
- Headers: Custom header management
- Content Types: Automatic content type detection
HyperChad Integration
- HTML Rendering: Server-side HTML generation
- Routing: URL routing and path matching
- Action Processing: Handle HyperChad actions
- Partial Updates: Support for partial page updates
- Asset Serving: Static asset management
Performance Features
- Async Processing: Fully asynchronous request handling
Installation
Add this to your Cargo.toml:
[dependencies]
hyperchad_renderer_html_http = { path = "../hyperchad/renderer/html/http" }
Usage
Basic HTTP Application
use hyperchad_renderer_html_http::HttpApp;
use hyperchad_renderer_html::DefaultHtmlTagRenderer;
use hyperchad_router::{Router, RouteRequest};
use hyperchad_template::container;
use http::{Request, Response, StatusCode};
use bytes::Bytes;
use qstring::QString;
async fn handle_request(req: Request<Vec<u8>>) -> Result<Response<Vec<u8>>, Box<dyn std::error::Error>> {
let router = Router::new();
let tag_renderer = DefaultHtmlTagRenderer::default();
let app = HttpApp::new(tag_renderer, router)
.with_title("My HyperChad App".to_string())
.with_description("A HyperChad HTTP application".to_string())
.with_viewport("width=device-width, initial-scale=1".to_string());
let query_str = req.uri().query().unwrap_or("");
let query = QString::from(query_str).into_iter().collect();
let route_request = RouteRequest {
path: req.uri().path().to_string(),
method: switchy::http::models::Method::from(req.method().as_str()),
query,
headers: req.headers().iter()
.map(|(k, v)| (k.to_string(), v.to_str().unwrap_or("").to_string()))
.collect(),
cookies: std::collections::BTreeMap::new(),
info: hyperchad_router::RequestInfo::default(),
body: Some(std::sync::Arc::new(Bytes::from(req.into_body()))),
};
let response = app.process(&route_request).await?;
Ok(response)
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
use hyper::service::{make_service_fn, service_fn};
use hyper::{Body, Server};
use std::convert::Infallible;
let make_svc = make_service_fn(|_conn| async {
Ok::<_, Infallible>(service_fn(|req: Request<Body>| async move {
let (parts, body) = req.into_parts();
let body_bytes = hyper::body::to_bytes(body).await.unwrap();
let req = Request::from_parts(parts, body_bytes.to_vec());
match handle_request(req).await {
Ok(response) => {
let (parts, body) = response.into_parts();
Ok::<_, Infallible>(Response::from_parts(parts, Body::from(body)))
}
Err(_) => Ok(Response::builder()
.status(500)
.body(Body::from("Internal Server Error"))
.unwrap()),
}
}))
});
let addr = ([127, 0, 0, 1], 3000).into();
let server = Server::bind(&addr).serve(make_svc);
println!("Server running on http://{}", addr);
server.await?;
Ok(())
}
Advanced Routing
use hyperchad_router::{RouteRequest, Router, RoutePath};
use hyperchad_template::{ContainerVecExt, container};
fn create_router() -> Router {
let router = Router::new();
router.add_route_result("/", |_req| async { Ok::<_, Box<dyn std::error::Error>>(render_home()) });
router.add_route_result("/about", |_req| async { Ok::<_, Box<dyn std::error::Error>>(render_about()) });
router.add_route_result("/users/{id}", |req| async move {
let user_id = req.path.strip_prefix("/users/").unwrap_or("");
Ok::<_, Box<dyn std::error::Error>>(render_user(user_id))
});
router.add_route_result("/api/users", |req| async move { handle_api_users(&req).await });
router
}
fn render_home() -> String {
let view = container! {
html {
head {
title { "Home - HyperChad HTTP" }
meta name="viewport" content="width=device-width, initial-scale=1";
}
body {
div class="container" {
h1 { "Welcome to HyperChad HTTP" }
p { "This is a framework-agnostic HTTP server." }
nav {
a href="/about" { "About" }
" | "
a href="/users/123" { "User Profile" }
" | "
a href="/api/users" { "API" }
}
div class="features" {
h2 { "Features" }
ul {
li { "Framework agnostic design" }
li { "Full HTTP standard support" }
li { "Static asset serving" }
li { "Action processing" }
}
}
}
}
}
};
view.to_string()
}
fn render_user(user_id: &str) -> String {
let view = container! {
html {
head {
title { format!("User {} - HyperChad HTTP", user_id) }
}
body {
div class="container" {
h1 { format!("User Profile: {}", user_id) }
div class="user-info" {
p { format!("User ID: {}", user_id) }
p { "Name: John Doe" }
p { "Email: john@example.com" }
}
a href="/" { "← Back to Home" }
}
}
}
};
view.to_string()
}
async fn handle_api_users(req: &RouteRequest) -> Result<Content, Box<dyn std::error::Error>> {
use hyperchad_renderer::Content;
match req.method.as_ref() {
"GET" => {
let users = serde_json::json!([
{"id": 1, "name": "Alice", "email": "alice@example.com"},
{"id": 2, "name": "Bob", "email": "bob@example.com"}
]);
Ok(Content::Json(users))
}
"POST" => {
let body = req.body.as_ref().unwrap();
let new_user: serde_json::Value = serde_json::from_slice(body)?;
let response = serde_json::json!({
"id": 123,
"name": new_user["name"],
"email": new_user["email"]
});
Ok(Content::Json(response))
}
_ => {
Ok(Content::Raw {
data: bytes::Bytes::from_static(b"Method Not Allowed"),
content_type: "text/plain".to_string(),
})
}
}
}
Static Asset Serving
Note: Static asset serving requires the assets feature (enabled by default).
use hyperchad_renderer_html_http::HttpApp;
use hyperchad_renderer_html::DefaultHtmlTagRenderer;
use hyperchad_router::Router;
use hyperchad_renderer::assets::AssetPathTarget;
use hyperchad_template::{ContainerVecExt, container};
use std::path::PathBuf;
fn create_app_with_assets() -> HttpApp<DefaultHtmlTagRenderer> {
let router = Router::new();
let tag_renderer = DefaultHtmlTagRenderer::default();
HttpApp::new(tag_renderer, router)
.with_static_asset_route_handler(|req| {
match req.path.as_str() {
"/css/style.css" => Some(AssetPathTarget::File(PathBuf::from("assets/style.css"))),
"/js/app.js" => Some(AssetPathTarget::File(PathBuf::from("assets/app.js"))),
path if path.starts_with("/images/") => path
.strip_prefix("/images/")
.map(|relative| AssetPathTarget::File(PathBuf::from("assets/images").join(relative))),
path if path.starts_with("/uploads/") => path
.strip_prefix("/uploads/")
.map(|relative| AssetPathTarget::File(PathBuf::from("uploads").join(relative))),
_ => None,
}
})
}
fn render_page_with_assets() -> String {
let view = container! {
html {
head {
title { "HyperChad with Assets" }
link rel="stylesheet" href="/css/style.css";
meta name="viewport" content="width=device-width, initial-scale=1";
}
body {
div class="container" {
h1 { "HyperChad with Static Assets" }
img src="/images/logo.png" alt="Logo" class="logo";
p { "This page includes CSS, JavaScript, and image assets." }
div class="gallery" {
img src="/uploads/photo1.jpg" alt="Photo 1";
img src="/uploads/photo2.jpg" alt="Photo 2";
}
}
script src="/js/app.js" {}
}
}
};
view.to_string()
}
Action Handling
Note: Action handling requires the actions feature (enabled by default).
use hyperchad_renderer_html_http::HttpApp;
use hyperchad_renderer_html::DefaultHtmlTagRenderer;
use hyperchad_router::Router;
use hyperchad_renderer::transformer::actions::logic::Value;
fn create_app_with_actions() -> HttpApp<DefaultHtmlTagRenderer> {
let (action_tx, action_rx) = flume::unbounded();
tokio::spawn(async move {
while let Ok((action_name, value)) = action_rx.recv_async().await {
match action_name.as_str() {
"submit_contact_form" => {
println!("Contact form submitted: {:?}", value);
}
"user_login" => {
println!("Login attempt: {:?}", value);
}
_ => {
println!("Unknown action: {}", action_name);
}
}
}
});
let router = Router::new();
let tag_renderer = DefaultHtmlTagRenderer::default();
HttpApp::new(tag_renderer, router)
.with_action_tx(action_tx)
}
Error Handling
use http::{Request, Response, StatusCode};
use hyperchad_template::{ContainerVecExt, container};
async fn handle_request_with_errors(req: Request<Vec<u8>>) -> Response<Vec<u8>> {
match process_request(req).await {
Ok(response) => response,
Err(e) => handle_error(e),
}
}
fn handle_error(error: Box<dyn std::error::Error>) -> Response<Vec<u8>> {
let (status, message) = match error.downcast_ref::<hyperchad_renderer_html_http::Error>() {
Some(hyperchad_renderer_html_http::Error::Navigate(nav_err)) => {
(StatusCode::NOT_FOUND, format!("Page not found: {}", nav_err))
}
Some(hyperchad_renderer_html_http::Error::IO(io_err)) => {
(StatusCode::INTERNAL_SERVER_ERROR, format!("IO error: {}", io_err))
}
_ => {
(StatusCode::INTERNAL_SERVER_ERROR, "Internal server error".to_string())
}
};
let error_page = container! {
html {
head {
title { format!("{} - Error", status.as_u16()) }
}
body {
div class="error-page" {
h1 { format!("Error {}", status.as_u16()) }
p { message }
a href="/" { "Go Home" }
}
}
}
};
Response::builder()
.status(status)
.header("Content-Type", "text/html")
.body(error_page.to_string().into_bytes())
.unwrap()
}
Middleware Integration
use http::{Request, Response};
use std::time::Instant;
async fn logging_middleware<F, Fut>(
req: Request<Vec<u8>>,
handler: F,
) -> Response<Vec<u8>>
where
F: FnOnce(Request<Vec<u8>>) -> Fut,
Fut: std::future::Future<Output = Response<Vec<u8>>>,
{
let start = Instant::now();
let method = req.method().clone();
let path = req.uri().path().to_string();
println!("→ {} {}", method, path);
let response = handler(req).await;
let duration = start.elapsed();
let status = response.status();
println!("← {} {} {} {:?}", method, path, status.as_u16(), duration);
response
}
async fn cors_middleware<F, Fut>(
req: Request<Vec<u8>>,
handler: F,
) -> Response<Vec<u8>>
where
F: FnOnce(Request<Vec<u8>>) -> Fut,
Fut: std::future::Future<Output = Response<Vec<u8>>>,
{
let mut response = handler(req).await;
let headers = response.headers_mut();
headers.insert("Access-Control-Allow-Origin", "*".parse().unwrap());
headers.insert("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS".parse().unwrap());
headers.insert("Access-Control-Allow-Headers", "Content-Type, Authorization".parse().unwrap());
response
}
async fn handle_with_middleware(req: Request<Vec<u8>>) -> Response<Vec<u8>> {
cors_middleware(req, |req| {
logging_middleware(req, |req| {
handle_request(req)
})
}).await
}
Feature Flags
Default features: actions, assets, debug, json
actions: Enable server-side action processing (depends on _json and serde)
assets: Enable static asset serving (depends on mime_guess, switchy_async, switchy_fs)
debug: Enable debug-specific functionality
json: Enable JSON content type support (depends on _json)
_json: Internal feature for JSON dependencies (do not enable directly)
HTTP Standards Compliance
Supported Methods
- GET: Retrieve resources
- POST: Create resources
- PUT: Update resources
- DELETE: Delete resources
- PATCH: Partial updates
- HEAD: Headers only
- OPTIONS: CORS preflight
Status Codes
- 2xx Success: 200 OK, 201 Created, 204 No Content
- 3xx Redirection: 301 Moved Permanently, 302 Found, 304 Not Modified
- 4xx Client Error: 400 Bad Request, 401 Unauthorized, 404 Not Found
- 5xx Server Error: 500 Internal Server Error, 503 Service Unavailable
Headers
- Content-Type: Automatic content type detection for assets and responses
- Custom headers can be added via middleware (see middleware example)
Dependencies
Core dependencies:
- http: Standard HTTP types and utilities
- hyperchad_renderer_html: Core HTML rendering functionality
- hyperchad_router: Routing and navigation
- hyperchad_renderer: Renderer abstractions and traits
- thiserror: Error handling
- flume: Multi-producer, multi-consumer channels for action handling
- serde_json: JSON serialization for internal use
Optional dependencies (enabled by features):
- serde: JSON deserialization for actions (enabled by
actions feature)
- mime_guess: Content-type detection for static assets (enabled by
assets feature)
- switchy_async & switchy_fs: Async file I/O for asset serving (enabled by
assets feature)
Integration
This renderer is designed for:
- Custom HTTP Servers: Build your own HTTP server
- Framework Integration: Integrate with existing frameworks
- Microservices: HTTP-based microservices
- API Gateways: Custom API gateway implementations
- Edge Computing: Edge server implementations
Performance Considerations
- Async: Fully asynchronous processing with tokio/async runtime support
- Memory: Efficient memory usage patterns
- File I/O: Async file operations for asset serving