13. HTTP Support

13.1 Introduction

The HTTP support allows for the execution of HTTP requests and the processing of inbound HTTP requests. Because interaction over HTTP is always synchronous, even if all that is returned is a 200 status code, the HTTP support consists of two gateway implementations: HttpInboundEndpoint and HttpRequestExecutingMessageHandler.

13.2 Http Inbound Gateway

To receive messages over HTTP you need to use an HTTP inbound Channel Adapter or Gateway. In common with the HttpInvoker support the HTTP inbound adapters need to be deployed within a servlet container. The easiest way to do this is to provide a servlet definition in web.xml, see Section 14.2, “HttpInvoker Inbound Gateway” for further details. Below is an example bean definition for a simple HTTP inbound endpoint.

<bean id="httpInbound" 
  class="org.springframework.integration.http.inbound.HttpRequestHandlingMessagingGateway">
  <property name="requestChannel" ref="httpRequestChannel" />
  <property name="replyChannel" ref="httpReplyChannel" />
</bean>

The HttpRequestHandlingMessagingGateway accepts a list of HttpMessageConverter instances or else relies on a default list. The converters allow customization of the mapping from HttpServletRequest to Message. The default converters encapsulate simple strategies, which for example will create a String message for a POST request where the content type starts with "text", see the Javadoc for full details.

Starting with this release MultiPart File support was implemented. If the request has been wrapped as a MultipartHttpServletRequest, when using the default converters, that request will be converted to a Message payload that is a MultiValueMap containing values that may be byte arrays, Strings, or instances of Spring's MultipartFile depending on the content type of the individual parts.

[Note]Note
The HTTP inbound Endpoint will locate a MultipartResolver in the context if one exists with the bean name "multipartResolver" (the same name expected by Spring's DispatcherServlet). If it does in fact locate that bean, then the support for MultipartFiles will be enabled on the inbound request mapper. Otherwise, it will fail when trying to map a multipart-file request to a Spring Integration Message. For more on Spring's support for MultipartResolvers, refer to the Spring Reference Manual.

In sending a response to the client there are a number of ways to customize the behavior of the gateway. By default the gateway will simply acknowledge that the request was received by sending a 200 status code back. It is possible to customize this response by providing a 'viewName' to be resolved by the Spring MVC ViewResolver. In the case that the gateway should expect a reply to the Message then setting the expectReply flag (constructor argument) will cause the gateway to wait for a reply Message before creating an HTTP response. Below is an example of a gateway configured to serve as a Spring MVC Controller with a view name. Because of the constructor arg value of TRUE, it wait for a reply. This also shows how to customize the HTTP methods accepted by the gateway, which are POST and GET by default.

<bean id="httpInbound" 
  class="org.springframework.integration.http.inbound.HttpRequestHandlingController">
  <constructor-arg value="true" /> <!-- indicates that a reply is expected -->
  <property name="requestChannel" ref="httpRequestChannel" />
  <property name="replyChannel" ref="httpReplyChannel" />
  <property name="viewName" value="jsonView" />
  <property name="supportedMethodNames" >
    <list>
      <value>GET</value>
      <value>DELETE</value>
    </list>
  </property>
</bean>

The reply message will be available in the Model map. The key that is used for that map entry by default is 'reply', but this can be overridden by setting the 'replyKey' property on the endpoint's configuration.

13.3 Http Outbound Gateway

To configure the HttpRequestExecutingMessageHandler write a bean definition like this:

<bean id="httpOutbound" 
  class="org.springframework.integration.http.outbound.HttpRequestExecutingMessageHandler">
  <constructor-arg value="http://localhost:8080/example" />
  <property name="outputChannel" ref="responseChannel" />
</bean>

This bean definition will execute HTTP requests by delegating to a RestTemplate. That template in turn delegates to a list of HttpMessageConverters to generate the HTTP request body from the Message payload. You can configure those converters as well as the ClientHttpRequestFactory instance to use:

<bean id="httpOutbound" 
  class="org.springframework.integration.http.outbound.HttpRequestExecutingMessageHandler">
  <constructor-arg value="http://localhost:8080/example" />
  <property name="outputChannel" ref="responseChannel" />
  <property name="messageConverters" ref="messageConverterList" />
  <property name="requestFactory" ref="customRequestFactory" />
</bean>

By default the HTTP request will be generated using an instance of SimpleClientHttpRequestFactory which uses the JDK HttpURLConnection. Use of the Apache Commons HTTP Client is also supported through the provided CommonsClientHttpRequestFactory which can be injected as shown above.

13.4 HTTP Namespace Support

Spring Integration provides an "http" namespace and schema definition. To include it in your configuration, simply provide the following URI within a namespace declaration: 'http://www.springframework.org/schema/integration/http'. The schema location should then map to 'http://www.springframework.org/schema/integration/http/spring-integration-http.xsd'.

To configure an inbound http channel adapter which is an instance of HttpInboundEndpoint configured not to expect a response.

<http:inbound-channel-adapter id="httpChannelAdapter" channel="requests" 
    supported-methods="PUT, DELETE"/>

To configure an inbound http gateway which expects a response.

 <http:inbound-gateway id="inboundGateway" 
    request-channel="requests" 
    reply-channel="responses"/>

To configure the outbound gateway you can use the namespace support as well. The following code snippet shows the different configuration options for an outbound Http gateway. Most importantly, notice that the 'http-method' and 'expected-response-type' are provided. Those are two of the most commonly configured values. The default http-method is POST, and the default response type is null. With a null response type, the payload of the reply Message would only contain the status code (e.g. 200) as long as it's a successful status (non-successful status codes will throw Exceptions). If you are expecting a different type, such as a String, then provide that fully-qualified class name as shown below.

<http:outbound-gateway id="example"
    request-channel="requests"
    url="http://localhost/test"
    http-method="POST"
    extract-request-payload="false"
    expected-response-type="java.lang.String"
    charset="UTF-8"
    request-factory="requestFactory"
    request-timeout="1234"
    reply-channel="replies"/>

If your outbound adapter is to be used in a unidirectional way, then you can use an outbound-channel-adapter instead. This means that a successful response will simply execute without sending any Messages to a reply channel. In the case of any non-successful response status code, it will throw an exception. The configuration looks very similar to the gateway:

<http:outbound-channel-adapter id="example"
      url="http://localhost/example"
      http-method="GET"
      channel="requests"
      charset="UTF-8"
      extract-payload="false"
      expected-response-type="java.lang.String"
      request-factory="someRequestFactory"
      order="3"
      auto-startup="false"/>

Mapping URI variables

If your URL contains URI variables you can map them using uri-variable sub element in Http Outbound Gateway configuration.

<http:outbound-gateway id="trafficGateway"
    url="http://local.yahooapis.com/trafficData?appid=YdnDemo&amp;zip={zipCode}" 
		              request-channel="trafficChannel" 
		              http-method="GET" 
		              expected-response-type="java.lang.String">
		<http:uri-variable name="zipCode" expression="payload.getZip()"/>
</http:outbound-gateway>

The uri-variable defines two attributes expression and value. You generally use the value attribute for literal values, but if the value you are trying to inject is dynamic and requires access to Message data you can use a SpEL expression via the expression attribute. In the above configuration the getZip() method will be invoked on the payload object of the Message and the result of that method will be used as the value for URI variable named 'zipCode'.

13.5  HTTP Header Mappings

Spring Integration provides support for Http Header mapping for both HTTP Request and HTTP Responses.

By default all standard Http Headers as defined here http://en.wikipedia.org/wiki/List_of_HTTP_header_fields will be mapped from the message to HTTP request/response headers without further configuration. However if you do need further customization you may provide additional configuration via convenient namespace support. You can provide a comma-separated list of header names, and you can also include simple patterns with the '*' character acting as a wildcard. If you do provide such values, it will override the default behavior. Basically, it assumes you are in complete control at that point. However, if you do want to include all of the standard HTTP headers, you can use the shortcut patterns: HTTP_REQUEST_HEADERS and HTTP_RESPONSE_HEADERS. Here are some examples:

<int-http:outbound-gateway id="httpGateway"
			url="http://localhost/test2"
			mapped-request-headers="foo, bar"
			mapped-response-headers="X-*, HTTP_RESPONSE_HEADERS"
			channel="someChannel"/>
			
<int-http:outbound-channel-adapter id="httpAdapter"
			url="http://localhost/test2"
			mapped-request-headers="foo, bar, HTTP_REQUEST_HEADERS"
			channel="someChannel"/>

The adapters and gateways will use the DefaultHttpHeaderMapper which now provides two static factory methods for "inbound" and "outbound" adapters so that the proper direction can be applied (mapping HTTP requests/responses IN/OUT as appropriate).

If further customization is required you can also configure a DefaultHttpHeaderMapper independently and inject it into the adapter via the header-mapper attribute.

<int-http:outbound-gateway id="httpGateway"
			url="http://localhost/test2"
			header-mapper="headerMapper"
			channel="someChannel"/>

<bean id="headerMapper" class="org.springframework.integration.http.support.DefaultHttpHeaderMapper">
		<property name="inboundHeaderNames" value="foo*, *bar, baz"/>
		<property name="outboundHeaderNames" value="a*b, d"/>
</bean>

Of course, you can even implement the HeaderMapper strategy interface directly and provide a reference to that if you need to do something other than what the DefaultHttpHeaderMapper supports.

13.6 HTTP Samples

13.6.1 Multipart HTTP request - RestTemplate (client) and Http Inbound Gateway (server)

This example demonstrates how simple it is to send a Multipart HTTP request via Spring's RestTemplate and receive it with a Spring Integration HTTP Inbound Adapter. All we are doing is creating a MultiValueMap and populating it with multi-part data. The RestTemplate will take care of the rest (no pun intended) by converting it to a MultipartHttpServletRequest . This particular client will send a multipart HTTP Request which contains the name of the company as well as an image file with the company logo.

RestTemplate template = new RestTemplate();
String uri = "http://localhost:8080/multipart-http/inboundAdapter.htm";
Resource s2logo = 
   new ClassPathResource("org/springframework/samples/multipart/spring09_logo.png");
MultiValueMap map = new LinkedMultiValueMap();
map.add("company""SpringSource");
map.add("company-logo", s2logo);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(new MediaType("multipart""form-data"));
HttpEntity request = new HttpEntity(map, headers);
ResponseEntity<?> httpResponse = template.exchange(uri, HttpMethod.POST, request, null);

That is all for the client.

On the server side we have the following configuration:

<int-http:inbound-channel-adapter id="httpInboundAdapter"
  channel="receiveChannel"
  name="/inboundAdapter.htm"
  supported-methods="GET, POST" />

<int:channel id="receiveChannel"/>

<int:service-activator input-channel="receiveChannel">
  <bean class="org.springframework.integration.samples.multipart.MultipartReceiver"/>
</int:service-activator>

<bean id="multipartResolver"
    class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/>

The 'httpInboundAdapter' will receive the request, convert it to a Message with a payload that is a LinkedMultiValueMap. We then are parsing that in the 'multipartReceiver' service-activator;

public void receive(LinkedMultiValueMap<String, Object> multipartRequest){
  System.out.println("### Successfully received multipart request ###");
  for (String elementName : multipartRequest.keySet()) {
    if (elementName.equals("company")){
      System.out.println("\t" + elementName + " - " +
        ((String[]) multipartRequest.getFirst("company"))[0]);
    }
    else if (elementName.equals("company-logo")){
      System.out.println("\t" + elementName + " - as UploadedMultipartFile: " +
        ((UploadedMultipartFile) multipartRequest.getFirst("company-logo")).
        											getOriginalFilename());
    }
  }
}

You should see the following output:

### Successfully received multipart request ###
   company - SpringSource
   company-logo - as UploadedMultipartFile: spring09_logo.png