diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-21 20:56:19 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-21 20:56:19 +0000 |
commit | 0b6210cd37b68b94252cb798598b12974a20e1c1 (patch) | |
tree | e371686554a877842d95aa94f100bee552ff2a8e /examples | |
parent | Initial commit. (diff) | |
download | node-undici-0b6210cd37b68b94252cb798598b12974a20e1c1.tar.xz node-undici-0b6210cd37b68b94252cb798598b12974a20e1c1.zip |
Adding upstream version 5.28.2+dfsg1+~cs23.11.12.3.upstream/5.28.2+dfsg1+_cs23.11.12.3upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'examples')
-rw-r--r-- | examples/ca-fingerprint/index.js | 80 | ||||
-rw-r--r-- | examples/fetch.js | 13 | ||||
-rw-r--r-- | examples/proxy-agent.js | 25 | ||||
-rw-r--r-- | examples/proxy/index.js | 49 | ||||
-rw-r--r-- | examples/proxy/proxy.js | 256 | ||||
-rw-r--r-- | examples/request.js | 18 |
6 files changed, 441 insertions, 0 deletions
diff --git a/examples/ca-fingerprint/index.js b/examples/ca-fingerprint/index.js new file mode 100644 index 0000000..792c08c --- /dev/null +++ b/examples/ca-fingerprint/index.js @@ -0,0 +1,80 @@ +'use strict' + +const crypto = require('crypto') +const https = require('https') +const { Client, buildConnector } = require('../..') +const pem = require('https-pem') + +const caFingerprint = getFingerprint(pem.cert.toString() + .split('\n') + .slice(1, -1) + .map(line => line.trim()) + .join('') +) + +const server = https.createServer(pem, (req, res) => { + res.setHeader('Content-Type', 'text/plain') + res.end('hello') +}) + +server.listen(0, function () { + const connector = buildConnector({ rejectUnauthorized: false }) + const client = new Client(`https://localhost:${server.address().port}`, { + connect (opts, cb) { + connector(opts, (err, socket) => { + if (err) { + cb(err) + } else if (getIssuerCertificate(socket).fingerprint256 !== caFingerprint) { + socket.destroy() + cb(new Error('Fingerprint does not match or malformed certificate')) + } else { + cb(null, socket) + } + }) + } + }) + + client.request({ + path: '/', + method: 'GET' + }, (err, data) => { + if (err) throw err + + const bufs = [] + data.body.on('data', (buf) => { + bufs.push(buf) + }) + data.body.on('end', () => { + console.log(Buffer.concat(bufs).toString('utf8')) + client.close() + server.close() + }) + }) +}) + +function getIssuerCertificate (socket) { + let certificate = socket.getPeerCertificate(true) + while (certificate && Object.keys(certificate).length > 0) { + // invalid certificate + if (certificate.issuerCertificate == null) { + return null + } + + // We have reached the root certificate. + // In case of self-signed certificates, `issuerCertificate` may be a circular reference. + if (certificate.fingerprint256 === certificate.issuerCertificate.fingerprint256) { + break + } + + // continue the loop + certificate = certificate.issuerCertificate + } + return certificate +} + +function getFingerprint (content, inputEncoding = 'base64', outputEncoding = 'hex') { + const shasum = crypto.createHash('sha256') + shasum.update(content, inputEncoding) + const res = shasum.digest(outputEncoding) + return res.toUpperCase().match(/.{1,2}/g).join(':') +} diff --git a/examples/fetch.js b/examples/fetch.js new file mode 100644 index 0000000..7ece2b8 --- /dev/null +++ b/examples/fetch.js @@ -0,0 +1,13 @@ +'use strict' + +const { fetch } = require('../') + +async function main () { + const res = await fetch('http://localhost:3001/') + + const data = await res.text() + console.log('response received', res.status) + console.log('headers', res.headers) + console.log('data', data) +} +main() diff --git a/examples/proxy-agent.js b/examples/proxy-agent.js new file mode 100644 index 0000000..7caf836 --- /dev/null +++ b/examples/proxy-agent.js @@ -0,0 +1,25 @@ +'use strict' + +const { request, setGlobalDispatcher, ProxyAgent } = require('../') + +setGlobalDispatcher(new ProxyAgent('http://localhost:8000/')) + +async function main () { + const { + statusCode, + headers, + trailers, + body + // send the request via the http://localhost:8000/ HTTP proxy + } = await request('http://localhost:3000/undici') + + console.log('response received', statusCode) + console.log('headers', headers) + + for await (const data of body) { + console.log('data', data) + } + + console.log('trailers', trailers) +} +main() diff --git a/examples/proxy/index.js b/examples/proxy/index.js new file mode 100644 index 0000000..5f35049 --- /dev/null +++ b/examples/proxy/index.js @@ -0,0 +1,49 @@ +const { Pool, Client } = require('../../') +const http = require('http') +const proxy = require('./proxy') + +const pool = new Pool('http://localhost:4001', { + connections: 256, + pipelining: 1 +}) + +async function run () { + await Promise.all([ + new Promise(resolve => { + // Proxy + http.createServer((req, res) => { + proxy({ req, res, proxyName: 'example' }, pool).catch(err => { + if (res.headersSent) { + res.destroy(err) + } else { + for (const name of res.getHeaderNames()) { + res.removeHeader(name) + } + res.statusCode = err.statusCode || 500 + res.end() + } + }) + }).listen(4000, resolve) + }), + new Promise(resolve => { + // Upstream + http.createServer((req, res) => { + res.end('hello world') + }).listen(4001, resolve) + }) + ]) + + const client = new Client('http://localhost:4000') + const { body } = await client.request({ + method: 'GET', + path: '/' + }) + + for await (const chunk of body) { + console.log(String(chunk)) + } +} + +run() + +// TODO: Add websocket example. diff --git a/examples/proxy/proxy.js b/examples/proxy/proxy.js new file mode 100644 index 0000000..bb9fcc4 --- /dev/null +++ b/examples/proxy/proxy.js @@ -0,0 +1,256 @@ +const net = require('net') +const { pipeline } = require('stream') +const createError = require('http-errors') + +module.exports = async function proxy (ctx, client) { + const { req, socket, proxyName } = ctx + + const headers = getHeaders({ + headers: req.rawHeaders, + httpVersion: req.httpVersion, + socket: req.socket, + proxyName + }) + + if (socket) { + const handler = new WSHandler(ctx) + client.dispatch({ + method: req.method, + path: req.url, + headers, + upgrade: 'Websocket' + }, handler) + return handler.promise + } else { + const handler = new HTTPHandler(ctx) + client.dispatch({ + method: req.method, + path: req.url, + headers, + body: req + }, handler) + return handler.promise + } +} + +class HTTPHandler { + constructor (ctx) { + const { req, res, proxyName } = ctx + + this.proxyName = proxyName + this.req = req + this.res = res + this.resume = null + this.abort = null + this.promise = new Promise((resolve, reject) => { + this.callback = err => err ? reject(err) : resolve() + }) + } + + onConnect (abort) { + if (this.req.aborted) { + abort() + } else { + this.abort = abort + this.res.on('close', abort) + } + } + + onHeaders (statusCode, headers, resume) { + if (statusCode < 200) { + return + } + + this.resume = resume + this.res.on('drain', resume) + this.res.writeHead(statusCode, getHeaders({ + headers, + proxyName: this.proxyName, + httpVersion: this.httpVersion + })) + } + + onData (chunk) { + return this.res.write(chunk) + } + + onComplete () { + this.res.off('close', this.abort) + this.res.off('drain', this.resume) + + this.res.end() + this.callback() + } + + onError (err) { + this.res.off('close', this.abort) + this.res.off('drain', this.resume) + + this.callback(err) + } +} + +class WSHandler { + constructor (ctx) { + const { req, socket, proxyName, head } = ctx + + setupSocket(socket) + + this.proxyName = proxyName + this.httpVersion = req.httpVersion + this.socket = socket + this.head = head + this.abort = null + this.promise = new Promise((resolve, reject) => { + this.callback = err => err ? reject(err) : resolve() + }) + } + + onConnect (abort) { + if (this.socket.destroyed) { + abort() + } else { + this.abort = abort + this.socket.on('close', abort) + } + } + + onUpgrade (statusCode, headers, socket) { + this.socket.off('close', this.abort) + + // TODO: Check statusCode? + + if (this.head && this.head.length) { + socket.unshift(this.head) + } + + setupSocket(socket) + + headers = getHeaders({ + headers, + proxyName: this.proxyName, + httpVersion: this.httpVersion + }) + + let head = '' + for (let n = 0; n < headers.length; n += 2) { + head += `\r\n${headers[n]}: ${headers[n + 1]}` + } + + this.socket.write(`HTTP/1.1 101 Switching Protocols\r\nconnection: upgrade\r\nupgrade: websocket${head}\r\n\r\n`) + + pipeline(socket, this.socket, socket, this.callback) + } + + onError (err) { + this.socket.off('close', this.abort) + + this.callback(err) + } +} + +// This expression matches hop-by-hop headers. +// These headers are meaningful only for a single transport-level connection, +// and must not be retransmitted by proxies or cached. +const HOP_EXPR = /^(te|host|upgrade|trailers|connection|keep-alive|http2-settings|transfer-encoding|proxy-connection|proxy-authenticate|proxy-authorization)$/i + +// Removes hop-by-hop and pseudo headers. +// Updates via and forwarded headers. +// Only hop-by-hop headers may be set using the Connection general header. +function getHeaders ({ + headers, + proxyName, + httpVersion, + socket +}) { + let via = '' + let forwarded = '' + let host = '' + let authority = '' + let connection = '' + + for (let n = 0; n < headers.length; n += 2) { + const key = headers[n] + const val = headers[n + 1] + + if (!via && key.length === 3 && key.toLowerCase() === 'via') { + via = val + } else if (!host && key.length === 4 && key.toLowerCase() === 'host') { + host = val + } else if (!forwarded && key.length === 9 && key.toLowerCase() === 'forwarded') { + forwarded = val + } else if (!connection && key.length === 10 && key.toLowerCase() === 'connection') { + connection = val + } else if (!authority && key.length === 10 && key === ':authority') { + authority = val + } + } + + let remove + if (connection && !HOP_EXPR.test(connection)) { + remove = connection.split(/,\s*/) + } + + const result = [] + for (let n = 0; n < headers.length; n += 2) { + const key = headers[n] + const val = headers[n + 1] + + if ( + key.charAt(0) !== ':' && + !HOP_EXPR.test(key) && + (!remove || !remove.includes(key)) + ) { + result.push(key, val) + } + } + + if (socket) { + result.push('forwarded', (forwarded ? forwarded + ', ' : '') + [ + `by=${printIp(socket.localAddress, socket.localPort)}`, + `for=${printIp(socket.remoteAddress, socket.remotePort)}`, + `proto=${socket.encrypted ? 'https' : 'http'}`, + `host=${printIp(authority || host || '')}` + ].join(';')) + } else if (forwarded) { + // The forwarded header should not be included in response. + throw new createError.BadGateway() + } + + if (proxyName) { + if (via) { + if (via.split(',').some(name => name.endsWith(proxyName))) { + throw new createError.LoopDetected() + } + via += ', ' + } + via += `${httpVersion} ${proxyName}` + } + + if (via) { + result.push('via', via) + } + + return result +} + +function setupSocket (socket) { + socket.setTimeout(0) + socket.setNoDelay(true) + socket.setKeepAlive(true, 0) +} + +function printIp (address, port) { + const isIPv6 = net.isIPv6(address) + let str = `${address}` + if (isIPv6) { + str = `[${str}]` + } + if (port) { + str = `${str}:${port}` + } + if (isIPv6 || port) { + str = `"${str}"` + } + return str +} diff --git a/examples/request.js b/examples/request.js new file mode 100644 index 0000000..1b03254 --- /dev/null +++ b/examples/request.js @@ -0,0 +1,18 @@ +'use strict' + +const { request } = require('../') + +async function main () { + const { + statusCode, + headers, + body + } = await request('http://localhost:3001/') + + const data = await body.text() + console.log('response received', statusCode) + console.log('headers', headers) + console.log('data', data) +} + +main() |