Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion lib/core/request.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ class Request {
expectContinue,
servername,
throwOnError,
maxRedirections
maxRedirections,
hints
}, handler) {
if (typeof path !== 'string') {
throw new InvalidArgumentError('path must be a string')
Expand Down Expand Up @@ -92,12 +93,18 @@ class Request {
throw new InvalidArgumentError('maxRedirections is not supported, use the redirect interceptor')
}

if (hints != null && (typeof hints !== 'number' || hints < 0)) {
throw new InvalidArgumentError('hints must be a positive number')
}

this.headersTimeout = headersTimeout

this.bodyTimeout = bodyTimeout

this.method = method

this.hints = hints ?? 0

this.abort = null

if (body == null) {
Expand Down
4 changes: 4 additions & 0 deletions lib/dispatcher/client-h1.js
Original file line number Diff line number Diff line change
Expand Up @@ -1114,6 +1114,10 @@ function writeH1 (client, request) {
socket[kBlocking] = true
}

if (socket.setPriority) {
socket.setPriority(request.hints)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What API is this?

}

let header = `${method} ${path} HTTP/1.1\r\n`

if (typeof host === 'string') {
Expand Down
2 changes: 1 addition & 1 deletion lib/dispatcher/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ const getDefaultNodeMaxHeaderSize = http &&
? () => http.maxHeaderSize
: () => { throw new InvalidArgumentError('http module not available or http.maxHeaderSize invalid') }

const noop = () => {}
const noop = () => { }

function getPipelining (client) {
return client[kPipelining] ?? client[kHTTPContext]?.defaultPipelining ?? 1
Expand Down
90 changes: 90 additions & 0 deletions test/ip-prioritization.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
'use strict'

const { test } = require('node:test')
const { Client } = require('..')
const { createServer } = require('node:http')
const { once } = require('node:events')

test('HTTP/1.1 Request Prioritization', async (t) => {
let priority = null

const server = createServer((req, res) => {
res.end('ok')
})

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

const client = new Client(`http://localhost:${server.address().port}`, {
connect: (opts, cb) => {
const socket = require('node:net').connect({
...opts,
host: opts.hostname,
port: opts.port
}, () => {
cb(null, socket)
})
socket.setPriority = (p) => {
priority = p
}
return socket
}
})

try {
await client.request({
path: '/',
method: 'GET',
hints: 42
})

// Check if priority was set
if (priority !== 42) {
throw new Error(`Expected priority 42, got ${priority}`)
}
} finally {
await client.close()
server.close()
}
})

test('HTTP/2 Connection Prioritization', async (t) => {
const net = require('node:net')
const buildConnector = require('../lib/core/connect')

let receivedHints = null
// Mock net.connect
t.mock.method(net, 'connect', (options) => {
receivedHints = options.hints

const socket = new (require('node:events').EventEmitter)()
socket.cork = () => { }
socket.uncork = () => { }
socket.destroy = () => { }
socket.ref = () => { }
socket.unref = () => { }
socket.setKeepAlive = () => socket
socket.setNoDelay = () => socket

// Simulate connection to allow callback to fire
process.nextTick(() => {
socket.emit('connect')
})

return socket
})

// Test buildConnector directly to ensure options passing
const connector = buildConnector({ hints: 123, allowH2: true })

await new Promise((resolve, reject) => {
connector({ hostname: 'localhost', host: 'localhost', protocol: 'http:', port: 3000 }, (err, socket) => {
if (err) reject(err)
else resolve(socket)
})
})

if (receivedHints !== 123) {
throw new Error(`Expected hints 123, got ${receivedHints}`)
}
})
1 change: 1 addition & 0 deletions types/connector.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ declare namespace buildConnector {
port?: number;
keepAlive?: boolean | null;
keepAliveInitialDelay?: number | null;
hints?: number | null;
}

export interface Options {
Expand Down
12 changes: 7 additions & 5 deletions types/dispatcher.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ declare class Dispatcher extends EventEmitter {
}

declare namespace Dispatcher {
export interface ComposedDispatcher extends Dispatcher {}
export interface ComposedDispatcher extends Dispatcher { }
export type Dispatch = Dispatcher['dispatch']
export type DispatcherComposeInterceptor = (dispatch: Dispatch) => Dispatch
export interface DispatchOptions {
Expand All @@ -113,6 +113,8 @@ declare namespace Dispatcher {
idempotent?: boolean;
/** Whether the response is expected to take a long time and would end up blocking the pipeline. When this is set to `true` further pipelining will be avoided on the same connection until headers have been received. Defaults to `method !== 'HEAD'`. */
blocking?: boolean;
/** The objective priority of the resource. Default: `0` */
hints?: number | null;
/** Upgrade the request. Should be used to specify the kind of upgrade i.e. `'Websocket'`. Default: `method === 'CONNECT' || null`. */
upgrade?: boolean | string | null;
/** The amount of time, in milliseconds, the parser will wait to receive the complete HTTP headers. Defaults to 300 seconds. */
Expand Down Expand Up @@ -213,10 +215,10 @@ declare namespace Dispatcher {
export type StreamFactory<TOpaque = null> = (data: StreamFactoryData<TOpaque>) => Writable

export interface DispatchController {
get aborted () : boolean
get paused () : boolean
get reason () : Error | null
abort (reason: Error): void
get aborted(): boolean
get paused(): boolean
get reason(): Error | null
abort(reason: Error): void
pause(): void
resume(): void
}
Expand Down