Http Server Timeouts
- Setting timeouts in server is important to conserve system resources and to protect from DDOS attack.
- File descriptors are limited.
- Malicious user can ioen many client connections, consuming all file descriptor. Server will not able to accept any new connection.
net/http Timeouts
- There are four main timeouts exposed in http.server
- Read Timeout: covers the time from when the connection is accepted, to when the request body is fully read.
- Read Header Timeout: amount of time allowed to read request headers.
- Write Timeout: covers the time from the end of the request header read to the end of the response write
- Idle Timeout: maximum amount of time to wait for the next request when keep-alive is enabled.

Set Timeouts by explicity using a Server
srv := &http.Server{
ReadTimeout: 1*time.Second,
ReadHeaderTimeout: 1*time.Second,
WriteTimeout: 1*time.Second,
IdleTimeout: 30*time.Second,
Handler: serveMux
}
- Set Connection timeouts when dealing with untrusted clients and networks.
- Protect Server from client which are slow to read and write.
HTTP Handler Functions
- Connection timeouts apply at network connection level.
- HTTP Handler Functions are unaware of these timeouts, they run to completion, consuming resources.
http.TimeoutHandler()
srv := http.Server{
Addr: "localhost:8000",
WriteTimeout: 2 * time.Second,
Handler: http.TimeoutHandler(http.HandlerFunc(slowHandler), 1*time.Second, "Timeout!\n"),
}
- net/http package provides TimeoutHandler()
- TimeoutHandler returns a Handler that run input handler with the given time limit.
- If input handler runs for longer than its time limit, the handler sends the client a 503 Service Unavailable error and HTML error message.
Context Timeouts and Cancellation
- Use Context timeouts and cancellation to propagate the cancellation signal down the call graph.
- The Request type already has a context attached to it.
ctx := req.Context()
- Server cancels this context when
- Client close the connection.
- Timeout
- ServeHTTp method returns.

Exercises
package main
import (
"context"
"database/sql"
"errors"
"fmt"
"log"
"net/http"
"time"
_ "github.com/lib/pq"
)
var db *sql.DB
func slowQuery(ctx context.Context) error {
_, err := db.ExecContext(ctx, "SELECT pg_sleep(5)")
return err
}
func slowHandler(w http.ResponseWriter, req *http.Request) {
start := time.Now()
err := slowQuery(req.Context())
if err != nil {
switch {
case errors.Is(err, context.Canceled):
log.Printf("Warning: %s\n", err.Error())
default:
log.Printf("Error: %s\n", err.Error())
}
return
}
fmt.Fprintln(w, "OK")
fmt.Printf("slowHandler took: %v\n", time.Since(start))
}
func main() {
var err error
connstr := "host=localhost port=5432 user=alice password=pa$$word dbname=wonderland sslmode=disable"
db, err = sql.Open("postgres", connstr)
if err != nil {
log.Fatal(err)
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err = db.PingContext(ctx); err != nil {
log.Fatal(err)
}
srv := http.Server{
Addr: "localhost:8000",
WriteTimeout: 2 * time.Second,
Handler: http.TimeoutHandler(http.HandlerFunc(slowHandler), 1*time.Second, "Timeout!\n"),
}
if err := srv.ListenAndServe(); err != nil {
fmt.Printf("Server failed: %s\n", err)
}
}