View Javadoc
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  public class HttpMessageReceiver extends TcpMessageReceiver
55  {
56      public HttpMessageReceiver(Connector connector, FlowConstruct flowConstruct, InboundEndpoint endpoint)
57              throws CreateException
58      {
59          super(connector, flowConstruct, endpoint);
60      }
61  
62      @Override
63      protected Work createWork(Socket socket) throws IOException
64      {
65          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          if (this.shouldConnect())
74          {
75              super.doConnect();
76          }
77      }
78  
79      protected boolean shouldConnect()
80      {
81          StringBuffer requestUri = new StringBuffer(80);
82          requestUri.append(endpoint.getProtocol()).append("://");
83          requestUri.append(endpoint.getEndpointURI().getHost());
84          requestUri.append(':').append(endpoint.getEndpointURI().getPort());
85          requestUri.append('*');
86  
87          MessageReceiver[] receivers = connector.getReceivers(requestUri.toString());
88          for (MessageReceiver receiver : receivers)
89          {
90              if (receiver.isConnected())
91              {
92                  return false;
93              }
94          }
95  
96          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         {
107             String encoding = endpoint.getEncoding();
108             if (encoding == null)
109             {
110                 encoding = connector.getMuleContext().getConfiguration().getDefaultEncoding();
111             }
112 
113             conn = new HttpServerConnection(socket, encoding, (HttpConnector) connector);
114 
115             final SocketAddress clientAddress = socket.getRemoteSocketAddress();
116             if (clientAddress != null)
117             {
118                 remoteClientAddress = clientAddress.toString();
119             }
120         }
121 
122         public void expired()
123         {
124             if (conn.isOpen())
125             {
126                 conn.close();
127             }
128         }
129 
130         public void run()
131         {
132             long keepAliveTimeout = ((TcpConnector) connector).getKeepAliveTimeout();
133 
134             try
135             {
136                 do
137                 {
138                     conn.setKeepAlive(false);
139 
140                     // Only add a monitor if the timeout has been set
141                     if (keepAliveTimeout > 0)
142                     {
143                         ((HttpConnector) connector).getKeepAliveMonitor().addExpirable(
144                             keepAliveTimeout, TimeUnit.MILLISECONDS, this);
145                     }
146 
147                     HttpRequest request = conn.readRequest();
148                     if (request == null)
149                     {
150                         break;
151                     }
152 
153                     // Ensure that we drop any monitors, we'll add again for the next request
154                     ((HttpConnector) connector).getKeepAliveMonitor().removeExpirable(this);
155 
156                     conn.writeResponse(processRequest(request));
157 
158                     if (request.getBody() != null)
159                     {
160                         request.getBody().close();
161                     }
162                 }
163                 while (conn.isKeepAlive());
164             }
165             catch (Exception e)
166             {
167                 getConnector().getMuleContext().getExceptionListener().handleException(e);
168             }
169             finally
170             {
171                 logger.debug("Closing HTTP connection.");
172 
173                 if (conn.isOpen())
174                 {
175                     conn.close();
176                     conn = null;
177 
178                     // Ensure that we drop any monitors
179                     ((HttpConnector) connector).getKeepAliveMonitor().removeExpirable(this);
180                 }
181             }
182         }
183 
184         protected HttpResponse processRequest(HttpRequest request) throws MuleException, IOException
185         {
186             RequestLine requestLine = request.getRequestLine();
187             String method = requestLine.getMethod();
188 
189             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                 return doRequest(request);
200             }
201             else
202             {
203                 return doBad(requestLine);
204             }
205         }
206 
207         protected HttpResponse doRequest(HttpRequest request) throws IOException, MuleException
208         {
209             sendExpect100(request);
210 
211             MuleMessage message = createMuleMessage(request);
212 
213             String path = message.getInboundProperty(HttpConnector.HTTP_REQUEST_PROPERTY);
214             int i = path.indexOf('?');
215             if (i > -1)
216             {
217                 path = path.substring(0, i);
218             }
219 
220             message.setProperty(HttpConnector.HTTP_REQUEST_PATH_PROPERTY, path, PropertyScope.INBOUND);
221 
222             if (logger.isDebugEnabled())
223             {
224                 logger.debug(message.getInboundProperty(HttpConnector.HTTP_REQUEST_PROPERTY));
225             }
226 
227             // determine if the request path on this request denotes a different receiver
228             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             if (receiver != null)
234             {
235                 message.setProperty(HttpConnector.HTTP_CONTEXT_PATH_PROPERTY,
236                                            HttpConnector.normalizeUrl(receiver.getEndpointURI().getPath()),
237                                            PropertyScope.INBOUND);
238 
239                 preRouteMessage(message);
240                 MuleEvent returnEvent = receiver.routeMessage(message);
241                 MuleMessage returnMessage = returnEvent == null ? null : returnEvent.getMessage();
242 
243                 Object tempResponse;
244                 if (returnMessage != null)
245                 {
246                     tempResponse = returnMessage.getPayload();
247                 }
248                 else
249                 {
250                     tempResponse = NullPayload.getInstance();
251                 }
252                 // This removes the need for users to explicitly adding the response transformer
253                 // ObjectToHttpResponse in their config
254                 if (tempResponse instanceof HttpResponse)
255                 {
256                     response = (HttpResponse) tempResponse;
257                 }
258                 else
259                 {
260                     response = transformResponse(returnMessage, returnEvent);
261                 }
262 
263                 response.setupKeepAliveFromRequestVersion(request.getRequestLine().getHttpVersion());
264                 HttpConnector httpConnector = (HttpConnector) connector;
265 
266                 Header connectionHeader = request.getFirstHeader("Connection");
267                 boolean endpointOverride = endpoint.getProperty("keepAlive") != null;
268                 boolean endpointKeepAliveValue = getEndpointKeepAliveValue(endpoint);
269 
270                 if (endpointOverride)
271                 {
272                     response.disableKeepAlive(!endpointKeepAliveValue);
273                 }
274                 else
275                 {
276                     response.disableKeepAlive(!httpConnector.isKeepAlive());
277                 }
278 
279                 if (connectionHeader != null)
280                 {
281                     String value = connectionHeader.getValue();
282                     if ("keep-alive".equalsIgnoreCase(value) && endpointKeepAliveValue)
283                     {
284                         response.setKeepAlive(true);
285 
286                         if (response.getHttpVersion().equals(HttpVersion.HTTP_1_0))
287                         {
288                             connectionHeader = new Header(HttpConstants.HEADER_CONNECTION, "Keep-Alive");
289                             response.setHeader(connectionHeader);
290                         }
291                     }
292                     else if ("close".equalsIgnoreCase(value) || !endpointKeepAliveValue)
293                     {
294                         response.setKeepAlive(false);
295                     }
296                 }
297                 else if (request.getRequestLine().getHttpVersion().equals(HttpVersion.HTTP_1_1))
298                 {
299                     response.setKeepAlive(endpointKeepAliveValue);
300                 }
301             }
302             else
303             {
304                 response = buildFailureResponse(request.getRequestLine(), message);
305             }
306             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             String value = (String) ep.getProperty("keepAlive");
316             if (value != null)
317             {
318                 return Boolean.parseBoolean(value);
319             }
320             return true;
321         }
322 
323         protected HttpResponse doOtherValid(RequestLine requestLine, String method) throws MuleException
324         {
325             MuleMessage message = createMuleMessage(null);
326             MuleEvent event = new DefaultMuleEvent(message, endpoint, new DefaultMuleSession(connector.getMuleContext()));
327             OptimizedRequestContext.unsafeSetEvent(event);
328             HttpResponse response = new HttpResponse();
329             response.setStatusLine(requestLine.getHttpVersion(), HttpConstants.SC_METHOD_NOT_ALLOWED);
330             response.setBody(HttpMessages.methodNotAllowed(method).toString() + HttpConstants.CRLF);
331             return transformResponse(response, event);
332         }
333 
334         protected HttpResponse doBad(RequestLine requestLine) throws MuleException
335         {
336             MuleMessage message = createMuleMessage(null);
337             MuleEvent event = new DefaultMuleEvent(message, endpoint, new DefaultMuleSession(connector.getMuleContext()));
338             OptimizedRequestContext.unsafeSetEvent(event);
339             HttpResponse response = new HttpResponse();
340             response.setStatusLine(requestLine.getHttpVersion(), HttpConstants.SC_BAD_REQUEST);
341             response.setBody(HttpMessages.malformedSyntax().toString() + HttpConstants.CRLF);
342             return transformResponse(response, event);
343         }
344 
345         private void sendExpect100(HttpRequest request) throws MuleException, IOException
346         {
347             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             HttpVersion requestVersion = requestLine.getHttpVersion();
354             if (HttpVersion.HTTP_1_1.equals(requestVersion))
355             {
356                 Header expectHeader = request.getFirstHeader(HttpConstants.HEADER_EXPECT);
357                 if (expectHeader != null)
358                 {
359                     String expectHeaderValue = expectHeader.getValue();
360                     if (HttpConstants.HEADER_EXPECT_CONTINUE_REQUEST_VALUE.equals(expectHeaderValue))
361                     {
362                         HttpResponse expected = new HttpResponse();
363                         expected.setStatusLine(requestLine.getHttpVersion(), HttpConstants.SC_CONTINUE);
364                         final DefaultMuleEvent event = new DefaultMuleEvent(new DefaultMuleMessage(expected,
365                             connector.getMuleContext()), endpoint, new DefaultMuleSession(flowConstruct,
366                             connector.getMuleContext()));
367                         RequestContext.setEvent(event);
368                         conn.writeResponse(transformResponse(expected, event));
369                     }
370                 }
371             }
372         }
373 
374         protected HttpResponse buildFailureResponse(RequestLine requestLine, MuleMessage message) throws MuleException
375         {
376             EndpointURI uri = endpoint.getEndpointURI();
377             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             if (logger.isDebugEnabled())
382             {
383                 logger.debug("Failed to bind to " + failedPath);
384             }
385 
386             HttpResponse response = new HttpResponse();
387             response.setStatusLine(requestLine.getHttpVersion(), HttpConstants.SC_NOT_FOUND);
388             response.setBody(HttpMessages.cannotBindToAddress(failedPath).toString());
389             DefaultMuleEvent event = new DefaultMuleEvent(new DefaultMuleMessage(response, connector.getMuleContext()), endpoint,
390                 new DefaultMuleSession(flowConstruct, connector.getMuleContext()));
391             RequestContext.setEvent(event);
392             // The DefaultResponseTransformer will set the necessary headers
393             return transformResponse(response, event);
394         }
395 
396         protected void preRouteMessage(MuleMessage message) throws MessagingException
397         {
398             message.setProperty(MuleProperties.MULE_REMOTE_CLIENT_ADDRESS, remoteClientAddress, PropertyScope.INBOUND);
399         }
400 
401         public void release()
402         {
403             conn.close();
404             conn = null;
405         }
406     }
407 
408     protected MessageReceiver getTargetReceiver(MuleMessage message, ImmutableEndpoint ep)
409             throws ConnectException
410     {
411         String path = message.getInboundProperty(HttpConnector.HTTP_REQUEST_PROPERTY);
412         int i = path.indexOf('?');
413         if (i > -1)
414         {
415             path = path.substring(0, i);
416         }
417 
418         StringBuffer requestUri = new StringBuffer(80);
419         if (path.indexOf("://") == -1)
420         {
421             requestUri.append(ep.getProtocol()).append("://");
422             requestUri.append(ep.getEndpointURI().getHost());
423             requestUri.append(':').append(ep.getEndpointURI().getPort());
424 
425             if (!"/".equals(path))
426             {
427                 requestUri.append(path);
428             }
429         }
430 
431         String uriStr = requestUri.toString();
432         // first check that there is a receiver on the root address
433         if (logger.isTraceEnabled())
434         {
435             logger.trace("Looking up receiver on connector: " + connector.getName() + " with URI key: "
436                     + requestUri.toString());
437         }
438 
439         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         if (receiver == null && !"/".equals(path))
444         {
445             if (logger.isDebugEnabled())
446             {
447                 logger.debug("Secondary lookup of receiver on connector: " + connector.getName()
448                         + " with URI key: " + requestUri.toString());
449             }
450 
451             receiver = findReceiverByStem(connector.getReceivers(), uriStr);
452 
453             if (receiver == null && logger.isWarnEnabled())
454             {
455                 logger.warn("No receiver found with secondary lookup on connector: " + connector.getName()
456                         + " with URI key: " + requestUri.toString());
457                 logger.warn("Receivers on connector are: "
458                         + MapUtils.toString(connector.getReceivers(), true));
459             }
460         }
461 
462         return receiver;
463     }
464 
465     protected HttpResponse transformResponse(Object response, MuleEvent event) throws MuleException
466     {
467         MuleMessage message;
468         if (response instanceof MuleMessage)
469         {
470             message = (MuleMessage) response;
471         }
472         else
473         {
474             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         message.applyTransformers(null, defaultResponseTransformers, HttpResponse.class);
479         return (HttpResponse) message.getPayload();
480     }
481 
482     public static MessageReceiver findReceiverByStem(Map<Object, MessageReceiver> receivers, String uriStr)
483     {
484         int match = 0;
485         MessageReceiver receiver = null;
486         for (Map.Entry<Object, MessageReceiver> e : receivers.entrySet())
487         {
488             String key = (String) e.getKey();
489             MessageReceiver candidate = e.getValue();
490             if (uriStr.startsWith(key) && match < key.length())
491             {
492                 match = key.length();
493                 receiver = candidate;
494             }
495         }
496         return receiver;
497     }
498 
499     @Override
500     protected void initializeMessageFactory() throws InitialisationException
501     {
502         HttpMuleMessageFactory factory;
503         try
504         {
505             factory = (HttpMuleMessageFactory) super.createMuleMessageFactory();
506 
507             boolean enableCookies = MapUtils.getBooleanValue(endpoint.getProperties(),
508                 HttpConnector.HTTP_ENABLE_COOKIES_PROPERTY, ((HttpConnector) connector).isEnableCookies());
509             factory.setEnableCookies(enableCookies);
510 
511             String cookieSpec = MapUtils.getString(endpoint.getProperties(),
512                 HttpConnector.HTTP_COOKIE_SPEC_PROPERTY, ((HttpConnector) connector).getCookieSpec());
513             factory.setCookieSpec(cookieSpec);
514 
515             factory.setExchangePattern(endpoint.getExchangePattern());
516 
517             muleMessageFactory = factory;
518         }
519         catch (CreateException ce)
520         {
521             Message message = MessageFactory.createStaticMessage(ce.getMessage());
522             throw new InitialisationException(message, ce, this);
523         }
524     }
525 
526     @Override
527     protected MuleMessage handleUnacceptedFilter(MuleMessage message)
528     {
529         if (logger.isDebugEnabled())
530         {
531             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         message.setOutboundProperty(HttpConnector.HTTP_STATUS_PROPERTY, String.valueOf(HttpConstants.SC_NOT_ACCEPTABLE));
537         return message;
538     }
539 }