本文介绍: websocket主要是wswss协议,其原理就是http协议升级ws协议,即ws是建立在http上的,所有路由正常写http路由然后处理一下websocket升级。在最近一次需求里,需要实现一个webSSH的功能,就是把terminal搬到web中来。此方法需要输入输出,和错误使用标准输入标准输出,能实现交互,但是我是需要接收websocket发的消息,及返回websocket输出。注: 实现read方法时,注意加个回车,不然指令是不会执行的,我在这里就卡了很久……

   在最近一次需求里,需要实现一个webSSH的功能,就是把terminal搬到web中来。要实现这个功能可以采用websocket+ssh来说实现

1.第一步实现websocket

websocket主要是wswss协议,其原理就是http协议升级成ws协议,即ws是建立在http上的,所有路由正常写http路由然后处理一下websocket升级。

注:我用echo框架

路由

backendApi.GET("/tools/ssh/ws", tools.WebSSH).Name = "webssh"

handler

func WebSSH(e echo.Context) error {
	var param tools.WebSShReq
	if err := utils.BindAndValidate(e, &param); err != nil {
		return err
	}
	if err := tools.Upgrade(e.Response().Writer, e.Request(), param); err != nil {
		return err
	}
	return e.JSON(http.StatusOK, "'")
}

:

websocket协议升级:

var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
	CheckOrigin: func(r *http.Request) bool {
		return true
	},
}

func Upgrade(w http.ResponseWriter, r *http.Request, param WebSShReq) (err error) {
	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		return
	}
	client := NewSSHClient(param)
	client.Ws = conn
	err = client.GenerateClient()
	if err != nil {
		fmt.Println("链接ssh错误", err)
		conn.WriteMessage(1, []byte(err.Error()))
		conn.Close()
		return err
	}
	go client.Write()
	return nil
}

2.第一版ssh实现

type SSHClient struct {
	Username  string `json:"username"`
	Password  string `json:"password"`
	IpAddress string `json:"ipaddress"`
	Port      int    `json:"port"`
	Client    *ssh.Client
	Ws        *websocket.Conn
    Session   *ssh.Session
}

// NewSSHClient 创建新的ssh客户端func NewSSHClient(param WebSShReq) SSHClient {
	client := SSHClient{}
	client.Username = param.Username
	client.Port = param.Port
	client.IpAddress = param.IpAddress
	client.Password = param.Password
	return client
}
func (t *SSHClient) GenerateClient() error {
	var (
		auth         []ssh.AuthMethod
		addr         string
		clientConfig *ssh.ClientConfig
		client       *ssh.Client
		config       ssh.Config
		err          error
	)
	auth = make([]ssh.AuthMethod, 0)
	auth = append(auth, ssh.Password(t.Password))
	config = ssh.Config{
		Ciphers: []string{"aes128-ctr", "aes192-ctr", "aes256-ctr", "arcfour256", "arcfour128", "aes128-cbc", "3des-cbc", "aes192-cbc", "aes256-cbc"},
	}
	clientConfig = &ssh.ClientConfig{
		User:    t.Username,
		Auth:    auth,
		Timeout: 5 * time.Second,
		Config:  config,
		HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
			return nil
		},
	}
	addr = fmt.Sprintf("%s:%d", t.IpAddress, t.Port)
	if client, err = ssh.Dial("tcp", addr, clientConfig); err != nil {
		return err
	}
	t.Client = client
	return nil
}

func (t *SSHClient) send(out, stderr *bytes.Buffer, cmd []byte) error {
	session, err := t.Client.NewSession()
	if err != nil {
		return errors.New("ssh session创建失败")
	}
	defer session.Close()
	session.Stdout = out
	session.Stderr = stderr
	return session.Run(string(cmd))
}
func (t *SSHClient) Write() {
	defer t.Client.Close()
	for {
		// p为用户输入
		_, p, err := t.Ws.ReadMessage()
		if err != nil {
			return
		}
		fmt.Println("webssh:", string(p))
		go func(data []byte) {
			var (
				out   bytes.Buffer
				stdEr bytes.Buffer
			)
			err2 := t.send(&out, &stdEr, data)
			if err2 != nil {
				t.Ws.WriteMessage(1, stdEr.Bytes())
				return
			}
			t.Ws.WriteMessage(1, out.Bytes())
		}(p)
	}
}

这样能实现正常指令,但是有个问题;不能切换目录,不完善

3.改进

使用终端交互模式,做到真正的webssh直接代码

改进方法

func (t *SSHClient) RunTerminal(stdout, stderr io.Writer, stdin io.Reader) error {
	session, err := t.Client.NewSession()
	if err != nil {
		return err
	}
	defer session.Close()

	session.Stdout = io.MultiWriter(os.Stdout, stdout)
	session.Stderr = io.MultiWriter(os.Stderr, stderr)
	session.Stdin = stdin
	modes := ssh.TerminalModes{
		ssh.ECHO:          0,
		ssh.TTY_OP_ISPEED: 14400,
		ssh.TTY_OP_OSPEED: 14400,
	}
	if err := session.RequestPty("xterm", 32, 160, modes); err != nil {
		return err
	}
	if err = session.Shell(); err != nil {
		log.Fatalf("start shell error: %s", err.Error())
	}
	if err = session.Wait(); err != nil {
		log.Fatalf("return error: %s", err.Error())
	}
	return nil
}

方法需要输入输出,和错误使用标准输入及标准输出,能实现交互,但是我是需要接收websocket发的消息,及返回websocket输出

故,需要实现io.writer和io.reader接口

type stdout struct {
	ws *websocket.Conn
}

func (s *stdout) Write(p []byte) (n int, err error) {
	// fmt.Println("SSH output:", string(p))
	err = s.ws.WriteMessage(1, p)
	return len(p), err
}

type stdIn struct {
	ws *websocket.Conn
}

func (s *stdIn) Read(p []byte) (n int, err error) {
	_, p2, err := s.ws.ReadMessage()
	// 指令需要一个回车
	if err != nil {
		return 0, err
	}

	n = copy(p, []byte(fmt.Sprintf("%sn", string(p2))))
	// fmt.Println("####", string(p))
	return n, err
}
func (s *stdIn) Close() error {
	return nil
}

注: 实现read方法时,注意加个回车,不然指令是不会执行的,我在这里就卡了很久……

最后,将upgrade方法中的,

go client.Write() 

换成

go client.RunTerminal(&stdout{conn}, &stdout{conn}, &stdIn{conn})

ok了。

4.最终效果如图

 

原文地址:https://blog.csdn.net/wangge20091126/article/details/130113262

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_45222.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注