成功最有效的方法就是向有经验的人学习!

webssh 实现 rz sz

需要引入的 js 文件:

#zmodem.min.js
https://github.com/pythonzm/Ops/blob/dev/static/xterm/zmodem/zmodem.min.js
#bootbox.min.js
https://github.com/pythonzm/Ops/blob/dev/static/js/bootbox.min.js

编辑前端页面
其中省略了其他无关内容

let socket = new WebSocket(ws_scheme + '://' + window.location.host + '/ws/webssh/{{ pk }}/?remote_ip={{ remote_ip }}' + '&width=' + cols + '&height=' + rows);

    socket.binaryType = "arraybuffer";

    let term = new Terminal({
        screenKeys: true,
        useStyle: true,
        cursorBlink: true,
        cols: cols,
        rows: rows
    });

    let zsentry = new Zmodem.Sentry({
        to_terminal: function (octets) {
        },  //i.e. send to the terminal

        on_detect(detection) {
            let zsession = detection.confirm();

            if (zsession.type === "send") {
                uploadFile(zsession);
            } else {
                downloadFile(zsession);
            }
        },

        on_retract: function () {
        },

        sender: function (octets) {
            socket.send(new Uint8Array(octets))
        },
    });

    socket.onopen = function () {
        // $("#terms").css("height", document.documentElement.clientHeight - $('.main-header').height());
        term.open(document.getElementById('terms'), true);
        // term.fit();
    };

    term.on('data', function (data) {
        socket.send(data);
    });

    socket.onmessage = function (msg) {
        if (typeof (msg.data) === 'string') {
            term.write(msg.data);
        } else {
            zsentry.consume(msg.data);
        }
    };
    socket.onerror = function () {
        document.getElementById('terms').innerHTML = '<h3>连接失败!</h3>';
    };
    socket.onclose = function (e) {
        // pass
        console.log(e)
    };

    function uploadFile(zsession) {
        let uploadHtml = "<div>" +
            "<label class='upload-area' style='width:100%;text-align:center;' for='fupload'>" +
            "<input id='fupload' name='fupload' type='file' style='display:none;' multiple='true'>" +
            "<i class='fa fa-cloud-upload fa-3x'></i>" +
            "<br />" +
            "点击选择文件" +
            "</label>" +
            "<br />" +
            "<span style='margin-left:5px !important;' id='fileList'></span>" +
            "</div><div class='clearfix'></div>";

        let upload_dialog = bootbox.dialog({
            message: uploadHtml,
            title: "上传文件",
            buttons: {
                cancel: {
                    label: '关闭',
                    className: 'btn-default',
                    callback: function (res) {
                        try {
                            // zsession 每 5s 发送一个 ZACK 包,5s 后会出现提示最后一个包是 ”ZACK“ 无法正常关闭
                            // 这里直接设置 _last_header_name 为 ZRINIT,就可以强制关闭了
                            zsession._last_header_name = "ZRINIT";
                            zsession.close();
                        } catch (e) {
                            console.log(e);
                        }
                    }
                },
            },
            closeButton: false,
        });

        function hideModal() {
            upload_dialog.modal('hide');
        }

        let file_el = document.getElementById("fupload");

        return new Promise((res) => {
            file_el.onchange = function (e) {
                let files_obj = file_el.files;
                hideModal();
                Zmodem.Browser.send_files(zsession, files_obj, {
                        on_offer_response(obj, xfer) {
                            if (xfer) {
                                // term.write("\r\n");
                            } else {
                                term.write(obj.name + " was upload skipped\r\n");
                            }
                        },
                        on_progress(obj, xfer) {
                            updateProgress(xfer);
                        },
                        on_file_complete(obj) {
                            term.write("\r\n" + obj.name + " was upload success\r\n");
                        },
                    }
                ).then(zsession.close.bind(zsession), console.error.bind(console)
                ).then(() => {
                    res();
                });
            };
        });
    }

    function updateProgress(xfer) {
        let detail = xfer.get_details();
        let name = detail.name;
        let total = detail.size;
        let percent;
        if (total === 0) {
            percent = 100
        } else {
            percent = Math.round(xfer._file_offset / total * 100);
        }

        term.write("\r" + name + ": " + total + " " + xfer._file_offset + " " + percent + "%    ");
    }

    function downloadFile(zsession) {
        zsession.on("offer", (xfer) => {

            let FILE_BUFFER = [];
            xfer.on("input", (payload) => {
                updateProgress(xfer);
                FILE_BUFFER.push(new Uint8Array(payload));
            });

            xfer.accept().then(
                () => {
                    Zmodem.Browser.save_to_disk(
                        FILE_BUFFER,
                        xfer.get_details().name
                    );
                },
                console.error.bind(console)
            );

        });

        zsession.on("session_end", () => {
            term.write("\r\n");
        });

        zsession.start();
    }

编辑后端内容
省略其他无关内容

zmodemszstart = b'rz\r**\x18B00000000000000\r\x8a'
zmodemend = b'**\x18B0800000000022d\r\x8a'
zmodemrzstart = b'rz waiting to receive.**\x18B0100000023be50\r\x8a'
zmodemcancel = b'\x18\x18\x18\x18\x18\x08\x08\x08\x08\x08'

class MyThread(threading.Thread):

    # 省略内容

    def run(self):
        while not self._stop_event.is_set() or not self.chan.chan.exit_status_ready():
            try:
                if self.chan.zmodemOO:
                    self.chan.zmodemOO = False
                    data = self.chan.chan.recv(2)
                    if not len(data):
                        return
                    if data == b'OO':
                        self.chan.send(bytes_data=data)
                        continue
                    else:
                        data += self.chan.chan.recv(4096)
                else:
                    data = self.chan.chan.recv(4096)
                    if not len(data):
                        return

                if self.chan.zmodem:
                    if zmodemend in data:
                        self.chan.zmodem = False
                        self.chan.zmodemOO = True

                    if zmodemcancel in data:
                        self.chan.zmodem = False
                        self.chan.chan.send('\n')
                    self.chan.send(bytes_data=data)
                else:
                    if zmodemszstart in data or zmodemrzstart in data:
                        self.chan.zmodem = True
                        self.chan.send(bytes_data=data)
                    else:
                        str_data = data.decode('utf-8', 'ignore')
                        self.chan.send(str_data)
            except timeout:
                break
        self.chan.send('\n由于长时间没有操作,连接已断开!', close=True)
        self.stdout.append([time.time() - self.start_time, 'o', '\n由于长时间没有操作,连接已断开!'])
        self.chan.close()

class MySSH(WebsocketConsumer):
        def __init__(self, *args, **kwargs):
        super(MySSH, self).__init__(*args, **kwargs)
        # 省略其他内容
        self.zmodem = False
        self.zmodemOO = False

    # 省略其他内容

    def receive(self, text_data=None, bytes_data=None):
        if text_data:
            self.chan.send(text_data)
        else:
            self.chan.send(bytes_data)
赞(2) 打赏
未经允许不得转载:陈桂林博客 » webssh 实现 rz sz
分享到

大佬们的评论 1

  1. #1

    zmodem.min.js怎么引进来啊?我单独去下载这个文件引入的话 会报错找不到Zmodem

    G-Pig2年前 (2021-11-12)回复

全新“一站式”建站,高质量、高售后的一条龙服务

微信 抖音 支付宝 百度 头条 快手全平台打通信息流

橙子建站.极速智能建站8折购买虚拟主机

觉得文章有用就打赏一下文章作者

非常感谢你的打赏,我们将继续给力更多优质内容,让我们一起创建更加美好的网络世界!

支付宝扫一扫打赏

微信扫一扫打赏

登录

找回密码

注册