NanoHTTPDのガイド

  • link:/category/programming/ [プログラミング]

  • REST

1. 前書き

https://github.com/NanoHttpd/nanohttpd[NanoHTTPD]は、Javaで書かれたオープンソースの軽量なWebサーバーです。
このチュートリアルでは、いくつかのREST APIを作成してその機能を調べます。

2. プロジェクトのセットアップ

<dependency>
    <groupId>org.nanohttpd</groupId>
    <artifactId>nanohttpd</artifactId>
    <version>2.3.1</version>
</dependency>
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");
    }
}
link:/curl-rest[cURL]でプロジェクトを試してみましょう。
> curl 'http://localhost:8080/'
Hello world

3. REST API

HTTPメソッドの方法では、NanoHTTPDはGET、POST、PUT、DELETE、HEAD、TRACE、およびその他のいくつかを許可します。
簡単に言えば、サポートされているHTTP動詞は、enumメソッドを介して見つけることができます。 これがどうなるか見てみましょう。

3.1. HTTP GET

まず、GETを見てみましょう。 たとえば、アプリケーションがGETリクエストを受信したときにのみコンテンツを返したいとします。
link:/intro-to-servlets[Java Servlet container]とは異なり、__ doGet __methodは使用できません。代わりに、_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");
}
それはかなり簡単でしたよね? 新しいエンドポイントをカールして簡単なテストを実行し、リクエストパラメータ_itemId_が正しく読み取られることを確認します。
> curl 'http://localhost:8080/?itemId=23Bk8'
Requested itemId = 23Bk8

3.2. HTTP POST

以前にGETに反応し、URLからパラメーターを読み取りました。
最も人気のある2つのHTTPメソッドをカバーするために、POSTを処理します(したがって、リクエスト本文を読み取ります)。
@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");
}
_cURL_コマンドに本文を含めます:
> curl -X POST -d 'deliveryAddress=Washington nr 4&quantity=5''http://localhost:8080/'
Request body = deliveryAddress=Washington nr 4&quantity=5
残りのHTTPメソッドは本質的に非常に似ているため、それらをスキップします。

4. クロスオリジンリソースシェアリング

link:/spring-cors []
_-cors–cors =” http://dashboard.myApp.com http://admin.myapp.com” _
_addHeader_
@Override
public Response serve(IHTTPSession session) {
    Response response = newFixedLengthResponse("Hello world");
    response.addHeader("Access-Control-Allow-Origin", "*");
    return response;
}
> curl -v 'http://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. ファイルアップロード

<dependency>
    <groupId>org.nanohttpd</groupId>
    <artifactId>nanohttpd-apache-fileupload</artifactId>
    <version>2.3.1</version>
</dependency>
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
    <scope>provided</scope>
</dependency>
https://search.maven.org/search?q=a:javax.servlet-api%20AND%20g:javax.servlet[_servlet-api_dependency]も必要であることに注意してください(それ以外の場合はコンパイルを取得します)エラー)。
NanoHTTPDが公開するのは、_NanoFileUpload_というクラスです。
@Override
public Response serve(IHTTPSession session) {
    try {
        List<FileItem> 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");
}
ちょっと、試してみましょう:
> curl -F '[email protected]/pathToFile.txt' 'http://localhost:8080'
Uploaded files: 1

6. 複数のルート

まず、必要なhttps://search.maven.org/search?q=a:nanohttpd-nanolets[_nanolets_の依存関係]を追加しましょう。
<dependency>
    <groupId>org.nanohttpd</groupId>
    <artifactId>nanohttpd-nanolets</artifactId>
    <version>2.3.1</version>
</dependency>
そして、_RouterNanoHTTPD、_を使用してメインクラスを拡張し、実行ポートを定義して、サーバーをデーモンとして実行します。
_addMappings_メソッドは、ハンドラーを定義する場所です。
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
    }
}
addRoute("/", IndexHandler.class); // inside addMappings method
そして、新しいルートをテストするために次のことができます。
> curl 'http://localhost:8080'
<html><body><h2>Hello world!</h3></body></html>
次に、新しい_UserHandler_を作成しましょう
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;
    }
}
このルートを呼び出すには、再度_cURL_コマンドを発行します。
> curl -X POST 'http://localhost:8080/users'
UserA, UserB, UserC
public static class StoreHandler extends GeneralHandler {
    @Override
    public Response get(
      UriResource uriResource, Map<String, String> urlParams, IHTTPSession session) {
        return newFixedLengthResponse("Retrieving store for id = "
          + urlParams.get("storeId"));
    }
}
新しいAPIを確認しましょう。
> curl 'http://localhost:8080/stores/123'
Retrieving store for id = 123

7. HTTPS

HTTPSを使用するには、証明書が必要です。 詳細については、https://www.baeldung.com/java-ssl [SSLに関する当社の記事]を参照してください。
https://letsencrypt.org/[Let's Encrypt]のようなサービスを使用するか、次のように単純に自己署名証明書を生成できます。
> 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
次に、この_keystore.jks_をクラスパス上の場所(たとえば、dMavenプロジェクトの_src / main / resources_フォルダーなど)にコピーします。
その後、_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
}
そして今、私たちはそれを試すことができます。 _cURL_はデフォルトで自己署名証明書を検証できないため、—_ insecure_パラメーターの使用に注意してください。
> curl --insecure 'https://localhost:8443'
HTTPS call is a success

8. WebSockets

NanoHTTPDはlink:/rest-vs-websockets[WebSockets]をサポートしています。
WebSocketの最も単純な実装を作成しましょう。 このために、_NanoWSD_クラスを拡張する必要があります。 また、https://search.maven.org/search?q = a:nanohttpd-websocket%20AND%20g:org.nanohttpd [WebSocketの_NanoHTTPD_依存関係:]を追加する必要もあります。
<dependency>
    <groupId>org.nanohttpd</groupId>
    <artifactId>nanohttpd-websocket</artifactId>
    <version>2.3.1</version>
</dependency>
実装のために、単純なテキストペイロードで返信します。
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
            }
        }
    }
}
今回は_cURL_の代わりに、_https://github.com/websockets/wscat [wscat] _を使用します。
> wscat -c localhost:8080
hello
hello to you
bye
bye to you

9. 結論

まとめると、NanoHTTPDライブラリを使用するプロジェクトを作成しました。 次に、RESTful APIを定義し、HTTP関連の機能をさらに調査しました。 最後に、WebSocketも実装しました。
これらのすべてのスニペットの実装は、https://github.com/eugenp/tutorials/tree/master/libraries-server [over on GitHub]で入手できます。