configuration.md 10 KB

Configuration

Configuration objects for dummy backend servers and reverse proxies.

BackendConfig

::: httphound.backend.BackendConfig

options:
  show_root_heading: true
  show_source: false

Overview

BackendConfig defines the behavior of the dummy backend HTTP server used in tests.

Basic Usage

from httphound.main import BackendConfig

# Minimal configuration (all defaults)
backend_config = BackendConfig()

# Custom configuration
backend_config = BackendConfig(
    host="127.0.0.1",
    port=9999,
    response_status=200,
    response_headers={"X-Custom": "value"},
    response_body="Hello from backend"
)

Parameters

host: str

IP address for backend to bind to.

Default: "127.0.0.1"

BackendConfig(host="127.0.0.1")  # Localhost only
BackendConfig(host="0.0.0.0")    # All interfaces

This is used also to populate HAProxy configuration file (when in template mode).

port: int

Port number for backend to listen on.

Default: 9999

!!! warning "Type Matters"

Must be an integer, not a string!
    BackendConfig(port=9999)   # correct
    BackendConfig(port="9999") # wrong!

response_status: int

HTTP status code to return when a request to the backend is made.

Default: 200

BackendConfig(response_status=200)  # OK
BackendConfig(response_status=201)  # Created
BackendConfig(response_status=301)  # Moved Permanently
BackendConfig(response_status=404)  # Not Found
BackendConfig(response_status=500)  # Internal Server Error

response_headers: Dict[str, str]

HTTP headers to include in response.

Default: {} (empty dict)

BackendConfig(response_headers={
    "Content-Type": "application/json",
    "X-Custom-Header": "value123",
    "Cache-Control": "no-cache"
})

!!! tip "Header Case"

Header names will be sent as specified. HTTP is case-insensitive for headers, but preserve the case you want. HAProxy usually lowercase these.

response_body: str

Response body content.

Default: "OK"

BackendConfig(response_body="Hello World")
BackendConfig(response_body='{"status": "ok"}')
BackendConfig(response_body="<html><body>Test</body></html>")

Examples

Mock JSON API Backend

backend_config = BackendConfig(
    port=9999,
    response_status=200,
    response_headers={
        "Content-Type": "application/json",
        "X-API-Version": "1.0"
    },
    response_body='{"status": "success", "data": [1, 2, 3]}'
)

Error Response Backend

backend_config = BackendConfig(
    response_status=503,
    response_headers={
        "Content-Type": "text/plain",
        "Retry-After": "300"
    },
    response_body="Service temporarily unavailable"
)

Redirect Backend

backend_config = BackendConfig(
    response_status=301,
    response_headers={
        "Location": "https://example.com/new-location"
    },
    response_body=""
)

ProxyConfig

::: httphound.proxy.ProxyConfig

options:
  show_root_heading: true
  show_source: false

Overview

ProxyConfig defines how HAProxy is configured and started. Supports two modes:

  • Template mode: Generate config from Jinja2 template (default)
  • Production mode: Use existing HAProxy configuration files

Basic Usage

Template Mode (Default)

from httphound.main import ProxyConfig
from pathlib import Path

proxy_config = ProxyConfig(
    binary_path=Path.home() / "bin/haproxy",
    template_path="haproxy.cfg.tpl",
    listen_port=4242
)

Production Mode

proxy_config = ProxyConfig(
    binary_path="/usr/sbin/haproxy",
    config_mode="production",
    production_config_path="/etc/haproxy/haproxy.cfg",
    backend_name_to_patch="app_backend",
    bind_address_override="*:4242"
)

Common Parameters

binary_path: str

Path to HAProxy binary. Must exists on the filesystem and executable as the user who runs the tests.

Default: "/usr/sbin/haproxy"

ProxyConfig(binary_path="/usr/sbin/haproxy")         # Default path if HAProxy is installed as debian package
ProxyConfig(binary_path=Path.home() / "bin/haproxy")
ProxyConfig(binary_path="/usr/local/bin/haproxy")

working_dir: str

Temporary directory for generated configs.

Default: "/tmp/httphound"

ProxyConfig(working_dir="/tmp/httphound")
ProxyConfig(working_dir="/tmp/my-tests")

config_mode: str

Configuration mode: "template" or "production".

Default: "template"

ProxyConfig(config_mode="template")     # Use Jinja2 template
ProxyConfig(config_mode="production")   # Use existing config

extra_args: List[str]

Additional command-line arguments for HAProxy.

Default: []

ProxyConfig(extra_args=["-dM"])  # Memory debug mode
ProxyConfig(extra_args=["-D"])   # Daemon mode

Template Mode Parameters

Used when config_mode="template" (default).

template_path: str

Path to Jinja2 template file.

Default: "haproxy.cfg.tpl"

ProxyConfig(template_path="haproxy.cfg.tpl")
ProxyConfig(template_path="templates/my-haproxy.tpl")

listen_addr: str

Address for HAProxy to bind to.

Default: "*" (all interfaces)

ProxyConfig(listen_addr="*")           # All interfaces
ProxyConfig(listen_addr="127.0.0.1")   # Localhost only
ProxyConfig(listen_addr="0.0.0.0")     # Explicit all

listen_port: int

Port for HAProxy to listen on.

Default: 4242

ProxyConfig(listen_port=4242)   # Default
ProxyConfig(listen_port=8080)   # Custom

template_vars: Optional[Dict[str, List[str]]]

Additional variables to pass to template. Each key corresponds to the HAProxy template section where the directives must be added.

Default: {}

ProxyConfig(template_vars={
    "max_connections": 1000,
    "timeout_client": "30s",
    "template_directives": {
        "global": [
            "maxconn 1000",
        ],
        "defaults": [
            "retries 3",
        ],
        "frontend_http": [
            "http-request deny deny_status 404 if path_beg -i /deny",
        ],
    }
})

These variables are rendered

In template:

global
    ...
    {%- if template_directives["global"] -%}
    {{ for directive in template_directives["global"] }}
    {{ directive }}
    {%- endfor -%}
    {%- endif -%}

defaults
    ...
    {%- if template_directives["defaults"] -%}
    {{ for directive in template_directives["defaults"] }}
    {{ directive }}
    {%- endfor -%}
    {%- endif -%}


frontend http
    {%- if template_directives["frontend_http"] -%}
    {{ for directive in template_directives["frontend_http"] }}
    {{ directive }}
    {%- endfor -%}
    {%- endif -%}

Production Mode Parameters

Used when config_mode="production".

production_config_path: str

Path to main HAProxy configuration file.

Required in production mode

ProxyConfig(
    config_mode="production",
    production_config_path="/etc/haproxy/haproxy.cfg"
)

production_config_base_dir: str

Base directory containing config and includes (for multi-file configs).

Optional - if set, entire directory tree is copied.

ProxyConfig(
    config_mode="production",
    production_config_path="/etc/haproxy/haproxy.cfg",
    production_config_base_dir="/etc/haproxy/conf.d"  # Copies whole directory
)

backend_name_to_patch: str

Name of backend section to patch with test backend address.

Default: "default_backend"

ProxyConfig(
    config_mode="production",
    production_config_path="/etc/haproxy/haproxy.cfg",
    backend_name_to_patch="app_backend"  # Must match config
)

In HAProxy config:

backend app_backend
    server app1 10.0.1.10:8080
    server app2 10.0.1.11:8080

After patching:

backend app_backend
    server app1 127.0.0.1:9999  # Test backend
    server app2 127.0.0.1:9999  # Test backend

bind_address_override: str

Override all bind addresses to avoid port conflicts.

Optional - if not set, original bind addresses are used.

ProxyConfig(
    config_mode="production",
    production_config_path="/etc/haproxy/haproxy.cfg",
    bind_address_override="*:4242"  # Override all binds
)

Original config:

frontend web
    bind *:80
    bind *:443 ssl crt /path/to/cert

After override:

frontend web
    bind *:4242
    bind *:4242 ssl crt /path/to/cert

skip_backend_injection: bool

If True, don't patch backend server addresses.

Default: False

ProxyConfig(
    config_mode="production",
    production_config_path="/etc/haproxy/haproxy.cfg",
    skip_backend_injection=True  # Don't modify backends
)

Use this when:

  • Production config already points to test backend
  • Tests doesn't need to receive backend responses (test proxy configuration only)
  • You've manually edited the config
  • Backend patching is too complex

Examples

Simple Template Mode

from pathlib import Path

proxy_config = ProxyConfig(
    binary_path=Path.home() / "bin/haproxy",
    config_mode="template",
    template_path="haproxy.cfg.tpl",
    listen_port=4242
)

Template with custom directives

proxy_config = ProxyConfig(
    binary_path="/usr/sbin/haproxy",
    template_path="templates/advanced.tpl",
    listen_port=8080,
    template_directives={
        "frontend_http": [
            "http-request deny deny_status 404 if path_beg -i /deny",
        ],
    },
)

Production Mode - Single File

proxy_config = ProxyConfig(
    binary_path="/usr/sbin/haproxy",
    config_mode="production",
    production_config_path="/etc/haproxy/haproxy.cfg",
    backend_name_to_patch="app_backend",
    bind_address_override="127.0.0.1:4242"
)

Production Mode - Multi-File

proxy_config = ProxyConfig(
    binary_path="/usr/sbin/haproxy",
    config_mode="production",
    production_config_path="/etc/haproxy/haproxy.cfg",
    production_config_base_dir="/etc/haproxy",  # Copy whole tree
    backend_name_to_patch="web_backend",
    bind_address_override="*:4242"
)

Production Mode - Manual Backend

# When production config already has correct backend address
proxy_config = ProxyConfig(
    config_mode="production",
    production_config_path="/tmp/test-haproxy.cfg",
    skip_backend_injection=True,  # Don't patch
    bind_address_override="*:4242"
)

# Backend must match config
backend_config = BackendConfig(
    host="127.0.0.1",
    port=8080  # Must match what's in config
)