前言
前面几篇文章用Netty搭建了不同传输协议的的消息接收发送服务并解决的粘包半包问题。
Netty-搭建一个简单的消息接收发送服务https://blog.youkuaiyun.com/yimint/article/details/132061057?spm=1001.2014.3001.5501Netty-搭建一个简单的Json协议消息接收发送服务
https://blog.youkuaiyun.com/yimint/article/details/132075254?spm=1001.2014.3001.5501Netty-搭建一个简单的Protobuf协议消息接收发送服务
https://blog.youkuaiyun.com/yimint/article/details/132145975?spm=1001.2014.3001.5501
这篇文章用Netty搭建一个简单的HTTP服务器。
一、Netty——HTTP
在这之前先了解Http协议和特点以及Netty对HTTP的封装,相关文章网上已经有非常详细的资料了,我推荐以下两篇文章:
Netty之Httphttps://zhuanlan.zhihu.com/p/135239211netty对http协议解析原理解析
https://cloud.tencent.com/developer/article/1035923
二、Netty搭建HTTP服务器
1.引入依赖
<dependencies>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.22</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.21.Final</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.20.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.3</version>
</dependency>
</dependencies>
2.服务端
public class EchoHttpServer {
private final static Logger LOGGER = LoggerFactory.getLogger(EchoHttpServer.class);
public static final String SERVER_IP = "127.0.0.1";
public static final int SERVER_PORT = 54123;
public static void main(String[] args) {
ServerBootstrap b = new ServerBootstrap();
NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
NioEventLoopGroup workGroup = new NioEventLoopGroup();
try {
b.group(bossGroup, workGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
//请求解码器和响应编码器,等价于下面两行
// pipeline.addLast(new HttpServerCodec());
//请求解码器
pipeline.addLast(new HttpRequestDecoder());
//响应编码器
pipeline.addLast(new HttpResponseEncoder());
// HttpObjectAggregator 将HTTP消息的多个部分合成一条完整的HTTP消息
// HttpObjectAggregator 用于解析Http完整请求
// 把多个消息转换为一个单一的完全FullHttpRequest或是FullHttpResponse,
// 原因是HTTP解码器会在解析每个HTTP消息中生成多个消息对象
// 如 HttpRequest/HttpResponse,HttpContent,LastHttpContent
pipeline.addLast(new HttpObjectAggregator(65535));
pipeline.addLast(new ChunkedWriteHandler());
// 自定义的业务handler
pipeline.addLast(new HttpEchoHandler());
}
});
Channel ch = b.bind(SERVER_PORT).sync().channel();
LOGGER.info("HTTP ECHO 服务已经启动 http://{}:{}/", "127.0.0.1", SERVER_PORT);
// 等待服务端监听到端口关闭
ch.closeFuture().sync();
} catch (Exception ex) {
LOGGER.error(ExceptionUtil.stacktraceToString(ex));
}finally {
workGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
}
public class HttpEchoHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
private final static Logger LOGGER = LoggerFactory.getLogger(HttpEchoHandler.class);
@Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
if (!request.decoderResult().isSuccess()) {//判断解码是否成功
HttpProtocolHelper.sendError(ctx, BAD_REQUEST);
return;
}
/**
* 缓存HTTP协议的版本号
*/
HttpProtocolHelper.cacheHttpProtocol(ctx, request);
Map<String, Object> echo = new HashMap<>();
// 1.获取URI
String uri = request.uri();
echo.put("request uri", uri);
LOGGER.info("request uri:{}", uri);
// 2.获取请求方法
HttpMethod method = request.method();
echo.put("request method", method.toString());
LOGGER.info("request method:{}", method);
// 3.获取请求头
Map<String, Object> echoHeaders = new HashMap<>();
HttpHeaders headers = request.headers();
for (Map.Entry<String, String> header : headers.entries()) {
echoHeaders.put(header.getKey(), header.getValue());
}
echo.put("request header", echoHeaders);
LOGGER.info("request header:{}", echoHeaders);
/**
* 获取uri请求参数
*/
Map<String, Object> uriDatas = paramsFromUri(request);
echo.put("paramsFromUri", uriDatas);
LOGGER.info("request paramsFromUri:{}", uriDatas);
// 处理POST请求
if (POST.equals(request.method())) {
/**
* 获取请求体数据到 map
*/
Map<String, Object> postData = dataFromPost(request);
echo.put("dataFromPost", postData);
LOGGER.info("request dataFromPost:{}", postData);
}
/**
* 回显内容转换成json字符串
*/
String sendContent = JSONUtil.toJsonStr(echo);
/**
* 发送回显内容到客户端
*/
HttpProtocolHelper.sendJsonContent(ctx, sendContent);
}
/*
* 从URI后面获取请求的参数
*/
private Map<String, Object> paramsFromUri(FullHttpRequest fullHttpRequest) {
Map<String, Object> params = new HashMap<String, Object>();
// 把URI后面的参数串,分割成key-value形式
QueryStringDecoder decoder = new QueryStringDecoder(fullHttpRequest.uri());
// 提取key-value形式的参数串
Map<String, List<String>> paramList = decoder.parameters();
//迭代key-value形式的参数串
for (Map.Entry<String, List<String>> entry : paramList.entrySet()) {
params.put(entry.getKey(), entry.getValue().get(0));
}
return params;
}
/*
* 获取POST方式传递的请求体数据
*/
private Map<String, Object> dataFromPost(FullHttpRequest fullHttpRequest) {
Map<String, Object> postData = null;
try {
String contentType = fullHttpRequest.headers().get("Content-Type").trim();
//普通form表单数据,非multipart形式表单
if (contentType.contains("application/x-www-form-urlencoded")) {
postData = formBodyDecode(fullHttpRequest);
}
//multipart形式表单
else if (contentType.contains("multipart/form-data")) {
postData = formBodyDecode(fullHttpRequest);
}
// 解析json数据
else if (contentType.contains("application/json")) {
postData = jsonBodyDecode(fullHttpRequest);
} else if (contentType.contains("text/plain")) {
ByteBuf content = fullHttpRequest.content();
byte[] reqContent = new byte[content.readableBytes()];
content.readBytes(reqContent);
String text = new String(reqContent, StandardCharsets.UTF_8);
postData = new HashMap<>();
postData.put("text", text);
}
return postData;
} catch (UnsupportedEncodingException e) {
return null;
}
}
/*
* 解析from表单数据
*/
private Map<String, Object> formBodyDecode(FullHttpRequest fullHttpRequest) {
Map<String, Object> params = new HashMap<String, Object>();
try {
HttpPostRequestDecoder decoder =
new HttpPostRequestDecoder(new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE),
fullHttpRequest,
CharsetUtil.UTF_8);
List<InterfaceHttpData> postData = decoder.getBodyHttpDatas();
if (postData == null || postData.isEmpty()) {
decoder = new HttpPostRequestDecoder(fullHttpRequest);
if (fullHttpRequest.content().isReadable()) {
String json = fullHttpRequest.content().toString(CharsetUtil.UTF_8);
params.put("body", json);
}
}
for (InterfaceHttpData data : postData) {
if (data.getHttpDataType() == InterfaceHttpData.HttpDataType.Attribute) {
MixedAttribute attribute = (MixedAttribute) data;
params.put(attribute.getName(), attribute.getValue());
} else if (data.getHttpDataType() == InterfaceHttpData.HttpDataType.FileUpload) {
MixedFileUpload fileUpload = (MixedFileUpload) data;
String filename = fileUpload.getFilename();
byte[] content = fileUpload.get();
String contentType = fileUpload.getContentType();
params.put("filename", filename);
params.put("contentType", contentType);
params.put("content", Arrays.toString(content));
fileUpload.release();
}
}
} catch (IOException e) {
e.printStackTrace();
}
return params;
}
/*
* 解析json数据(Content-Type = application/json)
*/
private Map<String, Object> jsonBodyDecode(FullHttpRequest fullHttpRequest) throws UnsupportedEncodingException {
ByteBuf content = fullHttpRequest.content();
byte[] reqContent = new byte[content.readableBytes()];
if (reqContent.length == 0) {
return null;
}
Map<String, Object> params = new HashMap<String, Object>();
content.readBytes(reqContent);
String strContent = new String(reqContent, StandardCharsets.UTF_8);
JSONObject jsonParams = JSONUtil.parseObj(strContent);
for (Object key : jsonParams.keySet()) {
params.put(key.toString(), jsonParams.get(key));
}
return params;
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
if (ctx.channel().isActive()) {
HttpProtocolHelper.sendError(ctx, INTERNAL_SERVER_ERROR);
}
}
}
三、请求测试
启动HTTP服务器
浏览器GET请求服务
postman POST请求服务
总结
至此,一个简单的基于Netty的HTTP服务器就搭完了,HTTP相关知识点还是很重要的,为后续基于Netty搭建一个轻量级的WEB框架打下基础。