Chromium Code Reviews| Index: components/cronet/android/test/src/org/chromium/net/Http2TestHandler.java |
| diff --git a/components/cronet/android/test/src/org/chromium/net/Http2TestHandler.java b/components/cronet/android/test/src/org/chromium/net/Http2TestHandler.java |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..b647d8200d518df76d44298692b6f675d78b4838 |
| --- /dev/null |
| +++ b/components/cronet/android/test/src/org/chromium/net/Http2TestHandler.java |
| @@ -0,0 +1,231 @@ |
| +// Copyright 2015 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +package org.chromium.net; |
| + |
| +import org.chromium.base.Log; |
| + |
| +import java.util.Locale; |
| +import java.util.Map; |
| + |
| +import static io.netty.buffer.Unpooled.copiedBuffer; |
| +import static io.netty.buffer.Unpooled.unreleasableBuffer; |
| +import static io.netty.handler.codec.http.HttpResponseStatus.OK; |
| +import static io.netty.handler.logging.LogLevel.INFO; |
| + |
| +import io.netty.buffer.ByteBuf; |
| +import io.netty.buffer.ByteBufUtil; |
| +import io.netty.channel.ChannelHandlerContext; |
| +import io.netty.handler.codec.http2.AbstractHttp2ConnectionHandlerBuilder; |
| +import io.netty.handler.codec.http2.DefaultHttp2Headers; |
| +import io.netty.handler.codec.http2.Http2ConnectionDecoder; |
| +import io.netty.handler.codec.http2.Http2ConnectionEncoder; |
| +import io.netty.handler.codec.http2.Http2ConnectionHandler; |
| +import io.netty.handler.codec.http2.Http2Exception; |
| +import io.netty.handler.codec.http2.Http2Flags; |
| +import io.netty.handler.codec.http2.Http2FrameListener; |
| +import io.netty.handler.codec.http2.Http2FrameLogger; |
| +import io.netty.handler.codec.http2.Http2Headers; |
| +import io.netty.handler.codec.http2.Http2Settings; |
| +import io.netty.util.CharsetUtil; |
| + |
| +/** |
| + * HTTP/2 test handler for Cronet BidirectionalStream tests. |
| + */ |
| +public final class Http2TestHandler extends Http2ConnectionHandler implements Http2FrameListener { |
| + // Some Url Paths that have special meaning. |
| + public static final String ECHO_ALL_HEADERS_PATH = "/echoallheaders"; |
| + public static final String ECHO_HEADER_PATH = "/echoheader"; |
| + public static final String ECHO_METHOD_PATH = "/echomethod"; |
| + public static final String ECHO_STREAM_PATH = "/echostream"; |
| + public static final String ECHO_TRAILERS_PATH = "/echotrailers"; |
| + |
| + private static final String TAG = "cr_Http2TestHandler"; |
| + private static final Http2FrameLogger sLogger = |
| + new Http2FrameLogger(INFO, Http2TestHandler.class); |
| + private static final ByteBuf RESPONSE_BYTES = |
| + unreleasableBuffer(copiedBuffer("HTTP/2 Test Server", CharsetUtil.UTF_8)); |
| + private boolean mEchoStream; |
|
xunjieli
2016/01/27 22:17:10
Seems like this handler can only handle one active
xunjieli
2016/01/27 22:23:24
Also bug here. If first url is echo stream, and th
mef
2016/01/29 00:15:30
Great catch! Currently there are no tests that use
mef
2016/01/29 00:15:30
Done.
|
| + private Http2Headers mResponseHeaders; |
| + |
| + /** |
| + * Builder for HTTP/2 test handler. |
| + */ |
| + public static final class Builder |
| + extends AbstractHttp2ConnectionHandlerBuilder<Http2TestHandler, Builder> { |
| + public Builder() { |
| + frameLogger(sLogger); |
| + } |
| + |
| + @Override |
| + public Http2TestHandler build() { |
| + return super.build(); |
| + } |
| + |
| + @Override |
| + protected Http2TestHandler build(Http2ConnectionDecoder decoder, |
| + Http2ConnectionEncoder encoder, Http2Settings initialSettings) { |
| + Http2TestHandler handler = new Http2TestHandler(decoder, encoder, initialSettings); |
| + frameListener(handler); |
| + return handler; |
| + } |
| + } |
| + |
| + private Http2TestHandler(Http2ConnectionDecoder decoder, Http2ConnectionEncoder encoder, |
| + Http2Settings initialSettings) { |
| + super(decoder, encoder, initialSettings); |
| + } |
| + |
| + @Override |
| + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { |
| + super.exceptionCaught(ctx, cause); |
| + Log.e(TAG, "An exception was caught", cause); |
| + ctx.close(); |
| + throw new Exception("Exception Caught", cause); |
| + } |
| + |
| + private static Http2Headers createResponseHeadersFromRequestHeaders( |
| + Http2Headers requestHeaders) { |
| + // Create response headers by echoing request headers. |
| + Http2Headers responseHeaders = new DefaultHttp2Headers().status(OK.codeAsText()); |
| + for (Map.Entry<CharSequence, CharSequence> header : requestHeaders) { |
| + if (!header.getKey().toString().startsWith(":")) { |
| + responseHeaders.add("echo-" + header.getKey(), header.getValue()); |
| + } |
| + } |
| + |
| + responseHeaders.add("echo-method", requestHeaders.get(":method").toString()); |
| + return responseHeaders; |
| + } |
| + |
| + private static String getEchoAllHeadersResponse(Http2Headers headers) { |
| + StringBuilder response = new StringBuilder(); |
| + for (Map.Entry<CharSequence, CharSequence> header : headers) { |
| + response.append(header.getKey() + ": " + header.getValue() + "\r\n"); |
| + } |
| + return response.toString(); |
| + } |
| + |
| + private static String getEchoHeaderResponse(Http2Headers headers) { |
| + String[] splitPath = headers.path().toString().split("\\?"); |
| + if (splitPath.length <= 1) return "Header name not found."; |
| + |
| + String headerName = splitPath[1].toLowerCase(Locale.US); |
| + if (headers.get(headerName) == null) return "Header not found:" + headerName; |
| + |
| + return headers.get(headerName).toString(); |
| + } |
| + |
| + private void doSendResponse(ChannelHandlerContext ctx, int streamId, ByteBuf payload) { |
| + // Send a frame for the response status |
| + encoder().writeHeaders(ctx, streamId, mResponseHeaders, 0, false, ctx.newPromise()); |
| + encoder().writeData(ctx, streamId, payload, 0, true, ctx.newPromise()); |
| + ctx.flush(); |
| + } |
| + |
| + private void doEchoStream(ChannelHandlerContext ctx, int streamId, Http2Headers headers, |
|
xunjieli
2016/01/27 22:17:10
This method is confusingly named. I was expecting
mef
2016/01/29 00:15:30
Done.
|
| + int padding, boolean endOfStream) { |
| + mEchoStream = true; |
| + // Send a frame for the response headers. |
| + encoder().writeHeaders(ctx, streamId, mResponseHeaders, 0, endOfStream, ctx.newPromise()); |
| + ctx.flush(); |
| + } |
| + |
| + private void doEchoTrailers(ChannelHandlerContext ctx, int streamId) { |
| + Http2Headers responseHeaders = new DefaultHttp2Headers().status(OK.codeAsText()); |
| + encoder().writeHeaders(ctx, streamId, responseHeaders, 0, false, ctx.newPromise()); |
| + encoder().writeData(ctx, streamId, RESPONSE_BYTES.duplicate(), 0, false, ctx.newPromise()); |
| + Http2Headers responseTrailers = mResponseHeaders.add("trailer", "value1", "Value2"); |
| + encoder().writeHeaders(ctx, streamId, responseTrailers, 0, true, ctx.newPromise()); |
| + ctx.flush(); |
| + } |
| + |
| + @Override |
| + public int onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, |
| + boolean endOfStream) throws Http2Exception { |
| + int processed = data.readableBytes() + padding; |
| + if (mEchoStream) { |
| + encoder().writeData(ctx, streamId, data.retain(), 0, endOfStream, ctx.newPromise()); |
|
xunjieli
2016/01/27 22:17:10
use doEchoStream? see previous comment.
mef
2016/01/29 00:15:30
Done.
|
| + ctx.flush(); |
| + } else if (endOfStream) { |
| + doSendResponse(ctx, streamId, data.retain()); |
| + } |
| + return processed; |
| + } |
| + |
| + @Override |
| + public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, |
| + int padding, boolean endOfStream) throws Http2Exception { |
| + mResponseHeaders = createResponseHeadersFromRequestHeaders(headers); |
| + String path = headers.path().toString(); |
| + if (path.startsWith(ECHO_STREAM_PATH)) { |
| + doEchoStream(ctx, streamId, headers, padding, endOfStream); |
| + } else if (path.startsWith(ECHO_TRAILERS_PATH)) { |
| + doEchoTrailers(ctx, streamId); |
| + } else if (endOfStream) { |
| + mResponseHeaders = new DefaultHttp2Headers().status(OK.codeAsText()); |
| + ByteBuf content = ctx.alloc().buffer(); |
| + try { |
| + if (path.startsWith(ECHO_ALL_HEADERS_PATH)) { |
| + ByteBufUtil.writeAscii(content, getEchoAllHeadersResponse(headers)); |
| + } else if (path.startsWith(ECHO_HEADER_PATH)) { |
| + ByteBufUtil.writeAscii(content, getEchoHeaderResponse(headers)); |
| + } else if (path.startsWith(ECHO_METHOD_PATH)) { |
| + ByteBufUtil.writeAscii(content, headers.method()); |
| + } else { |
| + content.writeBytes(RESPONSE_BYTES.duplicate()); |
| + ByteBufUtil.writeAscii(content, " - via HTTP/2"); |
| + } |
| + } catch (Exception e) { |
| + ByteBufUtil.writeAscii(content, "Exception: " + e.toString()); |
| + } |
| + |
| + doSendResponse(ctx, streamId, content); |
| + } |
| + } |
| + |
| + @Override |
| + public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, |
| + int streamDependency, short weight, boolean exclusive, int padding, boolean endOfStream) |
| + throws Http2Exception { |
| + onHeadersRead(ctx, streamId, headers, padding, endOfStream); |
| + } |
| + |
| + @Override |
| + public void onPriorityRead(ChannelHandlerContext ctx, int streamId, int streamDependency, |
| + short weight, boolean exclusive) throws Http2Exception {} |
| + |
| + @Override |
| + public void onRstStreamRead(ChannelHandlerContext ctx, int streamId, long errorCode) |
| + throws Http2Exception {} |
| + |
| + @Override |
| + public void onSettingsAckRead(ChannelHandlerContext ctx) throws Http2Exception {} |
| + |
| + @Override |
| + public void onSettingsRead(ChannelHandlerContext ctx, Http2Settings settings) |
| + throws Http2Exception {} |
| + |
| + @Override |
| + public void onPingRead(ChannelHandlerContext ctx, ByteBuf data) throws Http2Exception {} |
| + |
| + @Override |
| + public void onPingAckRead(ChannelHandlerContext ctx, ByteBuf data) throws Http2Exception {} |
| + |
| + @Override |
| + public void onPushPromiseRead(ChannelHandlerContext ctx, int streamId, int promisedStreamId, |
| + Http2Headers headers, int padding) throws Http2Exception {} |
| + |
| + @Override |
| + public void onGoAwayRead(ChannelHandlerContext ctx, int lastStreamId, long errorCode, |
| + ByteBuf debugData) throws Http2Exception {} |
| + |
| + @Override |
| + public void onWindowUpdateRead(ChannelHandlerContext ctx, int streamId, int windowSizeIncrement) |
| + throws Http2Exception {} |
| + |
| + @Override |
| + public void onUnknownFrame(ChannelHandlerContext ctx, byte frameType, int streamId, |
| + Http2Flags flags, ByteBuf payload) throws Http2Exception {} |
| +} |