Upload Files
Apart from receiving data input in the form of forms and query params, we usually also need data input in the form of files from our users. Golang actually already has this feature to handle file upload management. This makes it easier for us if we create a website that can accept file input.
When we want to receive an uploaded file, we need to parse it first using Request.ParseMultipartFrom(size)
or we can retrieve the file data using Request.FormFile(name)
which automatically parses it first. The data contained in the multipart
package such as multipart.File
as the file representation and multipart.FileHeader
as the file information.
Example of File Upload Implementation
Create an HTML template like the one below with the file name upload.form.html
.
<!DOCTYPE html>
<html>
<head>
<title>Form</title>
</head>
<body>
<form action="/upload" method="post" enctype="multipart/form-data">
<label>Name : <input type="text" name="name"></label><br>
<label>File : <input type="file" name="file"></label><br>
<input type="submit" value="Upload">
</form>
</body>
</html>
After that, we create an upload handler form page like the one below.
func UploadFormHandler(w http.ResponseWriter, r *http.Request) {
err := myTemplates.ExecuteTemplate(w, "upload.form.html", nil)
if err != nil {
panic(err)
}
}
And add the handler function to the mux
router so that the page is registered.
mux.HandleFunc("/upload-form", UploadFormHandler)
Do a build
again and run the program and it should appear as shown below.
Next, we have to create a handle function to capture POS
, which is the process that will save the file uploaded by the user.
func UploadHandler(w http.ResponseWriter, r *http.Request) {
file, fileHeader, err := r.FormFile("file")
if err != nil {
panic(err)
}
fileDestination, err := os.Create("./resources/" + fileHeader.Filename)
if err != nil {
panic(err)
}
_, err = io.Copy(fileDestination, file)
if err != nil {
panic(err)
}
name := r.PostFormValue("name")
myTemplates.ExecuteTemplate(w, "upload.success.html", map[string]interface{}{
"Name": name,
"File": "/static/" + fileHeader.Filename,
})
}
Make sure our handler is registered on the mux
router.
mux.HandleFunc("/upload", UploadHandler)
After that, we also need to create a page template. After the upload action, it will return to the success page for creating the upload.success.html
file as below.
<!DOCTYPE html>
<html>
<head>
<title>Success {{.Name}}</title>
</head>
<body>
<h1>{{ .Name }}</h1>
<a href="{{ .File }}">File</a>
</body>
</html>
In the previous tutorial here, we set the files in the resources
folder to be read on the /static
path so that we can access the files in the resources
folder by accessing the path page on a website like this.
http://localhost:8080/static/<nama-file>
Run the program then we try to upload any file to carry out testing as below.
The display after a successful upload is as below.
This is what it looks like when the file has been opened because the path is static
and directed to the resources
folder.
Create Unit Test Upload
How do I test the upload handler function? Here’s how we create unit tests when we create the upload handler.
//go:embed resources/tutorial-golang.webp
var uploadFileTest []byte
func TestUploadHandler(t *testing.T) {
type args struct {
name string
}
tests := []struct {
name string
args args
want string
}{
{
name: "success",
args: args{
name: "success upload",
},
want: "<!DOCTYPE html>\n<html>\n <head>\n <title>Success success upload</title>\n </head>\n <body>\n <h1>success upload</h1>\n <a href=\"/static/contoh-upload.jpg\">File</a>\n </body>\n</html>",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
body := new(bytes.Buffer)
writer := multipart.NewWriter(body)
writer.WriteField("name", tt.args.name)
file, _ := writer.CreateFormFile("file", "contoh-upload.jpg")
file.Write(uploadFileTest)
writer.Close()
request := httptest.NewRequest(http.MethodPost, "http://localhost/upload", body)
request.Header.Set("Content-Type", writer.FormDataContentType())
recorder := httptest.NewRecorder()
UploadHandler(recorder, request)
bodyResponse, _ := io.ReadAll(recorder.Result().Body)
bodyString := string(bodyResponse)
if !reflect.DeepEqual(bodyString, tt.want) {
t.Errorf("response = %#v, want = %#v\n", bodyString, tt.want)
}
})
}
}