View Javadoc

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