Skip to main content

Creating a web server with Golang

Creating a web server with Golang

Golang is a great language for creating simple yet efficient web servers and web services. It provides a built-in HTTP package that contains utilities for quickly creating a web or file server.

The goal of this tutorial is to create a web server that can accept a GET request and serve a response. We’ll use the server to serve static files, acting as a file server. We’ll then make the web server respond to a POST request coming from a form submission, such as a contact form.

Without further ado, let’s explore how to build your first web server with Golang.

Setup

You’ll need Go version 1.11 or higher to follow this tutorial.

In this section, we’ll create all the necessary files and establish the correct file structure. After that, we’ll import our packages to test whether the setup works. Don’t worry — the setup is very basic for the Golang web server.

Create the following files and folders according to the structure below. The file server.go sits at the root of your project, as does the static folder, which contains two HTML files: index.htmland form.html.

- server.go
- static/
- - index.html
- - form.html

Now let’s write some code. Open the server.go file and import the required packages. We’ll use fmt to print useful data to the terminal and log to print fatal errors in case the web server crashes.

The net/http is the most important package. It provides all the functionality for creating an HTTP client or server implementation such as a Golang web server.

package main

import (
    "fmt"
    "log"
    "net/http"
)

Lastly, let’s add a simple main() function in the server.go file that prints a message to the terminal.

func main() {
    fmt.Printf("Starting server at port 8080\n")
}

To test the setup, start the fictive server with the following command.

go run server.go

If you followed along with the setup, you should see the following output in your terminal.

Starting server at port 8080

If all looks good, the next step is to create a web server.

Starting a web server with GET routes

At this stage, we’ll create a web server that is actually served on port 8080 and can respond to incoming GET requests.

Let’s modify the code in our main() function to start a web server at port 8080. The ListenAndServe method is exported by the http packet we imported during step one. This method allows us to start the web server and specify the port to listen for incoming requests.

Note that the port parameter needs to be passed as a string prepended by colon punctuation. The second parameter accepts a handler to configure the server for HTTP/2. However, this isn’t important for this tutorial, so we can safely pass nil as the second argument.

 func main() {
    fmt.Printf("Starting server at port 8080\n")
    if err := http.ListenAndServe(":8080", nil); err != nil {
        log.Fatal(err)
    }
}

At this point, the server can start, but it still doesn’t know how to handle requests. We need to pass handlers to the server so it knows how to respond to incoming requests and which requests to accept.

We’ll use the HandleFunc function to add route handlers to the web server. The first argument accepts the path it needs to listen for /hello. Here, you tell the server to listen for any incoming requests for http://localhost:8080/hello. The second argument accepts a function that holds the business logic to correctly respond to the request.

By default, this function accepts a ResponseWriter to send a response back and a Request object that provides more information about the request itself. For example, you can access information about the sent headers, which can be useful for authenticating the request.

func main() {
    http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request){
        fmt.Fprintf(w, "Hello!")
    })


    fmt.Printf("Starting server at port 8080\n")
    if err := http.ListenAndServe(":8080", nil); err != nil {
        log.Fatal(err)
    }
}

As you can see, the handler sends a Hello!” message as we pass this response to the ResponseWriter.

Now let’s try out this setup. Start the web server with go run server.go and visit http://localhost:8000/hello. If the server responds with "Hello!", you can continue to the next step, where you’ll learn how to add basic security to your Golang web server routes.

Add basic security to routes

It goes without saying that security is important. Let’s explore some basic strategies to enhance the security of your Go web server.

Before we do, we should take a moment to increase the readability of our code. Let’s create the helloHandler function, which holds all the logic related to the /hello request.

func helloHandler(w http.ResponseWriter, r *http.Request) {
    if r.URL.Path != "/hello" {
        http.Error(w, "404 not found.", http.StatusNotFound)
        return
    }

    if r.Method != "GET" {
        http.Error(w, "Method is not supported.", http.StatusNotFound)
        return
    }


    fmt.Fprintf(w, "Hello!")
}

This handler uses the Request object to check whether the requested path is correct. This is a very basic example of how you can use the Request object.

If the path is incorrect, the server returns a StatusNotFound error to the user. To write an error to the user, you can use the http.Error method. Notice that the StatusNotFound code corresponds to a 404 error. All status codes can be found in the Golang documentation.

Next, we add a check for verifying the type of the request. If the method doesn’t correspond to GET, the server returns a new error. When both checks pass, the server returns its success response "Hello!".

The last thing we need to do is modify the handleFunc function in our main() function to accept the above helloHandler function.

http.HandleFunc("/hello", helloHandler)

Below is the full code for your server.go file.

package main


import (
    "fmt"
    "log"
    "net/http"
)

func helloHandler(w http.ResponseWriter, r *http.Request) {
    if r.URL.Path != "/hello" {
        http.Error(w, "404 not found.", http.StatusNotFound)
        return
    }

    if r.Method != "GET" {
        http.Error(w, "Method is not supported.", http.StatusNotFound)
        return
    }


    fmt.Fprintf(w, "Hello!")
}


func main() {
    http.HandleFunc("/hello", helloHandler) // Update this line of code


    fmt.Printf("Starting server at port 8080\n")
    if err := http.ListenAndServe(":8080", nil); err != nil {
        log.Fatal(err)
    }
}

Next, we’ll start the Go web server with go run server.go. You can test your security by sending a POST request to http://localhost:8080/hello using a tool such as Postman or cURL.

Start a static web server

In this step, we’ll create a simple file server to host static files. This will be a very simple addition to the web server.

To make sure we have content to serve on the web server, let’s modify the index.html file located in the static folder. To keep things simple, just add a heading to the file that says “StaticWebsite.” If you wish, you can add more files or styling files to make your web server look a bit nicer.

<html>
  <head>
    <title>Static Website</title>
  </head>
  <body>
    <h2>Static Website</h2>
  </body>
</html>

To serve the static folder, you’ll have to add two lines of code to server.go. The first line of code creates the file server object using the FileServer function. This function accepts a path in the http.Dir type. Therefore, we have to convert the string path “./static to an http.Dir path type.

Don’t forget to specify the Handle route, which accepts a path and the fileserver. This function acts in the same way as the HandleFunc function, with some small differences. For more on the FileServer object, check the documentation.

func main() {
    fileServer := http.FileServer(http.Dir("./static")) // New code
    http.Handle("/", fileServer) // New code
    http.HandleFunc("/hello", helloHandler)


    fmt.Printf("Starting server at port 8080\n")
    if err := http.ListenAndServe(":8080", nil); err != nil {
        log.Fatal(err)
    }
}

It’s time to try out the code. Fire up the server with go run server.go and visit http://localhost:8080/. You should see the “Static Website” header.

Accept a form submission POST request

Lastly, the web server has to respond to a form submission.

Let’s add some content to the form.html file in the staticfolder. Notice that the form action is sent to /form. This means the POST request from the form will be sent to http://localhost:8080/form. The form itself asks for input for two variables: name and address.

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8" />
</head>
<body>
<div>
  <form method="POST" action="/form">     
      <label>Name</label><input name="name" type="text" value="" />
      <label>Address</label><input name="address" type="text" value="" />
      <input type="submit" value="submit" />
  </form>
</div>
</body>
</html>

The next step is to create the handler to accept the /form request. The form.html file is already served via the FileServer and can be accessed via http://localhost:8080/form.html.

First, the function has to call ParseForm() to parse the raw query and update r.PostForm and r.Form. This will allow us to access the name and address values via the r.FormValue method.

At the end of the function, we write both values to the ResponseWriter using fmt.Fprintf.

func formHandler(w http.ResponseWriter, r *http.Request) {
    if err := r.ParseForm(); err != nil {
        fmt.Fprintf(w, "ParseForm() err: %v", err)
        return
    }
    fmt.Fprintf(w, "POST request successful")
    name := r.FormValue("name")
    address := r.FormValue("address")

    fmt.Fprintf(w, "Name = %s\n", name)
    fmt.Fprintf(w, "Address = %s\n", address)
}

Don’t forget to add the new form handler route to the main()function.

http.HandleFunc("/form", formHandler)

Now, the full code looks like this.

package main


import (
    "fmt"
    "log"
    "net/http"
)

func formHandler(w http.ResponseWriter, r *http.Request) {
    if err := r.ParseForm(); err != nil {
        fmt.Fprintf(w, "ParseForm() err: %v", err)
        return
    }
    fmt.Fprintf(w, "POST request successful")
    name := r.FormValue("name")
    address := r.FormValue("address")
    fmt.Fprintf(w, "Name = %s\n", name)
    fmt.Fprintf(w, "Address = %s\n", address)
}

func helloHandler(w http.ResponseWriter, r *http.Request) {
    if r.URL.Path != "/hello" {
        http.Error(w, "404 not found.", http.StatusNotFound)
        return
    }

    if r.Method != "GET" {
        http.Error(w, "Method is not supported.", http.StatusNotFound)
        return
    }


    fmt.Fprintf(w, "Hello!")
}


func main() {
    fileServer := http.FileServer(http.Dir("./static"))
    http.Handle("/", fileServer)
    http.HandleFunc("/form", formHandler)
    http.HandleFunc("/hello", helloHandler)


    fmt.Printf("Starting server at port 8080\n")
    if err := http.ListenAndServe(":8080", nil); err != nil {
        log.Fatal(err)
    }
}

Trying out the form handler

We can test the form by starting the server with go run server.go. When the server starts, visit http://localhost:8080/form.html. You should see two input fields and a submit button.

Form Handler

When you’ve filled out the form, hit the submit button. The server should process your POST request and show you the result on the http://localhost:8080/form response page such as the below response.

Form Result

If you see the result above, you’ve successfully created your first Golang web and file server. Congratulations!

If you want to explore Golang web servers further, the Golang HTTP package documentation is full of great examples. This tutorial on writing web apps in Go is another great resource that covers most of the basics.

Comments

Popular posts from this blog

How to use Ngx-Charts in Angular ?

Charts helps us to visualize large amount of data in an easy to understand and interactive way. This helps businesses to grow more by taking important decisions from the data. For example, e-commerce can have charts or reports for product sales, with various categories like product type, year, etc. In angular, we have various charting libraries to create charts.  Ngx-charts  is one of them. Check out the list of  best angular chart libraries .  In this article, we will see data visualization with ngx-charts and how to use ngx-charts in angular application ? We will see, How to install ngx-charts in angular ? Create a vertical bar chart Create a pie chart, advanced pie chart and pie chart grid Introduction ngx-charts  is an open-source and declarative charting framework for angular2+. It is maintained by  Swimlane . It is using Angular to render and animate the SVG elements with all of its binding and speed goodness and uses d3 for the excellent math functio...

Understand Angular’s forRoot and forChild

  forRoot   /   forChild   is a pattern for singleton services that most of us know from routing. Routing is actually the main use case for it and as it is not commonly used outside of it, I wouldn’t be surprised if most Angular developers haven’t given it a second thought. However, as the official Angular documentation puts it: “Understanding how  forRoot()  works to make sure a service is a singleton will inform your development at a deeper level.” So let’s go. Providers & Injectors Angular comes with a dependency injection (DI) mechanism. When a component depends on a service, you don’t manually create an instance of the service. You  inject  the service and the dependency injection system takes care of providing an instance. import { Component, OnInit } from '@angular/core'; import { TestService } from 'src/app/services/test.service'; @Component({ selector: 'app-test', templateUrl: './test.component.html', styleUrls: ['./test.compon...

How to solve Puppeteer TimeoutError: Navigation timeout of 30000 ms exceeded

During the automation of multiple tasks on my job and personal projects, i decided to move on  Puppeteer  instead of the old school PhantomJS. One of the most usual problems with pages that contain a lot of content, because of the ads, images etc. is the load time, an exception is thrown (specifically the TimeoutError) after a page takes more than 30000ms (30 seconds) to load totally. To solve this problem, you will have 2 options, either to increase this timeout in the configuration or remove it at all. Personally, i prefer to remove the limit as i know that the pages that i work with will end up loading someday. In this article, i'll explain you briefly 2 ways to bypass this limitation. A. Globally on the tab The option that i prefer, as i browse multiple pages in the same tab, is to remove the timeout limit on the tab that i use to browse. For example, to remove the limit you should add: await page . setDefaultNavigationTimeout ( 0 ) ;  COPY SNIPPET The setDefaultNav...