]> WPIA git - gigi.git/blob - lib/jetty/org/eclipse/jetty/server/HttpChannel.java
updating jetty to jetty-9.2.16.v2016040
[gigi.git] / lib / jetty / org / eclipse / jetty / server / HttpChannel.java
1 //
2 //  ========================================================================
3 //  Copyright (c) 1995-2016 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
30 import javax.servlet.DispatcherType;
31 import javax.servlet.RequestDispatcher;
32 import javax.servlet.http.HttpServletRequest;
33
34 import org.eclipse.jetty.http.HttpField;
35 import org.eclipse.jetty.http.HttpFields;
36 import org.eclipse.jetty.http.HttpGenerator;
37 import org.eclipse.jetty.http.HttpGenerator.ResponseInfo;
38 import org.eclipse.jetty.http.HttpHeader;
39 import org.eclipse.jetty.http.HttpHeaderValue;
40 import org.eclipse.jetty.http.HttpMethod;
41 import org.eclipse.jetty.http.HttpParser;
42 import org.eclipse.jetty.http.HttpStatus;
43 import org.eclipse.jetty.http.HttpURI;
44 import org.eclipse.jetty.http.HttpVersion;
45 import org.eclipse.jetty.http.MimeTypes;
46 import org.eclipse.jetty.io.ByteBufferPool;
47 import org.eclipse.jetty.io.ChannelEndPoint;
48 import org.eclipse.jetty.io.EndPoint;
49 import org.eclipse.jetty.io.EofException;
50 import org.eclipse.jetty.server.HttpChannelState.Action;
51 import org.eclipse.jetty.server.handler.ContextHandler;
52 import org.eclipse.jetty.server.handler.ErrorHandler;
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         __currentChannel.set(channel);
91         return last;
92     }
93
94     private final AtomicBoolean _committed = new AtomicBoolean();
95     private final AtomicInteger _requests = new AtomicInteger();
96     private final Connector _connector;
97     private final HttpConfiguration _configuration;
98     private final EndPoint _endPoint;
99     private final HttpTransport _transport;
100     private final HttpURI _uri;
101     private final HttpChannelState _state;
102     private final Request _request;
103     private final Response _response;
104     private HttpVersion _version = HttpVersion.HTTP_1_1;
105     private boolean _expect = false;
106     private boolean _expect100Continue = false;
107     private boolean _expect102Processing = false;
108
109     public HttpChannel(Connector connector, HttpConfiguration configuration, EndPoint endPoint, HttpTransport transport, HttpInput<T> input)
110     {
111         _connector = connector;
112         _configuration = configuration;
113         _endPoint = endPoint;
114         _transport = transport;
115
116         _uri = new HttpURI(URIUtil.__CHARSET);
117         _state = new HttpChannelState(this);
118         input.init(_state);
119         _request = new Request(this, input);
120         _response = new Response(this, new HttpOutput(this));
121
122         if (LOG.isDebugEnabled())
123             LOG.debug("new {} -> {},{},{}",this,_endPoint,_endPoint.getConnection(),_state);
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             if (action==Action.COMPLETE)
410             {
411                 try
412                 {
413                     _state.completed();
414
415                     if (!_response.isCommitted() && !_request.isHandled())
416                     {
417                         _response.sendError(404);
418                     }
419                     else
420                     {
421                         // Complete generating the response
422                         _response.closeOutput();
423                     }
424                 }
425                 catch(EofException|ClosedChannelException e)
426                 {
427                     LOG.debug(e);
428                 }
429                 catch(Exception e)
430                 {
431                     LOG.warn("complete failed",e);
432                 }
433                 finally
434                 {
435                     _request.setHandled(true);
436                     _transport.completed();
437                 }
438             }
439         }
440         finally
441         {
442             setCurrentHttpChannel(last);
443             if (threadName != null && LOG.isDebugEnabled())
444                 Thread.currentThread().setName(threadName);
445         }
446
447         if (LOG.isDebugEnabled())
448             LOG.debug("{} handle exit, result {}", this, action);
449
450         return action!=Action.WAIT;
451     }
452
453     /**
454      * <p>Sends an error 500, performing a special logic to detect whether the request is suspended,
455      * to avoid concurrent writes from the application.</p>
456      * <p>It may happen that the application suspends, and then throws an exception, while an application
457      * spawned thread writes the response content; in such case, we attempt to commit the error directly
458      * bypassing the {@link ErrorHandler} mechanisms and the response OutputStream.</p>
459      *
460      * @param x the Throwable that caused the problem
461      */
462     protected void handleException(Throwable x)
463     {
464         try
465         {
466             _request.setAttribute(RequestDispatcher.ERROR_EXCEPTION,x);
467             _request.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE,x.getClass());
468             if (_state.isSuspended())
469             {
470                 HttpFields fields = new HttpFields();
471                 fields.add(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE);
472                 ResponseInfo info = new ResponseInfo(_request.getHttpVersion(), fields, 0, HttpStatus.INTERNAL_SERVER_ERROR_500, null, _request.isHead());
473                 boolean committed = sendResponse(info, null, true);
474                 if (!committed)
475                     LOG.warn("Could not send response error 500: "+x);
476                 _request.getAsyncContext().complete();
477             }
478             else if (isCommitted())
479             {
480                 abort();
481                 if (!(x instanceof EofException))
482                     LOG.warn("Could not send response error 500: "+x);
483             }
484             else
485             {
486                 _response.setHeader(HttpHeader.CONNECTION.asString(),HttpHeaderValue.CLOSE.asString());
487                 _response.sendError(500, x.getMessage());
488             }
489         }
490         catch (IOException e)
491         {
492             // We tried our best, just log
493             LOG.debug("Could not commit response error 500", e);
494         }
495     }
496
497     public boolean isExpecting100Continue()
498     {
499         return _expect100Continue;
500     }
501
502     public boolean isExpecting102Processing()
503     {
504         return _expect102Processing;
505     }
506
507     @Override
508     public String toString()
509     {
510         return String.format("%s@%x{r=%s,c=%b,a=%s,uri=%s}",
511                 getClass().getSimpleName(),
512                 hashCode(),
513                 _requests,
514                 _committed.get(),
515                 _state.getState(),
516                 _uri);
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         HttpFields fields = _response.getHttpFields();
660         switch (_version)
661         {
662             case HTTP_0_9:
663                 break;
664
665             case HTTP_1_0:
666                 if (_configuration.getSendDateHeader() && !fields.contains(HttpHeader.DATE))
667                     _response.getHttpFields().add(_connector.getServer().getDateField());
668                 break;
669
670             case HTTP_1_1:
671                 if (_configuration.getSendDateHeader() && !fields.contains(HttpHeader.DATE))
672                     _response.getHttpFields().add(_connector.getServer().getDateField());
673
674                 if (_expect)
675                 {
676                     badMessage(HttpStatus.EXPECTATION_FAILED_417,null);
677                     return true;
678                 }
679
680                 break;
681
682             default:
683                 throw new IllegalStateException();
684         }
685
686         return true;
687     }
688
689     @Override
690     public boolean content(T item)
691     {
692         if (LOG.isDebugEnabled())
693             LOG.debug("{} content {}", this, item);
694         @SuppressWarnings("unchecked")
695         HttpInput<T> input = (HttpInput<T>)_request.getHttpInput();
696         input.content(item);
697
698         return false;
699     }
700
701     @Override
702     public boolean messageComplete()
703     {
704         if (LOG.isDebugEnabled())
705             LOG.debug("{} messageComplete", this);
706         _request.getHttpInput().messageComplete();
707         return true;
708     }
709
710     @Override
711     public void earlyEOF()
712     {
713         _request.getHttpInput().earlyEOF();
714     }
715
716     @Override
717     public void badMessage(int status, String reason)
718     {
719         if (status < 400 || status > 599)
720             status = HttpStatus.BAD_REQUEST_400;
721
722         try
723         {
724             if (_state.handling()==Action.REQUEST_DISPATCH)
725             {
726                 ByteBuffer content=null;
727                 HttpFields fields=new HttpFields();
728
729                 ErrorHandler handler=getServer().getBean(ErrorHandler.class);
730                 if (handler!=null)
731                     content=handler.badMessageError(status,reason,fields);
732
733                 sendResponse(new ResponseInfo(HttpVersion.HTTP_1_1,fields,0,status,reason,false),content ,true);
734             }
735         }
736         catch (IOException e)
737         {
738             LOG.debug(e);
739         }
740         finally
741         {
742             if (_state.unhandle()==Action.COMPLETE)
743                 _state.completed();
744             else
745                 throw new IllegalStateException();
746         }
747     }
748
749     protected boolean sendResponse(ResponseInfo info, ByteBuffer content, boolean complete, final Callback callback)
750     {
751         boolean committing = _committed.compareAndSet(false, true);
752         if (committing)
753         {
754             // We need an info to commit
755             if (info==null)
756                 info = _response.newResponseInfo();
757
758             // wrap callback to process 100 responses
759             final int status=info.getStatus();
760             final Callback committed = (status<200&&status>=100)?new Commit100Callback(callback):new CommitCallback(callback);
761
762             // committing write
763             _transport.send(info, content, complete, committed);
764         }
765         else if (info==null)
766         {
767             // This is a normal write
768             _transport.send(content, complete, callback);
769         }
770         else
771         {
772             callback.failed(new IllegalStateException("committed"));
773         }
774         return committing;
775     }
776
777     protected boolean sendResponse(ResponseInfo info, ByteBuffer content, boolean complete) throws IOException
778     {
779         try(Blocker blocker = _response.getHttpOutput().acquireWriteBlockingCallback())
780         {
781             boolean committing = sendResponse(info,content,complete,blocker);
782             blocker.block();
783             return committing;
784         }
785     }
786
787     public boolean isCommitted()
788     {
789         return _committed.get();
790     }
791
792     /**
793      * <p>Non-Blocking write, committing the response if needed.</p>
794      *
795      * @param content  the content buffer to write
796      * @param complete whether the content is complete for the response
797      * @param callback Callback when complete or failed
798      */
799     protected void write(ByteBuffer content, boolean complete, Callback callback)
800     {
801         sendResponse(null,content,complete,callback);
802     }
803
804     protected void execute(Runnable task)
805     {
806         _connector.getExecutor().execute(task);
807     }
808
809     public Scheduler getScheduler()
810     {
811         return _connector.getScheduler();
812     }
813
814     /* ------------------------------------------------------------ */
815     /**
816      * @return true if the HttpChannel can efficiently use direct buffer (typically this means it is not over SSL or a multiplexed protocol)
817      */
818     public boolean useDirectBuffers()
819     {
820         return getEndPoint() instanceof ChannelEndPoint;
821     }
822
823     /**
824      * If a write or similar to this channel fails this method should be called. The standard implementation
825      * is to call {@link HttpTransport#abort()}
826      */
827     public void abort()
828     {
829         _transport.abort();
830     }
831
832     private class CommitCallback implements Callback
833     {
834         private final Callback _callback;
835
836         private CommitCallback(Callback callback)
837         {
838             _callback = callback;
839         }
840
841         @Override
842         public void succeeded()
843         {
844             _callback.succeeded();
845         }
846
847         @Override
848         public void failed(final Throwable x)
849         {
850             if (x instanceof EofException || x instanceof ClosedChannelException)
851             {
852                 LOG.debug(x);
853                 _callback.failed(x);
854                 _response.getHttpOutput().closed();
855             }
856             else
857             {
858                 LOG.warn("Commit failed",x);
859                 _transport.send(HttpGenerator.RESPONSE_500_INFO,null,true,new Callback()
860                 {
861                     @Override
862                     public void succeeded()
863                     {
864                         _callback.failed(x);
865                         _response.getHttpOutput().closed();
866                     }
867
868                     @Override
869                     public void failed(Throwable th)
870                     {
871                         LOG.ignore(th);
872                         _callback.failed(x);
873                         _response.getHttpOutput().closed();
874                     }
875                 });
876             }
877         }
878     }
879
880     private class Commit100Callback extends CommitCallback
881     {
882         private Commit100Callback(Callback callback)
883         {
884             super(callback);
885         }
886
887         @Override
888         public void succeeded()
889         {
890             if (_committed.compareAndSet(true, false))
891                 super.succeeded();
892             else
893                 super.failed(new IllegalStateException());
894         }
895
896     }
897
898 }