# Gin Administrator & Developer Guide

This guide is for the operator running a Gin-based service in production,
the architect integrating Gin into a larger system, and the contributor
working on the framework itself. Every claim below is derived from the
Gin source at commit `d3ffc99` (release `v1.12.0`).

If you're looking for "how to write a handler", start with the **Gin User
Guide** instead. This document focuses on architecture, configuration
surface, deployment patterns, performance, security, internals, and
integration points.

---

## 1. Architecture Overview

Gin is a single-process HTTP framework. It composes Go's standard
`net/http` server with three custom layers: a radix-tree multiplexer, a
middleware chain abstraction, and a request-bound `Context` that wraps the
`http.ResponseWriter` and exposes ergonomic helpers.

The framework intentionally has no built-in process supervisor, no
out-of-process state, and no opinion about where you store session data,
cache responses, or speak to a database. It is a stand-alone library you
embed inside a Go binary.

### 1.1 High-level diagram

```
                 ┌────────────────────────────────────────────────────┐
                 │                  *gin.Engine                       │
 incoming        │                                                    │
 *http.Request → │  ServeHTTP(w, r):                                  │
                 │   1. routeTreesUpdated.Do(updateRouteTrees)        │
                 │   2. c := pool.Get().(*Context)  // sync.Pool      │
                 │   3. c.writermem.reset(w); c.Request = r           │
                 │   4. handleHTTPRequest(c)                          │
                 │      ┌───────────────────────────────────────┐     │
                 │      │  methodTrees (radix tree per method)  │     │
                 │      │  ────────────────────────────────────  │     │
                 │      │  GET  → root *node                     │     │
                 │      │  POST → root *node                     │     │
                 │      │   ...                                   │    │
                 │      │  root.getValue(path, params, ...)      │     │
                 │      └────────────────┬──────────────────────┘     │
                 │                        ▼                            │
                 │               handlers HandlersChain                │
                 │                        ▼                            │
                 │           ┌────────────────────────┐                │
                 │           │ Logger() → Recovery()  │ middleware     │
                 │           │      → user middleware │ chain          │
                 │           │      → final handler   │                │
                 │           └────────────────────────┘                │
                 │                        ▼                            │
                 │              c.Writer.Write(...)                    │
                 │   5. pool.Put(c)                                    │
                 └────────────────────────────────────────────────────┘
```

### 1.2 Component inventory

| Component | Path | Role |
|---|---|---|
| `Engine` | `gin.go` | The framework instance. Embeds `RouterGroup`, owns route trees, config flags, sync.Pool of contexts. |
| `RouterGroup` | `routergroup.go` | Path/middleware prefix builder. The engine itself is a root group. |
| `IRouter` / `IRoutes` | `routergroup.go` | Interfaces every group satisfies. |
| `Context` | `context.go` | Per-request handle. Pooled. Carries `Params`, `Errors`, `Keys`, `Writer`, etc. |
| `HandlerFunc` / `HandlersChain` | `gin.go` | `func(*Context)` and `[]HandlerFunc`. The chain plus an `index` is the middleware machinery. |
| `node` / `methodTree` | `tree.go` | Radix-tree implementation derived from `julienschmidt/httprouter`. |
| `responseWriter` | `response_writer.go` | Wraps `http.ResponseWriter`, tracks status/size, supports flush/hijack/push. |
| `Error` / `ErrorType` | `errors.go` | Tagged error attached to context with `c.Error()`. |
| `binding` package | `binding/` | Pluggable Binding/BindingBody/BindingUri implementations for every supported MIME. |
| `render` package | `render/` | One `Render` implementation per MIME (`JSON`, `HTML`, `XML`, `BSON`, `MsgPack`, `PDF`, …). |
| `codec/json` | `codec/json/` | Pluggable JSON encoder: stdlib (`json.go`), `goccy/go-json`, `json-iterator`, `bytedance/sonic`. |
| `internal/bytesconv` | `internal/bytesconv/` | Zero-copy `[]byte` ⇄ `string` helpers. |
| `internal/fs` | `internal/fs/` | Thin `http.FileSystem` wrapper used by template loaders. |
| `ginS` | `ginS/gins.go` | Optional process-wide singleton wrapper around the package functions. |
| `Logger` / `LoggerWithConfig` | `logger.go` | Access-log middleware; ANSI-colored when stdout is a TTY. |
| `Recovery` / `CustomRecovery` | `recovery.go` | Panic-catching middleware. |
| `BasicAuth*` | `auth.go` | Constant-time HTTP Basic and Proxy-Auth middleware. |

### 1.3 Technology stack

| Layer | Choice | Why |
|---|---|---|
| Language | Go 1.25+ | Stdlib-friendly; native `net/http` integration. |
| HTTP server | `net/http` | Battle-tested. Gin is an `http.Handler`. |
| HTTP/2 | `golang.org/x/net/http2` (+ `h2c`) | Automatically used by `ListenAndServeTLS`; `UseH2C` enables cleartext h2. |
| HTTP/3 / QUIC | `quic-go/quic-go` v0.59.0 | Used by `RunQUIC`. |
| Routing | Custom radix tree | Derived from `julienschmidt/httprouter` (license preserved on every file in `tree.go`). |
| Validation | `go-playground/validator/v10` v10.30.1 | Tag-driven; tag name overridden to `binding:`. |
| JSON (default) | `encoding/json` | Pluggable via `codec/json` interface. |
| JSON (alt) | `bytedance/sonic` v1.15.0, `goccy/go-json` v0.10.6, `json-iterator/go` v1.1.12 | Selected via build tag. |
| YAML | `goccy/go-yaml` v1.19.2 | Replaces stdlib YAML (which doesn't exist) and `gopkg.in/yaml.v3`. |
| TOML | `pelletier/go-toml/v2` v2.2.4 | Parsing + rendering. |
| Protobuf | `google.golang.org/protobuf` v1.36.11 | `proto.Marshal`/`Unmarshal`. |
| MsgPack | `ugorji/go/codec` v1.3.1 | Encoded only when `nomsgpack` build tag is **not** set. |
| BSON | `mongo-driver/v2` v2.5.0 | Used by both `binding.BSON` and `render.BSON`. |
| SSE | `gin-contrib/sse` v1.1.0 | Used by `c.SSEvent`. |
| Terminal detect | `mattn/go-isatty` v0.0.20 | Decides ANSI color in `Logger`. |
| Reflection helper | `modern-go/reflect2` v1.0.2 | Indirect via `json-iterator`. |

### 1.4 Data flow for a single request

1. `Engine.ServeHTTP` is invoked by `http.Server`.
2. `routeTreesUpdated.Do` (a `sync.Once`) runs `updateRouteTrees`, replacing
   the placeholder `\:` escape with `:` in every node path. (One-time fixup
   that lets users register `/literal\:colon` paths.)
3. A `*Context` is fetched from `engine.pool` (`sync.Pool`). The pool's
   factory pre-allocates `params` and `skippedNodes` slices sized by
   `engine.maxParams` / `engine.maxSections`, so steady-state per-request
   allocation is near zero.
4. `c.writermem.reset(w)` rewires the response writer wrapper.
5. `c.reset()` clears per-request state.
6. `handleHTTPRequest(c)` looks up the method's tree, calls
   `root.getValue(rPath, c.params, c.skippedNodes, unescape)`, populates
   `c.Params` and `c.handlers`, and invokes `c.Next()` which walks the
   handler chain.
7. After all handlers return, `pool.Put(c)` returns the context to the
   pool.

### 1.5 Concurrency model

- The radix-tree mutates only during route registration. All
  `engine.GET`, `engine.Group`, `group.Static`, etc., must be called
  before `engine.Run` (or whichever `Run*` you choose).
- After the first `ServeHTTP`, `routeTreesUpdated.Do(updateRouteTrees)`
  fixes up escaped colons and the trees become read-only for the lifetime
  of the engine.
- Each request gets its own `*Context` (pooled). The pool ensures that
  goroutine A's context cannot leak into goroutine B's request unless you
  explicitly call `c.Copy()` to detach it.
- `Context.Set`/`Get`/`MustGet`/`Delete` use a `sync.RWMutex`
  (`Context.mu`) to make `Keys` map access safe for goroutines spawned
  from a handler.

> [!NOTE]
> The radix-tree commits to the assumption that route registration is
> single-threaded. Calling `r.GET` from multiple goroutines while the
> server is already accepting connections is undefined behavior — never
> documented as supported, and the comment in `tree.go:addRoute` says so:
> "Not concurrency-safe!"

### 1.6 Filesystem layout

```
gin/
├── auth.go               BasicAuth + BasicAuthForProxy + Accounts
├── context.go            *Context type and ~100 helper methods
├── debug.go              IsDebugging, debugPrint*
├── doc.go                package-level documentation only
├── errors.go             Error, ErrorType bitmask, errorMsgs
├── fs.go                 OnlyFilesFS / Dir helpers
├── gin.go                Engine, New, Default, Run*, ServeHTTP
├── logger.go             Logger middleware + LogFormatterParams
├── mode.go               Debug/Release/Test mode + GIN_MODE handling
├── path.go               cleanPath / fixedPath helpers
├── recovery.go           Recovery / CustomRecovery middleware
├── response_writer.go    *responseWriter (status, size, hijack, push, flush)
├── routergroup.go        IRouter, IRoutes, RouterGroup, Group/Use/Static…
├── tree.go               radix-tree node, addRoute, getValue
├── utils.go              H, Bind helper, WrapF/WrapH, joinPaths
├── version.go            const Version = "v1.12.0"
├── binding/              one file per content type + form_mapping helpers
├── render/               one file per render type
├── codec/json/           pluggable JSON API surface
├── internal/bytesconv/   string<->[]byte fast paths
├── internal/fs/          http.FileSystem wrapper for templates
└── ginS/                 thin singleton wrapper
```

### 1.7 What Gin is NOT

- **Not a process supervisor.** No restart, no health-check endpoint
  unless you write one, no metrics endpoint.
- **Not an ORM.** Gin holds opinions about HTTP, not databases.
- **Not a CSRF / session library.** Use community middleware
  (gin-contrib) or roll your own; Gin only provides cookie helpers and
  same-site enums via `c.SetSameSite`.
- **Not a clustered runtime.** A single Gin process binds a single
  network address (or socket / fd / QUIC listener). Run multiple replicas
  behind a load balancer for horizontal scale.

---

## 2. System Requirements

### 2.1 Build environment

| Item | Requirement |
|---|---|
| Go toolchain | 1.25.0 or newer (declared by `go.mod`) |
| Module mode | Required (`GO111MODULE=on` or implicit since Go 1.16) |
| CPU | Any Go-supported architecture (amd64, arm64, ppc64le, s390x, 386, riscv64) |
| Memory | A few hundred MB during `go build` |
| Disk | ~50 MB module cache including indirect dependencies |
| Network | Egress to `proxy.golang.org` and `sum.golang.org` for fetch + checksum |

### 2.2 Runtime environment

| Item | Notes |
|---|---|
| OS | Linux, macOS, Windows, *BSD, illumos — any Go-supported target. Some `Run*` methods (e.g., `RunUnix`, `RunFd`) are POSIX-only. |
| File descriptors | Default 1024 limit on Linux is fine for moderate traffic; raise via `ulimit -n` for >1k concurrent connections. |
| TCP | At least one outbound or inbound interface. `Run` binds 0.0.0.0 by default. |
| TLS material | For `RunTLS` / `RunQUIC`: a PEM-encoded cert + private key pair. Test material lives in `testdata/certificate/{cert,key}.pem` (do not use in production). |
| Templates | If using `LoadHTMLGlob`, the template files must be readable by the process at runtime. |

### 2.3 Optional dependencies

These are pulled in transitively only if you use the relevant feature:

- `bytedance/sonic` — only when JSON codec is set to sonic.
- `quic-go/quic-go` + `quic-go/qpack` — only when `RunQUIC` is called.
- `mongo-driver/v2` — only when binding or rendering BSON.

---

## 3. Configuration Reference

This section is the authoritative list of every public configuration knob,
its default, its source, and its effect.

### 3.1 Engine fields (`gin.Engine`)

| Field | Type | Default | Source | Effect |
|---|---|---|---|---|
| `RedirectTrailingSlash` | `bool` | `true` | `gin.go` | Auto-redirect missing/extra trailing slash to canonical form. |
| `RedirectFixedPath` | `bool` | `false` | `gin.go` | Try case-insensitive match after cleaning path. |
| `HandleMethodNotAllowed` | `bool` | `false` | `gin.go` | Return 405 with `Allow:` header when path matches but method doesn't. |
| `ForwardedByClientIP` | `bool` | `true` | `gin.go` | If true and remote IP is in trusted-proxy CIDRs, parse `RemoteIPHeaders`. |
| `AppEngine` | `bool` | `false` | `gin.go` | **Deprecated.** Use `TrustedPlatform = PlatformGoogleAppEngine`. |
| `UseRawPath` | `bool` | `false` | `gin.go` | Match against `URL.RawPath`. |
| `UseEscapedPath` | `bool` | `false` | `gin.go` | Match against `URL.EscapedPath()`; overrides `UseRawPath`. |
| `UnescapePathValues` | `bool` | `true` | `gin.go` | Unescape extracted parameter values. |
| `RemoveExtraSlash` | `bool` | `false` | `gin.go` | Run `path.Clean` on request path before lookup. |
| `RemoteIPHeaders` | `[]string` | `["X-Forwarded-For", "X-Real-IP"]` | `gin.go` | Headers consulted to compute `c.ClientIP()`. |
| `TrustedPlatform` | `string` | `""` | `gin.go` | Set to `PlatformGoogleAppEngine` / `PlatformCloudflare` / `PlatformFlyIO` to trust that platform's IP header. |
| `MaxMultipartMemory` | `int64` | `32 << 20` (32 MB) | `gin.go` | Buffer size for multipart parsing; remainder spills to disk. |
| `UseH2C` | `bool` | `false` | `gin.go` | Wrap with `h2c.NewHandler` so plain HTTP serves HTTP/2 cleartext. |
| `ContextWithFallback` | `bool` | `false` | `gin.go` | `c.Deadline/Done/Err/Value` fall through to `c.Request.Context()` if local Keys lacks the value. |
| `HTMLRender` | `render.HTMLRender` | `nil` | `gin.go` | Set by `LoadHTMLGlob`/`LoadHTMLFiles`/`LoadHTMLFS`/`SetHTMLTemplate`. |
| `FuncMap` | `template.FuncMap` | `{}` | `gin.go` | Funcs available in HTML templates. |

Trusted platform constants:

| Constant | Header trusted |
|---|---|
| `gin.PlatformGoogleAppEngine` | `X-Appengine-Remote-Addr` |
| `gin.PlatformCloudflare` | `CF-Connecting-IP` |
| `gin.PlatformFlyIO` | `Fly-Client-IP` |

> [!IMPORTANT]
> When `TrustedPlatform` is set to a non-empty value, Gin uses that
> single header as the canonical client IP and skips both `RemoteIPHeaders`
> and the trusted-proxy check. Use it only when you are confident that
> the platform-injected header cannot be spoofed by an external caller.

### 3.2 Mode constants (`mode.go`)

| Constant | Value | Set by |
|---|---|---|
| `gin.DebugMode` | `"debug"` | Default; auto-selected when no `GIN_MODE` and no test flag. |
| `gin.ReleaseMode` | `"release"` | `gin.SetMode(gin.ReleaseMode)` or `GIN_MODE=release`. |
| `gin.TestMode` | `"test"` | Auto-selected when `flag.Lookup("test.v")` is non-nil; or `GIN_MODE=test`. |
| `gin.EnvGinMode` | `"GIN_MODE"` | The env var name itself. |

The package-level `init()` in `mode.go` reads `os.Getenv("GIN_MODE")` and
calls `SetMode`, panicking with `"gin mode unknown: <value>"` on any
non-recognized value.

### 3.3 Other tunable globals

| Symbol | Default | Source | Purpose |
|---|---|---|---|
| `gin.DefaultWriter` | `os.Stdout` | `mode.go` | Where `Logger()` and debug prints go. |
| `gin.DefaultErrorWriter` | `os.Stderr` | `mode.go` | Where `Recovery()` and debug-error prints go. |
| `gin.DebugPrintRouteFunc` | `nil` | `debug.go` | Hook for custom route-print formatting. |
| `gin.DebugPrintFunc` | `nil` | `debug.go` | Hook for custom debug-print formatting. |
| `binding.Validator` | `*defaultValidator` | `binding/binding.go` | Validation engine. Replace with your own `StructValidator` to swap entirely. |
| `binding.EnableDecoderUseNumber` | `false` | `binding/json.go` | Decode JSON numbers as `json.Number` (not float64). |
| `binding.EnableDecoderDisallowUnknownFields` | `false` | `binding/json.go` | Reject unknown JSON fields. |

Setters that flip these from `gin`-package land:

```go
gin.DisableBindValidation()                  // Validator = nil
gin.EnableJsonDecoderUseNumber()             // EnableDecoderUseNumber = true
gin.EnableJsonDecoderDisallowUnknownFields() // EnableDecoderDisallowUnknownFields = true
```

### 3.4 Environment variables

| Variable | Read by | Effect |
|---|---|---|
| `GIN_MODE` | `mode.go:init` | Sets the engine mode at process start. Values: `debug`, `release`, `test`. |
| `PORT` | `utils.go:resolveAddress` | Selected by `r.Run()` when no address is supplied. |
| `TERM` | `logger.go:LoggerWithConfig` | If `dumb`, suppresses ANSI color in the access log. |

> [!IMPORTANT]
> Both `GIN_MODE` and `PORT` are the only environment variables Gin reads
> directly. Everything else is owned by your application; Gin never reads
> a config file from disk and never writes to one.

### 3.5 LoggerConfig

`gin.LoggerWithConfig(LoggerConfig{...})` accepts:

| Field | Type | Default | Purpose |
|---|---|---|---|
| `Formatter` | `LogFormatter` | `defaultLogFormatter` | Function returning the line to write. |
| `Output` | `io.Writer` | `gin.DefaultWriter` | Where to write. |
| `SkipPaths` | `[]string` | `nil` | Skip lines for these full request paths. |
| `SkipQueryString` | `bool` | `false` | Strip the query string from the logged path. |
| `Skip` | `Skipper` (`func(*Context) bool`) | `nil` | Per-request skip predicate. |

`LogFormatterParams` exposes: `Request`, `TimeStamp`, `StatusCode`,
`Latency`, `ClientIP`, `Method`, `Path`, `ErrorMessage`, `BodySize`,
`Keys`. Color helpers: `StatusCodeColor`, `LatencyColor`, `MethodColor`,
`ResetColor`, `IsOutputColor`.

Color toggles:

```go
gin.DisableConsoleColor() // never colorize
gin.ForceConsoleColor()   // always colorize
```

By default the formatter colorizes only when `gin.DefaultWriter` is a TTY
and `TERM != "dumb"`.

### 3.6 RecoveryFunc

`gin.CustomRecovery(handle RecoveryFunc)` accepts a `func(c *Context, err
any)` that runs after a panic is caught (and after the broken-pipe
short-circuit fires). The `defaultHandleRecovery` simply calls
`c.AbortWithStatus(http.StatusInternalServerError)`.

### 3.7 Functional options (`OptionFunc`)

`gin.New(opts...)` and `gin.Default(opts...)` apply each `OptionFunc` to
the new engine before returning. The pattern:

```go
func WithMaxUploadMB(n int64) gin.OptionFunc {
    return func(e *gin.Engine) { e.MaxMultipartMemory = n << 20 }
}

r := gin.Default(WithMaxUploadMB(8))
```

`engine.With(opts...)` is the same operation on an existing engine.

### 3.8 Configuration files

There are none. Gin does not parse YAML, TOML, JSON, or ENV files itself.
Use `viper`, `koanf`, `os.Getenv`, or whatever your application prefers,
and apply values to engine fields before `Run`.

---

## 4. Deployment & Scaling

### 4.1 Deployment topology

A typical production layout:

```
                 ┌────────────┐
   Internet  ──→ │  Load LB   │ (NGINX, HAProxy, ALB)
                 │  TLS term  │
                 └─────┬──────┘
                       │ HTTP/1.1 or h2c
        ┌──────────────┼──────────────┐
        ▼              ▼              ▼
   ┌────────┐     ┌────────┐     ┌────────┐
   │ gin #1 │     │ gin #2 │     │ gin #3 │  (replica set)
   └────────┘     └────────┘     └────────┘
```

The Gin process is stateless. Multiple replicas + sticky-by-cookie or
round-robin LB scaling is the canonical pattern.

### 4.2 Listening modes

| Mode | When to use |
|---|---|
| `r.Run(":8080")` | Most common. Simple HTTP listener; LB does TLS. |
| `r.RunTLS(":443", cert, key)` | Direct HTTPS termination (small-to-medium scale). |
| `r.RunUnix("/run/app.sock")` | Single-host setups; nginx talks via Unix socket for lower overhead. |
| `r.RunFd(fd)` | systemd socket activation (`SystemdSockets()` from `coreos/go-systemd`, then `RunFd(int(socketFile.Fd()))`). |
| `r.RunListener(l)` | Pre-configured listener (e.g., `tls.NewListener` with custom config). |
| `r.RunQUIC(":443", cert, key)` | HTTP/3 endpoint. Pair with HTTP/2 fallback on the LB. |
| `r.UseH2C = true; r.Run(":8080")` | HTTP/2 cleartext for service-to-service traffic. |

### 4.3 Build the binary

```bash
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
  go build -trimpath -ldflags="-s -w" -o myservice ./cmd/myservice
```

Stripping symbols (`-s -w`) cuts a significant fraction of the binary
size; `-trimpath` removes local file paths from the binary for
reproducible builds.

For `nomsgpack`:

```bash
go build -tags nomsgpack -trimpath -ldflags="-s -w" -o myservice ./cmd/myservice
```

### 4.4 Sample Dockerfile

```dockerfile
FROM golang:1.25-alpine AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /out/app ./cmd/app

FROM gcr.io/distroless/static:nonroot
COPY --from=build /out/app /app
EXPOSE 8080
ENV GIN_MODE=release
ENTRYPOINT ["/app"]
```

Key choices:

- `distroless/static:nonroot` — minimal attack surface; no shell.
- `GIN_MODE=release` baked in.
- `CGO_ENABLED=0` so the binary is statically linked and matches the
  distroless `static` base.

### 4.5 Sample systemd unit

```ini
[Unit]
Description=My Gin Service
After=network.target

[Service]
Type=simple
User=app
Environment=GIN_MODE=release
Environment=PORT=8080
ExecStart=/usr/local/bin/myservice
Restart=on-failure
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target
```

For socket activation, replace `Type=simple` with `Type=notify` and add a
`.socket` unit that hands a file descriptor; consume with `RunFd`.

### 4.6 Horizontal scaling notes

- **Statelessness:** Don't store anything in `c.Keys` that you expect to
  survive across requests. Use Redis/Postgres/etc.
- **Sticky sessions:** Only required if you cache user state in process
  memory (don't). If you must, configure your LB to pin by cookie or
  source IP.
- **Health check:** Add a `r.GET("/healthz", ...)` returning 200; point
  your LB and orchestrator at it.
- **Graceful shutdown:** `r.Run` has no `Shutdown`. Use `*http.Server`
  directly (User Guide §5.10) and listen for SIGTERM.

> [!TIP]
> When running behind Kubernetes, set `terminationGracePeriodSeconds` to
> exceed your in-flight request timeout, and configure a `preStop` hook
> that sleeps just long enough for the LB to drain the pod before
> SIGTERM lands.

### 4.7 Vertical sizing

- **CPU:** Gin is CPU-bound on JSON encode/decode and on regex/path
  cleanup for `RedirectFixedPath`. A modern x86 core handles tens of
  thousands of requests per second on small payloads.
- **Memory:** Per-request memory is dominated by the request body. The
  context pool keeps allocations tight; expect a few KB of headroom per
  in-flight request.
- **GOMAXPROCS:** Set to the number of cores allocated to the container
  (e.g., via `automaxprocs` or by reading the cgroup quota). Defaults to
  the host's CPU count, which over-allocates inside containers.

---

## 5. Operations

### 5.1 Logs

Gin writes two logical streams:

- **Access log** — one line per request via `Logger()`, to
  `gin.DefaultWriter` (stdout by default).
- **Error / panic log** — `Recovery()` panics + `debugPrint*` warnings,
  to `gin.DefaultErrorWriter` (stderr).

The default access-log format:

```text
[GIN] 2026/04/28 - 14:50:00 | 200 |   1.234ms |  10.0.0.5 | GET     "/api/v1/users/42"
```

Parsed positionally. For structured logs, swap the formatter:

```go
r.Use(gin.LoggerWithConfig(gin.LoggerConfig{
    Formatter: func(p gin.LogFormatterParams) string {
        b, _ := json.Marshal(map[string]any{
            "ts":     p.TimeStamp.UTC().Format(time.RFC3339Nano),
            "status": p.StatusCode,
            "lat_ms": p.Latency.Milliseconds(),
            "ip":     p.ClientIP,
            "method": p.Method,
            "path":   p.Path,
        })
        return string(b) + "\n"
    },
    Output: os.Stdout,
}))
```

### 5.2 Health checks

There is no built-in health endpoint. Add one:

```go
r.GET("/healthz", func(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{"status": "ok", "version": gin.Version})
})

r.GET("/readyz", func(c *gin.Context) {
    if !db.Ping() {
        c.AbortWithStatusJSON(503, gin.H{"db": "down"})
        return
    }
    c.JSON(200, gin.H{"status": "ready"})
})
```

> [!NOTE]
> Distinguish *liveness* (process is up) from *readiness* (process is
> capable of serving traffic). `/healthz` should always succeed if the
> process is running; `/readyz` should fail when downstream dependencies
> are unhealthy so orchestrators stop sending traffic.

### 5.3 Metrics

No built-in metrics. Pair with `prometheus/client_golang` and a
middleware:

```go
import "github.com/prometheus/client_golang/prometheus"
import "github.com/prometheus/client_golang/prometheus/promhttp"

requestDuration := prometheus.NewHistogramVec(/* ... */)
prometheus.MustRegister(requestDuration)

r.Use(func(c *gin.Context) {
    start := time.Now()
    c.Next()
    requestDuration.WithLabelValues(
        c.Request.Method,
        c.FullPath(),                    // route pattern, not raw URL
        strconv.Itoa(c.Writer.Status()),
    ).Observe(time.Since(start).Seconds())
})

r.GET("/metrics", gin.WrapH(promhttp.Handler()))
```

`c.FullPath()` is the right label — it returns `/users/:id`, not
`/users/42`, so cardinality stays bounded.

### 5.4 Tracing

OpenTelemetry integrates via middleware that creates a span around each
request. The pattern:

```go
r.Use(func(c *gin.Context) {
    ctx, span := tracer.Start(c.Request.Context(), c.FullPath())
    defer span.End()
    c.Request = c.Request.WithContext(ctx)
    c.Next()
    span.SetAttributes(
        attribute.Int("http.status_code", c.Writer.Status()),
        attribute.String("http.method", c.Request.Method),
    )
})
```

Set `r.ContextWithFallback = true` so the trace context propagates through
`c.Deadline/Done/Err/Value`.

### 5.5 Common runbooks

#### Process pegged at 100% CPU

1. Capture a profile: `go tool pprof http://localhost:8080/debug/pprof/profile?seconds=30`
   (assumes you've registered the `net/http/pprof` package with `gin.WrapH`).
2. Look for hot functions in JSON encoding (`encoding/json.Marshal`),
   `sync.(*Mutex).Lock`, or template execution.
3. Check whether `gin.IndentedJSON` is used in hot paths — it is roughly
   10x slower than `gin.JSON`; replace with `gin.JSON` if so.

#### Process leaking memory

1. Capture a heap profile:
   `go tool pprof http://localhost:8080/debug/pprof/heap`.
2. Check `c.Set` usage — if you store unbounded data in `c.Keys` and copy
   contexts into a goroutine via `c.Copy()`, that data outlives the
   request.
3. Inspect template glob — if `LoadHTMLGlob` is called on every request
   (it shouldn't be), each call re-parses templates and leaks closures.

#### Connections refused / stuck pending

1. `ss -ltn | grep <port>` — confirm the listener is open.
2. Check `ulimit -n` and the listener backlog (set via
   `net.Listen` if you use `RunListener`).
3. Look for handlers blocking forever. The Recovery middleware does
   *not* time out long-running handlers; you must add a request timeout
   middleware yourself or set `WriteTimeout` / `ReadTimeout` on the
   underlying `*http.Server` (use `RunListener` to install them).

> [!TIP]
> Use `http.TimeoutHandler(handler, dur, msg)` wrapped via
> `gin.WrapH(...)` for per-request budgets. It returns 503 if the handler
> exceeds the deadline, but ungracefully — the goroutine continues running.

### 5.6 Backup & state

Gin holds no persistent state. Backup considerations are entirely about
the data store you use. The only on-disk files Gin touches are the ones
your handlers create (uploads via `SaveUploadedFile`, templates loaded
from disk).

---

## 6. Performance

### 6.1 Throughput characteristics

The benchmark suite (`benchmarks_test.go`) measures the steady-state cost
of hitting an empty handler. Representative results on commodity x86:

- `BenchmarkOneRoute` — serving a single registered route with no
  middleware. Lowest possible cost.
- `BenchmarkRecoveryMiddleware` — adds the cost of a deferred
  `recover()` per request.
- `BenchmarkLoggerMiddleware` — adds the cost of formatting one log line
  to a `mockWriter` (no I/O to the kernel).
- `BenchmarkManyHandlers` — adds three trivial middleware to confirm the
  loop is not super-linear.
- `Benchmark5Params` — five path parameters; tests the radix-tree
  walking + parameter capture cost.
- `BenchmarkOneRouteJSON`, `BenchmarkOneRouteHTML`,
  `BenchmarkOneRouteString` — measure renderer cost separately.
- `Benchmark404`, `Benchmark404Many` — measure the not-found path,
  including the "trailing slash" / "fixed path" probes.

These confirm Gin's allocation budget per request is bounded and small.
Reproduce locally:

```bash
go test -bench=. -benchmem -count=5 ./... | tee bench.txt
```

### 6.2 Hot-path optimizations Gin uses

- **Context pooling.** `engine.pool.New` allocates a `*Context` with
  pre-sized `Params` and `skippedNodes` slices. After the first warmup,
  `pool.Get`/`Put` is allocation-free in steady state.
- **Zero-copy strings.** `internal/bytesconv.{StringToBytes,BytesToString}`
  use `unsafe` to alias `[]byte` and `string` headers without copy. Used
  in basic-auth comparison (`auth.go`), path canonicalization
  (`tree.go`), and JSONP bookkeeping.
- **Radix tree.** The router avoids regex; matching is byte-by-byte
  longest-common-prefix. Each level keeps children sorted by hit count
  (`incrementChildPrio`) so frequently used routes are reached first.
- **Sync.Once for tree finalization.** `routeTreesUpdated.Do` runs the
  one-time path fixup on the first request rather than at registration,
  so registration is fast.
- **Lazy query/form caches.** `c.queryCache` and `c.formCache` are
  populated on first access and reused for subsequent reads.
- **Pluggable JSON.** `codec/json/api.go` exposes a `JSONAPI` interface;
  drop-in replacements for `encoding/json` are available via build tags
  (sonic, go-json, jsoniter).

### 6.3 Tuning levers

| Lever | When to use |
|---|---|
| Build with `-tags sonic` (or your JSON of choice) | Hot JSON paths > 30% of CPU time. |
| `r.MaxMultipartMemory = small` | If you handle small multipart bodies, lower this so memory pressure stays low. |
| Replace `Logger()` with custom formatter | If `defaultLogFormatter` shows up in profiles, swap to a non-colorized minimal formatter. |
| `gin.DisableConsoleColor()` | Silences ANSI escape codes (small but consistent saving in non-TTY logs). |
| Custom validator | The default validator caches metadata per type via `sync.Once`; first request per type pays the reflection cost. |
| Reduce middleware depth | Each middleware is a function call + `c.index` increment. Keep the global stack short. |

> [!TIP]
> Always measure before optimizing. Run `pprof` and confirm the bottleneck
> before swapping JSON codecs or rewriting middleware. The default
> `encoding/json` is the path of least surprise and adequate for most
> workloads.

### 6.4 Anti-patterns

- **`gin.IndentedJSON` in hot paths.** ~10x slower than `gin.JSON`. Use
  for debug endpoints only.
- **`SetHTMLTemplate` after server start.** The function is **not** thread
  safe (`debug.go:debugPrintWARNINGSetHTMLTemplate` warns explicitly).
  Call it before `Run`.
- **Reading `c.Request.Body` in a middleware without buffering.** Once
  read, it's gone. Use `ShouldBindBodyWith*` to make the body re-readable.
- **`c.Copy()` in every request without need.** It clones `Keys` and
  `Params`; the allocation is real. Only use it when handing the context
  to a goroutine that outlives the request.

---

## 7. Security

Gin is a thin HTTP framework — security responsibilities are split between
the framework, the operator, and the application code. This section
catalogs the framework's surface and identifies places where you must add
your own controls.

### 7.1 What Gin handles for you

- **Constant-time Basic Auth comparison.** `auth.go:searchCredential`
  uses `crypto/subtle.ConstantTimeCompare` against the full
  `Basic base64(user:pass)` value, mitigating timing attacks.
- **HTML auto-escaping in templates.** Because Gin uses `html/template`
  (not `text/template`) for `LoadHTMLGlob`, contextual auto-escaping
  applies — `{{.UntrustedInput}}` is escaped by template type.
- **Authorization-header masking in panic dumps.**
  `recovery.go:secureRequestDump` rewrites any `Authorization:` line in
  the panic dump to `Authorization: *`. Other headers are unmodified.
- **`SecureJSON` array-prefix protection.** When the top-level value is a
  JSON array, `c.SecureJSON` prefixes the output with the configurable
  `secureJSONPrefix` (default `while(1);`). This frustrates pre-2014 JSON
  hijacking attacks. Configure via `engine.SecureJsonPrefix(prefix)`.
- **Path canonicalization.** `RemoveExtraSlash` and `RedirectFixedPath`
  reduce the surface for path-traversal-style routing tricks.
- **Trusted-proxies enforcement.** `c.ClientIP()` only reads
  `RemoteIPHeaders` if the connecting peer is in `trustedCIDRs`. The
  default is `0.0.0.0/0` + `::/0` (i.e., trust everyone) — see §7.4.

### 7.2 What Gin does NOT handle

- **CSRF protection.** No built-in CSRF token middleware. Use a community
  package (e.g., `gin-contrib/csrf`) or implement double-submit cookies
  yourself.
- **Rate limiting.** No built-in. Use `golang.org/x/time/rate` or a
  proxy-level limiter.
- **Content Security Policy / Strict-Transport-Security.** Set these
  yourself with `c.Header(...)` or a community middleware.
- **CORS.** No built-in. `gin-contrib/cors` is the canonical choice.
- **Body size cap.** `MaxMultipartMemory` only governs multipart parsing.
  For total body size, wrap with `http.MaxBytesReader` in middleware.
- **Slow-loris / read-timeout.** Gin uses `&http.Server{Handler: ...}`
  with **no** read or write timeout. Set them via `RunListener` or
  construct the `http.Server` yourself.
- **Encryption at rest / TLS material rotation.** Operational concerns
  outside the framework.

> [!CAUTION]
> The default `r.Run`-built `http.Server` has zero read, write, and idle
> timeouts. A single misbehaving client (or an attacker holding a
> connection open with a slow trickle of bytes) can keep a goroutine
> alive indefinitely. **Always** construct `*http.Server` yourself, set
> `ReadHeaderTimeout`, `ReadTimeout`, `WriteTimeout`, and `IdleTimeout`,
> then call `r.RunListener` or pass `r.Handler()` to your server.

### 7.3 TLS / HTTPS

`r.RunTLS(addr, certFile, keyFile)` wraps `http.ListenAndServeTLS`. The
underlying `tls.Config` is the Go default — modern ciphers, TLS 1.2+,
ALPN for HTTP/2.

To customize TLS (e.g., minimum version, cipher suites, ALPN, mutual
TLS):

```go
listener, err := tls.Listen("tcp", ":443", &tls.Config{
    MinVersion: tls.VersionTLS12,
    Certificates: []tls.Certificate{cert},
    // ClientCAs / ClientAuth for mTLS, etc.
})
if err != nil { log.Fatal(err) }
r.RunListener(listener)
```

For QUIC/HTTP/3, `r.RunQUIC` accepts a cert + key; the handshake is
HTTP/3-only. Combine with HTTP/2 fallback at the LB if some clients are
HTTP/2-only.

### 7.4 Trusted proxies (the most-misunderstood setting)

By default Gin trusts every proxy: `trustedProxies = ["0.0.0.0/0",
"::/0"]`. Combined with `ForwardedByClientIP = true` and
`RemoteIPHeaders = ["X-Forwarded-For", "X-Real-IP"]`, this means **any
external client can supply an `X-Forwarded-For` header and Gin will
believe it.**

`engine.isUnsafeTrustedProxies` returns `true` for this default
configuration; the `Run`/`RunTLS`/`RunUnix`/`RunFd`/`RunQUIC`/`RunListener`
methods all log a `[WARNING] You trusted all proxies, this is NOT safe`
debug print on startup.

Hardening:

```go
// Trust only the LB CIDR
r.SetTrustedProxies([]string{"10.0.0.0/8"})

// OR: disable header parsing entirely
r.SetTrustedProxies(nil)
```

When `SetTrustedProxies(nil)` is in effect, `c.ClientIP()` returns the TCP
peer address only. That's the right answer when Gin is the edge.

> [!WARNING]
> If you log, rate-limit, ban, or audit on `c.ClientIP()`, you are
> effectively trusting whatever `X-Forwarded-For` says — and any external
> client can spoof that. Lock down `SetTrustedProxies` before the first
> production deployment.

### 7.5 Authentication patterns

Gin ships `BasicAuth` and `BasicAuthForProxy`. Both compare in constant
time. Neither implements rate limiting or account lockout — add those at
the application layer.

For token / OAuth / JWT auth, write a middleware:

```go
func RequireToken() gin.HandlerFunc {
    return func(c *gin.Context) {
        token := c.GetHeader("Authorization")
        if !strings.HasPrefix(token, "Bearer ") {
            c.AbortWithStatusJSON(401, gin.H{"error": "missing bearer token"})
            return
        }
        claims, err := verify(strings.TrimPrefix(token, "Bearer "))
        if err != nil {
            c.AbortWithStatusJSON(401, gin.H{"error": "invalid token"})
            return
        }
        c.Set("claims", claims)
        c.Next()
    }
}
```

`AbortWithStatusJSON` ensures handlers below the middleware never run.

### 7.6 Cookie security

`c.SetCookie(name, value, maxAge, path, domain, secure, httpOnly)`
constructs a single `http.Cookie`. The `secure` and `httpOnly` flags are
required for session cookies; `c.SetSameSite(http.SameSiteStrictMode)` (or
`Lax`) before `SetCookie` adds the `SameSite` attribute.

`c.SetCookieData(*http.Cookie)` is the lower-level escape hatch for setting
all cookie fields (including `MaxAge=-1` to delete, `Partitioned`,
`Quoted`, etc.).

### 7.7 Header sanitization

Gin does not sanitize response headers — `c.Header(k, v)` is a literal
write. Reject newlines from user-controlled input before passing it to
`Header` to prevent header-injection.

### 7.8 Recovery panic handling

`Recovery()` writes a 500 response with no body and prints the panic +
stack trace to stderr. In **debug mode** the dump is verbose (request
body + headers + stack); in **release mode** only the panic value + stack
are printed. The `Authorization:` header is masked in both modes
(`secureRequestDump`).

> [!CAUTION]
> Other sensitive headers (`Cookie`, `X-API-Key`, etc.) are **not**
> masked by Recovery. If you log panics to a centralized system, consider
> wrapping `CustomRecoveryWithWriter` to scrub headers before they hit
> your log pipeline.

### 7.9 Dependency security

Gin's go.mod pins major versions of cryptographic and parsing libraries.
At commit `d3ffc99`:

- `github.com/go-playground/validator/v10 v10.30.1`
- `golang.org/x/crypto v0.49.0`
- `golang.org/x/net v0.52.0`
- `github.com/quic-go/quic-go v0.59.0`

Run `go list -m -u all` periodically to spot upstream advisories, and
keep `golang.org/x/{crypto,net}` close to upstream — security fixes
ship there first.

Issue #4236 (CLOSED) tracked GO-2025-3563; the dependency was upgraded.

---

## 8. Developer Internals

This section is for contributors and integrators who need to know how Gin
works at the source level.

### 8.1 The radix tree (`tree.go`)

The router is a per-method radix tree. Each `methodTree` holds a `root
*node`; `engine.trees` is a slice of them. `engine.addRoute(method, path,
handlers)` finds (or creates) the tree for the method and calls
`root.addRoute(path, handlers)`.

Node anatomy:

```go
type node struct {
    path      string         // prefix matched at this node
    indices   string          // first byte of each child path; index by byte search
    wildChild bool            // true if the last child is a :param or *catchAll
    nType     nodeType        // static / root / param / catchAll
    priority  uint32          // hit count; used for re-ordering siblings
    children  []*node         // sorted by priority (most-hit first)
    handlers  HandlersChain   // the chain to invoke when this leaf matches
    fullPath  string          // for c.FullPath()
}
```

Insertion (`addRoute`) walks the tree byte-by-byte using
`longestCommonPrefix`. When a new path diverges, the node splits: a child
is inserted carrying the old path tail, and a new branch carries the new
path tail. Wildcards (`:name`, `*name`) become `param` or `catchAll`
nodes.

Lookup (`getValue`) walks the same byte-by-byte path. When it hits a
`param` node, it captures the corresponding URL segment into the
`Params` slice; for `catchAll` it captures the entire remaining path.

Performance properties:

- **O(L)** where L is the URL path length. No regex, no backtracking
  (modulo a small `skippedNodes` stack used for wildcard fallback).
- **Re-ordering siblings.** Each successful match calls
  `incrementChildPrio` to bump the child's priority and reorder it
  toward the front, so frequently used routes are matched first.
- **Cap on parameters.** `engine.maxParams` and `engine.maxSections`
  track the largest path's parameter / `/`-section count and drive the
  pre-sized `Params` slice in the pool factory.

### 8.2 The middleware chain (`HandlersChain` + `Context.Next`)

`HandlersChain` is `[]HandlerFunc`. The chain for a route is built at
registration:

```
finalChain = parentGroup.Handlers ++ groupHandlers ++ routeHandler
```

`Context.Next` walks `c.handlers` from `c.index+1` to the end, invoking
each. `Context.Abort` sets `c.index = abortIndex` (a sentinel). After
that, `Next` becomes a no-op.

Two implications:

1. The chain can be *paused* at any point by calling `c.Next()` from
   inside a middleware. Code before `c.Next()` runs on the way in; code
   after runs on the way back. This is how Gin's logger captures the
   final status code.
2. The chain can be *abandoned* by calling `c.Abort()`. Subsequent
   middlewares are skipped on the way back as well, because each
   handler's `Next()` call sees `c.index >= abortIndex` and returns
   immediately.

Final chain length is capped at `abortIndex - 1 = 63` total handlers
(the `assert1(finalSize < int(abortIndex), "too many handlers")` check
in `routergroup.go:combineHandlers`).

### 8.3 `Context` lifecycle

```
Engine.ServeHTTP
  ├── pool.Get   → *Context (reused or newly allocated)
  ├── reset      → clear Params, Keys, Errors, Accepted, queryCache, formCache
  ├── handleHTTPRequest
  │     ├── tree lookup → set Params / handlers / fullPath
  │     ├── Next()      → run middleware chain
  │     └── writeHeaderNow if not aborted
  └── pool.Put   → return *Context for reuse
```

`Context.Copy` returns a detached copy safe to pass to a goroutine. It
shallow-copies `engine` and clones `Keys` + `Params`. The copy's
`writermem.ResponseWriter` is set to `nil` because writing to the
original response after the request returns would race with the pool.

### 8.4 The binding system (`binding/`)

Three interfaces:

```go
type Binding interface {
    Name() string
    Bind(*http.Request, any) error
}

type BindingBody interface {
    Binding
    BindBody([]byte, any) error
}

type BindingUri interface {
    Name() string
    BindUri(map[string][]string, any) error
}
```

Each MIME has a one-line implementer (`jsonBinding`, `xmlBinding`, etc.)
that decodes the request body (or query/form) into the target value and
calls `validate(obj)` to run struct-tag validation.

`binding.Default(method, contentType)` selects the right binder:
`GET → Form`, `POST application/json → JSON`, `… application/xml → XML`,
`… multipart/form-data → FormMultipart`, `… application/toml → TOML`,
`… application/bson → BSON`, fallback `Form`.

Form mapping (`binding/form_mapping.go`) handles `form:"key"` tagged
structs. It supports primitives, slices, maps, time, custom types via
`UnmarshalParam`/`UnmarshalText`, and `multipart.FileHeader` for upload
fields. The error `"unknown type"` (`form_mapping.go:23`) fires when a
field's reflect kind isn't in the supported set.

### 8.5 The render system (`render/`)

A single interface:

```go
type Render interface {
    Render(http.ResponseWriter) error
    WriteContentType(http.ResponseWriter)
}
```

Each MIME is a struct (e.g., `JSON{Data: any}`, `XML{Data: any}`,
`HTML{Template, Name, Data}`, `BSON{Data}`, `PDF{Data []byte}`). The
`Context.Render(code, r)` method writes the status, calls
`r.WriteContentType(w)`, then `r.Render(w)`.

HTML is special because it has two flavors:

- `HTMLProduction{Template, Delims}` — pre-parsed, shared template;
  fast.
- `HTMLDebug{Files | Glob | FileSystem+Patterns, Delims, FuncMap}` —
  reparses templates on every request; used in debug mode.

`render.Delims{Left, Right}` lets you swap `{{`/`}}` for any other
delimiter pair (useful when the templates need to be rendered through
multiple template engines).

### 8.6 Pluggable JSON codec (`codec/json/`)

The package exposes one variable:

```go
var API JSONAPI = jsonApi{}
```

Where `JSONAPI` is:

```go
type JSONAPI interface {
    Marshal(any) ([]byte, error)
    Unmarshal([]byte, any) error
    NewEncoder(io.Writer) JSONEncoder
    NewDecoder(io.Reader) JSONDecoder
    MarshalIndent(any, prefix, indent string) ([]byte, error)
    Indent(*bytes.Buffer, []byte, string, string) error
}
```

The default `jsonApi` wraps `encoding/json`. Build tags switch the
implementation:

| File | Build tag | Backend |
|---|---|---|
| `codec/json/json.go` | (no tag) | `encoding/json` |
| `codec/json/go_json.go` | `go_json` | `goccy/go-json` |
| `codec/json/jsoniter.go` | `jsoniter` | `json-iterator/go` |
| `codec/json/sonic.go` | `sonic` | `bytedance/sonic` |

Pick at compile time:

```bash
go build -tags sonic ./...
```

### 8.7 `internal/bytesconv`

Two functions perform unsafe `[]byte` ⇄ `string` aliasing without
copying. They're correct as long as the resulting string is treated as
read-only and the underlying byte slice is not mutated. Used in
performance-critical paths: basic-auth comparison
(`auth.go:searchCredential`), path canonicalization (`tree.go`), JSONP
handling (`render/json.go`), and panic-dump masking (`recovery.go`).

### 8.8 Testing helpers (`test_helpers.go`)

`gin.CreateTestContext(w http.ResponseWriter)` returns
`(*gin.Context, *gin.Engine)` so unit tests can construct a context
without booting a server. The engine is a `gin.Default` instance.

The `test_helpers.go` file also contains the internal helper
`waitForServerReady(url, attempts)` used by integration tests
(`gin_integration_test.go`); it polls the URL with exponential backoff
and returns when the server is up.

### 8.9 Code patterns and conventions

- **Comment style:** Every exported symbol is preceded by a Go-doc
  comment that begins with the symbol name. The framework follows
  `revive` and `gofumpt` formatting (configured in `.golangci.yml`).
- **Errors:** Use `errors.New` for static text, `fmt.Errorf` with `%w`
  for wrapping. Typed errors are tagged via `*gin.Error.Type` so
  middleware can filter by `ErrorTypeBind`, `ErrorTypeRender`,
  `ErrorTypePrivate`, `ErrorTypePublic`, `ErrorTypeAny`.
- **Panics for programmer error.** Bad route paths, empty Basic-Auth
  credentials, invalid HTTP methods, and double-running `Run` are panics
  (or returned errors) — never silent failures.
- **No goroutines from the framework.** Gin itself never spawns a
  goroutine. The user's handlers and middleware do.
- **No global mutable state in hot paths.** Route trees and pools live
  on the `*Engine` value, not in package-level vars (with the exception
  of `ginMode`, `DefaultWriter`, and similar).

### 8.10 Test layout

```
gin/
├── *_test.go                  in-package tests for every public file
├── benchmarks_test.go         pkg-level benchmarks
├── gin_integration_test.go    spawns real listeners
├── githubapi_test.go          regression test for routing edge cases
├── routes_test.go             routing-tree exhaustive matrix
├── binding/*_test.go
├── render/*_test.go
└── ginS/gins_test.go
```

CI runs `go test ./...` (linted by golangci-lint, configured in
`.golangci.yml`). The `Makefile` exposes `make test`, `make lint`, and
`make bench` targets.

### 8.11 Known issues and tech debt

- **Issue #4572 (OPEN)** — `X-Forwarded-For` parser doesn't accept RFC
  7239 `Forwarded:` syntax. Workaround documented in User Guide §6.
- **Issue #4622 (OPEN)** — `SaveUploadedFile` chmods existing parent
  directories on some filesystems. Avoid saving directly into shared
  directories.
- **Issue #4068 (OPEN)** — `c.SaveUploadedFile` permissions are platform
  dependent; the `perm ...fs.FileMode` variadic argument lets you pin
  the saved file's mode but not the directory's.
- **Issue #4624 (OPEN)** — Param binding and validation are coupled in
  the `binding:` tag. A future feature request asks for separate hooks.
- **Issue #4493 (CLOSED)** — Typed parameters (e.g., `:id<int>`) are not
  supported. Bind via `c.ShouldBindUri` and validator tags instead.
- **Issue #4332 (CLOSED)** — There is no `Engine.Shutdown`. Use
  `*http.Server` directly (User Guide §5.10).
- **Issue #3047 (CLOSED)** — No way to opt a single route out of an
  applied middleware. Use route groups instead.

### 8.12 Contributing

Standard Go workflow:

```bash
git clone https://github.com/gin-gonic/gin
cd gin
go test ./...
golangci-lint run
```

The repo uses GoReleaser (`.goreleaser.yaml`) for releases — the build
section is `skip: true` because Gin is a library, not a binary.

The `.golangci.yml` enables `revive`, `gosec`, `errorlint`,
`misspell`, `nilerr`, `nakedret`, `perfsprint`, `testifylint`,
`usestdlibvars`, and several others. Run `golangci-lint run` before
sending a PR.

---

## 9. Integration Points

### 9.1 Importing as a library

Gin is a Go library and integrates anywhere a Go binary runs.
Single-import:

```go
import "github.com/gin-gonic/gin"
```

Sub-packages:

```go
import (
    "github.com/gin-gonic/gin/binding"
    "github.com/gin-gonic/gin/render"
    "github.com/gin-gonic/gin/codec/json"
)
```

The `ginS` package is a stylistic option only — every function delegates
to a `sync.OnceValue`-initialized `gin.Default()` instance.

### 9.2 Wrapping standard `http.Handler` middleware

`gin.WrapH(h http.Handler) gin.HandlerFunc` and `gin.WrapF(f
http.HandlerFunc)` adapt stdlib middleware. Use them for:

- `prometheus.Handler()` (`gin.WrapH(promhttp.Handler())`)
- `pprof.Handler("...")` (`gin.WrapH(pprof.Handler("heap"))`)
- Any third-party `http.Handler` you want to mount on a Gin route.

### 9.3 Mounting Gin inside another router

`*gin.Engine` implements `http.Handler`, so you can mount it under
`http.ServeMux`:

```go
mainMux := http.NewServeMux()
mainMux.Handle("/api/", http.StripPrefix("/api", apiEngine))
mainMux.Handle("/", staticEngine)
http.ListenAndServe(":8080", mainMux)
```

Note that `http.StripPrefix` is essential when mounting under a prefix —
Gin's tree expects URL paths to start at `/`, not at the mount point.

### 9.4 Embedding inside a `*http.Server`

```go
srv := &http.Server{
    Addr:              ":8080",
    Handler:           r.Handler(), // not r directly, so UseH2C is honored
    ReadHeaderTimeout: 10 * time.Second,
    ReadTimeout:       30 * time.Second,
    WriteTimeout:      30 * time.Second,
    IdleTimeout:       120 * time.Second,
    TLSConfig:         &tls.Config{MinVersion: tls.VersionTLS12},
}
go srv.ListenAndServeTLS("", "")
```

This is the recommended path for any production deployment because it
gives you total control over timeouts, TLS, and shutdown.

### 9.5 Gin + reverse proxy

Behind nginx / Traefik / Caddy / HAProxy:

```
┌─────────┐     ┌─────────┐     ┌─────────┐
│ Client  │ ──→ │ NGINX   │ ──→ │ Gin     │
│         │     │ (TLS,   │     │ (HTTP/1)│
│         │     │  CORS,  │     │         │
│         │     │  Cache) │     │         │
└─────────┘     └─────────┘     └─────────┘
```

Configuration checklist:

- Set `r.SetTrustedProxies` to the LB's CIDR.
- Have the LB inject `X-Forwarded-For` and `X-Real-IP` (Gin reads both
  by default).
- Have the LB inject `X-Forwarded-Proto` if you redirect on scheme.
- Disable Gin's `RedirectTrailingSlash` if the LB already canonicalizes.

### 9.6 Gin + WebSocket

Gin doesn't ship WebSocket support directly, but `*gin.Context` exposes
`c.IsWebsocket()` (true if the request has the upgrade header) and gives
you the underlying `c.Writer` (which implements `http.Hijacker` —
verified by the integration test `TestPusher` in
`gin_integration_test.go`). Use `gorilla/websocket` or `nhooyr/websocket`:

```go
upgrader := websocket.Upgrader{}
r.GET("/ws", func(c *gin.Context) {
    conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
    if err != nil { return }
    defer conn.Close()
    for {
        _, msg, err := conn.ReadMessage()
        if err != nil { return }
        conn.WriteMessage(websocket.TextMessage, msg)
    }
})
```

`c.Writer.Pusher()` exposes the HTTP/2 push interface on TLS connections.

### 9.7 Gin + Server-Sent Events

The `gin-contrib/sse` package is imported directly by `context.go`.
`c.SSEvent(name, msg)` writes a single event; `c.Stream(step)` is the
canonical loop. The event-stream `Content-Type` is set automatically
(`render/reader.go`).

### 9.8 Gin + gRPC

Gin and gRPC normally run on separate ports. To multiplex them on one
port, use `golang.org/x/net/http2/h2c` (already a transitive dep when
`UseH2C` is true) and dispatch by `Content-Type: application/grpc`:

```go
r.UseH2C = true
mux := http.NewServeMux()
mux.Handle("/grpc.MyService/", grpcServer)
mux.Handle("/", r.Handler())
http.ListenAndServe(":8080", mux)
```

For HTTP/3 + gRPC, see `RunQUIC`.

### 9.9 Gin + database/queue/etc.

Gin does not own state. Persist via the database driver of your choice:

- `database/sql` for SQL.
- `redis/go-redis` for Redis.
- `mongo-driver/v2` for MongoDB (already a Gin dep, used for BSON
  rendering).
- `lib/pq`, `pgx`, `mysql`, `mongo`, etc.

The conventional pattern is to construct your dependencies in `main`
and capture them in handler closures via dependency injection:

```go
func main() {
    db := openDB()
    r := gin.Default()
    r.GET("/users/:id", makeGetUser(db))
    r.Run()
}

func makeGetUser(db *sql.DB) gin.HandlerFunc {
    return func(c *gin.Context) {
        // ...
    }
}
```

### 9.10 Gin + observability platforms

| Platform | Integration |
|---|---|
| Prometheus | `gin.WrapH(promhttp.Handler())` + per-request histogram middleware. Use `c.FullPath()` as the route label. |
| OpenTelemetry | Span-creating middleware around `c.Next()`. Set `r.ContextWithFallback = true` so `context.Context` works in handlers. |
| Datadog / New Relic | Same pattern; they ship Gin-specific middleware in their go-agent SDKs. |
| Jaeger | OTel-compatible; same as above. |
| Sentry | `sentry-go` ships a Gin middleware that wraps panics. |

### 9.11 Versioning and release cadence

Releases follow semver. The `Version` constant in `version.go` is the
canonical source of truth at runtime. `go.mod` declares
`go 1.25.0` — the minimum supported toolchain.

GoReleaser (`.goreleaser.yaml`) configures the release notes generator;
groups commit messages by `feat`, `fix`, `chore`, `refactor`, `build/ci`,
`docs`. The release notes appear on the GitHub Releases page.

### 9.12 Forking notes

The radix-tree implementation in `tree.go` carries the original
`julienschmidt/httprouter` BSD-style license. Any fork that touches
`tree.go` must preserve the copyright notice. The rest of Gin is MIT
licensed.

---

_Generated April 2026 from gin-gonic/gin at d3ffc99. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
