1

我有一个应用程序使用 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;
    }
}
4

0 回答 0