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