private ExecutionResponse CreateTimeoutResponse(string route, int requestId) { var response = new ExecutionResponse(HttpStatusCode.SeeOther); var responseUri = $"{Settings.ApiBaseUri}/v1/{route}/{requestId}"; response.Headers.Add("Location", responseUri); response.StatusMessage = "Execution Timeout - Follow Location Header for Response"; return(response); }
public async Task <ExecutionResponse> ExecuteRouteAsync(string route, ExecutionRequest request) { // Load the route to be executed so we can get its identifier. var routeEntry = await _traceContext.TraceTimeAsync("Load-Route", _routeRepository.GetRouteByIdentifierAsync(route)); // Ensure the route is online. if (!routeEntry.IsOnline) { throw new OfflineException("Route is offline."); } // Ensure the route is published. if (!routeEntry.PublishedOn.HasValue) { throw new OfflineException("Route has not yet been published."); } // Create request data in database which we'll match the response to after execution. var serializedHeaders = HeaderUtility.SerializeHeaders(request.Headers); var requestEntry = new Request { RouteId = routeEntry.Id, Method = request.Method.ToString(), Uri = request.Uri.ToString(), RequestHeaders = serializedHeaders, RequestPayload = request.Body, IpAddress = request.IpAddress, OccurredOn = DateTimeOffset.UtcNow }; requestEntry = await _traceContext.TraceTimeAsync("Create-Request", _requestRepository.CreateRequestAsync(requestEntry)); // We will send just a pointer to the request in the database via the service bus to side step size restrictions. // We'll use the machine name as the correlation ID to allow the message to be returned to this waiting instance. var requestMessage = new BrokeredMessage { ReplyTo = Settings.ServiceBusResponseTopicName, CorrelationId = Environment.MachineName }; requestMessage.Properties["RouteId"] = routeEntry.Id; requestMessage.Properties["RequestId"] = requestEntry.Id; requestMessage.Properties["MachineName"] = Environment.MachineName; var topicClient = await _traceContext.TraceTimeAsync("Create-Topic-Client", _topicFactory.CreateTopicClientAsync(Settings.ServiceBusRequestTopicName)); await _traceContext.TraceTimeAsync("Send-Request", topicClient.SendAsync(requestMessage)); // We'll try to get a message every second to find our desired message. Otherwise we'll keep trying for up to configured duration. var maxWaitTime = TimeSpan.FromMinutes(Settings.ServiceBusResponseTimeoutMinutes); try { // We'll listen on the response pipeline observable for incoming messages that originated as // requests from this machine, and are filtered by CorrelationId (which is the machine name). // We have added a filter that will only listen for messages generated from this waiting // thread. We'll also specify that we'll wait no longer than the maximum specified timeout. // When we receive the message, we'll load the details from the database and convert it to // an ExecutionResponse object and select the first one (which there should only be one). var executionResponse = await _traceContext.TraceTimeAsync("Receive-Generate-Response", _responsePipeline.ResponseMessages .Where(bm => (int)bm.Properties["RequestId"] == requestEntry.Id) .Timeout(maxWaitTime) .Select(async bm => { // We have found our message, requery the database to get the response data. var responseEntry = await _traceContext.TraceTimeAsync("Load-Request-By-ID", _requestRepository.GetRequestByIdAsync(requestEntry.Id)); // Build a ExecutionResponse using details from database. var responseStatusCode = (HttpStatusCode)(responseEntry.StatusCode ?? 204); var deserializedHeaders = _traceContext.TraceTime("Deserialize-Headers", () => ExecutionResponse.DeserializeHeaders(responseEntry.ResponseHeaders)); var response = new ExecutionResponse(responseStatusCode) { StatusMessage = responseEntry.StatusMessage, Body = responseEntry.ResponsePayload, Headers = deserializedHeaders }; return(response); }).FirstOrDefault()); // We'll only arrive here if we actually received a matching message. Otherwise // a TimeoutException will be thrown and will produce a timeout response. return(executionResponse); } catch (TimeoutException) { // Maximum wait time elapsed, create an empty response and return it. return(_traceContext.TraceTime("Create-Empty-Response", () => CreateTimeoutResponse(route, requestEntry.Id))); } }