首页 文章 精选 留言 我的

精选列表

搜索[副语言],共10000篇文章
优秀的个人博客,低调大师

Go语言:xterm.js-websocket Web终端堡垒机

1.前言 因为公司业务需要在自己的私有云服务器上添加添加WebSsh终端,同时提供输入命令审计功能. 从google上可以了解到xterm.js是一个非常出色的web终端库,包括VSCode很多成熟的产品都使用这个前端库.使用起来也比较简单. 难点是怎么把ssh命令行转换成websocket通讯,来提供Stdin,stdout输出到xterm.js中,接下来就详解技术细节. 全部代码都可以在我的Github.com/dejavuzhou/felix中可以查阅到. 2.知识储备 linux下载stdin,stdou和stderr简单概念 熟悉Golang官方库golang.org/x/crypto/ssh 了解gorilla/websocket的基本用法 gin-gonic/gin,当然你也可以使用其他的路由包替代,或者直接使用标准库 (前端)websocket (前端)xterm.js 3.数据逻辑图 Golang堡垒机主要功能就是把SSH协议数据使用websocket协议转发给xterm.js浏览器. 堡垒机Golang服务UML 4.代码实现 4.1创建gin Handler func 注册gin路由 api.GET("ws/:id", internal.WsSsh) ssh2ws/internal/ws_ssh.go package internal import ( "bytes" "github.com/dejavuzhou/felix/flx" "github.com/dejavuzhou/felix/models" "github.com/dejavuzhou/felix/utils" "github.com/gin-gonic/gin" "github.com/gorilla/websocket" "github.com/sirupsen/logrus" "net/http" "strconv" "time" ) var upGrader = websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024 * 1024 * 10, CheckOrigin: func(r *http.Request) bool { return true }, } // handle webSocket connection. // first,we establish a ssh connection to ssh server when a webSocket comes; // then we deliver ssh data via ssh connection between browser and ssh server. // That is, read webSocket data from browser (e.g. 'ls' command) and send data to ssh server via ssh connection; // the other hand, read returned ssh data from ssh server and write back to browser via webSocket API. func WsSsh(c *gin.Context) { v, ok := c.Get("user") if !ok { logrus.Error("jwt token can't find auth user") return } userM, ok := v.(*models.User) if !ok { logrus.Error("context user is not a models.User type obj") return } cols, err := strconv.Atoi(c.DefaultQuery("cols", "120")) if wshandleError(c, err) { return } rows, err := strconv.Atoi(c.DefaultQuery("rows", "32")) if wshandleError(c, err) { return } idx, err := parseParamID(c) if wshandleError(c, err) { return } mc, err := models.MachineFind(idx) if wshandleError(c, err) { return } client, err := flx.NewSshClient(mc) if wshandleError(c, err) { return } defer client.Close() startTime := time.Now() ssConn, err := utils.NewSshConn(cols, rows, client) if wshandleError(c, err) { return } defer ssConn.Close() // after configure, the WebSocket is ok. wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil) if wshandleError(c, err) { return } defer wsConn.Close() quitChan := make(chan bool, 3) var logBuff = new(bytes.Buffer) // most messages are ssh output, not webSocket input go ssConn.ReceiveWsMsg(wsConn, logBuff, quitChan) go ssConn.SendComboOutput(wsConn, quitChan) go ssConn.SessionWait(quitChan) <-quitChan //write logs xtermLog := models.TermLog{ EndTime: time.Now(), StartTime: startTime, UserId: userM.ID, Log: logBuff.String(), MachineId: idx, MachineName: mc.Name, MachineIp: mc.Ip, MachineHost: mc.Host, UserName: userM.Username, } err = xtermLog.Create() if wshandleError(c, err) { return } logrus.Info("websocket finished") } 代码详解 31~52行使用gin来获取url中的参数(js websocket库)只可以把参数定义到cookie和和url-query中,所以这里包括token(不是在header-Authorization中)在内的参数全部在url中获取 53~56行到数据库中获取保存的ssh连接信息 57~68行创建ssh-session 69~74行升级得到websocketConn(Reader/Writer) 75~85行(核心代码)ssh Session 和 websocket 信息进行交换和处理,同时处理好线程退出 86~104行处理ssh输入命令(logBuff),当session结束的时候技术输入的命令到数据库中,提供日后审计只用 4.1.1 func NewSshConn(cols, rows int, sshClient *ssh.Client) (*SshConn, error)创建ssh-session-pty I 获取stdin pipline stdinP, err := sshSession.StdinPipe() II 初始化wsBufferWriter,赋值给ssh-session.Stdout和ssh-session.Stderr type wsBufferWriter struct { buffer bytes.Buffer mu sync.Mutex } ... ... ... comboWriter := new(wsBufferWriter) //ssh.stdout and stderr will write output into comboWriter sshSession.Stdout = comboWriter sshSession.Stderr = comboWriter 现在comboWriter就是sshSession的stdout和stderr,可以通过comboWriter获取ssh输出 4.2 第75~85行核心代码解析 4.2.1 quitChan 用来处理 for select loop退出,代码示例 for { select { case <-quitChan: //exit loop return default: fmt.Println("do some stuff") } } 4.2.2 var logBuff = new(bytes.Buffer) 暂存session中的stdin命令,websocket session 结束之后,获取logBuff.String(),写入数据库 Log: logBuff.String(), ... <-quitChan //write logs xtermLog := models.TermLog{ EndTime: time.Now(), StartTime: startTime, UserId: userM.ID, Log: logBuff.String(), MachineId: idx, MachineName: mc.Name, MachineIp: mc.Ip, MachineHost: mc.Host, UserName: userM.Username, } err = xtermLog.Create() if wshandleError(c, err) { return } ... 4.2.3 go ssConn.ReceiveWsMsg(wsConn, logBuff, quitChan) 处理ws消息并转发给ssh-Session stdinPipe,同时暂存消息到logBuff //ReceiveWsMsg receive websocket msg do some handling then write into ssh.session.stdin func (ssConn *SshConn) ReceiveWsMsg(wsConn *websocket.Conn, logBuff *bytes.Buffer, exitCh chan bool) { //tells other go routine quit defer setQuit(exitCh) for { select { case <-exitCh: return default: //read websocket msg _, wsData, err := wsConn.ReadMessage() if err != nil { logrus.WithError(err).Error("reading webSocket message failed") return } //unmashal bytes into struct msgObj := wsMsg{} if err := json.Unmarshal(wsData, &msgObj); err != nil { logrus.WithError(err).WithField("wsData", string(wsData)).Error("unmarshal websocket message failed") } switch msgObj.Type { case wsMsgResize: //handle xterm.js size change if msgObj.Cols > 0 && msgObj.Rows > 0 { if err := ssConn.Session.WindowChange(msgObj.Rows, msgObj.Cols); err != nil { logrus.WithError(err).Error("ssh pty change windows size failed") } } case wsMsgCmd: //handle xterm.js stdin decodeBytes, err := base64.StdEncoding.DecodeString(msgObj.Cmd) if err != nil { logrus.WithError(err).Error("websock cmd string base64 decoding failed") } if _, err := ssConn.StdinPipe.Write(decodeBytes); err != nil { logrus.WithError(err).Error("ws cmd bytes write to ssh.stdin pipe failed") } //write input cmd to log buffer if _, err := logBuff.Write(decodeBytes); err != nil { logrus.WithError(err).Error("write received cmd into log buffer failed") } } } } } _, wsData, err := wsConn.ReadMessage() 读取websocket 发送的消息 if err := json.Unmarshal(wsData, &msgObj); err != nil { 序列化消息,消息结构必须前端xterm.js-websocket协商一直,建议使用 const ( wsMsgCmd = "cmd"//处理ssh命令 wsMsgResize = "resize"//处理xterm.js dom尺寸变化事件,详解xterm.js文档 ) type wsMsg struct { Type string `json:"type"` Cmd string `json:"cmd"` Cols int `json:"cols"` Rows int `json:"rows"` } - case wsMsgResize处理xterm.js 终端尺寸变化事件 wsMsgCmd 处理xterm.js 命令输入 if _, err := ssConn.StdinPipe.Write(decodeBytes); err != nil { 把ws xterm.js,前端input命令写入到ssh-session-stdin-pipline ssh.seesion 如果检测到到 decodeBytes 包含执行符('r'),sshSession会执行命令,包把执行结果输出到comboWriter if _, err := logBuff.Write(decodeBytes); err != nil { 把ws.xterm.js 前端input命令记录到 logBuff 4.2.4 go ssConn.SendComboOutput(wsConn, quitChan) 把ssh.Session的comboWriter中的数据每隔120ms 通过调用websocketConn.WriteMessage方法返回给xterm.js+websocketClient 前端 func (ssConn *SshConn) SendComboOutput(wsConn *websocket.Conn, exitCh chan bool) { //tells other go routine quit defer setQuit(exitCh) //every 120ms write combine output bytes into websocket response tick := time.NewTicker(time.Millisecond * time.Duration(120)) //for range time.Tick(120 * time.Millisecond){} defer tick.Stop() for { select { case <-tick.C: //write combine output bytes into websocket response if err := flushComboOutput(ssConn.ComboOutput, wsConn); err != nil { logrus.WithError(err).Error("ssh sending combo output to webSocket failed") return } case <-exitCh: return } } } ... ... ... //flushComboOutput flush ssh.session combine output into websocket response func flushComboOutput(w *wsBufferWriter, wsConn *websocket.Conn) error { if w.buffer.Len() != 0 { err := wsConn.WriteMessage(websocket.TextMessage, w.buffer.Bytes()) if err != nil { return err } w.buffer.Reset() } return nil } 4.2.5 go ssConn.SessionWait(quitChan) 注意这里的go 关键字不能去掉,否在导致不能处理quitChan,导致协程泄露. func (ssConn *SshConn) SessionWait(quitChan chan bool) { if err := ssConn.Session.Wait(); err != nil { logrus.WithError(err).Error("ssh session wait failed") setQuit(quitChan) } } 4.前端vuejs.demo代码 可以提供给前端开发人员参考,当然可以让他直接查xterm.js官方文档,但是websocket 数据库结构必须前后端协商一致 vuejs+xterm.js+websocket示例代码 <template> <el-dialog :visible.sync="v" :title="obj.user + '@' + obj.host" @opened="doOpened" @open="doOpen" @close="doClose" center fullscreen > <div ref="terminal"></div> </el-dialog> </template> <script> import {Terminal} from "xterm"; import * as fit from "xterm/lib/addons/fit/fit"; import {Base64} from "js-base64"; import * as webLinks from "xterm/lib/addons/webLinks/webLinks"; import * as search from "xterm/lib/addons/search/search"; import "xterm/lib/addons/fullscreen/fullscreen.css"; import "xterm/dist/xterm.css" import config from "@/config/config" let defaultTheme = { foreground: "#ffffff", background: "#1b212f", cursor: "#ffffff", selection: "rgba(255, 255, 255, 0.3)", black: "#000000", brightBlack: "#808080", red: "#ce2f2b", brightRed: "#f44a47", green: "#00b976", brightGreen: "#05d289", yellow: "#e0d500", brightYellow: "#f4f628", magenta: "#bd37bc", brightMagenta: "#d86cd8", blue: "#1d6fca", brightBlue: "#358bed", cyan: "#00a8cf", brightCyan: "#19b8dd", white: "#e5e5e5", brightWhite: "#ffffff" }; let bindTerminalResize = (term, websocket) => { let onTermResize = size => { websocket.send( JSON.stringify({ type: "resize", rows: size.rows, cols: size.cols }) ); }; // register resize event. term.on("resize", onTermResize); // unregister resize event when WebSocket closed. websocket.addEventListener("close", function () { term.off("resize", onTermResize); }); }; let bindTerminal = (term, websocket, bidirectional, bufferedTime) => { term.socket = websocket; let messageBuffer = null; let handleWebSocketMessage = function (ev) { if (bufferedTime && bufferedTime > 0) { if (messageBuffer) { messageBuffer += ev.data; } else { messageBuffer = ev.data; setTimeout(function () { term.write(messageBuffer); }, bufferedTime); } } else { term.write(ev.data); } }; let handleTerminalData = function (data) { websocket.send( JSON.stringify({ type: "cmd", cmd: Base64.encode(data) // encode data as base64 format }) ); }; websocket.onmessage = handleWebSocketMessage; if (bidirectional) { term.on("data", handleTerminalData); } // send heartbeat package to avoid closing webSocket connection in some proxy environmental such as nginx. let heartBeatTimer = setInterval(function () { websocket.send(JSON.stringify({type: "heartbeat", data: ""})); }, 20 * 1000); websocket.addEventListener("close", function () { websocket.removeEventListener("message", handleWebSocketMessage); term.off("data", handleTerminalData); delete term.socket; clearInterval(heartBeatTimer); }); }; export default { props: {obj: {type: Object, require: true}, visible: Boolean}, name: "CompTerm", data() { return { isFullScreen:false, searchKey:"", v: this.visible, ws: null, term: null, thisV: this.visible }; }, watch: { visible(val) { this.v = val;//新增result的watch,监听变更并同步到myResult上 } }, computed: { wsUrl() { let token = localStorage.getItem('token'); return `${config.wsBase}/api/ws/${this.obj.ID || 0}?cols=${this.term.cols}&rows=${this.term.rows}&_t=${token}` } }, methods: { onWindowResize() { //console.log("resize") this.term.fit(); // it will make terminal resized. }, doLink(ev, url) { if (ev.type === 'click') { window.open(url) } }, doClose() { window.removeEventListener("resize", this.onWindowResize); // term.off("resize", this.onTerminalResize); if (this.ws) { this.ws.close() } if (this.term) { this.term.dispose() } this.$emit('pclose', false)//子组件对openStatus修改后向父组件发送事件通知 }, doOpen() { }, doOpened() { Terminal.applyAddon(fit); Terminal.applyAddon(webLinks); Terminal.applyAddon(search); this.term = new Terminal({ rows: 35, fontSize: 18, cursorBlink: true, cursorStyle: 'bar', bellStyle: "sound", theme: defaultTheme }); this.term.open(this.$refs.terminal); this.term.webLinksInit(this.doLink); // term.on("resize", this.onTerminalResize); window.addEventListener("resize", this.onWindowResize); this.term.fit(); // first resizing this.ws = new WebSocket(this.wsUrl); this.ws.onerror = () => { this.$message.error('ws has no token, please login first'); this.$router.push({name: 'login'}); }; this.ws.onclose = () => { this.term.setOption("cursorBlink", false); this.$message("console.web_socket_disconnect") }; bindTerminal(this.term, this.ws, true, -1); bindTerminalResize(this.term, this.ws); }, }, } </script> <style scoped> </style> 5. 最终效果 6. 完整项目代码 1. 快速效果预览 git clone https://github.com/dejavuzhou/felix cd felix go mod download go install echo "添加 GOBIN 到 PATH环境变量" echo "或者" go get github.com/dejavuzhou/felix echo "go build && ./felix sshw" 执行代码felix sshw 2. Go后端代码:ssh2ws代码地址 3. Xtermjs前端代码:dejavuzhou/felixfe 4. 【原文地址tech.mojotv.cn】 5. 【Demo】 felix.mojotv.cn

优秀的个人博客,低调大师

Hanlp自然语言处理中的词典格式说明

使用过hanlp的都知道hanlp中有许多词典,它们的格式都是非常相似的,形式都是文本文档,随时可以修改。本篇文章详细介绍了hanlp中的词典格式,以满足用户自定义的需要。 基本格式 词典分为词频词性词典和词频词典。 1、词频词性词典(如CoreNatureDictionary.txt) (1)每一行代表一个单词,格式遵从[单词] [词性A] [A的频次] [词性B] [B的频次] ...。 (2)支持省略词性和频次,直接一行一个单词。 (3).txt词典文件的分隔符为空格或制表符,所以不支持含有空格的词语。如果需要支持空格,请使用英文逗号,分割的纯文本.csv文件。在使用Excel等富文本编辑器时,则请注意保存为纯文本形式。 2、词频词典(如CoreNatureDictionary.ngram.txt) (1)每一行代表一个单词或条目,格式遵从[单词] [单词的频次]。 (2)每一行的分隔符为空格或制表符。 少数词典有自己的专用格式,比如同义词词典兼容《同义词词林扩展版》的文本格式,而转移矩阵词典则是一个csv表格。 下文主要介绍通用词典,如不注明,词典特指通用词典。 数据结构 Trie树(字典树)是HanLP中使用最多的数据结构,为此,我实现了通用的Trie树,支持泛型、遍历、储存、载入。 用户自定义词典采用AhoCorasickDoubleArrayTrie和二分Trie树储存,其他词典采用基于双数组Trie树(DoubleArrayTrie)实现的AC自动机AhoCorasickDoubleArrayTrie。关于一些常用数据结构的性能评估,请参考wiki。 储存形式 词典有两个形态:文本文件(filename.txt)和缓存文件(filename.txt.bin或filename.txt.trie.dat和filename.txt.trie.value)。 1、文本文件 ·采用明文储存,UTF-8编码,CRLF换行符。 2、缓存文件 (1)就是一些二进制文件,通常在文本文件的文件名后面加上.bin表示。有时候是.trie.dat和.trie.value。后者是历史遗留产物,分别代表trie树的数组和值。 (2)如果你修改了任何词典,只有删除缓存才能生效。 修改方法 HanLP的核心词典训练自人民日报2014语料,语料不是完美的,总会存在一些错误。这些错误可能会导致分词出现奇怪的结果,这时请打开调试模式排查问题: HanLP.Config.enableDebug(); (1)核心词性词频词典 a)比如你在data/dictionary/CoreNatureDictionary.txt中发现了一个不是词的词,或者词性标注得明显不对,那么你可以修改它,然后删除缓存文件使其生效。 B)目前CoreNatureDictionary.ngram.txt的缓存依赖于CoreNatureDictionary.txt的缓存,修改了后者之后必须同步删除前者的缓存,否则可能出错 (2)核心二元文法词典 a) 二元文法词典data/dictionary/CoreNatureDictionary.ngram.txt储存的是两个词的接续,如果你发现不可能存在这种接续时,删掉即可。 B)你也可以添加你认为合理的接续,但是这两个词必须同时在核心词典中才会生效。 (3)命名实体识别词典 a)基于角色标注的命名实体识别比较依赖词典,所以词典的质量大幅影响识别质量。 b)这些词典的格式与原理都是类似的,请阅读相应的文章或代码修改它。

优秀的个人博客,低调大师

NLP自然语言处理中的hanlp分词实例

本篇分享的依然是关于hanlp的分词使用,文章内容分享自 gladosAI 的博客,本篇文章中提出了一个问题,hanlp分词影响了实验判断结果。为何会如此,不妨一起学习一下 gladosAI 的这篇文章。 学习内容 在之前的实验中得到了不在词向量里的词与分词结果,结果有500多个词不在词向量里,解决方案就是重新分词,或再追加训练这些词到词向量里。但后者相对麻烦且目前样本量不大。我跟据词向量的作者[6]所使用的分词工具来分词,会比不同工具(jieba)的效果要好,因为都是同一模式的分词,分出来的词应该都会存在于大型语料库中。实验证明思路是对的,最后结果是只有60几个词不在词向量里,其中大部分为名词,还有些为因语音翻译问题所造成的出错连词,所有这些词也只出现一次,这部分可以考虑最后删去也不会影响结果。改善未出现词是个关键步骤,因为此后模型会用到词向量,如果未出现词过多,会影响词向量效果。 问题:不过最后HANLP分词影响了实验判断结果,准确率从93%(jieba分词,同模型同参数)下降到90%。 实验:使用HanLP分词 1,前期准备,(环境ubuntu,python3)安装JAVA-10[3](hanlp是JAVA开发的,即使使用python调用pyhanlp需要借助java),jpype(python中虚拟java环境),hanlp(开源中文处理工具,不只是分词还有各种工具),hanlp的root路径配置及data数据包放置[4] 2,主要程序[5] w2v_model = KeyedVectors.load_word2vec_format(w2vpath, binary=False, unicode_errors='ignore') # 加载词向量 hanlppath=\"-Djava.class.path=/media/glados/Learning/project/NLP/hanlp/hanlp-1.6.4.jar:/media/glados/Learning/project/NLP/hanlp/" jp.startJVM(jp.getDefaultJVMPath(), hanlppath) # , "-Xms1g", "-Xmx1g") # 启动JVM, Xmx1g分配1g内存 jp.JClass('com.hankcs.hanlp.HanLP$Config').ShowTermNature = False # 关闭分词属性显示 HanLP = jp.JClass('com.hankcs.hanlp.HanLP') #普通分词模式 words = str(HanLP.segment(sentence)) #分词将结果转为str words = re.sub('[反斜杠[反斜杠],\n]', ' ', words) # 这里注意实际程序是单\,在blog里会出问题,所以用反斜杠替代 words = words.split() words = del_stopword(words) ... jp.shutdownJVM() # 最后关闭java虚拟环境 使用的是HANLP的普通分词功能,另外需注意,hanlp.segment()不能直接输出或赋值给python,因为其是java环境中数据,所以只有转为str()后,再进行处理,否则会报错#Afatalerror。(另外还有其他java与python数据对应问题,在这里没遇到,请参考其他) 词向量选用的是“Mixed-large综合”[6],其包括百度wiki百科、人民日报等,总共1293214个词。 Hanlp的中部份功能没法使用,例如精确分词CRF。另外,要先加载词向量再加载java虚拟环境。#AfatalerrorhasbeendetectedbytheJavaRuntimeEnvironment 3,实验结果 (模型使用的是特征为tfidf的lsi模型, 参数:num_topics=3, 判断是否相似阀值为0.45,即大于0.45为true相似 ) 同模型同参数下,jieba分词结果 jieba分词未出现在词向量的约500多,有些词出现了好几次,而hanlp分词只有60几个未出现,且多数为名词,只出现过一次。 4,分析 在样本中,所有样本分词结果jieba比hanlp要多分出100个词左右。所以推测因hanlp分词细粒度大,分出词少,导致较少的共现词出现(也可能是hanlp分词精度更高,分出很多虚词被停止词表过滤造成),也就是说,lsi+tfidf模型对词细粒度大、分词少的分词器不友好,所以最后hanlp出错率更大。 jieba与hanlp都是很不错的分词器,结巴使用更方便。hanlp准确度要高一些(感觉),而且与文中提到的词向量相匹配。 (我免贵姓AI,jieba:我免/贵姓/AI,hanlp:我/免/贵姓/AI,实际:我/免贵/姓AI)

优秀的个人博客,低调大师

自然语言处理 依存树的可视化

目标:NLP中依存树的可视化 首先需要准备的工具 Hanlp(借助hanlp神经网络的高性能依存句法分析器模型) Dependency Viewer可视化工具 对于一句话text,可以采用hanlp中的下图代码转化为conll格式 下图是采用了一些测试句子的结果(在conll文件中以空行隔开): 在dependency View中进行展示,打开dependency View,选择File----read Conll file打开本地的conll文件,即可看到依存树展示的结果 文章来源于醉意流年go的博客

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册