mirror of
https://github.com/aspnet/JavaScriptServices.git
synced 2025-12-26 03:27:30 +00:00
Add UseReactDevelopmentServer() middleware. Factor out common code.
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNetCore.NodeServices.Util
|
||||
{
|
||||
/// <summary>
|
||||
/// Wraps a <see cref="StreamReader"/> to expose an evented API, issuing notifications
|
||||
/// when the stream emits partial lines, completed lines, or finally closes.
|
||||
/// </summary>
|
||||
internal class EventedStreamReader
|
||||
{
|
||||
public delegate void OnReceivedChunkHandler(ArraySegment<char> chunk);
|
||||
public delegate void OnReceivedLineHandler(string line);
|
||||
public delegate void OnStreamClosedHandler();
|
||||
|
||||
public event OnReceivedChunkHandler OnReceivedChunk;
|
||||
public event OnReceivedLineHandler OnReceivedLine;
|
||||
public event OnStreamClosedHandler OnStreamClosed;
|
||||
|
||||
private readonly StreamReader _streamReader;
|
||||
private readonly StringBuilder _linesBuffer;
|
||||
|
||||
public EventedStreamReader(StreamReader streamReader)
|
||||
{
|
||||
_streamReader = streamReader ?? throw new ArgumentNullException(nameof(streamReader));
|
||||
_linesBuffer = new StringBuilder();
|
||||
Task.Factory.StartNew(Run);
|
||||
}
|
||||
|
||||
public Task<Match> WaitForMatch(Regex regex, TimeSpan timeout = default)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<Match>();
|
||||
var completionLock = new object();
|
||||
|
||||
OnReceivedLineHandler onReceivedLineHandler = null;
|
||||
OnStreamClosedHandler onStreamClosedHandler = null;
|
||||
|
||||
void ResolveIfStillPending(Action applyResolution)
|
||||
{
|
||||
lock (completionLock)
|
||||
{
|
||||
if (!tcs.Task.IsCompleted)
|
||||
{
|
||||
OnReceivedLine -= onReceivedLineHandler;
|
||||
OnStreamClosed -= onStreamClosedHandler;
|
||||
applyResolution();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onReceivedLineHandler = line =>
|
||||
{
|
||||
var match = regex.Match(line);
|
||||
if (match.Success)
|
||||
{
|
||||
ResolveIfStillPending(() => tcs.SetResult(match));
|
||||
}
|
||||
};
|
||||
|
||||
onStreamClosedHandler = () =>
|
||||
{
|
||||
ResolveIfStillPending(() => tcs.SetException(new EndOfStreamException()));
|
||||
};
|
||||
|
||||
OnReceivedLine += onReceivedLineHandler;
|
||||
OnStreamClosed += onStreamClosedHandler;
|
||||
|
||||
if (timeout != default)
|
||||
{
|
||||
var timeoutToken = new CancellationTokenSource(timeout);
|
||||
timeoutToken.Token.Register(() =>
|
||||
{
|
||||
ResolveIfStillPending(() => tcs.SetCanceled());
|
||||
});
|
||||
}
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
private async Task Run()
|
||||
{
|
||||
var buf = new char[8 * 1024];
|
||||
while (true)
|
||||
{
|
||||
var chunkLength = await _streamReader.ReadAsync(buf, 0, buf.Length);
|
||||
if (chunkLength == 0)
|
||||
{
|
||||
OnClosed();
|
||||
break;
|
||||
}
|
||||
|
||||
OnChunk(new ArraySegment<char>(buf, 0, chunkLength));
|
||||
|
||||
var lineBreakPos = Array.IndexOf(buf, '\n', 0, chunkLength);
|
||||
if (lineBreakPos < 0)
|
||||
{
|
||||
_linesBuffer.Append(buf, 0, chunkLength);
|
||||
}
|
||||
else
|
||||
{
|
||||
_linesBuffer.Append(buf, 0, lineBreakPos + 1);
|
||||
OnCompleteLine(_linesBuffer.ToString());
|
||||
_linesBuffer.Clear();
|
||||
_linesBuffer.Append(buf, lineBreakPos + 1, chunkLength - (lineBreakPos + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnChunk(ArraySegment<char> chunk)
|
||||
{
|
||||
var dlg = OnReceivedChunk;
|
||||
dlg?.Invoke(chunk);
|
||||
}
|
||||
|
||||
private void OnCompleteLine(string line)
|
||||
{
|
||||
var dlg = OnReceivedLine;
|
||||
dlg?.Invoke(line);
|
||||
}
|
||||
|
||||
private void OnClosed()
|
||||
{
|
||||
var dlg = OnStreamClosed;
|
||||
dlg?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.AspNetCore.NodeServices.Util
|
||||
{
|
||||
/// <summary>
|
||||
/// Captures the completed-line notifications from a <see cref="EventedStreamReader"/>,
|
||||
/// combining the data into a single <see cref="string"/>.
|
||||
/// </summary>
|
||||
internal class EventedStreamStringReader : IDisposable
|
||||
{
|
||||
private EventedStreamReader _eventedStreamReader;
|
||||
private bool _isDisposed;
|
||||
private StringBuilder _stringBuilder = new StringBuilder();
|
||||
|
||||
public EventedStreamStringReader(EventedStreamReader eventedStreamReader)
|
||||
{
|
||||
_eventedStreamReader = eventedStreamReader
|
||||
?? throw new ArgumentNullException(nameof(eventedStreamReader));
|
||||
_eventedStreamReader.OnReceivedLine += OnReceivedLine;
|
||||
}
|
||||
|
||||
public string ReadAsString() => _stringBuilder.ToString();
|
||||
|
||||
private void OnReceivedLine(string line) => _stringBuilder.AppendLine(line);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_isDisposed)
|
||||
{
|
||||
_eventedStreamReader.OnReceivedLine -= OnReceivedLine;
|
||||
_isDisposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Console;
|
||||
|
||||
namespace Microsoft.AspNetCore.SpaServices.Util
|
||||
{
|
||||
internal static class LoggerFinder
|
||||
{
|
||||
public static ILogger GetOrCreateLogger(
|
||||
IApplicationBuilder appBuilder,
|
||||
string logCategoryName)
|
||||
{
|
||||
// If the DI system gives us a logger, use it. Otherwise, set up a default one.
|
||||
var loggerFactory = appBuilder.ApplicationServices.GetService<ILoggerFactory>();
|
||||
var logger = loggerFactory != null
|
||||
? loggerFactory.CreateLogger(logCategoryName)
|
||||
: new ConsoleLogger(logCategoryName, null, false);
|
||||
return logger;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace Microsoft.AspNetCore.SpaServices.Util
|
||||
{
|
||||
internal static class TcpPortFinder
|
||||
{
|
||||
public static int FindAvailablePort()
|
||||
{
|
||||
var listener = new TcpListener(IPAddress.Loopback, 0);
|
||||
listener.Start();
|
||||
try
|
||||
{
|
||||
return ((IPEndPoint)listener.LocalEndpoint).Port;
|
||||
}
|
||||
finally
|
||||
{
|
||||
listener.Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user