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.server;
20  
21  import java.io.IOException;
22  import java.nio.ByteBuffer;
23  
24  import org.eclipse.jetty.http.HttpField;
25  import org.eclipse.jetty.http.HttpFields;
26  import org.eclipse.jetty.http.HttpGenerator;
27  import org.eclipse.jetty.http.HttpHeader;
28  import org.eclipse.jetty.http.HttpHeaderValue;
29  import org.eclipse.jetty.http.MetaData;
30  import org.eclipse.jetty.http.PreEncodedHttpField;
31  import org.eclipse.jetty.http2.IStream;
32  import org.eclipse.jetty.http2.api.Stream;
33  import org.eclipse.jetty.http2.frames.DataFrame;
34  import org.eclipse.jetty.http2.frames.HeadersFrame;
35  import org.eclipse.jetty.io.ByteBufferPool;
36  import org.eclipse.jetty.io.EndPoint;
37  import org.eclipse.jetty.server.Connector;
38  import org.eclipse.jetty.server.HttpChannel;
39  import org.eclipse.jetty.server.HttpConfiguration;
40  import org.eclipse.jetty.server.HttpInput;
41  import org.eclipse.jetty.util.BufferUtil;
42  import org.eclipse.jetty.util.Callback;
43  import org.eclipse.jetty.util.log.Log;
44  import org.eclipse.jetty.util.log.Logger;
45  
46  public class HttpChannelOverHTTP2 extends HttpChannel
47  {
48      private static final Logger LOG = Log.getLogger(HttpChannelOverHTTP2.class);
49      private static final HttpField SERVER_VERSION = new PreEncodedHttpField(HttpHeader.SERVER, HttpConfiguration.SERVER_VERSION);
50      private static final HttpField POWERED_BY = new PreEncodedHttpField(HttpHeader.X_POWERED_BY, HttpConfiguration.SERVER_VERSION);
51  
52      private boolean _expect100Continue;
53      private boolean _delayedUntilContent;
54  
55      public HttpChannelOverHTTP2(Connector connector, HttpConfiguration configuration, EndPoint endPoint, HttpTransportOverHTTP2 transport)
56      {
57          super(connector, configuration, endPoint, transport);
58      }
59  
60      private IStream getStream()
61      {
62          return getHttpTransport().getStream();
63      }
64  
65      @Override
66      public boolean isExpecting100Continue()
67      {
68          return _expect100Continue;
69      }
70  
71      public Runnable onRequest(HeadersFrame frame)
72      {
73          MetaData.Request request = (MetaData.Request)frame.getMetaData();
74          HttpFields fields = request.getFields();
75  
76          // HTTP/2 sends the Host header as the :authority
77          // pseudo-header, so we need to synthesize a Host header.
78          if (!fields.contains(HttpHeader.HOST))
79          {
80              String authority = request.getURI().getAuthority();
81              if (authority != null)
82              {
83                  // Lower-case to be consistent with other HTTP/2 headers.
84                  fields.put("host", authority);
85              }
86          }
87  
88          _expect100Continue = fields.contains(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString());
89  
90          HttpFields response = getResponse().getHttpFields();
91          if (getHttpConfiguration().getSendServerVersion())
92              response.add(SERVER_VERSION);
93          if (getHttpConfiguration().getSendXPoweredBy())
94              response.add(POWERED_BY);
95  
96          onRequest(request);
97  
98          boolean endStream = frame.isEndStream();
99          if (endStream)
100             onRequestComplete();
101 
102         _delayedUntilContent = getHttpConfiguration().isDelayDispatchUntilContent() &&
103                 !endStream && !_expect100Continue;
104 
105         if (LOG.isDebugEnabled())
106         {
107             Stream stream = getStream();
108             LOG.debug("HTTP2 Request #{}/{}, delayed={}:{}{} {} {}{}{}",
109                     stream.getId(), Integer.toHexString(stream.getSession().hashCode()),
110                     _delayedUntilContent, System.lineSeparator(),
111                     request.getMethod(), request.getURI(), request.getVersion(),
112                     System.lineSeparator(), fields);
113         }
114 
115         return _delayedUntilContent ? null : this;
116     }
117 
118     public Runnable onPushRequest(MetaData.Request request)
119     {
120         onRequest(request);
121         getRequest().setAttribute("org.eclipse.jetty.pushed", Boolean.TRUE);
122         onRequestComplete();
123 
124         if (LOG.isDebugEnabled())
125         {
126             Stream stream = getStream();
127             LOG.debug("HTTP2 PUSH Request #{}/{}:{}{} {} {}{}{}",
128                     stream.getId(), Integer.toHexString(stream.getSession().hashCode()), System.lineSeparator(),
129                     request.getMethod(), request.getURI(), request.getVersion(),
130                     System.lineSeparator(), request.getFields());
131         }
132 
133         return this;
134     }
135 
136     @Override
137     public HttpTransportOverHTTP2 getHttpTransport()
138     {
139         return (HttpTransportOverHTTP2)super.getHttpTransport();
140     }
141 
142     @Override
143     public void recycle()
144     {
145         _expect100Continue = false;
146         _delayedUntilContent = false;
147         super.recycle();
148         getHttpTransport().recycle();
149     }
150 
151     @Override
152     protected void commit(MetaData.Response info)
153     {
154         super.commit(info);
155         if (LOG.isDebugEnabled())
156         {
157             Stream stream = getStream();
158             LOG.debug("HTTP2 Commit Response #{}/{}:{}{} {} {}{}{}",
159                     stream.getId(), Integer.toHexString(stream.getSession().hashCode()), System.lineSeparator(), info.getVersion(), info.getStatus(), info.getReason(),
160                     System.lineSeparator(), info.getFields());
161         }
162     }
163 
164     public Runnable requestContent(DataFrame frame, final Callback callback)
165     {
166         // We must copy the data since we do not know when the
167         // application will consume its bytes (we queue them by
168         // calling onContent()), and we cannot stop the parsing
169         // since there may be frames for other streams.
170         final ByteBufferPool byteBufferPool = getByteBufferPool();
171         ByteBuffer original = frame.getData();
172         int length = original.remaining();
173         final ByteBuffer copy = byteBufferPool.acquire(length, original.isDirect());
174         BufferUtil.clearToFill(copy);
175         copy.put(original).flip();
176 
177         boolean handle = onContent(new HttpInput.Content(copy)
178         {
179             @Override
180             public boolean isNonBlocking()
181             {
182                 return callback.isNonBlocking();
183             }
184 
185             @Override
186             public void succeeded()
187             {
188                 byteBufferPool.release(copy);
189                 callback.succeeded();
190             }
191 
192             @Override
193             public void failed(Throwable x)
194             {
195                 byteBufferPool.release(copy);
196                 callback.failed(x);
197             }
198         });
199 
200         boolean endStream = frame.isEndStream();
201         if (endStream)
202             handle |= onRequestComplete();
203 
204         if (LOG.isDebugEnabled())
205         {
206             Stream stream = getStream();
207             LOG.debug("HTTP2 Request #{}/{}: {} bytes of {} content, handle: {}",
208                     stream.getId(),
209                     Integer.toHexString(stream.getSession().hashCode()),
210                     length,
211                     endStream ? "last" : "some",
212                     handle);
213         }
214 
215         boolean delayed = _delayedUntilContent;
216         _delayedUntilContent = false;
217 
218         return handle || delayed ? this : null;
219     }
220 
221     /**
222      * If the associated response has the Expect header set to 100 Continue,
223      * then accessing the input stream indicates that the handler/servlet
224      * is ready for the request body and thus a 100 Continue response is sent.
225      *
226      * @throws IOException if the InputStream cannot be created
227      */
228     @Override
229     public void continue100(int available) throws IOException
230     {
231         // If the client is expecting 100 CONTINUE, then send it now.
232         // TODO: consider using an AtomicBoolean ?
233         if (isExpecting100Continue())
234         {
235             _expect100Continue = false;
236 
237             // is content missing?
238             if (available == 0)
239             {
240                 if (getResponse().isCommitted())
241                     throw new IOException("Committed before 100 Continues");
242 
243                 boolean committed = sendResponse(HttpGenerator.CONTINUE_100_INFO, null, false);
244                 if (!committed)
245                     throw new IOException("Concurrent commit while trying to send 100-Continue");
246             }
247         }
248     }
249 }