Version: v0.9.0

Handlers

In web applications handlers are the pieces of software which receive the request sent by the browser to the server and give back a response. They are the centerpiece in every MVC application as they are the ones who call the Model and the View, if necessary, to create the response.

Usually there are two types of handlers: Middlewares and Controllers. Both are supported in Kalgan.

Middlewares

Middlewares are the elements which catch the request before it arrives to the controller. Usually they're used when the same task must be done in more than one controller (for example, if we want to check whether the user is logged). Kalgan calls the middleware based on the data defined in the Route that matches the request sent by the browser.

Basically, Middlewares are Rust functions which receive the struct kalgan::http::request::Request as a parameter and return the struct kalgan::handler::middleware::Outcome. In between, they can append data to the Hashmap field Request.middleware which can be used by the controller. Once the Middleware returns the Outcome Kalgan passes the request to the controller (as long as the outcome is successful).

By convention it's expected to locate our middlewares inside folder src/middleware/ and declare all of them in file src/middleware.rs, above the resolver. It's recommended to name the file with the extension _middleware but not the within function(s) which handle the request.

Middleware Resolver

The middleware resolver is a Factory Method to create the Outcome. It's a function which receives the Request and a string with the path of the middleware and, based on a match statement, returns the Outcome created by the matched middleware. We must include every middleware in the resolver, otherwise Kalgan won't be able to find it.

For example:

pub(crate) mod check_access_middleware;

use kalgan::http::request::Request;
use kalgan::handler::middleware::Outcome;

pub(crate) fn resolver(request: &mut Request, middleware: &str,) -> Result<Outcome, String> {
    match middleware {
        "check_access_middleware::redirect_if_not_logged" =>Ok(backoffice_middleware::redirect_if_not_logged(request)),
        "check_access_middleware::redirect_if_logged" => Ok(backoffice_middleware::redirect_if_logged(request)),
        _ => Err(format!("Middleware {} not found in resolver.", &middleware)),
    }
}

By convention the fn resolver should be located in src/middleware.rs.

Finally, we must remember to pass the resolver as the third parameter in function kalgan::run of our main.rs. Otherwise it must be set to None.

...
mod controller;
mod middleware;

fn main() {
    kalgan::run("config/app", controller::resolver, Some(middleware::resolver));
}

Middleware Outcome

Unlike controllers, middlwares don't return a kalgan::http::response::Response but kalgan::handler::middleware::Outcome instead. However, in certain cases the latter can include the former.

Find the definition of this struct in the code below:

...
#[derive(Debug)]
pub struct Outcome {
    pub success: bool,
    pub response: Option<Response>
}
... 

Let take a look at these fields:

Outcome.success It's set to true if the request is passed to the controller defined in the Route. Otherwise it must be set to false.
Outcome.response If the outcome is not successful a Response must me given back. Otherwise None should be return.

Controllers

Basically, controllers are Rust functions which receive the struct kalgan::http::request::Request as a parameter and return the struct kalgan::http::response::Response. Kalgan calls the controller based on the data defined in the Route that matches the request sent by the browser. Every controller is attached to -at least- one route.

Controller Definition

Regarding the architecture and naming convention, Kalgan follows the guidelines used in some of the most popular web frameworks. It's recommended to name the file controller with the extension _controller but not the within function(s) which handle the request. These functions are usually known as actions and the controllers are the objects which store them. We can see these actions as what the controllers do.

For example, given a CRUD of actions for the users in our application, we'd organise our controller as follows:

use kalgan::http::{ request::Request, response::Response };

pub(crate) fn create(request: &Request) -> Response {
    ...
}
pub(crate) fn read(request: &Request) -> Response {
    ...
}
pub(crate) fn update(request: &Request) -> Response {
    ...
}
pub(crate) fn delete(request: &Request) -> Response {
    ...
}

Again, by convention it's expected to locate our controllers inside folder src/controller/ and declare all the modules in file src/controller.rs, above the resolver.

Controller Resolver

The controller resolver is a Factory Method to create the Response. Basically, it's a function which receives the Request and a string with the path of the controller and, based on a match statement, returns the Response created by the matched controller. We must include every controller in the resolver, otherwise Kalgan won't be able to find it.

For example:

pub(crate) mod hello_world_controller;
pub(crate) mod foo_bar_controller;

use kalgan::http::{ request::Request, response::Response };

pub(crate) fn resolver(request: &Request, controller: &str) -> Result<Response, String> {
    match controller {
        "hello_world_controller::index" => Ok(hello_world_controller::index(request)),
        "foo_bar_controller::index" => Ok(foo_bar_controller::index(request)),
        _ => Err(format!("Controller {} not found in resolver.", &controller))
    }
}

By convention the fn resolver should be located in src/controller.rs.

Finally, we must remember to pass the resolver as the second parameter in function kalgan::run of our main.rs.

...
mod controller;

fn main() {
    kalgan::run("config/app", controller::resolver, None);
}

Notice that in this example the middleware::resolver is set to None.

Controller Response

There are several ways to create the Response in the controller. Our choice will depend on the type of response we want to create.

1. Creating the struct from scratch:

We must call the method Response::new() to create the instance followed by the appropriate setters. For example:

use kalgan::http::{ request::Request, response };
pub(crate) fn foobar(request: &Request) -> response::Response {
    ...
    let contents: String = "...";
    response::Response::new()
        .set_status(200)
        .set_content_type("text/html; charset=UTF-8")
        .set_content_length(contents.len())
        .set_content(&contents)
}

If you want a more detailed description of this struct, see Request / Response in the docs.

2. Calling the function kalgan::http::response::render():

With this function we delegate the creation of the struct. However it's only available if we're rendering the templates with Tera. We must pass the location of the template and the kalgan::template::Context. For example:

use kalgan::http::{ request::Request, response };
pub(crate) fn foobar(request: &Request) -> response::Response {
    ...
    response::render("foo/foobar.html", kalgan::template::Context::new())
}

With this function we delegate the creation of the struct. However it's only available if we're rendering the templates with Tera. We must pass the location of the template and the kalgan::template::Context. For example:

3. Calling the macro render!():

use kalgan::http::{ request::Request, response::Response };
pub(crate) fn foobar(request: &Request) -> Response {
    ...
    render!("foo/foobar.html")
}

If you come from a different language you might feel more confortable with this one.

Other built-in functions

Aside from the previous ones, Kalgan offers a few other functions to create some of the most common responses:

kalgan::http::response::json(contents: &str) Creates a JSON response.
kalgan::http::response::xml(contents: &str) Creates a XML response.
kalgan::http::response::redirect(url: String) Creates a URL redirection (302 response).

Last but not least, we have another function the create error responses. See Triggering errors in the docs.

Handling forms data

Unlike other web frameworks, currently Kalgan doesn't provide any tool to render HTML forms. However it's ready to handle the input data of these forms.

These input data are stored in fields Request.input and Request.files of struct Request (see Request / Response in the docs). Therefore, both middlewares and controllers are capable of handle forms data.

As these fields are private, we'll have to use the methods commonly known as getters to get their value: Request.get_input() and Request.get_files().

Handling input data

HTML form fields are collected in Request.input, which is basically a string to string Hashmap.

Managing these input data is pretty straightforward. Let's see an example:

...
<form action="/" method="post">
    <input type="text" name="user_name">
    <input type="password" name="user_password">
    <input type="submit" value="Upload Data">
</form>
...
...
dbg!(&request.get_input()["user_name"]);
dbg!(&request.get_input()["user_password"]);
...

Handling input files

HTML form files are collected in Request.files field, which is a string to kalgan::http:request::File Hashmap, that is to say: HashMap<String, File<'a>>.

Find the definition of File in the code below:

#[derive(Serialize, Deserialize, Debug)]
pub struct File<'a> {
    pub filename: String,
    pub content_type: String,
    pub content: &'a [u8]
}

Let's see an example of file upload management:

...
<form action="/" method="post" enctype="multipart/form-data">
    <input type="file" name="my_input_file">
    <input type="submit" value="Upload File">
</form>
...
...
let upload_file = request.get_files()["my_input_file"];
dbg!(&upload_file.filename);
dbg!(&upload_file.content_type);

// We can save the file as follows:
let file_path = PathBuf::from(...);
let mut file = fs::File::create(file_path)?;
file.write(&upload_file.content)?;
...