View Javadoc

1   /*
2    * $Id: HttpMessageReceiver.java 23284 2011-10-31 23:07:39Z 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.ExceptionHelper;
32  import org.mule.config.i18n.Message;
33  import org.mule.config.i18n.MessageFactory;
34  import org.mule.session.DefaultMuleSession;
35  import org.mule.transport.ConnectException;
36  import org.mule.transport.NullPayload;
37  import org.mule.transport.http.i18n.HttpMessages;
38  import org.mule.transport.tcp.TcpConnector;
39  import org.mule.transport.tcp.TcpMessageReceiver;
40  import org.mule.util.MapUtils;
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.Map;
48  import java.util.Set;
49  import java.util.concurrent.TimeUnit;
50  
51  import javax.resource.spi.work.Work;
52  
53  import org.apache.commons.httpclient.Header;
54  import org.apache.commons.httpclient.HttpVersion;
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      public HttpMessageReceiver(Connector connector, FlowConstruct flowConstruct, InboundEndpoint endpoint)
63              throws CreateException
64      {
65          super(connector, flowConstruct, endpoint);
66      }
67  
68      @Override
69      protected Work createWork(Socket socket) throws IOException
70      {
71          return new HttpWorker(socket);
72      }
73  
74      @Override
75      protected void doConnect() throws ConnectException
76      {
77          // If we already have an endpoint listening on this socket don't try and
78          // start another serversocket
79          if (this.shouldConnect())
80          {
81              super.doConnect();
82          }
83      }
84  
85      protected boolean shouldConnect()
86      {
87          StringBuffer requestUri = new StringBuffer(80);
88          requestUri.append(endpoint.getProtocol()).append("://");
89          requestUri.append(endpoint.getEndpointURI().getHost());
90          requestUri.append(':').append(endpoint.getEndpointURI().getPort());
91          requestUri.append('*');
92  
93          MessageReceiver[] receivers = connector.getReceivers(requestUri.toString());
94          for (MessageReceiver receiver : receivers)
95          {
96              if (receiver.isConnected())
97              {
98                  return false;
99              }
100         }
101 
102         return true;
103     }
104 
105     @SuppressWarnings("synthetic-access")
106     protected class HttpWorker implements Work, Expirable
107     {
108         private HttpServerConnection conn;
109         private String remoteClientAddress;
110 
111         public HttpWorker(Socket socket) throws IOException
112         {
113             String encoding = endpoint.getEncoding();
114             if (encoding == null)
115             {
116                 encoding = connector.getMuleContext().getConfiguration().getDefaultEncoding();
117             }
118 
119             conn = new HttpServerConnection(socket, encoding, (HttpConnector) connector);
120 
121             final SocketAddress clientAddress = socket.getRemoteSocketAddress();
122             if (clientAddress != null)
123             {
124                 remoteClientAddress = clientAddress.toString();
125             }
126         }
127 
128         @Override
129         public void expired()
130         {
131             if (conn.isOpen())
132             {
133                 conn.close();
134             }
135         }
136 
137         @Override
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                     try
162                     {
163                         HttpResponse response = processRequest(request);
164                         conn.writeResponse(response);
165                     }
166                     catch (Exception e)
167                     {
168                         MuleEvent response = null;
169                         if (e instanceof MessagingException)
170                         {
171                             MuleEvent event = ((MessagingException) e).getEvent();
172                             response = event.getFlowConstruct().getExceptionListener().handleException(e, event);
173                         }
174                         else
175                         {
176                             getConnector().getMuleContext().getExceptionListener().handleException(e);
177                         }
178 
179 
180                         if (response != null &&
181                             response.getMessage().getExceptionPayload() != null &&
182                             response.getMessage().getExceptionPayload().getException() instanceof MessagingException)
183                         {
184                             e = (Exception) response.getMessage().getExceptionPayload().getException();
185                         }
186                         if (response != null && response.getMessage().getExceptionPayload() == null)
187                         {
188                             conn.writeResponse(transformResponse(response.getMessage(), response));
189                         }
190                         else
191                         {
192                             //MULE-5656 There was custom code here for mapping status codes to exceptions
193                             //I have removed this code and now make an explicit call to the Exception helper,
194                             //but the real fix is to make sure Mule handles this automatically through the
195                             //InboundExceptionDetailsMessageProcessor
196 
197                             //Response code mappings are loaded from META-INF/services/org/mule/config/http-exception-mappings.properties
198                             String temp = ExceptionHelper.getErrorMapping(connector.getProtocol(), e.getClass());
199                             int httpStatus = Integer.valueOf(temp);
200 
201                             if (e instanceof MessagingException)
202                             {
203                                 MuleEvent event = ((MessagingException) e).getEvent();
204                                 conn.writeResponse(buildFailureResponse(event, e.getMessage(),httpStatus));
205                             }
206                             else
207                             {
208                                 conn.writeResponse(buildFailureResponse(request.getRequestLine().getHttpVersion(), httpStatus, e.getMessage()));
209                             }
210                         }
211                         break;
212                     }
213                     finally
214                     {
215                         // Ensure that we drop any monitors
216                         ((HttpConnector) connector).getKeepAliveMonitor().removeExpirable(this);
217 
218                         if (request.getBody() != null)
219                         {
220                             request.getBody().close();
221                         }
222                     }
223                 }
224                 while (conn.isKeepAlive());
225             }
226             catch (Exception e)
227             {
228                 getConnector().getMuleContext().getExceptionListener().handleException(e);
229             }
230             finally
231             {
232                 logger.debug("Closing HTTP connection.");
233 
234                 if (conn.isOpen())
235                 {
236                     conn.close();
237                     conn = null;
238                 }
239             }
240         }
241 
242 
243 
244         protected HttpResponse processRequest(HttpRequest request) throws MuleException, IOException
245         {
246             RequestLine requestLine = request.getRequestLine();
247             String method = requestLine.getMethod();
248 
249             if (method.equals(HttpConstants.METHOD_GET)
250                     || method.equals(HttpConstants.METHOD_HEAD)
251                     || method.equals(HttpConstants.METHOD_POST)
252                     || method.equals(HttpConstants.METHOD_OPTIONS)
253                     || method.equals(HttpConstants.METHOD_PUT)
254                     || method.equals(HttpConstants.METHOD_DELETE)
255                     || method.equals(HttpConstants.METHOD_TRACE)
256                     || method.equals(HttpConstants.METHOD_CONNECT))
257             {
258                 return doRequest(request);
259             }
260             else
261             {
262                 return doBad(requestLine);
263             }
264         }
265 
266         protected HttpResponse doRequest(HttpRequest request) throws IOException, MuleException
267         {
268             sendExpect100(request);
269 
270             MuleMessage message = createMuleMessage(request);
271 
272             String path = message.getInboundProperty(HttpConnector.HTTP_REQUEST_PROPERTY);
273             int i = path.indexOf('?');
274             if (i > -1)
275             {
276                 path = path.substring(0, i);
277             }
278 
279             message.setProperty(HttpConnector.HTTP_REQUEST_PATH_PROPERTY, path, PropertyScope.INBOUND);
280 
281             if (logger.isDebugEnabled())
282             {
283                 logger.debug(message.getInboundProperty(HttpConnector.HTTP_REQUEST_PROPERTY));
284             }
285 
286             // determine if the request path on this request denotes a different receiver
287             MessageReceiver receiver = getTargetReceiver(message, endpoint);
288 
289             HttpResponse response;
290             // the response only needs to be transformed explicitly if
291             // A) the request was not served or B) a null result was returned
292             if (receiver != null)
293             {
294                 message.setProperty(HttpConnector.HTTP_CONTEXT_PATH_PROPERTY,
295                                            HttpConnector.normalizeUrl(receiver.getEndpointURI().getPath()),
296                                            PropertyScope.INBOUND);
297 
298                 preRouteMessage(message);
299                 MuleEvent returnEvent = receiver.routeMessage(message);
300                 MuleMessage returnMessage = returnEvent == null ? null : returnEvent.getMessage();
301 
302                 Object tempResponse;
303                 if (returnMessage != null)
304                 {
305                     tempResponse = returnMessage.getPayload();
306                 }
307                 else
308                 {
309                     tempResponse = NullPayload.getInstance();
310                 }
311                 // This removes the need for users to explicitly adding the response transformer
312                 // ObjectToHttpResponse in their config
313                 if (tempResponse instanceof HttpResponse)
314                 {
315                     response = (HttpResponse) tempResponse;
316                 }
317                 else
318                 {
319                     response = transformResponse(returnMessage, returnEvent);
320                 }
321 
322                 response.setupKeepAliveFromRequestVersion(request.getRequestLine().getHttpVersion());
323                 HttpConnector httpConnector = (HttpConnector) connector;
324                 response.disableKeepAlive(!httpConnector.isKeepAlive());
325 
326                 Header connectionHeader = request.getFirstHeader("Connection");
327                 if (connectionHeader != null)
328                 {
329                     String value = connectionHeader.getValue();
330                     boolean endpointOverride = getEndpointKeepAliveValue(endpoint);
331                     if ("keep-alive".equalsIgnoreCase(value) && endpointOverride)
332                     {
333                         response.setKeepAlive(true);
334 
335                         if (response.getHttpVersion().equals(HttpVersion.HTTP_1_0))
336                         {
337                             connectionHeader = new Header(HttpConstants.HEADER_CONNECTION, "Keep-Alive");
338                             response.setHeader(connectionHeader);
339                         }
340                     }
341                     else if ("close".equalsIgnoreCase(value))
342                     {
343                         response.setKeepAlive(false);
344                     }
345                 }
346             }
347             else
348             {
349                 EndpointURI uri = endpoint.getEndpointURI();
350                 String failedPath = String.format("%s://%s:%d%s",
351                                                   uri.getScheme(), uri.getHost(), uri.getPort(),
352                                                   message.getInboundProperty(HttpConnector.HTTP_REQUEST_PATH_PROPERTY));
353                 response = buildFailureResponse(request.getRequestLine().getHttpVersion(), HttpConstants.SC_NOT_FOUND,
354                                                 HttpMessages.cannotBindToAddress(failedPath).toString());
355             }
356             return response;
357         }
358 
359         /**
360          * Check if endpoint has a keep-alive property configured. Note the translation from
361          * keep-alive in the schema to keepAlive here.
362          */
363         private boolean getEndpointKeepAliveValue(ImmutableEndpoint ep)
364         {
365             String value = (String) ep.getProperty("keepAlive");
366             if (value != null)
367             {
368                 return Boolean.parseBoolean(value);
369             }
370             return true;
371         }
372 
373         protected HttpResponse doOtherValid(RequestLine requestLine, String method) throws MuleException
374         {
375             MuleMessage message = createMuleMessage(null);
376             MuleEvent event = new DefaultMuleEvent(message, (InboundEndpoint) endpoint, new DefaultMuleSession(connector.getMuleContext()));
377             OptimizedRequestContext.unsafeSetEvent(event);
378             HttpResponse response = new HttpResponse();
379             response.setStatusLine(requestLine.getHttpVersion(), HttpConstants.SC_METHOD_NOT_ALLOWED);
380             response.setBody(HttpMessages.methodNotAllowed(method).toString() + HttpConstants.CRLF);
381             return transformResponse(response, event);
382         }
383 
384         protected HttpResponse doBad(RequestLine requestLine) throws MuleException
385         {
386             MuleMessage message = createMuleMessage(null);
387             MuleEvent event = new DefaultMuleEvent(message, (InboundEndpoint) endpoint, new DefaultMuleSession(connector.getMuleContext()));
388             OptimizedRequestContext.unsafeSetEvent(event);
389             HttpResponse response = new HttpResponse();
390             response.setStatusLine(requestLine.getHttpVersion(), HttpConstants.SC_BAD_REQUEST);
391             response.setBody(HttpMessages.malformedSyntax().toString() + HttpConstants.CRLF);
392             return transformResponse(response, event);
393         }
394 
395         private void sendExpect100(HttpRequest request) throws MuleException, IOException
396         {
397             RequestLine requestLine = request.getRequestLine();
398 
399             // respond with status code 100, for Expect handshake
400             // according to rfc 2616 and http 1.1
401             // the processing will continue and the request will be fully
402             // read immediately after
403             HttpVersion requestVersion = requestLine.getHttpVersion();
404             if (HttpVersion.HTTP_1_1.equals(requestVersion))
405             {
406                 Header expectHeader = request.getFirstHeader(HttpConstants.HEADER_EXPECT);
407                 if (expectHeader != null)
408                 {
409                     String expectHeaderValue = expectHeader.getValue();
410                     if (HttpConstants.HEADER_EXPECT_CONTINUE_REQUEST_VALUE.equals(expectHeaderValue))
411                     {
412                         HttpResponse expected = new HttpResponse();
413                         expected.setStatusLine(requestLine.getHttpVersion(), HttpConstants.SC_CONTINUE);
414                         final DefaultMuleEvent event = new DefaultMuleEvent(new DefaultMuleMessage(expected,
415                             connector.getMuleContext()), (InboundEndpoint) endpoint, new DefaultMuleSession(flowConstruct,
416                             connector.getMuleContext()));
417                         RequestContext.setEvent(event);
418                         conn.writeResponse(transformResponse(expected, event));
419                     }
420                 }
421             }
422         }
423 
424         private HttpResponse buildFailureResponse(MuleEvent event, String description, int httpStatusCode) throws MuleException
425         {
426             event.getMessage().setOutboundProperty(HttpConnector.HTTP_STATUS_PROPERTY,httpStatusCode);
427             event.getMessage().setPayload(description);
428             return transformResponse(event.getMessage(), event);
429         }
430 
431         protected HttpResponse buildFailureResponse(HttpVersion version, int statusCode, String description) throws MuleException
432         {
433             HttpResponse response = new HttpResponse();
434             response.setStatusLine(version, statusCode);
435             response.setBody(description);
436             DefaultMuleEvent event = new DefaultMuleEvent(new DefaultMuleMessage(response, connector.getMuleContext()), (InboundEndpoint) endpoint,
437                 new DefaultMuleSession(flowConstruct, connector.getMuleContext()));
438             RequestContext.setEvent(event);
439             // The DefaultResponseTransformer will set the necessary headers
440             return transformResponse(response, event);
441         }
442 
443         protected void preRouteMessage(MuleMessage message) throws MessagingException
444         {
445             message.setProperty(MuleProperties.MULE_REMOTE_CLIENT_ADDRESS, remoteClientAddress, PropertyScope.INBOUND);
446         }
447 
448         @Override
449         public void release()
450         {
451             conn.close();
452             conn = null;
453         }
454     }
455 
456     protected MessageReceiver getTargetReceiver(MuleMessage message, ImmutableEndpoint ep)
457             throws ConnectException
458     {
459         String path = message.getInboundProperty(HttpConnector.HTTP_REQUEST_PROPERTY);
460         int i = path.indexOf('?');
461         if (i > -1)
462         {
463             path = path.substring(0, i);
464         }
465 
466         StringBuffer requestUri = new StringBuffer(80);
467         if (path.indexOf("://") == -1)
468         {
469             requestUri.append(ep.getProtocol()).append("://");
470             requestUri.append(ep.getEndpointURI().getHost());
471             requestUri.append(':').append(ep.getEndpointURI().getPort());
472 
473             if (!"/".equals(path))
474             {
475                 requestUri.append(path);
476             }
477         }
478 
479         String uriStr = requestUri.toString();
480         // first check that there is a receiver on the root address
481         if (logger.isTraceEnabled())
482         {
483             logger.trace("Looking up receiver on connector: " + connector.getName() + " with URI key: "
484                     + requestUri.toString());
485         }
486 
487         MessageReceiver receiver = connector.lookupReceiver(uriStr);
488 
489         // If no receiver on the root and there is a request path, look up the
490         // received based on the root plus request path
491         if (receiver == null && !"/".equals(path))
492         {
493             if (logger.isDebugEnabled())
494             {
495                 logger.debug("Secondary lookup of receiver on connector: " + connector.getName()
496                         + " with URI key: " + requestUri.toString());
497             }
498 
499             receiver = findReceiverByStem(connector.getReceivers(), uriStr);
500 
501             if (receiver == null && logger.isWarnEnabled())
502             {
503                 logger.warn("No receiver found with secondary lookup on connector: " + connector.getName()
504                         + " with URI key: " + requestUri.toString());
505                 logger.warn("Receivers on connector are: "
506                         + MapUtils.toString(connector.getReceivers(), true));
507             }
508         }
509 
510         return receiver;
511     }
512 
513     protected HttpResponse transformResponse(Object response, MuleEvent event) throws MuleException
514     {
515         MuleMessage message;
516         if (response instanceof MuleMessage)
517         {
518             message = (MuleMessage) response;
519         }
520         else
521         {
522             message = new DefaultMuleMessage(response, connector.getMuleContext());
523         }
524         //TODO RM*: Maybe we can have a generic Transformer wrapper rather that using DefaultMuleMessage (or another static utility
525         //class
526         message.applyTransformers(null, defaultResponseTransformers, HttpResponse.class);
527         return (HttpResponse) message.getPayload();
528     }
529 
530     public static MessageReceiver findReceiverByStem(Map<Object, MessageReceiver> receivers, String uriStr)
531     {
532         int match = 0;
533         MessageReceiver receiver = null;
534         for (Map.Entry<Object, MessageReceiver> e : receivers.entrySet())
535         {
536             String key = (String) e.getKey();
537             MessageReceiver candidate = e.getValue();
538             if (uriStr.startsWith(key) && match < key.length())
539             {
540                 match = key.length();
541                 receiver = candidate;
542             }
543         }
544         return receiver;
545     }
546 
547     @Override
548     protected void initializeMessageFactory() throws InitialisationException
549     {
550         HttpMuleMessageFactory factory;
551         try
552         {
553             factory = (HttpMuleMessageFactory) super.createMuleMessageFactory();
554 
555             boolean enableCookies = MapUtils.getBooleanValue(endpoint.getProperties(),
556                 HttpConnector.HTTP_ENABLE_COOKIES_PROPERTY, ((HttpConnector) connector).isEnableCookies());
557             factory.setEnableCookies(enableCookies);
558 
559             String cookieSpec = MapUtils.getString(endpoint.getProperties(),
560                 HttpConnector.HTTP_COOKIE_SPEC_PROPERTY, ((HttpConnector) connector).getCookieSpec());
561             factory.setCookieSpec(cookieSpec);
562 
563             factory.setExchangePattern(endpoint.getExchangePattern());
564 
565             muleMessageFactory = factory;
566         }
567         catch (CreateException ce)
568         {
569             Message message = MessageFactory.createStaticMessage(ce.getMessage());
570             throw new InitialisationException(message, ce, this);
571         }
572     }
573 
574     @Override
575     protected MuleMessage handleUnacceptedFilter(MuleMessage message)
576     {
577         if (logger.isDebugEnabled())
578         {
579             logger.debug("Message request '"
580                          + message.getInboundProperty(HttpConnector.HTTP_REQUEST_PROPERTY)
581                          + "' is being rejected since it does not match the filter on this endpoint: "
582                          + endpoint);
583         }
584         message.setOutboundProperty(HttpConnector.HTTP_STATUS_PROPERTY, String.valueOf(HttpConstants.SC_NOT_ACCEPTABLE));
585         return message;
586     }
587 }