How to process file uploads in Go
This article will teach you how you can upload single or multiple files to a Golang web server, and show progress reporting on file uploads
Let’s jump into Go by working through a hands-on project together. This tutorial will introduce you to a few common Go concepts such as variabes, error handling, loops, packages, and more by showing you how to use them to tackle a classic beginner programming problem.
In this tutorial, we’ll build a “Guess the number” game in Go. In this game, the program will generate a random integer between 1 and 100, and then prompt the player to enter a guess.
After a guess is entered, the program will notify the player if the guess was higher or lower than the random number, and prompt the player for another guess. Otherwise, if the guess is correct, a congratulatory message will be printed on the screen and the program will exit.
The objective here is to give you your first bit of experience with writing a Go program, and for you to gain a basic understanding of what writing a Go program involves.
At the end of this tutorial, you should have a program that works in a similar manner to the demo below:
You need to have Go installed on your computer. You can visit the Go website to view installation instructions for your operating system.
Create a new directory anywhere on your filesystem, and change into it with the commands below:
$ mkdir guessing-game
$ cd guessing-game
Next, initialise your project with a go.mod
file. Replace <username>
with
your GitHub username.
$ go mod init github.com/<username>/guessing-game
Finally, create a main.go
file in your project root, and open it in your
favourite text editor. You will write all the code for this project in this
file.
$ vim main.go
As you can observe from the demo, when the program runs, a random integer between 1 and 100 is generated. The player is subsequently prompted to guess what number was generated and feedback is provided to the player if the guess is higher or lower than the secret number. If the guess is equal to the secret number, a congratulatory message is printed and the program exits.
Let’s start by generating a random number between 1 and 100 in the next section.
To generate a random number, we’ll be making use of the math/rand
package from
the standard library. Modify your main.go
file shown below:
Let’s go over the code line by line. The first step is to declare the package
that this file belongs to. In this case, it’s the main
package. The main
package is special in Go as it serves as the entry point for every executable
program.
import (
"fmt"
"math/rand"
)
To print text to the terminal, we need to bring the fmt
package into scope. It
provides several methods that we can leverage
to print the result of an operation to the standard output. We also need to
import the rand
package before we can use it. When importing more than one
package, you can place the package names inside parenthesis. This prevents you
from repeating the import
keyword on every line.
Note that the rand
package was imported as math/rand
. This is because it is
nested inside the math
package as a subdirectory. The convention in Go is that
package name is the same as the last element in the import path.
func main(){}
As discussed in the last article,
the main
function is the entry point to the program. It is called
automatically when you run the program.
min, max := 1, 100
This above statement represents a short variable declaration in Go and it can
only be used inside functions. Here, we’re creating two new variables (min
and
max
) and assigning the values 1 and 100 to them respectively. The min
and
max
variables represent the range within which the random number will be
generated.
secretNumber := rand.Intn(max-min) + min
Here’s where we generate a secret number within the min
and max
constraints.
The rand
package exports an Intn
method that returns a psuedo-random
positive number between 0 and its argument which should be a positive integer.
In this case, the argument is 99
(max
- min
), so the range of numbers that
can be generated will be between 0 and 98. min
is added to the output of
rand.Intn()
so as to change the range to 1…99 instead.
Note that the max
number is exclusive in the range while min
is inclusive.
If you wanted numbers between 1-100, you have to change the max
variable to
101
.
fmt.Println("The secret number is", secretNumber)
The fmt
package exposes a Println
method which prints its arguments to the
console and adds a newline at the end. If there are more than one arguments,
they are separated with a space.
You can view the documentation for packages in the standard library by using the godoc
command. Run this command in your terminal, and go to http://localhost:6060, then click the Packages link and scroll down until you find the package you want to investigate.
One great thing about this tool is that if you install a third-party package, you will be able to browse its documentation here too, offline and always available!
Save your main.go
file and run the program in the terminal using go run main.go
. You should get a similar output to the one shown below:
$ go run main.go
The secret number is 24
$ go run main.go
The secret number is 24
$ go run main.go
The secret number is 24
$ go run main.go
The secret number is 24
It appears that the same number is printed to the screen each time. This is definitely not what we want. But why is this happening? There’s a simple explanation.
The numbers that the rand
package generates are based on a specific initial
value called its seed. The default seed is 1, so unless you change it, you’ll
always get the same output. The seed needs to be something that’s unique and
always changing, so that when the program is run multiple times, you’ll get
better random values. A popular choice for this initial seed is the current time
in nanoseconds which will most probably be a different value on each execution.
Here’s how to change the initial seed of the rand
generator:
// note that `rand.Seed()` expects an `int64` type so make sure
// that whatever you pass into it produces an `int64` value.
// Also, it needs to be called before any other methods from the
// `rand` package
rand.Seed(time.Now().UnixNano())
Here’s how your main.go
file should look like after adding the code snippet
above:
Notice the introduction of the time
package amongst the imports. This is the
package that provides functionality for measuring and displaying time in Go. It
exports a Now()
method that returns the current time and the UnixNano()
method which returns the number of nanoseconds elapsed since January 1, 1970 UTC
as an int64
value.
Although method chaining is used here (time.Now().UnixNano()
), it’s not a pattern that you’ll see or use too often in Go due to the way errors are handled in the language.
Try running the program again. You should get different random numbers, and they should all be numbers between 1 and 100.
$ go run main.go
The secret number is 91
$ go run main.go
The secret number is 77
$ go run main.go
The secret number is 84
$ go run main.go
The secret number is 6
The next step is to allow the user to enter a guess and then check that it’s in the expected format (that is, it must be an integer).
Here’s the code that helps us achieve that:
Let’s go over each of the new additions so as to understand what they all do:
fmt.Println("Guess a number between 1 and 100")
fmt.Println("Please input your guess")
The above lines introduces the game and prompts the player to enter a guess.
The next step is to accept input from the
player. The bufio
and os
packages are what we’ll be using to capture the
user’s input. Notice how they’re both imported at the top of the file.
reader := bufio.NewReader(os.Stdin)
The above line of code declares a new variable called reader
and initialises
it to the return value of bufio.NewReader(os.Stdin)
.
The NewReader()
method of bufio
takes a value that implements the
io.Reader interface. os.Stdin
represents
an open File that points to the standard
input file descriptor, and also implements the io.Reader
interface which is
why we’re able to pass it as an argument to NewReader()
.
input, err := reader.ReadString('\n')
The NewReader()
method returns a bufio.Reader struct which has a method called ReadString()
which takes in a delimiter (the newline character \n
in this case). This method reads the user’s input until the first occurrence of the delimiter in the string, and returns two values: a string containing the data up to and including the delimiter, and an error (if any). The former is stored in the input
variable while the latter value (the error) stored in the err
variable.
In Go, functions can return multiple values (usually, a result and an error), and you have to account for each one when assigning variables based on a function’s return values.
if err != nil {
fmt.Println("An error occured while reading input. Please try again", err)
return
}
The above snippet handles the error that might occur when using the ReadString
method. This is the idiomatic way to handle errors in Go. Before using the
result of ReadString, the error value is checked to see if it’s not nil
. If
so, a message is printed to the screen and the main
function returns causing
the program to exit.
input = strings.TrimSuffix(input, "\n")
Assuming err
is nil, program execution will move to the next line shown above.
Remember that ReadString()
includes the delimiter in the result value. This
means that the input
will contain the newline character in addition to
whatever the player entered.
For example, if the player types 30 and presses Enter, the value of
input
will be 30\n. But we don’t want this /n
part in the input so we need
to get rid of it. This can be achieved using the TrimSuffix()
method from the
strings
package. All we need to do is pass the string and the trailing suffix.
It will then return the provided the string without the suffix, so 30\n
will
become just 30
.
Variables in Go are mutable, so you can reassign to them as shown above. The only thing that cannot change during reassignment is the type of the variable.
guess, err := strconv.Atoi(input)
The next step we need to convert the input
string to an integer so that we can
compare it numerically to the secretNumber
variable created in the last
section.
To convert a string to an integer in Go, The Atoi()
method from
the strconv
package is employed. This method attempts to convert the provided
string into an integer and returns an integer and an error which are assigned
to guess
and err
respectively.
if err != nil {
fmt.Println("Invalid input. Please enter an integer value")
return
}
The usage of Atoi()
can easily result in an error which must be handled
appropriately. For example, if the player’s input contained an alphabet, there
would be no way to convert it to an integer. Therefore we need to stop the
program and remind the player to provide an integer input.
fmt.Println("Your guess is", guess)
But if there is no error, the player’s guess will be printed on the screen.
Save the file and run the program using go run main.go
. You should get a
similar output to the one below:
$ go run main.go
45
Guess a number between 1 and 100
Please input your guess
20
Your guess is 20
Now that we have both the player’s guess, and the secret number, it’s time to compare them. We’ll provide feedback to the player if the guess is higher or lower than the secret number. If the guess is equal to the secret number, we’ll print out a congratulatory message and exit the program.
Add the following code to the end of your main
function below the line that prints the guess
variable:
Run the program a few times to verify that the code works as expected.
$ go run main.go
The secret number is 72
Guess a number between 1 and 100
Please input your guess
65
Your guess is 65
Your guess is smaller than the secret number. Try again
$ go run main.go
The secret number is 85
Guess a number between 1 and 100
Please input your guess
93
Your guess is 93
Your guess is bigger than the secret number. Try again
$ go run main.go
The secret number is 78
Guess a number between 1 and 100
Please input your guess
78
Your guess is 78
Correct, you Legend!
Our program mostly works but the player can only input one guess, and the program exits regardless of whether the guess is correct or not. Let’s change this behaviour so that the player is able to continue guessing until the correct number is guessed. We will also inform the player of the number of attempts made before winning the game.
Change your main
function as shown below:
We created a for
loop above, and moved all the code below the line that
prompts the user to input a guess into it.
In Go, using a for loop without a loop condition creates an infinite loop, and
the only way to break out of this sort of loop is by using the break
keyword
which we’re doing when the player guesses correctly. This also causes the
program to exit.
Notice the attempts
variable declaration just before the loop. It’s how we
keep track of the number of times a player had to guess before making a correct
guess. When game is won, the number of attempts is printed in the congratulatory
message.
Also note that the error handling from the Atoi()
and ReadString()
methods
have been changed slightly to use the continue
keyword in place of return
to
ensure that loop skips to its next iteration when an invalid input is received
instead of exiting.
If you run the program now, you’ll be able to play the game and enter several guesses without the program exiting.
$ go run main.go
The secret number is 20
Guess a number between 1 and 100
Please input your guess
26
Your guess is 26
Your guess is bigger than the secret number. Try again
Please input your guess
22
Your guess is 22
Your guess is bigger than the secret number. Try again
Please input your guess
20
Your guess is 20
Correct, you Legend! You guessed right after 3 attempts
Let’s wrap up the game by deleting the line that prints out the secretNumber
to the screen. The final code is shown below:
Congratulations! You’ve successfully created a guessing game in Go and learnt many new concepts such as variables, loops, functions, control flow, error handling and more along the way.
If you have any questions, please leave a comment below and I’ll get back to you. Thanks for reading!
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.