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 }