View Javadoc

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