Instradamento delle applicazioni in gioco in Java

1. Panoramica

Il routing è un concetto comune che appare nella maggior parte dei framework di sviluppo web, incluso Spring MVC.

Una rotta è un pattern URL mappato a un gestore. Il gestore può essere un file fisico, come un asset scaricabile nell'applicazione Web o una classe che elabora la richiesta, come un controller in un'applicazione MVC.

In questo tutorial, esploreremo l'aspetto del routing nello sviluppo di applicazioni web con Play Framework.

2. Configurazione

Innanzitutto, dovremo creare un'applicazione Java Play. I dettagli su come configurare Play Framework su una macchina sono disponibili nel nostro articolo introduttivo.

Alla fine della configurazione, dovremmo avere un'applicazione Play funzionante a cui possiamo accedere da un browser.

3. Routing HTTP

Allora come fa Play a sapere quale controller consultare ogni volta che inviamo una richiesta HTTP? La risposta a questa domanda si trova nel file di configurazione app / conf / routes .

Il router di Play traduce le richieste HTTP in chiamate di azione. Le richieste HTTP sono considerate eventi nell'architettura MVC e il router reagisce consultando il file di route per quale controller e quale azione in quel controller eseguire.

Ciascuno di questi eventi fornisce un router con due parametri: un percorso di richiesta con la sua stringa di query e il metodo HTTP della richiesta.

4. Routing di base con il gioco

Affinché il router possa svolgere il proprio lavoro, il file conf / routes deve definire le mappature dei metodi HTTP e dei pattern URI per le azioni del controller appropriate:

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

Tutti i file di route devono anche mappare le risorse statiche nella cartella play-routing / public disponibile per il client sull'endpoint / assets .

Si noti la sintassi per la definizione delle rotte HTTP e l' azione del controller dello spazio del pattern URI dello spazio del metodo HTTP .

5. Pattern URI

In questa sezione, spiegheremo un po 'i pattern URI.

5.1. Modelli URI statici

I primi tre modelli di URI sopra sono statici. Ciò significa che la mappatura degli URL alle risorse avviene senza ulteriori elaborazioni nelle azioni del controller.

Finché viene chiamato un metodo controller, restituisce una risorsa statica il cui contenuto è determinato prima della richiesta.

5.2. Pattern URI dinamici

L'ultimo pattern URI sopra è dinamico. Ciò significa che l'azione del controller che serve una richiesta su questi URI necessita di alcune informazioni dalla richiesta per determinare la risposta. Nel caso precedente, si aspetta un nome di file.

La normale sequenza di eventi è che il router riceve un evento, sceglie il percorso dall'URL, decodifica i suoi segmenti e li passa al controller.

I parametri di percorso e query vengono quindi inseriti nell'azione del controller come parametri. Lo dimostreremo con un esempio nelle prossime sezioni.

6. Routing avanzato con il gioco

In questa sezione, discuteremo in dettaglio le opzioni avanzate nel routing utilizzando i pattern URI dinamici.

6.1. Parametri del percorso semplice

I parametri del percorso semplice sono parametri senza nome in un URL di richiesta che vengono visualizzati dopo l'host e la porta e vengono analizzati in ordine di apparizione.

All'interno di play-routing / app / HomeController.java , creiamo una nuova azione:

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

Vogliamo essere in grado di scegliere un parametro di percorso dall'URL della richiesta e mapparlo al nome della variabile.

Il router otterrà questi valori da una configurazione del percorso.

Quindi, apriamo play-routing / conf / routes e creiamo una mappatura per questa nuova azione:

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

Si noti come informiamo un router che il nome è un segmento di percorso dinamico con la sintassi dei due punti e quindi procediamo per passarlo come parametro alla chiamata all'azione di saluto.

Ora, carichiamo // locahost: 9000 / greet / john nel browser e saremo accolti per nome:

Hello john

Accade così che se il nostro parametro di azione è di tipo stringa, possiamo passarlo durante la chiamata di azione senza specificare il tipo di parametro , sebbene questo non sia lo stesso per altri tipi.

Ravviviamo il nostro endpoint / greet con le informazioni sull'età.

Torna all'azione di benvenuto di HomeController , la cambieremo in:

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

E il percorso per:

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

Si noti anche la sintassi Scala per la dichiarazione di una variabile, age: Integer . In Java, useremmo la sintassi Integer age . Il Play Framework è costruito in Scala. Di conseguenza, c'è molta sintassi di scala.

Carichiamo // localhost: 9000 / greet / john / 26 :

Hello john, you are 26 years old

6.2. Caratteri jolly nei parametri del percorso

Nel nostro file di configurazione delle rotte, l'ultima mappatura è:

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

Usiamo un carattere jolly nella parte dinamica del percorso. Quello che stiamo dicendo a Play è che qualunque valore sostituisca * file nella richiesta effettiva dovrebbe essere analizzato nel suo insieme e non decodificato come in altri casi di parametri di percorso.

In questo esempio, il controller è un controller integrato, Assets , che consente al client di scaricare file dalla cartella play-routing / public . Quando carichiamo //localhost:9000/assets/images/favicon.png , dovremmo vedere l'immagine della favicon di Play nel browser poiché è presente nella cartella / public / images .

Creiamo la nostra azione di esempio in 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); }

e lo scrittore si dirige verso:

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

Ora caricando // localhost: 9000 / writer vediamo:

Routing in Play by: Baeldung ID: 1

Premendo // localhost: 9000 / writer? Id = 10 si ottiene:

Routing in Play by: Baeldung ID: 10

Che dire di // localhost: 9000 / writer / john ?

Routing in Play by: john ID: 1

E infine, // localhost: 9000 / writer / john? Id = 5 restituisce:

Routing in Play by: john ID: 5

9. Conclusione

In questo articolo, abbiamo esplorato la nozione di Routing nelle applicazioni Play. Abbiamo anche un articolo sulla creazione di un'API RESTful con Play Framework in cui i concetti di routing in questo tutorial vengono applicati in un esempio pratico.

Come al solito, il codice sorgente di questo tutorial è disponibile su GitHub.