※本記事は、“Java 18’s Simple Web Server: A tool for the command line and beyond” の翻訳記事です。

2022年4月8日| 7分読む

Julia Boes


コーディング、テスト、プロトタイピング、デバッグをその場で行える新しい最小構成サーバーの活用法を学ぶ

 

Java 18のSimple Web Serverは、JEP 408jdk.httpserver モジュールに追加された最小構成のHTTP静的ファイル・サーバーです。このサーバーができるのは、1つのディレクトリ階層の静的ファイルをHTTP/1.1で公開することだけです。動的なコンテンツや、他のHTTPバージョンはサポートされていません。

このWebサーバーの仕様は、JDKをより親しみやすくするという包括的な目標に基づいています。。このサーバーは、最低限の機能のみを搭載しており、簡単に設定して早速使い始められるツールなので、目の前のタスクにすぐに全力で取りかかって集中できるようになります。また、シンプルな設計により、高機能な商用グレードのサーバーと混同されることはありません。そもそも本番環境向けには、はるかに優れたサーバーが存在しており、そのような用途でSimple Web Serverを使うべきではありません。しかし、プロトタイプを行う、その場でコーディングを試す、テストをするといった用途では、優れた力を発揮します。

このサーバーがサポートするのは、HEADおよびGETリクエスト・メソッドのみです。その他のリクエストを受け取ると、501 - Not Implemented405 - Not Allowedのレスポンスが返されます。HEADおよびGETリクエストは、次のように処理されます。

  • リクエストされたリソースがファイルなら、そのコンテンツを提供する
  • リクエストされたリソースがディレクトリでそこにインデックス・ファイルが存在するなら、そのインデックス・ファイルのコンテンツを提供する

  • 上記以外の場合は、ディレクトリの一覧を返す

 

jwebserverコマンドライン・ツール

jwebserver ツールは、次のオプションに対応しています。

jwebserver [-b bind address] [-p port] [-d directory]
           [-o none|info|verbose] [-h to show options]
           [-version to show version information]

すべてのオプションに短い表記と長い表記があり、ヘルプ・メッセージやバージョン情報を表示する慣用的なオプションもあります。使用できるオプションは次のとおりです。

  • -h or -? または --help: ヘルプ・メッセージを表示して終了します。
  • -b addr または --bind-address addr: バインドするアドレスを指定します。デフォルトは 127.0.0.1 または ::1 (ループバック)です。 -b 0.0.0.0 または -b ::を使うと、すべてのインタフェースに対してバインドすることができます。
  • -d dir または --directory dir: 公開するディレクトリを指定します。デフォルトはカレント・ディレクトリです。
  • -o level または --output level: 出力形式を指定します。レベルは、none, info, verboseのいずれかです。デフォルトは infoです。
  • -p port または --port port: リスニングするポートを指定します。デフォルトは8000です。
  • -version または --version: Simple Web Serverのバージョン情報を表示して終了します。

 

サーバーの起動

次のコマンドを実行すると、Simple Web Serverが起動します。

$ jwebserver

デフォルトでは、ループバック・アドレスとポート8000がバインドされ、カレント・ディレクトリが公開されます。起動に成功すると、サーバーがフォアグラウンドで動作し、System.out にローカル・アドレスと、公開対象のディレクトリの絶対パス(例: /cwd)を含むメッセージが表示されます。次に例を示します。

$ jwebserver
Binding to loopback by default. For all interfaces use "-b 0.0.0.0" or "-b ::".
Serving /cwd and subdirectories on 127.0.0.1 port 8000
URL http://127.0.0.1:8000/

 

サーバーの構成

それぞれのオプションを使うと、デフォルトの構成を変更することができます。たとえば、Simple Web Serverをすべてのインタフェースにバインドするには、次のようにします。

$ jwebserver -b 0.0.0.0
Serving /cwd and subdirectories on 0.0.0.0 (all interfaces) port 8000
URL http://123.456.7.891:8000/

警告:このコマンドを使うと、ネットワーク上のすべてのホストがこのサーバーにアクセスできるようになります。これを行うのは、サーバーによって機密情報が漏洩しないことが確実な場合のみにしてください。

もう1つ例を挙げましょう。ポート9000でサーバーを実行するには、次のようにします。

$ jwebserver -p 9000

デフォルトでは、すべてのリクエストがログとしてコンソールに表示されます。この出力は次のようになります。

127.0.0.1 - - [10/Feb/2021:14:34:11 +0000] "GET /some/subdirectory/ HTTP/1.1" 200 –

このログ出力は、-o オプションで変更することができます。デフォルトの設定は infoです。 verbose を設定すると、それに加えてリクエスト・ヘッダーやレスポンス・ヘッダーの情報、リクエストされたリソースの絶対パスが表示されます。

 

サーバーの停止

Simple Web Serverは、いったん正常に起動すると、停止するまで動作を続けます。UNIXプラットフォームでは、SIGINTシグナルを送ることでサーバーを停止することができます。これを行うには、ターミナル・ウィンドウで[Ctrl]+[C]を押します。

ここまででサーバーの起動、構成、停止の方法を説明してきましたが、これがjwebserverの持つ機能の全容です。最低限の機能しかありませんが、Web開発の一般的なユースケースやWebサービスのテストに対応できるだけの十分な構成を行うことができます。さらに、システム間でのファイルの共有や閲覧も可能です。

jwebserverツールはさまざまな状況で活躍してくれますが、既存のコードからSimple Web Serverのコンポーネントを使いたい場合や、サーバーをさらにカスタマイズしたい場合もあるかもしれません。そのようなときに利用できるのが、一連の新しいAPIポイントです。

 

新しいcom.sun.net.httpserver APIポイント

JEP 408では、このコマンドライン・ツールのシンプルさと、com.sun.net.httpserver APIの「自分ですべてを書く」アプローチとのギャップを埋めるため、サーバーの作成やカスタマイズに利用できる一連の新しいAPIポイントが導入されています(com.sun.net.httpserver パッケージは、2006年以降のJDKに含まれています)。

このサーバーの主要コンポーネントである新しいSimpleFileServer クラスには、3つの静的メソッドがあります。この3つのメソッドを使うと、サーバー・インスタンス、ファイル・ハンドラ、出力フィルタを簡単に取得することができます。そして必要に応じ、それぞれの機能をカスタマイズしたり、既存のコードと組み合わせたりすることができます。

サーバー・インスタンスの取得: createFileServer メソッドは、静的なファイル・サーバーを返します。その際に、バインドするアドレスとポート、公開するルート・ディレクトリ、出力レベルを指定します。返されたサーバーは、起動したり、さらに構成したりすることができます。次に例を示します。

注: 本記事のソース・コード例では、Javaの便利なRead-Eval-Print Loop(REPL)シェルである jshell を使用します。

jshell> import com.sun.net.httpserver.*;

jshell> var server = SimpleFileServer.createFileServer(new InetSocketAddress(8080), 
   ...> Path.of("/some/path"), OutputLevel.VERBOSE); 
jshell> server.start()

ファイル:ハンドラ・インスタンスの取得: createFileHandler メソッドは、指定したルート・ディレクトリを公開するファイル・ハンドラを返します。このハンドラを新規サーバーや既存サーバーに追加することができます。なお、ここですばらしいのは、オーバーロードされたHttpServer::create メソッドがAPIに追加されていることです。そのおかげで、ファイル・ハンドラを含むサーバーを1回の呼出しで初期化することができます。

jshell> var handler = SimpleFileServer.createFileHandler(Path.of("/some/path"));
jshell> var server = HttpServer.create(new InetSocketAddress(8080), 
   ...> 10, "/somecontext/", handler); 
jshell> server.start();

出力フィルタの取得:createOutputFilter メソッドは、出力ストリームと出力レベルを受け取り、ロギング・フィルタを返します。このフィルタを既存サーバーに追加することができます。

jshell> var filter = SimpleFileServer.createOutputFilter(System.out, 
   ...> OutputLevel.INFO); 
jshell> var server = HttpServer.create(new InetSocketAddress(8080), 
   ...> 10, "/somecontext/", new SomeHandler(), filter); 
jshell> server.start();

 

ハンドラの強化

サーバーのコンポーネントを簡単に取得できるようにしたSimple Web Serverチームは、次に、既存の com.sun.net.httpserver APIの連携機能を強化したいと考えました。特に力を入れたのが、ハンドラを作成して組み合わせることができるようにする機能です。これがリクエスト処理ロジックの中核となります。

これを行うために、HttpHandlers クラスが導入されました。このクラスには、2つの新しいメソッドがあります。

  • HttpHandlers::of ある決まった状態(ステータス・コード、一連のヘッダー、レスポンス本体)をまとめたレスポンス・ハンドラを返す
  • HttpHandlers::handleOrElse 条件によって2つのハンドラを組み合わせる

次に使い方の例を示します。

jshell> Predicate<Request> IS_GET = r -> r.getRequestMethod().equals("GET");

jshell> var jsonHandler = HttpHandlers.of(200, 
   ...> Headers.of("Content-Type", "application/json"),
   ...> Files.readString(Path.of("some.json")));

jshell> var notAllowedHandler = HttpHandlers.of(405, Headers.of("Allow", "GET"), "");

jshell> var handler = HttpHandlers.handleOrElse(IS_GET, jsonHandler, notAllowedHandler);

上記の例のjsonHandlerは、常にステータス・コード200、指定したヘッダー、レスポンス本体(指定したJSONファイルの内容)をまとめて返すレスポンス・ハンドラです。もう1つのnotAllowedHandlerは、常に405レスポンスを返します。組み合わせられたハンドラは、着信するリクエストのリクエスト・メソッドを条件に基づいてチェックし、そのリクエストを適切なハンドラに転送します。

以上のことから、この2つの新しいメソッドは、リクエスト処理のカスタム・ロジックを書く際に役立ちます。そのため、さまざまなテストやデバッグに活用することができます。

 

リクエストの状態への対応

テストやデバッグを行う場合、リクエストを処理する前に、リクエストの特定のプロパティを調べてそれに対応したい場合もあります。

これを実現するため、事前処理を行うフィルタを返すメソッド Filter.adaptRequestを使うことができます。このフィルタにより、リクエストのURI、メソッド、ヘッダーを読み込むことができ、必要に応じて変更することも可能です。

jshell> var filter = Filter.adaptRequest("Add Foo header", 
   ...> request -> request.with("Foo", List.of("Bar"))); 
jshell> var server = HttpServer.create(new InetSocketAddress(8080), 10, "/", someHandler, 
   ...> filter); 
jshell> server.start();

上記の例のフィルタは、すべての着信リクエストにFooヘッダーを追加してから、 someHandler に渡しています。この機能を使うと、簡単に既存のハンドラの対応と機能の拡張をすることができます。

 

高度な機能

Simple Web Serverでは、これらの新しいAPIポイントが利用できるので、さほど目立たないものの興味深い他の応用例にも手が届くようになります。たとえば、Simple File Server APIを使って、 .zip ファイル・システムやJavaランタイム・ディレクトリを公開するインメモリ・ファイル・サーバーを作成することができます。詳しい内容や、そういったサーバーなどの例のコード・スニペットは、筆者の最新記事「Simple Web Serverを扱う」(英語)をご覧ください。

 

まとめ

Simple Web Serverはプロトタイプ、デバッグ、テストのタスクに役立つように設計されたので、最小構成のコマンドライン・ツールと柔軟な一連のAPIポイントの組合せでこの目的を実現することを期待しています。 jwebserver ツールにより、基本的なサーバー機能がコマンドラインで実現します。一方、Simple Web Server APIポイントは、完全にカスタマイズできるサーバー・コンポーネントを提供します。そのため、あまり一般的でない使い方にも対応することができます。

 

より詳しい情報