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