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");
}
cURLコマンドに本文を含めます。
> curl -X POST -d 'deliveryAddress=Washington nr 4&quantity=5''http://localhost:8080/'
Request body = deliveryAddress=Washington nr 4&quantity=5
残りのHTTPメソッドは本質的に非常に似ているため、スキップします。
4. クロスオリジンリソースシェアリング
@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に必要な依存関係を追加しましょう。
<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で利用できます。