Я пытаюсь установить двустороннее взаимное подтверждение связи между клиентом и сервером, где сервер использует TLS.API от сервера принимает файл через почтовый запрос, а клиент настроен с хранилищем ключей и может отправлять файлы размером менее 50 КБ.API начал работать с ошибкой Borken Pipe
при отправке размером более 50 КБ.Я нашел ссылку здесь , которая говорит о подобной проблеме.
Как предложено в статье, я изменил код, чтобы отправить запрос get для установления безопасного соединения перед отправкой исходного запроса с файлом.Проблема по-прежнему сохраняется и выдает ту же ошибку.
Настройка клиента:
@Bean
@DependsOn(value = {"customRestTemplateCustomizer"})
public RestTemplateBuilder restTemplateBuilder() {
try {
return new RestTemplateBuilder()
.requestFactory(this::clientHttpRequestFactory)
.rootUri(properties.getBaseUrl())
.errorHandler(new RestTemplateErrorHandler());
} catch (Exception e) {
log.info("Exception thrown while setting ssl context : {} ", e.getMessage());
throw e;
}
}
@Bean
ClientHttpRequestFactory clientHttpRequestFactory() {
HttpComponentsClientHttpRequestFactory httpComponentsClientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(httpClient());
httpComponentsClientHttpRequestFactory.setBufferRequestBody(false);
return httpComponentsClientHttpRequestFactory;
}
@Bean
HttpClient httpClient() {
SSLContext sslContext = sslContext();
SSLConnectionSocketFactory sslSocketFactory = socketFactory(sslContext);
return HttpClients.custom()
.setSSLSocketFactory(sslSocketFactory)
.build();
}
@Bean
@SneakyThrows
public SSLContext sslContext(){
return new SSLContextBuilder()
.loadKeyMaterial(ResourceUtils.getFile(keyStore), keyStorePassword.toCharArray(), keyStorePassword.toCharArray())
.build();
}
@Bean
public SSLConnectionSocketFactory socketFactory(SSLContext sslContext) {
return new SSLConnectionSocketFactory(sslContext);
}
static {
HttpsURLConnection.setDefaultHostnameVerifier((hostname, session) -> hostname.equals(url));
}
@Bean
public CustomRestTemplateCustomizer customRestTemplateCustomizer() {
return new CustomRestTemplateCustomizer();
}
получить вызов для установления соединения:
//get call before making actual call
String response = restTemplate.getForObject("www.getapi.com", String.class);
// фактический вызов
final HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
headers.setConnection("keep-alive");
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
body.add("SigningId", signingId);
body.add("Payload", new StreamedFile(inputStream, fileName));
body.add("Payload Hash", hashValue);
body.add("Callback URL", callbackURL);
ResponseEntity<Response> responseEntity = restTemplate.postForEntity(
outgoingURL,
new HttpEntity<>(body, headers),
Response.class);
Исключение:
org.springframework.web.client.ResourceAccessException: I/O error on POST request for "www.postapi.com": null; nested exception is org.apache.http.client.ClientProtocolException] with root cause
java.net.SocketException: Broken pipe (Write failed)
at java.net.SocketOutputStream.socketWrite0(Native Method) ~[na:1.8.0_202]
at java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:111) ~[na:1.8.0_202]
at java.net.SocketOutputStream.write(SocketOutputStream.java:155) ~[na:1.8.0_202]
at sun.security.ssl.OutputRecord.writeBuffer(OutputRecord.java:431) ~[na:1.8.0_202]
at sun.security.ssl.OutputRecord.write(OutputRecord.java:417) ~[na:1.8.0_202]
at sun.security.ssl.SSLSocketImpl.writeRecordInternal(SSLSocketImpl.java:879) ~[na:1.8.0_202]
at sun.security.ssl.SSLSocketImpl.writeRecord(SSLSocketImpl.java:850) ~[na:1.8.0_202]
at sun.security.ssl.AppOutputStream.write(AppOutputStream.java:123) ~[na:1.8.0_202]
at org.apache.http.impl.io.SessionOutputBufferImpl.streamWrite(SessionOutputBufferImpl.java:124) ~[httpcore-4.4.11.jar:4.4.11]
at org.apache.http.impl.io.SessionOutputBufferImpl.write(SessionOutputBufferImpl.java:160) ~[httpcore-4.4.11.jar:4.4.11]
at org.apache.http.impl.io.ContentLengthOutputStream.write(ContentLengthOutputStream.java:113) ~[httpcore-4.4.11.jar:4.4.11]
at org.apache.http.impl.io.ContentLengthOutputStream.write(ContentLengthOutputStream.java:120) ~[httpcore-4.4.11.jar:4.4.11]
at org.springframework.util.StreamUtils.copy(StreamUtils.java:106) ~[spring-core-5.1.5.RELEASE.jar:5.1.5.RELEASE]
at org.springframework.http.client.InterceptingClientHttpRequest$InterceptingRequestExecution.lambda$execute$1(InterceptingClientHttpRequest.java:102) ~[spring-web-5.1.5.RELEASE.jar:5.1.5.RELEASE]
at org.springframework.http.client.HttpComponentsStreamingClientHttpRequest$StreamingHttpEntity.writeTo(HttpComponentsStreamingClientHttpRequest.java:152) ~[spring-web-5.1.5.RELEASE.jar:5.1.5.RELEASE]
at org.apache.http.impl.execchain.RequestEntityProxy.writeTo(RequestEntityProxy.java:123) ~[httpclient-4.5.1.jar:4.5.1]
at org.apache.http.impl.DefaultBHttpClientConnection.sendRequestEntity(DefaultBHttpClientConnection.java:156) ~[httpcore-4.4.11.jar:4.4.11]
at org.apache.http.impl.conn.CPoolProxy.sendRequestEntity(CPoolProxy.java:162) ~[httpclient-4.5.1.jar:4.5.1]
Поскольку я думал, что httpclient закрывает соединение для каждого запроса, я добавил
headers.setConnection("keep-alive");
, но это не сработало.Я пытался найти информацию о закрытии httpconnecton, но многие говорят, что по умолчанию он использует то же соединение, что и сеанс.
К сожалению, код сервера написан на python, и я не имею представления о его реализации.Он работает для файлов размером менее 50 КБ, поэтому моя конфигурация безопасности хорошая.