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