💬

HttpClientでHostヘッダーをセットする

2021/05/22に公開

Java11から導入されたHttpClientを利用してHTTPリクエストを行う際にHTTPリクエストヘッダーのHostパラメータをセットするとExceptionが発生します。
これを解消してHostヘッダーを付与したままリクエストする方法になります。

Hostヘッダーをセットしたい状況としてはVirtualHostのサーバへ接続したいというものが考えられます。

今回はnginxでVirtualHostを立ててテストしていますので念のため設定を記載しておきます。

接続先準備

/etc/nginx/conf.d/default.conf
server {
    listen 80;
    server_name test1.co.jp;

    access_log /var/log/nginx/test1-access.log;
    error_log  /var/log/nginx/test1-error.log;

    location / {
        add_header 'Content-Type' 'application/json';
        root  /var/www/test1/html;
        index index.json;
    }
}

server {
    listen 80;
    server_name test2.co.jp;

    access_log /var/log/nginx/test2-access.log;
    error_log  /var/log/nginx/test2-error.log;

    location / {
        add_header 'Content-Type' 'application/json';
        root  /var/www/test2/html;
        index index.json;
    }
}
test1.co.jpに接続
$ curl http://localhost -v -H "Host: test1.co.jp"
{
  "server":"test1",
  "message":"test1 hello"
}
test1.co.jpに接続
curl http://localhost -v -H "Host: test2.co.jp"
{
  "server":"test2",
  "message":"test2 hello"
}

HttpClientで接続

最初に接続するコードを記載しておきます。

public class Main {
    public static void main(String... args) {
        try {
            HttpRequest request = HttpRequest
                    .newBuilder(URI.create("http://localhost"))
                    .setHeader("Host", "test2.co.jp")
                    .build();

            HttpResponse<String> response = HttpClient.newBuilder().build().send(request, HttpResponse.BodyHandlers.ofString());
            System.out.println(response.body());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Exception発生例

上記コードを普通に実行した際に発生するExceptionを記載しておきます。

java.lang.IllegalArgumentException: restricted header name: "Host"
	at java.net.http/jdk.internal.net.http.common.Utils.newIAE(Utils.java:282)
	at java.net.http/jdk.internal.net.http.HttpRequestBuilderImpl.checkNameAndValue(HttpRequestBuilderImpl.java:110)
	at java.net.http/jdk.internal.net.http.HttpRequestBuilderImpl.setHeader(HttpRequestBuilderImpl.java:119)
	at java.net.http/jdk.internal.net.http.HttpRequestBuilderImpl.setHeader(HttpRequestBuilderImpl.java:43)
	at virtualhost.Connection.main(Connection.java:13)

このようにjava.net.http.HttpRequestにヘッダーをセットする際、特定のヘッダーについてはセットすることが許可されずExceptionが発生するようになります。

JDK16でヘッダーにセットすることが許可されていないヘッダーはconnection content-length expect host upgradeになります。

JDK11の場合は、connection content-length date expect from host upgrade via warningが許可されておらず、また今回の対応方法もJDK12から追加された機能のため利用できませんのでご注意ください。

対応方法

System Propertyの jdk.httpclient.allowRestrictedHeaders に許可したいヘッダー名をカンマ区切りでセットすることで許可されます。

public class Main {
    public static void main(String... args) {
        System.setProperty("jdk.httpclient.allowRestrictedHeaders", "host");

        try {
            HttpRequest request = HttpRequest
                    .newBuilder(URI.create("http://localhost"))
                    .setHeader("Host", "test2.co.jp")
                    .build();

            HttpResponse<String> response = HttpClient.newBuilder().build().send(request, HttpResponse.BodyHandlers.ofString());
            System.out.println(response.body());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
実行結果
{
  "server":"test2",
  "message":"test2 hello"
}

もしくは Java実行時に -Djdk.httpclient.allowRestrictedHeaders を指定することでも実現可能です。

実行結果
$ java -Djdk.httpclient.allowRestrictedHeaders=host Main
{
  "server":"test2",
  "message":"test2 hello"
}

HttpURLConnectionの場合

参考となりますが、この事象はHttpURLConnectionでも発生しうるので念のためこちらの対応方法についても記載しておきます。
ただしこちらはExceptionは発生せず、リクエストはされるものの、Hostの内容が書き変わらないという事象になります。

sun.net.http.allowRestrictedHeaders にtrueを指定することで許可されていない全てのヘッダーが変更可能になります。

コード例
public class Main {
    public static void main(String[] args) throws IOException {
        System.setProperty("sun.net.http.allowRestrictedHeaders", "true");

        URL url = new URL("http://localhost");
        HttpURLConnection urlconn = (HttpURLConnection) url.openConnection();
        urlconn.setRequestMethod("GET");
        urlconn.addRequestProperty("Host", "test2.co.jp");

        try (AutoCloseable c = () -> urlconn.disconnect();
             InputStreamReader inReader = new InputStreamReader(urlconn.getInputStream());
             BufferedReader reader = new BufferedReader(inReader);
        ) {
            urlconn.connect();

            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
{
  "server":"test2",
  "message":"test2 hello"
}

参考となりますが、HttpURLConnectionで変更が許可されていないヘッダーは、Access-Control-Request-Headers Access-Control-Request-Method Connection Content-Length Content-Transfer-Encoding Host Keep-Alive Origin Trailer Transfer-Encoding Upgrade Via になります。

Discussion