camel-http-common - as a common module
Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/e51b7f8d Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/e51b7f8d Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/e51b7f8d Branch: refs/heads/master Commit: e51b7f8d9320072a64f60a23836c7ce15ffb45e7 Parents: 600063a Author: Claus Ibsen <davscl...@apache.org> Authored: Thu Jul 23 09:23:46 2015 +0200 Committer: Claus Ibsen <davscl...@apache.org> Committed: Thu Jul 23 15:04:30 2015 +0200 ---------------------------------------------------------------------- .../camel/http/common/CamelFileDataSource.java | 52 ++ .../apache/camel/http/common/CamelServlet.java | 247 ++++++++++ .../camel/http/common/DefaultHttpBinding.java | 494 +++++++++++++++++++ .../apache/camel/http/common/HttpBinding.java | 160 ++++++ .../camel/http/common/HttpCommonComponent.java | 75 +++ .../camel/http/common/HttpCommonEndpoint.java | 383 ++++++++++++++ .../camel/http/common/HttpConfiguration.java | 143 ++++++ .../apache/camel/http/common/HttpConstants.java | 26 + .../apache/camel/http/common/HttpConsumer.java | 80 +++ .../apache/camel/http/common/HttpConverter.java | 98 ++++ .../http/common/HttpHeaderFilterStrategy.java | 49 ++ .../apache/camel/http/common/HttpHelper.java | 429 ++++++++++++++++ .../apache/camel/http/common/HttpMessage.java | 81 +++ .../common/HttpOperationFailedException.java | 75 +++ .../HttpProtocolHeaderFilterStrategy.java | 87 ++++ .../HttpServletResolveConsumerStrategy.java | 50 ++ .../http/common/HttpServletUrlRewrite.java | 45 ++ .../common/ServletResolveConsumerStrategy.java | 37 ++ .../apache/camel/http/common/UrlRewrite.java | 41 ++ .../UrlRewriteHttpServletRequestAdapter.java | 49 ++ 20 files changed, 2701 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/e51b7f8d/components/camel-http-common/src/main/java/org/apache/camel/http/common/CamelFileDataSource.java ---------------------------------------------------------------------- diff --git a/components/camel-http-common/src/main/java/org/apache/camel/http/common/CamelFileDataSource.java b/components/camel-http-common/src/main/java/org/apache/camel/http/common/CamelFileDataSource.java new file mode 100644 index 0000000..8cb4c91 --- /dev/null +++ b/components/camel-http-common/src/main/java/org/apache/camel/http/common/CamelFileDataSource.java @@ -0,0 +1,52 @@ +/** + * 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.apache.camel.http.common; + +import java.io.File; +import javax.activation.FileDataSource; +import javax.activation.FileTypeMap; + +public class CamelFileDataSource extends FileDataSource { + private final String fileName; + private FileTypeMap typeMap; + + public CamelFileDataSource(File file, String fileName) { + super(file); + this.fileName = fileName; + } + + public String getContentType() { + if (typeMap == null) { + return FileTypeMap.getDefaultFileTypeMap().getContentType(fileName); + } else { + return typeMap.getContentType(fileName); + } + } + + public void setFileTypeMap(FileTypeMap map) { + typeMap = map; + } + + public String getName() { + if (fileName != null) { + return fileName; + } else { + return super.getName(); + } + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/e51b7f8d/components/camel-http-common/src/main/java/org/apache/camel/http/common/CamelServlet.java ---------------------------------------------------------------------- diff --git a/components/camel-http-common/src/main/java/org/apache/camel/http/common/CamelServlet.java b/components/camel-http-common/src/main/java/org/apache/camel/http/common/CamelServlet.java new file mode 100644 index 0000000..061bf47 --- /dev/null +++ b/components/camel-http-common/src/main/java/org/apache/camel/http/common/CamelServlet.java @@ -0,0 +1,247 @@ +/** + * 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.apache.camel.http.common; + +import java.io.IOException; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.camel.Exchange; +import org.apache.camel.ExchangePattern; +import org.apache.camel.impl.DefaultExchange; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A servlet to use as a Camel route as entry. + */ +public class CamelServlet extends HttpServlet { + private static final long serialVersionUID = -7061982839117697829L; + protected final Logger log = LoggerFactory.getLogger(getClass()); + + /** + * We have to define this explicitly so the name can be set as we can not always be + * sure that it is already set via the init method + */ + private String servletName; + + private ServletResolveConsumerStrategy servletResolveConsumerStrategy = new HttpServletResolveConsumerStrategy(); + private final ConcurrentMap<String, HttpConsumer> consumers = new ConcurrentHashMap<String, HttpConsumer>(); + + @Override + public void init(ServletConfig config) throws ServletException { + super.init(config); + this.servletName = config.getServletName(); + } + + @Override + protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + log.trace("Service: {}", request); + + // Is there a consumer registered for the request. + HttpConsumer consumer = resolve(request); + if (consumer == null) { + log.debug("No consumer to service request {}", request); + response.sendError(HttpServletResponse.SC_NOT_FOUND); + return; + } + + // are we suspended? + if (consumer.isSuspended()) { + log.debug("Consumer suspended, cannot service request {}", request); + response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE); + return; + } + + // if its an OPTIONS request then return which method is allowed + if ("OPTIONS".equals(request.getMethod())) { + String s; + if (consumer.getEndpoint().getHttpMethodRestrict() != null) { + s = "OPTIONS," + consumer.getEndpoint().getHttpMethodRestrict(); + } else { + // allow them all + s = "GET,HEAD,POST,PUT,DELETE,TRACE,OPTIONS,CONNECT,PATCH"; + } + response.addHeader("Allow", s); + response.setStatus(HttpServletResponse.SC_OK); + return; + } + + if (consumer.getEndpoint().getHttpMethodRestrict() != null + && !consumer.getEndpoint().getHttpMethodRestrict().contains(request.getMethod())) { + response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED); + return; + } + + if ("TRACE".equals(request.getMethod()) && !consumer.isTraceEnabled()) { + response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED); + return; + } + + // create exchange and set data on it + Exchange exchange = new DefaultExchange(consumer.getEndpoint(), ExchangePattern.InOut); + + if (consumer.getEndpoint().isBridgeEndpoint()) { + exchange.setProperty(Exchange.SKIP_GZIP_ENCODING, Boolean.TRUE); + exchange.setProperty(Exchange.SKIP_WWW_FORM_URLENCODED, Boolean.TRUE); + } + if (consumer.getEndpoint().isDisableStreamCache()) { + exchange.setProperty(Exchange.DISABLE_HTTP_STREAM_CACHE, Boolean.TRUE); + } + + // we override the classloader before building the HttpMessage just in case the binding + // does some class resolution + ClassLoader oldTccl = overrideTccl(exchange); + HttpHelper.setCharsetFromContentType(request.getContentType(), exchange); + exchange.setIn(new HttpMessage(exchange, request, response)); + // set context path as header + String contextPath = consumer.getEndpoint().getPath(); + exchange.getIn().setHeader("CamelServletContextPath", contextPath); + + String httpPath = (String)exchange.getIn().getHeader(Exchange.HTTP_PATH); + // here we just remove the CamelServletContextPath part from the HTTP_PATH + if (contextPath != null + && httpPath.startsWith(contextPath)) { + exchange.getIn().setHeader(Exchange.HTTP_PATH, + httpPath.substring(contextPath.length())); + } + + // we want to handle the UoW + try { + consumer.createUoW(exchange); + } catch (Exception e) { + log.error("Error processing request", e); + throw new ServletException(e); + } + + try { + if (log.isTraceEnabled()) { + log.trace("Processing request for exchangeId: {}", exchange.getExchangeId()); + } + // process the exchange + consumer.getProcessor().process(exchange); + } catch (Exception e) { + exchange.setException(e); + } + + try { + // now lets output to the response + if (log.isTraceEnabled()) { + log.trace("Writing response for exchangeId: {}", exchange.getExchangeId()); + } + Integer bs = consumer.getEndpoint().getResponseBufferSize(); + if (bs != null) { + log.trace("Using response buffer size: {}", bs); + response.setBufferSize(bs); + } + consumer.getBinding().writeResponse(exchange, response); + } catch (IOException e) { + log.error("Error processing request", e); + throw e; + } catch (Exception e) { + log.error("Error processing request", e); + throw new ServletException(e); + } finally { + consumer.doneUoW(exchange); + restoreTccl(exchange, oldTccl); + } + } + + /** + * @deprecated use {@link ServletResolveConsumerStrategy#resolve(javax.servlet.http.HttpServletRequest, java.util.Map)} + */ + @Deprecated + protected HttpConsumer resolve(HttpServletRequest request) { + return getServletResolveConsumerStrategy().resolve(request, getConsumers()); + } + + public void connect(HttpConsumer consumer) { + log.debug("Connecting consumer: {}", consumer); + consumers.put(consumer.getEndpoint().getEndpointUri(), consumer); + } + + public void disconnect(HttpConsumer consumer) { + log.debug("Disconnecting consumer: {}", consumer); + consumers.remove(consumer.getEndpoint().getEndpointUri()); + } + + public String getServletName() { + return servletName; + } + + public void setServletName(String servletName) { + this.servletName = servletName; + } + + public ServletResolveConsumerStrategy getServletResolveConsumerStrategy() { + return servletResolveConsumerStrategy; + } + + public void setServletResolveConsumerStrategy(ServletResolveConsumerStrategy servletResolveConsumerStrategy) { + this.servletResolveConsumerStrategy = servletResolveConsumerStrategy; + } + + public Map<String, HttpConsumer> getConsumers() { + return Collections.unmodifiableMap(consumers); + } + + /** + * Override the Thread Context ClassLoader if need be. + * + * @param exchange + * @return old classloader if overridden; otherwise returns null + */ + protected ClassLoader overrideTccl(final Exchange exchange) { + ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader(); + ClassLoader appCtxCl = exchange.getContext().getApplicationContextClassLoader(); + if (oldClassLoader == null || appCtxCl == null) { + return null; + } + + if (!oldClassLoader.equals(appCtxCl)) { + Thread.currentThread().setContextClassLoader(appCtxCl); + if (log.isTraceEnabled()) { + log.trace("Overrode TCCL for exchangeId {} to {} on thread {}", + new Object[] {exchange.getExchangeId(), appCtxCl, Thread.currentThread().getName()}); + } + return oldClassLoader; + } + return null; + } + + /** + * Restore the Thread Context ClassLoader if the old TCCL is not null. + */ + protected void restoreTccl(final Exchange exchange, ClassLoader oldTccl) { + if (oldTccl == null) { + return; + } + Thread.currentThread().setContextClassLoader(oldTccl); + if (log.isTraceEnabled()) { + log.trace("Restored TCCL for exchangeId {} to {} on thread {}", + new String[] {exchange.getExchangeId(), oldTccl.toString(), Thread.currentThread().getName()}); + } + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/e51b7f8d/components/camel-http-common/src/main/java/org/apache/camel/http/common/DefaultHttpBinding.java ---------------------------------------------------------------------- diff --git a/components/camel-http-common/src/main/java/org/apache/camel/http/common/DefaultHttpBinding.java b/components/camel-http-common/src/main/java/org/apache/camel/http/common/DefaultHttpBinding.java new file mode 100644 index 0000000..dcb3f20 --- /dev/null +++ b/components/camel-http-common/src/main/java/org/apache/camel/http/common/DefaultHttpBinding.java @@ -0,0 +1,494 @@ +/** + * 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.apache.camel.http.common; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.Serializable; +import java.net.URLDecoder; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.Map; +import javax.activation.DataHandler; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.camel.Endpoint; +import org.apache.camel.Exchange; +import org.apache.camel.InvalidPayloadException; +import org.apache.camel.Message; +import org.apache.camel.RuntimeCamelException; +import org.apache.camel.StreamCache; +import org.apache.camel.converter.stream.CachedOutputStream; +import org.apache.camel.spi.HeaderFilterStrategy; +import org.apache.camel.util.GZIPHelper; +import org.apache.camel.util.IOHelper; +import org.apache.camel.util.MessageHelper; +import org.apache.camel.util.ObjectHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Binding between {@link HttpMessage} and {@link HttpServletResponse}. + * <p/> + * Uses by default the {@link org.apache.camel.http.common.HttpHeaderFilterStrategy} + */ +public class DefaultHttpBinding implements HttpBinding { + + private static final Logger LOG = LoggerFactory.getLogger(DefaultHttpBinding.class); + private boolean useReaderForPayload; + private boolean eagerCheckContentAvailable; + private boolean transferException; + private HeaderFilterStrategy headerFilterStrategy = new HttpHeaderFilterStrategy(); + + public DefaultHttpBinding() { + } + + @Deprecated + public DefaultHttpBinding(HeaderFilterStrategy headerFilterStrategy) { + this.headerFilterStrategy = headerFilterStrategy; + } + + @Deprecated + public DefaultHttpBinding(HttpCommonEndpoint endpoint) { + this.headerFilterStrategy = endpoint.getHeaderFilterStrategy(); + this.transferException = endpoint.isTransferException(); + } + + public void readRequest(HttpServletRequest request, HttpMessage message) { + LOG.trace("readRequest {}", request); + + // lets force a parse of the body and headers + message.getBody(); + // populate the headers from the request + Map<String, Object> headers = message.getHeaders(); + + //apply the headerFilterStrategy + Enumeration<?> names = request.getHeaderNames(); + while (names.hasMoreElements()) { + String name = (String)names.nextElement(); + String value = request.getHeader(name); + // use http helper to extract parameter value as it may contain multiple values + Object extracted = HttpHelper.extractHttpParameterValue(value); + // mapping the content-type + if (name.toLowerCase().equals("content-type")) { + name = Exchange.CONTENT_TYPE; + } + if (headerFilterStrategy != null + && !headerFilterStrategy.applyFilterToExternalHeaders(name, extracted, message.getExchange())) { + HttpHelper.appendHeader(headers, name, extracted); + } + } + + if (request.getCharacterEncoding() != null) { + headers.put(Exchange.HTTP_CHARACTER_ENCODING, request.getCharacterEncoding()); + message.getExchange().setProperty(Exchange.CHARSET_NAME, request.getCharacterEncoding()); + } + + try { + populateRequestParameters(request, message); + } catch (Exception e) { + throw new RuntimeCamelException("Cannot read request parameters due " + e.getMessage(), e); + } + + Object body = message.getBody(); + // reset the stream cache if the body is the instance of StreamCache + if (body instanceof StreamCache) { + ((StreamCache)body).reset(); + } + + // store the method and query and other info in headers as String types + headers.put(Exchange.HTTP_METHOD, request.getMethod()); + headers.put(Exchange.HTTP_QUERY, request.getQueryString()); + headers.put(Exchange.HTTP_URL, request.getRequestURL().toString()); + headers.put(Exchange.HTTP_URI, request.getRequestURI()); + headers.put(Exchange.HTTP_PATH, request.getPathInfo()); + headers.put(Exchange.CONTENT_TYPE, request.getContentType()); + + if (LOG.isTraceEnabled()) { + LOG.trace("HTTP method {}", request.getMethod()); + LOG.trace("HTTP query {}", request.getQueryString()); + LOG.trace("HTTP url {}", request.getRequestURL()); + LOG.trace("HTTP uri {}", request.getRequestURI()); + LOG.trace("HTTP path {}", request.getPathInfo()); + LOG.trace("HTTP content-type {}", request.getContentType()); + } + + // if content type is serialized java object, then de-serialize it to a Java object + if (request.getContentType() != null && HttpConstants.CONTENT_TYPE_JAVA_SERIALIZED_OBJECT.equals(request.getContentType())) { + try { + InputStream is = message.getExchange().getContext().getTypeConverter().mandatoryConvertTo(InputStream.class, body); + Object object = HttpHelper.deserializeJavaObjectFromStream(is, message.getExchange().getContext()); + if (object != null) { + message.setBody(object); + } + } catch (Exception e) { + throw new RuntimeCamelException("Cannot deserialize body to Java object", e); + } + } + + populateAttachments(request, message); + } + + protected void populateRequestParameters(HttpServletRequest request, HttpMessage message) throws Exception { + //we populate the http request parameters without checking the request method + Map<String, Object> headers = message.getHeaders(); + Enumeration<?> names = request.getParameterNames(); + while (names.hasMoreElements()) { + String name = (String)names.nextElement(); + // there may be multiple values for the same name + String[] values = request.getParameterValues(name); + LOG.trace("HTTP parameter {} = {}", name, values); + + if (values != null) { + for (String value : values) { + if (headerFilterStrategy != null + && !headerFilterStrategy.applyFilterToExternalHeaders(name, value, message.getExchange())) { + HttpHelper.appendHeader(headers, name, value); + } + } + } + } + + LOG.trace("HTTP method {} with Content-Type {}", request.getMethod(), request.getContentType()); + Boolean flag = message.getHeader(Exchange.SKIP_WWW_FORM_URLENCODED, Boolean.class); + boolean skipWwwFormUrlEncoding = flag != null ? flag : false; + if (request.getMethod().equals("POST") && request.getContentType() != null + && request.getContentType().startsWith(HttpConstants.CONTENT_TYPE_WWW_FORM_URLENCODED) + && !skipWwwFormUrlEncoding) { + String charset = request.getCharacterEncoding(); + if (charset == null) { + charset = "UTF-8"; + } + // Push POST form params into the headers to retain compatibility with DefaultHttpBinding + String body = message.getBody(String.class); + if (ObjectHelper.isNotEmpty(body)) { + for (String param : body.split("&")) { + String[] pair = param.split("=", 2); + if (pair.length == 2) { + String name = URLDecoder.decode(pair[0], charset); + String value = URLDecoder.decode(pair[1], charset); + if (headerFilterStrategy != null + && !headerFilterStrategy.applyFilterToExternalHeaders(name, value, message.getExchange())) { + HttpHelper.appendHeader(headers, name, value); + } + } else { + throw new IllegalArgumentException("Invalid parameter, expected to be a pair but was " + param); + } + } + } + } + } + + protected void populateAttachments(HttpServletRequest request, HttpMessage message) { + // check if there is multipart files, if so will put it into DataHandler + Enumeration<?> names = request.getAttributeNames(); + while (names.hasMoreElements()) { + String name = (String) names.nextElement(); + Object object = request.getAttribute(name); + LOG.trace("HTTP attachment {} = {}", name, object); + if (object instanceof File) { + String fileName = request.getParameter(name); + message.addAttachment(fileName, new DataHandler(new CamelFileDataSource((File)object, fileName))); + } + } + } + + public void writeResponse(Exchange exchange, HttpServletResponse response) throws IOException { + Message target = exchange.hasOut() ? exchange.getOut() : exchange.getIn(); + if (exchange.isFailed()) { + if (exchange.getException() != null) { + doWriteExceptionResponse(exchange.getException(), response); + } else { + // it must be a fault, no need to check for the fault flag on the message + doWriteFaultResponse(target, response, exchange); + } + } else { + if (exchange.hasOut()) { + // just copy the protocol relates header if we do not have them + copyProtocolHeaders(exchange.getIn(), exchange.getOut()); + } + doWriteResponse(target, response, exchange); + } + } + + private void copyProtocolHeaders(Message request, Message response) { + if (request.getHeader(Exchange.CONTENT_ENCODING) != null) { + String contentEncoding = request.getHeader(Exchange.CONTENT_ENCODING, String.class); + response.setHeader(Exchange.CONTENT_ENCODING, contentEncoding); + } + if (checkChunked(response, response.getExchange())) { + response.setHeader(Exchange.TRANSFER_ENCODING, "chunked"); + } + } + + public void doWriteExceptionResponse(Throwable exception, HttpServletResponse response) throws IOException { + // 500 for internal server error + response.setStatus(500); + + if (isTransferException()) { + // transfer the exception as a serialized java object + HttpHelper.writeObjectToServletResponse(response, exception); + } else { + // write stacktrace as plain text + response.setContentType("text/plain"); + PrintWriter pw = response.getWriter(); + exception.printStackTrace(pw); + pw.flush(); + } + } + + public void doWriteFaultResponse(Message message, HttpServletResponse response, Exchange exchange) throws IOException { + message.setHeader(Exchange.HTTP_RESPONSE_CODE, 500); + doWriteResponse(message, response, exchange); + } + + public void doWriteResponse(Message message, HttpServletResponse response, Exchange exchange) throws IOException { + // set the status code in the response. Default is 200. + if (message.getHeader(Exchange.HTTP_RESPONSE_CODE) != null) { + int code = message.getHeader(Exchange.HTTP_RESPONSE_CODE, Integer.class); + response.setStatus(code); + } + // set the content type in the response. + String contentType = MessageHelper.getContentType(message); + if (contentType != null) { + response.setContentType(contentType); + } + + // append headers + // must use entrySet to ensure case of keys is preserved + for (Map.Entry<String, Object> entry : message.getHeaders().entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue(); + // use an iterator as there can be multiple values. (must not use a delimiter) + final Iterator<?> it = ObjectHelper.createIterator(value, null); + while (it.hasNext()) { + String headerValue = exchange.getContext().getTypeConverter().convertTo(String.class, it.next()); + if (headerValue != null && headerFilterStrategy != null + && !headerFilterStrategy.applyFilterToCamelHeaders(key, headerValue, exchange)) { + response.addHeader(key, headerValue); + } + } + } + + // write the body. + if (message.getBody() != null) { + if (GZIPHelper.isGzip(message)) { + doWriteGZIPResponse(message, response, exchange); + } else { + doWriteDirectResponse(message, response, exchange); + } + } + } + + protected boolean isText(String contentType) { + if (contentType != null) { + String temp = contentType.toLowerCase(); + if (temp.indexOf("text") >= 0 || temp.indexOf("html") >= 0) { + return true; + } + } + return false; + } + + protected int copyStream(InputStream is, OutputStream os, int bufferSize) throws IOException { + try { + // copy stream, and must flush on each write as etc Jetty has better performance when + // flushing after writing to its servlet output stream + return IOHelper.copy(is, os, bufferSize, true); + } finally { + IOHelper.close(os, is); + } + } + + protected void doWriteDirectResponse(Message message, HttpServletResponse response, Exchange exchange) throws IOException { + // if content type is serialized Java object, then serialize and write it to the response + String contentType = message.getHeader(Exchange.CONTENT_TYPE, String.class); + if (contentType != null && HttpConstants.CONTENT_TYPE_JAVA_SERIALIZED_OBJECT.equals(contentType)) { + try { + Object object = message.getMandatoryBody(Serializable.class); + HttpHelper.writeObjectToServletResponse(response, object); + // object is written so return + return; + } catch (InvalidPayloadException e) { + throw new IOException(e); + } + } + + // prefer streaming + InputStream is = null; + if (checkChunked(message, exchange)) { + is = message.getBody(InputStream.class); + } else { + // try to use input stream first, so we can copy directly + if (!isText(contentType)) { + is = exchange.getContext().getTypeConverter().tryConvertTo(InputStream.class, message.getBody()); + } + } + + if (is != null) { + ServletOutputStream os = response.getOutputStream(); + if (!checkChunked(message, exchange)) { + CachedOutputStream stream = new CachedOutputStream(exchange); + try { + // copy directly from input stream to the cached output stream to get the content length + int len = copyStream(is, stream, response.getBufferSize()); + // we need to setup the length if message is not chucked + response.setContentLength(len); + OutputStream current = stream.getCurrentStream(); + if (current instanceof ByteArrayOutputStream) { + if (LOG.isDebugEnabled()) { + LOG.debug("Streaming (direct) response in non-chunked mode with content-length {}"); + } + ByteArrayOutputStream bos = (ByteArrayOutputStream) current; + bos.writeTo(os); + } else { + if (LOG.isDebugEnabled()) { + LOG.debug("Streaming response in non-chunked mode with content-length {} and buffer size: {}", len, len); + } + copyStream(stream.getInputStream(), os, len); + } + } finally { + IOHelper.close(is, os); + } + } else { + if (LOG.isDebugEnabled()) { + LOG.debug("Streaming response in chunked mode with buffer size {}", response.getBufferSize()); + } + copyStream(is, os, response.getBufferSize()); + } + } else { + // not convertable as a stream so fallback as a String + String data = message.getBody(String.class); + if (data != null) { + // set content length and encoding before we write data + String charset = IOHelper.getCharsetName(exchange, true); + final int dataByteLength = data.getBytes(charset).length; + response.setCharacterEncoding(charset); + response.setContentLength(dataByteLength); + if (LOG.isDebugEnabled()) { + LOG.debug("Writing response in non-chunked mode as plain text with content-length {} and buffer size: {}", dataByteLength, response.getBufferSize()); + } + try { + response.getWriter().print(data); + } finally { + response.getWriter().flush(); + } + } + } + } + + protected boolean checkChunked(Message message, Exchange exchange) { + boolean answer = true; + if (message.getHeader(Exchange.HTTP_CHUNKED) == null) { + // check the endpoint option + Endpoint endpoint = exchange.getFromEndpoint(); + if (endpoint instanceof HttpCommonEndpoint) { + answer = ((HttpCommonEndpoint)endpoint).isChunked(); + } + } else { + answer = message.getHeader(Exchange.HTTP_CHUNKED, boolean.class); + } + return answer; + } + + protected void doWriteGZIPResponse(Message message, HttpServletResponse response, Exchange exchange) throws IOException { + byte[] bytes; + try { + bytes = message.getMandatoryBody(byte[].class); + } catch (InvalidPayloadException e) { + throw ObjectHelper.wrapRuntimeCamelException(e); + } + + byte[] data = GZIPHelper.compressGZIP(bytes); + ServletOutputStream os = response.getOutputStream(); + try { + if (LOG.isDebugEnabled()) { + LOG.debug("Streaming response as GZIP in non-chunked mode with content-length {} and buffer size: {}", data.length, response.getBufferSize()); + } + response.setContentLength(data.length); + os.write(data); + os.flush(); + } finally { + IOHelper.close(os); + } + } + + public Object parseBody(HttpMessage httpMessage) throws IOException { + // lets assume the body is a reader + HttpServletRequest request = httpMessage.getRequest(); + // there is only a body if we have a content length, or its -1 to indicate unknown length + int len = request.getContentLength(); + LOG.trace("HttpServletRequest content-length: {}", len); + if (len == 0) { + return null; + } + if (isUseReaderForPayload()) { + // use reader to read the response body + return request.getReader(); + } else { + // if we do not know if there is any data at all, then make sure to check the stream first + if (len < 0 && isEagerCheckContentAvailable()) { + InputStream is = request.getInputStream(); + if (is.available() == 0) { + // no data so return null + return null; + } + } + // read the response body from servlet request + return HttpHelper.readRequestBodyFromServletRequest(request, httpMessage.getExchange()); + } + } + + public boolean isUseReaderForPayload() { + return useReaderForPayload; + } + + public void setUseReaderForPayload(boolean useReaderForPayload) { + this.useReaderForPayload = useReaderForPayload; + } + + public boolean isEagerCheckContentAvailable() { + return eagerCheckContentAvailable; + } + + public void setEagerCheckContentAvailable(boolean eagerCheckContentAvailable) { + this.eagerCheckContentAvailable = eagerCheckContentAvailable; + } + + public boolean isTransferException() { + return transferException; + } + + public void setTransferException(boolean transferException) { + this.transferException = transferException; + } + + public HeaderFilterStrategy getHeaderFilterStrategy() { + return headerFilterStrategy; + } + + public void setHeaderFilterStrategy(HeaderFilterStrategy headerFilterStrategy) { + this.headerFilterStrategy = headerFilterStrategy; + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/e51b7f8d/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpBinding.java ---------------------------------------------------------------------- diff --git a/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpBinding.java b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpBinding.java new file mode 100644 index 0000000..d76ba10 --- /dev/null +++ b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpBinding.java @@ -0,0 +1,160 @@ +/** + * 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.apache.camel.http.common; + +import java.io.IOException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.camel.Exchange; +import org.apache.camel.Message; +import org.apache.camel.spi.HeaderFilterStrategy; + +/** + * A pluggable strategy for configuring the http binding so reading request and writing response + * can be customized using the Java Servlet API. + * <p/> + * This is also used by the <tt>camel-jetty</tt> component in the <tt>JettyHttpConsumer</tt> class. + */ +public interface HttpBinding { + + /** + * Strategy to read the given request and bindings it to the given message. + * + * @param request the request + * @param message the message to populate with data from request + */ + void readRequest(HttpServletRequest request, HttpMessage message); + + /** + * Parses the body from a {@link org.apache.camel.http.common.HttpMessage} + * + * @param httpMessage the http message + * @return the parsed body returned as either a {@link java.io.InputStream} or a {@link java.io.Reader} + * depending on the {@link #setUseReaderForPayload(boolean)} property. + * @throws java.io.IOException can be thrown + */ + Object parseBody(HttpMessage httpMessage) throws IOException; + + /** + * Writes the exchange to the servlet response. + * <p/> + * Default implementation will delegate to the following methods depending on the status of the exchange + * <ul> + * <li>doWriteResponse - processing returns a OUT message </li> + * <li>doWriteFaultResponse - processing returns a fault message</li> + * <li>doWriteResponse - processing returns an exception and status code 500</li> + * </ul> + * + * @param exchange the exchange + * @param response the http response + * @throws java.io.IOException can be thrown from http response + */ + void writeResponse(Exchange exchange, HttpServletResponse response) throws IOException; + + /** + * Strategy method that writes the response to the http response stream when an exception occurred + * + * @param exception the exception occurred + * @param response the http response + * @throws java.io.IOException can be thrown from http response + */ + void doWriteExceptionResponse(Throwable exception, HttpServletResponse response) throws IOException; + + /** + * Strategy method that writes the response to the http response stream for a fault message + * + * @param message the fault message + * @param response the http response + * @param exchange the exchange to provide context for header filtering + * @throws java.io.IOException can be thrown from http response + */ + void doWriteFaultResponse(Message message, HttpServletResponse response, Exchange exchange) throws IOException; + + /** + * Strategy method that writes the response to the http response stream for an OUT message + * + * @param message the OUT message + * @param response the http response + * @param exchange the exchange to provide context for header filtering + * @throws java.io.IOException can be thrown from http response + */ + void doWriteResponse(Message message, HttpServletResponse response, Exchange exchange) throws IOException; + + /** + * Should reader by used instead of input stream. + * + * @see #setUseReaderForPayload(boolean) for more details + * @return <tt>true</tt> if reader should be used + */ + boolean isUseReaderForPayload(); + + /** + * Should the {@link javax.servlet.http.HttpServletRequest#getReader()} be exposed as the payload of input messages in the Camel + * {@link org.apache.camel.Message#getBody()} or not. If false then the {@link javax.servlet.http.HttpServletRequest#getInputStream()} will be exposed. + * <p/> + * Is default <tt>false</tt>. + * + * @param useReaderForPayload whether to use reader or not + */ + void setUseReaderForPayload(boolean useReaderForPayload); + + /** + * If enabled and an Exchange failed processing on the consumer side, and if the caused Exception was send back + * serialized in the response as a application/x-java-serialized-object content type (for example using Jetty or + * Servlet Camel components). On the producer side the exception will be deserialized and thrown as is, + * instead of the HttpOperationFailedException. The caused exception is required to be serialized. + */ + boolean isTransferException(); + + /** + * Whether to eager check whether the HTTP requests has content if the content-length header is 0 or not present. + * This can be turned on in case HTTP clients do not send streamed data. + */ + boolean isEagerCheckContentAvailable(); + + /** + * Whether to eager check whether the HTTP requests has content if the content-length header is 0 or not present. + * This can be turned on in case HTTP clients do not send streamed data. + */ + void setEagerCheckContentAvailable(boolean eagerCheckContentAvailable); + + /** + * If enabled and an Exchange failed processing on the consumer side, and if the caused Exception was send back + * serialized in the response as a application/x-java-serialized-object content type (for example using Jetty or + * Servlet Camel components). On the producer side the exception will be deserialized and thrown as is, + * instead of the HttpOperationFailedException. The caused exception is required to be serialized. + */ + void setTransferException(boolean transferException); + + /** + * Gets the header filter strategy + * + * @return the strategy + */ + HeaderFilterStrategy getHeaderFilterStrategy(); + + /** + * Sets the header filter strategy to use. + * <p/> + * Will default use {@link org.apache.camel.http.common.HttpHeaderFilterStrategy} + * + * @param headerFilterStrategy the custom strategy + */ + void setHeaderFilterStrategy(HeaderFilterStrategy headerFilterStrategy); + +} http://git-wip-us.apache.org/repos/asf/camel/blob/e51b7f8d/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpCommonComponent.java ---------------------------------------------------------------------- diff --git a/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpCommonComponent.java b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpCommonComponent.java new file mode 100644 index 0000000..711a878 --- /dev/null +++ b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpCommonComponent.java @@ -0,0 +1,75 @@ +/** + * 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.apache.camel.http.common; + +import org.apache.camel.impl.HeaderFilterStrategyComponent; + +public abstract class HttpCommonComponent extends HeaderFilterStrategyComponent { + + protected HttpBinding httpBinding; + protected HttpConfiguration httpConfiguration; + + public HttpCommonComponent(Class<? extends HttpCommonEndpoint> endpointClass) { + super(endpointClass); + } + + /** + * Connects the URL specified on the endpoint to the specified processor. + * + * @param consumer the consumer + * @throws Exception can be thrown + */ + public void connect(HttpConsumer consumer) throws Exception { + } + + /** + * Disconnects the URL specified on the endpoint from the specified processor. + * + * @param consumer the consumer + * @throws Exception can be thrown + */ + public void disconnect(HttpConsumer consumer) throws Exception { + } + + @Override + protected boolean useIntrospectionOnEndpoint() { + return false; + } + + public HttpBinding getHttpBinding() { + return httpBinding; + } + + /** + * To use a custom HttpBinding to control the mapping between Camel message and HttpClient. + */ + public void setHttpBinding(HttpBinding httpBinding) { + this.httpBinding = httpBinding; + } + + public HttpConfiguration getHttpConfiguration() { + return httpConfiguration; + } + + /** + * To use the shared HttpConfiguration as base configuration. + */ + public void setHttpConfiguration(HttpConfiguration httpConfiguration) { + this.httpConfiguration = httpConfiguration; + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/e51b7f8d/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpCommonEndpoint.java ---------------------------------------------------------------------- diff --git a/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpCommonEndpoint.java b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpCommonEndpoint.java new file mode 100644 index 0000000..4ba2163 --- /dev/null +++ b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpCommonEndpoint.java @@ -0,0 +1,383 @@ +/** + * 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.apache.camel.http.common; + +import java.net.URI; +import java.net.URISyntaxException; + +import org.apache.camel.impl.DefaultEndpoint; +import org.apache.camel.spi.HeaderFilterStrategy; +import org.apache.camel.spi.HeaderFilterStrategyAware; +import org.apache.camel.spi.Metadata; +import org.apache.camel.spi.UriParam; +import org.apache.camel.spi.UriPath; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public abstract class HttpCommonEndpoint extends DefaultEndpoint implements HeaderFilterStrategyAware { + + // Note: all options must be documented with description in annotations so extended components can access the documentation + + private static final Logger LOG = LoggerFactory.getLogger(HttpCommonEndpoint.class); + + HttpCommonComponent component; + UrlRewrite urlRewrite; + + @UriPath(label = "producer", description = "The url of the HTTP endpoint to call.") @Metadata(required = "true") + URI httpUri; + @UriParam(description = "To use a custom HeaderFilterStrategy to filter header to and from Camel message.") + HeaderFilterStrategy headerFilterStrategy = new HttpHeaderFilterStrategy(); + @UriParam(description = "To use a custom HttpBinding to control the mapping between Camel message and HttpClient.") + HttpBinding binding; + @UriParam(label = "producer", defaultValue = "true", + description = "Option to disable throwing the HttpOperationFailedException in case of failed responses from the remote server." + + " This allows you to get all responses regardless of the HTTP status code.") + boolean throwExceptionOnFailure = true; + @UriParam(label = "producer", + description = "If the option is true, HttpProducer will ignore the Exchange.HTTP_URI header, and use the endpoint's URI for request." + + " You may also set the option throwExceptionOnFailure to be false to let the HttpProducer send all the fault response back.") + boolean bridgeEndpoint; + @UriParam(label = "consumer", + description = "Whether or not the consumer should try to find a target consumer by matching the URI prefix if no exact match is found.") + boolean matchOnUriPrefix; + @UriParam(defaultValue = "true", description = "If this option is false Jetty servlet will disable the HTTP streaming and set the content-length header on the response") + boolean chunked = true; + @UriParam(label = "consumer", + description = "Determines whether or not the raw input stream from Jetty is cached or not" + + " (Camel will read the stream into a in memory/overflow to file, Stream caching) cache." + + " By default Camel will cache the Jetty input stream to support reading it multiple times to ensure it Camel" + + " can retrieve all data from the stream. However you can set this option to true when you for example need" + + " to access the raw stream, such as streaming it directly to a file or other persistent store." + + " DefaultHttpBinding will copy the request input stream into a stream cache and put it into message body" + + " if this option is false to support reading the stream multiple times." + + " If you use Jetty to bridge/proxy an endpoint then consider enabling this option to improve performance," + + " in case you do not need to read the message payload multiple times.") + boolean disableStreamCache; + @UriParam(label = "producer", description = "The proxy host name") + String proxyHost; + @UriParam(label = "producer", description = "The proxy port number") + int proxyPort; + @UriParam(label = "producer", enums = "Basic,Digest,NTLM", description = "Authentication method for proxy, either as Basic, Digest or NTLM.") + String authMethodPriority; + @UriParam(description = "Option to disable throwing the HttpOperationFailedException in case of failed responses from the remote server." + + " This allows you to get all responses regardless of the HTTP status code.") + boolean transferException; + @UriParam(label = "consumer", + description = "Specifies whether to enable HTTP TRACE for this Jetty consumer. By default TRACE is turned off.") + boolean traceEnabled; + @UriParam(label = "consumer", + description = "Used to only allow consuming if the HttpMethod matches, such as GET/POST/PUT etc. Multiple methods can be specified separated by comma.") + String httpMethodRestrict; + @UriParam(label = "consumer", + description = "To use a custom buffer size on the javax.servlet.ServletResponse.") + Integer responseBufferSize; + @UriParam(label = "producer", + description = "If this option is true, The http producer won't read response body and cache the input stream") + boolean ignoreResponseBody; + @UriParam(label = "producer", defaultValue = "true", + description = "If this option is true then IN exchange headers will be copied to OUT exchange headers according to copy strategy." + + " Setting this to false, allows to only include the headers from the HTTP response (not propagating IN headers).") + boolean copyHeaders = true; + @UriParam(label = "consumer", + description = "Whether to eager check whether the HTTP requests has content if the content-length header is 0 or not present." + + " This can be turned on in case HTTP clients do not send streamed data.") + boolean eagerCheckContentAvailable; + + public HttpCommonEndpoint() { + } + + public HttpCommonEndpoint(String endPointURI, HttpCommonComponent component, URI httpURI) throws URISyntaxException { + super(endPointURI, component); + this.component = component; + this.httpUri = httpURI; + } + + public void connect(HttpConsumer consumer) throws Exception { + component.connect(consumer); + } + + public void disconnect(HttpConsumer consumer) throws Exception { + component.disconnect(consumer); + } + + public boolean isLenientProperties() { + // true to allow dynamic URI options to be configured and passed to external system for eg. the HttpProducer + return true; + } + + public boolean isSingleton() { + return true; + } + + + // Properties + //------------------------------------------------------------------------- + + public HttpBinding getBinding() { + if (binding == null) { + // create a new binding and use the options from this endpoint + binding = new DefaultHttpBinding(); + binding.setHeaderFilterStrategy(getHeaderFilterStrategy()); + binding.setTransferException(isTransferException()); + binding.setEagerCheckContentAvailable(isEagerCheckContentAvailable()); + } + return binding; + } + + /** + * To use a custom HttpBinding to control the mapping between Camel message and HttpClient. + */ + public void setBinding(HttpBinding binding) { + this.binding = binding; + } + + public String getPath() { + //if the path is empty, we just return the default path here + return httpUri.getPath().length() == 0 ? "/" : httpUri.getPath(); + } + + public int getPort() { + if (httpUri.getPort() == -1) { + if ("https".equals(getProtocol())) { + return 443; + } else { + return 80; + } + } + return httpUri.getPort(); + } + + public String getProtocol() { + return httpUri.getScheme(); + } + + public URI getHttpUri() { + return httpUri; + } + + /** + * The url of the HTTP endpoint to call. + */ + public void setHttpUri(URI httpUri) { + this.httpUri = httpUri; + } + + public HeaderFilterStrategy getHeaderFilterStrategy() { + return headerFilterStrategy; + } + + /** + * To use a custom HeaderFilterStrategy to filter header to and from Camel message. + */ + public void setHeaderFilterStrategy(HeaderFilterStrategy headerFilterStrategy) { + this.headerFilterStrategy = headerFilterStrategy; + } + + public boolean isThrowExceptionOnFailure() { + return throwExceptionOnFailure; + } + + /** + * Option to disable throwing the HttpOperationFailedException in case of failed responses from the remote server. + * This allows you to get all responses regardless of the HTTP status code. + */ + public void setThrowExceptionOnFailure(boolean throwExceptionOnFailure) { + this.throwExceptionOnFailure = throwExceptionOnFailure; + } + + public boolean isBridgeEndpoint() { + return bridgeEndpoint; + } + + /** + * If the option is true, HttpProducer will ignore the Exchange.HTTP_URI header, and use the endpoint's URI for request. + * You may also set the option throwExceptionOnFailure to be false to let the HttpProducer send all the fault response back. + */ + public void setBridgeEndpoint(boolean bridge) { + this.bridgeEndpoint = bridge; + } + + public boolean isMatchOnUriPrefix() { + return matchOnUriPrefix; + } + + /** + * Whether or not the consumer should try to find a target consumer by matching the URI prefix if no exact match is found. + * <p/> + * See more details at: http://camel.apache.org/how-do-i-let-jetty-match-wildcards.html + */ + public void setMatchOnUriPrefix(boolean match) { + this.matchOnUriPrefix = match; + } + + public boolean isDisableStreamCache() { + return this.disableStreamCache; + } + + /** + * Determines whether or not the raw input stream from Jetty is cached or not + * (Camel will read the stream into a in memory/overflow to file, Stream caching) cache. + * By default Camel will cache the Jetty input stream to support reading it multiple times to ensure it Camel + * can retrieve all data from the stream. However you can set this option to true when you for example need + * to access the raw stream, such as streaming it directly to a file or other persistent store. + * DefaultHttpBinding will copy the request input stream into a stream cache and put it into message body + * if this option is false to support reading the stream multiple times. + * If you use Jetty to bridge/proxy an endpoint then consider enabling this option to improve performance, + * in case you do not need to read the message payload multiple times. + */ + public void setDisableStreamCache(boolean disable) { + this.disableStreamCache = disable; + } + + public boolean isChunked() { + return this.chunked; + } + + /** + * If this option is false Jetty servlet will disable the HTTP streaming and set the content-length header on the response + */ + public void setChunked(boolean chunked) { + this.chunked = chunked; + } + + public String getProxyHost() { + return proxyHost; + } + + /** + * The proxy host name + */ + public void setProxyHost(String proxyHost) { + this.proxyHost = proxyHost; + } + + public int getProxyPort() { + return proxyPort; + } + + /** + * The proxy port number + */ + public void setProxyPort(int proxyPort) { + this.proxyPort = proxyPort; + } + + public String getAuthMethodPriority() { + return authMethodPriority; + } + + /** + * Authentication method for proxy, either as Basic, Digest or NTLM. + */ + public void setAuthMethodPriority(String authMethodPriority) { + this.authMethodPriority = authMethodPriority; + } + + public boolean isTransferException() { + return transferException; + } + + /** + * Option to disable throwing the HttpOperationFailedException in case of failed responses from the remote server. + * This allows you to get all responses regardless of the HTTP status code. + */ + public void setTransferException(boolean transferException) { + this.transferException = transferException; + } + + public boolean isTraceEnabled() { + return this.traceEnabled; + } + + /** + * Specifies whether to enable HTTP TRACE for this Jetty consumer. By default TRACE is turned off. + */ + public void setTraceEnabled(boolean traceEnabled) { + this.traceEnabled = traceEnabled; + } + + public String getHttpMethodRestrict() { + return httpMethodRestrict; + } + + /** + * Used to only allow consuming if the HttpMethod matches, such as GET/POST/PUT etc. + * Multiple methods can be specified separated by comma. + */ + public void setHttpMethodRestrict(String httpMethodRestrict) { + this.httpMethodRestrict = httpMethodRestrict; + } + + public UrlRewrite getUrlRewrite() { + return urlRewrite; + } + + /** + * Refers to a custom org.apache.camel.component.http.UrlRewrite which allows you to rewrite urls when you bridge/proxy endpoints. + * See more details at http://camel.apache.org/urlrewrite.html + */ + public void setUrlRewrite(UrlRewrite urlRewrite) { + this.urlRewrite = urlRewrite; + } + + public Integer getResponseBufferSize() { + return responseBufferSize; + } + + /** + * To use a custom buffer size on the javax.servlet.ServletResponse. + */ + public void setResponseBufferSize(Integer responseBufferSize) { + this.responseBufferSize = responseBufferSize; + } + + public boolean isIgnoreResponseBody() { + return ignoreResponseBody; + } + + /** + * If this option is true, The http producer won't read response body and cache the input stream. + */ + public void setIgnoreResponseBody(boolean ignoreResponseBody) { + this.ignoreResponseBody = ignoreResponseBody; + } + + /** + * If this option is true then IN exchange headers will be copied to OUT exchange headers according to copy strategy. + * Setting this to false, allows to only include the headers from the HTTP response (not propagating IN headers). + */ + public boolean isCopyHeaders() { + return copyHeaders; + } + + public void setCopyHeaders(boolean copyHeaders) { + this.copyHeaders = copyHeaders; + } + + public boolean isEagerCheckContentAvailable() { + return eagerCheckContentAvailable; + } + + /** + * Whether to eager check whether the HTTP requests has content if the content-length header is 0 or not present. + * This can be turned on in case HTTP clients do not send streamed data. + */ + public void setEagerCheckContentAvailable(boolean eagerCheckContentAvailable) { + this.eagerCheckContentAvailable = eagerCheckContentAvailable; + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/e51b7f8d/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpConfiguration.java ---------------------------------------------------------------------- diff --git a/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpConfiguration.java b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpConfiguration.java new file mode 100644 index 0000000..6a3e646 --- /dev/null +++ b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpConfiguration.java @@ -0,0 +1,143 @@ +/** + * 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.apache.camel.http.common; + +import java.io.Serializable; + +public class HttpConfiguration implements Serializable { + private static final long serialVersionUID = 1L; + + private String authMethod; + private String authUsername; + private String authPassword; + private String authDomain; + private String authHost; + + private String proxyAuthMethod; + private String proxyAuthUsername; + private String proxyAuthPassword; + private String proxyAuthDomain; + private String proxyAuthHost; + + private String proxyHost; + private int proxyPort; + private String authMethodPriority; + + public String getAuthUsername() { + return authUsername; + } + + public void setAuthUsername(String authUsername) { + this.authUsername = authUsername; + } + + public String getAuthPassword() { + return authPassword; + } + + public void setAuthPassword(String authPassword) { + this.authPassword = authPassword; + } + + public String getAuthDomain() { + return authDomain; + } + + public void setAuthDomain(String authDomain) { + this.authDomain = authDomain; + } + + public String getAuthHost() { + return authHost; + } + + public void setAuthHost(String authHost) { + this.authHost = authHost; + } + + public String getProxyAuthUsername() { + return proxyAuthUsername; + } + + public void setProxyAuthUsername(String proxyAuthUsername) { + this.proxyAuthUsername = proxyAuthUsername; + } + + public String getProxyAuthPassword() { + return proxyAuthPassword; + } + + public void setProxyAuthPassword(String proxyAuthPassword) { + this.proxyAuthPassword = proxyAuthPassword; + } + + public String getProxyAuthDomain() { + return proxyAuthDomain; + } + + public void setProxyAuthDomain(String proxyAuthDomain) { + this.proxyAuthDomain = proxyAuthDomain; + } + + public String getProxyAuthHost() { + return proxyAuthHost; + } + + public void setProxyAuthHost(String proxyAuthHost) { + this.proxyAuthHost = proxyAuthHost; + } + + public String getAuthMethod() { + return authMethod; + } + + public void setAuthMethod(String authMethod) { + this.authMethod = authMethod; + } + + public String getProxyAuthMethod() { + return proxyAuthMethod; + } + + public void setProxyAuthMethod(String proxyAuthMethod) { + this.proxyAuthMethod = proxyAuthMethod; + } + + public String getProxyHost() { + return proxyHost; + } + + public void setProxyHost(String proxyHost) { + this.proxyHost = proxyHost; + } + + public int getProxyPort() { + return proxyPort; + } + + public void setProxyPort(int proxyPort) { + this.proxyPort = proxyPort; + } + + public String getAuthMethodPriority() { + return authMethodPriority; + } + + public void setAuthMethodPriority(String authMethodPriority) { + this.authMethodPriority = authMethodPriority; + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/e51b7f8d/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpConstants.java ---------------------------------------------------------------------- diff --git a/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpConstants.java b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpConstants.java new file mode 100644 index 0000000..bccf724 --- /dev/null +++ b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpConstants.java @@ -0,0 +1,26 @@ +/** + * 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.apache.camel.http.common; + +public final class HttpConstants { + + public static final String CONTENT_TYPE_JAVA_SERIALIZED_OBJECT = "application/x-java-serialized-object"; + public static final String CONTENT_TYPE_WWW_FORM_URLENCODED = "application/x-www-form-urlencoded"; + + private HttpConstants() { + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/e51b7f8d/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpConsumer.java ---------------------------------------------------------------------- diff --git a/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpConsumer.java b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpConsumer.java new file mode 100644 index 0000000..f389081 --- /dev/null +++ b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpConsumer.java @@ -0,0 +1,80 @@ +/** + * 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.apache.camel.http.common; + +import org.apache.camel.Processor; +import org.apache.camel.SuspendableService; +import org.apache.camel.impl.DefaultConsumer; + +public class HttpConsumer extends DefaultConsumer implements SuspendableService { + private volatile boolean suspended; + private boolean traceEnabled; + + public HttpConsumer(HttpCommonEndpoint endpoint, Processor processor) { + super(endpoint, processor); + if (endpoint.isTraceEnabled()) { + setTraceEnabled(true); + } + } + + @Override + public HttpCommonEndpoint getEndpoint() { + return (HttpCommonEndpoint)super.getEndpoint(); + } + + public HttpBinding getBinding() { + return getEndpoint().getBinding(); + } + + public String getPath() { + return getEndpoint().getPath(); + } + + @Override + protected void doStart() throws Exception { + super.doStart(); + getEndpoint().connect(this); + suspended = false; + } + + @Override + protected void doStop() throws Exception { + suspended = false; + getEndpoint().disconnect(this); + super.doStop(); + } + + public void suspend() { + suspended = true; + } + + public void resume() { + suspended = false; + } + + public boolean isSuspended() { + return suspended; + } + + public boolean isTraceEnabled() { + return this.traceEnabled; + } + + public void setTraceEnabled(boolean traceEnabled) { + this.traceEnabled = traceEnabled; + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/e51b7f8d/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpConverter.java ---------------------------------------------------------------------- diff --git a/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpConverter.java b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpConverter.java new file mode 100644 index 0000000..8efa895 --- /dev/null +++ b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpConverter.java @@ -0,0 +1,98 @@ +/** + * 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.apache.camel.http.common; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.camel.Converter; +import org.apache.camel.Exchange; +import org.apache.camel.Message; +import org.apache.camel.util.GZIPHelper; + +/** + * Some converter methods making it easy to convert the body of a message to servlet types or to switch between + * the underlying {@link ServletInputStream} or {@link BufferedReader} payloads etc. + */ +@Converter +public final class HttpConverter { + + private HttpConverter() { + } + + @Converter + public static HttpServletRequest toServletRequest(Message message) { + if (message == null) { + return null; + } + return message.getHeader(Exchange.HTTP_SERVLET_REQUEST, HttpServletRequest.class); + } + + @Converter + public static HttpServletResponse toServletResponse(Message message) { + if (message == null) { + return null; + } + return message.getHeader(Exchange.HTTP_SERVLET_RESPONSE, HttpServletResponse.class); + } + + @Converter + public static ServletInputStream toServletInputStream(HttpMessage message) throws IOException { + HttpServletRequest request = toServletRequest(message); + if (request != null) { + return request.getInputStream(); + } + return null; + } + + @Converter + public static InputStream toInputStream(HttpMessage message, Exchange exchange) throws Exception { + return toInputStream(toServletRequest(message), exchange); + } + + @Converter + public static BufferedReader toReader(HttpMessage message) throws IOException { + HttpServletRequest request = toServletRequest(message); + if (request != null) { + return request.getReader(); + } + return null; + } + + @Converter + public static InputStream toInputStream(HttpServletRequest request, Exchange exchange) throws IOException { + if (request == null) { + return null; + } + InputStream is = request.getInputStream(); + if (is != null && is.available() <= 0) { + // there is no data, so we cannot uncompress etc. + return is; + } + if (exchange == null || !exchange.getProperty(Exchange.SKIP_GZIP_ENCODING, Boolean.FALSE, Boolean.class)) { + String contentEncoding = request.getHeader(Exchange.CONTENT_ENCODING); + return GZIPHelper.uncompressGzip(contentEncoding, is); + } else { + return is; + } + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/e51b7f8d/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpHeaderFilterStrategy.java ---------------------------------------------------------------------- diff --git a/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpHeaderFilterStrategy.java b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpHeaderFilterStrategy.java new file mode 100644 index 0000000..6df5b34 --- /dev/null +++ b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpHeaderFilterStrategy.java @@ -0,0 +1,49 @@ +/** + * 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.apache.camel.http.common; + +import org.apache.camel.impl.DefaultHeaderFilterStrategy; + +public class HttpHeaderFilterStrategy extends DefaultHeaderFilterStrategy { + + public HttpHeaderFilterStrategy() { + initialize(); + } + + protected void initialize() { + getOutFilter().add("content-length"); + getOutFilter().add("content-type"); + getOutFilter().add("host"); + // Add the filter for the Generic Message header + // http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.5 + getOutFilter().add("cache-control"); + getOutFilter().add("connection"); + getOutFilter().add("date"); + getOutFilter().add("pragma"); + getOutFilter().add("trailer"); + getOutFilter().add("transfer-encoding"); + getOutFilter().add("upgrade"); + getOutFilter().add("via"); + getOutFilter().add("warning"); + + setLowerCase(true); + + // filter headers begin with "Camel" or "org.apache.camel" + // must ignore case for Http based transports + setOutFilterPattern("(?i)(Camel|org\\.apache\\.camel)[\\.|a-z|A-z|0-9]*"); + } +}