summaryrefslogtreecommitdiffstats
path: root/examples
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-21 20:56:19 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-21 20:56:19 +0000
commit0b6210cd37b68b94252cb798598b12974a20e1c1 (patch)
treee371686554a877842d95aa94f100bee552ff2a8e /examples
parentInitial commit. (diff)
downloadnode-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.js80
-rw-r--r--examples/fetch.js13
-rw-r--r--examples/proxy-agent.js25
-rw-r--r--examples/proxy/index.js49
-rw-r--r--examples/proxy/proxy.js256
-rw-r--r--examples/request.js18
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()