Skip to main content

Overview

The realip middleware extracts the real client IP address from proxy headers. It’s essential when running behind load balancers or reverse proxies.

Installation

import "github.com/go-mizu/mizu/middlewares/realip"

Quick Start

app := mizu.New()
app.Use(realip.New())

Configuration

OptionTypeDefaultDescription
TrustedProxies[]string-Trusted proxy IPs/CIDRs
TrustedHeaders[]stringSee belowHeaders to check

Default Headers (checked in order)

  1. X-Forwarded-For
  2. X-Real-IP
  3. CF-Connecting-IP (Cloudflare)
  4. True-Client-IP

Examples

Basic Usage

app.Use(realip.New())

app.Get("/", func(c *mizu.Ctx) error {
    ip := realip.Get(c)
    return c.Text(200, "Your IP: "+ip)
})

Trusted Proxies

app.Use(realip.WithTrustedProxies(
    "10.0.0.0/8",
    "192.168.0.0/16",
))

Custom Configuration

app.Use(realip.WithOptions(realip.Options{
    TrustedProxies: []string{"10.0.0.0/8"},
    TrustedHeaders: []string{
        "X-Real-IP",
        "X-Forwarded-For",
    },
}))

API Reference

func New() mizu.Middleware
func WithTrustedProxies(proxies ...string) mizu.Middleware
func WithOptions(opts Options) mizu.Middleware
func FromContext(c *mizu.Ctx) string
func Get(c *mizu.Ctx) string  // Alias for FromContext

Technical Details

IP Extraction Process

The middleware follows a systematic approach to extract the real client IP:
  1. Remote IP Extraction: Extracts the IP from Request.RemoteAddr using net.SplitHostPort
  2. Proxy Trust Verification: Checks if the remote IP is in the trusted proxy list (CIDR notation supported)
  3. Header Processing: If trusted, iterates through configured headers in priority order
  4. IP Parsing: Extracts the first valid IP from comma-separated header values (for X-Forwarded-For)
  5. Fallback: Uses RemoteAddr if no valid IP found in headers

CIDR Network Parsing

The middleware automatically converts single IP addresses to CIDR notation:
  • IPv4 addresses: /32 suffix (e.g., 10.0.0.1 becomes 10.0.0.1/32)
  • IPv6 addresses: /128 suffix (e.g., ::1 becomes ::1/128)

Context Storage

The real IP is stored in the request context using a private contextKey{} type, preventing key collisions with other middleware or application code.

Header Priority

When multiple headers are set, the middleware checks them in the configured order and uses the first valid IP found. Default priority:
  1. X-Forwarded-For (supports comma-separated lists)
  2. X-Real-IP
  3. CF-Connecting-IP (Cloudflare)
  4. True-Client-IP

Security

Only trust proxy headers when behind a trusted proxy. Without TrustedProxies, all requests are trusted.

Testing

The middleware includes comprehensive test coverage for various scenarios:
Test CaseDescriptionExpected Behavior
Default Header Tests
uses X-Forwarded-ForMultiple IPs in X-Forwarded-For headerExtracts first IP (203.0.113.195) from comma-separated list
uses X-Real-IPSingle IP in X-Real-IP headerReturns the IP from X-Real-IP (198.51.100.178)
uses CF-Connecting-IPCloudflare connecting IP headerReturns the IP from CF-Connecting-IP (192.0.2.1)
falls back to RemoteAddrNo proxy headers presentUses RemoteAddr (192.168.1.100)
Trusted Proxy Tests
trusts headers from trusted proxyRequest from 10.0.0.1 with X-Forwarded-ForReturns IP from header (203.0.113.195)
ignores headers from untrusted sourceRequest from 192.168.1.1 (not in trusted range)Ignores header, returns RemoteAddr (192.168.1.1)
Single IP Trust
WithTrustedProxies single IPTrust specific IP (10.0.0.1)Accepts headers from exact IP match
Custom Headers
WithOptions custom headersConfigure X-Custom-IP as trusted headerExtracts IP from custom header (5.6.7.8)
API Compatibility
Get() alias testFromContext vs Get methodsBoth return identical IP values
Helper Function Tests
extractFirstIP - single IPParse “203.0.113.195”Returns 203.0.113.195
extractFirstIP - comma-separatedParse “203.0.113.195, 70.41.3.18, 150.172.238.178”Returns first IP (203.0.113.195)
extractFirstIP - whitespaceParse ” 203.0.113.195 “Trims and returns 203.0.113.195
extractFirstIP - invalidParse “invalid”Returns empty string
extractFirstIP - emptyParse ""Returns empty string
extractFirstIP - IPv6Parse “2001:db8::1”Returns IPv6 address
extractIP - with portParse “192.168.1.1:8080”Returns IP without port (192.168.1.1)
extractIP - without portParse “192.168.1.1”Returns IP as-is (192.168.1.1)
extractIP - IPv6 with portParse ”[::1]:8080”Returns IPv6 without port (::1)
isTrusted - within CIDRCheck 10.0.0.1 in 10.0.0.0/8Returns true
isTrusted - edge of CIDRCheck 10.255.255.255 in 10.0.0.0/8Returns true
isTrusted - outside CIDRCheck 11.0.0.1 in 10.0.0.0/8Returns false
isTrusted - multiple networksCheck 192.168.1.1 in [10.0.0.0/8, 192.168.0.0/16]Returns true (matches second network)