|
using System; |
|
using System.Collections.Generic; |
|
using System.Net; |
|
using System.Text; |
|
using System.Threading.Tasks; |
|
|
|
namespace UnitTest.Utilities; |
|
|
|
public class MockHttpServer : IDisposable |
|
{ |
|
private readonly HttpListener _httpListener; |
|
|
|
private volatile bool _keepGoing = true; |
|
|
|
private Task _mainLoop; |
|
|
|
private List<(Request Request, Response Response)> _responses = new List<(Request Request, Response Response)>(); |
|
|
|
public MockHttpServer(int port) |
|
{ |
|
BaseAddress = $"http://localhost:{port}/"; |
|
|
|
_httpListener = new HttpListener |
|
{ |
|
Prefixes = |
|
{ |
|
BaseAddress |
|
} |
|
}; |
|
} |
|
|
|
public string BaseAddress { get; private set; } |
|
|
|
/// <summary> |
|
/// Call this to start the web server |
|
/// </summary> |
|
public void Start() |
|
{ |
|
if (_mainLoop != null && !_mainLoop.IsCompleted) return; // Already started |
|
|
|
_mainLoop = MainLoop(); |
|
} |
|
|
|
/// <summary> |
|
/// Call this to stop the web server. It will not kill any requests currently being processed. |
|
/// </summary> |
|
public void Stop() |
|
{ |
|
_keepGoing = false; |
|
|
|
lock (_httpListener) |
|
{ |
|
// Use a lock so we don't kill a request that's currently being processed |
|
_httpListener.Stop(); |
|
} |
|
|
|
try |
|
{ |
|
_mainLoop.Wait(); |
|
} |
|
catch |
|
{ |
|
// Ignore |
|
} |
|
} |
|
|
|
public void AddResponse(Request request, Response response) |
|
{ |
|
_responses.Add((request, response)); |
|
} |
|
|
|
/// <summary> |
|
/// The main loop to handle requests into the HttpListener |
|
/// </summary> |
|
private async Task MainLoop() |
|
{ |
|
_httpListener.Start(); |
|
|
|
while (_keepGoing) |
|
{ |
|
try |
|
{ |
|
// GetContextAsync() returns when a new request come in |
|
var context = await _httpListener.GetContextAsync(); |
|
lock (_httpListener) |
|
{ |
|
if (_keepGoing) ProcessRequest(context); |
|
} |
|
} |
|
catch (Exception e) |
|
{ |
|
// This gets thrown when the listener is stopped |
|
if (e is HttpListenerException) return; |
|
} |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Handle an incoming request |
|
/// </summary> |
|
private void ProcessRequest(HttpListenerContext context) |
|
{ |
|
using (var response = context.Response) |
|
{ |
|
try |
|
{ |
|
var handled = false; |
|
|
|
foreach (var tuple in _responses) |
|
{ |
|
if (tuple.Request.Method != context.Request.HttpMethod) |
|
{ |
|
continue; |
|
} |
|
|
|
if (tuple.Request.Path != context.Request.RawUrl) |
|
{ |
|
continue; |
|
} |
|
|
|
response.StatusCode = (int)tuple.Response.StatusCode; |
|
response.ContentType = tuple.Response.ContentType; |
|
if (tuple.Response.Body != null) |
|
{ |
|
response.ContentLength64 = tuple.Response.Body.Length; |
|
response.OutputStream.Write(Encoding.UTF8.GetBytes(tuple.Response.Body)); |
|
} |
|
|
|
handled = true; |
|
break; |
|
} |
|
|
|
if (!handled) |
|
{ |
|
response.StatusCode = 404; |
|
} |
|
} |
|
catch (Exception ex) |
|
{ |
|
response.StatusCode = 500; |
|
response.ContentType = "application/json"; |
|
var buffer = Encoding.UTF8.GetBytes(ex.ToString()); |
|
response.ContentLength64 = buffer.Length; |
|
response.OutputStream.Write(buffer, 0, buffer.Length); |
|
} |
|
} |
|
} |
|
|
|
public record Request(string Method, string Path); |
|
|
|
public record Response(HttpStatusCode StatusCode, string Body, string ContentType); |
|
|
|
public void Dispose() |
|
{ |
|
Stop(); |
|
} |
|
} |