private async Task <IEnumerable <PackageInfo> > FindPackagesByIdAsyncCore( string id, SourceCacheContext cacheContext, ILogger logger, CancellationToken cancellationToken) { for (var retry = 0; retry < 3; ++retry) { var uri = _baseUri + "FindPackagesById()?id='" + id + "'"; var httpSourceCacheContext = HttpSourceCacheContext.Create(cacheContext, retry); try { var results = new List <PackageInfo>(); var uris = new HashSet <string>(StringComparer.OrdinalIgnoreCase); uris.Add(uri); var page = 1; var paging = true; while (paging) { // TODO: Pages for a package ID are cached separately. // So we will get inaccurate data when a page shrinks. // However, (1) In most cases the pages grow rather than shrink; // (2) cache for pages is valid for only 30 min. // So we decide to leave current logic and observe. paging = await _httpSource.GetAsync( new HttpSourceCachedRequest( uri, $"list_{id.ToLowerInvariant()}_page{page}", httpSourceCacheContext) { AcceptHeaderValues = { new MediaTypeWithQualityHeaderValue("application/atom+xml"), new MediaTypeWithQualityHeaderValue("application/xml") }, EnsureValidContents = stream => HttpStreamValidation.ValidateXml(uri, stream), MaxTries = 1 }, async httpSourceResult => { if (httpSourceResult.Status == HttpSourceResultStatus.NoContent) { // Team city returns 204 when no versions of the package exist // This should result in an empty list and we should not try to // read the stream as xml. return(false); } var doc = await V2FeedParser.LoadXmlAsync(httpSourceResult.Stream); var result = doc.Root .Elements(_xnameEntry) .Select(x => BuildModel(id, x)) .Where(x => x != null); results.AddRange(result); // Find the next url for continuation var nextUri = V2FeedParser.GetNextUrl(doc); // Stop if there's nothing else to GET if (string.IsNullOrEmpty(nextUri)) { return(false); } // check for any duplicate url and error out if (!uris.Add(nextUri)) { throw new FatalProtocolException(string.Format( CultureInfo.CurrentCulture, Strings.Protocol_duplicateUri, nextUri)); } uri = nextUri; page++; return(true); }, logger, cancellationToken); } return(results); } catch (Exception ex) when(retry < 2) { var message = string.Format(CultureInfo.CurrentCulture, Strings.Log_RetryingFindPackagesById, nameof(FindPackagesByIdAsyncCore), uri) + Environment.NewLine + ExceptionUtilities.DisplayMessage(ex); logger.LogMinimal(message); } catch (Exception ex) when(retry == 2) { var message = string.Format( CultureInfo.CurrentCulture, Strings.Log_FailedToRetrievePackage, id, uri); throw new FatalProtocolException(message, ex); } } return(null); }
internal async Task <XDocument> LoadXmlAsync( string uri, string cacheKey, bool ignoreNotFounds, SourceCacheContext sourceCacheContext, ILogger log, CancellationToken token) { if (cacheKey != null && sourceCacheContext != null) { var httpSourceCacheContext = HttpSourceCacheContext.Create(sourceCacheContext, 0); try { return(await _httpSource.GetAsync( new HttpSourceCachedRequest( uri, cacheKey, httpSourceCacheContext) { AcceptHeaderValues = { new MediaTypeWithQualityHeaderValue("application/atom+xml"), new MediaTypeWithQualityHeaderValue("application/xml") }, EnsureValidContents = stream => HttpStreamValidation.ValidateXml(uri, stream), MaxTries = 1, IgnoreNotFounds = ignoreNotFounds }, async response => { if (ignoreNotFounds && response.Status == HttpSourceResultStatus.NotFound) { // Treat "404 Not Found" as an empty response. return null; } else if (response.Status == HttpSourceResultStatus.NoContent) { // Always treat "204 No Content" as exactly that. return null; } else { return await LoadXmlAsync(response.Stream, token); } }, log, token)); } catch (Exception ex) { var message = string.Format( CultureInfo.CurrentCulture, Strings.Log_FailedToFetchV2FeedHttp, uri, ex.Message); throw new FatalProtocolException(message, ex); } } else { // return results without httpCache return(await _httpSource.ProcessResponseAsync( new HttpSourceRequest( () => { var request = HttpRequestMessageFactory.Create(HttpMethod.Get, uri, log); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/atom+xml")); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml")); return request; }) { IsRetry = true }, async response => { if (response.StatusCode == HttpStatusCode.OK) { var networkStream = await response.Content.ReadAsStreamAsync(); return await LoadXmlAsync(networkStream, token); } else if (ignoreNotFounds && response.StatusCode == HttpStatusCode.NotFound) { // Treat "404 Not Found" as an empty response. return null; } else if (response.StatusCode == HttpStatusCode.NoContent) { // Always treat "204 No Content" as exactly that. return null; } else { throw new FatalProtocolException(string.Format( CultureInfo.CurrentCulture, Strings.Log_FailedToFetchV2Feed, uri, (int)response.StatusCode, response.ReasonPhrase)); } }, sourceCacheContext, log, token)); } }