echo

package module
v0.6.1 Latest Latest
Warning

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

Go to latest
Published: Feb 26, 2026 License: MIT Imports: 18 Imported by: 0

README

Echo

A simplest implementation of a proxy server in Go, inspired by Whistle.

Features

  • HTTP Proxy: Supports standard HTTP proxying.
  • HTTPS/TCP Tunneling: Supports CONNECT method for HTTPS and generic TCP tunneling.
  • WebSocket Support: Supports WebSocket upgrades (hijacking) and tunneling.
  • Plugin System: Flexible plugin system to modify requests and responses.

Installation

go get github.com/ltaoo/echo

Quick Start

To start the proxy server, you need to provide a Root CA certificate and private key.

package main

import (
	"log"
	"net/http"
	"os"

	"github.com/ltaoo/echo"
)

func main() {
	// 1. Load Root CA (You need to generate these or use existing ones)
	// Ensure you have 'certs/rootCA.crt' and 'certs/rootCA.key'
	certFile, err := os.ReadFile("certs/rootCA.crt")
	if err != nil {
		log.Fatalf("Failed to read cert file: %v", err)
	}
	keyFile, err := os.ReadFile("certs/rootCA.key")
	if err != nil {
		log.Fatalf("Failed to read key file: %v", err)
	}

	// 2. Initialize Echo
	e, err := echo.NewEcho(certFile, keyFile)
	if err != nil {
		log.Fatalf("Failed to initialize Echo: %v", err)
	}

	// 3. Start Server
	server := &http.Server{
		Addr:    ":8888",
		Handler: e,
	}

	log.Println("Echo Proxy listening on :8888")
	if err := server.ListenAndServe(); err != nil {
		log.Fatal(err)
	}
}

Plugins

You can add plugins to intercept and modify requests/responses.

package main

import (
	"fmt"
	"strings"

	"github.com/ltaoo/echo"
	"github.com/ltaoo/echo/plugin"
)

func main() {
	// ... (Load certs as above) ...

	e, _ := echo.NewEcho(certFile, keyFile)

	// Define a plugin
	myPlugin := &plugin.Plugin{
		Match: "example.com", // Match requests to example.com
		OnRequest: func(ctx *plugin.Context) {
			fmt.Println("Intercepted request to example.com")
			ctx.SetRequestHeader("X-Custom-Header", "MyPlugin")
		},
		OnResponse: func(ctx *plugin.Context) {
			// Modify response body
			body, _ := ctx.GetResponseBody()
			newBody := strings.ReplaceAll(body, "Example Domain", "Hacked Domain")
			ctx.SetResponseBody(newBody)
		},
	}

	// Add plugin
	e.AddPlugin(myPlugin)

	// ... (Start server) ...
}

Usage

  1. Configure your browser or client to use the proxy:

    • Proxy Host: 127.0.0.1
    • Proxy Port: 8888
  2. Test with curl:

    # HTTP
    curl -x http://127.0.0.1:8888 http://example.com
    
    # HTTPS
    curl -x http://127.0.0.1:8888 https://example.com
    

Implementation Details

  • Uses Go's net/http for server handling.
  • handleHTTP for standard proxy requests (removes hop-by-hop headers).
  • handleTunnel for CONNECT requests (hijacks connection and tunnels TCP).
  • handleWebSocket for Upgrade: websocket requests (hijacks connection and tunnels TCP).

Documentation

Overview

Package echo provides a simple proxy server implementation in Go, inspired by Whistle.

Features

  • HTTP Proxy: Supports standard HTTP proxying.
  • HTTPS/TCP Tunneling: Supports CONNECT method for HTTPS and generic TCP tunneling.
  • WebSocket Support: Supports WebSocket upgrades (hijacking) and tunneling.
  • Plugin System: Flexible plugin system to modify requests and responses.

Quick Start

To start the proxy server, provide a Root CA certificate and private key:

certFile, _ := os.ReadFile("certs/rootCA.crt")
keyFile, _ := os.ReadFile("certs/rootCA.key")

e, err := echo.NewEcho(certFile, keyFile)
if err != nil {
	log.Fatal(err)
}

server := &http.Server{
	Addr:    ":8888",
	Handler: e,
}
server.ListenAndServe()

Plugins

Add plugins to intercept and modify requests/responses:

e.AddPlugin(&echo.Plugin{
	Match: "example.com",
	OnRequest: func(ctx *echo.Context) {
		ctx.SetRequestHeader("X-Custom-Header", "value")
	},
	OnResponse: func(ctx *echo.Context) {
		body, _ := ctx.GetResponseBody()
		ctx.SetResponseBody(strings.ReplaceAll(body, "old", "new"))
	},
})

Forwarding

Use TargetConfig to forward requests to a different server:

e.AddPlugin(&echo.Plugin{
	Match:  "example.com",
	Target: &echo.TargetConfig{Protocol: "http", Host: "localhost", Port: 3000},
})

Mock Response

Use MockResponse to return a static response:

e.AddPlugin(&echo.Plugin{
	Match: "example.com/api",
	MockResponse: &echo.MockResponse{
		StatusCode: 200,
		Headers:    map[string]string{"Content-Type": "application/json"},
		Body:       `{"status":"ok"}`,
	},
})

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func CopyHeader

func CopyHeader(dst, src http.Header)

CopyHeader copies headers from source to destination

func DecompressBody

func DecompressBody(res *http.Response) (io.ReadCloser, error)

DecompressBody returns a reader that decompresses the response body if needed

func DelHopHeaders

func DelHopHeaders(header http.Header)

DelHopHeaders removes hop-by-hop headers

func IsMatch

func IsMatch(hostname, pattern string) bool

IsMatch checks if a hostname matches a pattern Supports: - Exact match: "example.com" - Wildcard: "*.example.com" - Substring: "example" (matches "example.com", "test.example.com", etc.)

func IsWebSocketRequest

func IsWebSocketRequest(r *http.Request) bool

IsWebSocketRequest checks if the request is a WebSocket upgrade

func SetLogEnabled

func SetLogEnabled(enabled bool)

Types

type ConnectHandler

type ConnectHandler struct {
	CertManager  *cert.Manager
	PluginLoader *PluginLoader
	HTTPHandler  *HTTPHandler // Shared HTTP handler
	// contains filtered or unexported fields
}

ConnectHandler handles CONNECT requests and MITM

func (*ConnectHandler) HandleTunnel

func (h *ConnectHandler) HandleTunnel(w http.ResponseWriter, r *http.Request)

HandleTunnel handles the CONNECT request

type Context

type Context struct {
	Req *http.Request
	Res *http.Response // Nil in OnRequest
	// contains filtered or unexported fields
}

Context provides access to the request and response for plugins

func (*Context) DelRequestHeader

func (c *Context) DelRequestHeader(key string)

DelRequestHeader deletes a header from the request

func (*Context) DelResponseHeader

func (c *Context) DelResponseHeader(key string)

DelResponseHeader deletes a header from the response

func (*Context) GetMockResponse

func (c *Context) GetMockResponse() *MockResponse

GetMockResponse returns the set mock response

func (*Context) GetRequestHeader

func (c *Context) GetRequestHeader(key string) string

GetRequestHeader gets a header from the request

func (*Context) GetResponseBody

func (c *Context) GetResponseBody() (string, error)

GetResponseBody reads and returns the response body as a string It automatically decompresses the body if needed and updates the response to be uncompressed for subsequent reads.

func (*Context) GetResponseHeader

func (c *Context) GetResponseHeader(key string) string

GetResponseHeader gets a header from the response

func (*Context) Mock

func (c *Context) Mock(status int, headers map[string]string, body interface{})

Mock sets a mock response to be returned immediately

func (*Context) SetRequestHeader

func (c *Context) SetRequestHeader(key, value string)

SetRequestHeader sets a header on the request

func (*Context) SetResponseBody

func (c *Context) SetResponseBody(body string)

SetResponseBody sets the response body

func (*Context) SetResponseHeader

func (c *Context) SetResponseHeader(key, value string)

SetResponseHeader sets a header on the response

type Echo

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

func NewEcho

func NewEcho(certFile []byte, certKey []byte) (*Echo, error)

func (*Echo) AddPlugin

func (e *Echo) AddPlugin(plugin *Plugin)

func (*Echo) ServeHTTP

func (e *Echo) ServeHTTP(w http.ResponseWriter, r *http.Request)

type HTTPHandler

type HTTPHandler struct {
	PluginLoader *PluginLoader
	Transport    *http.Transport
}

HTTPHandler handles standard HTTP proxy requests

func NewHTTPHandler

func NewHTTPHandler(loader *PluginLoader) *HTTPHandler

NewHTTPHandler creates a new HTTP handler with a custom transport

func (*HTTPHandler) HandleRequest

func (h *HTTPHandler) HandleRequest(w http.ResponseWriter, r *http.Request)

HandleRequest processes the HTTP request

type MitmServer

type MitmServer struct {
	Port     int
	Listener net.Listener
}

type MockResponse

type MockResponse struct {
	StatusCode int
	Headers    map[string]string
	Body       interface{} // string or []byte
}

MockResponse defines a static response to return

type Plugin

type Plugin struct {
	Match        string
	Target       *TargetConfig
	MockResponse *MockResponse

	// Hooks
	OnRequest  func(ctx *Context)
	OnResponse func(ctx *Context)
}

Plugin represents a forwarding rule configuration

type PluginLoader

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

PluginLoader handles loading and managing plugins

func NewPluginLoader

func NewPluginLoader(plugins []*Plugin) (*PluginLoader, error)

NewPluginLoader creates a new plugin loader

func (*PluginLoader) AddPlugin

func (l *PluginLoader) AddPlugin(plugin *Plugin)

func (*PluginLoader) GetPlugins

func (l *PluginLoader) GetPlugins() []*Plugin

GetPlugins returns all loaded plugins

func (*PluginLoader) Load

func (l *PluginLoader) Load(plugins []*Plugin) error

Load loads plugins from the hardcoded registry

func (*PluginLoader) MatchPlugin

func (l *PluginLoader) MatchPlugin(hostname string) *Plugin

MatchPlugin finds the first plugin that matches the given hostname

func (*PluginLoader) MatchPluginForRequest

func (l *PluginLoader) MatchPluginForRequest(r *http.Request) *Plugin

func (*PluginLoader) MatchPlugins

func (l *PluginLoader) MatchPlugins(hostname string) []*Plugin

MatchPlugins returns all plugins that match the given hostname, in order

func (*PluginLoader) MatchPluginsForRequest

func (l *PluginLoader) MatchPluginsForRequest(r *http.Request) []*Plugin

MatchPluginsForRequest returns all plugins that match the given request URL/host, in order

type TargetConfig

type TargetConfig struct {
	Protocol string // http, https, ws, wss
	Host     string
	Port     int
}

TargetConfig defines where to forward requests

func (*TargetConfig) GetDefaultPort

func (t *TargetConfig) GetDefaultPort() int

GetDefaultPort returns the default port for the protocol

func (*TargetConfig) GetHostPort

func (t *TargetConfig) GetHostPort() string

GetHostPort returns the host:port combination

func (*TargetConfig) GetTargetURL

func (t *TargetConfig) GetTargetURL(path string) string

GetTargetURL returns the full target URL for forwarding

type WebSocketHandler

type WebSocketHandler struct {
	PluginLoader *PluginLoader
}

WebSocketHandler handles WebSocket upgrades

func (*WebSocketHandler) HandleUpgrade

func (h *WebSocketHandler) HandleUpgrade(w http.ResponseWriter, r *http.Request, isSecure bool)

HandleUpgrade handles the WebSocket upgrade request

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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