First of all, hello there! This is the first announcement after the call for maintainers back in summer, and includes the efforts of several new faces:

Together, as well as input from the original authors @bradleybeddoes and @smangelsdorf, we are the new maintainers of the Gotham project. Today we’re excited to announce the release of Gotham 0.3, which follows the evolution of the broader Rust ecosystem.

What’s in Gotham 0.3

Following along with the changes in libraries such as tokio and hyper, along with the release of the new http crate, Gotham has changed fairly significantly in this release, in terms of both public API and usability.

Some broad highlights of this release include:

  • Adoption of Tokio Reform and the new tokio crate.
  • Adoption of Hyper 0.12 and the new http crate.
  • Improvements to the internal routing algorithms.
  • Ability to customize the Tokio runtime used by Gotham.
  • Asynchronous static file serving via tokio-fs.
  • Many performance and usability improvements.

In the sections below, we’ll cover some of the more interesting features.

Ecosystem Updates

The 0.3 update has a heavy focus on adopting the changes in the broader Rust ecosystem, beginning with the adoption of the new tokio crate. This was contributed by @whitfin and had the added advantage of removing the only platform-specific code in Gotham. It also opened up several new APIs to control Gotham startup - such as thread count, executor, etc.

In addition Gotham 0.3 upgrades Hyper to 0.12 and adopts the new http crate, to keep Gotham up to date with the latest performance improvements and maintain a familiar API signature. This is due to a huge effort from @nyarly to adopt the new APIs internally, whilst keeping the impact to the Gotham API minimal.

Of course, aside from this, all dependencies of Gotham have been updated to their latest and greatest to avoid any potential bugs which might have been fixed since the last release.

Shorthand Responses

Gotham enables you to return a response through the use of the IntoResponse trait. In Gotham 0.3, this trait is implemented for some shorthand structures to enable easier return handling without having to construct a response. Here is a simple example:

const HELLO_WORLD: &'static str = "Hello, world!";

pub fn say_hello(state: State) -> (State, &'static str) {
    (state, HELLO_WORLD)
}

pub fn main() {
    gotham::start("127.0.0.1:7878", || Ok(say_hello))
}

This is possible as there is a blanket conversion for types implementing Into<Body> for Hyper. As &'static str is one such type, the conversion is automatic. Other basic types are also included, such as Bytes, String and Vec<u8> - making it possible to shorthand many reponses.

Taking it a little further, you can also control the status code and mime type of the response created, using Tuple structures:

(mime::Mime, Into<Body>)
(hyper::StatusCode, mime::Mime, Into<Body>)

This allows for a different style when defining requests. The old create_response helpers are still around though; so if you don’t like this syntax you can stick with the old.

Async Static File Serving

Mentioned as part of the 0.2 announcement, the ability to asynchronously serve static assets is very much something that has been requested to be available as part of Gotham. The 0.3 release includes initial support for this, courtesy of @colinbankier, via a simple API. Below are a few examples of exposing static assets:

build_simple_router(|route| {
    // You can use `to_file` on a route to expose a single asset:
    route.get("/").to_file("assets/index.html");

    // Alternatively, you can use `to_dir` to expose a directory of assets:
    route.get("/static").to_dir("assets");

    // Finally, you can customize options for compression, cache control, etc:
    route.get("/static").to_dir(
        FileOptions::new("assets")
            .with_cache_control("no-cache")
            .with_gzip(true)
            .build(),
    );
});

The structure of this API makes it extremely quick to implement a static site using Gotham. The snippet below shows a complete example of serving a static site from the assets directory using Gotham to serve the requests.

extern crate gotham;

use gotham::router::builder::*;

pub fn main() {
    gotham::start("127.0.0.1:7878", build_simple_router(|route| {
        route.get("/*").to_dir("assets");
    }))
}

We think this is a great improvement for those looking at Gotham for static sites!

Shared State Middlewares

A common question we often see is how to share state across requests. Whilst this has been possible via the use of middleware for a long time, it’s not always obvious exactly how to go about it - and it’s definitely a lot of boilerplate for something so simple. Contributed by @whitfin, Gotham 0.3 includes a new built-in middleware to offer shared state without having to bind it yourself. Below is an example of mutable state across requests, used to keep track of the number of requests the server has received. Note that dependencies and use clauses have been omitted for brevity:

/// Request counter, used to track the number of requests made.
///
/// Due to being shared across many worker threads, the internal counter
/// is bound inside an `Arc` (to enable sharing) and a `Mutex` (to enable
/// modification from multiple threads safely).
///
/// This struct must implement `Clone` and `StateData` to be applicable
/// for use with the `StateMiddleware`, and be shared via `Middleware`.
#[derive(Clone, StateData)]
struct RequestCounter {
    inner: Arc<Mutex<usize>>,
}

/// Counter implementation.
impl RequestCounter {
    /// Creates a new request counter, setting the base state to `0`.
    fn new() -> Self {
        Self {
            inner: Arc::new(Mutex::new(0)),
        }
    }

    /// Increments the internal counter state by `1`, and returns the
    /// new request counter as an atomic operation.
    fn incr(&self) -> usize {
        let mut w = self.inner.lock().unwrap();
        *w += 1;
        *w
    }
}

/// Basic `Handler` to say hello and return the current request count.
///
/// The request counter is shared via the state, so we can safely
/// borrow one from the provided state. As the counter uses locks
/// internally, we don't have to borrow a mutable reference either!
fn say_hello(state: State) -> (State, Response<Body>) {
    let message = {
        // borrow a reference to the counter from the state
        let counter = RequestCounter::borrow_from(&state);

        // create our message, incrementing our request counter
        format!("Hello from request #{}!\n", counter.incr())
    };

    // create the response
    let body = (message, mime::TEXT_PLAIN);
    let res = create_response(&state, StatusCode::OK, body);

    // done!
    (state, res)
}

/// Start a server and call the `Handler` we've defined above
/// for each `Request` we receive.
pub fn main() {
    // create our state middleware to share the counter
    let middleware = StateMiddleware::new(RequestCounter::new());

    // create a middleware pipeline from our middleware
    let pipeline = single_middleware(middleware);

    // construct a basic chain from our pipeline
    let (chain, pipelines) = single_pipeline(pipeline);

    // build a router with the chain & pipeline
    gotham::start("127.0.0.1:7878", build_router(chain, pipelines, |route| {
        route.get("/").to(say_hello);
    }))
}

Although this example looks quite large due to completeness, the relevant part to look at here is the StateMiddleware used inside main() to attach the RequestCounter as a middleware component. After this is configured you can easily borrow your counter (via RequestCounter::borrow_from(&state)) in any of your handlers and modify using the safety guarantees you’re used to.

Due to the routing API Gotham offers, it’s also possible to attach different states to different parts of your router to offer isolation across handlers. It should also be noted that you can attach multiple StateMiddleware to a single router, as long as the internal type (in this case RequestCounter) is unique.

Customized Startup

In Gotham 0.2 a server would automatically start on a predetermined number of threads, and would use a runtime entirely controlled by Gotham. Although this is typically the desired outcome, Gotham 0.3 introduces a few more APIs to control the server startup:

// Same as previous; uses default threads + runtime
gotham::start("127.0.0.1:7878", router());

// Allows control of thread counts; in this case a single thread
gotham::start_with_num_threads("127.0.0.1:7878", router(), 1);

// Allows control of the `TaskExecutor` used
gotham::start_on_executor("127.0.0.1:7878", executor());

// Returns a `Future` to spawn a Gotham server
gotham::init_server("127.0.0.1:7878", router());

Each of these APIs are just delegates to each other so there’s little maintenance overhead to offering them, but they provide a little extra control over the Gotham service. A good example of this was contributed by @im-0, who used init_server to implement a graceful shutdown for their application by using a select and a shutdown signal from tokio_signal.

A Note from the Maintainers

Although Gotham’s call for maintainers has been answered, please get in touch if you’re interested in contributing! We’re always interested in hearing new ideas and potential improvements to Gotham, especially those based on a 0.4 timeline. The 0.4 roadmap is still being figured out, so feel free to let us know your ideas.

We’d like to say thank you to the developers of our dependencies, the Rust language team themselves, and of course everybody who was involved in the 0.3 release - whether it be a new feature, an example or filing an issue, we couldn’t have done this without you.