En guide til NanoHTTPD

1. Introduktion

NanoHTTPD er en open source, letvægts, webserver skrevet i Java.

I denne vejledning opretter vi et par REST API'er for at udforske dens funktioner.

2. Opsætning af projekt

Lad os tilføje NanoHTTPD-kerneafhængigheden til vores pom.xml:

 org.nanohttpd nanohttpd 2.3.1 

For at oprette en simpel server skal vi udvide NanoHTTPD og tilsidesætte dens tjene metode:

public class App udvider NanoHTTPD {public App () kaster IOException {super (8080); start (NanoHTTPD.SOCKET_READ_TIMEOUT, falsk); } offentlig statisk ugyldig hoved (String [] args) kaster IOException {ny app (); } @ Override public Response serve (IHTTPSession session) {return newFixedLengthResponse ("Hej verden"); }}

Vi definerede vores kørende port som 8080 og server til at fungere som en dæmon (ingen timeout for læsning).

Når vi starter applikationen, URL'en // localhost: 8080 / vil returnere Hej Verden besked. Vi bruger NanoHTTPD # newFixedLengthResponse metode som en bekvem måde at opbygge en NanoHTTPD.Response objekt.

Lad os prøve vores projekt med cURL:

> krølle '// localhost: 8080 /' Hej verden

3. REST API

I vejen for HTTP-metoder tillader NanoHTTPD GET, POST, PUT, SLET, HEAD, TRACE og flere andre.

Kort sagt kan vi finde understøttede HTTP-verber via metoden enum. Lad os se, hvordan dette spiller ud.

3.1. HTTP FÅ

Lad os først se på GET. Sig for eksempel, at vi kun vil returnere indhold, når applikationen modtager en GET-anmodning.

I modsætning til Java Servlet-containere har vi ikke en doGet tilgængelig metode - i stedet kontrollerer vi bare værdien via getMethod:

@Override public Response serve (IHTTPSession session) {if (session.getMethod () == Method.GET) {String itemIdRequestParameter = session.getParameters (). Get ("itemId"). Get (0); returner newFixedLengthResponse ("Requested itemId =" + itemIdRequestParameter); } returner newFixedLengthResponse (Response.Status.NOT_FOUND, MIME_PLAINTEXT, "Den anmodede ressource findes ikke"); }

Det var ret simpelt, ikke? Lad os køre en hurtig test ved at krølle vores nye slutpunkt og se, at anmodningsparameteren itemId læses korrekt:

> curl '// localhost: 8080 /? itemId = 23Bk8' Anmodet itemId = 23Bk8

3.2. HTTP POST

Vi reagerede tidligere på en GET og læste en parameter fra URL'en.

For at dække de to mest populære HTTP-metoder er det tid for os at håndtere en POST (og således læse anmodningsorganet):

@Override public Response serve (IHTTPSession session) {if (session.getMethod () == Method.POST) {prøv {session.parseBody (nyt HashMap ()); String requestBody = session.getQueryParameterString (); returner newFixedLengthResponse ("Request body =" + requestBody); } fange (IOException | ResponseException e) {// handle}} returnere newFixedLengthResponse (Response.Status.NOT_FOUND, MIME_PLAINTEXT, "Den anmodede ressource findes ikke"); }
Bemærk, at før, da vi bad om anmodningsorganet, vi kaldte først parseBody metode. Det er fordi vi ønskede at indlæse anmodningsorganet til senere hentning.

Vi inkluderer en krop i vores krøller kommando:

> krølle -X POST -d 'deliveryAddress = Washington nr 4 & antal = 5' '// localhost: 8080 /' Forespørgsel = leveringAdresse = Washington nr 4 & antal = 5

De resterende HTTP-metoder har meget ens karakter, så vi springer dem over.

4. Ressourcedeling på tværs af oprindelse

Ved hjælp af CORS muliggør vi kommunikation på tværs af domæner. Den mest almindelige brugssag er AJAX-opkald fra et andet domæne. Den første tilgang, vi kan bruge, er at aktivere CORS til alle vores API'er. Bruger -cors argument giver vi adgang til alle domæner. Vi kan også definere, hvilke domæner vi tillader med –Cors = ”// dashboard.myApp.com //admin.myapp.com” . Den anden tilgang er at aktivere CORS til individuelle API'er. Lad os se, hvordan du bruger addHeader for at opnå dette:
@ Override public Response serve (IHTTPSession session) {Response response = newFixedLengthResponse ("Hej verden"); respons.addHeader ("Adgangskontrol-Tillad oprindelse", "*"); tilbagevenden svar }

Nu når vi krøller, får vi vores CORS-header tilbage:

> curl -v '// localhost: 8080' HTTP / 1.1 200 OK Content-Type: text / html Date: Thu, 13 Jun 2019 03:58:14 GMT Access-Control-Allow-Origin: * Connection: keep-alive Indholdslængde: 11 Hej verden

5. Upload af fil

NanoHTTPD har en separat afhængighed af filuploads, så lad os tilføje det til vores projekt:

 org.nanohttpd nanohttpd-apache-fileupload 2.3.1 javax.servlet javax.servlet-api 4.0.1 forudsat 

Bemærk, at servlet-api afhængighed er også nødvendig (ellers får vi en kompileringsfejl).

Hvad NanoHTTPD udsætter, er en klasse kaldet NanoFileUpload:

@ Override public Response serve (IHTTPSession session) {prøv {List files = new NanoFileUpload (new DiskFileItemFactory ()). ParseRequest (session); int uploadCount = 0; for (FileItem-fil: filer) {prøv {String fileName = file.getName (); byte [] fileContent = file.get (); Files.write (Paths.get (fileName), fileContent); uploadCount ++; } fange (Undtagelsesundtagelse) {// handle}} returnere newFixedLengthResponse (Response.Status.OK, MIME_PLAINTEXT, "Uploaded files" + uploadedCount + "out of" + files.size ()); } catch (IOException | FileUploadException e) {throw new IllegalArgumentException ("Kunne ikke håndtere filer fra API-anmodning", e); } returner newFixedLengthResponse (Response.Status.BAD_REQUEST, MIME_PLAINTEXT, "Fejl ved upload"); }

Hej, lad os prøve det:

> curl -F '[email protected] /pathToFile.txt' '// localhost: 8080' Uploadede filer: 1

6. Flere ruter

EN nanolet er som en servlet, men har en meget lav profil. Vi kan bruge dem til at definere mange ruter, der betjenes af en enkelt server (i modsætning til tidligere eksempler med en rute).

Lad os først tilføje den krævede afhængighed for nanoletter:

 org.nanohttpd nanohttpd-nanolets 2.3.1 

Og nu udvider vi vores hovedklasse ved hjælp af RouterNanoHTTPD, definer vores kørende port og få serveren kørt som en dæmon.

Det addMappings metoden er, hvor vi definerer vores håndterere:

offentlig klasse MultipleRoutesExample udvider RouterNanoHTTPD {public MultipleRoutesExample () kaster IOException {super (8080); addMappings (); start (NanoHTTPD.SOCKET_READ_TIMEOUT, falsk); } @Override public void addMappings () {// for at udfylde ruterne}}

Det næste trin er at definere vores addMappings metode. Lad os definere et par håndtere.

Den første er en IndexHandler klasse til “/” sti. Denne klasse leveres med NanoHTTPD-biblioteket og returnerer som standard a Hej Verden besked. Vi kan tilsidesætte getText metode, når vi ønsker et andet svar:

addRoute ("/", IndexHandler.class); // inde i addMappings-metoden

Og for at teste vores nye rute kan vi gøre:

> krølle '// localhost: 8080' 

Hej Verden!

For det andet, lad os oprette en ny UserHandler klasse, der udvider det eksisterende Standardhåndterer. Ruten for det vil være /brugere. Her spillede vi rundt med teksten, MIME-typen og statuskoden returneret:

offentlig statisk klasse UserHandler udvider DefaultHandler {@Override public String getText () {return "UserA, UserB, UserC"; } @ Override public String getMimeType () {return MIME_PLAINTEXT; } @ Override offentlig Response.IStatus getStatus () {return Response.Status.OK; }}

For at kalde denne rute udsteder vi en krøller kommando igen:

> krølle -X POST '// localhost: 8080 / brugeres UserA, UserB, UserC

Endelig kan vi udforske GeneralHandler med en ny StoreHandler klasse. Vi ændrede den returnerede besked for at inkludere storeId sektion af URL'en.

offentlig statisk klasse StoreHandler udvider GeneralHandler {@Override public Response get (UriResource uriResource, Map urlParams, IHTTPSession session) {return newFixedLengthResponse ("Henter butik til id =" + urlParams.get ("storeId")); }}

Lad os tjekke vores nye API:

> curl '// localhost: 8080 / stores / 123' Henter butik til id = 123

7. HTTPS

For at kunne bruge HTTPS har vi brug for et certifikat. Se vores artikel om SSL for mere detaljerede oplysninger.

Vi kunne bruge en tjeneste som Lad os kryptere, eller vi kan simpelthen generere et selvsigneret certifikat som følger:

> keytool -genkey -keyalg RSA -alias selvbestemt -keystore keystore.jks -storepass password -validity 360 -keysize 2048 -ext SAN = DNS: localhost, IP: 127.0.0.1 -validitet 9999

Dernæst kopierede vi dette keystore.jks til et sted på vores klassesti, som f.eks src / main / ressourcer mappe i et Maven-projekt.

Derefter kan vi henvise til det i et opkald til NanoHTTPD # makeSSLSocketFactory:

offentlig klasse HttpsExample udvider NanoHTTPD {public HttpsExample () kaster IOException {super (8080); makeSecure (NanoHTTPD.makeSSLSocketFactory ("/keystore.jks", "password" .toCharArray ()), null); start (NanoHTTPD.SOCKET_READ_TIMEOUT, falsk); } // hoved- og serveringsmetoder}

Og nu kan vi prøve det. Bemærk venligst brugen af ​​-usikker parameter, fordi krøller kan ikke bekræfte vores selvsignerede certifikat som standard:

> curl --insecure '// localhost: 8443' HTTPS-opkald er en succes

8. WebSockets

NanoHTTPD understøtter WebSockets.

Lad os oprette den enkleste implementering af en WebSocket. Til dette skal vi udvide NanoWSD klasse. Vi bliver også nødt til at tilføje NanoHTTPD afhængighed af WebSocket:

 org.nanohttpd nanohttpd-websocket 2.3.1 

Til vores implementering svarer vi bare med en simpel tekstnyttelast:

offentlig klasse WsdExample udvider NanoWSD {public WsdExample () kaster IOException {super (8080); start (NanoHTTPD.SOCKET_READ_TIMEOUT, falsk); } offentlig statisk ugyldig hoved (String [] args) kaster IOException {new WsdExample (); } @ Override beskyttet WebSocket openWebSocket (IHTTPSession ihttpSession) {returner ny WsdSocket (ihttpSession); } privat statisk klasse WsdSocket udvider WebSocket {public WsdSocket (IHTTPSession handshakeRequest) {super (handshakeRequest); } // tilsidesætte onOpen, onClose, onPong og onException metoder @ Override beskyttet ugyldigt onMessage (WebSocketFrame webSocketFrame) {prøv {send (webSocketFrame.getTextPayload () + "til dig"); } fange (IOException e) {// handle}}}}

I stedet for krøller denne gang bruger vi wscat:

> wscat -c localhost: 8080 hej hej til dig farvel til dig

9. Konklusion

For at opsummere det har vi oprettet et projekt, der bruger NanoHTTPD-biblioteket. Dernæst definerede vi RESTful API'er og udforskede flere HTTP-relaterede funktioner. I sidste ende implementerede vi også en WebSocket.

Implementeringen af ​​alle disse uddrag er tilgængelig på GitHub.