Skip to content

Commit

Permalink
Merge branch 'main' into handled-undefined-socket-in-onconnectimeout
Browse files Browse the repository at this point in the history
  • Loading branch information
Uzlopak committed Nov 11, 2024
2 parents adfc4f7 + 4d0eace commit ad1a2e6
Show file tree
Hide file tree
Showing 45 changed files with 693 additions and 272 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ jobs:

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@e2b3eafc8d227b0241d48be5f425d47c2d750a13 # v2.3.3
uses: github/codeql-action/init@662472033e021d55d94146f66f6058822b0b39fd # v2.3.3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
Expand All @@ -60,7 +60,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@e2b3eafc8d227b0241d48be5f425d47c2d750a13 # v2.3.3
uses: github/codeql-action/autobuild@662472033e021d55d94146f66f6058822b0b39fd # v2.3.3

# ℹ️ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
Expand All @@ -73,6 +73,6 @@ jobs:
# ./location_of_script_within_repo/buildscript.sh

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@e2b3eafc8d227b0241d48be5f425d47c2d750a13 # v2.3.3
uses: github/codeql-action/analyze@662472033e021d55d94146f66f6058822b0b39fd # v2.3.3
with:
category: "/language:${{matrix.language}}"
4 changes: 2 additions & 2 deletions .github/workflows/nodejs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
persist-credentials: false

- name: Dependency Review
uses: actions/dependency-review-action@5a2ce3f5b92ee19cbb1541a4984c76d921601d7c # v4.3.4
uses: actions/dependency-review-action@4081bf99e2866ebe428fc0477b69eb4fcda7220a # v4.4.0

lint:
name: Lint
Expand Down Expand Up @@ -199,6 +199,6 @@ jobs:
pull-requests: write
steps:
- name: Merge Dependabot PR
uses: fastify/github-action-merge-dependabot@3892334d1c649bb8119af3d22a3f3766bd5e593f # v3.10.2
uses: fastify/github-action-merge-dependabot@c3bde0759d4f24db16f7b250b2122bc2df57e817 # v3.11.0
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
4 changes: 2 additions & 2 deletions .github/workflows/scorecard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,14 @@ jobs:
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab.
- name: "Upload artifact"
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
with:
name: SARIF file
path: results.sarif
retention-days: 5

# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@e2b3eafc8d227b0241d48be5f425d47c2d750a13 # v3.26.10
uses: github/codeql-action/upload-sarif@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0
with:
sarif_file: results.sarif
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,8 @@ Refs: https://tools.ietf.org/html/rfc7231#section-5.1.1
### Pipelining

Undici will only use pipelining if configured with a `pipelining` factor
greater than `1`.
greater than `1`. Also it is important to pass `blocking: false` to the
request options to properly pipeline requests.

Undici always assumes that connections are persistent and will immediately
pipeline requests, without checking whether the connection is persistent.
Expand Down
2 changes: 2 additions & 0 deletions benchmarks/benchmark.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ const superagentAgent = new http.Agent({
const undiciOptions = {
path: '/',
method: 'GET',
blocking: false,
reset: false,
headersTimeout,
bodyTimeout
}
Expand Down
25 changes: 0 additions & 25 deletions build/Dockerfile

This file was deleted.

30 changes: 7 additions & 23 deletions build/wasm.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
'use strict'

const WASM_BUILDER_CONTAINER = 'ghcr.io/nodejs/wasm-builder@sha256:975f391d907e42a75b8c72eb77c782181e941608687d4d8694c3e9df415a0970' // v0.0.9

const { execSync } = require('node:child_process')
const { writeFileSync, readFileSync } = require('node:fs')
const { join, resolve } = require('node:path')

const ROOT = resolve(__dirname, '../')
const WASM_SRC = resolve(__dirname, '../deps/llhttp')
const WASM_OUT = resolve(__dirname, '../lib/llhttp')
const DOCKERFILE = resolve(__dirname, './Dockerfile')

// These are defined by build environment
const WASM_CC = process.env.WASM_CC || 'clang'
Expand Down Expand Up @@ -52,33 +53,16 @@ if (!platform && process.argv[2]) {
platform = execSync('docker info -f "{{.OSType}}/{{.Architecture}}"').toString().trim()
}

if (process.argv[2] === '--rm') {
const cmd = 'docker image rm llhttp_wasm_builder'

console.log(`> ${cmd}\n\n`)
try {
execSync(cmd, { stdio: 'inherit' })
} catch (e) {}

process.exit(0)
}

if (process.argv[2] === '--prebuild') {
const cmd = `docker build --platform=${platform.toString().trim()} -t llhttp_wasm_builder -f ${DOCKERFILE} ${ROOT}`

console.log(`> ${cmd}\n\n`)
execSync(cmd, { stdio: 'inherit' })

process.exit(0)
}

if (process.argv[2] === '--docker') {
let cmd = `docker run --rm -t --platform=${platform.toString().trim()}`
let cmd = `docker run --rm --platform=${platform.toString().trim()} `
if (process.platform === 'linux') {
cmd += ` --user ${process.getuid()}:${process.getegid()}`
}

cmd += ` --mount type=bind,source=${ROOT}/lib/llhttp,target=/home/node/undici/lib/llhttp llhttp_wasm_builder node build/wasm.js`
cmd += ` --mount type=bind,source=${ROOT}/lib/llhttp,target=/home/node/build/lib/llhttp \
--mount type=bind,source=${ROOT}/build,target=/home/node/build/build \
--mount type=bind,source=${ROOT}/deps,target=/home/node/build/deps \
-t ${WASM_BUILDER_CONTAINER} node build/wasm.js`
console.log(`> ${cmd}\n\n`)
execSync(cmd, { stdio: 'inherit' })
process.exit(0)
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/api/BalancedPool.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Extends: `undici.Dispatcher`

A pool of [Pool](Pool.md) instances connected to multiple upstreams.
A pool of [Pool](/docs/docs/api/Pool.md) instances connected to multiple upstreams.

Requests are not guaranteed to be dispatched in order of invocation.

Expand Down
4 changes: 2 additions & 2 deletions docs/docs/api/Dispatcher.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ Returns: `Boolean` - `false` if dispatcher is busy and further dispatch calls wo
* **headers** `UndiciHeaders | string[]` (optional) - Default: `null`.
* **query** `Record<string, any> | null` (optional) - Default: `null` - Query string params to be embedded in the request URL. Note that both keys and values of query are encoded using `encodeURIComponent`. If for some reason you need to send them unencoded, embed query params into path directly instead.
* **idempotent** `boolean` (optional) - Default: `true` if `method` is `'HEAD'` or `'GET'` - Whether the requests can be safely retried or not. If `false` the request won't be sent until all preceding requests in the pipeline has completed.
* **blocking** `boolean` (optional) - Default: `false` - Whether the response is expected to take a long time and would end up blocking the pipeline. When this is set to `true` further pipelining will be avoided on the same connection until headers have been received.
* **blocking** `boolean` (optional) - Default: `method !== 'HEAD'` - Whether the response is expected to take a long time and would end up blocking the pipeline. When this is set to `true` further pipelining will be avoided on the same connection until headers have been received.
* **upgrade** `string | null` (optional) - Default: `null` - Upgrade the request. Should be used to specify the kind of upgrade i.e. `'Websocket'`.
* **bodyTimeout** `number | null` (optional) - The timeout after which a request will time out, in milliseconds. Monitors time between receiving body data. Use `0` to disable it entirely. Defaults to 300 seconds.
* **headersTimeout** `number | null` (optional) - The amount of time, in milliseconds, the parser will wait to receive the complete HTTP headers while not sending the request. Defaults to 300 seconds.
Expand Down Expand Up @@ -1021,7 +1021,7 @@ The `dns` interceptor enables you to cache DNS lookups for a given duration, per
- It can be either `'4` or `6`.
- It will only take effect if `dualStack` is `false`.
- `lookup: (hostname: string, options: LookupOptions, callback: (err: NodeJS.ErrnoException | null, addresses: DNSInterceptorRecord[]) => void) => void` - Custom lookup function. Default: `dns.lookup`.
- For more info see [dns.lookup](https://nodejs.org/api/dns.html#dns_dns_lookup_hostname_options_callback).
- For more info see [dns.lookup](https://nodejs.org/api/dns.html#dns_dns_lookup_hostname_options_callback).
- `pick: (origin: URL, records: DNSInterceptorRecords, affinity: 4 | 6) => DNSInterceptorRecord` - Custom pick function. Default: `RoundRobin`.
- The function should return a single record from the records array.
- By default a simplified version of Round Robin is used.
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/api/MockClient.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Extends: `undici.Client`

A mock client class that implements the same api as [MockPool](MockPool.md).
A mock client class that implements the same api as [MockPool](/docs/docs/api/MockPool.md).

## `new MockClient(origin, [options])`

Expand Down
2 changes: 1 addition & 1 deletion docs/docs/api/Pool.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Extends: `undici.Dispatcher`

A pool of [Client](Client.md) instances connected to the same upstream target.
A pool of [Client](/docs/docs/api/Client.md) instances connected to the same upstream target.

Requests are not guaranteed to be dispatched in order of invocation.

Expand Down
4 changes: 2 additions & 2 deletions docs/docs/api/api-lifecycle.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Client Lifecycle

An Undici [Client](Client.md) can be best described as a state machine. The following list is a summary of the various state transitions the `Client` will go through in its lifecycle. This document also contains detailed breakdowns of each state.
An Undici [Client](/docs/docs/api/Client.md) can be best described as a state machine. The following list is a summary of the various state transitions the `Client` will go through in its lifecycle. This document also contains detailed breakdowns of each state.

> This diagram is not a perfect representation of the undici Client. Since the Client class is not actually implemented as a state-machine, actual execution may deviate slightly from what is described below. Consider this as a general resource for understanding the inner workings of the Undici client rather than some kind of formal specification.
Expand Down Expand Up @@ -28,7 +28,7 @@ stateDiagram-v2
[*] --> idle
idle --> pending : connect
idle --> destroyed : destroy/close
pending --> idle : timeout
pending --> destroyed : destroy
Expand Down
4 changes: 2 additions & 2 deletions docs/docs/best-practices/mocking-request.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 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.
Undici has its own mocking [utility](/docs/docs/api/MockAgent.md). It allow us to intercept undici HTTP requests and return mocked values instead. It can be useful for testing purposes.

Example:

Expand Down Expand Up @@ -73,7 +73,7 @@ const badRequest = await bankTransfer('1234567890', '100')
assert.deepEqual(badRequest, { message: 'bank account not found' })
```

Explore other MockAgent functionality [here](../api/MockAgent.md)
Explore other MockAgent functionality [here](/docs/docs/api/MockAgent.md)

## Debug Mock Value

Expand Down
2 changes: 1 addition & 1 deletion docs/docs/best-practices/proxy.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Connecting through a proxy is possible by:

- Using [ProxyAgent](../api/ProxyAgent.md).
- Using [ProxyAgent](/docs/docs/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
Expand Down
2 changes: 1 addition & 1 deletion docs/docsify/sidebar.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
* [MockErrors](/docs/api/MockErrors.md "Undici API - MockErrors")
* [API Lifecycle](/docs/api/api-lifecycle.md "Undici API - Lifecycle")
* [Diagnostics Channel Support](/docs/api/DiagnosticsChannel.md "Diagnostics Channel Support")
* [Debug](/docs/api/Debug.md.md "Undici API - Debugging Undici")
* [Debug](/docs/api/Debug.md "Undici API - Debugging Undici")
* [WebSocket](/docs/api/WebSocket.md "Undici API - WebSocket")
* [MIME Type Parsing](/docs/api/ContentType.md "Undici API - MIME Type Parsing")
* [CacheStorage](/docs/api/CacheStorage.md "Undici API - CacheStorage")
Expand Down
35 changes: 35 additions & 0 deletions docs/examples/proxy/fetch.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import * as http from 'node:http'
import { once } from 'node:events'
import { createProxy } from 'proxy'
import { ProxyAgent } from '../../../index.js'

const proxyServer = createProxy(http.createServer())
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' })
res.end('okay')
})

proxyServer.on('request', (req, res) => {
console.log(`Incoming request to ${req.url}`)
})

await once(proxyServer.listen(0), 'listening')
await once(server.listen(0), 'listening')

const { port: proxyPort } = proxyServer.address()
const { port } = server.address()

console.log(`Proxy listening on port ${proxyPort}`)
console.log(`Server listening on port ${port}`)
try {
// undici does a tunneling to the proxy server using CONNECT.
const agent = new ProxyAgent(`http://localhost:${proxyPort}`)
const response = await fetch(`http://localhost:${port}`, {
dispatcher: agent,
method: 'GET'
})
const data = await response.text()
console.log('Response data:', data)
} catch (e) {
console.log(e)
}
37 changes: 36 additions & 1 deletion docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,42 @@
noCompileLinks: [
'benchmarks/.*'
],
relativePath: true
relativePath: true,
markdown: {
renderer: {
// Mimic markedjs/marked behavior just modify href - https://github.com/markedjs/marked/blob/master/src/Renderer.ts#L178-L191
link(href, title, text) {
const originalHref = href;

if (href.startsWith('./')) {
// Use absolute path (e.g. ./docs/api.md => /docs/api.md) if href starts with ./ (e.g. ./api.md)
href = href.slice(1);
}

// Check for /docs/docs/ in the href and remove duplication it if present
href = href.startsWith('/docs/docs/') ? href.replace('/docs/', '/') : href;

// Check for /docs/ in the href and remove it if present
if (href.startsWith('/docs/')) {
// ignore paths /docs/api/ and /docs/best-practices/ in /docs/docs directory
if (!/^(\/docs\/(?:api|best-practices))(?:\/|$)/.test(href)) {
href = href.includes('/docs/') ? href.replace('/docs/', '/') : href;
}
}

let target = '';
if (originalHref.startsWith('http')) {
// External link - default behavior is to open in a new window
return `<a href="${originalHref}" ${title}" target="_blank" rel="noopener noreferrer">${text}</a>`;
}

title = title ? `title="${title}"` : '';
let out = `<a href="#${href}" ${title}">${text}</a>`;

return out;
},
},
},
}
</script>
<!-- Docsify v4 -->
Expand Down
2 changes: 1 addition & 1 deletion index.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export * from './types/index'
import Undici from './types/index'
export default Undici
export * from './types/index'
1 change: 1 addition & 0 deletions lib/cache/memory-cache-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ class MemoryStoreReadableStream extends Readable {
* @type {MemoryStoreValue}
*/
#value

/**
* @type {Buffer[]}
*/
Expand Down
2 changes: 1 addition & 1 deletion lib/core/request.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ class Request {
? method === 'HEAD' || method === 'GET'
: idempotent

this.blocking = blocking == null ? false : blocking
this.blocking = blocking ?? this.method !== 'HEAD'

this.reset = reset == null ? null : reset

Expand Down
8 changes: 5 additions & 3 deletions lib/dispatcher/client-h2.js
Original file line number Diff line number Diff line change
Expand Up @@ -510,16 +510,18 @@ function writeH2 (client, request) {
})

stream.once('error', function (err) {
stream.removeAllListeners('data')
abort(err)
})

stream.once('frameError', (type, code) => {
stream.removeAllListeners('data')
abort(new InformationalError(`HTTP/2: "frameError" received - type ${type}, code ${code}`))
})

// stream.on('aborted', () => {
// // TODO(HTTP/2): Support aborted
// })
stream.on('aborted', () => {
stream.removeAllListeners('data')
})

// stream.on('timeout', () => {
// // TODO(HTTP/2): Support timeout
Expand Down
Loading

0 comments on commit ad1a2e6

Please sign in to comment.