Netty学习笔记(五) 使用Netty构建静态网页服务器
👆关注微信公众号,获取更多编程内容
昨天在继续完善基于Netty构建的聊天室系统的过程中,发现了一个有意思的知识点,特此拿来做一个简单的静态网页服务器,好好的玩一玩Netty。
但是不管怎么说利用netty实现各种功能的流程都是类似的
- 配置ServerHandle
- (可选)实现自定义的编码器
- 完成ServerBootStarp的配置
- 启动服务
- 连接到该服务
好的,那么我们基于此来实现一个简单静态网页需求,要求实现能够通过地址访问html,js,css,以及图片等资源文件,那么开始吧
静态网页资源服务器
HttpServerHandleAdapter
这里是最为复杂的步骤,具体代码可以看注释。
package com.zhoutao123.simpleChat.html;
import io.netty.channel.*;
import io.netty.handler.codec.http.*;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.stream.ChunkedNioFile;
import java.io.File;
import java.io.RandomAccessFile;
public class HttpServerHandleAdapter extends SimpleChannelInboundHandler<FullHttpRequest> {
// 资源所在路径
private static final String location;
// 404文件页面地址
private static final File NOT_FOUND;
static {
// 构建资源所在路径,此处参数可优化为使用配置文件传入
location = "/home/tao/code/resource";
// 构建404页面
String path = location + "/404.html";
NOT_FOUND = new File(path);
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
// 获取URI
String uri = request.getUri();
// 设置不支持favicon.ico文件
if ("favicon.ico".equals(uri)) {
return;
}
// 根据路径地址构建文件
String path = location + uri;
File html = new File(path);
// 状态为1xx的话,继续请求
if (HttpHeaders.is100ContinueExpected(request)) {
send100Continue(ctx);
}
// 当文件不存在的时候,将资源指向NOT_FOUND
if (!html.exists()) {
html = NOT_FOUND;
}
RandomAccessFile file = new RandomAccessFile(html, "r");
HttpResponse response = new DefaultHttpResponse(request.getProtocolVersion(), HttpResponseStatus.OK);
// 文件没有发现设置状态为404
if (html == NOT_FOUND) {
response.setStatus(HttpResponseStatus.NOT_FOUND);
}
// 设置文件格式内容
if (path.endsWith(".html")){
response.headers().set(HttpHeaders.Names.CONTENT_TYPE, "text/html; charset=UTF-8");
}else if(path.endsWith(".js")){
response.headers().set(HttpHeaders.Names.CONTENT_TYPE, "application/x-javascript");
}else if(path.endsWith(".css")){
response.headers().set(HttpHeaders.Names.CONTENT_TYPE, "text/css; charset=UTF-8");
}
boolean keepAlive = HttpHeaders.isKeepAlive(request);
if (keepAlive) {
response.headers().set(HttpHeaders.Names.CONTENT_LENGTH, file.length());
response.headers().set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
}
ctx.write(response);
if (ctx.pipeline().get(SslHandler.class) == null) {
ctx.write(new DefaultFileRegion(file.getChannel(), 0, file.length()));
} else {
ctx.write(new ChunkedNioFile(file.getChannel()));
}
// 写入文件尾部
ChannelFuture future = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
if (!keepAlive) {
future.addListener(ChannelFutureListener.CLOSE);
}
file.close();
}
private static void send100Continue(ChannelHandlerContext ctx) {
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE);
ctx.writeAndFlush(response);
}
}
HttpServerInitializer
添加我们刚刚完成的HttpServerHandleAdapter
package com.zhoutao123.simpleChat.html;
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;
import io.netty.handler.stream.ChunkedWriteHandler;
public class HttpServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//将请求和应答消息编码或解码为HTTP消息
pipeline.addLast(new HttpServerCodec());
//将HTTP消息的多个部分组合成一条完整的HTTP消息
pipeline.addLast(new HttpObjectAggregator(64 * 1024));
pipeline.addLast(new ChunkedWriteHandler());
pipeline.addLast(new HttpServerHandleAdapter());
}
}
服务启动
public class Server {
private final static int port = 8080;
public static void main(String[] args) throws InterruptedException {
NioEventLoopGroup boss = new NioEventLoopGroup();
NioEventLoopGroup work = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(boss, work)
.channel(NioServerSocketChannel.class)
.childHandler(new HttpServerInitializer())
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture future = serverBootstrap.bind(port).sync();
// 等待服务器 socket 关闭 。
// 在这个例子中,这不会发生,但你可以优雅地关闭你的服务器。
future.channel().closeFuture().sync();
} finally {
work.shutdownGracefully();
boss.shutdownGracefully();
}
}
}
静态资源文件
静态资源文件这里主要测试html以及js和css文件,这里写了3个html文件(404.html用于当输入路径不存在的时候,跳转到的文件),一个js文件,一个css文件,一个logo图片,测试js和css在index.html文件中测试
需要注意的是这些资源文件需要放在上面代码中的location指定的位置,否则可能会出现访问不到的异常情况
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Netty静态资源服务器</title>
<link rel="stylesheet" href="css/style.css"/>
</head>
<body>
<h1 id="title">你好,欢迎来到基于Netty构建的静态资源服务器主页</h1>
<h1 style="color: blue">当前位置Index.html</h1>
<input type="button" value="启动问候语" onclick="sayHello()"/>
<a href="about.html">关于Netty和作者</a>
</body>
<script src="js/message.js"></script>
</html>
about.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>WebSocket Chat</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<h1 style="color: blue">这是关于界面</h1>
<img style="width: 250px" src="logo.jpg">
<a href="index.html">点击我,返回主页</a>
</body>
</html>
404.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>WebSocket Chat</title>
</head>
<body>
<h1 style="color: red">没有发现此页面</h1>
</body>
</html>
js文件
function sayHello() {
alert("你好,欢迎使用Netty服务器")
}
css文件
#title{
color: red;
text-underline: darkcyan;
}
input{
background-color: darkcyan;
}
文件整体结构
.
├── index.html
├── 404.html
├── about.html
├── logo.jpg
├── css
│ └── style.css
└── js
└── message.js
2 directories, 5 files
测试效果
浏览器输入URL即可访问
主页
可以看到访问成功,地址是localhost:8080/index.html ,开发者工具中展示状态均为200
调用JS效果
点击启动问候语会执行js脚本弹出窗口
跳转效果
点击超链接 ,即可跳转到about.html界面,这里引用了一个jpg图片
404页面
当输入一个不存在的文件的时候,可以看到状态为404.
总结
基于Netty实现的一个简单的静态资源服务器,可以说实现了基本的功能,但是还有其他很多idea可以实现,如负载均衡,设置配置文件(如资源所在路径以及端口等信息),