1'use strict'; 2 3const { 4 Symbol, 5} = primordials; 6 7const { setImmediate } = require('timers'); 8const assert = require('internal/assert'); 9const { Socket } = require('net'); 10const { JSStream } = internalBinding('js_stream'); 11const uv = internalBinding('uv'); 12let debug = require('internal/util/debuglog').debuglog( 13 'stream_socket', 14 (fn) => { 15 debug = fn; 16 } 17); 18const { owner_symbol } = require('internal/async_hooks').symbols; 19const { ERR_STREAM_WRAP } = require('internal/errors').codes; 20 21const kCurrentWriteRequest = Symbol('kCurrentWriteRequest'); 22const kCurrentShutdownRequest = Symbol('kCurrentShutdownRequest'); 23const kPendingShutdownRequest = Symbol('kPendingShutdownRequest'); 24 25function isClosing() { return this[owner_symbol].isClosing(); } 26 27function onreadstart() { return this[owner_symbol].readStart(); } 28 29function onreadstop() { return this[owner_symbol].readStop(); } 30 31function onshutdown(req) { return this[owner_symbol].doShutdown(req); } 32 33function onwrite(req, bufs) { return this[owner_symbol].doWrite(req, bufs); } 34 35/* This class serves as a wrapper for when the C++ side of Node wants access 36 * to a standard JS stream. For example, TLS or HTTP do not operate on network 37 * resources conceptually, although that is the common case and what we are 38 * optimizing for; in theory, they are completely composable and can work with 39 * any stream resource they see. 40 * 41 * For the common case, i.e. a TLS socket wrapping around a net.Socket, we 42 * can skip going through the JS layer and let TLS access the raw C++ handle 43 * of a net.Socket. The flipside of this is that, to maintain composability, 44 * we need a way to create "fake" net.Socket instances that call back into a 45 * "real" JavaScript stream. JSStreamSocket is exactly this. 46 */ 47class JSStreamSocket extends Socket { 48 constructor(stream) { 49 const handle = new JSStream(); 50 handle.close = (cb) => { 51 debug('close'); 52 this.doClose(cb); 53 }; 54 // Inside of the following functions, `this` refers to the handle 55 // and `this[owner_symbol]` refers to this JSStreamSocket instance. 56 handle.isClosing = isClosing; 57 handle.onreadstart = onreadstart; 58 handle.onreadstop = onreadstop; 59 handle.onshutdown = onshutdown; 60 handle.onwrite = onwrite; 61 62 stream.pause(); 63 stream.on('error', (err) => this.emit('error', err)); 64 const ondata = (chunk) => { 65 if (typeof chunk === 'string' || 66 stream.readableObjectMode === true) { 67 // Make sure that no further `data` events will happen. 68 stream.pause(); 69 stream.removeListener('data', ondata); 70 71 this.emit('error', new ERR_STREAM_WRAP()); 72 return; 73 } 74 75 debug('data', chunk.length); 76 if (this._handle) 77 this._handle.readBuffer(chunk); 78 }; 79 stream.on('data', ondata); 80 stream.once('end', () => { 81 debug('end'); 82 if (this._handle) 83 this._handle.emitEOF(); 84 }); 85 // Some `Stream` don't pass `hasError` parameters when closed. 86 stream.once('close', () => { 87 // Errors emitted from `stream` have also been emitted to this instance 88 // so that we don't pass errors to `destroy()` again. 89 this.destroy(); 90 }); 91 92 super({ handle, manualStart: true }); 93 this.stream = stream; 94 this[kCurrentWriteRequest] = null; 95 this[kCurrentShutdownRequest] = null; 96 this[kPendingShutdownRequest] = null; 97 this.readable = stream.readable; 98 this.writable = stream.writable; 99 100 // Start reading. 101 this.read(0); 102 } 103 104 // Allow legacy requires in the test suite to keep working: 105 // const { StreamWrap } = require('internal/js_stream_socket') 106 static get StreamWrap() { 107 return JSStreamSocket; 108 } 109 110 isClosing() { 111 return !this.readable || !this.writable; 112 } 113 114 readStart() { 115 this.stream.resume(); 116 return 0; 117 } 118 119 readStop() { 120 this.stream.pause(); 121 return 0; 122 } 123 124 doShutdown(req) { 125 // TODO(addaleax): It might be nice if we could get into a state where 126 // DoShutdown() is not called on streams while a write is still pending. 127 // 128 // Currently, the only part of the code base where that happens is the 129 // TLS implementation, which calls both DoWrite() and DoShutdown() on the 130 // underlying network stream inside of its own DoShutdown() method. 131 // Working around that on the native side is not quite trivial (yet?), 132 // so for now that is supported here. 133 134 if (this[kCurrentWriteRequest] !== null) { 135 this[kPendingShutdownRequest] = req; 136 return 0; 137 } 138 assert(this[kCurrentWriteRequest] === null); 139 assert(this[kCurrentShutdownRequest] === null); 140 this[kCurrentShutdownRequest] = req; 141 142 const handle = this._handle; 143 144 setImmediate(() => { 145 // Ensure that write is dispatched asynchronously. 146 this.stream.end(() => { 147 this.finishShutdown(handle, 0); 148 }); 149 }); 150 return 0; 151 } 152 153 // handle === this._handle except when called from doClose(). 154 finishShutdown(handle, errCode) { 155 // The shutdown request might already have been cancelled. 156 if (this[kCurrentShutdownRequest] === null) 157 return; 158 const req = this[kCurrentShutdownRequest]; 159 this[kCurrentShutdownRequest] = null; 160 handle.finishShutdown(req, errCode); 161 } 162 163 doWrite(req, bufs) { 164 assert(this[kCurrentWriteRequest] === null); 165 assert(this[kCurrentShutdownRequest] === null); 166 167 const handle = this._handle; 168 const self = this; 169 170 let pending = bufs.length; 171 172 this.stream.cork(); 173 // Use `var` over `let` for performance optimization. 174 for (var i = 0; i < bufs.length; ++i) 175 this.stream.write(bufs[i], done); 176 this.stream.uncork(); 177 178 // Only set the request here, because the `write()` calls could throw. 179 this[kCurrentWriteRequest] = req; 180 181 function done(err) { 182 if (!err && --pending !== 0) 183 return; 184 185 // Ensure that this is called once in case of error 186 pending = 0; 187 188 let errCode = 0; 189 if (err) { 190 errCode = uv[`UV_${err.code}`] || uv.UV_EPIPE; 191 } 192 193 // Ensure that write was dispatched 194 setImmediate(() => { 195 self.finishWrite(handle, errCode); 196 }); 197 } 198 199 return 0; 200 } 201 202 // handle === this._handle except when called from doClose(). 203 finishWrite(handle, errCode) { 204 // The write request might already have been cancelled. 205 if (this[kCurrentWriteRequest] === null) 206 return; 207 const req = this[kCurrentWriteRequest]; 208 this[kCurrentWriteRequest] = null; 209 210 handle.finishWrite(req, errCode); 211 if (this[kPendingShutdownRequest]) { 212 const req = this[kPendingShutdownRequest]; 213 this[kPendingShutdownRequest] = null; 214 this.doShutdown(req); 215 } 216 } 217 218 doClose(cb) { 219 const handle = this._handle; 220 221 // When sockets of the "net" module destroyed, they will call 222 // `this._handle.close()` which will also emit EOF if not emitted before. 223 // This feature makes sockets on the other side emit "end" and "close" 224 // even though we haven't called `end()`. As `stream` are likely to be 225 // instances of `net.Socket`, calling `stream.destroy()` manually will 226 // avoid issues that don't properly close wrapped connections. 227 this.stream.destroy(); 228 229 setImmediate(() => { 230 // Should be already set by net.js 231 assert(this._handle === null); 232 233 this.finishWrite(handle, uv.UV_ECANCELED); 234 this.finishShutdown(handle, uv.UV_ECANCELED); 235 236 cb(); 237 }); 238 } 239} 240 241module.exports = JSStreamSocket; 242