View Javadoc

1   /*
2    * $Id: HttpMessageReceiver.java 19400 2010-09-07 20:20:09Z mike.schilling $
3    * --------------------------------------------------------------------------------------
4    * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
5    *
6    * The software in this package is published under the terms of the CPAL v1.0
7    * license, a copy of which has been included with this distribution in the
8    * LICENSE.txt file.
9    */
10  
11  package org.mule.transport.http;
12  
13  import org.mule.DefaultMuleEvent;
14  import org.mule.DefaultMuleMessage;
15  import org.mule.OptimizedRequestContext;
16  import org.mule.RequestContext;
17  import org.mule.api.MessagingException;
18  import org.mule.api.MuleEvent;
19  import org.mule.api.MuleException;
20  import org.mule.api.MuleMessage;
21  import org.mule.api.config.MuleProperties;
22  import org.mule.api.construct.FlowConstruct;
23  import org.mule.api.endpoint.EndpointURI;
24  import org.mule.api.endpoint.ImmutableEndpoint;
25  import org.mule.api.endpoint.InboundEndpoint;
26  import org.mule.api.lifecycle.CreateException;
27  import org.mule.api.lifecycle.InitialisationException;
28  import org.mule.api.transport.Connector;
29  import org.mule.api.transport.MessageReceiver;
30  import org.mule.api.transport.PropertyScope;
31  import org.mule.config.i18n.Message;
32  import org.mule.config.i18n.MessageFactory;
33  import org.mule.session.DefaultMuleSession;
34  import org.mule.transport.ConnectException;
35  import org.mule.transport.NullPayload;
36  import org.mule.transport.http.i18n.HttpMessages;
37  import org.mule.transport.tcp.TcpConnector;
38  import org.mule.transport.tcp.TcpMessageReceiver;
39  import org.mule.util.MapUtils;
40  import org.mule.util.monitor.Expirable;
41  
42  import java.io.IOException;
43  import java.net.Socket;
44  import java.net.SocketAddress;
45  import java.util.Map;
46  
47  import javax.resource.spi.work.Work;
48  
49  import edu.emory.mathcs.backport.java.util.concurrent.TimeUnit;
50  
51  import org.apache.commons.httpclient.Header;
52  import org.apache.commons.httpclient.HttpVersion;
53  import org.apache.commons.logging.Log;
54  import org.apache.commons.logging.LogFactory;
55  
56  /**
57   * <code>HttpMessageReceiver</code> is a simple http server that can be used to
58   * listen for HTTP requests on a particular port.
59   */
60  public class HttpMessageReceiver extends TcpMessageReceiver
61  {
62      protected final Log logger = LogFactory.getLog(getClass());
63  
64      public HttpMessageReceiver(Connector connector, FlowConstruct flowConstruct, InboundEndpoint endpoint)
65              throws CreateException
66      {
67          super(connector, flowConstruct, endpoint);
68      }
69  
70      @Override
71      protected Work createWork(Socket socket) throws IOException
72      {
73          return new HttpWorker(socket);
74      }
75  
76      @Override
77      protected void doConnect() throws ConnectException
78      {
79          // If we already have an endpoint listening on this socket don't try and
80          // start another serversocket
81          if (this.shouldConnect())
82          {
83              super.doConnect();
84          }
85      }
86  
87      protected boolean shouldConnect()
88      {
89          StringBuffer requestUri = new StringBuffer(80);
90          requestUri.append(endpoint.getProtocol()).append("://");
91          requestUri.append(endpoint.getEndpointURI().getHost());
92          requestUri.append(':').append(endpoint.getEndpointURI().getPort());
93          requestUri.append('*');
94  
95          MessageReceiver[] receivers = connector.getReceivers(requestUri.toString());
96          for (MessageReceiver receiver : receivers)
97          {
98              if (receiver.isConnected())
99              {
100                 return false;
101             }
102         }
103 
104         return true;
105     }
106 
107     @SuppressWarnings("synthetic-access")
108     protected class HttpWorker implements Work, Expirable
109     {
110         private HttpServerConnection conn;
111         private String remoteClientAddress;
112 
113         public HttpWorker(Socket socket) throws IOException
114         {
115             String encoding = endpoint.getEncoding();
116             if (encoding == null)
117             {
118                 encoding = connector.getMuleContext().getConfiguration().getDefaultEncoding();
119             }
120 
121             conn = new HttpServerConnection(socket, encoding, (HttpConnector) connector);
122 
123             final SocketAddress clientAddress = socket.getRemoteSocketAddress();
124             if (clientAddress != null)
125             {
126                 remoteClientAddress = clientAddress.toString();
127             }
128         }
129         
130         public void expired()
131         {
132             if (conn.isOpen())
133             {
134                 conn.close();
135             }
136         }
137 
138         public void run()
139         {
140             long keepAliveTimeout = ((TcpConnector) connector).getKeepAliveTimeout();
141             
142             try
143             {
144                 do
145                 {
146                     conn.setKeepAlive(false);
147                     
148                     // Only add a monitor if the timeout has been set
149                     if (keepAliveTimeout > 0)
150                     {
151                         ((HttpConnector) connector).getKeepAliveMonitor().addExpirable(
152                             keepAliveTimeout, TimeUnit.MILLISECONDS, this);
153                     }
154                     
155                     HttpRequest request = conn.readRequest();
156                     if (request == null)
157                     {
158                         break;
159                     }
160                     
161                     // Ensure that we drop any monitors, we'll add again for the next request
162                     ((HttpConnector) connector).getKeepAliveMonitor().removeExpirable(this);
163                     
164                     conn.writeResponse(processRequest(request));
165                     
166                     if (request.getBody() != null)
167                     {
168                         request.getBody().close();
169                     }
170                 }
171                 while (conn.isKeepAlive());
172             }
173             catch (Exception e)
174             {
175                 getConnector().getMuleContext().getExceptionListener().handleException(e);
176             }
177             finally
178             {
179                 logger.debug("Closing HTTP connection.");
180 
181                 if (conn.isOpen())
182                 {
183                     conn.close();
184                     conn = null;
185                     
186                     // Ensure that we drop any monitors
187                     ((HttpConnector) connector).getKeepAliveMonitor().removeExpirable(this);
188                 }
189             }
190         }
191 
192         protected HttpResponse processRequest(HttpRequest request) throws MuleException, IOException
193         {
194             RequestLine requestLine = request.getRequestLine();
195             String method = requestLine.getMethod();
196 
197             if (method.equals(HttpConstants.METHOD_GET)
198                     || method.equals(HttpConstants.METHOD_HEAD)
199                     || method.equals(HttpConstants.METHOD_POST)
200                     || method.equals(HttpConstants.METHOD_OPTIONS)
201                     || method.equals(HttpConstants.METHOD_PUT)
202                     || method.equals(HttpConstants.METHOD_DELETE)
203                     || method.equals(HttpConstants.METHOD_TRACE)
204                     || method.equals(HttpConstants.METHOD_CONNECT))
205             {
206                 return doRequest(request);
207             }
208             else
209             {
210                 return doBad(requestLine);
211             }
212         }
213 
214         protected HttpResponse doRequest(HttpRequest request) throws IOException, MuleException
215         {
216             sendExpect100(request);
217 
218             MuleMessage message = createMuleMessage(request);
219 
220             String path = message.getInboundProperty(HttpConnector.HTTP_REQUEST_PROPERTY);
221             int i = path.indexOf('?');
222             if (i > -1)
223             {
224                 path = path.substring(0, i);
225             }
226 
227             message.setProperty(HttpConnector.HTTP_REQUEST_PATH_PROPERTY, path, PropertyScope.INBOUND);
228             
229             if (logger.isDebugEnabled())
230             {
231                 logger.debug(message.getInboundProperty(HttpConnector.HTTP_REQUEST_PROPERTY));
232             }
233 
234             // determine if the request path on this request denotes a different receiver
235             MessageReceiver receiver = getTargetReceiver(message, endpoint);
236             
237             HttpResponse response;
238             // the response only needs to be transformed explicitly if
239             // A) the request was not served or B) a null result was returned
240             if (receiver != null)
241             {
242                 message.setProperty(HttpConnector.HTTP_CONTEXT_PATH_PROPERTY,
243                                            HttpConnector.normalizeUrl(receiver.getEndpointURI().getPath()),
244                                            PropertyScope.INBOUND);
245 
246                 preRouteMessage(message);
247                 MuleEvent returnEvent = receiver.routeMessage(message);
248                 MuleMessage returnMessage = returnEvent == null ? null : returnEvent.getMessage();
249 
250                 Object tempResponse;
251                 if (returnMessage != null)
252                 {
253                     tempResponse = returnMessage.getPayload();
254                 }
255                 else
256                 {
257                     tempResponse = NullPayload.getInstance();
258                 }
259                 // This removes the need for users to explicitly adding the response transformer
260                 // ObjectToHttpResponse in their config
261                 if (tempResponse instanceof HttpResponse)
262                 {
263                     response = (HttpResponse) tempResponse;
264                 }
265                 else
266                 {
267                     response = transformResponse(returnMessage, returnEvent);
268                 }
269                 
270                 response.setupKeepAliveFromRequestVersion(request.getRequestLine().getHttpVersion());
271                 HttpConnector httpConnector = (HttpConnector) connector;
272                 response.disableKeepAlive(!httpConnector.isKeepAlive());
273                 
274                 Header connectionHeader = request.getFirstHeader("Connection");
275                 if (connectionHeader != null)
276                 {
277                     String value = connectionHeader.getValue();
278                     boolean endpointOverride = getEndpointKeepAliveValue(endpoint);
279                     if ("keep-alive".equalsIgnoreCase(value) && endpointOverride)
280                     {
281                         response.setKeepAlive(true);
282 
283                         if (response.getHttpVersion().equals(HttpVersion.HTTP_1_0))
284                         {
285                             connectionHeader = new Header(HttpConstants.HEADER_CONNECTION, "Keep-Alive");
286                             response.setHeader(connectionHeader);
287                         }
288                     }
289                     else if ("close".equalsIgnoreCase(value))
290                     {
291                         response.setKeepAlive(false);
292                     } 
293                 }
294             }
295             else
296             {
297                 response = buildFailureResponse(request.getRequestLine(), message);
298             }
299             return response;
300         }
301         
302         /**
303          * Check if endpoint has a keep-alive property configured. Note the translation from
304          * keep-alive in the schema to keepAlive here.
305          */
306         private boolean getEndpointKeepAliveValue(ImmutableEndpoint ep)
307         {
308             String value = (String) ep.getProperty("keepAlive");
309             if (value != null)
310             {
311                 return Boolean.parseBoolean(value);
312             }
313             return true;
314         }
315         
316         protected HttpResponse doOtherValid(RequestLine requestLine, String method) throws MuleException
317         {
318             MuleMessage message = createMuleMessage(null);
319             MuleEvent event = new DefaultMuleEvent(message, endpoint, new DefaultMuleSession(connector.getMuleContext()));
320             OptimizedRequestContext.unsafeSetEvent(event);
321             HttpResponse response = new HttpResponse();
322             response.setStatusLine(requestLine.getHttpVersion(), HttpConstants.SC_METHOD_NOT_ALLOWED);
323             response.setBody(HttpMessages.methodNotAllowed(method).toString() + HttpConstants.CRLF);
324             return transformResponse(response, event);
325         }
326 
327         protected HttpResponse doBad(RequestLine requestLine) throws MuleException
328         {
329             MuleMessage message = createMuleMessage(null);
330             MuleEvent event = new DefaultMuleEvent(message, endpoint, new DefaultMuleSession(connector.getMuleContext()));
331             OptimizedRequestContext.unsafeSetEvent(event);
332             HttpResponse response = new HttpResponse();
333             response.setStatusLine(requestLine.getHttpVersion(), HttpConstants.SC_BAD_REQUEST);
334             response.setBody(HttpMessages.malformedSyntax().toString() + HttpConstants.CRLF);
335             return transformResponse(response, event);
336         }
337 
338         private void sendExpect100(HttpRequest request) throws MuleException, IOException
339         {
340             RequestLine requestLine = request.getRequestLine();
341             
342             // respond with status code 100, for Expect handshake
343             // according to rfc 2616 and http 1.1
344             // the processing will continue and the request will be fully
345             // read immediately after
346             HttpVersion requestVersion = requestLine.getHttpVersion();
347             if (HttpVersion.HTTP_1_1.equals(requestVersion))
348             {
349                 Header expectHeader = request.getFirstHeader(HttpConstants.HEADER_EXPECT);
350                 if (expectHeader != null)
351                 {
352                     String expectHeaderValue = expectHeader.getValue();
353                     if (HttpConstants.HEADER_EXPECT_CONTINUE_REQUEST_VALUE.equals(expectHeaderValue))
354                     {
355                         HttpResponse expected = new HttpResponse();
356                         expected.setStatusLine(requestLine.getHttpVersion(), HttpConstants.SC_CONTINUE);
357                         final DefaultMuleEvent event = new DefaultMuleEvent(new DefaultMuleMessage(expected,
358                             connector.getMuleContext()), endpoint, new DefaultMuleSession(flowConstruct,
359                             connector.getMuleContext()));
360                         RequestContext.setEvent(event);
361                         conn.writeResponse(transformResponse(expected, event));
362                     }
363                 }
364             }
365         }
366 
367         protected HttpResponse buildFailureResponse(RequestLine requestLine, MuleMessage message) throws MuleException
368         {
369             EndpointURI uri = endpoint.getEndpointURI();
370             String failedPath = String.format("%s://%s:%d%s",
371                                               uri.getScheme(), uri.getHost(), uri.getPort(),
372                                               message.getInboundProperty(HttpConnector.HTTP_REQUEST_PATH_PROPERTY));
373 
374             if (logger.isDebugEnabled())
375             {
376                 logger.debug("Failed to bind to " + failedPath);
377             }
378 
379             HttpResponse response = new HttpResponse();
380             response.setStatusLine(requestLine.getHttpVersion(), HttpConstants.SC_NOT_FOUND);
381             response.setBody(HttpMessages.cannotBindToAddress(failedPath).toString());
382             DefaultMuleEvent event = new DefaultMuleEvent(new DefaultMuleMessage(response, connector.getMuleContext()), endpoint,
383                 new DefaultMuleSession(flowConstruct, connector.getMuleContext()));
384             RequestContext.setEvent(event);
385             // The DefaultResponseTransformer will set the necessary headers
386             return transformResponse(response, event);
387         }
388 
389         protected void preRouteMessage(MuleMessage message) throws MessagingException
390         {
391             message.setProperty(MuleProperties.MULE_REMOTE_CLIENT_ADDRESS, remoteClientAddress, PropertyScope.INBOUND);
392         }
393 
394         public void release()
395         {
396             conn.close();
397             conn = null;
398         }
399     }
400 
401     protected MessageReceiver getTargetReceiver(MuleMessage message, ImmutableEndpoint ep)
402             throws ConnectException
403     {
404         String path = message.getInboundProperty(HttpConnector.HTTP_REQUEST_PROPERTY);
405         int i = path.indexOf('?');
406         if (i > -1)
407         {
408             path = path.substring(0, i);
409         }
410 
411         StringBuffer requestUri = new StringBuffer(80);
412         if (path.indexOf("://") == -1)
413         {
414             requestUri.append(ep.getProtocol()).append("://");
415             requestUri.append(ep.getEndpointURI().getHost());
416             requestUri.append(':').append(ep.getEndpointURI().getPort());
417             
418             if (!"/".equals(path)) 
419             {
420                 requestUri.append(path);
421             }
422         }
423         
424         String uriStr = requestUri.toString();
425         // first check that there is a receiver on the root address
426         if (logger.isTraceEnabled())
427         {
428             logger.trace("Looking up receiver on connector: " + connector.getName() + " with URI key: "
429                     + requestUri.toString());
430         }
431 
432         MessageReceiver receiver = connector.lookupReceiver(uriStr);
433 
434         // If no receiver on the root and there is a request path, look up the
435         // received based on the root plus request path
436         if (receiver == null && !"/".equals(path))
437         {
438             if (logger.isDebugEnabled())
439             {
440                 logger.debug("Secondary lookup of receiver on connector: " + connector.getName()
441                         + " with URI key: " + requestUri.toString());
442             }
443 
444             receiver = findReceiverByStem(connector.getReceivers(), uriStr);
445             
446             if (receiver == null && logger.isWarnEnabled())
447             {
448                 logger.warn("No receiver found with secondary lookup on connector: " + connector.getName()
449                         + " with URI key: " + requestUri.toString());
450                 logger.warn("Receivers on connector are: "
451                         + MapUtils.toString(connector.getReceivers(), true));
452             }
453         }
454 
455         return receiver;
456     }
457 
458     protected HttpResponse transformResponse(Object response, MuleEvent event) throws MuleException
459     {
460         MuleMessage message;
461         if (response instanceof MuleMessage)
462         {
463             message = (MuleMessage) response;
464         }
465         else
466         {
467             message = new DefaultMuleMessage(response, connector.getMuleContext());
468         }
469         //TODO RM*: Maybe we can have a generic Transformer wrapper rather that using DefaultMuleMessage (or another static utility
470         //class
471         message.applyTransformers(null, defaultResponseTransformers, HttpResponse.class);
472         return (HttpResponse) message.getPayload();
473     }
474 
475     public static MessageReceiver findReceiverByStem(Map<Object, MessageReceiver> receivers, String uriStr)
476     {
477         int match = 0;
478         MessageReceiver receiver = null;
479         for (Map.Entry<Object, MessageReceiver> e : receivers.entrySet())
480         {
481             String key = (String) e.getKey();
482             MessageReceiver candidate = e.getValue();
483             if (uriStr.startsWith(key) && match < key.length())
484             {
485                 match = key.length();
486                 receiver = candidate;
487             }
488         }
489         return receiver;
490     }
491     
492     @Override
493     protected void initializeMessageFactory() throws InitialisationException
494     {
495         HttpMuleMessageFactory factory;
496         try
497         {
498             factory = (HttpMuleMessageFactory) super.createMuleMessageFactory();
499 
500             boolean enableCookies = MapUtils.getBooleanValue(endpoint.getProperties(),
501                 HttpConnector.HTTP_ENABLE_COOKIES_PROPERTY, ((HttpConnector) connector).isEnableCookies());
502             factory.setEnableCookies(enableCookies);
503 
504             String cookieSpec = MapUtils.getString(endpoint.getProperties(),
505                 HttpConnector.HTTP_COOKIE_SPEC_PROPERTY, ((HttpConnector) connector).getCookieSpec());
506             factory.setCookieSpec(cookieSpec);
507             
508             factory.setExchangePattern(endpoint.getExchangePattern());
509 
510             muleMessageFactory = factory;
511         }
512         catch (CreateException ce)
513         {
514             Message message = MessageFactory.createStaticMessage(ce.getMessage());
515             throw new InitialisationException(message, ce, this);
516         }
517     }
518     
519     @Override
520     protected MuleMessage handleUnacceptedFilter(MuleMessage message)
521     {
522         if (logger.isDebugEnabled())
523         {
524             logger.debug("Message request '"
525                          + message.getInboundProperty(HttpConnector.HTTP_REQUEST_PROPERTY)
526                          + "' is being rejected since it does not match the filter on this endpoint: "
527                          + endpoint);
528         }
529         message.setOutboundProperty(HttpConnector.HTTP_STATUS_PROPERTY, String.valueOf(HttpConstants.SC_NOT_ACCEPTABLE));
530         return message;
531     }
532 }