Golang Web Server: Handling HTTP Requests Like a Pro Read it later

5/5 - (1 vote)

Golang (or Go) is a programming language that has gained popularity in recent years due to its speed and ease of use. One of the most common use cases for Go is building HTTP web servers. In this blog, we will cover everything you need to know about building an HTTP web server in Golang.

Before diving into the code, let’s first understand the basics of HTTP web servers.

What is an HTTP Server?

An HTTP server is a program that listens for incoming HTTP requests and returns an HTTP response. The HTTP requests contain information such as the HTTP method (GET, POST, etc.), the URL being requested, and any data being sent in the request body. The HTTP response contains information such as the status code (200 for success, 404 for not found, etc.) and any data being sent in the response body.

Golang Web Server Types

The Golang net/http package provides various types for building web servers.

1. Golang HTTP Handle (http.Handle)

The http.Handle function registers a handler for the given pattern.

func Handle(pattern string, handler Handler)

The first argument to Go http.Handle is a string that represents the URL pattern to match. Second argument is a value that implements the http.Handler interface.

func main() {
    http.Handle("/", &myHandler{})
    http.ListenAndServe(":8080", nil)
}

type myHandler struct{}

func (h *myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "Hello, World!")
}

In the above example, we register the myHandler struct as a handler for the root URL (“/”) using the http.Handle function. The myHandler struct must implement the http.Handler interface by defining the ServeHTTP method.

We can also use a function that satisfies the http.HandlerFunc type as the second argument to http.Handle.

func main() {
    http.Handle("/", http.HandlerFunc(handler))
    http.ListenAndServe(":8080", nil)
}

func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "Hello, World!")
}

In this code, we register the handler function as a handler for the root URL (“/”) using the http.Handle function.

2. Go HTTP web server ResponseWriter (http.ResponseWriter)

The http.ResponseWriter interface is used to write the response to an HTTP request. It has methods for setting headers and writing data to the response.

type ResponseWriter interface {
    Header() http.Header
    Write([]byte) (int, error)
    WriteHeader(int)
}

The Header() method returns the header map that will be sent with the response. Write method writes the data to the response.

The WriteHeader method writes the status code to the response.

3. Golang HTTP web Server Request (http.Request)

The http.Request struct represents an HTTP request. It has fields for the HTTP method, URL, headers, and body.

type Request struct {
    Method string
    URL *url.URL
    Header http.Header
    Body io.ReadCloser
    // ...
}

The Method field is the HTTP method (GET, POST, etc.). URL field is a pointer to a url.URL struct that represents the URL of the request. The “Header” field is a map of the request headers. The Body field is a ReadCloser that represents the request body.

4. Go HTTP ServeMux (http.ServeMux)

The http.ServeMux struct is a multiplexer for HTTP requests. It routes requests to their corresponding handler functions based on the request path.

type ServeMux struct {
    func NewServeMux() *ServeMux
    func (mux *ServeMux) Handle(pattern string, handler Handler)
    func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request))
    func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string)
    func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request)
}

The http.ServeMux struct has methods for registering handler functions for different URL paths.

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", handler)
    mux.HandleFunc("/about", aboutHandler)
    http.ListenAndServe(":8080", mux)
}

func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "Welcome to the homepage!")
}

func aboutHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "This is the about page.")
}

In this code, we create a new http.ServeMux struct using the http.NewServeMux function. We register handler functions for the root URL (“/”) and the “/about” path using the mux.HandleFunc method.

Finally, we start the server using the http.ListenAndServe function, passing the mux object as the second argument.

5. Go web server HandleFunc (http.HandlerFunc)

The “http.HandlerFunc” type is a convenience type that allows us to define a handler function that satisfies the “http.Handler” interface.

type HandlerFunc func(ResponseWriter, *Request)

func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    f(w, r)
}

We can use the “http.HandlerFunc” type to convert any function that has the signature of a handler function to a type that satisfies the “http.Handler” interface.

func main() {
    http.Handle("/", http.HandlerFunc(handler))
    http.ListenAndServe(":8080", nil)
}

func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "Hello, World!")
}

In this code, we register the “handler” function as a handler for the root URL (“/”) using the “http.Handle” function. We use the “http.HandlerFunc” type to convert the “handler” function to a type that satisfies the “http.Handler” interface.

6. Golang web Server Response (http.Response)

The “http.Response” struct represents an HTTP response. It has fields for the status code, headers, and body.

type Response struct {
    Status string
    StatusCode int
    Proto string
    Header http.Header
    Body io.ReadCloser
    // ...
}

The “Status” field is a string that represents the status of the response (e.g. “200 OK”, “404 Not Found”). The “StatusCode” field is an integer that represents the HTTP status code (e.g. 200, 404). The “Header” field is a map of the response headers. The “Body” field is a ReadCloser that represents the response body.

We can use the “http.Response” struct to create an HTTP response and write it to the “http.ResponseWriter”.

func handler(w http.ResponseWriter, r *http.Request) {
    resp := &http.Response{
        StatusCode: http.StatusOK,
        Proto:      "HTTP/1.1",
        Header:     make(http.Header),
        Body:       ioutil.NopCloser(bytes.NewBufferString("Hello, World!")),
    }
    resp.Header.Set("Content-Type", "text/plain")
    resp.Write(w)
}

In the above code, we create an “http.Response” struct with a status code of 200, a “Content-Type” header of “text/plain”, and a body of “Hello, World!”. We then write the response to the “http.ResponseWriter” using the “resp.Write” method.

Difference between Handle and HandleFunc

The main differences between Handle and HandleFunc are:

  • Handle takes an object that implements the Handler interface as an argument, while HandleFunc takes a function that takes a ResponseWriter and a Request object as arguments.
  • Handle requires a custom struct and its associated method to be defined and registered, while HandleFunc allows the handler function to be defined and registered in a single step.
  • HandleFunc automatically wraps the given function in a Handler object that implements the Handler interface, while Handle requires us to define a custom struct and its associated method that implements the Handler interface.

Difference between Handler and HandleFunc

The main difference between Golang HTTP Server Handler and HandleFunc is that Handler is an interface, and we need to implement its ServeHTTP method to create a custom handler.

HandleFunc, on the other hand, is a method that takes a function as an argument and creates a Handler object by wrapping the function.

HandleFunc is more convenient to use for simple handlers that don’t require a custom type, while Handler is more flexible and allows us to create complex handlers with custom types.

Creating an HTTP Web Server in Golang

To create an HTTP web server in Golang, we first need to import the net/http package. This package provides all the necessary functionality for building an HTTP server.

import "net/http"

Go NewServeMux

Once we have imported the net/http package, we can create an HTTP server using the http.NewServeMux() function. This function returns a new ServeMux instance, which is a request multiplexer for HTTP servers.

func main() {
    mux := http.NewServeMux()
}

Next, we need to define the handler functions for each HTTP request. Handler functions are functions that are called when an HTTP request is received. They are responsible for generating the HTTP response.

func handlerFunc(w http.ResponseWriter, r *http.Request) {
    // generate the HTTP response
}

The http.ResponseWriter parameter is used to write the response data to the client, and the http.Request parameter contains information about the incoming HTTP request.

Now that we have defined our handler function, we need to register it with the ServeMux instance. This is done using the http.HandleFunc() function.

mux.HandleFunc("/", handlerFunc)

The first parameter to http.HandleFunc() is the URL path pattern that the handler function should be registered for. In this case, we are registering our handler function for the root URL path.

Go Web Server ListenAndServe

Finally, we can start the Golang HTTP server using the http.ListenAndServe() function.

http.ListenAndServe(":8080", mux)

The first parameter to http.ListenAndServe() is the address that the server should listen on. In this case, we are listening on port 8080. The second parameter is the ServeMux instance that contains all the registered handler functions.

Putting it all together:

package main

import (
    "fmt"
    "net/http"
)

func main() {
    mux := http.NewServeMux()

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

    http.ListenAndServe(":8080", mux)
}

Run server:

go run server.go

This command compiles and runs the server.go file. If everything is set up correctly, you should see the message “Hello, world!” printed on the screen.

Open your web browser and navigate to http://localhost:8080. You should see the message “Hello, world!” displayed in your browser. Congratulations, you have just created your first HTTP server in Golang!

Handling Web Server Routes in Go

Now that we have a basic understanding of how to create an HTTP web server in Golang, let’s explore how to handle different routes. In HTTP, a route is a specific URL path that a client can request. For example, the route /about might return information about the website or the company that runs it.

Let’s modify our code to handle two different routes: / and /about.

package main

import (
	"fmt"
	"net/http"
)

func main() {
	http.HandleFunc("/", home)
	http.HandleFunc("/about", about)
	http.ListenAndServe(":8080", nil)
}

func home(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Welcome to the home page!")
}

func about(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "This is the about page.")
}

Here we have created two new functions: home and about. The home function handles requests to the / route, while the about function handles requests to the /about route. Each function simply writes a different message to the response.

Let’s run the server again and test out our new routes. In your terminal, run the following command:

go run main.go

Open your web browser and navigate to http://localhost:8080 and http://localhost:8080/about. You should see the messages “Welcome to the home page!” and “This is the about page.” displayed in your browser, respectively.

Golang HTTP Headers

Setting HTTP headers is an important aspect of building an HTTP server. HTTP headers provide additional information about the request and response, such as content type, language, and encoding.

Some common HTTP headers are:

  1. Content-Type: Specifies the type of data in the request or response body.
  2. Content-Length: Specifies the length of the request or response body in bytes.
  3. User-Agent: Specifies the software used to send the request.
  4. Accept: Specifies the media types accepted by the client.
  5. Authorization: Specifies the authentication credentials for the request.

Setting Response Headers in Golang

To set response headers in Golang, we can use the “http.Header” type. The “http.Header” type is a map of string keys and string values that represent the headers. We can set headers using the “Set” method or add headers using the “Add” method.

func main() {
    http.HandleFunc("/", handler)
    http.ListenAndServe(":8080", nil)
}

func handler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "text/plain")
    fmt.Fprint(w, "Hello, World!")
}

In this code, we set the “Content-Type” header to “text/plain” using the “w.Header().Set” method. We then write the response using the “fmt.Fprint” function.

Setting Request Headers in Golang

To set request headers in Golang, we can use the http.NewRequest function. The http.NewRequest function creates a new request with the specified method, URL, and body. We can set headers using the “Header” field of the request.

func main() {
    req, err := http.NewRequest("GET", "https://example.com", nil)
    if err != nil {
        log.Fatal(err)
    }
    req.Header.Set("User-Agent", "My-User-Agent")
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()
    // handle response
}

In this code, we create a new request using the http.NewRequest function with the GET method, the “https://example.com” URL, and a nil body. We set the “User-Agent” header using the Header.Set method. We then make the request using the http.DefaultClient.Do function and handle the response.

Delete HTTP Headers in Golang

To delete a header from an HTTP request, we can use the “http.Request.Header.Del” method. This method takes a string argument that specifies the name of the header to be deleted.

func handler(w http.ResponseWriter, r *http.Request) {
    r.Header.Del("Authorization")
    // ...
}

In this example, we delete the “Authorization” header from the incoming HTTP request using the http.Request.Header.Del method.

Similarly, to delete a header from an HTTP response, we can use the http.ResponseWriter.Header().Del method. This method takes a string argument that specifies the name of the header to be deleted.

func handler(w http.ResponseWriter, r *http.Request) {
    w.Header().Del("Any-Option")
    // ...
}

In this example, we delete the “Any-Option” header from the outgoing HTTP response using the “http.ResponseWriter.Header().Del” method.

Go Web Server HTTP Status Codes

HTTP status codes are three-digit numbers returned by a server to indicate the status of a response to a client’s request.

In Golang, the “net/http” package provides constants for all the HTTP status codes. Here are the five classes of HTTP status codes and their corresponding Golang constants:

1xx (Informational): These status codes indicate that the server has received the request and is continuing to process it.

  • http.StatusContinue (100)
  • http.StatusSwitchingProtocols (101)

2xx (Success): These status codes indicate that the request was successfully received, understood, and accepted.

  • http.StatusOK (200)
  • http.StatusCreated (201)
  • http.StatusAccepted (202)
  • http.StatusNonAuthoritativeInfo (203)
  • http.StatusNoContent (204)
  • http.StatusResetContent (205)
  • http.StatusPartialContent (206)

3xx (Redirection): These status codes indicate that the client must take additional action to complete the request.

  • http.StatusMultipleChoices (300)
  • http.StatusMovedPermanently (301)
  • http.StatusFound (302)
  • http.StatusSeeOther (303)
  • http.StatusNotModified (304)
  • http.StatusUseProxy (305)
  • http.StatusTemporaryRedirect (307)
  • http.StatusPermanentRedirect (308)

4xx (Client Error): These status codes indicate that the client has made a bad request.

  • http.StatusBadRequest (400)
  • http.StatusUnauthorized (401)
  • http.StatusPaymentRequired (402)
  • http.StatusForbidden (403)
  • http.StatusNotFound (404)
  • http.StatusMethodNotAllowed (405)
  • http.StatusNotAcceptable (406)
  • http.StatusProxyAuthRequired (407)
  • http.StatusRequestTimeout (408)
  • http.StatusConflict (409)
  • http.StatusGone (410)
  • http.StatusLengthRequired (411)
  • http.StatusPreconditionFailed (412)
  • http.StatusRequestEntityTooLarge (413)
  • http.StatusRequestURITooLong (414)
  • http.StatusUnsupportedMediaType (415)
  • http.StatusRequestedRangeNotSatisfiable (416)
  • http.StatusExpectationFailed (417)
  • http.StatusTeapot (418)
  • http.StatusUnprocessableEntity (422)
  • http.StatusLocked (423)
  • http.StatusFailedDependency (424)
  • http.StatusUpgradeRequired (426)
  • http.StatusPreconditionRequired (428)
  • http.StatusTooManyRequests (429)
  • http.StatusRequestHeaderFieldsTooLarge (431)
  • http.StatusUnavailableForLegalReasons (451)

5xx (Server Error): These status codes indicate that the server has encountered an error while processing the request.

  • http.StatusInternalServerError (500)
  • http.StatusNotImplemented (501)
  • http.StatusBadGateway (502)
  • http.StatusServiceUnavailable (503)
  • http.StatusGatewayTimeout (504)
  • http.StatusHTTPVersionNotSupported (505)
  • http.StatusVariantAlsoNegotiates (506)
  • http.StatusInsufficientStorage (507)
  • http.StatusLoopDetected (508)
  • http.StatusNotExtended (510)
  • http.StatusNetworkAuthenticationRequired (511)

When writing When writing HTTP servers in Golang, it is important to understand how to use these status codes appropriately.

In Golang, we can set and delete HTTP status codes using the “net/http” package.

Setting HTTP Status Code in Go

To set an HTTP status code in Golang, we need to use the “http.ResponseWriter” interface. The interface provides a “WriteHeader” function that takes an HTTP status code as an argument.

func handler(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(http.StatusOK)
    fmt.Fprint(w, "OK")
}

In this code, we set the HTTP status code to 200 (OK) using the “WriteHeader” function of the ResponseWriter interface. We then write the response using the “fmt.Fprint” function.

Deleting HTTP Status Code in Golang

To delete an HTTP status code in Golang, we need to use the “http.ResponseWriter” interface. The interface provides a “Flush” function that sends any buffered data to the client and resets the ResponseWriter to its initial state.

func handler(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(http.StatusOK)
    fmt.Fprint(w, "OK")
    w.(http.Flusher).Flush()
    w.WriteHeader(http.StatusOK)
    fmt.Fprint(w, "OK")
}

In this example code, we set the HTTP status code to 200 (OK) using the “WriteHeader” function of the ResponseWriter interface. We then write the response using the “fmt.Fprint” function. Then call the “Flush” function of the ResponseWriter interface to send the buffered data to the client and reset the ResponseWriter to its initial state. We then set the HTTP status code to 200 (OK) again using the “WriteHeader” function of the ResponseWriter interface and write the response again.

Best Practices of Web Server in Go

some best practices that should be followed when building a Golang HTTP web server.

Secure Your Web Server with TLS

When dealing with sensitive data, it is always recommended to use Transport Layer Security (TLS). TLS provides encryption and authentication, making it difficult for attackers to intercept and tamper with data being transmitted. In Golang, TLS can be easily implemented using the “crypto/tls” package.

server := &http.Server{
    Addr: ":443",
    Handler: handler,
    TLSConfig: &tls.Config{
        Certificates: []tls.Certificate{cert},
    },
}
log.Fatal(server.ListenAndServeTLS("", ""))

In this code, we create an HTTP server that listens on port 443 and uses TLS for encryption and authentication. The server’s TLS configuration is defined using the “crypto/tls” package, and a TLS certificate is specified.

Better Resource Management with Context

The “context” package in Golang provides a way to carry deadlines, cancellation signals, and other request-scoped values across API boundaries and between processes. Using context in HTTP requests allows for better management of resources, cancellation of long-running requests, and more.

func handler(w http.ResponseWriter, r *http.Request) {
    ctx, cancel := context.WithTimeout(r.Context(), time.Second)
    defer cancel()

    // Use ctx in the code logic
}

In this code, we create a new context using the “context.WithTimeout” function and pass it the request’s context and a timeout of one second. We then use the context in the code logic and defer the cancellation of the context.

Separation of Concerns with Middleware

Middleware is a way to add functionality to an HTTP server without modifying the server’s core logic. It allows for better separation of concerns and can help reduce code duplication. In Golang, middleware can be implemented using the “net/http” package’s “HandlerFunc” function.

func logger(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        log.Printf("%s %s", r.Method, r.URL.Path)
        next.ServeHTTP(w, r)
    })
}

func handler(w http.ResponseWriter, r *http.Request) {
    // handler code
}

func main() {
    handler := http.HandlerFunc(handler)
    http.Handle("/", logger(handler))
    http.ListenAndServe(":8080", nil)
}

In this code, we define a “logger” middleware function that logs the HTTP method and URL path of incoming requests. We then wrap our main “handler” function with the “logger” middleware function using the “http.HandlerFunc” function. Finally, we pass the wrapped handler to the “http.Handle” function and listen on port 8080.

If you want to learn more about Golang and web development, check out the following resources:

Was This Article Helpful?

Leave a Reply

Your email address will not be published. Required fields are marked *