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 }