问题描述
我正在使用 google-http-client 和 google-http-client-apache-v2 库来发出 POST 请求 在代理后面。
// 1.- Setting ssl and proxy
HttpClientBuilder builder = HttpClientBuilder.create();
SSLContext sslContext = SslUtils.getTlsSslContext();
SslUtils.initSslContext(sslContext,GoogleUtils.getCertificateTrustStore(),SslUtils.getPkixTrustManagerFactory());
builder.setSSLSocketFactory(new SSLConnectionSocketFactory(sslContext));
builder.setProxy(new HttpHost(host,port));
CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(new AuthScope(host,port),new UsernamePasswordCredentials(user,pass));
builder.setDefaultCredentialsProvider(credentialsProvider);
// 2.- Build request
HttpTransport httpTransport = new ApacheHttpTransport(builder.build());
HttpRequestFactory factory = httpTransport.createRequestFactory(credential);
HttpContent httpContent = new ByteArrayContent("application/json","{}")
HttpRequest request = factory.buildrequest("POST",new GenericUrl(url),httpContent);
// 3.- Execute request
HttpResponse httpResponse = request.execute();
该请求产生一个NonRepeatableRequestException:
org.apache.http.client.ClientProtocolException
at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:187) ~[httpclient-4.5.13.jar!/:4.5.13]
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:83) ~[httpclient-4.5.13.jar!/:4.5.13]
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:108) ~[httpclient-4.5.13.jar!/:4.5.13]
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:56) ~[httpclient-4.5.13.jar!/:4.5.13]
at com.google.api.client.http.apache.v2.ApacheHttpRequest.execute(ApacheHttpRequest.java:73) ~[google-http-client-apache-v2-1.39.2.jar!/:?]
at com.google.api.client.http.HttpRequest.execute(HttpRequest.java:1012) ~[google-http-client-1.39.2.jar!/:1.39.2]
at
...
Caused by: org.apache.http.client.NonRepeatableRequestException: Cannot retry request with a non-repeatable request entity.
at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:225) ~[httpclient-4.5.13.jar!/:4.5.13]
at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:186) ~[httpclient-4.5.13.jar!/:4.5.13]
at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:89) ~[httpclient-4.5.13.jar!/:4.5.13]
at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110) ~[httpclient-4.5.13.jar!/:4.5.13]
at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:185) ~[httpclient-4.5.13.jar!/:4.5.13]
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:83) ~[httpclient-4.5.13.jar!/:4.5.13]
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:108) ~[httpclient-4.5.13.jar!/:4.5.13]
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:56) ~[httpclient-4.5.13.jar!/:4.5.13]
at com.google.api.client.http.apache.v2.ApacheHttpRequest.execute(ApacheHttpRequest.java:73) ~[google-http-client-apache-v2-1.39.2.jar!/:?]
at com.google.api.client.http.HttpRequest.execute(HttpRequest.java:1012) ~[google-http-client-1.39.2.jar!/:1.39.2]
似乎 ApacheHttpRequest 将 ByteArrayContent 包裹在 ContentEntity 中,可重复(参见 JavaDoc) > 即不可重复。
在谷歌图书馆内调试执行,代理返回“407 Proxy Authentication required”,然后它尝试重复请求(猜测包括凭据),并且由于谷歌图书馆使用的 ContentEntity 是不可重复的,因此出现异常。>
有没有办法避免与代理握手,包括在第一个请求中包含凭据以避免重用实体?
有没有办法告诉谷歌图书馆使用可重复实体?
尝试了以下库版本:
- google-api-client-1.31.5
- google-http-client-jackson2-1.39.2
- google-oauth-client-1.31.5
- google-http-client-apache-v2-1.39.2
- google-http-client-1.39.2
- httpclient-4.5.13
- httpcore-4.4.14
解决方法
我在 github 上发布的解决方法,以防它对某人有所帮助:
作为解决方法,我正在尝试将 ApacheHttpTransport 包装在 CustomApacheHttpTransport 中,将方法的结果委托给 ApacheHttpTransport,除了 >buildRequest 方法。
CustomApacheHttpTransport 中的这个 buildRequest 方法构建了一个 CustomApacheHttpRequest 类型的自定义请求。
public final class CustomApacheHttpTransport extends HttpTransport {
private ApacheHttpTransport apacheHttpTransport;
public CustomApacheHttpTransport (HttpClient httpClient) {
this.apacheHttpTransport = new ApacheHttpTransport(httpClient);
}
@Override
protected LowLevelHttpRequest buildRequest (String method,String url) {
HttpRequestBase requestBase;
if (method.equals("DELETE")) {
requestBase = new HttpDelete(url);
} else if (method.equals("GET")) {
requestBase = new HttpGet(url);
} else if (method.equals("HEAD")) {
requestBase = new HttpHead(url);
} else if (method.equals("PATCH")) {
requestBase = new HttpPatch(url);
} else if (method.equals("POST")) {
..
}
return new CustomApacheHttpRequest(apacheHttpTransport.getHttpClient(),requestBase);
}
}
此自定义请求类似于 ApacheHttpRequest,不同之处在于它在执行时会创建一个自定义实体 CustomContentEntity,该实体将根据请求内容是否支持重试而重复。
final class CustomApacheHttpRequest extends LowLevelHttpRequest {
private final HttpClient httpClient;
private final HttpRequestBase request;
private RequestConfig.Builder requestConfig;
CustomApacheHttpRequest (HttpClient httpClient,HttpRequestBase request) {
this.httpClient = httpClient;
this.request = request;
this.requestConfig = RequestConfig.custom().setRedirectsEnabled(false).setNormalizeUri(false).setStaleConnectionCheckEnabled(false);
}
...
@Override
public LowLevelHttpResponse execute () throws IOException {
if (this.getStreamingContent() != null) {
Preconditions.checkState(request instanceof HttpEntityEnclosingRequest,"Apache HTTP client does not support %s requests with content.",request.getRequestLine().getMethod());
CustomContentEntity entity = new CustomContentEntity(this.getContentLength(),this.getStreamingContent());
entity.setContentEncoding(this.getContentEncoding());
entity.setContentType(this.getContentType());
if (this.getContentLength() == -1L) {
entity.setChunked(true);
}
((HttpEntityEnclosingRequest) request).setEntity(entity);
}
request.setConfig(requestConfig.build());
return new CustomApacheHttpResponse(request,httpClient.execute(request));
}
}
CustomContentEntity 中的关键是 isRepeatable 方法,它不会总是返回 false 作为 ContentEntity 确实如此。
final class CustomContentEntity extends AbstractHttpEntity {
private final long contentLength;
private final StreamingContent streamingContent;
CustomContentEntity (long contentLength,StreamingContent streamingContent) {
this.contentLength = contentLength;
this.streamingContent = streamingContent;
}
@Override
public boolean isRepeatable () {
return ((HttpContent) streamingContent).retrySupported();
}
...
}
此外,我必须创建 CustomApacheHttpResponse 作为对 CustomApacheHttpRequest 的响应,因为 ApacheHttpResponse 是包私有的(CustomApacheHttpResponse 是与 ApacheHttpResponse 完全一样)。