Panduan untuk NanoHTTPD

1. Pengenalan

NanoHTTPD adalah pelayan web sumber terbuka dan ringan yang ditulis dalam Java.

Dalam tutorial ini, kami akan membuat beberapa API REST untuk meneroka ciri-cirinya.

2. Penyediaan Projek

Mari tambahkan kebergantungan teras NanoHTTPD ke pom.xml kami :

 org.nanohttpd nanohttpd 2.3.1 

Untuk membuat pelayan mudah, kita perlu memperluas NanoHTTPD dan mengatasi kaedah servisnya :

public class App extends NanoHTTPD { public App() throws IOException { super(8080); start(NanoHTTPD.SOCKET_READ_TIMEOUT, false); } public static void main(String[] args ) throws IOException { new App(); } @Override public Response serve(IHTTPSession session) { return newFixedLengthResponse("Hello world"); } }

Kami mendefinisikan port berjalan sebagai 8080 dan pelayan berfungsi sebagai daemon (tiada habis waktu baca).

Sebaik sahaja kami memulakan aplikasi, URL // localhost: 8080 / akan mengembalikan mesej Hello world . Kami menggunakan kaedah NanoHTTPD # newFixedLengthResponse sebagai kaedah mudah untuk membina objek NanoHTTPD.Response .

Mari cuba projek kami dengan cURL:

> curl '//localhost:8080/' Hello world

3. REST API

Dengan kaedah kaedah HTTP, NanoHTTPD membenarkan GET, POST, PUT, DELETE, HEAD, TRACE, dan beberapa yang lain.

Ringkasnya, kita dapat mencari kata kerja HTTP yang disokong melalui kaedah enum. Mari lihat bagaimana ini berlaku.

3.1. HTTP DAPATKAN

Pertama, mari lihat GET. Katakan, sebagai contoh, bahawa kami ingin mengembalikan kandungan hanya apabila aplikasi menerima permintaan GET.

Tidak seperti kontena Java Servlet, kami tidak mempunyai kaedah doGet yang tersedia - sebaliknya, kami hanya memeriksa nilainya melalui getMethod :

@Override public Response serve(IHTTPSession session) { if (session.getMethod() == Method.GET) { String itemIdRequestParameter = session.getParameters().get("itemId").get(0); return newFixedLengthResponse("Requested itemId = " + itemIdRequestParameter); } return newFixedLengthResponse(Response.Status.NOT_FOUND, MIME_PLAINTEXT, "The requested resource does not exist"); }

Itu cukup mudah, bukan? Mari jalankan ujian pantas dengan melengkapkan titik akhir baru kami dan melihat bahawa item parameter permintaanId dibaca dengan betul:

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

3.2. POST HTTP

Kami sebelum ini bertindak balas terhadap GET dan membaca parameter dari URL.

Untuk merangkumi dua kaedah HTTP yang paling popular, sudah tiba masanya kita menangani POST (dan dengan itu membaca badan permintaan):

@Override public Response serve(IHTTPSession session) { if (session.getMethod() == Method.POST) { try { session.parseBody(new HashMap()); String requestBody = session.getQueryParameterString(); return newFixedLengthResponse("Request body = " + requestBody); } catch (IOException | ResponseException e) { // handle } } return newFixedLengthResponse(Response.Status.NOT_FOUND, MIME_PLAINTEXT, "The requested resource does not exist"); }
Perhatikan bahawa sebelum kita meminta badan permintaan, kita pertama kali memanggil kaedah parseBody . Ini kerana kami ingin memuatkan badan permintaan untuk pengambilan kemudian.

Kami akan memasukkan badan dalam arahan cURL kami :

> curl -X POST -d 'deliveryAddress=Washington nr 4&quantity=5''//localhost:8080/' Request body = deliveryAddress=Washington nr 4&quantity=5

Kaedah HTTP yang selebihnya sangat serupa, jadi kami akan melewatkannya.

4. Perkongsian Sumber Merentas Asal

Dengan menggunakan CORS, kami mengaktifkan komunikasi merentas domain. Kes penggunaan yang paling biasa adalah panggilan AJAX dari domain yang berbeza. Pendekatan pertama yang boleh kita gunakan adalah mengaktifkan CORS untuk semua API kita. Dengan menggunakan argumen - -cors , kami akan membenarkan akses ke semua domain. Kami juga dapat menentukan domain mana yang kami izinkan dengan –cors = ”// dashboard.myApp.com //admin.myapp.com” . Pendekatan kedua adalah mengaktifkan CORS untuk API individu. Mari lihat bagaimana menggunakan addHeader untuk mencapainya:
@Override public Response serve(IHTTPSession session) { Response response = newFixedLengthResponse("Hello world"); response.addHeader("Access-Control-Allow-Origin", "*"); return response; }

Sekarang apabila kita mengetuk , kita akan mendapat kembali tajuk CORS kita:

> 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 Content-Length: 11 Hello world

5. Muat Naik Fail

NanoHTTPD mempunyai kebergantungan tersendiri untuk memuat naik fail, jadi mari kita tambahkan ke projek kami:

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

Harap maklum bahawa pergantungan servlet-api juga diperlukan (jika tidak, kita akan mendapat ralat penyusunan).

Apa yang NanoHTTPD dedahkan adalah kelas yang dipanggil NanoFileUpload :

@Override public Response serve(IHTTPSession session) { try { List files = new NanoFileUpload(new DiskFileItemFactory()).parseRequest(session); int uploadedCount = 0; for (FileItem file : files) { try { String fileName = file.getName(); byte[] fileContent = file.get(); Files.write(Paths.get(fileName), fileContent); uploadedCount++; } catch (Exception exception) { // handle } } return newFixedLengthResponse(Response.Status.OK, MIME_PLAINTEXT, "Uploaded files " + uploadedCount + " out of " + files.size()); } catch (IOException | FileUploadException e) { throw new IllegalArgumentException("Could not handle files from API request", e); } return newFixedLengthResponse( Response.Status.BAD_REQUEST, MIME_PLAINTEXT, "Error when uploading"); }

Hei, mari kita mencubanya:

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

6. Pelbagai Laluan

A nanolet adalah seperti servlet tetapi mempunyai profil yang sangat rendah. Kita boleh menggunakannya untuk menentukan banyak laluan yang dilayan oleh pelayan tunggal (tidak seperti contoh sebelumnya dengan satu laluan).

Pertama, mari tambahkan kebergantungan yang diperlukan untuk nanolet :

 org.nanohttpd nanohttpd-nanolets 2.3.1 

Dan sekarang kami akan melanjutkan kelas utama kami menggunakan RouterNanoHTTPD, menentukan port berjalan kami dan menjalankan pelayan sebagai daemon.

The addMappings method is where we'll define our handlers:

public class MultipleRoutesExample extends RouterNanoHTTPD { public MultipleRoutesExample() throws IOException { super(8080); addMappings(); start(NanoHTTPD.SOCKET_READ_TIMEOUT, false); } @Override public void addMappings() { // todo fill in the routes } }

The next step is to define our addMappings method. Let's define a few handlers.

The first one is an IndexHandler class to “/” path. This class comes with the NanoHTTPD library and returns by default a Hello World message. We can override the getText method when we want a different response:

addRoute("/", IndexHandler.class); // inside addMappings method

And to test our new route we can do:

> curl '//localhost:8080' 

Hello world!

Secondly, let's create a new UserHandler class which extends the existing DefaultHandler. The route for it will be /users. Here we played around with the text, MIME type, and the status code returned:

public static class UserHandler extends DefaultHandler { @Override public String getText() { return "UserA, UserB, UserC"; } @Override public String getMimeType() { return MIME_PLAINTEXT; } @Override public Response.IStatus getStatus() { return Response.Status.OK; } }

To call this route we'll issue a cURL command again:

> curl -X POST '//localhost:8080/users' UserA, UserB, UserC

Finally, we can explore the GeneralHandler with a new StoreHandler class. We modified the returned message to include the storeId section of the URL.

public static class StoreHandler extends GeneralHandler { @Override public Response get( UriResource uriResource, Map urlParams, IHTTPSession session) { return newFixedLengthResponse("Retrieving store for id = " + urlParams.get("storeId")); } }

Let's check our new API:

> curl '//localhost:8080/stores/123' Retrieving store for id = 123

7. HTTPS

In order to use the HTTPS, we'll need a certificate. Please refer to our article on SSL for more in-depth information.

We could use a service like Let's Encrypt or we can simply generate a self-signed certificate as follows:

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

Next, we'd copy this keystore.jks to a location on our classpath, like say the src/main/resources folder of a Maven project.

After that, we can reference it in a call to NanoHTTPD#makeSSLSocketFactory:

public class HttpsExample extends NanoHTTPD { public HttpsExample() throws IOException { super(8080); makeSecure(NanoHTTPD.makeSSLSocketFactory( "/keystore.jks", "password".toCharArray()), null); start(NanoHTTPD.SOCKET_READ_TIMEOUT, false); } // main and serve methods }

And now we can try it out. Please notice the use of the —insecure parameter, because cURL won't be able to verify our self-signed certificate by default:

> curl --insecure '//localhost:8443' HTTPS call is a success

8. WebSockets

NanoHTTPD supports WebSockets.

Let's create the simplest implementation of a WebSocket. For this, we'll need to extend the NanoWSD class. We'll also need to add the NanoHTTPD dependency for WebSocket:

 org.nanohttpd nanohttpd-websocket 2.3.1 

For our implementation, we'll just reply with a simple text payload:

public class WsdExample extends NanoWSD { public WsdExample() throws IOException { super(8080); start(NanoHTTPD.SOCKET_READ_TIMEOUT, false); } public static void main(String[] args) throws IOException { new WsdExample(); } @Override protected WebSocket openWebSocket(IHTTPSession ihttpSession) { return new WsdSocket(ihttpSession); } private static class WsdSocket extends WebSocket { public WsdSocket(IHTTPSession handshakeRequest) { super(handshakeRequest); } //override onOpen, onClose, onPong and onException methods @Override protected void onMessage(WebSocketFrame webSocketFrame) { try { send(webSocketFrame.getTextPayload() + " to you"); } catch (IOException e) { // handle } } } }

Instead of cURL this time, we'll use wscat:

> wscat -c localhost:8080 hello hello to you bye bye to you

9. Conclusion

Ringkasnya, kami telah membuat projek yang menggunakan perpustakaan NanoHTTPD. Seterusnya, kami menentukan API RESTful dan meneroka lebih banyak fungsi berkaitan HTTP. Pada akhirnya, kami juga melaksanakan WebSocket.

Pelaksanaan semua coretan ini boleh didapati di GitHub.