mitm-papandreou
Version:
Intercept and mock outgoing network TCP connections and HTTP requests for testing. Intercepts and gives you a Net.Socket, Http.IncomingMessage and Http.ServerResponse to test and respond with. Useful when testing code that hits remote servers.
181 lines (151 loc) • 6.54 kB
JavaScript
var DuplexStream = require("stream").Duplex
var Semver = require("semver")
var uniqueId = 0
var NO_ERROR = 0
var STREAM_STATE
var STREAM_BYTES_READ
var NODE_VERSION = process.version
exports = module.exports = InternalSocket
exports.pair = pair
var NODE_0_10 = Semver.satisfies(NODE_VERSION, ">= 0.10 < 0.11")
var NODE_10_AND_LATER = Semver.satisfies(NODE_VERSION, ">= 10")
var NODE_11_1_AND_LATER = Semver.satisfies(NODE_VERSION, ">= 11.1")
var NODE_11_2_AND_LATER = Semver.satisfies(NODE_VERSION, ">= 11.2")
var NODE_10_15_1_AND_MAJOR = Semver.satisfies(NODE_VERSION, ">= 10.15.1 < 11")
if (!NODE_0_10) var UV_EOF = process.binding("uv").UV_EOF
if (NODE_11_1_AND_LATER) {
STREAM_STATE = process.binding("stream_wrap").streamBaseState
STREAM_BYTES_READ = process.binding("stream_wrap").kReadBytesOrError
}
/**
* Sockets write to InternalSocket via write*String functions. The
* WritableStream.prototype.write function is just used internally by
* InternalSocket to queue data before pushing it to the other end via
* ReadableStream.prototype.push. The receiver will then forward it to its
* owner Socket via the onread property.
*
* InternalSocket is created for both the client side and the server side.
*/
function InternalSocket(remote) {
DuplexStream.call(this)
if (remote) this.remote = remote
this.id = ++uniqueId
// The "end" event follows ReadableStream.prototype.push(null).
this.on("data", readData.bind(this))
this.on("end", readEof.bind(this))
// The "finish" event follows WritableStream.prototype.end.
//
// There's WritableStream.prototype._final for processing before "finish" is
// emitted, but that's only available in Node v8 and later.
this.on("finish", this._write.bind(this, null, null, noop))
return this.pause(), this
}
InternalSocket.prototype = Object.create(DuplexStream.prototype, {
constructor: {value: InternalSocket, configurable: true, writeable: true}
})
// Node v0.11's ReadableStream.prototype.resume and
// ReadableStream.prototype.pause return self. InternalSocket's API states that
// they should return error codes instead.
//
// Node v0.11.13 called ReadableStream.prototype.read(0) synchronously, but
// v0.11.14 does it in the next tick. For easier sync use, call it here.
InternalSocket.prototype.readStart = function() { this.resume() }
InternalSocket.prototype.readStop = function() { this.pause() }
InternalSocket.prototype._read = noop
InternalSocket.prototype.ref = noop
InternalSocket.prototype.unref = noop
// Node v8 added "getAsyncId".
InternalSocket.prototype.getAsyncId = function() { return this.id }
InternalSocket.prototype._write = function(data, encoding, done) {
var remote = this.remote
process.nextTick(function() { remote.push(data, encoding); done() })
}
// Node v10 requires writev to be set on the handler because, while
// WritableStream expects _writev, internal/stream_base_commons.js calls
// req.handle.writev directly. It's given a flat array of data+type pairs.
if (NODE_10_AND_LATER) InternalSocket.prototype.writev = function(_req, data) {
for (var i = 0; i < data.length; ++i) this._write(data[i], data[++i], noop)
return NO_ERROR
}
// NOTE: Node v0.10 expects InternalSocket to return write request objects with
// a "oncomplete" and "cb" property. Node v0.11 expects it return an error
// instead. Node v10 expects it to return an error code.
// InternalSocket.prototype.writeBinaryString was introduced in Node v0.11.14.
InternalSocket.prototype.writeBinaryString = function(_req, data) {
this.write(data, "binary")
if (NODE_10_AND_LATER) return NO_ERROR
}
// InternalSocket.prototype.writeLatin1String was introduced in Node v6.4.
InternalSocket.prototype.writeLatin1String = function(_req, data) {
this.write(data, "latin1")
if (NODE_10_AND_LATER) return NO_ERROR
}
InternalSocket.prototype.writeBuffer = function(req, data) {
/* eslint consistent-return: 0 */
this.write(NODE_0_10 ? req : data)
if (NODE_0_10) return {}
else if (NODE_10_AND_LATER) return NO_ERROR
}
InternalSocket.prototype.writeUtf8String = function(req, data) {
/* eslint consistent-return: 0 */
this.write(NODE_0_10 ? req : data, "utf8")
if (NODE_0_10) return {}
else if (NODE_10_AND_LATER) return NO_ERROR
}
InternalSocket.prototype.writeAsciiString = function(req, data) {
/* eslint consistent-return: 0 */
this.write(NODE_0_10 ? req : data, "ascii")
if (NODE_0_10) return {}
else if (NODE_10_AND_LATER) return NO_ERROR
}
InternalSocket.prototype.writeUcs2String = function(req, data) {
/* eslint consistent-return: 0 */
this.write(NODE_0_10 ? req : data, "ucs2")
if (NODE_0_10) return {}
else if (NODE_10_AND_LATER) return NO_ERROR
}
// While it seems to have existed since Node v0.10, Node v11.2 requires
// "shutdown". AFAICT, "shutdown" is for shutting the writable side down and
// hence the use of WritableStream.prototype.end and waiting for the "finish"
// event.
if (
NODE_11_2_AND_LATER ||
NODE_10_15_1_AND_MAJOR
) InternalSocket.prototype.shutdown = function(req) {
this.once("finish", req.oncomplete.bind(req, NO_ERROR, req.handle))
this.end()
// Note v11.8 requires "shutdown" to return an error value, with "1"
// indicating a "synchronous finish" (as per Node's net.js) and "0"
// presumably success.
return 0
}
// I'm unsure of the relationship between InternalSocket.prototype.shutdown and
// InternalSocket.prototype.close.
InternalSocket.prototype.close = function(done) {
if (!this._writableState.finished) this.end(done)
else if (done) done()
}
// Node v0.10 will use writeQueueSize to see if it should set write request's
// "cb" property or write more immediately.
if (NODE_0_10) InternalSocket.prototype.writeQueueSize = 0
function pair() {
var a = Object.create(InternalSocket.prototype)
var b = Object.create(InternalSocket.prototype)
return [InternalSocket.call(a, b), InternalSocket.call(b, a)]
}
function readData(data) {
if (NODE_0_10) return void this.onread(data, 0, data.length)
if (!NODE_11_1_AND_LATER) return void this.onread(data.length, data)
// A system written not in 1960 that passes arguments to functions through
// _global_ mutable data structures…
STREAM_STATE[STREAM_BYTES_READ] = data.length
this.onread(data)
}
function readEof() {
if (this.onread == null) return
if (NODE_0_10) { process._errno = "EOF"; this.onread(null, 0, 0); return }
if (!NODE_11_1_AND_LATER) return void this.onread(UV_EOF)
STREAM_STATE[STREAM_BYTES_READ] = UV_EOF
this.onread()
}
function noop() {}