純規の暇人趣味ブログ

首を突っ込んで足を洗う

Jetty(組み込み)でこんな事したい時はどうするの?リスト

      2017/12/28    HimaJyun

一昨日紹介した奴を作ってる時の成果?です。

Jettyって便利なんだけど情報があんまり多くないんだよね、ましてや組み込みモードともなると。

Jettyでアレコレしたいよ

JavaプログラムでHTTP機能を内蔵する時にJettyが超便利なのは間違いありません。

便利というか、他に選択肢ないよね。情報が少なくてもやらざるを得ない状況はあるでしょう。という訳で少しでも情報を増やそうと思ふ。

使用するのはJetty 9.4.8、Java8、Maven、良くある組み合わせですね。

とりあえず良くありそうな基本的な設定から。

ポート番号の指定

new Server();する時にポート番号を渡す。

Server server = new Server(8080);

簡単。

サーバーバージョン表示したくない

定番ですね。

forでブンブン回してひたすらsetSendServerVersionをfalseにしていく

// Server server = new Server();
for(Connector c : server.getConnectors()) {
    for(ConnectionFactory f : c.getConnectionFactories()) {
        if(f instanceof  HttpConnectionFactory) {
            ((HttpConnectionFactory)f).getHttpConfiguration().setSendServerVersion(false); // <- コレ
        }
    }
}

ちなみにJava8のStreamでやるならflatMapを使う。

// Server server = new Server();
Stream.of(server.getConnectors())
    .flatMap(c -> c.getConnectionFactories().stream())
    .flatMap(f -> (f instanceof HttpConnectionFactory) ? Stream.of((HttpConnectionFactory) f) : Stream.empty())
    .forEach(f -> f.getHttpConfiguration().setSendServerVersion(false)); <- コレ

まだ始まったばかりなのに既にめんどくさい

Keep-Aliveの有効/無効

デフォルト有効みたいです、「明示的に有効にする」という事は出来ないみたい。

レスポンスヘッダに「Connection: Keep-Alive」はないが、これが必要なのはHTTP/1.0で、HTTP/1.1では指定されていなくてもKeep-Aliveを有効にするのが規格的に正しい挙動。

気になるなら「curl -v http://アドレス/ http://アドレス/」ってしてみるといいよ。「Re-using existing connection!」って表示されたら接続が再利用されている(Keep-Aliveが有効になっている)

無効にするなら、後述のカスタムヘッダを応答する方法で「Connection: close」を応答すればよい。

静的ファイル配信編

あんまり居ないと思うけど、静的ファイルを配信したい場合のアレコレ。

静的ファイル配信の基本形

基本的にはResourceHandlerを作成してServerに追加する。

HandlerList handlers = new HandlerList();

ResourceHandler resource = new ResourceHandler();
resource.setResourceBase("./webroot");

handlers.addHandler(resource);
server.setHandler(handlers);

ResourceHandlerに色々設定する、WebRootであればsetResourceBaseとか(ファイルを指定するのに引数がStringって嫌だなぁ……FileかPathが良い)

ファイル一覧の表示

世界で最初にこの機能を実装した人、何を思ってわざわざこんなお節介を……?

ResourceHandler#setDirectoriesListed()を設定する。

// 一覧表示しない
resource.setDirectoriesListed(false);
// 一覧表示する
resource.setDirectoriesListed(true);

これが役に立つ時って?

index.html以外も指定したい

ResourceHandler#setWelcomeFiles()で設定する。

resource.setWelcomeFiles(new String[]{ "index.html" });

引数がString配列ってのが地味にめんどい、String可変長引数にしてくれたらよかったのに。

ところで、ResourceHandlerってどちらにせよ静的ファイルしか返せないんだからindex.html以外を指定するような事はあるんだろうか?

キャッシュさせない

ResourceHandler#setCacheControl()を使う

resource.setCacheControl("no-store");

ちなみに「キャッシュさせない」のがno-storeであって、「キャッシュしても良いけど使う前に必ず最新か確認しろ」なのがno-cache

全然意味が違うので間違えちゃダメよん

込み入った事情編

込み入った事情があるような設定

バインドアドレスを指定したい

例えば「バックエンド用なので外部から接続されないように127.0.0.1にしたい」時とかかな?

ポート番号を指定していた場所にInetSocketAddressを指定する。

こんな感じ。

new Server(new InetSocketAddress("127.0.0.1",8080));

ただ、127.0.0.1にすると127.0.0.1でしか(::1では)アクセス出来なくなる。127.0.0.1と::1の両方にバインドさせるのはどうするんだろう?

レスポンスヘッダーを弄りたい

HeaderPatternRuleを使う。HeaderPatternRuleを使う場合はjetty-rewriteが必要。

<dependency>
    <groupId>org.eclipse.jetty</groupId>
    <artifactId>jetty-rewrite</artifactId>
    <version>9.4.8.v20171121</version>
</dependency>

でもって、例えば全てのレスポンスに「Connection: close」を付ける場合はこう

HandlerList handlers = new HandlerList();

// RewriteHandlerを用意
RewriteHandler rewrite = new RewriteHandler();
// HeaderPatternRuleを用意
HeaderPatternRule rule = new HeaderPatternRule();
rule.setPattern("/*");
rule.setName("Connection");
rule.setValue("close");
// RewriteHandlerにHeaderPatternRuleを追加
rewrite.addRule(rule);

handlers.addHandler(rewrite);
server.setHandler(handlers);

めんどくさいぞ……?!

setPatternが正規表現なのかglobなのかは知らない。「*.js」ってするとjsにだけマッチさせられる。

ロガーを変えたい

Javaロガー界隈がカオスなのは有名な話ですね。JettyはSlf4jを使っているので、ロガーを変えたい時はブリッジが必要です。

例として、Log4j2を使う場合はMavenにブリッジ(log4j-slf4j-impl)を追加。

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j-impl</artifactId>
    <version>2.10.0</version>
</dependency>

でもって、Slf4jを使うように設定する。

try {
    Log.setLog(new Slf4jLog());
    // 通常は必要になった段階で初期化されるが
    // 初期化を明示したい(初期化されるまで待ちたい)場合はinitialized()を呼ぶ
    //Log.initialized();
} catch (Exception e) {
    e.printStackTrace();
}

カオスになってきたね。

リクエストログをコンソールに出したい

Slf4jRequestLogをServer#setRequestLogにセットする。

server.setRequestLog(new Slf4jRequestLog());

厳密にはコンソールではなくSlf4jにログレベルinfoで送られているだけなので、ロガー側でコンソールに出力するように設定しておかなければならない。

めんどくさいね

Jetty、軽量かつ高機能なんだけどめんどくさいね。

今回紹介したのはほんの一例で、その気になればVirtualHostとかSSLとか、果てはHTTP/2やらWebSocketまで使えてしまう。

つまり、「めっちゃ設定めんどくさい」

とりあえず使うのはこれくらいだろうし、この記事はこれくらいで終わらせるとする。

 - プログラミング