diff --git a/components/camel-http-common/pom.xml b/components/camel-http-common/pom.xml
index 72a0a7412..0750cbc5a 100644
--- a/components/camel-http-common/pom.xml
+++ b/components/camel-http-common/pom.xml
@@ -30,14 +30,17 @@
camel-http-commonbundle
- Apache Camel :: Karaf :: Components :: Common
+ Apache Camel :: Karaf :: Components :: HTTP Common
+
+
org.apache.camel*;version=${camel-version}
- jakarta.servlet*;resolution:=optional,
+ javax.servlet*;resolution:=optional,
+ io.quarkus*;resolution:=optional,
*
@@ -45,39 +48,106 @@
org.apache.camel
- camel-http-common
+ camel-http-base
+ ${camel-version}
+
+
+ org.apache.camel
+ camel-cloud${camel-version}
-
-
- org.apache.camel
- *
-
-
+
+
+ org.apache.camel
+ camel-support
+ ${camel-version}
+
+
+ org.apache.camel
+ camel-attachments
+ ${camel-version}
+
+
+
+ jakarta.servlet
+ jakarta.servlet-api
+ 4.0.4
+ provided
+
+ org.apache.camel
+ camel-package-maven-plugin
+ ${camel-version}
+
+
+ false
+
+
+
+ generate
+ process-classes
+
+ generate
+
+
+
+ generate-postcompile
+ prepare-package
+
+ generate-postcompile
+
+
+
+
+
+ org.apache.camel
+ camel-core-model
+ ${camel-version}
+ compile
+
+
+ org.apache.maven.plugins
- maven-shade-plugin
+ maven-compiler-plugin
- package
+ recompile
+ process-classes
- shade
+ compile
+
+
+
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+
+
+ initialize
+
+ add-source
+ add-resource
-
-
- org.apache.camel:camel-http-common
-
-
+
+ src/generated/java
+
+
+
+ src/generated/resources
+
+
+
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 000000000..6c077bb55
--- /dev/null
+++ b/components/camel-http-common/src/main/java/org/apache/camel/http/common/CamelServlet.java
@@ -0,0 +1,522 @@
+/*
+ * 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.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
+
+import org.apache.camel.AsyncProcessor;
+import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
+import org.apache.camel.ExchangePattern;
+import org.apache.camel.Processor;
+import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.spi.ExecutorServiceManager;
+import org.apache.camel.support.LifecycleStrategySupport;
+import org.apache.camel.support.RestConsumerContextPathMatcher;
+import org.apache.camel.util.ObjectHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import static java.util.concurrent.TimeUnit.MINUTES;
+
+/**
+ * A servlet to use as a Camel route as entry.
+ */
+public class CamelServlet extends HttpServlet implements HttpRegistryProvider {
+ public static final String ASYNC_PARAM = "async";
+ public static final String FORCE_AWAIT_PARAM = "forceAwait";
+ public static final String EXECUTOR_REF_PARAM = "executorRef";
+ public static final List METHODS
+ = Arrays.asList("GET", "HEAD", "POST", "PUT", "DELETE", "TRACE", "OPTIONS", "CONNECT", "PATCH");
+
+ 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 boolean async;
+ private boolean forceAwait;
+ private String executorRef;
+
+ private final ConcurrentMap executorServicePerContext = new ConcurrentHashMap<>();
+
+ private ServletResolveConsumerStrategy servletResolveConsumerStrategy = new HttpServletResolveConsumerStrategy();
+ private final ConcurrentMap consumers = new ConcurrentHashMap<>();
+
+ @Override
+ public void init(ServletConfig config) throws ServletException {
+ super.init(config);
+ this.servletName = config.getServletName();
+
+ final String asyncParam = config.getInitParameter(ASYNC_PARAM);
+ this.async = asyncParam != null && ObjectHelper.toBoolean(asyncParam);
+ this.forceAwait = Boolean.parseBoolean(config.getInitParameter(FORCE_AWAIT_PARAM));
+ this.executorRef = config.getInitParameter(EXECUTOR_REF_PARAM);
+ log.trace("servlet '{}' initialized with: async={}", servletName, async);
+ }
+
+ @Override
+ protected void service(HttpServletRequest request, HttpServletResponse response) {
+ log.trace("Service: {}", request);
+ try {
+ handleService(request, response);
+ } catch (Exception e) {
+ // do not leak exception back to caller
+ log.warn("Error handling request due to: {}", e.getMessage(), e);
+ if (!response.isCommitted()) {
+ sendError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+ }
+ }
+ }
+
+ protected void handleService(HttpServletRequest req, HttpServletResponse resp) throws Exception {
+ if (isAsync()) {
+ handleAsync(req, resp);
+ } else {
+ doService(req, resp);
+ }
+ }
+
+ private void handleAsync(HttpServletRequest req, HttpServletResponse resp) throws Exception {
+ if (executorRef != null) {
+ HttpConsumer consumer = doResolve(req, resp); // can be done sync
+ if (consumer == null) {
+ return;
+ }
+ Executor pool = ObjectHelper.notNull(getExecutorService(consumer), executorRef);
+ final AsyncContext context = req.startAsync();
+ try {
+ pool.execute(() -> doAsyncExecution(req, resp, consumer, context));
+ } catch (final RuntimeException re) { // submit fails
+ context.complete();
+ throw re;
+ }
+ } else { // will use http servlet threads so normally http threads so better to enable useCamelExecutor
+ final AsyncContext context = req.startAsync();
+ try {
+ context.start(() -> doServiceAsync(context));
+ } catch (final RuntimeException re) { // submit fails
+ context.complete();
+ throw re;
+ }
+ }
+ }
+
+ private void doAsyncExecution(
+ HttpServletRequest req, HttpServletResponse resp, HttpConsumer consumer, AsyncContext context) {
+ try {
+ final CompletionStage> promise = doExecute(req, resp, consumer);
+ if (promise == null) { // early quit
+ context.complete();
+ } else {
+ promise.whenComplete((r, e) -> context.complete());
+ }
+ } catch (Exception e) {
+ onError(resp, e);
+ context.complete();
+ }
+ }
+
+ private void onError(HttpServletResponse resp, Exception e) {
+ //An error shouldn't occur as we should handle most error in doService
+ log.error("Error processing request", e);
+ sendError(resp, HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+ //Need to wrap it in RuntimeException as it occurs in a Runnable
+ throw new RuntimeCamelException(e);
+ }
+
+ protected Executor getExecutorService(HttpConsumer consumer) {
+ CamelContext camelContext = consumer.getEndpoint().getCamelContext();
+ Executor pool = camelContext.getRegistry().lookupByNameAndType(executorRef, Executor.class);
+ if (pool != null) {
+ return pool;
+ }
+
+ if (camelContext.isStopping() || camelContext.isStopped()) { // shouldn't occur but as a protection
+ return null;
+ }
+ return executorServicePerContext.computeIfAbsent(camelContext, ctx -> createExecutorService(ctx, camelContext));
+ }
+
+ private ExecutorService createExecutorService(CamelContext ctx, CamelContext camelContext) {
+ ExecutorServiceManager manager = camelContext.getExecutorServiceManager();
+ ExecutorService es = manager.newThreadPool(this, getClass().getSimpleName() + "Executor", executorRef);
+ if (es == null) {
+ getServletContext().log(
+ "ExecutorServiceRef " + executorRef + " not found in registry (as an ExecutorService instance) " +
+ "or as a thread pool profile, will default for " + ctx.getName() + ".");
+ es = manager.newDefaultThreadPool(this, getClass().getSimpleName() + "Executor");
+ }
+ ctx.addLifecycleStrategy(createLifecycleStrategy());
+ return es;
+ }
+
+ private LifecycleStrategySupport createLifecycleStrategy() {
+ return new LifecycleStrategySupport() {
+ @Override
+ public void onContextStopping(final CamelContext context) {
+ final ExecutorService service = executorServicePerContext.remove(context);
+ if (service != null && !service.isShutdown() && !service.isTerminated()) {
+ service.shutdownNow();
+ try { // give it a chance to finish before quitting
+ service.awaitTermination(1, MINUTES);
+ } catch (final InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+ };
+ }
+
+ /**
+ * This is used to handle request asynchronously
+ *
+ * @param context the {@link AsyncContext}
+ */
+ protected void doServiceAsync(AsyncContext context) {
+ final HttpServletRequest request = (HttpServletRequest) context.getRequest();
+ final HttpServletResponse response = (HttpServletResponse) context.getResponse();
+ try {
+ doService(request, response);
+ } catch (Exception e) {
+ //An error shouldn't occur as we should handle most of error in doService
+ onError(response, e);
+ } finally {
+ context.complete();
+ }
+ }
+
+ /**
+ * This is the logical implementation to handle request with {@link CamelServlet} This is where most exceptions
+ * should be handled
+ *
+ * @param request the {@link HttpServletRequest}
+ * @param response the {@link HttpServletResponse}
+ */
+ protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
+ log.trace("Service: {}", request);
+ HttpConsumer consumer = doResolve(request, response);
+ if (consumer != null) {
+ doExecute(request, response, consumer);
+ }
+ }
+
+ private CompletionStage> doExecute(HttpServletRequest req, HttpServletResponse res, HttpConsumer consumer)
+ throws Exception {
+ // are we suspended?
+ if (consumer.isSuspended()) {
+ log.debug("Consumer suspended, cannot service request {}", req);
+ sendError(res, HttpServletResponse.SC_SERVICE_UNAVAILABLE);
+ return null;
+ }
+
+ // if its an OPTIONS request then return which method is allowed
+ if ("OPTIONS".equals(req.getMethod()) && !consumer.isOptionsEnabled()) {
+ performOptionsRequest(req, res, consumer);
+ return null;
+ }
+
+ if (consumer.getEndpoint().getHttpMethodRestrict() != null
+ && !consumer.getEndpoint().getHttpMethodRestrict().contains(req.getMethod())) {
+ sendError(res, HttpServletResponse.SC_METHOD_NOT_ALLOWED);
+ return null;
+ }
+
+ if ("TRACE".equals(req.getMethod()) && !consumer.isTraceEnabled()) {
+ sendError(res, HttpServletResponse.SC_METHOD_NOT_ALLOWED);
+ return null;
+ }
+
+ // create exchange and set data on it
+ Exchange exchange = consumer.createExchange(false);
+ exchange.setPattern(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(req.getContentType(), exchange);
+ exchange.setIn(new HttpMessage(exchange, consumer.getEndpoint(), req, res));
+ // 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) {
+ exchange.setException(e);
+ }
+
+ boolean isAsync = false;
+ CompletionStage> result = null;
+ try {
+ if (log.isTraceEnabled()) {
+ log.trace("Processing request for exchangeId: {}", exchange.getExchangeId());
+ }
+ // process the exchange
+ final Processor processor = consumer.getProcessor();
+ isAsync = isAsync() && !forceAwait && AsyncProcessor.class.isInstance(processor);
+ if (isAsync) {
+ result = tryAsyncProcess(res, consumer, processor, exchange);
+ } else {
+ processor.process(exchange);
+ }
+ } catch (Exception e) {
+ exchange.setException(e);
+ }
+
+ try {
+ if (!isAsync) {
+ afterProcess(res, consumer, exchange, true);
+ }
+ } finally {
+ restoreTccl(exchange, oldTccl);
+ }
+ return result;
+ }
+
+ private CompletionStage> tryAsyncProcess(
+ HttpServletResponse res, HttpConsumer consumer, Processor processor, Exchange exchange) {
+ CompletionStage> result;
+ result = AsyncProcessor.class.cast(processor)
+ .processAsync(exchange)
+ .whenComplete((r, ex) -> {
+ if (ex != null) {
+ exchange.setException(ex);
+ } else {
+ try {
+ afterProcess(res, consumer, exchange, false);
+ } catch (Exception e) {
+ exchange.setException(e);
+ }
+ }
+ });
+ return result;
+ }
+
+ private void performOptionsRequest(HttpServletRequest req, HttpServletResponse res, HttpConsumer consumer) {
+ String allowedMethods = METHODS.stream()
+ .filter(m -> getServletResolveConsumerStrategy().isHttpMethodAllowed(req, m, getConsumers()))
+ .collect(Collectors.joining(","));
+ if (allowedMethods == null && consumer.getEndpoint().getHttpMethodRestrict() != null) {
+ allowedMethods = consumer.getEndpoint().getHttpMethodRestrict();
+ }
+ if (allowedMethods == null) {
+ // allow them all
+ allowedMethods = "GET,HEAD,POST,PUT,DELETE,TRACE,OPTIONS,CONNECT,PATCH";
+ }
+ if (!allowedMethods.contains("OPTIONS")) {
+ allowedMethods = allowedMethods + ",OPTIONS";
+ }
+ res.addHeader("Allow", allowedMethods);
+ res.setStatus(HttpServletResponse.SC_OK);
+ }
+
+ protected void afterProcess(
+ HttpServletResponse res, HttpConsumer consumer, Exchange exchange,
+ boolean rethrow)
+ throws Exception {
+ try {
+ // now lets output to the res
+ if (log.isTraceEnabled()) {
+ log.trace("Writing res for exchangeId: {}", exchange.getExchangeId());
+ }
+ Integer bs = consumer.getEndpoint().getResponseBufferSize();
+ if (bs != null) {
+ log.trace("Using res buffer size: {}", bs);
+ res.setBufferSize(bs);
+ }
+ consumer.getBinding().writeResponse(exchange, res);
+ } catch (IOException e) {
+ handleIOException(exchange, rethrow, e);
+ } catch (Exception e) {
+ handleException(exchange, rethrow, e);
+ } finally {
+ consumer.doneUoW(exchange);
+ consumer.releaseExchange(exchange, false);
+ }
+ }
+
+ private void handleException(Exchange exchange, boolean rethrow, Exception e) {
+ log.error("Error processing request", e);
+ if (rethrow) {
+ throw new RuntimeCamelException(e);
+ } else {
+ exchange.setException(e);
+ }
+ }
+
+ private void handleIOException(Exchange exchange, boolean rethrow, IOException e) throws IOException {
+ log.error("Error processing request", e);
+ if (rethrow) {
+ throw e;
+ } else {
+ exchange.setException(e);
+ }
+ }
+
+ private HttpConsumer doResolve(HttpServletRequest request, HttpServletResponse response) {
+ // Is there a consumer registered for the request.
+ HttpConsumer consumer = getServletResolveConsumerStrategy().resolve(request, getConsumers());
+ if (consumer == null) {
+ // okay we cannot process this requires so return either 404 or 405.
+ // to know if its 405 then we need to check if any other HTTP method would have a consumer for the "same" request
+ boolean hasAnyMethod = METHODS.stream()
+ .anyMatch(m -> getServletResolveConsumerStrategy().isHttpMethodAllowed(request, m, getConsumers()));
+ if (hasAnyMethod) {
+ log.debug("No consumer to service request {} as method {} is not allowed", request, request.getMethod());
+ sendError(response, HttpServletResponse.SC_METHOD_NOT_ALLOWED);
+ return null;
+ } else {
+ log.debug("No consumer to service request {} as resource is not found", request);
+ sendError(response, HttpServletResponse.SC_NOT_FOUND);
+ return null;
+ }
+ }
+ return consumer;
+ }
+
+ @Override
+ public void connect(HttpConsumer consumer) {
+ log.debug("Connecting consumer: {}", consumer);
+ String endpointUri = consumer.getEndpoint().getEndpointUri();
+ if (consumers.containsKey(endpointUri)) {
+ throw new IllegalStateException("Duplicate request path for " + endpointUri);
+ }
+ consumers.put(endpointUri, consumer);
+ RestConsumerContextPathMatcher.register(consumer.getPath());
+ }
+
+ @Override
+ public void disconnect(HttpConsumer consumer) {
+ log.debug("Disconnecting consumer: {}", consumer);
+ consumers.remove(consumer.getEndpoint().getEndpointUri());
+ RestConsumerContextPathMatcher.unRegister(consumer.getPath());
+ }
+
+ @Override
+ 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 boolean isAsync() {
+ return async;
+ }
+
+ public void setAsync(boolean async) {
+ this.async = async;
+ }
+
+ public Map getConsumers() {
+ return Collections.unmodifiableMap(consumers);
+ }
+
+ protected static void sendError(HttpServletResponse res, int code) {
+ try {
+ res.sendError(code);
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+
+ /**
+ * Override the Thread Context ClassLoader if need be.
+ *
+ * @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 {}",
+ 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 {}",
+ exchange.getExchangeId(), oldTccl, Thread.currentThread().getName());
+ }
+ }
+
+}
\ No newline at end of file
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 000000000..3a5212463
--- /dev/null
+++ b/components/camel-http-common/src/main/java/org/apache/camel/http/common/DefaultHttpBinding.java
@@ -0,0 +1,774 @@
+/*
+ * 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.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TimeZone;
+import java.util.concurrent.TimeoutException;
+import java.util.zip.GZIPOutputStream;
+
+import jakarta.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.ExchangePropertyKey;
+import org.apache.camel.InvalidPayloadException;
+import org.apache.camel.Message;
+import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.StreamCache;
+import org.apache.camel.attachment.AttachmentMessage;
+import org.apache.camel.attachment.CamelFileDataSource;
+import org.apache.camel.converter.stream.CachedOutputStream;
+import org.apache.camel.spi.HeaderFilterStrategy;
+import org.apache.camel.support.ExchangeHelper;
+import org.apache.camel.support.GZIPHelper;
+import org.apache.camel.support.MessageHelper;
+import org.apache.camel.support.ObjectHelper;
+import org.apache.camel.util.FileUtil;
+import org.apache.camel.util.IOHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.apache.camel.support.http.HttpUtil.determineResponseCode;
+
+/**
+ * Binding between {@link HttpMessage} and {@link HttpServletResponse}.
+ *
+ * Uses by default the {@link org.apache.camel.http.base.HttpHeaderFilterStrategy}
+ */
+public class DefaultHttpBinding implements HttpBinding {
+
+ /**
+ * Whether Date/Locale should be converted to String types (enabled by default)
+ */
+ public static final String DATE_LOCALE_CONVERSION = "CamelHttpBindingDateLocaleConversion";
+
+ /**
+ * The data format used for storing java.util.Date instances as a String value.
+ */
+ public static final String DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss zzz";
+
+ private static final Logger LOG = LoggerFactory.getLogger(DefaultHttpBinding.class);
+ private static final TimeZone TIME_ZONE_GMT = TimeZone.getTimeZone("GMT");
+
+ private boolean useReaderForPayload;
+ private boolean eagerCheckContentAvailable;
+ private boolean transferException;
+ private boolean muteException;
+ private boolean logException;
+ private boolean allowJavaSerializedObject;
+ private boolean mapHttpMessageBody = true;
+ private boolean mapHttpMessageHeaders = true;
+ private boolean mapHttpMessageFormUrlEncodedBody = true;
+ private HeaderFilterStrategy headerFilterStrategy = new org.apache.camel.http.base.HttpHeaderFilterStrategy();
+ private String fileNameExtWhitelist;
+
+ public DefaultHttpBinding() {
+ }
+
+ @Deprecated
+ public DefaultHttpBinding(HeaderFilterStrategy headerFilterStrategy) {
+ this.headerFilterStrategy = headerFilterStrategy;
+ }
+
+ @Deprecated
+ public DefaultHttpBinding(HttpCommonEndpoint endpoint) {
+ this.headerFilterStrategy = endpoint.getHeaderFilterStrategy();
+ this.transferException = endpoint.isTransferException();
+ this.muteException = endpoint.isMuteException();
+ this.logException = endpoint.isLogException();
+ if (endpoint.getComponent() != null) {
+ this.allowJavaSerializedObject = endpoint.getComponent().isAllowJavaSerializedObject();
+ }
+ }
+
+ @Override
+ public void readRequest(HttpServletRequest request, Message message) {
+ LOG.trace("readRequest {}", request);
+
+ // must read body before headers
+ if (mapHttpMessageBody) {
+ readBody(request, message);
+ }
+ if (mapHttpMessageHeaders) {
+ readHeaders(request, message);
+ }
+ if (mapHttpMessageFormUrlEncodedBody) {
+ try {
+ readFormUrlEncodedBody(request, message);
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeCamelException("Cannot read Form URL encoded body due " + e.getMessage(), e);
+ }
+ }
+
+ // populate the headers from the request
+ Map headers = message.getHeaders();
+
+ // always store these standard headers
+ // store the method and query and other info in headers as String types
+ String rawPath = getRawPath(request);
+ 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, rawPath);
+ // only set content type if not already extracted
+ headers.computeIfAbsent(Exchange.CONTENT_TYPE, k -> 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 {}", rawPath);
+ LOG.trace("HTTP content-type {}", headers.get(Exchange.CONTENT_TYPE));
+ }
+ }
+
+ protected void readHeaders(HttpServletRequest request, Message message) {
+ LOG.trace("readHeaders {}", request);
+
+ Map headers = message.getHeaders();
+
+ Enumeration> names = request.getHeaderNames();
+ while (names.hasMoreElements()) {
+ String name = (String) names.nextElement();
+ // mapping the content-type
+ if (name.equalsIgnoreCase("content-type")) {
+ name = Exchange.CONTENT_TYPE;
+ }
+ // some implementations like Jetty might return unique header names, while some others might not.
+ // Since we are going to call request.getHeaders() to get all values for a header name,
+ // we only need to process a header once.
+ if (!headers.containsKey(name)) {
+ Enumeration values = request.getHeaders(name);
+ while (values.hasMoreElements()) {
+ String value = values.nextElement();
+ // use http helper to extract parameter value as it may contain multiple values
+ Object extracted = HttpHelper.extractHttpParameterValue(value);
+ //apply the headerFilterStrategy
+ 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(ExchangePropertyKey.CHARSET_NAME, request.getCharacterEncoding());
+ }
+
+ try {
+ populateRequestParameters(request, message);
+ } catch (Exception e) {
+ throw new RuntimeCamelException("Cannot read request parameters due " + e.getMessage(), e);
+ }
+ }
+
+ protected void readBody(HttpServletRequest request, Message message) {
+ LOG.trace("readBody {}", request);
+
+ // Process attachments first as some servlet containers expect the body to not have been read at this point
+ populateAttachments(request, message);
+
+ // lets parse the body
+ Object body = message.getBody();
+ // reset the stream cache if the body is the instance of StreamCache
+ if (body instanceof StreamCache streamCache) {
+ streamCache.reset();
+ }
+
+ // 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())) {
+ // only deserialize java if allowed
+ if (allowJavaSerializedObject || isTransferException()) {
+ 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);
+ }
+ } else {
+ // set empty body
+ message.setBody(null);
+ }
+ }
+ }
+
+ protected void populateRequestParameters(HttpServletRequest request, Message message) {
+ //we populate the http request parameters without checking the request method
+ Map 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);
+ }
+ }
+ }
+ }
+ }
+
+ protected void readFormUrlEncodedBody(HttpServletRequest request, Message message) throws UnsupportedEncodingException {
+ LOG.trace("readFormUrlEncodedBody {}", request);
+ // should we extract key=value pairs from form bodies (application/x-www-form-urlencoded)
+ // and map those to Camel headers
+ if (mapHttpMessageBody && mapHttpMessageHeaders) {
+ LOG.trace("HTTP method {} with Content-Type {}", request.getMethod(), request.getContentType());
+ Map headers = message.getHeaders();
+ 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";
+ }
+
+ // lets parse the body
+ Object body = message.getBody();
+ // reset the stream cache if the body is the instance of StreamCache
+ if (body instanceof StreamCache streamCache) {
+ streamCache.reset();
+ }
+
+ // Push POST form params into the headers to retain compatibility with DefaultHttpBinding
+ String text = message.getBody(String.class);
+ if (org.apache.camel.util.ObjectHelper.isNotEmpty(text)) {
+ for (String param : text.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);
+ }
+ }
+ }
+
+ // reset the stream cache if the body is the instance of StreamCache
+ if (body instanceof StreamCache streamCache) {
+ streamCache.reset();
+ }
+ }
+ }
+ }
+
+ private String getRawPath(HttpServletRequest request) {
+ String uri = request.getRequestURI();
+ /**
+ * In async case, it seems that request.getContextPath() can return null
+ *
+ * @see https://dev.eclipse.org/mhonarc/lists/jetty-users/msg04669.html
+ */
+ String contextPath = request.getContextPath() == null ? "" : request.getContextPath();
+ String servletPath = request.getServletPath() == null ? "" : request.getServletPath();
+ return uri.substring(contextPath.length() + servletPath.length());
+ }
+
+ protected void populateAttachments(HttpServletRequest request, Message 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 fileObject) {
+ String fileName = request.getParameter(name);
+ // fix file name if using malicious parameter name
+ if (fileName != null) {
+ fileName = fileName.replaceAll("[\n\r\t]", "_");
+ }
+ // is the file name accepted
+ boolean accepted = true;
+ if (fileNameExtWhitelist != null) {
+ String ext = FileUtil.onlyExt(fileName);
+ if (ext != null) {
+ ext = ext.toLowerCase(Locale.US);
+ fileNameExtWhitelist = fileNameExtWhitelist.toLowerCase(Locale.US);
+ if (!fileNameExtWhitelist.equals("*") && !fileNameExtWhitelist.contains(ext)) {
+ accepted = false;
+ }
+ }
+ }
+ if (accepted) {
+ AttachmentMessage am = message.getExchange().getMessage(AttachmentMessage.class);
+ am.addAttachment(fileName, new DataHandler(new CamelFileDataSource(fileObject, fileName)));
+ } else {
+ LOG.debug(
+ "Cannot add file as attachment: {} because the file is not accepted according to fileNameExtWhitelist: {}",
+ fileName, fileNameExtWhitelist);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void writeResponse(Exchange exchange, HttpServletResponse response) throws IOException {
+ Message target = exchange.getMessage();
+ 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");
+ }
+ }
+
+ @Override
+ public void doWriteExceptionResponse(Throwable exception, HttpServletResponse response) throws IOException {
+ if (exception instanceof TimeoutException) {
+ response.setStatus(HttpServletResponse.SC_GATEWAY_TIMEOUT);
+ response.setContentType("text/plain");
+ response.getWriter().write("Timeout error");
+ } else if (isMuteException()) {
+ response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+ response.setContentLength(0);
+ response.setContentType("text/plain");
+ if (isLogException()) {
+ LOG.error("Server internal error response returned due to '{}'", exception.getMessage(), exception);
+ }
+ } else {
+ response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+
+ 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();
+ }
+ }
+ }
+
+ @Override
+ public void doWriteFaultResponse(Message message, HttpServletResponse response, Exchange exchange) throws IOException {
+ doWriteResponse(message, response, exchange);
+ }
+
+ @Override
+ public void doWriteResponse(Message message, HttpServletResponse response, Exchange exchange) throws IOException {
+ int statusCode = determineResponseCode(exchange, exchange.getMessage().getBody());
+ response.setStatus(statusCode);
+
+ // 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 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, true);
+ while (it.hasNext()) {
+ String headerValue = convertHeaderValueToString(exchange, 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 String convertHeaderValueToString(Exchange exchange, Object headerValue) {
+ if (headerValue instanceof Date date && convertDateAndLocaleLocally(exchange)) {
+ return toHttpDate(date);
+ } else {
+ if (headerValue instanceof Locale locale && convertDateAndLocaleLocally(exchange)) {
+ return toHttpLanguage(locale);
+ } else {
+ return exchange.getContext().getTypeConverter().convertTo(String.class, headerValue);
+ }
+ }
+ }
+
+ protected boolean convertDateAndLocaleLocally(Exchange exchange) {
+ // This check is done only if a given header value is Date or Locale
+ return exchange.getProperty(DATE_LOCALE_CONVERSION, Boolean.TRUE, Boolean.class);
+ }
+
+ protected boolean isText(String contentType) {
+ if (contentType != null) {
+ String temp = contentType.toLowerCase();
+ if (temp.contains("text") || temp.contains("html")) {
+ 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 (HttpConstants.CONTENT_TYPE_JAVA_SERIALIZED_OBJECT.equals(contentType)) {
+ if (allowJavaSerializedObject || isTransferException()) {
+ try {
+ Object object = message.getMandatoryBody(Serializable.class);
+ HttpHelper.writeObjectToServletResponse(response, object);
+ // object is written so return
+ return;
+ } catch (InvalidPayloadException e) {
+ throw new IOException(e);
+ }
+ } else {
+ throw new RuntimeCamelException(
+ "Content-type " + HttpConstants.CONTENT_TYPE_JAVA_SERIALIZED_OBJECT + " is not allowed");
+ }
+ }
+
+ // 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 bos) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Streaming (direct) response in non-chunked mode with content-length {}", len);
+ }
+ 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 = ExchangeHelper.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 httpCommonEndpoint) {
+ answer = httpCommonEndpoint.isChunked();
+ }
+ } else {
+ answer = message.getHeader(Exchange.HTTP_CHUNKED, boolean.class);
+ }
+ return answer;
+ }
+
+ protected void doWriteGZIPResponse(Message message, HttpServletResponse response, Exchange exchange) throws IOException {
+ ServletOutputStream os = response.getOutputStream();
+ GZIPOutputStream gos = new GZIPOutputStream(os);
+
+ Object body = exchange.getIn().getBody();
+ if (body instanceof InputStream is) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Streaming GZIP response in chunked mode with buffer size {}", response.getBufferSize());
+ }
+ copyStream(is, gos, response.getBufferSize());
+ } else {
+ byte[] bytes;
+ try {
+ bytes = message.getMandatoryBody(byte[].class);
+ } catch (InvalidPayloadException e) {
+ throw RuntimeCamelException.wrapRuntimeCamelException(e);
+ }
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Writing GZIP response in chunked mode from byte array with length: {}", bytes.length);
+ }
+ gos.write(bytes);
+ gos.flush();
+ IOHelper.close(gos);
+ }
+ }
+
+ @Override
+ public Object parseBody(HttpServletRequest request, Message message) throws IOException {
+ // lets assume the body is a reader
+ // 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, message.getExchange());
+ }
+ }
+
+ @Override
+ public boolean isUseReaderForPayload() {
+ return useReaderForPayload;
+ }
+
+ @Override
+ public void setUseReaderForPayload(boolean useReaderForPayload) {
+ this.useReaderForPayload = useReaderForPayload;
+ }
+
+ @Override
+ public boolean isEagerCheckContentAvailable() {
+ return eagerCheckContentAvailable;
+ }
+
+ @Override
+ public void setEagerCheckContentAvailable(boolean eagerCheckContentAvailable) {
+ this.eagerCheckContentAvailable = eagerCheckContentAvailable;
+ }
+
+ @Override
+ public boolean isTransferException() {
+ return transferException;
+ }
+
+ @Override
+ public void setTransferException(boolean transferException) {
+ this.transferException = transferException;
+ }
+
+ @Override
+ public boolean isMuteException() {
+ return muteException;
+ }
+
+ @Override
+ public void setMuteException(boolean muteException) {
+ this.muteException = muteException;
+ }
+
+ @Override
+ public boolean isLogException() {
+ return logException;
+ }
+
+ @Override
+ public void setLogException(boolean logException) {
+ this.logException = logException;
+ }
+
+ @Override
+ public boolean isAllowJavaSerializedObject() {
+ return allowJavaSerializedObject;
+ }
+
+ @Override
+ public void setAllowJavaSerializedObject(boolean allowJavaSerializedObject) {
+ this.allowJavaSerializedObject = allowJavaSerializedObject;
+ }
+
+ @Override
+ public HeaderFilterStrategy getHeaderFilterStrategy() {
+ return headerFilterStrategy;
+ }
+
+ @Override
+ public void setHeaderFilterStrategy(HeaderFilterStrategy headerFilterStrategy) {
+ this.headerFilterStrategy = headerFilterStrategy;
+ }
+
+ @Override
+ public boolean isMapHttpMessageBody() {
+ return mapHttpMessageBody;
+ }
+
+ @Override
+ public void setMapHttpMessageBody(boolean mapHttpMessageBody) {
+ this.mapHttpMessageBody = mapHttpMessageBody;
+ }
+
+ @Override
+ public boolean isMapHttpMessageHeaders() {
+ return mapHttpMessageHeaders;
+ }
+
+ @Override
+ public void setMapHttpMessageHeaders(boolean mapHttpMessageHeaders) {
+ this.mapHttpMessageHeaders = mapHttpMessageHeaders;
+ }
+
+ @Override
+ public boolean isMapHttpMessageFormUrlEncodedBody() {
+ return mapHttpMessageFormUrlEncodedBody;
+ }
+
+ @Override
+ public void setMapHttpMessageFormUrlEncodedBody(boolean mapHttpMessageFormUrlEncodedBody) {
+ this.mapHttpMessageFormUrlEncodedBody = mapHttpMessageFormUrlEncodedBody;
+ }
+
+ @Override
+ public String getFileNameExtWhitelist() {
+ return fileNameExtWhitelist;
+ }
+
+ @Override
+ public void setFileNameExtWhitelist(String fileNameExtWhitelist) {
+ this.fileNameExtWhitelist = fileNameExtWhitelist;
+ }
+
+ protected static SimpleDateFormat getHttpDateFormat() {
+ SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT, Locale.US);
+ dateFormat.setTimeZone(TIME_ZONE_GMT);
+ return dateFormat;
+ }
+
+ protected static String toHttpDate(Date date) {
+ SimpleDateFormat format = getHttpDateFormat();
+ return format.format(date);
+ }
+
+ protected static String toHttpLanguage(Locale locale) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(locale.getLanguage());
+ if (locale.getCountry() != null) {
+ // Locale.toString() will use a "_" separator instead,
+ // while '-' is expected in headers such as Content-Language, etc:
+ // http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.10
+ sb.append('-').append(locale.getCountry());
+ }
+ return sb.toString();
+ }
+}
\ No newline at end of file
diff --git a/components/camel-http-common/src/main/java/org/apache/camel/http/common/DefaultHttpRegistry.java b/components/camel-http-common/src/main/java/org/apache/camel/http/common/DefaultHttpRegistry.java
new file mode 100644
index 000000000..8b9575fd2
--- /dev/null
+++ b/components/camel-http-common/src/main/java/org/apache/camel/http/common/DefaultHttpRegistry.java
@@ -0,0 +1,158 @@
+/*
+ * 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.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import javax.servlet.Servlet;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DefaultHttpRegistry implements HttpRegistry {
+ private static final Logger LOG = LoggerFactory.getLogger(DefaultHttpRegistry.class);
+
+ private static final Map registries = new ConcurrentHashMap<>();
+
+ private final Lock lock = new ReentrantLock();
+
+ private final Set consumers;
+ private final Set providers;
+
+ public DefaultHttpRegistry() {
+ consumers = new HashSet<>();
+ providers = new HashSet<>();
+ }
+
+ /**
+ * Lookup or create a new registry if none exists with the given name
+ */
+ public static HttpRegistry getHttpRegistry(String name) {
+ return registries.computeIfAbsent(name, k -> new DefaultHttpRegistry());
+ }
+
+ /**
+ * Removes the http registry with the given name
+ */
+ public static void removeHttpRegistry(String name) {
+ registries.remove(name);
+ }
+
+ @Override
+ public void register(HttpConsumer consumer) {
+ lock.lock();
+ try {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Registering consumer for path {} providers present: {}", consumer.getPath(), providers.size());
+ }
+ consumers.add(consumer);
+ for (HttpRegistryProvider provider : providers) {
+ provider.connect(consumer);
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ @Override
+ public void unregister(HttpConsumer consumer) {
+ lock.lock();
+ try {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Unregistering consumer for path {}", consumer.getPath());
+ }
+ consumers.remove(consumer);
+ for (HttpRegistryProvider provider : providers) {
+ provider.disconnect(consumer);
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ @SuppressWarnings("rawtypes")
+ public void register(CamelServlet provider, Map properties) {
+ provider.setServletName((String) properties.get("servlet-name"));
+ register(provider);
+ }
+
+ @Override
+ public void register(HttpRegistryProvider provider) {
+ lock.lock();
+ try {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Registering CamelServlet with name {} consumers present: {}", provider.getServletName(),
+ consumers.size());
+ }
+ providers.add(provider);
+ for (HttpConsumer consumer : consumers) {
+ provider.connect(consumer);
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ @Override
+ public void unregister(HttpRegistryProvider provider) {
+ lock.lock();
+ try {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Unregistering CamelServlet with name {}", provider.getServletName());
+ }
+ providers.remove(provider);
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ @Override
+ public HttpRegistryProvider getCamelServlet(String servletName) {
+ lock.lock();
+ try {
+ for (HttpRegistryProvider provider : providers) {
+ if (provider.getServletName().equals(servletName)) {
+ return provider;
+ }
+ }
+ return null;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ public void setServlets(List servlets) {
+ lock.lock();
+ try {
+ providers.clear();
+ for (Servlet servlet : servlets) {
+ if (servlet instanceof HttpRegistryProvider httpRegistryProvider) {
+ providers.add(httpRegistryProvider);
+ }
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+
+}
\ No newline at end of file
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 000000000..ed5536db3
--- /dev/null
+++ b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpBinding.java
@@ -0,0 +1,263 @@
+/*
+ * 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.
+ *
+ * This is also used by the camel-jetty component in the JettyHttpConsumer 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, Message message);
+
+ /**
+ * Parses the body from servlet request
+ *
+ * @param request the request
+ * @param message the 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(HttpServletRequest request, Message message) throws IOException;
+
+ /**
+ * Writes the exchange to the servlet response.
+ *
+ * Default implementation will delegate to the following methods depending on the status of the exchange
+ *
+ *
doWriteResponse - processing returns a OUT message
+ *
doWriteFaultResponse - processing returns a fault message
+ *
doWriteResponse - processing returns an exception and status code 500
+ *
+ *
+ * @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 true if reader should be used
+ */
+ boolean isUseReaderForPayload();
+
+ /**
+ * Should the {@link jakarta.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 jakarta.servlet.http.HttpServletRequest#getInputStream()} will be exposed.
+ *
+ * Is default false.
+ *
+ * @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.
+ *
+ * This is by default turned off. If you enable this then be aware that Java will deserialize the incoming data from
+ * the request to Java and that can be a potential security risk.
+ */
+ boolean isTransferException();
+
+ /**
+ * If enabled and an Exchange failed processing on the consumer side the response's body won't contain the
+ * exception's stack trace.
+ */
+ boolean isMuteException();
+
+ /**
+ * If enabled and an Exchange failed processing on the consumer side the exception's stack trace will be logged when
+ * the exception stack trace is not sent in the response's body.
+ */
+ boolean isLogException();
+
+ /**
+ * Whether to allow java serialization when a request uses context-type=application/x-java-serialized-object
+ *
+ * This is by default turned off. If you enable this then be aware that Java will deserialize the incoming data from
+ * the request to Java and that can be a potential security risk.
+ */
+ boolean isAllowJavaSerializedObject();
+
+ /**
+ * 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 allow Exchange Body HTTP mapping
+ */
+ boolean isMapHttpMessageBody();
+
+ /**
+ * Whether to allow Exchange Headers HTTP mapping
+ */
+ boolean isMapHttpMessageHeaders();
+
+ /**
+ * Whether to allow Exchange Form URL Encoded Body HTTP mapping
+ */
+ boolean isMapHttpMessageFormUrlEncodedBody();
+
+ /**
+ * 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.
+ *
+ * This is by default turned off. If you enable this then be aware that Java will deserialize the incoming data from
+ * the request to Java and that can be a potential security risk.
+ */
+ void setTransferException(boolean transferException);
+
+ /**
+ * If enabled and an Exchange failed processing on the consumer side the response's body won't contain the
+ * exception's stack trace.
+ */
+ void setMuteException(boolean muteException);
+
+ /**
+ * If enabled and an Exchange failed processing on the consumer side the exception's stack trace will be logged when
+ * the exception stack trace is not sent in the response's body.
+ */
+ void setLogException(boolean logException);
+
+ /**
+ * Whether to allow java serialization when a request uses context-type=application/x-java-serialized-object
+ *
+ * This is by default turned off. If you enable this then be aware that Java will deserialize the incoming data from
+ * the request to Java and that can be a potential security risk.
+ *
+ * @param allowJavaSerializedObject true to allow serializing java objects
+ */
+ void setAllowJavaSerializedObject(boolean allowJavaSerializedObject);
+
+ /**
+ * Gets the header filter strategy
+ *
+ * @return the strategy
+ */
+ HeaderFilterStrategy getHeaderFilterStrategy();
+
+ /**
+ * Sets the header filter strategy to use.
+ *
+ * Will default use {@link org.apache.camel.http.base.HttpHeaderFilterStrategy}
+ *
+ * @param headerFilterStrategy the custom strategy
+ */
+ void setHeaderFilterStrategy(HeaderFilterStrategy headerFilterStrategy);
+
+ /**
+ * Whether to allow Exchange Body HTTP mapping
+ *
+ * This is by default turned on. If you disable this then be aware that the Exchange body won't be mapped to HTTP
+ */
+ void setMapHttpMessageBody(boolean mapHttpMessageBody);
+
+ /**
+ * Whether to allow Exchange Headers HTTP mapping
+ *
+ * This is by default turned on. If you disable this then be aware that the Exchange headers won't be mapped to HTTP
+ */
+ void setMapHttpMessageHeaders(boolean mapHttpMessageHeaders);
+
+ /**
+ * Whether to allow Exchange Form URL Encoded Body HTTP mapping
+ *
+ * This is by default turned on. If you disable this then be aware that the Exchange Form URL Encoded Body won't be
+ * mapped to HTTP
+ */
+ void setMapHttpMessageFormUrlEncodedBody(boolean mapHttpMessageFormUrlEncodedBody);
+
+ /**
+ * Whitelist of accepted filename extensions for accepting uploaded files.
+ *
+ * Multiple extensions can be separated by comma, such as txt,xml.
+ */
+ String getFileNameExtWhitelist();
+
+ /**
+ * Whitelist of accepted filename extensions for accepting uploaded files.
+ *
+ * Multiple extensions can be separated by comma, such as txt,xml.
+ */
+ void setFileNameExtWhitelist(String fileNameExtWhitelist);
+
+}
\ No newline at end of file
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 000000000..2e64299de
--- /dev/null
+++ b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpCommonComponent.java
@@ -0,0 +1,158 @@
+/*
+ * 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.util.Map;
+
+import org.apache.camel.spi.Metadata;
+import org.apache.camel.support.CamelContextHelper;
+import org.apache.camel.support.HeaderFilterStrategyComponent;
+
+public abstract class HttpCommonComponent extends HeaderFilterStrategyComponent {
+
+ @Metadata(label = "advanced",
+ description = "To use a custom HttpBinding to control the mapping between Camel message and HttpClient.")
+ protected HttpBinding httpBinding;
+ @Metadata(label = "advanced", description = "To use the shared HttpConfiguration as base configuration.")
+ protected HttpConfiguration httpConfiguration;
+ @Metadata(label = "consumer", defaultValue = "true",
+ description = "If enabled and an Exchange failed processing on the consumer side the response's body won't contain the exception's stack trace.")
+ protected boolean muteException = true;
+ @Metadata(label = "advanced",
+ description = "Whether to allow java serialization when a request uses context-type=application/x-java-serialized-object."
+ + " This is by default turned off. "
+ + " If you enable this then be aware that Java will deserialize the incoming data from the request to Java and that can be a potential security risk.")
+ protected boolean allowJavaSerializedObject;
+
+ protected HttpCommonComponent() {
+ }
+
+ /**
+ * Gets the parameter. This method doesn't resolve reference parameters in the registry.
+ *
+ * @param parameters the parameters
+ * @param key the key
+ * @param type the requested type to convert the value from the parameter
+ * @return the converted value parameter
+ */
+ public T getParameter(Map parameters, String key, Class type) {
+ return getParameter(parameters, key, type, null);
+ }
+
+ /**
+ * Gets the parameter. This method doesn't resolve reference parameters in the registry.
+ *
+ * @param parameters the parameters
+ * @param key the key
+ * @param type the requested type to convert the value from the parameter
+ * @param defaultValue use this default value if the parameter does not contain the key
+ * @return the converted value parameter
+ */
+ public T getParameter(Map parameters, String key, Class type, T defaultValue) {
+ Object value = parameters.get(key);
+ if (value == null) {
+ value = defaultValue;
+ }
+ if (value == null) {
+ return null;
+ }
+
+ return CamelContextHelper.convertTo(getCamelContext(), type, value);
+ }
+
+ /**
+ * 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 {
+ }
+
+ /**
+ * Checks whether the consumer is possible to connect to the endoint.
+ *
+ * @param consumer the consumer
+ * @throws Exception can be thrown
+ */
+ public boolean canConnect(HttpConsumer consumer) throws Exception {
+ return true;
+ }
+
+ @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;
+ }
+
+ public boolean isMuteException() {
+ return muteException;
+ }
+
+ /**
+ * If enabled and an Exchange failed processing on the consumer side the response's body won't contain the
+ * exception's stack trace.
+ */
+ public void setMuteException(boolean muteException) {
+ this.muteException = muteException;
+ }
+
+ public boolean isAllowJavaSerializedObject() {
+ return allowJavaSerializedObject;
+ }
+
+ /**
+ * Whether to allow java serialization when a request uses context-type=application/x-java-serialized-object.
+ *
+ * This is by default turned off. If you enable this then be aware that Java will deserialize the incoming data from
+ * the request to Java and that can be a potential security risk.
+ */
+ public void setAllowJavaSerializedObject(boolean allowJavaSerializedObject) {
+ this.allowJavaSerializedObject = allowJavaSerializedObject;
+ }
+
+}
\ No newline at end of file
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 000000000..673f9bcd2
--- /dev/null
+++ b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpCommonEndpoint.java
@@ -0,0 +1,897 @@
+/*
+ * 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.util.Map;
+
+import org.apache.camel.cloud.DiscoverableService;
+import org.apache.camel.cloud.ServiceDefinition;
+import org.apache.camel.http.base.cookie.CookieHandler;
+import org.apache.camel.spi.EndpointServiceLocation;
+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.apache.camel.support.DefaultEndpoint;
+import org.apache.camel.util.CollectionHelper;
+
+public abstract class HttpCommonEndpoint extends DefaultEndpoint
+ implements HeaderFilterStrategyAware, DiscoverableService, EndpointServiceLocation {
+
+ // Note: all options must be documented with description in annotations so extended components can access the documentation
+
+ HttpCommonComponent component;
+
+ @UriPath(label = "common", description = "The url of the HTTP endpoint to call.")
+ @Metadata(required = true)
+ URI httpUri;
+ @UriParam(label = "common,advanced",
+ description = "To use a custom HeaderFilterStrategy to filter header to and from Camel message.")
+ HeaderFilterStrategy headerFilterStrategy = new org.apache.camel.http.base.HttpHeaderFilterStrategy();
+ @UriParam(label = "common,advanced",
+ description = "To use a custom HttpBinding to control the mapping between Camel message and HttpClient.")
+ HttpBinding httpBinding;
+ @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 = "producer,advanced",
+ description = "If the option is true, HttpProducer will set the Host header to the value contained in the current exchange Host header, "
+ + "useful in reverse proxy applications where you want the Host header received by the downstream server to reflect the URL called by the upstream client, "
+ + "this allows applications which use the Host header to generate accurate URL's for a proxied service")
+ boolean preserveHostHeader;
+ @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 the Servlet will disable the HTTP streaming and set the content-length header on the response")
+ boolean chunked = true;
+ @UriParam(label = "common",
+ description = "Determines whether or not the raw input stream is cached or not."
+ + " The Camel consumer (camel-servlet, camel-jetty etc.) will by default cache the 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 Servlet 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."
+ + " The producer (camel-http) will by default cache the response body stream. If setting this option to true,"
+ + " then the producers will not cache the response body stream but use the response stream as-is (the stream can only be read once) as the message body.")
+ boolean disableStreamCache;
+ @UriParam(label = "common",
+ description = "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."
+ + " 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."
+ + " This is by default turned off. If you enable this then be aware that Java will deserialize the incoming"
+ + " data from the request to Java and that can be a potential security risk.")
+ boolean transferException;
+ @UriParam(label = "consumer",
+ description = "If enabled and an Exchange failed processing on the consumer side the response's body won't contain the exception's stack trace.")
+ boolean muteException;
+ @UriParam(label = "consumer",
+ description = "If enabled and an Exchange failed processing on the consumer side the exception's stack trace will be logged"
+ + " when the exception stack trace is not sent in the response's body.")
+ boolean logException;
+ @UriParam(label = "producer", defaultValue = "false",
+ description = "Specifies whether a Connection Close header must be added to HTTP Request. By default connectionClose is false.")
+ boolean connectionClose;
+ @UriParam(label = "consumer,advanced",
+ description = "Specifies whether to enable HTTP TRACE for this Servlet consumer. By default TRACE is turned off.")
+ boolean traceEnabled;
+ @UriParam(label = "consumer,advanced",
+ description = "Specifies whether to enable HTTP OPTIONS for this Servlet consumer. By default OPTIONS is turned off.")
+ boolean optionsEnabled;
+ @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 jakarta.servlet.ServletResponse.")
+ Integer responseBufferSize;
+ @UriParam(label = "producer,advanced",
+ description = "If this option is true, The http producer won't read response body and cache the input stream")
+ boolean ignoreResponseBody;
+ @UriParam(label = "producer,advanced", 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,advanced",
+ 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;
+ @UriParam(label = "consumer,advanced", defaultValue = "true",
+ description = "If this option is true then IN exchange Body of the exchange will be mapped to HTTP body."
+ + " Setting this to false will avoid the HTTP mapping.")
+ boolean mapHttpMessageBody = true;
+ @UriParam(label = "consumer,advanced", defaultValue = "true",
+ description = "If this option is true then IN exchange Headers of the exchange will be mapped to HTTP headers."
+ + " Setting this to false will avoid the HTTP Headers mapping.")
+ boolean mapHttpMessageHeaders = true;
+ @UriParam(label = "consumer,advanced", defaultValue = "true",
+ description = "If this option is true then IN exchange Form Encoded body of the exchange will be mapped to HTTP."
+ + " Setting this to false will avoid the HTTP Form Encoded body mapping.")
+ boolean mapHttpMessageFormUrlEncodedBody = true;
+ @UriParam(label = "producer,advanced", defaultValue = "200-299",
+ description = "The status codes which are considered a success response. The values are inclusive. Multiple ranges can be"
+ + " defined, separated by comma, e.g. 200-204,209,301-304. Each range must be a single number or from-to with the dash included.")
+ private String okStatusCodeRange = "200-299";
+ @UriParam(label = "consumer", defaultValue = "false",
+ description = "Configure the consumer to work in async mode")
+ private boolean async;
+ @UriParam(label = "producer,advanced", description = "Configure a cookie handler to maintain a HTTP session")
+ private CookieHandler cookieHandler;
+ @UriParam(label = "producer",
+ description = "Configure the HTTP method to use. The HttpMethod header cannot override this option if set.")
+ private HttpMethods httpMethod;
+
+ @UriParam(label = "producer,security",
+ description = "Authentication methods allowed to use as a comma separated list of values Basic, Digest or NTLM.")
+ private String authMethod;
+ @UriParam(label = "producer,security", enums = "Basic,Digest,NTLM",
+ description = "Which authentication method to prioritize to use, either as Basic, Digest or NTLM.")
+ private String authMethodPriority;
+ @UriParam(label = "producer,security", secret = true, description = "Authentication username")
+ private String authUsername;
+ @UriParam(label = "producer,security", secret = true, description = "Authentication password")
+ private String authPassword;
+ @UriParam(label = "producer,security", secret = true, description = "OAuth2 client id")
+ private String oauth2ClientId;
+ @UriParam(label = "producer,security", secret = true, description = "OAuth2 client secret")
+ private String oauth2ClientSecret;
+ @UriParam(label = "producer,security", description = "OAuth2 Token endpoint")
+ private String oauth2TokenEndpoint;
+ @UriParam(label = "producer,security", description = "OAuth2 scope")
+ private String oauth2Scope;
+ @UriParam(label = "producer,security", defaultValue = "false",
+ description = "Whether to cache OAuth2 client tokens.")
+ private boolean oauth2CacheTokens = false;
+ @UriParam(label = "producer,security", defaultValue = "3600",
+ description = "Default expiration time for cached OAuth2 tokens, in seconds. Used if token response does not contain 'expires_in' field.")
+ private long oauth2CachedTokensDefaultExpirySeconds = 3600L;
+ @UriParam(label = "producer,security", defaultValue = "5",
+ description = "Amount of time which is deducted from OAuth2 tokens expiry time to compensate for the time it takes OAuth2 Token Endpoint to send the token over http, in seconds. "
+ +
+ "Set this parameter to high value if you OAuth2 Token Endpoint answers slowly or you tokens expire quickly. "
+ +
+ "If you set this parameter to too small value, you can get 4xx http errors because camel will think that the received token is still valid, while in reality the token is expired for the Authentication server.")
+ private long oauth2CachedTokensExpirationMarginSeconds = 5L;
+ @UriParam(label = "producer,security", description = "Authentication domain to use with NTML")
+ private String authDomain;
+ @UriParam(label = "producer,security", description = "Authentication host to use with NTML")
+ private String authHost;
+ @UriParam(label = "producer,proxy", description = "Proxy hostname to use")
+ private String proxyHost;
+ @UriParam(label = "producer,proxy", description = "Proxy port to use")
+ private int proxyPort;
+ @UriParam(label = "producer,proxy", enums = "http,https", description = "Proxy authentication scheme to use")
+ private String proxyAuthScheme;
+ @UriParam(label = "producer,proxy", enums = "Basic,Digest,NTLM", description = "Proxy authentication method to use")
+ private String proxyAuthMethod;
+ @UriParam(label = "producer,proxy", secret = true, description = "Proxy authentication username")
+ private String proxyAuthUsername;
+ @UriParam(label = "producer,proxy", secret = true, description = "Proxy authentication password")
+ private String proxyAuthPassword;
+ @UriParam(label = "producer,proxy", description = "Proxy authentication host")
+ private String proxyAuthHost;
+ @UriParam(label = "producer,proxy", description = "Proxy authentication port")
+ private int proxyAuthPort;
+ @UriParam(label = "producer,proxy", description = "Proxy authentication domain to use with NTML")
+ private String proxyAuthDomain;
+ @UriParam(label = "producer,proxy", description = "Proxy authentication domain (workstation name) to use with NTML")
+ private String proxyAuthNtHost;
+
+ protected HttpCommonEndpoint() {
+ }
+
+ protected HttpCommonEndpoint(String endPointURI, HttpCommonComponent component, URI httpURI) {
+ super(endPointURI, component);
+ this.component = component;
+ this.httpUri = httpURI;
+ }
+
+ @Override
+ public String getServiceUrl() {
+ if (httpUri != null) {
+ return httpUri.toString();
+ }
+ return null;
+ }
+
+ @Override
+ public String getServiceProtocol() {
+ if (httpUri != null) {
+ return httpUri.getScheme();
+ }
+ return null;
+ }
+
+ public void connect(HttpConsumer consumer) throws Exception {
+ component.connect(consumer);
+ }
+
+ public void disconnect(HttpConsumer consumer) throws Exception {
+ component.disconnect(consumer);
+ }
+
+ public boolean canConnect(HttpConsumer consumer) throws Exception {
+ return component.canConnect(consumer);
+ }
+
+ @Override
+ public HttpCommonComponent getComponent() {
+ return (HttpCommonComponent) super.getComponent();
+ }
+
+ @Override
+ public boolean isLenientProperties() {
+ // true to allow dynamic URI options to be configured and passed to external system for eg. the HttpProducer
+ return true;
+ }
+
+ // Service Registration
+ //-------------------------------------------------------------------------
+
+ @Override
+ public Map getServiceProperties() {
+ return CollectionHelper.immutableMapOf(
+ ServiceDefinition.SERVICE_META_PORT, Integer.toString(getPort()),
+ ServiceDefinition.SERVICE_META_PATH, getPath(),
+ ServiceDefinition.SERVICE_META_PROTOCOL, getProtocol());
+ }
+
+ // Properties
+ //-------------------------------------------------------------------------
+
+ /**
+ * @deprecated use {@link #getHttpBinding()}
+ */
+ @Deprecated
+ public HttpBinding getBinding() {
+ return httpBinding;
+ }
+
+ public HttpBinding getHttpBinding() {
+ if (httpBinding == null) {
+ // create a new binding and use the options from this endpoint
+ httpBinding = new DefaultHttpBinding();
+ httpBinding.setHeaderFilterStrategy(getHeaderFilterStrategy());
+ httpBinding.setTransferException(isTransferException());
+ httpBinding.setMuteException(isMuteException());
+ if (getComponent() != null) {
+ httpBinding.setAllowJavaSerializedObject(getComponent().isAllowJavaSerializedObject());
+ }
+ httpBinding.setEagerCheckContentAvailable(isEagerCheckContentAvailable());
+ httpBinding.setMapHttpMessageBody(isMapHttpMessageBody());
+ httpBinding.setMapHttpMessageHeaders(isMapHttpMessageHeaders());
+ httpBinding.setMapHttpMessageFormUrlEncodedBody(isMapHttpMessageFormUrlEncodedBody());
+ }
+ 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 String getPath() {
+ //if the path is empty, we just return the default path here
+ return httpUri.getPath().isEmpty() ? "/" : 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;
+ }
+
+ @Override
+ public HeaderFilterStrategy getHeaderFilterStrategy() {
+ return headerFilterStrategy;
+ }
+
+ /**
+ * To use a custom HeaderFilterStrategy to filter header to and from Camel message.
+ */
+ @Override
+ 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 isPreserveHostHeader() {
+ return preserveHostHeader;
+ }
+
+ /**
+ * If the option is true, HttpProducer will set the Host header to the value contained in the current exchange Host
+ * header, useful in reverse proxy applications where you want the Host header received by the downstream server to
+ * reflect the URL called by the upstream client, this allows applications which use the Host header to generate
+ * accurate URL's for a proxied service
+ */
+ public void setPreserveHostHeader(boolean preserveHostHeader) {
+ this.preserveHostHeader = preserveHostHeader;
+ }
+
+ 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.
+ *
+ * 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 Servlet 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 Servlet 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 Servlet 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. + The http producer will by default cache the response body stream. If
+ * setting this option to true, + then the producers will not cache the response body stream but use the response
+ * stream as-is as the message body.
+ */
+ public void setDisableStreamCache(boolean disable) {
+ this.disableStreamCache = disable;
+ }
+
+ public boolean isChunked() {
+ return this.chunked;
+ }
+
+ /**
+ * If this option is false Servlet will disable the HTTP streaming and set the content-length header on the response
+ */
+ public void setChunked(boolean chunked) {
+ this.chunked = chunked;
+ }
+
+ public boolean isTransferException() {
+ return transferException;
+ }
+
+ public boolean isMuteException() {
+ return muteException;
+ }
+
+ public boolean isLogException() {
+ return logException;
+ }
+
+ public boolean isConnectionClose() {
+ return connectionClose;
+ }
+
+ /**
+ * If this option is true, the producer will add a Connection Close header to HTTP Request
+ */
+ public void setConnectionClose(boolean connectionClose) {
+ this.connectionClose = connectionClose;
+ }
+
+ /**
+ * 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. 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.
+ *
+ * This is by default turned off. If you enable this then be aware that Java will deserialize the incoming data from
+ * the request to Java and that can be a potential security risk.
+ */
+ public void setTransferException(boolean transferException) {
+ this.transferException = transferException;
+ }
+
+ /**
+ * If enabled and an Exchange failed processing on the consumer side the response's body won't contain the
+ * exception's stack trace.
+ */
+ public void setMuteException(boolean muteException) {
+ this.muteException = muteException;
+ }
+
+ /**
+ * If enabled and an Exchange failed processing on the consumer side the exception's stack trace will be logged when
+ * the exception stack trace is not sent in the response's body.
+ */
+ public void setLogException(boolean logException) {
+ this.logException = logException;
+ }
+
+ public boolean isTraceEnabled() {
+ return this.traceEnabled;
+ }
+
+ /**
+ * Specifies whether to enable HTTP TRACE for this Servlet consumer. By default TRACE is turned off.
+ */
+ public void setTraceEnabled(boolean traceEnabled) {
+ this.traceEnabled = traceEnabled;
+ }
+
+ public boolean isOptionsEnabled() {
+ return optionsEnabled;
+ }
+
+ /**
+ * Specifies whether to enable HTTP OPTIONS for this Servlet consumer. By default OPTIONS is turned off.
+ */
+ public void setOptionsEnabled(boolean optionsEnabled) {
+ this.optionsEnabled = optionsEnabled;
+ }
+
+ 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 Integer getResponseBufferSize() {
+ return responseBufferSize;
+ }
+
+ /**
+ * To use a custom buffer size on the jakarta.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;
+ }
+
+ public String getOkStatusCodeRange() {
+ return okStatusCodeRange;
+ }
+
+ /**
+ * The status codes which are considered a success response. The values are inclusive. Multiple ranges can be
+ * defined, separated by comma, e.g. 200-204,209,301-304. Each range must be a single number or from-to
+ * with the dash included.
+ *
+ * The default range is 200-299
+ */
+ public void setOkStatusCodeRange(String okStatusCodeRange) {
+ this.okStatusCodeRange = okStatusCodeRange;
+ }
+
+ public boolean isMapHttpMessageBody() {
+ return mapHttpMessageBody;
+ }
+
+ /**
+ * If this option is true, the IN exchange body will be mapped to HTTP
+ */
+ public void setMapHttpMessageBody(boolean mapHttpMessageBody) {
+ this.mapHttpMessageBody = mapHttpMessageBody;
+ }
+
+ public boolean isMapHttpMessageHeaders() {
+ return mapHttpMessageHeaders;
+ }
+
+ /**
+ * If this option is true, the IN exchange headers will be mapped to HTTP Headers
+ */
+ public void setMapHttpMessageHeaders(boolean mapHttpMessageHeaders) {
+ this.mapHttpMessageHeaders = mapHttpMessageHeaders;
+ }
+
+ public boolean isMapHttpMessageFormUrlEncodedBody() {
+ return mapHttpMessageFormUrlEncodedBody;
+ }
+
+ /**
+ * If this option is true then IN exchange Form Encoded body will be mapped to HTTP
+ */
+ public void setMapHttpMessageFormUrlEncodedBody(boolean mapHttpMessageFormUrlEncodedBody) {
+ this.mapHttpMessageFormUrlEncodedBody = mapHttpMessageFormUrlEncodedBody;
+ }
+
+ public boolean isAsync() {
+ return async;
+ }
+
+ /**
+ * If this option is true, the consumer will work in async mode
+ */
+ public void setAsync(boolean async) {
+ this.async = async;
+ }
+
+ public CookieHandler getCookieHandler() {
+ return cookieHandler;
+ }
+
+ /**
+ * Configure a cookie handler to maintain a HTTP session
+ */
+ public void setCookieHandler(CookieHandler cookieHandler) {
+ this.cookieHandler = cookieHandler;
+ }
+
+ public HttpMethods getHttpMethod() {
+ return httpMethod;
+ }
+
+ /**
+ * Configure the HTTP method to use. The HttpMethod header cannot override this option if set.
+ */
+ public void setHttpMethod(HttpMethods httpMethod) {
+ this.httpMethod = httpMethod;
+ }
+
+ public String getAuthMethod() {
+ return authMethod;
+ }
+
+ /**
+ * Authentication methods allowed to use as a comma separated list of values Basic, Digest or NTLM.
+ */
+ public void setAuthMethod(String authMethod) {
+ this.authMethod = authMethod;
+ }
+
+ public String getAuthMethodPriority() {
+ return authMethodPriority;
+ }
+
+ /**
+ * Which authentication method to prioritize to use, either as Basic, Digest or NTLM.
+ */
+ public void setAuthMethodPriority(String authMethodPriority) {
+ this.authMethodPriority = authMethodPriority;
+ }
+
+ public String getAuthUsername() {
+ return authUsername;
+ }
+
+ /**
+ * Authentication username
+ */
+ public void setAuthUsername(String authUsername) {
+ this.authUsername = authUsername;
+ }
+
+ public String getAuthPassword() {
+ return authPassword;
+ }
+
+ /**
+ * Authentication password
+ */
+ public void setAuthPassword(String authPassword) {
+ this.authPassword = authPassword;
+ }
+
+ public String getAuthDomain() {
+ return authDomain;
+ }
+
+ /**
+ * Authentication domain to use with NTML
+ */
+ public void setAuthDomain(String authDomain) {
+ this.authDomain = authDomain;
+ }
+
+ public String getAuthHost() {
+ return authHost;
+ }
+
+ /**
+ * Authentication host to use with NTML
+ */
+ public void setAuthHost(String authHost) {
+ this.authHost = authHost;
+ }
+
+ public String getProxyAuthScheme() {
+ return proxyAuthScheme;
+ }
+
+ /**
+ * Proxy authentication scheme to use
+ */
+ public void setProxyAuthScheme(String proxyAuthScheme) {
+ this.proxyAuthScheme = proxyAuthScheme;
+ }
+
+ public String getProxyAuthMethod() {
+ return proxyAuthMethod;
+ }
+
+ /**
+ * Proxy authentication method to use
+ */
+ public void setProxyAuthMethod(String proxyAuthMethod) {
+ this.proxyAuthMethod = proxyAuthMethod;
+ }
+
+ public String getProxyAuthUsername() {
+ return proxyAuthUsername;
+ }
+
+ /**
+ * Proxy authentication username
+ */
+ public void setProxyAuthUsername(String proxyAuthUsername) {
+ this.proxyAuthUsername = proxyAuthUsername;
+ }
+
+ public String getProxyAuthPassword() {
+ return proxyAuthPassword;
+ }
+
+ /**
+ * Proxy authentication password
+ */
+ public void setProxyAuthPassword(String proxyAuthPassword) {
+ this.proxyAuthPassword = proxyAuthPassword;
+ }
+
+ public String getProxyAuthDomain() {
+ return proxyAuthDomain;
+ }
+
+ /**
+ * Proxy authentication domain to use with NTML
+ */
+ public void setProxyAuthDomain(String proxyAuthDomain) {
+ this.proxyAuthDomain = proxyAuthDomain;
+ }
+
+ public String getProxyAuthHost() {
+ return proxyAuthHost;
+ }
+
+ /**
+ * Proxy authentication host to use with NTML
+ */
+ public void setProxyAuthHost(String proxyAuthHost) {
+ this.proxyAuthHost = proxyAuthHost;
+ }
+
+ public int getProxyAuthPort() {
+ return proxyAuthPort;
+ }
+
+ /**
+ * Proxy authentication port
+ */
+ public void setProxyAuthPort(int proxyAuthPort) {
+ this.proxyAuthPort = proxyAuthPort;
+ }
+
+ public String getProxyHost() {
+ return proxyHost;
+ }
+
+ /**
+ * Proxy hostname to use
+ */
+ public void setProxyHost(String proxyHost) {
+ this.proxyHost = proxyHost;
+ }
+
+ public int getProxyPort() {
+ return proxyPort;
+ }
+
+ /**
+ * Proxy port to use
+ */
+ public void setProxyPort(int proxyPort) {
+ this.proxyPort = proxyPort;
+ }
+
+ public String getProxyAuthNtHost() {
+ return proxyAuthNtHost;
+ }
+
+ /**
+ * Proxy authentication domain (workstation name) to use with NTML
+ */
+ public void setProxyAuthNtHost(String proxyAuthNtHost) {
+ this.proxyAuthNtHost = proxyAuthNtHost;
+ }
+
+ public String getOauth2ClientId() {
+ return this.oauth2ClientId;
+ }
+
+ /**
+ * OAuth2 Client id
+ */
+ public void setOauth2ClientId(String oauth2ClientId) {
+ this.oauth2ClientId = oauth2ClientId;
+ }
+
+ public String getOauth2ClientSecret() {
+ return this.oauth2ClientSecret;
+ }
+
+ /**
+ * OAuth2 Client secret
+ */
+ public void setOauth2ClientSecret(String oauth2ClientSecret) {
+ this.oauth2ClientSecret = oauth2ClientSecret;
+ }
+
+ public String getOauth2TokenEndpoint() {
+ return this.oauth2TokenEndpoint;
+ }
+
+ /**
+ * OAuth2 token endpoint
+ */
+ public void setOauth2TokenEndpoint(String oauth2TokenEndpoint) {
+ this.oauth2TokenEndpoint = oauth2TokenEndpoint;
+ }
+
+ public String getOauth2Scope() {
+ return oauth2Scope;
+ }
+
+ /**
+ * OAuth2 scope
+ */
+ public void setOauth2Scope(String oauth2Scope) {
+ this.oauth2Scope = oauth2Scope;
+ }
+
+ public boolean isOauth2CacheTokens() {
+ return oauth2CacheTokens;
+ }
+
+ /**
+ * Whether to cache OAuth2 client tokens.
+ */
+ public void setOauth2CacheTokens(boolean oauth2CacheTokens) {
+ this.oauth2CacheTokens = oauth2CacheTokens;
+ }
+
+ public long getOauth2CachedTokensDefaultExpirySeconds() {
+ return oauth2CachedTokensDefaultExpirySeconds;
+ }
+
+ /**
+ * Default expiration time for cached OAuth2 tokens, in seconds. Used if token response does not contain
+ * 'expires_in' field.
+ */
+ public void setOauth2CachedTokensDefaultExpirySeconds(long oauth2CachedTokensDefaultExpirySeconds) {
+ this.oauth2CachedTokensDefaultExpirySeconds = oauth2CachedTokensDefaultExpirySeconds;
+ }
+
+ public long getOauth2CachedTokensExpirationMarginSeconds() {
+ return oauth2CachedTokensExpirationMarginSeconds;
+ }
+
+ /**
+ * Amount of time which is deducted from OAuth2 tokens expiry time to compensate for the time it takes OAuth2 Token
+ * Endpoint to send the token over http, in seconds. Set this parameter to high value if you OAuth2 Token Endpoint
+ * answers slowly or you tokens expire quickly. If you set this parameter to too small value, you can get 4xx http
+ * errors because camel will think that the received token is still valid, while in reality the token is expired for
+ * the Authentication server.
+ */
+ public void setOauth2CachedTokensExpirationMarginSeconds(long cachedTokensExpirationMarginSeconds) {
+ this.oauth2CachedTokensExpirationMarginSeconds = cachedTokensExpirationMarginSeconds;
+ }
+}
\ No newline at end of file
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 000000000..4caacac4a
--- /dev/null
+++ b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpConfiguration.java
@@ -0,0 +1,327 @@
+/*
+ * 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;
+
+import org.apache.camel.spi.Metadata;
+import org.apache.camel.spi.UriParam;
+
+public class HttpConfiguration implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ @Metadata(label = "producer,security",
+ description = "Authentication methods allowed to use as a comma separated list of values Basic, Digest or NTLM.")
+ private String authMethod;
+ @Metadata(label = "producer,security", enums = "Basic,Digest,NTLM",
+ description = "Which authentication method to prioritize to use, either as Basic, Digest or NTLM.")
+ private String authMethodPriority;
+ @Metadata(label = "producer,security", secret = true, description = "Authentication username")
+ private String authUsername;
+ @Metadata(label = "producer,security", secret = true, description = "Authentication password")
+ private String authPassword;
+ @Metadata(label = "producer,security", secret = true, description = "OAuth2 client id")
+ private String oauth2ClientId;
+ @Metadata(label = "producer,security", secret = true, description = "OAuth2 client secret")
+ private String oauth2ClientSecret;
+ @Metadata(label = "producer,security", description = "OAuth2 token endpoint")
+ private String oauth2TokenEndpoint;
+ @Metadata(label = "producer,security", description = "OAuth2 scope")
+ private String oauth2Scope;
+ @UriParam(label = "producer,security", defaultValue = "false",
+ description = "Whether to cache OAuth2 client tokens.")
+ private boolean oauth2CacheTokens = false;
+ @UriParam(label = "producer,security", defaultValue = "3600",
+ description = "Default expiration time for cached OAuth2 tokens, in seconds. Used if token response does not contain 'expires_in' field.")
+ private long oauth2CachedTokensDefaultExpirySeconds = 3600L;
+ @UriParam(label = "producer,security", defaultValue = "5",
+ description = "Amount of time which is deducted from OAuth2 tokens expiry time to compensate for the time it takes OAuth2 Token Endpoint to send the token over http, in seconds. "
+ +
+ "Set this parameter to high value if you OAuth2 Token Endpoint answers slowly or you tokens expire quickly. "
+ +
+ "If you set this parameter to too small value, you can get 4xx http errors because camel will think that the received token is still valid, while in reality the token is expired for the Authentication server.")
+ private long oauth2CachedTokensExpirationMarginSeconds = 5L;
+ @Metadata(label = "producer,security", description = "Authentication domain to use with NTML")
+ private String authDomain;
+ @Metadata(label = "producer,security", description = "Authentication host to use with NTML")
+ private String authHost;
+ @Metadata(label = "producer,proxy", description = "Proxy hostname to use")
+ private String proxyHost;
+ @Metadata(label = "producer,proxy", description = "Proxy port to use")
+ private int proxyPort;
+ @Metadata(label = "producer,proxy", enums = "http,https", description = "Authentication scheme to use")
+ private String proxyAuthScheme;
+ @Metadata(label = "producer,proxy", enums = "Basic,Digest,NTLM", description = "Proxy authentication method to use")
+ private String proxyAuthMethod;
+ @Metadata(label = "producer,proxy", secret = true, description = "Proxy authentication username")
+ private String proxyAuthUsername;
+ @Metadata(label = "producer,proxy", secret = true, description = "Proxy authentication password")
+ private String proxyAuthPassword;
+ @Metadata(label = "producer,proxy", description = "Proxy authentication host")
+ private String proxyAuthHost;
+ @Metadata(label = "producer,proxy", description = "Proxy authentication port")
+ private int proxyAuthPort;
+ @Metadata(label = "producer,proxy", description = "Proxy authentication domain to use with NTML")
+ private String proxyAuthDomain;
+
+ public String getAuthMethod() {
+ return authMethod;
+ }
+
+ /**
+ * Authentication methods allowed to use as a comma separated list of values Basic, Digest or NTLM.
+ */
+ public void setAuthMethod(String authMethod) {
+ this.authMethod = authMethod;
+ }
+
+ public String getAuthMethodPriority() {
+ return authMethodPriority;
+ }
+
+ /**
+ * Which authentication method to prioritize to use, either as Basic, Digest or NTLM.
+ */
+ public void setAuthMethodPriority(String authMethodPriority) {
+ this.authMethodPriority = authMethodPriority;
+ }
+
+ public String getAuthUsername() {
+ return authUsername;
+ }
+
+ /**
+ * Authentication username
+ */
+ public void setAuthUsername(String authUsername) {
+ this.authUsername = authUsername;
+ }
+
+ public String getAuthPassword() {
+ return authPassword;
+ }
+
+ /**
+ * Authentication password
+ */
+ public void setAuthPassword(String authPassword) {
+ this.authPassword = authPassword;
+ }
+
+ public String getAuthDomain() {
+ return authDomain;
+ }
+
+ /**
+ * Authentication domain to use with NTML
+ */
+ public void setAuthDomain(String authDomain) {
+ this.authDomain = authDomain;
+ }
+
+ public String getAuthHost() {
+ return authHost;
+ }
+
+ /**
+ * Authentication host to use with NTML
+ */
+ public void setAuthHost(String authHost) {
+ this.authHost = authHost;
+ }
+
+ public String getProxyAuthScheme() {
+ return proxyAuthScheme;
+ }
+
+ /**
+ * Proxy authentication scheme to use
+ */
+ public void setProxyAuthScheme(String proxyAuthScheme) {
+ this.proxyAuthScheme = proxyAuthScheme;
+ }
+
+ public String getProxyAuthMethod() {
+ return proxyAuthMethod;
+ }
+
+ /**
+ * Proxy authentication method to use
+ */
+ public void setProxyAuthMethod(String proxyAuthMethod) {
+ this.proxyAuthMethod = proxyAuthMethod;
+ }
+
+ public String getProxyAuthUsername() {
+ return proxyAuthUsername;
+ }
+
+ /**
+ * Proxy authentication username
+ */
+ public void setProxyAuthUsername(String proxyAuthUsername) {
+ this.proxyAuthUsername = proxyAuthUsername;
+ }
+
+ public String getProxyAuthPassword() {
+ return proxyAuthPassword;
+ }
+
+ /**
+ * Proxy authentication password
+ */
+ public void setProxyAuthPassword(String proxyAuthPassword) {
+ this.proxyAuthPassword = proxyAuthPassword;
+ }
+
+ public String getProxyAuthDomain() {
+ return proxyAuthDomain;
+ }
+
+ /**
+ * Proxy authentication domain to use with NTML
+ */
+ public void setProxyAuthDomain(String proxyAuthDomain) {
+ this.proxyAuthDomain = proxyAuthDomain;
+ }
+
+ public String getProxyAuthHost() {
+ return proxyAuthHost;
+ }
+
+ /**
+ * Proxy authentication host
+ */
+ public void setProxyAuthHost(String proxyAuthHost) {
+ this.proxyAuthHost = proxyAuthHost;
+ }
+
+ public int getProxyAuthPort() {
+ return proxyAuthPort;
+ }
+
+ /**
+ * Proxy authentication port
+ */
+ public void setProxyAuthPort(int proxyAuthPort) {
+ this.proxyAuthPort = proxyAuthPort;
+ }
+
+ public String getProxyHost() {
+ return proxyHost;
+ }
+
+ /**
+ * Proxy hostname to use
+ */
+ public void setProxyHost(String proxyHost) {
+ this.proxyHost = proxyHost;
+ }
+
+ public int getProxyPort() {
+ return proxyPort;
+ }
+
+ /**
+ * Proxy port to use
+ */
+ public void setProxyPort(int proxyPort) {
+ this.proxyPort = proxyPort;
+ }
+
+ public String getOauth2ClientId() {
+ return this.oauth2ClientId;
+ }
+
+ /**
+ * OAuth2 Client id
+ */
+ public void setOauth2ClientId(String oauth2ClientId) {
+ this.oauth2ClientId = oauth2ClientId;
+ }
+
+ public String getOauth2ClientSecret() {
+ return this.oauth2ClientSecret;
+ }
+
+ /**
+ * OAuth2 Client secret
+ */
+ public void setOauth2ClientSecret(String oauth2ClientSecret) {
+ this.oauth2ClientSecret = oauth2ClientSecret;
+ }
+
+ public String getOauth2TokenEndpoint() {
+ return this.oauth2TokenEndpoint;
+ }
+
+ /**
+ * OAuth2 token endpoint
+ */
+ public void setOauth2TokenEndpoint(String oauth2TokenEndpoint) {
+ this.oauth2TokenEndpoint = oauth2TokenEndpoint;
+ }
+
+ public String getOauth2Scope() {
+ return oauth2Scope;
+ }
+
+ /**
+ * OAuth2 scope
+ */
+ public void setOauth2Scope(String oauth2Scope) {
+ this.oauth2Scope = oauth2Scope;
+ }
+
+ public boolean isOauth2CacheTokens() {
+ return oauth2CacheTokens;
+ }
+
+ /**
+ * Whether to cache OAuth2 client tokens.
+ */
+ public void setOauth2CacheTokens(boolean oauth2CacheTokens) {
+ this.oauth2CacheTokens = oauth2CacheTokens;
+ }
+
+ public long getOauth2CachedTokensDefaultExpirySeconds() {
+ return oauth2CachedTokensDefaultExpirySeconds;
+ }
+
+ /**
+ * Default expiration time for cached OAuth2 tokens, in seconds. Used if token response does not contain
+ * 'expires_in' field.
+ */
+ public void setOauth2CachedTokensDefaultExpirySeconds(long oauth2CachedTokensDefaultExpirySeconds) {
+ this.oauth2CachedTokensDefaultExpirySeconds = oauth2CachedTokensDefaultExpirySeconds;
+ }
+
+ public long getOauth2CachedTokensExpirationMarginSeconds() {
+ return oauth2CachedTokensExpirationMarginSeconds;
+ }
+
+ /**
+ * Amount of time which is deducted from OAuth2 tokens expiry time to compensate for the time it takes OAuth2 Token
+ * Endpoint to send the token over http, in seconds. Set this parameter to high value if you OAuth2 Token Endpoint
+ * answers slowly or you tokens expire quickly. If you set this parameter to too small value, you can get 4xx http
+ * errors because camel will think that the received token is still valid, while in reality the token is expired for
+ * the Authentication server.
+ */
+ public void setOauth2CachedTokensExpirationMarginSeconds(long oauth2CachedTokensExpirationMarginSeconds) {
+ this.oauth2CachedTokensExpirationMarginSeconds = oauth2CachedTokensExpirationMarginSeconds;
+ }
+}
\ No newline at end of file
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 000000000..32d08343e
--- /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() {
+ }
+}
\ No newline at end of file
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 000000000..502d84d5d
--- /dev/null
+++ b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpConsumer.java
@@ -0,0 +1,110 @@
+/*
+ * 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.Suspendable;
+import org.apache.camel.support.DefaultConsumer;
+
+public class HttpConsumer extends DefaultConsumer implements Suspendable {
+ protected volatile boolean canConnected;
+ private volatile boolean suspended;
+
+ private boolean traceEnabled;
+ private boolean optionsEnabled;
+
+ public HttpConsumer(HttpCommonEndpoint endpoint, Processor processor) {
+ super(endpoint, processor);
+ if (endpoint.isTraceEnabled()) {
+ setTraceEnabled(true);
+ }
+ if (endpoint.isOptionsEnabled()) {
+ setOptionsEnabled(true);
+ }
+ }
+
+ @Override
+ public boolean isHostedService() {
+ return true;
+ }
+
+ @Override
+ public HttpCommonEndpoint getEndpoint() {
+ return (HttpCommonEndpoint) super.getEndpoint();
+ }
+
+ public HttpBinding getBinding() {
+ return getEndpoint().getHttpBinding();
+ }
+
+ public String getPath() {
+ return getEndpoint().getPath();
+ }
+
+ @Override
+ protected void doStart() throws Exception {
+ super.doStart();
+ if (getEndpoint().canConnect(this)) {
+ canConnected = true;
+ getEndpoint().connect(this);
+ suspended = false;
+ }
+ }
+
+ @Override
+ protected void doStop() throws Exception {
+ suspended = false;
+ if (canConnected) {
+ canConnected = false;
+ getEndpoint().disconnect(this);
+ }
+ super.doStop();
+ }
+
+ @Override
+ protected void doSuspend() throws Exception {
+ suspended = true;
+ super.doSuspend();
+ }
+
+ @Override
+ protected void doResume() throws Exception {
+ suspended = false;
+ super.doResume();
+ }
+
+ @Override
+ public boolean isSuspended() {
+ return suspended;
+ }
+
+ public boolean isTraceEnabled() {
+ return this.traceEnabled;
+ }
+
+ public void setTraceEnabled(boolean traceEnabled) {
+ this.traceEnabled = traceEnabled;
+ }
+
+ public boolean isOptionsEnabled() {
+ return optionsEnabled;
+ }
+
+ public void setOptionsEnabled(boolean optionsEnabled) {
+ this.optionsEnabled = optionsEnabled;
+ }
+}
\ No newline at end of file
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 000000000..af868b191
--- /dev/null
+++ b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpConverter.java
@@ -0,0 +1,99 @@
+/*
+ * 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.support.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(generateLoader = true)
+public final class HttpConverter {
+
+ private HttpConverter() {
+ }
+
+ @Converter
+ public static HttpServletRequest toServletRequest(Message message) {
+ if (message instanceof HttpMessage hm) {
+ return hm.getRequest();
+ }
+ return null;
+ }
+
+ @Converter
+ public static HttpServletResponse toServletResponse(Message message) {
+ if (message instanceof HttpMessage hm) {
+ return hm.getResponse();
+ }
+ return null;
+ }
+
+ @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;
+ }
+ }
+
+}
\ No newline at end of file
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 000000000..688fd71ed
--- /dev/null
+++ b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpHeaderFilterStrategy.java
@@ -0,0 +1,45 @@
+/*
+ * 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.util.Set;
+
+import org.apache.camel.support.http.HttpUtil;
+
+/**
+ * @deprecated use {@link org.apache.camel.http.base.HttpHeaderFilterStrategy}
+ */
+@Deprecated
+public class HttpHeaderFilterStrategy extends org.apache.camel.http.base.HttpHeaderFilterStrategy {
+
+ public HttpHeaderFilterStrategy() {
+ initialize();
+ }
+
+ protected void initialize() {
+ final Set outFilter = getOutFilter();
+ HttpUtil.addCommonFilters(outFilter);
+
+ setLowerCase(true);
+
+ // filter headers begin with "Camel" or "org.apache.camel"
+ // must ignore case for Http based transports
+ setOutFilterStartsWith(CAMEL_FILTER_STARTS_WITH);
+ setInFilterStartsWith(CAMEL_FILTER_STARTS_WITH);
+ }
+
+}
\ No newline at end of file
diff --git a/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpHelper.java b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpHelper.java
new file mode 100644
index 000000000..552bbe92d
--- /dev/null
+++ b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpHelper.java
@@ -0,0 +1,362 @@
+/*
+ * 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.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.net.ProtocolException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
+import org.apache.camel.RuntimeExchangeException;
+import org.apache.camel.converter.stream.CachedOutputStream;
+import org.apache.camel.support.CamelObjectInputStream;
+import org.apache.camel.support.http.HttpUtil;
+import org.apache.camel.util.CollectionHelper;
+import org.apache.camel.util.IOHelper;
+import org.apache.camel.util.URISupport;
+import org.apache.camel.util.UnsafeUriCharactersEncoder;
+
+public final class HttpHelper {
+
+ private HttpHelper() {
+ // Helper class
+ }
+
+ public static boolean isSecureConnection(String uri) {
+ return org.apache.camel.http.base.HttpHelper.isSecureConnection(uri);
+ }
+
+ public static int[] parserHttpVersion(String s) throws ProtocolException {
+ return org.apache.camel.http.base.HttpHelper.parserHttpVersion(s);
+ }
+
+ public static void setCharsetFromContentType(String contentType, Exchange exchange) {
+ HttpUtil.setCharsetFromContentType(contentType, exchange);
+ }
+
+ public static String getCharsetFromContentType(String contentType) {
+ return HttpUtil.getCharsetFromContentType(contentType);
+ }
+
+ /**
+ * Writes the given object as response body to the servlet response
+ *
+ * The content type will be set to {@link HttpConstants#CONTENT_TYPE_JAVA_SERIALIZED_OBJECT}
+ *
+ * @param response servlet response
+ * @param target object to write
+ * @throws IOException is thrown if error writing
+ */
+ public static void writeObjectToServletResponse(ServletResponse response, Object target) throws IOException {
+ response.setContentType(HttpConstants.CONTENT_TYPE_JAVA_SERIALIZED_OBJECT);
+ writeObjectToStream(response.getOutputStream(), target);
+ }
+
+ /**
+ * Writes the given object as response body to the output stream
+ *
+ * @param stream output stream
+ * @param target object to write
+ * @throws IOException is thrown if error writing
+ */
+ public static void writeObjectToStream(OutputStream stream, Object target) throws IOException {
+ ObjectOutputStream oos = new ObjectOutputStream(stream);
+ oos.writeObject(target);
+ oos.flush();
+ IOHelper.close(oos);
+ }
+
+ /**
+ * Deserializes the input stream to a Java object
+ *
+ * @param is input stream for the Java object
+ * @return the java object, or null if input stream was null
+ * @throws ClassNotFoundException is thrown if class not found
+ * @throws IOException can be thrown
+ * @deprecated Camel 3.0 Please use the one which has the parameter of camel context
+ */
+ @Deprecated
+ public static Object deserializeJavaObjectFromStream(InputStream is) throws ClassNotFoundException, IOException {
+ return deserializeJavaObjectFromStream(is, null);
+ }
+
+ /**
+ * Deserializes the input stream to a Java object
+ *
+ * @param is input stream for the Java object
+ * @param context the camel context which could help us to apply the customer classloader
+ * @return the java object, or null if input stream was null
+ * @throws ClassNotFoundException is thrown if class not found
+ * @throws IOException can be thrown
+ */
+ public static Object deserializeJavaObjectFromStream(InputStream is, CamelContext context)
+ throws ClassNotFoundException, IOException {
+ if (is == null) {
+ return null;
+ }
+
+ Object answer;
+ ObjectInputStream ois = new CamelObjectInputStream(is, context);
+ try {
+ answer = ois.readObject();
+ } finally {
+ IOHelper.close(ois);
+ }
+
+ return answer;
+ }
+
+ /**
+ * Reads the request body from the given http servlet request.
+ *
+ * @param request http servlet request
+ * @param exchange the exchange
+ * @return the request body, can be null if no body
+ * @throws IOException is thrown if error reading request body
+ */
+ public static Object readRequestBodyFromServletRequest(HttpServletRequest request, Exchange exchange) throws IOException {
+ InputStream is = HttpConverter.toInputStream(request, exchange);
+ // when using servlet (camel-servlet and camel-jetty) then they should always use stream caching
+ // as the message body is parsed for url-form and other things, so we need to be able to re-read the message body
+ // however there is an option to turn this off, which is set as exchange property
+ boolean streamCaching = !exchange.getProperty(Exchange.DISABLE_HTTP_STREAM_CACHE, false, boolean.class);
+ if (streamCaching) {
+ return cacheResponseBodyFromInputStream(is, exchange);
+ } else {
+ return is;
+ }
+ }
+
+ /**
+ * Caches the response body from the given input stream, which is needed by
+ * {@link org.apache.camel.PollingConsumer}.
+ *
+ * @param is the input stream
+ * @param exchange the exchange
+ * @return the cached response body
+ * @throws IOException is thrown if error reading response body
+ */
+ public static Object cacheResponseBodyFromInputStream(InputStream is, Exchange exchange) throws IOException {
+ if (is == null) {
+ return null;
+ }
+ CachedOutputStream cos = new CachedOutputStream(exchange);
+ // do not close IS as it comes from http server such as servlet and the
+ // servlet input stream may be used by others besides Camel
+ IOHelper.copy(is, cos);
+ return cos.newStreamCache();
+ }
+
+ /**
+ * Creates the URL to invoke.
+ *
+ * @param exchange the exchange
+ * @param endpoint the endpoint
+ * @return the URL to invoke
+ */
+ public static String createURL(Exchange exchange, HttpCommonEndpoint endpoint) {
+ // rest producer may provide an override url to be used which we should discard if using (hence the remove)
+ String uri = (String) exchange.getIn().removeHeader(Exchange.REST_HTTP_URI);
+
+ if (uri == null && !(endpoint.isBridgeEndpoint())) {
+ uri = exchange.getIn().getHeader(Exchange.HTTP_URI, String.class);
+ }
+ if (uri == null) {
+ uri = endpoint.getHttpUri().toASCIIString();
+ }
+
+ // resolve placeholders in uri
+ try {
+ uri = exchange.getContext().resolvePropertyPlaceholders(uri);
+ } catch (Exception e) {
+ throw new RuntimeExchangeException("Cannot resolve property placeholders with uri: " + uri, exchange, e);
+ }
+
+ // append HTTP_PATH to HTTP_URI if it is provided in the header
+ String path = exchange.getIn().getHeader(Exchange.HTTP_PATH, String.class);
+ // NOW the HTTP_PATH is just related path, we don't need to trim it
+ if (path != null) {
+ if (path.length() > 1 && path.startsWith("/")) {
+ path = path.substring(1);
+ }
+ if (!path.isEmpty()) {
+ // inject the dynamic path before the query params, if there are any
+ int idx = uri.indexOf('?');
+
+ // if there are no query params
+ if (idx == -1) {
+ // make sure that there is exactly one "/" between HTTP_URI and HTTP_PATH
+ if (uri.endsWith("/") && path.startsWith("/")) {
+ uri = uri.concat(path.substring(1));
+ } else {
+ uri = uri.endsWith("/") || path.startsWith("/") ? uri : uri + "/";
+ uri = uri.concat(path);
+ }
+ } else {
+ // there are query params, so inject the relative path in the right place
+ String base = uri.substring(0, idx);
+ base = base.endsWith("/") ? base : base + "/";
+ base = base.concat(path.startsWith("/") ? path.substring(1) : path);
+ uri = base.concat(uri.substring(idx));
+ }
+ }
+ }
+
+ // ensure uri is encoded to be valid
+ uri = UnsafeUriCharactersEncoder.encodeHttpURI(uri);
+
+ return uri;
+ }
+
+ /**
+ * Creates the URI to invoke.
+ *
+ * @param exchange the exchange
+ * @param url the url to invoke
+ * @param endpoint the endpoint
+ * @return the URI to invoke
+ */
+ public static URI createURI(Exchange exchange, String url, HttpCommonEndpoint endpoint) throws URISyntaxException {
+ URI uri = new URI(url);
+ // rest producer may provide an override query string to be used which we should discard if using (hence the remove)
+ String queryString = (String) exchange.getIn().removeHeader(Exchange.REST_HTTP_QUERY);
+ // is a query string provided in the endpoint URI or in a header
+ // (header overrules endpoint, raw query header overrules query header)
+ if (queryString == null) {
+ queryString = exchange.getIn().getHeader(Exchange.HTTP_RAW_QUERY, String.class);
+ }
+ if (queryString == null) {
+ queryString = exchange.getIn().getHeader(Exchange.HTTP_QUERY, String.class);
+ }
+ if (queryString == null) {
+ queryString = endpoint.getHttpUri().getRawQuery();
+ }
+ // We should use the query string from the HTTP_URI header
+ if (queryString == null) {
+ queryString = uri.getRawQuery();
+ }
+ if (queryString != null) {
+ // need to encode query string
+ queryString = UnsafeUriCharactersEncoder.encodeHttpURI(queryString);
+ uri = URISupport.createURIWithQuery(uri, queryString);
+ }
+ return uri;
+ }
+
+ /**
+ * Appends the key/value to the headers.
+ *
+ * This implementation supports keys with multiple values. In such situations the value will be a
+ * {@link java.util.List} that contains the multiple values.
+ *
+ * @param headers headers
+ * @param key the key
+ * @param value the value
+ */
+ public static void appendHeader(Map headers, String key, Object value) {
+ CollectionHelper.appendEntry(headers, key, value);
+ }
+
+ /**
+ * Extracts the parameter value.
+ *
+ * This implementation supports HTTP multi value parameters which is based on the syntax of
+ * [value1, value2, value3] by returning a {@link List} containing the values.
+ *
+ * If the value is not a HTTP mulit value the value is returned as is.
+ *
+ * @param value the parameter value
+ * @return the extracted parameter value, see more details in javadoc.
+ */
+ public static Object extractHttpParameterValue(String value) {
+ return org.apache.camel.http.base.HttpHelper.extractHttpParameterValue(value);
+ }
+
+ /**
+ * Creates the HttpMethod to use to call the remote server, often either its GET or POST.
+ *
+ * @param exchange the exchange
+ * @return the created method
+ * @throws URISyntaxException
+ */
+ public static HttpMethods createMethod(Exchange exchange, HttpCommonEndpoint endpoint, boolean hasPayload)
+ throws URISyntaxException {
+ // is a query string provided in the endpoint URI or in a header (header overrules endpoint)
+ String queryString = exchange.getIn().getHeader(Exchange.HTTP_QUERY, String.class);
+ // We need also check the HTTP_URI header query part
+ String uriString = exchange.getIn().getHeader(Exchange.HTTP_URI, String.class);
+ // resolve placeholders in uriString
+ try {
+ uriString = exchange.getContext().resolvePropertyPlaceholders(uriString);
+ } catch (Exception e) {
+ throw new RuntimeExchangeException("Cannot resolve property placeholders with uri: " + uriString, exchange, e);
+ }
+ if (uriString != null) {
+ // in case the URI string contains unsafe characters
+ uriString = UnsafeUriCharactersEncoder.encodeHttpURI(uriString);
+ URI uri = new URI(uriString);
+ queryString = uri.getQuery();
+ }
+ if (queryString == null) {
+ queryString = endpoint.getHttpUri().getRawQuery();
+ }
+
+ HttpMethods answer;
+ if (endpoint.getHttpMethod() != null) {
+ // endpoint configured take precedence
+ answer = endpoint.getHttpMethod();
+ } else {
+ // compute what method to use either GET or POST (header take precedence)
+ HttpMethods m = exchange.getIn().getHeader(Exchange.HTTP_METHOD, HttpMethods.class);
+ if (m != null) {
+ // always use what end-user provides in a header
+ answer = m;
+ } else if (queryString != null) {
+ // if a query string is provided then use GET
+ answer = HttpMethods.GET;
+ } else {
+ // fallback to POST if we have payload, otherwise GET
+ answer = hasPayload ? HttpMethods.POST : HttpMethods.GET;
+ }
+ }
+
+ return answer;
+ }
+
+ /**
+ * Checks whether the given http status code is within the ok range
+ *
+ * @param statusCode the status code
+ * @param okStatusCodeRange the ok range (inclusive)
+ * @return true if ok, false otherwise
+ */
+ public static boolean isStatusCodeOk(int statusCode, String okStatusCodeRange) {
+ return org.apache.camel.http.base.HttpHelper.isStatusCodeOk(statusCode, okStatusCodeRange);
+ }
+
+}
\ No newline at end of file
diff --git a/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpMessage.java b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpMessage.java
new file mode 100644
index 000000000..51e58ca62
--- /dev/null
+++ b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpMessage.java
@@ -0,0 +1,116 @@
+/*
+ * 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.RuntimeCamelException;
+import org.apache.camel.support.DefaultMessage;
+import org.apache.camel.util.ObjectHelper;
+
+public class HttpMessage extends DefaultMessage {
+
+ private HttpServletRequest request;
+ private HttpServletResponse response;
+ private HttpCommonEndpoint endpoint;
+ private boolean requestRead;
+
+ public HttpMessage(Exchange exchange, HttpCommonEndpoint endpoint, HttpServletRequest request,
+ HttpServletResponse response) {
+ super(exchange);
+ init(exchange, endpoint, request, response);
+ }
+
+ private HttpMessage(HttpServletRequest request, HttpServletResponse response, Exchange exchange,
+ HttpCommonEndpoint endpoint,
+ boolean requestRead) {
+ super(exchange);
+ this.request = request;
+ this.response = response;
+ this.endpoint = endpoint;
+ this.requestRead = requestRead;
+ }
+
+ public void init(
+ Exchange exchange, HttpCommonEndpoint endpoint, HttpServletRequest request,
+ HttpServletResponse response) {
+ setExchange(exchange);
+ this.requestRead = false;
+ this.endpoint = endpoint;
+
+ this.request = request;
+ this.response = response;
+
+ // Check the setting of exchange
+ Boolean flag = exchange.getProperty(Exchange.SKIP_WWW_FORM_URLENCODED, Boolean.class);
+ if (flag != null && flag) {
+ this.setHeader(Exchange.SKIP_WWW_FORM_URLENCODED, Boolean.TRUE);
+ }
+
+ // use binding to read the request allowing end users to use their
+ // implementation of the binding
+ endpoint.getHttpBinding().readRequest(request, this);
+ }
+
+ @Override
+ public void reset() {
+ super.reset();
+ request = null;
+ response = null;
+ endpoint = null;
+ requestRead = false;
+ }
+
+ public HttpServletRequest getRequest() {
+ return request;
+ }
+
+ public HttpServletResponse getResponse() {
+ return response;
+ }
+
+ @Override
+ protected Object createBody() {
+ // HTTP request may be read only once
+ if (requestRead) {
+ return null;
+ }
+
+ try {
+ return endpoint.getHttpBinding().parseBody(request, this);
+ } catch (IOException e) {
+ throw new RuntimeCamelException(e);
+ } finally {
+ requestRead = true;
+ }
+ }
+
+ @Override
+ public HttpMessage newInstance() {
+ return new HttpMessage(request, response, getExchange(), endpoint, requestRead);
+ }
+
+ @Override
+ public String toString() {
+ // do not use toString on HTTP message
+ return "HttpMessage@" + ObjectHelper.getIdentityHashCode(this);
+ }
+}
\ No newline at end of file
diff --git a/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpMethods.java b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpMethods.java
new file mode 100644
index 000000000..0a9f67f1a
--- /dev/null
+++ b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpMethods.java
@@ -0,0 +1,30 @@
+/*
+ * 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 enum HttpMethods {
+
+ GET,
+ POST,
+ PUT,
+ DELETE,
+ HEAD,
+ OPTIONS,
+ TRACE,
+ PATCH
+
+}
\ No newline at end of file
diff --git a/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpProtocolHeaderFilterStrategy.java b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpProtocolHeaderFilterStrategy.java
new file mode 100644
index 000000000..89d3bba68
--- /dev/null
+++ b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpProtocolHeaderFilterStrategy.java
@@ -0,0 +1,20 @@
+/*
+ * 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 class HttpProtocolHeaderFilterStrategy extends org.apache.camel.http.base.HttpProtocolHeaderFilterStrategy {
+}
\ No newline at end of file
diff --git a/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpRegistry.java b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpRegistry.java
new file mode 100644
index 000000000..9af46a81e
--- /dev/null
+++ b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpRegistry.java
@@ -0,0 +1,39 @@
+/*
+ * 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;
+
+/**
+ * Keeps track of HttpConsumers and CamelServlets and connects them to each other. In OSGi there should be one
+ * HttpRegistry per bundle.
+ *
+ * A CamelServlet that should serve more than one bundle should be registered as an OSGi service. The
+ * {@link DefaultHttpRegistry} can then be configured to listen to service changes. See
+ * /examples/camel-example-servlet-httpregistry-blueprint for an example how to use this.
+ */
+public interface HttpRegistry {
+
+ void register(HttpConsumer consumer);
+
+ void unregister(HttpConsumer consumer);
+
+ void register(HttpRegistryProvider provider);
+
+ void unregister(HttpRegistryProvider provider);
+
+ HttpRegistryProvider getCamelServlet(String servletName);
+
+}
\ No newline at end of file
diff --git a/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpRegistryProvider.java b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpRegistryProvider.java
new file mode 100644
index 000000000..5dbb021d3
--- /dev/null
+++ b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpRegistryProvider.java
@@ -0,0 +1,30 @@
+/*
+ * 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;
+
+/**
+ * Usually class extending CamelServlet or simila e.g. HttpServet should implement this interface to be able to benefit
+ * from DefaultHttpRegistry
+ */
+public interface HttpRegistryProvider {
+
+ void connect(HttpConsumer consumer);
+
+ void disconnect(HttpConsumer consumer);
+
+ String getServletName();
+}
\ No newline at end of file
diff --git a/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpRestConsumerPath.java b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpRestConsumerPath.java
new file mode 100644
index 000000000..770ddfd6e
--- /dev/null
+++ b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpRestConsumerPath.java
@@ -0,0 +1,53 @@
+/*
+ * 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.support.RestConsumerContextPathMatcher;
+
+public class HttpRestConsumerPath implements RestConsumerContextPathMatcher.ConsumerPath {
+
+ private final HttpConsumer consumer;
+
+ public HttpRestConsumerPath(HttpConsumer consumer) {
+ this.consumer = consumer;
+ }
+
+ @Override
+ public String getRestrictMethod() {
+ return consumer.getEndpoint().getHttpMethodRestrict();
+ }
+
+ @Override
+ public String getConsumerPath() {
+ return consumer.getPath();
+ }
+
+ @Override
+ public HttpConsumer getConsumer() {
+ return consumer;
+ }
+
+ @Override
+ public boolean isMatchOnUriPrefix() {
+ return consumer.getEndpoint().isMatchOnUriPrefix();
+ }
+
+ @Override
+ public String toString() {
+ return getConsumerPath();
+ }
+}
\ No newline at end of file
diff --git a/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpRestHeaderFilterStrategy.java b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpRestHeaderFilterStrategy.java
new file mode 100644
index 000000000..0b6bfb580
--- /dev/null
+++ b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpRestHeaderFilterStrategy.java
@@ -0,0 +1,62 @@
+/*
+ * 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.Exchange;
+
+public class HttpRestHeaderFilterStrategy extends HttpHeaderFilterStrategy {
+
+ private final String templateUri;
+ private final String queryParameters;
+
+ public HttpRestHeaderFilterStrategy(String templateUri, String queryParameters) {
+ this.templateUri = templateUri;
+ this.queryParameters = queryParameters;
+ }
+
+ @Override
+ public boolean applyFilterToCamelHeaders(String headerName, Object headerValue, Exchange exchange) {
+ boolean answer = super.applyFilterToCamelHeaders(headerName, headerValue, exchange);
+ // using rest producer then headers are mapping to uri and query parameters using {key} syntax
+ // if there is a match to an existing Camel Message header, then we should filter (=true) this
+ // header as its already been mapped by the RestProducer from camel-core, and we do not want
+ // the header to included as HTTP header also (eg as duplicate value)
+ if (!answer) {
+ if (templateUri != null) {
+ String token = "{" + headerName + "}";
+ if (templateUri.contains(token)) {
+ answer = true;
+ }
+ }
+ if (!answer && queryParameters != null) {
+ String[] tokens = new String[4];
+ tokens[0] = "={" + headerName + "}";
+ tokens[1] = "={" + headerName + "?}";
+ tokens[2] = "=%7B" + headerName + "%7D";
+ tokens[3] = "=%7B" + headerName + "%3F%7D";
+ for (String token : tokens) {
+ if (queryParameters.contains(token)) {
+ answer = true;
+ break;
+ }
+ }
+ }
+ }
+ return answer;
+ }
+
+}
\ No newline at end of file
diff --git a/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpRestServletResolveConsumerStrategy.java b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpRestServletResolveConsumerStrategy.java
new file mode 100644
index 000000000..f43942ffb
--- /dev/null
+++ b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpRestServletResolveConsumerStrategy.java
@@ -0,0 +1,58 @@
+/*
+ * 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.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.camel.support.RestConsumerContextPathMatcher;
+
+/**
+ * A {@link org.apache.camel.http.common.HttpServletResolveConsumerStrategy} that supports the Rest DSL.
+ */
+public class HttpRestServletResolveConsumerStrategy extends HttpServletResolveConsumerStrategy {
+
+ @Override
+ protected HttpConsumer doResolve(HttpServletRequest request, String method, Map consumers) {
+ HttpConsumer answer = null;
+
+ String path = request.getPathInfo();
+ if (path == null) {
+ return null;
+ }
+ List> paths = new ArrayList<>();
+ for (final Map.Entry entry : consumers.entrySet()) {
+ paths.add(new HttpRestConsumerPath(entry.getValue()));
+ }
+
+ RestConsumerContextPathMatcher.ConsumerPath best
+ = RestConsumerContextPathMatcher.matchBestPath(method, path, paths);
+ if (best != null) {
+ answer = best.getConsumer();
+ }
+
+ if (answer == null) {
+ // fallback to default
+ answer = super.doResolve(request, method, consumers);
+ }
+
+ return answer;
+ }
+}
\ No newline at end of file
diff --git a/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpServletResolveConsumerStrategy.java b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpServletResolveConsumerStrategy.java
new file mode 100644
index 000000000..32fd84752
--- /dev/null
+++ b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpServletResolveConsumerStrategy.java
@@ -0,0 +1,84 @@
+/*
+ * 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.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.camel.support.RestConsumerContextPathMatcher;
+
+/**
+ * A default implementation of {@link org.apache.camel.http.common.ServletResolveConsumerStrategy}.
+ */
+public class HttpServletResolveConsumerStrategy implements ServletResolveConsumerStrategy {
+
+ @Override
+ public HttpConsumer resolve(HttpServletRequest request, Map consumers) {
+ return doResolve(request, request.getMethod(), consumers);
+ }
+
+ @Override
+ public boolean isHttpMethodAllowed(HttpServletRequest request, String method, Map consumers) {
+ return doResolve(request, method, consumers) != null;
+ }
+
+ protected HttpConsumer doResolve(HttpServletRequest request, String method, Map consumers) {
+ String path = request.getPathInfo();
+ if (path == null) {
+ return null;
+ }
+ HttpConsumer answer = consumers.get(path);
+
+ List candidates = resolveCandidates(request, consumers);
+ // extra filter by restrict
+ candidates = candidates.stream().filter(c -> matchRestMethod(method, c.getEndpoint().getHttpMethodRestrict()))
+ .collect(Collectors.toList());
+ if (candidates.size() == 1) {
+ answer = candidates.get(0);
+ }
+
+ return answer;
+ }
+
+ private List resolveCandidates(
+ HttpServletRequest request, Map consumers) {
+ String path = request.getPathInfo();
+
+ List candidates = new ArrayList<>();
+ for (Map.Entry entry : consumers.entrySet()) {
+ //We need to look up the consumer path here
+ String consumerPath = entry.getValue().getPath();
+ HttpConsumer consumer = entry.getValue();
+ boolean matchOnUriPrefix = consumer.getEndpoint().isMatchOnUriPrefix();
+ // Just make sure the we get the right consumer path first
+ if (RestConsumerContextPathMatcher.matchPath(path, consumerPath, matchOnUriPrefix)) {
+ candidates.add(consumer);
+ }
+ }
+ return candidates;
+ }
+
+ private static boolean matchRestMethod(String method, String restrict) {
+ return restrict == null || restrict.toLowerCase(Locale.ENGLISH).contains(method.toLowerCase(Locale.ENGLISH));
+ }
+
+}
\ No newline at end of file
diff --git a/components/camel-http-common/src/main/java/org/apache/camel/http/common/ServletResolveConsumerStrategy.java b/components/camel-http-common/src/main/java/org/apache/camel/http/common/ServletResolveConsumerStrategy.java
new file mode 100644
index 000000000..2fffe4af4
--- /dev/null
+++ b/components/camel-http-common/src/main/java/org/apache/camel/http/common/ServletResolveConsumerStrategy.java
@@ -0,0 +1,50 @@
+/*
+ * 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.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * Strategy to resolve which consumer to service an incoming {@link javax.servlet.http.HttpServletRequest}.
+ */
+public interface ServletResolveConsumerStrategy {
+
+ /**
+ * Resolve the consumer to use.
+ *
+ * @param request the http request
+ * @param consumers the map of registered consumers
+ * @return the consumer to service the request, or null if no match, which sends back a
+ * {@link javax.servlet.http.HttpServletResponse#SC_NOT_FOUND} to the client.
+ */
+ HttpConsumer resolve(HttpServletRequest request, Map consumers);
+
+ /**
+ * Checks if the http request method (GET, POST, etc) would be allow among the registered consumers.
+ *
+ * @param request the http request
+ * @param method the http method
+ * @param consumers the map of registered consumers
+ * @return true if the method is allowed and can be serviced. Otherwise a
+ * {@link javax.servlet.http.HttpServletResponse#SC_METHOD_NOT_ALLOWED} is returned to the
+ * client.
+ */
+ boolean isHttpMethodAllowed(HttpServletRequest request, String method, Map consumers);
+
+}
\ No newline at end of file
diff --git a/components/camel-servlet-osgi/pom.xml b/components/camel-servlet-osgi/pom.xml
new file mode 100644
index 000000000..359c7098a
--- /dev/null
+++ b/components/camel-servlet-osgi/pom.xml
@@ -0,0 +1,152 @@
+
+
+
+
+ 4.0.0
+
+
+ org.apache.camel.karaf
+ camel-karaf-components
+ 4.9.0-SNAPSHOT
+ ../pom.xml
+
+
+ camel-servlet-osgi
+ bundle
+ Apache Camel :: Karaf :: Components :: Servlet OSGi
+
+
+
+ org.apache.camel.karaf.component.servlet.osgi.*;version=${camel-version}
+
+
+ org.osgi.service.http,
+ javax.servlet*;version="[4,7)",
+ *
+
+
+
+
+
+ org.apache.camel
+ camel-support
+ ${camel-version}
+
+
+ org.apache.camel
+ camel-servlet
+ ${camel-version}
+
+
+
+ jakarta.servlet
+ jakarta.servlet-api
+ 4.0.4
+ provided
+
+
+ org.osgi
+ osgi.core
+ 8.0.0
+ provided
+
+
+ org.osgi
+ osgi.cmpn
+ 7.0.0
+ provided
+
+
+
+
+
+
+ org.apache.camel
+ camel-package-maven-plugin
+ ${camel-version}
+
+
+ false
+
+
+
+ generate
+ process-classes
+
+ generate
+
+
+
+ generate-postcompile
+ prepare-package
+
+ generate-postcompile
+
+
+
+
+
+ org.apache.camel
+ camel-core-model
+ ${camel-version}
+ compile
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ recompile
+ process-classes
+
+ compile
+
+
+
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+
+
+ initialize
+
+ add-source
+ add-resource
+
+
+
+ src/generated/java
+
+
+
+ src/generated/resources
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/components/camel-servlet-osgi/src/main/java/org/apache/camel/karaf/component/servlet/osgi/OsgiServletRegisterer.java b/components/camel-servlet-osgi/src/main/java/org/apache/camel/karaf/component/servlet/osgi/OsgiServletRegisterer.java
new file mode 100644
index 000000000..7624ce975
--- /dev/null
+++ b/components/camel-servlet-osgi/src/main/java/org/apache/camel/karaf/component/servlet/osgi/OsgiServletRegisterer.java
@@ -0,0 +1,109 @@
+/*
+ * 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.karaf.component.servlet.osgi;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.apache.camel.util.StringHelper;
+import org.osgi.service.http.HttpContext;
+import org.osgi.service.http.HttpService;
+
+import javax.servlet.http.HttpServlet;
+
+/**
+ * Register the given (CamelHttpTransport) Servlet with the OSGI
+ *
+ * HttpService
+ */
+public class OsgiServletRegisterer {
+
+ /**
+ * The alias is the name in the URI namespace of the Http Service at which the registration will be mapped
+ * An alias must begin with slash ('/') and must not end with slash ('/'), with the exception that an alias
+ * of the form "/" is used to denote the root alias.
+ */
+ private String alias;
+
+ /**
+ * The servlet name.
+ */
+ private String servletName = "CamelServlet";
+
+ /**
+ * Servlet to be registered
+ */
+ private HttpServlet servlet;
+
+ /**
+ * HttpService to register with. Get this with osgi:reference in the blueprint file
+ */
+ private HttpService httpService;
+
+ private HttpContext httpContext;
+
+ private boolean alreadyRegistered;
+
+ // The servlet will default have to match on uri prefix as some endpoints may do so
+ private volatile boolean matchOnUriPrefix = true;
+
+ public void setHttpService(HttpService httpService) {
+ this.httpService = httpService;
+ }
+
+ public void setAlias(String alias) {
+ this.alias = alias;
+ }
+
+ public void setServletName(String servletName) {
+ this.servletName = servletName;
+ }
+
+ public void setServlet(HttpServlet servlet) {
+ this.servlet = servlet;
+ }
+
+ public void setHttpContext(HttpContext httpContext) {
+ this.httpContext = httpContext;
+ }
+
+ public void setMatchOnUriPrefix(boolean matchOnUriPrefix) {
+ this.matchOnUriPrefix = matchOnUriPrefix;
+ }
+
+ public void register() throws Exception {
+ StringHelper.notEmpty(alias, "alias", this);
+ StringHelper.notEmpty(servletName, "servletName", this);
+
+ HttpContext actualHttpContext = (httpContext == null)
+ ? httpService.createDefaultHttpContext()
+ : httpContext;
+ final Dictionary initParams = new Hashtable<>();
+ initParams.put("matchOnUriPrefix", matchOnUriPrefix ? "true" : "false");
+ initParams.put("servlet-name", servletName);
+ httpService.registerServlet(alias, servlet, initParams, actualHttpContext);
+ alreadyRegistered = true;
+ }
+
+ public void unregister() {
+ if (alreadyRegistered) {
+ httpService.unregister(alias);
+ alreadyRegistered = false;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/components/camel-servlet/pom.xml b/components/camel-servlet/pom.xml
index 6beafb023..ff1e9dc85 100644
--- a/components/camel-servlet/pom.xml
+++ b/components/camel-servlet/pom.xml
@@ -32,11 +32,15 @@
bundleApache Camel :: Karaf :: Components :: Servlet
+
+
org.apache.camel*;version=${camel-version}
+ javax.servlet*,
+ io.quarkus*;resolution:=optional,
*
@@ -44,39 +48,95 @@
org.apache.camel
- camel-servlet
+ camel-support${camel-version}
-
-
- org.apache.camel
- *
-
-
+
+
+ org.apache.camel.karaf
+ camel-http-common
+ ${project.version}
+
+
+ jakarta.servlet
+ jakarta.servlet-api
+ 4.0.4
+ provided
+
+ org.apache.camel
+ camel-package-maven-plugin
+ ${camel-version}
+
+
+ false
+
+
+
+ generate
+ process-classes
+
+ generate
+
+
+
+ generate-postcompile
+ prepare-package
+
+ generate-postcompile
+
+
+
+
+
+ org.apache.camel
+ camel-core-model
+ ${camel-version}
+ compile
+
+
+ org.apache.maven.plugins
- maven-shade-plugin
+ maven-compiler-plugin
- package
+ recompile
+ process-classes
- shade
+ compile
+
+
+
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+
+
+ initialize
+
+ add-source
+ add-resource
-
-
- org.apache.camel:camel-servlet
-
-
+
+ src/generated/java
+
+
+
+ src/generated/resources
+
+
+
diff --git a/components/camel-servlet/src/main/java/org/apache/camel/component/servlet/AttachmentHttpBinding.java b/components/camel-servlet/src/main/java/org/apache/camel/component/servlet/AttachmentHttpBinding.java
new file mode 100644
index 000000000..8da816169
--- /dev/null
+++ b/components/camel-servlet/src/main/java/org/apache/camel/component/servlet/AttachmentHttpBinding.java
@@ -0,0 +1,122 @@
+/*
+ * 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.component.servlet;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Collection;
+import java.util.Locale;
+
+import jakarta.activation.DataSource;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.Part;
+
+import org.apache.camel.Message;
+import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.attachment.Attachment;
+import org.apache.camel.attachment.AttachmentMessage;
+import org.apache.camel.attachment.DefaultAttachment;
+import org.apache.camel.http.common.DefaultHttpBinding;
+import org.apache.camel.util.FileUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * To handle attachments with Servlet.
+ *
+ * This implementation is needed to deal with attachments when using Servlet.
+ */
+public final class AttachmentHttpBinding extends DefaultHttpBinding {
+
+ private static final Logger LOG = LoggerFactory.getLogger(AttachmentHttpBinding.class);
+
+ AttachmentHttpBinding() {
+ }
+
+ @Override
+ protected void populateAttachments(HttpServletRequest request, Message message) {
+ try {
+ Collection parts = request.getParts();
+ for (Part part : parts) {
+ String fileName = part.getName();
+ // is the file name accepted
+ boolean accepted = true;
+ if (getFileNameExtWhitelist() != null) {
+ String ext = FileUtil.onlyExt(fileName);
+ if (ext != null) {
+ ext = ext.toLowerCase(Locale.US);
+ String whiteList = getFileNameExtWhitelist().toLowerCase(Locale.US);
+ if (!whiteList.equals("*") && !whiteList.contains(ext)) {
+ accepted = false;
+ }
+ }
+ }
+
+ if (accepted) {
+ DataSource ds = new PartDataSource(part);
+ Attachment attachment = new DefaultAttachment(ds);
+ for (String headerName : part.getHeaderNames()) {
+ for (String headerValue : part.getHeaders(headerName)) {
+ attachment.addHeader(headerName, headerValue);
+ }
+ }
+ AttachmentMessage am = message.getExchange().getMessage(AttachmentMessage.class);
+ am.addAttachmentObject(part.getName(), attachment);
+ } else {
+ LOG.debug(
+ "Cannot add file as attachment: {} because the file is not accepted according to fileNameExtWhitelist: {}",
+ fileName, getFileNameExtWhitelist());
+ }
+ }
+ } catch (Exception e) {
+ throw new RuntimeCamelException("Cannot populate attachments", e);
+ }
+ }
+
+ public final class PartDataSource implements DataSource {
+ private final Part part;
+
+ PartDataSource(Part part) {
+ this.part = part;
+ }
+
+ public String getSubmittedFileName() {
+ return part.getSubmittedFileName();
+ }
+
+ @Override
+ public OutputStream getOutputStream() throws IOException {
+ return null;
+ }
+
+ @Override
+ public String getName() {
+ return part.getName();
+ }
+
+ @Override
+ public InputStream getInputStream() throws IOException {
+ return part.getInputStream();
+ }
+
+ @Override
+ public String getContentType() {
+ return part.getContentType();
+ }
+ }
+}
\ No newline at end of file
diff --git a/components/camel-servlet/src/main/java/org/apache/camel/component/servlet/CamelHttpTransportServlet.java b/components/camel-servlet/src/main/java/org/apache/camel/component/servlet/CamelHttpTransportServlet.java
new file mode 100644
index 000000000..47c7cf096
--- /dev/null
+++ b/components/camel-servlet/src/main/java/org/apache/camel/component/servlet/CamelHttpTransportServlet.java
@@ -0,0 +1,117 @@
+/*
+ * 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.component.servlet;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+
+import org.apache.camel.http.common.CamelServlet;
+import org.apache.camel.http.common.DefaultHttpRegistry;
+import org.apache.camel.http.common.HttpConsumer;
+import org.apache.camel.http.common.HttpRegistry;
+import org.apache.camel.http.common.HttpRegistryProvider;
+import org.apache.camel.http.common.HttpRestServletResolveConsumerStrategy;
+import org.apache.camel.util.ObjectHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Camel HTTP servlet which can be used in Camel routes to route servlet invocations in routes.
+ */
+public class CamelHttpTransportServlet extends CamelServlet {
+ private static final long serialVersionUID = -1797014782158930490L;
+ private static final Logger LOG = LoggerFactory.getLogger(CamelHttpTransportServlet.class);
+
+ private HttpRegistry httpRegistry;
+ private boolean ignoreDuplicateServletName;
+
+ @Override
+ public void init(ServletConfig config) throws ServletException {
+ super.init(config);
+
+ // use rest enabled resolver in case we use rest
+ this.setServletResolveConsumerStrategy(new HttpRestServletResolveConsumerStrategy());
+
+ String ignore = config.getInitParameter("ignoreDuplicateServletName");
+ if (ObjectHelper.isNotEmpty(ignore)) {
+ ignoreDuplicateServletName = Boolean.parseBoolean(ignore);
+ }
+
+ String name = config.getServletName();
+ String contextPath = config.getServletContext().getContextPath();
+
+ if (httpRegistry == null) {
+ httpRegistry = DefaultHttpRegistry.getHttpRegistry(name);
+ HttpRegistryProvider existing = httpRegistry.getCamelServlet(name);
+ if (existing != null) {
+ String msg = "Duplicate ServletName detected: " + name + ". Existing: " + existing + " This: " + this.toString()
+ + ". Its advised to use unique ServletName per Camel application.";
+ // always log so people can see it easier
+ if (isIgnoreDuplicateServletName()) {
+ LOG.warn(msg);
+ } else {
+ LOG.error(msg);
+ throw new ServletException(msg);
+ }
+ }
+ httpRegistry.register(this);
+ }
+
+ LOG.info("Initialized CamelHttpTransportServlet[name={}, contextPath={}]", getServletName(), contextPath);
+ }
+
+ @Override
+ public void destroy() {
+ DefaultHttpRegistry.removeHttpRegistry(getServletName());
+ if (httpRegistry != null) {
+ httpRegistry.unregister(this);
+ httpRegistry = null;
+ }
+ LOG.info("Destroyed CamelHttpTransportServlet[{}]", getServletName());
+ }
+
+ private ServletEndpoint getServletEndpoint(HttpConsumer consumer) {
+ if (!(consumer.getEndpoint() instanceof ServletEndpoint)) {
+ throw new RuntimeException(
+ "Invalid consumer type. Must be ServletEndpoint but is "
+ + consumer.getClass().getName());
+ }
+ return (ServletEndpoint) consumer.getEndpoint();
+ }
+
+ @Override
+ public void connect(HttpConsumer consumer) {
+ ServletEndpoint endpoint = getServletEndpoint(consumer);
+ if (endpoint.getServletName() != null && endpoint.getServletName().equals(getServletName())) {
+ super.connect(consumer);
+ }
+ }
+
+ public boolean isIgnoreDuplicateServletName() {
+ return ignoreDuplicateServletName;
+ }
+
+ @Override
+ public String toString() {
+ String name = getServletName();
+ if (name != null) {
+ return "CamelHttpTransportServlet[name=" + getServletName() + "]";
+ } else {
+ return "CamelHttpTransportServlet";
+ }
+ }
+}
\ No newline at end of file
diff --git a/components/camel-servlet/src/main/java/org/apache/camel/component/servlet/ServletComponent.java b/components/camel-servlet/src/main/java/org/apache/camel/component/servlet/ServletComponent.java
new file mode 100644
index 000000000..9d263587e
--- /dev/null
+++ b/components/camel-servlet/src/main/java/org/apache/camel/component/servlet/ServletComponent.java
@@ -0,0 +1,356 @@
+/*
+ * 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.component.servlet;
+
+import java.net.URI;
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Consumer;
+import org.apache.camel.Endpoint;
+import org.apache.camel.Exchange;
+import org.apache.camel.Processor;
+import org.apache.camel.http.common.DefaultHttpRegistry;
+import org.apache.camel.http.common.HttpBinding;
+import org.apache.camel.http.common.HttpCommonComponent;
+import org.apache.camel.http.common.HttpConsumer;
+import org.apache.camel.http.common.HttpRegistry;
+import org.apache.camel.spi.HeaderFilterStrategy;
+import org.apache.camel.spi.Metadata;
+import org.apache.camel.spi.RestApiConsumerFactory;
+import org.apache.camel.spi.RestConfiguration;
+import org.apache.camel.spi.RestConsumerFactory;
+import org.apache.camel.spi.annotations.Component;
+import org.apache.camel.support.CamelContextHelper;
+import org.apache.camel.support.RestComponentHelper;
+import org.apache.camel.util.FileUtil;
+import org.apache.camel.util.StringHelper;
+import org.apache.camel.util.URISupport;
+import org.apache.camel.util.UnsafeUriCharactersEncoder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component("servlet")
+public class ServletComponent extends HttpCommonComponent implements RestConsumerFactory, RestApiConsumerFactory {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ServletComponent.class);
+
+ @Metadata(label = "consumer", defaultValue = "CamelServlet",
+ description = "Default name of servlet to use. The default name is CamelServlet.")
+ private String servletName = "CamelServlet";
+ @Metadata(label = "consumer,advanced", description = "To use a custom org.apache.camel.component.servlet.HttpRegistry.")
+ private HttpRegistry httpRegistry;
+ @Metadata(label = "consumer,advanced",
+ description = "Whether to automatic bind multipart/form-data as attachments on the Camel Exchange."
+ + " The options attachmentMultipartBinding=true and disableStreamCache=false cannot work together."
+ + " Remove disableStreamCache to use AttachmentMultipartBinding."
+ + " This is turn off by default as this may require servlet specific configuration to enable this when using Servlet's.")
+ private boolean attachmentMultipartBinding;
+ @Metadata(label = "consumer,advanced",
+ description = "Whitelist of accepted filename extensions for accepting uploaded files."
+ + " Multiple extensions can be separated by comma, such as txt,xml.")
+ private String fileNameExtWhitelist;
+
+ public ServletComponent() {
+ }
+
+ public ServletComponent(Class extends ServletEndpoint> endpointClass) {
+ }
+
+ @Override
+ protected Endpoint createEndpoint(String uri, String remaining, Map parameters) throws Exception {
+ // must extract well known parameters before we create the endpoint
+ Boolean throwExceptionOnFailure = getAndRemoveParameter(parameters, "throwExceptionOnFailure", Boolean.class);
+ Boolean transferException = getAndRemoveParameter(parameters, "transferException", Boolean.class);
+ boolean muteException = getAndRemoveParameter(parameters, "muteException", boolean.class, isMuteException());
+ Boolean bridgeEndpoint = getAndRemoveParameter(parameters, "bridgeEndpoint", Boolean.class);
+ HttpBinding binding = resolveAndRemoveReferenceParameter(parameters, "httpBinding", HttpBinding.class);
+ Boolean matchOnUriPrefix = getAndRemoveParameter(parameters, "matchOnUriPrefix", Boolean.class);
+ String filteredServletName = getAndRemoveParameter(parameters, "servletName", String.class, getServletName());
+ String httpMethodRestrict = getAndRemoveParameter(parameters, "httpMethodRestrict", String.class);
+ HeaderFilterStrategy headerFilterStrategy
+ = resolveAndRemoveReferenceParameter(parameters, "headerFilterStrategy", HeaderFilterStrategy.class);
+ Boolean async = getAndRemoveParameter(parameters, "async", Boolean.class);
+ Boolean filteredAttachmentMultipartBinding
+ = getAndRemoveParameter(parameters, "attachmentMultipartBinding", Boolean.class);
+ Boolean disableStreamCache = getAndRemoveParameter(parameters, "disableStreamCache", Boolean.class);
+
+ if (lenientContextPath()) {
+ // the uri must have a leading slash for the context-path matching to work with servlet, and it can be something people
+ // forget to add and then the servlet consumer cannot match the context-path as would have been expected
+ String scheme = StringHelper.before(uri, ":");
+ String after = StringHelper.after(uri, ":");
+ // rebuild uri to have exactly one leading slash
+ while (after.startsWith("/")) {
+ after = after.substring(1);
+ }
+ after = "/" + after;
+ uri = scheme + ":" + after;
+ }
+
+ // restructure uri to be based on the parameters left as we dont want to include the Camel internal options
+ URI httpUri = URISupport.createRemainingURI(new URI(UnsafeUriCharactersEncoder.encodeHttpURI(uri)), parameters);
+
+ ServletEndpoint endpoint = createServletEndpoint(uri, this, httpUri);
+ endpoint.setServletName(filteredServletName);
+ endpoint.setFileNameExtWhitelist(fileNameExtWhitelist);
+ if (async != null) {
+ endpoint.setAsync(async);
+ }
+ if (headerFilterStrategy != null) {
+ endpoint.setHeaderFilterStrategy(headerFilterStrategy);
+ } else {
+ setEndpointHeaderFilterStrategy(endpoint);
+ }
+
+ // prefer to use endpoint configured over component configured
+ if (binding == null) {
+ // fallback to component configured
+ binding = getHttpBinding();
+ }
+ if (binding != null) {
+ endpoint.setHttpBinding(binding);
+ }
+ // should we use an exception for failed error codes?
+ if (throwExceptionOnFailure != null) {
+ endpoint.setThrowExceptionOnFailure(throwExceptionOnFailure);
+ }
+ // should we transfer exception as serialized object
+ if (transferException != null) {
+ endpoint.setTransferException(transferException);
+ }
+ endpoint.setMuteException(muteException);
+ if (bridgeEndpoint != null) {
+ endpoint.setBridgeEndpoint(bridgeEndpoint);
+ }
+ if (matchOnUriPrefix != null) {
+ endpoint.setMatchOnUriPrefix(matchOnUriPrefix);
+ }
+ if (httpMethodRestrict != null) {
+ endpoint.setHttpMethodRestrict(httpMethodRestrict);
+ }
+ if (filteredAttachmentMultipartBinding != null) {
+ endpoint.setAttachmentMultipartBinding(filteredAttachmentMultipartBinding);
+ } else {
+ endpoint.setAttachmentMultipartBinding(isAttachmentMultipartBinding());
+ }
+ if (disableStreamCache != null) {
+ endpoint.setDisableStreamCache(disableStreamCache);
+ }
+
+ // turn off stream caching if in attachment mode
+ if (endpoint.isAttachmentMultipartBinding()) {
+ if (disableStreamCache == null) {
+ // disableStreamCache not explicit configured so we can automatic change it
+ LOG.info("Disabling stream caching as attachmentMultipartBinding is enabled");
+ endpoint.setDisableStreamCache(true);
+ } else if (!disableStreamCache) {
+ throw new IllegalArgumentException(
+ "The options attachmentMultipartBinding=true and disableStreamCache=false cannot work together."
+ + " Remove disableStreamCache to use AttachmentMultipartBinding");
+ }
+ }
+
+ setProperties(endpoint, parameters);
+ return endpoint;
+ }
+
+ /**
+ * Whether defining the context-path is lenient and do not require an exact leading slash.
+ */
+ protected boolean lenientContextPath() {
+ return true;
+ }
+
+ /**
+ * Strategy to create the servlet endpoint.
+ */
+ protected ServletEndpoint createServletEndpoint(String endpointUri, ServletComponent component, URI httpUri)
+ throws Exception {
+ return new ServletEndpoint(endpointUri, component, httpUri);
+ }
+
+ @Override
+ public void connect(HttpConsumer consumer) throws Exception {
+ ServletConsumer sc = (ServletConsumer) consumer;
+ String name = sc.getEndpoint().getServletName();
+ HttpRegistry registry = httpRegistry;
+ if (registry == null) {
+ registry = DefaultHttpRegistry.getHttpRegistry(name);
+ }
+ registry.register(consumer);
+ }
+
+ @Override
+ public void disconnect(HttpConsumer consumer) throws Exception {
+ ServletConsumer sc = (ServletConsumer) consumer;
+ String name = sc.getEndpoint().getServletName();
+ HttpRegistry registry = httpRegistry;
+ if (registry == null) {
+ registry = DefaultHttpRegistry.getHttpRegistry(name);
+ }
+ registry.unregister(consumer);
+ }
+
+ public String getServletName() {
+ return servletName;
+ }
+
+ /**
+ * Default name of servlet to use. The default name is CamelServlet.
+ */
+ public void setServletName(String servletName) {
+ this.servletName = servletName;
+ }
+
+ public HttpRegistry getHttpRegistry() {
+ return httpRegistry;
+ }
+
+ /**
+ * To use a custom org.apache.camel.component.servlet.HttpRegistry.
+ */
+ public void setHttpRegistry(HttpRegistry httpRegistry) {
+ this.httpRegistry = httpRegistry;
+ }
+
+ public boolean isAttachmentMultipartBinding() {
+ return attachmentMultipartBinding;
+ }
+
+ /**
+ * Whether to automatic bind multipart/form-data as attachments on the Camel {@link Exchange}.
+ *
+ * The options attachmentMultipartBinding=true and disableStreamCache=false cannot work together. Remove
+ * disableStreamCache to use AttachmentMultipartBinding.
+ *
+ * This is turn off by default as this may require servlet specific configuration to enable this when using
+ * Servlet's.
+ */
+ public void setAttachmentMultipartBinding(boolean attachmentMultipartBinding) {
+ this.attachmentMultipartBinding = attachmentMultipartBinding;
+ }
+
+ public String getFileNameExtWhitelist() {
+ return fileNameExtWhitelist;
+ }
+
+ /**
+ * Whitelist of accepted filename extensions for accepting uploaded files.
+ *
+ * Multiple extensions can be separated by comma, such as txt,xml.
+ */
+ public void setFileNameExtWhitelist(String fileNameExtWhitelist) {
+ this.fileNameExtWhitelist = fileNameExtWhitelist;
+ }
+
+ @Override
+ public Consumer createConsumer(
+ CamelContext camelContext, Processor processor, String verb, String basePath, String uriTemplate,
+ String consumes, String produces, RestConfiguration configuration, Map parameters)
+ throws Exception {
+ return doCreateConsumer(camelContext, processor, verb, basePath, uriTemplate, configuration,
+ parameters, false);
+ }
+
+ @Override
+ public Consumer createApiConsumer(
+ CamelContext camelContext, Processor processor, String contextPath,
+ RestConfiguration configuration, Map parameters)
+ throws Exception {
+ // reuse the createConsumer method we already have. The api need to use GET and match on uri prefix
+ return doCreateConsumer(camelContext, processor, "GET", contextPath, null, configuration, parameters, true);
+ }
+
+ Consumer doCreateConsumer(
+ CamelContext camelContext, Processor processor, String verb, String basePath, String uriTemplate,
+ RestConfiguration configuration, Map parameters, boolean api)
+ throws Exception {
+
+ String path = basePath;
+ if (uriTemplate != null) {
+ // make sure to avoid double slashes
+ if (uriTemplate.startsWith("/")) {
+ path = path + uriTemplate;
+ } else {
+ path = path + "/" + uriTemplate;
+ }
+ }
+ path = FileUtil.stripLeadingSeparator(path);
+
+ // if no explicit port/host configured, then use port from rest configuration
+ RestConfiguration config = configuration;
+ if (config == null) {
+ config = CamelContextHelper.getRestConfiguration(getCamelContext(), "servlet");
+ }
+
+ Map map = RestComponentHelper.initRestEndpointProperties("servlet", config);
+ boolean cors = config.isEnableCORS();
+ if (cors) {
+ // allow HTTP Options as we want to handle CORS in rest-dsl
+ map.put("optionsEnabled", "true");
+ }
+
+ if (api) {
+ map.put("matchOnUriPrefix", "true");
+ }
+
+ RestComponentHelper.addHttpRestrictParam(map, verb, cors);
+
+ String url = RestComponentHelper.createRestConsumerUrl("servlet", path, map);
+
+ ServletEndpoint endpoint = (ServletEndpoint) camelContext.getEndpoint(url, parameters);
+
+ if (!map.containsKey("httpBinding")) {
+ // use the rest binding, if not using a custom http binding
+ HttpBinding binding = new ServletRestHttpBinding();
+ binding.setHeaderFilterStrategy(endpoint.getHeaderFilterStrategy());
+ binding.setTransferException(endpoint.isTransferException());
+ binding.setMuteException(endpoint.isMuteException());
+ binding.setEagerCheckContentAvailable(endpoint.isEagerCheckContentAvailable());
+ binding.setMapHttpMessageHeaders(endpoint.isMapHttpMessageHeaders());
+ binding.setMapHttpMessageFormUrlEncodedBody(endpoint.isMapHttpMessageFormUrlEncodedBody());
+ endpoint.setHttpBinding(binding);
+ }
+
+ // configure consumer properties
+ Consumer consumer = endpoint.createConsumer(processor);
+ if (config.getConsumerProperties() != null && !config.getConsumerProperties().isEmpty()) {
+ setProperties(camelContext, consumer, config.getConsumerProperties());
+ }
+
+ return consumer;
+ }
+
+ @Override
+ protected void doInit() throws Exception {
+ super.doInit();
+
+ try {
+ RestConfiguration config = CamelContextHelper.getRestConfiguration(getCamelContext(), "servlet");
+
+ // configure additional options on servlet configuration
+ if (config.getComponentProperties() != null && !config.getComponentProperties().isEmpty()) {
+ setProperties(this, config.getComponentProperties());
+ }
+ } catch (IllegalArgumentException e) {
+ // if there's a mismatch between the component and the rest-configuration,
+ // then getRestConfiguration throws IllegalArgumentException which can be
+ // safely ignored as it means there's no special conf for this component.
+ }
+ }
+}
\ No newline at end of file
diff --git a/components/camel-servlet/src/main/java/org/apache/camel/component/servlet/ServletConsumer.java b/components/camel-servlet/src/main/java/org/apache/camel/component/servlet/ServletConsumer.java
new file mode 100644
index 000000000..4b7f4d0d5
--- /dev/null
+++ b/components/camel-servlet/src/main/java/org/apache/camel/component/servlet/ServletConsumer.java
@@ -0,0 +1,32 @@
+/*
+ * 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.component.servlet;
+
+import org.apache.camel.Processor;
+import org.apache.camel.http.common.HttpConsumer;
+
+public class ServletConsumer extends HttpConsumer {
+
+ public ServletConsumer(ServletEndpoint endpoint, Processor processor) {
+ super(endpoint, processor);
+ }
+
+ @Override
+ public ServletEndpoint getEndpoint() {
+ return (ServletEndpoint) super.getEndpoint();
+ }
+}
\ No newline at end of file
diff --git a/components/camel-servlet/src/main/java/org/apache/camel/component/servlet/ServletEndpoint.java b/components/camel-servlet/src/main/java/org/apache/camel/component/servlet/ServletEndpoint.java
new file mode 100644
index 000000000..e12709b43
--- /dev/null
+++ b/components/camel-servlet/src/main/java/org/apache/camel/component/servlet/ServletEndpoint.java
@@ -0,0 +1,173 @@
+/*
+ * 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.component.servlet;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import org.apache.camel.Category;
+import org.apache.camel.Consumer;
+import org.apache.camel.Exchange;
+import org.apache.camel.Processor;
+import org.apache.camel.Producer;
+import org.apache.camel.http.common.DefaultHttpBinding;
+import org.apache.camel.http.common.HttpBinding;
+import org.apache.camel.http.common.HttpCommonEndpoint;
+import org.apache.camel.spi.Metadata;
+import org.apache.camel.spi.UriEndpoint;
+import org.apache.camel.spi.UriParam;
+import org.apache.camel.spi.UriPath;
+
+/**
+ * Serve HTTP requests by a Servlet.
+ */
+@UriEndpoint(firstVersion = "2.0.0", scheme = "servlet", extendsScheme = "http", title = "Servlet",
+ syntax = "servlet:contextPath", consumerOnly = true, category = { Category.HTTP })
+@Metadata(excludeProperties = "httpUri", annotations = {
+ "protocol=http",
+})
+public class ServletEndpoint extends HttpCommonEndpoint {
+
+ private HttpBinding binding;
+
+ @UriPath(label = "consumer")
+ @Metadata(required = true)
+ private String contextPath;
+ @UriParam(label = "consumer", defaultValue = "CamelServlet")
+ private String servletName;
+ @UriParam(label = "consumer,advanced")
+ private boolean attachmentMultipartBinding;
+ @UriParam(label = "consumer,advanced")
+ private String fileNameExtWhitelist;
+
+ public ServletEndpoint() {
+ }
+
+ public ServletEndpoint(String endPointURI, ServletComponent component, URI httpUri) throws URISyntaxException {
+ super(endPointURI, component, httpUri);
+ this.contextPath = httpUri.getPath();
+ }
+
+ @Override
+ public ServletComponent getComponent() {
+ return (ServletComponent) super.getComponent();
+ }
+
+ @Override
+ public HttpBinding getHttpBinding() {
+ // make sure we include servlet variant of the http binding
+ if (this.binding == null) {
+ // is attachment binding enabled?
+ if (isAttachmentMultipartBinding()) {
+ this.binding = new AttachmentHttpBinding();
+ } else {
+ this.binding = new DefaultHttpBinding();
+ }
+ this.binding.setFileNameExtWhitelist(getFileNameExtWhitelist());
+ this.binding.setTransferException(isTransferException());
+ this.binding.setMuteException(isMuteException());
+ this.binding.setLogException(isLogException());
+ if (getComponent() != null) {
+ this.binding.setAllowJavaSerializedObject(getComponent().isAllowJavaSerializedObject());
+ }
+ this.binding.setHeaderFilterStrategy(getHeaderFilterStrategy());
+ this.binding.setEagerCheckContentAvailable(isEagerCheckContentAvailable());
+ this.binding.setMapHttpMessageBody(isMapHttpMessageBody());
+ this.binding.setMapHttpMessageHeaders(isMapHttpMessageHeaders());
+ this.binding.setMapHttpMessageFormUrlEncodedBody(isMapHttpMessageFormUrlEncodedBody());
+ }
+ return this.binding;
+ }
+
+ @Override
+ public void setHttpBinding(HttpBinding binding) {
+ super.setHttpBinding(binding);
+ this.binding = binding;
+ }
+
+ public String getContextPath() {
+ return contextPath;
+ }
+
+ /**
+ * The context-path to use
+ */
+ public void setContextPath(String contextPath) {
+ this.contextPath = contextPath;
+ }
+
+ /**
+ * Name of the servlet to use
+ */
+ public void setServletName(String name) {
+ servletName = name;
+ }
+
+ public String getServletName() {
+ return servletName;
+ }
+
+ public boolean isAttachmentMultipartBinding() {
+ return attachmentMultipartBinding;
+ }
+
+ /**
+ * Whether to automatic bind multipart/form-data as attachments on the Camel {@link Exchange}.
+ *
+ * The options attachmentMultipartBinding=true and disableStreamCache=false cannot work together. Remove
+ * disableStreamCache to use AttachmentMultipartBinding.
+ *
+ * This is turn off by default as this may require servlet specific configuration to enable this when using
+ * Servlet's.
+ */
+ public void setAttachmentMultipartBinding(boolean attachmentMultipartBinding) {
+ this.attachmentMultipartBinding = attachmentMultipartBinding;
+ }
+
+ public String getFileNameExtWhitelist() {
+ return fileNameExtWhitelist;
+ }
+
+ /**
+ * Whitelist of accepted filename extensions for accepting uploaded files.
+ *
+ * Multiple extensions can be separated by comma, such as txt,xml.
+ */
+ public void setFileNameExtWhitelist(String fileNameExtWhitelist) {
+ this.fileNameExtWhitelist = fileNameExtWhitelist;
+ }
+
+ @Override
+ public Producer createProducer() throws Exception {
+ throw new UnsupportedOperationException(
+ "You cannot create producer with servlet endpoint, please consider to use http endpoint.");
+ }
+
+ @Override
+ public Consumer createConsumer(Processor processor) throws Exception {
+ ServletConsumer answer = new ServletConsumer(this, processor);
+ configureConsumer(answer);
+ return answer;
+ }
+
+ @Override
+ public boolean isLenientProperties() {
+ // in contrast to the HttpEndpoint, the ServletEndpoint knows about all it's options on the passed URI
+ return false;
+ }
+
+}
\ No newline at end of file
diff --git a/components/camel-servlet/src/main/java/org/apache/camel/component/servlet/ServletRestHttpBinding.java b/components/camel-servlet/src/main/java/org/apache/camel/component/servlet/ServletRestHttpBinding.java
new file mode 100644
index 000000000..76bcfd953
--- /dev/null
+++ b/components/camel-servlet/src/main/java/org/apache/camel/component/servlet/ServletRestHttpBinding.java
@@ -0,0 +1,56 @@
+/*
+ * 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.component.servlet;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.camel.Message;
+import org.apache.camel.http.common.DefaultHttpBinding;
+
+import static org.apache.camel.http.base.HttpHelper.evalPlaceholders;
+
+public class ServletRestHttpBinding extends DefaultHttpBinding {
+
+ public ServletRestHttpBinding() {
+ }
+
+ @Override
+ protected void populateRequestParameters(HttpServletRequest request, Message message) {
+ super.populateRequestParameters(request, message);
+
+ String path = request.getPathInfo();
+ if (path == null) {
+ return;
+ }
+
+ // in the endpoint the user may have defined rest {} placeholders
+ // so we need to map those placeholders with data from the incoming request context path
+
+ ServletEndpoint endpoint = (ServletEndpoint) message.getExchange().getFromEndpoint();
+ String consumerPath = endpoint.getPath();
+
+ if (useRestMatching(consumerPath)) {
+
+ evalPlaceholders(message.getHeaders(), path, consumerPath);
+ }
+ }
+
+ private boolean useRestMatching(String path) {
+ // only need to do rest matching if using { } placeholders
+ return path.indexOf('{') > -1;
+ }
+}
\ No newline at end of file
diff --git a/components/pom.xml b/components/pom.xml
index 49451bad3..1a02ff402 100644
--- a/components/pom.xml
+++ b/components/pom.xml
@@ -41,7 +41,8 @@
camel-as2camel-asn1camel-asterisk
- camel-atmosphere-websocket
+
+
camel-atomcamel-attachmentscamel-avro
@@ -260,6 +261,7 @@
camel-servicecamel-servicenowcamel-servlet
+ camel-servlet-osgicamel-shirocamel-sjmscamel-sjms2
diff --git a/features/src/main/feature/camel-features.xml b/features/src/main/feature/camel-features.xml
index d6762129b..2133da93b 100644
--- a/features/src/main/feature/camel-features.xml
+++ b/features/src/main/feature/camel-features.xml
@@ -393,12 +393,13 @@
mvn:org.javassist/javassist/${auto-detect-version}mvn:org.apache.camel.karaf/camel-asterisk/${project.version}
-
- camel-servlet
+
+
+
- wrap:mvn:org.atmosphere/atmosphere-runtime/${atmosphere-version}$overwrite=merge&Import-Package=jakarta.servlet;version:="[6,7)",*;resolution:=optional
- mvn:org.apache.camel.karaf/camel-atmosphere-websocket/${project.version}
-
+
+
+
camel-corewrap:mvn:com.apptasticsoftware/rssreader/${auto-detect-version}
@@ -2671,12 +2672,13 @@
mvn:org.apache.camel.karaf/camel-servicenow/${project.version}
+ httpcamel-core
- jakarta-servletmvn:org.apache.camel.karaf/camel-attachments/${project.version}mvn:org.apache.camel.karaf/camel-http-base/${project.version}mvn:org.apache.camel.karaf/camel-http-common/${project.version}mvn:org.apache.camel.karaf/camel-servlet/${project.version}
+ mvn:org.apache.camel.karaf/camel-servlet-osgi/${project.version}camel-core