从远程复制文件到字节数组。

huangapple go评论90阅读模式
英文:

Copy file from remote to byte[]

问题

我正在尝试弄清楚如何实现从远程复制文件并从缓冲区获取数据[]byte

我已经成功地通过参考这个指南实现了上传的部分:https://chuacw.ath.cx/development/b/chuacw/archive/2019/02/04/how-the-scp-protocol-works.aspx

go func内部,有SCPupload过程的实现,但我不知道如何修改它。

有什么建议吗?

func download(con *ssh.Client, buf bytes.Buffer, path string,) ([]byte,error) {
	//https://chuacw.ath.cx/development/b/chuacw/archive/2019/02/04/how-the-scp-protocol-works.aspx
	session, err := con.NewSession()
	if err != nil {
		return nil,err
	}

	buf.WriteString("sudo scp -f " + path + "\n")

	stdin, err := session.StdinPipe()
	if err != nil {
		return nil,err
	}
	go func() {
		defer stdin.Close()
		fmt.Fprint(stdin, "C0660 "+strconv.Itoa(len(content))+" file\n")
		stdin.Write(content)
		fmt.Fprint(stdin, "\x00")
	}()

	output, err := session.CombinedOutput("sudo scp -f " + path)
	buf.Write(output)
	if err != nil {
		return nil,&DeployError{
			Err:    err,
			Output: buf.String(),
		}
	}

	session.Close()

	session, err = con.NewSession()
	if err != nil {
		return nil,err
	}
	defer session.Close()

	return output,nil
}
英文:

I'm trying to figure out how to implement copying files from remote and get the data []byte from the buffer.

I have succeeded in doing the implementation with the upload by referring to this guide: https://chuacw.ath.cx/development/b/chuacw/archive/2019/02/04/how-the-scp-protocol-works.aspx

Inside the go func there's the implementation of the upload process of the SCP but I have no idea how to change it.

Any advice ?

func download(con *ssh.Client, buf bytes.Buffer, path string,) ([]byte,error) {
	//https://chuacw.ath.cx/development/b/chuacw/archive/2019/02/04/how-the-scp-protocol-works.aspx
	session, err := con.NewSession()
	if err != nil {
		return nil,err
	}

	buf.WriteString("sudo scp -f " + path + "\n")

	stdin, err := session.StdinPipe()
	if err != nil {
		return nil,err
	}
	go func() {
		defer stdin.Close()
		fmt.Fprint(stdin, "C0660 "+strconv.Itoa(len(content))+" file\n")
		stdin.Write(content)
		fmt.Fprint(stdin, "\x00")
	}()

	output, err := session.CombinedOutput("sudo scp -f " + path)
	buf.Write(output)
	if err != nil {
		return nil,&DeployError{
			Err:    err,
			Output: buf.String(),
		}
	}

	session.Close()

	session, err = con.NewSession()
	if err != nil {
		return nil,err
	}
	defer session.Close()

	return output,nil
}

答案1

得分: 1

下面是翻译好的内容:

func download(con *ssh.Client, path string) ([]byte, error) {
	//https://chuacw.ath.cx/development/b/chuacw/archive/2019/02/04/how-the-scp-protocol-works.aspx
	session, err := con.NewSession()
	if err != nil {
		return nil, err
	}
	defer session.Close()

	// 本地 -> 远程
	stdin, err := session.StdinPipe()
	if err != nil {
		return nil, err
	}
	defer stdin.Close()

	// 请求一个文件,注意目录需要不同的处理方式
	_, err = stdin.Write([]byte("sudo scp -f " + path + "\n"))
	if err != nil {
		return nil, err
	}

	// 远程 -> 本地
	stdout, err := session.StdoutPipe()
	if err != nil {
		return nil, err
	}

	// 创建一个用于协议消息的缓冲区
	const megabyte = 1 << 20
	b := make([]byte, megabyte)

	// 缓冲区偏移量
	off := 0

	var filesize int64

	// SCP 可能会发送多个协议消息,因此需要持续读取
	for {
		n, err := stdout.Read(b[off:])
		if err != nil {
			return nil, err
		}

		nl := bytes.Index(b[:off+n], []byte("\n"))
		// 如果缓冲区中没有换行符,需要继续读取
		if nl == -1 {
			off = off + n
			continue
		}

		// 读取完整的消息后,重置偏移量
		off = 0

		// 如果有换行符,表示已经获取到完整的协议消息
		msg := string(b[:nl])

		// 发送 0,表示 OK,否则 SCP 源不会发送下一条消息
		_, err = stdin.Write([]byte("0\n"))
		if err != nil {
			return nil, err
		}

		// 第一个字符是模式(C=文件,D=目录,E=目录结束,T=时间元数据)
		mode := msg[0]
		if mode != 'C' {
			// 目前忽略其他消息
			continue
		}

		// 文件消息 = Cmmmm <length> <filename>
		msgParts := strings.Split(msg, " ")
		if len(msgParts) > 1 {
			// 将第二部分 <length> 解析为十进制整数
			filesize, err = strconv.ParseInt(msgParts[1], 10, 64)
			if err != nil {
				return nil, err
			}
		}

		// 文件消息后面是包含文件内容的二进制数据
		break
	}

	// 使用限制读取器将 stdout 读取器包装起来,以确保不会读取超过文件大小的内容
	fileReader := io.LimitReader(stdout, filesize)
	// 使用现有的字节切片初始化字节缓冲区,如果文件大小 <= 1MB,则节省额外的分配
	buf := bytes.NewBuffer(b)

	// 将文件复制到字节缓冲区中
	_, err = io.Copy(buf, fileReader)
	return buf.Bytes(), err
}
英文:

The sink side is significantly more difficult than the source side. Made an example which should get you close to what you want. Note that I have not tested this code, that the error handling is sub optimal and it only supports 1/4th the protocol messages SCP may use. So you will still need to do some work to get it perfect.

With all that said, this is what I came up with:

func download(con *ssh.Client, path string) ([]byte, error) {
	//https://chuacw.ath.cx/development/b/chuacw/archive/2019/02/04/how-the-scp-protocol-works.aspx
	session, err := con.NewSession()
	if err != nil {
		return nil, err
	}
	defer session.Close()

	// Local -&gt; remote
	stdin, err := session.StdinPipe()
	if err != nil {
		return nil, err
	}
	defer stdin.Close()

	// Request a file, note that directories will require different handling
	_, err = stdin.Write([]byte(&quot;sudo scp -f &quot; + path + &quot;\n&quot;))
	if err != nil {
		return nil, err
	}

	// Remote -&gt; local
	stdout, err := session.StdoutPipe()
	if err != nil {
		return nil, err
	}

	// Make a buffer for the protocol messages
	const megabyte = 1 &lt;&lt; 20
	b := make([]byte, megabyte)

	// Offset into the buffer
	off := 0

	var filesize int64

	// SCP may send multiple protocol messages, so keep reading
	for {
		n, err := stdout.Read(b[off:])
		if err != nil {
			return nil, err
		}

		nl := bytes.Index(b[:off+n], []byte(&quot;\n&quot;))
		// If there is no newline in the buffer, we need to read more
		if nl == -1 {
			off = off + n
			continue
		}

		// We read a full message, reset the offset
		off = 0

		// if we did get a new line. We have the full protocol message
		msg := string(b[:nl])

		// Send back 0, which means OK, the SCP source will not send the next message otherwise
		_, err = stdin.Write([]byte(&quot;0\n&quot;))
		if err != nil {
			return nil, err
		}

		// First char is the mode (C=file, D=dir, E=End of dir, T=Time metadata)
		mode := msg[0]
		if mode != &#39;C&#39; {
			// Ignore other messags for now.
			continue
		}

		// File message = Cmmmm &lt;length&gt; &lt;filename&gt;
		msgParts := strings.Split(msg, &quot; &quot;)
		if len(msgParts) &gt; 1 {
			// Parse the second part &lt;length&gt; as an base 10 integer
			filesize, err = strconv.ParseInt(msgParts[1], 10, 64)
			if err != nil {
				return nil, err
			}
		}

		// The file message will be followed with binary data containing the file
		break
	}

	// Wrap the stdout reader in a limit reader so we will not read more than the filesize
	fileReader := io.LimitReader(stdout, filesize)
	// Seed the bytes buffer with the existing byte slice, saves additional allocation if file &lt;= 1mb
	buf := bytes.NewBuffer(b)

	// Copy the file into the bytes buffer
	_, err = io.Copy(buf, fileReader)
	return buf.Bytes(), err
}

huangapple
  • 本文由 发表于 2021年11月30日 23:11:37
  • 转载请务必保留本文链接:https://go.coder-hub.com/70171643.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定