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