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.http2.client.http;
20  
21  import java.io.IOException;
22  import java.nio.ByteBuffer;
23  import java.util.ArrayDeque;
24  import java.util.Locale;
25  import java.util.Queue;
26  
27  import org.eclipse.jetty.client.HttpChannel;
28  import org.eclipse.jetty.client.HttpExchange;
29  import org.eclipse.jetty.client.HttpReceiver;
30  import org.eclipse.jetty.client.HttpResponse;
31  import org.eclipse.jetty.http.HttpField;
32  import org.eclipse.jetty.http.HttpFields;
33  import org.eclipse.jetty.http.HttpStatus;
34  import org.eclipse.jetty.http.MetaData;
35  import org.eclipse.jetty.http2.ErrorCode;
36  import org.eclipse.jetty.http2.api.Stream;
37  import org.eclipse.jetty.http2.frames.DataFrame;
38  import org.eclipse.jetty.http2.frames.HeadersFrame;
39  import org.eclipse.jetty.http2.frames.PushPromiseFrame;
40  import org.eclipse.jetty.http2.frames.ResetFrame;
41  import org.eclipse.jetty.io.ByteBufferPool;
42  import org.eclipse.jetty.util.BufferUtil;
43  import org.eclipse.jetty.util.Callback;
44  import org.eclipse.jetty.util.IteratingCallback;
45  
46  public class HttpReceiverOverHTTP2 extends HttpReceiver implements Stream.Listener
47  {
48      private final ContentNotifier contentNotifier = new ContentNotifier();
49  
50      public HttpReceiverOverHTTP2(HttpChannel channel)
51      {
52          super(channel);
53      }
54  
55      @Override
56      protected HttpChannelOverHTTP2 getHttpChannel()
57      {
58          return (HttpChannelOverHTTP2)super.getHttpChannel();
59      }
60  
61      @Override
62      public void onHeaders(Stream stream, HeadersFrame frame)
63      {
64          HttpExchange exchange = getHttpExchange();
65          if (exchange == null)
66              return;
67  
68          HttpResponse response = exchange.getResponse();
69          MetaData.Response metaData = (MetaData.Response)frame.getMetaData();
70          response.version(metaData.getVersion()).status(metaData.getStatus()).reason(metaData.getReason());
71  
72          if (responseBegin(exchange))
73          {
74              HttpFields headers = metaData.getFields();
75              for (HttpField header : headers)
76              {
77                  if (!responseHeader(exchange, header))
78                      return;
79              }
80  
81              if (responseHeaders(exchange))
82              {
83                  int status = metaData.getStatus();
84                  boolean informational = HttpStatus.isInformational(status) && status != HttpStatus.SWITCHING_PROTOCOLS_101;
85                  if (frame.isEndStream() || informational)
86                      responseSuccess(exchange);
87              }
88          }
89      }
90  
91      @Override
92      public Stream.Listener onPush(Stream stream, PushPromiseFrame frame)
93      {
94          // Not supported.
95          stream.reset(new ResetFrame(stream.getId(), ErrorCode.REFUSED_STREAM_ERROR.code), Callback.NOOP);
96          return null;
97      }
98  
99      @Override
100     public void onData(Stream stream, DataFrame frame, Callback callback)
101     {
102         HttpExchange exchange = getHttpExchange();
103         if (exchange == null)
104         {
105             callback.failed(new IOException("terminated"));
106             return;
107         }
108 
109         // We must copy the data since we do not know when the
110         // application will consume the bytes and the parsing
111         // will continue as soon as this method returns, eventually
112         // leading to reusing the underlying buffer for more reads.
113         ByteBufferPool byteBufferPool = getHttpDestination().getHttpClient().getByteBufferPool();
114         ByteBuffer original = frame.getData();
115         int length = original.remaining();
116         final ByteBuffer copy = byteBufferPool.acquire(length, original.isDirect());
117         BufferUtil.clearToFill(copy);
118         copy.put(original);
119         BufferUtil.flipToFlush(copy, 0);
120 
121         contentNotifier.offer(new DataInfo(exchange, copy, callback, frame.isEndStream()));
122         contentNotifier.iterate();
123     }
124 
125     @Override
126     public void onReset(Stream stream, ResetFrame frame)
127     {
128         HttpExchange exchange = getHttpExchange();
129         if (exchange == null)
130             return;
131 
132         ErrorCode error = ErrorCode.from(frame.getError());
133         String reason = error == null ? "reset" : error.name().toLowerCase(Locale.ENGLISH);
134         exchange.getRequest().abort(new IOException(reason));
135     }
136 
137     @Override
138     public boolean onIdleTimeout(Stream stream, Throwable x)
139     {
140         responseFailure(x);
141         return true;
142     }
143 
144     private class ContentNotifier extends IteratingCallback
145     {
146         private final Queue<DataInfo> queue = new ArrayDeque<>();
147         private DataInfo dataInfo;
148 
149         private boolean offer(DataInfo dataInfo)
150         {
151             synchronized (this)
152             {
153                 return queue.offer(dataInfo);
154             }
155         }
156 
157         @Override
158         protected Action process() throws Exception
159         {
160             DataInfo dataInfo;
161             synchronized (this)
162             {
163                 dataInfo = queue.poll();
164             }
165 
166             if (dataInfo == null)
167             {
168                 DataInfo prevDataInfo = this.dataInfo;
169                 if (prevDataInfo != null && prevDataInfo.last)
170                     return Action.SUCCEEDED;
171                 return Action.IDLE;
172             }
173 
174             this.dataInfo = dataInfo;
175             responseContent(dataInfo.exchange, dataInfo.buffer, this);
176             return Action.SCHEDULED;
177         }
178 
179         @Override
180         public void succeeded()
181         {
182             ByteBufferPool byteBufferPool = getHttpDestination().getHttpClient().getByteBufferPool();
183             byteBufferPool.release(dataInfo.buffer);
184             dataInfo.callback.succeeded();
185             super.succeeded();
186         }
187 
188         @Override
189         protected void onCompleteSuccess()
190         {
191             responseSuccess(dataInfo.exchange);
192         }
193 
194         @Override
195         protected void onCompleteFailure(Throwable failure)
196         {
197             ByteBufferPool byteBufferPool = getHttpDestination().getHttpClient().getByteBufferPool();
198             byteBufferPool.release(dataInfo.buffer);
199             dataInfo.callback.failed(failure);
200             responseFailure(failure);
201         }
202     }
203 
204     private static class DataInfo
205     {
206         private final HttpExchange exchange;
207         private final ByteBuffer buffer;
208         private final Callback callback;
209         private final boolean last;
210 
211         private DataInfo(HttpExchange exchange, ByteBuffer buffer, Callback callback, boolean last)
212         {
213             this.exchange = exchange;
214             this.buffer = buffer;
215             this.callback = callback;
216             this.last = last;
217         }
218     }
219 }