Skip to content

Instantly share code, notes, and snippets.

@williamlsh
Last active February 12, 2019 13:21
Show Gist options
  • Save williamlsh/a4531c9d9bea4acea5d2868b3e367313 to your computer and use it in GitHub Desktop.
Save williamlsh/a4531c9d9bea4acea5d2868b3e367313 to your computer and use it in GitHub Desktop.
// Reference: https://www.reddit.com/r/golang/comments/apf6l5/multiple_files_upload_using_gos_standard_library/
//
// Original author: https://www.reddit.com/user/teizz
//
// handles multiple files being uploaded
// reads them in blocks of 4K
// writes them to a temporary file in $TMPDIR
// calculates and logs the SHA256 sum
// proceeds to remove the temporary file through a defer statement
package main
import (
"crypto/sha256"
"io"
"io/ioutil"
"log"
"mime/multipart"
"net/http"
"os"
)
var indexPage = `
<html>
<body>
<form
enctype="multipart/form-data"
action="http://localhost:8080/upload"
method="post"
>
<input type="file" name="files" multiple />
<input type="submit" value="upload" />
</form>
</body>
</html>
`
func indexHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte(indexPage))
}
func uploadHandler(w http.ResponseWriter, r *http.Request) {
// Function scope variables.
var (
fileSize int // bytes number of uploaded files
p *multipart.Part // for getting file info at end
err error
)
mr, err := r.MultipartReader()
if err != nil {
log.Printf("Hit error while opening multipart reader: %s", err.Error())
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// buffer to be used for reading bytes from files.
chunk := make([]byte, 4096) // 4k size byte slice
tempDir := os.TempDir() // temp dir for chunk files
tempFile, err := ioutil.TempFile(tempDir, "example-temp-file")
if err != nil {
log.Printf("Hit error while creating temp file: %s", err.Error())
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer os.Remove(tempFile.Name())
defer tempFile.Close()
// continue looping through all parts, *multipart.Reader.NextPart() will
// return an End of File when all parts have been read.
for {
p, err = mr.NextPart()
if err == io.EOF {
// err is io.EOF, files upload completes.
log.Printf("Hit last part of multipart upload")
w.WriteHeader(http.StatusOK)
w.Write([]byte("Files upload complete"))
break
}
if err != nil {
// A normal error occurred
log.Printf("Hit error while fetching next part: %s", err.Error())
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// continue reading the part stream of this loop until either done or err.
for {
n, err := p.Read(chunk)
if err == io.EOF {
break
}
if err != nil {
log.Printf("Hit error while reading chunk: %s", err.Error())
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if _, err = tempFile.Write(chunk[:n]); err != nil {
log.Printf("Hit error while writing chunk: %s", err.Error())
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
fileSize += n
log.Printf("Uploaded filesize: %d bytes\n", fileSize)
// Log file sum.
sum(tempFile)
}
}
// at this point the filename and the mimetype is known
log.Printf("Uploaded filename: %s\n", p.FileName())
log.Printf("Uploaded mimetype: %s\n", p.Header)
}
func sum(f *os.File) {
if n, err := f.Seek(0, 0); err != nil || n != 0 {
log.Printf("unable to seek to beginning of file: %q\n", f.Name())
h := sha256.New()
if _, err := io.Copy(h, f); err != nil {
log.Printf("unable to hash %q: %s\n", f.Name(), err.Error())
return
}
log.Printf("SHA256 sum of %q: %x\n", f.Name(), h.Sum(nil))
}
defer f.Truncate(0)
}
func main() {
log.Println("Gopher files upload service started!")
http.HandleFunc("/", indexHandler)
http.HandleFunc("/upload", uploadHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment