Network Architecture
How Bifurc routes requests, applies mocks, and avoids proxy loops.
Bifurc — Network Architecture
This document explains how every part of Bifurc's network layer works: mappings, mocks, proxy rules, RFC 6761 routing, WebSocket connections, and the Requests panel replay. It also answers the common question: "Will using a system proxy cause a loop?"
1. The Server
Bifurc runs a single raw TCP server bound to 127.0.0.1:<PORT> (default 9010). It is not an http.Server — it is net.createServer(). This means it reads raw bytes off the socket and manually parses the HTTP request line and headers.
Browser / App
│
▼
127.0.0.1:9010 ← Bifurc TCP server
Only two types of requests reach this server:
| How it arrives | Example |
|---|---|
RFC 6761 — browser resolves *.localhost natively | http://myapp.localhost/api/users |
| Forward proxy — OS/browser sends absolute URL | GET http://api.example.com/v1 HTTP/1.1 |
2. Request Routing — The Full Flowchart
Incoming TCP connection on 127.0.0.1:PORT
│
▼
Parse HTTP method + target
│
├─── method == CONNECT ──────────────► tcpTunnel()
│ (raw pipe, no inspection, no log)
│
▼
Extract Host header → host
│
├─── host == "localhost" ────────────► Serve HTML home page (no log)
│
├─── host ends with ".localhost" ────► RFC 6761 path (see §3)
│
├─── target starts with "http://" ──► Forward proxy path (see §4)
│ or "https://"
│
└─── anything else ──────────────────► 400 Bad Request
3. RFC 6761 Path (*.localhost domains)
RFC 6761 is an IETF standard that designates *.localhost as a special-use domain. All major browsers resolve any *.localhost subdomain to 127.0.0.1 without touching DNS. No /etc/hosts edits are needed.
How it works
- You create a mapping:
myapp.localhost→127.0.0.1:3000 - Browser navigates to
http://myapp.localhost - Browser connects to
127.0.0.1:80... but Bifurc isn't on port 80.
Wait — this only works if the browser is configured to send traffic to Bifurc on port 9010. There are two ways this happens:
Option A — System proxy set to 127.0.0.1:9010
The browser sends: GET http://myapp.localhost/ HTTP/1.1 (absolute URL). Bifurc receives it in the forward proxy path, but the host header is myapp.localhost which ends with .localhost, so it routes to the RFC 6761 path.
Option B — OS configured to listen on port 80 Less common, requires elevated privileges.
RFC 6761 Routing Flow
Request: GET / HTTP/1.1
Host: myapp.localhost
│
▼
host.endsWith(".localhost") == true
│
▼
Look up enabled mapping where domain == "myapp.localhost"
│
├─── Found ──────────────► proxyToUpstream("127.0.0.1:3000")
│ → direct http.request to 127.0.0.1:3000
│ → log entry: via="rfc6761"
│
└─── Not found ──────────► 404 "Not Mapped" page
→ log entry: via="error"
Critical: Mocks and proxy rules are NOT checked in the RFC 6761 path. Only mappings are checked here.
proxyToUpstream internals
proxyToUpstream(socket, method, target, path, headers, body, callback) makes a direct Node.js http.request to hostname:port. It never calls back into Bifurc's own TCP server. There is no loop.
4. Forward Proxy Path
When a browser or application is configured to use Bifurc as an HTTP proxy (127.0.0.1:9010), HTTP requests arrive as absolute URLs:
GET http://api.example.com/v1/users HTTP/1.1
Host: api.example.com
This is the "forward proxy path". The three sub-routes are checked in strict priority order:
rawTarget starts with "http://" or "https://"
│
▼
┌─────────────────────────────────────┐
│ 3a. Mock check (highest priority) │
└─────────────────────────────────────┘
│
├─── Mock matched ──────────────► serveMock() → write HTTP response to socket
│ → log entry: via="mock"
│
▼ (no mock matched)
┌─────────────────────────────────────┐
│ 3b. Proxy rule check │
└─────────────────────────────────────┘
│
├─── Rule matched ──────────────► proxyToUpstream(rule's target mapping)
│ → log entry: via="rule"
│
▼ (no rule matched)
┌─────────────────────────────────────┐
│ 3c. Passthrough (default) │
└─────────────────────────────────────┘
│
└────────────────────────────────► passthroughToUpstream()
→ http.request to original host
→ log entry: via="proxy"
5. Mappings
A mapping links a *.localhost domain to a backend target:
domain: "myapp.localhost"
target: "127.0.0.1:3000"
label: "Frontend Dev Server"
enabled: true
Mappings are only used in two situations:
- RFC 6761 path: the incoming
hostheader matchesmapping.domain - Proxy rules: a rule references a mapping by ID as its redirect target
Mappings are per-workspace. workspaceCfg() filters cfg.mappings to only those with workspaceId === activeWorkspaceId before any routing.
6. Proxy Rules
A proxy rule redirects matching URLs to a specific mapping target:
pattern: "^https?://api\\.staging\\.example\\.com" (regex)
targetMappingId: "abc123" (ID of a mapping)
enabled: true
Matching flow
For each enabled proxy rule (in order):
Test regex against full absolute URL string
│
├─── Matches ──────────────────► resolve mapping by targetMappingId
│ ├─── mapping found ──► proxyToUpstream(target)
│ └─── mapping deleted ─► 502 Bad Gateway
│
└─── No match ─────────────────► try next rule
(if no rules match → passthrough)
Proxy rules only fire in the forward proxy path. They have no effect on RFC 6761 requests.
7. Mocks
A mock short-circuits a real request and returns a configured response:
method: "GET" (or "*" for any method)
urlPattern: "/api/users" (exact) or ".*\/users.*" (regex)
useRegex: false
responseStatus: 200
responseHeaders: { "content-type": "application/json" }
responseBody: "[{\"id\":1}]"
enabled: true
Matching algorithm
For each enabled mock (in list order):
1. Method check: mock.method == "*" OR mock.method == request.method
2. URL check:
├─── useRegex == false: mock.urlPattern == full URL string (exact)
└─── useRegex == true: new RegExp(mock.urlPattern).test(full URL string)
│
└─── Both checks pass ──────────► serveMock()
Return configured response
(resolveVars() substitutes {{ENV_VAR}} placeholders)
Environment variable substitution
Mock body, headers, and URL patterns can use {{VARIABLE_NAME}} syntax. These are resolved against the active environment's variables at response time. For example:
{ "token": "{{API_KEY}}" }
becomes { "token": "secret123" } if the active environment has API_KEY=secret123.
Mocks only fire in the forward proxy path. They are never checked for RFC 6761 requests.
8. HTTPS CONNECT Tunnels
When a browser connects through a proxy to an HTTPS site, it first sends:
CONNECT api.example.com:443 HTTP/1.1
Bifurc responds 200 Connection Established and then creates a raw TCP pipe:
Browser ←──────── raw pipe ────────► api.example.com:443
There is no TLS termination, no inspection, no mock matching, and no logging for CONNECT tunnels. The encrypted traffic passes through opaquely.
9. The Loop Question: RFC 6761 + System Proxy
Question: If the OS/browser proxy is set to 127.0.0.1:9010, and a request to myapp.localhost arrives, will it loop back through Bifurc?
Answer: No. There is no loop.
Here is why:
Browser navigates to http://myapp.localhost/
│
│ (system proxy is 127.0.0.1:9010)
▼
Bifurc TCP server receives:
GET http://myapp.localhost/ HTTP/1.1
Host: myapp.localhost
│
▼
host = "myapp.localhost"
host.endsWith(".localhost") == true
│
▼
proxyToUpstream("127.0.0.1:3000")
│
│ Node.js http.request({ hostname: "127.0.0.1", port: 3000 })
│ This is a DIRECT connection, NOT through Bifurc
▼
Your actual app on port 3000
proxyToUpstream uses Node.js's http.request which connects directly to the specified hostname:port. It does not consult the OS proxy settings. It does not connect to Bifurc's own port. The chain terminates at your real service.
10. Requests Panel Replay
The Requests panel lets you re-send a captured request with edits. This uses a completely separate code path called replayRequest().
User clicks "Send" in Replay Editor
│
▼
window.api.replayRequest(method, url, headers, body)
│ (IPC call to Electron main process)
▼
replayRequest() in server.ts
│
│ http.request / https.request (direct Node.js call)
│ Does NOT go through 127.0.0.1:9010
│ Does NOT check mocks
│ Does NOT check proxy rules
▼
Target server (e.g., api.example.com:443)
Replay requests bypass Bifurc's proxy entirely. No mock matching, no proxy rules, no logging to the requests log.
11. WebSocket Connections
The WebSockets panel creates browser-native WebSocket connections:
User clicks "Connect" in WebSockets panel
│
▼
new WebSocket(url) ← browser native API, runs in renderer process
│
│ This is a direct WebSocket handshake (HTTP Upgrade)
│ Does NOT go through Bifurc's TCP server
│ Does NOT use the system proxy for WebSocket
▼
WebSocket server at target URL
WebSocket connections never touch Bifurc's proxy. The browser's WebSocket API sends an HTTP/1.1 101 Switching Protocols upgrade directly to the target host, bypassing any HTTP proxy configuration.
Up to 5 concurrent WebSocket connections are tracked in a module-level registry in renderer/lib/useWebSocket.ts.
12. Complete Traffic Map
┌─────────────────────────────────────────────────────────────────────────┐
│ BROWSER / APP │
│ │
│ *.localhost URL Forward Proxy Replay Panel WebSocket Panel │
│ │ (absolute URL) │ │ │
│ │ │ │ │ │
└───────┼──────────────────┼──────────────────┼───────────────┼───────────┘
│ │ │ │
▼ ▼ │ │
┌─────────────────────────────────┐ │ │
│ Bifurc 127.0.0.1:PORT │ │ │
│ │ │ │
│ RFC 6761 path │ │ │
│ → proxyToUpstream() │ │ │
│ │ │ │
│ Forward proxy path │ │ │
│ 1. Mock check │ │ │
│ 2. Proxy rule check │ │ │
│ 3. Passthrough │ │ │
│ │ │ │
│ CONNECT tunnel │ │ │
│ → raw TCP pipe │ │ │
└─────────────────────────────────┘ │ │
│ │ │ │
└──────────────────┼──────────────────┘ │
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ TARGET SERVER │ │ WS SERVER │
│ (your app, │ │ (direct conn) │
│ external API) │ └─────────────────┘
└─────────────────┘
13. Workspace Scoping
Every routing decision is scoped to the active workspace. Before dispatch, workspaceCfg() filters:
cfg.mappings→ only mappings withworkspaceId === activeWorkspaceIdcfg.proxyRules→ only rules withworkspaceId === activeWorkspaceIdcfg.mocks→ only mocks withworkspaceId === activeWorkspaceId
If no workspace is active (activeWorkspaceId is null/undefined), all items are visible.
14. Summary Table
| Feature | Path through Bifurc | Mock check | Proxy rule check | Logs |
|---|---|---|---|---|
*.localhost navigation | Yes — RFC 6761 path | No | No | Yes |
| HTTP via system proxy | Yes — forward proxy | Yes | Yes | Yes |
| HTTPS CONNECT tunnel | Yes — raw TCP pipe | No | No | No |
| Requests panel replay | No — direct Node.js http.request | No | No | No |
| WebSocket connection | No — direct browser WebSocket | No | No | No |