Processing user uploaded files is a common task in web development and it’s quite likely that you’ll need to develop a service that handles this task from time to time. This article will guide you through the process of handling file uploads on a Go web server and discuss common requirements such as multiple file uploads, progress reporting, and restricting file sizes.
In this tutorial, we will take a look at file uploads in Go and cover
common requirements such as setting size limits, file type restrictions, and
progress reporting. You can find the full source code for this tutorial on
GitHub.
Getting started
Clone this
repository to your
computer and cd into the created directory. You’ll see a main.go file which
contains the following code:
The code here is used to start a server on port 4500 and render the index.html
file on the root route. In the index.html file, we have a form containing a
file input which posts to an /upload route on the server.
Let’s go ahead and write the code we need to process file uploads from the
browser.
Set the maximum file size
It’s necessary to restrict the maximum size of file uploads to avoid a situation
where clients accidentally or maliciously upload gigantic files and end up
wasting server resources. In this section, we’ll set a maximum upload limit of
One Megabyte and show an error if the uploaded file is greater than the limit.
A common approach is to check the Content-Length request header and compare to
the maximum file size allowed to see if it’s exceeded or not.
ifr.ContentLength>MAX_UPLOAD_SIZE{http.Error(w,"The uploaded image is too big. Please use an image less than 1MB in size",http.StatusBadRequest)return}
I don’t recommended using this method because the Content-Length header can be
modified on the client to be any value regardless of the actual file size. It’s
better to rely on the
http.MaxBytesReader method
demonstrated below. Update your main.go file with the highlighted portion of
the following snippet:
The http.MaxBytesReader() method is used to limit the size of incoming request
bodies. For single file uploads, limiting the size of the request body provides
a good approximation of limiting the file size. The ParseMultipartForm()
method subsequently parses the request body as multipart/form-data up to the
max memory argument. If the uploaded file is larger than the argument to
ParseMultipartForm(), an error will occur.
Save the uploaded file
Next, let’s retrieve and save the uploaded file to the filesystem. Add the
highlighted portion of code snippet below to the end of the
uploadHandler() function:
Restrict the type of the uploaded file
Let’s say we want to limit the type of uploaded files to just images, and
specifically only JPEG and
PNG images. We need to detect the
MIME type of the
uploaded file and then compare it to the allowed MIME types to
determine if the server should continue processing the upload.
You can use the accept attribute in the file input to define the file types
that should be accepted, but you still need to double check on the server to
ensure that the input has not been tampered with. Add the highlighted portion of
the snippet below to the FileHandlerUpload function:
The DetectContentType() method is provided by the http package for the
purpose of detecting the content type of the given data. It considers (at most)
the first 512 bytes of data to determine the MIME type. This is why
we read the first 512 bytes of the file to an empty buffer before passing it to
the DetectContentType() method. If the resulting filetype is neither a
JPEG or PNG, an error is returned.
When we read the first 512 bytes of the uploaded file in order to determine the
content type, the underlying file stream pointer moves forward by 512 bytes.
When io.Copy() is called later, it continues reading from that position
resulting in a corrupted image file. The file.Seek() method is used to return
the pointer back to the start of the file so that io.Copy() starts from the
beginning.
Handle multiple files
If you want to handle the case where multiple files are being sent from the
client at once, you can manually parse and iterate over each file instead of
using FormFile(). After opening the file, the rest of the code is the same as
for single file uploads.
Report the upload progress
Next, let’s add progress reporting of the file upload. We can make use of the
io.TeeReader() method to count the number bytes read from an io.Reader (in
this case each file). Here’s how:
Conclusion
This wraps up our effort to handle file uploads in Go. Don’t forget to grab the
full source code for this tutorial on
GitHub. If you have any questions
or suggestions, feel free to leave a comment below.
Comments
Ground rules
Please keep your comments relevant to the topic, and respectful. I reserve the right to delete any comments that violate this rule. Feel free to request clarification, ask questions or submit feedback.