HTTP Server and Client

HTTP (Hypertext Transfer Protocol) is the foundation of communication on the World Wide Web. In this chapter, we'll explore building HTTP servers and clients in Go, covering everything from basic request handling to advanced features like middleware and concurrency.

HTTP is a protocol used for transmitting hypermedia documents, such as HTML files, over the internet. It operates on a client-server model, where clients make requests to servers, and servers respond with resources.

Basic HTTP Server

In this section, we’ll cover the basics of creating an HTTP server in Go, including handling requests and serving static files.

Creating an HTTP Server

Creating an HTTP server in Go is straightforward using the net/http package.

				
					package main

import (
    "fmt"
    "net/http"
)

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

func main() {
    http.HandleFunc("/", handler)
    fmt.Println("HTTP server listening on port 8080")
    http.ListenAndServe(":8080", nil)
}

				
			
  • We define a handler function that writes “Hello, World!” to the response writer (w).
  • We use http.HandleFunc() to register the handler function for the root (“/”) route.
  • We start the HTTP server using http.ListenAndServe() on port 8080.

Serving Static Files

You can serve static files, such as HTML, CSS, and JavaScript, using the http.FileServer() function.

				
					package main

import (
    "net/http"
)

func main() {
    http.Handle("/", http.FileServer(http.Dir("./static")))
    http.ListenAndServe(":8080", nil)
}

				
			
  • We use http.FileServer() to create a file server that serves files from the “static” directory.
  • We register the file server for the root (“/”) route.
  • The server listens on port 8080 using http.ListenAndServe().

HTTP Client

In this section, we’ll explore how to make HTTP requests from a Go program using the built-in net/http package.

Making HTTP Requests

Go provides a convenient http.Get() function for making GET requests to a URL.

				
					package main

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

func main() {
    resp, err := http.Get("https://jsonplaceholder.typicode.com/posts/1")
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    defer resp.Body.Close()

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

    fmt.Println(string(body))
}

				
			

Making POST Requests

You can make POST requests by creating an http.Client and using its Post() method.

				
					package main

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

func main() {
    url := "https://jsonplaceholder.typicode.com/posts"
    data := []byte(`{"title": "foo", "body": "bar", "userId": 1}`)

    resp, err := http.Post(url, "application/json", bytes.NewBuffer(data))
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    defer resp.Body.Close()

    fmt.Println("POST request successful")
}

				
			

Advanced HTTP Server

In this section, we’ll explore advanced features of HTTP servers in Go, such as middleware, routing, and concurrency

Using Middleware

Middleware allows you to intercept HTTP requests and responses to perform additional processing.

				
					package main

import (
    "fmt"
    "net/http"
)

func middleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        fmt.Println("Executing middleware...")
        next.ServeHTTP(w, r)
    })
}

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

func main() {
    http.Handle("/", middleware(http.HandlerFunc(handler)))
    fmt.Println("HTTP server with middleware listening on port 8080")
    http.ListenAndServe(":8080", nil)
}

				
			
  • We define a middleware function that logs a message before calling the next handler.
  • We use http.Handle() to register the middleware with the root (“/”) route.

Routing

Routing allows you to map HTTP requests to specific handlers based on the request URL and method.

				
					package main

import (
    "fmt"
    "net/http"

    "github.com/gorilla/mux"
)

func main() {
    r := mux.NewRouter()
    r.HandleFunc("/articles/{id}", ArticleHandler).Methods("GET")
    r.HandleFunc("/articles", CreateArticleHandler).Methods("POST")

    http.Handle("/", r)
    fmt.Println("HTTP server with routing listening on port 8080")
    http.ListenAndServe(":8080", nil)
}

func ArticleHandler(w http.ResponseWriter, r *http.Request) {
    // Retrieve article by ID
}

func CreateArticleHandler(w http.ResponseWriter, r *http.Request) {
    // Create a new article
}

				
			
  • We use the Gorilla Mux router (github.com/gorilla/mux) to define routes and their corresponding handlers.
  • The ArticleHandler function handles GET requests to “/articles/{id}”, while CreateArticleHandler handles POST requests to “/articles”.

Middleware Chaining

Middleware chaining allows you to chain multiple middleware functions together to execute in a specific order.

				
					package main

import (
    "fmt"
    "net/http"
)

func middleware1(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        fmt.Println("Executing middleware1...")
        next.ServeHTTP(w, r)
    })
}

func middleware2(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        fmt.Println("Executing middleware2...")
        next.ServeHTTP(w, r)
    })
}

func main() {
    finalHandler := http.HandlerFunc(final)
    http.Handle("/", middleware1(middleware2(finalHandler)))
    fmt.Println("HTTP server with middleware chaining listening on port 8080")
    http.ListenAndServe(":8080", nil)
}

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

				
			
  • We define two middleware functions (middleware1 and middleware2) that each log a message before calling the next middleware or the final handler.
  • We chain the middleware functions together in the server setup.

Graceful Shutdown

Graceful shutdown ensures that the server finishes processing active connections before shutting down.

				
					package main

import (
    "context"
    "fmt"
    "net/http"
    "os"
    "os/signal"
    "time"
)

func main() {
    srv := &http.Server{Addr: ":8080"}

    go func() {
        fmt.Println("HTTP server listening on port 8080")
        if err := srv.ListenAndServe(); err != nil {
            fmt.Printf("HTTP server error: %v\n", err)
        }
    }()

    // Wait for interrupt signal to gracefully shut down the server
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, os.Interrupt)
    <-quit
    fmt.Println("Shutting down server...")

    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()

    if err := srv.Shutdown(ctx); err != nil {
        fmt.Printf("HTTP server shutdown error: %v\n", err)
    }
    fmt.Println("Server gracefully stopped")
}

				
			
  • We start the HTTP server in a separate goroutine and listen for interrupt signals.
  • Upon receiving an interrupt signal, we initiate a graceful shutdown by calling srv.Shutdown() with a timeout context.

Connection Pooling

Connection pooling optimizes resource usage by reusing existing network connections instead of creating new ones for each request.

				
					package main

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

func main() {
    httpClient := &http.Client{
        Transport: &http.Transport{
            MaxIdleConns:        10,
            MaxIdleConnsPerHost: 10,
            IdleConnTimeout:     30 * time.Second,
        },
    }

    resp, err := httpClient.Get("https://example.com")
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    defer resp.Body.Close()

    // Process response
    fmt.Println("HTTP client request successful")
}

				
			
  • We configure the HTTP client to use connection pooling by setting the MaxIdleConns and MaxIdleConnsPerHost parameters.
  • The IdleConnTimeout parameter determines how long idle connections are kept alive before being closed.

Building HTTP servers and clients in Go is a crucial skill for developing web applications and services. By mastering basic HTTP handling, understanding advanced techniques like routing, middleware chaining, graceful shutdowns, and connection pooling, you can create efficient, scalable, and reliable HTTP-based systems. Experiment with different server configurations, explore third-party libraries, and optimize performance to build high-quality web applications with Go. Happy coding !❤️

Table of Contents

Contact here

Copyright © 2025 Diginode

Made with ❤️ in India