View Javadoc

1   /*
2    * $Id: HttpMessageReceiver.java 12117 2008-06-20 09:40:51Z dirk.olmes $
3    * --------------------------------------------------------------------------------------
4    * Copyright (c) MuleSource, Inc.  All rights reserved.  http://www.mulesource.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.DefaultMuleSession;
16  import org.mule.MuleServer;
17  import org.mule.NullSessionHandler;
18  import org.mule.OptimizedRequestContext;
19  import org.mule.RequestContext;
20  import org.mule.api.MessagingException;
21  import org.mule.api.MuleEvent;
22  import org.mule.api.MuleException;
23  import org.mule.api.MuleMessage;
24  import org.mule.api.config.MuleProperties;
25  import org.mule.api.endpoint.EndpointURI;
26  import org.mule.api.endpoint.ImmutableEndpoint;
27  import org.mule.api.endpoint.InboundEndpoint;
28  import org.mule.api.lifecycle.CreateException;
29  import org.mule.api.service.Service;
30  import org.mule.api.transformer.TransformerException;
31  import org.mule.api.transport.Connector;
32  import org.mule.api.transport.MessageAdapter;
33  import org.mule.api.transport.MessageReceiver;
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.ObjectUtils;
41  import org.mule.util.monitor.Expirable;
42  
43  import java.io.IOException;
44  import java.net.Socket;
45  import java.net.SocketAddress;
46  import java.util.HashMap;
47  import java.util.Iterator;
48  import java.util.Map;
49  
50  import javax.resource.spi.work.Work;
51  
52  import edu.emory.mathcs.backport.java.util.concurrent.TimeUnit;
53  
54  import org.apache.commons.httpclient.Cookie;
55  import org.apache.commons.httpclient.Header;
56  import org.apache.commons.httpclient.cookie.MalformedCookieException;
57  import org.apache.commons.logging.Log;
58  import org.apache.commons.logging.LogFactory;
59  
60  /**
61   * <code>HttpMessageReceiver</code> is a simple http server that can be used to
62   * listen for HTTP requests on a particular port.
63   */
64  public class HttpMessageReceiver extends TcpMessageReceiver
65  {
66      protected final Log logger = LogFactory.getLog(getClass());
67  
68      public HttpMessageReceiver(Connector connector, Service service, InboundEndpoint endpoint)
69              throws CreateException
70      {
71          super(connector, service, endpoint);
72      }
73  
74      // @Override
75      protected Work createWork(Socket socket) throws IOException
76      {
77          return new HttpWorker(socket);
78      }
79  
80      // @Override
81      protected void doConnect() throws ConnectException
82      {
83          // If we already have an endpoint listening on this socket don't try and
84          // start another serversocket
85          if (this.shouldConnect())
86          {
87              super.doConnect();
88          }
89      }
90  
91      protected boolean shouldConnect()
92      {
93          StringBuffer requestUri = new StringBuffer(80);
94          requestUri.append(endpoint.getProtocol()).append("://");
95          requestUri.append(endpoint.getEndpointURI().getHost());
96          requestUri.append(':').append(endpoint.getEndpointURI().getPort());
97          requestUri.append('*');
98  
99          MessageReceiver[] receivers = connector.getReceivers(requestUri.toString());
100         for (int i = 0; i < receivers.length; i++)
101         {
102             if (receivers[i].isConnected())
103             {
104                 return false;
105             }
106         }
107 
108         return true;
109     }
110 
111 
112     // @Override
113     protected MuleMessage handleUnacceptedFilter(MuleMessage message)
114     {
115         if (logger.isDebugEnabled())
116         {
117             logger.debug("Message request '" + message.getProperty(HttpConnector.HTTP_REQUEST_PROPERTY)
118                     + "' is being rejected since it does not match the filter on this endpoint: " + endpoint);
119         }
120         message.setProperty(HttpConnector.HTTP_STATUS_PROPERTY, String.valueOf(HttpConstants.SC_NOT_ACCEPTABLE));
121         return message;
122     }
123 
124     protected class HttpWorker implements Work, Expirable
125     {
126         private HttpServerConnection conn;
127         private String cookieSpec;
128         private boolean enableCookies;
129         private String remoteClientAddress;
130 
131         public HttpWorker(Socket socket) throws IOException
132         {
133             String encoding = endpoint.getEncoding();
134             if (encoding == null)
135             {
136                 encoding = MuleServer.getMuleContext().getConfiguration().getDefaultEncoding();
137             }
138 
139             conn = new HttpServerConnection(socket, encoding, (HttpConnector) connector);
140 
141             cookieSpec =
142                     MapUtils.getString(endpoint.getProperties(), HttpConnector.HTTP_COOKIE_SPEC_PROPERTY,
143                             ((HttpConnector) connector).getCookieSpec());
144             enableCookies =
145                     MapUtils.getBooleanValue(endpoint.getProperties(), HttpConnector.HTTP_ENABLE_COOKIES_PROPERTY,
146                             ((HttpConnector) connector).isEnableCookies());
147 
148             final SocketAddress clientAddress = socket.getRemoteSocketAddress();
149             if (clientAddress != null)
150             {
151                 remoteClientAddress = clientAddress.toString();
152             }
153         }
154         
155         public void expired()
156         {
157             if (conn.isOpen())
158             {
159                 conn.close();
160             }
161         }
162 
163         public void run()
164         {
165             long keepAliveTimeout = ((TcpConnector) connector).getKeepAliveTimeout();
166             
167             try
168             {
169                 do
170                 {
171                     conn.setKeepAlive(false);
172                     
173                     // Only add a monitor if the timeout has been set
174                     if(keepAliveTimeout > 0)
175                     {
176                         ((HttpConnector) connector).getKeepAliveMonitor().addExpirable(
177                             keepAliveTimeout, TimeUnit.MILLISECONDS, this);
178                     }
179                     
180                     HttpRequest request = conn.readRequest();
181                     if (request == null)
182                     {
183                         break;
184                     }
185                     
186                     // Ensure that we drop any monitors, we'll add again for the next request
187                     ((HttpConnector) connector).getKeepAliveMonitor().removeExpirable(this);
188                     
189                     conn.writeResponse(processRequest(request));
190                 }
191                 while (conn.isKeepAlive());
192             }
193             catch (Exception e)
194             {
195                 handleException(e);
196             }
197             finally
198             {
199                 logger.info("Closing HTTP connection.");
200 
201                 if (conn.isOpen())
202                 {
203                     conn.close();
204                     conn = null;
205                     
206                     // Ensure that we drop any monitors
207                     ((HttpConnector) connector).getKeepAliveMonitor().removeExpirable(this);
208                 }
209             }
210         }
211 
212         protected HttpResponse processRequest(HttpRequest request) throws MuleException, IOException
213         {
214             RequestLine requestLine = request.getRequestLine();
215             String method = requestLine.getMethod();
216 
217             if (method.equals(HttpConstants.METHOD_HEAD))
218             {
219                 return doHead(requestLine);
220             }
221             else if (method.equals(HttpConstants.METHOD_GET)
222                     || method.equals(HttpConstants.METHOD_POST)
223                     || method.equals(HttpConstants.METHOD_OPTIONS)
224                     || method.equals(HttpConstants.METHOD_PUT)
225                     || method.equals(HttpConstants.METHOD_DELETE)
226                     || method.equals(HttpConstants.METHOD_TRACE)
227                     || method.equals(HttpConstants.METHOD_CONNECT))
228             {
229                 return doRequest(request, requestLine);
230             }
231             else
232             {
233                 return doBad(requestLine);
234             }
235         }
236 
237         protected HttpResponse doHead(RequestLine requestLine) throws MuleException
238         {
239             MuleMessage message = new DefaultMuleMessage(NullPayload.getInstance());
240             MuleEvent event = new DefaultMuleEvent(message, endpoint, new DefaultMuleSession(message, new NullSessionHandler(), connector.getMuleContext()), true);
241             OptimizedRequestContext.unsafeSetEvent(event);
242             HttpResponse response = new HttpResponse();
243             response.setStatusLine(requestLine.getHttpVersion(), HttpConstants.SC_OK);
244             return transformResponse(response);
245         }
246 
247         protected HttpResponse doRequest(HttpRequest request,
248                                          RequestLine requestLine) throws IOException, MuleException
249         {
250             Map headers = parseHeaders(request);
251 
252             // TODO Mule 2.0 generic way to set stream message adapter
253             MessageAdapter adapter = buildStandardAdapter(request, headers);
254 
255             MuleMessage message = new DefaultMuleMessage(adapter);
256 
257             if (logger.isDebugEnabled())
258             {
259                 logger.debug(message.getProperty(HttpConnector.HTTP_REQUEST_PROPERTY));
260             }
261 
262             // determine if the request path on this request denotes a different receiver
263             MessageReceiver receiver = getTargetReceiver(message, endpoint);
264 
265             HttpResponse response;
266             // the response only needs to be transformed explicitly if
267             // A) the request was not served or B) a null result was returned
268             if (receiver != null)
269             {
270                 preRouteMessage(message);
271                 MuleMessage returnMessage = receiver.routeMessage(message, endpoint.isSynchronous(), null);
272 
273                 Object tempResponse;
274                 if (returnMessage != null)
275                 {
276                     tempResponse = returnMessage.getPayload();
277                 }
278                 else
279                 {
280                     tempResponse = NullPayload.getInstance();
281                 }
282                 // This removes the need for users to explicitly adding the response transformer
283                 // ObjectToHttpResponse in their config
284                 if (tempResponse instanceof HttpResponse)
285                 {
286                     response = (HttpResponse)tempResponse;
287                 }
288                 else
289                 {
290                     response = transformResponse(returnMessage);
291                 }
292                 response.disableKeepAlive(!((HttpConnector)connector).isKeepAlive());
293             }
294             else
295             {
296                 response = buildFailureResponse(requestLine, message);
297             }
298             return response;
299         }
300 
301         protected HttpResponse doOtherValid(RequestLine requestLine, String method) throws MuleException
302         {
303             MuleMessage message = new DefaultMuleMessage(NullPayload.getInstance());
304             MuleEvent event = new DefaultMuleEvent(message, endpoint, new DefaultMuleSession(message, new NullSessionHandler(), connector.getMuleContext()), true);
305             OptimizedRequestContext.unsafeSetEvent(event);
306             HttpResponse response = new HttpResponse();
307             response.setStatusLine(requestLine.getHttpVersion(), HttpConstants.SC_METHOD_NOT_ALLOWED);
308             response.setBody(HttpMessages.methodNotAllowed(method).toString() + HttpConstants.CRLF);
309             return transformResponse(response);
310         }
311 
312         protected HttpResponse doBad(RequestLine requestLine) throws MuleException
313         {
314             MuleMessage message = new DefaultMuleMessage(NullPayload.getInstance());
315             MuleEvent event = new DefaultMuleEvent(message, endpoint, new DefaultMuleSession(message, new NullSessionHandler(), connector.getMuleContext()), true);
316             OptimizedRequestContext.unsafeSetEvent(event);
317             HttpResponse response = new HttpResponse();
318             response.setStatusLine(requestLine.getHttpVersion(), HttpConstants.SC_BAD_REQUEST);
319             response.setBody(HttpMessages.malformedSyntax().toString() + HttpConstants.CRLF);
320             return transformResponse(response);
321         }
322 
323         protected MessageAdapter buildStandardAdapter(final HttpRequest request,
324                                                          final Map headers) throws MessagingException, TransformerException, IOException
325         {
326             final RequestLine requestLine = request.getRequestLine();
327 
328             sendExpect100(headers, requestLine);
329 
330             Object body = request.getBody();
331             if (body == null)
332             {
333                 body = requestLine.getUri();
334             }
335 
336             return connector.getMessageAdapter(new Object[]{body, headers});
337         }
338 
339         private void sendExpect100(Map headers, RequestLine requestLine)
340             throws TransformerException, IOException
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             if (HttpConstants.HTTP11.equals(headers.get(HttpConnector.HTTP_VERSION_PROPERTY)))
347             {
348                 // just in case we have something other than String in
349                 // the headers map
350                 String expectHeaderValue = ObjectUtils.toString(
351                         headers.get(HttpConstants.HEADER_EXPECT)).toLowerCase();
352                 if (HttpConstants.HEADER_EXPECT_CONTINUE_REQUEST_VALUE.equals(expectHeaderValue))
353                 {
354                     HttpResponse expected = new HttpResponse();
355                     expected.setStatusLine(requestLine.getHttpVersion(), HttpConstants.SC_CONTINUE);
356                     final DefaultMuleEvent event = new DefaultMuleEvent(new DefaultMuleMessage(expected), endpoint,
357                             new DefaultMuleSession(service, connector.getMuleContext()), true);
358                     RequestContext.setEvent(event);
359                     conn.writeResponse(transformResponse(expected));
360                 }
361             }
362         }
363 
364         protected HttpResponse buildFailureResponse(RequestLine requestLine, MuleMessage message) throws TransformerException
365         {
366             EndpointURI uri = endpoint.getEndpointURI();
367             String failedPath = uri.getScheme() + "://" + uri.getHost() + ":" + uri.getPort()
368                     + getRequestPath(message);
369 
370             if (logger.isDebugEnabled())
371             {
372                 logger.debug("Failed to bind to " + failedPath);
373             }
374 
375             HttpResponse response = new HttpResponse();
376             response.setStatusLine(requestLine.getHttpVersion(), HttpConstants.SC_NOT_FOUND);
377             response.setBody(HttpMessages.cannotBindToAddress(failedPath).toString());
378             RequestContext.setEvent(new DefaultMuleEvent(new DefaultMuleMessage(response), endpoint,
379                     new DefaultMuleSession(service, connector.getMuleContext()), true));
380             // The DefaultResponseTransformer will set the necessary headers
381             return transformResponse(response);
382         }
383 
384         protected Map parseHeaders(HttpRequest request) throws MalformedCookieException
385         {
386             RequestLine requestLine = request.getRequestLine();
387             Map headers = new HashMap();
388 
389             for (Iterator rhi = request.getHeaderIterator(); rhi.hasNext();)
390             {
391                 Header header = (Header)rhi.next();
392                 String headerName = header.getName();
393                 Object headerValue = header.getValue();
394 
395                 // fix Mule headers?
396                 if (headerName.startsWith("X-MULE"))
397                 {
398                     headerName = headerName.substring(2);
399                 }
400                 // Parse cookies?
401                 else if (headerName.equals(HttpConnector.HTTP_COOKIES_PROPERTY))
402                 {
403                     if (enableCookies)
404                     {
405                         Cookie[] cookies = CookieHelper.parseCookies(header, cookieSpec);
406                         if (cookies.length > 0)
407                         {
408                             // yum!
409                             headerValue = cookies;
410                         }
411                         else
412                         {
413                             // bad cookies?!
414                             continue;
415                         }
416                     }
417                     else
418                     {
419                         // no cookies for you!
420                         continue;
421                     }
422                 }
423 
424                 // accept header & value
425                 headers.put(headerName, headerValue);
426             }
427 
428             headers.put(HttpConnector.HTTP_METHOD_PROPERTY, requestLine.getMethod());
429             headers.put(HttpConnector.HTTP_REQUEST_PROPERTY, requestLine.getUri());
430             headers.put(HttpConnector.HTTP_VERSION_PROPERTY, requestLine.getHttpVersion().toString());
431             headers.put(HttpConnector.HTTP_COOKIE_SPEC_PROPERTY, cookieSpec);
432             return headers;
433         }
434 
435         protected void preRouteMessage(MuleMessage message)
436         {
437             message.setProperty(MuleProperties.MULE_REMOTE_CLIENT_ADDRESS, remoteClientAddress);
438         }
439 
440         public void release()
441         {
442             conn.close();
443             conn = null;
444         }
445     }
446 
447     protected String getRequestPath(MuleMessage message)
448     {
449         String path = (String) message.getProperty(HttpConnector.HTTP_REQUEST_PROPERTY);
450         int i = path.indexOf('?');
451         if (i > -1)
452         {
453             path = path.substring(0, i);
454         }
455         return path;
456     }
457 
458     protected MessageReceiver getTargetReceiver(MuleMessage message, ImmutableEndpoint endpoint)
459             throws ConnectException
460     {
461         String path = (String) message.getProperty(HttpConnector.HTTP_REQUEST_PROPERTY);
462         int i = path.indexOf('?');
463         if (i > -1)
464         {
465             path = path.substring(0, i);
466         }
467 
468         StringBuffer requestUri = new StringBuffer(80);
469         if (path.indexOf("://")==-1)
470         {
471             requestUri.append(endpoint.getProtocol()).append("://");
472             requestUri.append(endpoint.getEndpointURI().getHost());
473             requestUri.append(':').append(endpoint.getEndpointURI().getPort());
474         }
475         // first check that there is a receiver on the root address
476         if (logger.isTraceEnabled())
477         {
478             logger.trace("Looking up receiver on connector: " + connector.getName() + " with URI key: "
479                     + requestUri.toString());
480         }
481 
482         MessageReceiver receiver = connector.lookupReceiver(requestUri.toString());
483 
484         // If no receiver on the root and there is a request path, look up the
485         // received based on the root plus request path
486         if (receiver == null && !"/".equals(path))
487         {
488             // remove anything after the last '/'
489             int x = path.lastIndexOf('/');
490             if (x > 1 && path.indexOf('.') > x)
491             {
492                 requestUri.append(path.substring(0, x));
493             }
494             else
495             {
496                 requestUri.append(path);
497             }
498 
499             if (logger.isDebugEnabled())
500             {
501                 logger.debug("Secondary lookup of receiver on connector: " + connector.getName()
502                         + " with URI key: " + requestUri.toString());
503             }
504 
505             // try again
506             String uriStr = requestUri.toString();
507             receiver = connector.lookupReceiver(uriStr);
508 
509             if (receiver == null)
510             {
511 				receiver = findReceiverByStem(connector.getReceivers(), uriStr);
512 			}
513 
514             if (receiver == null && logger.isWarnEnabled())
515             {
516                 logger.warn("No receiver found with secondary lookup on connector: " + connector.getName()
517                         + " with URI key: " + requestUri.toString());
518                 logger.warn("Receivers on connector are: "
519                         + MapUtils.toString(connector.getReceivers(), true));
520             }
521         }
522 
523         return receiver;
524     }
525 
526     protected HttpResponse transformResponse(Object response) throws TransformerException
527     {
528         MuleMessage message;
529         if(response instanceof MuleMessage)
530         {
531             message = (MuleMessage)response;
532         }
533         else
534         {
535             message = new DefaultMuleMessage(response);
536         }
537         //TODO RM*: Maybe we can have a generic Transformer wrapper rather that using DefaultMuleMessage (or another static utility
538         //class
539         message.applyTransformers(connector.getDefaultResponseTransformers(), HttpResponse.class);
540         return (HttpResponse) message.getPayload();
541     }
542 
543     public static MessageReceiver findReceiverByStem(Map receivers, String uriStr)
544     {
545         int match = 0;
546         MessageReceiver receiver = null;
547         for (Iterator itr = receivers.entrySet().iterator(); itr.hasNext();)
548         {
549             Map.Entry e = (Map.Entry)itr.next();
550             String key = (String)e.getKey();
551             MessageReceiver candidate = (MessageReceiver)e.getValue();
552             if (uriStr.startsWith(key) && match < key.length())
553             {
554                 match = key.length();
555                 receiver = candidate;
556             }
557         }
558         return receiver;
559     }
560 
561 }