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 }