Dopo aver mostrato nell’articolo Server TCP con Vert.x come realizzare un server TCP con Vert.x, proseguiamo la serie sul framework implementando un server ed un client HTTP. Come sarà evidente nel seguito la modalità di utilizzo del framework a tale scopo è molto simile a quella vista nel caso del server TCP. Le versione del protocollo HTTP supportati sono HTTP/1.0, HTTP/1.1 e HTTP/2, con stile di programmazione molto simili. In particola per la versione 2 vertx supporta sia la versione basata su TSL (h2) che quella basata su TCP (h2c).
Creiamo quindi un progetto Maven ed inseriamo nel pom.xml
la dipendenza al framework:
1 2 3 4 5 |
<dependency> <groupId>io.vertx</groupId> <artifactId>vertx-core</artifactId> <version>3.5.4</version> </dependency> |
Implementazione del Server HTTP
Analogamente al caso TCP, procediamo creando la classe MainHttpServer()
ed implementando i tre step di:
- Creazione e configurazione del server HTTP;
- Definizione dell’handler di gestione delle richieste;
- Avvio del server.
Creazione e Configurazione del Server
Per istanziare il server HTTP è sufficiente invocare il metodo createHttpServer()
sull’istanza Vertx, eventualmente configurandolo con un oggetto di tipo HttpServerOptions
:
1 2 3 4 5 6 7 8 9 10 |
public static void main(String[] args) { Vertx vertx = Vertx.vertx(); HttpServerOptions options = new HttpServerOptions() .setPort(9090) .setHost("localhost"); HttpServer server = vertx.createHttpServer(options); } |
In particolare per abilitare la versione 2 del protocollo HTTP è sufficiente specificare le seguenti opzioni:
1 2 3 4 |
HttpServerOptions options = new HttpServerOptions() .setUseAlpn(true) .setSsl(true) .setKeyStoreOptions(new JksOptions().setPath("/path/to/my/keystore")); |
in cui il metodo setUseAlpn() abilita il protocollo ALPN (Application-Layer Protocol Negotiation) che è un’estensione di TLS che negozia il protocollo prima che il client e il server inizino a scambiarsi i dati.
Gestione delle Request
Successivamente definiamo un handler che dovrà gestire la comunicazione nel momento in cui una nuova connessione viene aperta. A tale scopo la classe HttpServer
fornisce il metodo requestHandler()
:
1 2 3 4 5 6 7 8 9 10 11 12 |
server.requestHandler( request -> { System.out.println( "URI: " + request.uri() ); System.out.println( "METHOD: " + request.method() ); System.out.println( "VERSIONE: " + request.version() ); System.out.println( "PATH: " + request.path() ); System.out.println( "QUERY: " + request.query() ); request.handler(buffer -> { System.out.println( buffer.toString() ); }); }); |
L’handler riceve in input on oggetto di tipo HttpServerRequest
che consente di interagire con la request HTTP, ad esempio per recuperare l’URI, il path i parametri di invocazione, gli header, etc. Inoltre il body della request può essere letto registrando un handler sulla request che sarà invocato dal framework ad ogni chunk (pezzetto) di contenuto ricevuto. La necessità dell’utilizzo di un handler per il body è dovuto al fatto che l’handler della request è invocato non appena la connessione è stabilita e gli header sono stati ricevuti dal server, ed in tale istante il body non è ancora disponibile.
Se si ha la necessità di collezionare tutto il body in memoria prima della sua elaborazione il programmatore ha due possibilità. La prima è quella di utilizzare un buffer in memoria in cui i diversi chunk saranno appesi ad ogni invocazione dell’handler. In tale caso il framework mette a disposizione un nuovo metodo endHandler()
che consente di registrare un handler invocato al termine della ricezione di tutto il body.
1 2 3 4 5 6 7 8 9 |
Buffer totalBuffer = Buffer.buffer(); request.handler(buffer -> { totalBuffer.appendBuffer(buffer); }); request.endHandler( v -> { System.out.println( totalBuffer.toString() ); }); |
La seconda possibilità è di lasciar fare questa operazione al framework ed utilizzare direttamente il metodo bodyHandler()
che questa volta consente di registrare un handler invocato solamente quando tutto il body è ricevuto.
1 2 3 |
request.bodyHandler(buffer -> { System.out.println( "Received " + buffer.length() + " bytes"); }); |
Avvio del Server
Infine dobbiamo avviare il server TCP invocando il metodo listen()
. Ancora una volta è possibile definire un handler per determinare l’esito dell’operazione, che altrimenti non ci sarebbe noto.
1 2 3 4 5 6 7 |
server.listen( res -> { if (res.succeeded()) { System.out.println("Server is now listening!"); } else { System.out.println("Failed to bind!"); } }); |
Test di Connessione
Facciamo un test eseguendo il progetto e successivamente inserendo in un browser l’url http://localhost:9090/httpvertx?q=1&q=2. Sulla console troveremo stampata la richiesta inviata dal browser:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
Server is now listening! URI: /httpvertx?q=1&q=2 METHOD: GET VERSIONE: HTTP_1_1 PATH: /httpvertx QUERY: q=1&q=2 q: 1 q: 2 Host: localhost:9090 Connection: keep-alive Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 Accept-Encoding: gzip, deflate, br Accept-Language: it-IT,it;q=0.9,en-US;q=0.8,en;q=0.7 |
Invio della Risposta
Dall’oggetto HttpServerRequest
è possibile estrarre l’oggetto HttpServerResponse
, da utilizzare per inviare una risposta al client, attraverso l’invocazione del metodo response()
. Di default lo status code restituito al client è 200 ma se necessario, ad esempio in caso di errore, è possibile indicare un codice differente mediante il metodo setStatusCode()
. Inoltre con il metodo setStatusMessage()
è anche possibile specificare un messaggio personalizzato di risposta. Se non si ha necessità di inviare un body al client è possibile poi utilizzare il metodo end()
per chiudere e terminare la comunicazione.
1 2 3 4 5 6 7 |
request.bodyHandler(buffer -> { System.out.println( "Received " + buffer.length() + " bytes"); HttpServerResponse response = request.response(); response.setStatusCode( 200 ); response.end(); }); |
Diversamente se si vuole inviare un contenuto nel body come risposta al client è necessario innanzitutto valorizzare l’header “Content-Length” e poi inviare il metodo write()
. In alternativa possiamo utilizzare il chunking HTTP per frammentare un contenuto particolarmente lungo e del quale non si ha a priori conoscenza della lunghezza.
1 2 3 4 5 6 7 8 9 |
request.bodyHandler(buffer -> { System.out.println( "Received " + buffer.length() + " bytes"); HttpServerResponse response = request.response(); response.setStatusCode( 200 ); response.setChunked(true); response.write( "Tanks!"); response.end(); }); |
Implementazione del Client HTTP
Creiamo la classe MainHttpClient
ed il relativo metodo main()
. Utilizziamo quindi il metodo createHttpClient()
dell’istanza Vertx per generare un oggetto di tipo HttpClient
che configureremo attraverso un oggetto HttpClientOptions
.
1 2 3 4 5 6 7 8 9 10 |
public static void main(String[] args) { Vertx vertx = Vertx.vertx(); HttpClientOptions options = new HttpClientOptions() .setDefaultHost("localhost") .setDefaultPort(9090); HttpClient client = vertx.createHttpClient(options); } |
Tra le varie opzioni di configurazione disponibili i metodo setDefaultHost()
e setDefaultPort()
consentono di non dover specificare ad ogni invocazioni l’host e la porta di connessione.
L’oggetto HttpClient
implementa tanti metodi quanti sono i principali metodi HTTP, ovvero un metodo get()
per HTTP GET, uno put()
per HTTP PUT, uno post()
per il HTTP POST, e così via. E’ possibile però utilizzare anche un metodo generico request()
che richiederà però di specificare il metodo HTTP desiderato.
1 2 3 4 |
HttpClientRequest request = client.request( HttpMethod.PUT, "/httpvertx?q=1&q=2", response -> { System.out.println("Received response with status code " + response.statusCode()); }); |
Infine è possibile inserire contenuto alla request valorizzando innanzitutto l’header content-length
(a meno che non si utilizzi il chunk, come visto sopra) e quindi utilizzando il metodo write()
.
1 2 3 4 |
request.putHeader("content-length", "12") .putHeader("content-type", "text/plain") .write("Hello world!") .end(); |
Una volta avviate le due classi MainHttpServer
e MainHttpClient
sulla console del server verrà stampato:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
Server is now listening! URI: /httpvertx?q=1&q=2 METHOD: PUT VERSIONE: HTTP_1_1 PATH: /httpvertx QUERY: q=1&q=2 q: 1 q: 2 content-length: 12 content-type: text/plain Host: localhost:9090 Hello world! Received 12 bytes |
Codice Sorgente
Il codice sorgente con tutti gli esempi mostrati è scaricabile qui vertx-http.