我有一个应用程序使用 openjdk-11.0.1 java.net.HttpClient 向 REST(ish) 端点发出多部分 POST 请求。该端点在执行任何工作时都会阻止响应,当工作完成时,它会返回响应。这个端点的性质是响应可以在 1s 到 1d 之间进行,所以这个连接可以保持很长时间。
所以无论如何,最近我开始遇到以下错误,即使目标服务器上的进程似乎仍然执行得很好。有没有人对此错误的可能原因有任何想法?
Caused by: java.util.concurrent.ExecutionException: java.io.IOException: HTTP/1.1 header parser received no bytes
at java.base/java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:395)
at java.base/java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1999)
at com.company.perf.rdp.profile.DatasetProfiler.toJson(DatasetProfiler.java:190)
... 9 common frames omitted
Caused by: java.io.IOException: HTTP/1.1 header parser received no bytes
at java.net.http/jdk.internal.net.http.common.Utils.wrapWithExtraDetail(Utils.java:293)
at java.net.http/jdk.internal.net.http.Http1Response$HeadersReader.onReadError(Http1Response.java:657)
at java.net.http/jdk.internal.net.http.Http1AsyncReceiver.checkForErrors(Http1AsyncReceiver.java:297)
at java.net.http/jdk.internal.net.http.Http1AsyncReceiver.flush(Http1AsyncReceiver.java:263)
at java.net.http/jdk.internal.net.http.common.SequentialScheduler$SynchronizedRestartableTask.run(SequentialScheduler.java:175)
at java.net.http/jdk.internal.net.http.common.SequentialScheduler$CompleteRestartableTask.run(SequentialScheduler.java:147)
at java.net.http/jdk.internal.net.http.common.SequentialScheduler$SchedulableTask.run(SequentialScheduler.java:198)
at java.net.http/jdk.internal.net.http.HttpClientImpl$DelegatingExecutor.execute(HttpClientImpl.java:153)
at java.net.http/jdk.internal.net.http.common.SequentialScheduler.runOrSchedule(SequentialScheduler.java:273)
at java.net.http/jdk.internal.net.http.common.SequentialScheduler.runOrSchedule(SequentialScheduler.java:242)
at java.net.http/jdk.internal.net.http.Http1AsyncReceiver.onReadError(Http1AsyncReceiver.java:506)
at java.net.http/jdk.internal.net.http.Http1AsyncReceiver$Http1TubeSubscriber.onComplete(Http1AsyncReceiver.java:591)
at java.net.http/jdk.internal.net.http.common.SSLTube$DelegateWrapper.onComplete(SSLTube.java:268)
at java.net.http/jdk.internal.net.http.common.SSLTube$SSLSubscriberWrapper.complete(SSLTube.java:411)
at java.net.http/jdk.internal.net.http.common.SSLTube$SSLSubscriberWrapper.onComplete(SSLTube.java:540)
at java.net.http/jdk.internal.net.http.common.SubscriberWrapper.checkCompletion(SubscriberWrapper.java:443)
at java.net.http/jdk.internal.net.http.common.SubscriberWrapper$DownstreamPusher.run1(SubscriberWrapper.java:322)
at java.net.http/jdk.internal.net.http.common.SubscriberWrapper$DownstreamPusher.run(SubscriberWrapper.java:261)
at java.net.http/jdk.internal.net.http.common.SequentialScheduler$SynchronizedRestartableTask.run(SequentialScheduler.java:175)
at java.net.http/jdk.internal.net.http.common.SequentialScheduler$CompleteRestartableTask.run(SequentialScheduler.java:147)
at java.net.http/jdk.internal.net.http.common.SequentialScheduler$SchedulableTask.run(SequentialScheduler.java:198)
at java.net.http/jdk.internal.net.http.common.SequentialScheduler.runOrSchedule(SequentialScheduler.java:271)
at java.net.http/jdk.internal.net.http.common.SequentialScheduler.runOrSchedule(SequentialScheduler.java:224)
at java.net.http/jdk.internal.net.http.common.SubscriberWrapper.outgoing(SubscriberWrapper.java:234)
at java.net.http/jdk.internal.net.http.common.SSLFlowDelegate$Reader.processData(SSLFlowDelegate.java:467)
at java.net.http/jdk.internal.net.http.common.SSLFlowDelegate$Reader$ReaderDownstreamPusher.run(SSLFlowDelegate.java:263)
at java.net.http/jdk.internal.net.http.common.SequentialScheduler$SynchronizedRestartableTask.run(SequentialScheduler.java:175)
at java.net.http/jdk.internal.net.http.common.SequentialScheduler$CompleteRestartableTask.run(SequentialScheduler.java:147)
at java.net.http/jdk.internal.net.http.common.SequentialScheduler$SchedulableTask.run(SequentialScheduler.java:198)
... 3 common frames omitted
Caused by: java.io.EOFException: EOF reached while reading
... 21 common frames omitted
这是我的 HttpClient 的创建:
default HttpClient create() {
final SSLContextBuilder sslContextBuilder;
try {
sslContextBuilder = new SSLContextBuilder().loadTrustMaterial(TrustSelfSignedStrategy.INSTANCE);
} catch (NoSuchAlgorithmException | KeyStoreException e) {
throw new RuntimeException("Unable to build SSLContext", e);
}
// PREVENTS HOST VALIDATION
final Properties props = System.getProperties();
props.setProperty("jdk.internal.httpclient.disableHostnameVerification", Boolean.TRUE.toString());
// SHOULD PREVENT HOST VALIDATION
final SSLParameters sslParams = new SSLParameters();
sslParams.setEndpointIdentificationAlgorithm(null);
try {
final SSLContext sslContext = sslContextBuilder.build();
ignoreExpiredCerts(sslContext);
return HttpClient.newBuilder().version(Version.HTTP_1_1).sslContext(sslContext).sslParameters(sslParams)
.build();
} catch (KeyManagementException | NoSuchAlgorithmException e) {
throw new RuntimeException("Unable to build HttpClient", e);
}
}
private void ignoreExpiredCerts(final SSLContext sslContext) throws KeyManagementException {
TrustManagerFactory tmf;
try {
tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
} catch (final NoSuchAlgorithmException e) {
throw new RuntimeException("Unable to build HttpClient", e);
}
try {
tmf.init((KeyStore) null);
} catch (final KeyStoreException e) {
throw new RuntimeException("Unable to build HttpClient", e);
}
final TrustManager[] trustManagers = tmf.getTrustManagers();
final X509TrustManager origTrustmanager = (X509TrustManager) trustManagers[0];
final AtomicBoolean logged = new AtomicBoolean(false);
final TrustManager[] wrappedTrustManagers = new TrustManager[] { new X509TrustManager() {
@Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return origTrustmanager.getAcceptedIssuers();
}
@Override
public void checkClientTrusted(final X509Certificate[] certs, final String authType)
throws CertificateException {
origTrustmanager.checkClientTrusted(certs, authType);
}
@Override
public void checkServerTrusted(final X509Certificate[] certs, final String authType)
throws CertificateException {
try {
origTrustmanager.checkServerTrusted(certs, authType);
} catch (final CertificateExpiredException e) {
if (!logged.get()) {
LOGGER.warn("Server certificate expired", e);
logged.set(true);
}
} catch (final Exception e) {
if (e.getCause() != null && e.getCause().getCause() != null
&& e.getCause().getCause() instanceof CertificateExpiredException) {
if (!logged.get()) {
LOGGER.warn("Server certificate expired", e.getCause().getCause());
logged.set(true);
}
} else {
throw e;
}
}
}
} };
sslContext.init(null, wrappedTrustManagers, null);
}
更新:
我没有找到答案。升级到最新的 java 版本会有所帮助(java httpclient 的 8 对 14 改变了很多)。
此外,此响应通常由下游服务器生成,而不是客户端。这意味着我连接的服务器关闭了连接。我们认为这是因为在传输大量数据时连接被检测为“空闲”。
对于较旧的 java httpclients,关闭 keepalive 似乎也有助于解决这个问题。在实例化您的 httpclient 之前,请进行以下调用:
// ALLOWS CONNECTION CLOSE HEADER
props.setProperty("jdk.httpclient.allowRestrictedHeaders", EnumSet.allOf(HttpHeaders.class).stream().filter(HttpHeaders::isRestricted).map(HttpHeaders::getName).map(StringUtils::lowerCase).collect(Collectors.joining(",")));
// TURN IT OFF
props.setProperty("jdk.httpclient.keepalive.timeout", "0");
在每个请求上,我都会关闭连接:
// CLOSE SOCKET AFTER EACH REQUEST
requestBuilder.header(HttpHeaders.CONNECTION.getName(), "close");
引用的 HttpHeaders 类:
package com.company.perf.api.http;
import org.apache.commons.lang3.StringUtils;
import com.company.api.http.client.IMakeHttpRequests;
public enum HttpHeaders {
ACCEPT("Accept"),
ACCEPT_CHARSET("Accept-Charset"),
ACCEPT_ENCODING("Accept-Encoding"),
ACCEPT_LANGUAGE("Accept-Language"),
ALLOW("Allow"),
AUTHORIZATION("Authorization"),
BOUNDARY("boundary"),
CACHE_CONTROL("Cache-Control"),
CONNECTION("Connection", true),
CONTENT_DISPOSITION("Content-Disposition"),
CONTENT_ENCODING("Content-Encoding"),
CONTENT_ID("Content-ID"),
CONTENT_LANGUAGE("Content-Language"),
CONTENT_LENGTH("Content-Length", true),
CONTENT_LOCATION("Content-Location"),
CONTENT_TYPE("Content-Type"),
COOKIE("Cookie"),
DATE("Date"),
ETAG("ETag"),
EXPECT("Expect", true),
EXPIRES("Expires"),
HOST("Host", true),
IF_MATCH("If-Match"),
IF_MODIFIED_SINCE("If-Modified-Since"),
IF_NONE_MATCH("If-None-Match"),
IF_UNMODIFIED_SINCE("If-Unmodified-Since"),
LAST_MODIFIED("Last-Modified"),
LINK("Link"),
LOCATION("Location"),
RETRY_AFTER("Retry-After"),
SET_COOKIE("Set-Cookie"),
UPGRADE("Upgrade", true),
USER_AGENT("User-Agent"),
VARY("Vary"),
WWW_AUTHENTICATE("WWW-Authenticate");
private final String name;
private final boolean restricted;
private HttpHeaders(final String name, final boolean restricted) {
assert StringUtils.isNotBlank(name) : "name cannot be blank";
this.name = name;
this.restricted = restricted;
}
private HttpHeaders(final String name) {
this(name, false);
}
public String getName() {
return this.name;
}
/**
* @return whether or not this header's use is restricted by the
* {@link IMakeHttpRequests Java HTTP Client}
*/
public boolean isRestricted() {
return this.restricted;
}
}