Anatomy of a Rocket Application
Rocket applications are incredibly easy to write! Tab through the examples below to get a glimpse into how a Rocket application is structured.
Routing
Rocket’s main task is to route incoming requests to the appropriate request handler using your application’s declared routes. Routes are declared using Rocket’s route attributes. The attribute describes the requests that match the route. The attribute is placed on top of a function that is the request handler for that route.
As an example, consider the simple route below:
#[get("/")]
fn index() -> &'static str {
"Hello, world!"
}
This route, named index
, will match against incoming HTTP GET
requests to the /
path, the index. The request handler returns a string. Rocket will use the string as the body of a fully formed HTTP response.
Dynamic Params
Rocket allows you to interpret segments of a request path dynamically. To illustrate, let’s use the following route:
#[get("/hello/<name>/<age>")]
fn hello(name: &str, age: u8) -> String {
format!("Hello, {} year old named {}!", age, name)
}
The hello
route above matches two dynamic path segments declared inside brackets in the path: <name>
and <age>
. Dynamic means that the segment can be any value the end-user desires.
Each dynamic parameter (name
and age
) must have a type, here &str
and u8
, respectively. Rocket will attempt to parse the string in the parameter’s position in the path into that type. The route will only be called if parsing succeeds. To parse the string, Rocket uses the FromParam trait, which you can implement for your own types!
Handling Data
Request body data is handled in a special way in Rocket: via the FromData trait. Any type that implements FromData
can be derived from incoming body data. To tell Rocket that you’re expecting request body data, the data
route argument is used with the name of the parameter in the request handler:
#[post("/login", data = "<user_form>")]
fn login(user_form: Form<UserLogin>) -> String {
// Use `user_form`, return a String.
}
The login
route above says that it expects data
of type Form<UserLogin>
in the user_form
parameter. The Form type is a built-in Rocket type that knows how to parse web forms into structures. Rocket will automatically attempt to parse the request body into the Form
and call the login
handler if parsing succeeds. Other built-in FromData
types include Data, Json, and Flash
In addition to dynamic path and data parameters, request handlers can also contain a third type of parameter: request guards. Request guards aren’t declared in the route attribute, and any number of them can appear in the request handler signature.
Request Guards
Request guards protect the handler from running unless some set of conditions are met by the incoming request metadata. For instance, if you are writing an API that requires sensitive calls to be accompanied by an API key in the request header, Rocket can protect those calls via a custom ApiKey
request guard:
#[get("/sensitive")]
fn sensitive(key: ApiKey) -> &'static str { ... }
ApiKey
protects the sensitive
handler from running incorrectly. In order for Rocket to call the sensitive
handler, the ApiKey
type needs to be derived through a FromRequest implementation, which in this case, validates the API key header. Request guards are a powerful and unique Rocket concept; they centralize application policy and invariants through types.
Responders
The return type of a request handler can be any type that implements Responder:
#[get("/")]
fn route() -> T { ... }
Above, T must implement Responder
. Rocket implements Responder
for many of the standard library types including &str
, String
, File
, Option
, and Result
. Rocket also implements custom responders such as Redirect, Flash, and Template.
The task of a Responder
is to generate a Response, if possible. Responder
s can fail with a status code. When they do, Rocket calls the corresponding error catcher, a catch
route, which can be declared as follows:
#[catch(404)]
fn not_found() -> T { ... }
Launching
Launching a Rocket application is the funnest part! For Rocket to begin dispatching requests to routes, the routes need to be mounted. After mounting, the application needs to be launched. These two steps, usually done in main
, look like:
rocket::ignite()
.mount("/base", routes![index, another])
.launch()
The mount
call takes a base path and a set of routes via the routes!
macro. The base path (/base
above) is prepended to the path of every route in the list. This effectively namespaces the routes, allowing for easier composition.
The launch
call starts the server. In development, Rocket prints useful information to the console to let you know everything is okay.
🚀 Rocket has launched from http://localhost:8000...
How Rocket Works
Every request received by a Rocket web application follows a simple 3-step process from request to response.
1 Validation
First, Rocket validates a matching request by ensuring that all of the types in a given handler can be derived from the incoming request. If the types cannot be derived, the request is forwarded to the next matching route until a route’s types validate or there are no more routes to try. If all routes fail, a customizable 404 error is returned.
#[post("/user", data = "<new_user>")]
fn new_user(admin: AdminUser, new_user: Form<User>) -> T {
...
}
For the new_user
handler above to be called, the following conditions must hold:
- The request method must be
POST
. - The request path must be
/user
. - The request must contain
data
in its body. - The request metadata must authenticate an
AdminUser
. - The request body must be a form that parses into a
User
struct.
2 Processing
Next, the request is processed by an arbitrary handler. This is where most of the business logic in an application resides, and the part of your applications you’ll likely spend the most time writing. In Rocket, handlers are simply functions - that’s it! The only caveat is that the function’s return type must implement the Responder
trait. The new_user
function above is an example of a handler.
3 Response
Finally, Rocket responds to the client by transforming the return value of the handler into an HTTP response. The HTTP response generated from the returned value depends on the type’s specific Responder
trait implementation.
fn route() -> T { ... }
If the function above is used as a handler, for instance, then the type T
must implement Responder
. Rocket provides many useful responder types out of the box. They include:
Json<T>
: Serializes the structure T into JSON and returns it to the client.Template
: Renders a template file and returns it to the client.Redirect
: Returns a properly formatted HTTP redirect.NamedFile
: Streams a given file to the client with the Content-Type taken from the file’s extension.Stream
: Streams data to the client from an arbitraryRead
value.- Many Primitive Types:
String
,&str
,File
,Option
,Result
, and others all implement theResponder
trait.