概要
- Java 標準ライブラリの com.sun.net.httpserver パッケージを使用して簡易的な Web サーバのサンプルを作る
com.sun.net.httpserver とは
com.sun.net.httpserver は HTTP サーバを構築可能な Java パッケージ。
com.sun.net.httpserver (Java SE 14 & JDK 14)
組込みのHTTPサーバーの構築に使用できる、単純で高度なHTTPサーバーAPIを提供します。 「HTTP」と「HTTPS」の両方がサポートされています。 APIは、RFC 2616 (HTTP 1.1)およびRFC 2818 (HTTP over TLS)の実装の一部を提供します。 このAPIで提供されないHTTP機能は、APIを使用してアプリケーション・コードで実装できます。
プログラマは、HttpHandlerインタフェースを実装する必要があります。 このインタフェースは、クライアントからの着信要求を処理するために呼び出されるコールバックを提供します。 HTTP要求とその応答を交換といいます。 HTTP交換は、HttpExchangeクラスによって表されます。 HttpServerクラスは、着信TCP接続の待機に使用され、これらの接続での要求をサーバーに登録されているハンドラにディスパッチします。
com.sun.net.httpserver パッケージは jdk.httpserver モジュール に属しているので注意。
Java Development Kit (JDK) APIはJDK固有のものであり、必ずしもJava SEプラットフォームのすべての実装で使用できるとは限りません。 これらのAPIは、名前がjdkで始まるモジュール内にあります。
今回の環境
- macOS 10.15.5 Catalina
- Java 14 (AdoptOpenJDK 14.0.1)
$ javac --version
javac 14.0.1
$ java --version
openjdk 14.0.1 2020-04-14
OpenJDK Runtime Environment AdoptOpenJDK (build 14.0.1+7)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 14.0.1+7, mixed mode, sharing)
Web サーバのサンプルコード
以下のソースコードを MyServer.java というファイル名で保存する。
import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import java.nio.charset.StandardCharsets;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
public class MyServer {
public static void main(String args[]) throws IOException {
// HTTP サーバを起動
int port = 8000;
HttpServer server = HttpServer.create(new InetSocketAddress(port), 0);
server.createContext("/", new MyHandler());
System.out.println("MyServer wakes up: port=" + port);
server.start();
}
// HTTP リクエストを処理するために呼び出されるハンドラ
private static class MyHandler implements HttpHandler {
// HTTP リクエストを処理する
public void handle(HttpExchange t) throws IOException {
System.out.println("**************************************************");
// 開始行を取得
String startLine =
t.getRequestMethod() + " " +
t.getRequestURI().toString() + " " +
t.getProtocol();
System.out.println(startLine);
// リクエストヘッダを取得
Headers reqHeaders = t.getRequestHeaders();
for (String name : reqHeaders.keySet()) {
System.out.println(name + ": " + reqHeaders.getFirst(name));
}
// リクエストボディを取得
InputStream is = t.getRequestBody();
byte[] b = is.readAllBytes();
is.close();
if (b.length != 0) {
System.out.println(); // 空行
System.out.println(new String(b, StandardCharsets.UTF_8));
}
// レスポンスボディを構築
// (ここでは Java 14 から正式導入された Switch Expressions と
// Java 14 でプレビュー機能として使えるヒアドキュメント的な Text Blocks 機能を使ってみる)
String resBody = switch (t.getRequestURI().toString()) {
case "/hello" -> "{\"message\": \"Hello, World!\"}";
case "/foobar" -> """
{
"foo": "bar",
"ふー": "ばー"
}""";
default -> "{}";
};
// Content-Length 以外のレスポンスヘッダを設定
Headers resHeaders = t.getResponseHeaders();
resHeaders.set("Content-Type", "application/json");
resHeaders.set("Last-Modified",
ZonedDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.RFC_1123_DATE_TIME));
resHeaders.set("Server",
"MyServer (" +
System.getProperty("java.vm.name") + " " +
System.getProperty("java.vm.vendor") + " " +
System.getProperty("java.vm.version") + ")");
// レスポンスヘッダを送信
int statusCode = 200;
long contentLength = resBody.getBytes(StandardCharsets.UTF_8).length;
t.sendResponseHeaders(statusCode, contentLength);
// レスポンスボディを送信
OutputStream os = t.getResponseBody();
os.write(resBody.getBytes());
os.close();
}
}
}
コンパイル
javac コマンドでコンパイルする。
Java 14 でプレビュー機能として使える Text Blocks を使っているのでオプションで --enable-preview --release 14 を指定する必要がある。
$ javac --enable-preview --release 14 MyServer.java
注意:MyServer.javaはプレビュー言語機能を使用します。
注意:詳細は、-Xlint:previewオプションを指定して再コンパイルしてください。
コンパイルすると MyServer.class ファイルが作られる。
$ ls
MyServer.class MyServer.java
Web サーバを起動
java コマンドで MyServer を指定すると Web サーバが起動する。
$ java --enable-preview MyServer
MyServer wakes up: port=8000
Web サーバに GET リクエスト
curl コマンドで /foobar に HTTP GET リクエストする。
レスポンスとして JSON が返ってくるのを確認できる。
$ curl -v http://localhost:8000/foobar
* Trying ::1:8000...
* Connected to localhost (::1) port 8000 (#0)
> GET /foobar HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.70.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Server: MyServer (OpenJDK 64-Bit Server VM AdoptOpenJDK 14.0.1+7)
< Date: Tue, 23 Jun 2020 23:48:57 GMT
< Last-modified: Tue, 23 Jun 2020 23:48:57 GMT
< Content-type: application/json
< Content-length: 40
<
{
"foo": "bar",
"ふー": "ばー"
* Connection #0 to host localhost left intact
}
MyServer プログラム側では HTTP GET リクエスト情報を出力している。
**************************************************
GET /foobar HTTP/1.1
Accept: */*
Host: localhost:8000
User-agent: curl/7.70.0
Web サーバに POST リクエスト
curl コマンドで /hello に JSON データを HTTP POST リクエストする。
レスポンスとして JSON が返ってくるのを確認できる。
$ curl -v -H "Content-Type: application/json" -d '{"foobar":"ふーばー"}' http://localhost:8000/hello
* Trying ::1:8000...
* Connected to localhost (::1) port 8000 (#0)
> POST /hello HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.70.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 25
>
* upload completely sent off: 25 out of 25 bytes
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Server: MyServer (OpenJDK 64-Bit Server VM AdoptOpenJDK 14.0.1+7)
< Date: Wed, 24 Jun 2020 20:31:18 GMT
< Last-modified: Wed, 24 Jun 2020 20:31:18 GMT
< Content-type: application/json
< Content-length: 28
<
* Connection #0 to host localhost left intact
{"message": "Hello, World!"}
MyServer プログラム側では HTTP POST リクエスト情報を出力している。
リクエストボディに指定された JSON が出力されている。
**************************************************
POST /hello HTTP/1.1
Accept: */*
Host: localhost:8000
User-agent: curl/7.70.0
Content-type: application/json
Content-length: 25
{"foobar":"ふーばー"}