JEP 408, Un Simple Serveur Web arrive en JDK18!

Contexte

L’objectif est que le développeur Java puisse avoir la possibilité de lancer un serveur web, comme cela est possible en node ou python.

Le but est de fournir un serveur web simple pour renvoyer des pages statiques, sans support de CGI ou Servlet.

De même, il n’est pas là pour fournir un serveur évolué. Donc, pas de concurrence avec Eclipse Jetty, Netty et Apache Tomcat, ni avec Apache Httpd ou nginx.

Ligne de commande

L’une des nouveautés est l’introduction de la commande jwebserver

Donc, pour démarrer le serveur, il suffit de taper la commande suivante :

jwebserver

Nous obtenons les messages suivants :

Binding to loopback by default. For all interfaces use "-b 0.0.0.0" or "-b ::".
Serving /tmp and subdirectories on 127.0.0.1 port 8000
URL http://127.0.0.1:8000/

Le serveur écoute par défaut uniquement sur la boucle locale loopback. Un message nous invite à utiliser l’option pour préciser l’interface ou tous les interfaces.

D’autres options sont aussi disponibles (format court et format long):

  • -b ou --bind-address pour préciser l’interface réseau

  • -p ou --port pour préciser le numéro de port

  • -h ou --help pour afficher le message d’aide

  • -o ou --output pour afficher des informations lors de traitement des requêtes. Les valeurs possibles sont : none | info | verbose). Par défaut, c’est la valeur info.

  • -d ou --directory pour préciser le répertoire où les fichiers seront retournés. Par défaut, c’est le répertoire courant.

avertissement Derrière la commande jwebserver, c’est la commande java -m jdk.httpserver qui est exécuté en réalité. La nouvelle commande est présente uniquement pour simplifier l’usage.

Utilisation

Nous allons créer un répertoire temporaire sous /tmp/jdk18, par exemple, et nous créons un nouveau répertoire json à l’intérieur.

Je lance la commande en précisant ce répertoire /tmp/jdk18 comme répertoire racine

jwebserver -d /tmp/jdk18

Avec un navigateur, nous allons à l’url : http://localhost:8000/. Nous obtenons l’affichage suivant

jwebserver list documents

Sans index.html, nous obtenons une page HTML généré qui liste les fichiers et les répertoires de la ressource demandée.

Maintenant, nous créons une page index.hml avec le code suivant :

<h1>Hello for JWebserver</h1>

En rafraichissant la page, nous obtenons l’affichage suivant :

jwebserver page avec index

A ce stade, nous pouvons développer un contenu statique avec HTML, CSS et Javascript.

Simuler un partenaire json

Nous allons maintenant créer un fichier bordeaux.json dans le répertoire json avec le contenu suivant :

{
  "DD": {
    "lat":48.85341,
    "lng":2.3488
  },
  "DMS":{
    "lat":"48º51'12.28\" N","lng":"2º20'55.68\" E"
  },
  "geohash":"u09tvmqrep",
  "UTM":"31U 452230.101975 5411364.70052959"
}

Avec le navigateur, nous pouvons aller sur la page http://localhost:8000/json. Nous obtenons la liste avec le contenu du dossier json, dont notre fichier.

jwebserver page json index

Puis, en cliquant sur le nom bordeaux.json, nous obtenons la page suivante :

jwebserver page json

Les traces des appels sont visibles au niveau de la console :

127.0.0.1 - - [02/janv./2022:13:49:42 +0100] "GET /json/ HTTP/1.1" 200 -
127.0.0.1 - - [02/janv./2022:13:49:44 +0100] "GET /json/bordeaux.json HTTP/1.1" 200 -

astuce Cela fonctionne de la même manière avec un fichier XML.

En activant la sortie à verbose, nous avons les informations sur les en-têtes passées

127.0.0.1 - - [02/janv./2022:11:38:00 +0100] "GET /json/ HTTP/1.1" 200 -
Resource requested: /tmp/jdk18/json
> Accept-encoding: gzip, deflate
> Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
> Sec-fetch-dest: document
> Sec-fetch-user: ?1
> Connection: keep-alive
> Sec-fetch-site: none
> Host: localhost:8000
> Dnt: 1
> Sec-fetch-mode: navigate
> User-agent: Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0
> Accept-language: fr,en-US;q=0.7,en;q=0.3
> Upgrade-insecure-requests: 1
>
< Date: Thu, 02 Jan 2022 10:38:00 GMT
< Last-modified: Wed, 2 Jan 2022 09:38:10 GMT
< Content-type: text/html; charset=UTF-8
< Content-length: 195
<
127.0.0.1 - - [06/janv./2022:11:38:07 +0100] "GET /json/bordeaux.json HTTP/1.1" 200 -
Resource requested: /tmp/jdk18/json/bordeaux.json
> Accept-encoding: gzip, deflate
> Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
> Sec-fetch-dest: document
> Sec-fetch-user: ?1
> Referer: http://localhost:8000/json/
> Connection: keep-alive
> Sec-fetch-site: same-origin
> Host: localhost:8000
> Sec-fetch-mode: navigate
> Dnt: 1
> User-agent: Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0
> Accept-language: fr,en-US;q=0.7,en;q=0.3
> Upgrade-insecure-requests: 1
>
< Date: Thu, 02 Jan 2022 10:38:07 GMT
< Last-modified: Wed, 2 Jan 2022 09:28:49 GMT
< Content-type: application/json
< Content-length: 159
<

API

L’idée a été d’utiliser le module jdk.httpserver qui contient l’API public com.sun.net.httpserver. C’est l’implémentation du serveur web embarqué dans le JDK depuis 2006. Ce package est officiellement supporté.

En revanche, pour faciliter l’utilisation, une classe SimpleFileServer a été défini pour simplifier la création d’un serveur web, L’interface est la suivante :

package com.sun.net.httpserver;

public final class SimpleFileServer {
    public static HttpServer createFileServer(InetSocketAddress addr,
                                              Path rootDirectory,
                                              OutputLevel outputLevel) {...}
    public static HttpHandler createFileHandler(Path rootDirectory) {...}
    public static Filter createOutputFilter(OutputStream out,
                                            OutputLevel outputLevel) {...}
    ...
}

avertissement La possibilité de rajouter l’interface réseau a été ajoutée par rapport à la version initiale.

jshell

Nous pouvons utiliser la commande jshell pour démarrer le serveur web.

import com.sun.net.httpserver.*; (1)

var server = SimpleFileServer.createFileServer(new InetSocketAddress(8000),
  Path.of("/tmp/jdk18"),
  SimpleFileServer.OutputLevel.INFO); (2)
server.start() (3)
  1. Importer le package correspondant au serveur web

  2. Créer une instance de notre serveur web

  3. Démarrer le serveur web

Le tour est joué. Nous pouvons aussi ajouter un autre handler vers un autre chemin.

server.createContext("/logs",
  SimpleFileServer.createFileHandler(Path.of("/var/log")));

Et voilà, le nouveau répertoire est disponible :

jwebserver ajout logs

Vous pouvez aller plus loin en créant votre propre handler en implémentant l’interface com.sun.net.httpserver.HttpHandler, mais cela ne correspond pas l’objectif initial.