Skip to content

Commit 3e0b509

Browse files
authored
Demo: Unify HTTP/WebSocket server for reverse proxy compatibility (#1)
1 parent b54f7b6 commit 3e0b509

File tree

3 files changed

+67
-8
lines changed

3 files changed

+67
-8
lines changed

demo/README.md

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@ Works on **Linux** and **macOS** (no Windows support yet).
1414
## What it does
1515

1616
- Starts an HTTP server on port 8080 (configurable via `PORT` env var)
17-
- Starts a WebSocket server on port 3001 for PTY communication
17+
- Serves WebSocket PTY on the same port at `/ws` endpoint
1818
- Opens a real shell session (bash, zsh, etc.)
1919
- Provides full PTY support (colors, cursor positioning, resize, etc.)
20+
- Supports reverse proxies (ngrok, nginx, etc.) via X-Forwarded-\* headers
2021

2122
## Usage
2223

@@ -30,6 +31,49 @@ PORT=3000 npx @ghostty-web/demo@next
3031

3132
Then open http://localhost:8080 in your browser.
3233

34+
## Reverse Proxy Support
35+
36+
The server now supports reverse proxies like ngrok, nginx, and others by:
37+
38+
- Serving WebSocket on the same HTTP port (no separate port needed)
39+
- Using relative WebSocket URLs on the client side
40+
- Automatic protocol detection (HTTP/HTTPS, WS/WSS)
41+
42+
This means the WebSocket connection automatically adapts to use the same protocol and host as the HTTP connection, making it work seamlessly through any reverse proxy.
43+
44+
### Example with ngrok
45+
46+
```bash
47+
# Start the demo server
48+
npx @ghostty-web/demo@next
49+
50+
# In another terminal, expose it via ngrok
51+
ngrok http 8080
52+
```
53+
54+
The terminal will work seamlessly through the ngrok URL! Both HTTP and WebSocket traffic will be properly proxied.
55+
56+
### Example with nginx
57+
58+
```nginx
59+
server {
60+
listen 80;
61+
server_name example.com;
62+
63+
location / {
64+
proxy_pass http://localhost:8080;
65+
proxy_http_version 1.1;
66+
proxy_set_header Upgrade $http_upgrade;
67+
proxy_set_header Connection "upgrade";
68+
proxy_set_header Host $host;
69+
proxy_set_header X-Forwarded-Host $host;
70+
proxy_set_header X-Forwarded-Proto $scheme;
71+
proxy_set_header X-Real-IP $remote_addr;
72+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
73+
}
74+
}
75+
```
76+
3377
## Security Warning
3478

3579
⚠️ **This server provides full shell access.**

demo/bin/demo.js

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ const __dirname = path.dirname(__filename);
2323

2424
const DEV_MODE = process.argv.includes('--dev');
2525
const HTTP_PORT = process.env.PORT || (DEV_MODE ? 8000 : 8080);
26-
const WS_PORT = 3001;
2726

2827
// ============================================================================
2928
// Locate ghostty-web assets
@@ -239,8 +238,9 @@ const HTML_TEMPLATE = `<!doctype html>
239238
statusText.textContent = text;
240239
}
241240
242-
// Connect to WebSocket PTY server
243-
const wsUrl = 'ws://' + window.location.hostname + ':${WS_PORT}/ws?cols=' + term.cols + '&rows=' + term.rows;
241+
// Connect to WebSocket PTY server (use same origin as HTTP server)
242+
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
243+
const wsUrl = protocol + '//' + window.location.host + '/ws?cols=' + term.cols + '&rows=' + term.rows;
244244
let ws;
245245
246246
function connect() {
@@ -386,8 +386,23 @@ function createPtySession(cols, rows) {
386386
return ptyProcess;
387387
}
388388

389-
// WebSocket server using ws package
390-
const wss = new WebSocketServer({ port: WS_PORT, path: '/ws' });
389+
// WebSocket server attached to HTTP server (same port)
390+
const wss = new WebSocketServer({ noServer: true });
391+
392+
// Handle HTTP upgrade for WebSocket connections
393+
httpServer.on('upgrade', (req, socket, head) => {
394+
const url = new URL(req.url, `http://${req.headers.host}`);
395+
396+
if (url.pathname === '/ws') {
397+
// In production, consider validating req.headers.origin to prevent CSRF
398+
// For development/demo purposes, we allow all origins
399+
wss.handleUpgrade(req, socket, head, (ws) => {
400+
wss.emit('connection', ws, req);
401+
});
402+
} else {
403+
socket.destroy();
404+
}
405+
});
391406

392407
wss.on('connection', (ws, req) => {
393408
const url = new URL(req.url, `http://${req.headers.host}`);
@@ -474,7 +489,7 @@ function printBanner(url) {
474489
console.log(' 🚀 ghostty-web demo server' + (DEV_MODE ? ' (dev mode)' : ''));
475490
console.log('═'.repeat(60));
476491
console.log(`\n 📺 Open: ${url}`);
477-
console.log(` 📡 WebSocket PTY: ws://localhost:${WS_PORT}/ws`);
492+
console.log(` 📡 WebSocket PTY: same endpoint /ws`);
478493
console.log(` 🐚 Shell: ${getShell()}`);
479494
console.log(` 📁 Home: ${homedir()}`);
480495
if (DEV_MODE) {

demo/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@
193193

194194
function connectWebSocket() {
195195
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
196-
const wsUrl = `${protocol}//${window.location.hostname}:3001/ws?cols=${term.cols}&rows=${term.rows}`;
196+
const wsUrl = `${protocol}//${window.location.host}/ws?cols=${term.cols}&rows=${term.rows}`;
197197

198198
ws = new WebSocket(wsUrl);
199199

0 commit comments

Comments
 (0)