@Test
  public void getRequestWithHeaders() throws Exception {
    DefaultHttpHeaderMapper headerMapper =
        (DefaultHttpHeaderMapper) TestUtils.getPropertyValue(withMappedHeaders, "headerMapper");

    HttpHeaders headers = new HttpHeaders();
    headers.set("foo", "foo");
    headers.set("bar", "bar");
    headers.set("baz", "baz");
    Map<String, Object> map = headerMapper.toHeaders(headers);
    assertTrue(map.size() == 2);
    assertEquals("foo", map.get("foo"));
    assertEquals("bar", map.get("bar"));
  }
Example #2
0
/**
 * Message sender implementation sending messages over Http.
 *
 * <p>Note: Message sender is only using POST request method to publish messages to the service
 * endpoint.
 *
 * @author Christoph Deppisch
 */
public class HttpMessageSender extends AbstractMessageSender {

  /** Http url as service destination */
  private String requestUrl;

  /** Request method */
  private HttpMethod requestMethod = HttpMethod.POST;

  /** The request charset */
  private String charset = "UTF-8";

  /** Default content type */
  private String contentType = "text/plain";

  /** The rest template */
  private RestTemplate restTemplate;

  /** Resolves dynamic endpoint uri */
  private EndpointUriResolver endpointUriResolver = new MessageHeaderEndpointUriResolver();

  /** Header mapper */
  private HeaderMapper<HttpHeaders> headerMapper = DefaultHttpHeaderMapper.outboundMapper();

  /** Should http errors be handled with reply message handler or simply throw exception */
  private ErrorHandlingStrategy errorHandlingStrategy = ErrorHandlingStrategy.PROPAGATE;

  /** Default constructor. */
  public HttpMessageSender() {
    restTemplate = new RestTemplate();
    restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());
  }

  /**
   * Constructor using custom client request factory.
   *
   * @param requestFactory the custom request factory.
   */
  public HttpMessageSender(ClientHttpRequestFactory requestFactory) {
    restTemplate = new RestTemplate();
    restTemplate.setRequestFactory(requestFactory);
  }

  /**
   * Constructor using custom rest template.
   *
   * @param restTemplate the custom rest template.
   */
  public HttpMessageSender(RestTemplate restTemplate) {
    this.restTemplate = restTemplate;
  }

  /**
   * @see com.consol.citrus.message.MessageSender#send(org.springframework.integration.Message)
   * @throws CitrusRuntimeException
   */
  public void send(Message<?> message) {
    String endpointUri;
    if (endpointUriResolver != null) {
      endpointUri = endpointUriResolver.resolveEndpointUri(message, getRequestUrl());
    } else {
      endpointUri = getRequestUrl();
    }

    log.info("Sending HTTP message to: '" + endpointUri + "'");

    if (log.isDebugEnabled()) {
      log.debug("Message to be sent:\n" + message.getPayload().toString());
    }

    HttpMethod method = requestMethod;
    if (message.getHeaders().containsKey(CitrusHttpMessageHeaders.HTTP_REQUEST_METHOD)) {
      method =
          HttpMethod.valueOf(
              (String) message.getHeaders().get(CitrusHttpMessageHeaders.HTTP_REQUEST_METHOD));
    }

    HttpEntity<?> requestEntity = generateRequest(message, method);

    restTemplate.setErrorHandler(new InternalResponseErrorHandler(message));
    ResponseEntity<?> response =
        restTemplate.exchange(endpointUri, method, requestEntity, String.class);

    log.info("HTTP message was successfully sent to endpoint: '" + endpointUri + "'");

    informReplyMessageHandler(
        buildResponseMessage(
            response.getHeaders(),
            response.getBody() != null ? response.getBody() : "",
            response.getStatusCode()),
        message);
  }

  /**
   * Builds the actual integration message from HTTP response entity.
   *
   * @param headers HTTP headers which will be transformed into Message headers
   * @param responseBody the HTTP body of the response
   * @param statusCode HTTP status code received
   * @return the response message
   */
  private Message<?> buildResponseMessage(
      HttpHeaders headers, Object responseBody, HttpStatus statusCode) {
    Map<String, ?> mappedHeaders = headerMapper.toHeaders(headers);

    Message<?> responseMessage =
        MessageBuilder.withPayload(responseBody)
            .copyHeaders(mappedHeaders)
            .copyHeaders(getCustomHeaders(headers, mappedHeaders))
            .setHeader(CitrusHttpMessageHeaders.HTTP_STATUS_CODE, statusCode)
            .setHeader(
                CitrusHttpMessageHeaders.HTTP_VERSION,
                "HTTP/1.1") // TODO check if we have access to version information
            .setHeader(CitrusHttpMessageHeaders.HTTP_REASON_PHRASE, statusCode.name())
            .build();

    return responseMessage;
  }

  /**
   * Message headers consist of standard HTTP message headers and custom headers. This method
   * assumes that all header entries that were not initially mapped by header mapper implementations
   * are custom headers.
   *
   * @param httpHeaders all message headers in their pre nature.
   * @param mappedHeaders the previously mapped header entries (all standard headers).
   * @return
   */
  private Map<String, String> getCustomHeaders(
      HttpHeaders httpHeaders, Map<String, ?> mappedHeaders) {
    Map<String, String> customHeaders = new HashMap<String, String>();

    for (Entry<String, List<String>> header : httpHeaders.entrySet()) {
      if (!mappedHeaders.containsKey(header.getKey())) {
        customHeaders.put(
            header.getKey(), StringUtils.collectionToCommaDelimitedString(header.getValue()));
      }
    }

    return customHeaders;
  }

  /**
   * Generate http request entity from Spring Integration message.
   *
   * @param requestMessage
   * @param method
   * @return
   */
  private HttpEntity<?> generateRequest(Message<?> requestMessage, HttpMethod method) {
    HttpHeaders httpHeaders = new HttpHeaders();
    headerMapper.fromHeaders(requestMessage.getHeaders(), httpHeaders);

    Map<String, ?> messageHeaders = requestMessage.getHeaders();
    for (Entry<String, ?> header : messageHeaders.entrySet()) {
      if (!header.getKey().startsWith(CitrusMessageHeaders.PREFIX)
          && !MessageUtils.isSpringInternalHeader(header.getKey())
          && !httpHeaders.containsKey(header.getKey())) {
        httpHeaders.add(header.getKey(), header.getValue().toString());
      }
    }

    Object payload = requestMessage.getPayload();
    if (httpHeaders.getContentType() == null) {
      httpHeaders.setContentType(
          MediaType.parseMediaType(
              contentType.contains("charset") ? contentType : contentType + ";charset=" + charset));
    }

    if (HttpMethod.POST.equals(method) || HttpMethod.PUT.equals(method)) {
      return new HttpEntity<Object>(payload, httpHeaders);
    }

    return new HttpEntity<Object>(httpHeaders);
  }

  /**
   * Handles error response messages constructing a proper response message which will be propagated
   * to the respective reply message handler for further processing.
   */
  private class InternalResponseErrorHandler implements ResponseErrorHandler {

    /** Request message associated with this response error handler */
    private Message<?> requestMessage;

    /** Default constructor provided with request message associated with this error handler. */
    public InternalResponseErrorHandler(Message<?> requestMessage) {
      this.requestMessage = requestMessage;
    }

    /**
     * Check for error HTTP status code in response message. Delegates to default Spring
     * implementation.
     */
    public boolean hasError(ClientHttpResponse response) throws IOException {
      return new DefaultResponseErrorHandler().hasError(response);
    }

    /** Handle error response message according to error strategy. */
    public void handleError(ClientHttpResponse response) throws IOException {
      if (errorHandlingStrategy.equals(ErrorHandlingStrategy.PROPAGATE)) {
        informReplyMessageHandler(
            buildResponseMessage(
                response.getHeaders(),
                response.getBody() != null ? response.getBody() : "",
                response.getStatusCode()),
            requestMessage);
      } else if (errorHandlingStrategy.equals(ErrorHandlingStrategy.THROWS_EXCEPTION)) {
        new DefaultResponseErrorHandler().handleError(response);
      } else {
        throw new CitrusRuntimeException("Unsupported error strategy: " + errorHandlingStrategy);
      }
    }
  }

  /**
   * Get the complete request URL.
   *
   * @return the urlPath
   */
  public String getRequestUrl() {
    return requestUrl;
  }

  /**
   * Set the complete request URL.
   *
   * @param url the url to set
   */
  public void setRequestUrl(String url) {
    this.requestUrl = url;
  }

  /**
   * Sets the endpoint uri resolver.
   *
   * @param endpointUriResolver the endpointUriResolver to set
   */
  public void setEndpointUriResolver(EndpointUriResolver endpointUriResolver) {
    this.endpointUriResolver = endpointUriResolver;
  }

  /**
   * Sets the restTemplate.
   *
   * @param restTemplate the restTemplate to set
   */
  public void setRestTemplate(RestTemplate restTemplate) {
    this.restTemplate = restTemplate;
  }

  /**
   * Sets the requestMethod.
   *
   * @param requestMethod the requestMethod to set
   */
  public void setRequestMethod(HttpMethod requestMethod) {
    this.requestMethod = requestMethod;
  }

  /**
   * Sets the charset.
   *
   * @param charset the charset to set
   */
  public void setCharset(String charset) {
    this.charset = charset;
  }

  /**
   * Sets the headerMapper.
   *
   * @param headerMapper the headerMapper to set
   */
  public void setHeaderMapper(HeaderMapper<HttpHeaders> headerMapper) {
    this.headerMapper = headerMapper;
  }

  /**
   * Sets the contentType.
   *
   * @param contentType the contentType to set
   */
  public void setContentType(String contentType) {
    this.contentType = contentType;
  }

  /**
   * Gets the errorHandlingStrategy.
   *
   * @return the errorHandlingStrategy
   */
  public ErrorHandlingStrategy getErrorHandlingStrategy() {
    return errorHandlingStrategy;
  }

  /**
   * Sets the errorHandlingStrategy.
   *
   * @param errorHandlingStrategy the errorHandlingStrategy to set
   */
  public void setErrorHandlingStrategy(ErrorHandlingStrategy errorHandlingStrategy) {
    this.errorHandlingStrategy = errorHandlingStrategy;
  }

  /**
   * Gets the requestMethod.
   *
   * @return the requestMethod
   */
  public HttpMethod getRequestMethod() {
    return requestMethod;
  }

  /**
   * Gets the charset.
   *
   * @return the charset
   */
  public String getCharset() {
    return charset;
  }

  /**
   * Gets the contentType.
   *
   * @return the contentType
   */
  public String getContentType() {
    return contentType;
  }

  /**
   * Gets the replyMessageHandler.
   *
   * @return the replyMessageHandler
   */
  public ReplyMessageHandler getReplyMessageHandler() {
    return replyMessageHandler;
  }

  /**
   * Gets the restTemplate.
   *
   * @return the restTemplate
   */
  public RestTemplate getRestTemplate() {
    return restTemplate;
  }

  /**
   * Gets the endpointUriResolver.
   *
   * @return the endpointUriResolver
   */
  public EndpointUriResolver getEndpointUriResolver() {
    return endpointUriResolver;
  }

  /**
   * Gets the headerMapper.
   *
   * @return the headerMapper
   */
  public HeaderMapper<HttpHeaders> getHeaderMapper() {
    return headerMapper;
  }

  /**
   * Sets the interceptors on this implementation's rest template.
   *
   * @param interceptors the interceptors to set
   */
  public void setInterceptors(List<ClientHttpRequestInterceptor> interceptors) {
    restTemplate.setInterceptors(interceptors);
  }
}
/**
 * Message controller implementation handling all incoming requests by forwarding to a message
 * handler for further processing.
 *
 * @author Christoph Deppisch
 */
@Controller
@RequestMapping("/*")
public class HttpMessageController {

  /** Message handler for incoming requests, providing proper responses */
  private MessageHandler messageHandler = new EmptyResponseProducingMessageHandler();

  /** Header mapper */
  private HeaderMapper<HttpHeaders> headerMapper = DefaultHttpHeaderMapper.inboundMapper();

  /** Default charset for response generation */
  private String charset = "UTF-8";

  /** Default content type for response generation */
  private String contentType = "text/plain";

  /** Hold the latest response message for message tracing reasons */
  private ResponseEntity<String> responseCache;

  @RequestMapping(
      value = "**",
      method = {RequestMethod.GET})
  @ResponseBody
  public ResponseEntity<String> handleGetRequest(HttpEntity<String> requestEntity) {
    return handleRequestInternal(HttpMethod.GET, requestEntity);
  }

  @RequestMapping(
      value = "**",
      method = {RequestMethod.POST})
  @ResponseBody
  public ResponseEntity<String> handlePostRequest(HttpEntity<String> requestEntity) {
    return handleRequestInternal(HttpMethod.POST, requestEntity);
  }

  @RequestMapping(
      value = "**",
      method = {RequestMethod.PUT})
  @ResponseBody
  public ResponseEntity<String> handlePutRequest(HttpEntity<String> requestEntity) {
    return handleRequestInternal(HttpMethod.PUT, requestEntity);
  }

  @RequestMapping(
      value = "**",
      method = {RequestMethod.DELETE})
  @ResponseBody
  public ResponseEntity<String> handleDeleteRequest(HttpEntity<String> requestEntity) {
    return handleRequestInternal(HttpMethod.DELETE, requestEntity);
  }

  @RequestMapping(
      value = "**",
      method = {RequestMethod.OPTIONS})
  @ResponseBody
  public ResponseEntity<String> handleOptionsRequest(HttpEntity<String> requestEntity) {
    return handleRequestInternal(HttpMethod.OPTIONS, requestEntity);
  }

  @RequestMapping(
      value = "**",
      method = {RequestMethod.HEAD})
  @ResponseBody
  public ResponseEntity<String> handleHeadRequest(HttpEntity<String> requestEntity) {
    return handleRequestInternal(HttpMethod.HEAD, requestEntity);
  }

  @RequestMapping(
      value = "**",
      method = {RequestMethod.TRACE})
  @ResponseBody
  public ResponseEntity<String> handleTraceRequest(HttpEntity<String> requestEntity) {
    return handleRequestInternal(HttpMethod.TRACE, requestEntity);
  }

  /**
   * Handles requests with message handler implementation. Previously sets Http request method as
   * header parameter.
   *
   * @param method
   * @param requestEntity
   * @return
   */
  private ResponseEntity<String> handleRequestInternal(
      HttpMethod method, HttpEntity<String> requestEntity) {
    Map<String, ?> httpRequestHeaders = headerMapper.toHeaders(requestEntity.getHeaders());
    Map<String, String> customHeaders = new HashMap<String, String>();
    for (Entry<String, List<String>> header : requestEntity.getHeaders().entrySet()) {
      if (!httpRequestHeaders.containsKey(header.getKey())) {
        customHeaders.put(
            header.getKey(), StringUtils.collectionToCommaDelimitedString(header.getValue()));
      }
    }

    HttpServletRequest request =
        ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
    UrlPathHelper pathHelper = new UrlPathHelper();

    customHeaders.put(CitrusHttpMessageHeaders.HTTP_REQUEST_URI, pathHelper.getRequestUri(request));
    customHeaders.put(
        CitrusHttpMessageHeaders.HTTP_CONTEXT_PATH, pathHelper.getContextPath(request));

    String queryParams = pathHelper.getOriginatingQueryString(request);
    customHeaders.put(
        CitrusHttpMessageHeaders.HTTP_QUERY_PARAMS, queryParams != null ? queryParams : "");

    customHeaders.put(CitrusHttpMessageHeaders.HTTP_REQUEST_METHOD, method.toString());

    Message<?> response =
        messageHandler.handleMessage(
            MessageBuilder.withPayload(requestEntity.getBody())
                .copyHeaders(convertHeaderTypes(httpRequestHeaders))
                .copyHeaders(customHeaders)
                .build());

    return generateResponse(response);
  }

  /**
   * Checks for collection typed header values and convert them to comma delimited String. We need
   * this for further header processing e.g when forwarding headers to JMS queues.
   *
   * @param headers the http request headers.
   */
  private Map<String, Object> convertHeaderTypes(Map<String, ?> headers) {
    Map<String, Object> convertedHeaders = new HashMap<String, Object>();

    for (Entry<String, ?> header : headers.entrySet()) {
      if (header.getValue() instanceof Collection<?>) {
        Collection<?> value = (Collection<?>) header.getValue();
        convertedHeaders.put(header.getKey(), StringUtils.collectionToCommaDelimitedString(value));
      } else if (header.getValue() instanceof MediaType) {
        convertedHeaders.put(header.getKey(), header.getValue().toString());
      } else {
        convertedHeaders.put(header.getKey(), header.getValue());
      }
    }

    return convertedHeaders;
  }

  /**
   * Generates the Http response message from given Spring Integration message.
   *
   * @param responseMessage message received from the message handler
   * @return an HTTP entity as response
   */
  private ResponseEntity<String> generateResponse(Message<?> responseMessage) {
    if (responseMessage == null) {
      return new ResponseEntity<String>(HttpStatus.OK);
    }

    HttpHeaders httpHeaders = new HttpHeaders();
    headerMapper.fromHeaders(responseMessage.getHeaders(), httpHeaders);

    Map<String, ?> messageHeaders = responseMessage.getHeaders();
    for (Entry<String, ?> header : messageHeaders.entrySet()) {
      if (!header.getKey().startsWith(CitrusMessageHeaders.PREFIX)
          && !MessageUtils.isSpringInternalHeader(header.getKey())
          && !httpHeaders.containsKey(header.getKey())) {
        httpHeaders.add(header.getKey(), header.getValue().toString());
      }
    }

    if (httpHeaders.getContentType() == null) {
      httpHeaders.setContentType(
          MediaType.parseMediaType(
              contentType.contains("charset") ? contentType : contentType + ";charset=" + charset));
    }

    HttpStatus status = HttpStatus.OK;
    if (responseMessage.getHeaders().containsKey(CitrusHttpMessageHeaders.HTTP_STATUS_CODE)) {
      status =
          HttpStatus.valueOf(
              Integer.valueOf(
                  responseMessage
                      .getHeaders()
                      .get(CitrusHttpMessageHeaders.HTTP_STATUS_CODE)
                      .toString()));
    }

    responseCache =
        new ResponseEntity<String>(responseMessage.getPayload().toString(), httpHeaders, status);

    return responseCache;
  }

  /**
   * Sets the messageHandler.
   *
   * @param messageHandler the messageHandler to set
   */
  public void setMessageHandler(MessageHandler messageHandler) {
    this.messageHandler = messageHandler;
  }

  /**
   * Sets the headerMapper.
   *
   * @param headerMapper the headerMapper to set
   */
  public void setHeaderMapper(HeaderMapper<HttpHeaders> headerMapper) {
    this.headerMapper = headerMapper;
  }

  /**
   * Sets the charset.
   *
   * @param charset the charset to set
   */
  public void setCharset(String charset) {
    this.charset = charset;
  }

  /**
   * Sets the contentType.
   *
   * @param contentType the contentType to set
   */
  public void setContentType(String contentType) {
    this.contentType = contentType;
  }

  /**
   * Gets the responseCache.
   *
   * @return the responseCache the responseCache to get.
   */
  public ResponseEntity<String> getResponseCache() {
    return responseCache;
  }
}