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 }