2014-09-04 10 views
8

Ich bin ein Blob Storage-System bauen und ich wählte Go als die Programmiersprache. Ich erstelle einen Stream, um einen mehrteiligen Datei-Upload vom Client zum Blob-Server durchzuführen.Golang io.copy zweimal auf der Anfrage Körper

Der Stream funktioniert gut, aber ich möchte einen sha1 Hash aus dem Anfragetext machen. Ich muss io.Copy den Körper zweimal. Der sha1 wird erstellt, aber die multipart Streams 0 Bytes danach.

  1. Für die Erstellung der Hash-
  2. Für den Körper als Multipart-Streaming

eine Ahnung, wie ich das tun kann?

der Client-Upload

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

die 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 

}

Upload Griff

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

Ich habe es gemacht, thx zu @OneofOne zu arbeiten ich poste das Endergebnis später, als ich das Durcheinander aufräumte, machte ich –

+0

Sie brauchen wirklich nicht so viel Code und Sie müssen sicher nicht eine ganze zusammenhängende Kopie von speichern dein Blob im RAM. Ich habe eine ähnliche Sache mit Multi-GB Blobs auf RAM ausgehungert Geräte ohne Problem gemacht. – Dustin

+0

@Dustin, was würdest du in diesem Fall tun? –

Antwort

11

Sie können das nicht direkt tun, aber Sie können einen Wrapper schreiben, die die Hashing auf io.Copy tut

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

// aktualisierte Version basiert auf @ Dustin Kommentar, da ich vergessen haben, komplett io.TeeReader existierte.

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

zu behandeln und Fehlerprüfung – fabrizioM

+2

@ FabrizioM Hash-Schreibvorgänge sind garantiert, keine Fehler zurückgeben. Stadtweite 6 Stunden Blackouts machen Spaß – OneOfOne

+0

eine Idee wie man das umsetzt? Ich habe den uploadhandle read func hinzugefügt –

1

Sie haben zwei Möglichkeiten.

Der direkteste Weg ist die Verwendung io.MultiWriter.

Aber wenn Sie den Hash benötigen, um die mehrteilige Ausgabe zu produzieren, dann müssen Sie zu einem bytes.Buffer kopieren und dann den Puffer zurück zu jedem Schreiber schreiben.

18

Ich würde vorschlagen, eine io.TeeReader, wenn Sie alle Lesevorgänge aus dem Blob durch die sha1 gleichzeitig drücken möchten.

bodyReader := io.TeeReader(body, hash) 

Jetzt, da der bodyReader während des Uploads verbraucht wird, wird der Hash automatisch aktualisiert.