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

时间:2014-09-04 17:10:26

标签: io go multipart

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

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

  1. 用于创建哈希
  2. 用于将身体作为多部分进行流式传输
  3. 任何想法我怎么能这样做?

    客户端上传

    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
    }
      .....
     }
    

    sha1 func

    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
    

    }

    上传句柄

    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
    

    }

5 个答案:

答案 0 :(得分:19)

如果你想同时将所有读取从blob推送到sha1,我建议使用io.TeeReader

bodyReader := io.TeeReader(body, hash)

现在,在上传过程中消耗了bodyReader,哈希值会自动更新。

答案 1 :(得分: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
}

//基于@ 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
}

答案 2 :(得分:1)

您有两种选择。

最直接的方法是使用io.MultiWriter

但是如果你需要哈希来产生多部分输出,那么你将不得不复制到bytes.Buffer,然后将缓冲区写回每个编写器。

答案 3 :(得分:0)

我们可以将流转换为字符串,然后根据需要多次创建。 例如

readerStream := your stream from source

buf := new(bytes.Buffer)
buf.ReadFrom(readerStream)
rawBody := buf.String()

newReader1 := strings.NewReader(rawBody)
newReader2 := strings.NewReader(rawBody)

但是,如果可以避免的话,那就太好了。

我不确定这是最好的方法。但这对我有用。

答案 4 :(得分:-1)

您可以使用Request.GetBody

package main

import (
   "io"
   "net/http"
   "os"
   "strings"
)

func main() {
   read := strings.NewReader("north east south west")
   req, e := http.NewRequest("GET", "https://stackoverflow.com", read)
   if e != nil {
      panic(e)
   }
   // one
   io.Copy(os.Stdout, req.Body)
   // two
   body, e := req.GetBody()
   if e != nil {
      panic(e)
   }
   io.Copy(os.Stdout, body)
}

https://golang.org/pkg/net/http#Request.GetBody