手撕http服务器——引入Netty

本文最后更新于 2024年7月12日 凌晨

理论储备

相关Netty知识请见文章:手撕RPC框架——引入Netty

更加深入的八股知识,待博主阅读完Netty实战后进行更新😀

代码实现

创建测试页面

404.html

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>404</title>
</head>
<body>
<h3>404 not found</h3>
</body>
</html>

index.html

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<h3>hello,http server</h3>
</body>
</html>

创建服务器接口

目前此接口还没添加函数,为了之后的扩展性,先行定义,之后在创建服务器直接实现该接口即可

1
2
3
public interface HttpServer {

}

初始化Netty配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class NettyHttpServerInitializer extends ChannelInitializer<SocketChannel> {

@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//解码器
socketChannel.pipeline().addLast("http-decoder", new HttpRequestDecoder());
//聚合器
socketChannel.pipeline().addLast("http-aggregator", new HttpObjectAggregator(1024 * 1024));
//编码器
socketChannel.pipeline().addLast("http-encoder", new HttpResponseEncoder());
//解决大码流问题
socketChannel.pipeline().addLast("http-chunked", new ChunkedWriteHandler());
//自定义处理handler
socketChannel.pipeline().addLast("http-server", new NettyHttpServerHandler());
}
}

实现Netty处理器

此部分对客户端发来的请求进行处理,并封装response进行返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
/**
* @Description 自定义handler处理器
* @author GuoZihan
* @date 22:50 2024/7/11
*/
public class NettyHttpServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> {


@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, FullHttpRequest fullHttpRequest) throws Exception {
//打印请求信息
System.out.println(fullHttpRequest);

FullHttpResponse response = null;
String str = "/index.html";

InputStream is = null;
//处理GET请求
if(fullHttpRequest.getMethod().equals(HttpMethod.GET)) {
//获取请求附带参数
System.out.println(getGetParamsFromChannel(fullHttpRequest));
//判断资源是否存在
System.out.println("uri-------" + fullHttpRequest.getUri());
String origin = fullHttpRequest.uri();
int index = fullHttpRequest.getUri().indexOf('?');
if(index != -1)
str = origin.substring(0, index);
System.out.println("real--------" + str);
if(fullHttpRequest.getUri().equals("/") || str.equals(fullHttpRequest.getUri())) {
//存在
is = this.getClass().getResourceAsStream("/index.html");
} else {
//404
is = this.getClass().getResourceAsStream("/404.html");
}
String resource = new Scanner(is).useDelimiter("\\Z").next();
ByteBuf buf = copiedBuffer(resource, CharsetUtil.UTF_8);
//结果返回给客户端
response = responseOK(HttpResponseStatus.OK, buf);
}
//POST请求
else if (fullHttpRequest.getMethod().equals(HttpMethod.POST)) {
System.out.println(getPostParamsFromChannel(fullHttpRequest));
//判断资源 是否存在
if (str.equals(fullHttpRequest.getUri())) {
//存在
is = this.getClass().getResourceAsStream("/index.html");

} else {
//404
is = this.getClass().getResourceAsStream("/404.html");
}
String resource = new Scanner(is).useDelimiter("\\Z").next();
ByteBuf buf = copiedBuffer(resource, CharsetUtil.UTF_8);
//结果返回客户端
response = responseOK(HttpResponseStatus.OK, buf);
}
else {
response = responseOK(HttpResponseStatus.INTERNAL_SERVER_ERROR, null);
}

//响应客户端
channelHandlerContext.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);

}
/**
* @Description 获取GET请求传递的参数
* @author GuoZihan
* @date 22:59 2024/7/11
*/
private Map<String, Object> getGetParamsFromChannel(FullHttpRequest fullHttpRequest) {
Map<String, Object> params = new HashMap<String, Object>();

if(fullHttpRequest.getMethod().equals(HttpMethod.GET)) {
QueryStringDecoder decoder = new QueryStringDecoder(fullHttpRequest.getUri());
Map<String, List<String>> paramList = decoder.parameters();
//将参数存储再params中
for(Map.Entry<String, List<String>> entry : paramList.entrySet()) {
params.put(entry.getKey(), entry.getValue().get(0));
}
return params;
} else {
return null;
}
}

/**
* @Description 获取POST请求传递的参数
* @author GuoZihan
* @date 23:10 2024/7/11
*/
private Map<String, Object> getPostParamsFromChannel(FullHttpRequest fullHttpRequest) {
Map<String, Object> params = new HashMap<String, Object>();

if(fullHttpRequest.getMethod().equals(HttpMethod.POST)) {
// 处理POST请求
String strContentType = fullHttpRequest.headers().get("Content-Type").trim();
if(strContentType.contains("x-www-form-urlencoded")) {
//form表单
} else if (strContentType.contains("application/json")) {
//json数据
} else {
return null;
}
return params;
} else {
return null;
}
}

/**
* @Description 解析form表单数据
* Content-Type = x-www-form-urlencoded
* @author GuoZihan
* @date 23:14 2024/7/11
*/
private Map<String, Object> getFormParams(FullHttpRequest fullHttpRequest) {
Map<String, Object> params = new HashMap<String, Object>();

HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(new DefaultHttpDataFactory(false), fullHttpRequest);
List<InterfaceHttpData> postData = decoder.getBodyHttpDatas();

for(InterfaceHttpData data : postData) {
if(data.getHttpDataType() == InterfaceHttpData.HttpDataType.Attribute) {
MemoryAttribute attribute = (MemoryAttribute) data;
params.put(attribute.getName(), attribute.getValue());
}
}
return params;
}

/**
* @Description 解析json数据
* Content-Type = application/json
* @author GuoZihan
* @date 23:18 2024/7/11
*/
private Map<String, Object> getJsonParams(FullHttpRequest fullHttpRequest) {
Map<String, Object> params = new HashMap<String, Object>();

ByteBuf content = fullHttpRequest.content();
byte[] reqContent = new byte[content.readableBytes()];
content.readBytes(reqContent);
String strContent = new String(reqContent, StandardCharsets.UTF_8);

JSONObject jsonParams = JSONObject.fromObject(strContent);
for (Object key : jsonParams.keySet())
params.put(key.toString(), jsonParams.get(key));

return params;
}

//封装响应-200
private FullHttpResponse responseOK(HttpResponseStatus status, ByteBuf content) {
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, content);
if(content != null) {
//加入内容信息
response.headers().set("Content-Type", "text/plain;charset=UTF-8");
response.headers().set("Content_Length", response.content().readableBytes());
}
return response;
}
}

创建Netty服务器

有了初始化以及定义了处理器,那么创建NettyServer即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@AllArgsConstructor
public class NettyHttpServer implements HttpServer {
private final int port;

public void run() {
//事件循环组,处理客户端连接
EventLoopGroup bossGroup = new NioEventLoopGroup();
//处理业务
EventLoopGroup workGroup = new NioEventLoopGroup();

try {
ServerBootstrap server = new ServerBootstrap();
server.group(bossGroup, workGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new NettyHttpServerInitializer());

//监听端口port,同步返回
ChannelFuture future = server.bind(this.port).sync();
System.out.println("The web server has been started at the address:http://localhost:"+ this.port);
//通道关闭时继续向后执行
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
workGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}

}
}

主函数

启动服务器,打开浏览器输入网址进行测试,或者用postman工具进行测试(推荐)

1
2
3
4
5
6
7
public class TestHttp {
public static void main(String[] args) {
NettyHttpServer server = new NettyHttpServer(9000);
server.run();
System.out.println("server close!");
}
}

下期预告:

  • 自定义封装request和response
  • 实现自定义编码器和解码器

手撕http服务器——引入Netty
https://love-enough.github.io/2024/07/11/手撕http服务器——引入Netty/
作者
GuoZihan
发布于
2024年7月11日
更新于
2024年7月12日
许可协议