From 36c31ea4c77a95a61f73a18d4538893df8241129 Mon Sep 17 00:00:00 2001 From: hliang Date: Mon, 16 Jun 2025 15:47:23 +0800 Subject: [PATCH 1/7] Update README.md --- README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/README.md b/README.md index 91fe191..2f346b6 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,24 @@ + + +
+ +#### 求内推 · base 深圳 爬虫 +**联系方式**: +邮箱:z@hl98.cn +微信:hl98_cn + +最近在看机会 +如有深圳爬虫相关岗位机会,请联系! + +
+ +--- + +### 支持作者 +如果觉得我的开源项目有帮助,欢迎扫码支持: + +image + ```dart __ _______..______ .______ ______ From dfac6d33ac988975585b3805030bf9f7a2bfd93b Mon Sep 17 00:00:00 2001 From: hliang Date: Mon, 16 Jun 2025 15:59:22 +0800 Subject: [PATCH 2/7] Update README.md --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2f346b6..625992b 100644 --- a/README.md +++ b/README.md @@ -280,11 +280,16 @@ http://127.0.0.1:12080/go?group=zzz&action=hello&clientId=hliang1713564563459 https://mp.weixin.qq.com/s/nvQNV33QkzFQtFscDqnXWw ## 常见问题 - 1. websocket连接失败 内容安全策略(Content Security Policy) Refused to connect to 'xx.xx' because it violates the following Content Security Policy directive: "connect-src 'self' - 这个网站不让连接websocket,可以用油猴注入使用,或者更改网页响应头 + 这个网站不让连接websocket + ------------------------------------------------- + 最简单的方式-使用插件绕过,可谷歌搜索 Disable Content Security Policy + 推荐:https://chromewebstore.google.com/detail/disable-content-security/eckgajjlhojckchohogcblfjhpfdmoge + ------------------------------------------------- + 或者可以用油猴注入使用,或更改网页响应头 + 2. 异步操作获取值 [参考](https://github.com/jxhczhl/JsRpc/issues/12) From 6754509d4bc45bc4693c824ba718b713effde12b Mon Sep 17 00:00:00 2001 From: hliang Date: Thu, 24 Jul 2025 12:43:55 +0800 Subject: [PATCH 3/7] Update README.md ^.^ --- README.md | 76 +++++++++++++++++++++++++------------------------------ 1 file changed, 34 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 625992b..da83eb9 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,3 @@ - - -
- -#### 求内推 · base 深圳 爬虫 -**联系方式**: -邮箱:z@hl98.cn -微信:hl98_cn - -最近在看机会 -如有深圳爬虫相关岗位机会,请联系! - -
- ---- - -### 支持作者 -如果觉得我的开源项目有帮助,欢迎扫码支持: - -image - ```dart __ _______..______ .______ ______ @@ -34,27 +13,37 @@ -- js逆向之远程调用(rpc)免去抠代码补环境 > 黑脸怪 - - * [目录结构](#目录结构) - * [基本介绍](#基本介绍) - * [实现](#实现) - * [食用方法](#食用方法) - * [打开编译好的文件,开启服务(releases下载)](#打开编译好的文件开启服务releases下载) - * [注入JS,构建通信环境(/resouces/JsEnv_De.js)](#注入js构建通信环境resoucesjsenv_dejs) - * [连接通信](#连接通信) - * [I 远程调用0:](#i-远程调用0) - * [接口传js代码让浏览器执行](#接口传js代码让浏览器执行) - * [Ⅱ 远程调用1: 浏览器预先注册js方法 传递函数名调用](#ⅱ-远程调用1-浏览器预先注册js方法-传递函数名调用) - * [远程调用1:无参获取值](#远程调用1无参获取值) - * [远程调用2:带参获取值](#远程调用2带参获取值) - * [远程调用3:带多个参获 并且使用post方式 取值](#远程调用3带多个参获-并且使用post方式-取值) - * [食用案例-爬虫练手-xx网第15题](#食用案例-爬虫练手-xx网第15题) - * [其他说明](#其他说明) - * [BUG修复](#bug修复) - * [其他案例](#其他案例) - * [常见问题](#常见问题) - * [TODO](#todo) - +- [交流平台](#交流平台) +- [目录结构](#目录结构) +- [基本介绍](#基本介绍) +- [实现](#实现) +- [食用方法](#食用方法) + - [打开编译好的文件,开启服务(releases下载)](#打开编译好的文件开启服务releases下载) + - [注入JS,构建通信环境(/resouces/JsEnv\_De.js)](#注入js构建通信环境resoucesjsenv_dejs) + - [连接通信](#连接通信) + - [I 远程调用0:](#i-远程调用0) + - [接口传js代码让浏览器执行](#接口传js代码让浏览器执行) + - [Ⅱ 远程调用1: 浏览器预先注册js方法 传递函数名调用](#ⅱ-远程调用1-浏览器预先注册js方法-传递函数名调用) + - [远程调用1:无参获取值](#远程调用1无参获取值) + - [远程调用2:带参获取值](#远程调用2带参获取值) + - [远程调用3:带多个参获 并且使用post方式 取值](#远程调用3带多个参获-并且使用post方式-取值) + - [远程调用4:获取页面基础信息](#远程调用4获取页面基础信息) +- [食用案例-爬虫练手-xx网第15题](#食用案例-爬虫练手-xx网第15题) +- [其他说明](#其他说明) +- [BUG修复](#bug修复) +- [其他案例](#其他案例) +- [常见问题](#常见问题) +- [TODO](#todo) + + +## 交流平台 + +为方便广大开发者更好地了解和使用 jsprc,我与猿人学平哥合作,我们共同建立了一个微信交流群。 扫码并备注 “jsrpc” 即可申请加入。 +在这个交流群中,我们会在群中优先分享工具的使用方法、实践案例,以及后续功能的更新与版本维护信息。 +同时,平哥也邀请了多位长期关注 jsrpc 的技术创作者加入,共同参与内容交流、经验分享以及共同对项目生态进行维护。 +欢迎感兴趣的朋友加入群聊,共建一个开放、实用的 jsrpc 工具交流社区。 + +王平 ## 目录结构 @@ -307,3 +296,6 @@ demo.regAction('token', async (resolve) => { ``` - [ ] ssl Docker Deploy - [ ] K8s Deploy + + +image From 0a8d4ec934b79b74c0d98c2be47793b7459c5837 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E6=B6=9B=E8=BE=89?= <1841397014@qq.com> Date: Tue, 29 Jul 2025 17:46:13 +0800 Subject: [PATCH 4/7] Update README.md chen-update --- README.md | 120 +++++++++++++----------------------------------------- 1 file changed, 28 insertions(+), 92 deletions(-) diff --git a/README.md b/README.md index da83eb9..446370e 100644 --- a/README.md +++ b/README.md @@ -9,11 +9,34 @@ ``` +## 交流平台 + +为了建立一个更好的 jsrpc 交流平台,我们与猿人学的平哥共同创建了一个微信交流群。 +希望借此机会,帮助广大开发者更深入地了解和使用 jsrpc。 + +### 加入方式 +请使用微信扫描以下二维码并备注“jsrpc”以申请加入我们的交流群。 + +### 交流群的内容 +在这个交流群里,我们将: +- 及时分享 jsrpc 的使用方法和实践案例。 +- 发布工具的后续功能更新和版本维护信息。 +- 邀请多位长期关注 jsrpc 的技术创作者共同参与内容交流和经验分享。 + +### 特别资源 +群内将提供更多的jsrpc 实操案例 、使用方法 及常见问题 汇总等资料, + +欢迎感兴趣的朋友踊跃加入,共同构建一个开放、实用的jsrpc工具交流社区。 + +王平 + +  +  +--- -- js逆向之远程调用(rpc)免去抠代码补环境 -> 黑脸怪 -- [交流平台](#交流平台) +> 作者-黑脸怪 - [目录结构](#目录结构) - [基本介绍](#基本介绍) - [实现](#实现) @@ -28,22 +51,8 @@ - [远程调用2:带参获取值](#远程调用2带参获取值) - [远程调用3:带多个参获 并且使用post方式 取值](#远程调用3带多个参获-并且使用post方式-取值) - [远程调用4:获取页面基础信息](#远程调用4获取页面基础信息) -- [食用案例-爬虫练手-xx网第15题](#食用案例-爬虫练手-xx网第15题) -- [其他说明](#其他说明) -- [BUG修复](#bug修复) -- [其他案例](#其他案例) -- [常见问题](#常见问题) -- [TODO](#todo) -## 交流平台 - -为方便广大开发者更好地了解和使用 jsprc,我与猿人学平哥合作,我们共同建立了一个微信交流群。 扫码并备注 “jsrpc” 即可申请加入。 -在这个交流群中,我们会在群中优先分享工具的使用方法、实践案例,以及后续功能的更新与版本维护信息。 -同时,平哥也邀请了多位长期关注 jsrpc 的技术创作者加入,共同参与内容交流、经验分享以及共同对项目生态进行维护。 -欢迎感兴趣的朋友加入群聊,共建一个开放、实用的 jsrpc 工具交流社区。 - -王平 ## 目录结构 @@ -219,83 +228,10 @@ resp = requests.get("http://127.0.0.1:12080/page/cookie?group=zzz") # 直接 list接口可查看当前注入的客户端信息 image -## 食用案例-爬虫练手-xx网第15题 - - 本题解是把它ajax获取数据那一个函数都复制下来,然后控制台调用这样子~ - - 1.f12查看请求,跟进去 找到ajax那块,可以看到call函数就是主要的ajax发包 输入页数就可以,那我们复制这个函数里面的代码备用 - -![image](https://user-images.githubusercontent.com/41224971/134793093-bac742e9-2f66-4fe4-b98b-7769d7379350.png) - - 2.先在控制台粘贴我的js环境,再注入一个rpc链接 注册一个call方法,名字自定义 第二个参数粘贴上面call的代码,小小修改一下 - 先定义num=param 这样就传参进来了,再定义一个变量来保存获取到的数据,resolve(变量) 就是发送。完了就注入好了,可以把f12关掉了 - -![image](https://user-images.githubusercontent.com/41224971/134795740-c62fce0d-7271-4b34-a9e5-07515b99ab81.png) +--- +##### 作者是个人开发者,开发和写文档工作量繁重。 - 3.调用接口就完事了,param就是传参页数 - -![image](https://user-images.githubusercontent.com/41224971/134799668-3dd385e7-f44c-4fb3-85ff-00d78c674865.png) - - 控制台可以关,但是注入的网页不要关哦 - -## 其他说明 -如果需要更改rpc服务的一些配置 比如端口号啊,https/wss服务,打印日志等 -可以在执行文件的同路径 下载[config.yaml]([链接地址](https://github.com/jxhczhl/JsRpc/blob/main/config.yaml))文件配置 -或使用-c参数指定配置文件路径 -./JsRpc.exe -c config1.yaml -![image](https://github.com/jxhczhl/JsRpc/assets/41224971/ad023b16-65b5-418e-8494-e988bb02fb12) - -group说明 -一般配置group名字不一样分开调用就行 -特别情况,可以一样的group名,比如3个客户端(标签演示)执行加密,程序会随机一个客户端来执行并返回。 -![image](https://github.com/jxhczhl/JsRpc/assets/41224971/6c111aea-1550-4683-a0c2-ed3c7e232d5a) -请确保action也都是一样 -![image](https://github.com/jxhczhl/JsRpc/assets/41224971/f6e0d713-6f5f-4d7d-b5e9-d7eb2d8316ae) -多个group除了随机 还可以根据clientId指定客户端执行 -http://127.0.0.1:12080/go?group=zzz&action=hello -http://127.0.0.1:12080/go?group=zzz&action=hello&clientId=hliang1713564563459 可选 - -## BUG修复 - -1.修复ResultSet函数,在并发处理环境下存在数据丢失,响应延迟等问题。 - -[//]: # (2.handlerRequest处理POST携带部分param参数调用存在JSON反序列化错误,可以使用JsEnv_Dev.js去处理) - -## 其他案例 - - 1. JsRpc实战-猿人学-反混淆刷题平台第20题(wasm) - https://mp.weixin.qq.com/s/DemSz2NRkYt9YL5fSUiMDQ - 2. 网洛者-反反爬练习平台第七题(JSVMPZL - 初体验) - https://mp.weixin.qq.com/s/nvQNV33QkzFQtFscDqnXWw - -## 常见问题 - 1. websocket连接失败 - 内容安全策略(Content Security Policy) - Refused to connect to 'xx.xx' because it violates the following Content Security Policy directive: "connect-src 'self' - 这个网站不让连接websocket - ------------------------------------------------- - 最简单的方式-使用插件绕过,可谷歌搜索 Disable Content Security Policy - 推荐:https://chromewebstore.google.com/detail/disable-content-security/eckgajjlhojckchohogcblfjhpfdmoge - ------------------------------------------------- - 或者可以用油猴注入使用,或更改网页响应头 - - 2. 异步操作获取值 - [参考](https://github.com/jxhczhl/JsRpc/issues/12) - - -## TODO - -- [ ] 异步方法调用 -```js -demo.regAction('token', async (resolve) => { - let token = await grecaptcha.execute(0, { action: '' }).then(function (token) { - return token - }); - resolve(token); -}) -``` -- [ ] ssl Docker Deploy -- [ ] K8s Deploy +##### 如果本项目对您有所帮助,不妨打赏一下 :) image From ff7d2327652a02517b44efbf275c60d59369e49a Mon Sep 17 00:00:00 2001 From: hliang <1820862012@qq.com> Date: Thu, 30 Oct 2025 22:54:04 +0800 Subject: [PATCH 5/7] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=89=93=E5=8D=B0?= =?UTF-8?q?=E6=B5=8F=E8=A7=88=E5=99=A8=E7=AB=AF=E7=9A=84IP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/config.go | 4 ++-- core/api.go | 22 +++++++++++++--------- core/engine.go | 9 +++++---- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/config/config.go b/config/config.go index 85d71e0..5f0d0c6 100644 --- a/config/config.go +++ b/config/config.go @@ -21,8 +21,8 @@ func ReadConf() ConfStruct { conf, err := initConf(ConfigPath) if err != nil { log.Warning( - "使用默认配置运行 ", err.Error(), - " 配置参考 https://github.com/jxhczhl/JsRpc/blob/main/config.yaml") + "使用默认配置运行 ", err.Error(), "\n", + "配置参考 https://github.com/jxhczhl/JsRpc/blob/main/config.yaml") } return conf } diff --git a/core/api.go b/core/api.go index 99ccd51..f1e48df 100644 --- a/core/api.go +++ b/core/api.go @@ -45,6 +45,7 @@ type ApiParam struct { type Clients struct { clientGroup string clientId string + clientIp string // 客户端ip actionData map[string]map[string]chan string // {"action":{"消息id":消息管道}} clientWs *websocket.Conn } @@ -61,12 +62,13 @@ func (c *Clients) writeToMap(funcName string, MessageId string, msg string) { } // NewClient initializes a new Clients instance -func NewClient(group string, uid string, ws *websocket.Conn) *Clients { +func NewClient(group string, uid string, ws *websocket.Conn, clientIp string) *Clients { return &Clients{ clientGroup: group, clientId: uid, actionData: make(map[string]map[string]chan string), // action有消息后就保存到chan里 clientWs: ws, + clientIp: clientIp, } } @@ -82,6 +84,8 @@ func ws(c *gin.Context) { if group == "" { return } + clientIP := c.ClientIP() + //没有给客户端id的话 就用uuid给他生成一个 if clientId == "" { clientId = utils.GetUUID() @@ -91,7 +95,7 @@ func ws(c *gin.Context) { log.Error("websocket err:", err) return } - client := NewClient(group, clientId, wsClient) + client := NewClient(group, clientId, wsClient, clientIP) hlSyncMap.Store(group+"->"+clientId, client) utils.LogPrint("新上线group:" + group + ",clientId:->" + clientId) clientNameJson := `{"registerId":"` + clientId + `"}` @@ -109,21 +113,21 @@ func ws(c *gin.Context) { messageStruct := MessageResponse{} err = json.Unmarshal(message, &messageStruct) if err != nil { - log.Error("接收到的消息不是设定的格式 不做处理", err) + log.Error("当前IP:", clientIP, "接收到的消息不是设定的格式 不做处理", err) } action := messageStruct.Action messageId := messageStruct.MessageId msg := messageStruct.ResponseData // 这里直接给管道塞数据,那么之前发送的时候要初始化好 if client.readFromMap(action, messageId) == nil { - log.Warning("当前消息id:", messageId, " 已被超时释放,回调的数据不做处理") + log.Warning("当前IP:", clientIP, "当前消息id:", messageId, " 已被超时释放,回调的数据不做处理") } else { client.writeToMap(action, messageId, msg) } if len(msg) > 100 { utils.LogPrint("id", messageId, "get_message:", msg[:101]+"......") } else { - utils.LogPrint("id", messageId, "get_message:", msg) + utils.LogPrint("IP", clientIP, "id", messageId, "get_message:", msg) } } @@ -181,7 +185,7 @@ func GetCookie(c *gin.Context) { return } c3 := make(chan string, 1) - go client.GQueryFunc("_execjs", utils.ConcatCode("document.cookie"), c3) + go client.GQueryFunc("_execjs", utils.ConcatCode("document.cookie"), c3, client.clientId) c.JSON(http.StatusOK, gin.H{"status": 200, "group": client.clientGroup, "clientId": client.clientId, "data": <-c3}) } @@ -192,7 +196,7 @@ func GetHtml(c *gin.Context) { return } c3 := make(chan string, 1) - go client.GQueryFunc("_execjs", utils.ConcatCode("document.documentElement.outerHTML"), c3) + go client.GQueryFunc("_execjs", utils.ConcatCode("document.documentElement.outerHTML"), c3, client.clientId) c.JSON(http.StatusOK, gin.H{"status": 200, "group": client.clientGroup, "clientId": client.clientId, "data": <-c3}) } @@ -214,7 +218,7 @@ func getResult(c *gin.Context) { return } c2 := make(chan string, 1) - go client.GQueryFunc(action, RequestParam.Param, c2) + go client.GQueryFunc(action, RequestParam.Param, c2, client.clientIp) //把管道传过去,获得值就返回了 c.JSON(http.StatusOK, gin.H{"status": 200, "group": client.clientGroup, "clientId": client.clientId, "data": <-c2}) @@ -240,7 +244,7 @@ func execjs(c *gin.Context) { return } c2 := make(chan string) - go client.GQueryFunc(Action, JsCode, c2) + go client.GQueryFunc(Action, JsCode, c2, client.clientIp) c.JSON(200, gin.H{"status": "200", "group": client.clientGroup, "name": client.clientId, "data": <-c2}) } diff --git a/core/engine.go b/core/engine.go index 134715e..de667a1 100644 --- a/core/engine.go +++ b/core/engine.go @@ -11,7 +11,7 @@ import ( ) // GQueryFunc 发送请求到客户端 -func (c *Clients) GQueryFunc(funcName string, param string, resChan chan<- string) { +func (c *Clients) GQueryFunc(funcName string, param string, resChan chan<- string, clientIp string) { if c.actionData[funcName] == nil { rwMu.Lock() c.actionData[funcName] = make(map[string]chan string) @@ -40,7 +40,7 @@ func (c *Clients) GQueryFunc(funcName string, param string, resChan chan<- strin WriteData := Message{Param: param, MessageId: MessageId, Action: funcName} data, err := json.Marshal(WriteData) if err != nil { - log.Error(err, "JSON序列化失败") + log.Error("当前IP:", clientIp, err, "JSON序列化失败") resChan <- "JSON序列化失败" return } @@ -49,7 +49,7 @@ func (c *Clients) GQueryFunc(funcName string, param string, resChan chan<- strin err = c.clientWs.WriteMessage(1, data) rwMu.Unlock() if err != nil { - log.Error(err, "写入数据失败") + log.Error("当前IP:", clientIp, err, "写入数据失败") resChan <- "rpc发送数据失败" return } @@ -65,9 +65,10 @@ func (c *Clients) GQueryFunc(funcName string, param string, resChan chan<- strin case res := <-resultChan: resChan <- res case <-ctx.Done(): - utils.LogPrint(MessageId + "超时了") + utils.LogPrint("当前IP:", clientIp, "超时了。", MessageId) resChan <- "获取结果超时 timeout" } + } func getRandomClient(group string, clientId string) *Clients { From 71635e5d8dee05e041a792061678557d931ac07b Mon Sep 17 00:00:00 2001 From: hliang <1820862012@qq.com> Date: Sun, 23 Nov 2025 06:41:24 +0800 Subject: [PATCH 6/7] =?UTF-8?q?js=E7=AB=AF=E6=8A=A5=E9=94=99=E6=89=93?= =?UTF-8?q?=E5=8D=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resouces/JsEnv_Dev.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/resouces/JsEnv_Dev.js b/resouces/JsEnv_Dev.js index 70c56c3..ea468bc 100644 --- a/resouces/JsEnv_Dev.js +++ b/resouces/JsEnv_Dev.js @@ -84,9 +84,14 @@ Hlclient.prototype.handlerRequest = function (requestJson) { } try { if (!result["param"]) { - theHandler(function (response) { + const async_result = theHandler(function (response) { _this.sendResult(action, message_id, response); }) + if (async_result && typeof async_result.then === "function") { + async_result.catch(e => { + _this.sendResult(action, message_id, "" + e); + }); + } return } var param = result["param"] @@ -99,7 +104,7 @@ Hlclient.prototype.handlerRequest = function (requestJson) { }, param) } catch (e) { console.log("error: " + e); - _this.sendResult(action, message_id, e); + _this.sendResult(action, message_id, "" + e); } } Hlclient.prototype.sendResult = function (action, message_id, e) { From 635ca739a119e37d80970193a0bdce7ca8378825 Mon Sep 17 00:00:00 2001 From: hliang Date: Fri, 26 Dec 2025 11:55:50 +0800 Subject: [PATCH 7/7] 0.0 --- config/config.go | 30 +++++++- core/api.go | 189 ++++++++++++++++++++++++++++++++++++++--------- core/engine.go | 60 +++++++++++++-- main.go | 6 ++ utils/logger.go | 5 ++ 5 files changed, 243 insertions(+), 47 deletions(-) diff --git a/config/config.go b/config/config.go index 5f0d0c6..c4c8926 100644 --- a/config/config.go +++ b/config/config.go @@ -4,14 +4,21 @@ import ( "JsRpc/utils" "errors" "flag" + "os" + log "github.com/sirupsen/logrus" "gopkg.in/yaml.v3" - "os" ) var DefaultTimeout = 30 func ReadConf() ConfStruct { + defer func() { + if r := recover(); r != nil { + log.Error("读取配置文件异常: ", r) + } + }() + var ConfigPath string // 定义命令行参数-c,后面跟着的是默认值以及参数说明 flag.StringVar(&ConfigPath, "c", "config.yaml", "指定配置文件的路径") @@ -28,6 +35,12 @@ func ReadConf() ConfStruct { } func initConf(path string) (ConfStruct, error) { + defer func() { + if r := recover(); r != nil { + log.Error("initConf panic recovered: ", r) + } + }() + defaultConf := ConfStruct{ BasicListen: `:12080`, HttpsServices: HttpsConfig{ @@ -44,19 +57,28 @@ func initConf(path string) (ConfStruct, error) { return defaultConf, errors.New("config path not found") } - file, _ := os.Open(path) // 因为上面已经判断了 文件是存在的 所以这里不用捕获错误 + file, err := os.Open(path) + if err != nil { + log.Error("打开配置文件失败: ", err) + return defaultConf, err + } defer func(file *os.File) { err := file.Close() if err != nil { + log.Error("关闭配置文件失败: ", err) } }(file) conf := ConfStruct{} decoder := yaml.NewDecoder(file) - err := decoder.Decode(&conf) + err = decoder.Decode(&conf) if err != nil { + log.Error("解析配置文件失败: ", err) return defaultConf, err } - DefaultTimeout = conf.DefaultTimeOut + // 设置超时时间 + if conf.DefaultTimeOut > 0 { + DefaultTimeout = conf.DefaultTimeOut + } return conf, nil } diff --git a/core/api.go b/core/api.go index f1e48df..8c56124 100644 --- a/core/api.go +++ b/core/api.go @@ -4,14 +4,15 @@ import ( "JsRpc/config" "JsRpc/utils" "encoding/json" - "github.com/gin-gonic/gin" - "github.com/gorilla/websocket" - log "github.com/sirupsen/logrus" - "github.com/unrolled/secure" "net/http" "strconv" "strings" "sync" + + "github.com/gin-gonic/gin" + "github.com/gorilla/websocket" + log "github.com/sirupsen/logrus" + "github.com/unrolled/secure" ) var ( @@ -56,6 +57,11 @@ func (c *Clients) readFromMap(funcName string, MessageId string) chan string { return c.actionData[funcName][MessageId] } func (c *Clients) writeToMap(funcName string, MessageId string, msg string) { + defer func() { + if r := recover(); r != nil { + log.Error("写入管道失败 (可能已关闭): ", r) + } + }() rwMu.Lock() defer rwMu.Unlock() c.actionData[funcName][MessageId] <- msg @@ -79,9 +85,17 @@ func GinJsonMsg(c *gin.Context, code int, msg string) { // ws, provides inject function for a job func ws(c *gin.Context) { + // 添加panic恢复机制 + defer func() { + if r := recover(); r != nil { + log.Error("ws handler panic recovered: ", r) + } + }() + group, clientId := c.Query("group"), c.Query("clientId") //必须要group名字,不然不让它连接ws if group == "" { + log.Warning("ws连接缺少group参数") return } clientIP := c.ClientIP() @@ -92,28 +106,41 @@ func ws(c *gin.Context) { } wsClient, err := upGrader.Upgrade(c.Writer, c.Request, nil) if err != nil { - log.Error("websocket err:", err) + log.Error("websocket upgrade err:", err) return } + + // 确保WebSocket连接最终被关闭 + defer func() { + if wsClient != nil { + _ = wsClient.Close() + utils.LogPrint(group+"->"+clientId, "下线了") + hlSyncMap.Delete(group + "->" + clientId) + } + }() + client := NewClient(group, clientId, wsClient, clientIP) hlSyncMap.Store(group+"->"+clientId, client) utils.LogPrint("新上线group:" + group + ",clientId:->" + clientId) clientNameJson := `{"registerId":"` + clientId + `"}` err = wsClient.WriteMessage(1, []byte(clientNameJson)) if err != nil { - log.Warning("注册成功,但发送回执信息失败") + log.Warning("注册成功,但发送回执信息失败: ", err) + return } for { //等待数据 _, message, err := wsClient.ReadMessage() if err != nil { + log.Debug("读取websocket消息失败,连接可能已断开: ", err) break } // 将得到的数据转成结构体 messageStruct := MessageResponse{} err = json.Unmarshal(message, &messageStruct) if err != nil { - log.Error("当前IP:", clientIP, "接收到的消息不是设定的格式 不做处理", err) + log.Error("当前IP:", clientIP, " 接收到的消息不是设定的格式,不做处理: ", err) + continue } action := messageStruct.Action messageId := messageStruct.MessageId @@ -125,40 +152,48 @@ func ws(c *gin.Context) { client.writeToMap(action, messageId, msg) } if len(msg) > 100 { - utils.LogPrint("id", messageId, "get_message:", msg[:101]+"......") + utils.LogPrint("id:", messageId, " get_message:", msg[:101]+"......") } else { - utils.LogPrint("IP", clientIP, "id", messageId, "get_message:", msg) + utils.LogPrint("IP:", clientIP, " id:", messageId, " get_message:", msg) } - } - defer func(ws *websocket.Conn) { - _ = ws.Close() - utils.LogPrint(group+"->"+clientId, "下线了") - hlSyncMap.Range(func(key, value interface{}) bool { - //client, _ := value.(*Clients) - if key == group+"->"+clientId { - hlSyncMap.Delete(key) - } - return true - }) - }(wsClient) } func wsTest(c *gin.Context) { - testClient, _ := upGrader.Upgrade(c.Writer, c.Request, nil) + defer func() { + if r := recover(); r != nil { + log.Error("wsTest handler panic recovered: ", r) + } + }() + + testClient, err := upGrader.Upgrade(c.Writer, c.Request, nil) + if err != nil { + log.Error("wsTest upgrade失败: ", err) + return + } + + // 确保连接关闭 + defer func() { + if testClient != nil { + _ = testClient.Close() + } + }() + for { //等待数据 _, message, err := testClient.ReadMessage() if err != nil { + log.Debug("wsTest读取消息失败: ", err) break } msg := string(message) utils.LogPrint("接收到测试消息", msg) - _ = testClient.WriteMessage(websocket.BinaryMessage, []byte(msg)) + err = testClient.WriteMessage(websocket.TextMessage, []byte(msg)) + if err != nil { + log.Error("wsTest发送消息失败: ", err) + break + } } - defer func(ws *websocket.Conn) { - _ = ws.Close() - }(testClient) } func checkRequestParam(c *gin.Context) (*Clients, string) { @@ -179,29 +214,66 @@ func checkRequestParam(c *gin.Context) (*Clients, string) { } func GetCookie(c *gin.Context) { + defer func() { + if r := recover(); r != nil { + log.Error("GetCookie handler panic recovered: ", r) + GinJsonMsg(c, http.StatusInternalServerError, "服务器内部错误") + } + }() + client, errorStr := checkRequestParam(c) if errorStr != "" { GinJsonMsg(c, http.StatusBadRequest, errorStr) return } c3 := make(chan string, 1) - go client.GQueryFunc("_execjs", utils.ConcatCode("document.cookie"), c3, client.clientId) + go func() { + defer func() { + if r := recover(); r != nil { + log.Error("GetCookie goroutine panic recovered: ", r) + c3 <- "获取cookie失败:内部错误" + } + }() + client.GQueryFunc("_execjs", utils.ConcatCode("document.cookie"), c3, client.clientId) + }() c.JSON(http.StatusOK, gin.H{"status": 200, "group": client.clientGroup, "clientId": client.clientId, "data": <-c3}) } func GetHtml(c *gin.Context) { + defer func() { + if r := recover(); r != nil { + log.Error("GetHtml handler panic recovered: ", r) + GinJsonMsg(c, http.StatusInternalServerError, "服务器内部错误") + } + }() + client, errorStr := checkRequestParam(c) if errorStr != "" { GinJsonMsg(c, http.StatusBadRequest, errorStr) return } c3 := make(chan string, 1) - go client.GQueryFunc("_execjs", utils.ConcatCode("document.documentElement.outerHTML"), c3, client.clientId) + go func() { + defer func() { + if r := recover(); r != nil { + log.Error("GetHtml goroutine panic recovered: ", r) + c3 <- "获取html失败:内部错误" + } + }() + client.GQueryFunc("_execjs", utils.ConcatCode("document.documentElement.outerHTML"), c3, client.clientId) + }() c.JSON(http.StatusOK, gin.H{"status": 200, "group": client.clientGroup, "clientId": client.clientId, "data": <-c3}) } // GetResult 接收web请求参数,并发给客户端获取结果 func getResult(c *gin.Context) { + defer func() { + if r := recover(); r != nil { + log.Error("getResult handler panic recovered: ", r) + GinJsonMsg(c, http.StatusInternalServerError, "服务器内部错误") + } + }() + var RequestParam ApiParam if err := c.ShouldBind(&RequestParam); err != nil { GinJsonMsg(c, http.StatusBadRequest, err.Error()) @@ -218,13 +290,27 @@ func getResult(c *gin.Context) { return } c2 := make(chan string, 1) - go client.GQueryFunc(action, RequestParam.Param, c2, client.clientIp) + go func() { + defer func() { + if r := recover(); r != nil { + log.Error("getResult goroutine panic recovered: ", r) + c2 <- "调用失败:内部错误" + } + }() + client.GQueryFunc(action, RequestParam.Param, c2, client.clientIp) + }() //把管道传过去,获得值就返回了 c.JSON(http.StatusOK, gin.H{"status": 200, "group": client.clientGroup, "clientId": client.clientId, "data": <-c2}) - } func execjs(c *gin.Context) { + defer func() { + if r := recover(); r != nil { + log.Error("execjs handler panic recovered: ", r) + GinJsonMsg(c, http.StatusInternalServerError, "服务器内部错误") + } + }() + var RequestParam ApiParam if err := c.ShouldBind(&RequestParam); err != nil { GinJsonMsg(c, http.StatusBadRequest, err.Error()) @@ -243,17 +329,32 @@ func execjs(c *gin.Context) { GinJsonMsg(c, http.StatusBadRequest, errorStr) return } - c2 := make(chan string) - go client.GQueryFunc(Action, JsCode, c2, client.clientIp) + c2 := make(chan string, 1) + go func() { + defer func() { + if r := recover(); r != nil { + log.Error("execjs goroutine panic recovered: ", r) + c2 <- "执行js代码失败:内部错误" + } + }() + client.GQueryFunc(Action, JsCode, c2, client.clientIp) + }() c.JSON(200, gin.H{"status": "200", "group": client.clientGroup, "name": client.clientId, "data": <-c2}) - } func getList(c *gin.Context) { + defer func() { + if r := recover(); r != nil { + log.Error("getList handler panic recovered: ", r) + GinJsonMsg(c, http.StatusInternalServerError, "服务器内部错误") + } + }() + var data = make(map[string][]string) hlSyncMap.Range(func(_, value interface{}) bool { client, ok := value.(*Clients) if !ok { + log.Warning("类型断言失败:无法将value转换为*Clients") return true // 继续遍历 } group := client.clientGroup @@ -281,12 +382,19 @@ func index(c *gin.Context) { func tlsHandler(HttpsHost string) gin.HandlerFunc { return func(c *gin.Context) { + defer func() { + if r := recover(); r != nil { + log.Error("tlsHandler panic recovered: ", r) + } + }() + secureMiddleware := secure.New(secure.Options{ SSLRedirect: true, SSLHost: HttpsHost, }) err := secureMiddleware.Process(c.Writer, c.Request) if err != nil { + log.Error("TLS处理失败: ", err) c.Abort() return } @@ -318,6 +426,12 @@ func setupRouters(conf config.ConfStruct) *gin.Engine { } func InitAPI(conf config.ConfStruct) { + defer func() { + if r := recover(); r != nil { + log.Fatal("InitAPI panic: ", r) + } + }() + if conf.CloseWebLog { // 将默认的日志输出器设置为空 gin.DefaultWriter = utils.LogWriter{} @@ -339,13 +453,18 @@ func InitAPI(conf config.ConfStruct) { sb.WriteString(conf.HttpsServices.HttpsListen) router.Use(tlsHandler(conf.HttpsServices.HttpsListen)) go func() { + defer func() { + if r := recover(); r != nil { + log.Error("HTTPS服务 panic recovered: ", r) + } + }() err := router.RunTLS( conf.HttpsServices.HttpsListen, conf.HttpsServices.PemPath, conf.HttpsServices.KeyPath, ) if err != nil { - log.Error(err) + log.Error("HTTPS服务启动失败: ", err) } }() } @@ -353,6 +472,6 @@ func InitAPI(conf config.ConfStruct) { err := router.Run(conf.BasicListen) if err != nil { - log.Errorln("服务启动失败..") + log.Fatal("HTTP服务启动失败: ", err) } } diff --git a/core/engine.go b/core/engine.go index de667a1..fd04424 100644 --- a/core/engine.go +++ b/core/engine.go @@ -5,13 +5,28 @@ import ( "JsRpc/utils" "context" "encoding/json" - log "github.com/sirupsen/logrus" "math/rand" "time" + + log "github.com/sirupsen/logrus" ) // GQueryFunc 发送请求到客户端 func (c *Clients) GQueryFunc(funcName string, param string, resChan chan<- string, clientIp string) { + defer func() { + if r := recover(); r != nil { + log.Error("GQueryFunc panic recovered: ", r) + // 尝试安全地发送错误消息 + defer func() { + if r2 := recover(); r2 != nil { + log.Error("发送错误消息到channel也失败了: ", r2) + } + }() + resChan <- "内部错误" + close(resChan) + } + }() + if c.actionData[funcName] == nil { rwMu.Lock() c.actionData[funcName] = make(map[string]chan string) @@ -31,16 +46,35 @@ func (c *Clients) GQueryFunc(funcName string, param string, resChan chan<- strin // 确保资源释放 defer func() { rwMu.Lock() - delete(c.actionData[funcName], MessageId) + if c.actionData[funcName] != nil { + if ch, exists := c.actionData[funcName][MessageId]; exists { + delete(c.actionData[funcName], MessageId) + func() { + defer func() { + if r := recover(); r != nil { + log.Debug("关闭已关闭的channel: ", r) + } + }() + close(ch) + }() + } + } rwMu.Unlock() - close(resChan) + func() { + defer func() { + if r := recover(); r != nil { + log.Debug("关闭resChan失败: ", r) + } + }() + close(resChan) + }() }() // 构造消息并发送 WriteData := Message{Param: param, MessageId: MessageId, Action: funcName} data, err := json.Marshal(WriteData) if err != nil { - log.Error("当前IP:", clientIp, err, "JSON序列化失败") + log.Error("当前IP:", clientIp, " JSON序列化失败: ", err) resChan <- "JSON序列化失败" return } @@ -49,7 +83,7 @@ func (c *Clients) GQueryFunc(funcName string, param string, resChan chan<- strin err = c.clientWs.WriteMessage(1, data) rwMu.Unlock() if err != nil { - log.Error("当前IP:", clientIp, err, "写入数据失败") + log.Error("当前IP:", clientIp, " 写入数据失败: ", err) resChan <- "rpc发送数据失败" return } @@ -68,18 +102,27 @@ func (c *Clients) GQueryFunc(funcName string, param string, resChan chan<- strin utils.LogPrint("当前IP:", clientIp, "超时了。", MessageId) resChan <- "获取结果超时 timeout" } - } func getRandomClient(group string, clientId string) *Clients { + defer func() { + if r := recover(); r != nil { + log.Error("getRandomClient panic recovered: ", r) + } + }() + var client *Clients // 不传递clientId时候,从group分组随便拿一个 if clientId != "" { clientName, ok := hlSyncMap.Load(group + "->" + clientId) - if ok == false { + if !ok { + return nil + } + client, ok = clientName.(*Clients) + if !ok { + log.Error("类型断言失败:无法将clientName转换为*Clients") return nil } - client, _ = clientName.(*Clients) return client } groupClients := make([]*Clients, 0) @@ -87,6 +130,7 @@ func getRandomClient(group string, clientId string) *Clients { hlSyncMap.Range(func(_, value interface{}) bool { tmpClients, ok := value.(*Clients) if !ok { + log.Warning("类型断言失败:无法将value转换为*Clients") return true } if tmpClients.clientGroup == group { diff --git a/main.go b/main.go index 3324d13..46b34fa 100644 --- a/main.go +++ b/main.go @@ -7,6 +7,12 @@ import ( ) func main() { + defer func() { + if r := recover(); r != nil { + utils.ErrorPrint("程序异常退出: ", r) + } + }() + utils.PrintJsRpc() // 开屏打印 utils.InitLogger() // 初始化日志 baseConf := config.ReadConf() // 读取日志信息 diff --git a/utils/logger.go b/utils/logger.go index 3ce58a2..5c97d63 100644 --- a/utils/logger.go +++ b/utils/logger.go @@ -32,3 +32,8 @@ func LogPrint(p ...interface{}) { log.Infoln(p) } } +func ErrorPrint(p ...interface{}) { + if isPrint { + log.Error(p) + } +}