Skip to main content

Overview

The proxy middleware provides reverse proxy functionality, forwarding requests to upstream servers. It supports load balancing, request modification, and response handling.

Installation

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

Quick Start

app := mizu.New()

// Forward all requests to upstream
app.Use(proxy.New("http://backend:8080"))

Configuration

OptionTypeDefaultDescription
Target*url.URLrequiredUpstream server URL
Rewritefunc(string) string-Path rewriter
ModifyRequestfunc(*http.Request)-Request modifier
ModifyResponsefunc(*http.Response) error-Response modifier
Transporthttp.RoundTripperDefaultHTTP transport
Timeouttime.Duration30sRequest timeout
PreserveHostboolfalseKeep original Host header
ErrorHandlerfunc(*mizu.Ctx, error) error-Error handler

Examples

Basic Proxy

app.Get("/api/*", nil, proxy.New("http://api-server:3000"))

Path Rewriting

app.Use(proxy.WithOptions(proxy.Options{
    Target: mustParseURL("http://backend:8080"),
    Rewrite: func(path string) string {
        return strings.TrimPrefix(path, "/api/v1")
    },
}))

Load Balancing

app.Use(proxy.Balancer([]string{
    "http://server1:8080",
    "http://server2:8080",
    "http://server3:8080",
}))

Request Modification

app.Use(proxy.WithOptions(proxy.Options{
    Target: mustParseURL("http://backend:8080"),
    ModifyRequest: func(req *http.Request) {
        req.Header.Set("X-Source", "gateway")
        req.Header.Del("Authorization") // Strip auth
    },
}))

Response Modification

app.Use(proxy.WithOptions(proxy.Options{
    Target: mustParseURL("http://backend:8080"),
    ModifyResponse: func(resp *http.Response) error {
        resp.Header.Set("X-Proxy", "mizu")
        return nil
    },
}))

Preserve Host Header

app.Use(proxy.WithOptions(proxy.Options{
    Target:       mustParseURL("http://backend:8080"),
    PreserveHost: true,
}))

Custom Timeout

app.Use(proxy.WithOptions(proxy.Options{
    Target:  mustParseURL("http://backend:8080"),
    Timeout: 60 * time.Second,
}))

API Reference

func New(target string) mizu.Middleware
func WithOptions(opts Options) mizu.Middleware
func Balancer(targets []string) mizu.Middleware

Headers Added

HeaderValue
X-Forwarded-ForClient IP chain
X-Forwarded-HostOriginal host
X-Forwarded-Protohttp or https

Technical Details

Request Flow

  1. URL Construction: The target URL is built by combining the upstream target with the request path. If a Rewrite function is provided, it transforms the path before joining.
  2. Path Joining: The singleJoiningSlash helper ensures proper path construction by handling slash prefixes/suffixes correctly, preventing issues like double slashes or missing slashes.
  3. Request Creation: A new HTTP request is created with the same method, context, and body as the original request.
  4. Header Propagation: All headers from the original request are copied to the proxy request using the copyHeaders function, which preserves multi-value headers.
  5. Forwarded Headers: The middleware automatically sets standard forwarding headers:
    • X-Forwarded-For: Appends client IP to any existing value (creates a chain)
    • X-Forwarded-Host: Sets the original host
    • X-Forwarded-Proto: Sets https or http based on TLS presence
  6. Host Header Handling: By default, the Host header is set to the upstream target’s host. When PreserveHost is true, the original Host header is preserved.
  7. Request Modification: If ModifyRequest is provided, it’s called before sending the request, allowing custom header manipulation or other modifications.
  8. HTTP Client Configuration:
    • Uses configurable transport (defaults to http.DefaultTransport)
    • Applies the specified timeout (defaults to 30 seconds)
    • Disables automatic redirect following (CheckRedirect returns ErrUseLastResponse)
  9. Response Handling: After receiving the upstream response:
    • ModifyResponse callback is invoked if configured
    • Response headers are copied to the client
    • Status code is written
    • Response body is streamed to the client using io.Copy
  10. Error Handling: Errors are passed to the custom ErrorHandler if configured, otherwise returns a 502 Bad Gateway response.

Load Balancer Implementation

The Balancer function implements simple round-robin load balancing:
  • Maintains a counter for selecting the next upstream
  • Uses modulo arithmetic to cycle through targets
  • Counter is not thread-safe but provides good-enough distribution for most use cases
  • Each request creates a new proxy middleware instance with the selected target

Performance Considerations

  • Streaming: Response body is streamed using io.Copy, avoiding buffering large responses in memory
  • Connection Reuse: Uses http.DefaultTransport by default, which includes connection pooling
  • Context Propagation: Request context is preserved, enabling timeout and cancellation propagation
  • Header Copying: Headers are copied efficiently without additional allocations for single-value headers

Best Practices

  1. Set Appropriate Timeouts: Configure timeouts based on your upstream service characteristics to prevent hanging requests.
  2. Error Handling: Implement custom error handlers for better error reporting and monitoring.
  3. Security: Be cautious with ModifyRequest to avoid exposing sensitive headers to untrusted upstreams.
  4. Path Rewriting: Use the Rewrite function for API versioning or routing transformations.
  5. Load Balancing: For production load balancing, consider implementing health checks and weighted distributions.

Testing

The proxy middleware includes comprehensive test coverage for all functionality:
Test CaseDescriptionExpected Behavior
TestNewBasic proxy functionalityForwards request to upstream, returns upstream response with headers
TestWithOptions_RewritePath rewriting with custom functionTransforms request path before forwarding (e.g., /users → /api/v2/users)
TestWithOptions_ModifyRequestRequest modification callbackCustom headers are added to upstream request
TestWithOptions_ModifyResponseResponse modification callbackCustom headers are added to client response
TestWithOptions_PreserveHostHost header preservationOriginal Host header is preserved when PreserveHost is true
TestWithOptions_ErrorHandlerCustom error handlingCustom error handler is invoked on proxy failure
TestWithOptions_QueryStringQuery parameter forwardingQuery strings are preserved and forwarded to upstream
TestWithOptions_POSTPOST request handlingPOST requests with body are proxied correctly
TestNew_InvalidURLInvalid target URL validationPanics with descriptive error on invalid URL
TestWithOptions_NoTargetMissing target validationPanics when target is not provided
TestBalancerRound-robin load balancingDistributes requests across multiple upstreams in rotation
TestBalancer_EmptyEmpty balancer targets validationPanics when no targets are provided