View Javadoc

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