This is an automated email from the ASF dual-hosted git repository. nightowl888 pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/lucenenet.git
commit 65ab43cdf57e00024b96244c04656edf1fe3ccc6 Author: Shad Storhaug <[email protected]> AuthorDate: Sat Nov 6 14:05:56 2021 +0700 Lucene.Net.Tests.Replicator.Http.HttpReplicatorTest: Added TestServerErrors() test from 4.8.1 --- Directory.Build.targets | 7 ++ build/Dependencies.props | 3 +- .../Http/HttpReplicatorTest.cs | 49 ++++++++- .../Http/ReplicationServlet.cs | 85 +++++++++++++++- .../ReplicatorTestCase.cs | 110 ++++++++++++++++++++- 5 files changed, 243 insertions(+), 11 deletions(-) diff --git a/Directory.Build.targets b/Directory.Build.targets index 8e87632..2e1dc9e 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -20,6 +20,13 @@ --> <Project> + <!-- Features in .NET 5.x and .NET 6.x only --> + <PropertyGroup Condition=" $(TargetFramework.StartsWith('net5.')) Or $(TargetFramework.StartsWith('net6.')) "> + + <DefineConstants>$(DefineConstants);FEATURE_ASPNETCORE_ENDPOINT_CONFIG</DefineConstants> + + </PropertyGroup> + <!-- Features in .NET Core 3.x, .NET 5.x, and .NET 6.x only --> <PropertyGroup Condition=" $(TargetFramework.StartsWith('netcoreapp3.')) Or $(TargetFramework.StartsWith('net5.')) Or '$(TargetFramework)' == 'net6.0' "> diff --git a/build/Dependencies.props b/build/Dependencies.props index 90afcab..836c8bc 100644 --- a/build/Dependencies.props +++ b/build/Dependencies.props @@ -41,7 +41,8 @@ <J2NPackageVersion>2.0.0-beta-0017</J2NPackageVersion> <LiquidTestReportsMarkdownPackageVersion>1.0.9</LiquidTestReportsMarkdownPackageVersion> <MicrosoftAspNetCoreHttpAbstractionsPackageVersion>2.0.0</MicrosoftAspNetCoreHttpAbstractionsPackageVersion> - <MicrosoftAspNetCoreTestHostPackageVersion>2.0.0</MicrosoftAspNetCoreTestHostPackageVersion> + <MicrosoftAspNetCoreTestHostPackageVersion>5.0.0</MicrosoftAspNetCoreTestHostPackageVersion> + <MicrosoftAspNetCoreTestHostPackageVersion Condition=" $(TargetFramework.StartsWith('net4')) Or $(TargetFramework.StartsWith('netcoreapp')) ">2.0.0</MicrosoftAspNetCoreTestHostPackageVersion> <MicrosoftCodeAnalysisAnalyzersPackageVersion>2.9.8</MicrosoftCodeAnalysisAnalyzersPackageVersion> <MicrosoftCodeAnalysisCSharpPackageVersion>2.6.1</MicrosoftCodeAnalysisCSharpPackageVersion> <MicrosoftCodeAnalysisCSharpWorkspacesPackageVersion>$(MicrosoftCodeAnalysisCSharpPackageVersion)</MicrosoftCodeAnalysisCSharpWorkspacesPackageVersion> diff --git a/src/Lucene.Net.Tests.Replicator/Http/HttpReplicatorTest.cs b/src/Lucene.Net.Tests.Replicator/Http/HttpReplicatorTest.cs index b3061ad..e9d9927 100644 --- a/src/Lucene.Net.Tests.Replicator/Http/HttpReplicatorTest.cs +++ b/src/Lucene.Net.Tests.Replicator/Http/HttpReplicatorTest.cs @@ -1,9 +1,10 @@ -using Lucene.Net.Documents; +using Lucene.Net.Documents; using Lucene.Net.Index; using Lucene.Net.Support; using Lucene.Net.Util; using Microsoft.AspNetCore.TestHost; using NUnit.Framework; +using System; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -42,11 +43,18 @@ namespace Lucene.Net.Replicator.Http private Directory serverIndexDir; private Directory handlerIndexDir; + private MockErrorConfig mockErrorConfig; + private void StartServer() { ReplicationService service = new ReplicationService(new Dictionary<string, IReplicator> { { "s1", serverReplicator } }); - server = NewHttpServer<ReplicationServlet>(service); +#if FEATURE_ASPNETCORE_ENDPOINT_CONFIG + server = NewHttpServer(service, mockErrorConfig); // Call like this to use ReplicationServerMiddleware on the specific path /replicate/{shard?}/{action?}, but allow other paths to be served +#else + server = NewHttpServer<ReplicationServlet>(service, mockErrorConfig); // Call like this to use ReplicationServlet as a Startup Class +#endif + port = ServerPort(server); host = ServerHost(server); } @@ -57,6 +65,7 @@ namespace Lucene.Net.Replicator.Http clientWorkDir = CreateTempDir("httpReplicatorTest"); handlerIndexDir = NewDirectory(); serverIndexDir = NewDirectory(); + mockErrorConfig = new MockErrorConfig(); // LUCENENET specific serverReplicator = new LocalReplicator(); StartServer(); @@ -108,5 +117,41 @@ namespace Lucene.Net.Replicator.Http ReopenReader(); assertEquals(2, int.Parse(reader.IndexCommit.UserData["ID"], NumberStyles.HexNumber)); } + + [Test] + public void TestServerErrors() + { + // tests the behaviour of the client when the server sends an error + IReplicator replicator = new HttpReplicator(host, port, ReplicationService.REPLICATION_CONTEXT + "/s1", server.CreateHandler()); + using ReplicationClient client = new ReplicationClient(replicator, new IndexReplicationHandler(handlerIndexDir, null), + new PerSessionDirectoryFactory(clientWorkDir.FullName)); + + try + { + PublishRevision(5); + + try + { + mockErrorConfig.RespondWithError = true; + client.UpdateNow(); + fail("expected exception"); + } + catch (Exception t) when (t.IsThrowable()) + { + // expected + } + + mockErrorConfig.RespondWithError = false; + client.UpdateNow(); // now it should work + ReopenReader(); + assertEquals(5, J2N.Numerics.Int32.Parse(reader.IndexCommit.UserData["ID"], 16)); + + client.Dispose(); + } + finally + { + mockErrorConfig.RespondWithError = false; + } + } } } diff --git a/src/Lucene.Net.Tests.Replicator/Http/ReplicationServlet.cs b/src/Lucene.Net.Tests.Replicator/Http/ReplicationServlet.cs index 1fa3b05..fd93eeb 100644 --- a/src/Lucene.Net.Tests.Replicator/Http/ReplicationServlet.cs +++ b/src/Lucene.Net.Tests.Replicator/Http/ReplicationServlet.cs @@ -1,9 +1,16 @@ -using Lucene.Net.Replicator.AspNetCore; +using Lucene.Net.Replicator.AspNetCore; using Lucene.Net.Replicator.Http.Abstractions; using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http.Features; +using System; using System.Threading.Tasks; +#if FEATURE_ASPNETCORE_ENDPOINT_CONFIG +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; +#endif + namespace Lucene.Net.Replicator.Http { /* @@ -23,15 +30,85 @@ namespace Lucene.Net.Replicator.Http * limitations under the License. */ + // ********************** Option 1: Use a Startup Class ******************************************** + // The startup class must define all middleware (app.Use) before the terminating endpoint (app.Run) + public class ReplicationServlet { - public void Configure(IApplicationBuilder app, IHostingEnvironment env, IReplicationService service) + public void Configure(IApplicationBuilder app, IReplicationService service, ReplicatorTestCase.MockErrorConfig mockErrorConfig) { - app.Run(async context => + // Middleware to throw an exception conditionally from our test server. + app.Use(async (context, next) => + { + if (mockErrorConfig.RespondWithError) + { + throw new ReplicatorTestCase.HttpResponseException(); + } + + await next(); + }); + + app.Run(async (context) => { + // LUCENENET: This is to allow synchronous IO to happen for these requests. + // LUCENENET TODO: Allow async operations from Replicator. + var syncIoFeature = context.Features.Get<IHttpBodyControlFeature>(); + if (syncIoFeature != null) + { + syncIoFeature.AllowSynchronousIO = true; + } + await Task.Yield(); service.Perform(context.Request, context.Response); }); } } + + // ********************** Option 2: Use Middleware with Endpoint Routing ******************************* + // Running ReplicationService as middleware allows registering other URL patterns so other services + // (such as controllers or razor pages) can be served from the same application. + +#if FEATURE_ASPNETCORE_ENDPOINT_CONFIG // Only available in .NET 5+ + public class ReplicationServiceMiddleware + { + private readonly RequestDelegate next; + private readonly IReplicationService service; + + public ReplicationServiceMiddleware(RequestDelegate next, IReplicationService service) + { + this.next = next ?? throw new ArgumentNullException(nameof(next)); + this.service = service ?? throw new ArgumentNullException(nameof(service)); + } + + public async Task InvokeAsync(HttpContext context) + { + // LUCENENET: This is to allow synchronous IO to happen for these requests. + // LUCENENET TODO: Allow async operations from Replicator. + var syncIoFeature = context.Features.Get<IHttpBodyControlFeature>(); + if (syncIoFeature != null) + { + syncIoFeature.AllowSynchronousIO = true; + } + + await Task.Yield(); + service.Perform(context.Request, context.Response); + + // This is a terminating endpoint. Do not call the next delegate/middleware in the pipeline. + } + } + + public static class ReplicationServiceRouteBuilderExtensions + { + public static IEndpointConventionBuilder MapReplicator(this IEndpointRouteBuilder endpoints, string pattern) + { + var pipeline = endpoints.CreateApplicationBuilder() + .UseMiddleware<ReplicationServiceMiddleware>() + .Build(); + + return endpoints + .Map(pattern, pipeline) + .WithDisplayName("Replication Service"); + } + } +#endif } \ No newline at end of file diff --git a/src/Lucene.Net.Tests.Replicator/ReplicatorTestCase.cs b/src/Lucene.Net.Tests.Replicator/ReplicatorTestCase.cs index 4e5562f..b0abc94 100644 --- a/src/Lucene.Net.Tests.Replicator/ReplicatorTestCase.cs +++ b/src/Lucene.Net.Tests.Replicator/ReplicatorTestCase.cs @@ -1,10 +1,16 @@ -using Lucene.Net.Replicator.Http; +using Lucene.Net.Replicator.Http; using Lucene.Net.Replicator.Http.Abstractions; using Lucene.Net.Util; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; using System; +using System.Threading.Tasks; + +#if FEATURE_ASPNETCORE_ENDPOINT_CONFIG +using Microsoft.AspNetCore.Builder; +#endif namespace Lucene.Net.Replicator { @@ -28,16 +34,73 @@ namespace Lucene.Net.Replicator [SuppressCodecs("Lucene3x")] public class ReplicatorTestCase : LuceneTestCase { - public static TestServer NewHttpServer<TStartUp>(IReplicationService service) where TStartUp : class + +#if FEATURE_ASPNETCORE_ENDPOINT_CONFIG + /// <summary> + /// Call this overload to use <see cref="ReplicationServiceMiddleware"/> to host <see cref="ReplicationService"/>. + /// </summary> + /// <param name="service">The <see cref="ReplicationService"/> that will be registered as singleton.</param> + /// <param name="mockErrorConfig">The <see cref="MockErrorConfig"/> that will be registered as singleton.</param> + /// <returns>A configured <see cref="TestServer"/> instance.</returns> + public static TestServer NewHttpServer(IReplicationService service, MockErrorConfig mockErrorConfig) + { + var builder = new WebHostBuilder() + .ConfigureServices(container => + { + container.AddRouting(); + container.AddSingleton(service); + container.AddSingleton(mockErrorConfig); + container.AddSingleton<ReplicationServiceMiddleware>(); + container.AddSingleton<MockErrorMiddleware>(); + }) + .Configure(app => + { + app.UseRouting(); + + // Middleware so we can mock a server exception and toggle the exception on and off. + app.UseMiddleware<MockErrorMiddleware>(); + + app.UseEndpoints(endpoints => + { + // This is to define the endpoint for Replicator. + // All URLs with the pattern /replicate/{shard?}/{action?} terminate here and any middleware that + // is expected to run for Replicator must be registered before this call. + endpoints.MapReplicator(ReplicationService.REPLICATION_CONTEXT + "/{shard?}/{action?}"); + + endpoints.MapGet("/{controller?}/{action?}/{id?}", async context => + { + // This is just to demonstrate allowing requests to other services/controllers in the same + // application. This isn't required, but is allowed. + await context.Response.WriteAsync("Hello World!"); + }); + }); + }); + var server = new TestServer(builder); + return server; + } +#else + /// <summary> + /// Call this overload to use <typeparamref name="TStartUp"/> as the Startup Class. + /// </summary> + /// <typeparam name="TStartUp">The type of startup class.</typeparam> + /// <param name="service">The <see cref="ReplicationService"/> that will be registered as singleton.</param> + /// <param name="mockErrorConfig">The <see cref="MockErrorConfig"/> that will be registered as singleton.</param> + /// <returns>A configured <see cref="TestServer"/> instance.</returns> + public static TestServer NewHttpServer<TStartUp>(IReplicationService service, MockErrorConfig mockErrorConfig) where TStartUp : class { - var server = new TestServer(new WebHostBuilder() + var builder = new WebHostBuilder() .ConfigureServices(container => { container.AddSingleton(service); - }).UseStartup<TStartUp>()); + container.AddSingleton(mockErrorConfig); + }) + .UseStartup<TStartUp>(); + + var server = new TestServer(builder); server.BaseAddress = new Uri("http://localhost" + ReplicationService.REPLICATION_CONTEXT); return server; } +#endif /// <summary> /// Returns a <see cref="server"/>'s port. @@ -62,5 +125,44 @@ namespace Lucene.Net.Replicator { server.Dispose(); } + + public class HttpResponseException : Exception + { + public int Status { get; set; } = 500; + + public object Value { get; set; } + } + + public class MockErrorMiddleware + { + private readonly RequestDelegate next; + private readonly MockErrorConfig mockErrorConfig; + + public MockErrorMiddleware(RequestDelegate next, MockErrorConfig mockErrorConfig) + { + this.next = next ?? throw new ArgumentNullException(nameof(next)); + this.mockErrorConfig = mockErrorConfig ?? throw new ArgumentNullException(nameof(mockErrorConfig)); + } + + public async Task InvokeAsync(HttpContext context) + { + var path = context.Request.Path; + if (path.StartsWithSegments(ReplicationService.REPLICATION_CONTEXT)) + { + if (mockErrorConfig.RespondWithError) + { + throw new HttpResponseException(); + } + } + + // Call the next delegate/middleware in the pipeline + await next(context); + } + } + + public class MockErrorConfig + { + public bool RespondWithError { get; set; } = false; + } } } \ No newline at end of file
