Golang : gin tutorial - 4 (Create a multipart/form-data POST request)
In the previous article we saw how exactly to create a POST
request which consumes a request body. In this article we will see how to consume a multipart/form-data
request using golang's gin-gonic and also how to write unit tests for it.
Step 1
Create controller function in the controller.go
file which will consume the multipart/form-data
request and read contents of the file.
The below controller shows how to Open a received file from the request, create a temp file and write data from the received file to it, and also close and delete the files which were created after exiting the controller function.
The controller function just returns the file contents.
func ConsumeFile(ctx *gin.Context) {
fileHeader, err := ctx.FormFile("file")
if err != nil {
ctx.Error(err)
return
}
//Open received file
csvFileToImport, err := fileHeader.Open()
if err != nil {
ctx.Error(err)
return
}
defer csvFileToImport.Close()
//Create temp file
tempFile, err := ioutil.TempFile("", fileHeader.Filename)
if err != nil {
ctx.Error(err)
return
}
defer tempFile.Close()
//Delete temp file after importing
defer os.Remove(tempFile.Name())
//Write data from received file to temp file
fileBytes, err := ioutil.ReadAll(csvFileToImport)
if err != nil {
ctx.Error(err)
return
}
_, err = tempFile.Write(fileBytes)
if err != nil {
ctx.Error(err)
return
}
ctx.JSON(http.StatusOK, string(fileBytes))
}
Step 2
Register the above created consume file controller function to gin as an endpoint as it was did before in the previous article in the main.go
file.
v1 := engine.Group("/api/v1")
{
v1.GET("/contents", content.GetContents)
v1.POST("/contents", content.PostContents)
v1.POST("/contents/import", content.ConsumeFile)
}
Step 3
Now, the next step is to write tests for the new multipart/form-data
endpoint. So, for that we need a file sample_success.txt
in a directory with any name and for the sake of an example there is a folder with the name txt
created. Now the file sample_success.txt
can be used in tests to check if the above created controller function works as expected.
Contents of the sample_success.txt
is set
some random content text
func TestConsumeFile_success(t *testing.T) {
//Create multipart request
body := new(bytes.Buffer)
multipartWriter := multipart.NewWriter(body)
//Create multipart header
fileHeader := make(textproto.MIMEHeader)
fileHeader.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s"`, "file", "sample_success.csv"))
fileHeader.Set("Content-Type", "text/plain")
writer, err := multipartWriter.CreatePart(fileHeader)
assert.Nil(t, err)
//Copy file to file multipart writer
file, err := os.Open("txt/sample_success.txt")
assert.Nil(t, err)
io.Copy(writer, file)
// close the writer before making the request
multipartWriter.Close()
w := httptest.NewRecorder()
ctx, r := gin.CreateTestContext(w)
r.POST("/content/import", ConsumeFile)
ctx.Request = httptest.NewRequest(http.MethodPost, "/content/import", body)
ctx.Request.Header.Add("Content-Type", multipartWriter.FormDataContentType())
r.ServeHTTP(w, ctx.Request)
assert.Equal(t, http.StatusOK, w.Code)
}
Step 4
Now, it is time to run the main.go
file to start the application and hit the endpoint http://localhost:8080/api/v1/contents/import
Example request using postman application.
If the endpoint is accessed via cUrl
then the command would look something like this
curl --location --request POST 'http://localhost:8080/api/v1/contents/import' \
--form 'file=@"/filePathToTheProject/txt/sample_success.txt"'
Subscribe to my newsletter
Read articles from Deepak directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by