HttpClientでHostヘッダーをセットする
Java11から導入されたHttpClientを利用してHTTPリクエストを行う際にHTTPリクエストヘッダーのHostパラメータをセットするとExceptionが発生します。
これを解消してHostヘッダーを付与したままリクエストする方法になります。
Hostヘッダーをセットしたい状況としてはVirtualHostのサーバへ接続したいというものが考えられます。
今回はnginxでVirtualHostを立ててテストしていますので念のため設定を記載しておきます。
接続先準備
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;
}
}
$ curl http://localhost -v -H "Host: test1.co.jp"
{
"server":"test1",
"message":"test1 hello"
}
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