Golang transmit files over a net/http server to clients

July 24, 2016

In this example I'll show you how to send a file from a net/http listener to a client. 

Update: resume functionality!

Check the updated version of this post.

The example

The example starts a web-server at port 80, you can access it locally in your browser by "http://localhost/" or "127.0.01".

If you want to test the example put some dummy files in the same folder as the compiled binary. 

We send our request with a GET parameter in the URL, the key is 'file' and the value should be the file you want to download.

E.g.

http://127.0.0.1/?file=IWantToDownloadThisFile.zip

The code

Everything is explained in the comments.

package main
import (
	"fmt"
	"io"
	"net/http"
	"os"
	"strconv"
)
func main() {
	fmt.Println("Starting http file sever")
	http.HandleFunc("/", HandleClient)
	err := http.ListenAndServe(":80", nil)
	if err != nil {
		fmt.Println(err)
	}
}
func HandleClient(writer http.ResponseWriter, request *http.Request) {
	//First of check if Get is set in the URL
	Filename := request.URL.Query().Get("file")
	if Filename == "" {
		//Get not set, send a 400 bad request
		http.Error(writer, "Get 'file' not specified in url.", 400)
		return
	}
	fmt.Println("Client requests: " + Filename)
	//Check if file exists and open
	Openfile, err := os.Open(Filename)
	defer Openfile.Close() //Close after function return
	if err != nil {
		//File not found, send 404
		http.Error(writer, "File not found.", 404)
		return
	}
	//File is found, create and send the correct headers
	//Get the Content-Type of the file
	//Create a buffer to store the header of the file in
	FileHeader := make([]byte, 512)
	//Copy the headers into the FileHeader buffer
	Openfile.Read(FileHeader)
	//Get content type of file
	FileContentType := http.DetectContentType(FileHeader)
	//Get the file size
	FileStat, _ := Openfile.Stat()                     //Get info from file
	FileSize := strconv.FormatInt(FileStat.Size(), 10) //Get file size as a string
	//Send the headers
	writer.Header().Set("Content-Disposition", "attachment; filename="+Filename)
	writer.Header().Set("Content-Type", FileContentType)
	writer.Header().Set("Content-Length", FileSize)
	//Send the file
	//We read 512 bytes from the file already, so we reset the offset back to 0
	Openfile.Seek(0, 0)
	io.Copy(writer, Openfile) //'Copy' the file to the client
	return
}

Do I have to send headers?

Yes, at the very least "Content-Disposition". In some older browsers if you don't specify a binary file as "attachment;" it will print out the binary content on the screen instead of saving it to the hard disk.

Specifying "attachment;" on an image or picture will make the browser download the file instead of showing it on the screen.

Also if you don't specify the "filename", chrome for example, would save the file as "download".

According the w3 HTTP protocol1 you must declare the "Content-Type".

Any HTTP/1.1 message containing an entity-body SHOULD include a Content-Type header field defining the media type of that body. If and only if the media type is not given by a Content-Type field, the recipient MAY attempt to guess the media type via inspection of its content and/or the name extension(s) of the URI used to identify the resource. If the media type remains unknown, the recipient SHOULD treat it as type "application/octet-stream".

If you don't pass the "Content-Length" header, the clients won't be able to see the progress of the download, and the browser will keep reading until the server closes connection, and in this case the browser wont be able to tell if the file was completely transmitted.

Comments
References
1
HTTP/1.1: Entity

https://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html#sec7.2.1

cached copy