View Javadoc

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