Closed zundaren closed 1 year ago
我猜:不行的时候,url 不是 localhost 或 127.0.0.1 了吧?
非 localhost 或 127.0.0.1 时,不支持 http,只能用 https,这个是浏览器的限制。
我猜:不行的时候,url 不是 localhost 或 127.0.0.1 了吧?
非 localhost 或 127.0.0.1 时,不支持 http,只能用 https,这个是浏览器的限制。
等下研究下这个协议问题,我看tabby里面trzsz插件好像也是你写的,里面是通过electron的api调文件窗口,这个不改源码可以实现吗,让go去打开窗口把结果给到trsz执行上传下载 (能否搞个选项1.浏览器自动操作,2.用户自定义打开窗口操作,3.其他)灵感来了
我先找下有没有go的trzsz协议,不用js处理试试看
我猜:不行的时候,url 不是 localhost 或 127.0.0.1 了吧?
非 localhost 或 127.0.0.1 时,不支持 http,只能用 https,这个是浏览器的限制。
gowails框架打包用的自定义的协议。。,但是为啥ui框自带的文件上传组件能打开窗口,都是在webkit环境中运行[https://github.com/vueComponent/ant-design-vue/blob/main/components/vc-upload/traverseFileTree.ts
go 可能可以用 https://github.com/trzsz/trzsz-go ,不过我在开发的时候,没把它设计成一个组件。
现在客户端是编译出一个 trzsz
可执行程序,如果封装好一点,应该也可以让其他程序引用。
客户端相关的代码主要在:https://github.com/trzsz/trzsz-go/blob/main/trzsz/trzsz.go
go 可能可以用 https://github.com/trzsz/trzsz-go ,不过我在开发的时候,没把它设计成一个组件。 现在客户端是编译出一个
trzsz
可执行程序,如果封装好一点,应该也可以让其他程序引用。客户端相关的代码主要在:https://github.com/trzsz/trzsz-go/blob/main/trzsz/trzsz.go
好滴,我研究下。 我查了下 window.showOpenFilePicker这些方法在不同浏览器兼容性不一样,如果创建input标签对话框能实现和前面一样的功能,那可以在掉不通系统api的情况下启用这个备选方案,这个可行不
input 标签只能实现上传,实现不了下载。
@zundaren 你的 js 是运行在 nodejs 环境里,还是运行在纯浏览器的环境里?如果是 nodejs 环境,可以自己实现弹出对话框的逻辑,和 electron 的实现类似,可参考 https://github.com/trzsz/trzsz.js/tree/main/examples/electron
如果 go 能控制远程服务器的输入和输出,可能可以使用 https://github.com/trzsz/trzsz-go ,trzsz-go 应该要稍微改一下,需要研究研究怎么改更易对接。
@zundaren 你的 js 是运行在 nodejs 环境里,还是运行在纯浏览器的环境里?如果是 nodejs 环境,可以自己实现弹出对话框的逻辑,和 electron 的实现类似,可参考 https://github.com/trzsz/trzsz.js/tree/main/examples/electron
如果 go 能控制远程服务器的输入和输出,可能可以使用 https://github.com/trzsz/trzsz-go ,trzsz-go 应该要稍微改一下,需要研究研究怎么改更易对接。
是在webkit内核运行,嵌入到go里面的,刚发现了两个设置项应该可以解决,感谢大佬帮忙 chooseSendFiles?: (directory?: boolean) => Promise<string[] | undefined>; chooseSaveDirectory?: () => Promise<string | undefined>;
这两个接口要求 js 能直接操作文件系统的,webkit 可能没权限,你可以试试看。
如果是 node-webkit 应该就可以。
如果是 node-webkit 应该就可以。
框架换不了,写太多代码了,chooseSendFiles这个方法我直接拦截trzsz的命令选文件发送是不是就行了,还需要解析协议之类的吗
只要实现那两个接口就行了,会自动回调它们的。如果不回调,可能是被判定为没有 fs 包,不能直接操作文件系统了。
node webkit 按理说是兼容 webkit 的,有可能你一行代码都不用改,就可以换成它。
node webkit 按理说是兼容 webkit 的,有可能你一行代码都不用改,就可以换成它。
..刚刚那两个接口没暴露出来调用不了. gowails windows下是用的webview2,可以正常打开上传下载,mac上面不清楚怎么玩,这个node webkit应该是用不了,程序打包就一个文件,双击直接就运行了,都调用不了其他的东西。。。而且这个一百兆体积太大了,我程序才20m环境就要这么大~
node webkit 是一个 sdk,应该是你的代码依赖它,可能原来的 webkit 就可以去掉了,然后编译出来,就和原来编译出来的一样了。我也没实际用过,猜的。
我去看了一下 https://wails.io/docs/howdoesitwork 你用这个的话,是不好换成 node webkit。而 webkit 是不能操作文件系统的。不过,我从 wails 的架构图看出,js 可以调 go ,而 go 是可以操作文件系统的。
我去看了一下 https://wails.io/docs/howdoesitwork 你用这个的话,是不好换成 node webkit。而 webkit 是不能操作文件系统的。不过,我从 wails 的架构图看出,js 可以调 go ,而 go 是可以操作文件系统的。
go打开窗口操作我都能做,就是js打不开窗口go也不能插手很难瘦,中午还研究了下用trzszgo做中转,没搞成功。。
trzsz-go 应该也是可以搞的,我要知道你是怎么与远程服务器交互的,才能知道怎么搞。
js 这个也是可以搞的,因为 js 可以调 go,你需要把读写文件的函数用 go 提供出来,不单单只是打开窗口,可能有一点复杂。
trzsz-go 应该也是可以搞的,我要知道你是怎么与远程服务器交互的,才能知道怎么搞。
js 这个也是可以搞的,因为 js 可以调 go,你需要把读写文件的函数用 go 提供出来,不单单只是打开窗口,可能有一点复杂。
就是xtermjs+websocket 和go ssh做了绑定,现在trz->websoket->trzszfilter->terminal, 我对js也不是很懂,如果可以继承trzsz修改读写文件窗口等方法就好办了,想拖拽也是读写文件,想着js读写应该没有问题啊。
问题是,在 webkit 里 js 没权限直接读写文件。
websocket 的读写是 go 负责吗?
问题是,在 webkit 里 js 没权限直接读写文件。
websocket 的读写是 go 负责吗?
对,js部分只负责渲染,大部分文件,ssh操作都是调用的go
那 go 应该也是可以搞的,不过 go 版我没有像 js 这样实现一个 Filter,所以对接可能会麻烦一些。不过 go 版现在领先于 js 版,它的传输速度会快一些。你的软件是开源的吗?可能简单看一下代码才知道具体怎么搞。
用 js 版的话,我刚看了 wails 的文档,大概知道怎么搞了,对接会比现在的 go 版简单一点。js 版要追上 go 版的传输速度的话,可能要较长的时间,等我有空了才会搞。
那 go 应该也是可以搞的,不过 go 版我没有像 js 这样实现一个 Filter,所以对接可能会麻烦一些。不过 go 版现在领先于 js 版,它的传输速度会快一些。你的软件是开源的吗?可能简单看一下代码才知道具体怎么搞。
用 js 版的话,我刚看了 wails 的文档,大概知道怎么搞了,对接会比现在的 go 版简单一点。js 版要追上 go 版的传输速度的话,可能要较长的时间,等我有空了才会搞。
我的主页有个go zmodem协议的demo,也是类似过滤器一样的,大佬先休息吧
是不是都用 golang.org/x/crypto/ssh
连接远程服务器的?
是不是都用
golang.org/x/crypto/ssh
连接远程服务器的?
是的
发现 go ssh 不像一个正常的终端,ctrl + c
之类都不可用,https://stackoverflow.com/questions/28921409/how-can-i-send-terminal-escape-sequences-through-ssh-with-go
最大的两个问题:
1、服务器输出 \n
,go ssh 总是转换成 \r\n
。
2、输入的内容,总是会 echo 回显。
这两个问题不管怎么设置都没用。
不知你能不能替换成其他的,如 https://github.com/creack/pty 等
发现 go ssh 不像一个正常的终端,
ctrl + c
之类都不可用,https://stackoverflow.com/questions/28921409/how-can-i-send-terminal-escape-sequences-through-ssh-with-go最大的两个问题: 1、服务器输出
\n
,go ssh 总是转换成\r\n
。 2、输入的内容,总是会 echo 回显。 这两个问题不管怎么设置都没用。不知你能不能替换成其他的,如 https://github.com/creack/pty 等
回显我设置的1,需要和前端做映射,\n和\r\n主要区分是换行,回车(光标位置),ctrl+c目前我通过xtermjs传给服务器也是没问题的,感觉你说的问题是有特殊字符要解码。我也研究下pty看能不能和xtermjs结合
pty好像是个本地客户端,gossh那个产生的是个服务器伪终端
我发现 go ssh 上下箭头键也是不能用的,设置了 tty 的属性也没用,很奇怪。
我发现 go ssh 上下箭头键也是不能用的,设置了 tty 的属性也没用,很奇怪。
xtermjs操作,接收data,websocket转发,copy websocket的[]byte到ssh的输入管道,目前上下左右,ctrl+c等操作就和xshell一样的, 是不是你的终端尺寸resize没发给服务器同步
可能是我直接编译成一个控制台程序有关系。方便的话,发个 xterm.js 的 demo 源码来看看?
可能是我直接编译成一个控制台程序有关系。方便的话,发个 xterm.js 的 demo 源码来看看?
代码主要的通信就是下面的 github.com/gorilla/websocket
<template>
<div ref="termRef" @dragover.prevent @drop.prevent="drag" @contextmenu.prevent="pasteFromClip($event)" @mouseup.middle="copy2clip(term.getSelection())"</div>
</template>
<script setup lang="ts">
let ws: WebSocket
let trzsz: TrzszFilter
let term: Terminal
const termRef = ref<HTMLElement>()
const fitAddon = new FitAddon()
const searchAddon = new SearchAddon()
let canvasAddon = new CanvasAddon();
const webglAddon = new WebglAddon();
webglAddon.onContextLoss(e => {
webglAddon.dispose();
if (term) {
term.loadAddon(canvasAddon)
}
});
function createWs() {
if (prop.cfg.port == "") {
throw "port is null"
}
ws = new WebSocket("ws://localhost:" + prop.cfg.port + "/ssh?clientId=" + prop.clientId);
ws.onopen = ev => {
createTerm()
resize()
init_trzsz()
}
ws.onerror = e => {
console.error("ws onError:", e)
closeAll()
}
ws.onclose = e => {
closeAll()
}
ws.onmessage = e => {
if (typeof e.data === "string") {
trzsz.processServerOutput(e.data)
} else if (e.data instanceof Blob) {
// let reader = new FileReader();
// reader.readAsText(data, "utf-8")
// reader.onloadend = ev => {
// // let msg = JSON.parse(reader.result as string)
// }
}
}
}
function init_trzsz() {
trzsz = new TrzszFilter({
sendToServer: (data) => ws.send(data),
writeToTerminal: (data) => {
if (typeof data === "string") {
term.write(data)
}
},
});
}
function createTerm() {
let {theme, fontSize, fontFamily} = mergeTheme();
term = new Terminal({
cols: tcols.value,
disableStdin: false,
letterSpacing: 0, // 字符间距
lineHeight: 1.2,
fontSize: fontSize,
fontFamily: fontFamily,
fontWeight: 500,
cursorBlink: true,
cursorStyle: 'block',
convertEol: true, //启用时,光标将设置为下一行的开头
scrollback: 10000, //终端中的回滚量
windowsMode: false,
allowProposedApi: true,
theme: theme,
});
term.onData((data) => {
if (ws) {
if (prop.sendAll) {
SSHApi.SendAll(data as string)
} else {
trzsz.processTerminalInput(data)
}
}
})
term.onBinary((data) => trzsz.processBinaryInput(data))
term.loadAddon(fitAddon)
term.loadAddon(searchAddon)
term.loadAddon(webglAddon)
term.open(termRef.value as HTMLElement)
term.focus()
window.addEventListener('resize', resize)
return term
}
function drag(e: DragEvent) {
if (!e.dataTransfer) {
return
}
trzsz.uploadFiles(e.dataTransfer.items).then(() => success()).catch((err) => console.log(err));
}
function sendBinaryData(obj: BinaryData) {
try {
ws.send(new Blob([JSON.stringify(obj)], {type : 'application/json'}))
} catch (e: unknown) {
console.error(e)
LogError("js ws sendBinaryData exception: " + String(e))
}
}
function setTermVh100() {
let ele = term.element as HTMLElement;
let attribute = ele.getAttribute("class") as string
let ss = attribute.split(" ");
if (!ss.includes("vh100")) {
ss.push("vh100")
ele.setAttribute("class", ss.join(" "))
}
}
const resize = useDebounceFn(() => {
const termResize = () => {
try {
fitAddon.fit()
term.resize(tcols.value, trows.value)
trzsz.setTerminalColumns(tcols.value)
sendBinaryData({type: "windowSize", high: term.rows, width: term.cols} as WindowSize)
setTermVh100()
calcSuspPos()
}catch (e) {
console.error(e)
}
}
let count = 0
let result: number[] = []
let timer = setInterval(function () {
if (count > 180) {
clearInterval(timer)
closeAll()
return
}
if (result.length != 0 && result.pop() == 1) {
clearInterval(timer)
termResize()
count = 0
return
}
count++
let cw = Number(term.textarea?.style.width?.replace("px", ""))
if (cw != 0) {
let ch = term.textarea?.offsetHeight as number
tcols.value = Math.floor(prop.w as number / cw)
trows.value = Math.floor(prop.h as number / ch)
result.push(1)
} else {
result.push(0)
}
}, 10)
}, 50)
function pasteFromClip(e: any) {
ClipboardGetText().then(v => {
term.paste(v)
})
}
onMounted(() => {
createWs()
})
watch([prop], (value: any, oldValue: any, onCleanup: any) => {
if (prop.dragging) {
setTermVh100()
return
}
resize()
})
</script>
// ======================================================================================
// ======================================================================================go
func WsServer() {
gin.SetMode(gin.ReleaseMode)
router := gin.New()
router.RedirectTrailingSlash = true
router.Use(cors.Default())
router.GET("/ssh", func(c *gin.Context) {
cli, b := GetConnector(c.Query("clientId"))
if !b {
return
}
conn, err := upgrade.Upgrade(c.Writer, c.Request, nil)
if err != nil {
return
}
conn.EnableWriteCompression(true)
err = conn.SetCompressionLevel(5)
if err != nil {
return
}
go cli.BridgeWS(conn)
})
server := &http.Server{
Addr: fmt.Sprintf("127.0.0.1:%s", "8888"),
Handler: router,
ReadHeaderTimeout: 0,
}
go server.ListenAndServe()
}
func (c *SshConnector) BridgeWS(conn *websocket.Conn) {
c.Ws.Conn = conn
_ = c.Ws.Conn.SetReadDeadline(time.Now().Add(messageWait))
msgType, msg, err := c.Ws.Conn.ReadMessage()
if err != nil {
return
}
if msgType != websocket.BinaryMessage {
return
}
wdSize := new(windowSize)
if err = json.Unmarshal(msg, wdSize); err != nil {
return
}
c.Ws.Session, err = c.NativeSsh.NewSession()
if err != nil {
return
}
c.Ws.Session.Stderr = os.Stderr
inPipe, err := c.Ws.Session.StdinPipe()
if err != nil {
return
}
outPipe, err := c.Ws.Session.StdoutPipe()
if err != nil {
return
}
c.Ws.InPipe = inPipe
c.Ws.OutPipe = outPipe
if err := c.Ws.Session.RequestPty("xterm-256color", wdSize.High, wdSize.Width, terminalModes); err != nil {
g.Log.Error(fmt.Sprintf("ssh session RequestPty error: %+v", err))
return
}
if err := c.Ws.Session.Shell(); err != nil {
g.Log.Error(fmt.Sprintf("ssh session Shell error: %+v", err))
return
}
go func() {
wsRead(c)
}()
go func() {
wsWrite(c)
}()
}
func wsWrite(c *SshConnector) {
for {
bytes := bufPool.Get().([]byte)
n, err := c.Ws.OutPipe.Read(bytes)
if err != nil {
return
}
if n > 0 {
_ = c.Ws.Conn.SetWriteDeadline(time.Now().Add(messageWait))
if err := c.Ws.Conn.WriteMessage(websocket.TextMessage, c.Decode(bytes[:n])); err != nil {
return
}
}
bufPool.Put(bytes)
time.Sleep(200 * time.Microsecond)
}
}
func wsRead(c *SshConnector) {
var infiniteWait time.Time
_ = c.Ws.Conn.SetReadDeadline(infiniteWait)
go func() {
time.Sleep(100 * time.Millisecond)
_, _ = c.Ws.InPipe.Write([]byte("export PS1='[\\u@\\h \\W]\\$ ' \r"))
}()
for {
msgType, data, err := c.Ws.Conn.ReadMessage()
if err != nil {
return
}
if msgType != websocket.BinaryMessage {
_, err = c.Ws.InPipe.Write(data)
if err != nil {
return
}
continue
}
p := make(map[string]any, 0)
if err = json.Unmarshal(data, &p); err != nil {
g.Log.Error(fmt.Sprintf("ws read binarymessage decode error %v", err))
return
}
//fmt.Printf("%v\n", p)
v, ok := p["type"]
if !ok || v == "" {
return
}
switch v {
case HeartType:
_ = c.Ws.Conn.SetWriteDeadline(time.Now().Add(messageWait))
msg := ujson.ToBytes(PongMsg{Type: HeartType})
if err := c.Ws.Conn.WriteMessage(websocket.BinaryMessage, msg); err != nil {
return
}
case WindowSizeType:
wdSize := &windowSize{}
_ = maputil.MapTo(p, wdSize)
if err := c.Ws.Session.WindowChange(wdSize.High, wdSize.Width); err != nil {
break
}
}
}
}
打包一份能编译运行的(最好精简一下,把不影响基础运行的去掉),发到我的邮箱?
lonnywong@qq.com
打包一份能编译运行的(最好精简一下,把不影响基础运行的去掉),发到我的邮箱?
go的trzszfilter我写了一个,上传下载都没问题,就是里面进度条,传输协议都是copy出来改改,你的代码升级了使用者不好改,感觉这部分还是需要拆出来一个公共的依赖,协议对接输入输出管道,进度条单独设置管道
我周末重构后,会提供一个 go 版的 filter 接口。
@zundaren go 版 filter 代码已提交 https://github.com/trzsz/trzsz-go/commit/2af6c381d37a986ced4a57773894fc7a6ffa0c50
@zundaren go 版 filter 代码已提交 trzsz/trzsz-go@2af6c38
上传下载拖拽都测过,暂时没什么大问题,
服务器直接yum安装的,测了两个服务器,ctrl+c的显示一个有报错,一个stoped,功能没什么问题
这个客户端的版本和服务器端的版本是否需要一致,如果后期服务器软件升级,连接协议这块有没有什么要注意的
不需要一致,会往前兼容的。
那个问题好复现不?如果可以复现,试试启用 log,下面这样:
trzszFilter = trzsz.NewTrzszFilter(clientIn, clientOut, serverIn, serverOut, trzsz.TrzszOptions{
TerminalColumns: width,
DetectTraceLog: true,
})
登录服务器之后,先执行 echo -e '<ENABLE_TRZSZ_TRACE_LOG\x3E'
,然后你会看到一个日志文件路径,在本地电脑上的。
然后传文件,如果能复现,把日志文件发给我看看,可以发到我邮箱。
那个问题好复现不?如果可以复现,试试启用 log,下面这样:
trzszFilter = trzsz.NewTrzszFilter(clientIn, clientOut, serverIn, serverOut, trzsz.TrzszOptions{ TerminalColumns: width, DetectTraceLog: true, })
登录服务器之后,先执行
echo -e '<ENABLE_TRZSZ_TRACE_LOG\x3E'
,然后你会看到一个日志文件路径,在本地电脑上的。 然后传文件,如果能复现,把日志文件发给我看看,可以发到我邮箱。那个问题好复现不?如果可以复现,试试启用 log,下面这样:
trzszFilter = trzsz.NewTrzszFilter(clientIn, clientOut, serverIn, serverOut, trzsz.TrzszOptions{ TerminalColumns: width, DetectTraceLog: true, })
登录服务器之后,先执行
echo -e '<ENABLE_TRZSZ_TRACE_LOG\x3E'
,然后你会看到一个日志文件路径,在本地电脑上的。 然后传文件,如果能复现,把日志文件发给我看看,可以发到我邮箱。
我的问题,把异常随便写了errors.new, 应该要用newSimpleTrzszError
mac系统,我使用 xtermjs5.1.0,trzszjs, go wails,浏览器端可以正常打开文件窗口,但是打包成客户端后无法打开文件选择窗口, 我使用的antdv自带的上传组件是可以打开选择窗口的,这个要怎么弄下呢