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