Saturday, May 21, 2016

Making Play route case-insensitive - part 1

Play routes are case sensitive. It means, that if the route is defined like this:
 GET /thisIsMyPage       controllers.HomeController.exactRoute
the URL should match exactly the route including the letters case. Any attempt to provide different letter cases in the URL will result with NotFound error.

The one and only "good" URL is:
 localhost:9000/thisIsMyPage

The "bad" URL examples:
localhost:9000/thisismypage
localhost:9000/ThisIsMyHomePage

There are a plenty of "bad" URL-s - the longer is the URL, the more possibilities to use the wrong case. The whole this thing is rather annoying.

Before deepening into the solution for case-insensitive route let's recall what are Play dynamic and static routes.

Play dynamic and static routes


Play supports both static and dynamic routes.

Static route is a constant and defines a particular URL request.
Data for a static routes may be provided as a query parameters or in the body.

Example of the static route:
GET /thisIsMyPage       controllers.HomeController.exactRoute

The URL for a static route may contain optionally query parameters.
URL examples:
localhost:9000/thisIsMyPage
localhost:9000/thisIsMyPage?message=good news are coming

Dynamic route contains a variable part(s) and may be used for multiple requests depending on the variable part value.
The table below summarizes dynamic route cases:

Purpose Special character
1. Single or multiple variables in the path.
Using the prefix “:” means that the variable is exactly one URI segment. By default the path variables are of the
String type. If a conversion is required, the type should be mentioned explicitly.
Max number of variables in the path is 21.
:

Examples:
/clients/:id controllers.Clients.show(id: Long)
/clients/:id/orders controllers.Clients.showOrders(id: Long)
/clients/:id/order/:orderid controllers.Clients.showOrder(id: Long, orderid: Long)
2. Dynamic path, spanning several segments.
The symbol “*” indicates that anything following the static part until the space is found is a value of a variable, following the *.
*
Examples:
/files/*file controllers.Application.download(file: String)
If the URL is like /files/images/logo.png, the value of file will be images/logo.png
3. Custom regular expression for variables.
The symbol “$” indicates the custom regular expression instead of “:”, which follows wrapped with angle brackets <>. The rest is similar to the case 1.
$
Examples:
/clients/$id<[0-9]+> controllers.Clients.show(id: Long)
Valid URL: /clients/1234
/clients/$id<[a-zA-Z]{2}> controllers.Clients.show(id: Long)
Valid URL: /clients/Ab

How to make insensitive static route


Solution for insensitive route is a bit of trick: the static part, which should be insensitive, should be converted to be dynamic with custom regular expression.

The original route:
 GET /thisIsMyPage       controllers.HomeController.insensitiveRoute

The insensitive dynamic route:
 GET /$dummy<(?i)thisIsMyPage>       controllers.HomeController.insensitiveRoute(dummy:String)

The parameter dummy makes the route to be dynamic, but it is never used by the controller.
Since the route is dynamic, it may contain a regex. The regex <(?i) defines the route as insensitive.

Bring it all together


The example demonstrates implementation of the same route for both case-sensitive and insensitive option.
Of course in a real project this will not happen and only one of that will be used.

1. The routes entries for both case-sensitive and insensitive routes:
 # exact route example  
 GET /thisIsMyPage          controllers.HomeController.exactRoute(myMessage)  
 # case-insensitive route example  
 GET /$dummy<(?i)thisIsMyPage>          controllers.HomeController.insensitiveRoute(dummy)  

Note: The exact route expects to receive a query parameter named myMessage, but we will ignore this for now. This is considered in the next post.

2. The HomeController implements two methods for each case (again, separate methods were defined for demonstration purpose):
public class HomeController extends Controller {  
   public Result exactRoute(String message) {  
     return ok(examplepage.render("This is the exact route:" + message));  
   }
  
   public Result insensitiveRoute(String dummy) {  
     return ok(examplepage.render("This is insensitive route"));  
   }  
}

3. Both routes opens the same page, defined as a Play html template examplepage.scala.html, which expects to receive an input argument message, which is displayed as a header. The page itself is send as an argument to the main template:
 @*  
  * This template takes a single argument, a String containing a  
  * message to display.  
  *@  
 @(message: String)  
 @*  
  * Call the `main` template with two arguments. The first  
  * argument is a `String` with the title of the page, the second  
  * argument is an `Html` object containing the body of the page.  
  *@  
 @main("CodePreference - Example") {  
      <h1>@message</h1>  
      <p>Play template is compiled into html file</p>  
      <p>It will be embedded into the jar</p>  
 }  

The URL with the exact route:
http://localhost:9000/thisIsMyPage?myMessage=do it now
opens the window:


The URL with different letters case:
http://localhost:9000/thisisMyPage
opens the window:


All the sources on Git.

The next post (part 2) explains, how to make the query parameters case-insensitive.

No comments :

About the author

My Photo
I trust only simple code and believe that code should be handsome. This is not a matter of technology, but professional approach, consolidated after years of software development. I enjoy to cause things working and feel very happy, when I manage to solve a problem.
Back to Top