Skip to content

Latest commit

 

History

33 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Echo Proxy With Headers

This project is a reverse proxy built using the Echo framework in Golang. It supports modifying request and response headers, removing unwanted headers, and dynamically configuring upstream services via environment variables.

Features

  • Proxy multiple upstream services based on the Host header.
  • Modify request and response headers.
  • Remove specific headers from responses.
  • Rewrite request paths using regex patterns.
  • Conditional proxying based on headers or query parameters.
  • Route a single host to multiple upstreams via ordered rules (header, query param, or path prefix).
  • Configuration via json file.
  • Supports Docker deployment.

Installation

As a Library

You can use this project as a library in your own Go application:

import (
	"github.com/roketid/echo-proxy"
)

func main() {
	configs := echoproxy.LoadConfig()
	port := "8080"
	echoproxy.RunProxy(configs, port)
}

As a Standalone Application

Prerequisites

  • Install Go
  • Clone this repository:
    git clone https://github.com/roketid/echo-proxy.git
    cd echoproxy
  • Build and run with config file:
    go build -o proxy-server ./cmd/main.go
    ./proxy-server -config config.json
  • Or run with base64-encoded config from environment:
    # Encode your config.json as base64
    export PROXY_CONFIG=$(base64 -w 0 < config.json)
    ./proxy-server -config-env PROXY_CONFIG

Configuration

Via config.json File

Create a config.json file with the following example configuration:

{
  "example.com": {
    "upstream": "https://example.com",
    "host_override": "",
    "request_headers": {"X-Custom-Header": "MyValue"},
    "response_headers": {"X-Response-Header": "ResponseValue"},
    "remove_headers": ["Server", "X-Powered-By", "Set-Cookie"],
    "condition": {
      "header": "X-Api-Key",
      "value": "secret-key"
    },
    "fallback_behavior": "404"
  },
  "another.com": {
    "upstream": "https://another.com",
    "host_override": "",
    "request_headers": {"X-Another-Header": "AnotherValue"},
    "response_headers": {"X-Another-Response": "AnotherResponseValue"},
    "remove_headers": ["Server", "Set-Cookie"]
  }
}

Via Environment Variable (Base64 Encoded)

You can also pass your configuration as a base64-encoded JSON string via an environment variable. This is useful for containerized deployments:

# Encode your config.json as base64
export PROXY_CONFIG=$(base64 -w 0 < config.json)

# Run the proxy with ENV config
./proxy-server -config-env PROXY_CONFIG

Example with inline config:

# Create base64 config
CONFIG_JSON='{"example.com": {"upstream": "https://example.com"}}'
export PROXY_CONFIG=$(echo -n "$CONFIG_JSON" | base64 -w 0)

# Run the proxy
./proxy-server -config-env PROXY_CONFIG

This approach is ideal for Kubernetes, Docker, and other containerized environments where you want to pass configuration through environment variables.

Conditional Proxying

You can add a condition object to a host config to only proxy requests that match a specific header or query parameter value. Only equality is supported:

  • To match a header:
    "condition": {
      "header": "X-Api-Key",
      "value": "secret-key"
    }
  • To match a query parameter:
    "condition": {
      "query_param": "token",
      "value": "mytoken"
    }

If the condition is not met, the proxy will use the fallback_behavior (e.g., "404", "bad_gateway", or proxy to a fallback upstream if fallback_upstream is set).

Multiple Upstreams per Host (Routes)

A single host key normally maps to one upstream (with an optional condition + fallback_upstream for a two-way choice). If you need to route the same host to more than two upstreams, use routes instead: an ordered list of rules, each with its own condition and upstream. The first matching route wins; a route with no condition is a catch-all and should go last.

{
  "api.example.com": {
    "routes": [
      { "condition": { "header": "target-app", "value": "b" }, "upstream": "https://service-b.example.com" },
      { "condition": { "query_param": "region", "value": "eu" }, "upstream": "https://eu.example.com" },
      { "condition": { "path_prefix": "/files/" }, "upstream": "https://files.example.com" },
      { "upstream": "https://default.example.com" }
    ],
    "request_headers": {},
    "remove_headers": ["Server"]
  }
}
  • Conditions support header, query_param (matched by equality against value), or path_prefix (matches when the request path starts with the given prefix).
  • Each route can set its own upstream, host_override, and path_rewrite_regex/path_rewrite_replacement. Everything else (request_headers, response_headers, remove_headers, timeouts) is shared from the host-level config.
  • If no route matches, the host's fallback_behavior/fallback_upstream is used, just like the single-condition case (defaulting to 502 Bad Gateway if unset).
  • routes takes precedence over the top-level upstream/condition fields when both are present, but existing configs using only upstream/condition keep working unchanged.

Fallback for Unmatched Hosts

If a request's Host header doesn't match any configured host, the proxy normally responds with 502 Bad Gateway. You can override this by adding a reserved "*" entry to your config — it's used only when nothing else matches:

{
  "*": {
    "upstream": "https://default-upstream.example.com"
  }
}

If the "*" entry has an upstream, unmatched requests are reverse-proxied to it just like any other host (headers, timeouts, path rewriting, etc. all apply). If it has no upstream but sets static_index_file, that file is served instead:

{
  "*": {
    "static_index_file": "./public/index.html"
  }
}

If neither is set (or no "*" entry exists at all), the proxy falls back to the default 502 Bad Gateway response.

Configuring the Fallback via Environment Variables

If your config doesn't define a "*" entry, you can set it via environment variables instead — handy when you don't want to edit config.json per environment:

  • DEFAULT_UPSTREAM: default upstream URL for unmatched hosts.
  • DEFAULT_STATIC_INDEX_FILE: path to a static HTML file to serve for unmatched hosts (used only if DEFAULT_UPSTREAM is not set).
export DEFAULT_UPSTREAM="https://default-upstream.example.com"
./proxy-server -config config.json

An explicit "*" entry in config.json (or PROXY_CONFIG) always takes precedence over these environment variables.

Path Rewriting

You can rewrite request paths using regex patterns by adding path_rewrite_regex and path_rewrite_replacement to your host config:

{
  "api.example.com": {
    "upstream": "https://backend.example.com",
    "path_rewrite_regex": "^/v1/(.*)",
    "path_rewrite_replacement": "/api/$1"
  }
}

This configuration will rewrite requests from /v1/users to /api/users before forwarding to the upstream server.

Common use cases:

  • Add prefix: Transform /users/api/v2/users

    "path_rewrite_regex": "^/(.*)",
    "path_rewrite_replacement": "/api/v2/$1"
  • Remove prefix: Transform /v1/users/users

    "path_rewrite_regex": "^/v1/(.*)",
    "path_rewrite_replacement": "/$1"
  • Replace path segment: Transform /old/path/new/path

    "path_rewrite_regex": "^/old/(.*)",
    "path_rewrite_replacement": "/new/$1"

Timeout Configuration

You can configure timeout settings for each upstream service (all values in seconds):

{
  "api.example.com": {
    "upstream": "https://backend.example.com",
    "dial_timeout": 30,      // Connection establishment timeout
    "read_timeout": 30,      // Read operation timeout
    "write_timeout": 30,     // Write operation timeout
    "idle_timeout": 90       // Connection idle timeout
  }
}

Default timeouts are:

  • dial_timeout: 30 seconds
  • read_timeout: 30 seconds
  • write_timeout: 30 seconds
  • idle_timeout: 90 seconds

Performance Optimizations for High Traffic

The proxy includes several optimizations for handling high-traffic scenarios:

  • Pre-compiled regex patterns: Path rewriting regex patterns are compiled once at startup, not per-request
  • Pre-parsed URLs: Upstream URLs are parsed once at initialization
  • Connection pooling: Configurable HTTP connection pooling with persistent connections
    • Max 100 concurrent connections per host
    • Max 100 idle connections
    • 30-second keep-alive interval
  • Efficient header management: Fast header modification and removal
  • Concurrent request handling: Full support for concurrent requests with proper resource management

Using Docker

Build and Run the Container

docker build -f docker/Dockerfile -t roketid/echo-proxy .
docker run -p 8080:8080 -v $(PWD)/config.json:/app/config.json roketid/echo-proxy

With Base64-Encoded Config (Environment Variable)

The Docker image automatically detects if PROXY_CONFIG environment variable is set and uses it:

# Encode your config to base64
CONFIG_B64=$(base64 -w 0 < config.json)

# Run with base64 config
docker run -p 8080:8080 -e PROXY_CONFIG="$CONFIG_B64" roketid/echo-proxy

Using Pre-built Image (GitHub Container Registry)

# With config file
docker run -p 8080:8080 -v $(PWD)/config.json:/app/config.json ghcr.io/roketid/echo-proxy:main

# With base64 config from environment
docker run -p 8080:8080 -e PROXY_CONFIG="$CONFIG_B64" ghcr.io/roketid/echo-proxy:main

How It Works

The Dockerfile CMD automatically selects the right approach:

  • If PROXY_CONFIG environment variable is set → uses -config-env PROXY_CONFIG
  • Otherwise → uses config file from CONFIG_PATH (default: /app/config.json)

This makes it flexible for both traditional volume-mounted configs and containerized deployments using environment variables.

GitHub Actions - Automated Docker Image Deployment

This project includes a GitHub Actions workflow that builds and pushes the Docker image to GitHub Container Registry when changes are pushed to the main branch.

Contributing

Feel free to submit issues or pull requests for improvements.

License

MIT License

About

Using golang (echo framework) to proxy many websites

Topics

Resources

Stars

1 star

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages