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