mirror of
https://github.com/apache/httpcomponents-client.git
synced 2025-02-26 12:28:02 +00:00
Refactored execution proxy classes; entity enclosing requests are enhanced only when necessary
git-svn-id: https://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk@1416177 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
a2a5d87980
commit
03769ceecc
@ -42,8 +42,8 @@
|
||||
* <p/>
|
||||
* Important: please note it is required for decorators that implement post execution aspects
|
||||
* or response post-processing of any sort to release resources associated with the response
|
||||
* by calling {@link HttpResponseProxy#close()} methods in case of an I/O, protocol or runtime
|
||||
* exception, or in case the response is not propagated to the caller.
|
||||
* by calling {@link CloseableHttpResponse#close()} methods in case of an I/O, protocol or
|
||||
* runtime exception, or in case the response is not propagated to the caller.
|
||||
*
|
||||
* @since 4.3
|
||||
*/
|
||||
|
@ -27,6 +27,7 @@
|
||||
|
||||
package org.apache.http.impl.client.execchain;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@ -43,7 +44,7 @@
|
||||
* @since 4.3
|
||||
*/
|
||||
@ThreadSafe
|
||||
class ConnectionReleaseTriggerImpl implements ConnectionReleaseTrigger, Cancellable {
|
||||
class ConnectionReleaseTriggerImpl implements ConnectionReleaseTrigger, Cancellable, Closeable {
|
||||
|
||||
private final Log log;
|
||||
|
||||
@ -145,4 +146,8 @@ public boolean isReleased() {
|
||||
return this.released;
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
abortConnection();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,67 @@
|
||||
package org.apache.http.impl.client.execchain;
|
||||
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Proxy;
|
||||
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpEntityEnclosingRequest;
|
||||
import org.apache.http.HttpRequest;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.annotation.NotThreadSafe;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
|
||||
/**
|
||||
* Execution proxies for HTTP message objects.
|
||||
*
|
||||
* @since 4.3
|
||||
*/
|
||||
@NotThreadSafe
|
||||
class ExecProxies {
|
||||
|
||||
static void enhanceEntity(final HttpEntityEnclosingRequest request) {
|
||||
HttpEntity entity = request.getEntity();
|
||||
if (entity != null && !entity.isRepeatable() && !isEnhanced(entity)) {
|
||||
HttpEntity proxy = (HttpEntity) Proxy.newProxyInstance(
|
||||
HttpEntity.class.getClassLoader(),
|
||||
new Class<?>[] { HttpEntity.class },
|
||||
new RequestEntityExecHandler(entity));
|
||||
request.setEntity(proxy);
|
||||
}
|
||||
}
|
||||
|
||||
static boolean isEnhanced(final HttpEntity entity) {
|
||||
if (entity != null && Proxy.isProxyClass(entity.getClass())) {
|
||||
InvocationHandler handler = Proxy.getInvocationHandler(entity);
|
||||
return handler instanceof RequestEntityExecHandler;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static boolean isRepeatable(final HttpRequest request) {
|
||||
if (request instanceof HttpEntityEnclosingRequest) {
|
||||
HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity();
|
||||
if (entity != null) {
|
||||
if (isEnhanced(entity)) {
|
||||
RequestEntityExecHandler handler = (RequestEntityExecHandler)
|
||||
Proxy.getInvocationHandler(entity);
|
||||
if (!handler.isConsumed()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return entity.isRepeatable();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static CloseableHttpResponse enhanceResponse(
|
||||
final HttpResponse original,
|
||||
final ConnectionReleaseTriggerImpl connReleaseTrigger) {
|
||||
return (CloseableHttpResponse) Proxy.newProxyInstance(
|
||||
ResponseProxyHandler.class.getClassLoader(),
|
||||
new Class<?>[] { CloseableHttpResponse.class },
|
||||
new ResponseProxyHandler(original, connReleaseTrigger));
|
||||
}
|
||||
|
||||
}
|
@ -35,6 +35,7 @@
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpEntityEnclosingRequest;
|
||||
import org.apache.http.HttpRequest;
|
||||
import org.apache.http.HttpVersion;
|
||||
import org.apache.http.ProtocolException;
|
||||
import org.apache.http.ProtocolVersion;
|
||||
import org.apache.http.RequestLine;
|
||||
@ -53,12 +54,15 @@
|
||||
public class HttpRequestWrapper extends AbstractHttpMessage implements HttpRequest {
|
||||
|
||||
private final HttpRequest original;
|
||||
|
||||
private final String method;
|
||||
private ProtocolVersion version;
|
||||
private URI uri;
|
||||
|
||||
private HttpRequestWrapper(final HttpRequest request) {
|
||||
super();
|
||||
this.original = request;
|
||||
this.version = this.original.getRequestLine().getProtocolVersion();
|
||||
this.method = this.original.getRequestLine().getMethod();
|
||||
if (request instanceof HttpUriRequest) {
|
||||
this.uri = ((HttpUriRequest) request).getURI();
|
||||
} else {
|
||||
@ -71,6 +75,10 @@ public ProtocolVersion getProtocolVersion() {
|
||||
return this.original.getProtocolVersion();
|
||||
}
|
||||
|
||||
public void setProtocolVersion(final ProtocolVersion version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public URI getURI() {
|
||||
return this.uri;
|
||||
}
|
||||
@ -80,28 +88,23 @@ public void setURI(final URI uri) {
|
||||
}
|
||||
|
||||
public RequestLine getRequestLine() {
|
||||
ProtocolVersion version = this.original.getRequestLine().getProtocolVersion();
|
||||
String method = this.original.getRequestLine().getMethod();
|
||||
String uritext = null;
|
||||
String requestUri = null;
|
||||
if (this.uri != null) {
|
||||
uritext = this.uri.toASCIIString();
|
||||
requestUri = this.uri.toASCIIString();
|
||||
} else {
|
||||
uritext = this.original.getRequestLine().getUri();
|
||||
requestUri = this.original.getRequestLine().getUri();
|
||||
}
|
||||
if (uritext == null || uritext.length() == 0) {
|
||||
uritext = "/";
|
||||
if (requestUri == null || requestUri.length() == 0) {
|
||||
requestUri = "/";
|
||||
}
|
||||
return new BasicRequestLine(method, uritext, version);
|
||||
ProtocolVersion version = this.version != null ? this.version : HttpVersion.HTTP_1_1;
|
||||
return new BasicRequestLine(this.method, requestUri, version);
|
||||
}
|
||||
|
||||
public HttpRequest getOriginal() {
|
||||
return this.original;
|
||||
}
|
||||
|
||||
public boolean isRepeatable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getRequestLine() + " " + this.headergroup;
|
||||
@ -115,7 +118,7 @@ static class HttpEntityEnclosingRequestWrapper extends HttpRequestWrapper
|
||||
public HttpEntityEnclosingRequestWrapper(final HttpEntityEnclosingRequest request)
|
||||
throws ProtocolException {
|
||||
super(request);
|
||||
setEntity(request.getEntity());
|
||||
this.entity = request.getEntity();
|
||||
}
|
||||
|
||||
public HttpEntity getEntity() {
|
||||
@ -123,7 +126,7 @@ public HttpEntity getEntity() {
|
||||
}
|
||||
|
||||
public void setEntity(final HttpEntity entity) {
|
||||
this.entity = entity != null ? new RequestEntityWrapper(entity) : null;
|
||||
this.entity = entity;
|
||||
}
|
||||
|
||||
public boolean expectContinue() {
|
||||
@ -131,11 +134,6 @@ public boolean expectContinue() {
|
||||
return expect != null && HTTP.EXPECT_CONTINUE.equalsIgnoreCase(expect.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRepeatable() {
|
||||
return this.entity == null || this.entity.isRepeatable();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static HttpRequestWrapper wrap(final HttpRequest request) throws ProtocolException {
|
||||
|
@ -36,6 +36,7 @@
|
||||
import org.apache.http.ConnectionReuseStrategy;
|
||||
import org.apache.http.HttpClientConnection;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpEntityEnclosingRequest;
|
||||
import org.apache.http.HttpException;
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.HttpRequest;
|
||||
@ -164,6 +165,10 @@ public CloseableHttpResponse execute(
|
||||
context.setAttribute(ClientContext.PROXY_AUTH_STATE, proxyAuthState);
|
||||
}
|
||||
|
||||
if (request instanceof HttpEntityEnclosingRequest) {
|
||||
ExecProxies.enhanceEntity((HttpEntityEnclosingRequest) request);
|
||||
}
|
||||
|
||||
Object userToken = context.getUserToken();
|
||||
|
||||
final ConnectionRequest connRequest = connManager.requestConnection(route, userToken);
|
||||
@ -214,7 +219,7 @@ public CloseableHttpResponse execute(
|
||||
HttpResponse response = null;
|
||||
for (int execCount = 1;; execCount++) {
|
||||
|
||||
if (execCount > 1 && !request.isRepeatable()) {
|
||||
if (execCount > 1 && !ExecProxies.isRepeatable(request)) {
|
||||
throw new NonRepeatableRequestException("Cannot retry request " +
|
||||
"with a non-repeatable request entity.");
|
||||
}
|
||||
@ -327,9 +332,9 @@ public CloseableHttpResponse execute(
|
||||
if (entity == null || !entity.isStreaming()) {
|
||||
// connection not needed and (assumed to be) in re-usable state
|
||||
releaseTrigger.releaseConnection();
|
||||
return HttpResponseProxy.newProxy(response, null);
|
||||
return ExecProxies.enhanceResponse(response, null);
|
||||
} else {
|
||||
return HttpResponseProxy.newProxy(response, releaseTrigger);
|
||||
return ExecProxies.enhanceResponse(response, releaseTrigger);
|
||||
}
|
||||
} catch (ConnectionShutdownException ex) {
|
||||
InterruptedIOException ioex = new InterruptedIOException(
|
||||
|
@ -32,6 +32,7 @@
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.http.HttpEntityEnclosingRequest;
|
||||
import org.apache.http.HttpException;
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.HttpRequest;
|
||||
@ -116,6 +117,9 @@ public CloseableHttpResponse execute(
|
||||
HttpRequest original = currentRequest.getOriginal();
|
||||
currentRequest = HttpRequestWrapper.wrap(redirect);
|
||||
currentRequest.setHeaders(original.getAllHeaders());
|
||||
if (original instanceof HttpEntityEnclosingRequest) {
|
||||
ExecProxies.enhanceEntity((HttpEntityEnclosingRequest) request);
|
||||
}
|
||||
|
||||
URI uri = currentRequest.getURI();
|
||||
HttpHost newTarget = URIUtils.extractHost(uri);
|
||||
|
@ -0,0 +1,62 @@
|
||||
package org.apache.http.impl.client.execchain;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.annotation.NotThreadSafe;
|
||||
|
||||
/**
|
||||
* A wrapper class for {@link HttpEntity} enclosed in a request message.
|
||||
*
|
||||
* @since 4.3
|
||||
*/
|
||||
@NotThreadSafe
|
||||
class RequestEntityExecHandler implements InvocationHandler {
|
||||
|
||||
private static final Method WRITE_TO_METHOD;
|
||||
|
||||
static {
|
||||
try {
|
||||
WRITE_TO_METHOD = HttpEntity.class.getMethod("writeTo", OutputStream.class);
|
||||
} catch (NoSuchMethodException ex) {
|
||||
throw new Error(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private final HttpEntity original;
|
||||
private boolean consumed = false;
|
||||
|
||||
RequestEntityExecHandler(final HttpEntity original) {
|
||||
super();
|
||||
this.original = original;
|
||||
}
|
||||
|
||||
public HttpEntity getOriginal() {
|
||||
return original;
|
||||
}
|
||||
|
||||
public boolean isConsumed() {
|
||||
return consumed;
|
||||
}
|
||||
|
||||
public Object invoke(
|
||||
final Object proxy, final Method method, final Object[] args) throws Throwable {
|
||||
try {
|
||||
if (method.equals(WRITE_TO_METHOD)) {
|
||||
this.consumed = true;
|
||||
}
|
||||
return method.invoke(original, args);
|
||||
} catch (InvocationTargetException ex) {
|
||||
Throwable cause = ex.getCause();
|
||||
if (cause != null) {
|
||||
throw cause;
|
||||
} else {
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
package org.apache.http.impl.client.execchain;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.annotation.NotThreadSafe;
|
||||
import org.apache.http.entity.HttpEntityWrapper;
|
||||
|
||||
/**
|
||||
* A wrapper class for {@link HttpEntity} enclosed in a request message.
|
||||
*
|
||||
* @since 4.3
|
||||
*/
|
||||
@NotThreadSafe
|
||||
class RequestEntityWrapper extends HttpEntityWrapper {
|
||||
|
||||
private boolean consumed = false;
|
||||
|
||||
RequestEntityWrapper(final HttpEntity entity) {
|
||||
super(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRepeatable() {
|
||||
if (!this.consumed) {
|
||||
return true;
|
||||
} else {
|
||||
return super.isRepeatable();
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public void consumeContent() throws IOException {
|
||||
consumed = true;
|
||||
super.consumeContent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(final OutputStream outstream) throws IOException {
|
||||
consumed = true;
|
||||
super.writeTo(outstream);
|
||||
}
|
||||
|
||||
public boolean isConsumed() {
|
||||
return consumed;
|
||||
}
|
||||
|
||||
}
|
@ -27,16 +27,15 @@
|
||||
|
||||
package org.apache.http.impl.client.execchain;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.annotation.NotThreadSafe;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
|
||||
/**
|
||||
* A proxy class for {@link HttpResponse} that can be used to release client connection
|
||||
@ -45,12 +44,22 @@
|
||||
* @since 4.3
|
||||
*/
|
||||
@NotThreadSafe
|
||||
class HttpResponseProxy implements InvocationHandler {
|
||||
class ResponseProxyHandler implements InvocationHandler {
|
||||
|
||||
private static final Method CLOSE_METHOD;
|
||||
|
||||
static {
|
||||
try {
|
||||
CLOSE_METHOD = Closeable.class.getMethod("close");
|
||||
} catch (NoSuchMethodException ex) {
|
||||
throw new Error(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private final HttpResponse original;
|
||||
private final ConnectionReleaseTriggerImpl connReleaseTrigger;
|
||||
|
||||
private HttpResponseProxy(
|
||||
ResponseProxyHandler(
|
||||
final HttpResponse original,
|
||||
final ConnectionReleaseTriggerImpl connReleaseTrigger) {
|
||||
super();
|
||||
@ -70,8 +79,7 @@ public void close() throws IOException {
|
||||
|
||||
public Object invoke(
|
||||
final Object proxy, final Method method, final Object[] args) throws Throwable {
|
||||
String mname = method.getName();
|
||||
if (mname.equals("close")) {
|
||||
if (method.equals(CLOSE_METHOD)) {
|
||||
close();
|
||||
return null;
|
||||
} else {
|
||||
@ -88,13 +96,4 @@ public Object invoke(
|
||||
}
|
||||
}
|
||||
|
||||
public static CloseableHttpResponse newProxy(
|
||||
final HttpResponse original,
|
||||
final ConnectionReleaseTriggerImpl connReleaseTrigger) {
|
||||
return (CloseableHttpResponse) Proxy.newProxyInstance(
|
||||
HttpResponseProxy.class.getClassLoader(),
|
||||
new Class<?>[] { CloseableHttpResponse.class },
|
||||
new HttpResponseProxy(original, connReleaseTrigger));
|
||||
}
|
||||
|
||||
}
|
@ -33,13 +33,11 @@
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HttpException;
|
||||
import org.apache.http.HttpRequest;
|
||||
import org.apache.http.annotation.Immutable;
|
||||
import org.apache.http.client.HttpRequestRetryHandler;
|
||||
import org.apache.http.client.NonRepeatableRequestException;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpExecutionAware;
|
||||
import org.apache.http.client.methods.HttpUriRequest;
|
||||
import org.apache.http.client.protocol.HttpClientContext;
|
||||
import org.apache.http.conn.routing.HttpRoute;
|
||||
|
||||
@ -86,8 +84,7 @@ public CloseableHttpResponse execute(
|
||||
try {
|
||||
return this.requestExecutor.execute(route, request, context, execAware);
|
||||
} catch (IOException ex) {
|
||||
HttpRequest original = request.getOriginal();
|
||||
if (original instanceof HttpUriRequest && ((HttpUriRequest) original).isAborted()) {
|
||||
if (execAware != null && execAware.isAborted()) {
|
||||
this.log.debug("Request has been aborted");
|
||||
throw ex;
|
||||
}
|
||||
@ -100,7 +97,7 @@ public CloseableHttpResponse execute(
|
||||
if (this.log.isDebugEnabled()) {
|
||||
this.log.debug(ex.getMessage(), ex);
|
||||
}
|
||||
if (!request.isRepeatable()) {
|
||||
if (!ExecProxies.isRepeatable(request)) {
|
||||
this.log.debug("Cannot retry non-repeatable request");
|
||||
throw new NonRepeatableRequestException("Cannot retry request " +
|
||||
"with a non-repeatable request entity", ex);
|
||||
|
@ -1,64 +0,0 @@
|
||||
/*
|
||||
* ====================================================================
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package org.apache.http.impl.client.execchain;
|
||||
|
||||
import java.io.OutputStream;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.impl.client.execchain.RequestEntityWrapper;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
public class TestRequestEntityWrapper {
|
||||
|
||||
private HttpEntity entity;
|
||||
private RequestEntityWrapper wrapper;
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
entity = Mockito.mock(HttpEntity.class);
|
||||
wrapper = new RequestEntityWrapper(entity);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonRepeatableEntityWriteTo() throws Exception {
|
||||
Mockito.when(entity.isRepeatable()).thenReturn(false);
|
||||
|
||||
Assert.assertTrue(wrapper.isRepeatable());
|
||||
|
||||
OutputStream outstream = Mockito.mock(OutputStream.class);
|
||||
wrapper.writeTo(outstream);
|
||||
|
||||
Assert.assertTrue(wrapper.isConsumed());
|
||||
Assert.assertFalse(wrapper.isRepeatable());
|
||||
|
||||
Mockito.verify(entity).writeTo(outstream);
|
||||
}
|
||||
|
||||
}
|
@ -259,7 +259,7 @@ public void testBasicAuthenticationFailureOnNonRepeatablePutDontExpectContinue()
|
||||
this.localServer.register("*", new AuthHandler());
|
||||
|
||||
TestCredentialsProvider credsProvider = new TestCredentialsProvider(
|
||||
new UsernamePasswordCredentials("test", "test"));
|
||||
new UsernamePasswordCredentials("test", "boom"));
|
||||
|
||||
this.httpclient = HttpClients.custom().setCredentialsProvider(credsProvider).build();
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user