【Netty】实战:基于Http的Web服务器
目录
一、实现ChannelHandler
二、实现ChannelInitializer
三、实现服务器启动程序
四、测试
本文来实现一个简单的Web服务器,当用户在浏览器访问Web服务器时,可以返回响应的内容给用户。很简单,就三步。
一、实现ChannelHandler
package cn.md.netty.httpserver;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.util.CharsetUtil;
/**
* * @Author: Martin
* * @Date 2024/9/1 17:47
* * @Description
**/
public class HttpServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
/**
* Is called for each message of type {@link I}.
*
* @param ctx the {@link ChannelHandlerContext} which this {@link SimpleChannelInboundHandler}
* belongs to
* @param msg the message to handle
* @throws Exception is thrown if an error occurred
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {
// 打印Http请求
printHttpRequest(msg);
String uri = msg.uri();
String resp;
switch (uri) {
case "/":
resp = "hello world";
break;
case "/test":
resp = "test";
break;
case "/hi":
resp = "hello";
break;
default:
resp = "404";
}
// 返回http格式响应
returnHttpResp(ctx, resp);
}
private void returnHttpResp(ChannelHandlerContext ctx, String msg) {
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK,
Unpooled.copiedBuffer(msg, CharsetUtil.UTF_8));
response.headers().set(HttpHeaderNames.CONTENT_LENGTH,msg.length());
ctx.writeAndFlush(response)
.addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
}
private void printHttpRequest(FullHttpRequest msg) {
String uri = msg.uri();
HttpMethod method = msg.method();
HttpVersion httpVersion = msg.protocolVersion();
// 打印请求行
System.out.println("uri:" + uri + " method:" + method + " httpVersion:" + httpVersion);
HttpHeaders headers = msg.headers();
for (String name : headers.names()) {
System.out.println(name + ":" + headers.get(name));
}
System.out.println("");
System.out.println(msg.content().toString(CharsetUtil.UTF_8));
}
}
二、实现ChannelInitializer
package cn.md.netty.httpserver;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
/**
* * @Author: Martin
* * @Date 2024/9/1 17:55
* * @Description
**/
public class HttpServerChannelInitializer extends ChannelInitializer<SocketChannel> {
/**
* This method will be called once the {@link Channel} was registered. After the method returns this instance
* will be removed from the {@link ChannelPipeline} of the {@link Channel}.
*
* @param ch the {@link Channel} which was registered.
* @throws Exception is thrown if an error occurs. In that case it will be handled by
* {@link #exceptionCaught(ChannelHandlerContext, Throwable)} which will by default close
* the {@link Channel}.
*/
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// 添加自定义的handler
ch.pipeline().addLast("codec",new HttpServerCodec()) // 添加编解码器
// 添加聚合器,聚合为一个完整的 FullHttpMessage
.addLast("aggregator",new HttpObjectAggregator(1024*1024*10))
.addLast("handler",new HttpServerHandler());
}
}
三、实现服务器启动程序
package cn.md.netty.httpserver;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/**
* * @Author: Martin
* * @Date 2024/9/1 18:01
* * @Description
**/
public class HttpServer {
public static void main(String[] args) {
NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap serverBootstrap = new ServerBootstrap();
try {
ChannelFuture channelFuture = serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new HttpServerChannelInitializer())
//服务器在处理客户端连接请求时的等待队列长度。
//当服务器接收到客户端的连接请求时,如果服务器正在处理其他连接或者处于忙碌状态,新的连接请求将被放入等待队列中。
.option(ChannelOption.SO_BACKLOG, 128)
//底层套接字级别设置的选项,由操作系统的 TCP/IP 协议栈实现保活机制。
//当开启后,在一定时间没有数据传输时,操作系统自动发送保活探测报文来检测连接是否仍然有效。
.option(ChannelOption.SO_KEEPALIVE, true)
.bind(8888)
.sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
}
四、测试
我是马丁,如果你喜欢,麻烦点个赞~ 下期见~