1. 序章

NanoHTTPD は、Javaで記述されたオープンソースの軽量Webサーバーです。

このチュートリアルでは、その機能を調べるためにいくつかのRESTAPIを作成します。

2. プロジェクトの設定

しましょう NanoHTTPDコア依存関係pom.xmlに追加します。

<dependency>
    <groupId>org.nanohttpd</groupId>
    <artifactId>nanohttpd</artifactId>
    <version>2.3.1</version>
</dependency>

単純なサーバーを作成するには、 NanoHTTPD を拡張し、そのserveメソッドをオーバーライドする必要があります。

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");
    }
}

実行中のポートを8080として定義し、サーバーをデーモンとして機能するように定義しました(読み取りタイムアウトなし)。

アプリケーションを起動すると、URL http:// localhost:8080/Helloworldメッセージを返します。 NanoHTTPD.Response オブジェクトを作成する便利な方法として、 NanoHTTPD#newFixedLengthResponseメソッドを使用しています。

cURLでプロジェクトを試してみましょう。

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

3. REST API

HTTPメソッドの方法で、NanoHTTPDは、GET、POST、PUT、DELETE、HEAD、TRACE、およびその他のいくつかを許可します。

簡単に言えば、enumメソッドを介してサポートされているHTTP動詞を見つけることができます。 これがどのように機能するか見てみましょう。

3.1. HTTP GET

まず、GETを見てみましょう。 たとえば、アプリケーションがGETリクエストを受信したときにのみコンテンツを返したいとします。

Javaサーブレットコンテナとは異なり、 doGet メソッドは使用できません。代わりに、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. HTTPPOST

以前、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");
}
リクエスト本文を要求する前に、最初にparseBodyメソッドを呼び出したことに注意してください。これは、後で取得するためにリクエスト本文をロードしたかったためです。

cURLコマンドに本文を含めます。

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

残りのHTTPメソッドは本質的に非常に似ているため、スキップします。

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

使用する CORS、クロスドメイン通信を有効にします。 最も一般的な使用例は、別のドメインからのAJAX呼び出しです。
 
使用できる最初のアプローチは、すべてのAPIでCORSを有効にすることです。 を使用する-cors 引数として、すべてのドメインへのアクセスを許可します。 許可するドメインを定義することもできます –cors =” http://dashboard.myApp.com http://admin.myapp.com”.
 
2番目のアプローチは、個々のAPIに対してCORSを有効にすることです。 使い方を見てみましょう addHeader これを達成するには:
@Override 
public Response serve(IHTTPSession session) {
    Response response = newFixedLengthResponse("Hello world"); 
    response.addHeader("Access-Control-Allow-Origin", "*");
    return response;
}

cURL を実行すると、CORSヘッダーが返されます。

> 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. ファイルのアップロード

NanoHTTPDには、ファイルのアップロードに対して個別の依存関係があるため、プロジェクトに追加しましょう。

<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>

servlet-api依存関係も必要であることに注意してください(そうしないと、コンパイルエラーが発生します)。

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 'filename=@/pathToFile.txt' 'http://localhost:8080'
Uploaded files: 1

6. 複数のルート

nanolet はサーブレットに似ていますが、プロファイルが非常に低くなっています。 それらを使用して、単一のサーバーによって提供される多くのルートを定義できます(1つのルートを持つ前の例とは異なります).

まず、nanoletに必要な依存関係を追加しましょう。

<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
    }
}

次のステップは、addMappingsメソッドを定義することです。 いくつかのハンドラーを定義しましょう。 

最初のものは IndexHandlerクラスから「/」パスへ。 このクラスにはNanoHTTPDライブラリが付属しており、デフォルトで HelloWorldメッセージを返します。 別の応答が必要な場合は、getTextメソッドをオーバーライドできます。

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

そして、新しいルートをテストするために、次のことができます。

> curl 'http://localhost:8080' 
<html><body><h2>Hello world!</h3></body></html>

次に、新しいものを作成しましょう UserHandler 既存のDefaultHandlerを拡張するクラス。 そのルートは/usersになります。 ここでは、テキスト、MIMEタイプ、および返されたステータスコードを試してみました。

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

最後に、新しいStoreHandlerクラスを使用してGeneralHandlerを探索できます。 返されたメッセージを変更して、URLのstoreIdセクションを含めました。

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を使用するには、証明書が必要です。 詳細については、SSLに関する記事を参照してください。

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 を、Mavenプロジェクトの 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. WebSocket

NanoHTTPDはWebSocketsをサポートします。

WebSocketの最も単純な実装を作成しましょう。 このためには、NanoWSDクラスを拡張する必要があります。 また、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の代わりに、wscatを使用します。

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

9. 結論

要約すると、NanoHTTPDライブラリを使用するプロジェクトを作成しました。 次に、RESTful APIを定義し、HTTP関連の機能をさらに詳しく調べました。 最後に、WebSocketも実装しました。

これらすべてのスニペットの実装は、GitHub利用できます。