Add HTTP/2 Netty tiles example
Motivation: Adding an example that showcases Netty’s HTTP/2 codec and that is slightly more complex than the existing hello-world example. It is based on the Gopher tiles example available here: https://http2.golang.org/gophertiles?latency=0 Modifications: Moved current http2 example to http2/helloworld. Added http2 tiles example under http2/tiles. Result: A Netty tiles example is available.
@ -14,6 +14,15 @@
|
||||
*/
|
||||
package io.netty.example.http2;
|
||||
|
||||
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.handler.codec.http.QueryStringDecoder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Utility methods used by the example client and server.
|
||||
*/
|
||||
@ -24,5 +33,51 @@ public final class Http2ExampleUtil {
|
||||
*/
|
||||
public static final String UPGRADE_RESPONSE_HEADER = "Http-To-Http2-Upgrade";
|
||||
|
||||
/**
|
||||
* Size of the block to be read from the input stream.
|
||||
*/
|
||||
private static final int BLOCK_SIZE = 1024;
|
||||
|
||||
private Http2ExampleUtil() { }
|
||||
|
||||
/**
|
||||
* @param string the string to be converted to an integer.
|
||||
* @param defaultValue the default value
|
||||
* @return the integer value of a string or the default value, if the string is either null or empty.
|
||||
*/
|
||||
public static int toInt(String string, int defaultValue) {
|
||||
if (string != null && !string.isEmpty()) {
|
||||
return Integer.valueOf(string);
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an InputStream into a byte array.
|
||||
* @param input the InputStream.
|
||||
* @return a byte array representation of the InputStream.
|
||||
* @throws IOException if an I/O exception of some sort happens while reading the InputStream.
|
||||
*/
|
||||
public static ByteBuf toByteBuf(InputStream input) throws IOException {
|
||||
ByteBuf buf = Unpooled.buffer();
|
||||
int n = 0;
|
||||
do {
|
||||
n = buf.writeBytes(input, BLOCK_SIZE);
|
||||
} while (n > 0);
|
||||
return buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param query the decoder of query string
|
||||
* @param key the key to lookup
|
||||
* @return the first occurrence of that key in the string parameters
|
||||
*/
|
||||
public static String firstValue(QueryStringDecoder query, String key) {
|
||||
checkNotNull(query, "Query can't be null!");
|
||||
List<String> values = query.parameters().get(key);
|
||||
if (values == null || values.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return values.get(0);
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package io.netty.example.http2.client;
|
||||
package io.netty.example.http2.helloworld.client;
|
||||
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.buffer.Unpooled;
|
@ -12,7 +12,7 @@
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package io.netty.example.http2.client;
|
||||
package io.netty.example.http2.helloworld.client;
|
||||
|
||||
import static io.netty.handler.logging.LogLevel.INFO;
|
||||
|
@ -12,7 +12,7 @@
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package io.netty.example.http2.client;
|
||||
package io.netty.example.http2.helloworld.client;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelPromise;
|
@ -12,7 +12,7 @@
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package io.netty.example.http2.client;
|
||||
package io.netty.example.http2.helloworld.client;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
@ -13,7 +13,7 @@
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.example.http2.server;
|
||||
package io.netty.example.http2.helloworld.server;
|
||||
|
||||
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
||||
import io.netty.buffer.ByteBuf;
|
@ -13,7 +13,7 @@
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package io.netty.example.http2.server;
|
||||
package io.netty.example.http2.helloworld.server;
|
||||
|
||||
import static io.netty.buffer.Unpooled.copiedBuffer;
|
||||
import static io.netty.buffer.Unpooled.unreleasableBuffer;
|
@ -12,7 +12,7 @@
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package io.netty.example.http2.server;
|
||||
package io.netty.example.http2.helloworld.server;
|
||||
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.handler.codec.http2.Http2ConnectionHandler;
|
@ -14,7 +14,7 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package io.netty.example.http2.server;
|
||||
package io.netty.example.http2.helloworld.server;
|
||||
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
@ -71,12 +71,11 @@ public final class Http2Server {
|
||||
sslCtx = null;
|
||||
}
|
||||
// Configure the server.
|
||||
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
|
||||
EventLoopGroup workerGroup = new NioEventLoopGroup();
|
||||
EventLoopGroup group = new NioEventLoopGroup();
|
||||
try {
|
||||
ServerBootstrap b = new ServerBootstrap();
|
||||
b.option(ChannelOption.SO_BACKLOG, 1024);
|
||||
b.group(bossGroup, workerGroup)
|
||||
b.group(group)
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.handler(new LoggingHandler(LogLevel.INFO))
|
||||
.childHandler(new Http2ServerInitializer(sslCtx));
|
||||
@ -88,8 +87,7 @@ public final class Http2Server {
|
||||
|
||||
ch.closeFuture().sync();
|
||||
} finally {
|
||||
bossGroup.shutdownGracefully();
|
||||
workerGroup.shutdownGracefully();
|
||||
group.shutdownGracefully();
|
||||
}
|
||||
}
|
||||
}
|
@ -14,7 +14,7 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package io.netty.example.http2.server;
|
||||
package io.netty.example.http2.helloworld.server;
|
||||
|
||||
import io.netty.channel.ChannelHandlerAdapter;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2015 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package io.netty.example.http2.tiles;
|
||||
|
||||
import static io.netty.buffer.Unpooled.copiedBuffer;
|
||||
import static io.netty.buffer.Unpooled.unmodifiableBuffer;
|
||||
import static io.netty.buffer.Unpooled.unreleasableBuffer;
|
||||
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH;
|
||||
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
|
||||
import static io.netty.handler.codec.http.HttpResponseStatus.CONTINUE;
|
||||
import static io.netty.handler.codec.http.HttpResponseStatus.OK;
|
||||
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
|
||||
import static io.netty.util.CharsetUtil.UTF_8;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
import io.netty.handler.codec.http.HttpHeaderUtil;
|
||||
import io.netty.handler.codec.http.HttpRequest;
|
||||
import io.netty.handler.codec.http2.Http2CodecUtil;
|
||||
|
||||
/**
|
||||
* Handles the exceptional case where HTTP 1.x was negotiated under TLS.
|
||||
*/
|
||||
public final class FallbackRequestHandler extends SimpleChannelInboundHandler<HttpRequest> {
|
||||
|
||||
private static final ByteBuf response = unmodifiableBuffer(unreleasableBuffer(copiedBuffer("<!DOCTYPE html>"
|
||||
+ "<html><body><h2>To view the example you need a browser that supports HTTP/2 ("
|
||||
+ Http2CodecUtil.TLS_UPGRADE_PROTOCOL_NAME
|
||||
+ ")</h2></body></html>", UTF_8)));
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, HttpRequest req) throws Exception {
|
||||
if (HttpHeaderUtil.is100ContinueExpected(req)) {
|
||||
ctx.write(new DefaultFullHttpResponse(HTTP_1_1, CONTINUE));
|
||||
}
|
||||
|
||||
ByteBuf content = ctx.alloc().buffer();
|
||||
content.writeBytes(response.duplicate());
|
||||
|
||||
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK, content);
|
||||
response.headers().set(CONTENT_TYPE, "text/html; charset=UTF-8");
|
||||
response.headers().setInt(CONTENT_LENGTH, response.content().readableBytes());
|
||||
|
||||
ctx.write(response).addListener(ChannelFutureListener.CLOSE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
|
||||
ctx.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
cause.printStackTrace();
|
||||
ctx.close();
|
||||
}
|
||||
}
|
79
example/src/main/java/io/netty/example/http2/tiles/Html.java
Normal file
@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2015 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package io.netty.example.http2.tiles;
|
||||
|
||||
import static io.netty.util.CharsetUtil.UTF_8;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Static and dynamically generated HTML for the example.
|
||||
*/
|
||||
public final class Html {
|
||||
|
||||
public static final String IP = System.getProperty("ip", "127.0.0.1");
|
||||
|
||||
public static final byte[] FOOTER = "</body></html>".getBytes(UTF_8);
|
||||
|
||||
public static final byte[] HEADER = ("<!DOCTYPE html><html><head lang=\"en\"><title>Netty HTTP/2 Example</title>"
|
||||
+ "<style>body {background:#DDD;} div#netty { line-height:0;}</style>"
|
||||
+ "<link rel=\"shortcut icon\" href=\"about:blank\">"
|
||||
+ "<meta charset=\"UTF-8\"></head><body>A grid of 200 tiled images is shown below. Compare:"
|
||||
+ "<p>[<a href='https://" + url(Http2Server.PORT) + "?latency=0'>HTTP/2, 0 latency</a>] [<a href='http://"
|
||||
+ url(HttpServer.PORT) + "?latency=0'>HTTP/1, 0 latency</a>]<br/>" + "[<a href='https://"
|
||||
+ url(Http2Server.PORT) + "?latency=30'>HTTP/2, 30ms latency</a>] [<a href='http://" + url(HttpServer.PORT)
|
||||
+ "?latency=30'>HTTP/1, 30ms latency</a>]<br/>" + "[<a href='https://" + url(Http2Server.PORT)
|
||||
+ "?latency=200'>HTTP/2, 200ms latency</a>] [<a href='http://" + url(HttpServer.PORT)
|
||||
+ "?latency=200'>HTTP/1, 200ms latency</a>]<br/>" + "[<a href='https://" + url(Http2Server.PORT)
|
||||
+ "?latency=1000'>HTTP/2, 1s latency</a>] [<a href='http://" + url(HttpServer.PORT)
|
||||
+ "?latency=1000'>HTTP/1, " + "1s latency</a>]<br/>").getBytes(UTF_8);
|
||||
|
||||
private static final int IMAGES_X_AXIS = 20;
|
||||
|
||||
private static final int IMAGES_Y_AXIS = 10;
|
||||
|
||||
private Html() {
|
||||
}
|
||||
|
||||
private static String url(int port) {
|
||||
return IP + ":" + port + "/http2";
|
||||
}
|
||||
|
||||
public static byte[] body(int latency) {
|
||||
int r = Math.abs(new Random().nextInt());
|
||||
// The string to be built contains 13192 fixed characters plus the variable latency and random cache-bust.
|
||||
int numberOfCharacters = 13192 + stringLength(latency) + stringLength(r);
|
||||
StringBuilder sb = new StringBuilder(numberOfCharacters).append("<div id=\"netty\">");
|
||||
for (int y = 0; y < IMAGES_Y_AXIS; y++) {
|
||||
for (int x = 0; x < IMAGES_X_AXIS; x++) {
|
||||
sb.append("<img width=30 height=29 src='/http2?x=")
|
||||
.append(x)
|
||||
.append("&y=").append(y)
|
||||
.append("&cachebust=").append(r)
|
||||
.append("&latency=").append(latency)
|
||||
.append("'>");
|
||||
}
|
||||
sb.append("<br/>\r\n");
|
||||
}
|
||||
sb.append("</div>");
|
||||
return sb.toString().getBytes(UTF_8);
|
||||
}
|
||||
|
||||
private static int stringLength(int value) {
|
||||
return Integer.toString(value).length() * IMAGES_X_AXIS * IMAGES_Y_AXIS;
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2015 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package io.netty.example.http2.tiles;
|
||||
|
||||
import static io.netty.handler.codec.http.HttpHeaderNames.CONNECTION;
|
||||
import static io.netty.handler.codec.http.HttpHeaderUtil.isKeepAlive;
|
||||
import static io.netty.handler.codec.http.HttpResponseStatus.CONTINUE;
|
||||
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
import io.netty.handler.codec.http.HttpHeaderUtil;
|
||||
import io.netty.handler.codec.http.HttpHeaderValues;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Handles the requests for the tiled image using HTTP 1.x as a protocol.
|
||||
*/
|
||||
public final class Http1RequestHandler extends Http2RequestHandler {
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
|
||||
if (HttpHeaderUtil.is100ContinueExpected(request)) {
|
||||
ctx.write(new DefaultFullHttpResponse(HTTP_1_1, CONTINUE));
|
||||
}
|
||||
super.channelRead0(ctx, request);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void sendResponse(final ChannelHandlerContext ctx, String streamId, int latency,
|
||||
final FullHttpResponse response, final FullHttpRequest request) {
|
||||
HttpHeaderUtil.setContentLength(response, response.content().readableBytes());
|
||||
ctx.executor().schedule(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (isKeepAlive(request)) {
|
||||
response.headers().set(CONNECTION, HttpHeaderValues.KEEP_ALIVE);
|
||||
ctx.writeAndFlush(response);
|
||||
} else {
|
||||
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
|
||||
}
|
||||
}
|
||||
}, latency, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2015 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package io.netty.example.http2.tiles;
|
||||
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.http2.DefaultHttp2Connection;
|
||||
import io.netty.handler.codec.http2.DefaultHttp2FrameReader;
|
||||
import io.netty.handler.codec.http2.DefaultHttp2FrameWriter;
|
||||
import io.netty.handler.codec.http2.Http2ConnectionHandler;
|
||||
import io.netty.handler.codec.http2.Http2OrHttpChooser;
|
||||
import io.netty.handler.codec.http2.HttpToHttp2ConnectionHandler;
|
||||
import io.netty.handler.codec.http2.InboundHttp2ToHttpAdapter;
|
||||
|
||||
import javax.net.ssl.SSLEngine;
|
||||
|
||||
/**
|
||||
* Used during protocol negotiation, the main function of this handler is to
|
||||
* return the HTTP/1.1 or HTTP/2 handler once the protocol has been negotiated.
|
||||
*/
|
||||
public class Http2OrHttpHandler extends Http2OrHttpChooser {
|
||||
|
||||
public Http2OrHttpHandler(int maxHttpContentLength) {
|
||||
super(maxHttpContentLength);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SelectedProtocol getProtocol(SSLEngine engine) {
|
||||
String[] protocol = engine.getSession().getProtocol().split(":");
|
||||
if (protocol != null && protocol.length > 1) {
|
||||
SelectedProtocol selectedProtocol = SelectedProtocol.protocol(protocol[1]);
|
||||
System.err.println("Selected Protocol is " + selectedProtocol);
|
||||
return selectedProtocol;
|
||||
}
|
||||
return SelectedProtocol.UNKNOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addHttp2Handlers(ChannelHandlerContext ctx) {
|
||||
DefaultHttp2Connection connection = new DefaultHttp2Connection(true);
|
||||
DefaultHttp2FrameWriter writer = new DefaultHttp2FrameWriter();
|
||||
DefaultHttp2FrameReader reader = new DefaultHttp2FrameReader();
|
||||
InboundHttp2ToHttpAdapter listener = new InboundHttp2ToHttpAdapter.Builder(connection).propagateSettings(true)
|
||||
.validateHttpHeaders(false).maxContentLength(1024 * 100).build();
|
||||
|
||||
ctx.pipeline().addLast("httpToHttp2", new HttpToHttp2ConnectionHandler(connection,
|
||||
// Loggers can be activated for debugging purposes
|
||||
// new Http2InboundFrameLogger(reader, TilesHttp2ToHttpHandler.logger),
|
||||
// new Http2OutboundFrameLogger(writer, TilesHttp2ToHttpHandler.logger)
|
||||
reader, writer, listener));
|
||||
ctx.pipeline().addLast("fullHttpRequestHandler", new Http2RequestHandler());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ChannelHandler createHttp1RequestHandler() {
|
||||
return new FallbackRequestHandler();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Http2ConnectionHandler createHttp2RequestHandler() {
|
||||
return null; // NOOP
|
||||
}
|
||||
}
|
@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright 2015 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package io.netty.example.http2.tiles;
|
||||
|
||||
import static io.netty.buffer.Unpooled.EMPTY_BUFFER;
|
||||
import static io.netty.example.http2.Http2ExampleUtil.firstValue;
|
||||
import static io.netty.example.http2.Http2ExampleUtil.toInt;
|
||||
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
|
||||
import static io.netty.handler.codec.http.HttpHeaderUtil.setContentLength;
|
||||
import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
|
||||
import static io.netty.handler.codec.http.HttpResponseStatus.OK;
|
||||
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
|
||||
import static java.lang.Integer.valueOf;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
import io.netty.handler.codec.http.QueryStringDecoder;
|
||||
import io.netty.handler.codec.http2.HttpUtil;
|
||||
import io.netty.handler.codec.http2.InboundHttp2ToHttpAdapter;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Handles all the requests for data. It receives a {@link FullHttpRequest},
|
||||
* which has been converted by a {@link InboundHttp2ToHttpAdapter} before it
|
||||
* arrived here. For further details, check {@link Http2OrHttpHandler} where the
|
||||
* pipeline is setup.
|
||||
*/
|
||||
public class Http2RequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
|
||||
|
||||
private static final String LATENCY_FIELD_NAME = "latency";
|
||||
private static final int MIN_LATENCY = 0;
|
||||
private static final int MAX_LATENCY = 1000;
|
||||
private static final String IMAGE_COORDINATE_Y = "y";
|
||||
private static final String IMAGE_COORDINATE_X = "x";
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
|
||||
QueryStringDecoder queryString = new QueryStringDecoder(request.uri());
|
||||
String streamId = streamId(request);
|
||||
int latency = toInt(firstValue(queryString, LATENCY_FIELD_NAME), 0);
|
||||
if (latency < MIN_LATENCY || latency > MAX_LATENCY) {
|
||||
sendBadRequest(ctx, streamId);
|
||||
return;
|
||||
}
|
||||
String x = firstValue(queryString, IMAGE_COORDINATE_X);
|
||||
String y = firstValue(queryString, IMAGE_COORDINATE_Y);
|
||||
if (x == null || y == null) {
|
||||
handlePage(ctx, streamId, latency, request);
|
||||
} else {
|
||||
handleImage(x, y, ctx, streamId, latency, request);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendBadRequest(ChannelHandlerContext ctx, String streamId) {
|
||||
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST, EMPTY_BUFFER);
|
||||
streamId(response, streamId);
|
||||
ctx.writeAndFlush(response);
|
||||
}
|
||||
|
||||
private void handleImage(String x, String y, ChannelHandlerContext ctx, String streamId, int latency,
|
||||
FullHttpRequest request) {
|
||||
ByteBuf image = ImageCache.INSTANCE.image(valueOf(x), valueOf(y));
|
||||
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK, image.duplicate());
|
||||
response.headers().set(CONTENT_TYPE, "image/jpeg");
|
||||
sendResponse(ctx, streamId, latency, response, request);
|
||||
}
|
||||
|
||||
private void handlePage(ChannelHandlerContext ctx, String streamId, int latency, FullHttpRequest request) {
|
||||
byte[] body = Html.body(latency);
|
||||
ByteBuf content = ctx.alloc().buffer(Html.HEADER.length + body.length + Html.FOOTER.length);
|
||||
content.writeBytes(Html.HEADER);
|
||||
content.writeBytes(body);
|
||||
content.writeBytes(Html.FOOTER);
|
||||
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK, content);
|
||||
response.headers().set(CONTENT_TYPE, "text/html; charset=UTF-8");
|
||||
sendResponse(ctx, streamId, latency, response, request);
|
||||
}
|
||||
|
||||
protected void sendResponse(final ChannelHandlerContext ctx, String streamId, int latency,
|
||||
final FullHttpResponse response, final FullHttpRequest request) {
|
||||
setContentLength(response, response.content().readableBytes());
|
||||
streamId(response, streamId);
|
||||
ctx.executor().schedule(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ctx.writeAndFlush(response);
|
||||
}
|
||||
}, latency, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
private String streamId(FullHttpRequest request) {
|
||||
return request.headers().get(HttpUtil.ExtensionHeaderNames.STREAM_ID.text());
|
||||
}
|
||||
|
||||
private void streamId(FullHttpResponse response, String streamId) {
|
||||
response.headers().set(HttpUtil.ExtensionHeaderNames.STREAM_ID.text(), streamId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
cause.printStackTrace();
|
||||
ctx.close();
|
||||
}
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright 2015 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package io.netty.example.http2.tiles;
|
||||
|
||||
import static io.netty.handler.codec.http2.Http2OrHttpChooser.SelectedProtocol.HTTP_1_1;
|
||||
import static io.netty.handler.codec.http2.Http2OrHttpChooser.SelectedProtocol.HTTP_2;
|
||||
import static io.netty.handler.codec.http2.Http2SecurityUtil.CIPHERS;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.handler.ssl.ApplicationProtocolConfig;
|
||||
import io.netty.handler.ssl.ApplicationProtocolConfig.Protocol;
|
||||
import io.netty.handler.ssl.ApplicationProtocolConfig.SelectedListenerFailureBehavior;
|
||||
import io.netty.handler.ssl.ApplicationProtocolConfig.SelectorFailureBehavior;
|
||||
import io.netty.handler.ssl.SslContext;
|
||||
import io.netty.handler.ssl.SslContextBuilder;
|
||||
import io.netty.handler.ssl.SupportedCipherSuiteFilter;
|
||||
import io.netty.handler.ssl.util.SelfSignedCertificate;
|
||||
|
||||
import java.security.cert.CertificateException;
|
||||
|
||||
import javax.net.ssl.SSLException;
|
||||
|
||||
/**
|
||||
* Demonstrates a Http2 server using Netty to display a bunch of images and
|
||||
* simulate latency. It is a Netty version of the <a href="https://http2.golang.org/gophertiles?latency=0">
|
||||
* Go lang HTTP2 tiles demo</a>.
|
||||
*/
|
||||
public class Http2Server {
|
||||
|
||||
public static final int PORT = Integer.parseInt(System.getProperty("http2-port", "8443"));
|
||||
static final int MAX_CONTENT_LENGTH = 1024 * 100;
|
||||
|
||||
private final EventLoopGroup group;
|
||||
|
||||
public Http2Server(EventLoopGroup eventLoopGroup) {
|
||||
group = eventLoopGroup;
|
||||
}
|
||||
|
||||
public ChannelFuture start() throws Exception {
|
||||
final SslContext sslCtx = configureTLS();
|
||||
ServerBootstrap b = new ServerBootstrap();
|
||||
b.option(ChannelOption.SO_BACKLOG, 1024);
|
||||
b.group(group).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(SocketChannel ch) throws Exception {
|
||||
ch.pipeline().addLast(sslCtx.newHandler(ch.alloc()), new Http2OrHttpHandler(MAX_CONTENT_LENGTH));
|
||||
}
|
||||
});
|
||||
|
||||
Channel ch = b.bind(PORT).sync().channel();
|
||||
return ch.closeFuture();
|
||||
}
|
||||
|
||||
private SslContext configureTLS() throws CertificateException, SSLException {
|
||||
SelfSignedCertificate ssc = new SelfSignedCertificate();
|
||||
ApplicationProtocolConfig apn = new ApplicationProtocolConfig(Protocol.ALPN,
|
||||
// NO_ADVERTISE is currently the only mode supported by both OpenSsl and JDK providers.
|
||||
SelectorFailureBehavior.NO_ADVERTISE,
|
||||
// ACCEPT is currently the only mode supported by both OpenSsl and JDK providers.
|
||||
SelectedListenerFailureBehavior.ACCEPT,
|
||||
HTTP_2.protocolName(), HTTP_1_1.protocolName());
|
||||
final SslContext sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey(), null)
|
||||
.ciphers(CIPHERS, SupportedCipherSuiteFilter.INSTANCE)
|
||||
.applicationProtocolConfig(apn).build();
|
||||
return sslCtx;
|
||||
}
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2015 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package io.netty.example.http2.tiles;
|
||||
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.handler.codec.http.HttpObjectAggregator;
|
||||
import io.netty.handler.codec.http.HttpRequestDecoder;
|
||||
import io.netty.handler.codec.http.HttpResponseEncoder;
|
||||
import io.netty.handler.logging.LogLevel;
|
||||
import io.netty.handler.logging.LoggingHandler;
|
||||
|
||||
/**
|
||||
* Demonstrates a http server using Netty to display a bunch of images, simulate
|
||||
* latency and compare it against the http2 implementation.
|
||||
*/
|
||||
public final class HttpServer {
|
||||
|
||||
public static final int PORT = Integer.parseInt(System.getProperty("http-port", "8080"));
|
||||
private static final int MAX_CONTENT_LENGTH = 1024 * 100;
|
||||
|
||||
private final EventLoopGroup group;
|
||||
|
||||
public HttpServer(EventLoopGroup eventLoopGroup) {
|
||||
group = eventLoopGroup;
|
||||
}
|
||||
|
||||
public ChannelFuture start() throws Exception {
|
||||
ServerBootstrap b = new ServerBootstrap();
|
||||
b.option(ChannelOption.SO_BACKLOG, 1024);
|
||||
|
||||
b.group(group).channel(NioServerSocketChannel.class).handler(new LoggingHandler(LogLevel.INFO))
|
||||
.childHandler(new ChannelInitializer<SocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(SocketChannel ch) throws Exception {
|
||||
ChannelPipeline pipeline = ch.pipeline();
|
||||
pipeline.addLast("httpRequestDecoder", new HttpRequestDecoder());
|
||||
pipeline.addLast("httpResponseEncoder", new HttpResponseEncoder());
|
||||
pipeline.addLast("httpChunkAggregator", new HttpObjectAggregator(MAX_CONTENT_LENGTH));
|
||||
pipeline.addLast("httpRequestHandler", new Http1RequestHandler());
|
||||
}
|
||||
});
|
||||
|
||||
Channel ch = b.bind(PORT).sync().channel();
|
||||
return ch.closeFuture();
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2015 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package io.netty.example.http2.tiles;
|
||||
|
||||
import static io.netty.buffer.Unpooled.unmodifiableBuffer;
|
||||
import static io.netty.buffer.Unpooled.unreleasableBuffer;
|
||||
import static io.netty.example.http2.Http2ExampleUtil.toByteBuf;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Caches the images to avoid reading them every time from the disk.
|
||||
*/
|
||||
public final class ImageCache {
|
||||
|
||||
public static ImageCache INSTANCE = new ImageCache();
|
||||
|
||||
private final Map<String, ByteBuf> imageBank = new HashMap<String, ByteBuf>(200);
|
||||
|
||||
private ImageCache() {
|
||||
init();
|
||||
}
|
||||
|
||||
public static String name(int x, int y) {
|
||||
return "tile-" + y + "-" + x + ".jpeg";
|
||||
}
|
||||
|
||||
public ByteBuf image(int x, int y) {
|
||||
return imageBank.get(name(x, y));
|
||||
}
|
||||
|
||||
private void init() {
|
||||
for (int y = 0; y < 10; y++) {
|
||||
for (int x = 0; x < 20; x++) {
|
||||
try {
|
||||
String name = name(x, y);
|
||||
ByteBuf fileBytes = unreleasableBuffer(unmodifiableBuffer(toByteBuf(getClass()
|
||||
.getResourceAsStream(name))));
|
||||
imageBank.put(name, fileBytes);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2015 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package io.netty.example.http2.tiles;
|
||||
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Launches both Http and Http2 servers using Netty to display a set of images
|
||||
* and simulate latency. It is a Netty version of the <a
|
||||
* href="https://http2.golang.org/gophertiles?latency=0"> Go lang HTTP2 tiles
|
||||
* demo</a>.
|
||||
* </p>
|
||||
* <p>
|
||||
* Please note that if you intent to use the JDK provider for SSL, you MUST use JDK 1.8.
|
||||
* Previous JDK versions don't have any cipher suite that is suitable for use with HTTP/2.
|
||||
* The associated ALPN library for your JDK version can be found here:
|
||||
* http://eclipse.org/jetty/documentation/current/alpn-chapter.html#alpn-versions.
|
||||
* Alternatively, you can use the OpenSsl provider. Please make sure that you run OpenSsl
|
||||
* version 1.0.2 or greater.
|
||||
* </p>
|
||||
*/
|
||||
public final class Launcher {
|
||||
|
||||
public static void main(String[] args) {
|
||||
EventLoopGroup group = new NioEventLoopGroup();
|
||||
Http2Server http2 = new Http2Server(group);
|
||||
HttpServer http = new HttpServer(group);
|
||||
try {
|
||||
http2.start();
|
||||
System.err.println("Open your web browser and navigate to " + "http://" + Html.IP + ":" + HttpServer.PORT);
|
||||
http.start().sync();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
group.shutdownGracefully();
|
||||
}
|
||||
}
|
||||
}
|
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-0-0.jpeg
Executable file
After Width: | Height: | Size: 643 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-0-1.jpeg
Executable file
After Width: | Height: | Size: 647 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-0-10.jpeg
Executable file
After Width: | Height: | Size: 643 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-0-11.jpeg
Executable file
After Width: | Height: | Size: 643 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-0-12.jpeg
Executable file
After Width: | Height: | Size: 643 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-0-13.jpeg
Executable file
After Width: | Height: | Size: 643 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-0-14.jpeg
Executable file
After Width: | Height: | Size: 643 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-0-15.jpeg
Executable file
After Width: | Height: | Size: 643 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-0-16.jpeg
Executable file
After Width: | Height: | Size: 643 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-0-17.jpeg
Executable file
After Width: | Height: | Size: 643 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-0-18.jpeg
Executable file
After Width: | Height: | Size: 643 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-0-19.jpeg
Executable file
After Width: | Height: | Size: 643 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-0-2.jpeg
Executable file
After Width: | Height: | Size: 743 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-0-3.jpeg
Executable file
After Width: | Height: | Size: 764 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-0-4.jpeg
Executable file
After Width: | Height: | Size: 767 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-0-5.jpeg
Executable file
After Width: | Height: | Size: 825 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-0-6.jpeg
Executable file
After Width: | Height: | Size: 795 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-0-7.jpeg
Executable file
After Width: | Height: | Size: 715 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-0-8.jpeg
Executable file
After Width: | Height: | Size: 643 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-0-9.jpeg
Executable file
After Width: | Height: | Size: 643 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-1-0.jpeg
Executable file
After Width: | Height: | Size: 643 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-1-1.jpeg
Executable file
After Width: | Height: | Size: 757 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-1-10.jpeg
Executable file
After Width: | Height: | Size: 665 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-1-11.jpeg
Executable file
After Width: | Height: | Size: 643 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-1-12.jpeg
Executable file
After Width: | Height: | Size: 643 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-1-13.jpeg
Executable file
After Width: | Height: | Size: 643 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-1-14.jpeg
Executable file
After Width: | Height: | Size: 643 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-1-15.jpeg
Executable file
After Width: | Height: | Size: 643 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-1-16.jpeg
Executable file
After Width: | Height: | Size: 643 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-1-17.jpeg
Executable file
After Width: | Height: | Size: 643 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-1-18.jpeg
Executable file
After Width: | Height: | Size: 643 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-1-19.jpeg
Executable file
After Width: | Height: | Size: 643 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-1-2.jpeg
Executable file
After Width: | Height: | Size: 725 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-1-3.jpeg
Executable file
After Width: | Height: | Size: 726 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-1-4.jpeg
Executable file
After Width: | Height: | Size: 748 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-1-5.jpeg
Executable file
After Width: | Height: | Size: 760 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-1-6.jpeg
Executable file
After Width: | Height: | Size: 700 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-1-7.jpeg
Executable file
After Width: | Height: | Size: 759 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-1-8.jpeg
Executable file
After Width: | Height: | Size: 716 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-1-9.jpeg
Executable file
After Width: | Height: | Size: 661 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-2-0.jpeg
Executable file
After Width: | Height: | Size: 715 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-2-1.jpeg
Executable file
After Width: | Height: | Size: 705 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-2-10.jpeg
Executable file
After Width: | Height: | Size: 721 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-2-11.jpeg
Executable file
After Width: | Height: | Size: 643 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-2-12.jpeg
Executable file
After Width: | Height: | Size: 643 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-2-13.jpeg
Executable file
After Width: | Height: | Size: 643 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-2-14.jpeg
Executable file
After Width: | Height: | Size: 643 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-2-15.jpeg
Executable file
After Width: | Height: | Size: 643 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-2-16.jpeg
Executable file
After Width: | Height: | Size: 643 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-2-17.jpeg
Executable file
After Width: | Height: | Size: 643 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-2-18.jpeg
Executable file
After Width: | Height: | Size: 643 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-2-19.jpeg
Executable file
After Width: | Height: | Size: 643 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-2-2.jpeg
Executable file
After Width: | Height: | Size: 685 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-2-3.jpeg
Executable file
After Width: | Height: | Size: 740 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-2-4.jpeg
Executable file
After Width: | Height: | Size: 654 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-2-5.jpeg
Executable file
After Width: | Height: | Size: 824 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-2-6.jpeg
Executable file
After Width: | Height: | Size: 682 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-2-7.jpeg
Executable file
After Width: | Height: | Size: 673 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-2-8.jpeg
Executable file
After Width: | Height: | Size: 794 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-2-9.jpeg
Executable file
After Width: | Height: | Size: 698 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-3-0.jpeg
Executable file
After Width: | Height: | Size: 734 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-3-1.jpeg
Executable file
After Width: | Height: | Size: 686 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-3-10.jpeg
Executable file
After Width: | Height: | Size: 722 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-3-11.jpeg
Executable file
After Width: | Height: | Size: 643 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-3-12.jpeg
Executable file
After Width: | Height: | Size: 643 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-3-13.jpeg
Executable file
After Width: | Height: | Size: 643 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-3-14.jpeg
Executable file
After Width: | Height: | Size: 643 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-3-15.jpeg
Executable file
After Width: | Height: | Size: 643 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-3-16.jpeg
Executable file
After Width: | Height: | Size: 643 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-3-17.jpeg
Executable file
After Width: | Height: | Size: 643 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-3-18.jpeg
Executable file
After Width: | Height: | Size: 643 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-3-19.jpeg
Executable file
After Width: | Height: | Size: 643 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-3-2.jpeg
Executable file
After Width: | Height: | Size: 685 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-3-3.jpeg
Executable file
After Width: | Height: | Size: 710 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-3-4.jpeg
Executable file
After Width: | Height: | Size: 643 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-3-5.jpeg
Executable file
After Width: | Height: | Size: 772 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-3-6.jpeg
Executable file
After Width: | Height: | Size: 669 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-3-7.jpeg
Executable file
After Width: | Height: | Size: 666 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-3-8.jpeg
Executable file
After Width: | Height: | Size: 751 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-3-9.jpeg
Executable file
After Width: | Height: | Size: 806 B |
BIN
example/src/main/resources/io/netty/example/http2/tiles/tile-4-0.jpeg
Executable file
After Width: | Height: | Size: 728 B |