com.sun.net.httpserver

Java9でパッケージcom.sun.net.httpserverがAPI仕様のメインドキュメントに加わった。
今までの版でもドキュメントは存在していたがオプショナルなドキュメントだった。
このパッケージの説明にはパッケージ内のクラスを利用したHTTPサーバの例が示されている。
この例はJava9になっても前の版と変わらず同じものが示されているのだが、
以前から少々気になっている箇所があり、この機会に書いておく。

誰でも見られる元の文書の例をそのまま引用しても仕方ないので、
少し補ってHTTPサーバとして動作する完結したものにしてみる。

import com.sun.net.httpserver.HttpServer;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;

public class FixedPhraseServer {
    public static void main(String[] args) throws IOException {
        HttpServer server = HttpServer.create(new InetSocketAddress("localhost", 50080), 0);
        server.createContext("/", exch -> {
            try (InputStream is = exch.getRequestBody()) {
                is.readAllBytes();
            }
            String res = "This is the response";
            exch.sendResponseHeaders(200, res.length());
            try (OutputStream os = exch.getResponseBody()) {
                os.write(res.getBytes());
            }
        });
        server.start();
    }
}

このHTTPサーバを起動した上で、

$ telnet localhost 50080
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
GET / HTTP/1.1
Host: localhost
Connection: close

HTTP/1.1 200 OK
Date: Thu, 30 Nov 2017 03:00:00 GMT
Content-length: 20

This is the responseConnection closed by foreign host.
$ 

確かに想定した動作になっている。
しかし、

            //String res = "This is the response";
            String res = "これが応答です";

応答本体のメッセージを上のようにすると、

$ telnet localhost 50080
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
GET / HTTP/1.1
Host: localhost
Connection: close

HTTP/1.1 200 OK
Date: Thu, 30 Nov 2017 03:01:25 GMT
Content-length: 7

Connection closed by foreign host.
$ 

応答本体がおかしなことになっている。

これはsendResponseHeadersメソッドにres.length()を渡しているからである。
ここは文字数でなく応答本体のバイト数を渡さなければならない。
したがって、

import com.sun.net.httpserver.HttpServer;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.nio.charset.Charset;

public class FixedPhraseServer {
    public static void main(String[] args) throws IOException {
        HttpServer server = HttpServer.create(new InetSocketAddress("localhost", 50080), 0);
        server.createContext("/", exch -> {
            try (InputStream is = exch.getRequestBody()) {
                is.readAllBytes();
            }
            byte[] res = "これが応答です".getBytes(Charset.forName("UTF-8"));
            exch.getResponseHeaders().add("Content-Type", "text/plain; charset=utf-8");
            exch.sendResponseHeaders(200, res.length);
            try (OutputStream os = exch.getResponseBody()) {
                os.write(res);
            }
        });
        server.start();
    }
}

と変更すると、

$ telnet localhost 50080
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
GET / HTTP/1.1
Host: localhost
Connection: close

HTTP/1.1 200 OK
Date: Thu, 30 Nov 2017 03:05:30 GMT
Content-type: text/plain; charset=utf-8
Content-length: 21

これが応答ですConnection closed by foreign host.
$ 

思った通りの応答を返してくれた。もちろん、パッケージの説明にある例であれば、
実際に確かめたように期待した動作になるだろう。
しかし、文字数とバイト数とに差があれば途端に破綻する。
例示されたソースからはバイト数ではなく文字数を渡せとしか読み取れない。
たとえ結果としては両者が一致する例だとしても、
これでは例として少々問題があるのではと前から気になっていたのだった。