aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/node_modules/undici/docs/best-practices
diff options
context:
space:
mode:
authorEgor Tensin <egor@tensin.name>2024-01-28 11:18:12 +0100
committerEgor Tensin <egor@tensin.name>2024-01-28 11:18:12 +0100
commit2d40e8046a08373b1740922690b695a16d5e5fa6 (patch)
treeeb086741d1bb01ef9ed3d0ac8e1301447e835971 /node_modules/undici/docs/best-practices
parentworkflows/test: upgrade actions (diff)
downloadcleanup-path-2d40e8046a08373b1740922690b695a16d5e5fa6.tar.gz
cleanup-path-2d40e8046a08373b1740922690b695a16d5e5fa6.zip
bump version in package.json, upgrade dependencies
Diffstat (limited to 'node_modules/undici/docs/best-practices')
-rw-r--r--node_modules/undici/docs/best-practices/client-certificate.md64
-rw-r--r--node_modules/undici/docs/best-practices/mocking-request.md136
-rw-r--r--node_modules/undici/docs/best-practices/proxy.md127
-rw-r--r--node_modules/undici/docs/best-practices/writing-tests.md20
4 files changed, 347 insertions, 0 deletions
diff --git a/node_modules/undici/docs/best-practices/client-certificate.md b/node_modules/undici/docs/best-practices/client-certificate.md
new file mode 100644
index 0000000..4fc84ec
--- /dev/null
+++ b/node_modules/undici/docs/best-practices/client-certificate.md
@@ -0,0 +1,64 @@
+# Client certificate
+
+Client certificate authentication can be configured with the `Client`, the required options are passed along through the `connect` option.
+
+The client certificates must be signed by a trusted CA. The Node.js default is to trust the well-known CAs curated by Mozilla.
+
+Setting the server option `requestCert: true` tells the server to request the client certificate.
+
+The server option `rejectUnauthorized: false` allows us to handle any invalid certificate errors in client code. The `authorized` property on the socket of the incoming request will show if the client certificate was valid. The `authorizationError` property will give the reason if the certificate was not valid.
+
+### Client Certificate Authentication
+
+```js
+const { readFileSync } = require('fs')
+const { join } = require('path')
+const { createServer } = require('https')
+const { Client } = require('undici')
+
+const serverOptions = {
+ ca: [
+ readFileSync(join(__dirname, 'client-ca-crt.pem'), 'utf8')
+ ],
+ key: readFileSync(join(__dirname, 'server-key.pem'), 'utf8'),
+ cert: readFileSync(join(__dirname, 'server-crt.pem'), 'utf8'),
+ requestCert: true,
+ rejectUnauthorized: false
+}
+
+const server = createServer(serverOptions, (req, res) => {
+ // true if client cert is valid
+ if(req.client.authorized === true) {
+ console.log('valid')
+ } else {
+ console.error(req.client.authorizationError)
+ }
+ res.end()
+})
+
+server.listen(0, function () {
+ const tls = {
+ ca: [
+ readFileSync(join(__dirname, 'server-ca-crt.pem'), 'utf8')
+ ],
+ key: readFileSync(join(__dirname, 'client-key.pem'), 'utf8'),
+ cert: readFileSync(join(__dirname, 'client-crt.pem'), 'utf8'),
+ rejectUnauthorized: false,
+ servername: 'agent1'
+ }
+ const client = new Client(`https://localhost:${server.address().port}`, {
+ connect: tls
+ })
+
+ client.request({
+ path: '/',
+ method: 'GET'
+ }, (err, { body }) => {
+ body.on('data', (buf) => {})
+ body.on('end', () => {
+ client.close()
+ server.close()
+ })
+ })
+})
+```
diff --git a/node_modules/undici/docs/best-practices/mocking-request.md b/node_modules/undici/docs/best-practices/mocking-request.md
new file mode 100644
index 0000000..6954392
--- /dev/null
+++ b/node_modules/undici/docs/best-practices/mocking-request.md
@@ -0,0 +1,136 @@
+# Mocking Request
+
+Undici has its own mocking [utility](../api/MockAgent.md). It allow us to intercept undici HTTP requests and return mocked values instead. It can be useful for testing purposes.
+
+Example:
+
+```js
+// bank.mjs
+import { request } from 'undici'
+
+export async function bankTransfer(recipient, amount) {
+ const { body } = await request('http://localhost:3000/bank-transfer',
+ {
+ method: 'POST',
+ headers: {
+ 'X-TOKEN-SECRET': 'SuperSecretToken',
+ },
+ body: JSON.stringify({
+ recipient,
+ amount
+ })
+ }
+ )
+ return await body.json()
+}
+```
+
+And this is what the test file looks like:
+
+```js
+// index.test.mjs
+import { strict as assert } from 'assert'
+import { MockAgent, setGlobalDispatcher, } from 'undici'
+import { bankTransfer } from './bank.mjs'
+
+const mockAgent = new MockAgent();
+
+setGlobalDispatcher(mockAgent);
+
+// Provide the base url to the request
+const mockPool = mockAgent.get('http://localhost:3000');
+
+// intercept the request
+mockPool.intercept({
+ path: '/bank-transfer',
+ method: 'POST',
+ headers: {
+ 'X-TOKEN-SECRET': 'SuperSecretToken',
+ },
+ body: JSON.stringify({
+ recipient: '1234567890',
+ amount: '100'
+ })
+}).reply(200, {
+ message: 'transaction processed'
+})
+
+const success = await bankTransfer('1234567890', '100')
+
+assert.deepEqual(success, { message: 'transaction processed' })
+
+// if you dont want to check whether the body or the headers contain the same value
+// just remove it from interceptor
+mockPool.intercept({
+ path: '/bank-transfer',
+ method: 'POST',
+}).reply(400, {
+ message: 'bank account not found'
+})
+
+const badRequest = await bankTransfer('1234567890', '100')
+
+assert.deepEqual(badRequest, { message: 'bank account not found' })
+```
+
+Explore other MockAgent functionality [here](../api/MockAgent.md)
+
+## Debug Mock Value
+
+When the interceptor and the request options are not the same, undici will automatically make a real HTTP request. To prevent real requests from being made, use `mockAgent.disableNetConnect()`:
+
+```js
+const mockAgent = new MockAgent();
+
+setGlobalDispatcher(mockAgent);
+mockAgent.disableNetConnect()
+
+// Provide the base url to the request
+const mockPool = mockAgent.get('http://localhost:3000');
+
+mockPool.intercept({
+ path: '/bank-transfer',
+ method: 'POST',
+}).reply(200, {
+ message: 'transaction processed'
+})
+
+const badRequest = await bankTransfer('1234567890', '100')
+// Will throw an error
+// MockNotMatchedError: Mock dispatch not matched for path '/bank-transfer':
+// subsequent request to origin http://localhost:3000 was not allowed (net.connect disabled)
+```
+
+## Reply with data based on request
+
+If the mocked response needs to be dynamically derived from the request parameters, you can provide a function instead of an object to `reply`:
+
+```js
+mockPool.intercept({
+ path: '/bank-transfer',
+ method: 'POST',
+ headers: {
+ 'X-TOKEN-SECRET': 'SuperSecretToken',
+ },
+ body: JSON.stringify({
+ recipient: '1234567890',
+ amount: '100'
+ })
+}).reply(200, (opts) => {
+ // do something with opts
+
+ return { message: 'transaction processed' }
+})
+```
+
+in this case opts will be
+
+```
+{
+ method: 'POST',
+ headers: { 'X-TOKEN-SECRET': 'SuperSecretToken' },
+ body: '{"recipient":"1234567890","amount":"100"}',
+ origin: 'http://localhost:3000',
+ path: '/bank-transfer'
+}
+```
diff --git a/node_modules/undici/docs/best-practices/proxy.md b/node_modules/undici/docs/best-practices/proxy.md
new file mode 100644
index 0000000..bf10295
--- /dev/null
+++ b/node_modules/undici/docs/best-practices/proxy.md
@@ -0,0 +1,127 @@
+# Connecting through a proxy
+
+Connecting through a proxy is possible by:
+
+- Using [AgentProxy](../api/ProxyAgent.md).
+- Configuring `Client` or `Pool` constructor.
+
+The proxy url should be passed to the `Client` or `Pool` constructor, while the upstream server url
+should be added to every request call in the `path`.
+For instance, if you need to send a request to the `/hello` route of your upstream server,
+the `path` should be `path: 'http://upstream.server:port/hello?foo=bar'`.
+
+If you proxy requires basic authentication, you can send it via the `proxy-authorization` header.
+
+### Connect without authentication
+
+```js
+import { Client } from 'undici'
+import { createServer } from 'http'
+import proxy from 'proxy'
+
+const server = await buildServer()
+const proxyServer = await buildProxy()
+
+const serverUrl = `http://localhost:${server.address().port}`
+const proxyUrl = `http://localhost:${proxyServer.address().port}`
+
+server.on('request', (req, res) => {
+ console.log(req.url) // '/hello?foo=bar'
+ res.setHeader('content-type', 'application/json')
+ res.end(JSON.stringify({ hello: 'world' }))
+})
+
+const client = new Client(proxyUrl)
+
+const response = await client.request({
+ method: 'GET',
+ path: serverUrl + '/hello?foo=bar'
+})
+
+response.body.setEncoding('utf8')
+let data = ''
+for await (const chunk of response.body) {
+ data += chunk
+}
+console.log(response.statusCode) // 200
+console.log(JSON.parse(data)) // { hello: 'world' }
+
+server.close()
+proxyServer.close()
+client.close()
+
+function buildServer () {
+ return new Promise((resolve, reject) => {
+ const server = createServer()
+ server.listen(0, () => resolve(server))
+ })
+}
+
+function buildProxy () {
+ return new Promise((resolve, reject) => {
+ const server = proxy(createServer())
+ server.listen(0, () => resolve(server))
+ })
+}
+```
+
+### Connect with authentication
+
+```js
+import { Client } from 'undici'
+import { createServer } from 'http'
+import proxy from 'proxy'
+
+const server = await buildServer()
+const proxyServer = await buildProxy()
+
+const serverUrl = `http://localhost:${server.address().port}`
+const proxyUrl = `http://localhost:${proxyServer.address().port}`
+
+proxyServer.authenticate = function (req, fn) {
+ fn(null, req.headers['proxy-authorization'] === `Basic ${Buffer.from('user:pass').toString('base64')}`)
+}
+
+server.on('request', (req, res) => {
+ console.log(req.url) // '/hello?foo=bar'
+ res.setHeader('content-type', 'application/json')
+ res.end(JSON.stringify({ hello: 'world' }))
+})
+
+const client = new Client(proxyUrl)
+
+const response = await client.request({
+ method: 'GET',
+ path: serverUrl + '/hello?foo=bar',
+ headers: {
+ 'proxy-authorization': `Basic ${Buffer.from('user:pass').toString('base64')}`
+ }
+})
+
+response.body.setEncoding('utf8')
+let data = ''
+for await (const chunk of response.body) {
+ data += chunk
+}
+console.log(response.statusCode) // 200
+console.log(JSON.parse(data)) // { hello: 'world' }
+
+server.close()
+proxyServer.close()
+client.close()
+
+function buildServer () {
+ return new Promise((resolve, reject) => {
+ const server = createServer()
+ server.listen(0, () => resolve(server))
+ })
+}
+
+function buildProxy () {
+ return new Promise((resolve, reject) => {
+ const server = proxy(createServer())
+ server.listen(0, () => resolve(server))
+ })
+}
+```
+
diff --git a/node_modules/undici/docs/best-practices/writing-tests.md b/node_modules/undici/docs/best-practices/writing-tests.md
new file mode 100644
index 0000000..57549de
--- /dev/null
+++ b/node_modules/undici/docs/best-practices/writing-tests.md
@@ -0,0 +1,20 @@
+# Writing tests
+
+Undici is tuned for a production use case and its default will keep
+a socket open for a few seconds after an HTTP request is completed to
+remove the overhead of opening up a new socket. These settings that makes
+Undici shine in production are not a good fit for using Undici in automated
+tests, as it will result in longer execution times.
+
+The following are good defaults that will keep the socket open for only 10ms:
+
+```js
+import { request, setGlobalDispatcher, Agent } from 'undici'
+
+const agent = new Agent({
+ keepAliveTimeout: 10, // milliseconds
+ keepAliveMaxTimeout: 10 // milliseconds
+})
+
+setGlobalDispatcher(agent)
+```