Panduan untuk NanoHTTPD

1. Perkenalan

NanoHTTPD adalah server web open-source, ringan, dan ditulis dalam Java.

Dalam tutorial ini, kami akan membuat beberapa REST API untuk menjelajahi fitur-fiturnya.

2. Pengaturan Proyek

Mari tambahkan ketergantungan inti NanoHTTPD ke pom.xml kita :

 org.nanohttpd nanohttpd 2.3.1 

Untuk membuat server sederhana, kita perlu memperluas NanoHTTPD dan mengganti metode penyajiannya :

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 kami yang sedang berjalan sebagai 8080 dan server untuk bekerja sebagai daemon (tanpa batas waktu baca).

Setelah kita memulai aplikasi, URL // localhost: 8080 / akan mengembalikan pesan Hello world . Kami menggunakan metode NanoHTTPD # newFixedLengthResponse sebagai cara yang nyaman untuk membangun objek NanoHTTPD.Response .

Mari kita coba proyek kita dengan cURL:

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

3. REST API

Dalam cara metode HTTP, NanoHTTPD memungkinkan GET, POST, PUT, DELETE, HEAD, TRACE, dan beberapa lainnya.

Sederhananya, kita dapat menemukan verba HTTP yang didukung melalui metode enum. Mari kita lihat bagaimana hasilnya.

3.1. HTTP GET

Pertama, mari kita lihat GET. Katakanlah, sebagai contoh, kita ingin mengembalikan konten hanya ketika aplikasi menerima permintaan GET.

Tidak seperti kontainer Java Servlet, kami tidak memiliki metode doGet yang tersedia - sebagai gantinya, 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 sangat sederhana, bukan? Mari kita jalankan pengujian cepat dengan menggulung titik akhir baru kita dan melihat bahwa parameter permintaan itemId dibaca dengan benar:

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

3.2. HTTP POST

Kami sebelumnya bereaksi terhadap GET dan membaca parameter dari URL.

Untuk membahas dua metode HTTP paling populer, inilah saatnya bagi kita untuk menangani POST (dan membaca isi 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 bahwa sebelumnya ketika kita meminta isi permintaan, pertama kita memanggil metode parseBody . Itu karena kami ingin memuat badan permintaan untuk pengambilan nanti.

Kami akan menyertakan tubuh dalam perintah cURL kami :

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

Metode HTTP yang tersisa sangat mirip, jadi kami akan melewatkannya.

4. Berbagi Sumber Daya Lintas-Asal

Dengan menggunakan CORS, kami mengaktifkan komunikasi lintas domain. Kasus penggunaan yang paling umum adalah panggilan AJAX dari domain yang berbeda. Pendekatan pertama yang dapat kita gunakan adalah mengaktifkan CORS untuk semua API kita. Dengan menggunakan argumen - -cors , kami akan mengizinkan 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 kita lihat cara menggunakan addHeader untuk mencapai ini:
@Override public Response serve(IHTTPSession session) { Response response = newFixedLengthResponse("Hello world"); response.addHeader("Access-Control-Allow-Origin", "*"); return response; }

Sekarang saat kita menggulung , kita akan mendapatkan header CORS kita kembali:

> 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. Unggah File

NanoHTTPD memiliki ketergantungan terpisah untuk unggahan file, jadi mari tambahkan ke proyek kami:

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

Harap dicatat bahwa ketergantungan servlet-api juga diperlukan (jika tidak, kita akan mendapatkan kesalahan kompilasi).

Apa yang NanoHTTPD tunjukkan adalah kelas yang disebut 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, ayo kita coba:

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

6. Beberapa Rute

Sebuah nanolet seperti servlet tetapi memiliki profil yang sangat rendah. Kita dapat menggunakannya untuk menentukan banyak rute yang dilayani oleh satu server (tidak seperti contoh sebelumnya dengan satu rute).

Pertama, mari tambahkan ketergantungan yang diperlukan untuk nanolets :

 org.nanohttpd nanohttpd-nanolets 2.3.1 

Dan sekarang kita akan memperluas kelas utama kita menggunakan RouterNanoHTTPD, menentukan port kita yang sedang berjalan dan menjalankan server 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

Singkatnya, kami telah membuat proyek yang menggunakan pustaka NanoHTTPD. Selanjutnya, kami mendefinisikan RESTful API dan menjelajahi lebih banyak fungsi terkait HTTP. Pada akhirnya, kami juga menerapkan WebSocket.

Penerapan semua cuplikan ini tersedia di GitHub.