View Javadoc

1   /*
2    * Copyright (c) 2012 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.eclipse.jetty.spdy;
18  
19  import java.nio.ByteBuffer;
20  import java.util.Map;
21  import java.util.concurrent.ConcurrentHashMap;
22  import java.util.concurrent.Future;
23  import java.util.concurrent.TimeUnit;
24  import java.util.concurrent.atomic.AtomicInteger;
25  
26  import org.eclipse.jetty.spdy.api.ByteBufferDataInfo;
27  import org.eclipse.jetty.spdy.api.DataInfo;
28  import org.eclipse.jetty.spdy.api.Handler;
29  import org.eclipse.jetty.spdy.api.HeadersInfo;
30  import org.eclipse.jetty.spdy.api.ReplyInfo;
31  import org.eclipse.jetty.spdy.api.RstInfo;
32  import org.eclipse.jetty.spdy.api.Session;
33  import org.eclipse.jetty.spdy.api.Stream;
34  import org.eclipse.jetty.spdy.api.StreamFrameListener;
35  import org.eclipse.jetty.spdy.api.StreamStatus;
36  import org.eclipse.jetty.spdy.frames.ControlFrame;
37  import org.eclipse.jetty.spdy.frames.DataFrame;
38  import org.eclipse.jetty.spdy.frames.HeadersFrame;
39  import org.eclipse.jetty.spdy.frames.SynReplyFrame;
40  import org.eclipse.jetty.spdy.frames.SynStreamFrame;
41  import org.eclipse.jetty.spdy.frames.WindowUpdateFrame;
42  import org.slf4j.Logger;
43  import org.slf4j.LoggerFactory;
44  
45  public class StandardStream implements IStream
46  {
47      private static final Logger logger = LoggerFactory.getLogger(Stream.class);
48      private final Map<String, Object> attributes = new ConcurrentHashMap<>();
49      private final SynStreamFrame frame;
50      private final ISession session;
51      private final AtomicInteger windowSize;
52      private volatile StreamFrameListener listener;
53      private volatile boolean opened;
54      private volatile boolean halfClosed;
55      private volatile boolean closed;
56  
57      public StandardStream(SynStreamFrame frame, ISession session, int windowSize)
58      {
59          this.frame = frame;
60          this.session = session;
61          this.windowSize = new AtomicInteger(windowSize);
62          this.halfClosed = frame.isClose();
63      }
64  
65      @Override
66      public int getId()
67      {
68          return frame.getStreamId();
69      }
70  
71      @Override
72      public byte getPriority()
73      {
74          return frame.getPriority();
75      }
76  
77      @Override
78      public int getWindowSize()
79      {
80          return windowSize.get();
81      }
82  
83      @Override
84      public void updateWindowSize(int delta)
85      {
86          int size = windowSize.addAndGet(delta);
87          logger.debug("Updated window size by {}, new window size {}", delta, size);
88      }
89  
90      @Override
91      public Session getSession()
92      {
93          return session;
94      }
95  
96      public boolean isHalfClosed()
97      {
98          return halfClosed;
99      }
100 
101     @Override
102     public Object getAttribute(String key)
103     {
104         return attributes.get(key);
105     }
106 
107     @Override
108     public void setAttribute(String key, Object value)
109     {
110         attributes.put(key, value);
111     }
112 
113     @Override
114     public Object removeAttribute(String key)
115     {
116         return attributes.remove(key);
117     }
118 
119     @Override
120     public void setStreamFrameListener(StreamFrameListener listener)
121     {
122         this.listener = listener;
123     }
124 
125     @Override
126     public void updateCloseState(boolean close)
127     {
128         if (close)
129         {
130             if (isHalfClosed())
131                 closed = true;
132             else
133                 halfClosed = true;
134         }
135     }
136 
137     @Override
138     public void process(ControlFrame frame)
139     {
140         switch (frame.getType())
141         {
142             case SYN_STREAM:
143             {
144                 opened = true;
145                 break;
146             }
147             case SYN_REPLY:
148             {
149                 opened = true;
150                 SynReplyFrame synReply = (SynReplyFrame)frame;
151                 updateCloseState(synReply.isClose());
152                 ReplyInfo replyInfo = new ReplyInfo(synReply.getHeaders(), synReply.isClose());
153                 notifyOnReply(replyInfo);
154                 break;
155             }
156             case HEADERS:
157             {
158                 HeadersFrame headers = (HeadersFrame)frame;
159                 updateCloseState(headers.isClose());
160                 HeadersInfo headersInfo = new HeadersInfo(headers.getHeaders(), headers.isClose(), headers.isResetCompression());
161                 notifyOnHeaders(headersInfo);
162                 break;
163             }
164             case WINDOW_UPDATE:
165             {
166                 WindowUpdateFrame windowUpdate = (WindowUpdateFrame)frame;
167                 updateWindowSize(windowUpdate.getWindowDelta());
168                 break;
169             }
170             case RST_STREAM:
171             {
172                 // TODO:
173                 break;
174             }
175             default:
176             {
177                 throw new IllegalStateException();
178             }
179         }
180         session.flush();
181     }
182 
183     @Override
184     public void process(DataFrame frame, ByteBuffer data)
185     {
186         if (!opened)
187         {
188             session.rst(new RstInfo(getId(), StreamStatus.PROTOCOL_ERROR));
189             return;
190         }
191 
192         updateCloseState(frame.isClose());
193 
194         ByteBufferDataInfo dataInfo = new ByteBufferDataInfo(data, frame.isClose(), frame.isCompress())
195         {
196             @Override
197             public void consume(int delta)
198             {
199                 super.consume(delta);
200 
201                 // This is the algorithm for flow control.
202                 // This method may be called multiple times with delta=1, but we only send a window
203                 // update when the whole dataInfo has been consumed.
204                 // Other policies may be to send window updates when consumed() is greater than
205                 // a certain threshold, etc. but for now the policy is not pluggable for simplicity.
206                 // Note that the frequency of window updates depends on the read buffer, that
207                 // should not be too smaller than the window size to avoid frequent window updates.
208                 // Therefore, a pluggable policy should be able to modify the read buffer capacity.
209                 if (consumed() == length() && !isClosed())
210                     windowUpdate(length());
211             }
212         };
213         notifyOnData(dataInfo);
214         session.flush();
215     }
216 
217     private void windowUpdate(int delta)
218     {
219         if (delta > 0)
220         {
221             WindowUpdateFrame windowUpdateFrame = new WindowUpdateFrame(session.getVersion(), getId(), delta);
222             session.control(this, windowUpdateFrame, 0, TimeUnit.MILLISECONDS, null, null);
223         }
224     }
225 
226     private void notifyOnReply(ReplyInfo replyInfo)
227     {
228         final StreamFrameListener listener = this.listener;
229         try
230         {
231             if (listener != null)
232             {
233                 logger.debug("Invoking reply callback with {} on listener {}", replyInfo, listener);
234                 listener.onReply(this, replyInfo);
235             }
236         }
237         catch (Exception x)
238         {
239             logger.info("Exception while notifying listener " + listener, x);
240         }
241     }
242 
243     private void notifyOnHeaders(HeadersInfo headersInfo)
244     {
245         final StreamFrameListener listener = this.listener;
246         try
247         {
248             if (listener != null)
249             {
250                 logger.debug("Invoking headers callback with {} on listener {}", frame, listener);
251                 listener.onHeaders(this, headersInfo);
252             }
253         }
254         catch (Exception x)
255         {
256             logger.info("Exception while notifying listener " + listener, x);
257         }
258     }
259 
260     private void notifyOnData(DataInfo dataInfo)
261     {
262         final StreamFrameListener listener = this.listener;
263         try
264         {
265             if (listener != null)
266             {
267                 logger.debug("Invoking data callback with {} on listener {}", dataInfo, listener);
268                 listener.onData(this, dataInfo);
269                 logger.debug("Invoked data callback with {} on listener {}", dataInfo, listener);
270             }
271         }
272         catch (Exception x)
273         {
274             logger.info("Exception while notifying listener " + listener, x);
275         }
276     }
277 
278     @Override
279     public Future<Void> reply(ReplyInfo replyInfo)
280     {
281         Promise<Void> result = new Promise<>();
282         reply(replyInfo, 0, TimeUnit.MILLISECONDS, result);
283         return result;
284     }
285 
286     @Override
287     public void reply(ReplyInfo replyInfo, long timeout, TimeUnit unit, Handler<Void> handler)
288     {
289         updateCloseState(replyInfo.isClose());
290         SynReplyFrame frame = new SynReplyFrame(session.getVersion(), replyInfo.getFlags(), getId(), replyInfo.getHeaders());
291         session.control(this, frame, timeout, unit, handler, null);
292     }
293 
294     @Override
295     public Future<Void> data(DataInfo dataInfo)
296     {
297         Promise<Void> result = new Promise<>();
298         data(dataInfo, 0, TimeUnit.MILLISECONDS, result);
299         return result;
300     }
301 
302     @Override
303     public void data(DataInfo dataInfo, long timeout, TimeUnit unit, Handler<Void> handler)
304     {
305         // Cannot update the close state here, because the data that we send may
306         // be flow controlled, so we need the stream to update the window size.
307         session.data(this, dataInfo, timeout, unit, handler, null);
308     }
309 
310     @Override
311     public Future<Void> headers(HeadersInfo headersInfo)
312     {
313         Promise<Void> result = new Promise<>();
314         headers(headersInfo, 0, TimeUnit.MILLISECONDS, result);
315         return result;
316     }
317 
318     @Override
319     public void headers(HeadersInfo headersInfo, long timeout, TimeUnit unit, Handler<Void> handler)
320     {
321         updateCloseState(headersInfo.isClose());
322         HeadersFrame frame = new HeadersFrame(session.getVersion(), headersInfo.getFlags(), getId(), headersInfo.getHeaders());
323         session.control(this, frame, timeout, unit, handler, null);
324     }
325 
326     @Override
327     public boolean isClosed()
328     {
329         return closed;
330     }
331 
332     @Override
333     public String toString()
334     {
335         return String.format("stream=%d v%d closed=%s", getId(), session.getVersion(), isClosed() ? "true" : isHalfClosed() ? "half" : "false");
336     }
337 }