/// <summary> /// Allows this instance to receive and process incoming requests. /// </summary> /// <exception cref="PlayerStateException"/> public void Start() { lock (statelock) { if (CurrentState != State.Off) { throw new PlayerStateException(CurrentState, "Player has already started."); } httpListener.Start(); Task.Run(() => { while (httpListener.IsListening) { var context = httpListener.GetContext(); var playerRequest = context.Request; var playerResponse = context.Response; lock (statelock) { try { switch (CurrentState) { case State.Playing: { var mock = (JObject)record.Read(); var mockRequest = MockRequest.FromJson((JObject)mock["request"]); var mockPlayerRequest = MockRequest.FromHttpRequest(remoteAddress, playerRequest); MockResponse mockResponse; var differences = mockRequest.GetDifferences(mockPlayerRequest).ToArray(); if (differences.Length == 0) { mockResponse = MockResponse.FromJson((JObject)mock["response"]); } else { var differencesMessage = string.Join(", ", differences.Select(d => $"{d.Property} (Recorded='{d.ThisValue}', Requested='{d.OtherValue}')")); var message = $"Player could not play the request at {playerRequest.Url.PathAndQuery}. " + $"The request doesn't match the current recorded one: {differencesMessage}"; mockResponse = MockResponse.FromPlayerError(PlayerErrorCode.RequestNotFound, "Player request mismatch", message); } BuildResponse(playerResponse, mockResponse); } break; case State.Recording: { var mockRequest = MockRequest.FromHttpRequest(remoteAddress, playerRequest); var request = BuildRequest(mockRequest); MockResponse mockResponse; try { using (var response = (HttpWebResponse)request.GetResponse()) { mockResponse = MockResponse.FromHttpResponse(response); } } catch (WebException ex) { if (ex.Response == null) { throw ex; } mockResponse = MockResponse.FromHttpResponse((HttpWebResponse)ex.Response); } var mock = JObject.FromObject(new { request = mockRequest.ToJson(), response = mockResponse.ToJson() }); record.Write(mock); BuildResponse(playerResponse, mockResponse); } break; default: throw new PlayerStateException(CurrentState, "Player is not in operation."); } } catch (Exception ex) { PlayerErrorCode errorCode; string process; switch (CurrentState) { case State.Playing: errorCode = PlayerErrorCode.PlayException; process = "play"; break; case State.Recording: errorCode = PlayerErrorCode.RecordException; process = "record"; break; default: errorCode = PlayerErrorCode.Exception; process = "process"; break; } var mockResponse = MockResponse.FromPlayerError(errorCode, "Player exception", $"Player could not {process} the request at {playerRequest.Url.PathAndQuery} because of exception: {ex}"); BuildResponse(playerResponse, mockResponse); } finally { playerResponse.Close(); } } } }); CurrentState = State.Idle; } }