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  
16  package io.netty.handler.codec.http2;
17  
18  import io.netty.util.internal.UnstableApi;
19  
20  import java.util.ArrayList;
21  import java.util.Iterator;
22  import java.util.List;
23  
24  import static io.netty.handler.codec.http2.Http2CodecUtil.CONNECTION_STREAM_ID;
25  import static io.netty.util.internal.ObjectUtil.checkNotNull;
26  
27  /**
28   * Exception thrown when an HTTP/2 error was encountered.
29   */
30  @UnstableApi
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      public Http2Error error() {
66          return error;
67      }
68  
69      /**
70       * Provide a hint as to what type of shutdown should be executed. Note this hint may be ignored.
71       */
72      public ShutdownHint shutdownHint() {
73          return shutdownHint;
74      }
75  
76      /**
77       * Use if an error has occurred which can not be isolated to a single stream, but instead applies
78       * to the entire connection.
79       * @param error The type of error as defined by the HTTP/2 specification.
80       * @param fmt String with the content and format for the additional debug data.
81       * @param args Objects which fit into the format defined by {@code fmt}.
82       * @return An exception which can be translated into a HTTP/2 error.
83       */
84      public static Http2Exception connectionError(Http2Error error, String fmt, Object... args) {
85          return new Http2Exception(error, String.format(fmt, args));
86      }
87  
88      /**
89       * Use if an error has occurred which can not be isolated to a single stream, but instead applies
90       * to the entire connection.
91       * @param error The type of error as defined by the HTTP/2 specification.
92       * @param cause The object which caused the error.
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 a HTTP/2 error.
96       */
97      public static Http2Exception connectionError(Http2Error error, Throwable cause,
98              String fmt, Object... args) {
99          return new Http2Exception(error, String.format(fmt, args), cause);
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 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 a HTTP/2 error.
109      */
110     public static Http2Exception closedStreamError(Http2Error error, String fmt, Object... args) {
111         return new ClosedStreamCreationException(error, String.format(fmt, args));
112     }
113 
114     /**
115      * Use if an error which can be isolated to a single stream has occurred.  If the {@code id} is not
116      * {@link Http2CodecUtil#CONNECTION_STREAM_ID} then a {@link Http2Exception.StreamException} will be returned.
117      * Otherwise the error is considered a connection error and a {@link Http2Exception} is returned.
118      * @param id The stream id for which the error is isolated to.
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 If the {@code id} is not
123      * {@link Http2CodecUtil#CONNECTION_STREAM_ID} then a {@link Http2Exception.StreamException} will be returned.
124      * Otherwise the error is considered a connection error and a {@link Http2Exception} is returned.
125      */
126     public static Http2Exception streamError(int id, Http2Error error, String fmt, Object... args) {
127         return CONNECTION_STREAM_ID == id ?
128                 Http2Exception.connectionError(error, fmt, args) :
129                     new StreamException(id, error, String.format(fmt, args));
130     }
131 
132     /**
133      * Use if an error which can be isolated to a single stream has occurred.  If the {@code id} is not
134      * {@link Http2CodecUtil#CONNECTION_STREAM_ID} then a {@link Http2Exception.StreamException} will be returned.
135      * Otherwise the error is considered a connection error and a {@link Http2Exception} is returned.
136      * @param id The stream id for which the error is isolated to.
137      * @param error The type of error as defined by the HTTP/2 specification.
138      * @param cause The object which caused the error.
139      * @param fmt String with the content and format for the additional debug data.
140      * @param args Objects which fit into the format defined by {@code fmt}.
141      * @return If the {@code id} is not
142      * {@link Http2CodecUtil#CONNECTION_STREAM_ID} then a {@link Http2Exception.StreamException} will be returned.
143      * Otherwise the error is considered a connection error and a {@link Http2Exception} is returned.
144      */
145     public static Http2Exception streamError(int id, Http2Error error, Throwable cause,
146             String fmt, Object... args) {
147         return CONNECTION_STREAM_ID == id ?
148                 Http2Exception.connectionError(error, cause, fmt, args) :
149                     new StreamException(id, error, String.format(fmt, args), cause);
150     }
151 
152     /**
153      * A specific stream error resulting from failing to decode headers that exceeds the max header size list.
154      * If the {@code id} is not {@link Http2CodecUtil#CONNECTION_STREAM_ID} then a
155      * {@link Http2Exception.StreamException} will be returned. Otherwise the error is considered a
156      * connection error and a {@link Http2Exception} is returned.
157      * @param id The stream id for which the error is isolated to.
158      * @param error The type of error as defined by the HTTP/2 specification.
159      * @param onDecode Whether this error was caught while decoding headers
160      * @param fmt String with the content and format for the additional debug data.
161      * @param args Objects which fit into the format defined by {@code fmt}.
162      * @return If the {@code id} is not
163      * {@link Http2CodecUtil#CONNECTION_STREAM_ID} then a {@link HeaderListSizeException}
164      * will be returned. Otherwise the error is considered a connection error and a {@link Http2Exception} is
165      * returned.
166      */
167     public static Http2Exception headerListSizeError(int id, Http2Error error, boolean onDecode,
168             String fmt, Object... args) {
169         return CONNECTION_STREAM_ID == id ?
170                 Http2Exception.connectionError(error, fmt, args) :
171                     new HeaderListSizeException(id, error, String.format(fmt, args), onDecode);
172     }
173 
174     /**
175      * Check if an exception is isolated to a single stream or the entire connection.
176      * @param e The exception to check.
177      * @return {@code true} if {@code e} is an instance of {@link Http2Exception.StreamException}.
178      * {@code false} otherwise.
179      */
180     public static boolean isStreamError(Http2Exception e) {
181         return e instanceof StreamException;
182     }
183 
184     /**
185      * Get the stream id associated with an exception.
186      * @param e The exception to get the stream id for.
187      * @return {@link Http2CodecUtil#CONNECTION_STREAM_ID} if {@code e} is a connection error.
188      * Otherwise the stream id associated with the stream error.
189      */
190     public static int streamId(Http2Exception e) {
191         return isStreamError(e) ? ((StreamException) e).streamId() : CONNECTION_STREAM_ID;
192     }
193 
194     /**
195      * Provides a hint as to if shutdown is justified, what type of shutdown should be executed.
196      */
197     public static enum ShutdownHint {
198         /**
199          * Do not shutdown the underlying channel.
200          */
201         NO_SHUTDOWN,
202         /**
203          * Attempt to execute a "graceful" shutdown. The definition of "graceful" is left to the implementation.
204          * An example of "graceful" would be wait for some amount of time until all active streams are closed.
205          */
206         GRACEFUL_SHUTDOWN,
207         /**
208          * Close the channel immediately after a {@code GOAWAY} is sent.
209          */
210         HARD_SHUTDOWN;
211     }
212 
213     /**
214      * Used when a stream creation attempt fails but may be because the stream was previously closed.
215      */
216     public static final class ClosedStreamCreationException extends Http2Exception {
217         private static final long serialVersionUID = -6746542974372246206L;
218 
219         public ClosedStreamCreationException(Http2Error error) {
220             super(error);
221         }
222 
223         public ClosedStreamCreationException(Http2Error error, String message) {
224             super(error, message);
225         }
226 
227         public ClosedStreamCreationException(Http2Error error, String message, Throwable cause) {
228             super(error, message, cause);
229         }
230     }
231 
232     /**
233      * Represents an exception that can be isolated to a single stream (as opposed to the entire connection).
234      */
235     public static class StreamException extends Http2Exception {
236         private static final long serialVersionUID = 602472544416984384L;
237         private final int streamId;
238 
239         StreamException(int streamId, Http2Error error, String message) {
240             super(error, message, ShutdownHint.NO_SHUTDOWN);
241             this.streamId = streamId;
242         }
243 
244         StreamException(int streamId, Http2Error error, String message, Throwable cause) {
245             super(error, message, cause, ShutdownHint.NO_SHUTDOWN);
246             this.streamId = streamId;
247         }
248 
249         public int streamId() {
250             return streamId;
251         }
252     }
253 
254     public static final class HeaderListSizeException extends StreamException {
255         private static final long serialVersionUID = -8807603212183882637L;
256 
257         private final boolean decode;
258 
259         HeaderListSizeException(int streamId, Http2Error error, String message, boolean decode) {
260             super(streamId, error, message);
261             this.decode = decode;
262         }
263 
264         public boolean duringDecode() {
265             return decode;
266         }
267     }
268 
269     /**
270      * Provides the ability to handle multiple stream exceptions with one throw statement.
271      */
272     public static final class CompositeStreamException extends Http2Exception implements Iterable<StreamException> {
273         private static final long serialVersionUID = 7091134858213711015L;
274         private final List<StreamException> exceptions;
275 
276         public CompositeStreamException(Http2Error error, int initialCapacity) {
277             super(error, ShutdownHint.NO_SHUTDOWN);
278             exceptions = new ArrayList<StreamException>(initialCapacity);
279         }
280 
281         public void add(StreamException e) {
282             exceptions.add(e);
283         }
284 
285         @Override
286         public Iterator<StreamException> iterator() {
287             return exceptions.iterator();
288         }
289     }
290 }