请求正文中的Golang io.copy两次

Ant*_*ter 10 io multipart go

我正在构建一个blob存储系统,我选择了Go作为编程语言.我创建了一个流来执行从客户端到blob服务器的多部分文件上载.

流工作正常,但我想从请求正文中生成一个sha1哈希.我需要io.Copy身体两次.sha1被创建但是多部分在此之后流0字节.

  1. 用于创建哈希
  2. 用于将身体作为多部分流式传输

任何想法我怎么能这样做?

客户端上传

func (c *Client) Upload(h *UploadHandle) (*PutResult, error) {
body, bodySize, err := h.Read()
if err != nil {
    return nil, err
}

// Creating a sha1 hash from the bytes of body
dropRef, err := drop.Sha1FromReader(body)
if err != nil {
    return nil, err
}

bodyReader, bodyWriter := io.Pipe()
writer := multipart.NewWriter(bodyWriter)

errChan := make(chan error, 1)
go func() {
    defer bodyWriter.Close()
    part, err := writer.CreateFormFile(dropRef, dropRef)
    if err != nil {
        errChan <- err
        return
    }
    if _, err := io.Copy(part, body); err != nil {
        errChan <- err
        return
    }
    if err = writer.Close(); err != nil {
        errChan <- err
    }
}()

req, err := http.NewRequest("POST", c.Server+"/drops/upload", bodyReader)
req.Header.Add("Content-Type", writer.FormDataContentType())
resp, err := c.Do(req)
if err != nil {
    return nil, err
}
  .....
 }
Run Code Online (Sandbox Code Playgroud)

sha1功能

func Sha1FromReader(src io.Reader) (string, error) {
hash := sha1.New()
_, err := io.Copy(hash, src)
if err != nil {
    return "", err
}
return hex.EncodeToString(hash.Sum(nil)), nil
Run Code Online (Sandbox Code Playgroud)

}

上传句柄

func (h *UploadHandle) Read() (io.Reader, int64, error) {
var b bytes.Buffer

hw := &Hasher{&b, sha1.New()}
n, err := io.Copy(hw, h.Contents)

if err != nil {
    return nil, 0, err
}

return &b, n, nil
Run Code Online (Sandbox Code Playgroud)

}

Jim*_*imB 19

我建议使用一个io.TeeReader如果你想同时从blob推送所有读取通过sha1.

bodyReader := io.TeeReader(body, hash)
Run Code Online (Sandbox Code Playgroud)

现在,在上传期间使用bodyReader时,哈希会自动更新.


One*_*One 11

你不能直接这样做,但你可以编写一个在io.Copy上进行散列的包装器

// this works for either a reader or writer, 
//  but if you use both in the same time the hash will be wrong.
type Hasher struct {
    io.Writer
    io.Reader
    hash.Hash
    Size uint64
}

func (h *Hasher) Write(p []byte) (n int, err error) {
    n, err = h.Writer.Write(p)
    h.Hash.Write(p)
    h.Size += uint64(n)
    return
}

func (h *Hasher) Read(p []byte) (n int, err error) {
    n, err = h.Reader.Read(p)
    h.Hash.Write(p[:n]) //on error n is gonna be 0 so this is still safe.
    return
}

func (h *Hasher) Sum() string {
    return hex.EncodeToString(h.Hash.Sum(nil))
}

func (h *UploadHandle) Read() (io.Reader, string, int64, error) {
    var b bytes.Buffer

    hashedReader := &Hasher{Reader: h.Contents, Hash: sha1.New()}
    n, err := io.Copy(&b, hashedReader)

    if err != nil {
        return nil, "", 0, err
    }

    return &b, hashedReader.Sum(), n, nil
}
Run Code Online (Sandbox Code Playgroud)

//基于@ Dustin评论的更新版本,因为我完全忘记了io.TeeReader存在.

func (h *UploadHandle) Read() (io.Reader, string, int64, error) {
    var b bytes.Buffer

    hash := sha1.New()
    n, err := io.Copy(&b, io.TeeReader(h.Contents, hash))

    if err != nil {
        return nil, "", 0, err
    }

    return &b, hex.EncodeToString(hash.Sum(nil)), n, nil
}
Run Code Online (Sandbox Code Playgroud)

  • @fabrizioM哈希写入保证不会返回错误.城市广泛的6小时停电很有趣 (2认同)
  • 我发布了一个[游乐场链接](http://play.golang.org/p/0OqUUl3uNE),其机制更小,更便宜.(一半的代码是测试io.Reader只是为了产生一些输出).这段代码重新发明了`io.TeeReader`,但有一个更具体但更容易出错的API. (2认同)