Голанг Ио.копировать дважды в тело запроса
Я создаю систему хранения blob, и я выбрал Go в качестве языка программирования. Я создаю поток для отправки многостраничного файла с клиента на blob-сервер.
поток работает нормально, но я хочу сделать хэш sha1 из тела запроса. Мне нужно Ио.Скопируйте тело дважды. Sha1 создается, но после этого составные потоки 0 байт.
- для создания хэш -
- для потоковой передачи тела как multipart
любой как я могу это сделать?
загрузка клиента
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
}
3 ответов
вы не можете сделать это напрямую, но вы можете написать обертку, которая делает хэширование на io.Копия
// 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
}
Я бы предложил использовать io.TeeReader
если вы хотите одновременно протолкнуть все чтения из blob через sha1.
bodyReader := io.TeeReader(body, hash)
теперь, когда bodyReader потребляется во время загрузки, хэш автоматически обновляется.
у вас есть два варианта.
самый прямой способ-использовать io.MultiWriter
.
но если вам нужен хэш для создания многостраничного вывода, вам придется скопировать в bytes.Buffer
а затем запишите буфер обратно каждому писателю.