timeout

package module
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 18, 2025 License: MIT Imports: 7 Imported by: 83

README

Timeout

Run Tests codecov Go Report Card GoDoc

Timeout is a Gin middleware that wraps a handler and aborts its execution if a specified timeout is reached. This is useful for preventing slow handlers from blocking your server.


Features

  • Abort request processing if it exceeds a configurable timeout.
  • Customizable timeout response.
  • Can be used as route or global middleware.
  • Compatible with other Gin middleware.

Installation

go get github.com/gin-contrib/timeout

Quick Start

A minimal example that times out a slow handler:

package main

import (
  "log"
  "net/http"
  "time"

  "github.com/gin-contrib/timeout"
  "github.com/gin-gonic/gin"
)

func emptySuccessResponse(c *gin.Context) {
  time.Sleep(200 * time.Microsecond)
  c.String(http.StatusOK, "")
}

func main() {
  r := gin.New()

  r.GET("/", timeout.New(
    timeout.WithTimeout(100*time.Microsecond),
    timeout.WithHandler(emptySuccessResponse),
  ))

  if err := r.Run(":8080"); err != nil {
    log.Fatal(err)
  }
}

Advanced Usage

1. Custom Timeout Response

You can define a custom response when a timeout occurs:

func testResponse(c *gin.Context) {
  c.String(http.StatusRequestTimeout, "custom timeout response")
}

r.GET("/", timeout.New(
  timeout.WithTimeout(100*time.Microsecond),
  timeout.WithHandler(emptySuccessResponse),
  timeout.WithResponse(testResponse),
))

2. Global Middleware

Apply the timeout middleware to all routes:

func testResponse(c *gin.Context) {
  c.String(http.StatusRequestTimeout, "timeout")
}

func timeoutMiddleware() gin.HandlerFunc {
  return timeout.New(
    timeout.WithTimeout(500*time.Millisecond),
    timeout.WithHandler(func(c *gin.Context) {
      c.Next()
    }),
    timeout.WithResponse(testResponse),
  )
}

func main() {
  r := gin.New()
  r.Use(timeoutMiddleware())
  r.GET("/slow", func(c *gin.Context) {
    time.Sleep(800 * time.Millisecond)
    c.Status(http.StatusOK)
  })
  if err := r.Run(":8080"); err != nil {
    log.Fatal(err)
  }
}

3. Logging Timeout Events

You can combine the timeout middleware with custom logging for timeout events:

import (
  "log/slog"
  "net/http"
  "time"

  "github.com/gin-contrib/timeout"
  "github.com/gin-gonic/gin"
)

func main() {
  r := gin.Default()

  r.Use(timeout.New(
    timeout.WithTimeout(100*time.Microsecond),
  ), func(c *gin.Context) {
    c.Next()
    if c.Writer.Status() == http.StatusRequestTimeout {
      slog.Error("request timeout")
    }
  })

  r.GET("/long", func(c *gin.Context) {
    time.Sleep(10 * time.Second)
    c.String(http.StatusOK, "long time ago")
  })

  s := &http.Server{
    Addr:              ":8000",
    Handler:           r,
    ReadTimeout:       30 * time.Second,
    WriteTimeout:      30 * time.Second,
    ReadHeaderTimeout: time.Second * 5,
  }

  if err := s.ListenAndServe(); err != nil {
    slog.Error("ListenAndServe failed", "err", err)
  }
}

4. Combining with Other Middleware

You can stack the timeout middleware with other middleware, such as authentication or logging:

func testResponse(c *gin.Context) {
  c.String(http.StatusRequestTimeout, "timeout")
}

// Custom timeout middleware
func timeoutMiddleware() gin.HandlerFunc {
  return timeout.New(
    timeout.WithTimeout(500*time.Millisecond),
    timeout.WithResponse(testResponse),
  )
}

// Example auth middleware
func authMiddleware() gin.HandlerFunc {
  return func(c *gin.Context) {
    debug := c.Query("debug")
    if debug != "true" {
      c.Next()
      return
    }
    c.AbortWithStatus(401)
  }
}

func main() {
  r := gin.New()
  r.Use(gin.Logger())
  r.Use(timeoutMiddleware())
  r.Use(authMiddleware())
  r.Use(gin.Recovery())

  r.GET("/", func(c *gin.Context) {
    time.Sleep(1 * time.Second)
    c.String(http.StatusOK, "Hello world!")
  })

  if err := r.Run(":8080"); err != nil {
    log.Fatal(err)
  }
}

Real-World Example: Testing Timeout

Suppose your handler always takes longer than the timeout:

r.GET("/", func(c *gin.Context) {
  time.Sleep(1 * time.Second)
  c.String(http.StatusOK, "Hello world!")
})

With a 500ms timeout, any request will return HTTP 408:

curl -i http://localhost:8080/

Expected response:

HTTP/1.1 408 Request Timeout
Content-Type: text/plain; charset=utf-8

timeout

More Examples

See the _example directory for more complete and advanced usage scenarios.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func New

func New(opts ...Option) gin.HandlerFunc

New wraps a handler and aborts the process of the handler if the timeout is reached

Types

type BufferPool added in v0.0.2

type BufferPool struct {
	// contains filtered or unexported fields
}

BufferPool represents a pool of buffers. It uses sync.Pool to manage the reuse of buffers, reducing memory allocation and garbage collection overhead.

func (*BufferPool) Get added in v0.0.2

func (p *BufferPool) Get() *bytes.Buffer

Get returns a buffer from the buffer pool. If the pool is empty, a new buffer is created and returned. This method ensures the reuse of buffers, improving performance.

func (*BufferPool) Put added in v0.0.2

func (p *BufferPool) Put(buf *bytes.Buffer)

Put adds a buffer back to the pool. This method allows the buffer to be reused in the future, reducing the number of memory allocations.

type Option

type Option func(*Timeout)

Option for timeout

func WithResponse

func WithResponse(h gin.HandlerFunc) Option

WithResponse add gin handler

func WithTimeout

func WithTimeout(timeout time.Duration) Option

WithTimeout set timeout

type Timeout

type Timeout struct {
	// contains filtered or unexported fields
}

Timeout struct

type Writer added in v0.0.2

type Writer struct {
	gin.ResponseWriter
	// contains filtered or unexported fields
}

Writer is a writer with memory buffer

func NewWriter added in v0.0.2

func NewWriter(w gin.ResponseWriter, buf *bytes.Buffer) *Writer

NewWriter will return a timeout.Writer pointer

func (*Writer) FreeBuffer added in v0.0.2

func (w *Writer) FreeBuffer()

FreeBuffer will release buffer pointer

func (*Writer) Header added in v0.0.2

func (w *Writer) Header() http.Header

Header will get response headers

func (*Writer) Status added in v0.0.4

func (w *Writer) Status() int

Status we must override Status func here, or the http status code returned by gin.Context.Writer.Status() will always be 200 in other custom gin middlewares.

func (*Writer) Write added in v0.0.2

func (w *Writer) Write(data []byte) (int, error)

Write will write data to response body

func (*Writer) WriteHeader added in v0.0.2

func (w *Writer) WriteHeader(code int)

WriteHeader sends an HTTP response header with the provided status code. If the response writer has already written headers or if a timeout has occurred, this method does nothing.

func (*Writer) WriteHeaderNow added in v1.1.0

func (w *Writer) WriteHeaderNow()

WriteHeaderNow the reason why we override this func is: once calling the func WriteHeaderNow() of based gin.ResponseWriter, this Writer can no longer apply the cached headers to the based gin.ResponseWriter. see test case `TestWriter_WriteHeaderNow` for details.

func (*Writer) WriteString added in v0.0.2

func (w *Writer) WriteString(s string) (int, error)

WriteString will write string to response body

Directories

Path Synopsis
_example
example01 command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL