Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add rate limiting support #19

Merged
merged 8 commits into from
Jan 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion .github/workflows/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ jobs:
strategy:
matrix:
targetFramework: [net6.0, net7.0, net8.0]
project: [App, Tests]

include:
- targetFramework: net6.0
Expand All @@ -90,4 +91,14 @@ jobs:
run: dotnet publish ./src/NuGetUtility/NuGetUtility.csproj --configuration Release -o ./release -f ${{ matrix.targetFramework }} --no-restore

- name: check
run: dotnet ./release/NuGetUtility.dll -ji ./.github/workflows/assets/projectsToCheck.json -t -a ./.github/workflows/assets/allowedLicenses.json -o JsonPretty -override ./.github/workflows/assets/overwritePackageInformation.json -ignore ./.github/workflows/assets/ignorePackages.json -mapping ./.github/workflows/assets/urlToLicenseMapping.json
run: dotnet ./release/NuGetUtility.dll -ji ./.github/workflows/assets/${{ matrix.project }}/projectsToCheck.json -t -a ./.github/workflows/assets/${{ matrix.project }}/allowedLicenses.json -o JsonPretty -override ./.github/workflows/assets/${{ matrix.project }}/overwritePackageInformation.json -ignore ./.github/workflows/assets/${{ matrix.project }}/ignorePackages.json -mapping ./.github/workflows/assets/${{ matrix.project }}/urlToLicenseMapping.json -d ./licenses/${{ matrix.project }}/${{ matrix.targetFramework }}

- name: show downloaded licenses
shell: pwsh
run: |
foreach($file in Get-ChildItem -Path ./licenses/${{ matrix.project }}/${{ matrix.targetFramework }})
{
Write-Host ::group::$file
Get-Content $file.FullName
Write-Host ::endgroup::
}
1 change: 1 addition & 0 deletions .github/workflows/assets/App/allowedLicenses.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
["MIT","Apache-2.0","MS-EULA"]
2 changes: 2 additions & 0 deletions .github/workflows/assets/App/overwritePackageInformation.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[
]
1 change: 1 addition & 0 deletions .github/workflows/assets/App/projectsToCheck.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
["./src/NuGetUtility/NuGetUtility.csproj"]
2 changes: 2 additions & 0 deletions .github/workflows/assets/App/urlToLicenseMapping.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{
}
1 change: 1 addition & 0 deletions .github/workflows/assets/Tests/ignorePackages.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
["NETStandard.Library"]
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,4 @@
"Version": "2.1.1",
"License": "MIT"
}
]
]
1 change: 1 addition & 0 deletions .github/workflows/assets/Tests/projectsToCheck.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
["./tests/NuGetUtility.Test/NuGetUtility.Test.csproj"]
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"https://raw.githubusercontent.com/bchavez/Bogus/master/LICENSE": "MIT"
}
}
1 change: 0 additions & 1 deletion .github/workflows/assets/projectsToCheck.json

This file was deleted.

17 changes: 12 additions & 5 deletions src/NuGetUtility/LicenseValidator/LicenseValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ public LicenseValidator(IImmutableDictionary<Uri, string> licenseMapping,
}

public async Task<IEnumerable<LicenseValidationResult>> Validate(
IAsyncEnumerable<ReferencedPackageWithContext> packages)
IAsyncEnumerable<ReferencedPackageWithContext> packages,
CancellationToken token)
{
var result = new ConcurrentDictionary<LicenseNameAndVersion, LicenseValidationResult>();
await foreach (ReferencedPackageWithContext info in packages)
Expand All @@ -45,7 +46,7 @@ public async Task<IEnumerable<LicenseValidationResult>> Validate(
}
else if (info.PackageInfo.LicenseUrl != null)
{
await ValidateLicenseByUrl(info.PackageInfo, info.Context, result);
await ValidateLicenseByUrl(info.PackageInfo, info.Context, result, token);
}
else
{
Expand Down Expand Up @@ -150,14 +151,20 @@ private void ValidateLicenseByMetadata(IPackageMetadata info,

private async Task ValidateLicenseByUrl(IPackageMetadata info,
string context,
ConcurrentDictionary<LicenseNameAndVersion, LicenseValidationResult> result)
ConcurrentDictionary<LicenseNameAndVersion, LicenseValidationResult> result,
CancellationToken token)
{
if (info.LicenseUrl!.IsAbsoluteUri)
{
try
{
await _fileDownloader.DownloadFile(info.LicenseUrl,
$"{info.Identity.Id}__{info.Identity.Version}.html");
$"{info.Identity.Id}__{info.Identity.Version}.html",
token);
}
catch (OperationCanceledException)
{
// swallow cancellation
}
catch (Exception e)
{
Expand Down Expand Up @@ -207,7 +214,7 @@ private bool IsLicenseValid(string licenseId)
return true;
}

return _allowedLicenses.Any(l => l.Equals(licenseId));
return _allowedLicenses.Any(allowedLicense => allowedLicense.Equals(licenseId));
}

private string GetLicenseNotAllowedMessage(string license)
Expand Down
2 changes: 1 addition & 1 deletion src/NuGetUtility/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ private async Task<int> OnExecuteAsync(CancellationToken cancellationToken)
});
IAsyncEnumerable<ReferencedPackageWithContext> downloadedLicenseInformation =
packagesForProject.SelectMany(p => GetPackageInfos(p, overridePackageInformation, cancellationToken));
var results = (await validator.Validate(downloadedLicenseInformation)).ToList();
var results = (await validator.Validate(downloadedLicenseInformation, cancellationToken)).ToList();

if (projectReaderExceptions.Any())
{
Expand Down
36 changes: 31 additions & 5 deletions src/NuGetUtility/Wrapper/HttpClientWrapper/FileDownloader.cs
Original file line number Diff line number Diff line change
@@ -1,22 +1,48 @@
namespace NuGetUtility.Wrapper.HttpClientWrapper
using System;

namespace NuGetUtility.Wrapper.HttpClientWrapper
{
public class FileDownloader : IFileDownloader
{
private readonly SemaphoreSlim _parallelDownloadLimiter = new SemaphoreSlim(10, 10);
private readonly HttpClient _client;
private readonly string _downloadDirectory;
private const int EXPONENTIAL_BACKOFF_WAIT_TIME_MILLISECONDS = 200;
private const int MAX_RETRIES = 5;

public FileDownloader(HttpClient client, string downloadDirectory)
{
_client = client;
_downloadDirectory = downloadDirectory;
}

public async Task DownloadFile(Uri url, string fileName)
public async Task DownloadFile(Uri url, string fileName, CancellationToken token)
{
await using FileStream file = File.OpenWrite(Path.Combine(_downloadDirectory, fileName));
await using Stream downloadStream = await _client.GetStreamAsync(url);
await _parallelDownloadLimiter.WaitAsync(token);
try
{
for (int i = 0; i < MAX_RETRIES; i++)
{
await using FileStream file = File.OpenWrite(Path.Combine(_downloadDirectory, fileName));
var request = new HttpRequestMessage(HttpMethod.Get, url);

HttpResponseMessage response = await _client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, token);
response.EnsureSuccessStatusCode();
if (response.StatusCode == System.Net.HttpStatusCode.TooManyRequests)
{
await Task.Delay((int)Math.Pow(EXPONENTIAL_BACKOFF_WAIT_TIME_MILLISECONDS, i + 1), token);
continue;
}
using Stream downloadStream = await response.Content.ReadAsStreamAsync(token);

await downloadStream.CopyToAsync(file);
await downloadStream.CopyToAsync(file, token);
return;
}
}
finally
{
_parallelDownloadLimiter.Release();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
{
public interface IFileDownloader
{
public Task DownloadFile(Uri url, string fileName);
public Task DownloadFile(Uri url, string fileName, CancellationToken token);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
{
public class NopFileDownloader : IFileDownloader
{
public Task DownloadFile(Uri url, string fileName)
public Task DownloadFile(Uri url, string fileName, CancellationToken token)
{
return Task.CompletedTask;
}
Expand Down
Loading