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 }