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