@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")); }
/** * 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; } }