Issue 69: removed unusable or redundant code wrt http error handling

git-svn-id: http://jclouds.googlecode.com/svn/trunk@1455 3d8758e0-26b5-11de-8745-db77d3ebf521
This commit is contained in:
adrian.f.cole 2009-06-22 07:21:03 +00:00
parent 7e181c92b0
commit 39e96d041f
22 changed files with 276 additions and 534 deletions

View File

@ -33,15 +33,10 @@ import org.jclouds.aws.s3.filters.RequestAuthorizeSignature;
import org.jclouds.aws.s3.handlers.ParseAWSErrorFromXmlContent; import org.jclouds.aws.s3.handlers.ParseAWSErrorFromXmlContent;
import org.jclouds.aws.s3.internal.LiveS3Connection; import org.jclouds.aws.s3.internal.LiveS3Connection;
import org.jclouds.http.HttpConstants; import org.jclouds.http.HttpConstants;
import org.jclouds.http.HttpErrorHandler;
import org.jclouds.http.HttpRequestFilter; import org.jclouds.http.HttpRequestFilter;
import org.jclouds.http.HttpResponseHandler;
import org.jclouds.http.HttpRetryHandler; import org.jclouds.http.HttpRetryHandler;
import org.jclouds.http.annotation.ClientErrorHandler;
import org.jclouds.http.annotation.RedirectHandler;
import org.jclouds.http.annotation.RetryHandler;
import org.jclouds.http.annotation.ServerErrorHandler;
import org.jclouds.http.handlers.BackoffLimitedRetryHandler; import org.jclouds.http.handlers.BackoffLimitedRetryHandler;
import org.jclouds.http.handlers.CloseContentAndSetExceptionHandler;
import org.jclouds.logging.Logger; import org.jclouds.logging.Logger;
import com.google.inject.AbstractModule; import com.google.inject.AbstractModule;
@ -73,22 +68,16 @@ public class LiveS3ConnectionModule extends AbstractModule {
@Override @Override
protected void configure() { protected void configure() {
bindResponseHandlers();
bind(S3Connection.class).to(LiveS3Connection.class).in(Scopes.SINGLETON); bind(S3Connection.class).to(LiveS3Connection.class).in(Scopes.SINGLETON);
bind(HttpRetryHandler.class).annotatedWith(RetryHandler.class).to( bind(HttpRetryHandler.class).to(BackoffLimitedRetryHandler.class).in(Scopes.SINGLETON);
BackoffLimitedRetryHandler.class).in(Scopes.SINGLETON); bindErrorHandler();
requestInjection(this); requestInjection(this);
logger.info("S3 Context = %1$s://%2$s:%3$s", (isSecure ? "https" : "http"), address, port); logger.info("S3 Context = %1$s://%2$s:%3$s", (isSecure ? "https" : "http"), address, port);
} }
protected void bindResponseHandlers() { protected void bindErrorHandler() {
bind(HttpResponseHandler.class).annotatedWith(RedirectHandler.class).to( bind(HttpErrorHandler.class).to(ParseAWSErrorFromXmlContent.class).in(Scopes.SINGLETON);
CloseContentAndSetExceptionHandler.class).in(Scopes.SINGLETON);
bind(HttpResponseHandler.class).annotatedWith(ClientErrorHandler.class).to(
ParseAWSErrorFromXmlContent.class).in(Scopes.SINGLETON);
bind(HttpResponseHandler.class).annotatedWith(ServerErrorHandler.class).to(
ParseAWSErrorFromXmlContent.class).in(Scopes.SINGLETON);
} }
@Provides @Provides

View File

@ -35,7 +35,7 @@ import org.jclouds.aws.s3.reference.S3Headers;
import org.jclouds.aws.s3.xml.S3ParserFactory; import org.jclouds.aws.s3.xml.S3ParserFactory;
import org.jclouds.http.HttpFutureCommand; import org.jclouds.http.HttpFutureCommand;
import org.jclouds.http.HttpResponse; import org.jclouds.http.HttpResponse;
import org.jclouds.http.HttpResponseHandler; import org.jclouds.http.HttpErrorHandler;
import org.jclouds.logging.Logger; import org.jclouds.logging.Logger;
import com.google.inject.Inject; import com.google.inject.Inject;
@ -47,7 +47,7 @@ import com.google.inject.Inject;
* @author Adrian Cole * @author Adrian Cole
* *
*/ */
public class ParseAWSErrorFromXmlContent implements HttpResponseHandler { public class ParseAWSErrorFromXmlContent implements HttpErrorHandler {
@Resource @Resource
protected Logger logger = Logger.NULL; protected Logger logger = Logger.NULL;

View File

@ -28,15 +28,10 @@ import static org.testng.Assert.assertEquals;
import org.jclouds.aws.s3.handlers.ParseAWSErrorFromXmlContent; import org.jclouds.aws.s3.handlers.ParseAWSErrorFromXmlContent;
import org.jclouds.aws.s3.reference.S3Constants; import org.jclouds.aws.s3.reference.S3Constants;
import org.jclouds.aws.s3.xml.config.S3ParserModule; import org.jclouds.aws.s3.xml.config.S3ParserModule;
import org.jclouds.http.HttpResponseHandler; import org.jclouds.http.HttpErrorHandler;
import org.jclouds.http.HttpRetryHandler; import org.jclouds.http.HttpRetryHandler;
import org.jclouds.http.annotation.ClientErrorHandler;
import org.jclouds.http.annotation.RedirectHandler;
import org.jclouds.http.annotation.RetryHandler;
import org.jclouds.http.annotation.ServerErrorHandler;
import org.jclouds.http.config.JavaUrlHttpFutureCommandClientModule; import org.jclouds.http.config.JavaUrlHttpFutureCommandClientModule;
import org.jclouds.http.handlers.BackoffLimitedRetryHandler; import org.jclouds.http.handlers.BackoffLimitedRetryHandler;
import org.jclouds.http.handlers.CloseContentAndSetExceptionHandler;
import org.testng.annotations.Test; import org.testng.annotations.Test;
import com.google.inject.Guice; import com.google.inject.Guice;
@ -74,22 +69,10 @@ public class S3ContextModuleTest {
}, new JavaUrlHttpFutureCommandClientModule()); }, new JavaUrlHttpFutureCommandClientModule());
} }
private static class ClientErrorHandlerTest {
@Inject
@ClientErrorHandler
HttpResponseHandler errorHandler;
}
@Test
void testClientErrorHandler() {
ClientErrorHandlerTest error = createInjector().getInstance(ClientErrorHandlerTest.class);
assertEquals(error.errorHandler.getClass(), ParseAWSErrorFromXmlContent.class);
}
private static class ServerErrorHandlerTest { private static class ServerErrorHandlerTest {
@Inject @Inject
@ServerErrorHandler HttpErrorHandler errorHandler;
HttpResponseHandler errorHandler;
} }
@Test @Test
@ -98,21 +81,9 @@ public class S3ContextModuleTest {
assertEquals(error.errorHandler.getClass(), ParseAWSErrorFromXmlContent.class); assertEquals(error.errorHandler.getClass(), ParseAWSErrorFromXmlContent.class);
} }
private static class RedirectHandlerTest {
@Inject
@RedirectHandler
HttpResponseHandler errorHandler;
}
@Test
void testRedirectHandler() {
RedirectHandlerTest error = createInjector().getInstance(RedirectHandlerTest.class);
assertEquals(error.errorHandler.getClass(), CloseContentAndSetExceptionHandler.class);
}
private static class RetryHandlerTest { private static class RetryHandlerTest {
@Inject @Inject
@RetryHandler
HttpRetryHandler retryHandler; HttpRetryHandler retryHandler;
} }

View File

@ -26,11 +26,7 @@ package org.jclouds.aws.s3.suncloud.config;
import org.jclouds.aws.s3.config.LiveS3ConnectionModule; import org.jclouds.aws.s3.config.LiveS3ConnectionModule;
import org.jclouds.aws.s3.config.S3ConnectionModule; import org.jclouds.aws.s3.config.S3ConnectionModule;
import org.jclouds.aws.s3.suncloud.handlers.ParseSunCloudS3ErrorFromXmlContent; import org.jclouds.aws.s3.suncloud.handlers.ParseSunCloudS3ErrorFromXmlContent;
import org.jclouds.http.HttpResponseHandler; import org.jclouds.http.HttpErrorHandler;
import org.jclouds.http.annotation.ClientErrorHandler;
import org.jclouds.http.annotation.RedirectHandler;
import org.jclouds.http.annotation.ServerErrorHandler;
import org.jclouds.http.handlers.CloseContentAndSetExceptionHandler;
import com.google.inject.Scopes; import com.google.inject.Scopes;
@ -42,13 +38,9 @@ import com.google.inject.Scopes;
@S3ConnectionModule @S3ConnectionModule
public class SunCloudS3ConnectionModule extends LiveS3ConnectionModule { public class SunCloudS3ConnectionModule extends LiveS3ConnectionModule {
protected void bindResponseHandlers() { protected void bindErrorHandler() {
bind(HttpResponseHandler.class).annotatedWith(RedirectHandler.class).to( bind(HttpErrorHandler.class).to(ParseSunCloudS3ErrorFromXmlContent.class)
CloseContentAndSetExceptionHandler.class).in(Scopes.SINGLETON); .in(Scopes.SINGLETON);
bind(HttpResponseHandler.class).annotatedWith(ClientErrorHandler.class).to(
ParseSunCloudS3ErrorFromXmlContent.class).in(Scopes.SINGLETON);
bind(HttpResponseHandler.class).annotatedWith(ServerErrorHandler.class).to(
ParseSunCloudS3ErrorFromXmlContent.class).in(Scopes.SINGLETON);
} }
} }

View File

@ -36,7 +36,7 @@ import org.jclouds.aws.s3.suncloud.domain.SunCloudS3Error;
import org.jclouds.aws.s3.xml.S3ParserFactory; import org.jclouds.aws.s3.xml.S3ParserFactory;
import org.jclouds.http.HttpFutureCommand; import org.jclouds.http.HttpFutureCommand;
import org.jclouds.http.HttpResponse; import org.jclouds.http.HttpResponse;
import org.jclouds.http.HttpResponseHandler; import org.jclouds.http.HttpErrorHandler;
import org.jclouds.logging.Logger; import org.jclouds.logging.Logger;
import org.jclouds.util.Utils; import org.jclouds.util.Utils;
@ -49,7 +49,7 @@ import com.google.inject.Inject;
* @author Adrian Cole * @author Adrian Cole
* *
*/ */
public class ParseSunCloudS3ErrorFromXmlContent implements HttpResponseHandler { public class ParseSunCloudS3ErrorFromXmlContent implements HttpErrorHandler {
@Resource @Resource
protected Logger logger = Logger.NULL; protected Logger logger = Logger.NULL;

View File

@ -28,8 +28,8 @@ package org.jclouds.http;
* *
* @author Adrian Cole * @author Adrian Cole
*/ */
public interface HttpResponseHandler { public interface HttpErrorHandler {
public static final HttpResponseHandler NOOP = new HttpResponseHandler() { public static final HttpErrorHandler NOOP = new HttpErrorHandler() {
public void handle(HttpFutureCommand<?> command, HttpResponse response) { public void handle(HttpFutureCommand<?> command, HttpResponse response) {
} }
}; };

View File

@ -32,7 +32,7 @@ package org.jclouds.http;
*/ */
public interface HttpRetryHandler { public interface HttpRetryHandler {
public static final HttpRetryHandler ALWAYS_RETRY = new HttpRetryHandler() { public static final HttpRetryHandler ALWAYS_RETRY = new HttpRetryHandler() {
public boolean retryRequest(HttpFutureCommand<?> command, HttpResponse response) public boolean shouldRetryRequest(HttpFutureCommand<?> command, HttpResponse response)
{ {
return true; return true;
} }
@ -42,12 +42,7 @@ public interface HttpRetryHandler {
* Return true if the command should be retried. This method should only be * Return true if the command should be retried. This method should only be
* invoked when the response has failed with a HTTP 5xx error indicating a * invoked when the response has failed with a HTTP 5xx error indicating a
* server-side error. * server-side error.
*
* @param command
* @param response
* @return
* @throws InterruptedException
*/ */
boolean retryRequest(HttpFutureCommand<?> command, HttpResponse response) boolean shouldRetryRequest(HttpFutureCommand<?> command, HttpResponse response)
throws InterruptedException; throws InterruptedException;
} }

View File

@ -1,44 +0,0 @@
/**
*
* Copyright (C) 2009 Global Cloud Specialists, Inc. <info@globalcloudspecialists.com>
*
* ====================================================================
* 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.
* ====================================================================
*/
package org.jclouds.http.annotation;
import com.google.inject.BindingAnnotation;
import java.lang.annotation.Target;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
/**
* Implies that the object can address {@link org.jclouds.http.HttpResponse}s
* that contain status code 4xx.
*
* @author Adrian Cole
*/
@BindingAnnotation
@Target( { FIELD, PARAMETER, METHOD })
@Retention(RUNTIME)
public @interface ClientErrorHandler {
}

View File

@ -1,44 +0,0 @@
/**
*
* Copyright (C) 2009 Global Cloud Specialists, Inc. <info@globalcloudspecialists.com>
*
* ====================================================================
* 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.
* ====================================================================
*/
package org.jclouds.http.annotation;
import com.google.inject.BindingAnnotation;
import java.lang.annotation.Target;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
/**
* Implies that the object can address {@link org.jclouds.http.HttpResponse}s that contain status
* code 3xx.
*
* @author Adrian Cole
*/
@BindingAnnotation
@Target( { FIELD, PARAMETER, METHOD })
@Retention(RUNTIME)
public @interface RedirectHandler {
}

View File

@ -1,43 +0,0 @@
/**
*
* Copyright (C) 2009 Global Cloud Specialists, Inc. <info@globalcloudspecialists.com>
*
* ====================================================================
* 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.
* ====================================================================
*/
package org.jclouds.http.annotation;
import com.google.inject.BindingAnnotation;
import java.lang.annotation.Target;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
/**
* Implies that the object can address {@link org.jclouds.http.HttpRetryHandler}s.
*
* @author James Murty
*/
@BindingAnnotation
@Target( { FIELD, PARAMETER, METHOD })
@Retention(RUNTIME)
public @interface RetryHandler {
}

View File

@ -1,44 +0,0 @@
/**
*
* Copyright (C) 2009 Global Cloud Specialists, Inc. <info@globalcloudspecialists.com>
*
* ====================================================================
* 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.
* ====================================================================
*/
package org.jclouds.http.annotation;
import com.google.inject.BindingAnnotation;
import java.lang.annotation.Target;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
/**
* Implies that the object can address {@link org.jclouds.http.HttpResponse}s that contain status
* code 5xx.
*
* @author Adrian Cole
*/
@BindingAnnotation
@Target( { FIELD, PARAMETER, METHOD })
@Retention(RUNTIME)
public @interface ServerErrorHandler {
}

View File

@ -67,7 +67,7 @@ public class BackoffLimitedRetryHandler implements HttpRetryHandler {
this.retryCountLimit = retryCountLimit; this.retryCountLimit = retryCountLimit;
} }
public boolean retryRequest(HttpFutureCommand<?> command, HttpResponse response) public boolean shouldRetryRequest(HttpFutureCommand<?> command, HttpResponse response)
throws InterruptedException throws InterruptedException
{ {
IOUtils.closeQuietly(response.getContent()); IOUtils.closeQuietly(response.getContent());

View File

@ -28,14 +28,14 @@ import java.io.IOException;
import org.jclouds.http.HttpFutureCommand; import org.jclouds.http.HttpFutureCommand;
import org.jclouds.http.HttpResponse; import org.jclouds.http.HttpResponse;
import org.jclouds.http.HttpResponseException; import org.jclouds.http.HttpResponseException;
import org.jclouds.http.HttpResponseHandler; import org.jclouds.http.HttpErrorHandler;
import org.jclouds.util.Utils; import org.jclouds.util.Utils;
/** /**
* *
* @author Adrian Cole * @author Adrian Cole
*/ */
public class CloseContentAndSetExceptionHandler implements HttpResponseHandler { public class CloseContentAndSetExceptionHandler implements HttpErrorHandler {
public void handle(HttpFutureCommand<?> command, HttpResponse response) { public void handle(HttpFutureCommand<?> command, HttpResponse response) {
String content; String content;

View File

@ -23,64 +23,75 @@
*/ */
package org.jclouds.http.internal; package org.jclouds.http.internal;
import java.net.URI; import java.io.IOException;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import javax.annotation.Resource; import javax.annotation.Resource;
import org.jclouds.http.HttpErrorHandler;
import org.jclouds.http.HttpFutureCommand; import org.jclouds.http.HttpFutureCommand;
import org.jclouds.http.HttpFutureCommandClient; import org.jclouds.http.HttpFutureCommandClient;
import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpRequestFilter; import org.jclouds.http.HttpRequestFilter;
import org.jclouds.http.HttpResponse; import org.jclouds.http.HttpResponse;
import org.jclouds.http.HttpResponseHandler;
import org.jclouds.http.HttpRetryHandler; import org.jclouds.http.HttpRetryHandler;
import org.jclouds.http.annotation.ClientErrorHandler;
import org.jclouds.http.annotation.RedirectHandler;
import org.jclouds.http.annotation.RetryHandler;
import org.jclouds.http.annotation.ServerErrorHandler;
import org.jclouds.http.handlers.BackoffLimitedRetryHandler; import org.jclouds.http.handlers.BackoffLimitedRetryHandler;
import org.jclouds.http.handlers.CloseContentAndSetExceptionHandler; import org.jclouds.http.handlers.CloseContentAndSetExceptionHandler;
import org.jclouds.logging.Logger; import org.jclouds.logging.Logger;
import com.google.inject.Inject; import com.google.inject.Inject;
public abstract class BaseHttpFutureCommandClient implements HttpFutureCommandClient { public abstract class BaseHttpFutureCommandClient<Q> implements HttpFutureCommandClient {
protected final URI target;
@Resource @Resource
protected Logger logger = Logger.NULL; protected Logger logger = Logger.NULL;
@Inject(optional = true) @Inject(optional = true)
protected List<HttpRequestFilter> requestFilters = Collections.emptyList(); protected List<HttpRequestFilter> requestFilters = Collections.emptyList();
@RedirectHandler
@Inject(optional = true)
protected HttpResponseHandler redirectHandler = new CloseContentAndSetExceptionHandler();
@ClientErrorHandler
@Inject(optional = true)
protected HttpResponseHandler clientErrorHandler = new CloseContentAndSetExceptionHandler();
@ServerErrorHandler
@Inject(optional = true)
protected HttpResponseHandler serverErrorHandler = new CloseContentAndSetExceptionHandler();
@RetryHandler @Inject(optional = true)
protected HttpErrorHandler httpErrorHandler = new CloseContentAndSetExceptionHandler();
@Inject(optional = true) @Inject(optional = true)
protected HttpRetryHandler httpRetryHandler = new BackoffLimitedRetryHandler(5); protected HttpRetryHandler httpRetryHandler = new BackoffLimitedRetryHandler(5);
@Inject public void submit(HttpFutureCommand<?> command) {
public BaseHttpFutureCommandClient(URI target) { HttpRequest request = command.getRequest();
this.target = target;
Q nativeRequest = null;
try {
for (HttpRequestFilter filter : requestFilters) {
filter.filter(request);
} }
HttpResponse response = null;
for (;;) {
logger.trace("%1$s - converting request %2$s", request.getEndPoint(), request);
nativeRequest = convert(request);
response = invoke(nativeRequest);
int statusCode = response.getStatusCode();
if (statusCode >= 500 && httpRetryHandler.shouldRetryRequest(command, response))
continue;
break;
}
handleResponse(command, response);
} catch (Exception e) {
command.setException(e);
} finally {
cleanup(nativeRequest);
}
}
protected abstract Q convert(HttpRequest request) throws IOException;
protected abstract HttpResponse invoke(Q nativeRequest) throws IOException;
protected abstract void cleanup(Q nativeResponse);
protected void handleResponse(HttpFutureCommand<?> command, HttpResponse response) { protected void handleResponse(HttpFutureCommand<?> command, HttpResponse response) {
int code = response.getStatusCode(); int code = response.getStatusCode();
if (code >= 500) { if (code >= 300) {
serverErrorHandler.handle(command, response); httpErrorHandler.handle(command, response);
} else if (code >= 400 && code < 500) {
clientErrorHandler.handle(command, response);
} else if (code >= 300 && code < 400) {
redirectHandler.handle(command, response);
} else { } else {
command.getResponseFuture().setResponse(response); command.getResponseFuture().setResponse(response);
command.getResponseFuture().run(); command.getResponseFuture().run();

View File

@ -30,70 +30,36 @@ import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL; import java.net.URL;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.jclouds.http.HttpConstants; import org.jclouds.http.HttpConstants;
import org.jclouds.http.HttpFutureCommand;
import org.jclouds.http.HttpFutureCommandClient; import org.jclouds.http.HttpFutureCommandClient;
import org.jclouds.http.HttpMethod;
import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpRequestFilter;
import org.jclouds.http.HttpResponse; import org.jclouds.http.HttpResponse;
import com.google.inject.Inject;
/** /**
* Basic implementation of a {@link HttpFutureCommandClient}. * Basic implementation of a {@link HttpFutureCommandClient}.
* *
* @author Adrian Cole * @author Adrian Cole
*/ */
public class JavaUrlHttpFutureCommandClient extends BaseHttpFutureCommandClient { public class JavaUrlHttpFutureCommandClient extends BaseHttpFutureCommandClient<HttpURLConnection> {
@Inject @Override
public JavaUrlHttpFutureCommandClient(URI target) throws MalformedURLException { protected HttpResponse invoke(HttpURLConnection connection) throws IOException {
super(target);
}
public void submit(HttpFutureCommand<?> command) {
HttpRequest request = command.getRequest();
HttpURLConnection connection = null;
try {
HttpResponse response = null;
for (;;) {
for (HttpRequestFilter filter : requestFilters) {
filter.filter(request);
}
logger.trace("%1$s - converting request %2$s", target, request);
connection = openJavaConnection(request);
logger.trace("%1$s - submitting request %2$s", target, connection);
response = getResponse(connection);
logger.trace("%1$s - received response %2$s", target, response);
if (response.getStatusCode() >= 500 && httpRetryHandler.retryRequest(command, response))
continue;
break;
}
handleResponse(command, response);
} catch (Exception e) {
command.setException(e);
} finally {
// DO NOT disconnect, as it will also close the unconsumed
// outputStream from above.
if (request.getMethod().equals(HttpMethod.HEAD))
connection.disconnect();
}
}
protected HttpResponse getResponse(HttpURLConnection connection) throws IOException {
HttpResponse response = new HttpResponse(); HttpResponse response = new HttpResponse();
if (logger.isTraceEnabled())
logger.trace("%1$s - submitting request %2$s, headers: %3$s", connection.getURL()
.getHost(), connection.getURL(), connection.getRequestProperties());
InputStream in; InputStream in;
try { try {
in = connection.getInputStream(); in = connection.getInputStream();
} catch (IOException e) { } catch (IOException e) {
in = connection.getErrorStream(); in = connection.getErrorStream();
} }
if (logger.isTraceEnabled())
logger.info("%1$s - received response code %2$s, headers: %3$s", connection.getURL()
.getHost(), connection.getResponseCode(), connection.getHeaderFields());
if (in != null) { if (in != null) {
response.setContent(in); response.setContent(in);
} }
@ -106,8 +72,9 @@ public class JavaUrlHttpFutureCommandClient extends BaseHttpFutureCommandClient
return response; return response;
} }
protected HttpURLConnection openJavaConnection(HttpRequest request) throws IOException { @Override
URL url = new URL(target.toURL(), request.getUri()); protected HttpURLConnection convert(HttpRequest request) throws IOException {
URL url = new URL(request.getEndPoint().toURL(), request.getUri());
HttpURLConnection connection = (HttpURLConnection) url.openConnection(); HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setDoOutput(true); connection.setDoOutput(true);
connection.setAllowUserInteraction(false); connection.setAllowUserInteraction(false);
@ -143,4 +110,14 @@ public class JavaUrlHttpFutureCommandClient extends BaseHttpFutureCommandClient
} }
return connection; return connection;
} }
/**
* Only disconnect if there is no content, as disconnecting will throw away unconsumed content.
*/
@Override
protected void cleanup(HttpURLConnection connection) {
if (connection.getContentLength() == 0)
connection.disconnect();
}
} }

View File

@ -34,7 +34,6 @@ import java.util.concurrent.ExecutionException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.jclouds.http.annotation.RetryHandler;
import org.jclouds.http.commands.GetString; import org.jclouds.http.commands.GetString;
import org.jclouds.http.config.JavaUrlHttpFutureCommandClientModule; import org.jclouds.http.config.JavaUrlHttpFutureCommandClientModule;
import org.jclouds.http.handlers.BackoffLimitedRetryHandler; import org.jclouds.http.handlers.BackoffLimitedRetryHandler;

View File

@ -37,6 +37,7 @@ import org.jclouds.http.commands.CommandFactory;
import org.jclouds.http.commands.config.HttpCommandsModule; import org.jclouds.http.commands.config.HttpCommandsModule;
import org.jclouds.lifecycle.Closer; import org.jclouds.lifecycle.Closer;
import org.jclouds.logging.jdk.config.JDKLoggingModule; import org.jclouds.logging.jdk.config.JDKLoggingModule;
import org.jclouds.util.Utils;
import org.mortbay.jetty.Handler; import org.mortbay.jetty.Handler;
import org.mortbay.jetty.Request; import org.mortbay.jetty.Request;
import org.mortbay.jetty.Server; import org.mortbay.jetty.Server;
@ -98,9 +99,17 @@ public abstract class BaseJettyTest {
Handler server2Handler = new AbstractHandler() { Handler server2Handler = new AbstractHandler() {
public void handle(String target, HttpServletRequest request, public void handle(String target, HttpServletRequest request,
HttpServletResponse response, int dispatch) throws IOException, ServletException { HttpServletResponse response, int dispatch) throws IOException, ServletException {
if (request.getMethod().equals("PUT")) {
if (request.getContentLength() > 0) {
Utils.toStringAndClose(request.getInputStream());
} else {
response.sendError(500, "no content");
}
} else {
response.setContentType("text/xml"); response.setContentType("text/xml");
response.setStatus(HttpServletResponse.SC_OK); response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().println(XML2); response.getWriter().println(XML2);
}
((Request) request).setHandled(true); ((Request) request).setHandled(true);
} }
}; };

View File

@ -35,14 +35,13 @@ import java.util.Properties;
* @author Adrian Cole * @author Adrian Cole
*/ */
@Test @Test
public class JavaUrlHttpFutureCommandFutureCommandClientTest extends BaseHttpFutureCommandClientTest { public class JavaUrlHttpFutureCommandClientTest extends BaseHttpFutureCommandClientTest {
protected Module createClientModule() { protected Module createClientModule() {
return new JavaUrlHttpFutureCommandClientModule(); return new JavaUrlHttpFutureCommandClientModule();
} }
protected void addConnectionProperties(Properties props) { protected void addConnectionProperties(Properties props) {
//NONE // NONE
} }
} }

View File

@ -111,7 +111,7 @@ public class BackoffLimitedRetryHandlerTest {
assertEquals(response.getContent().available(), 1); assertEquals(response.getContent().available(), 1);
assertEquals(response.getContent().read(), 1); assertEquals(response.getContent().read(), 1);
handler.retryRequest(command, response); handler.shouldRetryRequest(command, response);
assertEquals(response.getContent().available(), 0); assertEquals(response.getContent().available(), 0);
assertEquals(response.getContent().read(), -1); assertEquals(response.getContent().read(), -1);
@ -123,13 +123,13 @@ public class BackoffLimitedRetryHandlerTest {
"uri", new ReturnStringIf200()); "uri", new ReturnStringIf200());
HttpResponse response = new HttpResponse(); HttpResponse response = new HttpResponse();
handler.retryRequest(command, response); handler.shouldRetryRequest(command, response);
assertEquals(command.getFailureCount(), 1); assertEquals(command.getFailureCount(), 1);
handler.retryRequest(command, response); handler.shouldRetryRequest(command, response);
assertEquals(command.getFailureCount(), 2); assertEquals(command.getFailureCount(), 2);
handler.retryRequest(command, response); handler.shouldRetryRequest(command, response);
assertEquals(command.getFailureCount(), 3); assertEquals(command.getFailureCount(), 3);
} }
@ -139,17 +139,17 @@ public class BackoffLimitedRetryHandlerTest {
"uri", new ReturnStringIf200()); "uri", new ReturnStringIf200());
HttpResponse response = new HttpResponse(); HttpResponse response = new HttpResponse();
assertEquals(handler.retryRequest(command, response), true); // Failure 1 assertEquals(handler.shouldRetryRequest(command, response), true); // Failure 1
assertEquals(handler.retryRequest(command, response), true); // Failure 2 assertEquals(handler.shouldRetryRequest(command, response), true); // Failure 2
assertEquals(handler.retryRequest(command, response), true); // Failure 3 assertEquals(handler.shouldRetryRequest(command, response), true); // Failure 3
assertEquals(handler.retryRequest(command, response), true); // Failure 4 assertEquals(handler.shouldRetryRequest(command, response), true); // Failure 4
assertEquals(handler.retryRequest(command, response), true); // Failure 5 assertEquals(handler.shouldRetryRequest(command, response), true); // Failure 5
assertEquals(handler.retryRequest(command, response), false); // Failure 6 assertEquals(handler.shouldRetryRequest(command, response), false); // Failure 6
} }
} }

View File

@ -37,10 +37,9 @@ import java.util.List;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.jclouds.http.HttpConstants; import org.jclouds.http.HttpConstants;
import org.jclouds.http.HttpFutureCommand;
import org.jclouds.http.HttpFutureCommandClient; import org.jclouds.http.HttpFutureCommandClient;
import org.jclouds.http.HttpHeaders;
import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpRequestFilter;
import org.jclouds.http.HttpResponse; import org.jclouds.http.HttpResponse;
import org.jclouds.http.internal.BaseHttpFutureCommandClient; import org.jclouds.http.internal.BaseHttpFutureCommandClient;
@ -58,61 +57,12 @@ import com.google.inject.Inject;
* *
* @author Adrian Cole * @author Adrian Cole
*/ */
public class URLFetchServiceClient extends BaseHttpFutureCommandClient { public class URLFetchServiceClient extends BaseHttpFutureCommandClient<HTTPRequest> {
private final URLFetchService urlFetchService; private final URLFetchService urlFetchService;
private final int port;
private final boolean isSecure;
@Inject @Inject
public URLFetchServiceClient(URI target, URLFetchService urlFetchService) public URLFetchServiceClient(URLFetchService urlFetchService) throws MalformedURLException {
throws MalformedURLException {
super(target);
this.urlFetchService = urlFetchService; this.urlFetchService = urlFetchService;
this.port = target.getPort();
this.isSecure = target.getScheme().equals("https");
}
public void submit(HttpFutureCommand<?> command) {
HttpRequest request = command.getRequest();
HTTPResponse gaeResponse = null;
try {
for (HttpRequestFilter filter : requestFilters) {
filter.filter(request);
}
HttpResponse response = null;
for (;;) {
logger.trace("%1$s - converting request %2$s", target, request);
HTTPRequest gaeRequest = convert(request);
if (logger.isTraceEnabled())
logger.trace("%1$s - submitting request %2$s, headers: %3$s", target, gaeRequest
.getURL(), headersAsString(gaeRequest.getHeaders()));
gaeResponse = this.urlFetchService.fetch(gaeRequest);
if (logger.isTraceEnabled())
logger.info("%1$s - received response code %2$s, headers: %3$s", target, gaeResponse
.getResponseCode(), headersAsString(gaeResponse.getHeaders()));
response = convert(gaeResponse);
int statusCode = response.getStatusCode();
if (statusCode >= 500 && httpRetryHandler.retryRequest(command, response))
continue;
break;
}
handleResponse(command, response);
} catch (Exception e) {
if (gaeResponse != null && gaeResponse.getContent() != null) {
logger.error(e, "error encountered during the execution: %1$s%n%2$s", gaeResponse,
new String(gaeResponse.getContent()));
}
command.setException(e);
}
}
String headersAsString(List<HTTPHeader> headers) {
StringBuilder builder = new StringBuilder("");
for (HTTPHeader header : headers)
builder.append("[").append(header.getName()).append("=").append(header.getValue()).append(
"],");
return builder.toString();
} }
/** /**
@ -142,7 +92,7 @@ public class URLFetchServiceClient extends BaseHttpFutureCommandClient {
} }
@VisibleForTesting @VisibleForTesting
HttpResponse convert(HTTPResponse gaeResponse) { protected HttpResponse convert(HTTPResponse gaeResponse) {
HttpResponse response = new HttpResponse(); HttpResponse response = new HttpResponse();
response.setStatusCode(gaeResponse.getResponseCode()); response.setStatusCode(gaeResponse.getResponseCode());
for (HTTPHeader header : gaeResponse.getHeaders()) { for (HTTPHeader header : gaeResponse.getHeaders()) {
@ -155,17 +105,11 @@ public class URLFetchServiceClient extends BaseHttpFutureCommandClient {
} }
@VisibleForTesting @VisibleForTesting
HTTPRequest convert(HttpRequest request) throws IOException { protected HTTPRequest convert(HttpRequest request) throws IOException {
String hostHeader = request.getFirstHeaderOrNull(HttpConstants.HOST);
URL url; convertHostHeaderToEndPoint(request);
// As host headers are not supported in GAE/J v1.2.1, we'll change the
// hostname of the destination to the same value as the host header URL url = new URL(request.getEndPoint().toURL(), request.getUri());
if (hostHeader != null) {
url = new URL(new URL(isSecure ? "https" : "http", hostHeader, port, "/"), request
.getUri());
} else {
url = new URL(target.toURL(), request.getUri());
}
FetchOptions options = disallowTruncate(); FetchOptions options = disallowTruncate();
followRedirectsUnlessRequestContainsPayload(request, options); followRedirectsUnlessRequestContainsPayload(request, options);
@ -174,13 +118,10 @@ public class URLFetchServiceClient extends BaseHttpFutureCommandClient {
.toString()), options); .toString()), options);
for (String header : request.getHeaders().keySet()) { for (String header : request.getHeaders().keySet()) {
// GAE/J v1.2.1 re-writes the host header, so we'll skip it.
if (!header.equals(HttpConstants.HOST)) {
for (String value : request.getHeaders().get(header)) { for (String value : request.getHeaders().get(header)) {
gaeRequest.addHeader(new HTTPHeader(header, value)); gaeRequest.addHeader(new HTTPHeader(header, value));
} }
} }
}
if (request.getPayload() != null) { if (request.getPayload() != null) {
changeRequestContentToBytes(request); changeRequestContentToBytes(request);
@ -191,6 +132,24 @@ public class URLFetchServiceClient extends BaseHttpFutureCommandClient {
return gaeRequest; return gaeRequest;
} }
/**
* As host headers are not supported in GAE/J v1.2.1, we'll change the hostname of the
* destination to the same value as the host header
*
* @param request
*/
@VisibleForTesting
void convertHostHeaderToEndPoint(HttpRequest request) {
String hostHeader = request.getFirstHeaderOrNull(HttpConstants.HOST);
if (hostHeader != null) {
request.setEndPoint(URI.create(String.format("%1$s://%2$s:%3$d", request.getEndPoint()
.getScheme(), hostHeader, request.getEndPoint().getPort())));
request.getHeaders().removeAll(HttpHeaders.HOST);
}
}
private void followRedirectsUnlessRequestContainsPayload(HttpRequest request, private void followRedirectsUnlessRequestContainsPayload(HttpRequest request,
FetchOptions options) { FetchOptions options) {
if (request.getPayload() != null) if (request.getPayload() != null)
@ -198,4 +157,32 @@ public class URLFetchServiceClient extends BaseHttpFutureCommandClient {
else else
options.followRedirects(); options.followRedirects();
} }
/**
* nothing to clean up.
*/
@Override
protected void cleanup(HTTPRequest nativeRequest) {
}
@Override
protected HttpResponse invoke(HTTPRequest request) throws IOException {
if (logger.isTraceEnabled())
logger.trace("%1$s - submitting request %2$s, headers: %3$s", request.getURL().getHost(),
request.getURL(), headersAsString(request.getHeaders()));
HTTPResponse response = urlFetchService.fetch(request);
if (logger.isTraceEnabled())
logger.info("%1$s - received response code %2$s, headers: %3$s", request.getURL()
.getHost(), response.getResponseCode(), headersAsString(response.getHeaders()));
return convert(response);
}
String headersAsString(List<HTTPHeader> headers) {
StringBuilder builder = new StringBuilder("");
for (HTTPHeader header : headers)
builder.append("[").append(header.getName()).append("=").append(header.getValue()).append(
"],");
return builder.toString();
}
} }

View File

@ -64,7 +64,16 @@ public class URLFetchServiceClientTest {
@BeforeTest @BeforeTest
void setupClient() throws MalformedURLException { void setupClient() throws MalformedURLException {
endPoint = URI.create("http://localhost:80"); endPoint = URI.create("http://localhost:80");
client = new URLFetchServiceClient(endPoint, createNiceMock(URLFetchService.class)); client = new URLFetchServiceClient(createNiceMock(URLFetchService.class));
}
@Test
void testConvertHostHeaderToEndPoint() {
HttpRequest request = new HttpRequest(endPoint, HttpMethod.GET, "foo");
request.getHeaders().put(HttpHeaders.HOST, "weird");
client.convertHostHeaderToEndPoint(request);
assertEquals(request.getEndPoint().getHost(), "weird");
assert request.getFirstHeaderOrNull(HttpHeaders.HOST) == null;
} }
@Test @Test

View File

@ -35,14 +35,10 @@ import org.apache.http.HttpResponse;
import org.apache.http.nio.entity.ConsumingNHttpEntity; import org.apache.http.nio.entity.ConsumingNHttpEntity;
import org.apache.http.nio.protocol.NHttpRequestExecutionHandler; import org.apache.http.nio.protocol.NHttpRequestExecutionHandler;
import org.apache.http.protocol.HttpContext; import org.apache.http.protocol.HttpContext;
import org.jclouds.http.HttpErrorHandler;
import org.jclouds.http.HttpFutureCommand; import org.jclouds.http.HttpFutureCommand;
import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpResponseHandler;
import org.jclouds.http.HttpRetryHandler; import org.jclouds.http.HttpRetryHandler;
import org.jclouds.http.annotation.ClientErrorHandler;
import org.jclouds.http.annotation.RedirectHandler;
import org.jclouds.http.annotation.RetryHandler;
import org.jclouds.http.annotation.ServerErrorHandler;
import org.jclouds.http.handlers.BackoffLimitedRetryHandler; import org.jclouds.http.handlers.BackoffLimitedRetryHandler;
import org.jclouds.http.handlers.CloseContentAndSetExceptionHandler; import org.jclouds.http.handlers.CloseContentAndSetExceptionHandler;
import org.jclouds.http.httpnio.util.HttpNioUtils; import org.jclouds.http.httpnio.util.HttpNioUtils;
@ -55,27 +51,16 @@ import com.google.inject.Inject;
* *
* @author Adrian Cole * @author Adrian Cole
*/ */
public class HttpNioFutureCommandExecutionHandler implements public class HttpNioFutureCommandExecutionHandler implements NHttpRequestExecutionHandler {
NHttpRequestExecutionHandler {
private final ExecutorService executor; private final ExecutorService executor;
@Resource @Resource
protected Logger logger = Logger.NULL; protected Logger logger = Logger.NULL;
private final ConsumingNHttpEntityFactory entityFactory; private final ConsumingNHttpEntityFactory entityFactory;
private final BlockingQueue<HttpFutureCommand<?>> commandQueue; private final BlockingQueue<HttpFutureCommand<?>> commandQueue;
@RedirectHandler
@Inject(optional = true) @Inject(optional = true)
private HttpResponseHandler redirectHandler = new CloseContentAndSetExceptionHandler(); private HttpErrorHandler serverErrorHandler = new CloseContentAndSetExceptionHandler();
@ClientErrorHandler
@Inject(optional = true)
private HttpResponseHandler clientErrorHandler = new CloseContentAndSetExceptionHandler();
@ServerErrorHandler
@Inject(optional = true)
private HttpResponseHandler serverErrorHandler = new CloseContentAndSetExceptionHandler();
@RetryHandler
@Inject(optional = true) @Inject(optional = true)
protected HttpRetryHandler httpRetryHandler = new BackoffLimitedRetryHandler(5); protected HttpRetryHandler httpRetryHandler = new BackoffLimitedRetryHandler(5);
@ -84,10 +69,8 @@ public class HttpNioFutureCommandExecutionHandler implements
} }
@Inject @Inject
public HttpNioFutureCommandExecutionHandler( public HttpNioFutureCommandExecutionHandler(ConsumingNHttpEntityFactory entityFactory,
ConsumingNHttpEntityFactory entityFactory, ExecutorService executor, BlockingQueue<HttpFutureCommand<?>> commandQueue) {
ExecutorService executor,
BlockingQueue<HttpFutureCommand<?>> commandQueue) {
this.executor = executor; this.executor = executor;
this.entityFactory = entityFactory; this.entityFactory = entityFactory;
this.commandQueue = commandQueue; this.commandQueue = commandQueue;
@ -97,8 +80,7 @@ public class HttpNioFutureCommandExecutionHandler implements
} }
public HttpEntityEnclosingRequest submitRequest(HttpContext context) { public HttpEntityEnclosingRequest submitRequest(HttpContext context) {
HttpFutureCommand<?> command = (HttpFutureCommand<?>) context HttpFutureCommand<?> command = (HttpFutureCommand<?>) context.removeAttribute("command");
.removeAttribute("command");
if (command != null) { if (command != null) {
HttpRequest object = command.getRequest(); HttpRequest object = command.getRequest();
return HttpNioUtils.convertToApacheRequest(object); return HttpNioUtils.convertToApacheRequest(object);
@ -107,13 +89,12 @@ public class HttpNioFutureCommandExecutionHandler implements
} }
public ConsumingNHttpEntity responseEntity(HttpResponse response, public ConsumingNHttpEntity responseEntity(HttpResponse response, HttpContext context)
HttpContext context) throws IOException { throws IOException {
return entityFactory.create(response.getEntity()); return entityFactory.create(response.getEntity());
} }
public void handleResponse(HttpResponse apacheResponse, HttpContext context) public void handleResponse(HttpResponse apacheResponse, HttpContext context) throws IOException {
throws IOException {
HttpNioFutureCommandConnectionHandle handle = (HttpNioFutureCommandConnectionHandle) context HttpNioFutureCommandConnectionHandle handle = (HttpNioFutureCommandConnectionHandle) context
.removeAttribute("command-handle"); .removeAttribute("command-handle");
if (handle != null) { if (handle != null) {
@ -126,19 +107,19 @@ public class HttpNioFutureCommandExecutionHandler implements
if (code >= 500) { if (code >= 500) {
boolean retryRequest = false; boolean retryRequest = false;
try { try {
retryRequest = httpRetryHandler.retryRequest(command, response); retryRequest = httpRetryHandler.shouldRetryRequest(command, response);
} catch (InterruptedException ie) { } catch (InterruptedException ie) {
// TODO: Add interrupt exception to command and abort? // TODO: Add interrupt exception to command and abort?
} }
if (retryRequest) { if (retryRequest) {
commandQueue.add(command); commandQueue.add(command);
} else { } else {
this.serverErrorHandler.handle(command, response); serverErrorHandler.handle(command, response);
} }
} else if (code >= 400 && code < 500) { } else if (code >= 400 && code < 500) {
this.clientErrorHandler.handle(command, response); serverErrorHandler.handle(command, response);
} else if (code >= 300 && code < 400) { } else if (code >= 300 && code < 400) {
this.redirectHandler.handle(command, response); serverErrorHandler.handle(command, response);
} else { } else {
processResponse(response, command); processResponse(response, command);
} }
@ -151,8 +132,7 @@ public class HttpNioFutureCommandExecutionHandler implements
} }
} }
protected void releaseConnectionToPool( protected void releaseConnectionToPool(HttpNioFutureCommandConnectionHandle handle) {
HttpNioFutureCommandConnectionHandle handle) {
try { try {
handle.release(); handle.release();
} catch (InterruptedException e) { } catch (InterruptedException e) {
@ -163,8 +143,7 @@ public class HttpNioFutureCommandExecutionHandler implements
protected void processResponse(org.jclouds.http.HttpResponse response, protected void processResponse(org.jclouds.http.HttpResponse response,
HttpFutureCommand<?> command) throws IOException { HttpFutureCommand<?> command) throws IOException {
command.getResponseFuture().setResponse(response); command.getResponseFuture().setResponse(response);
logger.trace("submitting response task %1$s", command logger.trace("submitting response task %1$s", command.getResponseFuture());
.getResponseFuture());
executor.submit(command.getResponseFuture()); executor.submit(command.getResponseFuture());
} }