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    * https://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  
16  package io.netty.handler.codec.http2;
17  
18  import io.netty.util.internal.PlatformDependent;
19  import io.netty.util.internal.SuppressJava6Requirement;
20  import io.netty.util.internal.ThrowableUtil;
21  import io.netty.util.internal.UnstableApi;
22  
23  import java.util.ArrayList;
24  import java.util.Arrays;
25  import java.util.Iterator;
26  import java.util.List;
27  
28  import static io.netty.handler.codec.http2.Http2CodecUtil.CONNECTION_STREAM_ID;
29  import static io.netty.util.internal.ObjectUtil.checkNotNull;
30  
31  /**
32   * Exception thrown when an HTTP/2 error was encountered.
33   */
34  @UnstableApi
35  public class Http2Exception extends Exception {
36      private static final long serialVersionUID = -6941186345430164209L;
37      private final Http2Error error;
38      private final ShutdownHint shutdownHint;
39  
40      public Http2Exception(Http2Error error) {
41          this(error, ShutdownHint.HARD_SHUTDOWN);
42      }
43  
44      public Http2Exception(Http2Error error, ShutdownHint shutdownHint) {
45          this.error = checkNotNull(error, "error");
46          this.shutdownHint = checkNotNull(shutdownHint, "shutdownHint");
47      }
48  
49      public Http2Exception(Http2Error error, String message) {
50          this(error, message, ShutdownHint.HARD_SHUTDOWN);
51      }
52  
53      public Http2Exception(Http2Error error, String message, ShutdownHint shutdownHint) {
54          super(message);
55          this.error = checkNotNull(error, "error");
56          this.shutdownHint = checkNotNull(shutdownHint, "shutdownHint");
57      }
58  
59      public Http2Exception(Http2Error error, String message, Throwable cause) {
60          this(error, message, cause, ShutdownHint.HARD_SHUTDOWN);
61      }
62  
63      public Http2Exception(Http2Error error, String message, Throwable cause, ShutdownHint shutdownHint) {
64          super(message, cause);
65          this.error = checkNotNull(error, "error");
66          this.shutdownHint = checkNotNull(shutdownHint, "shutdownHint");
67      }
68  
69      static Http2Exception newStatic(Http2Error error, String message, ShutdownHint shutdownHint,
70                                      Class<?> clazz, String method) {
71          final Http2Exception exception;
72          if (PlatformDependent.javaVersion() >= 7) {
73              exception = new StacklessHttp2Exception(error, message, shutdownHint, true);
74          } else {
75              exception = new StacklessHttp2Exception(error, message, shutdownHint);
76          }
77          return ThrowableUtil.unknownStackTrace(exception, clazz, method);
78      }
79  
80      @SuppressJava6Requirement(reason = "uses Java 7+ Exception.<init>(String, Throwable, boolean, boolean)" +
81              " but is guarded by version checks")
82      private Http2Exception(Http2Error error, String message, ShutdownHint shutdownHint, boolean shared) {
83          super(message, null, false, true);
84          assert shared;
85          this.error = checkNotNull(error, "error");
86          this.shutdownHint = checkNotNull(shutdownHint, "shutdownHint");
87      }
88  
89      public Http2Error error() {
90          return error;
91      }
92  
93      /**
94       * Provide a hint as to what type of shutdown should be executed. Note this hint may be ignored.
95       */
96      public ShutdownHint shutdownHint() {
97          return shutdownHint;
98      }
99  
100     /**
101      * Use if an error has occurred which can not be isolated to a single stream, but instead applies
102      * to the entire connection.
103      * @param error The type of error as defined by the HTTP/2 specification.
104      * @param fmt String with the content and format for the additional debug data.
105      * @param args Objects which fit into the format defined by {@code fmt}.
106      * @return An exception which can be translated into an HTTP/2 error.
107      */
108     public static Http2Exception connectionError(Http2Error error, String fmt, Object... args) {
109         return new Http2Exception(error, formatErrorMessage(fmt, args));
110     }
111 
112     /**
113      * Use if an error has occurred which can not be isolated to a single stream, but instead applies
114      * to the entire connection.
115      * @param error The type of error as defined by the HTTP/2 specification.
116      * @param cause The object which caused the error.
117      * @param fmt String with the content and format for the additional debug data.
118      * @param args Objects which fit into the format defined by {@code fmt}.
119      * @return An exception which can be translated into an HTTP/2 error.
120      */
121     public static Http2Exception connectionError(Http2Error error, Throwable cause,
122             String fmt, Object... args) {
123         return new Http2Exception(error, formatErrorMessage(fmt, args), cause);
124     }
125 
126     /**
127      * Use if an error has occurred which can not be isolated to a single stream, but instead applies
128      * to the entire connection.
129      * @param error The type of error as defined by the HTTP/2 specification.
130      * @param fmt String with the content and format for the additional debug data.
131      * @param args Objects which fit into the format defined by {@code fmt}.
132      * @return An exception which can be translated into an HTTP/2 error.
133      */
134     public static Http2Exception closedStreamError(Http2Error error, String fmt, Object... args) {
135         return new ClosedStreamCreationException(error, formatErrorMessage(fmt, args));
136     }
137 
138     /**
139      * Use if an error which can be isolated to a single stream has occurred.  If the {@code id} is not
140      * {@link Http2CodecUtil#CONNECTION_STREAM_ID} then a {@link StreamException} will be returned.
141      * Otherwise the error is considered a connection error and a {@link Http2Exception} is returned.
142      * @param id The stream id for which the error is isolated to.
143      * @param error The type of error as defined by the HTTP/2 specification.
144      * @param fmt String with the content and format for the additional debug data.
145      * @param args Objects which fit into the format defined by {@code fmt}.
146      * @return If the {@code id} is not
147      * {@link Http2CodecUtil#CONNECTION_STREAM_ID} then a {@link StreamException} will be returned.
148      * Otherwise the error is considered a connection error and a {@link Http2Exception} is returned.
149      */
150     public static Http2Exception streamError(int id, Http2Error error, String fmt, Object... args) {
151         return CONNECTION_STREAM_ID == id ?
152                 connectionError(error, fmt, args) :
153                     new StreamException(id, error, formatErrorMessage(fmt, args));
154     }
155 
156     /**
157      * Use if an error which can be isolated to a single stream has occurred.  If the {@code id} is not
158      * {@link Http2CodecUtil#CONNECTION_STREAM_ID} then a {@link StreamException} will be returned.
159      * Otherwise the error is considered a connection error and a {@link Http2Exception} is returned.
160      * @param id The stream id for which the error is isolated to.
161      * @param error The type of error as defined by the HTTP/2 specification.
162      * @param cause The object which caused the error.
163      * @param fmt String with the content and format for the additional debug data.
164      * @param args Objects which fit into the format defined by {@code fmt}.
165      * @return If the {@code id} is not
166      * {@link Http2CodecUtil#CONNECTION_STREAM_ID} then a {@link StreamException} will be returned.
167      * Otherwise the error is considered a connection error and a {@link Http2Exception} is returned.
168      */
169     public static Http2Exception streamError(int id, Http2Error error, Throwable cause,
170             String fmt, Object... args) {
171         return CONNECTION_STREAM_ID == id ?
172                 connectionError(error, cause, fmt, args) :
173                     new StreamException(id, error, formatErrorMessage(fmt, args), cause);
174     }
175 
176     /**
177      * A specific stream error resulting from failing to decode headers that exceeds the max header size list.
178      * If the {@code id} is not {@link Http2CodecUtil#CONNECTION_STREAM_ID} then a
179      * {@link StreamException} will be returned. Otherwise the error is considered a
180      * connection error and a {@link Http2Exception} is returned.
181      * @param id The stream id for which the error is isolated to.
182      * @param error The type of error as defined by the HTTP/2 specification.
183      * @param onDecode Whether this error was caught while decoding headers
184      * @param fmt String with the content and format for the additional debug data.
185      * @param args Objects which fit into the format defined by {@code fmt}.
186      * @return If the {@code id} is not
187      * {@link Http2CodecUtil#CONNECTION_STREAM_ID} then a {@link HeaderListSizeException}
188      * will be returned. Otherwise the error is considered a connection error and a {@link Http2Exception} is
189      * returned.
190      */
191     public static Http2Exception headerListSizeError(int id, Http2Error error, boolean onDecode,
192             String fmt, Object... args) {
193         return CONNECTION_STREAM_ID == id ?
194                 connectionError(error, fmt, args) :
195                     new HeaderListSizeException(id, error, formatErrorMessage(fmt, args), onDecode);
196     }
197 
198     private static String formatErrorMessage(String fmt, Object[] args) {
199         if (fmt == null) {
200             if (args == null || args.length == 0) {
201                 return "Unexpected error";
202             }
203             return "Unexpected error: " + Arrays.toString(args);
204         }
205         return String.format(fmt, args);
206     }
207 
208     /**
209      * Check if an exception is isolated to a single stream or the entire connection.
210      * @param e The exception to check.
211      * @return {@code true} if {@code e} is an instance of {@link StreamException}.
212      * {@code false} otherwise.
213      */
214     public static boolean isStreamError(Http2Exception e) {
215         return e instanceof StreamException;
216     }
217 
218     /**
219      * Get the stream id associated with an exception.
220      * @param e The exception to get the stream id for.
221      * @return {@link Http2CodecUtil#CONNECTION_STREAM_ID} if {@code e} is a connection error.
222      * Otherwise the stream id associated with the stream error.
223      */
224     public static int streamId(Http2Exception e) {
225         return isStreamError(e) ? ((StreamException) e).streamId() : CONNECTION_STREAM_ID;
226     }
227 
228     /**
229      * Provides a hint as to if shutdown is justified, what type of shutdown should be executed.
230      */
231     public enum ShutdownHint {
232         /**
233          * Do not shutdown the underlying channel.
234          */
235         NO_SHUTDOWN,
236         /**
237          * Attempt to execute a "graceful" shutdown. The definition of "graceful" is left to the implementation.
238          * An example of "graceful" would be wait for some amount of time until all active streams are closed.
239          */
240         GRACEFUL_SHUTDOWN,
241         /**
242          * Close the channel immediately after a {@code GOAWAY} is sent.
243          */
244         HARD_SHUTDOWN
245     }
246 
247     /**
248      * Used when a stream creation attempt fails but may be because the stream was previously closed.
249      */
250     public static final class ClosedStreamCreationException extends Http2Exception {
251         private static final long serialVersionUID = -6746542974372246206L;
252 
253         public ClosedStreamCreationException(Http2Error error) {
254             super(error);
255         }
256 
257         public ClosedStreamCreationException(Http2Error error, String message) {
258             super(error, message);
259         }
260 
261         public ClosedStreamCreationException(Http2Error error, String message, Throwable cause) {
262             super(error, message, cause);
263         }
264     }
265 
266     /**
267      * Represents an exception that can be isolated to a single stream (as opposed to the entire connection).
268      */
269     public static class StreamException extends Http2Exception {
270         private static final long serialVersionUID = 602472544416984384L;
271         private final int streamId;
272 
273         StreamException(int streamId, Http2Error error, String message) {
274             super(error, message, ShutdownHint.NO_SHUTDOWN);
275             this.streamId = streamId;
276         }
277 
278         StreamException(int streamId, Http2Error error, String message, Throwable cause) {
279             super(error, message, cause, ShutdownHint.NO_SHUTDOWN);
280             this.streamId = streamId;
281         }
282 
283         public int streamId() {
284             return streamId;
285         }
286     }
287 
288     public static final class HeaderListSizeException extends StreamException {
289         private static final long serialVersionUID = -8807603212183882637L;
290 
291         private final boolean decode;
292 
293         HeaderListSizeException(int streamId, Http2Error error, String message, boolean decode) {
294             super(streamId, error, message);
295             this.decode = decode;
296         }
297 
298         public boolean duringDecode() {
299             return decode;
300         }
301     }
302 
303     /**
304      * Provides the ability to handle multiple stream exceptions with one throw statement.
305      */
306     public static final class CompositeStreamException extends Http2Exception implements Iterable<StreamException> {
307         private static final long serialVersionUID = 7091134858213711015L;
308         private final List<StreamException> exceptions;
309 
310         public CompositeStreamException(Http2Error error, int initialCapacity) {
311             super(error, ShutdownHint.NO_SHUTDOWN);
312             exceptions = new ArrayList<StreamException>(initialCapacity);
313         }
314 
315         public void add(StreamException e) {
316             exceptions.add(e);
317         }
318 
319         @Override
320         public Iterator<StreamException> iterator() {
321             return exceptions.iterator();
322         }
323     }
324 
325     private static final class StacklessHttp2Exception extends Http2Exception {
326 
327         private static final long serialVersionUID = 1077888485687219443L;
328 
329         StacklessHttp2Exception(Http2Error error, String message, ShutdownHint shutdownHint) {
330             super(error, message, shutdownHint);
331         }
332 
333         StacklessHttp2Exception(Http2Error error, String message, ShutdownHint shutdownHint, boolean shared) {
334             super(error, message, shutdownHint, shared);
335         }
336 
337         // Override fillInStackTrace() so we not populate the backtrace via a native call and so leak the
338         // Classloader.
339         @Override
340         public Throwable fillInStackTrace() {
341             return this;
342         }
343     }
344 }