001 /**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.camel.component.jetty;
018
019 import java.io.File;
020 import java.net.URI;
021 import java.util.HashMap;
022 import java.util.List;
023 import java.util.Map;
024
025 import javax.management.MBeanServer;
026
027 import org.apache.camel.CamelContext;
028 import org.apache.camel.Endpoint;
029 import org.apache.camel.RuntimeCamelException;
030 import org.apache.camel.component.http.CamelServlet;
031 import org.apache.camel.component.http.HttpBinding;
032 import org.apache.camel.component.http.HttpComponent;
033 import org.apache.camel.component.http.HttpConsumer;
034 import org.apache.camel.component.http.HttpEndpoint;
035 import org.apache.camel.spi.ManagementAgent;
036 import org.apache.camel.spi.ManagementStrategy;
037 import org.apache.camel.util.CastUtils;
038 import org.apache.camel.util.IntrospectionSupport;
039 import org.apache.camel.util.ObjectHelper;
040 import org.apache.camel.util.URISupport;
041 import org.apache.commons.logging.Log;
042 import org.apache.commons.logging.LogFactory;
043 import org.eclipse.jetty.client.Address;
044 import org.eclipse.jetty.client.HttpClient;
045 import org.eclipse.jetty.jmx.MBeanContainer;
046 import org.eclipse.jetty.server.Connector;
047 import org.eclipse.jetty.server.Handler;
048 import org.eclipse.jetty.server.Server;
049 import org.eclipse.jetty.server.handler.ContextHandlerCollection;
050 import org.eclipse.jetty.server.handler.HandlerCollection;
051 import org.eclipse.jetty.server.handler.HandlerWrapper;
052 import org.eclipse.jetty.server.nio.SelectChannelConnector;
053 import org.eclipse.jetty.server.session.SessionHandler;
054 import org.eclipse.jetty.server.ssl.SslSocketConnector;
055 import org.eclipse.jetty.servlet.FilterHolder;
056 import org.eclipse.jetty.servlet.ServletContextHandler;
057 import org.eclipse.jetty.servlet.ServletHolder;
058 import org.eclipse.jetty.servlets.MultiPartFilter;
059 import org.eclipse.jetty.util.component.LifeCycle;
060 import org.eclipse.jetty.util.thread.QueuedThreadPool;
061 import org.eclipse.jetty.util.thread.ThreadPool;
062
063 /**
064 * An HttpComponent which starts an embedded Jetty for to handle consuming from
065 * the http endpoints.
066 *
067 * @version $Revision: 20445 $
068 */
069 public class JettyHttpComponent extends HttpComponent {
070 public static final String TMP_DIR = "CamelJettyTempDir";
071
072 protected static final HashMap<String, ConnectorRef> CONNECTORS = new HashMap<String, ConnectorRef>();
073
074 private static final transient Log LOG = LogFactory.getLog(JettyHttpComponent.class);
075 private static final String JETTY_SSL_KEYSTORE = "org.eclipse.jetty.ssl.keystore";
076
077 protected String sslKeyPassword;
078 protected String sslPassword;
079 protected String sslKeystore;
080 protected Map<Integer, SslSocketConnector> sslSocketConnectors;
081 protected HttpClient httpClient;
082 protected ThreadPool httpClientThreadPool;
083 protected Integer httpClientMinThreads;
084 protected Integer httpClientMaxThreads;
085 protected MBeanContainer mbContainer;
086 protected boolean enableJmx;
087
088 class ConnectorRef {
089 Server server;
090 Connector connector;
091 CamelServlet servlet;
092 int refCount;
093
094 public ConnectorRef(Server server, Connector connector, CamelServlet servlet) {
095 this.server = server;
096 this.connector = connector;
097 this.servlet = servlet;
098 increment();
099 }
100
101 public int increment() {
102 return ++refCount;
103 }
104
105 public int decrement() {
106 return --refCount;
107 }
108 }
109
110 @Override
111 protected Endpoint createEndpoint(String uri, String remaining, Map<String, Object> parameters) throws Exception {
112 String addressUri = uri.startsWith("jetty:") ? remaining : uri;
113 Map<String, Object> httpClientParameters = new HashMap<String, Object>(parameters);
114
115 // must extract well known parameters before we create the endpoint
116 List<Handler> handlerList = resolveAndRemoveReferenceListParameter(parameters, "handlers", Handler.class);
117 HttpBinding binding = resolveAndRemoveReferenceParameter(parameters, "httpBindingRef", HttpBinding.class);
118 Boolean throwExceptionOnFailure = getAndRemoveParameter(parameters, "throwExceptionOnFailure", Boolean.class);
119 Boolean bridgeEndpoint = getAndRemoveParameter(parameters, "bridgeEndpoint", Boolean.class);
120 Boolean matchOnUriPrefix = getAndRemoveParameter(parameters, "matchOnUriPrefix", Boolean.class);
121 Boolean enableJmx = getAndRemoveParameter(parameters, "enableJmx", Boolean.class);
122
123 // configure http client if we have url configuration for it
124 // http client is only used for jetty http producer (hence not very commonly used)
125 HttpClient client = null;
126 if (IntrospectionSupport.hasProperties(parameters, "httpClient.")) {
127 // set additional parameters on http client
128 // only create client when needed
129 client = getHttpClient();
130 IntrospectionSupport.setProperties(client, parameters, "httpClient.");
131 // validate that we could resolve all httpClient. parameters as this component is lenient
132 validateParameters(uri, parameters, "httpClient.");
133 }
134 // keep the configure parameters for the http client
135 for (String key : parameters.keySet()) {
136 httpClientParameters.remove(key);
137 }
138 URI endpointUri = URISupport.createRemainingURI(new URI(addressUri), CastUtils.cast(httpClientParameters));
139
140 // restructure uri to be based on the parameters left as we dont want to include the Camel internal options
141 URI httpUri = URISupport.createRemainingURI(new URI(addressUri), CastUtils.cast(parameters));
142
143 // create endpoint after all known parameters have been extracted from parameters
144 JettyHttpEndpoint endpoint = new JettyHttpEndpoint(this, endpointUri.toString(), httpUri);
145 setEndpointHeaderFilterStrategy(endpoint);
146
147 if (client != null) {
148 endpoint.setClient(client);
149 }
150 if (handlerList.size() > 0) {
151 endpoint.setHandlers(handlerList);
152 }
153 // prefer to use endpoint configured over component configured
154 if (binding == null) {
155 // fallback to component configured
156 binding = getHttpBinding();
157 }
158 if (binding != null) {
159 endpoint.setBinding(binding);
160 }
161 // should we use an exception for failed error codes?
162 if (throwExceptionOnFailure != null) {
163 endpoint.setThrowExceptionOnFailure(throwExceptionOnFailure);
164 }
165 if (bridgeEndpoint != null) {
166 endpoint.setBridgeEndpoint(bridgeEndpoint);
167 }
168 if (matchOnUriPrefix != null) {
169 endpoint.setMatchOnUriPrefix(matchOnUriPrefix);
170 }
171
172 if (enableJmx != null) {
173 endpoint.setEnableJmx(enableJmx);
174 } else {
175 // set this option based on setting of JettyHttpComponent
176 endpoint.setEnableJmx(isEnableJmx());
177 }
178
179 setProperties(endpoint, parameters);
180 return endpoint;
181 }
182
183 /**
184 * Connects the URL specified on the endpoint to the specified processor.
185 */
186 @Override
187 public void connect(HttpConsumer consumer) throws Exception {
188 // Make sure that there is a connector for the requested endpoint.
189 JettyHttpEndpoint endpoint = (JettyHttpEndpoint)consumer.getEndpoint();
190 String connectorKey = getConnectorKey(endpoint);
191
192 synchronized (CONNECTORS) {
193 ConnectorRef connectorRef = CONNECTORS.get(connectorKey);
194 if (connectorRef == null) {
195 Connector connector;
196 if ("https".equals(endpoint.getProtocol())) {
197 connector = getSslSocketConnector(endpoint.getPort());
198 } else {
199 connector = new SelectChannelConnector();
200 }
201 connector.setPort(endpoint.getPort());
202 connector.setHost(endpoint.getHttpUri().getHost());
203 if ("localhost".equalsIgnoreCase(endpoint.getHttpUri().getHost())) {
204 LOG.warn("You use localhost interface! It means that no external connections will be available."
205 + " Don't you want to use 0.0.0.0 instead (all network interfaces)? " + endpoint);
206 }
207 Server server = createServer();
208 if (endpoint.isEnableJmx()) {
209 enableJmx(server);
210 }
211 server.addConnector(connector);
212
213 connectorRef = new ConnectorRef(server, connector, createServletForConnector(server, connector, endpoint.getHandlers()));
214 // must enable session before we start
215 if (endpoint.isSessionSupport()) {
216 enableSessionSupport(connectorRef.server, connectorKey);
217 }
218 connectorRef.server.start();
219
220 CONNECTORS.put(connectorKey, connectorRef);
221
222 } else {
223 // ref track the connector
224 connectorRef.increment();
225 }
226 // check the session support
227 if (endpoint.isSessionSupport()) {
228 enableSessionSupport(connectorRef.server, connectorKey);
229 }
230 connectorRef.servlet.connect(consumer);
231 }
232 }
233
234 private void enableJmx(Server server) {
235 MBeanContainer containerToRegister = getMbContainer();
236 if (containerToRegister != null) {
237 LOG.info("Jetty JMX Extensions is enabled");
238 server.getContainer().addEventListener(containerToRegister);
239 // Since we may have many Servers running, don't tie the MBeanContainer
240 // to a Server lifecycle or we end up closing it while it is still in use.
241 //server.addBean(mbContainer);
242 }
243 }
244
245 private void enableSessionSupport(Server server, String connectorKey) throws Exception {
246 ServletContextHandler context = (ServletContextHandler)server.getChildHandlerByClass(ServletContextHandler.class);
247 if (context.getSessionHandler() == null) {
248 SessionHandler sessionHandler = new SessionHandler();
249 if (context.isStarted()) {
250 throw new IllegalStateException("Server has already been started. Cannot enabled sessionSupport on " + connectorKey);
251 } else {
252 context.setSessionHandler(sessionHandler);
253 }
254 }
255 }
256
257 /**
258 * Disconnects the URL specified on the endpoint from the specified processor.
259 */
260 @Override
261 public void disconnect(HttpConsumer consumer) throws Exception {
262 // If the connector is not needed anymore then stop it
263 HttpEndpoint endpoint = consumer.getEndpoint();
264 String connectorKey = getConnectorKey(endpoint);
265
266 synchronized (CONNECTORS) {
267 ConnectorRef connectorRef = CONNECTORS.get(connectorKey);
268 if (connectorRef != null) {
269 connectorRef.servlet.disconnect(consumer);
270 if (connectorRef.decrement() == 0) {
271 connectorRef.server.removeConnector(connectorRef.connector);
272 connectorRef.connector.stop();
273 connectorRef.server.stop();
274 CONNECTORS.remove(connectorKey);
275 // Camel controls the lifecycle of these entities so remove the
276 // registered MBeans when Camel is done with the managed objects.
277 if (mbContainer != null) {
278 mbContainer.removeBean(connectorRef.server);
279 mbContainer.removeBean(connectorRef.connector);
280 }
281 }
282 }
283 }
284 }
285
286 private String getConnectorKey(HttpEndpoint endpoint) {
287 return endpoint.getProtocol() + ":" + endpoint.getHttpUri().getHost() + ":" + endpoint.getPort();
288 }
289
290 // Properties
291 // -------------------------------------------------------------------------
292
293 public String getSslKeyPassword() {
294 return sslKeyPassword;
295 }
296
297 public void setSslKeyPassword(String sslKeyPassword) {
298 this.sslKeyPassword = sslKeyPassword;
299 }
300
301 public String getSslPassword() {
302 return sslPassword;
303 }
304
305 public void setSslPassword(String sslPassword) {
306 this.sslPassword = sslPassword;
307 }
308
309 public void setKeystore(String sslKeystore) {
310 this.sslKeystore = sslKeystore;
311 }
312
313 public String getKeystore() {
314 return sslKeystore;
315 }
316
317 public SslSocketConnector getSslSocketConnector(int port) {
318 SslSocketConnector answer = null;
319 if (sslSocketConnectors != null) {
320 answer = sslSocketConnectors.get(port);
321 }
322 if (answer == null) {
323 answer = createSslSocketConnector();
324 } else {
325 // try the keystore system property as a backup, jetty doesn't seem
326 // to read this property anymore
327 String keystoreProperty = System.getProperty(JETTY_SSL_KEYSTORE);
328 if (keystoreProperty != null) {
329 answer.setKeystore(keystoreProperty);
330 }
331
332 }
333 return answer;
334 }
335
336 public SslSocketConnector createSslSocketConnector() {
337 SslSocketConnector answer = new SslSocketConnector();
338 // with default null values, jetty ssl system properties
339 // and console will be read by jetty implementation
340 answer.setPassword(sslPassword);
341 answer.setKeyPassword(sslKeyPassword);
342 if (sslKeystore != null) {
343 answer.setKeystore(sslKeystore);
344 } else {
345 // try the keystore system property as a backup, jetty doesn't seem
346 // to read this property anymore
347 String keystoreProperty = System.getProperty(JETTY_SSL_KEYSTORE);
348 if (keystoreProperty != null) {
349 answer.setKeystore(keystoreProperty);
350 }
351 }
352
353 return answer;
354 }
355
356 public void setSslSocketConnectors(Map <Integer, SslSocketConnector> connectors) {
357 sslSocketConnectors = connectors;
358 }
359
360 public synchronized HttpClient getHttpClient() {
361 if (httpClient == null) {
362 httpClient = new HttpClient();
363 httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
364
365 if (System.getProperty("http.proxyHost") != null && System.getProperty("http.proxyPort") != null) {
366 String host = System.getProperty("http.proxyHost");
367 int port = Integer.parseInt(System.getProperty("http.proxyPort"));
368 if (LOG.isDebugEnabled()) {
369 LOG.debug("Java System Property http.proxyHost and http.proxyPort detected. Using http proxy host: "
370 + host + " port: " + port);
371 }
372 httpClient.setProxy(new Address(host, port));
373 }
374
375 // use QueueThreadPool as the default bounded is deprecated (see SMXCOMP-157)
376 if (getHttpClientThreadPool() == null) {
377 QueuedThreadPool qtp = new QueuedThreadPool();
378 if (httpClientMinThreads != null) {
379 qtp.setMinThreads(httpClientMinThreads.intValue());
380 }
381 if (httpClientMaxThreads != null) {
382 qtp.setMaxThreads(httpClientMaxThreads.intValue());
383 }
384 try {
385 qtp.start();
386 } catch (Exception e) {
387 throw new RuntimeCamelException("Error starting JettyHttpClient thread pool: " + qtp, e);
388 }
389 setHttpClientThreadPool(qtp);
390 }
391 httpClient.setThreadPool(getHttpClientThreadPool());
392 }
393 return httpClient;
394 }
395
396 public void setHttpClient(HttpClient httpClient) {
397 this.httpClient = httpClient;
398 }
399
400 public ThreadPool getHttpClientThreadPool() {
401 return httpClientThreadPool;
402 }
403
404 public void setHttpClientThreadPool(ThreadPool httpClientThreadPool) {
405 this.httpClientThreadPool = httpClientThreadPool;
406 }
407
408 public Integer getHttpClientMinThreads() {
409 return httpClientMinThreads;
410 }
411
412 public void setHttpClientMinThreads(Integer httpClientMinThreads) {
413 this.httpClientMinThreads = httpClientMinThreads;
414 }
415
416 public Integer getHttpClientMaxThreads() {
417 return httpClientMaxThreads;
418 }
419
420 public void setHttpClientMaxThreads(Integer httpClientMaxThreads) {
421 this.httpClientMaxThreads = httpClientMaxThreads;
422 }
423
424 public void setEnableJmx(boolean enableJmx) {
425 this.enableJmx = enableJmx;
426 }
427
428 public boolean isEnableJmx() {
429 return enableJmx;
430 }
431
432 public synchronized MBeanContainer getMbContainer() {
433 // If null, provide the default implementation.
434 if (mbContainer == null) {
435 MBeanServer mbs = null;
436
437 final ManagementStrategy mStrategy = this.getCamelContext().getManagementStrategy();
438 final ManagementAgent mAgent = mStrategy.getManagementAgent();
439 if (mAgent != null) {
440 mbs = mAgent.getMBeanServer();
441 }
442
443 if (mbs != null) {
444 mbContainer = new MBeanContainer(mbs);
445 startMbContainer();
446 } else {
447 LOG.warn("JMX disabled in CamelContext. Jetty JMX extensions will remain disabled.");
448 }
449 }
450
451 return this.mbContainer;
452 }
453
454 public void setMbContainer(MBeanContainer mbContainer) {
455 this.mbContainer = mbContainer;
456 }
457
458 // Implementation methods
459 // -------------------------------------------------------------------------
460 protected CamelServlet createServletForConnector(Server server, Connector connector, List<Handler> handlers) throws Exception {
461 ServletContextHandler context = new ServletContextHandler(server, "/", ServletContextHandler.NO_SECURITY | ServletContextHandler.NO_SESSIONS);
462 context.setConnectorNames(new String[] {connector.getName()});
463
464 if (handlers != null && !handlers.isEmpty()) {
465 for (Handler handler : handlers) {
466 if (handler instanceof HandlerWrapper) {
467 ((HandlerWrapper) handler).setHandler(server.getHandler());
468 server.setHandler(handler);
469 } else {
470 HandlerCollection handlerCollection = new HandlerCollection();
471 handlerCollection.addHandler(server.getHandler());
472 handlerCollection.addHandler(handler);
473 server.setHandler(handlerCollection);
474 }
475 }
476 }
477
478 // use Jetty continuations
479 CamelServlet camelServlet = new CamelContinuationServlet();
480 ServletHolder holder = new ServletHolder();
481 holder.setServlet(camelServlet);
482 CamelContext camelContext = this.getCamelContext();
483 FilterHolder filterHolder = new FilterHolder();
484 filterHolder.setInitParameter("deleteFiles", "true");
485 if (ObjectHelper.isNotEmpty(camelContext.getProperties().get(TMP_DIR))) {
486 File file = new File(camelContext.getProperties().get(TMP_DIR));
487 if (!file.isDirectory()) {
488 throw new RuntimeCamelException("The temp file directory of camel-jetty is not exists, please recheck it with directory name :"
489 + camelContext.getProperties().get(TMP_DIR));
490 }
491 context.setAttribute("javax.servlet.context.tempdir", file);
492 }
493 filterHolder.setFilter(new MultiPartFilter());
494 //add the default MultiPartFilter filter for it
495 context.addFilter(filterHolder, "/*", 0);
496 context.addServlet(holder, "/*");
497
498 return camelServlet;
499 }
500
501 protected Server createServer() throws Exception {
502 Server server = new Server();
503 ContextHandlerCollection collection = new ContextHandlerCollection();
504 server.setHandler(collection);
505 return server;
506 }
507
508 /**
509 * Starts {@link #mbContainer} and registers the container with itself as a managed bean
510 * logging an error if there is a problem starting the container.
511 * Does nothing if {@link #mbContainer} is {@code null}.
512 */
513 protected void startMbContainer() {
514 if (mbContainer != null && !mbContainer.isStarted()) {
515 try {
516 mbContainer.start();
517 // Publish the container itself for consistency with
518 // traditional embedded Jetty configurations.
519 mbContainer.addBean(mbContainer);
520 } catch (Throwable e) {
521 LOG.warn("Could not start Jetty MBeanContainer. Jetty JMX extensions will remain disabled.", e);
522 }
523 }
524 }
525
526 @Override
527 protected void doStart() throws Exception {
528 super.doStart();
529 if (httpClientThreadPool != null && httpClientThreadPool instanceof LifeCycle) {
530 LifeCycle lc = (LifeCycle) httpClientThreadPool;
531 lc.start();
532 }
533 if (httpClient != null && !httpClient.isStarted()) {
534 httpClient.start();
535 }
536
537 startMbContainer();
538 }
539
540 @Override
541 protected void doStop() throws Exception {
542 super.doStop();
543 if (CONNECTORS.size() > 0) {
544 for (ConnectorRef connectorRef : CONNECTORS.values()) {
545 connectorRef.server.removeConnector(connectorRef.connector);
546 connectorRef.connector.stop();
547 connectorRef.server.stop();
548 // Camel controls the lifecycle of these entities so remove the
549 // registered MBeans when Camel is done with the managed objects.
550 if (mbContainer != null) {
551 mbContainer.removeBean(connectorRef.server);
552 mbContainer.removeBean(connectorRef.connector);
553 }
554 }
555 CONNECTORS.clear();
556 }
557 if (httpClient != null) {
558 httpClient.stop();
559 }
560 if (httpClientThreadPool != null && httpClientThreadPool instanceof LifeCycle) {
561 LifeCycle lc = (LifeCycle) httpClientThreadPool;
562 lc.stop();
563 }
564 if (mbContainer != null) {
565 mbContainer.stop();
566 }
567 }
568 }