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.
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 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)
}
w
).http.HandleFunc()
to register the handler function for the root (“/”) route.http.ListenAndServe()
on port 8080.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)
}
http.FileServer()
to create a file server that serves files from the “static” directory.http.ListenAndServe()
.In this section, we’ll explore how to make HTTP requests from a Go program using the built-in net/http
package.
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))
}
http.Get()
.ioutil.ReadAll()
and print it to the console.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")
}
In this section, we’ll explore advanced features of HTTP servers in Go, such as middleware, routing, and concurrency
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)
}
http.Handle()
to register the middleware with the root (“/”) route.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
}
github.com/gorilla/mux
) to define routes and their corresponding handlers.ArticleHandler
function handles GET requests to “/articles/{id}”, while CreateArticleHandler
handles POST requests to “/articles”.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!")
}
middleware1
and middleware2
) that each log a message before calling the next middleware or the final handler.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")
}
srv.Shutdown()
with a timeout context.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")
}
MaxIdleConns
and MaxIdleConnsPerHost
parameters.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 !❤️