View Javadoc
1   /*
2    * Copyright 2014 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
5    * "License"); you may not use this file except in compliance with the License. You may obtain a
6    * 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 distributed under the License
11   * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12   * or implied. See the License for the specific language governing permissions and limitations under
13   * the License.
14   */
15  package io.netty.handler.codec.http2;
16  
17  import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
18  import static io.netty.handler.codec.http2.Http2Exception.connectionError;
19  import java.util.Map.Entry;
20  
21  import io.netty.channel.ChannelHandlerContext;
22  import io.netty.handler.codec.AsciiString;
23  import io.netty.handler.codec.TextHeaders.EntryVisitor;
24  import io.netty.handler.codec.http.DefaultHttpHeaders;
25  import io.netty.handler.codec.http.FullHttpMessage;
26  import io.netty.handler.codec.http.HttpHeaders;
27  import io.netty.util.collection.IntObjectHashMap;
28  import io.netty.util.collection.IntObjectMap;
29  import io.netty.util.internal.PlatformDependent;
30  
31  /**
32   * Translate header/data/priority HTTP/2 frame events into HTTP events.  Just as {@link InboundHttp2ToHttpAdapter}
33   * may generate multiple {@link FullHttpMessage} objects per stream, this class is more likely to
34   * generate multiple messages per stream because the chances of an HTTP/2 event happening outside
35   * the header/data message flow is more likely.
36   */
37  public final class InboundHttp2ToHttpPriorityAdapter extends InboundHttp2ToHttpAdapter {
38      private static final AsciiString OUT_OF_MESSAGE_SEQUENCE_METHOD = new AsciiString(
39              HttpUtil.OUT_OF_MESSAGE_SEQUENCE_METHOD.toString());
40      private static final AsciiString OUT_OF_MESSAGE_SEQUENCE_PATH = new AsciiString(
41              HttpUtil.OUT_OF_MESSAGE_SEQUENCE_PATH);
42      private static final AsciiString OUT_OF_MESSAGE_SEQUENCE_RETURN_CODE = new AsciiString(
43              HttpUtil.OUT_OF_MESSAGE_SEQUENCE_RETURN_CODE.toString());
44      private final IntObjectMap<HttpHeaders> outOfMessageFlowHeaders;
45  
46      public static final class Builder extends InboundHttp2ToHttpAdapter.Builder {
47  
48          /**
49           * Creates a new {@link InboundHttp2ToHttpPriorityAdapter} builder for the specified {@link Http2Connection}.
50           *
51           * @param connection The object which will provide connection notification events for the current connection
52           */
53          public Builder(Http2Connection connection) {
54              super(connection);
55          }
56  
57          @Override
58          public InboundHttp2ToHttpPriorityAdapter build() {
59              final InboundHttp2ToHttpPriorityAdapter instance = new InboundHttp2ToHttpPriorityAdapter(this);
60              instance.connection.addListener(instance);
61              return instance;
62          }
63      }
64  
65      InboundHttp2ToHttpPriorityAdapter(Builder builder) {
66          super(builder);
67          outOfMessageFlowHeaders = new IntObjectHashMap<HttpHeaders>();
68      }
69  
70      @Override
71      protected void removeMessage(int streamId) {
72          super.removeMessage(streamId);
73          outOfMessageFlowHeaders.remove(streamId);
74      }
75  
76      /**
77       * Get either the header or the trailing headers depending on which is valid to add to
78       * @param msg The message containing the headers and trailing headers
79       * @return The headers object which can be appended to or modified
80       */
81      private static HttpHeaders getActiveHeaders(FullHttpMessage msg) {
82          return msg.content().isReadable() ? msg.trailingHeaders() : msg.headers();
83      }
84  
85      /**
86       * This method will add the {@code headers} to the out of order headers map
87       * @param streamId The stream id associated with {@code headers}
88       * @param headers Newly encountered out of order headers which must be stored for future use
89       */
90      private void importOutOfMessageFlowHeaders(int streamId, HttpHeaders headers) {
91          final HttpHeaders outOfMessageFlowHeader = outOfMessageFlowHeaders.get(streamId);
92          if (outOfMessageFlowHeader == null) {
93              outOfMessageFlowHeaders.put(streamId, headers);
94          } else {
95              outOfMessageFlowHeader.setAll(headers);
96          }
97      }
98  
99      /**
100      * Take any saved out of order headers and export them to {@code headers}
101      * @param streamId The stream id to search for out of order headers for
102      * @param headers If any out of order headers exist for {@code streamId} they will be added to this object
103      */
104     private void exportOutOfMessageFlowHeaders(int streamId, final HttpHeaders headers) {
105         final HttpHeaders outOfMessageFlowHeader = outOfMessageFlowHeaders.get(streamId);
106         if (outOfMessageFlowHeader != null) {
107             headers.setAll(outOfMessageFlowHeader);
108         }
109     }
110 
111     /**
112      * This will remove all headers which are related to priority tree events
113      * @param headers The headers to remove the priority tree elements from
114      */
115     private static void removePriorityRelatedHeaders(HttpHeaders headers) {
116         headers.remove(HttpUtil.ExtensionHeaderNames.STREAM_DEPENDENCY_ID.text());
117         headers.remove(HttpUtil.ExtensionHeaderNames.STREAM_WEIGHT.text());
118     }
119 
120     /**
121      * Initializes the pseudo header fields for out of message flow HTTP/2 headers
122      * @param headers The headers to be initialized with pseudo header values
123      */
124     private void initializePseudoHeaders(Http2Headers headers) {
125         if (connection.isServer()) {
126             headers.method(OUT_OF_MESSAGE_SEQUENCE_METHOD).path(OUT_OF_MESSAGE_SEQUENCE_PATH);
127         } else {
128             headers.status(OUT_OF_MESSAGE_SEQUENCE_RETURN_CODE);
129         }
130     }
131 
132     /**
133      * Add all the HTTP headers into the HTTP/2 headers object
134      * @param httpHeaders The HTTP headers to translate to HTTP/2
135      * @param http2Headers The target HTTP/2 headers
136      */
137     private static void addHttpHeadersToHttp2Headers(HttpHeaders httpHeaders, final Http2Headers http2Headers) {
138         try {
139             httpHeaders.forEachEntry(new EntryVisitor() {
140                 @Override
141                 public boolean visit(Entry<CharSequence, CharSequence> entry) throws Exception {
142                     http2Headers.add(AsciiString.of(entry.getKey()), AsciiString.of(entry.getValue()));
143                     return true;
144                 }
145             });
146         } catch (Exception ex) {
147             PlatformDependent.throwException(ex);
148         }
149     }
150 
151     @Override
152     protected void fireChannelRead(ChannelHandlerContext ctx, FullHttpMessage msg, int streamId) {
153         exportOutOfMessageFlowHeaders(streamId, getActiveHeaders(msg));
154         super.fireChannelRead(ctx, msg, streamId);
155     }
156 
157     @Override
158     protected FullHttpMessage processHeadersBegin(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
159             boolean endOfStream, boolean allowAppend, boolean appendToTrailer) throws Http2Exception {
160         FullHttpMessage msg = super.processHeadersBegin(ctx, streamId, headers,
161                 endOfStream, allowAppend, appendToTrailer);
162         if (msg != null) {
163             exportOutOfMessageFlowHeaders(streamId, getActiveHeaders(msg));
164         }
165         return msg;
166     }
167 
168     @Override
169     public void priorityTreeParentChanged(Http2Stream stream, Http2Stream oldParent) {
170         Http2Stream parent = stream.parent();
171         FullHttpMessage msg = messageMap.get(stream.id());
172         if (msg == null) {
173             // msg may be null if a HTTP/2 frame event in received outside the HTTP message flow
174             // For example a PRIORITY frame can be received in any state
175             // and the HTTP message flow exists in OPEN.
176             if (parent != null && !parent.equals(connection.connectionStream())) {
177                 HttpHeaders headers = new DefaultHttpHeaders();
178                 headers.setInt(HttpUtil.ExtensionHeaderNames.STREAM_DEPENDENCY_ID.text(), parent.id());
179                 importOutOfMessageFlowHeaders(stream.id(), headers);
180             }
181         } else {
182             if (parent == null) {
183                 removePriorityRelatedHeaders(msg.headers());
184                 removePriorityRelatedHeaders(msg.trailingHeaders());
185             } else if (!parent.equals(connection.connectionStream())) {
186                 HttpHeaders headers = getActiveHeaders(msg);
187                 headers.setInt(HttpUtil.ExtensionHeaderNames.STREAM_DEPENDENCY_ID.text(), parent.id());
188             }
189         }
190     }
191 
192     @Override
193     public void onWeightChanged(Http2Stream stream, short oldWeight) {
194         FullHttpMessage msg = messageMap.get(stream.id());
195         final HttpHeaders headers;
196         if (msg == null) {
197             // msg may be null if a HTTP/2 frame event in received outside the HTTP message flow
198             // For example a PRIORITY frame can be received in any state
199             // and the HTTP message flow exists in OPEN.
200             headers = new DefaultHttpHeaders();
201             importOutOfMessageFlowHeaders(stream.id(), headers);
202         } else {
203             headers = getActiveHeaders(msg);
204         }
205         headers.setShort(HttpUtil.ExtensionHeaderNames.STREAM_WEIGHT.text(), stream.weight());
206     }
207 
208     @Override
209     public void onPriorityRead(ChannelHandlerContext ctx, int streamId, int streamDependency, short weight,
210                     boolean exclusive) throws Http2Exception {
211         FullHttpMessage msg = messageMap.get(streamId);
212         if (msg == null) {
213             HttpHeaders httpHeaders = outOfMessageFlowHeaders.remove(streamId);
214             if (httpHeaders == null) {
215                 throw connectionError(PROTOCOL_ERROR, "Priority Frame recieved for unknown stream id %d", streamId);
216             }
217 
218             Http2Headers http2Headers = new DefaultHttp2Headers();
219             initializePseudoHeaders(http2Headers);
220             addHttpHeadersToHttp2Headers(httpHeaders, http2Headers);
221             msg = newMessage(streamId, http2Headers, validateHttpHeaders);
222             fireChannelRead(ctx, msg, streamId);
223         }
224     }
225 }