xref: /aosp_15_r20/external/cronet/components/cronet/android/test/src/org/chromium/net/Http2TestHandler.java (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2015 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.net;
6 
7 import static io.netty.buffer.Unpooled.copiedBuffer;
8 import static io.netty.buffer.Unpooled.unreleasableBuffer;
9 import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
10 import static io.netty.handler.codec.http.HttpResponseStatus.OK;
11 import static io.netty.handler.logging.LogLevel.INFO;
12 
13 import io.netty.buffer.ByteBuf;
14 import io.netty.buffer.ByteBufUtil;
15 import io.netty.channel.ChannelHandlerContext;
16 import io.netty.handler.codec.http2.AbstractHttp2ConnectionHandlerBuilder;
17 import io.netty.handler.codec.http2.DefaultHttp2Headers;
18 import io.netty.handler.codec.http2.Http2ConnectionDecoder;
19 import io.netty.handler.codec.http2.Http2ConnectionEncoder;
20 import io.netty.handler.codec.http2.Http2ConnectionHandler;
21 import io.netty.handler.codec.http2.Http2Exception;
22 import io.netty.handler.codec.http2.Http2Flags;
23 import io.netty.handler.codec.http2.Http2FrameListener;
24 import io.netty.handler.codec.http2.Http2FrameLogger;
25 import io.netty.handler.codec.http2.Http2Headers;
26 import io.netty.handler.codec.http2.Http2Settings;
27 import io.netty.util.CharsetUtil;
28 
29 import org.chromium.base.Log;
30 
31 import java.io.ByteArrayOutputStream;
32 import java.io.IOException;
33 import java.io.UnsupportedEncodingException;
34 import java.util.HashMap;
35 import java.util.Locale;
36 import java.util.Map;
37 import java.util.concurrent.CountDownLatch;
38 
39 /** HTTP/2 test handler for Cronet BidirectionalStream tests. */
40 public final class Http2TestHandler extends Http2ConnectionHandler implements Http2FrameListener {
41     // Some Url Paths that have special meaning.
42     public static final String ECHO_ALL_HEADERS_PATH = "/echoallheaders";
43     public static final String ECHO_HEADER_PATH = "/echoheader";
44     public static final String ECHO_METHOD_PATH = "/echomethod";
45     public static final String ECHO_STREAM_PATH = "/echostream";
46     public static final String ECHO_TRAILERS_PATH = "/echotrailers";
47     public static final String SERVE_SIMPLE_BROTLI_RESPONSE = "/simplebrotli";
48     public static final String REPORTING_COLLECTOR_PATH = "/reporting-collector";
49     public static final String SUCCESS_WITH_NEL_HEADERS_PATH = "/success-with-nel";
50     public static final String COMBINED_HEADERS_PATH = "/combinedheaders";
51     public static final String HANGING_REQUEST_PATH = "/hanging-request";
52 
53     private static final String TAG = Http2TestHandler.class.getSimpleName();
54     private static final Http2FrameLogger sLogger =
55             new Http2FrameLogger(INFO, Http2TestHandler.class);
56     private static final ByteBuf RESPONSE_BYTES =
57             unreleasableBuffer(copiedBuffer("HTTP/2 Test Server", CharsetUtil.UTF_8));
58 
59     private HashMap<Integer, RequestResponder> mResponderMap = new HashMap<>();
60 
61     private ReportingCollector mReportingCollector;
62     private String mServerUrl;
63     private CountDownLatch mHangingUrlLatch;
64 
65     /** Builder for HTTP/2 test handler. */
66     public static final class Builder
67             extends AbstractHttp2ConnectionHandlerBuilder<Http2TestHandler, Builder> {
Builder()68         public Builder() {
69             frameLogger(sLogger);
70         }
71 
setReportingCollector(ReportingCollector reportingCollector)72         public Builder setReportingCollector(ReportingCollector reportingCollector) {
73             mReportingCollector = reportingCollector;
74             return this;
75         }
76 
setServerUrl(String serverUrl)77         public Builder setServerUrl(String serverUrl) {
78             mServerUrl = serverUrl;
79             return this;
80         }
81 
setHangingUrlLatch(CountDownLatch hangingUrlLatch)82         public Builder setHangingUrlLatch(CountDownLatch hangingUrlLatch) {
83             mHangingUrlLatch = hangingUrlLatch;
84             return this;
85         }
86 
87         @Override
build()88         public Http2TestHandler build() {
89             return super.build();
90         }
91 
92         @Override
build( Http2ConnectionDecoder decoder, Http2ConnectionEncoder encoder, Http2Settings initialSettings)93         protected Http2TestHandler build(
94                 Http2ConnectionDecoder decoder,
95                 Http2ConnectionEncoder encoder,
96                 Http2Settings initialSettings) {
97             Http2TestHandler handler =
98                     new Http2TestHandler(
99                             decoder,
100                             encoder,
101                             initialSettings,
102                             mReportingCollector,
103                             mServerUrl,
104                             mHangingUrlLatch);
105             frameListener(handler);
106             return handler;
107         }
108 
109         private ReportingCollector mReportingCollector;
110         private String mServerUrl;
111         private CountDownLatch mHangingUrlLatch;
112     }
113 
114     private class RequestResponder {
onHeadersRead( ChannelHandlerContext ctx, int streamId, boolean endOfStream, Http2Headers headers)115         void onHeadersRead(
116                 ChannelHandlerContext ctx,
117                 int streamId,
118                 boolean endOfStream,
119                 Http2Headers headers) {
120             encoder()
121                     .writeHeaders(
122                             ctx,
123                             streamId,
124                             createResponseHeadersFromRequestHeaders(headers),
125                             0,
126                             endOfStream,
127                             ctx.newPromise());
128             ctx.flush();
129         }
130 
onDataRead( ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, boolean endOfStream)131         int onDataRead(
132                 ChannelHandlerContext ctx,
133                 int streamId,
134                 ByteBuf data,
135                 int padding,
136                 boolean endOfStream) {
137             int processed = data.readableBytes() + padding;
138             encoder().writeData(ctx, streamId, data.retain(), 0, true, ctx.newPromise());
139             ctx.flush();
140             return processed;
141         }
142 
sendResponseString(ChannelHandlerContext ctx, int streamId, String responseString)143         void sendResponseString(ChannelHandlerContext ctx, int streamId, String responseString) {
144             ByteBuf content = ctx.alloc().buffer();
145             ByteBufUtil.writeAscii(content, responseString);
146             encoder()
147                     .writeHeaders(
148                             ctx,
149                             streamId,
150                             createDefaultResponseHeaders(),
151                             0,
152                             false,
153                             ctx.newPromise());
154             encoder().writeData(ctx, streamId, content, 0, true, ctx.newPromise());
155             ctx.flush();
156         }
157     }
158 
159     private class EchoStreamResponder extends RequestResponder {
160         @Override
onHeadersRead( ChannelHandlerContext ctx, int streamId, boolean endOfStream, Http2Headers headers)161         void onHeadersRead(
162                 ChannelHandlerContext ctx,
163                 int streamId,
164                 boolean endOfStream,
165                 Http2Headers headers) {
166             // Send a frame for the response headers.
167             encoder()
168                     .writeHeaders(
169                             ctx,
170                             streamId,
171                             createResponseHeadersFromRequestHeaders(headers),
172                             0,
173                             endOfStream,
174                             ctx.newPromise());
175             ctx.flush();
176         }
177 
178         @Override
onDataRead( ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, boolean endOfStream)179         int onDataRead(
180                 ChannelHandlerContext ctx,
181                 int streamId,
182                 ByteBuf data,
183                 int padding,
184                 boolean endOfStream) {
185             int processed = data.readableBytes() + padding;
186             encoder().writeData(ctx, streamId, data.retain(), 0, endOfStream, ctx.newPromise());
187             ctx.flush();
188             return processed;
189         }
190     }
191 
192     private class CombinedHeadersResponder extends RequestResponder {
193         @Override
onHeadersRead( ChannelHandlerContext ctx, int streamId, boolean endOfStream, Http2Headers headers)194         void onHeadersRead(
195                 ChannelHandlerContext ctx,
196                 int streamId,
197                 boolean endOfStream,
198                 Http2Headers headers) {
199             ByteBuf content = ctx.alloc().buffer();
200             ByteBufUtil.writeAscii(content, "GET");
201             Http2Headers responseHeaders = new DefaultHttp2Headers().status(OK.codeAsText());
202             // Upon receiving, the following two headers will be jointed by '\0'.
203             responseHeaders.add("foo", "bar");
204             responseHeaders.add("foo", "bar2");
205             encoder().writeHeaders(ctx, streamId, responseHeaders, 0, false, ctx.newPromise());
206             encoder().writeData(ctx, streamId, content, 0, true, ctx.newPromise());
207             ctx.flush();
208         }
209     }
210 
211     private class HangingRequestResponder extends RequestResponder {
212         @Override
onHeadersRead( ChannelHandlerContext ctx, int streamId, boolean endOfStream, Http2Headers headers)213         void onHeadersRead(
214                 ChannelHandlerContext ctx,
215                 int streamId,
216                 boolean endOfStream,
217                 Http2Headers headers) {
218             try {
219                 mHangingUrlLatch.await();
220             } catch (InterruptedException e) {
221             }
222         }
223     }
224 
225     private class EchoHeaderResponder extends RequestResponder {
226         @Override
onHeadersRead( ChannelHandlerContext ctx, int streamId, boolean endOfStream, Http2Headers headers)227         void onHeadersRead(
228                 ChannelHandlerContext ctx,
229                 int streamId,
230                 boolean endOfStream,
231                 Http2Headers headers) {
232             String[] splitPath = headers.path().toString().split("\\?");
233             if (splitPath.length <= 1) {
234                 sendResponseString(ctx, streamId, "Header name not found.");
235                 return;
236             }
237 
238             String headerName = splitPath[1].toLowerCase(Locale.US);
239             if (headers.get(headerName) == null) {
240                 sendResponseString(ctx, streamId, "Header not found:" + headerName);
241                 return;
242             }
243 
244             sendResponseString(ctx, streamId, headers.get(headerName).toString());
245         }
246     }
247 
248     private class EchoAllHeadersResponder extends RequestResponder {
249         @Override
onHeadersRead( ChannelHandlerContext ctx, int streamId, boolean endOfStream, Http2Headers headers)250         void onHeadersRead(
251                 ChannelHandlerContext ctx,
252                 int streamId,
253                 boolean endOfStream,
254                 Http2Headers headers) {
255             StringBuilder response = new StringBuilder();
256             for (Map.Entry<CharSequence, CharSequence> header : headers) {
257                 response.append(header.getKey() + ": " + header.getValue() + "\r\n");
258             }
259             sendResponseString(ctx, streamId, response.toString());
260         }
261     }
262 
263     private class EchoMethodResponder extends RequestResponder {
264         @Override
onHeadersRead( ChannelHandlerContext ctx, int streamId, boolean endOfStream, Http2Headers headers)265         void onHeadersRead(
266                 ChannelHandlerContext ctx,
267                 int streamId,
268                 boolean endOfStream,
269                 Http2Headers headers) {
270             sendResponseString(ctx, streamId, headers.method().toString());
271         }
272     }
273 
274     private class EchoTrailersResponder extends RequestResponder {
275         @Override
onHeadersRead( ChannelHandlerContext ctx, int streamId, boolean endOfStream, Http2Headers headers)276         void onHeadersRead(
277                 ChannelHandlerContext ctx,
278                 int streamId,
279                 boolean endOfStream,
280                 Http2Headers headers) {
281             encoder()
282                     .writeHeaders(
283                             ctx,
284                             streamId,
285                             createDefaultResponseHeaders(),
286                             0,
287                             false,
288                             ctx.newPromise());
289             encoder()
290                     .writeData(
291                             ctx, streamId, RESPONSE_BYTES.duplicate(), 0, false, ctx.newPromise());
292             Http2Headers responseTrailers =
293                     createResponseHeadersFromRequestHeaders(headers)
294                             .add("trailer", "value1", "Value2");
295             encoder().writeHeaders(ctx, streamId, responseTrailers, 0, true, ctx.newPromise());
296             ctx.flush();
297         }
298     }
299 
300     // A RequestResponder that serves a simple Brotli-encoded response.
301     private class ServeSimpleBrotliResponder extends RequestResponder {
302         @Override
onHeadersRead( ChannelHandlerContext ctx, int streamId, boolean endOfStream, Http2Headers headers)303         void onHeadersRead(
304                 ChannelHandlerContext ctx,
305                 int streamId,
306                 boolean endOfStream,
307                 Http2Headers headers) {
308             Http2Headers responseHeaders = new DefaultHttp2Headers().status(OK.codeAsText());
309             byte[] quickfoxCompressed = {
310                 0x0b, 0x15, -0x80, 0x54, 0x68, 0x65, 0x20, 0x71, 0x75, 0x69, 0x63, 0x6b, 0x20, 0x62,
311                 0x72, 0x6f, 0x77, 0x6e, 0x20, 0x66, 0x6f, 0x78, 0x20, 0x6a, 0x75, 0x6d, 0x70, 0x73,
312                 0x20, 0x6f, 0x76, 0x65, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6c, 0x61, 0x7a, 0x79,
313                 0x20, 0x64, 0x6f, 0x67, 0x03
314             };
315             ByteBuf content = copiedBuffer(quickfoxCompressed);
316             responseHeaders.add("content-encoding", "br");
317             encoder().writeHeaders(ctx, streamId, responseHeaders, 0, false, ctx.newPromise());
318             encoder().writeData(ctx, streamId, content, 0, true, ctx.newPromise());
319             ctx.flush();
320         }
321     }
322 
323     // A RequestResponder that implements a Reporting collector.
324     private class ReportingCollectorResponder extends RequestResponder {
325         private ByteArrayOutputStream mPartialPayload = new ByteArrayOutputStream();
326 
327         @Override
onHeadersRead( ChannelHandlerContext ctx, int streamId, boolean endOfStream, Http2Headers headers)328         void onHeadersRead(
329                 ChannelHandlerContext ctx,
330                 int streamId,
331                 boolean endOfStream,
332                 Http2Headers headers) {}
333 
334         @Override
onDataRead( ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, boolean endOfStream)335         int onDataRead(
336                 ChannelHandlerContext ctx,
337                 int streamId,
338                 ByteBuf data,
339                 int padding,
340                 boolean endOfStream) {
341             int processed = data.readableBytes() + padding;
342             try {
343                 data.readBytes(mPartialPayload, data.readableBytes());
344             } catch (IOException e) {
345             }
346             if (endOfStream) {
347                 processPayload(ctx, streamId);
348             }
349             return processed;
350         }
351 
processPayload(ChannelHandlerContext ctx, int streamId)352         private void processPayload(ChannelHandlerContext ctx, int streamId) {
353             boolean succeeded = false;
354             try {
355                 String payload = mPartialPayload.toString(CharsetUtil.UTF_8.name());
356                 succeeded = mReportingCollector.addReports(payload);
357             } catch (UnsupportedEncodingException e) {
358             }
359             Http2Headers responseHeaders;
360             if (succeeded) {
361                 responseHeaders = new DefaultHttp2Headers().status(OK.codeAsText());
362             } else {
363                 responseHeaders = new DefaultHttp2Headers().status(BAD_REQUEST.codeAsText());
364             }
365             encoder().writeHeaders(ctx, streamId, responseHeaders, 0, true, ctx.newPromise());
366             ctx.flush();
367         }
368     }
369 
370     // A RequestResponder that serves a successful response with Reporting and NEL headers
371     private class SuccessWithNELHeadersResponder extends RequestResponder {
372         @Override
onHeadersRead( ChannelHandlerContext ctx, int streamId, boolean endOfStream, Http2Headers headers)373         void onHeadersRead(
374                 ChannelHandlerContext ctx,
375                 int streamId,
376                 boolean endOfStream,
377                 Http2Headers headers) {
378             Http2Headers responseHeaders = new DefaultHttp2Headers().status(OK.codeAsText());
379             responseHeaders.add("report-to", getReportToHeader());
380             responseHeaders.add("nel", getNELHeader());
381             encoder().writeHeaders(ctx, streamId, responseHeaders, 0, true, ctx.newPromise());
382             ctx.flush();
383         }
384 
385         @Override
onDataRead( ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, boolean endOfStream)386         int onDataRead(
387                 ChannelHandlerContext ctx,
388                 int streamId,
389                 ByteBuf data,
390                 int padding,
391                 boolean endOfStream) {
392             int processed = data.readableBytes() + padding;
393             return processed;
394         }
395 
getReportToHeader()396         private String getReportToHeader() {
397             return String.format(
398                     "{\"group\": \"nel\", \"max_age\": 86400, "
399                             + "\"endpoints\": [{\"url\": \"%s%s\"}]}",
400                     mServerUrl, REPORTING_COLLECTOR_PATH);
401         }
402 
getNELHeader()403         private String getNELHeader() {
404             return "{\"report_to\": \"nel\", \"max_age\": 86400, \"success_fraction\": 1.0}";
405         }
406     }
407 
createDefaultResponseHeaders()408     private static Http2Headers createDefaultResponseHeaders() {
409         return new DefaultHttp2Headers().status(OK.codeAsText());
410     }
411 
createResponseHeadersFromRequestHeaders( Http2Headers requestHeaders)412     private static Http2Headers createResponseHeadersFromRequestHeaders(
413             Http2Headers requestHeaders) {
414         // Create response headers by echoing request headers.
415         Http2Headers responseHeaders = new DefaultHttp2Headers().status(OK.codeAsText());
416         for (Map.Entry<CharSequence, CharSequence> header : requestHeaders) {
417             if (!header.getKey().toString().startsWith(":")) {
418                 responseHeaders.add("echo-" + header.getKey(), header.getValue());
419             }
420         }
421 
422         responseHeaders.add("echo-method", requestHeaders.get(":method").toString());
423         return responseHeaders;
424     }
425 
Http2TestHandler( Http2ConnectionDecoder decoder, Http2ConnectionEncoder encoder, Http2Settings initialSettings, ReportingCollector reportingCollector, String serverUrl, CountDownLatch hangingUrlLatch)426     private Http2TestHandler(
427             Http2ConnectionDecoder decoder,
428             Http2ConnectionEncoder encoder,
429             Http2Settings initialSettings,
430             ReportingCollector reportingCollector,
431             String serverUrl,
432             CountDownLatch hangingUrlLatch) {
433         super(decoder, encoder, initialSettings);
434         mReportingCollector = reportingCollector;
435         mServerUrl = serverUrl;
436         mHangingUrlLatch = hangingUrlLatch;
437     }
438 
439     @Override
exceptionCaught(ChannelHandlerContext ctx, Throwable cause)440     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
441         super.exceptionCaught(ctx, cause);
442         Log.e(TAG, "An exception was caught", cause);
443         ctx.close();
444         throw new Exception("Exception Caught", cause);
445     }
446 
447     @Override
onDataRead( ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, boolean endOfStream)448     public int onDataRead(
449             ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, boolean endOfStream)
450             throws Http2Exception {
451         RequestResponder responder = mResponderMap.get(streamId);
452         if (endOfStream) {
453             mResponderMap.remove(streamId);
454         }
455         return responder.onDataRead(ctx, streamId, data, padding, endOfStream);
456     }
457 
458     @Override
onHeadersRead( ChannelHandlerContext ctx, int streamId, Http2Headers headers, int padding, boolean endOfStream)459     public void onHeadersRead(
460             ChannelHandlerContext ctx,
461             int streamId,
462             Http2Headers headers,
463             int padding,
464             boolean endOfStream)
465             throws Http2Exception {
466         String path = headers.path().toString();
467         RequestResponder responder;
468         if (path.startsWith(ECHO_STREAM_PATH)) {
469             responder = new EchoStreamResponder();
470         } else if (path.startsWith(ECHO_TRAILERS_PATH)) {
471             responder = new EchoTrailersResponder();
472         } else if (path.startsWith(ECHO_ALL_HEADERS_PATH)) {
473             responder = new EchoAllHeadersResponder();
474         } else if (path.startsWith(ECHO_HEADER_PATH)) {
475             responder = new EchoHeaderResponder();
476         } else if (path.startsWith(ECHO_METHOD_PATH)) {
477             responder = new EchoMethodResponder();
478         } else if (path.startsWith(SERVE_SIMPLE_BROTLI_RESPONSE)) {
479             responder = new ServeSimpleBrotliResponder();
480         } else if (path.startsWith(REPORTING_COLLECTOR_PATH)) {
481             responder = new ReportingCollectorResponder();
482         } else if (path.startsWith(SUCCESS_WITH_NEL_HEADERS_PATH)) {
483             responder = new SuccessWithNELHeadersResponder();
484         } else if (path.startsWith(COMBINED_HEADERS_PATH)) {
485             responder = new CombinedHeadersResponder();
486         } else if (path.startsWith(HANGING_REQUEST_PATH)) {
487             responder = new HangingRequestResponder();
488         } else {
489             responder = new RequestResponder();
490         }
491 
492         responder.onHeadersRead(ctx, streamId, endOfStream, headers);
493 
494         if (!endOfStream) {
495             mResponderMap.put(streamId, responder);
496         }
497     }
498 
499     @Override
onHeadersRead( ChannelHandlerContext ctx, int streamId, Http2Headers headers, int streamDependency, short weight, boolean exclusive, int padding, boolean endOfStream)500     public void onHeadersRead(
501             ChannelHandlerContext ctx,
502             int streamId,
503             Http2Headers headers,
504             int streamDependency,
505             short weight,
506             boolean exclusive,
507             int padding,
508             boolean endOfStream)
509             throws Http2Exception {
510         onHeadersRead(ctx, streamId, headers, padding, endOfStream);
511     }
512 
513     @Override
onPriorityRead( ChannelHandlerContext ctx, int streamId, int streamDependency, short weight, boolean exclusive)514     public void onPriorityRead(
515             ChannelHandlerContext ctx,
516             int streamId,
517             int streamDependency,
518             short weight,
519             boolean exclusive)
520             throws Http2Exception {}
521 
522     @Override
onRstStreamRead(ChannelHandlerContext ctx, int streamId, long errorCode)523     public void onRstStreamRead(ChannelHandlerContext ctx, int streamId, long errorCode)
524             throws Http2Exception {}
525 
526     @Override
onSettingsAckRead(ChannelHandlerContext ctx)527     public void onSettingsAckRead(ChannelHandlerContext ctx) throws Http2Exception {}
528 
529     @Override
onSettingsRead(ChannelHandlerContext ctx, Http2Settings settings)530     public void onSettingsRead(ChannelHandlerContext ctx, Http2Settings settings)
531             throws Http2Exception {}
532 
533     @Override
onPingRead(ChannelHandlerContext ctx, ByteBuf data)534     public void onPingRead(ChannelHandlerContext ctx, ByteBuf data) throws Http2Exception {}
535 
536     @Override
onPingAckRead(ChannelHandlerContext ctx, ByteBuf data)537     public void onPingAckRead(ChannelHandlerContext ctx, ByteBuf data) throws Http2Exception {}
538 
539     @Override
onPushPromiseRead( ChannelHandlerContext ctx, int streamId, int promisedStreamId, Http2Headers headers, int padding)540     public void onPushPromiseRead(
541             ChannelHandlerContext ctx,
542             int streamId,
543             int promisedStreamId,
544             Http2Headers headers,
545             int padding)
546             throws Http2Exception {}
547 
548     @Override
onGoAwayRead( ChannelHandlerContext ctx, int lastStreamId, long errorCode, ByteBuf debugData)549     public void onGoAwayRead(
550             ChannelHandlerContext ctx, int lastStreamId, long errorCode, ByteBuf debugData)
551             throws Http2Exception {}
552 
553     @Override
onWindowUpdateRead(ChannelHandlerContext ctx, int streamId, int windowSizeIncrement)554     public void onWindowUpdateRead(ChannelHandlerContext ctx, int streamId, int windowSizeIncrement)
555             throws Http2Exception {}
556 
557     @Override
onUnknownFrame( ChannelHandlerContext ctx, byte frameType, int streamId, Http2Flags flags, ByteBuf payload)558     public void onUnknownFrame(
559             ChannelHandlerContext ctx,
560             byte frameType,
561             int streamId,
562             Http2Flags flags,
563             ByteBuf payload)
564             throws Http2Exception {}
565 }
566