diff --git a/src/Microsoft.AspNetCore.NodeServices/Configuration/Configuration.cs b/src/Microsoft.AspNetCore.NodeServices/Configuration/Configuration.cs
index b487251..4bda4d6 100644
--- a/src/Microsoft.AspNetCore.NodeServices/Configuration/Configuration.cs
+++ b/src/Microsoft.AspNetCore.NodeServices/Configuration/Configuration.cs
@@ -48,7 +48,8 @@ namespace Microsoft.AspNetCore.NodeServices
case NodeHostingModel.Http:
return new HttpNodeInstance(options.ProjectPath, /* port */ 0, options.WatchFileExtensions);
case NodeHostingModel.Socket:
- return new SocketNodeInstance(options.ProjectPath, options.WatchFileExtensions);
+ var pipeName = "pni-" + Guid.NewGuid().ToString("D"); // Arbitrary non-clashing string
+ return new SocketNodeInstance(options.ProjectPath, options.WatchFileExtensions, pipeName);
default:
throw new ArgumentException("Unknown hosting model: " + options.HostingModel);
}
diff --git a/src/Microsoft.AspNetCore.NodeServices/Content/Node/entrypoint-socket.js b/src/Microsoft.AspNetCore.NodeServices/Content/Node/entrypoint-socket.js
index 2c1cd9d..355bbbb 100644
--- a/src/Microsoft.AspNetCore.NodeServices/Content/Node/entrypoint-socket.js
+++ b/src/Microsoft.AspNetCore.NodeServices/Content/Node/entrypoint-socket.js
@@ -176,7 +176,7 @@
// 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 listenAddress = (useWindowsNamedPipes ? '\\\\.\\pipe\\' : '/tmp/') + parsedArgs.pipename;
+ var listenAddress = (useWindowsNamedPipes ? '\\\\.\\pipe\\' : '/tmp/') + parsedArgs.listenAddress;
server.listen(listenAddress);
diff --git a/src/Microsoft.AspNetCore.NodeServices/HostingModels/HttpNodeInstance.cs b/src/Microsoft.AspNetCore.NodeServices/HostingModels/HttpNodeInstance.cs
index 13d7527..c1fdbaf 100644
--- a/src/Microsoft.AspNetCore.NodeServices/HostingModels/HttpNodeInstance.cs
+++ b/src/Microsoft.AspNetCore.NodeServices/HostingModels/HttpNodeInstance.cs
@@ -9,6 +9,18 @@ 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.
+ ///
+ /// TODO: Remove the file-watching logic from here and centralise it in OutOfProcessNodeInstance, implementing
+ /// the actual watching in .NET code (not Node), for consistency across platforms.
+ ///
+ ///
internal class HttpNodeInstance : OutOfProcessNodeInstance
{
private static readonly Regex PortMessageRegex =
@@ -19,7 +31,7 @@ namespace Microsoft.AspNetCore.NodeServices.HostingModels
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
- private HttpClient _client;
+ private readonly HttpClient _client;
private bool _disposed;
private int _portNumber;
@@ -47,9 +59,6 @@ namespace Microsoft.AspNetCore.NodeServices.HostingModels
protected override async Task InvokeExportAsync(NodeInvocationInfo invocationInfo)
{
- await EnsureReady();
-
- // TODO: Use System.Net.Http.Formatting (PostAsJsonAsync etc.)
var payloadJson = JsonConvert.SerializeObject(invocationInfo, JsonSerializerSettings);
var payload = new StringContent(payloadJson, Encoding.UTF8, "application/json");
var response = await _client.PostAsync("http://localhost:" + _portNumber, payload);
@@ -97,6 +106,9 @@ namespace Microsoft.AspNetCore.NodeServices.HostingModels
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)
{
@@ -108,12 +120,6 @@ namespace Microsoft.AspNetCore.NodeServices.HostingModels
}
}
- protected override void OnBeforeLaunchProcess()
- {
- // Prepare to receive a new port number
- _portNumber = 0;
- }
-
protected override void Dispose(bool disposing) {
base.Dispose(disposing);
diff --git a/src/Microsoft.AspNetCore.NodeServices/HostingModels/NodeInvocationException.cs b/src/Microsoft.AspNetCore.NodeServices/HostingModels/NodeInvocationException.cs
index ee056ab..733af44 100644
--- a/src/Microsoft.AspNetCore.NodeServices/HostingModels/NodeInvocationException.cs
+++ b/src/Microsoft.AspNetCore.NodeServices/HostingModels/NodeInvocationException.cs
@@ -4,9 +4,17 @@ namespace Microsoft.AspNetCore.NodeServices.HostingModels
{
public class NodeInvocationException : Exception
{
+ public bool NodeInstanceUnavailable { get; private set; }
+
public NodeInvocationException(string message, string details)
: base(message + Environment.NewLine + details)
{
}
+
+ public NodeInvocationException(string message, string details, bool nodeInstanceUnavailable)
+ : this(message, details)
+ {
+ NodeInstanceUnavailable = nodeInstanceUnavailable;
+ }
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs b/src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs
index 8018d7f..498c316 100644
--- a/src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs
+++ b/src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs
@@ -6,37 +6,43 @@ using System.Threading.Tasks;
namespace Microsoft.AspNetCore.NodeServices.HostingModels
{
///
- /// Class responsible for launching the Node child process, determining when it is ready to accept invocations,
- /// and finally killing it when the parent process exits. Also it restarts the child process if it dies.
+ /// 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
{
- private readonly object _childProcessLauncherLock;
- private string _commandLineArguments;
- private readonly StringAsTempFile _entryPointScript;
- private Process _nodeProcess;
- private TaskCompletionSource _nodeProcessIsReadySource;
- private readonly string _projectPath;
+ private const string ConnectionEstablishedMessage = "[Microsoft.AspNetCore.NodeServices:Listening]";
+ private readonly TaskCompletionSource