mirror of
https://github.com/cypress-io/cypress.git
synced 2026-04-29 19:41:16 -05:00
c1a345dce2
* https-proxy: unused file * server: wrap all https requests that use a proxy * server: use request lib in ensureUrl if proxy is in use. this makes runs tab work behind a proxy * electron: pass --proxy-server to app itself, so the embedded github login page works * cli: first attempt at env vars from windows registry * cli: api cleanup * cli: lint * cli: fix crash on no proxy, add tests * add desktop-gui watch to terminals.json * cli: pass along --proxy-source * electron: pass --proxy-bypass-list too * server: whitelist proxy* args * cli: better wording * desktop-gui: display proxy settings * extension: force proxy [wip] * extension: finally, i am victorious over coffeescript * extension: add -loopback to bypasslist * extension: revert changes Revert "extension: force proxy [wip]" This reverts commit 3ab6ba42a763f25ee65f12eb8b79eb597efc9b11. * desktop-gui: skip proxysettings if there aren't any * https-proxy, server: proxy directConnections using https-proxy-agent * https-agent: pool httpsAgents * https-proxy: work when they're not on a proxy * https-proxy: ci - use agent 1.0 * https-proxy: tests * desktop-gui: hide proxy settings when not using proxy * https-proxy: pass req through to https-proxy-agent callback * cli: use get-windows-proxy * desktop-gui: always show proxy settings * server: use get-windows-proxy * electron, server: supply electron proxy config when window launched * server: fix * https-proxy: cleanup * server: clean up ensureUrl * https-proxy: cleanup * cli: fix * cli: fix destructuring * server: enable ForeverAgent to pool HTTPS/HTTP connections #3192 * server: updating snapshot * https-proxy: don't crash, do error if proxy unreachable * https-proxy: * get-windows-proxy@1.0.0 * https-proxy: use proxy-from-env to decide on a proxy for a url * server: fallback to HTTP_PROXY globally if HTTPS_PROXY not set * server: proxy args test * cli: add proxy tests * cli: add test that loadSystemProxySettings is called during download * cli, server: account for the fact that CI has some proxy vars set * https-proxy: "" * cli, https-proxy, server: "" * desktop-gui: update settings gui * desktop-gui: cypress tests for proxy settings * server: strict undefined check * cli, server: move get-windows-proxy to scope, optionalDeps * server, cli: use new and improved get-windows-proxy * cli, server: 1.5.0 * server: re-check for proxy since cli may have failed to load the lib * server, cli: 1.5.1 * server: NO_PROXY=localhost by default, clean up * https-proxy: disable Nagle's on proxy sockets \#3192 * https-proxy: use setNoDelay on upstream, cache https agent * https-proxy: test basic auth * https-proxy: add todo: remove this * server: add custom HTTP(s) Agent implementation w keepalive, tunneling * server: typescript for agent * add ts to zunder * server: more ts * ts: add missing Agent type declaration * server: create CombinedAgent * server: use agent in more places * ts: more declarations * server: make script work even if debug port not supplied * server: begin some testing * server, ts: agent, tests * server: test * server: agent works with websockets now * server: update snapshot * server: work out some more bugs with websockets * server: more websockets * server: add net_profiler * https-proxy: fix dangling socket on direct connection * server: fix potential 'headers have already been sent' * https-proxy: nab another dangler * server: update test to expect agent * https-proxy: fix failing test * desktop-gui: change on-link * server: add tests for empty response case * server: tests * server: send keep-alive with requests * server: make net profiler hook on socket.connect * server: only hook profiler once * server: update tests, add keep-alive test * server: only regen headers if needed * server: move http_overrides into CombinedAgent, make it proxy-proof for #112 * server: update snapshot * server: undo * server: avoid circular dependency * https-proxy, server: use our Agent instead of https-proxy-agent * server: add dependency back * cli: actually use proxy for download * server, launcher, ts: typescript * Revert "server, launcher, ts: typescript" This reverts commitd3f8b8bbb6. * Revert "Revert "server, launcher, ts: typescript"" This reverts commit818dfdfd00. * ts, server: respond to PR * server, ts: types * ts: really fix types * https-proxy, server: export CA from https-proxy * agent, server, https-proxy: move agent to own package * agent => networking, move connect into networking * fix tests * fix test * networking: respond to PR changes, add more unit tests * rename ctx * networking, ts: add more tests * server: add ensureUrl tests * https-proxy: remove https-proxy-agent * server: use CombinedAgent for API * server: updates * add proxy performance tests * add perf tests to workflow * circle * run perf tests with --no-sandbox * networking, ts: ch-ch-ch-ch-changes * server, networking: pr changes * run networking tests in circle * server: fix performance test * https-proxy: test that sockets are being closed * https-proxy: write, not emit * networking: fix test * networking: bubble err in connect * networking: style * networking: clean p connect error handling * networking => network * server: make perf tests really work * server: really report * server: use args from browser * server: use AI to determine max run time * server: load electron only when needed Co-authored-by: Brian Mann <brian@cypress.io>
265 lines
6.7 KiB
JavaScript
265 lines
6.7 KiB
JavaScript
const fs = require('fs')
|
|
const debug = require('debug')('net-profiler')
|
|
|
|
function getCaller (level = 5) {
|
|
try {
|
|
return new Error().stack.split('\n')[level].slice(7)
|
|
} catch (e) {
|
|
return 'unknown'
|
|
}
|
|
}
|
|
|
|
function getLogPath (logPath) {
|
|
if (!logPath) {
|
|
const os = require('os')
|
|
const dirName = fs.mkdtempSync(`${os.tmpdir()}/net-profiler-`)
|
|
|
|
logPath = `${dirName}/timeline.txt`
|
|
}
|
|
|
|
return logPath
|
|
}
|
|
|
|
function Connection (host, port, type = 'connection', toHost, toPort) {
|
|
this.type = type
|
|
this.host = host || 'localhost'
|
|
this.port = port
|
|
this.toHost = toHost || 'localhost'
|
|
this.toPort = toPort
|
|
}
|
|
|
|
Connection.prototype.beginning = function () {
|
|
switch (this.type) {
|
|
case 'server':
|
|
return `O server began listening on ${this.host}:${this.port} at ${getCaller()}`
|
|
case 'client':
|
|
return `C client connected from ${this.host}:${this.port} to server on ${this.toHost}:${this.toPort}`
|
|
default:
|
|
return `X connection opened to ${this.host}:${this.port} by ${getCaller()}`
|
|
}
|
|
}
|
|
|
|
Connection.prototype.ending = function () {
|
|
switch (this.type) {
|
|
case 'server':
|
|
return 'O server closed'
|
|
case 'client':
|
|
return 'C client disconnected'
|
|
default:
|
|
return 'X connection closed'
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Tracks all incoming and outgoing network connections and logs a timeline of network traffic to a file.
|
|
*
|
|
* @param options.net the `net` object to stub, default: nodejs net object
|
|
* @param options.tickMs the number of milliseconds between ticks in the profile, default: 1000
|
|
* @param options.tickWhenNoneActive should ticks be recorded when no connections are active, default: false
|
|
* @param options.logPath path to the file to append to, default: new file in your temp directory
|
|
*/
|
|
function NetProfiler (options = {}) {
|
|
if (!(this instanceof NetProfiler)) return new NetProfiler(options)
|
|
|
|
if (!options.net) {
|
|
options.net = require('net')
|
|
}
|
|
|
|
this.net = options.net
|
|
this.proxies = {}
|
|
this.activeConnections = []
|
|
this.startTs = new Date() / 1000
|
|
this.tickMs = options.tickMs || 1000
|
|
this.tickWhenNoneActive = options.tickWhenNoneActive || false
|
|
|
|
this.logPath = getLogPath(options.logPath)
|
|
debug('logging to ', this.logPath)
|
|
|
|
this.startProfiling()
|
|
}
|
|
|
|
NetProfiler.prototype.install = function () {
|
|
const net = this.net
|
|
const self = this
|
|
|
|
function netSocketPrototypeConnectApply (target, thisArg, args) {
|
|
const client = target.bind(thisArg)(...args)
|
|
|
|
let options = self.net._normalizeArgs(args)[0]
|
|
|
|
if (Array.isArray(options)) {
|
|
options = options[0]
|
|
}
|
|
|
|
options.host = options.host || 'localhost'
|
|
|
|
const connection = new Connection(options.host, options.port)
|
|
|
|
client.on('close', () => {
|
|
self.removeActiveConnection(connection)
|
|
})
|
|
|
|
self.addActiveConnection(connection)
|
|
|
|
return client
|
|
}
|
|
|
|
function netServerPrototypeListenApply (target, thisArg, args) {
|
|
const server = thisArg
|
|
|
|
server.on('listening', () => {
|
|
const { host, port } = server.address()
|
|
|
|
const connection = new Connection(host, port, 'server')
|
|
|
|
self.addActiveConnection(connection)
|
|
server.on('close', () => {
|
|
self.removeActiveConnection(connection)
|
|
})
|
|
|
|
server.on('connection', (client) => {
|
|
const clientConn = new Connection(client.remoteAddress, client.remotePort, 'client', host, port)
|
|
|
|
self.addActiveConnection(clientConn)
|
|
client.on('close', () => {
|
|
self.removeActiveConnection(clientConn)
|
|
})
|
|
})
|
|
})
|
|
|
|
const listener = target.bind(thisArg)(...args)
|
|
|
|
return listener
|
|
}
|
|
|
|
this.proxies['net.Socket.prototype.connect'] = Proxy.revocable(net.Socket.prototype.connect, {
|
|
apply: netSocketPrototypeConnectApply,
|
|
})
|
|
|
|
this.proxies['net.Server.prototype.listen'] = Proxy.revocable(net.Server.prototype.listen, {
|
|
apply: netServerPrototypeListenApply,
|
|
})
|
|
|
|
net.Socket.prototype.connect = this.proxies['net.Socket.prototype.connect'].proxy
|
|
net.Server.prototype.listen = this.proxies['net.Server.prototype.listen'].proxy
|
|
}
|
|
|
|
NetProfiler.prototype.uninstall = function () {
|
|
const net = this.net
|
|
|
|
net.Socket.prototype.connect = this.proxies['net.Socket.prototype.connect'].proxy['[[Target]]']
|
|
net.Server.prototype.listen = this.proxies['net.Server.prototype.listen'].proxy['[[Target]]']
|
|
|
|
this.proxies.forEach((proxy) => {
|
|
proxy.revoke()
|
|
})
|
|
}
|
|
|
|
NetProfiler.prototype.startProfiling = function () {
|
|
this.install()
|
|
debug('profiling started')
|
|
this.logStream = fs.openSync(this.logPath, 'a')
|
|
this.writeTimeline('Profiling started!')
|
|
this.startTimer()
|
|
}
|
|
|
|
NetProfiler.prototype.startTimer = function () {
|
|
if (!this.tickMs) {
|
|
return
|
|
}
|
|
|
|
this.timer = setInterval(() => {
|
|
const tick = this.tickWhenNoneActive || this.activeConnections.find((x) => {
|
|
return !!x
|
|
})
|
|
|
|
if (tick) {
|
|
this.writeTimeline()
|
|
}
|
|
}, this.tickMs)
|
|
}
|
|
|
|
NetProfiler.prototype.stopTimer = function () {
|
|
clearInterval(this.timer)
|
|
}
|
|
|
|
NetProfiler.prototype.stopProfiling = function () {
|
|
this.writeTimeline('Profiling stopped!')
|
|
this.stopTimer()
|
|
fs.closeSync(this.logStream)
|
|
debug('profiling ended')
|
|
this.uninstall()
|
|
}
|
|
|
|
NetProfiler.prototype.addActiveConnection = function (connection) {
|
|
let index = this.activeConnections.findIndex((x) => {
|
|
return typeof x === 'undefined'
|
|
})
|
|
|
|
if (index === -1) {
|
|
index = this.activeConnections.length
|
|
this.activeConnections.push(connection)
|
|
} else {
|
|
this.activeConnections[index] = connection
|
|
}
|
|
|
|
this.writeTimeline(index, connection.beginning())
|
|
}
|
|
|
|
NetProfiler.prototype.removeActiveConnection = function (connection) {
|
|
let index = this.activeConnections.findIndex((x) => {
|
|
return x === connection
|
|
})
|
|
|
|
this.writeTimeline(index, connection.ending())
|
|
this.activeConnections[index] = undefined
|
|
}
|
|
|
|
NetProfiler.prototype.getTimestamp = function () {
|
|
let elapsed = (new Date() / 1000 - this.startTs).toString()
|
|
const parts = elapsed.split('.', 2)
|
|
|
|
if (!parts[1]) {
|
|
parts[1] = '000'
|
|
}
|
|
|
|
while (parts[1].length < 3) {
|
|
parts[1] += '0'
|
|
}
|
|
|
|
elapsed = `${parts[0]}.${parts[1] ? parts[1].slice(0, 3) : '000'}`
|
|
|
|
while (elapsed.length < 11) {
|
|
elapsed = ` ${elapsed}`
|
|
}
|
|
|
|
return elapsed
|
|
}
|
|
|
|
NetProfiler.prototype.writeTimeline = function (index, message) {
|
|
if (!message) {
|
|
message = index || ''
|
|
index = this.activeConnections.length
|
|
}
|
|
|
|
let row = ` ${this.activeConnections.map((conn, i) => {
|
|
if (conn) {
|
|
return ['|', '1', 'l', ':'][i % 4]
|
|
}
|
|
|
|
return ' '
|
|
}).join(' ')}`
|
|
|
|
if (message) {
|
|
const column = 3 + index * 4
|
|
|
|
row = `${row.substring(0, column - 2)}[ ${message} ]${row.substring(2 + column + message.length)}`
|
|
}
|
|
|
|
row = `${this.getTimestamp()}${row.replace(/\s+$/, '')}\n`
|
|
|
|
fs.writeSync(this.logStream, row)
|
|
}
|
|
|
|
module.exports = NetProfiler
|