Skip to content

Commit

Permalink
Add ServerBase for shared Node and Ruby Sass code
Browse files Browse the repository at this point in the history
Add cleanup routine for Sass Server
Update Sass to 3.4.7
  • Loading branch information
davidtme committed Nov 2, 2014
1 parent eff1b12 commit 87bd5c3
Show file tree
Hide file tree
Showing 7 changed files with 238 additions and 232 deletions.
Binary file modified EditorExtensions/Resources/Tools/sass.exe
Binary file not shown.
18 changes: 12 additions & 6 deletions EditorExtensions/SCSS/Compilers/ScssCompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Microsoft.VisualStudio.Utilities;
using Microsoft.Web.Editor;
using System.Threading.Tasks;
using System.Web;

namespace MadsKristensen.EditorExtensions.Scss
{
Expand All @@ -18,6 +19,16 @@ public class ScssCompiler : CssCompilerBase
public override bool MinifyInPlace { get { return WESettings.Instance.Scss.MinifyInPlace; } }
public override bool GenerateSourceMap { get { return WESettings.Instance.Scss.GenerateSourceMaps && !MinifyInPlace; } }

public override async Task<CompilerResult> CompileAsync(string sourceFileName, string targetFileName)
{
if (WESettings.Instance.Scss.UseRubyRuntime)
{
await RubyScssServer.Up();
}

return await base.CompileAsync(sourceFileName, targetFileName);
}

protected override string GetPath(string sourceFileName, string targetFileName)
{
GetOrCreateGlobalSettings(RtlCssCompiler.ConfigFileName);
Expand All @@ -33,13 +44,8 @@ protected override string GetPath(string sourceFileName, string targetFileName)
}
else
{
Task.Factory.StartNew(() =>
{
return RubyScssServer.Up();
}).Wait();

parameters.Add("service", "RubySCSS");
parameters.Add("rubyAuth", RubyScssServer.AuthenticationToken);
parameters.Add("rubyAuth", HttpUtility.UrlEncode(RubyScssServer.AuthenticationToken));
parameters.Add("rubyPort", RubyScssServer.Port.ToString());
}

Expand Down
2 changes: 1 addition & 1 deletion EditorExtensions/Shared/Compilers/NodeExecutorBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public abstract class NodeExecutorBase
public abstract string TargetExtension { get; }
public abstract string ServiceName { get; }

public async Task<CompilerResult> CompileAsync(string sourceFileName, string targetFileName)
public virtual async Task<CompilerResult> CompileAsync(string sourceFileName, string targetFileName)
{
bool onlyPreview = false;

Expand Down
140 changes: 11 additions & 129 deletions EditorExtensions/Shared/Compilers/NodeServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,60 +10,19 @@

namespace MadsKristensen.EditorExtensions
{
public sealed class NodeServer : IDisposable
public sealed class NodeServer : ServerBase
{
private static readonly string _nodePath = Path.Combine(Path.Combine(Path.GetDirectoryName(typeof(NodeServer).Assembly.Location), @"Resources"), @"nodejs\node.exe");
private string _authenticationToken;
private static NodeServer _server;
private HttpClient _client;
private Process _process;
private string _address;
private int _port;

public static async Task Up()
{
AsyncLock mutex = new AsyncLock();

if (await HeartbeatCheck())
return;

while (true)
{
using (await mutex.LockAsync())
{
if (_server == null || _server._process == null || _server._process.HasExited)
_server = new NodeServer();
}

using (Task task = Task.Delay(200))
{
await task.ConfigureAwait(false);

if (await HeartbeatCheck())
break;
}
}
_server = await ServerBase.Up(_server);
}

public static void Down()
{
if (_server != null && !_server._process.HasExited)
{
_server._process.Kill();
_server._process.Dispose();
_server.Dispose();
}
}

private static async Task<bool> HeartbeatCheck()
{
if (_server == null) return false;
try
{
await _server.CallWebServer(_server._address);
return true;
}
catch { return false; }
ServerBase.Down(_server);
}

public static async Task<CompilerResult> CallServiceAsync(string path, bool reattempt = false)
Expand All @@ -72,106 +31,29 @@ public static async Task<CompilerResult> CallServiceAsync(string path, bool reat
return await _server.CallService(path, reattempt);
}

private NodeServer()
public NodeServer()
: base()
{
SelectAvailablePort();
_address = string.Format(CultureInfo.InvariantCulture, "http://127.0.0.1:{0}/", _port);
_client = new HttpClient();

Initialize();

_client.DefaultRequestHeaders.Add("origin", "web essentials");
_client.DefaultRequestHeaders.Add("user-agent", "web essentials");
_client.DefaultRequestHeaders.Add("web-essentials", "web essentials");
_client.DefaultRequestHeaders.Add("auth", _authenticationToken);
}

private void SelectAvailablePort()
{
Random rand = new Random();
TcpConnectionInformation[] connections = IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpConnections();

do
_port = rand.Next(1024, 65535);
while (connections.Any(t => t.LocalEndPoint.Port == _port));
}

private void Initialize()
{
byte[] randomNumber = new byte[32];

using (RandomNumberGenerator crypto = RNGCryptoServiceProvider.Create())
crypto.GetBytes(randomNumber);

_authenticationToken = Convert.ToBase64String(randomNumber);

ProcessStartInfo start = new ProcessStartInfo(_nodePath)
{
WorkingDirectory = Path.GetDirectoryName(_nodePath),
WindowStyle = ProcessWindowStyle.Hidden,
Arguments = string.Format(CultureInfo.InvariantCulture, @"tools\server\we-nodejs-server.js --port {0} --anti-forgery-token {1} --environment production --process-id {2}", _port, _authenticationToken, Process.GetCurrentProcess().Id),
UseShellExecute = false,
CreateNoWindow = true
};

_process = Process.Start(start);
}

private async Task<CompilerResult> CallService(string path, bool reattempt)
protected override string ProcessStartArgumentsFormat
{
string newPath = string.Format("{0}?{1}", _address, path);
HttpResponseMessage response;
try
get
{
response = await CallWebServer(newPath);

// Retry once.
if (!response.IsSuccessStatusCode && !reattempt)
return await RetryOnce(path);

var responseData = await response.Content.ReadAsAsync<NodeServerUtilities.Response>();

return await responseData.GetCompilerResult();
return @"tools\server\we-nodejs-server.js --port {0} --anti-forgery-token {1} --environment production --process-id {2}";
}
catch
{
Logger.Log("Something went wrong reaching: " + Uri.EscapeUriString(newPath));
}

// Retry once.
if (!reattempt)
return await RetryOnce(path);

return null;
}

private async Task<CompilerResult> RetryOnce(string path)
{
return await NodeServer.CallServiceAsync(path, true);
}

// Making this a separate method so it can throw to caller
// which is a test criterion for HearbeatCheck.
private async Task<HttpResponseMessage> CallWebServer(string path)
protected override string ServerPath
{
return await _client.GetAsync(path);
}

private void Dispose(bool disposing)
{
if (disposing)
get
{
if (!_process.HasExited)
_process.Kill();

_client.Dispose();
return _nodePath;
}
}

public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}
112 changes: 16 additions & 96 deletions EditorExtensions/Shared/Compilers/RubyScssServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,128 +10,48 @@
using System.Threading.Tasks;
namespace MadsKristensen.EditorExtensions
{
public sealed class RubyScssServer : IDisposable
public sealed class RubyScssServer : ServerBase
{
private static readonly string _sassServerPath = Path.Combine(Path.Combine(Path.GetDirectoryName(typeof(RubyScssServer).Assembly.Location), @"Resources"), @"Tools\sass.exe");
private string _authenticationToken;
private static RubyScssServer _server;
private HttpClient _client;
private Process _process;
private string _address;
private int _port;

public static async Task Up()
{
AsyncLock mutex = new AsyncLock();

if (await HeartbeatCheck())
return;

while (true)
{
using (await mutex.LockAsync())
{
if (_server == null || _server._process == null || _server._process.HasExited)
_server = new RubyScssServer();
}

using (Task task = Task.Delay(200))
{
await task.ConfigureAwait(false);

if (await HeartbeatCheck())
break;
}
}
_server = await ServerBase.Up(_server);
}

public static void Down()
{
if (_server != null && !_server._process.HasExited)
{
_server._process.Kill();
_server._process.Dispose();
_server.Dispose();
}
}

private static async Task<bool> HeartbeatCheck()
{
if (_server == null) return false;
try
{
await _server.CallWebServer(_server._address + "/status");
return true;
}
catch { return false; }
ServerBase.Down(_server);
}

public RubyScssServer()
: base()
{
SelectAvailablePort();
_address = string.Format(CultureInfo.InvariantCulture, "http://127.0.0.1:{0}/", _port);
_client = new HttpClient();

Initialize();

}

private void SelectAvailablePort()
protected override string HeartbeatCheckPath
{
Random rand = new Random();
TcpConnectionInformation[] connections = IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpConnections();

do
_port = rand.Next(1024, 65535);
while (connections.Any(t => t.LocalEndPoint.Port == _port));
}

private void Initialize()
{
byte[] randomNumber = new byte[32];

using (RandomNumberGenerator crypto = RNGCryptoServiceProvider.Create())
crypto.GetBytes(randomNumber);

_authenticationToken = Convert.ToBase64String(randomNumber);

ProcessStartInfo start = new ProcessStartInfo(_sassServerPath)
get
{
WorkingDirectory = Path.GetDirectoryName(_sassServerPath),
WindowStyle = ProcessWindowStyle.Hidden,
Arguments = string.Format(CultureInfo.InvariantCulture, @"start {0} {1}", _port, _authenticationToken),
UseShellExecute = false,
CreateNoWindow = true
};

_process = Process.Start(start);
}

private async Task<HttpResponseMessage> CallWebServer(string path)
{
return await _client.GetAsync(path);
return "status";
}
}

private void Dispose(bool disposing)
protected override string ProcessStartArgumentsFormat
{
if (disposing)
get
{
try
{
if (!_process.HasExited)
_process.Kill();
}
catch (InvalidOperationException e) { }
catch { throw; }

_client.Dispose();
return @"start {0} {1} {2}";
}
}

public void Dispose()
protected override string ServerPath
{
Dispose(true);
GC.SuppressFinalize(this);
get
{
return _sassServerPath;
}
}

public static string AuthenticationToken
Expand Down
Loading

0 comments on commit 87bd5c3

Please sign in to comment.