用Netty实现HTTP服务器一
目标
通过实现HTTP服务器示例,学习Netty中内置的一些Handler,可以帮我们快速的实现一些上层协议。最终我们实现一个简单版本的类似spring mvc的web框架。
创建HTTP Server
创建一个HttpServer的过程与创建TCP服务类似,只不过添加的childHandler不太一样,我们需要添加处理HTTP协议的handler
public class HttpServer {
public static void main(String[] args) {
EventLoopGroup boss = new NioEventLoopGroup();
EventLoopGroup work = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(boss,work).channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG,1024)
.childOption(ChannelOption.SO_KEEPALIVE,true)
.childOption(ChannelOption.RCVBUF_ALLOCATOR,new AdaptiveRecvByteBufAllocator())
.childOption(ChannelOption.TCP_NODELAY,true)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new HttpServerCodec())
.addLast(new ChunkedWriteHandler())
.addLast(new HttpObjectAggregator(64*1024))
.addLast(new MyHttpServerHandler());
}
});
ChannelFuture future = serverBootstrap.bind(8080).sync();
Channel channel = future.channel();
channel.closeFuture().sync();
}catch (Exception e){
e.printStackTrace();
}finally {
boss.shutdownGracefully();
work.shutdownGracefully();
}
}
}
- 我们将服务端口绑定到8080
- 首先添加的是 HttpServerCodec,它包含了对Http协议的编码和解码的功能,也可以分开添加编码(HttpResponseEncoder)和解码(HttpRequestDecoder)
- 添加对大数据流的支持:ChunkedWriteHandler
- 添加Http消息聚合,因为HTTP允许将大的消息体分割成多个数据块进行传输。因此我们要想直接处理完整的消息内容需要添加这个handler
- 添加我们的业务处理handler
Http业务处理
我们首先简单的处理HTTP请求,在后台打印出请求方法和uri,然后返回hello world
public class MyHttpServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {
String methodName = msg.method().name();
String requestUri = msg.uri();
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.copiedBuffer("Hello World".getBytes()));
response.headers().add(HttpHeaderNames.CONTENT_TYPE, "text/plain");
System.out.println(String.format("methodName:%s,requestUri:%s", methodName, requestUri));
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
}
}
这里MyHttpServerHandler 继承类时传入的泛型是FullHttpRequest,这里的类型与你上一个处理的handler有关,我们示例中上一个handler是HttpObjectAggregator,聚合后的数据我们读取就是FullHttpRequest类型了
记得写完数据后要关闭掉链接哦,不然浏览器一直等待
到这里我们已经完成了一个简单的HTTP服务,如果我们要实现一个静态的web服务器,指定一个路径为基地址,通过path访问路径下的html,要如何做呢
实现静态web服务器
实现在运行目录的app目录下作为网站基地址,实现静态网页的访问
public class StaticHttpServer {
static class Server{
private String basePath;
public Server(String basePath) {
this.basePath = basePath;
}
public void start(){
EventLoopGroup boss = new NioEventLoopGroup();
EventLoopGroup work = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(boss,work).channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG,1024)
.childOption(ChannelOption.SO_KEEPALIVE,true)
.childOption(ChannelOption.RCVBUF_ALLOCATOR,new AdaptiveRecvByteBufAllocator())
.childOption(ChannelOption.TCP_NODELAY,true)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new HttpServerCodec())
.addLast(new ChunkedWriteHandler())
.addLast(new HttpObjectAggregator(64*1024))
.addLast(new MyHttpServerHandler(Server.this.basePath));
}
});
ChannelFuture future = serverBootstrap.bind(8080).sync();
Channel channel = future.channel();
channel.closeFuture().sync();
}catch (Exception e){
e.printStackTrace();
}finally {
boss.shutdownGracefully();
work.shutdownGracefully();
}
}
}
public static void main(String[] args) {
String basePath = Server.class.getClassLoader().getResource("app").getPath();
System.out.println(String.format("basePath:%s",basePath));
Server server = new Server(basePath);
server.start();
}
}
创建服务和handler时我们传入一个basePath作为静态网站的基地址,把网页文件放置在里面即可通过path访问。
下面我们看看handler的处理代码
public class MyHttpServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
private String basePath;
public MyHttpServerHandler(String basePath) {
this.basePath = basePath;
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {
String methodName = msg.method().name();
String requestUri = msg.uri();
if (!"GET".equals(methodName)) {
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.copiedBuffer("Not Support".getBytes()));
response.headers().add(HttpHeaderNames.CONTENT_TYPE, "text/plain");
return ;
}
this.sendResponse(requestUri,ctx);
}
private void sendResponse(String uri,ChannelHandlerContext ctx) throws IOException {
String filename = "index";
String path = uri.substring(0,uri.lastIndexOf("/"));
if (!"/".equals(uri)){
//请求
filename = uri.substring(uri.lastIndexOf("/")+1);
}
System.out.println(String.format("path:%s,filename:%s", path, filename));
File file = new File(basePath + path + "/" + filename+".html");
if (!file.exists()){
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.NOT_FOUND);
response.headers().add(HttpHeaderNames.CONTENT_TYPE, "text/plain");
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
return;
}
//读取文件到buf中返回
RandomAccessFile accessFile = new RandomAccessFile(file,"r");
FileChannel fileChannel = accessFile.getChannel();
ByteBuf buf = Unpooled.wrappedBuffer(fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, file.length()));
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK,buf);
response.headers().add(HttpHeaderNames.CONTENT_TYPE, "text/html");
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
}
}
我们通过请求uri,获取到要访问的路径和文件,然后把文件数据作为html文档格式返回。如果未找到则返回404