View Javadoc

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