We’ve already covered basic downloading of files - this post goes beyond that to create a more complete downloader by including progress reporting of the download. This means if you’re pulling down large files you are able to see how the download’s going.
In our basic example we pass the response body into io.Copy() but if we use a TeeReader we can pass our counter to keep track of the progress.
We also save the file as a temporary file while downloading so we don’t overwrite a valid file until the file is fully downloaded.
packagemainimport("fmt""io""net/http""os""strings""github.com/dustin/go-humanize")// WriteCounter counts the number of bytes written to it. It implements to the io.Writer interface
// and we can pass this into io.TeeReader() which will report progress on each write cycle.
typeWriteCounterstruct{Totaluint64}func(wc*WriteCounter)Write(p[]byte)(int,error){n:=len(p)wc.Total+=uint64(n)wc.PrintProgress()returnn,nil}func(wcWriteCounter)PrintProgress(){// Clear the line by using a character return to go back to the start and remove
// the remaining characters by filling it with spaces
fmt.Printf("\r%s",strings.Repeat(" ",35))// Return again and print current status of download
// We use the humanize package to print the bytes in a meaningful way (e.g. 10 MB)
fmt.Printf("\rDownloading... %s complete",humanize.Bytes(wc.Total))}funcmain(){fmt.Println("Download Started")fileUrl:="/wikipedia/commons/d/d6/Wp-w4-big.jpg"err:=DownloadFile("avatar.jpg",fileUrl)iferr!=nil{panic(err)}fmt.Println("Download Finished")}// DownloadFile will download a url to a local file. It's efficient because it will
// write as it downloads and not load the whole file into memory. We pass an io.TeeReader
// into Copy() to report progress on the download.
funcDownloadFile(filepathstring,urlstring)error{// Create the file, but give it a tmp file extension, this means we won't overwrite a
// file until it's downloaded, but we'll remove the tmp extension once downloaded.
out,err:=os.Create(filepath+".tmp")iferr!=nil{returnerr}// Get the data
resp,err:=http.Get(url)iferr!=nil{out.Close()returnerr}deferresp.Body.Close()// Create our progress reporter and pass it to be used alongside our writer
counter:=&WriteCounter{}if_,err=io.Copy(out,io.TeeReader(resp.Body,counter));err!=nil{out.Close()returnerr}// The progress use the same line so print a new line once it's finished downloading
fmt.Print("\n")// Close the file without defer so it can happen before Rename()
out.Close()iferr=os.Rename(filepath+".tmp",filepath);err!=nil{returnerr}returnnil}
This will output:
1
2
3
4
$ go run download-file.go
Download Started
Downloading... 111 MB complete
Download Finished
And here’s what it looks like:
Related Posts
URL Encode a String
–
If you are coming from a PHP background you’re probably very used to functions like urlencode() and rawurlencode(). The good news is you can do the same in Go and rather simply too. In the net/url package there’s a QueryEscape function which accepts a string and will return the string with all the special characters encoded so they can be added to a url safely. An example of is converting the ‘+’ character into %2B.
Mock S3 Uploads in Go Tests
–
A common scenario a back-end web developer might encounter is writing code which uploads a file to an external storage platform, like S3 or Azure. This is simple enough, but writing tests for this code which are isolated from the dependencies isn’t quite as straight forward. We can achieve this in Go through the use of interfaces and creating a “mock” uploader when our tests run.
Below we’ve build an example to show this, first showing the test and then the code it’s testing.
Read a File to String
–
This is a matching post to “Writing to a File” and explains how to simply get the contents of a file as text and print it to screen.
There are different ways to achieve this in Go - all valid. In this guide though we’ve gone for the simple approach. Using ioutil makes this easy for us by not having to worry about closing files or using buffers. At the cost though of not having flexibility over which parts of the file we need.
Edd is the Lead Developer at Hoowla, a prop-tech startup, where he spends much of his time working on production-ready Go and PHP code. He loves coding, but also enjoys cycling and camping in his spare time.
See something which isn't right? You can contribute to this page on GitHub or just let us know in the comments below - Thanks for reading!