Expand description
§webmachine-rust
Port of Webmachine-Ruby (https://github.com/webmachine/webmachine-ruby) to Rust.
webmachine-rust is a port of the Ruby version of webmachine. It implements a finite state machine for the HTTP protocol that provides semantic HTTP handling (based on the diagram from the webmachine project). It is basically a HTTP toolkit for building HTTP-friendly applications using the Hyper rust crate.
Webmachine-rust works with Hyper and sits between the Hyper Handler and your application code. It provides a resource struct with callbacks to handle the decisions required as the state machine is executed against the request with the following sequence.
REQUEST -> Hyper Handler -> WebmachineDispatcher -> Resource -> Your application code -> WebmachineResponse -> Hyper -> RESPONSE
§Features
- Handles the hard parts of content negotiation, conditional requests, and response codes for you.
- Provides a resource trait and struct with points of extension to let you describe what is relevant about your particular resource.
§Missing Features
Currently, the following features from webmachine-ruby have not been implemented:
- Visual debugger
- Streaming response bodies
§Implementation Deficiencies:
This implementation has the following deficiencies:
- Automatically decoding request bodies and encoding response bodies.
- No easy mechanism to generate bodies with different content types (e.g. JSON vs. XML).
- Dynamically determining the methods allowed on the resource.
§Getting started with Hyper
Follow the getting started documentation from the Hyper crate to setup a Hyper service for your server.
There are two ways of using this crate. You can either use the WebmachineResource
struct and add callbacks
for the state you need to modify, or you can create your own resource structs and implement the
Resource
trait.
You need to define a WebmachineDispatcher that maps resource paths to your webmachine resources (WebmachineResource or structs that implement Resource). WebmachineResource defines all the callbacks (via Closures) and values required to implement a resource.
Note: This example uses the maplit crate to provide the btreemap
macro and the log crate for the logging macros.
use std::convert::Infallible;
use std::future::ready;
use std::io::Read;
use std::net::SocketAddr;
use std::sync::Arc;
use webmachine_rust::*;
use webmachine_rust::context::*;
use webmachine_rust::headers::*;
use bytes::Bytes;
use futures_util::future::FutureExt;
use hyper::{body, Request};
use hyper::server::conn::http1;
use hyper::service::service_fn;
use hyper_util::rt::TokioIo;
use maplit::btreemap;
use serde_json::{Value, json};
use tracing::error;
use tokio::net::TcpListener;
async fn start_server() -> anyhow::Result<()> {
// setup the dispatcher, which maps paths to resources. We wrap it in an Arc so we can
// use it in the loop below.
let dispatcher = Arc::new(WebmachineDispatcher {
routes: btreemap!{
"/myresource" => WebmachineDispatcher::box_resource(WebmachineResource {
// Methods allowed on this resource
allowed_methods: owned_vec(&["OPTIONS", "GET", "HEAD", "POST"]),
// if the resource exists callback
resource_exists: callback(|_, _| true),
// callback to render the response for the resource, it has to be async
render_response: async_callback(|_, _| {
let json_response = json!({
"data": [1, 2, 3, 4]
});
ready(Ok(Some(Bytes::from(json_response.to_string())))).boxed()
}),
// callback to process the post for the resource
process_post: async_callback(|_, _| /* Handle the post here */ ready(Ok(true)).boxed() ),
// default everything else
.. WebmachineResource::default()
})
}
});
// Create a Hyper server that delegates to the dispatcher. See https://hyper.rs/guides/1/server/hello-world/
let addr: SocketAddr = "0.0.0.0:8080".parse()?;
let listener = TcpListener::bind(addr).await?;
loop {
let dispatcher = dispatcher.clone();
let (stream, _) = listener.accept().await?;
let io = TokioIo::new(stream);
tokio::task::spawn(async move {
if let Err(err) = http1::Builder::new()
.serve_connection(io, service_fn(|req: Request<body::Incoming>| dispatcher.dispatch(req)))
.await
{
error!("Error serving connection: {:?}", err);
}
});
}
Ok(())
}
§Example implementations
For an example of a project using this crate, have a look at the Pact Mock Server from the Pact reference implementation.
Modules§
- content_
negotiation - The
content_negotiation
module deals with handling media types, languages, charsets and encodings as per https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html. - context
- The
context
module encapsulates the context of the environment that the webmachine is executing in. Basically wraps the request and response. - headers
- The
headers
deals with parsing and formatting request and response headers - paths
- Utilities for matching URI paths
Macros§
- h
- Simple macro to convert a string to a
HeaderValue
struct.
Structs§
- Webmachine
Dispatcher - The main hyper dispatcher
- Webmachine
Resource - Struct to represent a resource in webmachine
Traits§
- Resource
- All webmachine resources implement this trait
Functions§
- async_
callback - Wrap a callback in a structure that is safe to call between threads
- callback
- Wrap a callback in a structure that is safe to call between threads
- owned_
vec - Convenience function to create a vector of string structs from a slice of strings
Type Aliases§
- Async
Webmachine Callback - Wrap an async callback in a structure that is safe to call between threads
- Webmachine
Callback - Type of a Webmachine resource callback