Activité Premier serveur HTTP
Consigne
- La durée de l’activité est estimée à environ 225 minutes (dont environ 45 min pour la mise en route).
- Avant de commencer l’activité :
- Lire la mise en situation, prendre connaissance des objectifs, du résultat attendu, et des ressources à disposition.
- Lire une première fois en entier la section Mise en route.
- Effectuez le travail décrit dans la section Mise en route et effectuez les tâches demandées dans la section Tâches.
Mise en situation
Vous avez été chargé de réaliser une application de messagerie instantanée pour votre site web. L’entreprise développe en Java avec le framework Spring et Spring Boot. Comme vous n’avez jamais pratiqué le développement d’application web en Java et que vous ne connaissez pas le framework Spring, vous décidez de vous lancer dans une activité d’exploration afin d’acquérir quelques bases.
Objectifs
À la fin de ce travail, vous devez :
- Connaître la différence entre le framework Spring et Spring Boot.
- Connaître la notion d’annotation en Java.
- Connaître la structure d’une application Spring Boot.
- Connaître l’annotation
@SpringBootApplicationet l’instructionSpringApplication.run(App.class, args);. - Connaître les annotations
@Controller,@RequestMapping, et@RequestBody. - Connaître les annotations
@GetMapping,@PostMapping,@PutMapping,@DeleteMapping. - Connaître les utilitaires curl et postman.
Résultat attendu
- Un projet Java fonctionnel
- Une section dans votre rapport
Ressources
Matériel :
- Visual Studio Code
- Docker Desktop, Colima, Rancher Desktop
Mise en route
Installer Docker
Dans ce module, nous aurons besoin de conteneurs pour exécuter un serveur HTTP, notre application, notre SGBD, etc.
Toutefois, sous Windows et macOS, il n’est pas possible d’exécuter directement des conteneurs Linux. La raison est qu’un conteneur utilise le noyau du système d’exploitation de l’hôte. Pour exécuter des conteneurs Linux sous Windows ou macOS, ceux-ci doivent l’être dans une machine virtuelle équipée d’un OS GNU/Linux.
Pour cela, on peut utiliser une distribution de Docker qui met en place et gère cette infrastructure pour nous. Les trois distributions suivantes sont compatibles avec Visual Studio Code :
- Docker Desktop : C’est la solution la plus simple sous Windows, macOS et Linux si vous disposez de votre propre matériel. Sur un PC d’entreprise, vous devez vous assurer que votre entreprise possède une licence pour Docker Desktop.
- Rancher Desktop : C’est la solution recommandée sous Windows, macOS et Linux si vous disposez d’un PC d’entreprise et que votre entreprise ne possède pas de licence pour Docker Desktop.
- Colima : C’est une solution libre et plus légère que Docker Desktop et Rancher Desktop pour macOS. Sa mise en œuvre est toutefois un peu plus difficile.
Installez la solution la plus appropriée en utilisant votre gestionnaire de paquets (brew, choco, etc.)
Installer les packs d’extensions pour VSCode
Pour ce module nous avons besoin de plusieurs extensions pour Visual Studio Code (VSCode) :
- Extension Pack for Java
- Spring Boot Extension Pack
- Editor config
Ouvrez VSCode et installez ces trois extensions.
Récupérer le projet depuis Gitlab
Pour récupérer le projet, restez dans VSCode, ouvrez une fenêtre de terminal intégré, rendez-vous dans votre répertoire de projet (cd $HOME/projects), créez un répertoire pour le module 295 (mkdir m295), rendez-vous dans ce répertoire (cd m295), et lancez les commandes suivantes :
1
2
git clone git@gitlab.epai-ict.ch:m295/java/firstserver.git
code -r firstserver
Si la commande git clone vous renvoie une erreur, cela signifie probablement que votre clé SSH n’est pas chargée, ou que vous ne l’avez pas ajouté à votre compte GitLab. Si vous ne savez pas comment faire, demandez de l’aide à votre enseignant (ne cherchez pas à contourner le problème, vous aurez très souvent besoin de cloner des projets).
Quand le projet est chargé, assurez-vous que votre distribution Docker (Rancher Desktop, Docker Desktop ou Colima) est lancée, puis tapez Shift-Command-P ou Shift-Ctrl-P pour ouvrir la liste des commandes de VSCode. Recherchez la commande dev containers: reopen in container dans la liste (tapez le début de la commande pour filtrer le contenu de la liste) et sélectionnez cette commande pour l’exécuter.
Cette commande lance l’exécution des conteneurs définis par le fichier docker-compose.yml, situé dans le répertoire .devcontainer (conteneur de développement) du projet, puis ouvre ce dernier dans le conteneur devcontainer. Le répertoire du projet est mappé dans le système de fichiers du conteneur : le code source reste donc sur votre PC (aucune copie des fichiers).
Note : Si les conteneurs n’existent pas encore, leur création peut prendre plusieurs minutes. Vous pouvez suivre la progression dans la fenêtre de log de VSCode.
Lorsque la création des conteneurs est terminée et que le projet est ouvert, ouvrez une fenêtre de terminal intégré et lancez la commande suivante pour vous assurer que les conteneurs ont été correctement créés.
1
mysql -h db -u root -pepai123
La commande doit ouvrir l’interpréteur de commande du serveur mariadb. Si c’est bien le cas, quittez l’interpréteur et lancez les commandes ci-dessous pour builder le projet et exécuter le fichier .jar.
1
2
mvn verify
java -jar target/firstserver-0.0.1-SNAPSHOT.jar
La première commande compile le projet, exécute les tests et prépare le fichier .jar (une archive qui contient les classes compilées). Elle devrait se terminer par un message qui indique “BUILD SUCCESS”. La deuxième commande lance l’exécution du fichier .jar à l’aide de l’interpréteur java et devrait afficher le message «Hello, World!» dans le terminal.
N’allez pas plus loin tant que cela ne fonctionne pas et demandez de l’aide au besoin.
Spring et Spring Boot
Spring est un framework développé par VMware (Broadcom), depuis l’acquisition de SpringSource en 2009. Au moment de l’acquisition, SpringSource était l’un des principaux contributeurs du projet Apache Tomcat.
Spring Boot est une extension du framework Spring qui permet de produire une application serveur directement exécutable avec un serveur HTTP embarqué (typiquement Tomcat ou Jetty).
Pour réaliser un premier serveur HTTP avec Spring et Spring Boot, on a besoin de la bibliothèque suivante :
- spring-boot-starter-web
Les bibliothèques dont le nom commence par spring-boot-starter permettent d’ajouter facilement des dépendances à notre projet. Spring Boot détecte automatiquement ces starters au démarrage et s’occupe de toute la configuration pour nous (instanciation des objets, connexions, etc.). Nous pouvons donc nous concentrer sur notre code métier.
Pour initialiser et configurer le framework dans les tests unitaires, nous avons également besoin de la bibliothèque :
- spring-boot-starter-test
Enfin, pour produire un fichier jar exécutable avec un serveur HTTP embarqué, on ajoute encore le plugin suivant :
- spring-boot-maven-plugin
Ces dépendances ont déjà été ajoutées dans le fichier pom.xml de votre projet.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
...
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Ajoutez ici vos autres dépédances -->
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
...
Tâches
Créer le serveur HTTP
Pour réaliser un serveur HTTP avec le framework Spring, on commence par annoter la classe principale avec l’annotation @SpringBootApplication.
1
2
3
4
5
6
7
8
9
10
11
package ch.epai.ict.m295.firstserver;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class FirstserverApplication {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
Puis, dans la méthode main, on utilise la méthode SpringApplication.run pour instancier et démarrer le serveur HTTP.
1
2
3
4
5
6
7
8
9
10
11
12
package ch.epai.ict.m295.firstserver;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class FirstserverApplication {
public static void main(String[] args) {
SpringApplication.run(FirstserverApplication.class, args);
}
}
Nous pouvons maintenant lancer le projet pour vérifier qu’il fonctionne, en cliquant le petit run qui devrait se trouver juste au-dessus de la méthode main. Le démarrage du serveur devrait ressembler à cela :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.3.4)
2024-10-10T18:27:02.047+02:00 INFO 36322 --- [firstserver] [ main] c.e.i.m.f.FirstserverApplication : Starting FirstserverApplication using Java 23 with PID 36322 (/Users/frossardj/projects/firstserver/target/classes started by frossardj in /Users/frossardj/projects/firstserver)
2024-10-10T18:27:02.050+02:00 INFO 36322 --- [firstserver] [ main] c.e.i.m.f.FirstserverApplication : No active profile set, falling back to 1 default profile: "default"
2024-10-10T18:27:02.613+02:00 INFO 36322 --- [firstserver] [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 8080 (http)
2024-10-10T18:27:02.622+02:00 INFO 36322 --- [firstserver] [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2024-10-10T18:27:02.622+02:00 INFO 36322 --- [firstserver] [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.30]
2024-10-10T18:27:02.670+02:00 INFO 36322 --- [firstserver] [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2024-10-10T18:27:02.671+02:00 INFO 36322 --- [firstserver] [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 579 ms
2024-10-10T18:27:02.944+02:00 INFO 36322 --- [firstserver] [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path '/'
2024-10-10T18:27:02.949+02:00 INFO 36322 --- [firstserver] [ main] c.e.i.m.f.FirstserverApplication : Started FirstserverApplication in 1.157 seconds (process running for 1.473)
En parcourant ces informations de démarrage, on peut apprendre que le serveur Tomcat a démarré sur le port 8080. Ce comportement est défini par la bibliothèque spring-boot-starter-web. Au démarrage, grâce à l’annotation @SpringBootApplication, Spring Boot a détecté la présence de cette bibliothèque et a initialisé l’application en conséquence. Si nécessaire, ce comportement peut être modifié, soit par l’ajout de code, soit par l’ajout et l’exclusion de bibliothèques starter.
Note : L’annotation @SpringBootApplication est en réalité un raccourci qui combine trois annotations : @SpringBootConfiguration (configuration), @EnableAutoConfiguration (auto-configuration), et @ComponentScan (détection automatique des composants).
Ouvrez un navigateur et naviguez vers l’URI http://localhost:8080/.
Est-ce que le serveur HTTP fonctionne correctement, selon vous ? Comment le savez-vous ?
Pour arrêter le serveur, placez le curseur dans la fenêtre de terminal où le serveur est lancé et pressez les touches Ctrl-c.
Créer un gestionnaire de requête
Pour créer un gestionnaire de requête, nous commençons par créer un nouveau fichier que nous appelons HomeController.java, dans le même répertoire que le fichier FirstserverApllication.java.
Ce fichier devrait avoir le contenu ci-dessous. Si ce n’est pas le cas, copiez ce code dans le fichier.
1
2
3
4
5
package ch.epai.ict.m295.firstserver;
public class HomeController {
}
Dans cette classe, nous allons ajouter une méthode handleGetHomeHtml et pour le moment, nous allons simplement écrire un message dans la sortie standard. Si ce message apparaît dans le terminal, nous saurons que la méthode a bien été exécutée.
1
2
3
4
5
6
7
8
9
10
11
12
13
package ch.epai.ict.m295.firstserver;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
public class HomeController {
public void handleGetHomeHtml(
HttpServletRequest request,
HttpServletResponse response) {
System.out.println("Home page requested");
}
}
D’après ce nous avons vue dans la présentation sur les frameworks, nous devrions maintenant ajouter ce gestionnaire de requête à la liste des gestionnaires de requête du serveur HTTP. Avec Spring, cela ne se fait pas de manière impérative (avec des instructions), mais de manière déclarative avec des annotations.
Pour informer le framework que notre classe est un contrôleur qui contient des gestionnaires de requête, nous l’annotons avec @Controller. Pour indiquer que la méthode handleGetHomeHtml est le gestionnaire de requête pour l’URI racine / et pour la méthode GET, on utilise l’annotation @RequestMapping(value = "/", method = RequestMethod.GET) :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package ch.epai.ict.m295.firstserver;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@Controller
public class HomeController {
@RequestMapping(value = "/", method = RequestMethod.GET)
public void handleGetHomeHtml(
HttpServletRequest request,
HttpServletResponse response) {
System.out.println("Home page requested");
}
}
Nous pouvons maintenant relancer l’application de la même manière que précédemment, ouvrir un navigateur et naviguer sur l’URI http://localhost:8080/.
Cette fois, la page n’affiche rien du tout puisque nous n’avons rien écrit dans la réponse, mais on peut voir dans la fenêtre du terminal intégré le message qui nous indique que le gestionnaire de requête a bien été invoqué.
Annotations de mapping pour les gestionaires de requêtes
L’annotation @RequestMapping permet, comme son nom l’indique, de mapper un chemin et une ou plusieurs méthodes HTTP à une méthode pour en faire un gestionnaire de requête. Si on ne spécifie qu’une seule méthode, on peut utiliser les annotations @GetMapping, @PostMapping, @PutMapping, etc.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package ch.epai.ict.m295.firstserver;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@Controller
public class HomeController {
@GetMapping(value = "/")
public void handleGetHomeHtml(
HttpServletRequest request,
HttpServletResponse response) {
System.out.println("Home page requested");
}
}
Écrire des données dans la réponse
On peut utiliser les méthodes de la réponse pour indiquer le code de statut et le type de contenu de la réponse, et le flux de sortie du socket attaché à la réponse pour envoyer le contenu.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package ch.epai.ict.m295.firstserver;
import java.io.IOException;
import java.io.PrintWriter;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@Controller
public class HomeController {
@GetMapping(value = "/")
public void handleGetHomeHtml(
HttpServletRequest request,
HttpServletResponse response) throws IOException {
// Récupère des données d'une manière ou d'une autre
String title = "Page d'accueil";
String data = "Bienvenue sur la page d'accueil !";
response.setContentType("text/html");
response.setStatus(200);
PrintWriter out = response.getWriter();
out.println("<html>");
out.println("<head>");
out.println(String.format("<meta charset=\"UTF-8\">", title));
out.println(String.format("<title>%s</title>", title));
out.println("</head>");
out.println("<body>");
out.println(String.format("<h1>%s</h1>", title));
out.println(String.format("<p>%s</p>", data));
out.println("</body>");
out.println("</html>");
out.flush();
}
}
On relance le serveur et cette fois le navigateur affiche un contenu en HTML lorsque l’on navigue vers l’URI http://localhost:8080/.
On remarque que l’on utilise pas l’objet request, il n’est donc pas nécessaire de le spécifier dans les argument de la méthode. Lorsque Spring Boot charge les gestionnaires de requête, il utilise les mécanisme d’introspéction de Java (Java Reflection) pour obtenir la signature des méthodes et savoir quelles information passer en paramètres.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package ch.epai.ict.m295.firstserver;
import java.io.IOException;
import java.io.PrintWriter;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import jakarta.servlet.http.HttpServletResponse;
@Controller
public class HomeController {
@GetMapping(value = "/")
public void handleGetHomeHtml(
HttpServletResponse response) throws IOException {
// Récupère des données d'une manière ou d'une autre
String title = "Page d'accueil";
String data = "Bienvenue sur la page d'accueil !";
response.setContentType("text/html");
response.setStatus(200);
PrintWriter out = response.getWriter();
out.println("<html>");
out.println("<head>");
out.println(String.format("<meta charset=\"UTF-8\">", title));
out.println(String.format("<title>%s</title>", title));
out.println("</head>");
out.println("<body>");
out.println(String.format("<h1>%s</h1>", title));
out.println(String.format("<p>%s</p>", data));
out.println("</body>");
out.println("</html>");
out.flush();
}
}
Relancer le serveur et assurez-vous qu’il produit le même résultat.
Enfin, on peut également se passer de l’objet request en indiquant à Spring Boot que la valeur renvoyée par la méthode est le contenu de la réponse HTTP. Pour cela on peut utiliser l’annotation @ResponseBody sur la méthode (nous verrons plus tard que l’on peut également utiliser cette annotation sur la classe du controlleur).
Toutefois, comme nous n’avons plus l’objet réponse, nous ne pouvons plus l’utiliser pour spécifier le type du contenu (response.setContentType("text/html");) ni pour spécifier le code de status (response.setStatus(200)). À la place, on le fait à l’aide de l’argument produces de l’annotation de mapping et l’annotation @ResponseStatus.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package ch.epai.ict.m295.firstserver;
import java.io.IOException;
import java.io.PrintWriter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
@Controller
public class HomeController {
@GetMapping(value = "/", produces = "text/html")
@ResponseBody
@ResponseStatus(HttpStatus.OK) // peut être omise si la réponse est 200
public String handleGetHomeHtml() throws IOException {
// Récupère des données d'une manière ou d'une autre
String title = "Page d'accueil";
String data = "Bienvenue sur la page d'accueil !";
// Construit le contenu avec un StringBuilder (plus rapide que la concaténation)
StringBuilder out = new StringBuilder();
out.append("<html>");
out.append("<head>");
out.append(String.format("<meta charset=\"UTF-8\">", title));
out.append(String.format("<title>%s</title>", title));
out.append("</head>");
out.append("<body>");
out.append(String.format("<h1>%s</h1>", title));
out.append(String.format("<p>%s</p>", data));
out.append("</body>");
out.append("</html>");
return out.toString();
}
}
Lancez une nouvelle fois le serveur et assurez-vous qu’il produit toujours le même résultat.
Créer la réponse avec un template
Cette manière de faire nous donnes un contrôle total sur la manière de traiter la requête. Toutefois, l’utilisation du writer pour créer la réponse est laborieuse et difficile à maintenir. De plus, comme le code HTML est imbriqué dans le code Java, il n’est pas possible de sous-traiter la réalisation du code HTML à des spécialistes.
Pour résoudre ces problèmes, on utilise généralement des templates (modèles). Un template est un fichier de texte qui contient des espaces réservés, souvent appelés placeholders, qui sont remplis avec des données dynamiques au moment de l’exécution.
Pour spécifier les espaces réservés, on utilise un langage de template. Un langage de template permet généralement spécifier des variables à remplacer, mais aussi des structure de choix (if-then-else) pour rendre contionnelle certaines partie du template, et des boucles (for-each) pour répéter un partie du template.
Dans notre projet, nous allons utiliser le langage de template FreeMarker. Pour cela, nous devons d’abord ajouter la bibliothèque spring-boot-starter-freemarker à la liste des dépendances dans le fichier pom.xml.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
...
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Ajoutez ici vos autres dépédances -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
...
Nous devons ensuite configurer Spring Boot pour lui indiquer où trouver les templates et configurer le moteur de template FreeMarker, en ajoutant les entrées ci-dessous dans le fichier /src/main/resources/application.properties de notre projet.
1
2
3
spring.freemarker.template-loader-path=classpath:/templates/
spring.freemarker.suffix=.ftl
spring.freemarker.charset=UTF-8
Pour créer le template de notre réponse, nous devons d’abord créer le répertoire templates dans le répertoire /src/main/resources du projet, puis un fichier que nous appellerons home.ftl avec le code de la figure ci-dessous.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${title}</title>
<style>
body { font-family: Arial, sans-serif; margin: 0; padding: 0; }
header, footer { background: #333; color: #fff; padding: 10px; text-align: center; }
main { padding: 20px; }
</style>
</head>
<body>
<header>
<h1>Première application Spring Boot</h1>
</header>
<main>
<h1>${title}</h1>
<p>${greetings}</p>
</main>
<footer>
<p>2024,Première application</p>
</footer>
</body>
</html>
Les variables à remplacer sont spécifiées par leur nom entre accolades précédées d’un signe dolard (${...}).
Modifions maintenant notre gestionnaire de requête pour utiliser ce template. Dans ce nouveau gestionnaire, nous n’allons plus utiliser directement la requête et la réponse, nous laisserons cette tâche au framework. Nous allons utiliser un objet de type Model que le framework instancie pour nous et qui nous permet de spécifier la valeur des variables du templagte (dans notre cas, les variable title et greetings). Puis nous indiquerons au framework le template qu’il doit instancier en renvoyant son nom du template sans extention (dans notre cas, home).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package ch.epai.ict.m295.firstserver;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
public class HomeController {
@GetMapping(value = "/", produces = "text/html")
public String handleGetHomeHtml(Model model) {
// Récupère des données d'une manière ou d'une autre
String title = "Page d'accueil";
String message = "Bienvenue sur la page d'accueil !";
// Assigne les valeurs des variables du template. Il n'est pas nécessaire que le nom de la
// variable Java soit le même que celui de la variable du tamplate.
model.addAttribute("title", title);
model.addAttribute("greetings", message);
return "home";
}
}
Attention : Pour cette exemple, il ne faut pas ajouter l’annotation @ResponseBody. En effet, la valeur renvoyée par la méthode n’est pas le contenu de la réponse HTTP, mais le nom du template que le framework doit utiliser pour produire ce contenu.
Redémarrez le serveur et naviguez une nouvelle fois vers l’URI http://localhost:8080/ pour voir le résultat.
Répondre avec un contenu JSON
Par défaut, le framework suppose que la réponse contient une représentation HTML (text/html). Si l’on veut renvoyer une représentation JSON de la même ressource, on crée un deuxième gestionnaire de requête en indiquant que le contenu est de type application/json.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package ch.epai.ict.m295.firstserver;
import java.io.IOException;
import java.io.PrintWriter;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@Controller
public class HomeController {
@GetMapping(value = "/", produces = "text/html")
public String handleGetHomeHtml(Model model) {
// Récupère des données d'une manière ou d'une autre
String title = "Page d'accueil";
String message = "Bienvenue sur la page d'accueil !";
model.addAttribute("title", title);
model.addAttribute("greetings", message);
return "home";
}
@ResponseBody
@GetMapping(value = "/", produces = "application/json")
public String handleGetHomeJson() throws IOException {
// Récupère des données d'une manière ou d'une autre
String title = "Page d'accueil";
String message = "Bienvenue sur la page d'accueil !";
StringBuilder out = new StringBuilder();
out.append("{");
out.append(String.format(" \"title\" : \"%s\"", title));
out.append(String.format(" \"content\" : \"%s\"", message));
out.append("}");
// La valeur de retour est le contenu de la réponse (response body)
return out.toString();
}
}
Redémarrez le serveur et navigez une nouvelle fois vers http://localhost:8080/. Vous remarquez que pour le navigateur le serveur renvoie une représentation HTML.
Pour obtenir une représentation JSON, on peut utiliser l’utilitaire Postman. Installez l’utilitaire et créez un compte si ce n’est pas déjà fait, puis créez une nouvelle requête HTTP pour l’URI http://localhost:8080/. Avant d’envoyer la requête, affichez les entêtes, désélectionnez l’entête Accept: */* et ajoutez un entête accept: application/json.
Produire du JSON directement avec des objets
Jusque là, nous avons produit la représentation JSON de la ressource directement sous la forme d’une chaîne de caractère. Cela fonctionne, mais ce n’est pas très pratique. Comme JSON est un moyen de représenter des objets et des tableaux, nous pouvons utiliser le framework pour produire une représentation JSON à partir d’un objet ou d’une liste Java.
Définissons par exemple, la classe Message:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package ch.epai.ict.m295.firstserver;
public class Message {
private String title;
private String content;
public Message(String title, String content) {
this.title = title;
this.content = content;
}
public String getTitle() {
return title;
}
public String getContent() {
return content;
}
}
Nous pouvons maintenant renvoyer une instance de cette classe comme contenu de notre réponse et le framework se charge de produire une représentation JSON de cet objet en utilisant le nom des variables membres comme nom des attributs (ici, title et content).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package ch.epai.ict.m295.firstserver;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class HomeController {
@GetMapping(value = "/")
public String handleGetHomeHtml(Model model) {
// Récupère des données d'une manière ou d'une autre
String title = "Page d'accueil";
String data = "Bienvenue sur la page d'accueil !";
model.addAttribute("title", title);
model.addAttribute("greetings", data);
// La valeur de retour est le nom du template
return "home";
}
@GetMapping(value = "/", produces = "application/json")
@ResponseBody
public Message handleGetHomeJson() {
// Récupère des données d'une manière ou d'une autre
String title = "Page d'accueil";
String data = "Bienvenue sur la page d'accueil !";
Message message = new Message(title, data);
return message;
}
}
Redémarrez une dernière fois l’application et assurez-vous qu’elle fonctionne correctement dans le navigateur et dans Postman.