Skip to content

Commit 341cd4f

Browse files
Implement SocketNodeInstance
1 parent 32ebaec commit 341cd4f

23 files changed

+3982
-8
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
/bin/
2+
/node_modules/

src/Microsoft.AspNetCore.NodeServices/Configuration.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ public static INodeServices CreateNodeServices(NodeServicesOptions options)
3939
{
4040
case NodeHostingModel.Http:
4141
return new HttpNodeInstance(options.ProjectPath, /* port */ 0, watchFileExtensions);
42+
case NodeHostingModel.Socket:
43+
return new SocketNodeInstance(options.ProjectPath, watchFileExtensions);
4244
case NodeHostingModel.InputOutputStream:
4345
return new InputOutputStreamNodeInstance(options.ProjectPath);
4446
default:

src/Microsoft.AspNetCore.NodeServices/Content/Node/entrypoint-socket.js

Lines changed: 408 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System;
2+
3+
namespace Microsoft.AspNetCore.NodeServices
4+
{
5+
public class NodeInvocationException : Exception
6+
{
7+
public NodeInvocationException(string message, string details)
8+
: base(message + Environment.NewLine + details)
9+
{
10+
}
11+
}
12+
}

src/Microsoft.AspNetCore.NodeServices/HostingModels/OutOfProcessNodeInstance.cs

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.NodeServices
1313
public abstract class OutOfProcessNodeInstance : INodeServices
1414
{
1515
private readonly object _childProcessLauncherLock;
16-
private readonly string _commandLineArguments;
16+
private string _commandLineArguments;
1717
private readonly StringAsTempFile _entryPointScript;
1818
private Process _nodeProcess;
1919
private TaskCompletionSource<bool> _nodeProcessIsReadySource;
@@ -27,6 +27,12 @@ public OutOfProcessNodeInstance(string entryPointScript, string projectPath, str
2727
_projectPath = projectPath;
2828
_commandLineArguments = commandLineArguments ?? string.Empty;
2929
}
30+
31+
public string CommandLineArguments
32+
{
33+
get { return _commandLineArguments; }
34+
set { _commandLineArguments = value; }
35+
}
3036

3137
protected Process NodeProcess
3238
{
@@ -59,12 +65,23 @@ public void Dispose()
5965

6066
public abstract Task<T> Invoke<T>(NodeInvocationInfo invocationInfo);
6167

68+
protected void ExitNodeProcess()
69+
{
70+
if (_nodeProcess != null && !_nodeProcess.HasExited)
71+
{
72+
// TODO: Is there a more graceful way to end it? Or does this still let it perform any cleanup?
73+
_nodeProcess.Kill();
74+
}
75+
}
76+
6277
protected async Task EnsureReady()
6378
{
6479
lock (_childProcessLauncherLock)
6580
{
6681
if (_nodeProcess == null || _nodeProcess.HasExited)
6782
{
83+
this.OnBeforeLaunchProcess();
84+
6885
var startInfo = new ProcessStartInfo("node")
6986
{
7087
Arguments = "\"" + _entryPointScript.FileName + "\" " + _commandLineArguments,
@@ -89,7 +106,6 @@ protected async Task EnsureReady()
89106
startInfo.Environment.Add("NODE_PATH", nodePathValue);
90107
#endif
91108

92-
OnBeforeLaunchProcess();
93109
_nodeProcess = Process.Start(startInfo);
94110
ConnectToInputOutputStreams();
95111
}
@@ -162,11 +178,7 @@ protected virtual void Dispose(bool disposing)
162178
_entryPointScript.Dispose();
163179
}
164180

165-
if (_nodeProcess != null && !_nodeProcess.HasExited)
166-
{
167-
_nodeProcess.Kill();
168-
// TODO: Is there a more graceful way to end it? Or does this still let it perform any cleanup?
169-
}
181+
ExitNodeProcess();
170182

171183
_disposed = true;
172184
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using System.IO;
2+
using System.IO.Pipes;
3+
using System.Threading.Tasks;
4+
5+
namespace Microsoft.AspNetCore.NodeServices.HostingModels.PhysicalConnections
6+
{
7+
internal class NamedPipeConnection : StreamConnection
8+
{
9+
private bool _disposedValue = false;
10+
private NamedPipeClientStream _namedPipeClientStream;
11+
12+
public override async Task<Stream> Open(string address)
13+
{
14+
_namedPipeClientStream = new NamedPipeClientStream(".", address, PipeDirection.InOut);
15+
await _namedPipeClientStream.ConnectAsync().ConfigureAwait(false);
16+
return _namedPipeClientStream;
17+
}
18+
19+
public override void Dispose()
20+
{
21+
if (!_disposedValue)
22+
{
23+
if (_namedPipeClientStream != null)
24+
{
25+
_namedPipeClientStream.Dispose();
26+
}
27+
28+
_disposedValue = true;
29+
}
30+
}
31+
}
32+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using System;
2+
using System.IO;
3+
using System.Runtime.InteropServices;
4+
using System.Threading.Tasks;
5+
6+
namespace Microsoft.AspNetCore.NodeServices.HostingModels.PhysicalConnections
7+
{
8+
internal abstract class StreamConnection : IDisposable
9+
{
10+
public abstract Task<Stream> Open(string address);
11+
public abstract void Dispose();
12+
13+
public static StreamConnection Create()
14+
{
15+
var useNamedPipes = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
16+
if (useNamedPipes)
17+
{
18+
return new NamedPipeConnection();
19+
}
20+
else
21+
{
22+
return new UnixDomainSocketConnection();
23+
}
24+
}
25+
}
26+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using System.IO;
2+
using System.Net.Sockets;
3+
using System.Threading.Tasks;
4+
5+
namespace Microsoft.AspNetCore.NodeServices.HostingModels.PhysicalConnections
6+
{
7+
internal class UnixDomainSocketConnection : StreamConnection
8+
{
9+
private bool _disposedValue = false;
10+
private NetworkStream _networkStream;
11+
private Socket _socket;
12+
13+
public override async Task<Stream> Open(string address)
14+
{
15+
var endPoint = new UnixDomainSocketEndPoint("/tmp/" + address);
16+
_socket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Unspecified);
17+
await _socket.ConnectAsync(endPoint).ConfigureAwait(false);
18+
_networkStream = new NetworkStream(_socket);
19+
return _networkStream;
20+
}
21+
22+
public override void Dispose()
23+
{
24+
if (!_disposedValue)
25+
{
26+
if (_networkStream != null)
27+
{
28+
_networkStream.Dispose();
29+
}
30+
31+
if (_socket != null)
32+
{
33+
_socket.Dispose();
34+
}
35+
36+
_disposedValue = true;
37+
}
38+
}
39+
}
40+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
using System;
2+
using System.Net;
3+
using System.Net.Sockets;
4+
using System.Text;
5+
6+
namespace Microsoft.AspNetCore.NodeServices.HostingModels.PhysicalConnections
7+
{
8+
// From System.IO.Pipes/src/System/Net/Sockets/UnixDomainSocketEndPoint.cs (an internal class in System.IO.Pipes)
9+
internal sealed class UnixDomainSocketEndPoint : EndPoint
10+
{
11+
private const AddressFamily EndPointAddressFamily = AddressFamily.Unix;
12+
13+
private static readonly Encoding s_pathEncoding = Encoding.UTF8;
14+
private static readonly int s_nativePathOffset = 2; // = offsetof(struct sockaddr_un, sun_path). It's the same on Linux and OSX
15+
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
16+
private static readonly int s_nativeAddressSize = s_nativePathOffset + s_nativePathLength;
17+
18+
private readonly string _path;
19+
private readonly byte[] _encodedPath;
20+
21+
public UnixDomainSocketEndPoint(string path)
22+
{
23+
if (path == null)
24+
{
25+
throw new ArgumentNullException(nameof(path));
26+
}
27+
28+
_path = path;
29+
_encodedPath = s_pathEncoding.GetBytes(_path);
30+
31+
if (path.Length == 0 || _encodedPath.Length > s_nativePathLength)
32+
{
33+
throw new ArgumentOutOfRangeException(nameof(path));
34+
}
35+
}
36+
37+
internal UnixDomainSocketEndPoint(SocketAddress socketAddress)
38+
{
39+
if (socketAddress == null)
40+
{
41+
throw new ArgumentNullException(nameof(socketAddress));
42+
}
43+
44+
if (socketAddress.Family != EndPointAddressFamily ||
45+
socketAddress.Size > s_nativeAddressSize)
46+
{
47+
throw new ArgumentOutOfRangeException(nameof(socketAddress));
48+
}
49+
50+
if (socketAddress.Size > s_nativePathOffset)
51+
{
52+
_encodedPath = new byte[socketAddress.Size - s_nativePathOffset];
53+
for (int i = 0; i < _encodedPath.Length; i++)
54+
{
55+
_encodedPath[i] = socketAddress[s_nativePathOffset + i];
56+
}
57+
58+
_path = s_pathEncoding.GetString(_encodedPath, 0, _encodedPath.Length);
59+
}
60+
else
61+
{
62+
_encodedPath = Array.Empty<byte>();
63+
_path = string.Empty;
64+
}
65+
}
66+
67+
public override SocketAddress Serialize()
68+
{
69+
var result = new SocketAddress(AddressFamily.Unix, s_nativeAddressSize);
70+
71+
for (int index = 0; index < _encodedPath.Length; index++)
72+
{
73+
result[s_nativePathOffset + index] = _encodedPath[index];
74+
}
75+
result[s_nativePathOffset + _encodedPath.Length] = 0; // path must be null-terminated
76+
77+
return result;
78+
}
79+
80+
public override EndPoint Create(SocketAddress socketAddress) => new UnixDomainSocketEndPoint(socketAddress);
81+
82+
public override AddressFamily AddressFamily => EndPointAddressFamily;
83+
84+
public override string ToString() => _path;
85+
}
86+
}

0 commit comments

Comments
 (0)