Routage dans les applications Play en Java

1. Vue d'ensemble

Le routage est un concept courant qui apparaît dans la plupart des frameworks de développement Web, y compris Spring MVC.

Une route est un modèle d'URL mappé à un gestionnaire. Le gestionnaire peut être un fichier physique, tel qu'un actif téléchargeable dans l'application Web ou une classe qui traite la demande, comme un contrôleur dans une application MVC.

Dans ce didacticiel, nous allons explorer l'aspect du routage dans le développement d'applications Web avec Play Framework.

2. Configuration

Tout d'abord, nous devons créer une application Java Play. Les détails sur la configuration de Play Framework sur une machine sont disponibles dans notre article d'introduction.

À la fin de la configuration, nous devrions avoir une application Play fonctionnelle à laquelle nous pouvons accéder à partir d'un navigateur.

3. Routage HTTP

Alors, comment Play sait-il quel contrôleur consulter chaque fois que nous envoyons une requête HTTP? La réponse à cette question se trouve dans le fichier de configuration app / conf / routes .

Le routeur de Play traduit les requêtes HTTP en appels à l'action. Les requêtes HTTP sont considérées comme des événements dans l'architecture MVC et le routeur y réagit en consultant le fichier de routes pour quel contrôleur et quelle action dans ce contrôleur exécuter.

Chacun de ces événements fournit un routeur avec deux paramètres: un chemin de requête avec sa chaîne de requête et la méthode HTTP de la requête.

4. Routage de base avec Play

Pour que le routeur fasse son travail, le fichier conf / routes doit définir les mappages des méthodes HTTP et des modèles d'URI aux actions de contrôleur appropriées:

GET / controllers.HomeController.index GET / assets/*file controllers.Assets.versioned(path="/public", file: Asset)

Tous les fichiers de routes doivent également mapper les ressources statiques dans le dossier play-routing / public disponible pour le client sur le point de terminaison / assets .

Notez la syntaxe de la définition des routes HTTP et l' action du contrôleur d' espace de modèle URI d' espace de méthode HTTP .

5. Modèles d'URI

Dans cette section, nous expliquerons un peu les modèles d'URI.

5.1. Modèles d'URI statiques

Les trois premiers modèles d'URI ci-dessus sont statiques. Cela signifie que le mappage des URL aux ressources se produit sans aucun traitement supplémentaire dans les actions du contrôleur.

Tant qu'une méthode de contrôleur est appelée, elle renvoie une ressource statique dont le contenu est déterminé avant la requête.

5.2. Modèles d'URI dynamiques

Le dernier modèle d'URI ci-dessus est dynamique. Cela signifie que l'action du contrôleur traitant une demande sur ces URI a besoin de certaines informations de la demande pour déterminer la réponse. Dans le cas ci-dessus, il attend un nom de fichier.

La séquence normale d'événements est que le routeur reçoit un événement, choisit le chemin de l'URL, décode ses segments et les transmet au contrôleur.

Les paramètres de chemin et de requête sont ensuite injectés dans l'action du contrôleur en tant que paramètres. Nous allons le démontrer avec un exemple dans les sections suivantes.

6. Routage avancé avec Play

Dans cette section, nous aborderons en détail les options avancées de routage à l'aide des modèles d'URI dynamiques.

6.1. Paramètres de chemin simple

Les paramètres de chemin simples sont des paramètres sans nom dans une URL de requête qui apparaissent après l'hôte et le port et sont analysés par ordre d'apparition.

Dans play-routing / app / HomeController.java , créons une nouvelle action:

public Result greet(String name) { return ok("Hello " + name); }

Nous voulons pouvoir choisir un paramètre de chemin à partir de l'URL de la demande et le mapper au nom de la variable.

Le routeur obtiendra ces valeurs à partir d'une configuration d'itinéraire.

Alors, ouvrons play-routing / conf / routes et créons un mappage pour cette nouvelle action:

GET /greet/:name controllers.HomeController.greet(name: String)

Remarquez comment nous informons un routeur que le nom est un segment de chemin dynamique avec la syntaxe deux-points, puis passez-le en paramètre à l'appel à l'action de salutation.

Maintenant, chargeons // locahost: 9000 / greet / john dans le navigateur, et nous serons accueillis par nom:

Hello john

Il se trouve que si notre paramètre d'action est de type chaîne, nous pouvons le transmettre pendant l'appel à l'action sans spécifier le type de paramètre , bien que ce ne soit pas la même chose pour les autres types.

Pimentons notre point de terminaison / greet avec des informations sur l'âge.

Retour à HomeController action greet « s, nous allons changer à:

public Result greet(String name, int age) { return ok("Hello " + name + ", you are " + age + " years old"); }

Et la route vers:

GET /greet/:name/:age controllers.HomeController.greet(name: String, age: Integer)

Notez également la syntaxe Scala pour déclarer une variable, age: Integer . En Java, nous utiliserions la syntaxe de l' âge Integer . Le cadre de jeu est construit dans Scala. Par conséquent, il y a beaucoup de syntaxe scala.

Chargeons // localhost: 9000 / greet / john / 26 :

Hello john, you are 26 years old

6.2. Caractères génériques dans les paramètres de chemin

Dans notre fichier de configuration des routes, le dernier mappage est:

GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset)

Nous utilisons un caractère générique dans la partie dynamique du chemin. Ce que nous disons à Play, c'est que toute valeur qui remplace le fichier * dans la requête réelle doit être analysée dans son ensemble et non décodée comme dans d'autres cas de paramètres de chemin.

Dans cet exemple, le contrôleur est un contrôleur intégré, Assets , qui permet au client de télécharger des fichiers à partir du dossier play-routing / public . Lorsque nous chargeons //localhost:9000/assets/images/favicon.png , nous devrions voir l'image du favicon Play dans le navigateur car il est présent dans le dossier / public / images .

Créons notre propre exemple d'action dans HomeController.java :

public Result introduceMe(String data) { String[] clientData = data.split(","); return ok("Your name is " + clientData[0] + ", you are " + clientData[1] + " years old"); }

Notice that in this action, we receive one String parameter and apply our logic to decode it. In this case, the logic is to split a comma-delimited String into an array. Previously, we depended on a router to decode this data for us.

With wildcards, we are on our own. We're hoping that the client gets our syntax correct while passing this data in. Ideally, we should validate the incoming String before using it.

Let's create a route to this action:

GET /*data controllers.HomeController.introduceMe(data)

Now load the URL //localhost:9000/john,26. This will print:

Your name is john, you are 26 years old

6.3. Regex in Path Parameters

Just like wildcards, we can use regular expressions for the dynamic part. Let's add an action that receives a number and returns its square:

public Result squareMe(Long num) { return ok(num + " Squared is " + (num * num)); }

Now we'll add its route:

GET /square/$num controllers.HomeController.squareMe(num:Long)

Let's place this route below the introduceMe route to introduce a new concept. We can only handle routes where the regex part is a positive integer with this routing configuration.

Now if we have placed the route as instructed in the previous paragraph, and we load //localhost:9000/square/2, we should be greeted with an ArrayIndexOutOfBoundsException:

If we check the error logs in the server console, we will realize that the action call was actually performed on introduceMe action rather than squareMe action. As said earlier about wildcards, we are on our own and we did not validate incoming data.

Instead of a comma-delimited string, the introduceMe method was called with the string “square/2“. Consequently, after splitting it, we got an array of size one. Trying to reach index 1 then threw the exception.

Naturally, we would expect the call to be routed to the squareMe method. Why was it routed to introduceMe? The reason is a Play feature we'll cover next called Routing Priority.

7. Routing Priority

If there is a conflict between routes as there is between squareMe and introduceMe, then Play picks the first route in declaration order.

Why is there a conflict? Because of the wildcard context path /*data matches any request URL apart from the base path /. So every route whose URI pattern uses wildcards should appear last in order.

Now let's change the declaration order of the routes such that the introduceMe route comes after squareMe and reload:

2 Squared is 4

To test the power of regular expressions in a route, try loading //locahost:9000/square/-1, a router will fail to match the squareMe route. Instead, it will match introduceMe, and we'll get the ArrayIndexOutOfBoundsException again.

This is because -1 does not match by the provided regular expression, neither does any alphabetic character.

8. Parameters

Up until this point, we've covered the syntax for declaring parameter types in the routes file.

In this section, we'll look at more options available to us when dealing with parameters in routes.

8.1. Parameters With Fixed Values

Sometimes we'll want to use a fixed value for a parameter. This is our way of telling Play to use the path parameter provided or if the request context is the path /, then use a certain fixed value.

Another way of looking at it is having two endpoints or context paths leading to the same controller action — with one endpoint requiring a parameter from the request URL and defaulting to the other in case the said parameter is absent.

To demonstrate this, let's add a writer() action to the HomeController:

public Result writer() { return ok("Routing in Play by Baeldung"); }

Assuming we don't always want our API to return a String:

Routing in Play by Baeldung

We want to control it by sending the name of an author of the article along with the request, defaulting to the fixed value Baeldung only if the request does not have the author parameter.

So let's further change the writer action by adding a parameter:

public Result writer(String author) { return ok("REST API with Play by " + author); }

Let's also see how to add a fixed value parameter to the route:

GET /writer controllers.HomeController.writer(author = "Baeldung") GET /writer/:author controllers.HomeController.writer(author: String)

Notice how we now have two separate routes all leading to the HomeController.index action instead of one.

When we now load //localhost:9000/writer from the browser we get:

Routing in Play by Baeldung

And when we load //localhost:9000/writer/john, we get:

Routing in Play by john

8.2. Parameters With Default Values

Apart from having fixed values, parameters can also have default values. Both provide fallback values to the controller action parameters in case the request does not provide the required values.

The difference between the two is that fixed values are used as a fallback for path parameters while default values are used as a fallback for query parameters.

Path parameters are of the form //localhost:9000/param1/param2 and query parameters are of the form //localhost:9000/?param1=value1¶m2=value2.

The second difference is in the syntax of declaring the two in a route. Fixed value parameters use the assignment operator as in:

author = "Baeldung"

While default values use a different type of assignment:

author ?= "Baeldung"

We use the ?= operator which conditionally assigns Baeldung to author in case author is found to contain no value.

To have a complete demonstration, let's create the HomeController.writer action. Let's say, apart from the author's name which is a path parameter, we also want to pass author id as a query parameter which should default to 1 if not passed in the request.

We'll change writer action to:

public Result writer(String author, int id) { return ok("Routing in Play by: " + author + " ID: " + id); }

et l' écrivain se dirige vers:

GET /writer controllers.HomeController.writer(author="Baeldung", id: Int ?= 1) GET /writer/:author controllers.HomeController.writer(author: String, id: Int ?= 1)

Maintenant en cours de chargement // localhost: 9000 / writer, nous voyons:

Routing in Play by: Baeldung ID: 1

Frapper // localhost: 9000 / writer? Id = 10 nous donne:

Routing in Play by: Baeldung ID: 10

Qu'en est-il de // localhost: 9000 / writer / john ?

Routing in Play by: john ID: 1

Et enfin, // localhost: 9000 / writer / john? Id = 5 renvoie:

Routing in Play by: john ID: 5

9. Conclusion

Dans cet article, nous avons exploré la notion de routage dans les applications Play. Nous avons également un article sur la création d'une API RESTful avec Play Framework où les concepts de routage de ce didacticiel sont appliqués dans un exemple pratique.

Comme d'habitude, le code source de ce tutoriel est disponible à l'adresse over sur GitHub.