Skip to content

Commit a8827f7

Browse files
authored
Symbols perf improvement (#8446)
* Symbols perf improvement * applied feedback
1 parent 933fe45 commit a8827f7

3 files changed

Lines changed: 105 additions & 128 deletions

File tree

src/NuGetGallery/App_Data/Files/Content/flags.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525
"NuGetGallery.DisplayVulnerabilities": "Enabled",
2626
"NuGetGallery.ManagePackagesVulnerabilities": "Enabled",
2727
"NuGetGallery.DisplayFuGetLinks": "Enabled",
28-
"NuGetGallery.PatternSetTfmHeuristics": "Enabled"
28+
"NuGetGallery.PatternSetTfmHeuristics": "Enabled",
29+
"NuGetGallery.EmbeddedIcons": "Enabled"
2930
},
3031
"Flights": {
3132
"NuGetGallery.TyposquattingFlight": {

src/NuGetGallery/Controllers/ApiController.cs

Lines changed: 34 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -193,17 +193,42 @@ public ApiController(
193193
[ActionName("GetSymbolPackageApi")]
194194
public virtual async Task<ActionResult> GetSymbolPackage(string id, string version)
195195
{
196-
return await GetPackageInternal(id, version, isSymbolPackage: true);
196+
// some security paranoia about URL hacking somehow creating e.g. open redirects
197+
// validate user input: explicit calls to the same validators used during Package Registrations
198+
// Ideally shouldn't be necessary?
199+
if (!PackageIdValidator.IsValidPackageId(id ?? string.Empty))
200+
{
201+
return new HttpStatusCodeWithBodyResult(HttpStatusCode.BadRequest, "The format of the package id is invalid");
202+
}
203+
204+
if (string.IsNullOrEmpty(version))
205+
{
206+
return new HttpStatusCodeWithBodyResult(HttpStatusCode.BadRequest, "Version is required");
207+
}
208+
209+
// Check if version semantically correct
210+
if (!NuGetVersion.TryParse(version, out NuGetVersion dummy))
211+
{
212+
return new HttpStatusCodeWithBodyResult(HttpStatusCode.BadRequest, "The package version is not a valid semantic version");
213+
}
214+
215+
// Normalize the version
216+
version = NuGetVersionFormatter.Normalize(version);
217+
218+
// There's no guarantee the symbols package exists in the returned path. We just provide the path it should be at.
219+
return await SymbolPackageFileService.CreateDownloadSymbolPackageActionResultAsync(
220+
HttpContext.Request.Url,
221+
id, version);
197222
}
198223

199224
[HttpGet]
200225
[ActionName("GetPackageApi")]
201226
public virtual async Task<ActionResult> GetPackage(string id, string version)
202227
{
203-
return await GetPackageInternal(id, version, isSymbolPackage: false);
228+
return await GetPackageInternal(id, version);
204229
}
205230

206-
protected internal async Task<ActionResult> GetPackageInternal(string id, string version, bool isSymbolPackage = false)
231+
protected internal async Task<ActionResult> GetPackageInternal(string id, string version)
207232
{
208233
// some security paranoia about URL hacking somehow creating e.g. open redirects
209234
// validate user input: explicit calls to the same validators used during Package Registrations
@@ -219,24 +244,13 @@ protected internal async Task<ActionResult> GetPackageInternal(string id, string
219244
if (!string.IsNullOrEmpty(version))
220245
{
221246
// if version is non-null, check if it's semantically correct and normalize it.
222-
NuGetVersion dummy;
223-
if (!NuGetVersion.TryParse(version, out dummy))
247+
if (!NuGetVersion.TryParse(version, out NuGetVersion dummy))
224248
{
225249
return new HttpStatusCodeWithBodyResult(HttpStatusCode.BadRequest, "The package version is not a valid semantic version");
226250
}
227251

228252
// Normalize the version
229253
version = NuGetVersionFormatter.Normalize(version);
230-
231-
if (isSymbolPackage)
232-
{
233-
package = PackageService.FindPackageByIdAndVersionStrict(id, version);
234-
235-
if (package == null)
236-
{
237-
return new HttpStatusCodeWithBodyResult(HttpStatusCode.NotFound, string.Format(CultureInfo.CurrentCulture, Strings.PackageWithIdAndVersionNotFound, id, version));
238-
}
239-
}
240254
}
241255
else
242256
{
@@ -271,30 +285,14 @@ protected internal async Task<ActionResult> GetPackageInternal(string id, string
271285
return new HttpStatusCodeWithBodyResult(HttpStatusCode.ServiceUnavailable, Strings.DatabaseUnavailable_TrySpecificVersion);
272286
}
273287

274-
if (isSymbolPackage)
288+
if (ConfigurationService.Features.TrackPackageDownloadCountInLocalDatabase)
275289
{
276-
var latestAvailableSymbolsPackage = package.LatestAvailableSymbolPackage();
277-
278-
if (latestAvailableSymbolsPackage == null)
279-
{
280-
return new HttpStatusCodeWithBodyResult(HttpStatusCode.NotFound, string.Format(CultureInfo.CurrentCulture, Strings.SymbolsPackage_PackageNotAvailable, id, version));
281-
}
282-
283-
return await SymbolPackageFileService.CreateDownloadSymbolPackageActionResultAsync(
284-
HttpContext.Request.Url,
285-
id, version);
290+
await PackageService.IncrementDownloadCountAsync(id, version);
286291
}
287-
else
288-
{
289-
if (ConfigurationService.Features.TrackPackageDownloadCountInLocalDatabase)
290-
{
291-
await PackageService.IncrementDownloadCountAsync(id, version);
292-
}
293292

294-
return await PackageFileService.CreateDownloadPackageActionResultAsync(
295-
HttpContext.Request.Url,
296-
id, version);
297-
}
293+
return await PackageFileService.CreateDownloadPackageActionResultAsync(
294+
HttpContext.Request.Url,
295+
id, version);
298296
}
299297

300298
[HttpGet]

tests/NuGetGallery.Facts/Controllers/ApiControllerFacts.cs

Lines changed: 69 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1968,134 +1968,124 @@ public async Task WillNotUnlistThePackageIfItIsLocked()
19681968
}
19691969
}
19701970

1971-
public class TheGetPackageAction
1971+
public class TheGetSymbolPackage
19721972
: TestContainer
19731973
{
1974-
[Theory]
1975-
[InlineData(true)]
1976-
[InlineData(false)]
1977-
public async Task GetPackageReturns400ForEvilPackageName(bool isSymbolPackage)
1974+
[Fact]
1975+
public async Task GetSymbolPackageReturns400ForEvilPackageName()
19781976
{
19791977
var controller = new TestableApiController(GetConfigurationService());
1980-
var result = await controller.GetPackageInternal("../..", "1.0.0.0", isSymbolPackage);
1978+
var result = await controller.GetSymbolPackage("../..", "1.0.0.0");
19811979
var badRequestResult = (HttpStatusCodeWithBodyResult)result;
19821980
Assert.Equal(400, badRequestResult.StatusCode);
19831981
}
19841982

1983+
[Fact]
1984+
public async Task GetSymbolPackageReturns400ForEvilPackageVersion()
1985+
{
1986+
var controller = new TestableApiController(GetConfigurationService());
1987+
var result2 = await controller.GetSymbolPackage("Foo", "10../..1.0");
1988+
var badRequestResult2 = (HttpStatusCodeWithBodyResult)result2;
1989+
Assert.Equal(400, badRequestResult2.StatusCode);
1990+
}
1991+
19851992
[Theory]
1986-
[InlineData(true)]
1987-
[InlineData(false)]
1988-
public async Task GetPackageReturns400ForEvilPackageVersion(bool isSymbolPackage)
1993+
[InlineData("")]
1994+
[InlineData(null)]
1995+
public async Task GetSymbolPackageReturns400IfNoVersionProvided(string version)
19891996
{
19901997
var controller = new TestableApiController(GetConfigurationService());
1991-
var result2 = await controller.GetPackageInternal("Foo", "10../..1.0", isSymbolPackage);
1998+
var result2 = await controller.GetSymbolPackage("Foo", version);
19921999
var badRequestResult2 = (HttpStatusCodeWithBodyResult)result2;
19932000
Assert.Equal(400, badRequestResult2.StatusCode);
19942001
}
19952002

19962003
[Fact]
1997-
public async Task GetPackageReturns404IfPackageIsNotFound()
2004+
public async Task GetSymbolsPackageReturnsPackage()
19982005
{
19992006
// Arrange
20002007
const string packageId = "Baz";
2001-
const string packageVersion = "1.0.1";
2002-
var actionResult = new RedirectResult("http://foo");
2008+
const string version = "1.0.1+notnormalized";
2009+
const string normalizedVersion = "1.0.1";
2010+
2011+
var actionResult = new EmptyResult();
20032012

20042013
var controller = new TestableApiController(GetConfigurationService(), MockBehavior.Strict);
2005-
controller.MockPackageService
2006-
.Setup(x => x.FindPackageByIdAndVersionStrict(packageId, packageVersion))
2007-
.Returns((Package)null).Verifiable();
2008-
controller.MockPackageFileService.Setup(s => s.CreateDownloadPackageActionResultAsync(It.IsAny<Uri>(), packageId, packageVersion))
2014+
2015+
controller
2016+
.MockSymbolPackageFileService
2017+
.Setup(s => s.CreateDownloadSymbolPackageActionResultAsync(HttpRequestUrl, packageId, normalizedVersion))
20092018
.Returns(Task.FromResult<ActionResult>(actionResult))
20102019
.Verifiable();
20112020

2021+
var httpRequest = new Mock<HttpRequestBase>(MockBehavior.Strict);
2022+
httpRequest.SetupGet(r => r.Url).Returns(HttpRequestUrl);
2023+
2024+
var httpContext = new Mock<HttpContextBase>(MockBehavior.Strict);
2025+
httpContext.SetupGet(c => c.Request).Returns(httpRequest.Object);
2026+
2027+
var controllerContext = new ControllerContext(new RequestContext(httpContext.Object, new RouteData()), controller);
2028+
controller.ControllerContext = controllerContext;
2029+
20122030
// Act
2013-
var result = await controller.GetPackageInternal(packageId, packageVersion);
2031+
var result = await controller.GetSymbolPackage(packageId, version);
20142032

20152033
// Assert
2016-
Assert.IsType<RedirectResult>(result);
2034+
Assert.Same(actionResult, result);
2035+
2036+
controller.MockSymbolPackageFileService.Verify();
20172037
}
2038+
}
20182039

2040+
public class TheGetPackageAction
2041+
: TestContainer
2042+
{
20192043
[Fact]
2020-
public async Task GetPackageReturns404ForSymbolPackageIfPackageIsNotFound()
2044+
public async Task GetPackageReturns400ForEvilPackageName()
20212045
{
2022-
// Arrange
2023-
const string packageId = "Baz";
2024-
const string packageVersion = "1.0.1";
2025-
2026-
var controller = new TestableApiController(GetConfigurationService(), MockBehavior.Strict);
2027-
controller.MockPackageService
2028-
.Setup(x => x.FindPackageByIdAndVersionStrict(packageId, packageVersion))
2029-
.Returns((Package)null).Verifiable();
2030-
2031-
// Act
2032-
var result = (HttpStatusCodeWithBodyResult)await controller.GetPackageInternal(packageId, packageVersion, isSymbolPackage: true);
2046+
var controller = new TestableApiController(GetConfigurationService());
2047+
var result = await controller.GetPackage("../..", "1.0.0.0");
2048+
var badRequestResult = (HttpStatusCodeWithBodyResult)result;
2049+
Assert.Equal(400, badRequestResult.StatusCode);
2050+
}
20332051

2034-
// Assert
2035-
Assert.Equal((int)HttpStatusCode.NotFound, result.StatusCode);
2052+
[Fact]
2053+
public async Task GetPackageReturns400ForEvilPackageVersion()
2054+
{
2055+
var controller = new TestableApiController(GetConfigurationService());
2056+
var result2 = await controller.GetPackage("Foo", "10../..1.0");
2057+
var badRequestResult2 = (HttpStatusCodeWithBodyResult)result2;
2058+
Assert.Equal(400, badRequestResult2.StatusCode);
20362059
}
20372060

2038-
[Theory]
2039-
[InlineData(PackageStatus.Deleted)]
2040-
[InlineData(PackageStatus.FailedValidation)]
2041-
[InlineData(PackageStatus.Validating)]
2042-
public async Task GetPackageReturnsLastAvailableSymbolPackage(PackageStatus status)
2061+
[Fact]
2062+
public async Task GetPackageReturns404IfPackageIsNotFound()
20432063
{
20442064
// Arrange
20452065
const string packageId = "Baz";
2046-
const string packageVersion = "1.0.1";
2047-
var package = new Package() { PackageRegistration = new PackageRegistration() { Id = packageId }, Version = packageVersion };
2048-
var latestSymbolPackage = new SymbolPackage()
2049-
{
2050-
Key = 1,
2051-
Package = package,
2052-
StatusKey = status,
2053-
Created = DateTime.Today.AddDays(-1)
2054-
};
2055-
var oldAvailableSymbolPackage = new SymbolPackage()
2056-
{
2057-
Key = 2,
2058-
Package = package,
2059-
StatusKey = PackageStatus.Available,
2060-
Created = DateTime.Today.AddDays(-2)
2061-
};
2062-
package.SymbolPackages.Add(oldAvailableSymbolPackage);
2063-
package.SymbolPackages.Add(latestSymbolPackage);
20642066

20652067
var controller = new TestableApiController(GetConfigurationService(), MockBehavior.Strict);
20662068
controller.MockPackageService
2067-
.Setup(x => x.FindPackageByIdAndVersionStrict(packageId, packageVersion))
2068-
.Returns(package).Verifiable();
2069-
controller.MockSymbolPackageFileService
2070-
.Setup(x => x.CreateDownloadSymbolPackageActionResultAsync(It.IsAny<Uri>(), packageId, packageVersion))
2071-
.Returns(Task.FromResult<ActionResult>(new HttpStatusCodeWithBodyResult(HttpStatusCode.OK, "Test package")))
2072-
.Verifiable();
2069+
.Setup(x => x.FindPackageByIdAndVersion(packageId, null, SemVerLevelKey.SemVer2, false))
2070+
.Returns((Package)null).Verifiable();
20732071

20742072
// Act
2075-
var result = (HttpStatusCodeWithBodyResult)await controller.GetPackageInternal(packageId, packageVersion, isSymbolPackage: true);
2073+
var result = await controller.GetPackageInternal(packageId, null);
20762074

20772075
// Assert
2078-
Assert.Equal((int)HttpStatusCode.OK, result.StatusCode);
2079-
controller.MockSymbolPackageFileService.Verify();
2076+
controller.MockPackageService.Verify();
2077+
2078+
var notFoundResult = (HttpStatusCodeWithBodyResult)result;
2079+
Assert.Equal(404, notFoundResult.StatusCode);
20802080
}
20812081

2082-
[Theory]
2083-
[InlineData(true)]
2084-
[InlineData(false)]
2085-
public async Task GetPackageReturnsPackageIfItExists(bool isSymbolPackage)
2082+
[Fact]
2083+
public async Task GetPackageReturnsPackageIfItExists()
20862084
{
20872085
// Arrange
20882086
const string packageId = "Baz";
20892087
var package = new Package() { Version = "1.0.01", NormalizedVersion = "1.0.1" };
20902088
var actionResult = new EmptyResult();
2091-
var availableSymbolPackage = new SymbolPackage()
2092-
{
2093-
Key = 2,
2094-
Package = package,
2095-
StatusKey = PackageStatus.Available,
2096-
Created = DateTime.Today.AddDays(-1)
2097-
};
2098-
package.SymbolPackages.Add(availableSymbolPackage);
20992089

21002090
var controller = new TestableApiController(GetConfigurationService(), MockBehavior.Strict);
21012091
controller
@@ -2107,11 +2097,6 @@ public async Task GetPackageReturnsPackageIfItExists(bool isSymbolPackage)
21072097
.Setup(s => s.CreateDownloadPackageActionResultAsync(HttpRequestUrl, packageId, package.NormalizedVersion))
21082098
.Returns(Task.FromResult<ActionResult>(actionResult))
21092099
.Verifiable();
2110-
controller
2111-
.MockSymbolPackageFileService
2112-
.Setup(s => s.CreateDownloadSymbolPackageActionResultAsync(HttpRequestUrl, packageId, package.NormalizedVersion))
2113-
.Returns(Task.FromResult<ActionResult>(actionResult))
2114-
.Verifiable();
21152100

21162101
NameValueCollection headers = new NameValueCollection();
21172102
headers.Add("NuGet-Operation", "Install");
@@ -2128,18 +2113,11 @@ public async Task GetPackageReturnsPackageIfItExists(bool isSymbolPackage)
21282113
controller.ControllerContext = controllerContext;
21292114

21302115
// Act
2131-
var result = await controller.GetPackageInternal(packageId, "1.0.01", isSymbolPackage);
2116+
var result = await controller.GetPackage(packageId, "1.0.01");
21322117

21332118
// Assert
21342119
Assert.Same(actionResult, result);
2135-
if (isSymbolPackage)
2136-
{
2137-
controller.MockSymbolPackageFileService.Verify();
2138-
}
2139-
else
2140-
{
2141-
controller.MockPackageFileService.Verify();
2142-
}
2120+
controller.MockPackageFileService.Verify();
21432121

21442122
controller.MockPackageService.Verify();
21452123
controller.MockUserService.Verify();

0 commit comments

Comments
 (0)