Files
peardock/build/shims/node-http.cjs
T
Raven Scott ab99136cb6
Release rolling / release (push) Has been cancelled
Fix Bare HTTP shim treating registry URL paths as Unix sockets.
HTTPSAgent extends the patched Agent; mistaking path for socketPath made every registry digest check fail with ENOENT and broke image update indicators.
2026-07-15 15:55:29 -04:00

152 lines
4.6 KiB
JavaScript

/**
* Node-compatible http for Bare — adds Unix socket support.
*
* bare-http1's Agent always uses bare-tcp and ignores `socketPath`.
* dockerode/docker-modem requires:
* http.request({ socketPath: '/var/run/docker.sock', path: '/version', ... })
*
* Without this, requests go to TCP localhost:80 → reverse-proxy 301 to HTTPS.
*
* Important: only `socketPath` selects a Unix socket. HTTP option `path` is the
* request URL path (/v2/…, /containers/…), not a filesystem socket. Using `path`
* as a socket breaks bare-https (HTTPSAgent extends this Agent) with ENOENT.
*
* Unix/IPC sockets (bare-pipe) do not implement TCP keep-alive; bare-http1's
* global Agent calls socket.setKeepAlive on free and crashes with:
* TypeError: this._socket.setKeepAlive is not a function
*/
'use strict'
const http = require('bare-http1')
const net = require('bare-net')
/**
* bare-net Socket.setKeepAlive delegates to the underlying handle.
* bare-pipe has no setKeepAlive — wrap so Agent keep-alive is a no-op.
*/
function hardenSocket(socket) {
if (!socket || socket.__peardockHardened) return socket
const wrap = (name) => {
const orig = typeof socket[name] === 'function' ? socket[name].bind(socket) : null
socket[name] = function (...args) {
if (!orig) return this
try {
return orig(...args)
} catch {
return this
}
}
}
wrap('setKeepAlive')
wrap('setNoDelay')
wrap('setTimeout')
wrap('ref')
wrap('unref')
// Ensure methods always exist even if missing on the object
if (typeof socket.setKeepAlive !== 'function') {
socket.setKeepAlive = function () {
return this
}
}
if (typeof socket.setNoDelay !== 'function') {
socket.setNoDelay = function () {
return this
}
}
if (typeof socket.unref !== 'function') {
socket.unref = function () {
return this
}
}
if (typeof socket.ref !== 'function') {
socket.ref = function () {
return this
}
}
socket.__peardockHardened = true
return socket
}
function patchAgent(Agent) {
if (!Agent || Agent.prototype.__peardockSocketPathPatched) return Agent
const origCreate = Agent.prototype.createConnection
const origGetName = Agent.prototype.getName
const origKeepAlive = Agent.prototype.keepSocketAlive
Agent.prototype.createConnection = function createConnection(opts, callback) {
const o = opts || {}
// ONLY socketPath means "connect via Unix socket". Never treat `path` as a
// socket — in HTTP request options, `path` is the URL path (e.g. /v2/…/manifests/tag).
// bare-https's HTTPSAgent extends this Agent and calls super.createConnection(opts);
// mistaking URL path for a socket yields ENOENT "no such file or directory" on
// every registry HTTPS call (image update checks, etc.).
// dockerode always sets socketPath explicitly for the Docker Engine socket.
const sock = typeof o.socketPath === 'string' && o.socketPath ? o.socketPath : null
if (sock) {
const socket = hardenSocket(net.createConnection({ path: sock }))
if (typeof callback === 'function') {
socket.once('connect', () => callback(null, socket))
}
return socket
}
let socket
if (typeof origCreate === 'function') {
socket = origCreate.call(this, o, callback)
} else {
socket = net.createConnection(o, callback)
}
return hardenSocket(socket)
}
Agent.prototype.getName = function getName(opts) {
const o = opts || {}
if (o.socketPath) return `unix:${o.socketPath}:`
if (typeof origGetName === 'function') return origGetName.call(this, o)
return `${o.host || 'localhost'}:${o.port || 80}`
}
Agent.prototype.keepSocketAlive = function keepSocketAlive(socket) {
// Prefer original implementation when safe
try {
if (typeof origKeepAlive === 'function') {
return origKeepAlive.call(this, socket)
}
} catch {
// fall through
}
// Unix sockets / bare-pipe: no TCP keep-alive; still unref for pooling
try {
if (typeof socket?.setKeepAlive === 'function') {
socket.setKeepAlive(true, this._keepAlive > 0 ? this._keepAlive : 1000)
}
} catch {
// ignore
}
try {
if (typeof socket?.unref === 'function') socket.unref()
} catch {
// ignore
}
// Return true so free sockets can be reused (docker.sock benefits from pooling)
return this._keepAlive !== -1
}
Agent.prototype.__peardockSocketPathPatched = true
return Agent
}
patchAgent(http.Agent)
if (http.globalAgent?.constructor) {
patchAgent(http.globalAgent.constructor)
}
module.exports = http
module.exports.default = http