Create Golang Web Server [With & Without HTTPS]


Written By - admin
Advertisement

In this tutorial we will go step by step and create a simple web server using golang. Additionally I will show you the steps to

 

1. Create Simple Web Server and Client (Hello World)

Here is a small golang program which sets up a web server that listens on port 8000 and serves a simple "Hello, World!" message when a request is made to the root path ("/").

package main

import (
	"fmt"
	"net/http"
)

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

	http.ListenAndServe(":8000", nil)
}

The http.HandleFunc function is used to register a function to be called when a request is made to the root path ("/"). The function takes an http.ResponseWriter and an *http.Request as arguments, and writes "Hello, World!" to the response writer.

The http.ListenAndServe function is used to start the web server. It takes a port number and a handler as arguments. In this case, we're passing nil as the handler because we have already registered our function using http.HandleFunc.

Execute the program:

# go run main.go

Now from a different terminal, try to access this web server:

# curl http://localhost:8000
Hello, World!

Instead of using curl, we can also write a simple golang client to connect to the web server and print the response status:

package main

import (
	"fmt"
	"net/http"
)

func main() {
	res, err := http.Get("http://localhost:8000/")
	if err != nil {
		fmt.Println("Error:", err)
		return
	}
	defer res.Body.Close()

	fmt.Println("Response:", res.Status)
}

Let's execute the client.go file:

Advertisement
# go run client.go 
Response: 200 OK

 

2. Add timeout to your web server

We can also define a read and write timeout for our web server, here is an example code:

package main

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

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "Hello, World!")
	})

	server := &http.Server{
		Addr:         ":8000",
		Handler:      mux,
		ReadTimeout:  5 * time.Second,
		WriteTimeout: 10 * time.Second,
	}

	server.ListenAndServe()
}

In this example:

  • The http.NewServeMux function is used to create a new request multiplexer.
  • The mux.HandleFunc("/", ...) function is used to register a function as the handler for all requests to the root path ("/").
  • The http.Server{...} struct is used to configure the server.
  • The server.Addr field is set to ":8000" to listen on port 8000.
  • The server.Handler field is set to the multiplexer mux created earlier.
  • The server.ReadTimeout field is set to 5 seconds to timeout read operations after 5 seconds.
  • The server.WriteTimeout field is set to 10 seconds to timeout write operations after 10 seconds.
  • The server.ListenAndServe() function is used to start the web server and handle incoming requests using the registered handler.

 

 

3. Serve index.html file with your web server

We can use http.ServeFile function to serve a specific file. Here's an example to server index.html file which is available inside web/index.html on my setup:

package main

import (
	"net/http"
)

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		http.ServeFile(w, r, "web/index.html")
	})
	http.ListenAndServe(":8000", nil)
}

This is my sample index.html file:

<!DOCTYPE html>
<html>
<head>
	<title>My Web Page</title>
</head>
<body>
	<h1>Hello, World!</h1>
</body>
</html>

Execute the code to start the web server:

# go run main.go

Now try to access your web server using browser or any CLI command such as curl:

Create Golang Web Server [With & Without HTTPS]

 

3. Create simple web server to download files

If we have a requirement to setup a web server which can be used by users to download files, then you can check below example:

package main

import (
	"net/http"
)

func main() {
	http.Handle("/files/", http.StripPrefix("/files/", http.FileServer(http.Dir("downloads"))))
	http.ListenAndServe(":8000", nil)
}

Here,

Advertisement
  • The http.StripPrefix("/files/", ...) function is used to remove the "/files/" prefix from the request URL before passing it to the file server.
  • The http.FileServer(http.Dir("downloads")) function creates a file server that serves files from the "downloads" directory located in the same directory of the go file.
  • http.Handle("/files/", ...) is used to register the file server as the handler for all requests that start with "/files/".
  • The http.ListenAndServe(":8000", nil) function is used to start the web server on port 8000 and handle incoming requests using the registered handler.
  • Now when a client requests the http://localhost:8000/files/somefile.txt the server will look for the somefile.txt in the downloads directory and serve it to the client for download.

Here is my downloads directory content which is located in the same path as my go file:

# tree downloads/
downloads/
├── config.yaml
├── data.json
├── file.csv
├── file.txt
├── file.xml
├── go.mod
├── go.sum
├── main.go
├── script.sh
└── source_file.txt

0 directories, 10 files

Let's start our web server:

$ go run main.go

Now access the web server on GUI and verify the files are available for download:

Create Golang Web Server [With & Without HTTPS]

We can also enhance the code to print a message on the console with the filename which was downloaded, here is the updated code:

package main

import (
	"fmt"
	"net/http"
)

func main() {
	http.HandleFunc("/files/", func(w http.ResponseWriter, r *http.Request) {
		fileName := r.URL.Path[len("/files/"):]

		fmt.Println("Downloading file: " + fileName)
		http.ServeFile(w, r, "downloads/"+fileName)
	})
	http.ListenAndServe(":8000", nil)
}

With this you can re-start your web server and now if we try to download any file then a message is printed on the console:

# go run main.go 
Downloading file: go.mod
Downloading file: file.csv
^Csignal: interrupt

 

4. Create a web server and client using HTTPS

Example-1: Using self-signed certificates (Without CA Cert)

In this section we will create self-signed certificates and use them to secure our web server. In production environment it is recommended to use third party CA signed certificates instead of self-signed certificates as there will no client or server level validation here. We will still have to use insecure flag to perform any kind of communication with this web server.

Advertisement

Use the following command to generate self-signed certificate for localhost web server:

openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes -subj '/CN=localhost'

This will generate cert.pem and key.pem file.

Here is the updated code for our web server to use this certificate:

package main

import (
	"fmt"
	"net/http"
)

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "Hello, World!")
	})
	http.ListenAndServeTLS(":8000", "cert.pem", "key.pem", nil)
}

Start the web server:

# go run main.go

Let's try to access this using curl:

# curl https://localhost:8000/
curl: (60) SSL certificate problem: self-signed certificate
More details here: https://curl.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above.

As expected, curl is reporting validation errors. To overcome this we have to use --insecure flag:

# curl --insecure https://localhost:8000/
Hello, World!

Here is a sample client.go which uses tls.Config{InsecureSkipVerify: true} to ignore certificate validation errors.

package main

import (
	"crypto/tls"
	"fmt"
	"io/ioutil"
	"net/http"
)

func main() {
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
	}
	client := &http.Client{Transport: tr}

	res, err := client.Get("https://localhost:8000/")
	if err != nil {
		fmt.Println("Error:", err)
		return
	}
	defer res.Body.Close()

	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		fmt.Println("Error reading response body:", err)
		return
	}

	fmt.Println("Response:", res.Status)
	fmt.Println("Text received:", string(body))
}

Let's execute this client code:

# go run client.go 
Response: 200 OK
Text received: Hello, World!

 

Example-2: Using CA signed certificate

If you have a valid CA certificate then you can use that to sign a server certificate and then use them with your web server. You can follow our certificate management articles to learn all the tips and tricks related to generating different types of certificates.

I am using following script and file to generate the CA certificate and other certificates required for our secure web server. For the sake of this article, I will not be able to explain the steps but you can read the original article from where I am using this script:

Advertisement
#!/bin/bash

myPath="/root/goexamples"
SERVER_KEY="$myPath/server.key"
SERVER_CSR="$myPath/server.csr"
SERVER_CRT="$myPath/server.crt"
CA_KEY="$myPath/ca.key"
CA_CRT="$myPath/cacert.pem"
CA_EXTFILE="$myPath/ca_cert.cnf"
SERVER_EXT="$myPath/server_ext.cnf"
SERVER_CONF="$myPath/server_cert.cnf"
OPENSSL_CMD="/usr/bin/openssl"
COMMON_NAME="$1"

function generate_root_ca {

    ## generate rootCA private key
    echo "Generating RootCA private key"
    if [[ ! -f $CA_KEY ]];then
       $OPENSSL_CMD genrsa -out $CA_KEY 4096 2>/dev/null
       [[ $? -ne 0 ]] && echo "ERROR: Failed to generate $CA_KEY" && exit 1
    else
       echo "$CA_KEY seems to be already generated, skipping the generation of RootCA certificate"
       return 0
    fi

    ## generate rootCA certificate
    echo "Generating RootCA certificate"
    $OPENSSL_CMD req -new -x509 -days 3650 -config $CA_EXTFILE -key $CA_KEY -out $CA_CRT 2>/dev/null
    [[ $? -ne 0 ]] && echo "ERROR: Failed to generate $CA_CRT" && exit 1

    ## read the certificate
    echo "Verify RootCA certificate"
    $OPENSSL_CMD  x509 -noout -text -in $CA_CRT >/dev/null 2>&1
    [[ $? -ne 0 ]] && echo "ERROR: Failed to read $CA_CRT" && exit 1
}

function generate_server_certificate {

    echo "Generating server private key"
    $OPENSSL_CMD genrsa -out $SERVER_KEY 4096 2>/dev/null
    [[ $? -ne 0 ]] && echo "ERROR: Failed to generate $SERVER_KEY" && exit 1

    echo "Generating certificate signing request for server"
    $OPENSSL_CMD req -new -key $SERVER_KEY -out $SERVER_CSR -config $SERVER_CONF 2>/dev/null
    [[ $? -ne 0 ]] && echo "ERROR: Failed to generate $SERVER_CSR" && exit 1

    echo "Generating RootCA signed server certificate"
    $OPENSSL_CMD x509 -req -in $SERVER_CSR -CA $CA_CRT -CAkey $CA_KEY -out $SERVER_CRT -CAcreateserial -days 365 -sha512 -extfile $SERVER_EXT 2>/dev/null
    [[ $? -ne 0 ]] && echo "ERROR: Failed to generate $SERVER_CRT" && exit 1

    echo "Verifying the server certificate against RootCA"
    $OPENSSL_CMD verify -CAfile $CA_CRT $SERVER_CRT >/dev/null 2>&1
     [[ $? -ne 0 ]] && echo "ERROR: Failed to verify $SERVER_CRT against $CA_CRT" && exit 1

}

# MAIN
generate_root_ca
generate_server_certificate

This script requires some additional configuration files namely server_cert.cnf, ca_cert.cnf and server_ext.cnf to automate the process:

# server_cert.cnf
default_bit = 4096
distinguished_name = req_distinguished_name
prompt = no

[req_distinguished_name]
countryName             = IN
stateOrProvinceName     = Karnataka
localityName            = Bengaluru
organizationName        = GoLinuxCloud
commonName              = example.com

# server_ext.cnf
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
extendedKeyUsage = serverAuth, clientAuth
subjectAltName = @alt_names

[alt_names]
DNS.1 = localhost

# ca_cert.cnf
[req]
distinguished_name = req_distinguished_name
x509_extensions = v3_ca
prompt = no

[req_distinguished_name]
countryName             = IN
stateOrProvinceName     = Karnataka
localityName            = Bengaluru
organizationName        = GoLinuxCloud
commonName              = rootca.com

[ v3_ca ]
basicConstraints=critical,CA:TRUE
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid:always,issuer

Execute the script:

# ./gen_certs.sh 
Generating RootCA private key
Generating RootCA certificate
Verify RootCA certificate
Generating server private key
Generating certificate signing request for server
Generating RootCA signed server certificate
Verifying the server certificate against RootCA

Let's verify our certificates:

[root@server goexamples]# ls -l server*
-rw-r--r-- 1 root root  279 Jan 21 16:55 server_cert.cnf
-rw-r--r-- 1 root root 2102 Jan 21 16:57 server.crt
-rw-r--r-- 1 root root 1691 Jan 21 16:57 server.csr
-rw-r--r-- 1 root root  242 Jan 21 16:55 server_ext.cnf
-rw------- 1 root root 3272 Jan 21 16:57 server.key

[root@server goexamples]# ls -l ca*
-rw-r--r-- 1 root root  403 Jan 21 16:55 ca_cert.cnf
-rw-r--r-- 1 root root 2017 Jan 21 16:57 cacert.pem
-rw------- 1 root root 3272 Jan 21 16:57 ca.key

Now we will use the server.crt and server.key with our web server:

package main

import (
	"fmt"
	"net/http"
)

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "Hello, World!")
	})
	http.ListenAndServeTLS(":8000", "server.crt", "server.key", nil)
}

Start the web server:

# go run main.go

Now we can use curl command to query this web server by specifying the CA certificate which was used to sign the certificate:

# curl --cacert cacert.pem https://localhost:8000
Hello, World!

We can also create golang based client to query this web server. Here you can notice that instead of using InsecureSkipVerify: true we are use our CA certificate to establish the communication.

package main

import (
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"io/ioutil"
	"net/http"
)

func main() {
	caCert, err := ioutil.ReadFile("cacert.pem")
	if err != nil {
		fmt.Println("Error reading CA certificate:", err)
		return
	}
	caCertPool := x509.NewCertPool()
	caCertPool.AppendCertsFromPEM(caCert)

	tr := &http.Transport{
		TLSClientConfig: &tls.Config{
			RootCAs: caCertPool,
		},
	}
	client := &http.Client{Transport: tr}

	res, err := client.Get("https://localhost:8000/")
	if err != nil {
		fmt.Println("Error:", err)
		return
	}
	defer res.Body.Close()

	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		fmt.Println("Error reading response body:", err)
		return
	}

	fmt.Println("Response:", res.Status)
	fmt.Println("Text received:", string(body))
}

In this example:

  • The ioutil.ReadFile("cacert.pem") is used to read the CA certificate from the file.
  • The x509.NewCertPool() is used to create a new certificate pool and caCertPool.AppendCertsFromPEM(caCert) is used to add the CA certificate to the pool
  • The http.Transport{...} is used to create an HTTP transport with the custom tls.Config which includes the RootCAs.
  • The http.Client{...} is used to create an HTTP client with the custom transport.
  • The client.Get(...) is used to send a GET request to the server using the custom client.

Let's execute our client code:

# go run client.go 
Response: 200 OK
Text received: Hello, World!

So we are getting 200 OK response which means the communication is working between client and server.

Advertisement

 

Summary

Here is a summary of what we have learned in this tutorial:

  • To set up a simple web server in Go, we can use the http.ListenAndServe or http.ListenAndServeTLS functions, along with http.HandleFunc or http.Handle to handle different routes and requests.
  • To add a timeout to a web server, we can use the http.Server.ReadTimeout and http.Server.WriteTimeout fields to set the timeout for reading and writing.
  • To connect to a server using a self-signed certificate, you can use the http package with a custom tls.Config that has the InsecureSkipVerify=true or alternatively we can use RootCAs to specify the root CA Certificate which can used to do the validation.

 

References

net/http - Go Packages
Golang. What to use? http.ServeFile(..) or http.FileServer(..)?

 

Didn't find what you were looking for? Perform a quick search across GoLinuxCloud

If my articles on GoLinuxCloud has helped you, kindly consider buying me a coffee as a token of appreciation.

Buy GoLinuxCloud a Coffee

For any other feedbacks or questions you can either use the comments section or contact me form.

Thank You for your support!!

Leave a Comment