Skip to content

Architecture

Overview

┌──────────────────────────────────────────────────────────────┐
│  Client (browser / mobile app)                               │
└────────────────────────┬─────────────────────────────────────┘
                         │ HTTP / CONNECT
┌────────────────────────▼─────────────────────────────────────┐
│  pypproxy/proxy/proxy.py  (port :8080)                           │
│  asyncio TCP server                                          │
│  ├─ HTTP  → intercept → forward upstream (httpx, HTTP/2)     │
│  ├─ CONNECT → TLS termination (MITM)                         │
│  │    ├─ HTTP/HTTPS → intercept → forward upstream           │
│  │    ├─ WebSocket  → pypproxy/proto/ws.py                       │
│  │    ├─ gRPC       → pypproxy/proto/grpc.py                     │
│  │    └─ MQTT       → pypproxy/proto/mqtt.py                     │
│  └─ ignored hosts → raw TCP tunnel (passthrough)             │
└─────────────┬────────────────────────┬───────────────────────┘
              │                        │
┌─────────────▼──────┐    ┌────────────▼──────────────────────┐
│  pypproxy/cert/ca.py   │    │  pypproxy/interceptor/                 │
│  CA + per-host TLS │    │  apply rules, record entries       │
│  SSL Context cache │    └────────────┬───────────────────────┘
│                    │                 │
│  pypproxy/cert/        │    ┌────────────▼───────────────────────┐
│  client_cert.py    │    │  pypproxy/store/store.py               │
│  Mutual TLS certs  │    │  in-memory store + SQLite persist  │
└────────────────────┘    │  asyncio pub/sub                   │
                          └────────────┬───────────────────────┘
          ┌────────────────────────────┼──────────────────────┐
          │                            │                      │
┌─────────▼──────────┐   ┌─────────────▼─────────┐  ┌────────▼──────────┐
│  pypproxy/api/         │   │  pypproxy/ui/app.py        │  │  pypproxy/ui/cui.py   │
│  FastAPI REST API  │   │  NiceGUI 4-tab UI      │  │  rich terminal UI │
│  + WebSocket /ws   │   │  Traffic/Resender/     │  │  (CUI mode)       │
│  Bulk/Export APIs  │   │  Bulk/Diff             │  └───────────────────┘
└────────────────────┘   └─────────────────────────┘

Package overview

Package Responsibility
pypproxy/proxy asyncio TCP server; HTTP forwarding; TLS MITM for CONNECT; raw tunnel for ignored hosts
pypproxy/cert/ca CA certificate generation; per-host SSL Context cache
pypproxy/cert/client_cert Client certificate management for mutual TLS
pypproxy/interceptor Apply rules to requests and responses; record entries in the store
pypproxy/intercept Manual intercept manager; pause requests for user review
pypproxy/rule Rule evaluation engine; condition matching; priority ordering
pypproxy/store/store Thread-safe in-memory traffic store; asyncio pub/sub
pypproxy/store/db SQLite persistence via aiosqlite; load/save entries
pypproxy/store/filter_parser Filter expression parser (host == x && method == POST)
pypproxy/api FastAPI REST endpoints, WebSocket streaming, bulk/export APIs
pypproxy/ui/app NiceGUI 4-tab browser UI (Traffic, Resender, Bulk Sender, Diff)
pypproxy/ui/settings Settings page (rules, SSL passthrough, DNS, ports, client certs)
pypproxy/ui/detail Request/response detail panel with body view selector
pypproxy/ui/resender Resender tab — edit and re-send requests
pypproxy/ui/bulk_sender_ui Bulk Sender tab — parallel payload sending and race testing
pypproxy/ui/diff_view Diff tab — unified diff between two captured entries
pypproxy/ui/intercept_dialog Intercept dialog — pause, edit, forward or drop requests
pypproxy/ui/cui rich terminal UI (CUI mode)
pypproxy/proto/ws WebSocket frame relay and logging
pypproxy/proto/grpc gRPC length-prefix frame decoding
pypproxy/proto/mqtt MQTT frame decoding and detection
pypproxy/script Python script engine; on_request / on_response hooks
pypproxy/replay Async HTTP replay and parallel fuzzing via httpx
pypproxy/bulk Bulk sender and race condition test runner
pypproxy/dns Built-in DNS server with domain spoofing
pypproxy/exporter JSON/HAR export and rule import/export
pypproxy/codec Content-encoding decode (gzip/br/deflate); binary format decode (Protobuf/MessagePack/CBOR)
pypproxy/config YAML config loading

Key design decisions

asyncio TCP server

The proxy is a raw asyncio.start_server TCP server that parses HTTP manually. This lets a single connection handle HTTP/1.1 keep-alive, CONNECT tunnels, WebSocket upgrades, and MQTT detection without switching servers mid-connection.

TLS termination with loop.start_tls()

After responding 200 Connection Established to a CONNECT request, pypproxy calls loop.start_tls() to upgrade the existing asyncio transport to TLS server-side. Per-host certificates are cached as ssl.SSLContext objects.

SQLite persistence via aiosqlite

All captured traffic is stored in memory for fast access. Writes to SQLite are fire-and-forget via asyncio.run_coroutine_threadsafe. On startup, store.load_from_db() restores prior sessions. The DB path defaults to ~/.pypproxy/pypproxy.db.

Store pub/sub

The Store maintains a list of asyncio.Queue subscribers. The proxy calls loop.call_soon_threadsafe to push entries into queues from the proxy coroutine. The UI polls each queue to receive live updates without blocking.

HTTP/2

All upstream requests use httpx with http2=True. httpx negotiates HTTP/2 via ALPN where the server supports it and falls back to HTTP/1.1 transparently.

Filter expression engine

The filter bar in the UI accepts a structured expression parsed by pypproxy/store/filter_parser.py. The parser tokenizes field op value conditions and evaluates them with AND/OR short-circuit logic against Entry objects in memory.

Binary format detection

pypproxy/codec.py implements sniff_content_type() which combines Content-Type inspection with a JSON parse attempt and a binary entropy heuristic to guess the best display mode. Protobuf decoding uses wire-type heuristics without requiring a .proto schema.

GUI / CUI startup

In GUI mode, ui.run() owns the event loop and the proxy is launched via nicegui_app.on_startup. In CUI mode, asyncio.run() owns the loop and asyncio.gather runs the proxy, uvicorn API server, and rich TUI concurrently.