public async Task <PagedCollection <Models.Requests.Request> > GetRequestsByDateRangeAsync(RouteIdentifier id, int skip, int take, DateTimeOffset?from, DateTimeOffset?to) { using (var db = new SubrouteContext()) { // Limit the request results by date range if one was provided. var query = db.Requests.AsNoTracking() .Where(r => from.HasValue && r.OccurredOn >= from) .Where(r => to.HasValue && r.OccurredOn <= to); // Ensure we are only returning requests for the specified route. query = id.Type == RouteIdentifierType.Id ? query.Where(r => r.RouteId == id.Id) : query.Where(r => r.Route.Uri == id.Uri); // Apply an order to the query to reverse chronological. query = query.OrderByDescending(r => r.OccurredOn); // Project and page query results into our wire format and materialize. // We project the results so we don't pull back the entire payload contents. var pagedResults = await query .Skip(skip) .Take(take) .Select(Models.Requests.Request.Map) .ToArrayAsync(); // Get total count and final projected results. var total = await query.CountAsync(); // Iterate the results and deserialize the headers for each request. foreach (var request in pagedResults) { request.RequestHeaders = HeaderUtility.DeserializeHeaders(request.SerializedRequestHeaders); request.ResponseHeaders = HeaderUtility.DeserializeHeaders(request.SerializedResponseHeaders); } return(new PagedCollection <Models.Requests.Request> { Skip = skip, Take = take, TotalCount = total, Results = pagedResults }); } }
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))); } }