View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.server;
20  
21  import java.io.IOException;
22  import java.net.InetSocketAddress;
23  import java.nio.ByteBuffer;
24  import java.nio.channels.ClosedChannelException;
25  import java.util.concurrent.atomic.AtomicBoolean;
26  import java.util.concurrent.atomic.AtomicInteger;
27  
28  import javax.servlet.DispatcherType;
29  import javax.servlet.RequestDispatcher;
30  
31  import org.eclipse.jetty.http.HttpField;
32  import org.eclipse.jetty.http.HttpFields;
33  import org.eclipse.jetty.http.HttpGenerator;
34  import org.eclipse.jetty.http.HttpGenerator.ResponseInfo;
35  import org.eclipse.jetty.http.HttpHeader;
36  import org.eclipse.jetty.http.HttpHeaderValue;
37  import org.eclipse.jetty.http.HttpMethod;
38  import org.eclipse.jetty.http.HttpParser;
39  import org.eclipse.jetty.http.HttpStatus;
40  import org.eclipse.jetty.http.HttpURI;
41  import org.eclipse.jetty.http.HttpVersion;
42  import org.eclipse.jetty.http.MimeTypes;
43  import org.eclipse.jetty.io.ByteBufferPool;
44  import org.eclipse.jetty.io.ChannelEndPoint;
45  import org.eclipse.jetty.io.EndPoint;
46  import org.eclipse.jetty.io.EofException;
47  import org.eclipse.jetty.server.HttpChannelState.Next;
48  import org.eclipse.jetty.server.handler.ErrorHandler;
49  import org.eclipse.jetty.util.BlockingCallback;
50  import org.eclipse.jetty.util.Callback;
51  import org.eclipse.jetty.util.StringUtil;
52  import org.eclipse.jetty.util.URIUtil;
53  import org.eclipse.jetty.util.log.Log;
54  import org.eclipse.jetty.util.log.Logger;
55  import org.eclipse.jetty.util.thread.Scheduler;
56  
57  
58  /* ------------------------------------------------------------ */
59  /** HttpChannel.
60   * Represents a single endpoint for HTTP semantic processing.
61   * The HttpChannel is both a HttpParser.RequestHandler, where it passively receives events from
62   * an incoming HTTP request, and a Runnable, where it actively takes control of the request/response
63   * life cycle and calls the application (perhaps suspending and resuming with multiple calls to run).
64   * The HttpChannel signals the switch from passive mode to active mode by returning true to one of the
65   * HttpParser.RequestHandler callbacks.   The completion of the active phase is signalled by a call to
66   * HttpTransport.completed().
67   *
68   */
69  public class HttpChannel<T> implements HttpParser.RequestHandler<T>, Runnable
70  {
71      private static final Logger LOG = Log.getLogger(HttpChannel.class);
72      private static final ThreadLocal<HttpChannel<?>> __currentChannel = new ThreadLocal<>();
73  
74      public static HttpChannel<?> getCurrentHttpChannel()
75      {
76          return __currentChannel.get();
77      }
78  
79      protected static void setCurrentHttpChannel(HttpChannel<?> channel)
80      {
81          __currentChannel.set(channel);
82      }
83  
84      private final AtomicBoolean _committed = new AtomicBoolean();
85      private final AtomicInteger _requests = new AtomicInteger();
86      private final Connector _connector;
87      private final HttpConfiguration _configuration;
88      private final EndPoint _endPoint;
89      private final HttpTransport _transport;
90      private final HttpURI _uri;
91      private final HttpChannelState _state;
92      private final Request _request;
93      private final Response _response;
94      private final BlockingCallback _writeblock=new BlockingCallback();
95      private HttpVersion _version = HttpVersion.HTTP_1_1;
96      private boolean _expect = false;
97      private boolean _expect100Continue = false;
98      private boolean _expect102Processing = false;
99  
100     public HttpChannel(Connector connector, HttpConfiguration configuration, EndPoint endPoint, HttpTransport transport, HttpInput<T> input)
101     {
102         _connector = connector;
103         _configuration = configuration;
104         _endPoint = endPoint;
105         _transport = transport;
106 
107         _uri = new HttpURI(URIUtil.__CHARSET);
108         _state = new HttpChannelState(this);
109         _request = new Request(this, input);
110         _response = new Response(this, new HttpOutput(this));
111     }
112 
113     public HttpChannelState getState()
114     {
115         return _state;
116     }
117 
118     public HttpVersion getHttpVersion()
119     {
120         return _version;
121     }
122 
123     BlockingCallback getWriteBlockingCallback()
124     {
125         return _writeblock;
126     }
127 
128     /**
129      * @return the number of requests handled by this connection
130      */
131     public int getRequests()
132     {
133         return _requests.get();
134     }
135 
136     public Connector getConnector()
137     {
138         return _connector;
139     }
140 
141     public ByteBufferPool getByteBufferPool()
142     {
143         return _connector.getByteBufferPool();
144     }
145 
146     public HttpConfiguration getHttpConfiguration()
147     {
148         return _configuration;
149     }
150 
151     public Server getServer()
152     {
153         return _connector.getServer();
154     }
155 
156     public Request getRequest()
157     {
158         return _request;
159     }
160 
161     public Response getResponse()
162     {
163         return _response;
164     }
165 
166     public EndPoint getEndPoint()
167     {
168         return _endPoint;
169     }
170 
171     public InetSocketAddress getLocalAddress()
172     {
173         return _endPoint.getLocalAddress();
174     }
175 
176     public InetSocketAddress getRemoteAddress()
177     {
178         return _endPoint.getRemoteAddress();
179     }
180 
181     @Override
182     public int getHeaderCacheSize()
183     {
184         return _configuration.getHeaderCacheSize();
185     }
186 
187     /**
188      * If the associated response has the Expect header set to 100 Continue,
189      * then accessing the input stream indicates that the handler/servlet
190      * is ready for the request body and thus a 100 Continue response is sent.
191      *
192      * @throws IOException if the InputStream cannot be created
193      */
194     public void continue100(int available) throws IOException
195     {
196         // If the client is expecting 100 CONTINUE, then send it now.
197         // TODO: consider using an AtomicBoolean ?
198         if (isExpecting100Continue())
199         {
200             _expect100Continue = false;
201 
202             // is content missing?
203             if (available == 0)
204             {
205                 if (_response.isCommitted())
206                     throw new IOException("Committed before 100 Continues");
207 
208                 // TODO: break this dependency with HttpGenerator
209                 boolean committed = sendResponse(HttpGenerator.CONTINUE_100_INFO, null, false);
210                 if (!committed)
211                     throw new IOException("Concurrent commit while trying to send 100-Continue");
212             }
213         }
214     }
215 
216     public void reset()
217     {
218         _committed.set(false);
219         _expect = false;
220         _expect100Continue = false;
221         _expect102Processing = false;
222         _request.recycle();
223         _response.recycle();
224         _uri.clear();
225     }
226 
227     @Override
228     public void run()
229     {
230         handle();
231     }
232 
233     /* ------------------------------------------------------------ */
234     /**
235      * @return True if the channel is ready to continue handling (ie it is not suspended)
236      */
237     public boolean handle()
238     {
239         LOG.debug("{} handle enter", this);
240 
241         setCurrentHttpChannel(this);
242 
243         String threadName = null;
244         if (LOG.isDebugEnabled())
245         {
246             threadName = Thread.currentThread().getName();
247             Thread.currentThread().setName(threadName + " - " + _uri);
248         }
249 
250         // Loop here to handle async request redispatches.
251         // The loop is controlled by the call to async.unhandle in the
252         // finally block below.  Unhandle will return false only if an async dispatch has
253         // already happened when unhandle is called.
254         HttpChannelState.Next next = _state.handling();
255         while (next==Next.CONTINUE && getServer().isRunning())
256         {
257             try
258             {
259                 _request.setHandled(false);
260                 _response.getHttpOutput().reopen();
261 
262                 if (_state.isInitial())
263                 {
264                     _request.setTimeStamp(System.currentTimeMillis());
265                     _request.setDispatcherType(DispatcherType.REQUEST);
266 
267                     for (HttpConfiguration.Customizer customizer : _configuration.getCustomizers())
268                         customizer.customize(getConnector(),_configuration,_request);
269                     getServer().handle(this);
270                 }
271                 else
272                 {
273                     if (_request.getHttpChannelState().isExpired())
274                     {
275                         _request.setDispatcherType(DispatcherType.ERROR);
276                         _request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE,new Integer(500));
277                         _request.setAttribute(RequestDispatcher.ERROR_MESSAGE,"Async Timeout");
278                         _request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI,_request.getRequestURI());
279                         _response.setStatusWithReason(500,"Async Timeout");
280                     }
281                     else
282                         _request.setDispatcherType(DispatcherType.ASYNC);
283                     getServer().handleAsync(this);
284                 }
285             }
286             catch (Error e)
287             {
288                 if ("ContinuationThrowable".equals(e.getClass().getSimpleName()))
289                     LOG.ignore(e);
290                 else
291                     throw e;
292             }
293             catch (Exception e)
294             {
295                 if (e instanceof EofException)
296                     LOG.debug(e);
297                 else
298                     LOG.warn(String.valueOf(_uri), e);
299                 _state.error(e);
300                 _request.setHandled(true);
301                 handleException(e);
302             }
303             finally
304             {
305                 next = _state.unhandle();
306             }
307         }
308 
309         if (threadName != null && LOG.isDebugEnabled())
310             Thread.currentThread().setName(threadName);
311         setCurrentHttpChannel(null);
312 
313         if (next==Next.COMPLETE)
314         {
315             try
316             {
317                 _state.completed();
318 
319                 if (!_response.isCommitted() && !_request.isHandled())
320                     _response.sendError(404);
321                 else
322                     // Complete generating the response
323                     _response.closeOutput();
324             }
325             catch(EofException|ClosedChannelException e)
326             {
327                 LOG.debug(e);
328             }
329             catch(Exception e)
330             {
331                 LOG.warn("complete failed",e);
332             }
333             finally
334             {
335                 _request.setHandled(true);
336                 _transport.completed();
337             }
338         }
339 
340         LOG.debug("{} handle exit, result {}", this, next);
341 
342         return next!=Next.WAIT;
343     }
344 
345     /**
346      * <p>Sends an error 500, performing a special logic to detect whether the request is suspended,
347      * to avoid concurrent writes from the application.</p>
348      * <p>It may happen that the application suspends, and then throws an exception, while an application
349      * spawned thread writes the response content; in such case, we attempt to commit the error directly
350      * bypassing the {@link ErrorHandler} mechanisms and the response OutputStream.</p>
351      *
352      * @param x the Throwable that caused the problem
353      */
354     protected void handleException(Throwable x)
355     {
356         try
357         {
358             if (_state.isSuspended())
359             {
360                 HttpFields fields = new HttpFields();
361                 ResponseInfo info = new ResponseInfo(_request.getHttpVersion(), fields, 0, HttpStatus.INTERNAL_SERVER_ERROR_500, null, _request.isHead());
362                 boolean committed = sendResponse(info, null, true);
363                 if (!committed)
364                     LOG.warn("Could not send response error 500: "+x);
365             }
366             else if (isCommitted())
367             {
368                 if (!(x instanceof EofException))
369                     LOG.warn("Could not send response error 500: "+x);
370             }
371             else
372             {
373                 _request.setAttribute(RequestDispatcher.ERROR_EXCEPTION,x);
374                 _request.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE,x.getClass());
375                 _response.sendError(500, x.getMessage());
376             }
377         }
378         catch (IOException e)
379         {
380             // We tried our best, just log
381             LOG.debug("Could not commit response error 500", e);
382         }
383     }
384 
385     public boolean isExpecting100Continue()
386     {
387         return _expect100Continue;
388     }
389 
390     public boolean isExpecting102Processing()
391     {
392         return _expect102Processing;
393     }
394 
395     @Override
396     public String toString()
397     {
398         return String.format("%s@%x{r=%s,a=%s,uri=%s}",
399                 getClass().getSimpleName(),
400                 hashCode(),
401                 _requests,
402                 _state.getState(),
403                 _state.getState()==HttpChannelState.State.IDLE?"-":_request.getRequestURI()
404             );
405     }
406 
407     @Override
408     public boolean startRequest(HttpMethod httpMethod, String method, ByteBuffer uri, HttpVersion version)
409     {
410         _expect = false;
411         _expect100Continue = false;
412         _expect102Processing = false;
413 
414         if (_request.getTimeStamp() == 0)
415             _request.setTimeStamp(System.currentTimeMillis());
416         _request.setMethod(httpMethod, method);
417 
418         if (httpMethod == HttpMethod.CONNECT)
419             _uri.parseConnect(uri.array(),uri.arrayOffset()+uri.position(),uri.remaining());
420         else
421             _uri.parse(uri.array(),uri.arrayOffset()+uri.position(),uri.remaining());
422         _request.setUri(_uri);
423 
424         String path;
425         try
426         {
427             path = _uri.getDecodedPath();
428         }
429         catch (Exception e)
430         {
431             LOG.warn("Failed UTF-8 decode for request path, trying ISO-8859-1");
432             LOG.ignore(e);
433             path = _uri.getDecodedPath(StringUtil.__ISO_8859_1);
434         }
435         
436         String info = URIUtil.canonicalPath(path);
437 
438         if (info == null)
439         {
440             if( path==null && _uri.getScheme()!=null &&_uri.getHost()!=null)
441             {
442                 info = "/";
443                 _request.setRequestURI("");
444             }
445             else
446             {
447                 badMessage(400,null);
448                 return true;
449             }
450         }
451         _request.setPathInfo(info);
452         _version = version == null ? HttpVersion.HTTP_0_9 : version;
453         _request.setHttpVersion(_version);
454 
455         return false;
456     }
457 
458     @Override
459     public boolean parsedHeader(HttpField field)
460     {
461         HttpHeader header=field.getHeader();
462         String value=field.getValue();
463         if (value == null)
464             value = "";
465         if (header != null)
466         {
467             switch (header)
468             {
469                 case EXPECT:
470                     if (_version.getVersion()>=HttpVersion.HTTP_1_1.getVersion())
471                     {
472                         HttpHeaderValue expect = HttpHeaderValue.CACHE.get(value);
473                         switch (expect == null ? HttpHeaderValue.UNKNOWN : expect)
474                         {
475                             case CONTINUE:
476                                 _expect100Continue = true;
477                                 break;
478 
479                             case PROCESSING:
480                                 _expect102Processing = true;
481                                 break;
482 
483                             default:
484                                 String[] values = value.split(",");
485                                 for (int i = 0; values != null && i < values.length; i++)
486                                 {
487                                     expect = HttpHeaderValue.CACHE.get(values[i].trim());
488                                     if (expect == null)
489                                         _expect = true;
490                                     else
491                                     {
492                                         switch (expect)
493                                         {
494                                             case CONTINUE:
495                                                 _expect100Continue = true;
496                                                 break;
497                                             case PROCESSING:
498                                                 _expect102Processing = true;
499                                                 break;
500                                             default:
501                                                 _expect = true;
502                                         }
503                                     }
504                                 }
505                         }
506                     }
507                     break;
508 
509                 case CONTENT_TYPE:
510                     MimeTypes.Type mime = MimeTypes.CACHE.get(value);
511                     String charset = (mime == null || mime.getCharset() == null) ? MimeTypes.getCharsetFromContentType(value) : mime.getCharset().toString();
512                     if (charset != null)
513                         _request.setCharacterEncodingUnchecked(charset);
514                     break;
515                 default:
516             }
517         }
518 
519         if (field.getName()!=null)
520             _request.getHttpFields().add(field);
521         return false;
522     }
523 
524     @Override
525     public boolean parsedHostHeader(String host, int port)
526     {
527         _request.setServerName(host);
528         _request.setServerPort(port);
529         return false;
530     }
531 
532     @Override
533     public boolean headerComplete()
534     {
535         _requests.incrementAndGet();
536         switch (_version)
537         {
538             case HTTP_0_9:
539                 break;
540 
541             case HTTP_1_0:
542                 if (_configuration.getSendDateHeader())
543                     _response.getHttpFields().put(_connector.getServer().getDateField());
544                 break;
545 
546             case HTTP_1_1:
547                 if (_configuration.getSendDateHeader())
548                     _response.getHttpFields().put(_connector.getServer().getDateField());
549 
550                 if (_expect)
551                 {
552                     badMessage(HttpStatus.EXPECTATION_FAILED_417,null);
553                     return true;
554                 }
555 
556                 break;
557 
558             default:
559                 throw new IllegalStateException();
560         }
561 
562         return true;
563     }
564 
565     @Override
566     public boolean content(T item)
567     {
568         if (LOG.isDebugEnabled())
569             LOG.debug("{} content {}", this, item);
570         @SuppressWarnings("unchecked")
571         HttpInput<T> input = (HttpInput<T>)_request.getHttpInput();
572         input.content(item);
573 
574         return false;
575     }
576 
577     @Override
578     public boolean messageComplete()
579     {
580         _request.getHttpInput().shutdown();
581         return true;
582     }
583 
584     @Override
585     public void earlyEOF()
586     {
587         _request.getHttpInput().earlyEOF();
588     }
589 
590     @Override
591     public void badMessage(int status, String reason)
592     {
593         if (status < 400 || status > 599)
594             status = HttpStatus.BAD_REQUEST_400;
595 
596         try
597         {
598             if (_state.handling()==Next.CONTINUE)
599                 sendResponse(new ResponseInfo(HttpVersion.HTTP_1_1,new HttpFields(),0,status,reason,false),null,true);
600         }
601         catch (IOException e)
602         {
603             LOG.warn("badMessage",e);
604         }
605         finally
606         {
607             if (_state.unhandle()==Next.COMPLETE)
608                 _state.completed();
609         }
610     }
611 
612     protected boolean sendResponse(ResponseInfo info, ByteBuffer content, boolean complete, final Callback callback)
613     {
614         // TODO check that complete only set true once by changing _committed to AtomicRef<Enum>
615         boolean committing = _committed.compareAndSet(false, true);
616         if (committing)
617         {
618             // We need an info to commit
619             if (info==null)
620                 info = _response.newResponseInfo();
621 
622             // wrap callback to process 100 or 500 responses
623             final int status=info.getStatus();
624             final Callback committed = (status<200&&status>=100)?new Commit100Callback(callback):new CommitCallback(callback);
625 
626             // committing write
627             _transport.send(info, content, complete, committed);
628         }
629         else if (info==null)
630         {
631             // This is a normal write
632             _transport.send(content, complete, callback);
633         }
634         else
635         {
636             callback.failed(new IllegalStateException("committed"));
637         }
638         return committing;
639     }
640 
641     protected boolean sendResponse(ResponseInfo info, ByteBuffer content, boolean complete) throws IOException
642     {
643         boolean committing=sendResponse(info,content,complete,_writeblock);
644         _writeblock.block();
645         return committing;
646     }
647 
648     protected boolean isCommitted()
649     {
650         return _committed.get();
651     }
652 
653     /**
654      * <p>Blocking write, committing the response if needed.</p>
655      *
656      * @param content  the content buffer to write
657      * @param complete whether the content is complete for the response
658      * @throws IOException if the write fails
659      */
660     protected void write(ByteBuffer content, boolean complete) throws IOException
661     {
662         sendResponse(null,content,complete,_writeblock);
663         _writeblock.block();
664     }
665 
666     /**
667      * <p>Non-Blocking write, committing the response if needed.</p>
668      *
669      * @param content  the content buffer to write
670      * @param complete whether the content is complete for the response
671      * @param callback Callback when complete or failed
672      */
673     protected void write(ByteBuffer content, boolean complete, Callback callback)
674     {
675         sendResponse(null,content,complete,callback);
676     }
677 
678     protected void execute(Runnable task)
679     {
680         _connector.getExecutor().execute(task);
681     }
682 
683     public Scheduler getScheduler()
684     {
685         return _connector.getScheduler();
686     }
687 
688     /* ------------------------------------------------------------ */
689     /**
690      * @return true if the HttpChannel can efficiently use direct buffer (typically this means it is not over SSL or a multiplexed protocol)
691      */
692     public boolean useDirectBuffers()
693     {
694         return getEndPoint() instanceof ChannelEndPoint;
695     }
696 
697     /**
698      * If a write or similar to this channel fails this method should be called. The standard implementation
699      * of {@link #failed()} is a noop. But the different implementations of HttpChannel might want to take actions.
700      */
701     public void failed()
702     {
703     }
704 
705     private class CommitCallback implements Callback
706     {
707         private final Callback _callback;
708 
709         private CommitCallback(Callback callback)
710         {
711             _callback = callback;
712         }
713 
714         @Override
715         public void succeeded()
716         {
717             _callback.succeeded();
718         }
719 
720         @Override
721         public void failed(final Throwable x)
722         {
723             if (x instanceof EofException || x instanceof ClosedChannelException)
724             {
725                 LOG.debug(x);
726                 _callback.failed(x);
727                 _response.getHttpOutput().closed();
728             }
729             else
730             {
731                 LOG.warn("commit failed",x);
732                 _transport.send(HttpGenerator.RESPONSE_500_INFO,null,true,new Callback()
733                 {
734                     @Override
735                     public void succeeded()
736                     {
737                         _callback.failed(x);
738                         _response.getHttpOutput().closed();
739                     }
740 
741                     @Override
742                     public void failed(Throwable th)
743                     {
744                         LOG.ignore(th);
745                         _callback.failed(x);
746                         _response.getHttpOutput().closed();
747                     }
748                 });
749             }
750         }
751     }
752 
753     private class Commit100Callback extends CommitCallback
754     {
755         private Commit100Callback(Callback callback)
756         {
757             super(callback);
758         }
759 
760         @Override
761         public void succeeded()
762         {
763              _committed.set(false);
764              super.succeeded();
765         }
766 
767     }
768 }