templates.docs.routing.html Maven / Gradle / Ivy
Show all versions of spincast-website Show documentation
{#==========================================
Docs : "Routing"
==========================================#}
{#==========================================
Routing
==========================================#}
Routing
Let's learn how the routing process works in Spincast, and how to create
routes and filters.
Note that we won't decribe all the available APIs here! The complete APIs can be found on the
Routing plugin's page.
{#==========================================
Section "routing / The routing process"
==========================================#}
The routing process
When an HTTP request arrives, the router is asked to find what route handlers should be used to handle it.
There can be more than one route handler for a single request because of filters.
Filters are standard route handlers, but that run before or after the main handler.
There can be only one main handler but multiple
before and after filters. Even if multiple main handlers would match
a request, the router will only pick one (by default, it keeps the first one matching).
Those matching routes handlers are called in a
specific order. As we'll see in the Filters section, a
filter can have a position which indicates when the filter should run. So it is
possible to specify that we want a filter to run before or after another one.
The routing process we are discussing is called the Found routing type. The Found routing
type is the one active when at least one main handler matches the request. This is the most frequent case,
when valid requests are received.
But what if no main handler matches? What if we receive a request for an invalid URL? Then,
we enter the Not Found routing type.
We'll see in the Routing Types section that we can declare some routes
as supporting this Not Found routing type. Those routes are the only ones going to be considered
when the Not Found routing type is active.
Note that we can also enter the Not Found routing
type if a NotFoundException is thrown by one of the route handlers. Suppose
you have a /users/${userId} route, and a request for /users/42
arrives; a main handler is found and called,
but let's say the user 42 is not found in the system. The handler can then throw a
NotFoundException exception, and a new Not Found typed routing process
will be triggered.
The third and final routing type is Exception.
If an exception occures during a Found or a Not Found
routing process, the response is reset, and a new Exception routing process starts.
This is true except for the NotFoundException and
some other special exceptions
which can trigger a different behavior.
You can define your custom Not Found and Exception
handlers, and this is highly recommended, but there are some basic provided ones, if you don't.
There are two important things to remember about the routing process:
-
When the routing process of a new type starts, the code to find the matching handlers is restarted from the beginning.
For example, if you have some "before"
filters that
have already been run during the initial Found routing process, and then your main handler
throws an exception, the
routing process will be restarted, and those filters may be run again.
The only difference between the first routing process and the
second is the routing type required for a route to be considered, when trying to find
the matching route handlers. Only the routes that have been configured to support the
route type of the current routing process are considered.
-
When a routing process starts, the current response is reset. Its
buffer is emptied, and the headers are reset. Of course, if the response's
headers have already been flushed, this is not possible, though.
Finally, Spincast also supports WebSockets. Make sure you read the dedicated section about
WebSockets since it diverges from standard routing!
{#==========================================
Section "routing / Adding a route"
==========================================#}
Adding a route
Now, let's learn how to define our routes!
First, you get an instance of the router.
There are methods on the router to start a route builder. As its name indicates,
this object uses a builder pattern to help you create a route. Let's see that in details.
{#==========================================
Section "routing / HTTP method and path"
==========================================#}
HTTP method and path
You start a route builder by choosing the HTTP method
and path you want this route to handle.
router.GET("/") ...
router.POST("/users") ...
router.DELETE("/users/${userId}") ...
// Handles all HTTP methods
router.ALL("/books") ...
// Handles POST and PUT requests only
router.SOME("/books", HttpMethod.POST, HttpMethod.PUT) ...
{#==========================================
Section "routing / Dynamic parameters"
==========================================#}
Dynamic parameters
You can use dynamic parameters in the paths of your routes.
For example, the following route will match any request with an
URL starting with /users/ and followed by another token:
router.GET("/users/${userId}")
By doing so, your route handlers can then access the actual value of this
dynamic parameter. For example :
public void myHandler(IAppRequestContext context) {
String userId = context.request().getPathParam("userId");
// Do something with the user id...
}
If a /users/42 request is handled, userId would be "42", in this
example.
Note that this "/users/${userId}" example will only match
URLs of exactly two tokens! An URL like "/users/42/book/123"
won't match!
If you want to match more than one path tokens as a single value, you have to use a
splat parameter. For example, the following path
does match both "/users/42" and "/users/42/book/123"
router.GET("/users/${userId}/*{remaining}")
In that case, the route handler would have access to two path parameters:
userId, will be "42", and remaining will
be "book/123".
As you can see, a regular dynamic parameter syntax is ${paramName}, where
a splat parameter syntax is *{paramName}.
A dynamic parameter can also contain a regular expression pattern. The
syntax is ${paramName:pattern}, where "pattern" is the regular expression
to use.
In the following example, only requests starting with /users/ and followed by a numeric value
will match. In other words, /users/1 and /users/42 would match,
but not /users/abc:
router.GET("/users/${param1:\\d+}")
Finally, a dynamic parameter can also contain what we call a pattern alias.
Instead of having to type the pattern each time you need it, you can
use an alias for it. The syntax is ${paramName:<alias>}.
Spincast has some built-in pattern aliases:
// Matches only alpha characters (A to Z)
router.GET("/${param1:<A>}")
// Matches only numeric characters (0 to 9)
router.GET("/${param1:<N>}")
// Matches only alphanumeric characters (A to Z and 0 to 9)
router.GET("/${param1:<AN>}")
// Matches only alpha characters (A to Z) + "-" and "_"
router.GET("/${param1:<A+>}")
// Matches only numeric characters (0 to 9) + "-" and "_"
router.GET("/${param1:<N+>}")
// Matches only alphanumeric characters (A to Z and 0 to 9) + "-" and "_"
router.GET("/${param1:<AN+>}")
You can also specify your own aliases.
You do that using the addRouteParamPatternAlias(...) method
on the router. For example :
// We register a new alias
router.addRouteParamPatternAlias("USERS", "user|users|usr");
// And we use it!
router.GET("/${param1:<USERS>}/${userId}")
This pattern would match /user/123, /users/123
and /usr/123, but not /nope/123.
{#==========================================
Section "routing / Routing Types"
==========================================#}
Routing Types
You can specify for which routing type your route should be
considered. The three routing types are:
-
Found
-
Not Found
-
Exception
If no route type is specified when creating a route, Found is used,
so the route won't be considered during a Not Found or Exception routing process.
// Only considered during the "Found" routing process
// (this is the default, and it is not required to specify it)
router.GET("/").found()
// Only considered during a "Not Found" routing process
router.GET("/").notFound()
// Only considered during an "Exception" routing process
router.GET("/").exception()
// Always considered!
router.GET("/").allRoutingTypes()
// Only considered during a "Not Found"
// or an "Exception" routing process
router.GET("/").notFound().exception()
There are also some shortcuts to quickly define a Not Found
or an Exception route :
// Synonym of :
// router.ALL("/*{path}").notFound().save(handler)
router.notFound(handler);
// Synonym of :
// router.ALL("/*{path}").exception().save(handler)
router.exception(handler);
{#==========================================
Section "routing / Content-Types"
==========================================#}
Content-Types
You can also specify if the route should be used only when some
particular Content-Types are specified as being
accepted by the request.
For example, you may have a route handler for a /users URL that will
produce Json, and a another handler that will produce
XML, for the very same URL.
If no Content-Type is specified when building a route,
the route doesn't care about it.
// Only considered if the request accepts HTML
router.GET("/users").html() ...
// Only considered if the request accepts Json
router.GET("/users").json() ...
// Only considered if the request accepts XML
router.GET("/users").xml() ...
// Only considered if the request accepts HTML or plain text
router.GET("/users").accept(ContentTypeDefaults.HTML, ContentTypeDefaults.TEXT) ...
// Only considered if the request accepts PDF
router.GET("/users").acceptAsString("application/pdf") ...
{#==========================================
Section "routing / HTTP Caching"
==========================================#}
HTTP Caching route options
Later in the documentation, you will find a dedicated HTTP Caching section,
containing all the information about HTTP caching using Spincast. Here, we're only going to list the options
that can be used when building a route.
For both regular routes and static resources routes, you can use the
cache(...) method to send appropriate cache headers
to the client :
// Default cache headers will be sent (the default is configurable).
router.GET("/test").cache().save(handler);
// Sends headers so the client caches the resource for 60 seconds.
router.GET("/test").cache(60).save(handler);
// Sends headers so the client caches the resource for 60 seconds.
// Also specifies that this cache should be *private*.
// See : https://goo.gl/VotTdD
router.GET("/test").cache(60, true).save(handler);
// Sends headers so the client caches the resource for 60 seconds,
// but so a CDN (proxy) caches it for 30 seconds only.
router.GET("/test").cache(60, false, 30).save(handler);
// The "cache()" method is available on static resources routes too!
router.file("/favicon.ico").cache(86400).classpath("/public/favicon.ico").save();
On a standard route, it is also possible to use the noCache()
method to send headers asking the client to disable any caching
of the associated resource :
router.GET("/test").noCache().save(handler);
Again, make sure you read the dedicated HTTP Caching section for
the full documentation.
{#==========================================
Section "routing / Saving"
==========================================#}
Saving the route
When your route is complete, you save it to the router by passing
the route handler to use to the save(...) method.
Using Java 8 you can use a method handler or a lambda:
// A method handler
router.GET("/").save(controller::indexHandler);
// A lambda expression
router.GET("/").save(context -> controller.indexHandler(context));
Using Java 7, you have to declare an inline handler :
router.GET("/").save(new IAppHandler() {
@Override
public void handle(IAppRequestContext context) {
controller.indexHandler(context)
}
});
A complete example could be :
// Will be run for a GET request accepting Json or XML, when the
// user is not found.
router.GET("/users/${userId}").notFound().json().xml().save(usersController::userNotFound);
{#==========================================
Section "routing / Filters"
==========================================#}
Filters
Filters are standard route handlers, with the exception
that they run before
or after the main handler.
You can declare a filter exactly like you would declare a
standard route, but using the extra "position" property. The
filter's position indicate when this filter should be run. The lower
the position number is, the sooner the filter will run. The
main handlers are considered as having a position of 0, so filters
with a position below 0 are before filters, and those with a
position greater than 0 are after filters.
// This filter is a "before" filter and
// will be run first.
router.GET("/").pos(-3).save(ctl::filter1);
// This filter is a "before" filter and
// will be run second.
router.GET("/").pos(-1).save(ctl::filter2);
// This is not a filter, it's a main handler
// and the ".pos(0)" part is superfluous!
router.GET("/").pos(0).save(ctl::mainHandler);
// This filter is an "after" filter
router.GET("/").pos(100).save(ctl::filter3);
There also are shortcuts that you can use if you don't need to
specify fine grain information and just want to add a quick before
or after filter:
// Will be applied to any request, at position "-10".
//
// Synonym of :
// router.ALL("/*{path}").pos(-10).save(ctl::filter)
router.before().save(ctl::filter);
// Will be applied to any request starting with "/users",
// at position "-10".
//
// Synonym of :
// router.ALL("/users").pos(-10).save(ctl::filter)
router.before("/users").save(ctl::filter);
// Will be applied to any request, at position "10".
//
// Synonym of :
// router.ALL("/*{path}").pos(10)
router.after().save(ctl::filter);
// This actually generates two filters.
// Will be applied to any request, both at
// position "-10" and position "10".
router.beforeAndAfter().save(ctl::filter);
A route can disable a filter that would otherwise be run for it
using the skip(...) method. The filter must have
an "id" so it can be targetted, though.
// This "myFilter" filter will be applied on any route...
router.before().id("myFilter").save(ctl::filter);
// ... except on this one!
router.GET("/test").skip("myFilter").save(ctl::testHandler);
You can also add inline filters to be run only on
on a specific route:
// This route contains four handlers:
// two before filters, the main handler, and one after filter.
router.GET("/users/${userId}")
.before(ctl::beforeFilter1)
.before(ctl::beforeFilter2)
.after(ctl::afterFilter)
.save(ctl::mainHandler);
Inline filters don't have a position: they are
run in the order they are declared. Also, they always run just before
or just after the main handler. So they are always run closer to the
main handler than the global filters.
The inline filters have access to
the same request properties than their associated main handler: same
path parameters, same queryString parameters, etc.
Finally, note that a filter can decide if it needs to run or not,
at runtime. Using the routing() add-on,
a filter can know if the current route type is Found, Not Found
or Exception, and then decide to run or not! For example:
public void myFilterHandler(IAppRequestContext context) {
// The current route is an "Exception" one,
// we don't apply the filter.
if(context.routing().isExceptionRoute()) {
return;
}
// Or :
// The current route is a "Not Found" one,
// we don't apply the filter.
if(context.routing().isNotFoundRoute()) {
return;
}
// Run the filter...
}
{#==========================================
Section "routing / WebSockets"
==========================================#}
WebSockets
Because of the particular nature of WebSockets, we decided
to aggregate all the documentation about them
in a dedicated WebSockets section.
Make sure you read that section to learn everything about WebSockets and the creation of their
routes!
But, if all you need is a quick WebSocket route definition example, here's one. You use
the router object to start the creation, as for a standard HTTP route:
router.websocket("/chat").before(someFilter).save(chatWebsocketController);
{#==========================================
Section "routing / Static resources"
==========================================#}
Static resources
You can tell Spincast that some files and directories are
static resources.
Doing so, the resources will be served by the HTTP server directly,
and requests for them won't even reach the framework.
Those static resources can be on the classpath or on the file system. For example:
// Will serve all requests starting with the
// URL "/public" with files under the classpath
// directory "/public_files".
router.dir("/public").classpath("/public_files").save();
// Uses an absolute path on the file system directory
// as a static resources root.
router.dir("/public").pathAbsolute("/user/www/myprojet/public_files").save();
// Uses a path relative to the Spincast writable directory,
// on the file system, as a static resources root.
router.dir("/public").pathRelative("/public_files").save();
// Will serve the requests for a specific file,
// here "/favicon.ico", with a file from the classpath.
router.file("/favicon.ico").classpath("/public/favicon.ico").save();
// Uses an absolute path on the file system file
// as the static resource target.
router.file("/favicon.ico").pathAbsolute("/user/www/myprojet/public_files/favicon.ico").save();
// Uses a path relative to the Spincast writable directory,
// on the file system, as the static resource target.
router.file("/favicon.ico").pathRelative("/public_files/favicon.ico").save();
Be aware that since requests for static resources don't reach the framework, filters don't apply to them! Even a
"catch all" filter such as router.ALL("/*{path}").pos(-1).save(handler)
won't be applied.
For the same reason, there are some limitations about the dynamic parameters the
route of a static resource can contain. For plain static resource (no generator), only a dir
can contains a dynamic parameter and it can only be
a splat parameter at the very end of its route. For example :
-
This is valid :
dir(/one/two/*{remaining})
-
This is invalid :
dir(/one/*{remaining}/two)
-
This is invalid :
dir(/${param})
-
This is invalid :
file(/one/two/*{remaining})
Note that a file resource has priority over of a dir resource since
it is more precise. For example, let's take those two routes :
router.dir("/public").cache(3600).classpath("/public_files").save();
router.file("/public/test.txt").cache(60).classpath("/public_files/test.txt").save();
Even if the "test.txt" file is under the "/public" directory,
a cache of 60 seconds will be applied, not 3600.
Finally, note that the static resource routes have precedence over all other
routes, so if you declare router.dir("/", "/public") for example, then
no route at all would ever reach the framework, everything would be considered
as static. This is, by the way, a quick way to serve a static website using Spincast!
{#==========================================
Section "routing / Dynamic resources"
==========================================#}
Dynamic resources
A variation on static resources is what we call dynamic resources. When
declaring a static resource, you can provide a generator. The generator is a
standard route handler that will receive
the request if the static resource is not found. The job of this generator is to
generate the missing resource and to return it as the response. Spincast will automatically
intercept the response body, save it and, next time this static resource is requested,
it will be served directly by the HTTP server!
router.dir("/images/tags/${tagId}").pathAbsolute("/generated_tags")
.save(new IDefaultHandler() {
@Override
public void handle(IDefaultRequestContext context) {
String tagId = context.request().getPathParam("tagId");
byte[] tagBytes = generateTagImage(tagId);
context.response().sendBytes(tagBytes, "image/png");
}
});
Or, for a single file :
router.file("/css/generated.css")
.pathAbsolute("/user/www/myprojet/public_files/css/generated.css")
.save(new IDefaultHandler() {
@Override
public void handle(IDefaultRequestContext context) {
String css = generateCssFile(context.request().getRequestPath());
context.response().sendCharacters(css, "text/css");
}
});
Dynamic resources only work when .pathAbsolute(...) or
.pathRelative(...) are used, not
.classpath(...), and Spincast must have write permissions on the
target directory.
Like for plain static resources, the route of a dynamic resource can contain
dynamic parameters, but the rules are a little bit different :
-
The route of a
file dynamic resource can contain dynamic parameters but no splat parameter.
For example :
router.file("/test/${fileName}").pathAbsolute("/usr/someDir/${fileName}").save(generator);
-
The route of a
dir dynamic resource can only contain a splat parameter, at its end.
For example :
router.dir("/generated/*{splat}").pathRelative("/someDir").save(generator);
-
The target path of a
file dynamic resource can use the dynamic parameter
from the associated route. For example this is valid :
router.file("/test/${fileName}").pathAbsolute("/usr/someDir/${fileName}").save(generator);
-
The target path of a
dir dynamic resource can not use the splat parameter
from the associated route. For example this is not valid :
// This is not valid
router.dir("/generated/*{splat}").pathAbsolute("/someDir/*{splat}").save(generator);
{#==========================================
Section "routing / Cors"
==========================================#}
Cors
Cors, Cross-Origin Resource Sharing, allows you to
specify some resources in your application that can be accessed by a browser from another domain. For example,
let's say your Spincast application runs on domain http://www.example1.com, and you want to allow another
site, http://www.example2.com, to access some of your APIs, or some of your files. By default,
browsers don't allow such cross domains requests. You have to enable cors
for them to work.
There is a provided filter to enable cors on regular routes. Since filters
are not applied to static resources, there is also a special configuration to add
cors to those:
// Enable cors for every routes of your application,
// except for static resources.
getRouter().cors();
// Enable cors for all routes matching the specified path,
// except for static resources.
getRouter().cors("/api/*{path}");
// Enable cors on a static resource directory.
getRouter().dir("/public").classpath("/public_files")
.cors().save();
// Enable cors on a static resource file.
getRouter().file("/public/test.txt").classpath("/public_files/test.txt")
.cors().save();
There are some parameters available when configuring cors(). The only differences between
available parameters for regular routes and for static resources, it that you can't specify
the HTTP methods for
static resources: they are always GET, HEAD and OPTIONS.
// The default :
// - Allows any origins (domains)
// - Allows cookies
// - Allows any HTTP methods (except for static resources,
// which allow GET, HEAD and OPTIONS only)
// - Allows any headers to be sent by the browser
// - A Max-Age of 24h is specified for caching purposes
//
// But :
// - Only basic headers are allowed to be read from the browser.
.cors()
// Like the default, but also allows some extra headers
// to be read from the browser.
.cors(Sets.newHashSet("*"),
Sets.newHashSet("extra-header-1", "extra-header-2"))
// Only allows the domains "http://example2.com" and "https://example3.com" to access
// your APIs.
.cors(Sets.newHashSet("http://example2.com", "https://example3.com"))
// Like the default, but only allows the specified extra headers
// to be sent by the browser.
.cors(Sets.newHashSet("*"),
null,
Sets.newHashSet("extra-header-1", "extra-header-2"))
// Like the default, but doesn't allow cookies.
.cors(Sets.newHashSet("*"),
null,
Sets.newHashSet("*"),
false)
// Like the default, but only allows the extra "PUT" method
// in addition to GET, POST, HEAD and OPTIONS
// (which are always allowed). Not applicable to
// static resources.
.cors(Sets.newHashSet("*"),
null,
Sets.newHashSet("*"),
true,
Sets.newHashSet(HttpMethod.PUT))
// Like the default, but specifies a Max-Age of 60 seconds
// instead of 24 hours.
.cors(Sets.newHashSet("*"),
null,
Sets.newHashSet("*"),
true,
Sets.newHashSet(HttpMethod.values()),
60)
{#==========================================
Section "routing / HTTP Authentication"
==========================================#}
HTTP Authentication
If you need a section of your application to be protected so only privileged users
can access it, you can use
HTTP Authentication. With HTTP Authentication, the server itself
manages the authentication, so no request will ever reach the
framework or a protected resource unless the correct username/password is given.
Here's a
Protected page example. To access it, you have
to provide the correct username/password combination: Stromgol/Laroche.
You enable HTTP Authentication by specifying the base
path of the URL associated with the section to protect, and a realm name
(The term "realm" is a synonym for "a protected section"). Note that
the name of the realm will be
displayed to the user when the server asks for the username/password.
A realm
can be added using the server directly, but the preferred method is to use
the httpAuth(...) method on the router:
router.httpAuth("/admin", "Protected Admin Section");
Now, any request for "/admin" or any URL under it ("/admin/files/secret.txt", for example)
will be intercepted and a username/password will be required to access the requested page or resource.
Of course, you also have to associate some username/password combinations to a realm, otherwise no one
will have access to the protected resources. You add such credentials using the server
object itself. This can
be done next to the routes definitions, or in a dedicated class, it doesn't matter. Just make sure you use the
correct realm name:
getServer().addHttpAuthentication("Protected Admin Section", "Stromgol", "Laroche")
Have a look at the server's
Javadoc
to see all methods related to HTTP Authentication.
Finally, note that HTTP Authentication is a quick and easy way to protect a section
of your application but, if you need more control, a form based authentication may be
preferred.
{#==========================================
Section "routing / Redirection rules"
==========================================#}
Redirection rules
Using the router, you can specify that a route must be redirected another one. This is
useful, for example, when you change the URL of a resource but don't want the
existing links to break.
To add a redirection rule, simply use the redirect(...) method on the router:
router.redirect("/the-original-route").to("/the-new-route");
Here are the available options when creating such redirection rule:
-
redirect(originalRoute): starts the creation of the direction rule
by specifying the original route.
-
permanently(): specify that the redirection must be permanent (301). This
is the default and it is not required to specify it.
-
temporarily(): specify that the redirection must be temporary (302).
-
to(newRoute): saves the redirection rule by specifying the new route.
Note that if the original route contains dynamic parameters
(splat parameters included), you can use those in the definition of the new route.
For example, this redirection rule will redirect
"/books/42" to "/catalog/item/42":
router.redirect("/books/${bookId}").to("/catalog/item/${bookId}");
Those dynamic parameters can be used anywhere in the new route definition, not
just as full tokens! For example, this redirection rule will redirect
"/types/books/42" to "/catalog-books/42":
router.redirect("/types/${typeId}/${itemId}").to("/catalog-${typeId}/${itemId}");
Finally, note that a redirection rule is implemented as a "before" filter and
must, in general, be the very first one to run. Its position is configurable using
ISpincastRouterConfig and its default value is -1000.
When this filter runs, any other filters or route handlers are skipped,
and the redirection header is returned to the client immediately.
{#==========================================
Section "routing / Special exceptions"
==========================================#}
Special exceptions
You can throw any exception and this is going to trigger the Exception routing process.
But some exceptions are provided by Spincast and have a special behavior.
-
RedirectException: This exception will stop
the current routing process (as any other exceptions),
will send a redirection header to the user and will end the exchange.
public void myHandler(IAppRequestContext context) {
throw new RedirectException("/new-url", true);
}
Note that you can also redirect the user using
context.response().redirect("/new-url", true), instead of throwing a
RedirectException exception. The
difference is that in the exception version, the remaining handlers
are not run and the redirection headers are sent
immediately.
-
ForwardRouteException: This exception allows
you to change the route, to forward the request
to another route without doing any client side redirection. A new
routing process will start, but this time using the
specified url instead of the url from the original request.
public void myHandler(IAppRequestContext context) {
throw new ForwardRouteException("/new-url");
}
-
SkipRemainingHandlersException: This exception stops
the current routing process, but without starting any new one. In other words, the
remaining handlers won't be run, and the response will be
sent as is, without any more modification.
public void myHandler(IAppRequestContext context) {
context.response().sendPlainText("I'm the last thing sent!");
throw new SkipRemainingHandlersException();
}
-
CustomStatusCodeException: This exception allows you to
set an HTTP status code to return. Then, your custom
Exception handler can check this code and display something
accordingly. Also, in case you use the provided Exception handler, at least
you have control over the status code sent to the user.
public void myHandler(IAppRequestContext context) {
throw new CustomStatusCodeException("Forbidden!", HttpStatus.SC_FORBIDDEN);
}
-
PublicException: This exception allows you to
specify a message that will be displayed to the user. This is
mainly useful if you use the provided Exception handler since,
in a custom handler, you would have full control over what to send
as a response anyway...
public void myHandler(IAppRequestContext context) {
throw new PublicException("You don't have the right to access this page",
HttpStatus.SC_FORBIDDEN);
}
{#==========================================
Section "routing / Router is dynamic"
==========================================#}
The router is dynamic
Note that the router is dynamic, which means you can always add new routes to it. This
also means you don't have to define all your routes at the same place, you can let the
controllers (or even the plugins) define their own routes!
For example:
public class UserController implements IUserController {
@Inject
protected void init(IDefaultRouter router) {
addRoutes(router);
}
protected void addRoutes(IDefaultRouter router) {
router.GET("/users/${userId}").save(this::getUser);
router.POST("/users").save(this::addUser);
router.DELETE("/users/${userId}").save(this::deleteUser);
}
@Override
public void getUser(IDefaultRequestContext context) {
//...
}
@Override
public void addUser(IDefaultRequestContext context) {
//...
}
@Override
public void deleteUser(IDefaultRequestContext context) {
//...
}
}
Explanation :
-
3-6 : An
init() method
receives the router.
-
8-12 : The controller adds its own routes. Here, the
UserController is responsible to add routes related to users.