private async Task <bool> QuerySources(IList <PersistedDataSource> sources) { Events.Write.BeginQuerySources(sources); var success = false; var transferRequest = new TransferRequest { DataType = PersistedDataProtocol.GetPersistedTypeCodeFromType(typeof(TInternal)), Timeout = (this.Timeout.Seconds * 9) / 10, // 90% of timeout goes to the child MaxFanout = this.MaxFanout, Sources = new List <string>() }; foreach (var source in sources) { source.Status = PersistedDataSourceStatus.Unknown; transferRequest.Sources.Add(source.Name); } var serverOffset = this.randomNumberGenerator.Next(sources.Count); var selectedSource = sources[serverOffset]; var request = new HttpRequestMessage(HttpMethod.Post, this.CreateRequestUri(selectedSource.Name)); using (var ms = (this.memoryStreamManager.GetStream())) using (var writeStream = new WriterStream(ms, this.memoryStreamManager)) { var writer = writeStream.CreateCompactBinaryWriter(); writer.Write(transferRequest); request.Content = new ByteArrayContent(ms.ToArray()); } try { Events.Write.BeginSendSourceQuery(selectedSource, request.RequestUri); var response = await this.httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); Events.Write.EndSendSourceQuery(selectedSource, (int)response.StatusCode, response.ReasonPhrase); if (!response.IsSuccessStatusCode) { switch (response.StatusCode) { case HttpStatusCode.NotFound: foreach (var source in sources) { source.Status = PersistedDataSourceStatus.Unavailable; } return(true); default: return(false); } } Events.Write.BeginReceiveSourceResponseBody(selectedSource); var length = response.Content.Headers.ContentLength ?? 0; using (var dataStream = this.memoryStreamManager.GetStream("PersistedDataAggregator/Http/Read", (int)length, true)) using (var responseStream = await response.Content.ReadAsStreamAsync()) { responseStream.CopyTo(dataStream); dataStream.Position = 0; using (var dataReader = new PersistedDataReader(dataStream, this.memoryStreamManager, this.DimensionSet)) { Events.Write.EndReceiveSourceResponseBody(selectedSource, (int)dataStream.Length); success = true; if (dataStream.Length == 0) { Events.Write.EmptyResponseReceivedFromSource(selectedSource); // Over the line. Mark it zero. Or just available, but empty (which is okay!) foreach (var source in sources) { source.Status = PersistedDataSourceStatus.Available; } } else { try { this.UnpackResponse(dataReader); } catch (PersistedDataException ex) { Events.Write.PersistedDataExceptionFromSource(selectedSource, ex); success = false; } } } } } catch (OperationCanceledException) { } catch (Exception ex) { if (ex is HttpRequestException || ex is IOException || ex is WebException || ex is ObjectDisposedException) { Events.Write.HttpExceptionFromSource(selectedSource, ex); } else { throw; } } Events.Write.EndQuerySources(success, sources); return(success); }
public override async Task<Response> ProcessRequest(Request request) { var counterName = request.Path; if (string.IsNullOrEmpty(counterName)) { return request.CreateErrorResponse(HttpStatusCode.BadRequest, "Path or command is invalid."); } if (request.QueryParameters.Count != 2 || !request.QueryParameters.ContainsKey(ReservedDimensions.StartTimeDimension) || !request.QueryParameters.ContainsKey(ReservedDimensions.EndTimeDimension)) { return request.CreateErrorResponse(HttpStatusCode.BadRequest, "Invalid query parameters. Must specify only start and end time."); } DateTime start, end; if (!DateTime.TryParse(request.QueryParameters[ReservedDimensions.StartTimeDimension], out start)) { return request.CreateErrorResponse(HttpStatusCode.BadRequest, "Invalid start time."); } if (!DateTime.TryParse(request.QueryParameters[ReservedDimensions.EndTimeDimension], out end)) { return request.CreateErrorResponse(HttpStatusCode.BadRequest, "Invalid end time."); } var message = new TransferRequest(); if (request.HasInputBody) { message = await request.ReadInputBody<TransferRequest>(); } var ms = request.GetStream(); // This is a tiered aggregation request, handle it separately. if (message.Sources != null && message.Sources.Count > 1) { try { using (var aggregator = PersistedDataProtocol.CreateAggregatorForSampleType(message.DataType, counterName, message.Sources, start, end, dataManager.MemoryStreamManager)) { if (message.Timeout > 0) { aggregator.Timeout = TimeSpan.FromSeconds(message.Timeout); } if (message.MaxFanout > 0) { aggregator.MaxFanout = (int)message.MaxFanout; } // This shouldn't happen, given that one of the servers we want to talk to is.. us. if (!await aggregator.Run()) { ms.Dispose(); return request.CreateErrorResponse(HttpStatusCode.InternalServerError, "All child requests failed."); } if (!aggregator.WriteToStream(ms)) { // TODO: If we have no results but none of our queries failed we can definitively 404, // for now lazy. ms.Dispose(); return request.CreateErrorResponse(HttpStatusCode.InternalServerError, "No cntent matched."); } } } catch (ArgumentException) { ms.Dispose(); return request.CreateErrorResponse(HttpStatusCode.BadRequest, "Request parameters are invalid."); } } else { var counter = this.dataManager.GetCounter<Counter>(counterName); if (counter == null || !counter.SerializeData(start, end, ms)) { ms.Dispose(); return request.CreateErrorResponse(HttpStatusCode.NotFound, "The requested data is not available."); } } // after writing the response the server will handle disposing the stream for us. return new Response(request, HttpStatusCode.OK, ms) {ContentType = ResponseType}; }