
👆關注微信公眾號,獲取更多編程內容
昨天在繼續完善基於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可以實現,如負載均衡,設置配置文件(如資源所在路徑以及端口等信息),