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 ...
随机推荐
- NVIDIA Maxine Video Effects SDK 編程指南 - 实践小记
NVIDIA Maxine Video Effects SDK 編程指南 - 实践小记 本篇博客重点只说Video Effect的部分,此外还有Audio Effect的部分.还有AR部分,不在本篇范 ...
- MySQL中都有哪些锁?
MySQL中都有哪些锁 为什么需要锁 在计算机系统中,锁(Lock)是一种同步机制,用于控制对共享资源的访问.它确保在任何给定时间内只有一个线程能够访问受保护的共享资源,从而避免了由并发访问导致的数据 ...
- [MAUI]弧形进度条与弧形滑块的交互实现
@ 目录 弧形基类 定义 绘制弧 弧形进度条(ProgressBar) 添加动画 宽度补偿 文本 弧形滑块(Slider) 创建控制柄 拖动事件处理 项目地址 进度条(ProgressBar)用于展示 ...
- Shodan使用指南
Shodan是用于搜索连接到互联网的设备的工具.与搜索引擎可以帮助你找到网站不同,Shodan可以帮助你找到有关台式机,服务器,IoT设备等的信息.此信息包括元数据,例如在每个设备上运行的软件. Sh ...
- 瞬间抠图!揭秘 ZEGO 绿幕抠图算法背后的技术
抠图是图像处理中最常见的操作之一,指的是将图像中需要的部分从画面中精确的提取出来. 抠图的主要功能是为了后期的合成做准备.在 Photoshop 中,抠图的方法有很多种,最常见的有通道抠图.蒙版抠图. ...
- 【EF Core】主从实体关系与常见实体关系的区别
上次老周扯了有关主.从实体的话题,本篇咱们再挖一下,主.从实体之间建立的关系,跟咱们常用的一对一.一对多这些关系之间有什么不同. 先看看咱们从学习数据库开始就特熟悉的常用关系--多对多.一对一.一对多 ...
- C#解析匿名JSON数据
C#解析匿名JSON数据 使用工具:Newtonsoft.Json 使用方式: //模拟数据 var jsonData = JsonConvert.SerializeObject(new { Name ...
- 简单了解:Linux 硬盘分区管理命令 fdisk、parted、sfdisk、sgdisk
支持分区格式 详情链接 fdisk MBR https://zhuanlan.zhihu.com/p/56273534 parted MBR.GPT https://www.cnblogs.com/m ...
- 你真的知道吗?catch、finally和return哪个先执行
我的一位朋友前阵子遇到一个问题,问题的核心就是try--catch--finally中catch和finally代码块到底哪个先执.这个问题看起来很简单,当然是"catch先执行.final ...
- 图解算法,原理逐步揭开「GitHub 热点速览」
想必每个面过大厂的小伙伴都被考过算法,那么有没有更快了解算法的方式呢?这是一个老项目,hello-algo 用图解的方式让你了解运行原理.此外,SQL 闯关自学项目也是一个让你能好好掌握 SQL 技术 ...