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