Example #1
0
        public static HttpCacheResult InitializeHttpCacheResult(
            string httpCacheDirectory,
            Uri baseUri,
            string cacheKey,
            HttpSourceCacheContext context)
        {
            // When the MaxAge is TimeSpan.Zero, this means the caller is passing in a folder different than
            // the global HTTP cache used by default. Additionally, creating and cleaning up the directory is
            // all the responsibility of the caller.
            var    maxAge = context.MaxAge;
            string newFile;
            string cacheFile;

            if (!maxAge.Equals(TimeSpan.Zero))
            {
                var baseFolderName = RemoveInvalidFileNameChars(ComputeHash(baseUri.OriginalString));
                var baseFileName   = RemoveInvalidFileNameChars(cacheKey) + ".dat";

                var cacheFolder = Path.Combine(httpCacheDirectory, baseFolderName);

                cacheFile = Path.Combine(cacheFolder, baseFileName);

                newFile = cacheFile + "-new";
            }
            else
            {
                cacheFile = Path.Combine(context.RootTempFolder, Path.GetRandomFileName());

                newFile = Path.Combine(context.RootTempFolder, Path.GetRandomFileName());
            }

            return(new HttpCacheResult(maxAge, newFile, cacheFile));
        }
Example #2
0
        // Read the source's end point to get the index json.
        // An exception will be thrown on failure.
        private async Task <ServiceIndexResourceV3> GetServiceIndexResourceV3(
            SourceRepository source,
            DateTime utcNow,
            ILogger log,
            CancellationToken token)
        {
            var url = source.PackageSource.Source;
            var httpSourceResource = await source.GetResourceAsync <HttpSourceResource>(token);

            var client = httpSourceResource.HttpSource;

            const int maxRetries = 3;

            for (var retry = 1; retry <= maxRetries; retry++)
            {
                using (var sourceCacheContext = new SourceCacheContext())
                {
                    var cacheContext = HttpSourceCacheContext.Create(sourceCacheContext, isFirstAttempt: retry == 1);

                    try
                    {
                        return(await client.GetAsync(
                                   new HttpSourceCachedRequest(
                                       url,
                                       "service_index",
                                       cacheContext)
                        {
                            EnsureValidContents = stream => HttpStreamValidation.ValidateJObject(url, stream),
                            MaxTries = 1,
                            IsRetry = retry > 1,
                            IsLastAttempt = retry == maxRetries
                        },
                                   async httpSourceResult =>
                        {
                            var result = await ConsumeServiceIndexStreamAsync(httpSourceResult.Stream, utcNow, token);

                            return result;
                        },
                                   log,
                                   token));
                    }
                    catch (Exception ex) when(retry < maxRetries)
                    {
                        var message = string.Format(CultureInfo.CurrentCulture, Strings.Log_RetryingServiceIndex, url)
                                      + Environment.NewLine
                                      + ExceptionUtilities.DisplayMessage(ex);

                        log.LogMinimal(message);
                    }
                    catch (Exception ex) when(retry == maxRetries)
                    {
                        var message = string.Format(CultureInfo.CurrentCulture, Strings.Log_FailedToReadServiceIndex, url);

                        throw new FatalProtocolException(message, ex);
                    }
                }
            }

            return(null);
        }
Example #3
0
        /// <summary>
        /// Query RegistrationIndex from nuget server for Package Manager UI. This implementation optimized for performance so instead of keeping giant JObject in memory we use strong types.
        /// </summary>
        /// <param name="httpSource">Httpsource instance</param>
        /// <param name="registrationUri">Package registration url</param>
        /// <param name="packageId">PackageId for package we're looking.</param>
        /// <param name="cacheContext">CacheContext for cache.</param>
        /// <param name="processAsync">Func expression used for HttpSource.cs</param>
        /// <param name="log">Logger Instance.</param>
        /// <param name="token">Cancellation token.</param>
        /// <returns></returns>
        private async Task <ValueTuple <RegistrationIndex, HttpSourceCacheContext> > LoadRegistrationIndexAsync(
            HttpSource httpSource,
            Uri registrationUri,
            string packageId,
            SourceCacheContext cacheContext,
            Func <HttpSourceResult, Task <RegistrationIndex> > processAsync,
            ILogger log,
            CancellationToken token)
        {
            var packageIdLowerCase = packageId.ToLowerInvariant();
            var retryCount         = 0;

            var httpSourceCacheContext = HttpSourceCacheContext.Create(cacheContext, retryCount);

            var index = await httpSource.GetAsync(
                new HttpSourceCachedRequest(
                    registrationUri.OriginalString,
                    $"list_{packageIdLowerCase}_index",
                    httpSourceCacheContext)
            {
                IgnoreNotFounds = true,
            },
                async httpSourceResult => await processAsync(httpSourceResult),
                log,
                token);

            return(new ValueTuple <RegistrationIndex, HttpSourceCacheContext>(index, httpSourceCacheContext));
        }
Example #4
0
        public static HttpCacheResult InitializeHttpCacheResult(
            string httpCacheDirectory,
            Uri sourceUri,
            string cacheKey,
            HttpSourceCacheContext context)
        {
            // When the MaxAge is TimeSpan.Zero, this means the caller is passing is using a temporary directory for
            // cache files, instead of the global HTTP cache used by default. Additionally, the cleaning up of this
            // directory is the responsibility of the caller.
            if (context.MaxAge > TimeSpan.Zero)
            {
                var baseFolderName = RemoveInvalidFileNameChars(ComputeHash(sourceUri.OriginalString));
                var baseFileName   = RemoveInvalidFileNameChars(cacheKey) + ".dat";
                var cacheFolder    = Path.Combine(httpCacheDirectory, baseFolderName);
                var cacheFile      = Path.Combine(cacheFolder, baseFileName);
                var newCacheFile   = cacheFile + "-new";

                return(new HttpCacheResult(
                           context.MaxAge,
                           newCacheFile,
                           cacheFile));
            }
            else
            {
                var temporaryFile    = Path.Combine(context.RootTempFolder, Path.GetRandomFileName());
                var newTemporaryFile = Path.Combine(context.RootTempFolder, Path.GetRandomFileName());

                return(new HttpCacheResult(
                           context.MaxAge,
                           newTemporaryFile,
                           temporaryFile));
            }
        }
Example #5
0
            public async Task WriteToCacheAsync(string cacheKey, string content)
            {
                var response = new HttpResponseMessage
                {
                    Content = new StringContent(content, Encoding.UTF8)
                };

                using (response)
                    using (var sourceCacheContext = new SourceCacheContext())
                    {
                        var httpSourceCacheContext = HttpSourceCacheContext.Create(sourceCacheContext, retryCount: 0);

                        var result = HttpCacheUtility.InitializeHttpCacheResult(
                            TestDirectory,
                            new Uri(FakeSource),
                            cacheKey,
                            httpSourceCacheContext);

                        await HttpCacheUtility.CreateCacheFileAsync(
                            result,
                            response,
                            stream => { },
                            CancellationToken.None);

                        result.Stream.Dispose();
                    }
            }
Example #6
0
        public static async Task CreateCacheFileAsync(
            HttpCacheResult result,
            string uri,
            HttpResponseMessage response,
            HttpSourceCacheContext context,
            Action <Stream> ensureValidContents,
            CancellationToken cancellationToken)
        {
            // The update of a cached file is divided into two steps:
            // 1) Delete the old file.
            // 2) Create a new file with the same name.
            using (var fileStream = new FileStream(
                       result.NewCacheFile,
                       FileMode.Create,
                       FileAccess.ReadWrite,
                       FileShare.None,
                       BufferSize,
                       useAsync: true))
            {
                using (var networkStream = await response.Content.ReadAsStreamAsync())
                {
                    await networkStream.CopyToAsync(fileStream, BufferSize, cancellationToken);
                }

                // Validate the content before putting it into the cache.
                fileStream.Seek(0, SeekOrigin.Begin);
                ensureValidContents?.Invoke(fileStream);
            }

            if (File.Exists(result.CacheFile))
            {
                // Process B can perform deletion on an opened file if the file is opened by process A
                // with FileShare.Delete flag. However, the file won't be actually deleted until A close it.
                // This special feature can cause race condition, so we never delete an opened file.
                if (!IsFileAlreadyOpen(result.CacheFile))
                {
                    File.Delete(result.CacheFile);
                }
            }

            // If the destination file doesn't exist, we can safely perform moving operation.
            // Otherwise, moving operation will fail.
            if (!File.Exists(result.CacheFile))
            {
                File.Move(
                    result.NewCacheFile,
                    result.CacheFile);
            }

            // Even the file deletion operation above succeeds but the file is not actually deleted,
            // we can still safely read it because it means that some other process just updated it
            // and we don't need to update it with the same content again.
            result.Stream = new FileStream(
                result.CacheFile,
                FileMode.Open,
                FileAccess.Read,
                FileShare.Read | FileShare.Delete,
                BufferSize,
                useAsync: true);
        }
Example #7
0
        /// <summary>
        /// Process RegistrationIndex
        /// </summary>
        /// <param name="httpSource">Httpsource instance.</param>
        /// <param name="rangeUri">Paged registration index url address.</param>
        /// <param name="packageId">PackageId for package we're checking.</param>
        /// <param name="lower">Lower bound of nuget package.</param>
        /// <param name="upper">Upper bound of nuget package.</param>
        /// <param name="httpSourceCacheContext">SourceCacheContext for cache.</param>
        /// <param name="log">Logger Instance.</param>
        /// <param name="token">Cancellation token.</param>
        /// <returns></returns>
        private Task <RegistrationPage> GetRegistratioIndexPageAsync(
            HttpSource httpSource,
            string rangeUri,
            string packageId,
            NuGetVersion lower,
            NuGetVersion upper,
            HttpSourceCacheContext httpSourceCacheContext,
            ILogger log,
            CancellationToken token)
        {
            var packageIdLowerCase = packageId.ToLowerInvariant();
            var registrationPage   = httpSource.GetAsync(
                new HttpSourceCachedRequest(
                    rangeUri,
                    $"list_{packageIdLowerCase}_range_{lower.ToNormalizedString()}-{upper.ToNormalizedString()}",
                    httpSourceCacheContext)
            {
                IgnoreNotFounds = true,
            },
                httpSourceResult => DeserializeStreamDataAsync <RegistrationPage>(httpSourceResult.Stream, token),
                log,
                token);

            return(registrationPage);
        }
Example #8
0
 private HttpCacheResult InitializeHttpCacheResult(PackageIdentity identity, SourceCacheContext sourceCacheContext)
 {
     return(HttpCacheUtility.InitializeHttpCacheResult(
                NuGetExe.GetHttpCachePath(this),
                new Uri(CurrentSource),
                $"nupkg_{identity.Id}.{identity.Version}",
                HttpSourceCacheContext.Create(sourceCacheContext, retryCount: 0)));
 }
Example #9
0
        private async Task <RepositorySignatureResource> GetRepositorySignatureResourceAsync(
            SourceRepository source,
            string repoSignUrl,
            ILogger log,
            CancellationToken token)
        {
            var httpSourceResource = await source.GetResourceAsync <HttpSourceResource>(token);

            var client = httpSourceResource.HttpSource;

            for (var retry = 0; retry < 3; retry++)
            {
                using (var sourecCacheContext = new SourceCacheContext())
                {
                    var cacheContext = HttpSourceCacheContext.Create(sourecCacheContext, retry);

                    try
                    {
                        return(await client.GetAsync(
                                   new HttpSourceCachedRequest(
                                       repoSignUrl,
                                       "repository_signature",
                                       cacheContext)
                        {
                            EnsureValidContents = stream => HttpStreamValidation.ValidateJObject(repoSignUrl, stream),
                            MaxTries = 1
                        },
                                   async httpSourceResult =>
                        {
                            var json = await httpSourceResult.Stream.AsJObjectAsync();

                            return new RepositorySignatureResource(json, source);
                        },
                                   log,
                                   token));
                    }
                    catch (Exception ex) when(retry < 2)
                    {
                        var message = string.Format(CultureInfo.CurrentCulture, Strings.Log_RetryingRepositorySignature, repoSignUrl)
                                      + Environment.NewLine
                                      + ExceptionUtilities.DisplayMessage(ex);

                        log.LogMinimal(message);
                    }
                    catch (Exception ex) when(retry == 2)
                    {
                        var message = string.Format(CultureInfo.CurrentCulture, Strings.Log_FailedToReadRepositorySignature, repoSignUrl);

                        throw new FatalProtocolException(message, ex);
                    }
                }
            }

            return(null);
        }
Example #10
0
        public HttpSourceCacheContext GetHttpCacheContext(int retryCount, bool directDownload = false)
        {
            // create http cache context from source cache instance
            var baseCache = _sourceContext;

            if (directDownload)
            {
                baseCache = _sourceContext.Clone();
                baseCache.DirectDownload = directDownload;
            }

            return(HttpSourceCacheContext.Create(baseCache, retryCount));
        }
Example #11
0
        // Read the source's end point to get the index json.
        // An exception will be thrown on failure.
        private async Task <ServiceIndexResourceV3> GetServiceIndexResourceV3(
            SourceRepository source,
            DateTime utcNow,
            ILogger log,
            CancellationToken token)
        {
            var url = source.PackageSource.Source;
            var httpSourceResource = await source.GetResourceAsync <HttpSourceResource>(token);

            var client = httpSourceResource.HttpSource;

            for (var retry = 0; retry < 3; retry++)
            {
                var cacheContext = HttpSourceCacheContext.CreateCacheContext(new SourceCacheContext(), retry);

                try
                {
                    using (var sourceResponse = await client.GetAsync(
                               new HttpSourceCachedRequest(
                                   url,
                                   "service_index",
                                   cacheContext)
                    {
                        EnsureValidContents = stream => HttpStreamValidation.ValidateJObject(url, stream)
                    },
                               log,
                               token))
                    {
                        return(ConsumeServiceIndexStream(sourceResponse.Stream, utcNow));
                    }
                }
                catch (Exception ex) when(retry < 2)
                {
                    var message = string.Format(CultureInfo.CurrentCulture, Strings.Log_RetryingServiceIndex, url)
                                  + Environment.NewLine
                                  + ExceptionUtilities.DisplayMessage(ex);

                    log.LogMinimal(message);
                }
                catch (Exception ex) when(retry == 2)
                {
                    var message = string.Format(CultureInfo.CurrentCulture, Strings.Log_FailedToReadServiceIndex, url);

                    log.LogError(message + Environment.NewLine + ExceptionUtilities.DisplayMessage(ex));

                    throw new FatalProtocolException(message, ex);
                }
            }

            return(null);
        }
Example #12
0
            public TestContext(TestDirectory testDirectory)
            {
                // data
                var source = FakeSource;

                CacheValidationException   = new Exception();
                NetworkValidationException = new Exception();
                CacheContent   = "cache";
                NetworkContent = "network";
                CacheKey       = "CacheKey";
                Url            = "https://fake.server/foo/bar/something.json";
                Credentials    = new NetworkCredential("foo", "bar");
                Throttle       = new Mock <IThrottle>();

                if (!RuntimeEnvironmentHelper.IsWindows)
                {
                    _cacheDirectory = "c810bdb33f8c56015efcaf435f94766aa0c4748c$https:_fake.server_users.json";
                }
                else
                {
                    // colon is not valid path character on Windows
                    _cacheDirectory = "c810bdb33f8c56015efcaf435f94766aa0c4748c$https_fake.server_users.json";
                }

                // dependencies
                var packageSource    = new PackageSource(source);
                var networkResponses = new Dictionary <string, string> {
                    { Url, NetworkContent }
                };
                var messageHandler  = new TestMessageHandler(networkResponses, string.Empty);
                var handlerResource = new TestHttpHandler(messageHandler);

                CacheContext     = new HttpSourceCacheContext();
                Logger           = new TestLogger();
                TestDirectory    = testDirectory;
                RetryHandlerMock = new Mock <IHttpRetryHandler>();

                // target
                HttpSource = new HttpSource(packageSource, () => Task.FromResult((HttpHandlerResource)handlerResource), Throttle.Object)
                {
                    HttpCacheDirectory = TestDirectory
                };
            }
Example #13
0
 /// <summary>
 /// Caching Get request.
 /// </summary>
 public Task <HttpSourceResult> GetAsync(
     string uri,
     string cacheKey,
     HttpSourceCacheContext cacheContext,
     ILogger log,
     bool ignoreNotFounds,
     Action <Stream> ensureValidContents,
     CancellationToken cancellationToken)
 {
     return(GetAsync(
                uri,
                new MediaTypeWithQualityHeaderValue[0],
                cacheKey,
                cacheContext,
                log,
                ignoreNotFounds,
                ensureValidContents,
                cancellationToken));
 }
        public HttpSourceCachedRequest(string uri, string cacheKey, HttpSourceCacheContext cacheContext)
        {
            if (uri == null)
            {
                throw new ArgumentNullException(nameof(uri));
            }

            if (cacheKey == null)
            {
                throw new ArgumentNullException(nameof(cacheKey));
            }

            if (cacheContext == null)
            {
                throw new ArgumentNullException(nameof(cacheContext));
            }

            Uri          = uri;
            CacheKey     = cacheKey;
            CacheContext = cacheContext;
        }
Example #15
0
        /// <summary>
        /// CatalogReader
        /// </summary>
        /// <param name="indexUri">URI of the catalog service or the feed service index.</param>
        /// <param name="httpSource">Custom HttpSource.</param>
        public CatalogReader(Uri indexUri, HttpSource httpSource, SourceCacheContext cacheContext, TimeSpan cacheTimeout, ILogger log)
        {
            _indexUri           = indexUri ?? throw new ArgumentNullException(nameof(indexUri));
            _log                = log ?? NullLogger.Instance;
            _sourceCacheContext = cacheContext ?? new SourceCacheContext();

            _httpSource = httpSource;

            // TODO: what should retry be?
            _cacheContext = HttpSourceCacheContext.Create(_sourceCacheContext, 5);

            if (_sourceCacheContext == null)
            {
                var sourceCacheContext = new SourceCacheContext()
                {
                    MaxAge = DateTimeOffset.UtcNow.Subtract(cacheTimeout),
                };

                _cacheContext = HttpSourceCacheContext.Create(sourceCacheContext, 5);
            }
        }
        internal static Task <NuspecReader> GetNuspecAsync(this HttpSource source, Uri uri, HttpSourceCacheContext cacheContext, ILogger log, CancellationToken token)
        {
            var cacheKey = GetHashKey(uri);

            var request = new HttpSourceCachedRequest(uri.AbsoluteUri, cacheKey, cacheContext)
            {
                IgnoreNotFounds     = false,
                EnsureValidContents = stream =>
                {
                    using (var reader = new StreamReader(stream, Encoding.UTF8, false, 8192, true))
                    {
                        XDocument.Load(reader);
                    }
                }
            };

            return(source.GetAsync(request, ProcessNuspec, log, token));
        }
        internal static Task <JObject> GetJObjectAsync(this HttpSource source, Uri uri, HttpSourceCacheContext cacheContext, ILogger log, CancellationToken token)
        {
            var cacheKey = GetHashKey(uri);

            var request = new HttpSourceCachedRequest(uri.AbsoluteUri, cacheKey, cacheContext)
            {
                EnsureValidContents = stream => CatalogReaderUtility.LoadJson(stream, true),
                IgnoreNotFounds     = false
            };

            return(source.GetAsync(request, ProcessJson, log, token));
        }
Example #18
0
        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));
            }
        }
Example #19
0
        private async Task <T> ProcessHttpSourceResultAsync <T>(
            PackageIdentity identity,
            string url,
            Func <HttpSourceResult, Task <T> > processAsync,
            SourceCacheContext cacheContext,
            ILogger logger,
            CancellationToken token)
        {
            for (var retry = 0; retry < 3; ++retry)
            {
                var httpSourceCacheContext = HttpSourceCacheContext.Create(cacheContext, retry);

                try
                {
                    return(await _httpSource.GetAsync(
                               new HttpSourceCachedRequest(
                                   url,
                                   "nupkg_" + identity.Id.ToLowerInvariant() + "." + identity.Version.ToNormalizedString(),
                                   httpSourceCacheContext)
                    {
                        EnsureValidContents = stream => HttpStreamValidation.ValidateNupkg(url, stream),
                        IgnoreNotFounds = true,
                        MaxTries = 1
                    },
                               async httpSourceResult => await processAsync(httpSourceResult),
                               logger,
                               token));
                }
                catch (TaskCanceledException) when(retry < 2)
                {
                    // Requests can get cancelled if we got the data from elsewhere, no reason to warn.
                    var message = string.Format(CultureInfo.CurrentCulture, Strings.Log_CanceledNupkgDownload, url);

                    logger.LogMinimal(message);
                }
                catch (Exception ex) when(retry < 2)
                {
                    var message = string.Format(
                        CultureInfo.CurrentCulture,
                        Strings.Log_FailedToDownloadPackage,
                        identity,
                        url)
                                  + Environment.NewLine
                                  + ExceptionUtilities.DisplayMessage(ex);

                    logger.LogMinimal(message);
                }
                catch (Exception ex) when(retry == 2)
                {
                    var message = string.Format(
                        CultureInfo.CurrentCulture,
                        Strings.Log_FailedToDownloadPackage,
                        identity,
                        url)
                                  + Environment.NewLine
                                  + ExceptionUtilities.DisplayMessage(ex);

                    logger.LogError(message);
                }
            }

            return(await processAsync(null));
        }
Example #20
0
        private async Task <SortedDictionary <NuGetVersion, PackageInfo> > FindPackagesByIdAsync(
            string id,
            SourceCacheContext cacheContext,
            ILogger logger,
            CancellationToken cancellationToken)
        {
            // Try each base URI 3 times.
            var maxTries           = 3 * _baseUris.Count;
            var packageIdLowerCase = id.ToLowerInvariant();

            for (var retry = 0; retry < maxTries; ++retry)
            {
                var baseUri = _baseUris[retry % _baseUris.Count].OriginalString;
                var uri     = baseUri + packageIdLowerCase + "/index.json";
                var httpSourceCacheContext = HttpSourceCacheContext.Create(cacheContext, retry);

                try
                {
                    return(await _httpSource.GetAsync(
                               new HttpSourceCachedRequest(
                                   uri,
                                   $"list_{packageIdLowerCase}",
                                   httpSourceCacheContext)
                    {
                        IgnoreNotFounds = true,
                        EnsureValidContents = stream => HttpStreamValidation.ValidateJObject(uri, stream),
                        MaxTries = 1
                    },
                               async httpSourceResult =>
                    {
                        var result = new SortedDictionary <NuGetVersion, PackageInfo>();

                        if (httpSourceResult.Status == HttpSourceResultStatus.OpenedFromDisk)
                        {
                            try
                            {
                                result = await ConsumeFlatContainerIndexAsync(httpSourceResult.Stream, id, baseUri, cancellationToken);
                            }
                            catch
                            {
                                logger.LogWarning(string.Format(CultureInfo.CurrentCulture, Strings.Log_FileIsCorrupt, httpSourceResult.CacheFile));

                                throw;
                            }
                        }
                        else if (httpSourceResult.Status == HttpSourceResultStatus.OpenedFromNetwork)
                        {
                            result = await ConsumeFlatContainerIndexAsync(httpSourceResult.Stream, id, baseUri, cancellationToken);
                        }

                        return result;
                    },
                               logger,
                               cancellationToken));
                }
                catch (Exception ex) when(retry < 2)
                {
                    var message = string.Format(CultureInfo.CurrentCulture, Strings.Log_RetryingFindPackagesById, nameof(FindPackagesByIdAsync), 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);
        }
        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 static Task <HttpSourceResult> GetNupkgAsync(this HttpSource source, Uri uri, HttpSourceCacheContext cacheContext, ILogger log, CancellationToken token)
        {
            var cacheKey = GetHashKey(uri);

            var request = new HttpSourceCachedRequest(uri.AbsoluteUri, cacheKey, cacheContext)
            {
                IgnoreNotFounds     = false,
                EnsureValidContents = stream =>
                {
                    using (var reader = new PackageArchiveReader(stream, leaveStreamOpen: true))
                    {
                        reader.NuspecReader.GetIdentity();
                    }
                }
            };

            return(source.GetAsync(request, ProcessResult, log, token));
        }
Example #23
0
        private async Task <T> ProcessHttpSourceResultAsync <T>(
            PackageIdentity identity,
            string url,
            Func <HttpSourceResult, Task <T> > processAsync,
            SourceCacheContext cacheContext,
            ILogger logger,
            CancellationToken token)
        {
            int maxRetries = _enhancedHttpRetryHelper.EnhancedHttpRetryEnabled ? _enhancedHttpRetryHelper.ExperimentalMaxNetworkTryCount : 3;

            for (var retry = 1; retry <= maxRetries; ++retry)
            {
                var httpSourceCacheContext = HttpSourceCacheContext.Create(cacheContext, isFirstAttempt: retry == 1);

                try
                {
                    return(await _httpSource.GetAsync(
                               new HttpSourceCachedRequest(
                                   url,
                                   "nupkg_" + identity.Id.ToLowerInvariant() + "." + identity.Version.ToNormalizedString(),
                                   httpSourceCacheContext)
                    {
                        EnsureValidContents = stream => HttpStreamValidation.ValidateNupkg(url, stream),
                        IgnoreNotFounds = true,
                        MaxTries = 1,
                        IsRetry = retry > 1,
                        IsLastAttempt = retry == maxRetries
                    },
                               async httpSourceResult => await processAsync(httpSourceResult),
                               logger,
                               token));
                }
                catch (TaskCanceledException) when(retry < maxRetries)
                {
                    // Requests can get cancelled if we got the data from elsewhere, no reason to warn.
                    var message = string.Format(CultureInfo.CurrentCulture, Strings.Log_CanceledNupkgDownload, url);

                    logger.LogMinimal(message);
                }
                catch (Exception ex) when(retry < maxRetries)
                {
                    var message = string.Format(
                        CultureInfo.CurrentCulture,
                        Strings.Log_FailedToDownloadPackage,
                        identity,
                        url)
                                  + Environment.NewLine
                                  + ExceptionUtilities.DisplayMessage(ex);

                    logger.LogMinimal(message);

                    if (_enhancedHttpRetryHelper.EnhancedHttpRetryEnabled &&
                        ex.InnerException != null &&
                        ex.InnerException is IOException &&
                        ex.InnerException.InnerException != null &&
                        ex.InnerException.InnerException is System.Net.Sockets.SocketException)
                    {
                        // An IO Exception with inner SocketException indicates server hangup ("Connection reset by peer").
                        // Azure DevOps feeds sporadically do this due to mandatory connection cycling.
                        // Stalling an extra <ExperimentalRetryDelayMilliseconds> gives Azure more of a chance to recover.
                        logger.LogVerbose("Enhanced retry: Encountered SocketException, delaying between tries to allow recovery");
                        await Task.Delay(TimeSpan.FromMilliseconds(_enhancedHttpRetryHelper.ExperimentalRetryDelayMilliseconds));
                    }
                }
                catch (Exception ex) when(retry == maxRetries)
                {
                    var message = string.Format(
                        CultureInfo.CurrentCulture,
                        Strings.Log_FailedToDownloadPackage,
                        identity,
                        url)
                                  + Environment.NewLine
                                  + ExceptionUtilities.DisplayMessage(ex);

                    logger.LogError(message);
                }
            }

            return(await processAsync(null));
        }
Example #24
0
        private async Task <SortedDictionary <NuGetVersion, PackageInfo> > FindPackagesByIdAsync(
            string id,
            SourceCacheContext cacheContext,
            ILogger logger,
            CancellationToken cancellationToken)
        {
            // Try each base URI _maxRetries times.
            var maxTries           = _maxRetries * _baseUris.Count;
            var packageIdLowerCase = id.ToLowerInvariant();

            for (var retry = 1; retry <= maxTries; ++retry)
            {
                var baseUri = _baseUris[retry % _baseUris.Count].OriginalString;
                var uri     = baseUri + packageIdLowerCase + "/index.json";
                var httpSourceCacheContext = HttpSourceCacheContext.Create(cacheContext, isFirstAttempt: retry == 1);

                try
                {
                    return(await _httpSource.GetAsync(
                               new HttpSourceCachedRequest(
                                   uri,
                                   $"list_{packageIdLowerCase}",
                                   httpSourceCacheContext)
                    {
                        IgnoreNotFounds = true,
                        EnsureValidContents = stream => HttpStreamValidation.ValidateJObject(uri, stream),
                        MaxTries = 1,
                        IsRetry = retry > 1,
                        IsLastAttempt = retry == maxTries
                    },
                               async httpSourceResult =>
                    {
                        var result = new SortedDictionary <NuGetVersion, PackageInfo>();

                        if (httpSourceResult.Status == HttpSourceResultStatus.OpenedFromDisk)
                        {
                            try
                            {
                                result = await ConsumeFlatContainerIndexAsync(httpSourceResult.Stream, id, baseUri, cancellationToken);
                            }
                            catch
                            {
                                logger.LogWarning(string.Format(CultureInfo.CurrentCulture, Strings.Log_FileIsCorrupt, httpSourceResult.CacheFile));

                                throw;
                            }
                        }
                        else if (httpSourceResult.Status == HttpSourceResultStatus.OpenedFromNetwork)
                        {
                            result = await ConsumeFlatContainerIndexAsync(httpSourceResult.Stream, id, baseUri, cancellationToken);
                        }

                        return result;
                    },
                               logger,
                               cancellationToken));
                }
                catch (Exception ex) when(retry < _maxRetries)
                {
                    var message = string.Format(CultureInfo.CurrentCulture, Strings.Log_RetryingFindPackagesById, nameof(FindPackagesByIdAsync), uri)
                                  + Environment.NewLine
                                  + ExceptionUtilities.DisplayMessage(ex);

                    logger.LogMinimal(message);

                    if (_enhancedHttpRetryHelper.IsEnabled &&
                        ex.InnerException != null &&
                        ex.InnerException is IOException &&
                        ex.InnerException.InnerException != null &&
                        ex.InnerException.InnerException is System.Net.Sockets.SocketException)
                    {
                        // An IO Exception with inner SocketException indicates server hangup ("Connection reset by peer").
                        // Azure DevOps feeds sporadically do this due to mandatory connection cycling.
                        // Stalling an extra <ExperimentalRetryDelayMilliseconds> gives Azure more of a chance to recover.
                        logger.LogVerbose("Enhanced retry: Encountered SocketException, delaying between tries to allow recovery");
                        await Task.Delay(TimeSpan.FromMilliseconds(_enhancedHttpRetryHelper.DelayInMilliseconds), cancellationToken);
                    }
                }
                catch (Exception ex) when(retry == _maxRetries)
                {
                    var message = string.Format(
                        CultureInfo.CurrentCulture,
                        Strings.Log_FailedToRetrievePackage,
                        id,
                        uri);

                    throw new FatalProtocolException(message, ex);
                }
            }

            return(null);
        }
 public HttpCacheResult InitializeHttpCacheResult(string httpCacheDirectory, Uri sourceUri, string cacheKey, HttpSourceCacheContext context)
 {
     return(HttpCacheUtility.InitializeHttpCacheResult(
                httpCacheDirectory,
                sourceUri,
                cacheKey,
                context));
 }
Example #26
0
        public async static Task <IEnumerable <JObject> > LoadRanges(
            HttpSource httpSource,
            Uri registrationUri,
            string packageId,
            VersionRange range,
            SourceCacheContext cacheContext,
            ILogger log,
            CancellationToken token)
        {
            var packageIdLowerCase = packageId.ToLowerInvariant();

            var httpSourceCacheContext = HttpSourceCacheContext.Create(cacheContext, 0);

            var index = await httpSource.GetAsync(
                new HttpSourceCachedRequest(
                    registrationUri.OriginalString,
                    $"list_{packageIdLowerCase}_index",
                    httpSourceCacheContext)
            {
                IgnoreNotFounds = true,
            },
                async httpSourceResult =>
            {
                return(await httpSourceResult.Stream.AsJObjectAsync(token));
            },
                log,
                token);

            if (index == null)
            {
                // The server returned a 404, the package does not exist
                return(Enumerable.Empty <JObject>());
            }

            IList <Task <JObject> > rangeTasks = new List <Task <JObject> >();

            foreach (JObject item in index["items"])
            {
                var lower = NuGetVersion.Parse(item["lower"].ToString());
                var upper = NuGetVersion.Parse(item["upper"].ToString());

                if (range.DoesRangeSatisfy(lower, upper))
                {
                    JToken items;
                    if (!item.TryGetValue("items", out items))
                    {
                        var rangeUri = item["@id"].ToString();

                        rangeTasks.Add(httpSource.GetAsync(
                                           new HttpSourceCachedRequest(
                                               rangeUri,
                                               $"list_{packageIdLowerCase}_range_{lower.ToNormalizedString()}-{upper.ToNormalizedString()}",
                                               httpSourceCacheContext)
                        {
                            IgnoreNotFounds = true,
                        },
                                           async httpSourceResult =>
                        {
                            return(await httpSourceResult.Stream.AsJObjectAsync(token));
                        },
                                           log,
                                           token));
                    }
                    else
                    {
                        rangeTasks.Add(Task.FromResult(item));
                    }
                }
            }

            await Task.WhenAll(rangeTasks.ToArray());

            return(rangeTasks.Select((t) => t.Result));
        }
        /// <summary>
        /// Read the source's end point to get the index json.
        /// Retries are logged to any provided <paramref name="log"/> as LogMinimal.
        /// </summary>
        /// <param name="source"></param>
        /// <param name="utcNow"></param>
        /// <param name="log"></param>
        /// <param name="token"></param>
        /// <exception cref="OperationCanceledException">Logged to any provided <paramref name="log"/> as LogMinimal prior to throwing.</exception>
        /// <exception cref="FatalProtocolException">Encapsulates all other exceptions.</exception>
        /// <returns></returns>
        private async Task <ServiceIndexResourceV3> GetServiceIndexResourceV3(
            SourceRepository source,
            DateTime utcNow,
            ILogger log,
            CancellationToken token)
        {
            var url = source.PackageSource.Source;
            var httpSourceResource = await source.GetResourceAsync <HttpSourceResource>(token);

            var client = httpSourceResource.HttpSource;

            int maxRetries = _enhancedHttpRetryHelper.IsEnabled ? _enhancedHttpRetryHelper.RetryCount : 3;

            for (var retry = 1; retry <= maxRetries; retry++)
            {
                using (var sourceCacheContext = new SourceCacheContext())
                {
                    var cacheContext = HttpSourceCacheContext.Create(sourceCacheContext, isFirstAttempt: retry == 1);

                    try
                    {
                        return(await client.GetAsync(
                                   new HttpSourceCachedRequest(
                                       url,
                                       "service_index",
                                       cacheContext)
                        {
                            EnsureValidContents = stream => HttpStreamValidation.ValidateJObject(url, stream),
                            MaxTries = 1,
                            IsRetry = retry > 1,
                            IsLastAttempt = retry == maxRetries
                        },
                                   async httpSourceResult =>
                        {
                            var result = await ConsumeServiceIndexStreamAsync(httpSourceResult.Stream, utcNow, token);

                            return result;
                        },
                                   log,
                                   token));
                    }
                    catch (OperationCanceledException ex)
                    {
                        var message = ExceptionUtilities.DisplayMessage(ex);
                        log.LogMinimal(message);
                        throw;
                    }
                    catch (Exception ex) when(retry < maxRetries)
                    {
                        var message = string.Format(CultureInfo.CurrentCulture, Strings.Log_RetryingServiceIndex, url)
                                      + Environment.NewLine
                                      + ExceptionUtilities.DisplayMessage(ex);

                        log.LogMinimal(message);

                        if (_enhancedHttpRetryHelper.IsEnabled &&
                            ex.InnerException != null &&
                            ex.InnerException is IOException &&
                            ex.InnerException.InnerException != null &&
                            ex.InnerException.InnerException is System.Net.Sockets.SocketException)
                        {
                            // An IO Exception with inner SocketException indicates server hangup ("Connection reset by peer").
                            // Azure DevOps feeds sporadically do this due to mandatory connection cycling.
                            // Stalling an extra <ExperimentalRetryDelayMilliseconds> gives Azure more of a chance to recover.
                            log.LogVerbose("Enhanced retry: Encountered SocketException, delaying between tries to allow recovery");
                            await Task.Delay(TimeSpan.FromMilliseconds(_enhancedHttpRetryHelper.DelayInMilliseconds), token);
                        }
                    }
                    catch (Exception ex) when(retry == maxRetries)
                    {
                        var message = string.Format(CultureInfo.CurrentCulture, Strings.Log_FailedToReadServiceIndex, url);

                        throw new FatalProtocolException(message, ex);
                    }
                }
            }

            return(null);
        }
Example #28
0
        private async Task <RepositorySignatureResource> GetRepositorySignatureResourceAsync(
            SourceRepository source,
            ServiceIndexEntry serviceEntry,
            ILogger log,
            CancellationToken token)
        {
            var repositorySignaturesResourceUri = serviceEntry.Uri;

            if (repositorySignaturesResourceUri == null || !string.Equals(repositorySignaturesResourceUri.Scheme, "https", StringComparison.OrdinalIgnoreCase))
            {
                throw new FatalProtocolException(string.Format(CultureInfo.CurrentCulture, Strings.RepositorySignaturesResourceMustBeHttps, source.PackageSource.Source));
            }

            var httpSourceResource = await source.GetResourceAsync <HttpSourceResource>(token);

            var client   = httpSourceResource.HttpSource;
            var cacheKey = GenerateCacheKey(serviceEntry);

            const int maxRetries = 3;

            for (var retry = 1; retry <= maxRetries; retry++)
            {
                using (var sourceCacheContext = new SourceCacheContext())
                {
                    var cacheContext = HttpSourceCacheContext.Create(sourceCacheContext, isFirstAttempt: retry == 1);

                    try
                    {
                        return(await client.GetAsync(
                                   new HttpSourceCachedRequest(
                                       serviceEntry.Uri.AbsoluteUri,
                                       cacheKey,
                                       cacheContext)
                        {
                            EnsureValidContents = stream => HttpStreamValidation.ValidateJObject(repositorySignaturesResourceUri.AbsoluteUri, stream),
                            MaxTries = 1,
                            IsRetry = retry > 1,
                            IsLastAttempt = retry == maxRetries
                        },
                                   async httpSourceResult =>
                        {
                            var json = await httpSourceResult.Stream.AsJObjectAsync(token);

                            return new RepositorySignatureResource(json, source);
                        },
                                   log,
                                   token));
                    }
                    catch (Exception ex) when(retry < maxRetries)
                    {
                        var message = string.Format(CultureInfo.CurrentCulture, Strings.Log_RetryingRepositorySignature, repositorySignaturesResourceUri.AbsoluteUri)
                                      + Environment.NewLine
                                      + ExceptionUtilities.DisplayMessage(ex);

                        log.LogMinimal(message);
                    }
                    catch (Exception ex) when(retry == maxRetries)
                    {
                        var message = string.Format(CultureInfo.CurrentCulture, Strings.Log_FailedToReadRepositorySignature, repositorySignaturesResourceUri.AbsoluteUri);

                        throw new FatalProtocolException(message, ex);
                    }
                }
            }

            return(null);
        }
Example #29
0
        /// <summary>
        /// Caching Get request.
        /// </summary>
        public async Task <HttpSourceResult> GetAsync(
            string uri,
            MediaTypeWithQualityHeaderValue[] accept,
            string cacheKey,
            HttpSourceCacheContext cacheContext,
            ILogger log,
            bool ignoreNotFounds,
            Action <Stream> ensureValidContents,
            CancellationToken cancellationToken)
        {
            var result = InitializeHttpCacheResult(cacheKey, cacheContext);

            return(await ConcurrencyUtilities.ExecuteWithFileLockedAsync(
                       result.CacheFile,
                       action : async token =>
            {
                result.Stream = TryReadCacheFile(uri, result.MaxAge, result.CacheFile);

                if (result.Stream != null)
                {
                    log.LogInformation(string.Format(CultureInfo.InvariantCulture, "  " + Strings.Http_RequestLog, "CACHE", uri));

                    // Validate the content fetched from the cache.
                    try
                    {
                        ensureValidContents?.Invoke(result.Stream);

                        result.Stream.Seek(0, SeekOrigin.Begin);

                        return new HttpSourceResult(
                            HttpSourceResultStatus.OpenedFromDisk,
                            result.CacheFile,
                            result.Stream);
                    }
                    catch (Exception e)
                    {
                        result.Stream.Dispose();
                        result.Stream = null;

                        string message = string.Format(CultureInfo.CurrentCulture, Strings.Log_InvalidCacheEntry, uri)
                                         + Environment.NewLine
                                         + ExceptionUtilities.DisplayMessage(e);
                        log.LogWarning(message);
                    }
                }

                Func <HttpRequestMessage> requestFactory = () =>
                {
                    var request = new HttpRequestMessage(HttpMethod.Get, uri);
                    foreach (var a in accept)
                    {
                        request.Headers.Accept.Add(a);
                    }
                    return request;
                };

                // Read the response headers before reading the entire stream to avoid timeouts from large packages.
                Func <Task <HttpResponseMessage> > httpRequest = () => SendWithCredentialSupportAsync(
                    requestFactory,
                    log,
                    token);

                using (var response = await httpRequest())
                {
                    if (ignoreNotFounds && response.StatusCode == HttpStatusCode.NotFound)
                    {
                        return new HttpSourceResult(HttpSourceResultStatus.NotFound);
                    }

                    if (response.StatusCode == HttpStatusCode.NoContent)
                    {
                        // Ignore reading and caching the empty stream.
                        return new HttpSourceResult(HttpSourceResultStatus.NoContent);
                    }

                    response.EnsureSuccessStatusCode();

                    await CreateCacheFileAsync(result, uri, response, cacheContext, ensureValidContents, token);

                    return new HttpSourceResult(
                        HttpSourceResultStatus.OpenedFromDisk,
                        result.CacheFile,
                        result.Stream);
                }
            },
                       token : cancellationToken));
        }