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;
20  
21  import java.io.IOException;
22  import java.nio.ByteBuffer;
23  import java.util.Queue;
24  import java.util.concurrent.Executor;
25  import java.util.concurrent.atomic.AtomicLong;
26  
27  import org.eclipse.jetty.http2.parser.Parser;
28  import org.eclipse.jetty.io.AbstractConnection;
29  import org.eclipse.jetty.io.ByteBufferPool;
30  import org.eclipse.jetty.io.EndPoint;
31  import org.eclipse.jetty.util.BufferUtil;
32  import org.eclipse.jetty.util.Callback;
33  import org.eclipse.jetty.util.ConcurrentArrayQueue;
34  import org.eclipse.jetty.util.log.Log;
35  import org.eclipse.jetty.util.log.Logger;
36  import org.eclipse.jetty.util.thread.ExecutionStrategy;
37  
38  public class HTTP2Connection extends AbstractConnection
39  {
40      protected static final Logger LOG = Log.getLogger(HTTP2Connection.class);
41  
42      private final Queue<Runnable> tasks = new ConcurrentArrayQueue<>();
43      private final HTTP2Producer producer = new HTTP2Producer();
44      private final AtomicLong bytesIn = new AtomicLong();
45      private final ByteBufferPool byteBufferPool;
46      private final Parser parser;
47      private final ISession session;
48      private final int bufferSize;
49      private final ExecutionStrategy executionStrategy;
50  
51      public HTTP2Connection(ByteBufferPool byteBufferPool, Executor executor, EndPoint endPoint, Parser parser, ISession session, int bufferSize, ExecutionStrategy.Factory executionFactory)
52      {
53          super(endPoint, executor);
54          this.byteBufferPool = byteBufferPool;
55          this.parser = parser;
56          this.session = session;
57          this.bufferSize = bufferSize;
58          this.executionStrategy = executionFactory.newExecutionStrategy(producer, executor);
59      }
60  
61      @Override
62      public long getBytesIn()
63      {
64          return bytesIn.get();
65      }
66  
67      @Override
68      public long getBytesOut()
69      {
70          return session.getBytesWritten();
71      }
72  
73      public ISession getSession()
74      {
75          return session;
76      }
77  
78      protected Parser getParser()
79      {
80          return parser;
81      }
82  
83      protected void setInputBuffer(ByteBuffer buffer)
84      {
85          producer.buffer = buffer;
86      }
87  
88      @Override
89      public void onOpen()
90      {
91          if (LOG.isDebugEnabled())
92              LOG.debug("HTTP2 Open {} ", this);
93          super.onOpen();
94          executionStrategy.execute();
95      }
96  
97      @Override
98      public void onClose()
99      {
100         if (LOG.isDebugEnabled())
101             LOG.debug("HTTP2 Close {} ", this);
102         super.onClose();
103     }
104 
105     @Override
106     public void onFillable()
107     {
108         if (LOG.isDebugEnabled())
109             LOG.debug("HTTP2 onFillable {} ", this);
110         executionStrategy.execute();
111     }
112 
113     private int fill(EndPoint endPoint, ByteBuffer buffer)
114     {
115         try
116         {
117             if (endPoint.isInputShutdown())
118                 return -1;
119             return endPoint.fill(buffer);
120         }
121         catch (IOException x)
122         {
123             LOG.debug("Could not read from " + endPoint, x);
124             return -1;
125         }
126     }
127 
128     @Override
129     public boolean onIdleExpired()
130     {
131         boolean idle = isFillInterested();
132         if (idle)
133         {
134             boolean close = session.onIdleTimeout();
135             if (close)
136                 session.close(ErrorCode.NO_ERROR.code, "idle_timeout", Callback.NOOP);
137         }
138         return false;
139     }
140 
141     protected void offerTask(Runnable task, boolean dispatch)
142     {
143         tasks.offer(task);
144         if (dispatch)
145             executionStrategy.dispatch();
146         else
147             executionStrategy.execute();
148     }
149 
150     @Override
151     public void close()
152     {
153         // We don't call super from here, otherwise we close the
154         // endPoint and we're not able to read or write anymore.
155         session.close(ErrorCode.NO_ERROR.code, "close", Callback.NOOP);
156     }
157 
158     protected class HTTP2Producer implements ExecutionStrategy.Producer
159     {
160         private final Callback fillCallback = new FillCallback();
161         private ByteBuffer buffer;
162 
163         @Override
164         public Runnable produce()
165         {
166             Runnable task = tasks.poll();
167             if (LOG.isDebugEnabled())
168                 LOG.debug("Dequeued task {}", task);
169             if (task != null)
170                 return task;
171 
172             if (isFillInterested())
173                 return null;
174 
175             if (buffer == null)
176                 buffer = byteBufferPool.acquire(bufferSize, false); // TODO: make directness customizable
177             boolean looping = BufferUtil.hasContent(buffer);
178             while (true)
179             {
180                 if (looping)
181                 {
182                     while (buffer.hasRemaining())
183                         parser.parse(buffer);
184 
185                     task = tasks.poll();
186                     if (LOG.isDebugEnabled())
187                         LOG.debug("Dequeued new task {}", task);
188                     if (task != null)
189                     {
190                         release();
191                         return task;
192                     }
193                 }
194 
195                 int filled = fill(getEndPoint(), buffer);
196                 if (LOG.isDebugEnabled())
197                     LOG.debug("Filled {} bytes", filled);
198 
199                 if (filled == 0)
200                 {
201                     release();
202                     getEndPoint().fillInterested(fillCallback);
203                     return null;
204                 }
205                 else if (filled < 0)
206                 {
207                     release();
208                     session.onShutdown();
209                     return null;
210                 }
211                 else
212                 {
213                     bytesIn.addAndGet(filled);
214                 }
215 
216                 looping = true;
217             }
218         }
219 
220         private void release()
221         {
222             if (buffer != null && !buffer.hasRemaining())
223             {
224                 byteBufferPool.release(buffer);
225                 buffer = null;
226             }
227         }
228     }
229 
230     private class FillCallback implements Callback.NonBlocking
231     {
232         @Override
233         public void succeeded()
234         {
235             onFillable();
236         }
237 
238         @Override
239         public void failed(Throwable x)
240         {
241             onFillInterestedFailed(x);
242         }
243     }
244 }