Created
August 21, 2023 14:43
-
-
Save andrewmackrodt/1394c0b4f1bcf1361e9127107a693854 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import pem from 'pem' | |
import fs from 'node:fs' | |
import http from 'node:http' | |
import https from 'node:https' | |
import net from 'node:net' | |
import { parse as parseURL } from 'node:url' | |
import tls, { SecureContext } from 'tls' | |
type Scheme = 'http' | 'https' | |
const rootCert = await new Promise<pem.CertificateCreationResult>((resolve, reject) => { | |
pem.createCertificate({ days: 3650, selfSigned: true }, (err, keys) => { | |
if (err) { | |
reject(err) | |
} else { | |
resolve(keys) | |
} | |
}) | |
}) | |
await fs.promises.writeFile('localhost.crt', rootCert.certificate) | |
async function createCertificate(commonName: string) { | |
return new Promise<pem.CertificateCreationResult>((resolve, reject) => { | |
pem.createCertificate({ | |
altNames: [commonName, `*.${commonName}`], | |
commonName, | |
days: 30, | |
organization: '', | |
organizationUnit: '', | |
serial: Date.now(), | |
serviceCertificate: rootCert.certificate, | |
serviceKey: rootCert.serviceKey, | |
}, (err, keys) => { | |
if (err) { | |
reject(err) | |
} else { | |
resolve(keys) | |
} | |
}) | |
}) | |
} | |
function proxy(scheme: Scheme, req: http.IncomingMessage, res: http.ServerResponse) { | |
const url = req.url!.match(/^https?:/i) | |
? parseURL(req.url!) | |
: parseURL(`${scheme}://${req.headers.host}${req.url}`) | |
const options: http.RequestOptions = { | |
hostname: url.hostname, | |
port: url.port, | |
path: url.path, | |
method: req.method, | |
headers: req.headers, | |
} | |
const cb = (proxyRes: http.IncomingMessage) => { | |
res.writeHead(proxyRes.statusCode!, proxyRes.headers) | |
proxyRes.pipe(res) | |
} | |
let proxyReq: http.ClientRequest | |
if (scheme === 'https') { | |
proxyReq = https.request(options, cb) | |
} else { | |
proxyReq = http.request(options, cb) | |
} | |
req.pipe(proxyReq) | |
} | |
const contexts: Record<string, SecureContext> = {} | |
const tlsServer = https.createServer( | |
{ | |
ALPNProtocols: ['http/1.1'], | |
SNICallback: async (commonName: string, cb: (err: Error | null, ctx?: SecureContext) => void) => { | |
if ( ! (commonName in contexts)) { | |
const cert = await createCertificate(commonName) | |
const { context } = tls.createSecureContext({ | |
key: cert.clientKey, | |
cert: cert.certificate, | |
ca: rootCert.certificate, | |
}) | |
contexts[commonName] = context | |
} | |
cb(null, contexts[commonName]) | |
}, | |
key: rootCert.clientKey, | |
cert: rootCert.certificate, | |
}, | |
(req, res) => proxy('https', req, res)) | |
const tlsPort = await new Promise<number>(resolve => { | |
tlsServer.listen(0, () => { | |
const port = (tlsServer.address() as net.AddressInfo).port | |
resolve(port) | |
}) | |
}) | |
const server = http.createServer((req, res) => proxy('http', req, res)) | |
server.on('connect', async (req, clientSocket, head) => { | |
const serverSocket = net.connect(tlsPort, '127.0.0.1', () => { | |
clientSocket.write('HTTP/1.1 200 Connection Established\r\n\r\n') | |
serverSocket.write(head) | |
serverSocket.pipe(clientSocket) | |
clientSocket.pipe(serverSocket) | |
}) | |
}) | |
server.listen(8000) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment