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