ratpack: a simpleton's HTTP framework (for rust-lang)

ratpack is idealized in the simplicity of the sinatra (ruby) framework in its goal, and attempts to be an alternative to other async HTTP frameworks such as tower, warp, axum, and tide.

ratpack tries to deliver a promise that any handler can also be middleware, by implementing a "chain of responsibility" pattern that crosses handler boundaries. In summary, what you return from the first handler is fed to the second, which returns to the third, until all handlers are processed, or an error is received. Errors can return valid status codes or plain text errors in the form of a HTTP 500 (Internal Service Error).

What ratpack is not

Example

Here is an example which carries global application state as an authentication token validator middleware handler, which then passes forward to a greeting handler. The greeting handler can also be re-used without authentication at a different endpoint, which is also demonstrated.

Note: this is available at examples/auth-with-state.rs. It can also be run with cargo: cargo run --example auth-with-state.

```rust use ratpack::prelude::*;

// We'll use authstate to (optionally) capture information about the token // being correct. if it is Some(true), the user was authed, if None, there was no // authentication performed.

[derive(Clone)]

struct AuthedState { authed: Option, }

// All transient state structs must have an initial state, which will be // initialized internally in the router. impl TransientState for AuthedState { fn initial() -> Self { Self { authed: None } } }

// our authtoken validator, this queries the app state and the header // X-AuthToken and compares the two. If there are any discrepancies, it // returns 401 Unauthorized. // // every handler & middleware takes and returns the same params and has the // same prototype. // async fn validate_authtoken( req: Request, resp: Option>, _params: Params, app: App, mut authstate: AuthedState, ) -> HTTPResult { if let (Some(token), Some(state)) = (req.headers().get("X-AuthToken"), app.state().await) { authstate.authed = Some(state.clone().lock().await.authtoken == token); Ok((req, resp, authstate)) } else { Err(Error::StatusCode(StatusCode::UNAUTHORIZED)) } }

// our hello responder; it simply echoes the name parameter provided in the // route. async fn hello( req: Request, _resp: Option>, params: Params, _app: App, authstate: AuthedState, ) -> HTTPResult { let name = params.get("name").unwrap(); let bytes = Body::from(format!("hello, {}!\n", name));

if let Some(authed) = authstate.authed {
    if authed {
        return Ok((
            req,
            Some(Response::builder().status(200).body(bytes).unwrap()),
            authstate,
        ));
    }
} else if authstate.authed.is_none() {
    return Ok((
        req,
        Some(Response::builder().status(200).body(bytes).unwrap()),
        authstate,
    ));
}

Err(Error::StatusCode(StatusCode::UNAUTHORIZED))

}

// Our global application state; must be Clone.

[derive(Clone)]

struct State { authtoken: &'static str, }

// ServerError is a catch-all for errors returned by serving content through // ratpack.

[tokio::main]

async fn main() -> Result<(), ServerError> { let mut app = App::withstate(State { authtoken: "867-5309", }); app.get("/auth/:name", composehandler!(validateauthtoken, hello)); app.get("/:name", composehandler!(hello));

app.serve("127.0.0.1:3000").await?;

Ok(())

} ```

Hitting this service with curl gives the result you'd expect:

``` % curl localhost:3000/erik hello, erik!

% curl -D- localhost:3000/auth/erik HTTP/1.1 401 Unauthorized content-length: 0 date: Fri, 21 Jan 2022 18:29:03 GMT

% curl -D- -H "X-AuthToken: 867-5309" localhost:3000/auth/erik HTTP/1.1 200 OK content-length: 13 date: Fri, 21 Jan 2022 18:29:19 GMT

hello, erik! ```

More information & documentation

For more information, see the docs.

Author

Erik Hollensbe erik.hollensbe@zerotier.com

License

BSD 3-Clause