Resources
Web applications expose resource URLs and handle requests for
these resources. Path parameters complicate
things but enable clean URLs.
A mapping between a resource and application code
which processes requests for the resource is called routing.
Civilian has a low-config, intrinsic approach to routing.
Introduction
From a clients perspective a web application consists of resources which are addressed by a URL and
can be requested via HTTP.
Internally, depending on whether the response content is generated at runtime, we talk about
dynamic
or
static resources. Usually we differentiate static from dynamic resources by using the name
assets
for the static kind.
Then main tasks with regard to resource processing are
- define the URLs of resources which can be requested by a client.
- for every dynamic resource write code to process requests for that resource and dynamically generate responses.
- provide files for all static resources (assets) and expose them via Civilians asset handling.
This chapter explains handling of dynamic resources. From now on we will simply call them resources.
Civilian uses the Resource class to model a resource. At runtime every application
possesses a list of all its resources, organized as resource tree.
The code which processes resource requests is implemented in Controller classes. In Civilian every resource is
associated with (at most) one controller class.
Resource |
Controller |
Resource("/users") |
com.myapp.UsersController |
Resource("/messages") |
com.myapp.MessagesController |
The
next chapter will explain the inner workings of controllers. Here
we describe how the resource tree is defined and how resources are mapped to controller classes.
In webapp lingo this is called
routing.
Some web frameworks require to explicitly specify the resource paths
and map them to controller classes/functions/code. This is fine for a small set of resources but tedious
for complex applications.
Civilian uses a more implicit approach: It scans the controller classes of an application and builds the resource
tree using naming conventions and annotations within the controllers.
Before we explain how this works we complicate things a little bit and introduce path parameters.
Path parameters
From the applications perspective, it is tempting to interpret an URL as a function, a request as a function call, and
URL parameters arguments to the function. For example in the following URL the id parameter could specify a database id of
a customer entity, and the sample request would therefore return a HTML page showing data of customer 1345:
https://example.org/crm/customers/show.html?id=1345
Contrary to that view the concept of
semantic or clean URLs
demands a different style of URL design: URLs identify resources and should not contain technical parameters which are instead
expressed in a RESTful way:
- Customer 1345 receives an own URL (which is different from customer 1346),
therefore the parameter
id
is made part of the request path.
- The URL segment
/show
is dropped. Instead the HTTP method "GET" is used to tell the server that we
intend to retrieve readonly information.
- The path extension
.html
is dropped. Instead the HTTP Accept header is used to request a specific content-type.
GET /crm/customers/1345
Accept: text/html
Accordingly, updating customer 1346, by sending JSON data, might be done with
PUT /crm/customers/1346
Content-Type: application/json
From a functional point of view, path segments like
/1345
or
/1346
can transport the same
information as parameters and can be used for the same purpose (namely identifying application data).
We call such segments
path parameters. (Please note that path parameters are not a syntax element of URLs but just
an interpretation of path segments as parameter values).
Of course, in order to answer such requests, the application will examine path parameter segments, convert them into a value
(e.g. a customer id) and use them in their calls to the data back-end.
Path parameters enable clean URLs, but internally require more sophisticated mapping of URLs to controller code:
With path parameters applications can potentially expose an unlimited number of resource URLs.
Therefore routing can't just use constant resource paths, but instead needs to map path schemes to controller code:
/crm/customers/{:customerId [0-9]+} |
⇒ com.crm.customers.id.IndexController |
At runtime the path parameter segments are converted into a path parameter value:
/crm/customers/1345 |
run IndexController with customerId = 1345 |
In Civilian path parameters are not constrained to a single path segment, but can also span multiple path segments.
Types of path parameter values can be of any kind, not just Strings or simple types.
/archive/2014/11/20 |
≡ Date(2014,11,20) |
/blog/posts/c3po/latest-musings |
≡ Post(id:"c3po", slug:"latest-musings") |
/en/intro |
≡ Locale.ENGLISH |
Extracting path parameter values from path strings is only one half of the game. When we want to build resource URLs
(for instance to be used in HTML hyperlinks) path parameter values need to be formatted and inserted into a path scheme.
Civilian takes a very formalized approach in handling path parameters:
- Every path parameter is described by an own PathParam object.
- A PathParam object knows how to extract its value from one or more path segments and
is able to format a value into one or more path segments.
- PathParam implementations exist for most common use cases, but you can easily build implementations
for more specific cases.
- Defining the URL of a resource includes to describe which path parameters are used in what URL parts.
- When a request is dispatched, and the matching resource is determined, all path parameters are automatically recognized,
their values extracted and made available via Request.getPathParam(PathParam).
This method does not take some string to identify the path param, but a PathParam object, therefore enforcing
compile time safety.
- When generating URLs via the Url class, you simply provide a value for every contained path parameter
and let the Url class do the formatting.
- All the path parameters used by an application are defined as application wide constants.
Application implementations register their path parameters via a PathParamMap passed to the constructor
of the application base class.
For an example please take a look at the source code of the CRM sample:
Routing
The resource URLs of an application build a hierarchy or tree. So does a file system, and publishing a directory of static resources as
website is easy:
File |
maps to URL Path |
/webapp/index.html |
/index.html |
/webapp/css/stlyes.css |
/css/styles.css |
Base mapping from controller classes to resource paths
Java classes also build a hierarchy and we use this to derive a default mapping from package and class name:
Class |
maps to URL Path |
com.demo.web.LoginController |
/login |
com.demo.web.posts.ListController |
/posts/list |
At application startup Civilian scans the classpath for all controller classes of the application.
To be recognized as controller, a class
- must have a simple name which ends with
Controller
- must (directly or indirectly) be derived from Controller
- must be in or below the controller root package which by default equals
the package of the application class.
The qualified controller class - minus the root package, dots converted to slashes, uppercase converted to lowercase
gives the resource path of the controller (relative to the application path).
One great advantage of this implicit mapping is that you can easily find the controller class given a resource url based
on the naming conventions. And contrary to explicit routing it can't get out of sync
when you rename classes or packages.
IndexControllers
Given the mapping based on package and class names we could not map a controller to the application root
/
.
To enable this, controller classes named
IndexController
are treated specially, and are mapped to the path corresponding to its package:
com.demo.web.IndexController |
/ |
com.demo.web.posts.IndexController |
/posts |
Using the @Segment annotation on packages and class names
When you specify the
@Segment annotation on a controller class or package (in its
package-info.java),
the annotation value then is used to extend the path, instead of the simple class name or the last package part:
@Segment("Civilian") package com.demo.web.civ |
/Civilian |
@Segment("temp") public class com.demo.web.civ.TmpController |
/Civilian/temp |
@Segment("index") public class com.demo.web.civ.IndexController |
/Civilian/index |
Therefore use the @Segment annotation to override the naming conventions when necessary.
(Or as an advanced technique alter the naming conventions).
Please not that the @Segment value is not interpreted as absolute path (like in JAX-RS) but rather
extends the path defined by the parent package.
Using the @PathParam annotation on packages and class names
Right now Controllers can only be mapped to constant paths. To map to path schemes which contain
path parameters, use the
@PathParam annotation on packages
or controller classes.
The value of the annotation is the
PathParam name.
@PathParam("userId") package com.demo.web.users.id< /td>
| /users/{userId:[a-z0-9]+} |
com.demo.web.users.id.ProfileController |
/users/{userId:[a-z0-9]+}/profile |
Using the @Segment annotation on controller methods
Given all these features every dynamic resource exposed by the application still requires an own controller class.
Nothing wrong with that, but for very small controller implementations this might seem a liitle bit oversized.
As we will see in the
Controller chapter, controller classes define
special methods as entry points for request processing.
You can place
@Segment on such methods and create a virtual sub-controller class
which processes requests for the corresponding sub-resource:
com.demo.web.SearchController |
/search |
@Segment("filter") com.demo.web.SearchController#processFilter() |
/search/filter |
So using @Segment annotations on methods you can implements multiple resource URLs in a single controller class.
URLs and extensions
In all the examples we followed the
clean URL
paradigma and did not use any extension in URLs.
Actually when a resource request is dispatched to a controller, the extension of the URL is simply ignored.
By default both these requests URLs yield the same result:
But when dealing with clients which cannot properly set an HTTP Accept-Header to express their preferred response content-type,
the URL extension can be used as a replacement for the Accept header.
GET /users.html ~ GET /users, Accept: text/html
Please study the
ExtensionMapping class how to configure such behavior.
Fine points of resource mappings
No resource may be mapped to more than one controller.
Doing so (e.g. using @Segment annotations), results in an error.
Abstract controller classes will not be mapped to a resource.
Checking resource mappings
The
admin app lists all resources of an application and the associated
controller classes. This allows for a fast check if the reality matches the intended routing.