Controller basics
Rwf comes with multiple pre-built controllers that can be used out of the box, for example, to handle WebSocket connections, REST-style interactions, or serving static files. For everything else, the Controller
trait can be implemented to handle any kind of HTTP requests.
What's a controller?
The controller is the C in MVC: it handles user interactions with the web app and performs actions on their behalf. A controller takes care of user inputs, like forms, and all other HTTP requests to the app.
Writing a controller
A controller is a plain Rust struct that implements the Controller
trait. As an example, let's write a controller that returns the current time in UTC.
Import types
The prelude module contains most of the types and traits necessary to work with Rwf. Including it will save you time and effort when writing code, but it's not required.
Define the struct
This struct has no fields, but you can add any internal state you want to keep track of in there. The Default
trait is derived automatically to provide a convenient way to instantiate it.
Implement the Controller
trait
#[async_trait]
impl Controller for CurrentTime {
/// This function handles incoming HTTP requests.
async fn handle(&self, request: &Request) -> Result<Response, Error> {
let time = OffsetDateTime::now_utc();
// This creates an HTTP "200 OK" response,
// with "Content-Type: text/plain" header.
let response = Response::new()
.text(format!("The current time is: {:?}", time));
Ok(response)
}
}
The Controller
trait is asynchronous. Support for async traits in Rust is still incomplete, so we use the async_trait
library to make it easy to use. The trait itself has a few methods, most of which have reasonable defaults. The only method that needs to be written by hand is async fn handle()
.
handle
The handle
method accepts a Request
and must return a Response
. The response can be any valid HTTP response, including 404
or even 500
.
Errors
If an error occurs inside the async fn handle
function, Rwf will return HTTP 500
automatically and display the error to the client.
Connecting controllers
Once you implement a controller, adding it to the app requires mapping it to a route. A route is a unique URL, starting at the root of the app. For example, /signup
is a route that could map to the Signup
controller, and allow your users to create accounts.
Adding controllers to the app happens at server startup. A server can be launched from an async task anywhere in the code, but typically is done so from the main
function:
use rwf::prelude::*;
use rwf::http::{self, Server};
#[tokio::main]
async fn main() -> Result<(), http::Error> {
Server::new(vec![
// Map the `/time` route to the `CurrentTime` controller.
route!("/time" => CurrentTime),
])
.launch("0.0.0.0:8000")
.await
}
Note
The route!
macro is a shorthand for calling CurrentTime::default().route("/time")
. We use it because it looks cool, but it's not required.
You can instantiate your controller struct in any way you need, and call the Controller::route
method when adding it to the server. Alternatively, you can implement the Default
trait like we did in this example and use the macro.
Test with cURL
Once the server is up and running, you can test your endpoints with cURL (or with a regular browser, like Firefox):
Split GET
from POST
Controllers that implement the Controller
trait don't make a distinction between HTTP request methods and handle all of them in one function. Most websites show pages via GET
requests and accept form submissions via POST
requests. To avoid writing boilerplate code in the handle
method, Rwf has another type of controller, PageController
which splits up the two methods into their own functions: async fn get
and async fn post
:
#[derive(Default, macros::PageController)]
struct Login;
impl PageController for Login {
/// Handle GET request.
async fn get(&self, request: &Request) -> Result<Response, Error> {
/* show page, by rendering a template */
}
/// Handle POST request.
async fn post(&self, request: &Request) -> Result<Response, Error> {
let form = request.form_data();
/* process form submission and redirect */
}
}
Learn more
Read more about working with controllers, requests, and responses: