1. Información general
En este tutorial, vamos a implementar un servidor simple en mayúsculas sobre HTTP con Netty , un marco asincrónico que nos da la flexibilidad para desarrollar aplicaciones de red en Java.
2. Bootstrapping del servidor
public class HttpServer { private int port; private static Logger logger = LoggerFactory.getLogger(HttpServer.class); // constructor // main method, same as simple protocol server public void run() throws Exception { ... ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .handler(new LoggingHandler(LogLevel.INFO)) .childHandler(new ChannelInitializer() { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline p = ch.pipeline(); p.addLast(new HttpRequestDecoder()); p.addLast(new HttpResponseEncoder()); p.addLast(new CustomHttpServerHandler()); } }); ... } }
Agregamos tres controladores a la canalización del servidor:
- HttpResponseEncoder de Netty - para serialización
- HttpRequestDecoder de Netty - para deserialización
- Nuestro propio CustomHttpServerHandler - para definir el comportamiento de nuestro servidor
Veamos el último controlador en detalle a continuación.
3. CustomHttpServerHandler
Analicémoslo para entender su funcionamiento.
3.1. Estructura del controlador
public class CustomHttpServerHandler extends SimpleChannelInboundHandler { private HttpRequest request; StringBuilder responseData = new StringBuilder(); @Override public void channelReadComplete(ChannelHandlerContext ctx) { ctx.flush(); } @Override protected void channelRead0(ChannelHandlerContext ctx, Object msg) { // implementation to follow } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { cause.printStackTrace(); ctx.close(); } }
Hasta ahora, todo lo que hemos visto es el código estándar.
Ahora sigamos con lo interesante, la implementación de channelRead0 .
3.2. Leyendo el canal
if (msg instanceof HttpRequest) { HttpRequest request = this.request = (HttpRequest) msg; if (HttpUtil.is100ContinueExpected(request)) { writeResponse(ctx); } responseData.setLength(0); responseData.append(RequestUtils.formatParams(request)); } responseData.append(RequestUtils.evaluateDecoderResult(request)); if (msg instanceof HttpContent) { HttpContent httpContent = (HttpContent) msg; responseData.append(RequestUtils.formatBody(httpContent)); responseData.append(RequestUtils.evaluateDecoderResult(request)); if (msg instanceof LastHttpContent) { LastHttpContent trailer = (LastHttpContent) msg; responseData.append(RequestUtils.prepareLastResponse(request, trailer)); writeResponse(ctx, trailer, responseData); } }
private void writeResponse(ChannelHandlerContext ctx) { FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, CONTINUE, Unpooled.EMPTY_BUFFER); ctx.write(response); }
StringBuilder formatParams(HttpRequest request) { StringBuilder responseData = new StringBuilder(); QueryStringDecoder queryStringDecoder = new QueryStringDecoder(request.uri()); Map
params = queryStringDecoder.parameters(); if (!params.isEmpty()) { for (Entry
p : params.entrySet()) { String key = p.getKey(); List vals = p.getValue(); for (String val : vals) { responseData.append("Parameter: ").append(key.toUpperCase()).append(" = ") .append(val.toUpperCase()).append("\r\n"); } } responseData.append("\r\n"); } return responseData; }
StringBuilder formatBody(HttpContent httpContent) { StringBuilder responseData = new StringBuilder(); ByteBuf content = httpContent.content(); if (content.isReadable()) { responseData.append(content.toString(CharsetUtil.UTF_8).toUpperCase()) .append("\r\n"); } return responseData; }
StringBuilder prepareLastResponse(HttpRequest request, LastHttpContent trailer) { StringBuilder responseData = new StringBuilder(); responseData.append("Good Bye!\r\n"); if (!trailer.trailingHeaders().isEmpty()) { responseData.append("\r\n"); for (CharSequence name : trailer.trailingHeaders().names()) { for (CharSequence value : trailer.trailingHeaders().getAll(name)) { responseData.append("P.S. Trailing Header: "); responseData.append(name).append(" = ").append(value).append("\r\n"); } } responseData.append("\r\n"); } return responseData; }
3.3. Escribir la respuesta
private void writeResponse(ChannelHandlerContext ctx, LastHttpContent trailer, StringBuilder responseData) { boolean keepAlive = HttpUtil.isKeepAlive(request); FullHttpResponse httpResponse = new DefaultFullHttpResponse(HTTP_1_1, ((HttpObject) trailer).decoderResult().isSuccess() ? OK : BAD_REQUEST, Unpooled.copiedBuffer(responseData.toString(), CharsetUtil.UTF_8)); httpResponse.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8"); if (keepAlive) { httpResponse.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, httpResponse.content().readableBytes()); httpResponse.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE); } ctx.write(httpResponse); if (!keepAlive) { ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE); } }
Para probar nuestro servidor, enviemos algunos comandos cURL y miremos las respuestas.
Por supuesto, necesitamos iniciar el servidor ejecutando la clase HttpServer antes de esto .
4.1. OBTENER Solicitud
Primero invoquemos el servidor, proporcionando una cookie con la solicitud:
curl //127.0.0.1:8080?param1=one
Como respuesta, obtenemos:
Parameter: PARAM1 = ONE Good Bye!
También podemos presionar //127.0.0.1:8080?param1=one desde cualquier navegador para ver el mismo resultado.
4.2. Solicitud POST
Como segunda prueba, enviemos un POST con contenido de muestra del cuerpo :
curl -d "sample content" -X POST //127.0.0.1:8080
Esta es la respuesta:
SAMPLE CONTENT Good Bye!
Esta vez, dado que nuestra solicitud contenía un cuerpo, el servidor lo devolvió en mayúsculas .
5. Conclusión
En este tutorial, vimos cómo implementar el protocolo HTTP, particularmente un servidor HTTP que usa Netty.
HTTP / 2 en Netty demuestra una implementación cliente-servidor del protocolo HTTP / 2.
Como siempre, el código fuente está disponible en GitHub.