diff --git a/src/HttpMock.Unit.Tests/RequestProcessorTests.cs b/src/HttpMock.Unit.Tests/RequestProcessorTests.cs index 295d4ad..16e0571 100644 --- a/src/HttpMock.Unit.Tests/RequestProcessorTests.cs +++ b/src/HttpMock.Unit.Tests/RequestProcessorTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using Moq; using NUnit.Framework; @@ -65,21 +66,21 @@ public void Custom_verb_should_return_handler_with_custom_method_set() { } [Test] - public void If_no_handlers_found_should_fire_onresponse_with_a_404() { + public async Task If_no_handlers_found_should_fire_onresponse_with_a_404() { _processor = new RequestProcessor(_ruleThatReturnsNoHandlers.Object, new RequestHandlerList()); _processor.Add(_requestHandlerFactory.Get("test")); HttpMockResponseHead capturedHead = null; byte[] capturedBody = null; - _processor.OnRequest(new HttpRequestHead(), null, (h, b) => { capturedHead = h; capturedBody = b; }); + await _processor.OnRequest(new HttpRequestHead(), null, (h, b) => { capturedHead = h; capturedBody = b; }); Assert.That(capturedHead.Status, Is.EqualTo("404 NotFound")); Assert.That(capturedBody, Is.Null); } [Test] - public void If_a_handler_found_should_fire_onresponse_with_that_repsonse() { + public async Task If_a_handler_found_should_fire_onresponse_with_that_repsonse() { _processor = new RequestProcessor(_ruleThatReturnsFirstHandler.Object, new RequestHandlerList()); RequestHandler requestHandler = _requestHandlerFactory.Get("test"); @@ -88,7 +89,7 @@ public void If_a_handler_found_should_fire_onresponse_with_that_repsonse() { HttpMockResponseHead capturedHead = null; byte[] capturedBody = null; - _processor.OnRequest(new HttpRequestHead { Headers = headers }, null, + await _processor.OnRequest(new HttpRequestHead { Headers = headers }, null, (h, b) => { capturedHead = h; capturedBody = b; }); Assert.That(capturedHead.Status, Is.EqualTo(requestHandler.ResponseBuilder.BuildHeaders().Status)); @@ -96,7 +97,7 @@ public void If_a_handler_found_should_fire_onresponse_with_that_repsonse() { } [Test] - public void Matching_HEAD_handler_should_output_handlers_expected_response_with_null_body() { + public async Task Matching_HEAD_handler_should_output_handlers_expected_response_with_null_body() { _processor = new RequestProcessor(_ruleThatReturnsFirstHandler.Object, new RequestHandlerList()); @@ -106,7 +107,7 @@ public void Matching_HEAD_handler_should_output_handlers_expected_response_with_ HttpMockResponseHead capturedHead = null; byte[] capturedBody = null; - _processor.OnRequest(httpRequestHead, null, (h, b) => { capturedHead = h; capturedBody = b; }); + await _processor.OnRequest(httpRequestHead, null, (h, b) => { capturedHead = h; capturedBody = b; }); Assert.That(capturedHead, Is.Not.Null); Assert.That(capturedBody, Is.Null); @@ -129,7 +130,7 @@ public void When_a_handler_is_added_should_be_able_to_find_it() { } [Test] - public void When_a_handler_is_hit_handlers_request_count_is_incremented() { + public async Task When_a_handler_is_hit_handlers_request_count_is_incremented() { string expectedPath = "/blah/test"; string expectedMethod = "GET"; @@ -138,14 +139,14 @@ public void When_a_handler_is_hit_handlers_request_count_is_incremented() { requestProcessor.Add(_requestHandlerFactory.Get(expectedPath)); var httpRequestHead = new HttpRequestHead { Headers = new Dictionary(), Uri = expectedPath, Method = expectedPath }; - requestProcessor.OnRequest(httpRequestHead, null, (h, b) => { }); + await requestProcessor.OnRequest(httpRequestHead, null, (h, b) => { }); var handler = requestProcessor.FindHandler(expectedMethod, expectedPath); Assert.That(handler.RequestCount(), Is.EqualTo(1)); } [Test] - public void Returns_mock_not_found_when_handler_constraints_cannot_be_verified() + public async Task Returns_mock_not_found_when_handler_constraints_cannot_be_verified() { var excludePhrase = "OhMyDaysssss"; @@ -160,7 +161,7 @@ public void Returns_mock_not_found_when_handler_constraints_cannot_be_verified() var p = new RequestProcessor(matchingRule.Object, new RequestHandlerList { handlerWithConstraints }); HttpMockResponseHead capturedHead = null; - p.OnRequest(new HttpRequestHead { Uri = "http://blah.com/cheese/" + excludePhrase }, null, + await p.OnRequest(new HttpRequestHead { Uri = "http://blah.com/cheese/" + excludePhrase }, null, (h, b) => capturedHead = h); Assert.That(capturedHead.Status, Is.EqualTo("404 NotFound")); diff --git a/src/HttpMock/BufferedBody.cs b/src/HttpMock/BufferedBody.cs index e1c67c7..4fd21e8 100644 --- a/src/HttpMock/BufferedBody.cs +++ b/src/HttpMock/BufferedBody.cs @@ -17,7 +17,7 @@ public BufferedBody(Func data) _dataFunc = data; } - public Func Length => () => _dataFunc().Length; + public Func Length => () => GetBytes().Length; public byte[] GetBytes() { @@ -29,7 +29,7 @@ public void SetRequestHeaders(IDictionary requestHeaders) { } class NoBody : IResponse { - public byte[] GetBytes() => new byte[0]; + public byte[] GetBytes() => Array.Empty(); public void SetRequestHeaders(IDictionary requestHeaders) { } } diff --git a/src/HttpMock/EndpointMatchingRule.cs b/src/HttpMock/EndpointMatchingRule.cs index f81f4d8..6a168d2 100644 --- a/src/HttpMock/EndpointMatchingRule.cs +++ b/src/HttpMock/EndpointMatchingRule.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; @@ -9,6 +10,7 @@ namespace HttpMock { public class EndpointMatchingRule : IMatchingRule { + private static readonly ConcurrentDictionary PathRegexCache = new(); private readonly HeaderMatch _headerMatch; private readonly QueryParamMatch _queryParamMatch; @@ -22,29 +24,30 @@ public bool IsEndpointMatch(IRequestHandler requestHandler, IHttpRequestHead req if (requestHandler.QueryParams == null) throw new ArgumentException("requestHandler QueryParams cannot be null"); - var requestQueryParams = GetQueryParams(request); - var requestHeaders = GetHeaders(request); + // Check cheapest conditions first before parsing query/headers + bool httpMethodsMatch = requestHandler.Method == request.Method; + if (!httpMethodsMatch) return false; bool uriStartsWith = MatchPath(requestHandler, request); + if (!uriStartsWith) return false; - bool httpMethodsMatch = requestHandler.Method == request.Method; - - bool queryParamMatch = true; bool shouldMatchQueryParams = (requestHandler.QueryParams.Count > 0); - if (shouldMatchQueryParams) { - queryParamMatch = _queryParamMatch.MatchQueryParams(requestHandler, requestQueryParams); + var requestQueryParams = GetQueryParams(request); + if (!_queryParamMatch.MatchQueryParams(requestHandler, requestQueryParams)) + return false; } - bool headerMatch = true; bool shouldMatchHeaders = requestHandler.RequestHeaders != null && requestHandler.RequestHeaders.Count > 0; if (shouldMatchHeaders) { - headerMatch = _headerMatch.MatchHeaders(requestHandler, requestHeaders); + var requestHeaders = GetHeaders(request); + if (!_headerMatch.MatchHeaders(requestHandler, requestHeaders)) + return false; } - return uriStartsWith && httpMethodsMatch && queryParamMatch && headerMatch; + return true; } private static bool MatchPath(IRequestHandler requestHandler, IHttpRequestHead request) @@ -55,7 +58,8 @@ private static bool MatchPath(IRequestHandler requestHandler, IHttpRequestHead r { pathToMatch = request.Uri.Substring(0, positionOfQueryStart); } - var pathMatch = new Regex(string.Format(@"^{0}\/*$", Regex.Escape(requestHandler.Path))); + var pathMatch = PathRegexCache.GetOrAdd(requestHandler.Path, + static path => new Regex($@"^{Regex.Escape(path)}\/*$", RegexOptions.Compiled)); return pathMatch.IsMatch(pathToMatch); } diff --git a/src/HttpMock/FileResponseBody.cs b/src/HttpMock/FileResponseBody.cs index 370e0a1..c96d000 100644 --- a/src/HttpMock/FileResponseBody.cs +++ b/src/HttpMock/FileResponseBody.cs @@ -9,6 +9,7 @@ namespace HttpMock { class FileResponseBody : IResponse { + private static readonly Regex RangeRegex = new(@"bytes=([\d]*)-([\d]*)", RegexOptions.Compiled); private readonly string _filepath; private readonly ILogger _log; private IDictionary _requestHeaders; @@ -23,26 +24,29 @@ public byte[] GetBytes() var fileInfo = new FileInfo(_filepath); using (FileStream fileStream = fileInfo.Open(FileMode.Open, FileAccess.Read)) { - var buffer = new byte[fileInfo.Length]; - fileStream.ReadExactly(buffer, 0, (int)fileInfo.Length); int length = (int)fileInfo.Length; int offset = 0; if (_requestHeaders != null && _requestHeaders.ContainsKey(HttpRequestHeader.Range.ToString())) { string range = _requestHeaders[HttpRequestHeader.Range.ToString()]; - Regex rangeEx = new Regex(@"bytes=([\d]*)-([\d]*)"); - if (rangeEx.IsMatch(range)) + var match = RangeRegex.Match(range); + if (match.Success) { - int from = Convert.ToInt32(rangeEx.Match(range).Groups[1].Value); - int to = Convert.ToInt32(rangeEx.Match(range).Groups[2].Value); + int from = Convert.ToInt32(match.Groups[1].Value); + int to = Convert.ToInt32(match.Groups[2].Value); offset = from; length = (to - from) + 1; } } + if (offset > 0) + { + fileStream.Seek(offset, SeekOrigin.Begin); + } + var result = new byte[length]; - Array.Copy(buffer, offset, result, 0, length); + fileStream.ReadExactly(result, 0, length); _log.LogDebug("Wrote {Length} bytes to buffer", length); return result; } diff --git a/src/HttpMock/HeaderMatch.cs b/src/HttpMock/HeaderMatch.cs index f6d2b37..718858b 100644 --- a/src/HttpMock/HeaderMatch.cs +++ b/src/HttpMock/HeaderMatch.cs @@ -7,17 +7,29 @@ namespace HttpMock public class HeaderMatch { internal bool MatchHeaders(IRequestHandler requestHandler, IDictionary requestHeaders) { - return requestHandler.RequestHeaders.All( - expectedHeader => requestHeaders.Any(header => HeadersMatch(expectedHeader, header))); - } - - private static bool HeadersMatch(KeyValuePair expectedHeader, KeyValuePair header) - { - if (!string.Equals(expectedHeader.Key, header.Key, StringComparison.OrdinalIgnoreCase)) + foreach (var expectedHeader in requestHandler.RequestHeaders) { - return false; + if (!requestHeaders.TryGetValue(expectedHeader.Key, out var actualValue)) + { + // Try case-insensitive key lookup as a fallback + bool found = false; + foreach (var header in requestHeaders) + { + if (string.Equals(header.Key, expectedHeader.Key, StringComparison.OrdinalIgnoreCase) + && string.Equals(header.Value, expectedHeader.Value, StringComparison.OrdinalIgnoreCase)) + { + found = true; + break; + } + } + if (!found) return false; + } + else if (!string.Equals(actualValue, expectedHeader.Value, StringComparison.OrdinalIgnoreCase)) + { + return false; + } } - return string.Equals(expectedHeader.Value, header.Value, StringComparison.OrdinalIgnoreCase); + return true; } } } \ No newline at end of file diff --git a/src/HttpMock/HttpMockRepository.cs b/src/HttpMock/HttpMockRepository.cs index 3dd68cd..8651b9c 100644 --- a/src/HttpMock/HttpMockRepository.cs +++ b/src/HttpMock/HttpMockRepository.cs @@ -1,46 +1,46 @@ -using System; -using System.Net; -using System.Net.Sockets; -using Microsoft.Extensions.Logging; - -namespace HttpMock -{ - public static class HttpMockRepository - { - private static readonly HttpServerFactory _httpServerFactory = new HttpServerFactory(); - - public static IHttpServer At(string uri, ILoggerFactory loggerFactory = null) - { - if (uri.Trim().EndsWith("/")) - { - throw new ArgumentException( - String.Format("Do not use a trailing slash for the server URI please: {0}", uri), "uri"); - } - - return At(new Uri(uri), loggerFactory); - } - - public static IHttpServer At(Uri uri, ILoggerFactory loggerFactory = null) - { - return _httpServerFactory.Get(uri, loggerFactory).WithNewContext(); - } - - /// - /// Asks the operating system for a free TCP port on the loopback interface. - /// - /// - /// The port is reserved by on port 0 and released - /// immediately. There is a small TOCTOU window between the release and the - /// caller binding to the port; this is an accepted limitation for test/mock usage. - /// - /// An available port number. - public static int FindFreePort() - { - using var listener = new TcpListener(IPAddress.Loopback, 0); - listener.Start(); - var port = ((IPEndPoint)listener.LocalEndpoint).Port; - listener.Stop(); - return port; - } - } -} +using System; +using System.Net; +using System.Net.Sockets; +using Microsoft.Extensions.Logging; + +namespace HttpMock +{ + public static class HttpMockRepository + { + private static readonly HttpServerFactory _httpServerFactory = new HttpServerFactory(); + + public static IHttpServer At(string uri, ILoggerFactory loggerFactory = null) + { + if (uri.Trim().EndsWith("/")) + { + throw new ArgumentException( + String.Format("Do not use a trailing slash for the server URI please: {0}", uri), "uri"); + } + + return At(new Uri(uri), loggerFactory); + } + + public static IHttpServer At(Uri uri, ILoggerFactory loggerFactory = null) + { + return _httpServerFactory.Get(uri, loggerFactory).WithNewContext(); + } + + /// + /// Asks the operating system for a free TCP port on the loopback interface. + /// + /// + /// The port is reserved by on port 0 and released + /// immediately. There is a small TOCTOU window between the release and the + /// caller binding to the port; this is an accepted limitation for test/mock usage. + /// + /// An available port number. + public static int FindFreePort() + { + using var listener = new TcpListener(IPAddress.Loopback, 0); + listener.Start(); + var port = ((IPEndPoint)listener.LocalEndpoint).Port; + listener.Stop(); + return port; + } + } +} diff --git a/src/HttpMock/HttpServer.cs b/src/HttpMock/HttpServer.cs index e9cfebd..82f5cce 100644 --- a/src/HttpMock/HttpServer.cs +++ b/src/HttpMock/HttpServer.cs @@ -4,8 +4,8 @@ using System.Net; using System.Net.Sockets; using System.Runtime.InteropServices; -using System.Text; using System.Threading; +using System.Threading.Tasks; using Microsoft.Extensions.Logging; namespace HttpMock @@ -17,7 +17,6 @@ public class HttpServer : IHttpServer private readonly IRequestProcessor _requestProcessor; private readonly Uri _uri; private HttpListener _listener; - private Thread _listenerThread; private volatile bool _running; /// @@ -49,15 +48,14 @@ public void Start() if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && !RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && !string.IsNullOrEmpty(host) && host != "+" && host != "*" && host != "localhost") { - _listener.Prefixes.Add(string.Format("http://{0}:{1}/", host, _uri.Port)); + _listener.Prefixes.Add($"http://{host}:{_uri.Port}/"); } - _listener.Prefixes.Add(string.Format("http://+:{0}/", _uri.Port)); + _listener.Prefixes.Add($"http://+:{_uri.Port}/"); _listener.Start(); _running = true; - _listenerThread = new Thread(ListenLoop) { IsBackground = true }; - _listenerThread.Start(); + _ = ListenLoopAsync(); } if (!IsAvailable()) { @@ -105,8 +103,6 @@ public void Dispose() try { _listener.Stop(); } catch { } try { _listener.Close(); } catch { } } - if (_listenerThread != null && _listenerThread.IsAlive) - _listenerThread.Join(500); } public IRequestStub Stub(Func func) @@ -125,19 +121,25 @@ public string WhatDoIHave() return _requestProcessor.WhatDoIHave(); } - private void ListenLoop() + private async Task ListenLoopAsync() { while (_running) { try { - var context = _listener.GetContext(); - ThreadPool.QueueUserWorkItem(_ => HandleContext(context)); + var context = await _listener.GetContextAsync(); + _ = HandleContextAsync(context).ContinueWith( + t => _log.LogError(t.Exception?.InnerException ?? t.Exception, "Unhandled error in request handler"), + TaskContinuationOptions.OnlyOnFaulted); } catch (HttpListenerException) { if (!_running) break; } + catch (ObjectDisposedException) + { + break; + } catch (Exception ex) { _log.LogError(ex, "Error in listen loop"); @@ -145,11 +147,11 @@ private void ListenLoop() } } - private void HandleContext(HttpListenerContext context) + private async Task HandleContextAsync(HttpListenerContext context) { try { - var headers = new Dictionary(); + var headers = new Dictionary(context.Request.Headers.Count); foreach (string key in context.Request.Headers) { if (key != null) @@ -165,7 +167,7 @@ private void HandleContext(HttpListenerContext context) }; Stream body = context.Request.HasEntityBody ? context.Request.InputStream : null; - _requestProcessor.OnRequest(requestHead, body, (responseHead, responseBody) => + await _requestProcessor.OnRequest(requestHead, body, (responseHead, responseBody) => WriteResponse(context.Response, responseHead, responseBody)); } catch (Exception ex) @@ -189,17 +191,13 @@ private static void WriteResponse(HttpListenerResponse response, HttpMockRespons { foreach (var header in head.Headers) { - switch (header.Key.ToLowerInvariant()) + if (string.Equals(header.Key, "Content-Type", StringComparison.OrdinalIgnoreCase)) + { + response.ContentType = header.Value; + } + else if (!string.Equals(header.Key, "Content-Length", StringComparison.OrdinalIgnoreCase)) { - case "content-type": - response.ContentType = header.Value; - break; - case "content-length": - // set below via ContentLength64 - break; - default: - response.Headers[header.Key] = header.Value; - break; + response.Headers[header.Key] = header.Value; } } } @@ -220,4 +218,4 @@ private static void WriteResponse(HttpListenerResponse response, HttpMockRespons } } } -} +} \ No newline at end of file diff --git a/src/HttpMock/HttpServerFactory.cs b/src/HttpMock/HttpServerFactory.cs index cb5e590..a19a437 100644 --- a/src/HttpMock/HttpServerFactory.cs +++ b/src/HttpMock/HttpServerFactory.cs @@ -15,11 +15,20 @@ public IHttpServer Get(Uri uri, ILoggerFactory loggerFactory = null) { if (_httpServers.TryGetValue(uri.Port, out var existing) && existing.IsAvailable()) return existing; + } - var server = BuildServer(uri, loggerFactory); + var server = BuildServer(uri, loggerFactory); + lock (_serverLock) + { + // Double-check: another thread may have created a server while we were building + if (_httpServers.TryGetValue(uri.Port, out var existing) && existing.IsAvailable()) + { + server.Dispose(); + return existing; + } _httpServers[uri.Port] = server; - return server; } + return server; } public IHttpServer Create(Uri uri, ILoggerFactory loggerFactory = null) diff --git a/src/HttpMock/IRequestProcessor.cs b/src/HttpMock/IRequestProcessor.cs index 9ba75d2..fbf3ec7 100644 --- a/src/HttpMock/IRequestProcessor.cs +++ b/src/HttpMock/IRequestProcessor.cs @@ -1,11 +1,12 @@ using System; using System.IO; +using System.Threading.Tasks; namespace HttpMock { public interface IRequestProcessor { - void OnRequest(IHttpRequestHead request, Stream requestBody, Action respond); + Task OnRequest(IHttpRequestHead request, Stream requestBody, Action respond); IRequestVerify FindHandler(string method, string path); void Add(RequestHandler requestHandler); void ClearHandlers(); diff --git a/src/HttpMock/RequestMatcher.cs b/src/HttpMock/RequestMatcher.cs index d2e4457..a1ec956 100644 --- a/src/HttpMock/RequestMatcher.cs +++ b/src/HttpMock/RequestMatcher.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Linq; namespace HttpMock { @@ -19,12 +18,17 @@ public RequestMatcher(IMatchingRule matchingRule) public IRequestHandler Match(IHttpRequestHead request, IEnumerable requestHandlerList, string body = null) { - var matches = requestHandlerList - .Where(handler => _matchingRule.IsEndpointMatch(handler, request)) - .Where(handler => handler.CanVerifyConstraintsFor(request.Uri)) - .Where(handler => handler.MatchesBody(body)); + foreach (var handler in requestHandlerList) + { + if (_matchingRule.IsEndpointMatch(handler, request) + && handler.CanVerifyConstraintsFor(request.Uri) + && handler.MatchesBody(body)) + { + return handler; + } + } - return matches.FirstOrDefault(); + return null; } } } \ No newline at end of file diff --git a/src/HttpMock/RequestProcessor.cs b/src/HttpMock/RequestProcessor.cs index d4faafb..6ae22a6 100644 --- a/src/HttpMock/RequestProcessor.cs +++ b/src/HttpMock/RequestProcessor.cs @@ -22,8 +22,11 @@ public RequestProcessor(IMatchingRule matchingRule, IRequestHandlerList requestH _log = (loggerFactory ?? HttpMockLogging.GetLoggerFactory()).CreateLogger(); } - public void OnRequest(IHttpRequestHead request, Stream requestBody, Action respond) { - _log.LogDebug("Start Processing request for : {Method}:{Uri}", SanitizeForLog(request.Method), SanitizeForLog(request.Uri)); + public async Task OnRequest(IHttpRequestHead request, Stream requestBody, Action respond) { + if (_log.IsEnabled(LogLevel.Debug)) + { + _log.LogDebug("Start Processing request for : {Method}:{Uri}", SanitizeForLog(request.Method), SanitizeForLog(request.Uri)); + } if (GetHandlerCount() < 1) { using var noHandlersActivity = HttpMockActivitySource.Source.StartActivity("httpmock.request"); noHandlersActivity?.SetTag("http.request.method", request.Method); @@ -40,7 +43,7 @@ public void OnRequest(IHttpRequestHead request, Stream requestBody, Action _log.LogError(t.Exception?.InnerException ?? t.Exception, "Unhandled error processing request"), - TaskContinuationOptions.OnlyOnFaulted); + await HandleRequest(request, bufferedBody, respond, handler); } private async Task HandleRequest(IHttpRequestHead request, string bufferedBody, Action respond, IRequestHandler handler) @@ -101,14 +101,17 @@ private async Task HandleRequest(IHttpRequestHead request, string bufferedBody, var statusCode = statusParts is { Length: > 0 } ? statusParts[0] : null; activity?.SetTag("http.response.status_code", statusCode); respond(responseHead, responseBody); - _log.LogDebug("End Processing request for : {Method}:{Uri}", SanitizeForLog(request.Method), SanitizeForLog(request.Uri)); + if (_log.IsEnabled(LogLevel.Debug)) + { + _log.LogDebug("End Processing request for : {Method}:{Uri}", SanitizeForLog(request.Method), SanitizeForLog(request.Uri)); + } } private static string SanitizeForLog(string value) => value?.Replace("\r", "\\r").Replace("\n", "\\n"); private int GetHandlerCount() { - return _handlers.Count(); + return _handlers.Count; } public IRequestVerify FindHandler(string method, string path) { @@ -132,7 +135,7 @@ private static void ReturnHttpMockNotFound(Action var notFoundResponse = new HttpMockResponseHead { - Status = string.Format("{0} {1}", 404, "NotFound"), + Status = $"{404} NotFound", Headers = dictionary }; respond(notFoundResponse, null); diff --git a/src/HttpMock/ResponseBuilder.cs b/src/HttpMock/ResponseBuilder.cs index cb2ec81..faba8ba 100644 --- a/src/HttpMock/ResponseBuilder.cs +++ b/src/HttpMock/ResponseBuilder.cs @@ -50,7 +50,7 @@ public HttpMockResponseHead BuildHeaders(int? contentLength = null) { return new HttpMockResponseHead { - Status = string.Format("{0} {1}", (int)_httpStatusCode, _httpStatusCode), + Status = $"{(int)_httpStatusCode} {_httpStatusCode}", Headers = _headers }; } @@ -80,7 +80,7 @@ public void WithFileRange(string pathToFile, int from, int to) var fileInfo = new FileInfo(pathToFile); _contentLength = () => (to - from) + 1; _response = new FileResponseBody(pathToFile); - AddHeader(HttpResponseHeader.ContentRange.ToString(), string.Format("bytes={0}-{1}/{2}", from, to, fileInfo.Length)); + AddHeader(HttpResponseHeader.ContentRange.ToString(), $"bytes={from}-{to}/{fileInfo.Length}"); } else { diff --git a/src/HttpMock/StubNotFoundResponse.cs b/src/HttpMock/StubNotFoundResponse.cs index 28815e2..5e58ce8 100644 --- a/src/HttpMock/StubNotFoundResponse.cs +++ b/src/HttpMock/StubNotFoundResponse.cs @@ -6,7 +6,7 @@ public class StubNotFoundResponse : IStubResponse { public ResponseBuilder Get(IHttpRequestHead request) { var stubNotFoundResponseBuilder = new ResponseBuilder(); - stubNotFoundResponseBuilder.Return(string.Format("Stub not found for {0} : {1}", request.Method, request.Uri)); + stubNotFoundResponseBuilder.Return($"Stub not found for {request.Method} : {request.Uri}"); stubNotFoundResponseBuilder.WithStatus(HttpStatusCode.NotFound); return stubNotFoundResponseBuilder; }