Refactored outbound HTTP support to include HttpRequest and HttpRequestExecutor (formerly HttpExchanger). Also refactored existing RequestMapper to InboundRequestMapper, and added OutboundRequestMapper.

This commit is contained in:
Mark Fisher
2009-03-19 18:55:11 +00:00
parent a75cddfa14
commit 703e883a02
16 changed files with 565 additions and 337 deletions

View File

@@ -0,0 +1,94 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* Licensed 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.springframework.integration.http;
import java.io.IOException;
import java.io.InputStream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Base class for {@link HttpRequestExecutor} implementations.
*
* @author Mark Fisher
* @author Juergen Hoeller
* @since 1.0.2
*/
public abstract class AbstractHttpRequestExecutor implements HttpRequestExecutor {
protected static final String HTTP_HEADER_ACCEPT_LANGUAGE = "Accept-Language";
protected static final String HTTP_HEADER_ACCEPT_ENCODING = "Accept-Encoding";
protected static final String HTTP_HEADER_CONTENT_ENCODING = "Content-Encoding";
protected static final String HTTP_HEADER_CONTENT_TYPE = "Content-Type";
protected static final String HTTP_HEADER_CONTENT_LENGTH = "Content-Length";
protected static final String ENCODING_GZIP = "gzip";
protected final Log logger = LogFactory.getLog(getClass());
private boolean acceptGzipEncoding = true;
/**
* Set whether to accept GZIP encoding, that is, whether to
* send the HTTP "Accept-Encoding" header with "gzip" as value.
* <p>Default is "true". Turn this flag off if you do not want
* GZIP response compression even if enabled on the HTTP server.
*/
public void setAcceptGzipEncoding(boolean acceptGzipEncoding) {
this.acceptGzipEncoding = acceptGzipEncoding;
}
/**
* Return whether to accept GZIP encoding, that is, whether to
* send the HTTP "Accept-Encoding" header with "gzip" as its value.
*/
public boolean isAcceptGzipEncoding() {
return this.acceptGzipEncoding;
}
/**
* Execute a request to send its content to its target URL.
* @param request the request to execute
* @return the InputStream result
* @throws IOException if thrown by I/O operations
* @throws Exception in case of general errors
*/
public final InputStream executeRequest(HttpRequest request) throws Exception {
if (logger.isDebugEnabled()) {
StringBuilder sb = new StringBuilder("Sending HTTP request to [" + request.getTargetUrl() + "]");
Integer contentLength = request.getContentLength();
if (contentLength != null) {
sb.append(", with size " + contentLength);
}
logger.debug(sb.toString());
}
return doExecuteRequest(request);
}
/**
* Subclasses must implement this method to execute the request.
*/
protected abstract InputStream doExecuteRequest(HttpRequest request) throws Exception;
}

View File

@@ -32,18 +32,18 @@ import org.springframework.web.bind.support.WebBindingInitializer;
import org.springframework.web.servlet.handler.DispatcherServletWebRequest;
/**
* RequestMapper implementation that binds the request parameter map to a
* target instance. The target instance may be a non-singleton bean as
* InboundRequestMapper implementation that binds the request parameter map to
* a target instance. The target instance may be a non-singleton bean as
* specified by the {@link #setTargetBeanName(String) 'targetBeanName'}
* property. Otherwise, this transformer's target type must provide a
* default no-arg constructor.
* property. Otherwise, this mapper's target type must provide a default,
* no-arg constructor.
*
* @author Mark Fisher
* @since 1.0.2
*/
public class DataBindingRequestMapper implements RequestMapper, BeanFactoryAware, InitializingBean {
public class DataBindingInboundRequestMapper implements InboundRequestMapper, BeanFactoryAware, InitializingBean {
private final Class<?> targetType;
private volatile Class<?> targetType = Object.class;
private volatile String targetBeanName;
@@ -54,12 +54,20 @@ public class DataBindingRequestMapper implements RequestMapper, BeanFactoryAware
private volatile boolean validated;
public DataBindingRequestMapper(Class<?> targetType) {
public DataBindingInboundRequestMapper() {
this.targetType = Object.class;
}
public DataBindingInboundRequestMapper(Class<?> targetType) {
Assert.notNull(targetType, "targetType must not be null");
this.targetType = targetType;
}
public void setTargetType(Class<?> targetType) {
this.targetType = targetType;
}
/**
* Specify the name of a bean definition to use when creating the target
* instance. The bean must <em>not</em> be a singleton, and it must be
@@ -89,6 +97,10 @@ public class DataBindingRequestMapper implements RequestMapper, BeanFactoryAware
}
public final void afterPropertiesSet() {
if (this.targetBeanName == null && Object.class.equals(this.targetType)) {
throw new IllegalArgumentException(
"When no 'targetBeanName' is provided, the 'targetType' must be more specific than Object.");
}
this.validateTargetBeanIfNecessary();
}
@@ -98,12 +110,16 @@ public class DataBindingRequestMapper implements RequestMapper, BeanFactoryAware
if (this.beanFactory.isSingleton(this.targetBeanName)) {
throw new IllegalArgumentException("binding target bean must not be a singleton");
}
Class<?> beanType = this.beanFactory.getType(this.targetBeanName);
if (beanType != null) {
Assert.isAssignable(this.targetType, beanType);
}
this.validated = true;
}
}
@SuppressWarnings("unchecked")
public Message<?> mapRequest(HttpServletRequest request) throws Exception {
public Message<?> toMessage(HttpServletRequest request) throws Exception {
ServletRequestDataBinder binder = new ServletRequestDataBinder(getTarget());
this.initBinder(binder, request);
binder.bind(request);

View File

@@ -17,7 +17,6 @@
package org.springframework.integration.http;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.util.ArrayList;
@@ -39,7 +38,7 @@ import org.springframework.integration.core.Message;
import org.springframework.integration.message.MessageBuilder;
/**
* Default implementation of {@link RequestMapper} for inbound HttpServletRequests.
* Default implementation of {@link InboundRequestMapper} for inbound HttpServletRequests.
* The request will be mapped according to the following rules:
* <ul>
* <li>For a GET request, the parameter Map will be copied as the payload.
@@ -64,16 +63,16 @@ import org.springframework.integration.message.MessageBuilder;
* @author Mark Fisher
* @since 1.0.2
*/
public class DefaultRequestMapper implements RequestMapper {
public class DefaultInboundRequestMapper implements InboundRequestMapper {
private Log logger = LogFactory.getLog(getClass());
public Message<?> mapRequest(HttpServletRequest request) throws ServletException, IOException, ResponseStatusCodeException {
public Message<?> toMessage(HttpServletRequest request) throws Exception {
Message<?> message = null;
String contentType = request.getContentType();
if (request.getMethod().equals("GET")) {
message = this.mapGetRequest(request);
message = this.createMessageFromGetRequest(request);
}
else {
Object payload = null;
@@ -121,7 +120,7 @@ public class DefaultRequestMapper implements RequestMapper {
}
@SuppressWarnings("unchecked")
private Message<?> mapGetRequest(HttpServletRequest request) {
private Message<?> createMessageFromGetRequest(HttpServletRequest request) {
if (logger.isDebugEnabled()) {
logger.debug("received GET request, using parameter map as payload");
}

View File

@@ -0,0 +1,139 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* Licensed 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.springframework.integration.http;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.net.URL;
import java.nio.charset.Charset;
import org.springframework.integration.core.Message;
import org.springframework.util.Assert;
/**
* Default implementation of {@link OutboundRequestMapper}.
*
* @author Mark Fisher
* @since 1.0.2
*/
public class DefaultOutboundRequestMapper implements OutboundRequestMapper {
private final URL defaultUrl;
private volatile String charset = "UTF-8";
public DefaultOutboundRequestMapper(URL defaultUrl) {
Assert.notNull(defaultUrl, "default URL must not be null");
this.defaultUrl = defaultUrl;
}
/**
* Specify the charset name to use for converting String-typed payloads to
* bytes. The default is 'UTF-8'.
*/
public void setCharset(String charset) {
Assert.isTrue(Charset.isSupported(charset), "unsupported charset '" + charset + "'");
this.charset = charset;
}
public HttpRequest fromMessage(Message<?> message) throws Exception {
Assert.notNull(message, "message must not be null");
Object payload = message.getPayload();
Assert.notNull(payload, "payload must not be null");
String contentType = null;
byte[] bytes = null;
if (payload instanceof byte[]) {
bytes = (byte[]) payload;
}
else if (payload instanceof String) {
bytes = ((String) payload).getBytes(this.charset);
contentType = "text/plain";
}
else if (payload instanceof Serializable) {
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
ObjectOutputStream objectStream = new ObjectOutputStream(byteStream);
objectStream.writeObject(payload);
objectStream.flush();
objectStream.close();
bytes = byteStream.toByteArray();
contentType = "application/x-java-serialized-object";
}
else {
throw new IllegalArgumentException(
"payload must be a byte array, String, or Serializable object");
}
URL url = this.resolveUrl(message);
String method = "POST"; // TODO: support GET for Map payload
return new DefaultHttpRequest(url, method, bytes, contentType);
}
/**
* Resolve the request URL for the given Message. This implementation
* simply returns the default URL as provided to the constructor.
*/
protected URL resolveUrl(Message<?> message) {
return this.defaultUrl;
}
class DefaultHttpRequest implements HttpRequest {
private final URL targetUrl;
private final String requestMethod;
private final ByteArrayOutputStream requestBody;
private final String contentType;
DefaultHttpRequest(URL targetUrl, String requestMethod, byte[] content, String contentType) throws IOException {
Assert.notNull(targetUrl, "target url must not be null");
this.targetUrl = targetUrl;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write(content);
this.requestBody = baos;
this.contentType = contentType;
this.requestMethod = (requestMethod != null) ? requestMethod : "POST";
}
public URL getTargetUrl() {
return this.targetUrl;
}
public String getRequestMethod() {
return this.requestMethod;
}
public ByteArrayOutputStream getBody() {
return this.requestBody;
}
public Integer getContentLength() {
return this.requestBody.size();
}
public String getContentType() {
return this.contentType;
}
}
}

View File

@@ -1,21 +0,0 @@
package org.springframework.integration.http;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* Strategy that will allow a http request response exchange a with a remote
* server.
*
*
* @author Iwein Fuld
*
*/
public interface HttpExchanger {
InputStream exchange(ByteArrayOutputStream requestBody) throws IOException;
void setContentType(String contentType);
}

View File

@@ -42,11 +42,11 @@ import org.springframework.web.servlet.View;
* By default GET and POST requests are accepted, but the 'supportedMethods'
* property may be set to include others or limit the options (e.g. POST only).
* By default the request will be converted to a Message payload according to
* the rules of the {@link DefaultRequestMapper}.
* the rules of the {@link DefaultInboundRequestMapper}.
* <p/>
* To customize the mapping of the request to the Message payload, provide
* a reference to a {@link RequestMapper} implementation to the
* {@link #setRequestMapper(RequestMapper)} method.
* a reference to an {@link InboundRequestMapper} implementation to the
* {@link #setRequestMapper(InboundRequestMapper)} method.
* <p/>
* The value for {@link #expectReply} is <code>false</code> by default.
* This means that as soon as the Message is created and passed to the
@@ -87,7 +87,7 @@ public class HttpInboundEndpoint extends SimpleMessagingGateway implements HttpR
private volatile boolean expectReply;
private volatile RequestMapper requestMapper = new DefaultRequestMapper();
private volatile InboundRequestMapper requestMapper = new DefaultInboundRequestMapper();
private volatile boolean extractReplyPayload = true;
@@ -120,11 +120,11 @@ public class HttpInboundEndpoint extends SimpleMessagingGateway implements HttpR
}
/**
* Specify a {@link RequestMapper} implementation to map from the
* Specify a {@link InboundRequestMapper} implementation to map from the
* inbound HTTP request to a Message. The default implementation
* is {@link DefaultRequestMapper}.
* is {@link DefaultInboundRequestMapper}.
*/
public void setRequestMapper(RequestMapper requestMapper) {
public void setRequestMapper(InboundRequestMapper requestMapper) {
Assert.notNull(requestMapper, "requestMapper must not be null");
this.requestMapper = requestMapper;
}
@@ -178,7 +178,7 @@ public class HttpInboundEndpoint extends SimpleMessagingGateway implements HttpR
return;
}
try {
Message<?> requestMessage = this.requestMapper.mapRequest(request);
Message<?> requestMessage = this.requestMapper.toMessage(request);
Object reply = this.handleRequestMessage(requestMessage);
this.generateResponse(requestMessage, reply, request, response);
}

View File

@@ -17,112 +17,47 @@
package org.springframework.integration.http;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.net.URL;
import org.springframework.integration.core.Message;
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
import org.springframework.integration.handler.ReplyMessageHolder;
import org.springframework.integration.message.MessageHandlingException;
import org.springframework.util.Assert;
import org.springframework.util.FileCopyUtils;
/**
* An outbound endpoint for executing an HTTP request and mapping the response
* to a reply Message.
*
* @author Mark Fisher
* @author Juergen Hoeller
* @author Iwein Fuld
* @since 1.0.2
*/
public class HttpOutboundEndpoint extends AbstractReplyProducingMessageHandler {
private volatile String charset = "UTF-8";
private final OutboundRequestMapper requestMapper;
private volatile boolean acceptGzipEncoding = true;
private volatile HttpRequestExecutor requestExecutor;
private volatile HttpExchanger exchanger;
public HttpOutboundEndpoint(String url) {
this.exchanger = new SimpleHttpExchanger(url);
public HttpOutboundEndpoint(URL url) {
this.requestMapper = new DefaultOutboundRequestMapper(url);
this.requestExecutor = new SimpleHttpRequestExecutor();
}
/**
* Specify the charset name to use for converting String-typed payloads to
* bytes. The default is 'UTF-8'.
*/
public void setCharset(String charset) {
Assert.isTrue(Charset.isSupported(charset), "unsupported charset '"
+ charset + "'");
this.charset = charset;
}
/**
* Specify the content type to use for sending HTTP requests.
* <p>
* Default is "application/x-java-serialized-object".
*/
public void setContentType(String contentType) {
Assert.notNull(contentType, "'contentType' must not be null");
this.exchanger.setContentType(contentType);
}
/**
* Set whether to accept GZIP encoding, that is, whether to send the HTTP
* "Accept-Encoding" header with "gzip" as value.
* <p>
* Default is "true". Turn this flag off if you do not want GZIP response
* compression even if enabled on the HTTP server.
*/
public void setAcceptGzipEncoding(boolean acceptGzipEncoding) {
this.acceptGzipEncoding = acceptGzipEncoding;
}
/**
* Return whether to accept GZIP encoding, that is, whether to send the HTTP
* "Accept-Encoding" header with "gzip" as value.
*/
public boolean isAcceptGzipEncoding() {
return this.acceptGzipEncoding;
}
@Override
protected void handleRequestMessage(Message<?> requestMessage,
ReplyMessageHolder replyMessageHolder) {
Object payload = requestMessage.getPayload();
byte[] bytes = null;
protected void handleRequestMessage(Message<?> requestMessage, ReplyMessageHolder replyMessageHolder) {
try {
if (payload instanceof byte[]) {
bytes = (byte[]) payload;
}
else if (payload instanceof String) {
bytes = ((String) payload).getBytes(this.charset);
}
else if (payload instanceof Serializable) {
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
ObjectOutputStream objectStream = new ObjectOutputStream(
byteStream);
objectStream.writeObject(payload);
objectStream.flush();
objectStream.close();
bytes = byteStream.toByteArray();
}
else {
throw new IllegalArgumentException(
"payload must be a byte array, String, or Serializable object");
}
ByteArrayOutputStream requestByteStream = new ByteArrayOutputStream();
requestByteStream.write(bytes);
HttpRequest request = this.requestMapper.fromMessage(requestMessage);
InputStream responseBody = this.requestExecutor.executeRequest(request);
ByteArrayOutputStream responseByteStream = new ByteArrayOutputStream();
InputStream responseBody = exchanger.exchange(requestByteStream);
FileCopyUtils.copy(responseBody, responseByteStream);
replyMessageHolder.set(responseByteStream.toByteArray());
}
catch (IOException e) {
catch (Exception e) {
throw new MessageHandlingException(requestMessage,
"failed to send HTTP request", e);
"failed to execute HTTP request", e);
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* Licensed 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.springframework.integration.http;
import java.io.ByteArrayOutputStream;
import java.net.URL;
/**
* Representation of an HTTP request to be executed by an implementation of
* the {@link HttpRequestExecutor} strategy.
*
* @author Mark Fisher
* @since 1.0.2
*/
public interface HttpRequest {
/**
* Return the target URL for this request.
*/
URL getTargetUrl();
/**
* Return the request method ("GET", "POST", etc).
*/
String getRequestMethod();
/**
* Return the content type for requests.
*/
String getContentType();
/**
* Return the content length if known, else <code>null</code>.
*/
Integer getContentLength();
/**
* Return the request body as a {@link ByteArrayOutputStream}.
*/
ByteArrayOutputStream getBody();
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* Licensed 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.springframework.integration.http;
import java.io.InputStream;
/**
* Strategy that will allow a http request response exchange with a remote
* server.
*
* @author Iwein Fuld
* @author Mark Fisher
* @since 1.0.2
*/
public interface HttpRequestExecutor {
InputStream executeRequest(HttpRequest request) throws Exception;
}

View File

@@ -18,16 +18,15 @@ package org.springframework.integration.http;
import javax.servlet.http.HttpServletRequest;
import org.springframework.integration.core.Message;
import org.springframework.integration.message.InboundMessageMapper;
/**
* Strategy interface for mapping from an inbound {@link HttpServletRequest}
* to a Message.
*
* @author Mark Fisher
* @since 1.0.2
*/
public interface RequestMapper {
Message<?> mapRequest(HttpServletRequest request) throws Exception;
public interface InboundRequestMapper extends InboundMessageMapper<HttpServletRequest> {
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* Licensed 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.springframework.integration.http;
import org.springframework.integration.message.OutboundMessageMapper;
/**
* Strategy for mapping to an {@link HttpRequest} from a message.
*
* @author Mark Fisher
* @since 1.0.2
*/
public interface OutboundRequestMapper extends OutboundMessageMapper<HttpRequest> {
}

View File

@@ -18,9 +18,10 @@ package org.springframework.integration.http;
/**
* Exception that provides a response status code. This can be used by
* {@link RequestMapper} implementations to indicate an error.
* {@link InboundRequestMapper} implementations to indicate an error.
*
* @author Mark Fisher
* @since 1.0.2
*/
@SuppressWarnings("serial")
public class ResponseStatusCodeException extends Exception {

View File

@@ -1,200 +0,0 @@
package org.springframework.integration.http;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.zip.GZIPInputStream;
import org.springframework.context.i18n.LocaleContext;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
public class SimpleHttpExchanger implements HttpExchanger {
/**
* Default content type: "application/x-java-serialized-object"
*/
public static final String CONTENT_TYPE_SERIALIZED_OBJECT = "application/x-java-serialized-object";
protected static final String HTTP_METHOD_POST = "POST";
protected static final String HTTP_HEADER_ACCEPT_LANGUAGE = "Accept-Language";
protected static final String HTTP_HEADER_ACCEPT_ENCODING = "Accept-Encoding";
protected static final String HTTP_HEADER_CONTENT_ENCODING = "Content-Encoding";
protected static final String HTTP_HEADER_CONTENT_TYPE = "Content-Type";
protected static final String HTTP_HEADER_CONTENT_LENGTH = "Content-Length";
protected static final String ENCODING_GZIP = "gzip";
private volatile String contentType = CONTENT_TYPE_SERIALIZED_OBJECT;
private final URL url;
private volatile boolean acceptGzipEncoding = true;
public SimpleHttpExchanger(String url) {
Assert.notNull(url, "url must not be null");
try {
this.url = new URL(url);
}
catch (MalformedURLException e) {
throw new IllegalArgumentException(e);
}
}
/**
* Specify the content type to use for sending HTTP requests.
* <p>
* Default is "application/x-java-serialized-object".
*/
public void setContentType(String contentType) {
Assert.notNull(contentType, "'contentType' must not be null");
this.contentType = contentType;
}
/**
* Return the content type used for sending HTTP requests.
*/
public String getContentType() {
return contentType;
}
/**
* Set whether to accept GZIP encoding, that is, whether to send the HTTP
* "Accept-Encoding" header with "gzip" as value.
* <p>
* Default is "true". Turn this flag off if you do not want GZIP response
* compression even if enabled on the HTTP server.
*/
public void setAcceptGzipEncoding(boolean acceptGzipEncoding) {
this.acceptGzipEncoding = acceptGzipEncoding;
}
/**
* Return whether to accept GZIP encoding, that is, whether to send the HTTP
* "Accept-Encoding" header with "gzip" as value.
*/
public boolean isAcceptGzipEncoding() {
return this.acceptGzipEncoding;
}
public InputStream exchange(ByteArrayOutputStream requestBody) throws IOException {
HttpURLConnection connection = this.openConnection();
this.prepareConnection(connection, requestBody.size());
this.writeRequestBody(connection, requestBody);
this.validateResponse(connection);
return this.readResponseBody(connection);
}
/**
* Open an HttpURLConnection for this gateway's URL.
* @return the HttpURLConnection for the given request
* @throws IOException if thrown by I/O methods
* @see java.net.URL#openConnection()
*/
private HttpURLConnection openConnection() throws IOException {
URLConnection con = this.url.openConnection();
if (!(con instanceof HttpURLConnection)) {
throw new IOException("Service URL [" + this.url
+ "] is not an HTTP URL");
}
return (HttpURLConnection) con;
}
/**
* Prepare the given HTTP connection.
* <p>
* This implementation specifies POST as method,
* "application/x-java-serialized-object" as "Content-Type" header, and the
* given content length as "Content-Length" header.
* @param con the HTTP connection to prepare
* @param contentLength the length of the content to send
* @throws IOException if thrown by HttpURLConnection methods
* @see java.net.HttpURLConnection#setRequestMethod
* @see java.net.HttpURLConnection#setRequestProperty
*/
private void prepareConnection(HttpURLConnection con, int contentLength)
throws IOException {
con.setDoOutput(true);
con.setRequestMethod(HTTP_METHOD_POST);
con.setRequestProperty(HTTP_HEADER_CONTENT_TYPE, getContentType());
con.setRequestProperty(HTTP_HEADER_CONTENT_LENGTH, Integer
.toString(contentLength));
LocaleContext locale = LocaleContextHolder.getLocaleContext();
if (locale != null) {
con.setRequestProperty(HTTP_HEADER_ACCEPT_LANGUAGE, StringUtils
.toLanguageTag(locale.getLocale()));
}
if (isAcceptGzipEncoding()) {
con.setRequestProperty(HTTP_HEADER_ACCEPT_ENCODING, ENCODING_GZIP);
}
}
private void writeRequestBody(HttpURLConnection con,
ByteArrayOutputStream baos) throws IOException {
baos.writeTo(con.getOutputStream());
}
private void validateResponse(HttpURLConnection con) throws IOException {
if (con.getResponseCode() >= 300) {
throw new IOException(
"Did not receive successful HTTP response: status code = "
+ con.getResponseCode() + ", status message = ["
+ con.getResponseMessage() + "]");
}
}
/**
* Extract the response body from the given executed remote invocation
* request.
* <p>
* This implementation simply reads the serialized invocation from the
* HttpURLConnection's InputStream. If the response is recognized as GZIP
* response, the InputStream will get wrapped in a GZIPInputStream.
* @param config the HTTP invoker configuration that specifies the target
* service
* @param con the HttpURLConnection to read the response body from
* @return an InputStream for the response body
* @throws IOException if thrown by I/O methods
* @see #isGzipResponse
* @see java.util.zip.GZIPInputStream
* @see java.net.HttpURLConnection#getInputStream()
* @see java.net.HttpURLConnection#getHeaderField(int)
* @see java.net.HttpURLConnection#getHeaderFieldKey(int)
*/
private InputStream readResponseBody(HttpURLConnection con)
throws IOException {
if (isGzipResponse(con)) {
// GZIP response found - need to unzip.
return new GZIPInputStream(con.getInputStream());
}
else {
// Plain response found.
return con.getInputStream();
}
}
/**
* Determine whether the given response is a GZIP response.
* <p>
* This implementation checks whether the HTTP "Content-Encoding" header
* contains "gzip" (in any casing).
* @param con the HttpURLConnection to check
*/
private boolean isGzipResponse(HttpURLConnection con) {
String encodingHeader = con
.getHeaderField(HTTP_HEADER_CONTENT_ENCODING);
return (encodingHeader != null && encodingHeader.toLowerCase().indexOf(
ENCODING_GZIP) != -1);
}
}

View File

@@ -0,0 +1,148 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* Licensed 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.springframework.integration.http;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.zip.GZIPInputStream;
import org.springframework.context.i18n.LocaleContext;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.util.StringUtils;
/**
* Implementation of {@link HttpRequestExecutor} that uses {@link HttpURLConnection}
* directly. This version has limited functionality but no additional dependencies.
* For more features, see {@link CommonsHttpRequestExecutor}. (TODO)
*
* @author Juergen Hoeller
* @author Iwein Fuld
* @author Mark Fisher
* @since 1.0.2
*/
public class SimpleHttpRequestExecutor extends AbstractHttpRequestExecutor {
@Override
protected InputStream doExecuteRequest(HttpRequest request) throws Exception {
HttpURLConnection connection = this.openConnection(request.getTargetUrl());
this.prepareConnection(connection, request);
this.writeRequestBody(connection, request.getBody());
this.validateResponse(connection);
return this.readResponseBody(connection);
}
/**
* Open an HttpURLConnection for the given request URL.
* @return the HttpURLConnection for the given request
* @throws IOException if thrown by I/O methods
* @see java.net.URL#openConnection()
*/
private HttpURLConnection openConnection(URL url) throws IOException {
URLConnection con = url.openConnection();
if (!(con instanceof HttpURLConnection)) {
throw new IOException("target URL [" + url + "] is not an HTTP URL");
}
return (HttpURLConnection) con;
}
/**
* Prepare the given HTTP connection.
* <p>
* The request method (e.g. "POST), "Content-Type" header, and content
* length will be determined from the provided {@link HttpRequest}.
* @param request HttpRequest for which the connection should be prepared
* @throws IOException if thrown by HttpURLConnection methods
* @see java.net.HttpURLConnection#setRequestMethod
* @see java.net.HttpURLConnection#setRequestProperty
*/
private void prepareConnection(HttpURLConnection con, HttpRequest request) throws IOException {
con.setDoOutput(true);
con.setRequestMethod(request.getRequestMethod());
String contentType = request.getContentType();
if (contentType != null) {
con.setRequestProperty(HTTP_HEADER_CONTENT_TYPE, contentType);
}
Integer contentLength = request.getContentLength();
if (contentLength != null) {
con.setRequestProperty(HTTP_HEADER_CONTENT_LENGTH, contentLength.toString());
}
LocaleContext locale = LocaleContextHolder.getLocaleContext();
if (locale != null) {
con.setRequestProperty(HTTP_HEADER_ACCEPT_LANGUAGE,
StringUtils.toLanguageTag(locale.getLocale()));
}
if (isAcceptGzipEncoding()) {
con.setRequestProperty(HTTP_HEADER_ACCEPT_ENCODING, ENCODING_GZIP);
}
}
private void writeRequestBody(HttpURLConnection con, ByteArrayOutputStream baos) throws IOException {
baos.writeTo(con.getOutputStream());
}
private void validateResponse(HttpURLConnection con) throws IOException {
if (con.getResponseCode() >= 300) {
throw new IOException(
"Did not receive successful HTTP response: status code = "
+ con.getResponseCode() + ", status message = ["
+ con.getResponseMessage() + "]");
}
}
/**
* Extract the response body from the connection after the request has
* been successfully executed.
* <p>
* This implementation simply reads the HttpURLConnection's InputStream.
* If the response is recognized as GZIP response, the InputStream will be
* wrapped in a GZIPInputStream.
* @param con the HttpURLConnection to read the response body from
* @return an InputStream for the response body
* @throws IOException if thrown by I/O methods
* @see #isGzipResponse
* @see java.util.zip.GZIPInputStream
* @see java.net.HttpURLConnection#getInputStream()
*/
private InputStream readResponseBody(HttpURLConnection con) throws IOException {
if (isGzipResponse(con)) {
// GZIP response found - need to unzip.
return new GZIPInputStream(con.getInputStream());
}
else {
// Plain response found.
return con.getInputStream();
}
}
/**
* Determine whether the given response is a GZIP response.
* <p>
* This implementation checks whether the HTTP "Content-Encoding" header
* contains "gzip" (in any casing).
* @param con the HttpURLConnection to check
*/
private boolean isGzipResponse(HttpURLConnection con) {
String encodingHeader = con.getHeaderField(HTTP_HEADER_CONTENT_ENCODING);
return (encodingHeader != null
&& encodingHeader.toLowerCase().indexOf(ENCODING_GZIP) != -1);
}
}

View File

@@ -157,8 +157,8 @@ public class HttpInboundEndpointTests {
@Test
public void handleRequest_withCustomRequestMapper_requestObjectIsInPayload()
throws ServletException, IOException {
endpoint.setRequestMapper(new RequestMapper() {
public Message<?> mapRequest(HttpServletRequest request) throws Exception {
endpoint.setRequestMapper(new InboundRequestMapper() {
public Message<?> toMessage(HttpServletRequest request) throws Exception {
return new StringMessage(request.getRequestURI());
}
});

View File

@@ -24,22 +24,22 @@ import org.junit.Test;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.integration.core.Message;
import org.springframework.integration.http.DataBindingRequestMapper;
import org.springframework.integration.http.DataBindingInboundRequestMapper;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
/**
* @author Mark Fisher
*/
public class DataBindingRequestMapperTests {
public class DataBindingInboundRequestMapperTests {
@Test
public void bindToType() throws Exception {
DataBindingRequestMapper mapper = new DataBindingRequestMapper(TestBean.class);
DataBindingInboundRequestMapper mapper = new DataBindingInboundRequestMapper(TestBean.class);
MockHttpServletRequest request = new MockHttpServletRequest();
request.setParameter("name", "testBean");
request.setParameter("age", "42");
Message<?> result = mapper.mapRequest(request);
Message<?> result = mapper.toMessage(request);
assertNotNull(result);
assertEquals(TestBean.class, result.getPayload().getClass());
TestBean payload = (TestBean) result.getPayload();
@@ -49,14 +49,14 @@ public class DataBindingRequestMapperTests {
@Test
public void bindToTypeWithBindingInitializer() throws Exception {
DataBindingRequestMapper mapper = new DataBindingRequestMapper(TestBean.class);
DataBindingInboundRequestMapper mapper = new DataBindingInboundRequestMapper(TestBean.class);
ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
initializer.setDirectFieldAccess(true);
mapper.setWebBindingInitializer(initializer);
MockHttpServletRequest request = new MockHttpServletRequest();
request.setParameter("name", "testBean");
request.setParameter("age", "42");
Message<?> result = mapper.mapRequest(request);
Message<?> result = mapper.toMessage(request);
assertNotNull(result);
assertEquals(TestBean.class, result.getPayload().getClass());
TestBean payload = (TestBean) result.getPayload();
@@ -70,12 +70,12 @@ public class DataBindingRequestMapperTests {
MutablePropertyValues properties = new MutablePropertyValues();
properties.addPropertyValue("name", "prototype");
context.registerPrototype("prototypeTarget", TestBean.class, properties);
DataBindingRequestMapper mapper = new DataBindingRequestMapper(TestBean.class);
DataBindingInboundRequestMapper mapper = new DataBindingInboundRequestMapper(TestBean.class);
mapper.setTargetBeanName("prototypeTarget");
mapper.setBeanFactory(context);
MockHttpServletRequest request = new MockHttpServletRequest();
request.setParameter("age", "42");
Message<?> result = mapper.mapRequest(request);
Message<?> result = mapper.toMessage(request);
assertNotNull(result);
assertEquals(TestBean.class, result.getPayload().getClass());
TestBean payload = (TestBean) result.getPayload();