View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2015 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.client;
20  
21  import java.nio.ByteBuffer;
22  import java.util.concurrent.atomic.AtomicReference;
23  
24  import org.eclipse.jetty.client.api.ContentProvider;
25  import org.eclipse.jetty.client.api.Request;
26  import org.eclipse.jetty.client.api.Result;
27  import org.eclipse.jetty.http.HttpHeader;
28  import org.eclipse.jetty.http.HttpHeaderValue;
29  import org.eclipse.jetty.util.BufferUtil;
30  import org.eclipse.jetty.util.Callback;
31  import org.eclipse.jetty.util.IteratingCallback;
32  import org.eclipse.jetty.util.log.Log;
33  import org.eclipse.jetty.util.log.Logger;
34  
35  /**
36   * {@link HttpSender} abstracts the algorithm to send HTTP requests, so that subclasses only implement
37   * the transport-specific code to send requests over the wire, implementing
38   * {@link #sendHeaders(HttpExchange, HttpContent, Callback)} and
39   * {@link #sendContent(HttpExchange, HttpContent, Callback)}.
40   * <p />
41   * {@link HttpSender} governs two state machines.
42   * <p />
43   * The request state machine is updated by {@link HttpSender} as the various steps of sending a request
44   * are executed, see {@link RequestState}.
45   * At any point in time, a user thread may abort the request, which may (if the request has not been
46   * completely sent yet) move the request state machine to {@link RequestState#FAILURE}.
47   * The request state machine guarantees that the request steps are executed (by I/O threads) only if
48   * the request has not been failed already.
49   * <p />
50   * The sender state machine is updated by {@link HttpSender} from three sources: deferred content notifications
51   * (via {@link #onContent()}), 100-continue notifications (via {@link #proceed(HttpExchange, Throwable)})
52   * and normal request send (via {@link #sendContent(HttpExchange, HttpContent, Callback)}).
53   * This state machine must guarantee that the request sending is never executed concurrently: only one of
54   * those sources may trigger the call to {@link #sendContent(HttpExchange, HttpContent, Callback)}.
55   *
56   * @see HttpReceiver
57   */
58  public abstract class HttpSender implements AsyncContentProvider.Listener
59  {
60      protected static final Logger LOG = Log.getLogger(HttpSender.class);
61  
62      private final AtomicReference<RequestState> requestState = new AtomicReference<>(RequestState.QUEUED);
63      private final AtomicReference<SenderState> senderState = new AtomicReference<>(SenderState.IDLE);
64      private final Callback commitCallback = new CommitCallback();
65      private final IteratingCallback contentCallback = new ContentCallback();
66      private final Callback lastCallback = new LastContentCallback();
67      private final HttpChannel channel;
68      private HttpContent content;
69      private Throwable failure;
70  
71      protected HttpSender(HttpChannel channel)
72      {
73          this.channel = channel;
74      }
75  
76      protected HttpChannel getHttpChannel()
77      {
78          return channel;
79      }
80  
81      protected HttpExchange getHttpExchange()
82      {
83          return channel.getHttpExchange();
84      }
85  
86      @Override
87      public void onContent()
88      {
89          HttpExchange exchange = getHttpExchange();
90          if (exchange == null)
91              return;
92  
93          while (true)
94          {
95              SenderState current = senderState.get();
96              switch (current)
97              {
98                  case IDLE:
99                  {
100                     SenderState newSenderState = SenderState.SENDING;
101                     if (updateSenderState(current, newSenderState))
102                     {
103                         if (LOG.isDebugEnabled())
104                             LOG.debug("Deferred content available, {} -> {}", current, newSenderState);
105                         contentCallback.iterate();
106                         return;
107                     }
108                     break;
109                 }
110                 case SENDING:
111                 {
112                     SenderState newSenderState = SenderState.SENDING_WITH_CONTENT;
113                     if (updateSenderState(current, newSenderState))
114                     {
115                         if (LOG.isDebugEnabled())
116                             LOG.debug("Deferred content available, {} -> {}", current, newSenderState);
117                         return;
118                     }
119                     break;
120                 }
121                 case EXPECTING:
122                 {
123                     SenderState newSenderState = SenderState.EXPECTING_WITH_CONTENT;
124                     if (updateSenderState(current, newSenderState))
125                     {
126                         if (LOG.isDebugEnabled())
127                             LOG.debug("Deferred content available, {} -> {}", current, newSenderState);
128                         return;
129                     }
130                     break;
131                 }
132                 case PROCEEDING:
133                 {
134                     SenderState newSenderState = SenderState.PROCEEDING_WITH_CONTENT;
135                     if (updateSenderState(current, newSenderState))
136                     {
137                         if (LOG.isDebugEnabled())
138                             LOG.debug("Deferred content available, {} -> {}", current, newSenderState);
139                         return;
140                     }
141                     break;
142                 }
143                 case SENDING_WITH_CONTENT:
144                 case EXPECTING_WITH_CONTENT:
145                 case PROCEEDING_WITH_CONTENT:
146                 case WAITING:
147                 {
148                     if (LOG.isDebugEnabled())
149                         LOG.debug("Deferred content available, {}", current);
150                     return;
151                 }
152                 default:
153                 {
154                     throw illegalSenderState(current);
155                 }
156             }
157         }
158     }
159 
160     public void send(HttpExchange exchange)
161     {
162         Request request = exchange.getRequest();
163         if (!queuedToBegin(request))
164             return;
165 
166         ContentProvider contentProvider = request.getContent();
167         HttpContent content = this.content = new HttpContent(contentProvider);
168 
169         SenderState newSenderState = SenderState.SENDING;
170         if (expects100Continue(request))
171             newSenderState = content.hasContent() ? SenderState.EXPECTING_WITH_CONTENT : SenderState.EXPECTING;
172         if (!updateSenderState(SenderState.IDLE, newSenderState))
173             throw illegalSenderState(SenderState.IDLE);
174 
175         // Setting the listener may trigger calls to onContent() by other
176         // threads so we must set it only after the sender state has been updated
177         if (contentProvider instanceof AsyncContentProvider)
178             ((AsyncContentProvider)contentProvider).setListener(this);
179 
180         if (!beginToHeaders(request))
181             return;
182 
183         sendHeaders(exchange, content, commitCallback);
184     }
185 
186     protected boolean expects100Continue(Request request)
187     {
188         return request.getHeaders().contains(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString());
189     }
190 
191     protected boolean queuedToBegin(Request request)
192     {
193         if (!updateRequestState(RequestState.QUEUED, RequestState.TRANSIENT))
194             return false;
195         if (LOG.isDebugEnabled())
196             LOG.debug("Request begin {}", request);
197         RequestNotifier notifier = getHttpChannel().getHttpDestination().getRequestNotifier();
198         notifier.notifyBegin(request);
199         if (!updateRequestState(RequestState.TRANSIENT, RequestState.BEGIN))
200             terminateRequest(getHttpExchange(), failure);
201         return true;
202     }
203 
204     protected boolean beginToHeaders(Request request)
205     {
206         if (!updateRequestState(RequestState.BEGIN, RequestState.TRANSIENT))
207             return false;
208         if (LOG.isDebugEnabled())
209             LOG.debug("Request headers {}{}{}", request, System.getProperty("line.separator"), request.getHeaders().toString().trim());
210         RequestNotifier notifier = getHttpChannel().getHttpDestination().getRequestNotifier();
211         notifier.notifyHeaders(request);
212         if (!updateRequestState(RequestState.TRANSIENT, RequestState.HEADERS))
213             terminateRequest(getHttpExchange(), failure);
214         return true;
215     }
216 
217     protected boolean headersToCommit(Request request)
218     {
219         if (!updateRequestState(RequestState.HEADERS, RequestState.TRANSIENT))
220             return false;
221         if (LOG.isDebugEnabled())
222             LOG.debug("Request committed {}", request);
223         RequestNotifier notifier = getHttpChannel().getHttpDestination().getRequestNotifier();
224         notifier.notifyCommit(request);
225         if (!updateRequestState(RequestState.TRANSIENT, RequestState.COMMIT))
226             terminateRequest(getHttpExchange(), failure);
227         return true;
228     }
229 
230     protected boolean someToContent(Request request, ByteBuffer content)
231     {
232         RequestState current = requestState.get();
233         switch (current)
234         {
235             case COMMIT:
236             case CONTENT:
237             {
238                 if (!updateRequestState(current, RequestState.TRANSIENT_CONTENT))
239                     return false;
240                 if (LOG.isDebugEnabled())
241                     LOG.debug("Request content {}{}{}", request, System.getProperty("line.separator"), BufferUtil.toDetailString(content));
242                 RequestNotifier notifier = getHttpChannel().getHttpDestination().getRequestNotifier();
243                 notifier.notifyContent(request, content);
244                 if (!updateRequestState(RequestState.TRANSIENT_CONTENT, RequestState.CONTENT))
245                     terminateRequest(getHttpExchange(), failure);
246                 return true;
247             }
248             default:
249             {
250                 return false;
251             }
252         }
253     }
254 
255     protected boolean someToSuccess(HttpExchange exchange)
256     {
257         RequestState current = requestState.get();
258         switch (current)
259         {
260             case COMMIT:
261             case CONTENT:
262             {
263                 // Mark atomically the request as completed, with respect
264                 // to concurrency between request success and request failure.
265                 boolean completed = exchange.requestComplete();
266                 if (!completed)
267                     return false;
268 
269                 requestState.set(RequestState.QUEUED);
270 
271                 // Reset to be ready for another request.
272                 reset();
273 
274                 // Mark atomically the request as terminated and succeeded,
275                 // with respect to concurrency between request and response.
276                 Result result = exchange.terminateRequest(null);
277 
278                 Request request = exchange.getRequest();
279                 if (LOG.isDebugEnabled())
280                     LOG.debug("Request success {}", request);
281                 HttpDestination destination = getHttpChannel().getHttpDestination();
282                 destination.getRequestNotifier().notifySuccess(exchange.getRequest());
283 
284                 terminateRequest(exchange, null, result);
285 
286                 return true;
287             }
288             default:
289             {
290                 return false;
291             }
292         }
293     }
294 
295     protected boolean anyToFailure(Throwable failure)
296     {
297         HttpExchange exchange = getHttpExchange();
298         if (exchange == null)
299             return false;
300 
301         // Mark atomically the request as completed, with respect
302         // to concurrency between request success and request failure.
303         boolean completed = exchange.requestComplete();
304         if (!completed)
305             return false;
306 
307         this.failure = failure;
308 
309         // Update the state to avoid more request processing.
310         RequestState current;
311         boolean fail;
312         while (true)
313         {
314             current = requestState.get();
315             if (updateRequestState(current, RequestState.FAILURE))
316             {
317                 fail = current != RequestState.TRANSIENT && current != RequestState.TRANSIENT_CONTENT;
318                 break;
319             }
320         }
321 
322         dispose();
323 
324         // Mark atomically the request as terminated and failed,
325         // with respect to concurrency between request and response.
326         Result result = exchange.terminateRequest(failure);
327 
328         Request request = exchange.getRequest();
329         if (LOG.isDebugEnabled())
330             LOG.debug("Request failure {} {}", exchange, failure);
331         HttpDestination destination = getHttpChannel().getHttpDestination();
332         destination.getRequestNotifier().notifyFailure(request, failure);
333 
334         if (fail)
335         {
336             terminateRequest(exchange, failure, result);
337         }
338         else
339         {
340             if (LOG.isDebugEnabled())
341                 LOG.debug("Concurrent failure: request termination skipped, performed by helpers");
342         }
343 
344         return true;
345     }
346 
347     private void terminateRequest(HttpExchange exchange, Throwable failure)
348     {
349         if (exchange != null)
350         {
351             Result result = exchange.terminateRequest(failure);
352             terminateRequest(exchange, failure, result);
353         }
354     }
355 
356     private void terminateRequest(HttpExchange exchange, Throwable failure, Result result)
357     {
358         Request request = exchange.getRequest();
359 
360         if (LOG.isDebugEnabled())
361             LOG.debug("Terminating request {}", request);
362 
363         if (result == null)
364         {
365             if (failure != null)
366             {
367                 if (LOG.isDebugEnabled())
368                     LOG.debug("Response failure from request {}", exchange);
369                 getHttpChannel().abortResponse(failure);
370             }
371         }
372         else
373         {
374             HttpDestination destination = getHttpChannel().getHttpDestination();
375             boolean ordered = destination.getHttpClient().isStrictEventOrdering();
376             if (!ordered)
377                 channel.exchangeTerminated(result);
378             if (LOG.isDebugEnabled())
379                 LOG.debug("Request/Response {} {}", failure == null ? "succeeded" : "failed", request);
380             HttpConversation conversation = exchange.getConversation();
381             destination.getResponseNotifier().notifyComplete(conversation.getResponseListeners(), result);
382             if (ordered)
383                 channel.exchangeTerminated(result);
384         }
385     }
386 
387     /**
388      * Implementations should send the HTTP headers over the wire, possibly with some content,
389      * in a single write, and notify the given {@code callback} of the result of this operation.
390      * <p />
391      * If there is more content to send, then {@link #sendContent(HttpExchange, HttpContent, Callback)}
392      * will be invoked.
393      *
394      * @param exchange the exchange to send
395      * @param content the content to send
396      * @param callback the callback to notify
397      */
398     protected abstract void sendHeaders(HttpExchange exchange, HttpContent content, Callback callback);
399 
400     /**
401      * Implementations should send the content at the {@link HttpContent} cursor position over the wire.
402      * <p />
403      * The {@link HttpContent} cursor is advanced by {@link HttpSender} at the right time, and if more
404      * content needs to be sent, this method is invoked again; subclasses need only to send the content
405      * at the {@link HttpContent} cursor position.
406      * <p />
407      * This method is invoked one last time when {@link HttpContent#isConsumed()} is true and therefore
408      * there is no actual content to send.
409      * This is done to allow subclasses to write "terminal" bytes (such as the terminal chunk when the
410      * transfer encoding is chunked) if their protocol needs to.
411      *
412      * @param exchange the exchange to send
413      * @param content the content to send
414      * @param callback the callback to notify
415      */
416     protected abstract void sendContent(HttpExchange exchange, HttpContent content, Callback callback);
417 
418     protected void reset()
419     {
420         content.close();
421         content = null;
422         senderState.set(SenderState.IDLE);
423     }
424 
425     protected void dispose()
426     {
427         HttpContent content = this.content;
428         if (content != null)
429             content.close();
430     }
431 
432     public void proceed(HttpExchange exchange, Throwable failure)
433     {
434         if (!expects100Continue(exchange.getRequest()))
435             return;
436 
437         if (failure != null)
438         {
439             anyToFailure(failure);
440             return;
441         }
442 
443         while (true)
444         {
445             SenderState current = senderState.get();
446             switch (current)
447             {
448                 case EXPECTING:
449                 {
450                     // We are still sending the headers, but we already got the 100 Continue.
451                     if (updateSenderState(current, SenderState.PROCEEDING))
452                     {
453                         if (LOG.isDebugEnabled())
454                             LOG.debug("Proceeding while expecting");
455                         return;
456                     }
457                     break;
458                 }
459                 case EXPECTING_WITH_CONTENT:
460                 {
461                     // More deferred content was submitted to onContent(), we already
462                     // got the 100 Continue, but we may be still sending the headers
463                     // (for example, with SSL we may have sent the encrypted data,
464                     // received the 100 Continue but not yet updated the decrypted
465                     // WriteFlusher so sending more content now may result in a
466                     // WritePendingException).
467                     if (updateSenderState(current, SenderState.PROCEEDING_WITH_CONTENT))
468                     {
469                         if (LOG.isDebugEnabled())
470                             LOG.debug("Proceeding while scheduled");
471                         return;
472                     }
473                     break;
474                 }
475                 case WAITING:
476                 {
477                     // We received the 100 Continue, now send the content if any.
478                     if (!updateSenderState(current, SenderState.SENDING))
479                         throw illegalSenderState(current);
480                     if (LOG.isDebugEnabled())
481                         LOG.debug("Proceeding while waiting");
482                     contentCallback.iterate();
483                     return;
484                 }
485                 default:
486                 {
487                     throw illegalSenderState(current);
488                 }
489             }
490         }
491     }
492 
493     public boolean abort(Throwable failure)
494     {
495         RequestState current = requestState.get();
496         boolean abortable = isBeforeCommit(current) || isSending(current);
497         return abortable && anyToFailure(failure);
498     }
499 
500     private boolean updateRequestState(RequestState from, RequestState to)
501     {
502         boolean updated = requestState.compareAndSet(from, to);
503         if (!updated)
504             LOG.debug("RequestState update failed: {} -> {}: {}", from, to, requestState.get());
505         return updated;
506     }
507 
508     private boolean updateSenderState(SenderState from, SenderState to)
509     {
510         boolean updated = senderState.compareAndSet(from, to);
511         if (!updated)
512             LOG.debug("SenderState update failed: {} -> {}: {}", from, to, senderState.get());
513         return updated;
514     }
515 
516     private boolean isBeforeCommit(RequestState requestState)
517     {
518         switch (requestState)
519         {
520             case TRANSIENT:
521             case QUEUED:
522             case BEGIN:
523             case HEADERS:
524                 return true;
525             default:
526                 return false;
527         }
528     }
529 
530     private boolean isSending(RequestState requestState)
531     {
532         switch (requestState)
533         {
534             case TRANSIENT_CONTENT:
535             case COMMIT:
536             case CONTENT:
537                 return true;
538             default:
539                 return false;
540         }
541     }
542 
543     private RuntimeException illegalSenderState(SenderState current)
544     {
545         return new IllegalStateException("Expected " + current + " found " + senderState.get() + " instead");
546     }
547 
548     @Override
549     public String toString()
550     {
551         return String.format("%s@%x(req=%s,snd=%s)",
552                 getClass().getSimpleName(),
553                 hashCode(),
554                 requestState,
555                 senderState);
556     }
557 
558     /**
559      * The request states {@link HttpSender} goes through when sending a request.
560      */
561     private enum RequestState
562     {
563         /**
564          * One of the state transition methods is being executed.
565          */
566         TRANSIENT,
567         /**
568          * The content transition method is being executed.
569          */
570         TRANSIENT_CONTENT,
571         /**
572          * The request is queued, the initial state
573          */
574         QUEUED,
575         /**
576          * The request has been dequeued
577          */
578         BEGIN,
579         /**
580          * The request headers (and possibly some content) is about to be sent
581          */
582         HEADERS,
583         /**
584          * The request headers (and possibly some content) have been sent
585          */
586         COMMIT,
587         /**
588          * The request content is being sent
589          */
590         CONTENT,
591         /**
592          * The request is failed
593          */
594         FAILURE
595     }
596 
597     /**
598      * The sender states {@link HttpSender} goes through when sending a request.
599      */
600     private enum SenderState
601     {
602         /**
603          * {@link HttpSender} is not sending request headers nor request content
604          */
605         IDLE,
606         /**
607          * {@link HttpSender} is sending the request header or request content
608          */
609         SENDING,
610         /**
611          * {@link HttpSender} is currently sending the request, and deferred content is available to be sent
612          */
613         SENDING_WITH_CONTENT,
614         /**
615          * {@link HttpSender} is sending the headers but will wait for 100 Continue before sending the content
616          */
617         EXPECTING,
618         /**
619          * {@link HttpSender} is currently sending the headers, will wait for 100 Continue, and deferred content is available to be sent
620          */
621         EXPECTING_WITH_CONTENT,
622         /**
623          * {@link HttpSender} has sent the headers and is waiting for 100 Continue
624          */
625         WAITING,
626         /**
627          * {@link HttpSender} is sending the headers, while 100 Continue has arrived
628          */
629         PROCEEDING,
630         /**
631          * {@link HttpSender} is sending the headers, while 100 Continue has arrived, and deferred content is available to be sent
632          */
633         PROCEEDING_WITH_CONTENT
634     }
635 
636     private class CommitCallback implements Callback
637     {
638         @Override
639         public void succeeded()
640         {
641             try
642             {
643                 content.succeeded();
644                 process();
645             }
646             // Catch-all for runtime exceptions
647             catch (Exception x)
648             {
649                 anyToFailure(x);
650             }
651         }
652 
653         @Override
654         public void failed(Throwable failure)
655         {
656             content.failed(failure);
657             anyToFailure(failure);
658         }
659 
660         private void process() throws Exception
661         {
662             HttpExchange exchange = getHttpExchange();
663             if (exchange == null)
664                 return;
665 
666             Request request = exchange.getRequest();
667             if (!headersToCommit(request))
668                 return;
669 
670             HttpContent content = HttpSender.this.content;
671 
672             if (!content.hasContent())
673             {
674                 // No content to send, we are done.
675                 someToSuccess(exchange);
676             }
677             else
678             {
679                 // Was any content sent while committing ?
680                 ByteBuffer contentBuffer = content.getContent();
681                 if (contentBuffer != null)
682                 {
683                     if (!someToContent(request, contentBuffer))
684                         return;
685                 }
686 
687                 while (true)
688                 {
689                     SenderState current = senderState.get();
690                     switch (current)
691                     {
692                         case SENDING:
693                         {
694                             contentCallback.iterate();
695                             return;
696                         }
697                         case SENDING_WITH_CONTENT:
698                         {
699                             // We have deferred content to send.
700                             updateSenderState(current, SenderState.SENDING);
701                             break;
702                         }
703                         case EXPECTING:
704                         {
705                             // We sent the headers, wait for the 100 Continue response.
706                             if (updateSenderState(current, SenderState.WAITING))
707                                 return;
708                             break;
709                         }
710                         case EXPECTING_WITH_CONTENT:
711                         {
712                             // We sent the headers, we have deferred content to send,
713                             // wait for the 100 Continue response.
714                             if (updateSenderState(current, SenderState.WAITING))
715                                 return;
716                             break;
717                         }
718                         case PROCEEDING:
719                         {
720                             // We sent the headers, we have the 100 Continue response,
721                             // we have no content to send.
722                             if (updateSenderState(current, SenderState.IDLE))
723                                 return;
724                             break;
725                         }
726                         case PROCEEDING_WITH_CONTENT:
727                         {
728                             // We sent the headers, we have the 100 Continue response,
729                             // we have deferred content to send.
730                             updateSenderState(current, SenderState.SENDING);
731                             break;
732                         }
733                         default:
734                         {
735                             throw illegalSenderState(current);
736                         }
737                     }
738                 }
739             }
740         }
741     }
742 
743     private class ContentCallback extends IteratingCallback
744     {
745         @Override
746         protected Action process() throws Exception
747         {
748             HttpExchange exchange = getHttpExchange();
749             if (exchange == null)
750                 return Action.IDLE;
751 
752             HttpContent content = HttpSender.this.content;
753             while (true)
754             {
755                 boolean advanced = content.advance();
756                 boolean consumed = content.isConsumed();
757                 if (LOG.isDebugEnabled())
758                     LOG.debug("Content {} consumed {} for {}", advanced, consumed, exchange.getRequest());
759 
760                 if (advanced)
761                 {
762                     sendContent(exchange, content, this);
763                     return Action.SCHEDULED;
764                 }
765 
766                 if (consumed)
767                 {
768                     sendContent(exchange, content, lastCallback);
769                     return Action.IDLE;
770                 }
771 
772                 SenderState current = HttpSender.this.senderState.get();
773                 switch (current)
774                 {
775                     case SENDING:
776                     {
777                         if (updateSenderState(current, SenderState.IDLE))
778                         {
779                             if (LOG.isDebugEnabled())
780                                 LOG.debug("Content is deferred for {}", exchange.getRequest());
781                             return Action.IDLE;
782                         }
783                         break;
784                     }
785                     case SENDING_WITH_CONTENT:
786                     {
787                         updateSenderState(current, SenderState.SENDING);
788                         break;
789                     }
790                     default:
791                     {
792                         throw illegalSenderState(current);
793                     }
794                 }
795             }
796         }
797 
798         @Override
799         public void succeeded()
800         {
801             ByteBuffer buffer = content.getContent();
802             someToContent(getHttpExchange().getRequest(), buffer);
803             content.succeeded();
804             super.succeeded();
805         }
806 
807         @Override
808         public void onCompleteFailure(Throwable failure)
809         {
810             content.failed(failure);
811             anyToFailure(failure);
812         }
813 
814         @Override
815         protected void onCompleteSuccess()
816         {
817             // Nothing to do, since we always return false from process().
818             // Termination is obtained via LastContentCallback.
819         }
820     }
821 
822     private class LastContentCallback implements Callback
823     {
824         @Override
825         public void succeeded()
826         {
827             content.succeeded();
828             HttpExchange exchange = getHttpExchange();
829             if (exchange == null)
830                 return;
831             someToSuccess(exchange);
832         }
833 
834         @Override
835         public void failed(Throwable failure)
836         {
837             content.failed(failure);
838             anyToFailure(failure);
839         }
840     }
841 }