Philter

Hex.pmDocsCI

Streaming HTTP proxy library with O(1) memory body observation for Elixir.

Philter — an alchemical potion or charm; from Greek philtron (φίλτρον), "love potion."

Features

Installation

Add philter to your list of dependencies in mix.exs:

def deps do
  [
    {:philter, "~> 0.1.0"}
  ]
end

Quick Start

  1. Add Finch to your supervision tree:
children = [
  {Finch, name: MyApp.Finch}
]
  1. Configure Philter:
# config/config.exs
config :philter, finch_name: MyApp.Finch
  1. Use in your controller:
def proxy(conn, _params) do
  Philter.proxy(conn, upstream: "https://api.example.com")
end

Or as a Plug in your router:

forward "/api", Philter.ProxyPlug, upstream: "https://api.example.com"

Body Observation

Philter captures observations about request and response bodies without buffering:

conn = Philter.proxy(conn, upstream: "https://api.example.com")

# Access observations from conn.private
req_obs = conn.private[:philter_request_observation]
resp_obs = conn.private[:philter_response_observation]

# Each observation contains:
# - :hash - SHA256 hash of the body
# - :size - Total body size in bytes
# - :preview - First 64KB of the body (UTF-8 safe truncation)
# - :body - Full body (only if under max_payload_size and content-type matches)
# - :duration_us - Processing time in microseconds

Handler Callbacks

Implement Philter.Handler to hook into the proxy lifecycle:

defmodule MyApp.ProxyHandler do
  use Philter.Handler

  @impl true
  def handle_request_started(metadata, state) do
    Logger.info("Proxying #{metadata.method} #{metadata.upstream_url}")
    {:ok, state}
  end

  @impl true
  def handle_response_started(metadata, state) do
    Logger.info("TTFB: #{metadata.time_to_first_byte_us}us")
    {:ok, state}
  end

  @impl true
  def handle_response_finished(result, state) do
    Logger.info("Completed: #{result.status} in #{result.duration_us}us")
    # result contains :request_observation and :response_observation
    {:ok, state}
  end
end

# Use it:
Philter.proxy(conn,
  upstream: "https://api.example.com",
  handler: {MyApp.ProxyHandler, %{}}
)

Configuration Options

Option Default Description
:finch_namePhilter.Finch Name of the Finch pool to use
:receive_timeout15_000 Response timeout in milliseconds
:max_payload_size1_048_576 Max body size for full accumulation (1MB)
:persistable_content_types JSON/XML/text Content types eligible for body storage

Override per-request:

Philter.proxy(conn,
  upstream: "https://api.example.com",
  receive_timeout: 60_000,
  max_payload_size: 5_242_880
)

Or set application defaults:

# config/config.exs
config :philter,
  finch_name: MyApp.Finch,
  receive_timeout: 30_000,
  max_payload_size: 5_242_880,
  persistable_content_types: ["application/json", "text/*"]

Documentation

Full documentation: https://hexdocs.pm/philter

License

Apache-2.0