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