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