Skip to content

3. Azioni: la risposta

Consideriamo l’architettura di un’applicazione Spring MVC:

In questo capitolo esaminiamo il processo che porta la richiesta [1] al controller e all'azione [2a] che la elaboreranno, un meccanismo denominato "routing". Presentiamo inoltre le diverse risposte [3] che un'azione può inviare al browser. Può trattarsi di qualcosa di diverso da una vista V [4b].

3.1. Il nuovo progetto

Creiamo un nuovo progetto Spring MVC:

  • in [1-2], creiamo un nuovo progetto basato su Spring Boot;
  • in [3], il nome del progetto Maven;
  • in [4], il gruppo Maven in cui verrà inserito il risultato della compilazione del progetto;
  • in [5], il nome assegnato al prodotto della compilazione;
  • in [6], una descrizione del progetto;
  • in [7], il pacchetto in cui verrà inserita la classe eseguibile del progetto;
  • in [8], la natura del progetto. Si tratta di un progetto web con viste Thymeleaf. Qui si vedono tutte le dipendenze Maven pronte all’uso offerte dal progetto Spring Boot;
  • in [9], si indica che il prodotto risultante dalla build Maven verrà impacchettato in un archivio jar e non war. Il progetto utilizzerà quindi un server Tomcat integrato che si troverà tra le sue dipendenze;
  • in [10], si procede con la procedura guidata;
  • in [11], si specifica la cartella del progetto;
  • in [12], il progetto generato;
  • in [14-15], si rinomina il pacchetto [istia.st.springmvc];
  • in [16], il nuovo nome del pacchetto;
  • in [17], il nuovo progetto;

Ora creiamo una nuova classe;

  • in [1-3], creiamo una nuova classe;
  • in [5] le assegniamo un nome e in [4] ne specifichiamo il pacchetto;
  • in [6] il nuovo progetto;

La classe è per ora la seguente:


package istia.st.springmvc;

public class ActionsController {

}

Modifichiamo questo codice nel modo seguente:


package istia.st.springmvc;

import org.springframework.web.bind.annotation.RestController;

@RestController
public class ActionsController {

}
  • riga 6: l'annotazione [@RestController] indica due cose:
    • che la classe [ActionsController] così annotata è un controller Spring MVC, quindi contiene azioni che gestiscono i URL dei clienti;
    • che il risultato di queste azioni viene inviato al client;

L'altra annotazione [@Controller] che abbiamo incontrato è diversa: le azioni di un controller così annotato restituiscono il nome della vista che deve essere visualizzata. È quindi la combinazione di questa vista e del modello costruito dall'azione per quella vista a fornire la risposta inviata al client.

Il cambiamento di struttura del nostro progetto comporta una modifica della configurazione del progetto:

  

La classe [Application] si evolve come segue:


package istia.st.springmvc.main;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan({"istia.st.springmvc.controllers"})
@EnableAutoConfiguration
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
  • riga 9: l’annotazione [ComponentScan] accetta come parametro un array di nomi di pacchetti in cui Spring Boot deve cercare i componenti Spring. Qui inseriamo in questo array il pacchetto [istia.st.springmvc.controllers] affinché venga trovato il controller annotato con [@RestController];

Realizzeremo diverse azioni nel controller per illustrarne le caratteristiche principali. Ci concentreremo innanzitutto sui vari tipi di risposta possibili di un’azione in un’applicazione senza viste.

3.2. [/a01, /a02] - Hello world

La nostra prima azione sarà la seguente:


@RestController
public class ActionsController {
    // ----------------------- hello world ------------------------
    @RequestMapping(value = "/a01", method = RequestMethod.GET)
    public String a01() {
        return "Greetings from Spring Boot!";
    }
}
  • riga 4: l'annotazione [RequestMapping] qualifica la richiesta elaborata dall'azione annotata:
    • l'attributo [value] è l'azione URL elaborata,
    • l'attributo [method] definisce il metodo accettato;

Pertanto, il metodo [a01] elabora la richiesta HTTP [GET /a01].

  • riga 5: il metodo [a01] restituisce un tipo [String] che verrà inviato così com'è al cliente;
  • riga 6: la stringa restituita;

Avviamo l’applicazione come abbiamo già fatto più volte, quindi con il client [Advanced Rest Client], richiediamo l’URL [/a01] con un GET [1-2]:

  • in [3], la risposta del server;
  • in [4], le intestazioni HTTP della risposta. Si nota che la codifica utilizzata è [ISO-8859-1]. Si può preferire la codifica UTF-8. È possibile configurarla;
  • in [5], si richiede lo stesso URL con il browser Chrome;

Aggiungiamo la seguente azione [/a02] nel controller [ActionsController] (a volte si confonderà URL con il metodo che la gestisce sotto il nome di azione):


    // ----------------------- caratteri accentati - UTF8 ------------------------
    @RequestMapping(value = "/a02", method = RequestMethod.GET, produces="text/plain;charset=UTF-8")
    public String a02() {
        return "caractères accentués : éèàôûî";
}
  • riga 2: l'attributo [produces="text/plain;charset=UTF-8"] indica che l'azione invia un flusso di testo con caratteri codificati nel formato [UTF-8]. Questo formato consente in particolare l'uso di caratteri accentati;

Per rendere effettiva questa nuova azione, dobbiamo riavviare l’applicazione:

 

Il risultato è il seguente:

  • in [1], si vede la natura del documento inviato dal server;
  • in [2-3], i caratteri accentati sono correttamente visualizzati;

3.3. [/a03]: generare un flusso XML

Aggiungiamo la seguente azione [/a03]:


    // ----------------------- text/xml ------------------------
    @RequestMapping(value = "/a03", method = RequestMethod.GET, produces = "text/xml;charset=UTF-8")
    public String a03() {
        String greeting = "<greetings><greeting>Greetings from Spring Boot!</greeting></greetings>";
        return greeting;
}
  • riga 2: l'attributo [produces="text/xml;charset=UTF-8"] indica che l'azione invia un flusso XML con caratteri codificati nel formato [UTF-8];

La sua esecuzione produce il seguente risultato:

  • in [1], l'intestazione HTTP specifica che il documento inviato è in formato HTML;
  • in [2], il browser Chrome utilizza questa informazione per formattare il testo XML ricevuto;

Ricordiamo che con Chrome è possibile accedere agli scambi HTTP tra il client e il server nella finestra di sviluppo (Ctrl-Maiusc-I):

Image

D'ora in poi, non si effettueranno sistematicamente screenshot degli scambi HTTP tra il client e il server. A volte, ci limiteremo a riportare il testo di tali scambi.

3.4. [/a04, /a05]: restituire un flusso jSON

Aggiungiamo la seguente azione [/a04]:


    // ----------------------- generare jSON ------------------------
    @RequestMapping(value = "/a04", method = RequestMethod.GET)
    public Map<String, Object> a04() {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("1", "un");
        map.put("2", new int[] { 4, 5 });
        return map;
}
  • riga 3: l’azione restituisce un tipo [Map], un dizionario. Ricordiamo che con un controller di tipo [@RestController], il risultato dell'azione è la risposta inviata al client. Poiché il protocollo HTTP è un protocollo di scambio di righe di testo, la risposta del client deve essere serializzata in una stringa di caratteri. A tal fine, Spring MVC utilizza vari convertitori [Objet <---> chaîne de caractères]. L’associazione di un oggetto specifico a un convertitore avviene tramite configurazione. In questo caso, l’autoconfigurazione di Spring Boot analizzerà le dipendenze del progetto:
 

Le dipendenze Jackson sopra indicate sono librerie per la serializzazione/deserializzazione di oggetti in stringhe jSON. Spring Boot utilizzerà quindi queste librerie per serializzare/deserializzare gli oggetti restituiti dalle azioni. Un esempio di codice Java per serializzare e deserializzare oggetti Java in jSON è riportato nel paragrafo 9.7.

Si noti che alla riga 2 non abbiamo specificato il tipo della risposta inviata. Vedremo quale tipo verrà inviato per impostazione predefinita.

I risultati in Chrome [1-3] sono i seguenti:

Aggiungiamo ora la seguente azione [/a05]:


    // ----------------------- generare jSON - 2 ------------------------
    @RequestMapping(value = "/a05", method = RequestMethod.GET)
    public Personne a05() {
        return new Personne(1,"carole",45);
}

La classe [Personne] è la seguente:

  

package istia.st.sprinmvc.models;

public class Personne {

    // identificativo
    private Integer id;
    // nome
    private String nom;
    // età
    private int age;

    // costruttori
    public Personne() {

    }

    public Personne(String nom, int age) {
        this.nom = nom;
        this.age = age;
    }

    public Personne(Integer id, String nom, int age) {
        this(nom, age);
        this.id = id;
    }

    @Override
    public String toString() {
        return String.format("[id=%s, nom=%s,  age=%d]", id, nom, age);
    }

    // getter e setter
...
}

L'esecuzione produce i seguenti risultati:

  • in [1], il server indica che il documento che sta inviando è jSON;
  • in [2], il documento jSON ricevuto;

3.5. [/a06]: restituire un flusso vuoto

Aggiungiamo la seguente azione [/a06]:


    // ----------------------- restituire un flusso vuoto ------------------------
    @RequestMapping(value = "/a06")
    public void a06() {
}
  • riga 3, l'azione [/a06] non restituisce nulla. Spring MVC genererà quindi una risposta vuota al client;

L'esecuzione produce i seguenti risultati:

 

Come si vede sopra, l’attributo HTTP [Content-Length] nella risposta indica che il server invia un documento vuoto.

3.6. [/a07, /a08, /a09]: natura del flusso con [Content-Type]

Aggiungiamo la seguente azione [/a07]:


    // ----------------------- text/html ------------------------
    @RequestMapping(value = "/a07", method = RequestMethod.GET, produces = "text/html;charset=UTF-8")
    public String a07() {
        String greeting = "<h1>Greetings from Spring Boot!</h1>";
        return greeting;
}
  • riga 2, l'azione [/a07] restituisce un flusso HTML [text/html];
  • riga 4: una stringa HTML;

L'esecuzione produce i seguenti risultati:

  • in [1], si vede che Chrome ha interpretato il tag HTML <h1> che visualizza il suo contenuto in caratteri grandi;

Ora facciamo la stessa cosa con la seguente azione [/a08]:


    // ----------------------- risultato di HTML in text/plain ------------------------
    @RequestMapping(value = "/a08", method = RequestMethod.GET, produces = "text/plain;charset=UTF-8")
    public String a08() {
        String greeting = "<h1>Greetings from Spring Boot!</h1>";
        return greeting;
}
  • riga 2: la risposta dell'azione è di tipo [text/plain];

I risultati sono i seguenti:

  • in [1], Chrome non ha interpretato il tag HTML <h1> perché il server gli ha comunicato che gli stava inviando un flusso [text/plain] [2];

Riproviamo con qualcosa di simile utilizzando l’azione [/a09]:


    // ----------------------- risultato di HTML in formato text/xml ------------------------
    @RequestMapping(value = "/a09", method = RequestMethod.GET, produces = "text/xml;charset=UTF-8")
    public String a09() {
        String greeting = "<h1>Greetings from Spring Boot!</h1>";
        return greeting;
}
  • riga 2: si invia un flusso di tipo [text/xml];

I risultati sono i seguenti:

  • in [1], Chrome non ha interpretato il tag HTML <h1> perché il server gli ha comunicato che gli stava inviando un flusso [text/xml] [2]. Ha quindi gestito il tag <h1> come un tag XML;

Da questi esempi si evince l’importanza dell’intestazione HTTP [Content-Type] nella risposta del server. Il browser utilizza questa intestazione per sapere come interpretare il documento che riceve;

3.7. [/a10, /a11, /a12]: reindirizzare il client

Creiamo un nuovo controller [RedirectController]:

 

Il codice di [RedirectCntroller] sarà per ora il seguente:


package istia.st.springmvc.controllers;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class RedirectController {
}
  • riga 7: utilizziamo l’annotazione [@Controller], per cui d’ora in poi, per impostazione predefinita, il tipo [String] del risultato delle azioni indicherà il nome di un’azione o di una vista;

Creiamo la seguente azione [/a10]:


    // ------------ reindirizzamento a un'azione di terze parti -----------------------
    @RequestMapping(value = "/a10", method = RequestMethod.GET)
    public String a10() {
        return "a01";
}
  • riga 4: restituiamo come risultato 'a01', che è il nome di un'azione. Sarà quindi questa azione a inviare la risposta al client;

Ecco un esempio:

  • in [2], abbiamo ricevuto il flusso dell'azione [/a01];
  • in [3], il browser visualizza l'URL dell'azione [/a10];

Ora creiamo la seguente azione [/a11]:


    // ------------ reindirizzamento temporaneo 302 verso un'azione di terze parti -----------------------
    @RequestMapping(value = "/a11", method = RequestMethod.GET)
    public String a11() {
        return "redirect:/a01";
}

Otteniamo i seguenti risultati:

  • nei log di Chrome [1-2] si vedono due richieste, una verso [/a11] e l’altra verso [/a01];
  • in [3], il server risponde con un codice [302] che richiede al browser client di reindirizzarsi verso ilURL indicato dall'intestazione HTTP [Location:] [4]. Il codice [302] è un codice di reindirizzamento temporaneo;

Il browser effettua quindi la seconda richiesta all'URL di reindirizzamento URL:

  • a [5], la seconda richiesta del client;
  • a [6], il browser client visualizza l'URL della richiesta di reindirizzamento;

Si potrebbe voler indicare un reindirizzamento permanente; in tal caso, è necessario inviare al client la seguente intestazione HTTP:

HTTP/1.1 301 Moved Permanently 

il che significa che il reindirizzamento è permanente. Questa differenza tra reindirizzamento temporaneo (302) e permanente (301) viene presa in considerazione da alcuni motori di ricerca.

Scriviamo l’azione [/a12] che effettuerà questo reindirizzamento permanente:


    // ------------ reindirizzamento permanente 301 verso un'azione di terze parti----------------
    @RequestMapping(value = "/a12", method = RequestMethod.GET)
    public void a12(HttpServletResponse response) {
        response.setStatus(301);
        response.addHeader("Location", "/a01");
}
  • riga 3: si richiede a Spring MVC di iniettare l’oggetto [HttpServletResponse] che incapsula la risposta inviata al cliente;
  • riga 4: si imposta il [status] della risposta, il [301] dell'intestazione HTTP:
HTTP/1.1 301 Moved Permanently
  • riga 5: si crea manualmente la intestazione HTTP come segue:
Location: /a01 

che costituisce l'intestazione di reindirizzamento URL.

L'esecuzione produce i seguenti risultati:

Da questo esempio si ricava il modo per:

  • generare lo stato della risposta HTTP;
  • includere un'intestazione HTTP nella risposta;

3.8. [/a13]: generare la risposta completa

È possibile controllare completamente la risposta, come mostra la seguente azione della classe [ResponsesController]:

  

    // ----------------------- generazione completa della risposta ------------------------
    @RequestMapping(value = "/a13")
    public void a13(HttpServletResponse response) throws IOException {
        response.setStatus(666);
        response.addHeader("header1", "qq chose");
        response.addHeader("Content-Type", "text/html;charset=UTF-8");
        String greeting = "<h1>Greetings from Spring Boot!</h1>";
        response.getWriter().write(greeting);
}
  • riga 3: il risultato dell'azione è [void]. In questo caso, per inviare una risposta non vuota al client, è necessario utilizzare l'oggetto [HttpServletResponse response] fornito da Spring MVC;
  • riga 4: si assegna alla risposta uno stato che non sarà riconosciuto dal client;
  • riga 5: si aggiunge un'intestazione HTTP che non sarà riconosciuta dal client;
  • riga 6: si aggiunge un'intestazione HTTP [Content-Type] per specificare il tipo di flusso che verrà inviato, in questo caso HTML;
  • righe 7-8: il documento che seguirà le intestazioni HTTP nella risposta;

I risultati sono i seguenti:

  • in [1] si riconoscono gli elementi della nostra risposta;
  • in [2-3], si nota che Chrome ha ignorato il fatto che:
    • lo stato HTTP della risposta non era uno stato HTTP riconosciuto,
    • che l'intestazione [header1] non era un'intestazione HTTP riconosciuta;

Se il client non è un browser ma un client programmato, si è liberi di utilizzare gli stati e le intestazioni che si desidera.