URL Dispatching and Mapping
Introduction
We will learn how to easily connect different URLs to their associated functions.
When a client requests a specific URL from the HTTP server,
it divides it into several parts (CGI variables): SCRIPT_NAME
, PATH_INFO
and QUERY_STRING
. For example
the URL /foo/bar.php/test?x=10
is separated into:
SCRIPT_NAME
=/foo/bar.php
PATH_INFO
=/test
QUERY_STRING
=x=10
CppCMS web applications are not distributed into several
scripts, but rather they're written using a single FastCGI application that runs on a specific "script" url, like "/myapp". So, generally, URLs will look like /myapp/page/10
, where
SCRIPT_NAME
= "/myapp" remains constant and PATH_INFO
= /page/10
is changed according to the user's needs.
So the mapping between the URL and the application's member functions is done by matching a regular expression against the PATH_INFO
part of the URL, even though this behaviour can be changed by using the mount_point
class.
This job is done by the cppcms::url_dispatcher
class.
The opposite of dispatching is "mapping", where the names of the application parts are converted to URLs that can be given to the user.
Code
Mapping
Lets rewrite our hello class:
Since we will be routing URLs, let's include the url_dispatcher.h
and url_mapper.h
header file. Your include list should look like this:
#include <cppcms/application.h> #include <cppcms/service.h> #include <cppcms/http_response.h> #include <cppcms/url_dispatcher.h> #include <cppcms/url_mapper.h> #include <cppcms/applications_pool.h> #include <iostream> #include <stdlib.h>
Now in our constructor, let's map some URLs to member functions:
hello(cppcms::service &srv) : cppcms::application(srv) { dispatcher().assign("/number/(\\d+)",&hello::number,this,1); mapper().assign("number","/number/{1}"); dispatcher().assign("/smile",&hello::smile,this); mapper().assign("smile","/smile"); dispatcher().assign("",&hello::welcome,this); mapper().assign(""); mapper().root("/hello"); }
In the fourth line we connect the regular expression /number/(\d+)
to the member function number
of this
instance that receives std::string
as parameter,
and the 1st captured subexpression is passed to it (note the parameter 1 that represents the 1st subexpression as a parameter)
In this case the subexpression is a nonempty string that contains digits.
In the 5th line we create a name for this part called
"number" and we provide an URL formatting pattern of /number/{1}
- the opposite of the regular expression where {1}
is a placeholder for the first parameter for formatting the appropriate URL.
In the lines 7-8 we connect the member function smile
to its URL and create a mapping. Because this URL
does not have any parameters it does not have
any placeholders.
On line 10 we connect the empty URL to the
welcome
member function, and on line 11 we define the default empty mapping pattern for the URL. Note, because
we pass only one parameter to the assign()
function,
we define the default URL for the application.
On line 13 we define the "root" of all the URLs for the
url_mapper
class, in our case it should be identical to
the SCRIPT_NAME
CGI variable.
Description
So when the web server receives requests that match
a script name /hello
it forwards them to the application:
/hello
-welcome
function would be called/hello/smile
-smile
function would be called/hello/number/10
-number
function would be called and receive a string "10" as a parameter.
Because the "routing table" between the URL and the
application's member functions is done, we don't need
to override the cppcms::application::main
member
function as it does all the dispatching for us.
Actions
The "number" member function.
void number(std::string num) { int no = atoi(num.c_str()); response().out() << "The number is " << no << "<br/>\n"; response().out() << "<a href='" << url("/") << "'>Go back</a>"; }
Note, we're using url("/")
as an abstract URL for the default application entry point - the /
part describes the root application (our single application) and the fact that nothing follows it shows that the default URL should be
given - the mapping to the welcome
member function.
In the same way we create the smile
member function:
void smile() { response().out() << ":-) <br/>\n"; response().out() << "<a href='" << url("/") << "'>Go back</a>"; }
Now we write out the welcome
action:
void welcome() { response().out() << "<h1> Welcome To Page with links </h1>\n" "<a href='" << url("/number",1) << "'>1</a><br>\n" "<a href='" << url("/number",15) << "'>15</a><br>\n" "<a href='" << url("/smile") << "' >:-)</a><br>\n"; }
Note, we show three URLs to the different application
parts: "number" - and we pass a parameter 1
to the cppcms::application::url
function that will be rendered instead of the placeholder {1}
, we do the same for
number 15 and pass an URL to "smile" without parameters.
Lets describe the actions that are taken in the example when we call url("/number",15)
:
- First for "/" we go to the topmost application, in out case to
this
one. - Next we match "number" against the known pattern "number" that was mapped to
"/number/{1}"
. - We fetch the pattern and use the parameter that is passed to
url
function and convert:/number/{1}
to/number/15
. - Finally we prepend the root URL path "/hello" and get our final URL
/hello/number/15
.
Note: the parameters to url
family of functions can be anything that can be written to std::ostream
- i.e. any object that defines operator <<
with standard C++ streams.
Running The Example
Recompile it:
c++ hello.cpp -lcppcms -o hello
And now for the exciting part, start your application:
./hello -c config.js
And hit the URL: http://localhost:8080/hello