Crails controllers are located in app/controllers
. They inherits the ControllerBase class, which allows the Router
to interact with them, and brings a few features along (csrf security, view rendering features, redirecting methods,
and exception filtering).
They are compiled along with the application, which means you must recompile and restart the server at every new revision.
This is what a Crails controller looks like:
#ifndef CRM_ACCOUNTS_HPP
# define CRM_ACCOUNTS_HPP
# include <crais/controller.hpp>
class CrmAccountsController : public Crails::Controller
{
public:
CrmAccountsController(Params& params) : Crails::Controller(params)
{
}
void index();
void show();
void _new();
void create();
void edit();
void update();
void destroy();
};
#endif
Some explanations about what you're seeing right now: First of all, the crails/appcontroller.hpp is a header that includes pretty much everything needed to write contollers with Crail: (DynStruct, Params, ControllerBase, SharedVars and more).
The controller class must inherits ControllerBase, which will implement some default behaviors (by default, no exception catcher or filters are set, but you can override these behaviors). Since the server might run in asynchronous mode, you must also be careful not to use any global variables or static attibutes unless you know what you're doing (the Boots lib implements some asynchronous development tools that you might wanna use if you need global stuff. Check out Boots/Sync/semaphore.hpp and Boots/Sync/mutex.hpp).
The Router::Initialize()
method located in the app/router.cpp
file: this is where you explain Crails in which
controllers and which methods each HTTP requests should head to.
Let's see what it looks like:
#include <crails/router.hpp>
#include "controllers/crm_accounts.hpp" // don't forget to include your controllers
void Crails::Router::Initialize(void)
{
SetRoute("GET", "/crm_accounts", CrmAccountsController, index);
SetRoute("GET", "/crm_accounts/new", CrmAccountsController, _new);
SetRoute("GET", "/crm_accounts/edit", CrmAccountsController, edit);
SetRoute("GET", "/crm_accounts/:id", CrmAccountsController, show);
SetRoute("DELETE", "/crm_accounts/:id", CrmAccountsController, destroy);
SetRoute("PUT", "/crm_accounts/:id", CrmAccountsController, update);
SetRoute("POST", "/crm_accounts", CrmAccountsController, create);
}
SetRoute takes four parameters: the request's type, the URL matcher, the controller's name and the method's name.
The routes are checked in the same order you wrote them in. This means that when you're using variables (:id
is the parameter in this exemple), you must make sure any route that might match the variable (in this exemple, /crm_accounts/new
and /crm_accounts/update
) is specified before the one using a variable.
We'll now see how to implement a controller method and how to link it to a route.
void CrmAccountsController::show()
{
std::string body;
body = "Hello World<br />";
body += params["id"].Value(); // params is a protected reference to the request's params
response["headers"]["Content-Type"] = "text/html"; // use this to set the response's headers, default is text/html
response["status"] = 200; // use this to set the response's status code. Default is 200.
response["body"] = body; // use this to set the response's body
}
Here's a not-quite-minimal method for a controller.
Let's first talk about the response
and params
objets. They are quite similar since they are both classes of the DynStruct type, however Params
implements other tools (for handling file upload for instance).
The param object is also a DynStruct, but it contains everything you need to know about the client's request: All the GET, form, multipart and routing parameters are stored at the root of the params object. Say your route is /crmacconts/:id, you would access the id parameter by using params["id"]. There's also an header node (params["headers"]) containing the header parameters of the request.
The session is managed directly by Crails. It might not be a good idea to send "Set-Cookie" headers, since Crails is going to handle those on its own. The session variable can be accessed through the Params object. For instance, if you wish to record the ID of a connected user:
params.Session()["current_user"] = "user_id";
And if you wish to remove it:
params.Session()["current_user"].Remove();
Flash is a particular part of the session which gets erased at the beginning of each request. It's a way of communicating between one request and the next one. For instance, to record a notification that needs to be displayed to an user:
void SessionsController::destroy()
{
params.Session()["current_user"].Remove();
params.Session()["flash"]["info"] = "You've been disconnected"; // Sets a message into flash
RedirectTo(params["headers"]["Referer"]); // Redirects to previous page
}
The flash variables aren't accessed the same way they're set. If you want to display the message:
void SessionsController::show()
{
std::cout << flash["info"].Value() << std::endl; // displays the message
}
CSRF is a system that use our encrypted cookie to authentify requests and guarantee the origin of POST, PUT and DELETE requests. An exception is thrown when the request cannot be identified.
Crails takes care of generating CSRF tokens (using a salt value, generated in config/salt.cpp
), and storing it in
the encrypted cookie. From the views, the token is inserted in a element, and inserted in forms using the public/js/crails.js
script.
Here's what an ecpp template must include to make the csrf authentication work:
#include <crails/params.hpp>
Params* @params;
string* @yield;
// END LINKING
<!DOCTYPE html>
<html>
<head>
<!-- insert the csrf token in the view' --!>
<meta name="csrf-token" content="<%= params->Session()["csrf_token"].Value() %>"/>
<!-- include the crails.js script --!>
<script type="text/javascript" src="/js/crails.js"/></script>
You may disable CSRF security by overloading the virtual bool must_protect_from_forgery() const
method.
Example:
class MyController : public Crails::Controller
{
bool must_protect_from_forgery() const
{
return (false);
}
;
Uploaded files are exposed to the developer in the Param object. You can access them using the Upload method:
const Params::File* Upload(const std::string& key) const;
The returned File struct implements the following attributes:
struct File
{
std::string key; // attribute 'name' in the file input html field
std::string temporary_path; // absolute path where the file was uploaded
std::string name; // original name of the file
std::string mimetype; // mimetype
};
Here's an example of contoller method using uploaded files:
#include <Boots/Utils/directory.hpp> // For filesystem operations
void MyController::file_upload()
{
const Params::File* file = params.upload("file_upload[upfile]");
std::stringstream html;
// If a file was uploaded, display it
if (file)
{
std::string display_file;
Filesystem::FileContent(file->temporary_path, display_file);
html << "<pre>" << display_file << "</pre>";
}
// Display the form to upload files
html << "<form mehod='post' action='/file_upload' enctype='multipart/form-data'>";
html << "<input type='file' name='file_upload[upfile]' /><br />";
html << "<input type='submit' value='Upload' />";
html << "</form>";
render(HTML, html.str());
}
This controller method will generate an upload form. If the query contains an uploaded file, it will be displayed at the top of the page. Create the routes with:
SetRoute("GET", "/file_upload", MyController, file_upload)
SetRoute("POST", "/file_upload", MyController, file_upload)
And test it by uploading text files.
You may also want to run some code before and after each of your controller methods. Using the constructor and destructor method is an acceptable way of reaching that goal.
You may for instance prevent your controller method from being called by having a response status being set in the constructor. Here's a pratical exemple, showing how you'd force a controller to only run if a user is authentified:
#include "app/models/user.hpp"
class ApplicationController : public Crails::Controller
{
protected:
ApplicationController(Params& params) : Crails::Controller(params)
{
}
SP(User) get_current_user()
{
if (current_user.Null() && params.Session()["user_id"].NotNil())
current_user = User::Find(params.Session()["user_id"].Value());
return current_user;
}
bool require_authentified_user()
{
if (get_current_user().Null())
{
response["status"] = 401; // Unauthorized
return false;
}
return true;
}
private:
SP(User) current_user;
}
class ProfileController : public ApplicationController
{
public:
ProfileController(Params& params) : ApplicationController(params)
{
require_authentified_user();
}
void show()
{
render(JSON, get_current_user().to_data());
}
}
Note that you may also use params["controller-data"]["action"]
to figure out which method is going to be called from the constructor. For instance, you may want to do:
class ProfileController : public ApplicationController
{
public:
ProfileController(Params& params) : ApplicationController(params)
{
if (params["controller-data"]["action"] == "show")
before_show();
}
void before_show()
{
require_authentified_user();
}
void index(); // User may not be authentified
void show(); // User has to be authentified
}