Announcing Gotham 0.3
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 res = create_response(&state, StatusCode::OK, mime::TEXT_PLAIN, message);
// 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.