mirror of
https://github.com/actions/setup-java.git
synced 2026-06-23 08:19:41 +00:00
feat: add graalvm community distribution support
This commit is contained in:
committed by
GitHub
parent
849c8f0094
commit
2321ab295d
@@ -119,6 +119,7 @@ Currently, the following distributions are supported:
|
|||||||
| `dragonwell` | [Alibaba Dragonwell JDK](https://dragonwell-jdk.io/) | [`dragonwell` license](https://www.aliyun.com/product/dragonwell/)
|
| `dragonwell` | [Alibaba Dragonwell JDK](https://dragonwell-jdk.io/) | [`dragonwell` license](https://www.aliyun.com/product/dragonwell/)
|
||||||
| `sapmachine` | [SAP SapMachine JDK/JRE](https://sapmachine.io/) | [`sapmachine` license](https://github.com/SAP/SapMachine/blob/sapmachine/LICENSE)
|
| `sapmachine` | [SAP SapMachine JDK/JRE](https://sapmachine.io/) | [`sapmachine` license](https://github.com/SAP/SapMachine/blob/sapmachine/LICENSE)
|
||||||
| `graalvm` | [Oracle GraalVM](https://www.graalvm.org/) | [`graalvm` license](https://www.oracle.com/downloads/licenses/graal-free-license.html)
|
| `graalvm` | [Oracle GraalVM](https://www.graalvm.org/) | [`graalvm` license](https://www.oracle.com/downloads/licenses/graal-free-license.html)
|
||||||
|
| `graalvm-community` | [GraalVM Community](https://github.com/graalvm/graalvm-ce-builds/releases) | [`graalvm-community` license](https://github.com/oracle/graal/blob/master/LICENSE)
|
||||||
| `jetbrains` | [JetBrains Runtime](https://github.com/JetBrains/JetBrainsRuntime/) | [`jetbrains` license](https://github.com/JetBrains/JetBrainsRuntime/blob/main/LICENSE)
|
| `jetbrains` | [JetBrains Runtime](https://github.com/JetBrains/JetBrainsRuntime/) | [`jetbrains` license](https://github.com/JetBrains/JetBrainsRuntime/blob/main/LICENSE)
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
@@ -126,6 +127,7 @@ Currently, the following distributions are supported:
|
|||||||
> - AdoptOpenJDK got moved to Eclipse Temurin and won't be updated anymore. It is highly recommended to migrate workflows from `adopt` and `adopt-openj9`, to `temurin` and `semeru` respectively, to keep receiving software and security updates. See more details in the [Good-bye AdoptOpenJDK post](https://blog.adoptopenjdk.net/2021/08/goodbye-adoptopenjdk-hello-adoptium/).
|
> - AdoptOpenJDK got moved to Eclipse Temurin and won't be updated anymore. It is highly recommended to migrate workflows from `adopt` and `adopt-openj9`, to `temurin` and `semeru` respectively, to keep receiving software and security updates. See more details in the [Good-bye AdoptOpenJDK post](https://blog.adoptopenjdk.net/2021/08/goodbye-adoptopenjdk-hello-adoptium/).
|
||||||
> - For Azul Zulu OpenJDK architectures x64 and arm64 are mapped to x86 / arm with proper hw_bitness.
|
> - For Azul Zulu OpenJDK architectures x64 and arm64 are mapped to x86 / arm with proper hw_bitness.
|
||||||
> - To comply with the GraalVM Free Terms and Conditions (GFTC) license, it is recommended to use GraalVM JDK 17 version 17.0.12, as this is the only version of GraalVM JDK 17 available under the GFTC license. Additionally, it is encouraged to consider upgrading to GraalVM JDK 21, which offers the latest features and improvements.
|
> - To comply with the GraalVM Free Terms and Conditions (GFTC) license, it is recommended to use GraalVM JDK 17 version 17.0.12, as this is the only version of GraalVM JDK 17 available under the GFTC license. Additionally, it is encouraged to consider upgrading to GraalVM JDK 21, which offers the latest features and improvements.
|
||||||
|
> - GraalVM Community is available as `distribution: 'graalvm-community'` for stable JDK 17 and later releases published on GitHub.
|
||||||
|
|
||||||
**NOTE:** Oracle JDK 17 licensing varies by patch level. As shown on the [JDK 17 Archive](https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html) (versions up to 17.0.12 are under the [NFTC](https://www.oracle.com/downloads/licenses/no-fee-license.html) license) and the [JDK 17.0.13+ Archive](https://www.oracle.com/java/technologies/javase/jdk17-0-13-later-archive-downloads.html) (versions 17.0.13 and later are under the [OTN](https://www.oracle.com/downloads/licenses/javase-license1.html) license). To stay on the free NFTC license, use `distribution: 'oracle'` with `java-version: '17.0.12'` (or earlier) instead of the floating `'17'`. Alternatively, upgrade to Oracle JDK 21+, which remains under the NFTC license.
|
**NOTE:** Oracle JDK 17 licensing varies by patch level. As shown on the [JDK 17 Archive](https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html) (versions up to 17.0.12 are under the [NFTC](https://www.oracle.com/downloads/licenses/no-fee-license.html) license) and the [JDK 17.0.13+ Archive](https://www.oracle.com/java/technologies/javase/jdk17-0-13-later-archive-downloads.html) (versions 17.0.13 and later are under the [OTN](https://www.oracle.com/downloads/licenses/javase-license1.html) license). To stay on the free NFTC license, use `distribution: 'oracle'` with `java-version: '17.0.12'` (or earlier) instead of the floating `'17'`. Alternatively, upgrade to Oracle JDK 21+, which remains under the NFTC license.
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,11 @@ import * as tc from '@actions/tool-cache';
|
|||||||
import * as http from '@actions/http-client';
|
import * as http from '@actions/http-client';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import {GraalVMDistribution} from '../../src/distributions/graalvm/installer';
|
import {
|
||||||
|
GraalVMCommunityDistribution,
|
||||||
|
GraalVMDistribution
|
||||||
|
} from '../../src/distributions/graalvm/installer';
|
||||||
|
import {getJavaDistribution} from '../../src/distributions/distribution-factory';
|
||||||
import {JavaInstallerOptions} from '../../src/distributions/base-models';
|
import {JavaInstallerOptions} from '../../src/distributions/base-models';
|
||||||
import * as util from '../../src/util';
|
import * as util from '../../src/util';
|
||||||
|
|
||||||
@@ -41,6 +45,7 @@ beforeAll(() => {
|
|||||||
|
|
||||||
describe('GraalVMDistribution', () => {
|
describe('GraalVMDistribution', () => {
|
||||||
let distribution: GraalVMDistribution;
|
let distribution: GraalVMDistribution;
|
||||||
|
let communityDistribution: GraalVMCommunityDistribution;
|
||||||
let mockHttpClient: jest.Mocked<http.HttpClient>;
|
let mockHttpClient: jest.Mocked<http.HttpClient>;
|
||||||
let spyCoreError: jest.SpyInstance;
|
let spyCoreError: jest.SpyInstance;
|
||||||
|
|
||||||
@@ -55,9 +60,11 @@ describe('GraalVMDistribution', () => {
|
|||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
|
|
||||||
distribution = new GraalVMDistribution(defaultOptions);
|
distribution = new GraalVMDistribution(defaultOptions);
|
||||||
|
communityDistribution = new GraalVMCommunityDistribution(defaultOptions);
|
||||||
|
|
||||||
mockHttpClient = new http.HttpClient() as jest.Mocked<http.HttpClient>;
|
mockHttpClient = new http.HttpClient() as jest.Mocked<http.HttpClient>;
|
||||||
(distribution as any).http = mockHttpClient;
|
(distribution as any).http = mockHttpClient;
|
||||||
|
(communityDistribution as any).http = mockHttpClient;
|
||||||
|
|
||||||
(util.getDownloadArchiveExtension as jest.Mock).mockReturnValue('tar.gz');
|
(util.getDownloadArchiveExtension as jest.Mock).mockReturnValue('tar.gz');
|
||||||
|
|
||||||
@@ -242,6 +249,21 @@ describe('GraalVMDistribution', () => {
|
|||||||
path: '/cached/java/path'
|
path: '/cached/java/path'
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should use a dedicated toolcache folder for GraalVM Community', async () => {
|
||||||
|
const result = await (communityDistribution as any).downloadTool(javaRelease);
|
||||||
|
|
||||||
|
expect(tc.cacheDir).toHaveBeenCalledWith(
|
||||||
|
path.join('/tmp/extracted', 'graalvm-jdk-17.0.5'),
|
||||||
|
'Java_GraalVM_Community_jdk',
|
||||||
|
'17.0.5',
|
||||||
|
'x64'
|
||||||
|
);
|
||||||
|
expect(result).toEqual({
|
||||||
|
version: '17.0.5',
|
||||||
|
path: '/cached/java/path'
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('findPackageForDownload', () => {
|
describe('findPackageForDownload', () => {
|
||||||
@@ -948,5 +970,96 @@ describe('GraalVMDistribution', () => {
|
|||||||
configurable: true
|
configurable: true
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('GraalVMCommunityDistribution', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.spyOn(communityDistribution, 'getPlatform').mockReturnValue('linux');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should resolve an exact GraalVM Community version from GitHub releases', async () => {
|
||||||
|
mockHttpClient.getJson.mockResolvedValue({
|
||||||
|
result: [
|
||||||
|
{
|
||||||
|
draft: false,
|
||||||
|
prerelease: false,
|
||||||
|
assets: [
|
||||||
|
{
|
||||||
|
name: 'graalvm-community-jdk-21.0.2_linux-x64_bin.tar.gz',
|
||||||
|
browser_download_url:
|
||||||
|
'https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-21.0.2/graalvm-community-jdk-21.0.2_linux-x64_bin.tar.gz'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await (communityDistribution as any).findPackageForDownload(
|
||||||
|
'21.0.2'
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
url: 'https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-21.0.2/graalvm-community-jdk-21.0.2_linux-x64_bin.tar.gz',
|
||||||
|
version: '21.0.2'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should resolve the latest GraalVM Community release for a major version', async () => {
|
||||||
|
mockHttpClient.getJson.mockResolvedValue({
|
||||||
|
result: [
|
||||||
|
{
|
||||||
|
draft: false,
|
||||||
|
prerelease: false,
|
||||||
|
assets: [
|
||||||
|
{
|
||||||
|
name: 'graalvm-community-jdk-21.0.1_linux-x64_bin.tar.gz',
|
||||||
|
browser_download_url:
|
||||||
|
'https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-21.0.1/graalvm-community-jdk-21.0.1_linux-x64_bin.tar.gz'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
draft: false,
|
||||||
|
prerelease: false,
|
||||||
|
assets: [
|
||||||
|
{
|
||||||
|
name: 'graalvm-community-jdk-21.0.2_linux-x64_bin.tar.gz',
|
||||||
|
browser_download_url:
|
||||||
|
'https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-21.0.2/graalvm-community-jdk-21.0.2_linux-x64_bin.tar.gz'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await (communityDistribution as any).findPackageForDownload(
|
||||||
|
'21'
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
url: 'https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-21.0.2/graalvm-community-jdk-21.0.2_linux-x64_bin.tar.gz',
|
||||||
|
version: '21.0.2'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject GraalVM Community early access requests', async () => {
|
||||||
|
(communityDistribution as any).stable = false;
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
(communityDistribution as any).findPackageForDownload('23')
|
||||||
|
).rejects.toThrow('GraalVM Community does not provide early access builds');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('distribution factory', () => {
|
||||||
|
it('should map graalvm-community to the community installer', () => {
|
||||||
|
const community = getJavaDistribution('graalvm-community', defaultOptions);
|
||||||
|
|
||||||
|
expect(community).toBeInstanceOf(GraalVMCommunityDistribution);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Vendored
+286
-91
@@ -28337,8 +28337,6 @@ function defaultFactory (origin, opts) {
|
|||||||
|
|
||||||
class Agent extends DispatcherBase {
|
class Agent extends DispatcherBase {
|
||||||
constructor ({ factory = defaultFactory, maxRedirections = 0, connect, ...options } = {}) {
|
constructor ({ factory = defaultFactory, maxRedirections = 0, connect, ...options } = {}) {
|
||||||
super()
|
|
||||||
|
|
||||||
if (typeof factory !== 'function') {
|
if (typeof factory !== 'function') {
|
||||||
throw new InvalidArgumentError('factory must be a function.')
|
throw new InvalidArgumentError('factory must be a function.')
|
||||||
}
|
}
|
||||||
@@ -28351,6 +28349,8 @@ class Agent extends DispatcherBase {
|
|||||||
throw new InvalidArgumentError('maxRedirections must be a positive number')
|
throw new InvalidArgumentError('maxRedirections must be a positive number')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
super(options)
|
||||||
|
|
||||||
if (connect && typeof connect !== 'function') {
|
if (connect && typeof connect !== 'function') {
|
||||||
connect = { ...connect }
|
connect = { ...connect }
|
||||||
}
|
}
|
||||||
@@ -28724,6 +28724,9 @@ const EMPTY_BUF = Buffer.alloc(0)
|
|||||||
const FastBuffer = Buffer[Symbol.species]
|
const FastBuffer = Buffer[Symbol.species]
|
||||||
const addListener = util.addListener
|
const addListener = util.addListener
|
||||||
const removeAllListeners = util.removeAllListeners
|
const removeAllListeners = util.removeAllListeners
|
||||||
|
const kIdleSocketValidation = Symbol('kIdleSocketValidation')
|
||||||
|
const kIdleSocketValidationTimeout = Symbol('kIdleSocketValidationTimeout')
|
||||||
|
const kSocketUsed = Symbol('kSocketUsed')
|
||||||
|
|
||||||
let extractBody
|
let extractBody
|
||||||
|
|
||||||
@@ -28946,15 +28949,60 @@ class Parser {
|
|||||||
|
|
||||||
const offset = llhttp.llhttp_get_error_pos(this.ptr) - currentBufferPtr
|
const offset = llhttp.llhttp_get_error_pos(this.ptr) - currentBufferPtr
|
||||||
|
|
||||||
|
if (ret !== constants.ERROR.OK) {
|
||||||
|
const body = data.subarray(offset)
|
||||||
|
|
||||||
if (ret === constants.ERROR.PAUSED_UPGRADE) {
|
if (ret === constants.ERROR.PAUSED_UPGRADE) {
|
||||||
this.onUpgrade(data.slice(offset))
|
this.onUpgrade(body)
|
||||||
} else if (ret === constants.ERROR.PAUSED) {
|
} else if (ret === constants.ERROR.PAUSED) {
|
||||||
this.paused = true
|
this.paused = true
|
||||||
socket.unshift(data.slice(offset))
|
socket.unshift(body)
|
||||||
} else if (ret !== constants.ERROR.OK) {
|
} else {
|
||||||
|
throw this.createError(ret, body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
util.destroy(socket, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
finish () {
|
||||||
|
assert(currentParser === null)
|
||||||
|
assert(this.ptr != null)
|
||||||
|
assert(!this.paused)
|
||||||
|
|
||||||
|
const { llhttp } = this
|
||||||
|
|
||||||
|
let ret
|
||||||
|
|
||||||
|
try {
|
||||||
|
currentParser = this
|
||||||
|
ret = llhttp.llhttp_finish(this.ptr)
|
||||||
|
} finally {
|
||||||
|
currentParser = null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret === constants.ERROR.OK) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret === constants.ERROR.PAUSED || ret === constants.ERROR.PAUSED_UPGRADE) {
|
||||||
|
this.paused = true
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.createError(ret, EMPTY_BUF)
|
||||||
|
}
|
||||||
|
|
||||||
|
createError (ret, data) {
|
||||||
|
const { llhttp, contentLength, bytesRead } = this
|
||||||
|
|
||||||
|
if (contentLength && bytesRead !== parseInt(contentLength, 10)) {
|
||||||
|
return new ResponseContentLengthMismatchError()
|
||||||
|
}
|
||||||
|
|
||||||
const ptr = llhttp.llhttp_get_error_reason(this.ptr)
|
const ptr = llhttp.llhttp_get_error_reason(this.ptr)
|
||||||
let message = ''
|
let message = ''
|
||||||
/* istanbul ignore else: difficult to make a test case for */
|
|
||||||
if (ptr) {
|
if (ptr) {
|
||||||
const len = new Uint8Array(llhttp.memory.buffer, ptr).indexOf(0)
|
const len = new Uint8Array(llhttp.memory.buffer, ptr).indexOf(0)
|
||||||
message =
|
message =
|
||||||
@@ -28962,11 +29010,8 @@ class Parser {
|
|||||||
Buffer.from(llhttp.memory.buffer, ptr, len).toString() +
|
Buffer.from(llhttp.memory.buffer, ptr, len).toString() +
|
||||||
')'
|
')'
|
||||||
}
|
}
|
||||||
throw new HTTPParserError(message, constants.ERROR[ret], data.slice(offset))
|
|
||||||
}
|
return new HTTPParserError(message, constants.ERROR[ret], data)
|
||||||
} catch (err) {
|
|
||||||
util.destroy(socket, err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy () {
|
destroy () {
|
||||||
@@ -28996,6 +29041,11 @@ class Parser {
|
|||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (client[kRunning] === 0) {
|
||||||
|
util.destroy(socket, new SocketError('bad response', util.getSocketInfo(socket)))
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
const request = client[kQueue][client[kRunningIdx]]
|
const request = client[kQueue][client[kRunningIdx]]
|
||||||
if (!request) {
|
if (!request) {
|
||||||
return -1
|
return -1
|
||||||
@@ -29099,6 +29149,11 @@ class Parser {
|
|||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (client[kRunning] === 0) {
|
||||||
|
util.destroy(socket, new SocketError('bad response', util.getSocketInfo(socket)))
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
const request = client[kQueue][client[kRunningIdx]]
|
const request = client[kQueue][client[kRunningIdx]]
|
||||||
|
|
||||||
/* istanbul ignore next: difficult to make a test case for */
|
/* istanbul ignore next: difficult to make a test case for */
|
||||||
@@ -29272,6 +29327,7 @@ class Parser {
|
|||||||
request.onComplete(headers)
|
request.onComplete(headers)
|
||||||
|
|
||||||
client[kQueue][client[kRunningIdx]++] = null
|
client[kQueue][client[kRunningIdx]++] = null
|
||||||
|
socket[kSocketUsed] = true
|
||||||
|
|
||||||
if (socket[kWriting]) {
|
if (socket[kWriting]) {
|
||||||
assert(client[kRunning] === 0)
|
assert(client[kRunning] === 0)
|
||||||
@@ -29330,6 +29386,9 @@ async function connectH1 (client, socket) {
|
|||||||
socket[kWriting] = false
|
socket[kWriting] = false
|
||||||
socket[kReset] = false
|
socket[kReset] = false
|
||||||
socket[kBlocking] = false
|
socket[kBlocking] = false
|
||||||
|
socket[kIdleSocketValidation] = 0
|
||||||
|
socket[kIdleSocketValidationTimeout] = null
|
||||||
|
socket[kSocketUsed] = false
|
||||||
socket[kParser] = new Parser(client, socket, llhttpInstance)
|
socket[kParser] = new Parser(client, socket, llhttpInstance)
|
||||||
|
|
||||||
addListener(socket, 'error', function (err) {
|
addListener(socket, 'error', function (err) {
|
||||||
@@ -29340,8 +29399,11 @@ async function connectH1 (client, socket) {
|
|||||||
// On Mac OS, we get an ECONNRESET even if there is a full body to be forwarded
|
// On Mac OS, we get an ECONNRESET even if there is a full body to be forwarded
|
||||||
// to the user.
|
// to the user.
|
||||||
if (err.code === 'ECONNRESET' && parser.statusCode && !parser.shouldKeepAlive) {
|
if (err.code === 'ECONNRESET' && parser.statusCode && !parser.shouldKeepAlive) {
|
||||||
// We treat all incoming data so for as a valid response.
|
const parserErr = parser.finish()
|
||||||
parser.onMessageComplete()
|
if (parserErr) {
|
||||||
|
this[kError] = parserErr
|
||||||
|
this[kClient][kOnError](parserErr)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29360,8 +29422,10 @@ async function connectH1 (client, socket) {
|
|||||||
const parser = this[kParser]
|
const parser = this[kParser]
|
||||||
|
|
||||||
if (parser.statusCode && !parser.shouldKeepAlive) {
|
if (parser.statusCode && !parser.shouldKeepAlive) {
|
||||||
// We treat all incoming data so far as a valid response.
|
const parserErr = parser.finish()
|
||||||
parser.onMessageComplete()
|
if (parserErr) {
|
||||||
|
util.destroy(this, parserErr)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29371,10 +29435,11 @@ async function connectH1 (client, socket) {
|
|||||||
const client = this[kClient]
|
const client = this[kClient]
|
||||||
const parser = this[kParser]
|
const parser = this[kParser]
|
||||||
|
|
||||||
|
clearIdleSocketValidation(this)
|
||||||
|
|
||||||
if (parser) {
|
if (parser) {
|
||||||
if (!this[kError] && parser.statusCode && !parser.shouldKeepAlive) {
|
if (!this[kError] && parser.statusCode && !parser.shouldKeepAlive) {
|
||||||
// We treat all incoming data so far as a valid response.
|
this[kError] = parser.finish() || this[kError]
|
||||||
parser.onMessageComplete()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this[kParser].destroy()
|
this[kParser].destroy()
|
||||||
@@ -29437,7 +29502,7 @@ async function connectH1 (client, socket) {
|
|||||||
return socket.destroyed
|
return socket.destroyed
|
||||||
},
|
},
|
||||||
busy (request) {
|
busy (request) {
|
||||||
if (socket[kWriting] || socket[kReset] || socket[kBlocking]) {
|
if (socket[kWriting] || socket[kReset] || socket[kBlocking] || socket[kIdleSocketValidation] === 1) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29475,6 +29540,31 @@ async function connectH1 (client, socket) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clearIdleSocketValidation (socket) {
|
||||||
|
if (socket[kIdleSocketValidationTimeout]) {
|
||||||
|
clearTimeout(socket[kIdleSocketValidationTimeout])
|
||||||
|
socket[kIdleSocketValidationTimeout] = null
|
||||||
|
}
|
||||||
|
|
||||||
|
socket[kIdleSocketValidation] = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function scheduleIdleSocketValidation (client, socket) {
|
||||||
|
socket[kIdleSocketValidation] = 1
|
||||||
|
socket[kIdleSocketValidationTimeout] = setTimeout(() => {
|
||||||
|
socket[kIdleSocketValidationTimeout] = null
|
||||||
|
socket[kIdleSocketValidation] = 2
|
||||||
|
|
||||||
|
if (client[kSocket] === socket && !socket.destroyed) {
|
||||||
|
client[kResume]()
|
||||||
|
}
|
||||||
|
}, 0)
|
||||||
|
socket[kIdleSocketValidationTimeout].unref?.()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('./client.js')} client
|
||||||
|
*/
|
||||||
function resumeH1 (client) {
|
function resumeH1 (client) {
|
||||||
const socket = client[kSocket]
|
const socket = client[kSocket]
|
||||||
|
|
||||||
@@ -29489,6 +29579,32 @@ function resumeH1 (client) {
|
|||||||
socket[kNoRef] = false
|
socket[kNoRef] = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (client[kRunning] === 0 && client[kPending] > 0 && socket[kSocketUsed]) {
|
||||||
|
if (socket[kIdleSocketValidation] === 0) {
|
||||||
|
scheduleIdleSocketValidation(client, socket)
|
||||||
|
socket[kParser].readMore()
|
||||||
|
if (socket.destroyed) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (socket[kIdleSocketValidation] === 1) {
|
||||||
|
socket[kParser].readMore()
|
||||||
|
if (socket.destroyed) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client[kRunning] === 0) {
|
||||||
|
socket[kParser].readMore()
|
||||||
|
if (socket.destroyed) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (client[kSize] === 0) {
|
if (client[kSize] === 0) {
|
||||||
if (socket[kParser].timeoutType !== TIMEOUT_KEEP_ALIVE) {
|
if (socket[kParser].timeoutType !== TIMEOUT_KEEP_ALIVE) {
|
||||||
socket[kParser].setTimeout(client[kKeepAliveTimeoutValue], TIMEOUT_KEEP_ALIVE)
|
socket[kParser].setTimeout(client[kKeepAliveTimeoutValue], TIMEOUT_KEEP_ALIVE)
|
||||||
@@ -29582,6 +29698,7 @@ function writeH1 (client, request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const socket = client[kSocket]
|
const socket = client[kSocket]
|
||||||
|
clearIdleSocketValidation(socket)
|
||||||
|
|
||||||
const abort = (err) => {
|
const abort = (err) => {
|
||||||
if (request.aborted || request.completed) {
|
if (request.aborted || request.completed) {
|
||||||
@@ -30903,9 +31020,10 @@ class Client extends DispatcherBase {
|
|||||||
autoSelectFamilyAttemptTimeout,
|
autoSelectFamilyAttemptTimeout,
|
||||||
// h2
|
// h2
|
||||||
maxConcurrentStreams,
|
maxConcurrentStreams,
|
||||||
allowH2
|
allowH2,
|
||||||
|
webSocket
|
||||||
} = {}) {
|
} = {}) {
|
||||||
super()
|
super({ webSocket })
|
||||||
|
|
||||||
if (keepAlive !== undefined) {
|
if (keepAlive !== undefined) {
|
||||||
throw new InvalidArgumentError('unsupported keepAlive, use pipelining=0 instead')
|
throw new InvalidArgumentError('unsupported keepAlive, use pipelining=0 instead')
|
||||||
@@ -31438,15 +31556,24 @@ const { kDestroy, kClose, kClosed, kDestroyed, kDispatch, kInterceptors } = __nc
|
|||||||
const kOnDestroyed = Symbol('onDestroyed')
|
const kOnDestroyed = Symbol('onDestroyed')
|
||||||
const kOnClosed = Symbol('onClosed')
|
const kOnClosed = Symbol('onClosed')
|
||||||
const kInterceptedDispatch = Symbol('Intercepted Dispatch')
|
const kInterceptedDispatch = Symbol('Intercepted Dispatch')
|
||||||
|
const kWebSocketOptions = Symbol('webSocketOptions')
|
||||||
|
|
||||||
class DispatcherBase extends Dispatcher {
|
class DispatcherBase extends Dispatcher {
|
||||||
constructor () {
|
constructor (opts) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
this[kDestroyed] = false
|
this[kDestroyed] = false
|
||||||
this[kOnDestroyed] = null
|
this[kOnDestroyed] = null
|
||||||
this[kClosed] = false
|
this[kClosed] = false
|
||||||
this[kOnClosed] = []
|
this[kOnClosed] = []
|
||||||
|
this[kWebSocketOptions] = opts?.webSocket ?? {}
|
||||||
|
}
|
||||||
|
|
||||||
|
get webSocketOptions () {
|
||||||
|
return {
|
||||||
|
maxFragments: this[kWebSocketOptions].maxFragments ?? 131072,
|
||||||
|
maxPayloadSize: this[kWebSocketOptions].maxPayloadSize ?? 128 * 1024 * 1024
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get destroyed () {
|
get destroyed () {
|
||||||
@@ -32010,8 +32137,8 @@ const kRemoveClient = Symbol('remove client')
|
|||||||
const kStats = Symbol('stats')
|
const kStats = Symbol('stats')
|
||||||
|
|
||||||
class PoolBase extends DispatcherBase {
|
class PoolBase extends DispatcherBase {
|
||||||
constructor () {
|
constructor (opts) {
|
||||||
super()
|
super(opts)
|
||||||
|
|
||||||
this[kQueue] = new FixedQueue()
|
this[kQueue] = new FixedQueue()
|
||||||
this[kClients] = []
|
this[kClients] = []
|
||||||
@@ -32271,8 +32398,6 @@ class Pool extends PoolBase {
|
|||||||
allowH2,
|
allowH2,
|
||||||
...options
|
...options
|
||||||
} = {}) {
|
} = {}) {
|
||||||
super()
|
|
||||||
|
|
||||||
if (connections != null && (!Number.isFinite(connections) || connections < 0)) {
|
if (connections != null && (!Number.isFinite(connections) || connections < 0)) {
|
||||||
throw new InvalidArgumentError('invalid connections')
|
throw new InvalidArgumentError('invalid connections')
|
||||||
}
|
}
|
||||||
@@ -32297,6 +32422,8 @@ class Pool extends PoolBase {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
super(options)
|
||||||
|
|
||||||
this[kInterceptors] = options.interceptors?.Pool && Array.isArray(options.interceptors.Pool)
|
this[kInterceptors] = options.interceptors?.Pool && Array.isArray(options.interceptors.Pool)
|
||||||
? options.interceptors.Pool
|
? options.interceptors.Pool
|
||||||
: []
|
: []
|
||||||
@@ -37381,32 +37508,25 @@ function parseUnparsedAttributes (unparsedAttributes, cookieAttributeList = {})
|
|||||||
// If the attribute-name case-insensitively matches the string
|
// If the attribute-name case-insensitively matches the string
|
||||||
// "SameSite", the user agent MUST process the cookie-av as follows:
|
// "SameSite", the user agent MUST process the cookie-av as follows:
|
||||||
|
|
||||||
// 1. Let enforcement be "Default".
|
|
||||||
let enforcement = 'Default'
|
|
||||||
|
|
||||||
const attributeValueLowercase = attributeValue.toLowerCase()
|
const attributeValueLowercase = attributeValue.toLowerCase()
|
||||||
|
|
||||||
|
// 1. If cookie-av's attribute-value is a case-insensitive match for
|
||||||
|
// "None", append an attribute to the cookie-attribute-list with an
|
||||||
|
// attribute-name of "SameSite" and an attribute-value of "None".
|
||||||
|
if (attributeValueLowercase === 'none') {
|
||||||
|
cookieAttributeList.sameSite = 'None'
|
||||||
|
} else if (attributeValueLowercase === 'strict') {
|
||||||
// 2. If cookie-av's attribute-value is a case-insensitive match for
|
// 2. If cookie-av's attribute-value is a case-insensitive match for
|
||||||
// "None", set enforcement to "None".
|
// "Strict", append an attribute to the cookie-attribute-list with
|
||||||
if (attributeValueLowercase.includes('none')) {
|
// an attribute-name of "SameSite" and an attribute-value of
|
||||||
enforcement = 'None'
|
// "Strict".
|
||||||
}
|
cookieAttributeList.sameSite = 'Strict'
|
||||||
|
} else if (attributeValueLowercase === 'lax') {
|
||||||
// 3. If cookie-av's attribute-value is a case-insensitive match for
|
// 3. If cookie-av's attribute-value is a case-insensitive match for
|
||||||
// "Strict", set enforcement to "Strict".
|
// "Lax", append an attribute to the cookie-attribute-list with an
|
||||||
if (attributeValueLowercase.includes('strict')) {
|
// attribute-name of "SameSite" and an attribute-value of "Lax".
|
||||||
enforcement = 'Strict'
|
cookieAttributeList.sameSite = 'Lax'
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. If cookie-av's attribute-value is a case-insensitive match for
|
|
||||||
// "Lax", set enforcement to "Lax".
|
|
||||||
if (attributeValueLowercase.includes('lax')) {
|
|
||||||
enforcement = 'Lax'
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. Append an attribute to the cookie-attribute-list with an
|
|
||||||
// attribute-name of "SameSite" and an attribute-value of
|
|
||||||
// enforcement.
|
|
||||||
cookieAttributeList.sameSite = enforcement
|
|
||||||
} else {
|
} else {
|
||||||
cookieAttributeList.unparsed ??= []
|
cookieAttributeList.unparsed ??= []
|
||||||
|
|
||||||
@@ -50112,40 +50232,35 @@ const tail = Buffer.from([0x00, 0x00, 0xff, 0xff])
|
|||||||
const kBuffer = Symbol('kBuffer')
|
const kBuffer = Symbol('kBuffer')
|
||||||
const kLength = Symbol('kLength')
|
const kLength = Symbol('kLength')
|
||||||
|
|
||||||
// Default maximum decompressed message size: 4 MB
|
|
||||||
const kDefaultMaxDecompressedSize = 4 * 1024 * 1024
|
|
||||||
|
|
||||||
class PerMessageDeflate {
|
class PerMessageDeflate {
|
||||||
/** @type {import('node:zlib').InflateRaw} */
|
/** @type {import('node:zlib').InflateRaw} */
|
||||||
#inflate
|
#inflate
|
||||||
|
|
||||||
#options = {}
|
#options = {}
|
||||||
|
|
||||||
/** @type {boolean} */
|
#maxPayloadSize = 0
|
||||||
#aborted = false
|
|
||||||
|
|
||||||
/** @type {Function|null} */
|
|
||||||
#currentCallback = null
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Map<string, string>} extensions
|
* @param {Map<string, string>} extensions
|
||||||
*/
|
*/
|
||||||
constructor (extensions) {
|
constructor (extensions, options) {
|
||||||
this.#options.serverNoContextTakeover = extensions.has('server_no_context_takeover')
|
this.#options.serverNoContextTakeover = extensions.has('server_no_context_takeover')
|
||||||
this.#options.serverMaxWindowBits = extensions.get('server_max_window_bits')
|
this.#options.serverMaxWindowBits = extensions.get('server_max_window_bits')
|
||||||
|
|
||||||
|
this.#maxPayloadSize = options.maxPayloadSize
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decompress a compressed payload.
|
||||||
|
* @param {Buffer} chunk Compressed data
|
||||||
|
* @param {boolean} fin Final fragment flag
|
||||||
|
* @param {Function} callback Callback function
|
||||||
|
*/
|
||||||
decompress (chunk, fin, callback) {
|
decompress (chunk, fin, callback) {
|
||||||
// An endpoint uses the following algorithm to decompress a message.
|
// An endpoint uses the following algorithm to decompress a message.
|
||||||
// 1. Append 4 octets of 0x00 0x00 0xff 0xff to the tail end of the
|
// 1. Append 4 octets of 0x00 0x00 0xff 0xff to the tail end of the
|
||||||
// payload of the message.
|
// payload of the message.
|
||||||
// 2. Decompress the resulting data using DEFLATE.
|
// 2. Decompress the resulting data using DEFLATE.
|
||||||
|
|
||||||
if (this.#aborted) {
|
|
||||||
callback(new MessageSizeExceededError())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.#inflate) {
|
if (!this.#inflate) {
|
||||||
let windowBits = Z_DEFAULT_WINDOWBITS
|
let windowBits = Z_DEFAULT_WINDOWBITS
|
||||||
|
|
||||||
@@ -50168,23 +50283,12 @@ class PerMessageDeflate {
|
|||||||
this.#inflate[kLength] = 0
|
this.#inflate[kLength] = 0
|
||||||
|
|
||||||
this.#inflate.on('data', (data) => {
|
this.#inflate.on('data', (data) => {
|
||||||
if (this.#aborted) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
this.#inflate[kLength] += data.length
|
this.#inflate[kLength] += data.length
|
||||||
|
|
||||||
if (this.#inflate[kLength] > kDefaultMaxDecompressedSize) {
|
if (this.#maxPayloadSize > 0 && this.#inflate[kLength] > this.#maxPayloadSize) {
|
||||||
this.#aborted = true
|
callback(new MessageSizeExceededError())
|
||||||
this.#inflate.removeAllListeners()
|
this.#inflate.removeAllListeners()
|
||||||
this.#inflate.destroy()
|
|
||||||
this.#inflate = null
|
this.#inflate = null
|
||||||
|
|
||||||
if (this.#currentCallback) {
|
|
||||||
const cb = this.#currentCallback
|
|
||||||
this.#currentCallback = null
|
|
||||||
cb(new MessageSizeExceededError())
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50197,14 +50301,13 @@ class PerMessageDeflate {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#currentCallback = callback
|
|
||||||
this.#inflate.write(chunk)
|
this.#inflate.write(chunk)
|
||||||
if (fin) {
|
if (fin) {
|
||||||
this.#inflate.write(tail)
|
this.#inflate.write(tail)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#inflate.flush(() => {
|
this.#inflate.flush(() => {
|
||||||
if (this.#aborted || !this.#inflate) {
|
if (!this.#inflate) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50212,7 +50315,6 @@ class PerMessageDeflate {
|
|||||||
|
|
||||||
this.#inflate[kBuffer].length = 0
|
this.#inflate[kBuffer].length = 0
|
||||||
this.#inflate[kLength] = 0
|
this.#inflate[kLength] = 0
|
||||||
this.#currentCallback = null
|
|
||||||
|
|
||||||
callback(null, full)
|
callback(null, full)
|
||||||
})
|
})
|
||||||
@@ -50248,6 +50350,12 @@ const {
|
|||||||
const { WebsocketFrameSend } = __nccwpck_require__(3264)
|
const { WebsocketFrameSend } = __nccwpck_require__(3264)
|
||||||
const { closeWebSocketConnection } = __nccwpck_require__(86897)
|
const { closeWebSocketConnection } = __nccwpck_require__(86897)
|
||||||
const { PerMessageDeflate } = __nccwpck_require__(19469)
|
const { PerMessageDeflate } = __nccwpck_require__(19469)
|
||||||
|
const { MessageSizeExceededError } = __nccwpck_require__(68707)
|
||||||
|
|
||||||
|
function failWebsocketConnectionWithCode (ws, code, reason) {
|
||||||
|
closeWebSocketConnection(ws, code, reason, Buffer.byteLength(reason))
|
||||||
|
failWebsocketConnection(ws, reason)
|
||||||
|
}
|
||||||
|
|
||||||
// This code was influenced by ws released under the MIT license.
|
// This code was influenced by ws released under the MIT license.
|
||||||
// Copyright (c) 2011 Einar Otto Stangvik <einaros@gmail.com>
|
// Copyright (c) 2011 Einar Otto Stangvik <einaros@gmail.com>
|
||||||
@@ -50256,6 +50364,7 @@ const { PerMessageDeflate } = __nccwpck_require__(19469)
|
|||||||
|
|
||||||
class ByteParser extends Writable {
|
class ByteParser extends Writable {
|
||||||
#buffers = []
|
#buffers = []
|
||||||
|
#fragmentsBytes = 0
|
||||||
#byteOffset = 0
|
#byteOffset = 0
|
||||||
#loop = false
|
#loop = false
|
||||||
|
|
||||||
@@ -50267,18 +50376,27 @@ class ByteParser extends Writable {
|
|||||||
/** @type {Map<string, PerMessageDeflate>} */
|
/** @type {Map<string, PerMessageDeflate>} */
|
||||||
#extensions
|
#extensions
|
||||||
|
|
||||||
|
/** @type {number} */
|
||||||
|
#maxFragments
|
||||||
|
|
||||||
|
/** @type {number} */
|
||||||
|
#maxPayloadSize
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import('./websocket').WebSocket} ws
|
* @param {import('./websocket').WebSocket} ws
|
||||||
* @param {Map<string, string>|null} extensions
|
* @param {Map<string, string>|null} extensions
|
||||||
|
* @param {{ maxFragments?: number, maxPayloadSize?: number }} [options]
|
||||||
*/
|
*/
|
||||||
constructor (ws, extensions) {
|
constructor (ws, extensions, options = {}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
this.ws = ws
|
this.ws = ws
|
||||||
this.#extensions = extensions == null ? new Map() : extensions
|
this.#extensions = extensions == null ? new Map() : extensions
|
||||||
|
this.#maxFragments = options.maxFragments ?? 0
|
||||||
|
this.#maxPayloadSize = options.maxPayloadSize ?? 0
|
||||||
|
|
||||||
if (this.#extensions.has('permessage-deflate')) {
|
if (this.#extensions.has('permessage-deflate')) {
|
||||||
this.#extensions.set('permessage-deflate', new PerMessageDeflate(extensions))
|
this.#extensions.set('permessage-deflate', new PerMessageDeflate(extensions, options))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50294,6 +50412,19 @@ class ByteParser extends Writable {
|
|||||||
this.run(callback)
|
this.run(callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#validatePayloadLength () {
|
||||||
|
if (
|
||||||
|
this.#maxPayloadSize > 0 &&
|
||||||
|
!isControlFrame(this.#info.opcode) &&
|
||||||
|
this.#info.payloadLength + this.#fragmentsBytes > this.#maxPayloadSize
|
||||||
|
) {
|
||||||
|
failWebsocketConnectionWithCode(this.ws, 1009, 'Payload size exceeds maximum allowed size')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs whenever a new chunk is received.
|
* Runs whenever a new chunk is received.
|
||||||
* Callback is called whenever there are no more chunks buffering,
|
* Callback is called whenever there are no more chunks buffering,
|
||||||
@@ -50382,6 +50513,10 @@ class ByteParser extends Writable {
|
|||||||
if (payloadLength <= 125) {
|
if (payloadLength <= 125) {
|
||||||
this.#info.payloadLength = payloadLength
|
this.#info.payloadLength = payloadLength
|
||||||
this.#state = parserStates.READ_DATA
|
this.#state = parserStates.READ_DATA
|
||||||
|
|
||||||
|
if (!this.#validatePayloadLength()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
} else if (payloadLength === 126) {
|
} else if (payloadLength === 126) {
|
||||||
this.#state = parserStates.PAYLOADLENGTH_16
|
this.#state = parserStates.PAYLOADLENGTH_16
|
||||||
} else if (payloadLength === 127) {
|
} else if (payloadLength === 127) {
|
||||||
@@ -50406,6 +50541,10 @@ class ByteParser extends Writable {
|
|||||||
|
|
||||||
this.#info.payloadLength = buffer.readUInt16BE(0)
|
this.#info.payloadLength = buffer.readUInt16BE(0)
|
||||||
this.#state = parserStates.READ_DATA
|
this.#state = parserStates.READ_DATA
|
||||||
|
|
||||||
|
if (!this.#validatePayloadLength()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
} else if (this.#state === parserStates.PAYLOADLENGTH_64) {
|
} else if (this.#state === parserStates.PAYLOADLENGTH_64) {
|
||||||
if (this.#byteOffset < 8) {
|
if (this.#byteOffset < 8) {
|
||||||
return callback()
|
return callback()
|
||||||
@@ -50428,6 +50567,10 @@ class ByteParser extends Writable {
|
|||||||
|
|
||||||
this.#info.payloadLength = lower
|
this.#info.payloadLength = lower
|
||||||
this.#state = parserStates.READ_DATA
|
this.#state = parserStates.READ_DATA
|
||||||
|
|
||||||
|
if (!this.#validatePayloadLength()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
} else if (this.#state === parserStates.READ_DATA) {
|
} else if (this.#state === parserStates.READ_DATA) {
|
||||||
if (this.#byteOffset < this.#info.payloadLength) {
|
if (this.#byteOffset < this.#info.payloadLength) {
|
||||||
return callback()
|
return callback()
|
||||||
@@ -50440,27 +50583,43 @@ class ByteParser extends Writable {
|
|||||||
this.#state = parserStates.INFO
|
this.#state = parserStates.INFO
|
||||||
} else {
|
} else {
|
||||||
if (!this.#info.compressed) {
|
if (!this.#info.compressed) {
|
||||||
this.#fragments.push(body)
|
if (!this.writeFragments(body)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.#maxPayloadSize > 0 && this.#fragmentsBytes > this.#maxPayloadSize) {
|
||||||
|
failWebsocketConnectionWithCode(this.ws, 1009, new MessageSizeExceededError().message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// If the frame is not fragmented, a message has been received.
|
// If the frame is not fragmented, a message has been received.
|
||||||
// If the frame is fragmented, it will terminate with a fin bit set
|
// If the frame is fragmented, it will terminate with a fin bit set
|
||||||
// and an opcode of 0 (continuation), therefore we handle that when
|
// and an opcode of 0 (continuation), therefore we handle that when
|
||||||
// parsing continuation frames, not here.
|
// parsing continuation frames, not here.
|
||||||
if (!this.#info.fragmented && this.#info.fin) {
|
if (!this.#info.fragmented && this.#info.fin) {
|
||||||
const fullMessage = Buffer.concat(this.#fragments)
|
websocketMessageReceived(this.ws, this.#info.binaryType, this.consumeFragments())
|
||||||
websocketMessageReceived(this.ws, this.#info.binaryType, fullMessage)
|
|
||||||
this.#fragments.length = 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#state = parserStates.INFO
|
this.#state = parserStates.INFO
|
||||||
} else {
|
} else {
|
||||||
this.#extensions.get('permessage-deflate').decompress(body, this.#info.fin, (error, data) => {
|
this.#extensions.get('permessage-deflate').decompress(
|
||||||
|
body,
|
||||||
|
this.#info.fin,
|
||||||
|
(error, data) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
failWebsocketConnection(this.ws, error.message)
|
const code = error instanceof MessageSizeExceededError ? 1009 : 1007
|
||||||
|
failWebsocketConnectionWithCode(this.ws, code, error.message)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#fragments.push(data)
|
if (!this.writeFragments(data)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.#maxPayloadSize > 0 && this.#fragmentsBytes > this.#maxPayloadSize) {
|
||||||
|
failWebsocketConnectionWithCode(this.ws, 1009, new MessageSizeExceededError().message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.#info.fin) {
|
if (!this.#info.fin) {
|
||||||
this.#state = parserStates.INFO
|
this.#state = parserStates.INFO
|
||||||
@@ -50469,13 +50628,13 @@ class ByteParser extends Writable {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
websocketMessageReceived(this.ws, this.#info.binaryType, Buffer.concat(this.#fragments))
|
websocketMessageReceived(this.ws, this.#info.binaryType, this.consumeFragments())
|
||||||
|
|
||||||
this.#loop = true
|
this.#loop = true
|
||||||
this.#state = parserStates.INFO
|
this.#state = parserStates.INFO
|
||||||
this.#fragments.length = 0
|
|
||||||
this.run(callback)
|
this.run(callback)
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
this.#loop = false
|
this.#loop = false
|
||||||
break
|
break
|
||||||
@@ -50527,6 +50686,35 @@ class ByteParser extends Writable {
|
|||||||
return buffer
|
return buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
writeFragments (fragment) {
|
||||||
|
if (
|
||||||
|
this.#maxFragments > 0 &&
|
||||||
|
this.#fragments.length === this.#maxFragments
|
||||||
|
) {
|
||||||
|
failWebsocketConnectionWithCode(this.ws, 1008, 'Too many message fragments')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#fragmentsBytes += fragment.length
|
||||||
|
this.#fragments.push(fragment)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
consumeFragments () {
|
||||||
|
const fragments = this.#fragments
|
||||||
|
|
||||||
|
if (fragments.length === 1) {
|
||||||
|
this.#fragmentsBytes = 0
|
||||||
|
return fragments.shift()
|
||||||
|
}
|
||||||
|
|
||||||
|
const output = Buffer.concat(fragments, this.#fragmentsBytes)
|
||||||
|
this.#fragments = []
|
||||||
|
this.#fragmentsBytes = 0
|
||||||
|
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
parseCloseBody (data) {
|
parseCloseBody (data) {
|
||||||
assert(data.length !== 1)
|
assert(data.length !== 1)
|
||||||
|
|
||||||
@@ -51562,7 +51750,14 @@ class WebSocket extends EventTarget {
|
|||||||
// once this happens, the connection is open
|
// once this happens, the connection is open
|
||||||
this[kResponse] = response
|
this[kResponse] = response
|
||||||
|
|
||||||
const parser = new ByteParser(this, parsedExtensions)
|
const webSocketOptions = this[kController]?.dispatcher?.webSocketOptions
|
||||||
|
const maxFragments = webSocketOptions?.maxFragments
|
||||||
|
const maxPayloadSize = webSocketOptions?.maxPayloadSize
|
||||||
|
|
||||||
|
const parser = new ByteParser(this, parsedExtensions, {
|
||||||
|
maxFragments,
|
||||||
|
maxPayloadSize
|
||||||
|
})
|
||||||
parser.on('drain', onParserDrain)
|
parser.on('drain', onParserDrain)
|
||||||
parser.on('error', onParserError.bind(this))
|
parser.on('error', onParserError.bind(this))
|
||||||
|
|
||||||
|
|||||||
Vendored
+286
-91
@@ -54063,8 +54063,6 @@ function defaultFactory (origin, opts) {
|
|||||||
|
|
||||||
class Agent extends DispatcherBase {
|
class Agent extends DispatcherBase {
|
||||||
constructor ({ factory = defaultFactory, maxRedirections = 0, connect, ...options } = {}) {
|
constructor ({ factory = defaultFactory, maxRedirections = 0, connect, ...options } = {}) {
|
||||||
super()
|
|
||||||
|
|
||||||
if (typeof factory !== 'function') {
|
if (typeof factory !== 'function') {
|
||||||
throw new InvalidArgumentError('factory must be a function.')
|
throw new InvalidArgumentError('factory must be a function.')
|
||||||
}
|
}
|
||||||
@@ -54077,6 +54075,8 @@ class Agent extends DispatcherBase {
|
|||||||
throw new InvalidArgumentError('maxRedirections must be a positive number')
|
throw new InvalidArgumentError('maxRedirections must be a positive number')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
super(options)
|
||||||
|
|
||||||
if (connect && typeof connect !== 'function') {
|
if (connect && typeof connect !== 'function') {
|
||||||
connect = { ...connect }
|
connect = { ...connect }
|
||||||
}
|
}
|
||||||
@@ -54450,6 +54450,9 @@ const EMPTY_BUF = Buffer.alloc(0)
|
|||||||
const FastBuffer = Buffer[Symbol.species]
|
const FastBuffer = Buffer[Symbol.species]
|
||||||
const addListener = util.addListener
|
const addListener = util.addListener
|
||||||
const removeAllListeners = util.removeAllListeners
|
const removeAllListeners = util.removeAllListeners
|
||||||
|
const kIdleSocketValidation = Symbol('kIdleSocketValidation')
|
||||||
|
const kIdleSocketValidationTimeout = Symbol('kIdleSocketValidationTimeout')
|
||||||
|
const kSocketUsed = Symbol('kSocketUsed')
|
||||||
|
|
||||||
let extractBody
|
let extractBody
|
||||||
|
|
||||||
@@ -54672,15 +54675,60 @@ class Parser {
|
|||||||
|
|
||||||
const offset = llhttp.llhttp_get_error_pos(this.ptr) - currentBufferPtr
|
const offset = llhttp.llhttp_get_error_pos(this.ptr) - currentBufferPtr
|
||||||
|
|
||||||
|
if (ret !== constants.ERROR.OK) {
|
||||||
|
const body = data.subarray(offset)
|
||||||
|
|
||||||
if (ret === constants.ERROR.PAUSED_UPGRADE) {
|
if (ret === constants.ERROR.PAUSED_UPGRADE) {
|
||||||
this.onUpgrade(data.slice(offset))
|
this.onUpgrade(body)
|
||||||
} else if (ret === constants.ERROR.PAUSED) {
|
} else if (ret === constants.ERROR.PAUSED) {
|
||||||
this.paused = true
|
this.paused = true
|
||||||
socket.unshift(data.slice(offset))
|
socket.unshift(body)
|
||||||
} else if (ret !== constants.ERROR.OK) {
|
} else {
|
||||||
|
throw this.createError(ret, body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
util.destroy(socket, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
finish () {
|
||||||
|
assert(currentParser === null)
|
||||||
|
assert(this.ptr != null)
|
||||||
|
assert(!this.paused)
|
||||||
|
|
||||||
|
const { llhttp } = this
|
||||||
|
|
||||||
|
let ret
|
||||||
|
|
||||||
|
try {
|
||||||
|
currentParser = this
|
||||||
|
ret = llhttp.llhttp_finish(this.ptr)
|
||||||
|
} finally {
|
||||||
|
currentParser = null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret === constants.ERROR.OK) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret === constants.ERROR.PAUSED || ret === constants.ERROR.PAUSED_UPGRADE) {
|
||||||
|
this.paused = true
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.createError(ret, EMPTY_BUF)
|
||||||
|
}
|
||||||
|
|
||||||
|
createError (ret, data) {
|
||||||
|
const { llhttp, contentLength, bytesRead } = this
|
||||||
|
|
||||||
|
if (contentLength && bytesRead !== parseInt(contentLength, 10)) {
|
||||||
|
return new ResponseContentLengthMismatchError()
|
||||||
|
}
|
||||||
|
|
||||||
const ptr = llhttp.llhttp_get_error_reason(this.ptr)
|
const ptr = llhttp.llhttp_get_error_reason(this.ptr)
|
||||||
let message = ''
|
let message = ''
|
||||||
/* istanbul ignore else: difficult to make a test case for */
|
|
||||||
if (ptr) {
|
if (ptr) {
|
||||||
const len = new Uint8Array(llhttp.memory.buffer, ptr).indexOf(0)
|
const len = new Uint8Array(llhttp.memory.buffer, ptr).indexOf(0)
|
||||||
message =
|
message =
|
||||||
@@ -54688,11 +54736,8 @@ class Parser {
|
|||||||
Buffer.from(llhttp.memory.buffer, ptr, len).toString() +
|
Buffer.from(llhttp.memory.buffer, ptr, len).toString() +
|
||||||
')'
|
')'
|
||||||
}
|
}
|
||||||
throw new HTTPParserError(message, constants.ERROR[ret], data.slice(offset))
|
|
||||||
}
|
return new HTTPParserError(message, constants.ERROR[ret], data)
|
||||||
} catch (err) {
|
|
||||||
util.destroy(socket, err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy () {
|
destroy () {
|
||||||
@@ -54722,6 +54767,11 @@ class Parser {
|
|||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (client[kRunning] === 0) {
|
||||||
|
util.destroy(socket, new SocketError('bad response', util.getSocketInfo(socket)))
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
const request = client[kQueue][client[kRunningIdx]]
|
const request = client[kQueue][client[kRunningIdx]]
|
||||||
if (!request) {
|
if (!request) {
|
||||||
return -1
|
return -1
|
||||||
@@ -54825,6 +54875,11 @@ class Parser {
|
|||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (client[kRunning] === 0) {
|
||||||
|
util.destroy(socket, new SocketError('bad response', util.getSocketInfo(socket)))
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
const request = client[kQueue][client[kRunningIdx]]
|
const request = client[kQueue][client[kRunningIdx]]
|
||||||
|
|
||||||
/* istanbul ignore next: difficult to make a test case for */
|
/* istanbul ignore next: difficult to make a test case for */
|
||||||
@@ -54998,6 +55053,7 @@ class Parser {
|
|||||||
request.onComplete(headers)
|
request.onComplete(headers)
|
||||||
|
|
||||||
client[kQueue][client[kRunningIdx]++] = null
|
client[kQueue][client[kRunningIdx]++] = null
|
||||||
|
socket[kSocketUsed] = true
|
||||||
|
|
||||||
if (socket[kWriting]) {
|
if (socket[kWriting]) {
|
||||||
assert(client[kRunning] === 0)
|
assert(client[kRunning] === 0)
|
||||||
@@ -55056,6 +55112,9 @@ async function connectH1 (client, socket) {
|
|||||||
socket[kWriting] = false
|
socket[kWriting] = false
|
||||||
socket[kReset] = false
|
socket[kReset] = false
|
||||||
socket[kBlocking] = false
|
socket[kBlocking] = false
|
||||||
|
socket[kIdleSocketValidation] = 0
|
||||||
|
socket[kIdleSocketValidationTimeout] = null
|
||||||
|
socket[kSocketUsed] = false
|
||||||
socket[kParser] = new Parser(client, socket, llhttpInstance)
|
socket[kParser] = new Parser(client, socket, llhttpInstance)
|
||||||
|
|
||||||
addListener(socket, 'error', function (err) {
|
addListener(socket, 'error', function (err) {
|
||||||
@@ -55066,8 +55125,11 @@ async function connectH1 (client, socket) {
|
|||||||
// On Mac OS, we get an ECONNRESET even if there is a full body to be forwarded
|
// On Mac OS, we get an ECONNRESET even if there is a full body to be forwarded
|
||||||
// to the user.
|
// to the user.
|
||||||
if (err.code === 'ECONNRESET' && parser.statusCode && !parser.shouldKeepAlive) {
|
if (err.code === 'ECONNRESET' && parser.statusCode && !parser.shouldKeepAlive) {
|
||||||
// We treat all incoming data so for as a valid response.
|
const parserErr = parser.finish()
|
||||||
parser.onMessageComplete()
|
if (parserErr) {
|
||||||
|
this[kError] = parserErr
|
||||||
|
this[kClient][kOnError](parserErr)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55086,8 +55148,10 @@ async function connectH1 (client, socket) {
|
|||||||
const parser = this[kParser]
|
const parser = this[kParser]
|
||||||
|
|
||||||
if (parser.statusCode && !parser.shouldKeepAlive) {
|
if (parser.statusCode && !parser.shouldKeepAlive) {
|
||||||
// We treat all incoming data so far as a valid response.
|
const parserErr = parser.finish()
|
||||||
parser.onMessageComplete()
|
if (parserErr) {
|
||||||
|
util.destroy(this, parserErr)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55097,10 +55161,11 @@ async function connectH1 (client, socket) {
|
|||||||
const client = this[kClient]
|
const client = this[kClient]
|
||||||
const parser = this[kParser]
|
const parser = this[kParser]
|
||||||
|
|
||||||
|
clearIdleSocketValidation(this)
|
||||||
|
|
||||||
if (parser) {
|
if (parser) {
|
||||||
if (!this[kError] && parser.statusCode && !parser.shouldKeepAlive) {
|
if (!this[kError] && parser.statusCode && !parser.shouldKeepAlive) {
|
||||||
// We treat all incoming data so far as a valid response.
|
this[kError] = parser.finish() || this[kError]
|
||||||
parser.onMessageComplete()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this[kParser].destroy()
|
this[kParser].destroy()
|
||||||
@@ -55163,7 +55228,7 @@ async function connectH1 (client, socket) {
|
|||||||
return socket.destroyed
|
return socket.destroyed
|
||||||
},
|
},
|
||||||
busy (request) {
|
busy (request) {
|
||||||
if (socket[kWriting] || socket[kReset] || socket[kBlocking]) {
|
if (socket[kWriting] || socket[kReset] || socket[kBlocking] || socket[kIdleSocketValidation] === 1) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55201,6 +55266,31 @@ async function connectH1 (client, socket) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clearIdleSocketValidation (socket) {
|
||||||
|
if (socket[kIdleSocketValidationTimeout]) {
|
||||||
|
clearTimeout(socket[kIdleSocketValidationTimeout])
|
||||||
|
socket[kIdleSocketValidationTimeout] = null
|
||||||
|
}
|
||||||
|
|
||||||
|
socket[kIdleSocketValidation] = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function scheduleIdleSocketValidation (client, socket) {
|
||||||
|
socket[kIdleSocketValidation] = 1
|
||||||
|
socket[kIdleSocketValidationTimeout] = setTimeout(() => {
|
||||||
|
socket[kIdleSocketValidationTimeout] = null
|
||||||
|
socket[kIdleSocketValidation] = 2
|
||||||
|
|
||||||
|
if (client[kSocket] === socket && !socket.destroyed) {
|
||||||
|
client[kResume]()
|
||||||
|
}
|
||||||
|
}, 0)
|
||||||
|
socket[kIdleSocketValidationTimeout].unref?.()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('./client.js')} client
|
||||||
|
*/
|
||||||
function resumeH1 (client) {
|
function resumeH1 (client) {
|
||||||
const socket = client[kSocket]
|
const socket = client[kSocket]
|
||||||
|
|
||||||
@@ -55215,6 +55305,32 @@ function resumeH1 (client) {
|
|||||||
socket[kNoRef] = false
|
socket[kNoRef] = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (client[kRunning] === 0 && client[kPending] > 0 && socket[kSocketUsed]) {
|
||||||
|
if (socket[kIdleSocketValidation] === 0) {
|
||||||
|
scheduleIdleSocketValidation(client, socket)
|
||||||
|
socket[kParser].readMore()
|
||||||
|
if (socket.destroyed) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (socket[kIdleSocketValidation] === 1) {
|
||||||
|
socket[kParser].readMore()
|
||||||
|
if (socket.destroyed) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client[kRunning] === 0) {
|
||||||
|
socket[kParser].readMore()
|
||||||
|
if (socket.destroyed) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (client[kSize] === 0) {
|
if (client[kSize] === 0) {
|
||||||
if (socket[kParser].timeoutType !== TIMEOUT_KEEP_ALIVE) {
|
if (socket[kParser].timeoutType !== TIMEOUT_KEEP_ALIVE) {
|
||||||
socket[kParser].setTimeout(client[kKeepAliveTimeoutValue], TIMEOUT_KEEP_ALIVE)
|
socket[kParser].setTimeout(client[kKeepAliveTimeoutValue], TIMEOUT_KEEP_ALIVE)
|
||||||
@@ -55308,6 +55424,7 @@ function writeH1 (client, request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const socket = client[kSocket]
|
const socket = client[kSocket]
|
||||||
|
clearIdleSocketValidation(socket)
|
||||||
|
|
||||||
const abort = (err) => {
|
const abort = (err) => {
|
||||||
if (request.aborted || request.completed) {
|
if (request.aborted || request.completed) {
|
||||||
@@ -56629,9 +56746,10 @@ class Client extends DispatcherBase {
|
|||||||
autoSelectFamilyAttemptTimeout,
|
autoSelectFamilyAttemptTimeout,
|
||||||
// h2
|
// h2
|
||||||
maxConcurrentStreams,
|
maxConcurrentStreams,
|
||||||
allowH2
|
allowH2,
|
||||||
|
webSocket
|
||||||
} = {}) {
|
} = {}) {
|
||||||
super()
|
super({ webSocket })
|
||||||
|
|
||||||
if (keepAlive !== undefined) {
|
if (keepAlive !== undefined) {
|
||||||
throw new InvalidArgumentError('unsupported keepAlive, use pipelining=0 instead')
|
throw new InvalidArgumentError('unsupported keepAlive, use pipelining=0 instead')
|
||||||
@@ -57164,15 +57282,24 @@ const { kDestroy, kClose, kClosed, kDestroyed, kDispatch, kInterceptors } = __nc
|
|||||||
const kOnDestroyed = Symbol('onDestroyed')
|
const kOnDestroyed = Symbol('onDestroyed')
|
||||||
const kOnClosed = Symbol('onClosed')
|
const kOnClosed = Symbol('onClosed')
|
||||||
const kInterceptedDispatch = Symbol('Intercepted Dispatch')
|
const kInterceptedDispatch = Symbol('Intercepted Dispatch')
|
||||||
|
const kWebSocketOptions = Symbol('webSocketOptions')
|
||||||
|
|
||||||
class DispatcherBase extends Dispatcher {
|
class DispatcherBase extends Dispatcher {
|
||||||
constructor () {
|
constructor (opts) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
this[kDestroyed] = false
|
this[kDestroyed] = false
|
||||||
this[kOnDestroyed] = null
|
this[kOnDestroyed] = null
|
||||||
this[kClosed] = false
|
this[kClosed] = false
|
||||||
this[kOnClosed] = []
|
this[kOnClosed] = []
|
||||||
|
this[kWebSocketOptions] = opts?.webSocket ?? {}
|
||||||
|
}
|
||||||
|
|
||||||
|
get webSocketOptions () {
|
||||||
|
return {
|
||||||
|
maxFragments: this[kWebSocketOptions].maxFragments ?? 131072,
|
||||||
|
maxPayloadSize: this[kWebSocketOptions].maxPayloadSize ?? 128 * 1024 * 1024
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get destroyed () {
|
get destroyed () {
|
||||||
@@ -57736,8 +57863,8 @@ const kRemoveClient = Symbol('remove client')
|
|||||||
const kStats = Symbol('stats')
|
const kStats = Symbol('stats')
|
||||||
|
|
||||||
class PoolBase extends DispatcherBase {
|
class PoolBase extends DispatcherBase {
|
||||||
constructor () {
|
constructor (opts) {
|
||||||
super()
|
super(opts)
|
||||||
|
|
||||||
this[kQueue] = new FixedQueue()
|
this[kQueue] = new FixedQueue()
|
||||||
this[kClients] = []
|
this[kClients] = []
|
||||||
@@ -57997,8 +58124,6 @@ class Pool extends PoolBase {
|
|||||||
allowH2,
|
allowH2,
|
||||||
...options
|
...options
|
||||||
} = {}) {
|
} = {}) {
|
||||||
super()
|
|
||||||
|
|
||||||
if (connections != null && (!Number.isFinite(connections) || connections < 0)) {
|
if (connections != null && (!Number.isFinite(connections) || connections < 0)) {
|
||||||
throw new InvalidArgumentError('invalid connections')
|
throw new InvalidArgumentError('invalid connections')
|
||||||
}
|
}
|
||||||
@@ -58023,6 +58148,8 @@ class Pool extends PoolBase {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
super(options)
|
||||||
|
|
||||||
this[kInterceptors] = options.interceptors?.Pool && Array.isArray(options.interceptors.Pool)
|
this[kInterceptors] = options.interceptors?.Pool && Array.isArray(options.interceptors.Pool)
|
||||||
? options.interceptors.Pool
|
? options.interceptors.Pool
|
||||||
: []
|
: []
|
||||||
@@ -63107,32 +63234,25 @@ function parseUnparsedAttributes (unparsedAttributes, cookieAttributeList = {})
|
|||||||
// If the attribute-name case-insensitively matches the string
|
// If the attribute-name case-insensitively matches the string
|
||||||
// "SameSite", the user agent MUST process the cookie-av as follows:
|
// "SameSite", the user agent MUST process the cookie-av as follows:
|
||||||
|
|
||||||
// 1. Let enforcement be "Default".
|
|
||||||
let enforcement = 'Default'
|
|
||||||
|
|
||||||
const attributeValueLowercase = attributeValue.toLowerCase()
|
const attributeValueLowercase = attributeValue.toLowerCase()
|
||||||
|
|
||||||
|
// 1. If cookie-av's attribute-value is a case-insensitive match for
|
||||||
|
// "None", append an attribute to the cookie-attribute-list with an
|
||||||
|
// attribute-name of "SameSite" and an attribute-value of "None".
|
||||||
|
if (attributeValueLowercase === 'none') {
|
||||||
|
cookieAttributeList.sameSite = 'None'
|
||||||
|
} else if (attributeValueLowercase === 'strict') {
|
||||||
// 2. If cookie-av's attribute-value is a case-insensitive match for
|
// 2. If cookie-av's attribute-value is a case-insensitive match for
|
||||||
// "None", set enforcement to "None".
|
// "Strict", append an attribute to the cookie-attribute-list with
|
||||||
if (attributeValueLowercase.includes('none')) {
|
// an attribute-name of "SameSite" and an attribute-value of
|
||||||
enforcement = 'None'
|
// "Strict".
|
||||||
}
|
cookieAttributeList.sameSite = 'Strict'
|
||||||
|
} else if (attributeValueLowercase === 'lax') {
|
||||||
// 3. If cookie-av's attribute-value is a case-insensitive match for
|
// 3. If cookie-av's attribute-value is a case-insensitive match for
|
||||||
// "Strict", set enforcement to "Strict".
|
// "Lax", append an attribute to the cookie-attribute-list with an
|
||||||
if (attributeValueLowercase.includes('strict')) {
|
// attribute-name of "SameSite" and an attribute-value of "Lax".
|
||||||
enforcement = 'Strict'
|
cookieAttributeList.sameSite = 'Lax'
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. If cookie-av's attribute-value is a case-insensitive match for
|
|
||||||
// "Lax", set enforcement to "Lax".
|
|
||||||
if (attributeValueLowercase.includes('lax')) {
|
|
||||||
enforcement = 'Lax'
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. Append an attribute to the cookie-attribute-list with an
|
|
||||||
// attribute-name of "SameSite" and an attribute-value of
|
|
||||||
// enforcement.
|
|
||||||
cookieAttributeList.sameSite = enforcement
|
|
||||||
} else {
|
} else {
|
||||||
cookieAttributeList.unparsed ??= []
|
cookieAttributeList.unparsed ??= []
|
||||||
|
|
||||||
@@ -75838,40 +75958,35 @@ const tail = Buffer.from([0x00, 0x00, 0xff, 0xff])
|
|||||||
const kBuffer = Symbol('kBuffer')
|
const kBuffer = Symbol('kBuffer')
|
||||||
const kLength = Symbol('kLength')
|
const kLength = Symbol('kLength')
|
||||||
|
|
||||||
// Default maximum decompressed message size: 4 MB
|
|
||||||
const kDefaultMaxDecompressedSize = 4 * 1024 * 1024
|
|
||||||
|
|
||||||
class PerMessageDeflate {
|
class PerMessageDeflate {
|
||||||
/** @type {import('node:zlib').InflateRaw} */
|
/** @type {import('node:zlib').InflateRaw} */
|
||||||
#inflate
|
#inflate
|
||||||
|
|
||||||
#options = {}
|
#options = {}
|
||||||
|
|
||||||
/** @type {boolean} */
|
#maxPayloadSize = 0
|
||||||
#aborted = false
|
|
||||||
|
|
||||||
/** @type {Function|null} */
|
|
||||||
#currentCallback = null
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Map<string, string>} extensions
|
* @param {Map<string, string>} extensions
|
||||||
*/
|
*/
|
||||||
constructor (extensions) {
|
constructor (extensions, options) {
|
||||||
this.#options.serverNoContextTakeover = extensions.has('server_no_context_takeover')
|
this.#options.serverNoContextTakeover = extensions.has('server_no_context_takeover')
|
||||||
this.#options.serverMaxWindowBits = extensions.get('server_max_window_bits')
|
this.#options.serverMaxWindowBits = extensions.get('server_max_window_bits')
|
||||||
|
|
||||||
|
this.#maxPayloadSize = options.maxPayloadSize
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decompress a compressed payload.
|
||||||
|
* @param {Buffer} chunk Compressed data
|
||||||
|
* @param {boolean} fin Final fragment flag
|
||||||
|
* @param {Function} callback Callback function
|
||||||
|
*/
|
||||||
decompress (chunk, fin, callback) {
|
decompress (chunk, fin, callback) {
|
||||||
// An endpoint uses the following algorithm to decompress a message.
|
// An endpoint uses the following algorithm to decompress a message.
|
||||||
// 1. Append 4 octets of 0x00 0x00 0xff 0xff to the tail end of the
|
// 1. Append 4 octets of 0x00 0x00 0xff 0xff to the tail end of the
|
||||||
// payload of the message.
|
// payload of the message.
|
||||||
// 2. Decompress the resulting data using DEFLATE.
|
// 2. Decompress the resulting data using DEFLATE.
|
||||||
|
|
||||||
if (this.#aborted) {
|
|
||||||
callback(new MessageSizeExceededError())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.#inflate) {
|
if (!this.#inflate) {
|
||||||
let windowBits = Z_DEFAULT_WINDOWBITS
|
let windowBits = Z_DEFAULT_WINDOWBITS
|
||||||
|
|
||||||
@@ -75894,23 +76009,12 @@ class PerMessageDeflate {
|
|||||||
this.#inflate[kLength] = 0
|
this.#inflate[kLength] = 0
|
||||||
|
|
||||||
this.#inflate.on('data', (data) => {
|
this.#inflate.on('data', (data) => {
|
||||||
if (this.#aborted) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
this.#inflate[kLength] += data.length
|
this.#inflate[kLength] += data.length
|
||||||
|
|
||||||
if (this.#inflate[kLength] > kDefaultMaxDecompressedSize) {
|
if (this.#maxPayloadSize > 0 && this.#inflate[kLength] > this.#maxPayloadSize) {
|
||||||
this.#aborted = true
|
callback(new MessageSizeExceededError())
|
||||||
this.#inflate.removeAllListeners()
|
this.#inflate.removeAllListeners()
|
||||||
this.#inflate.destroy()
|
|
||||||
this.#inflate = null
|
this.#inflate = null
|
||||||
|
|
||||||
if (this.#currentCallback) {
|
|
||||||
const cb = this.#currentCallback
|
|
||||||
this.#currentCallback = null
|
|
||||||
cb(new MessageSizeExceededError())
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75923,14 +76027,13 @@ class PerMessageDeflate {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#currentCallback = callback
|
|
||||||
this.#inflate.write(chunk)
|
this.#inflate.write(chunk)
|
||||||
if (fin) {
|
if (fin) {
|
||||||
this.#inflate.write(tail)
|
this.#inflate.write(tail)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#inflate.flush(() => {
|
this.#inflate.flush(() => {
|
||||||
if (this.#aborted || !this.#inflate) {
|
if (!this.#inflate) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75938,7 +76041,6 @@ class PerMessageDeflate {
|
|||||||
|
|
||||||
this.#inflate[kBuffer].length = 0
|
this.#inflate[kBuffer].length = 0
|
||||||
this.#inflate[kLength] = 0
|
this.#inflate[kLength] = 0
|
||||||
this.#currentCallback = null
|
|
||||||
|
|
||||||
callback(null, full)
|
callback(null, full)
|
||||||
})
|
})
|
||||||
@@ -75974,6 +76076,12 @@ const {
|
|||||||
const { WebsocketFrameSend } = __nccwpck_require__(3264)
|
const { WebsocketFrameSend } = __nccwpck_require__(3264)
|
||||||
const { closeWebSocketConnection } = __nccwpck_require__(86897)
|
const { closeWebSocketConnection } = __nccwpck_require__(86897)
|
||||||
const { PerMessageDeflate } = __nccwpck_require__(19469)
|
const { PerMessageDeflate } = __nccwpck_require__(19469)
|
||||||
|
const { MessageSizeExceededError } = __nccwpck_require__(68707)
|
||||||
|
|
||||||
|
function failWebsocketConnectionWithCode (ws, code, reason) {
|
||||||
|
closeWebSocketConnection(ws, code, reason, Buffer.byteLength(reason))
|
||||||
|
failWebsocketConnection(ws, reason)
|
||||||
|
}
|
||||||
|
|
||||||
// This code was influenced by ws released under the MIT license.
|
// This code was influenced by ws released under the MIT license.
|
||||||
// Copyright (c) 2011 Einar Otto Stangvik <einaros@gmail.com>
|
// Copyright (c) 2011 Einar Otto Stangvik <einaros@gmail.com>
|
||||||
@@ -75982,6 +76090,7 @@ const { PerMessageDeflate } = __nccwpck_require__(19469)
|
|||||||
|
|
||||||
class ByteParser extends Writable {
|
class ByteParser extends Writable {
|
||||||
#buffers = []
|
#buffers = []
|
||||||
|
#fragmentsBytes = 0
|
||||||
#byteOffset = 0
|
#byteOffset = 0
|
||||||
#loop = false
|
#loop = false
|
||||||
|
|
||||||
@@ -75993,18 +76102,27 @@ class ByteParser extends Writable {
|
|||||||
/** @type {Map<string, PerMessageDeflate>} */
|
/** @type {Map<string, PerMessageDeflate>} */
|
||||||
#extensions
|
#extensions
|
||||||
|
|
||||||
|
/** @type {number} */
|
||||||
|
#maxFragments
|
||||||
|
|
||||||
|
/** @type {number} */
|
||||||
|
#maxPayloadSize
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import('./websocket').WebSocket} ws
|
* @param {import('./websocket').WebSocket} ws
|
||||||
* @param {Map<string, string>|null} extensions
|
* @param {Map<string, string>|null} extensions
|
||||||
|
* @param {{ maxFragments?: number, maxPayloadSize?: number }} [options]
|
||||||
*/
|
*/
|
||||||
constructor (ws, extensions) {
|
constructor (ws, extensions, options = {}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
this.ws = ws
|
this.ws = ws
|
||||||
this.#extensions = extensions == null ? new Map() : extensions
|
this.#extensions = extensions == null ? new Map() : extensions
|
||||||
|
this.#maxFragments = options.maxFragments ?? 0
|
||||||
|
this.#maxPayloadSize = options.maxPayloadSize ?? 0
|
||||||
|
|
||||||
if (this.#extensions.has('permessage-deflate')) {
|
if (this.#extensions.has('permessage-deflate')) {
|
||||||
this.#extensions.set('permessage-deflate', new PerMessageDeflate(extensions))
|
this.#extensions.set('permessage-deflate', new PerMessageDeflate(extensions, options))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76020,6 +76138,19 @@ class ByteParser extends Writable {
|
|||||||
this.run(callback)
|
this.run(callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#validatePayloadLength () {
|
||||||
|
if (
|
||||||
|
this.#maxPayloadSize > 0 &&
|
||||||
|
!isControlFrame(this.#info.opcode) &&
|
||||||
|
this.#info.payloadLength + this.#fragmentsBytes > this.#maxPayloadSize
|
||||||
|
) {
|
||||||
|
failWebsocketConnectionWithCode(this.ws, 1009, 'Payload size exceeds maximum allowed size')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs whenever a new chunk is received.
|
* Runs whenever a new chunk is received.
|
||||||
* Callback is called whenever there are no more chunks buffering,
|
* Callback is called whenever there are no more chunks buffering,
|
||||||
@@ -76108,6 +76239,10 @@ class ByteParser extends Writable {
|
|||||||
if (payloadLength <= 125) {
|
if (payloadLength <= 125) {
|
||||||
this.#info.payloadLength = payloadLength
|
this.#info.payloadLength = payloadLength
|
||||||
this.#state = parserStates.READ_DATA
|
this.#state = parserStates.READ_DATA
|
||||||
|
|
||||||
|
if (!this.#validatePayloadLength()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
} else if (payloadLength === 126) {
|
} else if (payloadLength === 126) {
|
||||||
this.#state = parserStates.PAYLOADLENGTH_16
|
this.#state = parserStates.PAYLOADLENGTH_16
|
||||||
} else if (payloadLength === 127) {
|
} else if (payloadLength === 127) {
|
||||||
@@ -76132,6 +76267,10 @@ class ByteParser extends Writable {
|
|||||||
|
|
||||||
this.#info.payloadLength = buffer.readUInt16BE(0)
|
this.#info.payloadLength = buffer.readUInt16BE(0)
|
||||||
this.#state = parserStates.READ_DATA
|
this.#state = parserStates.READ_DATA
|
||||||
|
|
||||||
|
if (!this.#validatePayloadLength()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
} else if (this.#state === parserStates.PAYLOADLENGTH_64) {
|
} else if (this.#state === parserStates.PAYLOADLENGTH_64) {
|
||||||
if (this.#byteOffset < 8) {
|
if (this.#byteOffset < 8) {
|
||||||
return callback()
|
return callback()
|
||||||
@@ -76154,6 +76293,10 @@ class ByteParser extends Writable {
|
|||||||
|
|
||||||
this.#info.payloadLength = lower
|
this.#info.payloadLength = lower
|
||||||
this.#state = parserStates.READ_DATA
|
this.#state = parserStates.READ_DATA
|
||||||
|
|
||||||
|
if (!this.#validatePayloadLength()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
} else if (this.#state === parserStates.READ_DATA) {
|
} else if (this.#state === parserStates.READ_DATA) {
|
||||||
if (this.#byteOffset < this.#info.payloadLength) {
|
if (this.#byteOffset < this.#info.payloadLength) {
|
||||||
return callback()
|
return callback()
|
||||||
@@ -76166,27 +76309,43 @@ class ByteParser extends Writable {
|
|||||||
this.#state = parserStates.INFO
|
this.#state = parserStates.INFO
|
||||||
} else {
|
} else {
|
||||||
if (!this.#info.compressed) {
|
if (!this.#info.compressed) {
|
||||||
this.#fragments.push(body)
|
if (!this.writeFragments(body)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.#maxPayloadSize > 0 && this.#fragmentsBytes > this.#maxPayloadSize) {
|
||||||
|
failWebsocketConnectionWithCode(this.ws, 1009, new MessageSizeExceededError().message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// If the frame is not fragmented, a message has been received.
|
// If the frame is not fragmented, a message has been received.
|
||||||
// If the frame is fragmented, it will terminate with a fin bit set
|
// If the frame is fragmented, it will terminate with a fin bit set
|
||||||
// and an opcode of 0 (continuation), therefore we handle that when
|
// and an opcode of 0 (continuation), therefore we handle that when
|
||||||
// parsing continuation frames, not here.
|
// parsing continuation frames, not here.
|
||||||
if (!this.#info.fragmented && this.#info.fin) {
|
if (!this.#info.fragmented && this.#info.fin) {
|
||||||
const fullMessage = Buffer.concat(this.#fragments)
|
websocketMessageReceived(this.ws, this.#info.binaryType, this.consumeFragments())
|
||||||
websocketMessageReceived(this.ws, this.#info.binaryType, fullMessage)
|
|
||||||
this.#fragments.length = 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#state = parserStates.INFO
|
this.#state = parserStates.INFO
|
||||||
} else {
|
} else {
|
||||||
this.#extensions.get('permessage-deflate').decompress(body, this.#info.fin, (error, data) => {
|
this.#extensions.get('permessage-deflate').decompress(
|
||||||
|
body,
|
||||||
|
this.#info.fin,
|
||||||
|
(error, data) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
failWebsocketConnection(this.ws, error.message)
|
const code = error instanceof MessageSizeExceededError ? 1009 : 1007
|
||||||
|
failWebsocketConnectionWithCode(this.ws, code, error.message)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#fragments.push(data)
|
if (!this.writeFragments(data)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.#maxPayloadSize > 0 && this.#fragmentsBytes > this.#maxPayloadSize) {
|
||||||
|
failWebsocketConnectionWithCode(this.ws, 1009, new MessageSizeExceededError().message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.#info.fin) {
|
if (!this.#info.fin) {
|
||||||
this.#state = parserStates.INFO
|
this.#state = parserStates.INFO
|
||||||
@@ -76195,13 +76354,13 @@ class ByteParser extends Writable {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
websocketMessageReceived(this.ws, this.#info.binaryType, Buffer.concat(this.#fragments))
|
websocketMessageReceived(this.ws, this.#info.binaryType, this.consumeFragments())
|
||||||
|
|
||||||
this.#loop = true
|
this.#loop = true
|
||||||
this.#state = parserStates.INFO
|
this.#state = parserStates.INFO
|
||||||
this.#fragments.length = 0
|
|
||||||
this.run(callback)
|
this.run(callback)
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
this.#loop = false
|
this.#loop = false
|
||||||
break
|
break
|
||||||
@@ -76253,6 +76412,35 @@ class ByteParser extends Writable {
|
|||||||
return buffer
|
return buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
writeFragments (fragment) {
|
||||||
|
if (
|
||||||
|
this.#maxFragments > 0 &&
|
||||||
|
this.#fragments.length === this.#maxFragments
|
||||||
|
) {
|
||||||
|
failWebsocketConnectionWithCode(this.ws, 1008, 'Too many message fragments')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#fragmentsBytes += fragment.length
|
||||||
|
this.#fragments.push(fragment)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
consumeFragments () {
|
||||||
|
const fragments = this.#fragments
|
||||||
|
|
||||||
|
if (fragments.length === 1) {
|
||||||
|
this.#fragmentsBytes = 0
|
||||||
|
return fragments.shift()
|
||||||
|
}
|
||||||
|
|
||||||
|
const output = Buffer.concat(fragments, this.#fragmentsBytes)
|
||||||
|
this.#fragments = []
|
||||||
|
this.#fragmentsBytes = 0
|
||||||
|
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
parseCloseBody (data) {
|
parseCloseBody (data) {
|
||||||
assert(data.length !== 1)
|
assert(data.length !== 1)
|
||||||
|
|
||||||
@@ -77288,7 +77476,14 @@ class WebSocket extends EventTarget {
|
|||||||
// once this happens, the connection is open
|
// once this happens, the connection is open
|
||||||
this[kResponse] = response
|
this[kResponse] = response
|
||||||
|
|
||||||
const parser = new ByteParser(this, parsedExtensions)
|
const webSocketOptions = this[kController]?.dispatcher?.webSocketOptions
|
||||||
|
const maxFragments = webSocketOptions?.maxFragments
|
||||||
|
const maxPayloadSize = webSocketOptions?.maxPayloadSize
|
||||||
|
|
||||||
|
const parser = new ByteParser(this, parsedExtensions, {
|
||||||
|
maxFragments,
|
||||||
|
maxPayloadSize
|
||||||
|
})
|
||||||
parser.on('drain', onParserDrain)
|
parser.on('drain', onParserDrain)
|
||||||
parser.on('error', onParserError.bind(this))
|
parser.on('error', onParserError.bind(this))
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
- [Alibaba Dragonwell](#Alibaba-Dragonwell)
|
- [Alibaba Dragonwell](#Alibaba-Dragonwell)
|
||||||
- [SapMachine](#SapMachine)
|
- [SapMachine](#SapMachine)
|
||||||
- [GraalVM](#GraalVM)
|
- [GraalVM](#GraalVM)
|
||||||
|
- [GraalVM Community](#GraalVM-Community)
|
||||||
- [JetBrains](#JetBrains)
|
- [JetBrains](#JetBrains)
|
||||||
- [Installing custom Java package type](#Installing-custom-Java-package-type)
|
- [Installing custom Java package type](#Installing-custom-Java-package-type)
|
||||||
- [Installing custom Java architecture](#Installing-custom-Java-architecture)
|
- [Installing custom Java architecture](#Installing-custom-Java-architecture)
|
||||||
@@ -172,6 +173,21 @@ steps:
|
|||||||
native-image -cp java HelloWorldApp
|
native-image -cp java HelloWorldApp
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### GraalVM Community
|
||||||
|
**NOTE:** GraalVM Community is available for stable JDK 17 and later releases.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
- uses: actions/setup-java@v5
|
||||||
|
with:
|
||||||
|
distribution: 'graalvm-community'
|
||||||
|
java-version: '21'
|
||||||
|
- run: |
|
||||||
|
java -cp java HelloWorldApp
|
||||||
|
native-image -cp java HelloWorldApp
|
||||||
|
```
|
||||||
|
|
||||||
### JetBrains
|
### JetBrains
|
||||||
|
|
||||||
**NOTE:** JetBrains is only available for LTS versions on 11 or later (11, 17, 21, etc.).
|
**NOTE:** JetBrains is only available for LTS versions on 11 or later (11, 17, 21, etc.).
|
||||||
|
|||||||
@@ -11,7 +11,10 @@ import {CorrettoDistribution} from './corretto/installer';
|
|||||||
import {OracleDistribution} from './oracle/installer';
|
import {OracleDistribution} from './oracle/installer';
|
||||||
import {DragonwellDistribution} from './dragonwell/installer';
|
import {DragonwellDistribution} from './dragonwell/installer';
|
||||||
import {SapMachineDistribution} from './sapmachine/installer';
|
import {SapMachineDistribution} from './sapmachine/installer';
|
||||||
import {GraalVMDistribution} from './graalvm/installer';
|
import {
|
||||||
|
GraalVMCommunityDistribution,
|
||||||
|
GraalVMDistribution
|
||||||
|
} from './graalvm/installer';
|
||||||
import {JetBrainsDistribution} from './jetbrains/installer';
|
import {JetBrainsDistribution} from './jetbrains/installer';
|
||||||
|
|
||||||
enum JavaDistribution {
|
enum JavaDistribution {
|
||||||
@@ -29,6 +32,7 @@ enum JavaDistribution {
|
|||||||
Dragonwell = 'dragonwell',
|
Dragonwell = 'dragonwell',
|
||||||
SapMachine = 'sapmachine',
|
SapMachine = 'sapmachine',
|
||||||
GraalVM = 'graalvm',
|
GraalVM = 'graalvm',
|
||||||
|
GraalVMCommunity = 'graalvm-community',
|
||||||
JetBrains = 'jetbrains'
|
JetBrains = 'jetbrains'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,6 +78,8 @@ export function getJavaDistribution(
|
|||||||
return new SapMachineDistribution(installerOptions);
|
return new SapMachineDistribution(installerOptions);
|
||||||
case JavaDistribution.GraalVM:
|
case JavaDistribution.GraalVM:
|
||||||
return new GraalVMDistribution(installerOptions);
|
return new GraalVMDistribution(installerOptions);
|
||||||
|
case JavaDistribution.GraalVMCommunity:
|
||||||
|
return new GraalVMCommunityDistribution(installerOptions);
|
||||||
case JavaDistribution.JetBrains:
|
case JavaDistribution.JetBrains:
|
||||||
return new JetBrainsDistribution(installerOptions);
|
return new JetBrainsDistribution(installerOptions);
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import * as core from '@actions/core';
|
|||||||
import * as tc from '@actions/tool-cache';
|
import * as tc from '@actions/tool-cache';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
import semver from 'semver';
|
||||||
import {JavaBase} from '../base-installer';
|
import {JavaBase} from '../base-installer';
|
||||||
import {HttpCodes} from '@actions/http-client';
|
import {HttpCodes} from '@actions/http-client';
|
||||||
import {GraalVMEAVersion} from './models';
|
import {GraalVMEAVersion} from './models';
|
||||||
@@ -11,14 +12,24 @@ import {
|
|||||||
JavaInstallerResults
|
JavaInstallerResults
|
||||||
} from '../base-models';
|
} from '../base-models';
|
||||||
import {
|
import {
|
||||||
|
convertVersionToSemver,
|
||||||
extractJdkFile,
|
extractJdkFile,
|
||||||
getDownloadArchiveExtension,
|
getDownloadArchiveExtension,
|
||||||
getGitHubHttpHeaders,
|
getGitHubHttpHeaders,
|
||||||
renameWinArchive
|
getNextPageUrlFromLinkHeader,
|
||||||
|
isVersionSatisfies,
|
||||||
|
MAX_PAGINATION_PAGES,
|
||||||
|
renameWinArchive,
|
||||||
|
validatePaginationUrl
|
||||||
} from '../../util';
|
} from '../../util';
|
||||||
|
|
||||||
const GRAALVM_DL_BASE = 'https://download.oracle.com/graalvm';
|
const GRAALVM_DL_BASE = 'https://download.oracle.com/graalvm';
|
||||||
const GRAALVM_DOWNLOAD_URL = 'https://www.graalvm.org/downloads/';
|
const GRAALVM_DOWNLOAD_URL = 'https://www.graalvm.org/downloads/';
|
||||||
|
const GRAALVM_COMMUNITY_RELEASES_URL =
|
||||||
|
'https://api.github.com/repos/graalvm/graalvm-ce-builds/releases?per_page=100';
|
||||||
|
const GRAALVM_COMMUNITY_RELEASES_PAGE_ORIGIN = 'https://api.github.com';
|
||||||
|
const GRAALVM_COMMUNITY_DOWNLOAD_URL =
|
||||||
|
'https://github.com/graalvm/graalvm-ce-builds/releases';
|
||||||
const IS_WINDOWS = process.platform === 'win32';
|
const IS_WINDOWS = process.platform === 'win32';
|
||||||
const GRAALVM_PLATFORM = IS_WINDOWS ? 'windows' : process.platform;
|
const GRAALVM_PLATFORM = IS_WINDOWS ? 'windows' : process.platform;
|
||||||
const GRAALVM_MIN_VERSION = 17;
|
const GRAALVM_MIN_VERSION = 17;
|
||||||
@@ -26,9 +37,23 @@ const SUPPORTED_ARCHITECTURES = ['x64', 'aarch64'] as const;
|
|||||||
type SupportedArchitecture = (typeof SUPPORTED_ARCHITECTURES)[number];
|
type SupportedArchitecture = (typeof SUPPORTED_ARCHITECTURES)[number];
|
||||||
type OsVersions = 'linux' | 'macos' | 'windows';
|
type OsVersions = 'linux' | 'macos' | 'windows';
|
||||||
|
|
||||||
|
interface GraalVMCommunityAsset {
|
||||||
|
name: string;
|
||||||
|
browser_download_url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GraalVMCommunityRelease {
|
||||||
|
draft: boolean;
|
||||||
|
prerelease: boolean;
|
||||||
|
assets: GraalVMCommunityAsset[];
|
||||||
|
}
|
||||||
|
|
||||||
export class GraalVMDistribution extends JavaBase {
|
export class GraalVMDistribution extends JavaBase {
|
||||||
constructor(installerOptions: JavaInstallerOptions) {
|
constructor(
|
||||||
super('GraalVM', installerOptions);
|
installerOptions: JavaInstallerOptions,
|
||||||
|
distributionName = 'GraalVM'
|
||||||
|
) {
|
||||||
|
super(distributionName, installerOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async downloadTool(
|
protected async downloadTool(
|
||||||
@@ -85,40 +110,14 @@ export class GraalVMDistribution extends JavaBase {
|
|||||||
protected async findPackageForDownload(
|
protected async findPackageForDownload(
|
||||||
range: string
|
range: string
|
||||||
): Promise<JavaDownloadRelease> {
|
): Promise<JavaDownloadRelease> {
|
||||||
// Add input validation
|
this.validateVersionRange(range);
|
||||||
if (!range || typeof range !== 'string') {
|
const arch = this.getSupportedArchitecture();
|
||||||
throw new Error('Version range is required and must be a string');
|
|
||||||
}
|
|
||||||
|
|
||||||
const arch = this.distributionArchitecture();
|
|
||||||
if (!SUPPORTED_ARCHITECTURES.includes(arch as SupportedArchitecture)) {
|
|
||||||
throw new Error(
|
|
||||||
`Unsupported architecture: ${this.architecture}. Supported architectures are: ${SUPPORTED_ARCHITECTURES.join(', ')}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.stable) {
|
if (!this.stable) {
|
||||||
return this.findEABuildDownloadUrl(`${range}-ea`);
|
return this.findEABuildDownloadUrl(`${range}-ea`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.packageType !== 'jdk') {
|
const {platform, extension, major} = this.validateStableBuildRequest(range);
|
||||||
throw new Error('GraalVM provides only the `jdk` package type');
|
|
||||||
}
|
|
||||||
|
|
||||||
const platform = this.getPlatform();
|
|
||||||
const extension = getDownloadArchiveExtension();
|
|
||||||
const major = range.includes('.') ? range.split('.')[0] : range;
|
|
||||||
const majorVersion = parseInt(major);
|
|
||||||
|
|
||||||
if (isNaN(majorVersion)) {
|
|
||||||
throw new Error(`Invalid version format: ${range}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (majorVersion < GRAALVM_MIN_VERSION) {
|
|
||||||
throw new Error(
|
|
||||||
`GraalVM is only supported for JDK ${GRAALVM_MIN_VERSION} and later. Requested version: ${major}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const fileUrl = this.constructFileUrl(
|
const fileUrl = this.constructFileUrl(
|
||||||
range,
|
range,
|
||||||
@@ -134,6 +133,60 @@ export class GraalVMDistribution extends JavaBase {
|
|||||||
return {url: fileUrl, version: range};
|
return {url: fileUrl, version: range};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected validateVersionRange(range: string): void {
|
||||||
|
if (!range || typeof range !== 'string') {
|
||||||
|
throw new Error('Version range is required and must be a string');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getSupportedArchitecture(): SupportedArchitecture {
|
||||||
|
const arch = this.distributionArchitecture();
|
||||||
|
if (!SUPPORTED_ARCHITECTURES.includes(arch as SupportedArchitecture)) {
|
||||||
|
throw new Error(
|
||||||
|
`Unsupported architecture: ${this.architecture}. Supported architectures are: ${SUPPORTED_ARCHITECTURES.join(', ')}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return arch as SupportedArchitecture;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected validateStableBuildRequest(range: string): {
|
||||||
|
arch: SupportedArchitecture;
|
||||||
|
platform: OsVersions;
|
||||||
|
extension: string;
|
||||||
|
major: string;
|
||||||
|
} {
|
||||||
|
const arch = this.getSupportedArchitecture();
|
||||||
|
|
||||||
|
if (this.packageType !== 'jdk') {
|
||||||
|
throw new Error(
|
||||||
|
`${this.distribution} provides only the \`jdk\` package type`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const platform = this.getPlatform();
|
||||||
|
const extension = getDownloadArchiveExtension();
|
||||||
|
const major = range.includes('.') ? range.split('.')[0] : range;
|
||||||
|
const majorVersion = parseInt(major);
|
||||||
|
|
||||||
|
if (isNaN(majorVersion)) {
|
||||||
|
throw new Error(`Invalid version format: ${range}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (majorVersion < GRAALVM_MIN_VERSION) {
|
||||||
|
throw new Error(
|
||||||
|
`${this.distribution} is only supported for JDK ${GRAALVM_MIN_VERSION} and later. Requested version: ${major}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
arch,
|
||||||
|
platform,
|
||||||
|
extension,
|
||||||
|
major
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private constructFileUrl(
|
private constructFileUrl(
|
||||||
range: string,
|
range: string,
|
||||||
major: string,
|
major: string,
|
||||||
@@ -280,3 +333,140 @@ export class GraalVMDistribution extends JavaBase {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class GraalVMCommunityDistribution extends GraalVMDistribution {
|
||||||
|
constructor(installerOptions: JavaInstallerOptions) {
|
||||||
|
super(installerOptions, 'GraalVM Community');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get toolcacheFolderName(): string {
|
||||||
|
return `Java_GraalVM_Community_${this.packageType}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async findPackageForDownload(
|
||||||
|
range: string
|
||||||
|
): Promise<JavaDownloadRelease> {
|
||||||
|
this.validateVersionRange(range);
|
||||||
|
|
||||||
|
if (!this.stable) {
|
||||||
|
throw new Error('GraalVM Community does not provide early access builds');
|
||||||
|
}
|
||||||
|
|
||||||
|
const {arch, platform, extension} = this.validateStableBuildRequest(range);
|
||||||
|
const availableVersions = await this.getAvailableVersionsForPlatform(
|
||||||
|
platform,
|
||||||
|
arch,
|
||||||
|
extension
|
||||||
|
);
|
||||||
|
|
||||||
|
const satisfiedVersion = availableVersions
|
||||||
|
.filter(item => isVersionSatisfies(range, item.version))
|
||||||
|
.sort((a, b) => -semver.compareBuild(a.version, b.version))[0];
|
||||||
|
|
||||||
|
if (!satisfiedVersion) {
|
||||||
|
const error = this.createVersionNotFoundError(
|
||||||
|
range,
|
||||||
|
availableVersions.map(item => item.version),
|
||||||
|
`Platform: ${platform}`
|
||||||
|
);
|
||||||
|
error.message += `\nPlease check if this version is available at ${GRAALVM_COMMUNITY_DOWNLOAD_URL}.`;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return satisfiedVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getAvailableVersionsForPlatform(
|
||||||
|
platform: OsVersions,
|
||||||
|
arch: SupportedArchitecture,
|
||||||
|
extension: string
|
||||||
|
): Promise<JavaDownloadRelease[]> {
|
||||||
|
const headers = getGitHubHttpHeaders();
|
||||||
|
const availableVersions = new Map<string, JavaDownloadRelease>();
|
||||||
|
let releasesUrl: string | null = GRAALVM_COMMUNITY_RELEASES_URL;
|
||||||
|
let pageCount = 0;
|
||||||
|
|
||||||
|
while (releasesUrl) {
|
||||||
|
pageCount++;
|
||||||
|
const response = await this.http.getJson<GraalVMCommunityRelease[]>(
|
||||||
|
releasesUrl,
|
||||||
|
headers
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const release of response.result ?? []) {
|
||||||
|
if (release.draft || release.prerelease) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const asset of release.assets ?? []) {
|
||||||
|
const releaseForPlatform = this.toCommunityReleaseForPlatform(
|
||||||
|
asset,
|
||||||
|
platform,
|
||||||
|
arch,
|
||||||
|
extension
|
||||||
|
);
|
||||||
|
if (releaseForPlatform) {
|
||||||
|
availableVersions.set(
|
||||||
|
releaseForPlatform.version,
|
||||||
|
releaseForPlatform
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextUrl = getNextPageUrlFromLinkHeader(response.headers);
|
||||||
|
if (
|
||||||
|
nextUrl &&
|
||||||
|
!validatePaginationUrl(nextUrl, GRAALVM_COMMUNITY_RELEASES_PAGE_ORIGIN)
|
||||||
|
) {
|
||||||
|
core.warning(
|
||||||
|
`Ignoring pagination link with unexpected origin: ${nextUrl}`
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
releasesUrl = nextUrl;
|
||||||
|
|
||||||
|
if (!response.result || response.result.length === 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pageCount >= MAX_PAGINATION_PAGES) {
|
||||||
|
core.warning(
|
||||||
|
`Reached pagination safeguard limit (${MAX_PAGINATION_PAGES} pages) while listing GraalVM Community releases.`
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...availableVersions.values()];
|
||||||
|
}
|
||||||
|
|
||||||
|
private toCommunityReleaseForPlatform(
|
||||||
|
asset: GraalVMCommunityAsset,
|
||||||
|
platform: OsVersions,
|
||||||
|
arch: SupportedArchitecture,
|
||||||
|
extension: string
|
||||||
|
): JavaDownloadRelease | null {
|
||||||
|
const match = asset.name.match(
|
||||||
|
/^graalvm-community-jdk-(?<version>\d+(?:\.\d+)+)_(?<platform>linux|macos|windows)-(?<arch>x64|aarch64)_bin\.(?<extension>tar\.gz|zip)$/
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!match?.groups) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
match.groups.platform !== platform ||
|
||||||
|
match.groups.arch !== arch ||
|
||||||
|
match.groups.extension !== extension
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
version: convertVersionToSemver(match.groups.version),
|
||||||
|
url: asset.browser_download_url
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user