Go Up or Go Down
An Efficient Guide To Downloading and Uploading Files in Golang
With Golang, we have a straightforward and efficient way of creating and managing web servers. The Golang standard library has an inbuilt support for handling HTTP requests and sending back responses. Also, Its efficient memory management and rich concurrency support makes it simple to manage web services in Golang.
This article will show you how to handle files in your server with Golang and at the end of the article, you would be able to upload files to a server and download the files. It's almost like building a mini cloud-storage application.
With a POST /upload
request, you will be able to upload a file to our server and with a GET /download
request you will download a file from the server.
Prerequisite
Before proceeding, it is expected that you have a basic understanding of the Golang programming language and you have Golang installed on your machine.
In addition to this, the article will also be using the Gorilla Mux package, a powerful URL router and dispatcher for Go web servers. Therefore, it is recommended that the reader has Gorilla Mux installed and a basic understanding of how it works. If you do not have Gorilla Mux installed, you can install it by running the following command in your terminal: go get -u
github.com/gorilla/mux
Set Up Server
We would create a server that would be served on port 8000, then listen for incoming requests and process them as they come in.
Create a
main.go
file and a main function.In the main function create a
server.http.Server{}
type which will allow you to define parameters for running an HTTP serverCall the
ListenAndServe()
method which fires up the server.
You would also need to provide a Handler to enable proper routing whenever a request hits the server, for that Gorilla Mux would be used.
r := mux.NewRouter()
port := "8000"
server := http.Server{
Addr: fmt.Sprintf(":%s", port),
Handler: r,
IdleTimeout: 120 * time.Second,
}
go func() {
log.Printf("---- SERVER STARTING ON PORT: %s ----\n", port)
err := server.ListenAndServe()
if err != nil {
log.Printf("ERROR STARTING SERVER: %v", err)
os.Exit(1)
}
}()
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, os.Kill)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
sig := <-sigChan
log.Printf("Closing now, We've gotten signal: %v", sig)
ctx := context.Background()
server.Shutdown(ctx)
The server is run on a different go-routine so the main go-routine can listen for signals, once a signal has been sent to the channel, we can then proceed to a graceful shutdown.
Handle Upload
Now that the server is up and running, a function to upload files to the server is the next thing. For this, a POST
request and a form-data
as the body will be used, you will configure the server to accept a name and a file, so when uploading the file, it saves with that particular name on the server.
To get started, create a cmd/routes.go
file and in the file create a HandleUpload
function which takes in a responseWriter
and Request
Parameters.
Set the content type to handle form data using
("Content-Type", "application/x-www-form-urlencoded")
Parse the form and set the maximum size.
// set content type
w.Header().Set("Content-Type", "application/x-www-form-urlencoded")
// set file size to 10MB max
err := r.ParseMultipartForm(10 << 20)
if err != nil {
log.Println(err)
w.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(w).Encode("Error parsing form")
return
}
Get the file name from the form
Get the file from the form
// get name of file
name := r.Form.Get("name")
if name == "" {
w.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(w).Encode("specify a file name please")
return
}
// get file
f, handler, err := r.FormFile("file")
if err != nil {
log.Println(err)
w.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(w).Encode("something went wrong")
return
}
defer f.Close()
Get the file extension
Create a filepath
Copy the file to the server
// get file extension
fileExtension := strings.ToLower(filepath.Ext(handler.Filename))
// create folders
path := filepath.Join(".", "files")
_ = os.MkdirAll(path, os.ModePerm)
fullPath := path + "/" + name + fileExtension
// open and copy files
file, err := os.OpenFile(fullPath, os.O_WRONLY|os.O_CREATE, os.ModePerm)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(w).Encode("something went wrong")
return
}
defer file.Close()
_, err = io.Copy(file, f)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(w).Encode("something went wrong")
return
}
The full function should look like this
func HandleUpload(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/x-www-form-urlencoded")
// set file size to 10MB max
err := r.ParseMultipartForm(10 << 20)
if err != nil {
log.Println(err)
w.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(w).Encode("Error parsing form")
return
}
// get name of file
name := r.Form.Get("name")
if name == "" {
w.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(w).Encode("specify a file name please")
return
}
// get file
f, handler, err := r.FormFile("file")
if err != nil {
log.Println(err)
w.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(w).Encode("something went wrong")
return
}
defer f.Close()
// get file extension
fileExtension := strings.ToLower(filepath.Ext(handler.Filename))
// create folders
path := filepath.Join(".", "files")
_ = os.MkdirAll(path, os.ModePerm)
fullPath := path + "/" + name + fileExtension
// open and copy files
file, err := os.OpenFile(fullPath, os.O_WRONLY|os.O_CREATE, os.ModePerm)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(w).Encode("something went wrong")
return
}
defer file.Close()
_, err = io.Copy(file, f)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(w).Encode("something went wrong")
return
}
// send response
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode("File uploaded successfully")
}
A sample curl request would look like
curl --location --request POST 'localhost:8000/upload' \
--form 'name="testImg1"' \
--form 'file=@"/location.jpg"'
Handle Download
Downloading a file, you have to know the name of the file, the user can input this through diffrent ways but for this particular article you will get the file name from the URL.
Once you have the file name, create a filepath
Check if the file exists on the server
Set the file extension and set the content-disposition to force a download.
Use the
http.ServeFile
Method is used to serve the file to the end user.
Setting the content-disposition to attachment; filename="+filepath.Base(directory)
means the response will be treated as a file for download and also suggesting a name for the downloaded file.
func HandleDownload(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
// get the file name to download from url
name := r.URL.Query().Get("name")
// join to get the full file path
directory := filepath.Join("files", name)
// open file (check if exists)
ff, err := os.Open(directory)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(w).Encode("Unable to open file ")
return
}
// force a download with the content- disposition field
w.Header().Set("Content-Disposition", "attachment; filename="+filepath.Base(directory))
// serve file out.
http.ServeFile(w, r, directory)
}
A sample curl request to hit this end point would be
curl --location --request GET 'localhost:8000/download?name=test.jpg' \
--header 'Content-Type: application/json'
'
Summary
At the end of this article you should be able to:
Start up a server in Golang
Upload a file to a server
Download a file from a server
That concludes it! Thank you for taking the time to read this, and I hope you found it enjoyable. If you have any questions or suggestions, please feel free to leave them in the comment section below or reach out to me.
Wishing you a pleasant day! ๐
Resources
GitHub Repository: https://github.com/babafemi99/up-I-go
Official Golang Documentation : https://go.dev/doc/
Gorilla Mux Library : github.com/gorilla/mux