HTTP Server
The http.Server struct provides a configurable to create
an HTTP-server compared to the simple http.ListenAndServe function we have used.
Basic HTTP Server Configuration
http.Server allows you to explicitly define server parameters:
package main
import (
"net/http"
"time"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", handler)
server := &http.Server{
Addr: "127.0.0.1:8080",
Handler: mux,
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
IdleTimeout: 60 * time.Second,
}
err := server.ListenAndServe()
if err != nil {
panic(err)
}
}
func handler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello world"))
}
Key Configuration Options
Timeouts
Proper timeouts are critical for production REST APIs
- ReadTimeout: Maximum duration for reading the entire request, including body
- WriteTimeout: Maximum duration before timing out writes of the response
- IdleTimeout: Maximum amount of time to wait for the next request when keep-alives are enabled
Without proper timeouts, your server may be vulnerable to slow client attacks or resource exhaustion.
TLS Configuration
For secure HTTPS connections, you can configure TLS directly:
server := &http.Server{
Addr: "127.0.0.1:8443",
Handler: mux,
TLSConfig: &tls.Config{
MinVersion: tls.VersionTLS12,
},
}
err := server.ListenAndServeTLS("server.crt", "server.key")
if err != nil {
panic(err)
}
Graceful Shutdown
One of the most important features of using http.Server directly is the ability to implement graceful shutdown, which is essential for production REST APIs:
package main
import (
"context"
"errors"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
err := run()
if err != nil {
log.Printf("Error: %v", err)
os.Exit(1)
}
log.Println("Server stopped gracefully")
}
func run() error {
mux := http.NewServeMux()
mux.HandleFunc("/", handler)
server := &http.Server{
Addr: "127.0.0.1:8080",
Handler: mux,
}
errc := make(chan error, 1)
// start the server in a goroutine
go func() {
defer close(errc)
// when shutdown is called, the server will immediatelly return and stop accepting new connections
if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
errc <- err
return
}
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Shutting down server...")
// we give the server 5 seconds to finish the in progress requests
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// Disable keep-alives
server.SetKeepAlivesEnabled(false)
// we should wait for the server to finish
err := server.Shutdown(ctx)
if err != nil {
return err
}
return <-errc
}
func handler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello world"))
}
When to use http.Server and best practices
Always use the http.Server. An exception might be non production test apps
- Always set timeouts
- Implement graceful shutdown
- Use TLS on production