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.ArrayList; 021 import java.util.HashMap; 022 import java.util.List; 023 import java.util.Map; 024 025 import org.apache.camel.Endpoint; 026 import org.apache.camel.RuntimeCamelException; 027 import org.apache.camel.component.http.CamelServlet; 028 import org.apache.camel.component.http.HttpComponent; 029 import org.apache.camel.component.http.HttpConsumer; 030 import org.apache.camel.component.http.HttpEndpoint; 031 import org.apache.camel.util.CamelContextHelper; 032 import org.apache.camel.util.CastUtils; 033 import org.apache.camel.util.IntrospectionSupport; 034 import org.apache.camel.util.URISupport; 035 import org.apache.camel.util.UnsafeUriCharactersEncoder; 036 import org.apache.commons.logging.Log; 037 import org.apache.commons.logging.LogFactory; 038 import org.mortbay.component.LifeCycle; 039 import org.mortbay.jetty.Connector; 040 import org.mortbay.jetty.Handler; 041 import org.mortbay.jetty.Server; 042 import org.mortbay.jetty.client.Address; 043 import org.mortbay.jetty.client.HttpClient; 044 import org.mortbay.jetty.handler.ContextHandlerCollection; 045 import org.mortbay.jetty.nio.SelectChannelConnector; 046 import org.mortbay.jetty.security.SslSocketConnector; 047 import org.mortbay.jetty.servlet.Context; 048 import org.mortbay.jetty.servlet.ServletHolder; 049 import org.mortbay.jetty.servlet.SessionHandler; 050 import org.mortbay.thread.QueuedThreadPool; 051 import org.mortbay.thread.ThreadPool; 052 053 /** 054 * An HttpComponent which starts an embedded Jetty for to handle consuming from 055 * the http endpoints. 056 * 057 * @version $Revision: 835186 $ 058 */ 059 public class JettyHttpComponent extends HttpComponent { 060 061 protected static final HashMap<String, ConnectorRef> CONNECTORS = new HashMap<String, ConnectorRef>(); 062 063 private static final transient Log LOG = LogFactory.getLog(JettyHttpComponent.class); 064 private static final String JETTY_SSL_KEYSTORE = "jetty.ssl.keystore"; 065 066 protected String sslKeyPassword; 067 protected String sslPassword; 068 protected String sslKeystore; 069 protected Map<Integer, SslSocketConnector> sslSocketConnectors; 070 protected HttpClient httpClient; 071 protected ThreadPool httpClientThreadPool; 072 protected Integer httpClientMinThreads; 073 protected Integer httpClientMaxThreads; 074 075 class ConnectorRef { 076 Server server; 077 Connector connector; 078 CamelServlet servlet; 079 int refCount; 080 081 public ConnectorRef(Server server, Connector connector, CamelServlet servlet) { 082 this.server = server; 083 this.connector = connector; 084 this.servlet = servlet; 085 increment(); 086 } 087 088 public int increment() { 089 return ++refCount; 090 } 091 092 public int decrement() { 093 return --refCount; 094 } 095 } 096 097 098 @Override 099 protected Endpoint createEndpoint(String uri, String remaining, Map<String, Object> parameters) throws Exception { 100 uri = uri.startsWith("jetty:") ? remaining : uri; 101 102 // handlers 103 List<Handler> handlerList = new ArrayList<Handler>(); 104 String handlers = getAndRemoveParameter(parameters, "handlers", String.class); 105 if (handlers != null) { 106 // remove any leading # for reference lookup as we know its a reference lookup 107 handlers = handlers.replaceAll("#", ""); 108 // lookup each individual handler and add it to the list 109 for (String key : handlers.split(",")) { 110 handlerList.add(CamelContextHelper.mandatoryLookup(getCamelContext(), key, Handler.class)); 111 } 112 } 113 114 // configure regular parameters 115 configureParameters(parameters); 116 117 JettyHttpEndpoint result = new JettyHttpEndpoint(this, uri, null); 118 if (httpBinding != null) { 119 result.setBinding(httpBinding); 120 } 121 setEndpointHeaderFilterStrategy(result); 122 if (handlerList.size() > 0) { 123 result.setHandlers(handlerList); 124 } 125 setProperties(result, parameters); 126 127 // configure http client if we have url configuration for it 128 if (IntrospectionSupport.hasProperties(parameters, "httpClient.")) { 129 // configure Jetty http client 130 result.setClient(getHttpClient()); 131 // set additional parameters on http client 132 IntrospectionSupport.setProperties(getHttpClient(), parameters, "httpClient."); 133 // validate that we could resolve all httpClient. parameters as this component is lenient 134 validateParameters(uri, parameters, "httpClient."); 135 } 136 137 // create the http uri after we have configured all the parameters on the camel objects 138 URI httpUri = URISupport.createRemainingURI(new URI(UnsafeUriCharactersEncoder.encode(uri)), 139 CastUtils.cast(parameters)); 140 result.setHttpUri(httpUri); 141 142 return result; 143 } 144 145 /** 146 * Connects the URL specified on the endpoint to the specified processor. 147 */ 148 @Override 149 public void connect(HttpConsumer consumer) throws Exception { 150 // Make sure that there is a connector for the requested endpoint. 151 JettyHttpEndpoint endpoint = (JettyHttpEndpoint)consumer.getEndpoint(); 152 String connectorKey = getConnectorKey(endpoint); 153 154 synchronized (CONNECTORS) { 155 ConnectorRef connectorRef = CONNECTORS.get(connectorKey); 156 if (connectorRef == null) { 157 Connector connector; 158 if ("https".equals(endpoint.getProtocol())) { 159 connector = getSslSocketConnector(endpoint.getPort()); 160 } else { 161 connector = new SelectChannelConnector(); 162 } 163 connector.setPort(endpoint.getPort()); 164 connector.setHost(endpoint.getHttpUri().getHost()); 165 if ("localhost".equalsIgnoreCase(endpoint.getHttpUri().getHost())) { 166 LOG.warn("You use localhost interface! It means that no external connections will be available. Don't you want to use 0.0.0.0 instead (all network interfaces)?"); 167 } 168 Server server = createServer(); 169 server.addConnector(connector); 170 171 connectorRef = new ConnectorRef(server, connector, createServletForConnector(server, connector, endpoint.getHandlers())); 172 connector.start(); 173 174 CONNECTORS.put(connectorKey, connectorRef); 175 176 } else { 177 // ref track the connector 178 connectorRef.increment(); 179 } 180 // check the session support 181 if (endpoint.isSessionSupport()) { 182 enableSessionSupport(connectorRef.server); 183 } 184 connectorRef.servlet.connect(consumer); 185 } 186 } 187 188 private void enableSessionSupport(Server server) throws Exception { 189 Context context = (Context)server.getChildHandlerByClass(Context.class); 190 if (context.getSessionHandler() == null) { 191 SessionHandler sessionHandler = new SessionHandler(); 192 context.setSessionHandler(sessionHandler); 193 if (context.isStarted()) { 194 // restart the context 195 context.stop(); 196 context.start(); 197 } 198 } 199 200 } 201 202 /** 203 * Disconnects the URL specified on the endpoint from the specified processor. 204 */ 205 @Override 206 public void disconnect(HttpConsumer consumer) throws Exception { 207 // If the connector is not needed anymore then stop it 208 HttpEndpoint endpoint = consumer.getEndpoint(); 209 String connectorKey = getConnectorKey(endpoint); 210 211 synchronized (CONNECTORS) { 212 ConnectorRef connectorRef = CONNECTORS.get(connectorKey); 213 if (connectorRef != null) { 214 connectorRef.servlet.disconnect(consumer); 215 if (connectorRef.decrement() == 0) { 216 connectorRef.server.removeConnector(connectorRef.connector); 217 connectorRef.connector.stop(); 218 connectorRef.server.stop(); 219 CONNECTORS.remove(connectorKey); 220 } 221 } 222 } 223 } 224 225 private String getConnectorKey(HttpEndpoint endpoint) { 226 return endpoint.getProtocol() + ":" + endpoint.getHttpUri().getHost() + ":" + endpoint.getPort(); 227 } 228 229 // Properties 230 // ------------------------------------------------------------------------- 231 232 public String getSslKeyPassword() { 233 return sslKeyPassword; 234 } 235 236 public void setSslKeyPassword(String sslKeyPassword) { 237 this.sslKeyPassword = sslKeyPassword; 238 } 239 240 public String getSslPassword() { 241 return sslPassword; 242 } 243 244 public void setSslPassword(String sslPassword) { 245 this.sslPassword = sslPassword; 246 } 247 248 public void setKeystore(String sslKeystore) { 249 this.sslKeystore = sslKeystore; 250 } 251 252 public String getKeystore() { 253 return sslKeystore; 254 } 255 256 public SslSocketConnector getSslSocketConnector(int port) { 257 SslSocketConnector answer = null; 258 if (sslSocketConnectors != null) { 259 answer = sslSocketConnectors.get(port); 260 } 261 if (answer == null) { 262 answer = createSslSocketConnector(); 263 } else { 264 // try the keystore system property as a backup, jetty doesn't seem 265 // to read this property anymore 266 String keystoreProperty = System.getProperty(JETTY_SSL_KEYSTORE); 267 if (keystoreProperty != null) { 268 answer.setKeystore(keystoreProperty); 269 } 270 271 } 272 return answer; 273 } 274 275 public SslSocketConnector createSslSocketConnector() { 276 SslSocketConnector answer = new SslSocketConnector(); 277 // with default null values, jetty ssl system properties 278 // and console will be read by jetty implementation 279 answer.setPassword(sslPassword); 280 answer.setKeyPassword(sslKeyPassword); 281 if (sslKeystore != null) { 282 answer.setKeystore(sslKeystore); 283 } else { 284 // try the keystore system property as a backup, jetty doesn't seem 285 // to read this property anymore 286 String keystoreProperty = System.getProperty(JETTY_SSL_KEYSTORE); 287 if (keystoreProperty != null) { 288 answer.setKeystore(keystoreProperty); 289 } 290 } 291 292 return answer; 293 } 294 295 public void setSslSocketConnectors(Map <Integer, SslSocketConnector> connectors) { 296 sslSocketConnectors = connectors; 297 } 298 299 public synchronized HttpClient getHttpClient() { 300 if (httpClient == null) { 301 httpClient = new HttpClient(); 302 httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL); 303 304 if (System.getProperty("http.proxyHost") != null && System.getProperty("http.proxyPort") != null) { 305 String host = System.getProperty("http.proxyHost"); 306 int port = Integer.parseInt(System.getProperty("http.proxyPort")); 307 if (LOG.isDebugEnabled()) { 308 LOG.debug("Java System Property http.proxyHost and http.proxyPort detected. Using http proxy host: " 309 + host + " port: " + port); 310 } 311 httpClient.setProxy(new Address(host, port)); 312 } 313 314 // use QueueThreadPool as the default bounded is deprecated (see SMXCOMP-157) 315 if (getHttpClientThreadPool() == null) { 316 QueuedThreadPool qtp = new QueuedThreadPool(); 317 if (httpClientMinThreads != null) { 318 qtp.setMinThreads(httpClientMinThreads.intValue()); 319 } 320 if (httpClientMaxThreads != null) { 321 qtp.setMaxThreads(httpClientMaxThreads.intValue()); 322 } 323 try { 324 qtp.start(); 325 } catch (Exception e) { 326 throw new RuntimeCamelException("Error starting JettyHttpClient thread pool: " + qtp, e); 327 } 328 setHttpClientThreadPool(qtp); 329 } 330 httpClient.setThreadPool(getHttpClientThreadPool()); 331 } 332 return httpClient; 333 } 334 335 public void setHttpClient(HttpClient httpClient) { 336 this.httpClient = httpClient; 337 } 338 339 public ThreadPool getHttpClientThreadPool() { 340 return httpClientThreadPool; 341 } 342 343 public void setHttpClientThreadPool(ThreadPool httpClientThreadPool) { 344 this.httpClientThreadPool = httpClientThreadPool; 345 } 346 347 public Integer getHttpClientMinThreads() { 348 return httpClientMinThreads; 349 } 350 351 public void setHttpClientMinThreads(Integer httpClientMinThreads) { 352 this.httpClientMinThreads = httpClientMinThreads; 353 } 354 355 public Integer getHttpClientMaxThreads() { 356 return httpClientMaxThreads; 357 } 358 359 public void setHttpClientMaxThreads(Integer httpClientMaxThreads) { 360 this.httpClientMaxThreads = httpClientMaxThreads; 361 } 362 363 // Implementation methods 364 // ------------------------------------------------------------------------- 365 protected CamelServlet createServletForConnector(Server server, Connector connector, List<Handler> handlers) throws Exception { 366 CamelServlet camelServlet = new CamelServlet(); 367 368 Context context = new Context(server, "/", Context.NO_SECURITY | Context.NO_SESSIONS); 369 context.setConnectorNames(new String[] {connector.getName()}); 370 371 if (handlers != null) { 372 for (Handler handler : handlers) { 373 context.addHandler(handler); 374 } 375 } 376 377 ServletHolder holder = new ServletHolder(); 378 holder.setServlet(camelServlet); 379 context.addServlet(holder, "/*"); 380 connector.start(); 381 context.start(); 382 383 return camelServlet; 384 } 385 386 protected Server createServer() throws Exception { 387 Server server = new Server(); 388 ContextHandlerCollection collection = new ContextHandlerCollection(); 389 collection.setServer(server); 390 server.addHandler(collection); 391 server.start(); 392 return server; 393 } 394 395 @Override 396 protected void doStart() throws Exception { 397 super.doStart(); 398 if (httpClientThreadPool != null && httpClientThreadPool instanceof LifeCycle) { 399 LifeCycle lc = (LifeCycle) httpClientThreadPool; 400 lc.start(); 401 } 402 if (httpClient != null && !httpClient.isStarted()) { 403 httpClient.start(); 404 } 405 } 406 407 @Override 408 protected void doStop() throws Exception { 409 super.doStop(); 410 if (httpClient != null) { 411 httpClient.stop(); 412 } 413 if (httpClientThreadPool != null && httpClientThreadPool instanceof LifeCycle) { 414 LifeCycle lc = (LifeCycle) httpClientThreadPool; 415 lc.stop(); 416 } 417 } 418 }