Routes
Routes is a Java library for mapping HTTP requests to Java object methods. Routes runs within a Java servlet container and is an alternative to processing HTTP requests with the Servlet API. Routes makes it easy to turn this:
public class ApiRoutes
{
public String getUsers(HttpServletRequest request)
{
...
request.setAttribute("users", users);
return "users.jsp";
}
}
into an object that accepts HTTP GET requests at the path /api/users and renders the loaded users with the JSP file /WEB-INF/jsps/users.jsp.
Getting Started
Direct Download
You can download routes-1.3.jar directly and place in your project.
Using Maven
Add the following dependency into your Maven project:
<dependency>
<groupId>org.baswell</groupId>
<artifactId>routes</artifactId>
<version>1.3</version>
</dependency>
Dependencies
Routes runs within a Java Servlet container at API 2.4 or greater. Routes has no other external dependencies.
Servlet Container Configuration
There are three different ways Routes can be used within a Servlet container.
Routes Servlet
The RoutesServlet can be used to map HTTP requests to routes. Any HTTP request the RoutesServlet
does not find a matching route for is returned a 404 (HttpServletResponse.setStatus(404)
).
<servlet>
<servlet-name>RoutesServlet</servlet-name>
<servlet-class>org.baswell.routes.RoutesServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>RoutesServlet</servlet-name>
<url-pattern>/routes/*</url-pattern>
</servlet-mapping>
Routes Filter
The RoutesFilter may work better when Routes is not the only means of serving content for your application.
<filter>
<filter-name>RoutesFilter</filter-name>
<filter-class>org.baswell.routes.RoutesFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>RoutesFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
This filter should be placed last in your filter chain because chain processing will end here when a route match is found (chain.doFilter
is not called). If no match is found, then chain.doFilter
will be called so further processing can occur. This allows, for example, your application to serve up file resources (ex. html, jsp) directly as long as none of your routes match the file resource URL.
In addition to the filter-mapping
configuration, you can control which HTTP requests are candidates for routes with the ONLY
and EXCEPT
filter parameters (this can improve performance when it's known that certain HTTP paths won't map to routes).
<init-param>
<param-name>ONLY</param-name>
<param-value>/api/.*,/data/.*</param-value>
</init-param>
This parameter is a comma delimited list of Java regular expressions. In this example all HTTP requests with URL paths that start with /api/
or /data/
will be candidates for routes (as in url-pattern
the context path should be left off the expression).
<init-param>
<param-name>EXCEPT</param-name>
<param-value>.*\.html$,.*\.jsp$</param-value>
</init-param>
In this example all HTTP requests except URL paths that end with with .html
or .jsp
will be candidates for routes. Both ONLY
and EXCEPT
must be a list of valid Java regular expressions or an exception will be thrown when the RoutesFilter
is initialized.
Routes Engine
Both RoutesServlet
and RoutesFilter
use RoutesEngine
to match HTTP requests to routes. It can be used directly to manually handle when routes should be used to process HTTP requests.
...
RoutesEngine routesEngine = new RoutesEngine();
...
if (routesEngine.process(servletRequest, servletResponse))
{
// Request was processed, response has already been sent
}
else
{
// Request was not processed, do something with the response
}
...
The Routing Table
The RoutingTable is where you tell Routes which of your classes are candidates for HTTP requests. After your objects have been added call the build()
method to build the routing table. This method will throw a RoutesException
if you have any invalid route configuration.
RoutingTable routingTable = new RoutingTable();
routingTable.add(new IndexRoutes(), new HomeRoutes(), new HelpRoutes()).build();
The RoutingTable
should be treated as a singleton in your application. A static attribute will be set by the RoutingTable
when constructed that the RoutingServlet
and RoutingFilter
will use when called.
You can either add your route class objects or instance objects to the RoutingTable
.
// Both of these are acceptable
routingTable.add(IndexRoutes.class);
routingTable.add(new HomeRoutes());
If you add a class object then Routes will instantiate a new instance of this class (using the default constructor) for each matched request (the instantiation strategy can be controlled by using RouteInstancePool). If you add an instance object then that instance object will be used for every matched HTTP request. This means the route class must be thread safe.
If you are using Spring for dependency injection you can configure the RoutingTable
using the setRoutes()
method as:
<bean id="routingTable" class="org.baswell.routes.RoutingTable" init-method="build">
<property name="routes">
<list>
<ref bean="loginRoutes"/>
<ref bean="homeRoutes"/>
<ref bean="helpRoutes"/>
</list>
</property>
</bean>
Routes By Example
Routes imposes no class hierarchies or interfaces on your classes. There are two ways to tell Routes how your Java objects are matched to HTTP requests, by convention or by using annotations. If your class has no Routes annotations then all public methods are candidates to being matched to incoming HTTP requests (only the immediate class, public methods of any super classes are not candidates). Routes use a convention for converting unannotated classes and methods to HTTP candidates that you can override using RouteByConvention.
The annotations Routes (class level) and Route (method level) provide full control over how methods are matched to HTTP requests. By default if your class has at least one of these annotations then only methods with the Route
annotations can be matched to HTTP requests (public, unannotated methods will not be candidates). This can be override for the entire class with Routes.routeUnannotatedPublicMethods or globally with RoutesConfiguration.routeUnannotatedPublicMethods.
Example One: By Convention
public class LoginRoutes
{
public String get(HttpServletRequest request)
{
...
return "login.jsp";
}
public void post(HttpServletRequest request, HttpServletResponse response)
{...}
public String getForgotPassword(HttpServletRequest request)
{
...
return "login/forgotpassword.jsp";
}
public String postForgotPassword(HttpServletRequest request)
{
...
throw new RedirectTo("/login");
}
}
HTTP Request | Method Called |
---|---|
GET /login HTTP/1.1 |
get(request) |
By default the class name is used to form the first url segment, in this case /login. If the class name ends in Routes, Route, Controller, or Handler then this part of the class name will be removed from the path segment.Method names that just contain HTTP methods (ex. get, post) don't add anything to the matched path. The JSP file at /WEB-INF/jsps/login.jsp will be rendered to the user. You can change the root JSP directory with RoutesConfiguration.rootForwardPath | |
POST /login HTTP/1.1 |
post(request, response) |
Since this method does not return anything, it must handle the content sent back to the user with the HttpServletResponse object. |
|
GET /login/forgotpassword HTTP/1.1 |
getForgotPassword(request) |
The remaining method name after all HTTP methods are removed from the begging forms the next url segment to match. The JSP file at _/WEB-INF/jsps/login.jsp_ will be rendered to the user. | |
/login/ForGotpasSworD HTTP/1.1 |
getForgotPassword(request) |
By default matching in Routes for paths and parameters is case insensitive. This can be changed with RoutesConfiguration.caseInsensitve. | |
POST /login/forgotpassword HTTP/1.1 |
postForgotPassword(request) |
You can use the helper class RedirectTo to redirect the client to another page. Note that when this exception is thrown any @AfterRoute methods will not be called. If you want the after route callbacks to still take place you can return a string starting with the key redirect: such as redirect:/login or call HttpServletResponse.sendRedirect directly. | |
PUT /login HTTP/1.1 |
404 |
Would need a put() method defined for this request to be matched. You can also combine HTTP methods together so for example the method postPut() would be called for both POST and PUT requests with the path /login |
Example Two: Using Annotations
@Routes(forwardPath="login")
public class MyLoginRoutes
{
@Route("/login")
public String getLoginPage(HttpServletRequest request)
{
...
return "login.jsp";
}
@Route(value = "/login", respondsToMethods = {HttpMethod.POST, HttpMethod.PUT})
public void doLogin(HttpServletRequest request, HttpServletResponse response)
{...}
@Route(value = "/login/forgotpassword", respondsToMethods = {HttpMethod.GET})
public String showForgotPassword(HttpServletRequest request)
{
...
return "forgotpassword.jsp";
}
public void postForgotPassword(HttpServletRequest request)
{
...
throw new RedirectTo("/login");
}
}
HTTP Request | Method Called |
---|---|
GET /login HTTP/1.1 |
get(request) |
If no root level path annotation is specified (Routes.value) then the path specified in Root.value will form the full match path. Since no Route.respondsToMethod was specified in the annotation the accepted HTTP methods are taken from the method name as in the convention based approach. | |
POST /login HTTP/1.1 |
doLogin(request, response) |
PUT /login HTTP/1.1 |
doLogin(request, response) |
Since both Routes.value and Route.respondsToMethod are specified the method name has no impact on which HTTP requests are matched. | |
GET /login/forgotpassword HTTP/1.1 |
showForgotPassword(request) |
The JSP file at /WEB-INF/jsps/login/forgotpassword.jsp will be rendered to the user since Routes.forwardPath is specified. If the forward path does not begin with a / then the value is appended to RoutesConfiguration.rootForwardPath. If the forward path was /login then the the JSP file /login/forgotpassword.jsp would be rendered. | |
POST /forgotpassword HTTP/1.1 |
404 |
Since postForgotPassword is not annotated, it is not a candidate for HTTP requests. This can be overridden using Routes.routeUnannotatedPublicMethods or RoutesConfiguration.routeUnannotatedPublicMethods. |
|
Example Three: Mixed
abstract public class BaseRoutes
{
public String getSomeResource(HttpServletRequest request)
{...}
@Route(value="faq")
public String getFAQ(HttpServletRequest request)
{...}
}
@Routes(value="/login", routeUnannotatedPublicMethods=true, forwardPath="/login")
public class MyLoginRoutes extends BaseRoutes
{
@Route
public String getLoginPage(HttpServletRequest request)
{
...
return "login.jsp";
}
@Route(value = "/forgotpassword", respondsToMethods = {HttpMethod.GET})
public String showForgotPassword(HttpServletRequest request)
{
...
return "forgotpassword.jsp";
}
public String postForgotPassword(HttpServletRequest request)
{
...
return "redirect:/login";
}
}
HTTP Request | Method Called |
---|---|
GET /login HTTP/1.1 |
get(request) |
The root level path match is specified as /login in (Routes.value) since @Route doesn't specify a value, this is the full path matched for this method. | |
GET /login/forgotpassword HTTP/1.1 |
showForgotPassword(request) |
The path specified @Route is appended onto the path specified in @Routes to form the full match path /login/forgotpassword. The JSP file at /login/forgotpassword.jsp will be rendered to the user since @Routes.forwardPath starts with a /. | |
POST /login/forgotpassword HTTP/1.1 |
postForgotPassword(request) |
Since @Routes.routeUnannotatedPublicMethods is true, this public method is a candidate for HTTP requests. Both the path and accepted HTTP methods are taken by convention from the method name. Since @Routes.value is specified the path taken from this method name is appended to this value to form the full match path (/login/forgotpassword). If the returned string starts with redirect: then a redirect (302) will be returned to the client with text after the redirect: key sent as the URL to redirect to. | |
GET /login/someresource HTTP/1.1 |
404 |
GET /someresource HTTP/1.1 |
404 |
@Routes.routeUnannotatedPublicMethods only applies to the immediate class. Public, unannotated methods from super classes are not HTTP request candidates. | |
GET /login/faq HTTP/1.1 |
getFAQ(request) |
Annotated super class methods are candidates for HTTP requests. If BaseRoutes declared a @Routes it would be ignored, @Routes is only used if present on the immediate class (MyLoginRoutes). |
Example Four: Parameter Matching
The previous examples use path matching exclusively to determine how HTTP requests are mapped. Request parameters can also be used as a criteria for method matching.
@Routes("/api")
public class ApiRoutes
{
@Route("/users?expired=true")
public String getExpiredUsers(HttpServletRequest request)
{
...
return "expiredUsers.jsp";
}
@Route("/users?expired=false", defaultParameters="expired=false")
public String getActiveUsers(HttpServletRequest request)
{
...
return "activeUsers.jsp";
}
@Route("/users?expired=false&admin=true", defaultParameters="expired=false")
public String getActiveAdministrators(HttpServletRequest request)
{
...
return "activeAdministrators.jsp";
}
@Route("/users?expired=false&admin=true", defaultParameters="expired=false")
public String postActiveAdministrators(HttpServletRequest request)
{
...
return "activeAdministrators.jsp";
}
}
HTTP Request | Method Called |
---|---|
GET /api/users?expired=true&mediaType=xml HTTP/1.1 |
getExpiredUsers(request) |
Parameter matching is specified by adding a query string to the end @Route.value. All parameters specified in this query must be present and equal to the value specified for a match to be made on the method. Any additional parameters provided in the request that aren't specified in the query @Route.value query string will be ignored. | |
GET /api/users?expired=TRUE HTTP/1.1 |
getExpiredUsers(request) |
By default matching in Routes for paths and parameters is case insensitive. This can be changed in RoutesConfiguration.caseInsensitve. | |
GET /api/users?mediaType=json HTTP/1.1 |
getActiveUsers(request) |
If default values are specified for parameters using @Route.defaultParameters then the default parameter value will be used for the match comparison if the parameter is not provided in the request. | |
GET /api/users?expired=false&admin=true HTTP/1.1 |
getActiveAdministrators( request) |
Multiple parameters are delimited with &. Route methods with more parameter checks will be checked first which is why getActiveAdministrators is called here instead of getActiveUsers even though getActiveUsers is listed first. |
|
POST /api/users HTTP/1.1 Content-Type: application/x-www-form-urlencoded |
postActiveAdministrators( request) |
Parameters can be specified from the query string or from the body content when form encoded. |
Example Five: Pattern Matching
Routes supports the use of regular expressions for url path and parameter matching. To use a regular expression place the value between curly brackets {}
such as /users/{\d+}
. The value between these brackets must be a valid Java regular expressions with the following exceptions:
{*}
Matches any value. Shortcut for{.*}
.{**}
A double wildcard can only be used for url paths. It matches one or more url path segments of any value.{}
An empty set of brackets indicates that the regular expression is specified by a method parameter (next section).
The value matched against a path or parameter pattern can be passed in as a method parameter when the route method is invoked. The following basic Java types are supported for path or parameter values.
- String
- Character
- char
- Boolean
- boolean
- Byte
- byte
- Short
- short
- Integer
- int
- Long
- long
- Float
- float
- Double
- double
Routes uses the standard parse methods (Boolean.parseBoolean
, Integer.parseInt
, Float.parseFlow
) to coerce a pattern value into a method parameter. A String
method parameter type will receive the matched value unmodified and a Character
parameter will receive the first character of the matched value.
Routes matches patterns to method parameters by order. So for example in the route criteria /users/{\d+}/profile/{*}
the first pattern {\d+}
will map to the first method parameter that is one of the the types above. The second pattern {*}
will match to the second method parameter of those types. You don't have to specify method parameters for each pattern but because they are assigned in order, if for example you want the value for the second pattern assigned to a method parameter you have to map the first pattern to a method parameter too.
@Routes("/users")
public class UserRoutes
{
@Route("{\d+}")
public String getUserByIdInPath(int userId, HttpServletRequest request)
{...}
@Route("?id={\d+}")
public String getUserByIdInParamter(int userId, HttpServletRequest request)
{...}
@Route("/{*}")
public String getUserByName(String userName, HttpServletRequest request)
{...}
@Route("/{\d+}/{.+-.+}")
public String getShowUserProfileInPath(int userId, HttpServletRequest request)
{...}
@Route("/{\d+}?profileName={*}")
public String getShowUserProfileInParameter(int userId,
HttpServletRequest request,
String profileName)
{...}
@Route("/{*}/changepassword", defaultParameters="expired=false")
public String getChangePasswordById(int userId, HttpServletRequest request)
{...}
@Route("/{**}")
public String getCustomUsersNotFoundPage(HttpServletRequest request)
{...}
}
HTTP Request | Method Called |
---|---|
GET /users/23 HTTP/1.1 |
getUserByIdInPath(23, request) |
The regular expression {\d+} will match any numeric value. The numeric value matched will be provided in the userId parameter when invoked. If this method had no Integer parameter the HTTP request would still be matched and the method would be invoked without the value being provided. | |
GET /users?id=23 HTTP/1.1 |
getUserByIdInParamter(23, request) |
Regular expression for parameters work the same as paths. | |
GET /users?id=baswerc HTTP/1.1 |
404 |
The HTTP request does match any Routes because baswerc does not match the pattern {\d+}. | |
GET /users/baswerc HTTP/1.1 |
getUserByName("baswerc", request) |
GET /users/23A HTTP/1.1 |
getUserByName("23A", request) |
GET /users HTTP/1.1 |
404 |
GET /users/ HTTP/1.1 |
404 |
The wildcard pattern {*} will match against any value. In this example any value in the segment after /users that is not numeric will be matched to this method (since the numeric pattern method getUsersByIdInPath comes first in the class declaration it takes precedence). Note a wildcard declaration in a path or parameter will match against any value but the value must present (empty is not a match. | |
GET /users/23/basic-blue HTTP/1.1 |
getShowUserProfileInPath(23, request) |
Pattern values are supplied as method parameters in the order they were specified. You don't have to specify method parameters for all patterns in the route. | |
GET /users/23?profile=basic-blue HTTP/1.1 |
getShowUserProfileInParameter(23, request, "profile-basic") |
Pattern value parameters can be intermingled with the other allowed route method parameter types (ex. HttpServletRequest). | |
GET /users/23/changepassword HTTP/1.1 |
getChangePasswordById(23, request) |
GET /users/baswerc/changepassword HTTP/1.1 |
throw new RoutesException() |
Routes does not try to verify that path or parameter patterns are correctly mapped to method parameter types. If a path or parameter value cannot be coerced into method parameter value (ex. NumberFormatException) a RoutesException will be thrown and the request will end in error. Method parameter patterns discussed in this section can help with this. | |
GET /users/something/else/goes/here HTTP/1.1 |
getCustomUsersNotFoundPage(request) |
GET /users/23/abc HTTP/1.1 |
getCustomUsersNotFoundPage(request) |
GET /users?one=1 HTTP/1.1 |
getCustomUsersNotFoundPage(request) |
The double wildcard will match any arbitrary number of path segments that aren't matched by other criteria. Since the double wildcard can consume multiple path segments it will not match to any method parameters. |
Example Six: Method Parameter Patterns
Method parameter patterns are specified by an empty set of curly brackets {}
. The regular expression used for these is inferred from the method parameter this pattern is mapped to. The method parameter must be one of the standard types defined in the previous section (Boolean
, Byte
, Short
, Integer
, Long
, etc.) and it must be present in the method declaration. An exception will be thrown when {}
is used in the route criteria and there is no matching method parameter. For example the following routes class is invalid.
public class InvalidRoutes
{
// {} matches to the method parameter id
@Route("/valid/{}")
public String getValidRoute(int id, HttpServletRequest request)
{...}
// No matching method parameter to specify what regular expression is used
@Route("/invalid/{}")
public String getInvalidRoute(HttpServletRequest request)
{...}
}
The following will result in a RoutesException being thrown routingTable.add(InvalidRoutes.class).build()
.
@Routes("/users")
public class UserRoutes
{
@Route("{}?showDetails={}")
public String getUserByIdInPath(int userId,
boolean showDetails,
HttpServletRequest request)
{...}
@Route("?id={}")
public String getUserByIdInParamter(int userId, HttpServletRequest request)
{...}
@Route("/{}")
public String getUserByName(String userName, HttpServletRequest request)
{...}
@Route("/{}/{.+-.+}")
public String getShowUserProfileInPath(int userId,
String profile,
HttpServletRequest request)
{...}
@Route("/{}?profileName={}")
public String getShowUserProfileInParameter(int userId,
HttpServletRequest request,
String profileName)
{...}
}
HTTP Request | Method Called |
---|---|
GET /users/23?showDetails=true HTTP/1.1 |
getUserByIdInPath(23, true, request) |
Since userId is of type int , the first pattern used with match any value that can be coerced into a int . For the boolean type showDetails, the pattern used will match any (case insentive) value of true or false . |
|
GET /users?id=23 HTTP/1.1 |
getUserByIdInParamter(23, request) |
GET /users?id=baswerc HTTP/1.1 |
404 |
Method parameter patterns can be used for url path and parameter matching. "baswerc" cannot be coerced into an int so a match is not made. | |
GET /users/baswerc HTTP/1.1 |
getUserByName("baswerc", request) |
String method parameters will match any value. It's the same as using the wildcard pattern {*} . |
|
GET /users/23/basic-blue HTTP/1.1 |
getShowUserProfileInPath(23, "basic-blue", request) |
Method parameter pattern and explicit patterns can be mixed. | |
GET /users/23?profile=basic-blue HTTP/1.1 |
getShowUserProfileInParameter(23, request, "profile-basic") |
Method pattern parameters can be mix in with other allowed parameter types. |
Example Six: Responding To Media Type Requests
An additional, optional criteria that can be placed on Route methods is the MediaType the client is expecting for the response. @Route.responsesToMediaRequests is used to specify which type of media this route method is capable of serving. There are three different ways that Routes determines the type of media the client is expecting in the response. These are listed in the order of precedence.
- The value of the mediaType parameter (if present). For example the url request /api/users?mediaType=xml will map to XML
- File extension (if present) of the URL path. For example a request for /test.pdf will map to PDF.
- The value of the Accept header (if present). For example the header Accept: application/json will map to JSON.
@Routes("/api")
public class APIRoutes
{
@Route(value="/users", respondsToMediaRequests=MediaType.JSON)
public String getUsersInJSON(HttpServletRequest request)
{...}
@Route(value="/users", respondsToMediaRequests=MediaType.XML)
public String getUsersInXML(HttpServletRequest request)
{...}
@Route(value="/users", respondsToMediaRequests={MediaType.HTML, MediaType.PDF})
public String getUsersInHTMLOrPDF(HttpServletRequest request)
{...}
@Route(value="/users/{*}", respondsToMediaRequests=MediaType.PDF)
public String getUsersReportPDF(String reportName, HttpServletRequest request)
{...}
}
HTTP Request | Method Called |
---|---|
GET /users?mediaType=json HTTP/1.1 |
getUsersInJSON(request) |
GET /users HTTP/1.1 Accept: application/json |
getUsersInJSON(request) |
GET /users HTTP/1.1 Accept: application/xhtml+xml |
404 |
The value of the mediaType parameter should be the value of the MediaType enumeration. The value of the Accept header should be one of the MIMETypes. If none of the media criteria matches then the route method will not be matched | |
GET /users?mediaType=xml HTTP/1.1 Accept: application/json |
getUsersInXML(request) |
The mediaType parameter takes precedence over the Accept header. | |
GET /users HTTP/1.1 Accept: application/xhtml+xml |
getUsersInHTMLOrPDF(request) |
GET /users HTTP/1.1 Accept: application/pdf |
getUsersInHTMLOrPDF(request) |
If multiple MediaType are specified in the route critieria then a match will be made if any of the specified MediaType s are requested. |
|
GET /users/active.pdf HTTP/1.1 |
getUsersReportPDF("active.pdf", request) |
GET /users/active.pdf?mediaType=xml HTTP/1.1 |
404 |
The mediaType parameter takes precedence over the file extension. |
Route Method Parameters
Your route methods can have the following parameter types.
- A simple type (
Integer
,boolean
, etc.) mapped to a pattern in the route criteria. You can also define aList
of one of these simple types (ex.List<Short>
) if you expect the same parameter name to be used multiple times. - HttpServletRequest
- HttpServletResponse
- HttpSession
- Request URL
- RequestPath
- RequestParameters
- RequestContent
- RequestedMediaType
- Request parameters as
Map<String, List<String>>
- Request parameters as
Map<String, String>
If a route method declares any other parameter types then a RoutesException will be thrown when the RoutingTable is built.
Request Content
You can process the content a client sends in the request by using RequestContent as a parameter to your route method. When you define the RequestContent
container you should specify the class that the request content will be mapped to.
@Routes("/api")
public class APIRoutes()
{
@Route(expectedRequestMediaType = MediaType.JSON)
public void putUser(RequestContent<User> requestContent) throws IOException
{
User user = requestContent.get();
...
}
}
In this example Routes will try to map the submitted request content to the specified User
class. Routes current supports the following libraries for automatic conversion of request content.
To automatically convert the request content, Routes must determine the content type of request. The request content type is determined in the following order.
- If the type of
RequestContent
requires a certain content type then Routes will use that content type. For exampleRequestContent<org.json.simple.JSONObject>
dictates that the request content is JSON andRequestContent<org.w3c.Node>
dictates the request content is XML. - If Route.expectedRequestMediaType is specified then Routes will use that content type.
- If the Content-Type header is set it the request, Routes will use this content type.
- Routes will (very rudimentary) try to guess the content type from the content submitted.
Response Content
If your routes method returns something other than String
, Routes will try to determine the type of the object and the correction response action based upon that type. For example if the routes method returns a org.json.simple.JSONObject
, Routes will set the content type of the response (if not already set) to application/json and send toJSONString()
as the response of the HTTP request.
For libraries such as GSON and Jackson that operate on plain Java objects, you can give a hint to Routes on how to convert the returned object by using @Route.contentType.
@Routes("/api")
public class APIRoutes()
{
// If GSON is available on your application's classpath, then Routes will send back the
// users list as servletResponse.getWriter().write(new Gson().toJson(users));
//
// Otherwise if Jackson is available on your application's classpath then Routes will
// send back the users list as new
// ObjectMapper().writeValue(servletResponse.getWriter(), users);
@Route(value="/users", contentType = MIMETypes.JSON)
public List<User> getUsers()
{
...
return users;
}
}
If Routes can not figure out how to convert your complex object it will simple call toString()
on the returned object and return that as the content of the HTTP response.
Routes currently supports the following libraries for automatic conversion.
Pre & Post Route Events
You can tell Routes to make calls before and after a route method is invoked using the @BeforeRoute and @AfterRoute annotations. Methods with these annotations must be in the same class hiearchy as the route method to be invoked.
@BeforeRoute and @AfterRoute methods can use all the same method parameter types as route methods except the criteria pattern parameters (Integer
, double
, etc.) are not allowed.
abstract class AuthenticatedRoutes
{
@BeforeRoute(exceptTags="anonymousAllowed")
public void requireLoggedIn(HttpServletRequest request)
{
...
if (!loggedIn)
{
// Request processing will end here, route method will
// not be invoked.
throw new RedirectTo("/login");
}
}
@AfterRoute(exceptTags="anonymousAllowed")
public void afterLoggedInRequest(URL url)
{
log.info("Logged in request: " + url);
}
}
@Routes("/")
public class HomeRoutes extends AuthenticatedRoutes
{
// BeforeRoute and AfterRoute methods called.
@Route
public String get(HttpServletRequest request)
{...}
// BeforeRoute and AfterRoute methods not called.
@Route(value="/faq", tags="anonymousAllowed")
public String getFAQ()
{...}
}
Routes Configuration
All of configuration for Routes is contained in RoutesConfiguration. If you need to customize the default configuration then pass in this object to the RoutingTable
upon creation.
RoutesConfiguration configuration = new RoutesConfiguration();
configuration.routeUnannotatedPublicMethods = true
configuration.rootForwardPath = "/jsps";
RoutingTable routingTable = new RoutingTable(configuration);
...
Routes Meta Page
Routes can serve up a web page that allows you test various URL paths, parameter and media type combinations to see which of your route methods will be selected. To enable this tool specify the path for the meta page with RoutesConfiguration.routesMetaPath. You must make sure this meta page does not collide with any of your route method criteria (the route methods will always win when this happens). If you want to deploy this utility in a non-development environment you can enable authentication and authorization for this utility using MetaAuthenticator.
Additional Documentation
Developed By
Corey Baswell - [email protected]
License
Copyright 2015 Corey Baswell
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.