diff --git a/src/NuGet.Services.Search.Client/Client/SearchClient.cs b/src/NuGet.Services.Search.Client/Client/SearchClient.cs index 2760f8884d..97d0eda6e0 100644 --- a/src/NuGet.Services.Search.Client/Client/SearchClient.cs +++ b/src/NuGet.Services.Search.Client/Client/SearchClient.cs @@ -7,17 +7,32 @@ using System.Net; using System.Net.Http; using System.Threading.Tasks; +using Correlator.Extensions; using Newtonsoft.Json.Linq; using NuGet.Services.Client; +using NuGet.Services.Search.Client.Correlation; using NuGet.Services.Search.Models; namespace NuGet.Services.Search.Client { - public class SearchClient + public class SearchClient : ICorrelated { private readonly RetryingHttpClientWrapper _retryingHttpClientWrapper; private readonly ServiceDiscoveryClient _discoveryClient; private readonly string _resourceType; + private readonly HttpClient _httpClient; + + private CorrelationIdProvider _correlationIdProvider; + + public CorrelationIdProvider CorrelationIdProvider + { + get { return _correlationIdProvider; } + set + { + _correlationIdProvider = value; + _httpClient.EnsureCorrelationId(_correlationIdProvider.CorrelationId); + } + } /// /// Create a search service client from the specified base uri and credentials. @@ -55,10 +70,11 @@ public SearchClient(Uri baseUri, string resourceType, ICredentials credentials, handler = providedHandler; } - var httpClient = new HttpClient(handler, disposeHandler: true); + _httpClient = new HttpClient(handler, disposeHandler: true); - _retryingHttpClientWrapper = new RetryingHttpClientWrapper(httpClient, healthIndicatorStore); - _discoveryClient = new ServiceDiscoveryClient(httpClient, baseUri); + _retryingHttpClientWrapper = new RetryingHttpClientWrapper(_httpClient, healthIndicatorStore); + _discoveryClient = new ServiceDiscoveryClient(_httpClient, baseUri); + CorrelationIdProvider = new CorrelationIdProvider(); } private static readonly Dictionary SortNames = new Dictionary @@ -182,4 +198,4 @@ public async Task> GetDiagnostics() await _retryingHttpClientWrapper.GetAsync(requestEndpoints)); } } -} +} \ No newline at end of file diff --git a/src/NuGet.Services.Search.Client/Correlation/CorrelationIdProvider.cs b/src/NuGet.Services.Search.Client/Correlation/CorrelationIdProvider.cs new file mode 100644 index 0000000000..6ff9591f12 --- /dev/null +++ b/src/NuGet.Services.Search.Client/Correlation/CorrelationIdProvider.cs @@ -0,0 +1,29 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Net.Http; +using Correlator.Extensions; + +namespace NuGet.Services.Search.Client.Correlation +{ + public class CorrelationIdProvider + { + public Guid CorrelationId { get; private set; } + + public CorrelationIdProvider() + { + CorrelationId = Guid.NewGuid(); + } + + public CorrelationIdProvider(HttpRequestMessage request) + { + CorrelationId = request.GetClientCorrelationId(); + + if (CorrelationId == Guid.Empty) + { + CorrelationId = Guid.NewGuid(); + } + } + } +} \ No newline at end of file diff --git a/src/NuGet.Services.Search.Client/Correlation/ICorrelated.cs b/src/NuGet.Services.Search.Client/Correlation/ICorrelated.cs new file mode 100644 index 0000000000..3bacf6caa7 --- /dev/null +++ b/src/NuGet.Services.Search.Client/Correlation/ICorrelated.cs @@ -0,0 +1,13 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace NuGet.Services.Search.Client.Correlation +{ + /// + /// Indicated that the implementing class supports correlation id propogation, and thus excepts CorrelationIdProvider. + /// + public interface ICorrelated + { + CorrelationIdProvider CorrelationIdProvider { get; set; } + } +} \ No newline at end of file diff --git a/src/NuGet.Services.Search.Client/NuGet.Services.Search.Client.csproj b/src/NuGet.Services.Search.Client/NuGet.Services.Search.Client.csproj index e434a75ea8..f5112727f6 100644 --- a/src/NuGet.Services.Search.Client/NuGet.Services.Search.Client.csproj +++ b/src/NuGet.Services.Search.Client/NuGet.Services.Search.Client.csproj @@ -32,6 +32,11 @@ 4 + + False + ..\..\packages\WebApiCorrelator.1.1.0.0\lib\net452\Correlator.dll + True + ..\..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll True @@ -59,6 +64,11 @@ True + + False + ..\..\packages\Microsoft.AspNet.WebApi.Core.5.1.2\lib\net45\System.Web.Http.dll + True + @@ -75,6 +85,8 @@ + + @@ -83,6 +95,9 @@ + + + diff --git a/src/NuGet.Services.Search.Client/Scripts/jquery.ajax.correlator.min.js b/src/NuGet.Services.Search.Client/Scripts/jquery.ajax.correlator.min.js new file mode 100644 index 0000000000..5c092202e0 --- /dev/null +++ b/src/NuGet.Services.Search.Client/Scripts/jquery.ajax.correlator.min.js @@ -0,0 +1,5 @@ +/* +* Author: Lenny Granovsky +* License: MIT license +*/ +;if(typeof jQuery==="undefined"){throw new Error("AJAX correlation JavaScript plugin requires jQuery")}(function(b){var g="correlator";var a="ajax.correlation.id";var j=false;try{j=("sessionStorage" in window)&&window.sessionStorage!==null}catch(c){}var f={create:function(m){function k(){return Math.floor((1+Math.random())*65536).toString(16).substring(1)}var l=(m=="N")?k()+k()+k()+k()+k()+k()+k()+k():k()+k()+"-"+k()+"-"+k()+"-"+k()+"-"+k()+k()+k();return l},empty:function(k){if(k=="N"){return"00000000000000000000000000000000"}else{return"00000000-0000-0000-0000-000000000000"}}};function d(){if(j){var k=window.sessionStorage.getItem(a);if(k!=null&&k!="null"&&k.length>0){return k}}return null}function h(k){if(j){window.sessionStorage.setItem(a,k)}}var e=d();if(e===null){e=f.create();h(e)}var i={getCorrelationId:function(){return e},setCorrelationId:function(k){e=k;h(e);b.ajaxSetup({headers:{"X-CorrelationId":e}})},renewCorrelationId:function(){var k=f.create();i.setCorrelationId(k);return e}};b[g]=i;b.ajaxSetup({headers:{"X-CorrelationId":e}})})(jQuery); \ No newline at end of file diff --git a/src/NuGet.Services.Search.Client/packages.config b/src/NuGet.Services.Search.Client/packages.config index 094ca9cd03..5d27c09a8f 100644 --- a/src/NuGet.Services.Search.Client/packages.config +++ b/src/NuGet.Services.Search.Client/packages.config @@ -1,9 +1,11 @@  + + \ No newline at end of file diff --git a/src/NuGetGallery/App_Start/AppActivator.cs b/src/NuGetGallery/App_Start/AppActivator.cs index 2002191246..76b9a40eea 100644 --- a/src/NuGetGallery/App_Start/AppActivator.cs +++ b/src/NuGetGallery/App_Start/AppActivator.cs @@ -172,6 +172,15 @@ private static void AppPostStart(IAppConfiguration configuration) WebApiConfig.Register(GlobalConfiguration.Configuration); NuGetODataConfig.Register(GlobalConfiguration.Configuration); + // Attach correlator + var correlationHandler = new Correlator.Handlers.ClientCorrelationHandler + { + InitializeIfEmpty = true, + TraceCorrelation = true + }; + + GlobalConfiguration.Configuration.MessageHandlers.Add(correlationHandler); + Routes.RegisterRoutes(RouteTable.Routes, configuration.FeedOnlyMode); AreaRegistration.RegisterAllAreas(); diff --git a/src/NuGetGallery/Controllers/ODataV2FeedController.cs b/src/NuGetGallery/Controllers/ODataV2FeedController.cs index 6b1c6b2ef2..6162585b3f 100644 --- a/src/NuGetGallery/Controllers/ODataV2FeedController.cs +++ b/src/NuGetGallery/Controllers/ODataV2FeedController.cs @@ -9,15 +9,16 @@ using System.Web.Http; using System.Web.Http.OData; using System.Web.Http.OData.Query; +using NuGet.Frameworks; +using NuGet.Services.Search.Client.Correlation; +using NuGet.Versioning; using NuGetGallery.Configuration; +using NuGetGallery.Infrastructure.Lucene; using NuGetGallery.OData; using NuGetGallery.OData.QueryInterceptors; using NuGetGallery.WebApi; using QueryInterceptor; using WebApi.OutputCache.V2; -using NuGet.Versioning; -using NuGet.Frameworks; -using NuGetGallery.Infrastructure.Lucene; // ReSharper disable once CheckNamespace namespace NuGetGallery.Controllers @@ -61,6 +62,7 @@ public async Task Get(ODataQueryOptions option HijackableQueryParameters hijackableQueryParameters = null; if (SearchHijacker.IsHijackable(options, out hijackableQueryParameters) && _searchService is ExternalSearchService) { + SetCorrelation(); var searchAdaptorResult = await SearchAdaptor.FindByIdAndVersionCore( _searchService, GetTraditionalHttpContext().Request, packages, hijackableQueryParameters.Id, hijackableQueryParameters.Version, curatedFeed: null); @@ -133,6 +135,7 @@ private async Task GetCore(ODataQueryOptions o // try the search service try { + SetCorrelation(); var searchAdaptorResult = await SearchAdaptor.FindByIdAndVersionCore( _searchService, GetTraditionalHttpContext().Request, packages, id, version, curatedFeed: null); @@ -217,6 +220,7 @@ public async Task Search( .AsNoTracking(); // todo: search hijack should take options instead of manually parsing query options + SetCorrelation(); var searchAdaptorResult = await SearchAdaptor.SearchCore( _searchService, GetTraditionalHttpContext().Request, packages, searchTerm, targetFramework, includePrerelease, curatedFeed: null); @@ -238,7 +242,7 @@ public async Task Search( // Strip it of for backward compatibility. if (o.Top == null || (resultCount.HasValue && o.Top.Value >= resultCount.Value)) { - return SearchAdaptor.GetNextLink(Request.RequestUri, resultCount, new {searchTerm, targetFramework, includePrerelease}, o, s); + return SearchAdaptor.GetNextLink(Request.RequestUri, resultCount, new { searchTerm, targetFramework, includePrerelease }, o, s); } return null; }); @@ -385,5 +389,13 @@ where versionLookup[p.PackageRegistration.Id] } return updates; } + + private void SetCorrelation() + { + if (_searchService is ICorrelated) + { + ((ICorrelated)_searchService).CorrelationIdProvider = new CorrelationIdProvider(Request); + } + } } } \ No newline at end of file diff --git a/src/NuGetGallery/Infrastructure/Lucene/ExternalSearchService.cs b/src/NuGetGallery/Infrastructure/Lucene/ExternalSearchService.cs index ba1d8ca1d4..c3a454b8e4 100644 --- a/src/NuGetGallery/Infrastructure/Lucene/ExternalSearchService.cs +++ b/src/NuGetGallery/Infrastructure/Lucene/ExternalSearchService.cs @@ -11,12 +11,13 @@ using System.Web; using Newtonsoft.Json.Linq; using NuGet.Services.Search.Client; +using NuGet.Services.Search.Client.Correlation; using NuGetGallery.Configuration; using NuGetGallery.Diagnostics; namespace NuGetGallery.Infrastructure.Lucene { - public class ExternalSearchService : ISearchService, IIndexingService, IRawSearchService + public class ExternalSearchService : ISearchService, IIndexingService, IRawSearchService, ICorrelated { public static readonly string SearchRoundtripTimePerfCounter = "SearchRoundtripTime"; @@ -314,5 +315,11 @@ public void RegisterBackgroundJobs(IList jobs, IAppConfigu { // No background jobs to register! } + + public CorrelationIdProvider CorrelationIdProvider + { + get { return _client.CorrelationIdProvider; } + set { _client.CorrelationIdProvider = value; } + } } } diff --git a/src/NuGetGallery/NuGetGallery.csproj b/src/NuGetGallery/NuGetGallery.csproj index 83ca6f7e76..1fd0588070 100644 --- a/src/NuGetGallery/NuGetGallery.csproj +++ b/src/NuGetGallery/NuGetGallery.csproj @@ -131,6 +131,11 @@ ..\..\packages\Autofac.WebApi2.3.4.0\lib\net45\Autofac.Integration.WebApi.dll True + + False + ..\..\packages\WebApiCorrelator.1.1.0.0\lib\net452\Correlator.dll + True + False ..\..\packages\DynamicData.EFCodeFirstProvider.0.3.0.0\lib\net40\DynamicData.EFCodeFirstProvider.dll @@ -1541,6 +1546,7 @@ + diff --git a/src/NuGetGallery/Scripts/jquery.ajax.correlator.min.js b/src/NuGetGallery/Scripts/jquery.ajax.correlator.min.js new file mode 100644 index 0000000000..5c092202e0 --- /dev/null +++ b/src/NuGetGallery/Scripts/jquery.ajax.correlator.min.js @@ -0,0 +1,5 @@ +/* +* Author: Lenny Granovsky +* License: MIT license +*/ +;if(typeof jQuery==="undefined"){throw new Error("AJAX correlation JavaScript plugin requires jQuery")}(function(b){var g="correlator";var a="ajax.correlation.id";var j=false;try{j=("sessionStorage" in window)&&window.sessionStorage!==null}catch(c){}var f={create:function(m){function k(){return Math.floor((1+Math.random())*65536).toString(16).substring(1)}var l=(m=="N")?k()+k()+k()+k()+k()+k()+k()+k():k()+k()+"-"+k()+"-"+k()+"-"+k()+"-"+k()+k()+k();return l},empty:function(k){if(k=="N"){return"00000000000000000000000000000000"}else{return"00000000-0000-0000-0000-000000000000"}}};function d(){if(j){var k=window.sessionStorage.getItem(a);if(k!=null&&k!="null"&&k.length>0){return k}}return null}function h(k){if(j){window.sessionStorage.setItem(a,k)}}var e=d();if(e===null){e=f.create();h(e)}var i={getCorrelationId:function(){return e},setCorrelationId:function(k){e=k;h(e);b.ajaxSetup({headers:{"X-CorrelationId":e}})},renewCorrelationId:function(){var k=f.create();i.setCorrelationId(k);return e}};b[g]=i;b.ajaxSetup({headers:{"X-CorrelationId":e}})})(jQuery); \ No newline at end of file diff --git a/src/NuGetGallery/packages.config b/src/NuGetGallery/packages.config index 556e8121e4..43e25a6abb 100644 --- a/src/NuGetGallery/packages.config +++ b/src/NuGetGallery/packages.config @@ -92,6 +92,7 @@ +