diff --git a/JavaScriptServices.sln b/JavaScriptServices.sln
index dc3a3c3..0ec69b5 100644
--- a/JavaScriptServices.sln
+++ b/JavaScriptServices.sln
@@ -8,12 +8,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{27304DDE-AFB
src\Directory.Build.props = src\Directory.Build.props
EndProjectSection
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.NodeServices", "src\Microsoft.AspNetCore.NodeServices\Microsoft.AspNetCore.NodeServices.csproj", "{66B77203-1469-41DF-92F2-2BE6900BD36F}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.NodeServices.Sockets", "src\Microsoft.AspNetCore.NodeServices.Sockets\Microsoft.AspNetCore.NodeServices.Sockets.csproj", "{F46DEF99-6FAA-4406-B5D8-6FF34EF669E3}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.SpaServices", "src\Microsoft.AspNetCore.SpaServices\Microsoft.AspNetCore.SpaServices.csproj", "{66B071A8-EFC8-4A06-BEF6-06B99AE27EEC}"
-EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "misc", "misc", "{99EAF1FE-22C8-4526-BE78-74B24125D37F}"
ProjectSection(SolutionItems) = preProject
.gitignore = .gitignore
@@ -35,18 +29,6 @@ Global
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {66B77203-1469-41DF-92F2-2BE6900BD36F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {66B77203-1469-41DF-92F2-2BE6900BD36F}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {66B77203-1469-41DF-92F2-2BE6900BD36F}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {66B77203-1469-41DF-92F2-2BE6900BD36F}.Release|Any CPU.Build.0 = Release|Any CPU
- {F46DEF99-6FAA-4406-B5D8-6FF34EF669E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {F46DEF99-6FAA-4406-B5D8-6FF34EF669E3}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {F46DEF99-6FAA-4406-B5D8-6FF34EF669E3}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {F46DEF99-6FAA-4406-B5D8-6FF34EF669E3}.Release|Any CPU.Build.0 = Release|Any CPU
- {66B071A8-EFC8-4A06-BEF6-06B99AE27EEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {66B071A8-EFC8-4A06-BEF6-06B99AE27EEC}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {66B071A8-EFC8-4A06-BEF6-06B99AE27EEC}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {66B071A8-EFC8-4A06-BEF6-06B99AE27EEC}.Release|Any CPU.Build.0 = Release|Any CPU
{D40BD1C4-6A6F-4213-8535-1057F3EB3400}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D40BD1C4-6A6F-4213-8535-1057F3EB3400}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D40BD1C4-6A6F-4213-8535-1057F3EB3400}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -56,9 +38,6 @@ Global
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
- {66B77203-1469-41DF-92F2-2BE6900BD36F} = {27304DDE-AFB2-4F8B-B765-E3E2F11E886C}
- {F46DEF99-6FAA-4406-B5D8-6FF34EF669E3} = {27304DDE-AFB2-4F8B-B765-E3E2F11E886C}
- {66B071A8-EFC8-4A06-BEF6-06B99AE27EEC} = {27304DDE-AFB2-4F8B-B765-E3E2F11E886C}
{D40BD1C4-6A6F-4213-8535-1057F3EB3400} = {27304DDE-AFB2-4F8B-B765-E3E2F11E886C}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
diff --git a/src/Microsoft.AspNetCore.NodeServices.Sockets/.gitignore b/src/Microsoft.AspNetCore.NodeServices.Sockets/.gitignore
deleted file mode 100644
index 98edee7..0000000
--- a/src/Microsoft.AspNetCore.NodeServices.Sockets/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-/bin/
-/node_modules/
-yarn.lock
diff --git a/src/Microsoft.AspNetCore.NodeServices.Sockets/Content/Node/entrypoint-socket.js b/src/Microsoft.AspNetCore.NodeServices.Sockets/Content/Node/entrypoint-socket.js
deleted file mode 100644
index 89b72f4..0000000
--- a/src/Microsoft.AspNetCore.NodeServices.Sockets/Content/Node/entrypoint-socket.js
+++ /dev/null
@@ -1,524 +0,0 @@
-(function(e, a) { for(var i in a) e[i] = a[i]; }(exports, /******/ (function(modules) { // webpackBootstrap
-/******/ // The module cache
-/******/ var installedModules = {};
-
-/******/ // The require function
-/******/ function __webpack_require__(moduleId) {
-
-/******/ // Check if module is in cache
-/******/ if(installedModules[moduleId])
-/******/ return installedModules[moduleId].exports;
-
-/******/ // Create a new module (and put it into the cache)
-/******/ var module = installedModules[moduleId] = {
-/******/ exports: {},
-/******/ id: moduleId,
-/******/ loaded: false
-/******/ };
-
-/******/ // Execute the module function
-/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
-
-/******/ // Flag the module as loaded
-/******/ module.loaded = true;
-
-/******/ // Return the exports of the module
-/******/ return module.exports;
-/******/ }
-
-
-/******/ // expose the modules object (__webpack_modules__)
-/******/ __webpack_require__.m = modules;
-
-/******/ // expose the module cache
-/******/ __webpack_require__.c = installedModules;
-
-/******/ // __webpack_public_path__
-/******/ __webpack_require__.p = "";
-
-/******/ // Load entry module and return exports
-/******/ return __webpack_require__(0);
-/******/ })
-/************************************************************************/
-/******/ ([
-/* 0 */
-/***/ function(module, exports, __webpack_require__) {
-
- module.exports = __webpack_require__(1);
-
-
-/***/ },
-/* 1 */
-/***/ function(module, exports, __webpack_require__) {
-
- "use strict";
- // Limit dependencies to core Node modules. This means the code in this file has to be very low-level and unattractive,
- // but simplifies things for the consumer of this module.
- __webpack_require__(2);
- var net = __webpack_require__(3);
- var path = __webpack_require__(4);
- var readline = __webpack_require__(5);
- var ArgsUtil_1 = __webpack_require__(6);
- var ExitWhenParentExits_1 = __webpack_require__(7);
- var virtualConnectionServer = __webpack_require__(8);
- // Webpack doesn't support dynamic requires for files not present at compile time, so grab a direct
- // reference to Node's runtime 'require' function.
- var dynamicRequire = eval('require');
- // Signal to the .NET side when we're ready to accept invocations
- var server = net.createServer().on('listening', function () {
- console.log('[Microsoft.AspNetCore.NodeServices:Listening]');
- });
- // Each virtual connection represents a separate invocation
- virtualConnectionServer.createInterface(server).on('connection', function (connection) {
- readline.createInterface(connection, null).on('line', function (line) {
- try {
- // Get a reference to the function to invoke
- var invocation = JSON.parse(line);
- var invokedModule = dynamicRequire(path.resolve(process.cwd(), invocation.moduleName));
- var invokedFunction = invocation.exportedFunctionName ? invokedModule[invocation.exportedFunctionName] : invokedModule;
- // Prepare a callback for accepting non-streamed JSON responses
- var hasInvokedCallback_1 = false;
- var invocationCallback = function (errorValue, successValue) {
- if (hasInvokedCallback_1) {
- throw new Error('Cannot supply more than one result. The callback has already been invoked,'
- + ' or the result stream has already been accessed');
- }
- hasInvokedCallback_1 = true;
- connection.end(JSON.stringify({
- result: successValue,
- errorMessage: errorValue && (errorValue.message || errorValue),
- errorDetails: errorValue && (errorValue.stack || null)
- }));
- };
- // Also support streamed binary responses
- Object.defineProperty(invocationCallback, 'stream', {
- enumerable: true,
- get: function () {
- hasInvokedCallback_1 = true;
- return connection;
- }
- });
- // Actually invoke it, passing through any supplied args
- invokedFunction.apply(null, [invocationCallback].concat(invocation.args));
- }
- catch (ex) {
- connection.end(JSON.stringify({
- errorMessage: ex.message,
- errorDetails: ex.stack
- }));
- }
- });
- });
- // Begin listening now. The underlying transport varies according to the runtime platform.
- // On Windows it's Named Pipes; on Linux/OSX it's Domain Sockets.
- var useWindowsNamedPipes = /^win/.test(process.platform);
- var parsedArgs = ArgsUtil_1.parseArgs(process.argv);
- var listenAddress = (useWindowsNamedPipes ? '\\\\.\\pipe\\' : '/tmp/') + parsedArgs.listenAddress;
- server.listen(listenAddress);
- ExitWhenParentExits_1.exitWhenParentExits(parseInt(parsedArgs.parentPid));
-
-
-/***/ },
-/* 2 */
-/***/ function(module, exports) {
-
- // When Node writes to stdout/strerr, we capture that and convert the lines into calls on the
- // active .NET ILogger. But by default, stdout/stderr don't have any way of distinguishing
- // linebreaks inside log messages from the linebreaks that delimit separate log messages,
- // so multiline strings will end up being written to the ILogger as multiple independent
- // log messages. This makes them very hard to make sense of, especially when they represent
- // something like stack traces.
- //
- // To fix this, we intercept stdout/stderr writes, and replace internal linebreaks with a
- // marker token. When .NET receives the lines, it converts the marker tokens back to regular
- // linebreaks within the logged messages.
- //
- // Note that it's better to do the interception at the stdout/stderr level, rather than at
- // the console.log/console.error (etc.) level, because this takes place after any native
- // message formatting has taken place (e.g., inserting values for % placeholders).
- var findInternalNewlinesRegex = /\n(?!$)/g;
- var encodedNewline = '__ns_newline__';
- encodeNewlinesWrittenToStream(process.stdout);
- encodeNewlinesWrittenToStream(process.stderr);
- function encodeNewlinesWrittenToStream(outputStream) {
- var origWriteFunction = outputStream.write;
- outputStream.write = function (value) {
- // Only interfere with the write if it's definitely a string
- if (typeof value === 'string') {
- var argsClone = Array.prototype.slice.call(arguments, 0);
- argsClone[0] = encodeNewlinesInString(value);
- origWriteFunction.apply(this, argsClone);
- }
- else {
- origWriteFunction.apply(this, arguments);
- }
- };
- }
- function encodeNewlinesInString(str) {
- return str.replace(findInternalNewlinesRegex, encodedNewline);
- }
-
-
-/***/ },
-/* 3 */
-/***/ function(module, exports) {
-
- module.exports = require("net");
-
-/***/ },
-/* 4 */
-/***/ function(module, exports) {
-
- module.exports = require("path");
-
-/***/ },
-/* 5 */
-/***/ function(module, exports) {
-
- module.exports = require("readline");
-
-/***/ },
-/* 6 */
-/***/ function(module, exports) {
-
- "use strict";
- function parseArgs(args) {
- // Very simplistic parsing which is sufficient for the cases needed. We don't want to bring in any external
- // dependencies (such as an args-parsing library) to this file.
- var result = {};
- var currentKey = null;
- args.forEach(function (arg) {
- if (arg.indexOf('--') === 0) {
- var argName = arg.substring(2);
- result[argName] = undefined;
- currentKey = argName;
- }
- else if (currentKey) {
- result[currentKey] = arg;
- currentKey = null;
- }
- });
- return result;
- }
- exports.parseArgs = parseArgs;
-
-
-/***/ },
-/* 7 */
-/***/ function(module, exports) {
-
- /*
- In general, we want the Node child processes to be terminated as soon as the parent .NET processes exit,
- because we have no further use for them. If the .NET process shuts down gracefully, it will run its
- finalizers, one of which (in OutOfProcessNodeInstance.cs) will kill its associated Node process immediately.
-
- But if the .NET process is terminated forcefully (e.g., on Linux/OSX with 'kill -9'), then it won't have
- any opportunity to shut down its child processes, and by default they will keep running. In this case, it's
- up to the child process to detect this has happened and terminate itself.
-
- There are many possible approaches to detecting when a parent process has exited, most of which behave
- differently between Windows and Linux/OS X:
-
- - On Windows, the parent process can mark its child as being a 'job' that should auto-terminate when
- the parent does (http://stackoverflow.com/a/4657392). Not cross-platform.
- - The child Node process can get a callback when the parent disconnects (process.on('disconnect', ...)).
- But despite http://stackoverflow.com/a/16487966, no callback fires in any case I've tested (Windows / OS X).
- - The child Node process can get a callback when its stdin/stdout are disconnected, as described at
- http://stackoverflow.com/a/15693934. This works well on OS X, but calling stdout.resume() on Windows
- causes the process to terminate prematurely.
- - I don't know why, but on Windows, it's enough to invoke process.stdin.resume(). For some reason this causes
- the child Node process to exit as soon as the parent one does, but I don't see this documented anywhere.
- - You can poll to see if the parent process, or your stdin/stdout connection to it, is gone
- - You can directly pass a parent process PID to the child, and then have the child poll to see if it's
- still running (e.g., using process.kill(pid, 0), which doesn't kill it but just tests whether it exists,
- as per https://nodejs.org/api/process.html#process_process_kill_pid_signal)
- - Or, on each poll, you can try writing to process.stdout. If the parent has died, then this will throw.
- However I don't see this documented anywhere. It would be nice if you could just poll for whether or not
- process.stdout is still connected (without actually writing to it) but I haven't found any property whose
- value changes until you actually try to write to it.
-
- Of these, the only cross-platform approach that is actually documented as a valid strategy is simply polling
- to check whether the parent PID is still running. So that's what we do here.
- */
- "use strict";
- var pollIntervalMs = 1000;
- function exitWhenParentExits(parentPid) {
- setInterval(function () {
- if (!processExists(parentPid)) {
- // Can't log anything at this point, because out stdout was connected to the parent,
- // but the parent is gone.
- process.exit();
- }
- }, pollIntervalMs);
- }
- exports.exitWhenParentExits = exitWhenParentExits;
- function processExists(pid) {
- try {
- // Sending signal 0 - on all platforms - tests whether the process exists. As long as it doesn't
- // throw, that means it does exist.
- process.kill(pid, 0);
- return true;
- }
- catch (ex) {
- // If the reason for the error is that we don't have permission to ask about this process,
- // report that as a separate problem.
- if (ex.code === 'EPERM') {
- throw new Error("Attempted to check whether process " + pid + " was running, but got a permissions error.");
- }
- return false;
- }
- }
-
-
-/***/ },
-/* 8 */
-/***/ function(module, exports, __webpack_require__) {
-
- "use strict";
- var events_1 = __webpack_require__(9);
- var VirtualConnection_1 = __webpack_require__(10);
- // Keep this in sync with the equivalent constant in the .NET code. Both sides split up their transmissions into frames with this max length,
- // and both will reject longer frames.
- var MaxFrameBodyLength = 16 * 1024;
- /**
- * Accepts connections to a net.Server and adapts them to behave as multiplexed connections. That is, for each physical socket connection,
- * we track a list of 'virtual connections' whose API is a Duplex stream. The remote clients may open and close as many virtual connections
- * as they wish, reading and writing to them independently, without the overhead of establishing new physical connections each time.
- */
- function createInterface(server) {
- var emitter = new events_1.EventEmitter();
- server.on('connection', function (socket) {
- // For each physical socket connection, maintain a set of virtual connections. Issue a notification whenever
- // a new virtual connections is opened.
- var childSockets = new VirtualConnectionsCollection(socket, function (virtualConnection) {
- emitter.emit('connection', virtualConnection);
- });
- });
- return emitter;
- }
- exports.createInterface = createInterface;
- /**
- * Tracks the 'virtual connections' associated with a single physical socket connection.
- */
- var VirtualConnectionsCollection = (function () {
- function VirtualConnectionsCollection(_socket, _onVirtualConnectionCallback) {
- var _this = this;
- this._socket = _socket;
- this._onVirtualConnectionCallback = _onVirtualConnectionCallback;
- this._currentFrameHeader = null;
- this._virtualConnections = {};
- // If the remote end closes the physical socket, treat all the virtual connections as being closed remotely too
- this._socket.on('close', function () {
- Object.getOwnPropertyNames(_this._virtualConnections).forEach(function (id) {
- // A 'null' frame signals that the connection was closed remotely
- _this._virtualConnections[id].onReceivedData(null);
- });
- });
- this._socket.on('readable', this._onIncomingDataAvailable.bind(this));
- }
- /**
- * This is called whenever the underlying socket signals that it may have some data available to read. It will synchronously read as many
- * message frames as it can from the underlying socket, opens virtual connections as needed, and dispatches data to them.
- */
- VirtualConnectionsCollection.prototype._onIncomingDataAvailable = function () {
- var exhaustedAllData = false;
- while (!exhaustedAllData) {
- // We might already have a pending frame header from the previous time this method ran, but if not, that's the next thing we need to read
- if (this._currentFrameHeader === null) {
- this._currentFrameHeader = this._readNextFrameHeader();
- }
- if (this._currentFrameHeader === null) {
- // There's not enough data to fill a frameheader, so wait until more arrives later
- // The next attempt to read from the socket will start from the same place this one did (incomplete reads don't consume any data)
- exhaustedAllData = true;
- }
- else {
- var frameBodyLength = this._currentFrameHeader.bodyLength;
- var frameBodyOrNull = frameBodyLength > 0 ? this._socket.read(this._currentFrameHeader.bodyLength) : null;
- if (frameBodyOrNull !== null || frameBodyLength === 0) {
- // We have a complete frame header+body pair, so we can now dispatch this to a virtual connection. We set _currentFrameHeader back to null
- // so that the next thing we try to read is the next frame header.
- var headerCopy = this._currentFrameHeader;
- this._currentFrameHeader = null;
- this._onReceivedCompleteFrame(headerCopy, frameBodyOrNull);
- }
- else {
- // There's not enough data to fill the pending frame body, so wait until more arrives later
- // The next attempt to read from the socket will start from the same place this one did (incomplete reads don't consume any data)
- exhaustedAllData = true;
- }
- }
- }
- };
- VirtualConnectionsCollection.prototype._onReceivedCompleteFrame = function (header, bodyIfNotEmpty) {
- // An incoming zero-length frame signals that there's no more data to read.
- // Signal this to the Node stream APIs by pushing a 'null' chunk to it.
- var virtualConnection = this._getOrOpenVirtualConnection(header);
- virtualConnection.onReceivedData(header.bodyLength > 0 ? bodyIfNotEmpty : null);
- };
- VirtualConnectionsCollection.prototype._getOrOpenVirtualConnection = function (header) {
- if (this._virtualConnections.hasOwnProperty(header.connectionIdString)) {
- // It's an existing virtual connection
- return this._virtualConnections[header.connectionIdString];
- }
- else {
- // It's a new one
- return this._openVirtualConnection(header);
- }
- };
- VirtualConnectionsCollection.prototype._openVirtualConnection = function (header) {
- var _this = this;
- var beginWriteCallback = function (data, writeCompletedCallback) {
- // Only send nonempty frames, since empty ones are a signal to close the virtual connection
- if (data.length > 0) {
- _this._sendFrame(header.connectionIdBinary, data, writeCompletedCallback);
- }
- };
- var newVirtualConnection = new VirtualConnection_1.VirtualConnection(beginWriteCallback);
- newVirtualConnection.on('end', function () {
- // The virtual connection was closed remotely. Clean up locally.
- _this._onVirtualConnectionWasClosed(header.connectionIdString);
- });
- newVirtualConnection.on('finish', function () {
- // The virtual connection was closed locally. Clean up locally, and notify the remote that we're done.
- _this._onVirtualConnectionWasClosed(header.connectionIdString);
- _this._sendFrame(header.connectionIdBinary, new Buffer(0));
- });
- this._virtualConnections[header.connectionIdString] = newVirtualConnection;
- this._onVirtualConnectionCallback(newVirtualConnection);
- return newVirtualConnection;
- };
- /**
- * Attempts to read a complete frame header, synchronously, from the underlying socket.
- * If not enough data is available synchronously, returns null without consuming any data from the socket.
- */
- VirtualConnectionsCollection.prototype._readNextFrameHeader = function () {
- var headerBuf = this._socket.read(12);
- if (headerBuf !== null) {
- // We have enough data synchronously
- var connectionIdBinary = headerBuf.slice(0, 8);
- var connectionIdString = connectionIdBinary.toString('hex');
- var bodyLength = headerBuf.readInt32LE(8);
- if (bodyLength < 0 || bodyLength > MaxFrameBodyLength) {
- // Throwing here is going to bring down the whole process, so this cannot be allowed to happen in real use.
- // But it won't happen in real use, because this is only used with our .NET client, which doesn't violate this rule.
- throw new Error('Illegal frame body length: ' + bodyLength);
- }
- return { connectionIdBinary: connectionIdBinary, connectionIdString: connectionIdString, bodyLength: bodyLength };
- }
- else {
- // Not enough bytes are available synchronously, so none were consumed
- return null;
- }
- };
- VirtualConnectionsCollection.prototype._sendFrame = function (connectionIdBinary, data, callback) {
- // For all sends other than the last one, only invoke the callback if it failed.
- // Also, only invoke the callback at most once.
- var hasInvokedCallback = false;
- var finalCallback = callback && (function (error) {
- if (!hasInvokedCallback) {
- hasInvokedCallback = true;
- callback(error);
- }
- });
- var notFinalCallback = callback && (function (error) {
- if (error) {
- finalCallback(error);
- }
- });
- // The amount of data we're writing might exceed MaxFrameBodyLength, so split into frames as needed.
- // Note that we always send at least one frame, even if it's empty (because that's the close-virtual-connection signal).
- // If needed, this could be changed to send frames asynchronously, so that large sends could proceed in parallel
- // (though that would involve making a clone of 'data', to avoid the risk of it being mutated during the send).
- var bytesSent = 0;
- do {
- var nextFrameBodyLength = Math.min(MaxFrameBodyLength, data.length - bytesSent);
- var isFinalChunk = (bytesSent + nextFrameBodyLength) === data.length;
- this._socket.write(connectionIdBinary, notFinalCallback);
- this._sendInt32LE(nextFrameBodyLength, notFinalCallback);
- this._socket.write(data.slice(bytesSent, bytesSent + nextFrameBodyLength), isFinalChunk ? finalCallback : notFinalCallback);
- bytesSent += nextFrameBodyLength;
- } while (bytesSent < data.length);
- };
- /**
- * Sends a number serialized in the correct format for .NET to receive as a System.Int32
- */
- VirtualConnectionsCollection.prototype._sendInt32LE = function (value, callback) {
- var buf = new Buffer(4);
- buf.writeInt32LE(value, 0);
- this._socket.write(buf, callback);
- };
- VirtualConnectionsCollection.prototype._onVirtualConnectionWasClosed = function (id) {
- if (this._virtualConnections.hasOwnProperty(id)) {
- delete this._virtualConnections[id];
- }
- };
- return VirtualConnectionsCollection;
- }());
-
-
-/***/ },
-/* 9 */
-/***/ function(module, exports) {
-
- module.exports = require("events");
-
-/***/ },
-/* 10 */
-/***/ function(module, exports, __webpack_require__) {
-
- "use strict";
- var __extends = (this && this.__extends) || function (d, b) {
- for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
- function __() { this.constructor = d; }
- d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
- };
- var stream_1 = __webpack_require__(11);
- /**
- * Represents a virtual connection. Multiple virtual connections may be multiplexed over a single physical socket connection.
- */
- var VirtualConnection = (function (_super) {
- __extends(VirtualConnection, _super);
- function VirtualConnection(_beginWriteCallback) {
- var _this = _super.call(this) || this;
- _this._beginWriteCallback = _beginWriteCallback;
- _this._flowing = false;
- _this._receivedDataQueue = [];
- return _this;
- }
- VirtualConnection.prototype._read = function () {
- this._flowing = true;
- // Keep pushing data until we run out, or the underlying framework asks us to stop.
- // When we finish, the 'flowing' state is detemined by whether more data is still being requested.
- while (this._flowing && this._receivedDataQueue.length > 0) {
- var nextChunk = this._receivedDataQueue.shift();
- this._flowing = this.push(nextChunk);
- }
- };
- VirtualConnection.prototype._write = function (chunk, encodingIfString, callback) {
- if (typeof chunk === 'string') {
- chunk = new Buffer(chunk, encodingIfString);
- }
- this._beginWriteCallback(chunk, callback);
- };
- VirtualConnection.prototype.onReceivedData = function (dataOrNullToSignalEOF) {
- if (this._flowing) {
- this._flowing = this.push(dataOrNullToSignalEOF);
- }
- else {
- this._receivedDataQueue.push(dataOrNullToSignalEOF);
- }
- };
- return VirtualConnection;
- }(stream_1.Duplex));
- exports.VirtualConnection = VirtualConnection;
-
-
-/***/ },
-/* 11 */
-/***/ function(module, exports) {
-
- module.exports = require("stream");
-
-/***/ }
-/******/ ])));
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.NodeServices.Sockets/Microsoft.AspNetCore.NodeServices.Sockets.csproj b/src/Microsoft.AspNetCore.NodeServices.Sockets/Microsoft.AspNetCore.NodeServices.Sockets.csproj
deleted file mode 100644
index 9dab54a..0000000
--- a/src/Microsoft.AspNetCore.NodeServices.Sockets/Microsoft.AspNetCore.NodeServices.Sockets.csproj
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
-
- Socket-based RPC for Microsoft.AspNetCore.NodeServices.
- netstandard2.0
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/Microsoft.AspNetCore.NodeServices.Sockets/PhysicalConnections/NamedPipeConnection.cs b/src/Microsoft.AspNetCore.NodeServices.Sockets/PhysicalConnections/NamedPipeConnection.cs
deleted file mode 100644
index 5fcd667..0000000
--- a/src/Microsoft.AspNetCore.NodeServices.Sockets/PhysicalConnections/NamedPipeConnection.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-using System.IO;
-using System.IO.Pipes;
-using System.Threading.Tasks;
-
-namespace Microsoft.AspNetCore.NodeServices.Sockets.PhysicalConnections
-{
- internal class NamedPipeConnection : StreamConnection
- {
- private bool _disposedValue = false;
- private NamedPipeClientStream _namedPipeClientStream;
-
-#pragma warning disable 1998 // Because in the NET451 code path, there's nothing to await
- public override async Task Open(string address)
- {
- _namedPipeClientStream = new NamedPipeClientStream(
- ".",
- address,
- PipeDirection.InOut,
- PipeOptions.Asynchronous);
-
- await _namedPipeClientStream.ConnectAsync().ConfigureAwait(false);
-
- return _namedPipeClientStream;
- }
-#pragma warning restore 1998
-
- public override void Dispose()
- {
- if (!_disposedValue)
- {
- if (_namedPipeClientStream != null)
- {
- _namedPipeClientStream.Dispose();
- }
-
- _disposedValue = true;
- }
- }
- }
-}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.NodeServices.Sockets/PhysicalConnections/StreamConnection.cs b/src/Microsoft.AspNetCore.NodeServices.Sockets/PhysicalConnections/StreamConnection.cs
deleted file mode 100644
index cbd1f99..0000000
--- a/src/Microsoft.AspNetCore.NodeServices.Sockets/PhysicalConnections/StreamConnection.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-using System;
-using System.IO;
-using System.Threading.Tasks;
-
-namespace Microsoft.AspNetCore.NodeServices.Sockets.PhysicalConnections
-{
- internal abstract class StreamConnection : IDisposable
- {
- public abstract Task Open(string address);
- public abstract void Dispose();
-
- public static StreamConnection Create()
- {
- var useNamedPipes = System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(
- System.Runtime.InteropServices.OSPlatform.Windows);
- if (useNamedPipes)
- {
- return new NamedPipeConnection();
- }
- else
- {
- return new UnixDomainSocketConnection();
- }
- }
- }
-}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.NodeServices.Sockets/PhysicalConnections/UnixDomainSocketConnection.cs b/src/Microsoft.AspNetCore.NodeServices.Sockets/PhysicalConnections/UnixDomainSocketConnection.cs
deleted file mode 100644
index 6e7ebac..0000000
--- a/src/Microsoft.AspNetCore.NodeServices.Sockets/PhysicalConnections/UnixDomainSocketConnection.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-using System.IO;
-using System.Net.Sockets;
-using System.Threading.Tasks;
-
-namespace Microsoft.AspNetCore.NodeServices.Sockets.PhysicalConnections
-{
- internal class UnixDomainSocketConnection : StreamConnection
- {
- private bool _disposedValue = false;
- private NetworkStream _networkStream;
- private Socket _socket;
-
- public override async Task Open(string address)
- {
- var endPoint = new UnixDomainSocketEndPoint("/tmp/" + address);
- _socket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Unspecified);
- await _socket.ConnectAsync(endPoint).ConfigureAwait(false);
- _networkStream = new NetworkStream(_socket);
- return _networkStream;
- }
-
- public override void Dispose()
- {
- if (!_disposedValue)
- {
- if (_networkStream != null)
- {
- _networkStream.Dispose();
- }
-
- if (_socket != null)
- {
- _socket.Dispose();
- }
-
- _disposedValue = true;
- }
- }
- }
-}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.NodeServices.Sockets/PhysicalConnections/UnixDomainSocketEndPoint.cs b/src/Microsoft.AspNetCore.NodeServices.Sockets/PhysicalConnections/UnixDomainSocketEndPoint.cs
deleted file mode 100644
index b001163..0000000
--- a/src/Microsoft.AspNetCore.NodeServices.Sockets/PhysicalConnections/UnixDomainSocketEndPoint.cs
+++ /dev/null
@@ -1,86 +0,0 @@
-using System;
-using System.Net;
-using System.Net.Sockets;
-using System.Text;
-
-namespace Microsoft.AspNetCore.NodeServices.Sockets.PhysicalConnections
-{
- // From System.IO.Pipes/src/System/Net/Sockets/UnixDomainSocketEndPoint.cs (an internal class in System.IO.Pipes)
- internal sealed class UnixDomainSocketEndPoint : EndPoint
- {
- private const AddressFamily EndPointAddressFamily = AddressFamily.Unix;
-
- private static readonly Encoding s_pathEncoding = Encoding.UTF8;
- private static readonly int s_nativePathOffset = 2; // = offsetof(struct sockaddr_un, sun_path). It's the same on Linux and OSX
- private static readonly int s_nativePathLength = 91; // sockaddr_un.sun_path at http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/sys_un.h.html, -1 for terminator
- private static readonly int s_nativeAddressSize = s_nativePathOffset + s_nativePathLength;
-
- private readonly string _path;
- private readonly byte[] _encodedPath;
-
- public UnixDomainSocketEndPoint(string path)
- {
- if (path == null)
- {
- throw new ArgumentNullException(nameof(path));
- }
-
- _path = path;
- _encodedPath = s_pathEncoding.GetBytes(_path);
-
- if (path.Length == 0 || _encodedPath.Length > s_nativePathLength)
- {
- throw new ArgumentOutOfRangeException(nameof(path));
- }
- }
-
- internal UnixDomainSocketEndPoint(SocketAddress socketAddress)
- {
- if (socketAddress == null)
- {
- throw new ArgumentNullException(nameof(socketAddress));
- }
-
- if (socketAddress.Family != EndPointAddressFamily ||
- socketAddress.Size > s_nativeAddressSize)
- {
- throw new ArgumentOutOfRangeException(nameof(socketAddress));
- }
-
- if (socketAddress.Size > s_nativePathOffset)
- {
- _encodedPath = new byte[socketAddress.Size - s_nativePathOffset];
- for (int i = 0; i < _encodedPath.Length; i++)
- {
- _encodedPath[i] = socketAddress[s_nativePathOffset + i];
- }
-
- _path = s_pathEncoding.GetString(_encodedPath, 0, _encodedPath.Length);
- }
- else
- {
- _encodedPath = Array.Empty();
- _path = string.Empty;
- }
- }
-
- public override SocketAddress Serialize()
- {
- var result = new SocketAddress(AddressFamily.Unix, s_nativeAddressSize);
-
- for (int index = 0; index < _encodedPath.Length; index++)
- {
- result[s_nativePathOffset + index] = _encodedPath[index];
- }
- result[s_nativePathOffset + _encodedPath.Length] = 0; // path must be null-terminated
-
- return result;
- }
-
- public override EndPoint Create(SocketAddress socketAddress) => new UnixDomainSocketEndPoint(socketAddress);
-
- public override AddressFamily AddressFamily => EndPointAddressFamily;
-
- public override string ToString() => _path;
- }
-}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.NodeServices.Sockets/SocketNodeInstance.cs b/src/Microsoft.AspNetCore.NodeServices.Sockets/SocketNodeInstance.cs
deleted file mode 100644
index 1e6afd1..0000000
--- a/src/Microsoft.AspNetCore.NodeServices.Sockets/SocketNodeInstance.cs
+++ /dev/null
@@ -1,241 +0,0 @@
-using System.IO;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using Microsoft.AspNetCore.NodeServices.HostingModels;
-using Microsoft.AspNetCore.NodeServices.Sockets.PhysicalConnections;
-using Microsoft.AspNetCore.NodeServices.Sockets.VirtualConnections;
-using Microsoft.Extensions.Logging;
-using Newtonsoft.Json;
-using Newtonsoft.Json.Serialization;
-
-namespace Microsoft.AspNetCore.NodeServices.Sockets
-{
- ///
- /// A specialisation of the OutOfProcessNodeInstance base class that uses a lightweight binary streaming protocol
- /// to perform RPC invocations. The physical transport is Named Pipes on Windows, or Domain Sockets on Linux/Mac.
- /// For details on the binary streaming protocol, see
- /// Microsoft.AspNetCore.NodeServices.HostingModels.VirtualConnections.VirtualConnectionClient.
- /// The advantage versus using HTTP for RPC is that this is faster (not surprisingly - there's much less overhead
- /// because we don't need most of the functionality of HTTP.
- ///
- /// The address of the pipe/socket is selected randomly here on the .NET side and sent to the child process as a
- /// command-line argument (the address space is wide enough that there's no real risk of a clash, unlike when
- /// selecting TCP port numbers).
- ///
- ///
- internal class SocketNodeInstance : OutOfProcessNodeInstance
- {
- private readonly static JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings
- {
- ContractResolver = new CamelCasePropertyNamesContractResolver(),
- TypeNameHandling = TypeNameHandling.None
- };
-
- private readonly static int streamBufferSize = 16 * 1024;
- private readonly static UTF8Encoding utf8EncodingWithoutBom = new UTF8Encoding(false);
-
- private readonly SemaphoreSlim _connectionCreationSemaphore = new SemaphoreSlim(1);
- private bool _connectionHasFailed;
- private StreamConnection _physicalConnection;
- private string _socketAddress;
- private VirtualConnectionClient _virtualConnectionClient;
-
- public SocketNodeInstance(NodeServicesOptions options, string socketAddress)
- : base(
- EmbeddedResourceReader.Read(
- typeof(SocketNodeInstance),
- "/Content/Node/entrypoint-socket.js"),
- options.ProjectPath,
- options.WatchFileExtensions,
- MakeNewCommandLineOptions(socketAddress),
- options.ApplicationStoppingToken,
- options.NodeInstanceOutputLogger,
- options.EnvironmentVariables,
- options.InvocationTimeoutMilliseconds,
- options.LaunchWithDebugging,
- options.DebuggingPort)
- {
- _socketAddress = socketAddress;
- }
-
- protected override async Task InvokeExportAsync(NodeInvocationInfo invocationInfo, CancellationToken cancellationToken)
- {
- if (_connectionHasFailed)
- {
- // _connectionHasFailed implies a protocol-level error. The old instance is no longer of any use.
- var allowConnectionDraining = false;
-
- // This special exception type forces NodeServicesImpl to restart the Node instance
- throw new NodeInvocationException(
- "The SocketNodeInstance socket connection failed. See logs to identify the reason.",
- details: null,
- nodeInstanceUnavailable: true,
- allowConnectionDraining: allowConnectionDraining);
- }
-
- if (_virtualConnectionClient == null)
- {
- // Although we could pass the cancellationToken into EnsureVirtualConnectionClientCreated and
- // have it signal cancellations upstream, that would be a bad thing to do, because all callers
- // wait for the same connection task. There's no reason why the first caller should have the
- // special ability to cancel the connection process in a way that would affect subsequent
- // callers. So, each caller just independently stops awaiting connection if that call is cancelled.
- await ThrowOnCancellation(EnsureVirtualConnectionClientCreated(), cancellationToken);
- }
-
- // For each invocation, we open a new virtual connection. This gives an API equivalent to opening a new
- // physical connection to the child process, but without the overhead of doing so, because it's really
- // just multiplexed into the existing physical connection stream.
- bool shouldDisposeVirtualConnection = true;
- Stream virtualConnection = null;
- try
- {
- virtualConnection = _virtualConnectionClient.OpenVirtualConnection();
-
- // Send request
- WriteJsonLine(virtualConnection, invocationInfo);
-
- // Determine what kind of response format is expected
- if (typeof(T) == typeof(Stream))
- {
- // Pass through streamed binary response
- // It is up to the consumer to dispose this stream, so don't do so here
- shouldDisposeVirtualConnection = false;
- return (T)(object)virtualConnection;
- }
- else
- {
- // Parse and return non-streamed JSON response
- var response = await ReadJsonAsync>(virtualConnection, cancellationToken);
- if (response.ErrorMessage != null)
- {
- throw new NodeInvocationException(response.ErrorMessage, response.ErrorDetails);
- }
-
- return response.Result;
- }
- }
- finally
- {
- if (shouldDisposeVirtualConnection)
- {
- virtualConnection.Dispose();
- }
- }
- }
-
- private async Task EnsureVirtualConnectionClientCreated()
- {
- // Asynchronous equivalent to a 'lock(...) { ... }'
- await _connectionCreationSemaphore.WaitAsync();
- try
- {
- if (_virtualConnectionClient == null)
- {
- _physicalConnection = StreamConnection.Create();
-
- var connection = await _physicalConnection.Open(_socketAddress);
- _virtualConnectionClient = new VirtualConnectionClient(connection);
- _virtualConnectionClient.OnError += (ex) =>
- {
- // This callback is fired only if there's a protocol-level failure (e.g., child process disconnected
- // unexpectedly). It does *not* fire when RPC calls return errors. Since there's been a protocol-level
- // failure, this Node instance is no longer usable and should be discarded.
- _connectionHasFailed = true;
-
- OutputLogger.LogError(0, ex, ex.Message);
- };
- }
- }
- finally
- {
- _connectionCreationSemaphore.Release();
- }
- }
-
- protected override void Dispose(bool disposing)
- {
- if (disposing)
- {
- if (_virtualConnectionClient != null)
- {
- _virtualConnectionClient.Dispose();
- _virtualConnectionClient = null;
- }
-
- if (_physicalConnection != null)
- {
- _physicalConnection.Dispose();
- _physicalConnection = null;
- }
- }
-
- base.Dispose(disposing);
- }
-
- private static void WriteJsonLine(Stream stream, object serializableObject)
- {
- using (var streamWriter = new StreamWriter(stream, utf8EncodingWithoutBom, streamBufferSize, true))
- using (var jsonWriter = new JsonTextWriter(streamWriter))
- {
- jsonWriter.CloseOutput = false;
- jsonWriter.AutoCompleteOnClose = false;
-
- var serializer = JsonSerializer.Create(jsonSerializerSettings);
- serializer.Serialize(jsonWriter, serializableObject);
- jsonWriter.Flush();
-
- streamWriter.WriteLine();
- streamWriter.Flush();
- }
- }
-
- private static async Task ReadJsonAsync(Stream stream, CancellationToken cancellationToken)
- {
- var json = Encoding.UTF8.GetString(await ReadAllBytesAsync(stream, cancellationToken));
- return JsonConvert.DeserializeObject(json, jsonSerializerSettings);
- }
-
- private static async Task ReadAllBytesAsync(Stream input, CancellationToken cancellationToken)
- {
- byte[] buffer = new byte[streamBufferSize];
-
- using (var ms = new MemoryStream())
- {
- int read;
- while ((read = await input.ReadAsync(buffer, 0, buffer.Length, cancellationToken)) > 0)
- {
- ms.Write(buffer, 0, read);
- }
-
- return ms.ToArray();
- }
- }
-
- private static string MakeNewCommandLineOptions(string listenAddress)
- {
- return $"--listenAddress {listenAddress}";
- }
-
- private static Task ThrowOnCancellation(Task task, CancellationToken cancellationToken)
- {
- return task.IsCompleted
- ? task // If the task is already completed, no need to wrap it in a further layer of task
- : task.ContinueWith(
- _ => {}, // If the task completes, allow execution to continue
- cancellationToken,
- TaskContinuationOptions.ExecuteSynchronously,
- TaskScheduler.Default);
- }
-
-#pragma warning disable 649 // These properties are populated via JSON deserialization
- private class RpcJsonResponse
- {
- public TResult Result { get; set; }
- public string ErrorMessage { get; set; }
- public string ErrorDetails { get; set; }
- }
-#pragma warning restore 649
- }
-}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.NodeServices.Sockets/SocketNodeServicesOptionsExtensions.cs b/src/Microsoft.AspNetCore.NodeServices.Sockets/SocketNodeServicesOptionsExtensions.cs
deleted file mode 100644
index 4535638..0000000
--- a/src/Microsoft.AspNetCore.NodeServices.Sockets/SocketNodeServicesOptionsExtensions.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-using System;
-
-namespace Microsoft.AspNetCore.NodeServices.Sockets
-{
- ///
- /// Extension methods that help with populating a object.
- ///
- public static class NodeServicesOptionsExtensions
- {
- ///
- /// Configures the service so that it will use out-of-process
- /// Node.js instances and perform RPC calls over binary sockets (on Windows, this is
- /// implemented as named pipes; on other platforms it uses domain sockets).
- ///
- public static void UseSocketHosting(this NodeServicesOptions options)
- {
- var pipeName = "pni-" + Guid.NewGuid().ToString("D"); // Arbitrary non-clashing string
- options.NodeInstanceFactory = () => new SocketNodeInstance(options, pipeName);
- }
- }
-}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.NodeServices.Sockets/TypeScript/SocketNodeInstanceEntryPoint.ts b/src/Microsoft.AspNetCore.NodeServices.Sockets/TypeScript/SocketNodeInstanceEntryPoint.ts
deleted file mode 100644
index dccc63b..0000000
--- a/src/Microsoft.AspNetCore.NodeServices.Sockets/TypeScript/SocketNodeInstanceEntryPoint.ts
+++ /dev/null
@@ -1,79 +0,0 @@
-// Limit dependencies to core Node modules. This means the code in this file has to be very low-level and unattractive,
-// but simplifies things for the consumer of this module.
-import '../../Microsoft.AspNetCore.NodeServices/TypeScript/Util/OverrideStdOutputs';
-import * as net from 'net';
-import * as path from 'path';
-import * as readline from 'readline';
-import { Duplex } from 'stream';
-import { parseArgs } from '../../Microsoft.AspNetCore.NodeServices/TypeScript/Util/ArgsUtil';
-import { exitWhenParentExits } from '../../Microsoft.AspNetCore.NodeServices/TypeScript/Util/ExitWhenParentExits';
-import * as virtualConnectionServer from './VirtualConnections/VirtualConnectionServer';
-
-// Webpack doesn't support dynamic requires for files not present at compile time, so grab a direct
-// reference to Node's runtime 'require' function.
-const dynamicRequire: (name: string) => any = eval('require');
-
-// Signal to the .NET side when we're ready to accept invocations
-const server = net.createServer().on('listening', () => {
- console.log('[Microsoft.AspNetCore.NodeServices:Listening]');
-});
-
-// Each virtual connection represents a separate invocation
-virtualConnectionServer.createInterface(server).on('connection', (connection: Duplex) => {
- readline.createInterface(connection, null).on('line', line => {
- try {
- // Get a reference to the function to invoke
- const invocation = JSON.parse(line) as RpcInvocation;
- const invokedModule = dynamicRequire(path.resolve(process.cwd(), invocation.moduleName));
- const invokedFunction = invocation.exportedFunctionName ? invokedModule[invocation.exportedFunctionName] : invokedModule;
-
- // Prepare a callback for accepting non-streamed JSON responses
- let hasInvokedCallback = false;
- const invocationCallback = (errorValue, successValue) => {
- if (hasInvokedCallback) {
- throw new Error('Cannot supply more than one result. The callback has already been invoked,'
- + ' or the result stream has already been accessed');
- }
-
- hasInvokedCallback = true;
- connection.end(JSON.stringify({
- result: successValue,
- errorMessage: errorValue && (errorValue.message || errorValue),
- errorDetails: errorValue && (errorValue.stack || null)
- }));
- };
-
- // Also support streamed binary responses
- Object.defineProperty(invocationCallback, 'stream', {
- enumerable: true,
- get: (): Duplex => {
- hasInvokedCallback = true;
- return connection;
- }
- });
-
- // Actually invoke it, passing through any supplied args
- invokedFunction.apply(null, [invocationCallback].concat(invocation.args));
- } catch (ex) {
- connection.end(JSON.stringify({
- errorMessage: ex.message,
- errorDetails: ex.stack
- }));
- }
- });
-});
-
-// Begin listening now. The underlying transport varies according to the runtime platform.
-// On Windows it's Named Pipes; on Linux/OSX it's Domain Sockets.
-const useWindowsNamedPipes = /^win/.test(process.platform);
-const parsedArgs = parseArgs(process.argv);
-const listenAddress = (useWindowsNamedPipes ? '\\\\.\\pipe\\' : '/tmp/') + parsedArgs.listenAddress;
-server.listen(listenAddress);
-
-exitWhenParentExits(parseInt(parsedArgs.parentPid), /* ignoreSigint */ true);
-
-interface RpcInvocation {
- moduleName: string;
- exportedFunctionName: string;
- args: any[];
-}
diff --git a/src/Microsoft.AspNetCore.NodeServices.Sockets/TypeScript/VirtualConnections/VirtualConnection.ts b/src/Microsoft.AspNetCore.NodeServices.Sockets/TypeScript/VirtualConnections/VirtualConnection.ts
deleted file mode 100644
index de71f60..0000000
--- a/src/Microsoft.AspNetCore.NodeServices.Sockets/TypeScript/VirtualConnections/VirtualConnection.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import { Duplex } from 'stream';
-
-export type EndWriteCallback = (error?: any) => void;
-export type BeginWriteCallback = (data: Buffer, callback: EndWriteCallback) => void;
-
-/**
- * Represents a virtual connection. Multiple virtual connections may be multiplexed over a single physical socket connection.
- */
-export class VirtualConnection extends Duplex {
- private _flowing = false;
- private _receivedDataQueue: Buffer[] = [];
-
- constructor(private _beginWriteCallback: BeginWriteCallback) {
- super();
- }
-
- public _read() {
- this._flowing = true;
-
- // Keep pushing data until we run out, or the underlying framework asks us to stop.
- // When we finish, the 'flowing' state is detemined by whether more data is still being requested.
- while (this._flowing && this._receivedDataQueue.length > 0) {
- const nextChunk = this._receivedDataQueue.shift();
- this._flowing = this.push(nextChunk);
- }
- }
-
- public _write(chunk: Buffer | string, encodingIfString: string, callback: EndWriteCallback) {
- if (typeof chunk === 'string') {
- chunk = new Buffer(chunk as string, encodingIfString);
- }
-
- this._beginWriteCallback(chunk as Buffer, callback);
- }
-
- public onReceivedData(dataOrNullToSignalEOF: Buffer) {
- if (this._flowing) {
- this._flowing = this.push(dataOrNullToSignalEOF);
- } else {
- this._receivedDataQueue.push(dataOrNullToSignalEOF);
- }
- }
-}
diff --git a/src/Microsoft.AspNetCore.NodeServices.Sockets/TypeScript/VirtualConnections/VirtualConnectionServer.ts b/src/Microsoft.AspNetCore.NodeServices.Sockets/TypeScript/VirtualConnections/VirtualConnectionServer.ts
deleted file mode 100644
index 76b833d..0000000
--- a/src/Microsoft.AspNetCore.NodeServices.Sockets/TypeScript/VirtualConnections/VirtualConnectionServer.ts
+++ /dev/null
@@ -1,199 +0,0 @@
-import { Server, Socket } from 'net';
-import { EventEmitter } from 'events';
-import { Duplex } from 'stream';
-import { VirtualConnection, EndWriteCallback } from './VirtualConnection';
-
-// Keep this in sync with the equivalent constant in the .NET code. Both sides split up their transmissions into frames with this max length,
-// and both will reject longer frames.
-const MaxFrameBodyLength = 16 * 1024;
-
-/**
- * Accepts connections to a net.Server and adapts them to behave as multiplexed connections. That is, for each physical socket connection,
- * we track a list of 'virtual connections' whose API is a Duplex stream. The remote clients may open and close as many virtual connections
- * as they wish, reading and writing to them independently, without the overhead of establishing new physical connections each time.
- */
-export function createInterface(server: Server): EventEmitter {
- const emitter = new EventEmitter();
-
- server.on('connection', (socket: Socket) => {
- // For each physical socket connection, maintain a set of virtual connections. Issue a notification whenever
- // a new virtual connections is opened.
- const childSockets = new VirtualConnectionsCollection(socket, virtualConnection => {
- emitter.emit('connection', virtualConnection);
- });
- });
-
- return emitter;
-}
-
-/**
- * Tracks the 'virtual connections' associated with a single physical socket connection.
- */
-class VirtualConnectionsCollection {
- private _currentFrameHeader: FrameHeader = null;
- private _virtualConnections: { [id: string]: VirtualConnection } = {};
-
- constructor(private _socket: Socket, private _onVirtualConnectionCallback: (virtualConnection: Duplex) => void) {
- // If the remote end closes the physical socket, treat all the virtual connections as being closed remotely too
- this._socket.on('close', () => {
- Object.getOwnPropertyNames(this._virtualConnections).forEach(id => {
- // A 'null' frame signals that the connection was closed remotely
- this._virtualConnections[id].onReceivedData(null);
- });
- });
-
- this._socket.on('readable', this._onIncomingDataAvailable.bind(this));
- }
-
- /**
- * This is called whenever the underlying socket signals that it may have some data available to read. It will synchronously read as many
- * message frames as it can from the underlying socket, opens virtual connections as needed, and dispatches data to them.
- */
- private _onIncomingDataAvailable() {
- let exhaustedAllData = false;
-
- while (!exhaustedAllData) {
- // We might already have a pending frame header from the previous time this method ran, but if not, that's the next thing we need to read
- if (this._currentFrameHeader === null) {
- this._currentFrameHeader = this._readNextFrameHeader();
- }
-
- if (this._currentFrameHeader === null) {
- // There's not enough data to fill a frameheader, so wait until more arrives later
- // The next attempt to read from the socket will start from the same place this one did (incomplete reads don't consume any data)
- exhaustedAllData = true;
- } else {
- const frameBodyLength = this._currentFrameHeader.bodyLength;
- const frameBodyOrNull: Buffer = frameBodyLength > 0 ? this._socket.read(this._currentFrameHeader.bodyLength) : null;
- if (frameBodyOrNull !== null || frameBodyLength === 0) {
- // We have a complete frame header+body pair, so we can now dispatch this to a virtual connection. We set _currentFrameHeader back to null
- // so that the next thing we try to read is the next frame header.
- const headerCopy = this._currentFrameHeader;
- this._currentFrameHeader = null;
- this._onReceivedCompleteFrame(headerCopy, frameBodyOrNull);
- } else {
- // There's not enough data to fill the pending frame body, so wait until more arrives later
- // The next attempt to read from the socket will start from the same place this one did (incomplete reads don't consume any data)
- exhaustedAllData = true;
- }
- }
- }
- }
-
- private _onReceivedCompleteFrame(header: FrameHeader, bodyIfNotEmpty: Buffer) {
- // An incoming zero-length frame signals that there's no more data to read.
- // Signal this to the Node stream APIs by pushing a 'null' chunk to it.
- const virtualConnection = this._getOrOpenVirtualConnection(header);
- virtualConnection.onReceivedData(header.bodyLength > 0 ? bodyIfNotEmpty : null);
- }
-
- private _getOrOpenVirtualConnection(header: FrameHeader) {
- if (this._virtualConnections.hasOwnProperty(header.connectionIdString)) {
- // It's an existing virtual connection
- return this._virtualConnections[header.connectionIdString];
- } else {
- // It's a new one
- return this._openVirtualConnection(header);
- }
- }
-
- private _openVirtualConnection(header: FrameHeader) {
- const beginWriteCallback = (data, writeCompletedCallback) => {
- // Only send nonempty frames, since empty ones are a signal to close the virtual connection
- if (data.length > 0) {
- this._sendFrame(header.connectionIdBinary, data, writeCompletedCallback);
- }
- };
-
- const newVirtualConnection = new VirtualConnection(beginWriteCallback);
- newVirtualConnection.on('end', () => {
- // The virtual connection was closed remotely. Clean up locally.
- this._onVirtualConnectionWasClosed(header.connectionIdString);
- });
- newVirtualConnection.on('finish', () => {
- // The virtual connection was closed locally. Clean up locally, and notify the remote that we're done.
- this._onVirtualConnectionWasClosed(header.connectionIdString);
- this._sendFrame(header.connectionIdBinary, new Buffer(0));
- });
-
- this._virtualConnections[header.connectionIdString] = newVirtualConnection;
- this._onVirtualConnectionCallback(newVirtualConnection);
- return newVirtualConnection;
- }
-
- /**
- * Attempts to read a complete frame header, synchronously, from the underlying socket.
- * If not enough data is available synchronously, returns null without consuming any data from the socket.
- */
- private _readNextFrameHeader(): FrameHeader {
- const headerBuf: Buffer = this._socket.read(12);
- if (headerBuf !== null) {
- // We have enough data synchronously
- const connectionIdBinary = headerBuf.slice(0, 8);
- const connectionIdString = connectionIdBinary.toString('hex');
- const bodyLength = headerBuf.readInt32LE(8);
- if (bodyLength < 0 || bodyLength > MaxFrameBodyLength) {
- // Throwing here is going to bring down the whole process, so this cannot be allowed to happen in real use.
- // But it won't happen in real use, because this is only used with our .NET client, which doesn't violate this rule.
- throw new Error('Illegal frame body length: ' + bodyLength);
- }
-
- return { connectionIdBinary, connectionIdString, bodyLength };
- } else {
- // Not enough bytes are available synchronously, so none were consumed
- return null;
- }
- }
-
- private _sendFrame(connectionIdBinary: Buffer, data: Buffer, callback?: EndWriteCallback) {
- // For all sends other than the last one, only invoke the callback if it failed.
- // Also, only invoke the callback at most once.
- let hasInvokedCallback = false;
- const finalCallback: EndWriteCallback = callback && (error => {
- if (!hasInvokedCallback) {
- hasInvokedCallback = true;
- callback(error);
- }
- });
- const notFinalCallback: EndWriteCallback = callback && (error => {
- if (error) {
- finalCallback(error);
- }
- });
-
- // The amount of data we're writing might exceed MaxFrameBodyLength, so split into frames as needed.
- // Note that we always send at least one frame, even if it's empty (because that's the close-virtual-connection signal).
- // If needed, this could be changed to send frames asynchronously, so that large sends could proceed in parallel
- // (though that would involve making a clone of 'data', to avoid the risk of it being mutated during the send).
- let bytesSent = 0;
- do {
- const nextFrameBodyLength = Math.min(MaxFrameBodyLength, data.length - bytesSent);
- const isFinalChunk = (bytesSent + nextFrameBodyLength) === data.length;
- this._socket.write(connectionIdBinary, notFinalCallback);
- this._sendInt32LE(nextFrameBodyLength, notFinalCallback);
- this._socket.write(data.slice(bytesSent, bytesSent + nextFrameBodyLength), isFinalChunk ? finalCallback : notFinalCallback);
- bytesSent += nextFrameBodyLength;
- } while (bytesSent < data.length);
- }
-
- /**
- * Sends a number serialized in the correct format for .NET to receive as a System.Int32
- */
- private _sendInt32LE(value: number, callback?: EndWriteCallback) {
- const buf = new Buffer(4);
- buf.writeInt32LE(value, 0);
- this._socket.write(buf, callback);
- }
-
- private _onVirtualConnectionWasClosed(id: string) {
- if (this._virtualConnections.hasOwnProperty(id)) {
- delete this._virtualConnections[id];
- }
- }
-}
-
-interface FrameHeader {
- connectionIdBinary: Buffer;
- connectionIdString: string;
- bodyLength: number;
-}
diff --git a/src/Microsoft.AspNetCore.NodeServices.Sockets/TypeScript/tsconfig.json b/src/Microsoft.AspNetCore.NodeServices.Sockets/TypeScript/tsconfig.json
deleted file mode 100644
index 896fc88..0000000
--- a/src/Microsoft.AspNetCore.NodeServices.Sockets/TypeScript/tsconfig.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "compilerOptions": {
- "target": "es3",
- "module": "commonjs",
- "moduleResolution": "node",
- "types": ["node"]
- },
- "exclude": [
- "node_modules"
- ]
-}
diff --git a/src/Microsoft.AspNetCore.NodeServices.Sockets/VirtualConnections/VirtualConnection.cs b/src/Microsoft.AspNetCore.NodeServices.Sockets/VirtualConnections/VirtualConnection.cs
deleted file mode 100644
index 391b1f7..0000000
--- a/src/Microsoft.AspNetCore.NodeServices.Sockets/VirtualConnections/VirtualConnection.cs
+++ /dev/null
@@ -1,150 +0,0 @@
-using System;
-using System.IO;
-using System.Threading;
-using System.Threading.Tasks;
-using System.Threading.Tasks.Dataflow;
-
-namespace Microsoft.AspNetCore.NodeServices.Sockets.VirtualConnections
-{
- ///
- /// A virtual read/write connection, typically to a remote process. Multiple virtual connections can be
- /// multiplexed over a single physical connection (e.g., a named pipe, domain socket, or TCP socket).
- ///
- internal class VirtualConnection : Stream
- {
- private readonly static Task CompletedTask = Task.CompletedTask;
- private VirtualConnectionClient _host;
- private readonly BufferBlock _receivedDataQueue = new BufferBlock();
- private ArraySegment _receivedDataNotYetUsed;
- private bool _wasClosedByRemote;
- private bool _isDisposed;
-
- public VirtualConnection(long id, VirtualConnectionClient host)
- {
- Id = id;
- _host = host;
- }
-
- public long Id { get; }
-
- public override bool CanRead { get { return true; } }
- public override bool CanSeek { get { return false; } }
- public override bool CanWrite { get { return true; } }
-
- public override long Length
- {
- get { throw new NotImplementedException(); }
- }
-
- public override long Position
- {
- get { throw new NotImplementedException(); }
- set { throw new NotImplementedException(); }
- }
-
- public override void Flush()
- {
- // We're auto-flushing, so this is a no-op.
- }
-
- public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
- {
- if (_wasClosedByRemote)
- {
- return 0;
- }
-
- var bytesRead = 0;
- while (true)
- {
- // Pull as many applicable bytes as we can out of receivedDataNotYetUsed, then update its offset/length
- int bytesToExtract = Math.Min(count - bytesRead, _receivedDataNotYetUsed.Count);
- if (bytesToExtract > 0)
- {
- Buffer.BlockCopy(_receivedDataNotYetUsed.Array, _receivedDataNotYetUsed.Offset, buffer, bytesRead, bytesToExtract);
- _receivedDataNotYetUsed = new ArraySegment(_receivedDataNotYetUsed.Array, _receivedDataNotYetUsed.Offset + bytesToExtract, _receivedDataNotYetUsed.Count - bytesToExtract);
- bytesRead += bytesToExtract;
- }
-
- // If we've completely filled the output buffer, we're done
- if (bytesRead == count)
- {
- return bytesRead;
- }
-
- // We haven't yet filled the output buffer, so we must have exhausted receivedDataNotYetUsed instead.
- // We want to get the next block of data from the underlying queue.
- byte[] nextReceivedBlock;
- if (bytesRead > 0)
- {
- if (!_receivedDataQueue.TryReceive(null, out nextReceivedBlock))
- {
- // No more data is available synchronously, and we already have some data, so we can stop now
- return bytesRead;
- }
- }
- else
- {
- // Since we don't yet have anything, wait for the underlying source
- nextReceivedBlock = await _receivedDataQueue.ReceiveAsync(cancellationToken);
- }
-
- if (nextReceivedBlock.Length == 0)
- {
- // A zero-length block signals that the remote regards this virtual connection as closed
- _wasClosedByRemote = true;
- return bytesRead;
- }
- else
- {
- // We got some more data, so can continue trying to fill the output buffer
- _receivedDataNotYetUsed = new ArraySegment(nextReceivedBlock, 0, nextReceivedBlock.Length);
- }
- }
- }
-
- public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
- {
- if (_wasClosedByRemote)
- {
- throw new InvalidOperationException("The connection was already closed by the remote party");
- }
-
- return count > 0 ? _host.WriteAsync(Id, buffer, offset, count, cancellationToken) : CompletedTask;
- }
-
- public override int Read(byte[] buffer, int offset, int count)
- {
- return ReadAsync(buffer, offset, count, CancellationToken.None).Result;
- }
-
- public override long Seek(long offset, SeekOrigin origin)
- {
- throw new NotImplementedException();
- }
-
- public override void SetLength(long value)
- {
- throw new NotImplementedException();
- }
-
- public override void Write(byte[] buffer, int offset, int count)
- {
- WriteAsync(buffer, offset, count, CancellationToken.None).Wait();
- }
-
- protected override void Dispose(bool disposing)
- {
- if (disposing && !_isDisposed)
- {
- _isDisposed = true;
- _host.CloseInnerStream(Id, _wasClosedByRemote);
- }
- }
-
- public async Task AddDataToQueue(byte[] data)
- {
- await _receivedDataQueue.SendAsync(data);
- }
- }
-}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.NodeServices.Sockets/VirtualConnections/VirtualConnectionClient.cs b/src/Microsoft.AspNetCore.NodeServices.Sockets/VirtualConnections/VirtualConnectionClient.cs
deleted file mode 100644
index 48ec967..0000000
--- a/src/Microsoft.AspNetCore.NodeServices.Sockets/VirtualConnections/VirtualConnectionClient.cs
+++ /dev/null
@@ -1,238 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace Microsoft.AspNetCore.NodeServices.Sockets.VirtualConnections
-{
- ///
- /// A callback that will be invoked if the encounters a read error.
- ///
- ///
- public delegate void VirtualConnectionReadErrorHandler(Exception ex);
-
- ///
- /// Wraps an underlying physical read/write stream (e.g., named pipes, domain sockets, or TCP sockets) and
- /// exposes an API for making 'virtual connections', which act as independent read/write streams.
- /// Traffic over these virtual connections is multiplexed over the underlying physical stream. This is useful
- /// for fast stream-based inter-process communication because it avoids the overhead of opening a new physical
- /// connection each time a new communication channel is needed.
- ///
- internal class VirtualConnectionClient : IDisposable
- {
- internal const int MaxFrameBodyLength = 16 * 1024;
-
- public event VirtualConnectionReadErrorHandler OnError;
-
- private Stream _underlyingTransport;
- private Dictionary _activeInnerStreams;
- private long _nextInnerStreamId;
- private readonly SemaphoreSlim _streamWriterSemaphore = new SemaphoreSlim(1);
- private readonly object _readControlLock = new object();
- private Exception _readLoopExitedWithException;
- private readonly CancellationTokenSource _disposalCancellatonToken = new CancellationTokenSource();
- private bool _disposedValue = false;
-
- public VirtualConnectionClient(Stream underlyingTransport)
- {
- _underlyingTransport = underlyingTransport;
- _activeInnerStreams = new Dictionary();
-
- RunReadLoop();
- }
-
- public Stream OpenVirtualConnection()
- {
- // Improve discoverability of read-loop errors (in case the developer doesn't add an OnError listener)
- ThrowIfReadLoopFailed();
-
- var id = Interlocked.Increment(ref _nextInnerStreamId);
- var newInnerStream = new VirtualConnection(id, this);
- lock (_activeInnerStreams)
- {
- _activeInnerStreams.Add(id, newInnerStream);
- }
-
- return newInnerStream;
- }
-
- // It's async void because nothing waits for it to finish (it continues indefinitely). It signals any errors via
- // a separate channel.
- private async void RunReadLoop()
- {
- try
- {
- while (!_disposalCancellatonToken.IsCancellationRequested)
- {
- var remoteIsStillConnected = await ProcessNextFrameAsync();
- if (!remoteIsStillConnected)
- {
- CloseAllActiveStreams();
- }
- }
- }
- catch (Exception ex)
- {
- // Not all underlying transports correctly honor cancellation tokens. For example,
- // DomainSocketStreamTransport's ReadAsync ignores them, so we only know to stop
- // the read loop when the underlying stream is disposed and then it throws ObjectDisposedException.
- if (!(ex is TaskCanceledException || ex is ObjectDisposedException))
- {
- _readLoopExitedWithException = ex;
-
- var evt = OnError;
- if (evt != null)
- {
- evt(ex);
- }
- }
- }
- }
-
- private async Task ProcessNextFrameAsync()
- {
- // First read frame header
- var frameHeaderBuffer = await ReadExactLength(12);
- if (frameHeaderBuffer == null)
- {
- return false; // Underlying stream was closed
- }
-
- // Parse frame header, then read the frame body
- long streamId = BitConverter.ToInt64(frameHeaderBuffer, 0);
- int frameBodyLength = BitConverter.ToInt32(frameHeaderBuffer, 8);
- if (frameBodyLength < 0 || frameBodyLength > MaxFrameBodyLength)
- {
- throw new InvalidDataException("Illegal frame length: " + frameBodyLength);
- }
-
- var frameBody = await ReadExactLength(frameBodyLength);
- if (frameBody == null)
- {
- return false; // Underlying stream was closed
- }
-
- // Dispatch the frame to the relevant inner stream
- VirtualConnection innerStream;
- lock (_activeInnerStreams)
- {
- _activeInnerStreams.TryGetValue(streamId, out innerStream);
- }
-
- if (innerStream != null)
- {
- await innerStream.AddDataToQueue(frameBody);
- }
-
- return true;
- }
-
- private async Task ReadExactLength(int lengthToRead) {
- byte[] buffer = new byte[lengthToRead];
- var totalBytesRead = 0;
- var ct = _disposalCancellatonToken.Token;
- while (totalBytesRead < lengthToRead)
- {
- var chunkLengthRead = await _underlyingTransport.ReadAsync(buffer, totalBytesRead, lengthToRead - totalBytesRead, ct);
- if (chunkLengthRead == 0)
- {
- // Underlying stream was closed
- return null;
- }
-
- totalBytesRead += chunkLengthRead;
- }
-
- return buffer;
- }
-
- private void CloseAllActiveStreams()
- {
- IList innerStreamsCopy;
-
- // Only hold the lock while cloning the list of inner streams. Release the lock before
- // actually disposing them, because each 'dispose' call will try to take another lock
- // so it can remove that inner stream from activeInnerStreams.
- lock (_activeInnerStreams)
- {
- innerStreamsCopy = _activeInnerStreams.Values.ToList();
- }
-
- foreach (var stream in innerStreamsCopy)
- {
- stream.Dispose();
- }
- }
-
- public void Dispose()
- {
- if (!_disposedValue)
- {
- _disposedValue = true;
-
- _disposalCancellatonToken.Cancel(); // Stops the read loop
- CloseAllActiveStreams();
- }
- }
-
- public async Task WriteAsync(long innerStreamId, byte[] data, int offset, int count, CancellationToken cancellationToken)
- {
- // In case the amount of data to be sent exceeds the max frame length, split it into separate frames
- // Note that we always send at least one frame, even if it's empty, because the zero-length frame is the signal to close a virtual connection
- // (hence 'do..while' instead of just 'while').
- int bytesWritten = 0;
- do {
- // Improve discoverability of read-loop errors (in case the developer doesn't add an OnError listener)
- ThrowIfReadLoopFailed();
-
- // Hold the write lock only for the time taken to send a single frame, not all frames, to allow large sends to be proceed in parallel
- await _streamWriterSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
- try
- {
- // Write stream ID, then length prefix, then chunk payload, then flush
- var nextChunkBodyLength = Math.Min(MaxFrameBodyLength, count - bytesWritten);
- await _underlyingTransport.WriteAsync(BitConverter.GetBytes(innerStreamId), 0, 8, cancellationToken).ConfigureAwait(false);
- await _underlyingTransport.WriteAsync(BitConverter.GetBytes(nextChunkBodyLength), 0, 4, cancellationToken).ConfigureAwait(false);
-
- if (nextChunkBodyLength > 0)
- {
- await _underlyingTransport.WriteAsync(data, offset + bytesWritten, nextChunkBodyLength, cancellationToken).ConfigureAwait(false);
- bytesWritten += nextChunkBodyLength;
- }
-
- await _underlyingTransport.FlushAsync(cancellationToken).ConfigureAwait(false);
- }
- finally
- {
- _streamWriterSemaphore.Release();
- }
- } while (bytesWritten < count);
- }
-
- public void CloseInnerStream(long innerStreamId, bool isAlreadyClosedRemotely)
- {
- lock (_activeInnerStreams)
- {
- if (_activeInnerStreams.ContainsKey(innerStreamId))
- {
- _activeInnerStreams.Remove(innerStreamId);
- }
- }
-
- if (!isAlreadyClosedRemotely) {
- // Also notify the remote that this innerstream is closed
- WriteAsync(innerStreamId, new byte[0], 0, 0, new CancellationToken()).Wait();
- }
- }
-
- private void ThrowIfReadLoopFailed()
- {
- if (_readLoopExitedWithException != null)
- {
- throw new AggregateException("The connection failed - see InnerException for details.", _readLoopExitedWithException);
- }
- }
- }
-}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.NodeServices.Sockets/package.json b/src/Microsoft.AspNetCore.NodeServices.Sockets/package.json
deleted file mode 100644
index 5f8648c..0000000
--- a/src/Microsoft.AspNetCore.NodeServices.Sockets/package.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "name": "nodeservices.sockets",
- "version": "1.0.0",
- "description": "This is not really an NPM package and will not be published. This file exists only to reference compilation tools.",
- "main": "index.js",
- "scripts": {
- "test": "echo \"Error: no test specified\" && exit 1",
- "build": "./node_modules/.bin/webpack"
- },
- "author": "Microsoft",
- "license": "Apache-2.0",
- "devDependencies": {
- "@types/node": "^6.0.42",
- "ts-loader": "^0.8.2",
- "typescript": "^2.0.0",
- "webpack": "^1.13.1"
- }
-}
diff --git a/src/Microsoft.AspNetCore.NodeServices.Sockets/webpack.config.js b/src/Microsoft.AspNetCore.NodeServices.Sockets/webpack.config.js
deleted file mode 100644
index c11ba64..0000000
--- a/src/Microsoft.AspNetCore.NodeServices.Sockets/webpack.config.js
+++ /dev/null
@@ -1,20 +0,0 @@
-module.exports = {
- target: 'node',
- externals: ['fs', 'net', 'events', 'readline', 'stream'],
- resolve: {
- extensions: [ '.ts' ]
- },
- module: {
- loaders: [
- { test: /\.ts$/, loader: 'ts-loader' },
- ]
- },
- entry: {
- 'entrypoint-socket': ['./TypeScript/SocketNodeInstanceEntryPoint'],
- },
- output: {
- libraryTarget: 'commonjs',
- path: './Content/Node',
- filename: '[name].js'
- }
-};
diff --git a/src/Microsoft.AspNetCore.NodeServices/.gitignore b/src/Microsoft.AspNetCore.NodeServices/.gitignore
deleted file mode 100644
index 98edee7..0000000
--- a/src/Microsoft.AspNetCore.NodeServices/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-/bin/
-/node_modules/
-yarn.lock
diff --git a/src/Microsoft.AspNetCore.NodeServices/Configuration/NodeServicesFactory.cs b/src/Microsoft.AspNetCore.NodeServices/Configuration/NodeServicesFactory.cs
deleted file mode 100644
index 8432158..0000000
--- a/src/Microsoft.AspNetCore.NodeServices/Configuration/NodeServicesFactory.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using System;
-
-namespace Microsoft.AspNetCore.NodeServices
-{
- ///
- /// Supplies INodeServices instances.
- ///
- public static class NodeServicesFactory
- {
- ///
- /// Create an instance according to the supplied options.
- ///
- /// Options for creating the instance.
- /// An instance.
- public static INodeServices CreateNodeServices(NodeServicesOptions options)
- {
- if (options == null)
- {
- throw new ArgumentNullException(nameof (options));
- }
-
- return new NodeServicesImpl(options.NodeInstanceFactory);
- }
- }
-}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.NodeServices/Configuration/NodeServicesOptions.cs b/src/Microsoft.AspNetCore.NodeServices/Configuration/NodeServicesOptions.cs
deleted file mode 100644
index 384abad..0000000
--- a/src/Microsoft.AspNetCore.NodeServices/Configuration/NodeServicesOptions.cs
+++ /dev/null
@@ -1,114 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Threading;
-using Microsoft.AspNetCore.NodeServices.HostingModels;
-using Microsoft.Extensions.Logging;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.AspNetCore.Hosting;
-using Microsoft.Extensions.Logging.Console;
-
-namespace Microsoft.AspNetCore.NodeServices
-{
- ///
- /// Describes options used to configure an instance.
- ///
- public class NodeServicesOptions
- {
- internal const string TimeoutConfigPropertyName = nameof(InvocationTimeoutMilliseconds);
- private const int DefaultInvocationTimeoutMilliseconds = 60 * 1000;
- private const string LogCategoryName = "Microsoft.AspNetCore.NodeServices";
- private static readonly string[] DefaultWatchFileExtensions = { ".js", ".jsx", ".ts", ".tsx", ".json", ".html" };
-
- ///
- /// Creates a new instance of .
- ///
- /// The .
- public NodeServicesOptions(IServiceProvider serviceProvider)
- {
- if (serviceProvider == null)
- {
- throw new ArgumentNullException(nameof (serviceProvider));
- }
-
- EnvironmentVariables = new Dictionary();
- InvocationTimeoutMilliseconds = DefaultInvocationTimeoutMilliseconds;
- WatchFileExtensions = (string[])DefaultWatchFileExtensions.Clone();
-
- var hostEnv = serviceProvider.GetService();
- if (hostEnv != null)
- {
- // In an ASP.NET environment, we can use the IHostingEnvironment data to auto-populate a few
- // things that you'd otherwise have to specify manually
- ProjectPath = hostEnv.ContentRootPath;
- EnvironmentVariables["NODE_ENV"] = hostEnv.IsDevelopment() ? "development" : "production"; // De-facto standard values for Node
- }
- else
- {
- ProjectPath = Directory.GetCurrentDirectory();
- }
-
- var applicationLifetime = serviceProvider.GetService();
- if (applicationLifetime != null)
- {
- ApplicationStoppingToken = applicationLifetime.ApplicationStopping;
- }
-
- // If the DI system gives us a logger, use it. Otherwise, set up a default one.
- var loggerFactory = serviceProvider.GetService();
- NodeInstanceOutputLogger = loggerFactory != null
- ? loggerFactory.CreateLogger(LogCategoryName)
- : new ConsoleLogger(LogCategoryName, null, false);
-
- // By default, we use this package's built-in out-of-process-via-HTTP hosting/transport
- this.UseHttpHosting();
- }
-
- ///
- /// Specifies how to construct Node.js instances. An encapsulates all details about
- /// how Node.js instances are launched and communicated with. A new will be created
- /// automatically if the previous instance has terminated (e.g., because a source file changed).
- ///
- public Func NodeInstanceFactory { get; set; }
-
- ///
- /// If set, overrides the path to the root of your application. This path is used when locating Node.js modules relative to your project.
- ///
- public string ProjectPath { get; set; }
-
- ///
- /// If set, the Node.js instance should restart when any matching file on disk within your project changes.
- ///
- public string[] WatchFileExtensions { get; set; }
-
- ///
- /// The Node.js instance's stdout/stderr will be redirected to this .
- ///
- public ILogger NodeInstanceOutputLogger { get; set; }
-
- ///
- /// If true, the Node.js instance will accept incoming V8 debugger connections (e.g., from node-inspector).
- ///
- public bool LaunchWithDebugging { get; set; }
-
- ///
- /// If is true, the Node.js instance will listen for V8 debugger connections on this port.
- ///
- public int DebuggingPort { get; set; }
-
- ///
- /// If set, starts the Node.js instance with the specified environment variables.
- ///
- public IDictionary EnvironmentVariables { get; set; }
-
- ///
- /// Specifies the maximum duration, in milliseconds, that your .NET code should wait for Node.js RPC calls to return.
- ///
- public int InvocationTimeoutMilliseconds { get; set; }
-
- ///
- /// A token that indicates when the host application is stopping.
- ///
- public CancellationToken ApplicationStoppingToken { get; set; }
- }
-}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.NodeServices/Configuration/NodeServicesServiceCollectionExtensions.cs b/src/Microsoft.AspNetCore.NodeServices/Configuration/NodeServicesServiceCollectionExtensions.cs
deleted file mode 100644
index f74ae08..0000000
--- a/src/Microsoft.AspNetCore.NodeServices/Configuration/NodeServicesServiceCollectionExtensions.cs
+++ /dev/null
@@ -1,41 +0,0 @@
-using System;
-using Microsoft.AspNetCore.NodeServices;
-
-namespace Microsoft.Extensions.DependencyInjection
-{
- ///
- /// Extension methods for setting up NodeServices in an .
- ///
- public static class NodeServicesServiceCollectionExtensions
- {
- ///
- /// Adds NodeServices support to the .
- ///
- /// The .
- public static void AddNodeServices(this IServiceCollection serviceCollection)
- => AddNodeServices(serviceCollection, _ => {});
-
- ///
- /// Adds NodeServices support to the .
- ///
- /// The .
- /// A callback that will be invoked to populate the .
- public static void AddNodeServices(this IServiceCollection serviceCollection, Action setupAction)
- {
- if (setupAction == null)
- {
- throw new ArgumentNullException(nameof (setupAction));
- }
-
- serviceCollection.AddSingleton(typeof(INodeServices), serviceProvider =>
- {
- // First we let NodeServicesOptions take its defaults from the IServiceProvider,
- // then we let the developer override those options
- var options = new NodeServicesOptions(serviceProvider);
- setupAction(options);
-
- return NodeServicesFactory.CreateNodeServices(options);
- });
- }
- }
-}
diff --git a/src/Microsoft.AspNetCore.NodeServices/Content/Node/entrypoint-http.js b/src/Microsoft.AspNetCore.NodeServices/Content/Node/entrypoint-http.js
deleted file mode 100644
index 644cb36..0000000
--- a/src/Microsoft.AspNetCore.NodeServices/Content/Node/entrypoint-http.js
+++ /dev/null
@@ -1,361 +0,0 @@
-(function(e, a) { for(var i in a) e[i] = a[i]; }(exports, /******/ (function(modules) { // webpackBootstrap
-/******/ // The module cache
-/******/ var installedModules = {};
-
-/******/ // The require function
-/******/ function __webpack_require__(moduleId) {
-
-/******/ // Check if module is in cache
-/******/ if(installedModules[moduleId])
-/******/ return installedModules[moduleId].exports;
-
-/******/ // Create a new module (and put it into the cache)
-/******/ var module = installedModules[moduleId] = {
-/******/ exports: {},
-/******/ id: moduleId,
-/******/ loaded: false
-/******/ };
-
-/******/ // Execute the module function
-/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
-
-/******/ // Flag the module as loaded
-/******/ module.loaded = true;
-
-/******/ // Return the exports of the module
-/******/ return module.exports;
-/******/ }
-
-
-/******/ // expose the modules object (__webpack_modules__)
-/******/ __webpack_require__.m = modules;
-
-/******/ // expose the module cache
-/******/ __webpack_require__.c = installedModules;
-
-/******/ // __webpack_public_path__
-/******/ __webpack_require__.p = "";
-
-/******/ // Load entry module and return exports
-/******/ return __webpack_require__(0);
-/******/ })
-/************************************************************************/
-/******/ ([
-/* 0 */
-/***/ function(module, exports, __webpack_require__) {
-
- module.exports = __webpack_require__(1);
-
-
-/***/ },
-/* 1 */
-/***/ function(module, exports, __webpack_require__) {
-
- "use strict";
- // Limit dependencies to core Node modules. This means the code in this file has to be very low-level and unattractive,
- // but simplifies things for the consumer of this module.
- __webpack_require__(2);
- __webpack_require__(4);
- var http = __webpack_require__(5);
- var path = __webpack_require__(3);
- var ArgsUtil_1 = __webpack_require__(6);
- var ExitWhenParentExits_1 = __webpack_require__(7);
- // Webpack doesn't support dynamic requires for files not present at compile time, so grab a direct
- // reference to Node's runtime 'require' function.
- var dynamicRequire = eval('require');
- var server = http.createServer(function (req, res) {
- readRequestBodyAsJson(req, function (bodyJson) {
- var hasSentResult = false;
- var callback = function (errorValue, successValue) {
- if (!hasSentResult) {
- hasSentResult = true;
- if (errorValue) {
- respondWithError(res, errorValue);
- }
- else if (typeof successValue !== 'string') {
- // Arbitrary object/number/etc - JSON-serialize it
- var successValueJson = void 0;
- try {
- successValueJson = JSON.stringify(successValue);
- }
- catch (ex) {
- // JSON serialization error - pass it back to .NET
- respondWithError(res, ex);
- return;
- }
- res.setHeader('Content-Type', 'application/json');
- res.end(successValueJson);
- }
- else {
- // String - can bypass JSON-serialization altogether
- res.setHeader('Content-Type', 'text/plain');
- res.end(successValue);
- }
- }
- };
- // Support streamed responses
- Object.defineProperty(callback, 'stream', {
- enumerable: true,
- get: function () {
- if (!hasSentResult) {
- hasSentResult = true;
- res.setHeader('Content-Type', 'application/octet-stream');
- }
- return res;
- }
- });
- try {
- var resolvedPath = path.resolve(process.cwd(), bodyJson.moduleName);
- var invokedModule = dynamicRequire(resolvedPath);
- var func = bodyJson.exportedFunctionName ? invokedModule[bodyJson.exportedFunctionName] : invokedModule;
- if (!func) {
- throw new Error('The module "' + resolvedPath + '" has no export named "' + bodyJson.exportedFunctionName + '"');
- }
- func.apply(null, [callback].concat(bodyJson.args));
- }
- catch (synchronousException) {
- callback(synchronousException, null);
- }
- });
- });
- var parsedArgs = ArgsUtil_1.parseArgs(process.argv);
- var requestedPortOrZero = parsedArgs.port || 0; // 0 means 'let the OS decide'
- server.listen(requestedPortOrZero, 'localhost', function () {
- // Signal to HttpNodeHost which port it should make its HTTP connections on
- console.log('[Microsoft.AspNetCore.NodeServices.HttpNodeHost:Listening on port ' + server.address().port + '\]');
- // Signal to the NodeServices base class that we're ready to accept invocations
- console.log('[Microsoft.AspNetCore.NodeServices:Listening]');
- });
- ExitWhenParentExits_1.exitWhenParentExits(parseInt(parsedArgs.parentPid), /* ignoreSigint */ true);
- function readRequestBodyAsJson(request, callback) {
- var requestBodyAsString = '';
- request.on('data', function (chunk) { requestBodyAsString += chunk; });
- request.on('end', function () { callback(JSON.parse(requestBodyAsString)); });
- }
- function respondWithError(res, errorValue) {
- res.statusCode = 500;
- res.end(JSON.stringify({
- errorMessage: errorValue.message || errorValue,
- errorDetails: errorValue.stack || null
- }));
- }
-
-
-/***/ },
-/* 2 */
-/***/ function(module, exports, __webpack_require__) {
-
- "use strict";
- var path = __webpack_require__(3);
- var startsWith = function (str, prefix) { return str.substring(0, prefix.length) === prefix; };
- var appRootDir = process.cwd();
- function patchedLStat(pathToStatLong, fsReqWrap) {
- try {
- // If the lstat completes without errors, we don't modify its behavior at all
- return origLStat.apply(this, arguments);
- }
- catch (ex) {
- var shouldOverrideError = startsWith(ex.message, 'EPERM') // It's a permissions error
- && typeof appRootDirLong === 'string'
- && startsWith(appRootDirLong, pathToStatLong) // ... for an ancestor directory
- && ex.stack.indexOf('Object.realpathSync ') >= 0; // ... during symlink resolution
- if (shouldOverrideError) {
- // Fake the result to give the same result as an 'lstat' on the app root dir.
- // This stops Node failing to load modules just because it doesn't know whether
- // ancestor directories are symlinks or not. If there's a genuine file
- // permissions issue, it will still surface later when Node actually
- // tries to read the file.
- return origLStat.call(this, appRootDir, fsReqWrap);
- }
- else {
- // In any other case, preserve the original error
- throw ex;
- }
- }
- }
- ;
- // It's only necessary to apply this workaround on Windows
- var appRootDirLong = null;
- var origLStat = null;
- if (/^win/.test(process.platform)) {
- try {
- // Get the app's root dir in Node's internal "long" format (e.g., \\?\C:\dir\subdir)
- appRootDirLong = path._makeLong(appRootDir);
- // Actually apply the patch, being as defensive as possible
- var bindingFs = process.binding('fs');
- origLStat = bindingFs.lstat;
- if (typeof origLStat === 'function') {
- bindingFs.lstat = patchedLStat;
- }
- }
- catch (ex) {
- }
- }
-
-
-/***/ },
-/* 3 */
-/***/ function(module, exports) {
-
- module.exports = require("path");
-
-/***/ },
-/* 4 */
-/***/ function(module, exports) {
-
- // When Node writes to stdout/strerr, we capture that and convert the lines into calls on the
- // active .NET ILogger. But by default, stdout/stderr don't have any way of distinguishing
- // linebreaks inside log messages from the linebreaks that delimit separate log messages,
- // so multiline strings will end up being written to the ILogger as multiple independent
- // log messages. This makes them very hard to make sense of, especially when they represent
- // something like stack traces.
- //
- // To fix this, we intercept stdout/stderr writes, and replace internal linebreaks with a
- // marker token. When .NET receives the lines, it converts the marker tokens back to regular
- // linebreaks within the logged messages.
- //
- // Note that it's better to do the interception at the stdout/stderr level, rather than at
- // the console.log/console.error (etc.) level, because this takes place after any native
- // message formatting has taken place (e.g., inserting values for % placeholders).
- var findInternalNewlinesRegex = /\n(?!$)/g;
- var encodedNewline = '__ns_newline__';
- encodeNewlinesWrittenToStream(process.stdout);
- encodeNewlinesWrittenToStream(process.stderr);
- function encodeNewlinesWrittenToStream(outputStream) {
- var origWriteFunction = outputStream.write;
- outputStream.write = function (value) {
- // Only interfere with the write if it's definitely a string
- if (typeof value === 'string') {
- var argsClone = Array.prototype.slice.call(arguments, 0);
- argsClone[0] = encodeNewlinesInString(value);
- origWriteFunction.apply(this, argsClone);
- }
- else {
- origWriteFunction.apply(this, arguments);
- }
- };
- }
- function encodeNewlinesInString(str) {
- return str.replace(findInternalNewlinesRegex, encodedNewline);
- }
-
-
-/***/ },
-/* 5 */
-/***/ function(module, exports) {
-
- module.exports = require("http");
-
-/***/ },
-/* 6 */
-/***/ function(module, exports) {
-
- "use strict";
- function parseArgs(args) {
- // Very simplistic parsing which is sufficient for the cases needed. We don't want to bring in any external
- // dependencies (such as an args-parsing library) to this file.
- var result = {};
- var currentKey = null;
- args.forEach(function (arg) {
- if (arg.indexOf('--') === 0) {
- var argName = arg.substring(2);
- result[argName] = undefined;
- currentKey = argName;
- }
- else if (currentKey) {
- result[currentKey] = arg;
- currentKey = null;
- }
- });
- return result;
- }
- exports.parseArgs = parseArgs;
-
-
-/***/ },
-/* 7 */
-/***/ function(module, exports) {
-
- /*
- In general, we want the Node child processes to be terminated as soon as the parent .NET processes exit,
- because we have no further use for them. If the .NET process shuts down gracefully, it will run its
- finalizers, one of which (in OutOfProcessNodeInstance.cs) will kill its associated Node process immediately.
-
- But if the .NET process is terminated forcefully (e.g., on Linux/OSX with 'kill -9'), then it won't have
- any opportunity to shut down its child processes, and by default they will keep running. In this case, it's
- up to the child process to detect this has happened and terminate itself.
-
- There are many possible approaches to detecting when a parent process has exited, most of which behave
- differently between Windows and Linux/OS X:
-
- - On Windows, the parent process can mark its child as being a 'job' that should auto-terminate when
- the parent does (http://stackoverflow.com/a/4657392). Not cross-platform.
- - The child Node process can get a callback when the parent disconnects (process.on('disconnect', ...)).
- But despite http://stackoverflow.com/a/16487966, no callback fires in any case I've tested (Windows / OS X).
- - The child Node process can get a callback when its stdin/stdout are disconnected, as described at
- http://stackoverflow.com/a/15693934. This works well on OS X, but calling stdout.resume() on Windows
- causes the process to terminate prematurely.
- - I don't know why, but on Windows, it's enough to invoke process.stdin.resume(). For some reason this causes
- the child Node process to exit as soon as the parent one does, but I don't see this documented anywhere.
- - You can poll to see if the parent process, or your stdin/stdout connection to it, is gone
- - You can directly pass a parent process PID to the child, and then have the child poll to see if it's
- still running (e.g., using process.kill(pid, 0), which doesn't kill it but just tests whether it exists,
- as per https://nodejs.org/api/process.html#process_process_kill_pid_signal)
- - Or, on each poll, you can try writing to process.stdout. If the parent has died, then this will throw.
- However I don't see this documented anywhere. It would be nice if you could just poll for whether or not
- process.stdout is still connected (without actually writing to it) but I haven't found any property whose
- value changes until you actually try to write to it.
-
- Of these, the only cross-platform approach that is actually documented as a valid strategy is simply polling
- to check whether the parent PID is still running. So that's what we do here.
- */
- "use strict";
- var pollIntervalMs = 1000;
- function exitWhenParentExits(parentPid, ignoreSigint) {
- setInterval(function () {
- if (!processExists(parentPid)) {
- // Can't log anything at this point, because out stdout was connected to the parent,
- // but the parent is gone.
- process.exit();
- }
- }, pollIntervalMs);
- if (ignoreSigint) {
- // Pressing ctrl+c in the terminal sends a SIGINT to all processes in the foreground process tree.
- // By default, the Node process would then exit before the .NET process, because ASP.NET implements
- // a delayed shutdown to allow ongoing requests to complete.
- //
- // This is problematic, because if Node exits first, the CopyToAsync code in ConditionalProxyMiddleware
- // will experience a read fault, and logs a huge load of errors. Fortunately, since the Node process is
- // already set up to shut itself down if it detects the .NET process is terminated, all we have to do is
- // ignore the SIGINT. The Node process will then terminate automatically after the .NET process does.
- //
- // A better solution would be to have WebpackDevMiddleware listen for SIGINT and gracefully close any
- // ongoing EventSource connections before letting the Node process exit, independently of the .NET
- // process exiting. However, doing this well in general is very nontrivial (see all the discussion at
- // https://github.com/nodejs/node/issues/2642).
- process.on('SIGINT', function () {
- console.log('Received SIGINT. Waiting for .NET process to exit...');
- });
- }
- }
- exports.exitWhenParentExits = exitWhenParentExits;
- function processExists(pid) {
- try {
- // Sending signal 0 - on all platforms - tests whether the process exists. As long as it doesn't
- // throw, that means it does exist.
- process.kill(pid, 0);
- return true;
- }
- catch (ex) {
- // If the reason for the error is that we don't have permission to ask about this process,
- // report that as a separate problem.
- if (ex.code === 'EPERM') {
- throw new Error("Attempted to check whether process " + pid + " was running, but got a permissions error.");
- }
- return false;
- }
- }
-
-
-/***/ }
-/******/ ])));
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.NodeServices/HostingModels/HttpNodeInstance.cs b/src/Microsoft.AspNetCore.NodeServices/HostingModels/HttpNodeInstance.cs
deleted file mode 100644
index 69b12e4..0000000
--- a/src/Microsoft.AspNetCore.NodeServices/HostingModels/HttpNodeInstance.cs
+++ /dev/null
@@ -1,151 +0,0 @@
-using System;
-using System.IO;
-using System.Net.Http;
-using System.Text;
-using System.Text.RegularExpressions;
-using System.Threading;
-using System.Threading.Tasks;
-using Newtonsoft.Json;
-using Newtonsoft.Json.Serialization;
-
-namespace Microsoft.AspNetCore.NodeServices.HostingModels
-{
- ///
- /// A specialisation of the OutOfProcessNodeInstance base class that uses HTTP to perform RPC invocations.
- ///
- /// The Node child process starts an HTTP listener on an arbitrary available port (except where a nonzero
- /// port number is specified as a constructor parameter), and signals which port was selected using the same
- /// input/output-based mechanism that the base class uses to determine when the child process is ready to
- /// accept RPC invocations.
- ///
- ///
- internal class HttpNodeInstance : OutOfProcessNodeInstance
- {
- private static readonly Regex PortMessageRegex =
- new Regex(@"^\[Microsoft.AspNetCore.NodeServices.HttpNodeHost:Listening on port (\d+)\]$");
-
- private static readonly JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings
- {
- ContractResolver = new CamelCasePropertyNamesContractResolver(),
- TypeNameHandling = TypeNameHandling.None
- };
-
- private readonly HttpClient _client;
- private bool _disposed;
- private int _portNumber;
-
- public HttpNodeInstance(NodeServicesOptions options, int port = 0)
- : base(
- EmbeddedResourceReader.Read(
- typeof(HttpNodeInstance),
- "/Content/Node/entrypoint-http.js"),
- options.ProjectPath,
- options.WatchFileExtensions,
- MakeCommandLineOptions(port),
- options.ApplicationStoppingToken,
- options.NodeInstanceOutputLogger,
- options.EnvironmentVariables,
- options.InvocationTimeoutMilliseconds,
- options.LaunchWithDebugging,
- options.DebuggingPort)
- {
- _client = new HttpClient();
- _client.Timeout = TimeSpan.FromMilliseconds(options.InvocationTimeoutMilliseconds + 1000);
- }
-
- private static string MakeCommandLineOptions(int port)
- {
- return $"--port {port}";
- }
-
- protected override async Task InvokeExportAsync(
- NodeInvocationInfo invocationInfo, CancellationToken cancellationToken)
- {
- var payloadJson = JsonConvert.SerializeObject(invocationInfo, jsonSerializerSettings);
- var payload = new StringContent(payloadJson, Encoding.UTF8, "application/json");
- var response = await _client.PostAsync("http://localhost:" + _portNumber, payload, cancellationToken);
-
- if (!response.IsSuccessStatusCode)
- {
- // Unfortunately there's no true way to cancel ReadAsStringAsync calls, hence AbandonIfCancelled
- var responseJson = await response.Content.ReadAsStringAsync().OrThrowOnCancellation(cancellationToken);
- var responseError = JsonConvert.DeserializeObject(responseJson, jsonSerializerSettings);
-
- throw new NodeInvocationException(responseError.ErrorMessage, responseError.ErrorDetails);
- }
-
- var responseContentType = response.Content.Headers.ContentType;
- switch (responseContentType.MediaType)
- {
- case "text/plain":
- // String responses can skip JSON encoding/decoding
- if (typeof(T) != typeof(string))
- {
- throw new ArgumentException(
- "Node module responded with non-JSON string. This cannot be converted to the requested generic type: " +
- typeof(T).FullName);
- }
-
- var responseString = await response.Content.ReadAsStringAsync().OrThrowOnCancellation(cancellationToken);
- return (T)(object)responseString;
-
- case "application/json":
- var responseJson = await response.Content.ReadAsStringAsync().OrThrowOnCancellation(cancellationToken);
- return JsonConvert.DeserializeObject(responseJson, jsonSerializerSettings);
-
- case "application/octet-stream":
- // Streamed responses have to be received as System.IO.Stream instances
- if (typeof(T) != typeof(Stream) && typeof(T) != typeof(object))
- {
- throw new ArgumentException(
- "Node module responded with binary stream. This cannot be converted to the requested generic type: " +
- typeof(T).FullName + ". Instead you must use the generic type System.IO.Stream.");
- }
-
- return (T)(object)(await response.Content.ReadAsStreamAsync().OrThrowOnCancellation(cancellationToken));
-
- default:
- throw new InvalidOperationException("Unexpected response content type: " + responseContentType.MediaType);
- }
- }
-
- protected override void OnOutputDataReceived(string outputData)
- {
- // Watch for "port selected" messages, and when observed, store the port number
- // so we can use it when making HTTP requests. The child process will always send
- // one of these messages before it sends a "ready for connections" message.
- var match = _portNumber != 0 ? null : PortMessageRegex.Match(outputData);
- if (match != null && match.Success)
- {
- _portNumber = int.Parse(match.Groups[1].Captures[0].Value);
- }
- else
- {
- base.OnOutputDataReceived(outputData);
- }
- }
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
-
- if (!_disposed)
- {
- if (disposing)
- {
- _client.Dispose();
- }
-
- _disposed = true;
- }
- }
-
-#pragma warning disable 649 // These properties are populated via JSON deserialization
- private class RpcJsonResponse
- {
- public string ErrorMessage { get; set; }
- public string ErrorDetails { get; set; }
- }
-#pragma warning restore 649
- }
-}
diff --git a/src/Microsoft.AspNetCore.NodeServices/HostingModels/INodeInstance.cs b/src/Microsoft.AspNetCore.NodeServices/HostingModels/INodeInstance.cs
deleted file mode 100644
index a1ce021..0000000
--- a/src/Microsoft.AspNetCore.NodeServices/HostingModels/INodeInstance.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using System;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace Microsoft.AspNetCore.NodeServices.HostingModels
-{
- ///
- /// Represents an instance of Node.js to which Remote Procedure Calls (RPC) may be sent.
- ///
- public interface INodeInstance : IDisposable
- {
- ///
- /// Asynchronously invokes code in the Node.js instance.
- ///
- /// The JSON-serializable data type that the Node.js code will asynchronously return.
- /// A that can be used to cancel the invocation.
- /// The path to the Node.js module (i.e., JavaScript file) relative to your project root that contains the code to be invoked.
- /// If set, specifies the CommonJS export to be invoked. If not set, the module's default CommonJS export itself must be a function to be invoked.
- /// Any sequence of JSON-serializable arguments to be passed to the Node.js function.
- /// A representing the completion of the RPC call.
- Task InvokeExportAsync(CancellationToken cancellationToken, string moduleName, string exportNameOrNull, params object[] args);
- }
-}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.NodeServices/HostingModels/NodeInvocationException.cs b/src/Microsoft.AspNetCore.NodeServices/HostingModels/NodeInvocationException.cs
deleted file mode 100644
index dea3c2c..0000000
--- a/src/Microsoft.AspNetCore.NodeServices/HostingModels/NodeInvocationException.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-using System;
-
-namespace Microsoft.AspNetCore.NodeServices.HostingModels
-{
- ///
- /// Represents an exception caused by invoking Node.js code.
- ///
- public class NodeInvocationException : Exception
- {
- ///
- /// If true, indicates that the invocation failed because the Node.js instance could not be reached. For example,
- /// it might have already shut down or previously crashed.
- ///
- public bool NodeInstanceUnavailable { get; private set; }
-
- ///
- /// If true, indicates that even though the invocation failed because the Node.js instance could not be reached
- /// or needs to be restarted, that Node.js instance may remain alive for a period in order to complete any
- /// outstanding requests.
- ///
- public bool AllowConnectionDraining { get; private set;}
-
- ///
- /// Creates a new instance of .
- ///
- /// A description of the exception.
- /// Additional information, such as a Node.js stack trace, representing the exception.
- public NodeInvocationException(string message, string details)
- : base(message + Environment.NewLine + details)
- {
- }
-
- ///
- /// Creates a new instance of .
- ///
- /// A description of the exception.
- /// Additional information, such as a Node.js stack trace, representing the exception.
- /// Specifies a value for the flag.
- /// Specifies a value for the flag.
- public NodeInvocationException(string message, string details, bool nodeInstanceUnavailable, bool allowConnectionDraining)
- : this(message, details)
- {
- // Reject a meaningless combination of flags
- if (allowConnectionDraining && !nodeInstanceUnavailable)
- {
- throw new ArgumentException(
- $"The '${ nameof(allowConnectionDraining) }' parameter cannot be true " +
- $"unless the '${ nameof(nodeInstanceUnavailable) }' parameter is also true.");
- }
-
- NodeInstanceUnavailable = nodeInstanceUnavailable;
- AllowConnectionDraining = allowConnectionDraining;
- }
- }
-}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.NodeServices/HostingModels/NodeInvocationInfo.cs b/src/Microsoft.AspNetCore.NodeServices/HostingModels/NodeInvocationInfo.cs
deleted file mode 100644
index 86c4273..0000000
--- a/src/Microsoft.AspNetCore.NodeServices/HostingModels/NodeInvocationInfo.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-namespace Microsoft.AspNetCore.NodeServices.HostingModels
-{
- ///
- /// Describes an RPC call sent from .NET code to Node.js code.
- ///
- public class NodeInvocationInfo
- {
- ///
- /// Specifies the path to the Node.js module (i.e., .js file) relative to the project root.
- ///
- public string ModuleName { get; set; }
-
- ///
- /// If set, specifies the name of CommonJS function export to be invoked.
- /// If not set, the Node.js module's default export must itself be a function to be invoked.
- ///
- public string ExportedFunctionName { get; set; }
-
- ///
- /// A sequence of JSON-serializable arguments to be passed to the Node.js function being invoked.
- ///
- public object[] Args { get; set; }
- }
-}
diff --git a/src/Microsoft.AspNetCore.NodeServices/HostingModels/NodeServicesOptionsExtensions.cs b/src/Microsoft.AspNetCore.NodeServices/HostingModels/NodeServicesOptionsExtensions.cs
deleted file mode 100644
index 2cbe0a0..0000000
--- a/src/Microsoft.AspNetCore.NodeServices/HostingModels/NodeServicesOptionsExtensions.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-namespace Microsoft.AspNetCore.NodeServices.HostingModels
-{
- ///
- /// Extension methods that help with populating a object.
- ///
- public static class NodeServicesOptionsExtensions
- {
- ///
- /// Configures the service so that it will use out-of-process
- /// Node.js instances and perform RPC calls over HTTP.
- ///
- public static void UseHttpHosting(this NodeServicesOptions options)
- {
- options.NodeInstanceFactory = () => new HttpNodeInstance(options);
- }
- }
-}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs b/src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs
deleted file mode 100644
index b75827c..0000000
--- a/src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs
+++ /dev/null
@@ -1,475 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-using Microsoft.Extensions.Logging;
-
-namespace Microsoft.AspNetCore.NodeServices.HostingModels
-{
- ///
- /// Class responsible for launching a Node child process on the local machine, determining when it is ready to
- /// accept invocations, detecting if it dies on its own, and finally terminating it on disposal.
- ///
- /// This abstract base class uses the input/output streams of the child process to perform a simple handshake
- /// to determine when the child process is ready to accept invocations. This is agnostic to the mechanism that
- /// derived classes use to actually perform the invocations (e.g., they could use HTTP-RPC, or a binary TCP
- /// protocol, or any other RPC-type mechanism).
- ///
- ///
- public abstract class OutOfProcessNodeInstance : INodeInstance
- {
- ///
- /// The to which the Node.js instance's stdout/stderr is being redirected.
- ///
- protected readonly ILogger OutputLogger;
-
- private const string ConnectionEstablishedMessage = "[Microsoft.AspNetCore.NodeServices:Listening]";
- private readonly TaskCompletionSource