Netty集成HTTP的GET和POST通讯
核心就是ChannelInitializer的实现使用http 消息解码器
package com.coremain.handler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
/**
* @description: Netty服务端回调处理 HTTP
* @author: GuoTong
* @createTime: 2023-05-16 22:05
* @since JDK 1.8 OR 11
**/
public class NettyServerHTTPHandler extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) {
socketChannel.pipeline()
// http 消息解码器
.addLast(new HttpServerCodec())
// http 消息合并(2048 为消息最大长度)
.addLast(new HttpObjectAggregator(2048))
// 自定义消息处理业务类
.addLast(new HttpMessageHandler());
// 此处还可以进行添加其他处理(例如ssl)等
}
}
核心2就是ChannelInboundHandlerAdapter的实现
package com.coremain.handler;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.coremain.netty.init.ServiceInit;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.QueryStringDecoder;
import io.netty.handler.codec.http.multipart.DefaultHttpDataFactory;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
import io.netty.handler.codec.http.multipart.InterfaceHttpData;
import io.netty.handler.codec.http.multipart.MemoryAttribute;
import io.netty.util.CharsetUtil;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @description: HTTP消息处理器: 通道入站处理适配器。
* 整个的IO处理操作环节包括:
* 从通道读数据包、数据包解码、业务处理、目标数据编码、把数据包写到通道,然后由通道发送到对端,
* 入站处理,触发的方向为:自底向上,Netty的内部(如通道)到 ChannelInboundHandler入站处理器。
* 出站处理,触发的方向为:自顶向下,从ChannelOutboundHandler出站处理器 到Netty的内部(如通道)。
* 按照这种方向来分,前面数据包解码、业务处理两个环节——属于入站处理器 的工作;
* 后面目标数据编码、把数据包写到通道中两个环节——属于出站处理器的 工作
* @author: GuoTong
* @createTime: 2023-05-16 22:06
* @since JDK 1.8 OR 11
**/
@SuppressWarnings("all")
public class HttpMessageHandler extends ChannelInboundHandlerAdapter {
private static final String CONTENT_TYPE = "Content-Type";
private static final String CONTENT_LENGTH = "Content-Length";
private static final String CONNECTION = "Connection";
private static final String KEEP_ALIVE = "keep-alive";
private static final String FORM_CONTENT_TYPE = "application/x-www-form-urlencoded";
private static final String JSON_CONTENT_TYPE = "application/json";
private static final String MULTIPART_CONTENT_TYPE = "multipart/form-data";
private static final String TEXT_CONTENT_TYPE = "text/xml";
/**
* Description: 当通道注册完成后,Netty会调用fireChannelRegistered, 触发通道注册事件。
* 通道会启动该入站操作的流水线处理,在通道注册过的入站处理器Handler的channelRegistered方法,会被调用到
*
* @param ctx
* @author: GuoTong
* @date: 2023-05-17 19:53:18
* @return:void
*/
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
super.channelRegistered(ctx);
}
/**
* Description: 通道未注册
*
* @param ctx
* @author: GuoTong
* @date: 2023-05-17 19:55:48
* @return:void
*/
@Override
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
super.channelUnregistered(ctx);
}
/**
* Description: 当通道激活完成后,Netty会调用fireChannelActive, 触发通道激活事件。通道会启动该入站操作的流水线处理,
* 在通道注册过的入站处理器Handler的channelActive方法,会被调用到。
*
* @param ctx
* @author: GuoTong
* @date: 2023-05-17 19:56:37
* @return:void
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx);
}
/**
* Description: 当连接被断开或者不可用,Netty会调用fireChannelInactive, 触发连接不可用事 件。通道会启动对应的流水线处理,
* 在通道注册过的入站处理器Handler的channelInactive方法,会被调用到。
*
* @param ctx
* @author: GuoTong
* @date: 2023-05-17 19:57:06
* @return:void
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
super.channelInactive(ctx);
}
/**
* Description: 当通道缓冲区读完,Netty会调用fireChannelReadComplete, 触发通道读完事 件。通道会启动该入站操作的流水线处理
* ,在通道注册过的入站处理器Handler的channelReadComplete方法,会被调用到。
*
* @param ctx
* @author: GuoTong
* @date: 2023-05-17 19:57:31
* @return:void
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}
/**
* Description: 当通道处理过程发生异常时,Netty会调用fireExceptionCaught,触发异常捕获事件。通道会启动异常捕获的流水线处理,
* 在通道注册过的处理器Handler的 exceptionCaught方法,会被调用到。
* 注意,这个方法是在通道处理器中ChannelHandler定义的方法,入站处理器、出站处理器接口都继承到了该方法。
*
* @param ctx
* @param cause
* @author: GuoTong
* @date: 2023-05-17 19:58:31
* @return:void
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
/**
* Description: 当通道缓冲区可读,Netty会调用fireChannelRead, 触发通道可读事件。通道会启动该入站操作的流水线处理,
* 在通道注册过的入站处理器Handler的channelRead方法,会被调用到。
*
* @param ctx
* @param msg
* @author: GuoTong
* @date: 2023-05-17 19:58:09
* @return:void
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
// 仅处理http请求
if (msg instanceof FullHttpRequest) {
//客户端的请求对象
FullHttpRequest req = (FullHttpRequest) msg;
//新建一个返回消息的Json对象
String responseJson = "";
// 获取请求方式
HttpMethod method = req.method();
// 判断请求方式是GET还是POST
boolean isPost = method.equals(HttpMethod.POST);
//把客户端的请求数据格式化为Json对象
JSONObject requestJson = null;
try {
if (isPost) {
requestJson = getPostRequestParams(req);
} else {
requestJson = getUrlRequestParams(req);
}
} catch (Exception e) {
ResponseJson(ctx, req, "参数解析失败:" + e.getMessage());
return;
}
// 此处不进行请求方式区分,分局请求url 进行区分(url格式:/类标识/方法标识)
// 维护两个map, a: Map<类标识, Class(类对应的class)> b: Map<方法名, Class(方法参数对应的class)>
//获取客户端的URL
String uri = req.getUri();
String[] uris;
if (uri.contains("?")) {
uris = uri.substring(1, uri.indexOf("?")).split("/");
} else {
uris = uri.substring(1).split("/");
}
if (uris.length == 3) {
try {
Object result = dealService(requestJson, uris, isPost);
if (result instanceof String) {
responseJson = (String) result;
} else {
responseJson = JSON.toJSONString(result);
}
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
ResponseJson(ctx, req, "业务调用异常" + e.getMessage());
} catch (Exception e) {
ResponseJson(ctx, req, "业务调用异常" + e.getMessage());
}
} else {
ResponseJson(ctx, req, "访问路径不合法");
}
//向客户端发送结果
ResponseJson(ctx, req, responseJson);
}
}
/**
* 处理真正业务
*
* @param requestJson 请求参数
* @param uris ([项目标识,类标识,方法标识])
* @return
*/
private Object dealService(JSONObject requestJson, String[] uris, boolean isPost) throws Exception {
// 获取项目名
String project_identity = uris[0];
// 获取类标识
String class_identity = uris[1];
// 获取方法标识
String method_identity = uris[2];
// 获取类class 以及 方法参数class
Map<Class, Map<String, Class>> classClassMap = ServiceInit.CLASS_MAPPING.get(class_identity).get(method_identity);
List<Map.Entry<Class, Map<String, Class>>> collect = classClassMap.entrySet().stream().collect(Collectors.toList());
Map.Entry<Class, Map<String, Class>> classEntry = collect.get(0);
// 业务类class
Class serviceClass = classEntry.getKey();
// 参数class
Map<String, Class> paramClasses = classEntry.getValue();
// 封装参数的值
Object[] params = new Object[paramClasses.size()];
// 反射
Class[] classes = new Class[params.length];
if (isPost && paramClasses.size() == 1) {
Class c = paramClasses.entrySet().iterator().next().getValue();
params[0] = JSON.parseObject(requestJson.toJSONString(), c);
classes[0] = c;
} else {
Object[] webSendParams = requestJson.values().toArray();
if (paramClasses.size() != webSendParams.length) {
throw new Exception("参数不匹配");
}
// 获取参数类型,解析拼装参数的值
int i = 0;
for (Map.Entry<String, Class> entry : paramClasses.entrySet()) {
String key = entry.getKey();
Class pClass = entry.getValue();
// 以此拼接传入参数
Object value = webSendParams[i];
// 判断参数类型,强转
if (pClass.equals(String.class)) {
params[i] = (String) value;
} else {
// 默认不是String就是用Integer接收
params[i] = Integer.parseInt(value.toString());
}
classes[i] = pClass;
i++;
}
}
// 业务类对象
Object service = serviceClass.getDeclaredConstructor().newInstance();
// 根据方法标识反射调用该方法
Method method = serviceClass.getDeclaredMethod(method_identity, classes);
// 这样设置完之后,在外部也是可以访问private的。
method.setAccessible(true);
Object result = method.invoke(service, params);
// 返回结果
return result;
}
/**
* 响应HTTP的请求
*
* @param ctx
* @param req
* @param jsonStr
*/
private void ResponseJson(ChannelHandlerContext ctx, FullHttpRequest req, String jsonStr) {
byte[] jsonByteByte = (jsonStr + "\r\n").getBytes();
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.wrappedBuffer(jsonByteByte));
String contentType = req.headers().get(CONTENT_TYPE);
switch (contentType) {
case FORM_CONTENT_TYPE:
// 最常见的 POST 提交数据的方式了。浏览器的原生 表单
response.headers().set(CONTENT_TYPE, FORM_CONTENT_TYPE);
break;
case JSON_CONTENT_TYPE:
// 告诉服务端消息主体是序列化后的 JSON 字符串
response.headers().set(CONTENT_TYPE, JSON_CONTENT_TYPE);
break;
case MULTIPART_CONTENT_TYPE:
// 常见的 POST 数据提交的方式。我们使用表单上传文件时,必须让 表单的 enctype 等于 multipart/form-data。
response.headers().set(CONTENT_TYPE, MULTIPART_CONTENT_TYPE);
break;
case TEXT_CONTENT_TYPE:
// 它是一种使用 HTTP 作为传输协议,XML 作为编码方式的远程调用规范
response.headers().set(CONTENT_TYPE, TEXT_CONTENT_TYPE);
break;
default:
response.headers().set(CONTENT_TYPE, "text/json");
}
response.headers().setInt(CONTENT_LENGTH, response.content().readableBytes());
response.headers().set(CONNECTION, KEEP_ALIVE);
ctx.write(response);
ctx.flush();
ctx.close();
}
/**
* 获取post请求的内容
*
* @param request
* @return
*/
private JSONObject getPostRequestParams(FullHttpRequest request) {
// 获取请求的 Content-Type 类型
String contentType = request.headers().get(CONTENT_TYPE);
// 获取报文信息
ByteBuf jsonBuf = request.content();
String jsonStr = jsonBuf.toString(CharsetUtil.UTF_8);
JSONObject json = JSONObject.of();
switch (contentType) {
case FORM_CONTENT_TYPE:
// 最常见的 POST 提交数据的方式了。浏览器的原生 表单
String[] keyvalues = jsonStr.split("&");
for (int i = 0; i < keyvalues.length; i++) {
// 放入键值对
json.put(keyvalues[i], keyvalues[i + 1]);
// 指针前进一格
i++;
}
break;
case JSON_CONTENT_TYPE:
// 告诉服务端消息主体是序列化后的 JSON 字符串
json = JSON.parseObject(jsonStr);
break;
case MULTIPART_CONTENT_TYPE:
// 常见的 POST 数据提交的方式。我们使用表单上传文件时,必须让 表单的 enctype 等于 multipart/form-data。
Map<String, String> form = getFormRequestParams(request);
json = (JSONObject) JSON.toJSON(form);
break;
case TEXT_CONTENT_TYPE:
// 它是一种使用 HTTP 作为传输协议,XML 作为编码方式的远程调用规范
break;
}
return json;
}
/**
* 获取url参数
*
* @param request
* @return
*/
private JSONObject getUrlRequestParams(FullHttpRequest request) {
QueryStringDecoder decoder = new QueryStringDecoder(request.getUri());
Map<String, List<String>> parameters = decoder.parameters();
JSONObject jsonObject = new JSONObject();
Iterator<Map.Entry<String, List<String>>> iterator = parameters.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, List<String>> next = iterator.next();
List<String> value = next.getValue();
jsonObject.put(next.getKey(), value.size() > 1 ? value : next.getValue().get(0));
}
return jsonObject;
}
/**
* 获取表单参数
*
* @param request
* @return
*/
private Map<String, String> getFormRequestParams(FullHttpRequest request) {
HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(new DefaultHttpDataFactory(false), request);
List<InterfaceHttpData> httpPostData = decoder.getBodyHttpDatas();
Map<String, String> params = new HashMap<>();
for (InterfaceHttpData data : httpPostData) {
if (data.getHttpDataType() == InterfaceHttpData.HttpDataType.Attribute) {
MemoryAttribute attribute = (MemoryAttribute) data;
params.put(attribute.getName(), attribute.getValue());
}
}
return params;
}
}
Netty集成HTTP的GET和POST通讯的更多相关文章
- netty 集成 wss 安全链接
netty集成ssl完整参考指南(含完整源码) 虽然我们在内部rpc通信中使用的是基于认证和报文头加密的方式实现安全性,但是有些时候仍然需要使用SSL加密,可能是因为对接的三方系统需要,也可能是由于o ...
- Netty实现服务端客户端长连接通讯及心跳检测
通过netty实现服务端与客户端的长连接通讯,及心跳检测. 基本思路:netty服务端通过一个Map保存所有连接上来的客户端SocketChannel,客户端的Id作为Map的key.每 ...
- 即时通信系统中实现全局系统通知,并与Web后台集成【附C#开源即时通讯系统(支持广域网)——QQ高仿版IM最新源码】
像QQ这样的即时通信软件,时不时就会从桌面的右下角弹出一个小窗口,或是显示一个广告.或是一个新闻.或是一个公告等.在这里,我们将其统称为“全局系统通知”.很多使用C#开源即时通讯系统——GGTalk的 ...
- netty集成ssl完整参考指南(含完整源码)
虽然我们在内部rpc通信中使用的是基于认证和报文头加密的方式实现安全性,但是有些时候仍然需要使用SSL加密,可能是因为对接的三方系统需要,也可能是由于open的考虑.中午特地测了下netty下集成ss ...
- netty集成springboot
一 前言 springboot 如何集成netty实现mapper调用不为null的问题让好多读者都头疼过,知识追寻者发了一点时间做了个基本入门集成应用给读者们指明条正确的集成方式,我相信,只要你有n ...
- 3、netty第二个例子,使用netty建立客户端,与服务端通讯
第一个例子中,建立了http的服务器端,可以直接使用curl命令,或者浏览器直接访问. 在第二个例子中,建立一个netty的客户端来主动发送请求,模拟浏览器发送请求. 这里先启动服务端,再启动客户端, ...
- Netty集成Protobuf
一.创建Personproto.proto 创建Personproto.proto文件 syntax = "proto2"; package com.example.protobu ...
- NetCore Netty 框架 BT.Netty.RPC 系列随讲 二 WHO AM I 之 NETTY/NETTY 与 网络通讯 IO 模型之关系?
一:NETTY 是什么? Netty 是什么? 这个问题其实百度上一搜一堆. 这是官方话的描述:Netty 是一个基于NIO的客户.服务器端编程框架,使用Netty 可以确保你快速和简单的开发出一个 ...
- 《连载 | 物联网框架ServerSuperIO教程》1.4种通讯模式机制。附小文:招.NET开发,结果他转JAVA了,一切都是为了生活
参考文章: 1.SuperIO通讯框架介绍,含通信本质 2.C#跨平台物联网通讯框架ServerSuperIO(SSIO) 一.感慨 上大学的时候,没有学过C#,花了5块钱在地坛书市买了一本教程,也就 ...
- 通俗地讲,Netty 能做什么?
https://www.zhihu.com/question/24322387/answer/78947405 作者:郭无心链接:https://www.zhihu.com/question/2432 ...
随机推荐
- Centos7安装配置Hive
Centos7安装配置 一 . 安装 安装就不多做详述,选择好自己的镜像设置好路径即可 二 .配置 2.1 网络配置 桌面右键进入 cmd 命令编辑窗口,在 Linux 中设置网络的相关配置都需要管理 ...
- 数位DP?记忆化罢了!
我看了半天的数位 DP,DP 没学会,人倒是麻了. 解决什么 一般用于求解给你一个区间 \([l,r]\),问你其中满足条件的数有多少个. 这种题目还是蛮常见的,我们一般情况下暴力只能拿一少部分分,之 ...
- ASP.NET Core 6框架揭秘实例演示[40]:基于角色的授权
ASP.NET应用并没有对如何定义授权策略做硬性规定,所以我们完全根据用户具有的任意特性(如性别.年龄.学历.所在地区.宗教信仰.政治面貌等)来判断其是否具有获取目标资源或者执行目标操作的权限,但是针 ...
- Python运维开发之路《WEB框架:Django》
一.Web框架的本质 所有的web框架.web请求:本质上都是:socket 浏览器:socket客户端 服务器:socket服务端 1. socket服务端 import socket def ha ...
- 记一次 .NET 某游戏服务后端 内存暴涨分析
一:背景 1. 讲故事 前几天有位朋友找到我,说他们公司的后端服务内存暴涨,而且CPU的一个核也被打满,让我帮忙看下怎么回事,一般来说内存暴涨的问题都比较好解决,就让朋友抓一个 dump 丢过来,接下 ...
- 深度学习(五)——DatadLoader的使用
一.DataLoader简介 官网地址: torch.utils.data - PyTorch 2.0 documentation 1. DataLoder类 class torch.utils.da ...
- C++笔记(2)——函数
六. 函数 6.1 函数基础 一个典型的函数(function)定义包括:返回类型(return type).函数名字,由0或多个形参(parameter)组成的列表以及函数体.我们通过调用运算符来执 ...
- 数据标注工具 Label-Studio
文档抽取任务Label Studio使用指南 目录 1. 安装 2. 文档抽取任务标注 2.1 项目创建 2.2 数据上传 2.3 标签构建 2.4 任务标注 2.5 数据导出 2.6 数据转换 2. ...
- Unity UGUI的RectMask2D(2D遮罩)组件的介绍及使用
Unity UGUI的RectMask2D(2D遮罩)组件的介绍及使用 1. 什么是RectMask2D组件? RectMask2D是Unity UGUI中的一个组件,用于实现2D遮罩效果.它可以限制 ...
- JDK源码-StringJoiner源码分析
背景 功能描述:将多个元素使用指定符号前后连接为字符串:eg:1 2 3 4 5 , => 1,2,3,4,5 要点: 多个元素 指定分隔符 分隔符只在元素之间,不能作为第一或最后一个 使用方法 ...