This is the backend implementation of chunk file upload under golang platform. The backend system is only implemented. The philosophy behind is it attempted to upload everything in a single request. So, with the chunking strategy, they processed fixed-size chunk requests until all of the file parts are uploaded.
File Chunk data are transmitted from the client-side with the headers like content-type, content-range, content expiry etc, and file form data (filename, filetype). Therefore each chunk is processed by the server to get uploaded on the desired condition.
Go lang code
package main
import (
"chunk-file-upload/controller"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
)
func main() {
httpRouter := gin.Default()
httpRouter.Use(cors.New(cors.Config{
AllowOrigins: []string{"*"},
AllowMethods: []string{"PUT", "PATCH", "GET", "POST", "OPTIONS", "DELETE"},
AllowHeaders: []string{"*"},
AllowCredentials: true,
}))
httpRouter.POST("/chunk-upload", controller.ChunkUploadHandler)
httpRouter.Run(":8080")
}
package controller
import (
"io"
"strconv"
"strings"
"net/http"
"os"
"github.com/gin-gonic/gin"
)
// Input -> add binding model for input
type Input struct {
Model string `form:"model,omitempty" binding:"required"`
}
// Response -> response for the util scope
type Response struct {
Success bool `json:"success"`
Message string `json:"message"`
Name string `json:"name"`
}
func ChunkUploadHandler(c *gin.Context) {
var f *os.File
file, uploadFile, err := c.Request.FormFile("file")
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "content-type should be multipart/formdata"})
return
}
contentRangeHeader := c.Request.Header.Get("Content-Range")
rangeAndSize := strings.Split(contentRangeHeader, "/")
rangeParts := strings.Split(rangeAndSize[0], "-")
rangeMax, err := strconv.Atoi(rangeParts[1])
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Missing range in Content-Range header"})
return
}
fileSize, err := strconv.Atoi(rangeAndSize[1])
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Missing file size in Content-Range header"})
return
}
if fileSize > 100*1024*1024 {
c.JSON(http.StatusBadRequest, gin.H{"error": "File size should be less than 100MB"})
return
}
var input Input
err = c.ShouldBind(&input)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Missing model name in header"})
return
}
tempDir, err := os.UserHomeDir()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Error resolving home directory"})
return
}
if _, err := os.Stat(tempDir + "/" + input.Model); os.IsNotExist(err) {
err := os.Mkdir(tempDir+"/"+input.Model, 0777)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Error creating temporary directory"})
return
}
}
if f == nil {
f, err = os.OpenFile(tempDir+"/"+input.Model+"/"+uploadFile.Filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Error creating file"})
return
}
}
if _, err := io.Copy(f, file); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Error writing to a file"})
return
}
f.Close()
if rangeMax >= fileSize-1 {
combinedFile := tempDir + "/" + input.Model + "/" + uploadFile.Filename
uploadingFile, err := os.Open(combinedFile)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Failed to upload file "})
return
}
uploadingFile.Close()
response := &Response{
Success: true,
Message: "Uploaded Successfully",
Name: uploadFile.Filename,
}
c.JSON(http.StatusOK, gin.H{"data": response})
return
}
c.JSON(http.StatusOK, gin.H{"status": "uploading"})
}