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 the bytes (we queue them by
168         // calling onContent()), and the parsing will continue
169         // as soon as this method returns, eventually leading
170         // to reusing the underlying buffer for more reads.
171         final ByteBufferPool byteBufferPool = getByteBufferPool();
172         ByteBuffer original = frame.getData();
173         int length = original.remaining();
174         final ByteBuffer copy = byteBufferPool.acquire(length, original.isDirect());
175         BufferUtil.clearToFill(copy);
176         copy.put(original);
177         BufferUtil.flipToFlush(copy, 0);
178 
179         boolean handle = onContent(new HttpInput.Content(copy)
180         {
181             @Override
182             public boolean isNonBlocking()
183             {
184                 return callback.isNonBlocking();
185             }
186 
187             @Override
188             public void succeeded()
189             {
190                 byteBufferPool.release(copy);
191                 callback.succeeded();
192             }
193 
194             @Override
195             public void failed(Throwable x)
196             {
197                 byteBufferPool.release(copy);
198                 callback.failed(x);
199             }
200         });
201 
202         boolean endStream = frame.isEndStream();
203         if (endStream)
204             handle |= onRequestComplete();
205 
206         if (LOG.isDebugEnabled())
207         {
208             Stream stream = getStream();
209             LOG.debug("HTTP2 Request #{}/{}: {} bytes of {} content, handle: {}",
210                     stream.getId(),
211                     Integer.toHexString(stream.getSession().hashCode()),
212                     length,
213                     endStream ? "last" : "some",
214                     handle);
215         }
216 
217         boolean delayed = _delayedUntilContent;
218         _delayedUntilContent = false;
219 
220         return handle || delayed ? this : null;
221     }
222 
223     /**
224      * If the associated response has the Expect header set to 100 Continue,
225      * then accessing the input stream indicates that the handler/servlet
226      * is ready for the request body and thus a 100 Continue response is sent.
227      *
228      * @throws IOException if the InputStream cannot be created
229      */
230     @Override
231     public void continue100(int available) throws IOException
232     {
233         // If the client is expecting 100 CONTINUE, then send it now.
234         // TODO: consider using an AtomicBoolean ?
235         if (isExpecting100Continue())
236         {
237             _expect100Continue = false;
238 
239             // is content missing?
240             if (available == 0)
241             {
242                 if (getResponse().isCommitted())
243                     throw new IOException("Committed before 100 Continues");
244 
245                 boolean committed = sendResponse(HttpGenerator.CONTINUE_100_INFO, null, false);
246                 if (!committed)
247                     throw new IOException("Concurrent commit while trying to send 100-Continue");
248             }
249         }
250     }
251 }