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