2014-09-04 10 views
8

Sto costruendo un sistema di archiviazione BLOB e ho scelto Go come linguaggio di programmazione. Creo uno stream per eseguire un caricamento di file multipart dal client al server BLOB.Golang io.copy due volte sul corpo della richiesta

Lo stream funziona bene, ma voglio fare un hash sha1 dal corpo della richiesta. Ho bisogno di IO. Coprire il corpo due volte. Lo sha1 viene creato ma i multipart streams 0 byte dopo di ciò.

  1. Per creare l'hash
  2. per lo streaming il corpo come multipart

qualche idea di come posso fare questo?

l'upload cliente

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

lo SHA1 gestire 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 

}

Upload

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 

}

+0

Ho fatto in modo che funzionasse con @OneofOne ho postato il risultato finale più tardi quando ho ripulito il pasticcio che ho fatto –

+0

Non hai davvero bisogno di tanto codice e sicuramente non hai bisogno di memorizzare un'intera copia contigua di il tuo blob nella RAM. Ho fatto una cosa simile con blob multi-GB su dispositivi affamati di RAM senza problemi. – Dustin

+0

@Dustin cosa faresti in questo caso? –

risposta

11

Non si può farlo direttamente, ma si può scrivere un wrapper che fa il hashing sulla 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 
} 

// versione aggiornata sulla base di @ commento di Dustin da quando ho completare dimenticato io.TeeReader esistesse.

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 
} 
+0

e aggiungere il controllo degli errori – fabrizioM

+2

@fabrizioM Le scritture hash garantite non restituiscono errori. I blackout da 6 ore in tutta la città sono divertenti – OneOfOne

+0

qualche idea su come implementarli? Ho aggiunto la funzione di lettura uploadhandle –

1

Hai due opzioni.

Il modo più diretto è utilizzare io.MultiWriter.

Ma se è necessario l'hash per produrre l'output multipart, sarà necessario copiare su un bytes.Buffer e quindi scrivere il buffer su ciascun writer.

18

Suggerirei di utilizzare uno io.TeeReader se si desidera trasferire contemporaneamente tutte le letture dal BLOB attraverso lo sha1.

bodyReader := io.TeeReader(body, hash) 

Ora, come il bodyReader viene consumato durante il caricamento, l'hash viene aggiornato automaticamente.