Apache HttpClient 4.5.13 报错:java.net.SocketException: Broken pipe (Write failed)。

3
我有一个使用Java 8 Http客户端的应用程序,与一个服务器进行通信。该服务器会在以下情况下拒绝请求(例如,无效或缺少“Authorization”头部,载荷大于1MB)。当请求被拒绝时,我使用cURL看到了预期的HTTP响应代码(例如401未经授权,403禁止访问,413负载过大)。然而,当使用Apache HttpClient时,我得到了一个“java.net.SocketException: Broken pipe (Write failed)”错误。我认为这是因为客户端想要写入请求体,但服务器已经关闭连接而没有完全读取请求体。
理想情况下,我希望能够获取服务器的响应,以便处理“真正”的错误(例如告知用户其请求过大,或者让他们知道他们的会话不再有效)。如果没有来自服务器的响应,我将不得不假定这是网络错误并重试。是否有任何方法可以优雅地处理“java.net.SocketException: Broken pipe (Write failed)”异常并仍然读取服务器响应? 示例cURL请求
$> printf 'x%.0s' {1..1048577} | curl -i --data @- https://my.server.com/anything
HTTP/1.1 100 Continue

HTTP/1.1 413 Request Entity Too Large
Content-Type: application/json
X-Frame-Options: DENY
Content-Length: 92
Connection: Close

示例代码

import org.apache.http.HttpResponse;
import org.apache.http.client.entity.EntityBuilder;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;

public class Main {

    public final static void main(String[] args) throws Exception {
        CloseableHttpClient client = HttpClients.createDefault();
        try {
            HttpPost request = new HttpPost("https://my.server.com/anything");
            String largePayload = new String(new char[1024*1024+1]).replace('\0', 'x'); // >1MB
            request.setEntity(EntityBuilder.create().setText(largePayload).build());
            HttpResponse response = client.execute(request);
            System.out.println(response.getStatusLine());
        } finally {
            client.close();
        }
    }

}

完整的堆栈跟踪

Exception in thread "main" java.net.SocketException: Broken pipe (Write failed)
    at java.net.SocketOutputStream.socketWrite0(Native Method)
    at java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:111)
    at java.net.SocketOutputStream.write(SocketOutputStream.java:155)
    at sun.security.ssl.OutputRecord.writeBuffer(OutputRecord.java:431)
    at sun.security.ssl.OutputRecord.write(OutputRecord.java:417)
    at sun.security.ssl.SSLSocketImpl.writeRecordInternal(SSLSocketImpl.java:886)
    at sun.security.ssl.SSLSocketImpl.writeRecord(SSLSocketImpl.java:857)
    at sun.security.ssl.AppOutputStream.write(AppOutputStream.java:123)
    at org.apache.http.impl.conn.LoggingOutputStream.write(LoggingOutputStream.java:74)
    at org.apache.http.impl.io.SessionOutputBufferImpl.streamWrite(SessionOutputBufferImpl.java:124)
    at org.apache.http.impl.io.SessionOutputBufferImpl.write(SessionOutputBufferImpl.java:160)
    at org.apache.http.impl.io.ContentLengthOutputStream.write(ContentLengthOutputStream.java:113)
    at org.apache.http.impl.io.ContentLengthOutputStream.write(ContentLengthOutputStream.java:120)
    at org.apache.http.entity.StringEntity.writeTo(StringEntity.java:167)
    at org.apache.http.impl.DefaultBHttpClientConnection.sendRequestEntity(DefaultBHttpClientConnection.java:156)
    at org.apache.http.impl.conn.CPoolProxy.sendRequestEntity(CPoolProxy.java:152)
    at org.apache.http.protocol.HttpRequestExecutor.doSendRequest(HttpRequestExecutor.java:238)
    at org.apache.http.protocol.HttpRequestExecutor.execute(HttpRequestExecutor.java:123)
    at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:272)
    at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:186)
    at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:89)
    at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110)
    at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:185)
    at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:83)
    at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:108)
    at Main.main(main.java:15)

在收到异常后,你尝试获取请求的响应代码了吗?这可能有效。另一方面,服务器可能只是关闭了连接。如果发生这种情况,就不会有任何响应,客户端也无法解决这个问题。 - Stephen C
@StephenC 我实际上无法获取响应,因为那是抛出异常的方法(例如 HttpResponse response = client.execute(request))。 - jckdnk111
请展示所有相关代码和完整的堆栈跟踪(包括任何嵌套的堆栈跟踪)。将它们放在问题中。 - Stephen C
你应该能够从你的Java代码中获得与curl相同的行为。你应该可以发送完全相同的请求,因此获得相同的响应。如果你的Java代码没有达到预期效果,那么你可能做了一些不同/错误的事情。请提供你正在执行的curl命令、你的完整代码(最好是一个MCVE)、以及你所遇到的完整错误,包括完整的堆栈跟踪。将所有这些内容编辑并更新到你的问题中。 - CryptoFool
@StephenC 我已经更新了一些例子--如果您需要任何其他信息,请告诉我。感谢您的帮助! - jckdnk111
@jckdnk111,可以问一下你在服务器实现方面使用的是什么技术吗?我们在使用Spring Boot时遇到了相同的问题,不幸的是setExpectContinueEnabled(true)并没有起到帮助作用。 - Claus Polanka
2个回答

3

我遇到了类似的问题,我通过将以下配置添加到httpClient中来解决它

RequestConfig requestConfig = RequestConfig.custom()
            .setExpectContinueEnabled(true).build();
HttpClientBuilder clientBuilder = HttpClientBuilder.create()
            .setDefaultRequestConfig(requestConfig);
CloseableHttpClient httpClient = clientBuilder.build();

在下面的链接中,你可以找到如何执行此操作的说明:http://httpcomponents.10934.n7.nabble.com/Broken-pipe-Write-failed-when-making-Unauthorized-request-td34235.html.

编辑:上面的链接已经失效,这里是一个类似的可用链接:https://lists.apache.org/thread/p38b3lrjt4vhyqthwhqcq3vsx3dpr3wj

以下是 Joe 要求的摘要: 情景是服务器基于报头拒绝请求并关闭连接,因此客户端会收到“broken pipe”错误。 "expect-continued" 报头告诉客户端等待 100 状态,表示服务器已接受了报头,并且客户端应该发送其余数据。请注意,并非所有服务器都能正确执行此操作,并且仍然可能在发送 100 状态后关闭连接。


1
那个链接现在已经失效了。你能否为什么这样做可以解决问题添加一些解释? - Joe
谢谢,这行代码对我有用:setExpectContinueEnabled(true) - Ravindra Kushwaha

0

ExpectContinueEnabled的解释如下

这个抽象类是支持“Expect: 100-continue”握手的所有HTTP方法的基础。

100(继续)状态的目的(请参阅RFC 2616第10.1.1节了解更多详细信息)是允许发送带有请求正文的请求消息的客户端在发送请求正文之前确定源服务器是否愿意接受请求(基于请求头)。在某些情况下,如果服务器在不查看正文的情况下拒绝消息,则客户端发送正文可能不合适或效率极低。

应谨慎使用“Expect: 100-continue”握手,因为它可能会导致不支持HTTP/1.1协议的HTTP服务器和代理出现问题。

Reference: https://hc.apache.org/httpclient-legacy/apidocs/org/apache/commons/httpclient/methods/ExpectContinueMethod.html


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接