/** * 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