[][src]Crate webmachine_rust

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 -> WebmachineResource -> Your application -> WebmachineResponse -> Hyper -> RESPONSE

Features

  • Handles the hard parts of content negotiation, conditional requests, and response codes for you.
  • Provides a resource 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:

  • Only supports Hyper
  • WebmachineDispatcher and WebmachineResource are not shareable between threads.
  • Automatically decoding request bodies and encoding response bodies.
  • No easy mechanism to generate bodies with different content types (e.g. JSON vs. XML).
  • No easy mechanism for handling sub-paths in a resource.
  • Does not work with keep alive enabled (does not manage the Hyper thread pool).
  • Dynamically determining the methods allowed on the resource.
  • Compiled against Hyper with all features turned off (no HTTPS).

Getting started

Follow the getting started documentation from the Hyper crate to setup a Hyper Handler for your server. Then from the handle function, you need to define a WebmachineDispatcher that maps resource paths to your webmachine resources (WebmachineResource). Each 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::sync::Arc;
 use hyper::server::{Handler, Server, Request, Response};
 use webmachine_rust::*;
 use webmachine_rust::context::*;
 use webmachine_rust::headers::*;
 use serde_json::{Value, json};
 use std::io::Read;


 fn from_hyper(req: &mut Request) -> http::Request<Vec<u8>> {
  let mut request = http::Request::builder()
    .uri(req.uri.to_string())
    .method(req.method.as_ref());
  for header in req.headers.iter() {
    request = request.header(header.name(), header.value_string());
  }
  let mut buffer = Vec::new();
  req.read_to_end(&mut buffer);
  request.body(buffer.clone()).unwrap()
 }

 struct ServerHandler {
 }

 impl Handler for ServerHandler {

     fn handle(&self, mut req: Request, res: Response) {
         // setup the dispatcher, which maps paths to resources
         let dispatcher = WebmachineDispatcher::new(
             btreemap!{
                 "/myresource".to_string() => Arc::new(WebmachineResource {
                     // Methods allowed on this resource
                     allowed_methods: vec!["OPTIONS".to_string(), "GET".to_string(),
                        "HEAD".to_string(), "POST".to_string()],
                     // if the resource exists callback
                     resource_exists: Box::new(|context| true),
                     // callback to render the response for the resource
                     render_response: Box::new(|_| {
                         let json_response = json!({
                            "data": [1, 2, 3, 4]
                         });
                         Some(json_response.to_string())
                     }),
                     // callback to process the post for the resource
                     process_post: Box::new(|context|  /* Handle the post here */ Ok(true) ),
                     // default everything else
                     .. WebmachineResource::default()
                 })
             }
         );
         // then dispatch the request to the web machine.
         match dispatcher.dispatch(from_hyper(&mut req)) {
             Ok(res) => (),
             Err(err) => warn!("Error generating response - {}", err)
         };
     }
 }

 pub fn start_server() {
     match Server::http(format!("0.0.0.0:0").as_str()) {
         Ok(mut server) => {
             // It is important to turn keep alive off
             server.keep_alive(None);
             server.handle(ServerHandler {});
         },
         Err(err) => {
             error!("could not start server: {}", err);
         }
     }
 }

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

Macros

h

Simple macro to convert a string to a HeaderValue struct.

Structs

WebmachineDispatcher

The main hyper dispatcher

WebmachineResource

Struct to represent a resource in webmachine