diff --git a/Directory.Build.targets b/Directory.Build.targets
index efbd8089f3..a8c0f7cfa5 100644
--- a/Directory.Build.targets
+++ b/Directory.Build.targets
@@ -27,7 +27,8 @@
- $(AlternatePublishRootDirectory)/$(TargetFramework)/$(MSBuildProjectName)/
+
+ $(AlternatePublishRootDirectory)/$(TargetFramework)/$(MSBuildProjectName)/
@@ -38,7 +39,8 @@
-
+
$(DefineConstants);FEATURE_ASPNETCORE_TESTHOST
$(DefineConstants);FEATURE_UTF8_TOUTF16
@@ -46,7 +48,8 @@
-
+
$(DefineConstants);FEATURE_RANDOM_NEXTINT64_NEXTSINGLE
$(DefineConstants);FEATURE_SPANFORMATTABLE
@@ -55,22 +58,26 @@
-
+
$(DefineConstants);FEATURE_READONLYSET
-
+
$(DefineConstants);FEATURE_PROCESS_KILL_ENTIREPROCESSTREE
$(DefineConstants);FEATURE_STRING_CONCAT_READONLYSPAN
-
-
+
+
$(DefineConstants);NETSTANDARD
$(DefineConstants);FEATURE_CULTUREINFO_CURRENTCULTURE_SETTER
@@ -78,9 +85,16 @@
portable
-
-
-
+
+
+ $(DefineConstants);FEATURE_HTTPCONTENT_READASSTREAM
+ $(DefineConstants);FEATURE_HTTPCONTENT_READASSTREAM_CANCELLATIONTOKEN
+
+
+
$(DefineConstants);FEATURE_ARRAY_FILL
$(DefineConstants);FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
@@ -92,22 +106,28 @@
-
-
+
+
$(DefineConstants);FEATURE_STRING_CONTAINS_STRINGCOMPARISON
-
-
+
+
$(DefineConstants);FEATURE_ICONFIGURATIONROOT_PROVIDERS
-
-
+
+
$(DefineConstants);FEATURE_ASSEMBLY_GETCALLINGASSEMBLY
$(DefineConstants);FEATURE_FILESTREAM_LOCK
@@ -116,14 +136,17 @@
-
-
+
+
$(DefineConstants);FEATURE_SERIALIZABLE_EXCEPTIONS
$(DefineConstants);FEATURE_SERIALIZABLE
-
+
@@ -132,7 +155,8 @@
-
+
$(DefineConstants);FEATURE_ICONFIGURATIONROOT_PROVIDERS
@@ -142,7 +166,8 @@
$(DefineConstants);NETFRAMEWORK
-
$(DefineConstants);FEATURE_CODE_ACCESS_SECURITY
$(DefineConstants);FEATURE_MEMORYMAPPEDFILESECURITY
@@ -152,9 +177,12 @@
full
-
-
-
+
+
+
$(DefineConstants);FEATURE_OPENNLP
@@ -163,34 +191,45 @@
+ https://docs.microsoft.com/en-us/nuget/create-packages/symbol-packages-snupkg#nugetorg-symbol-package-constraints -->
portable
-
+
<_Parameter1>%(InternalsVisibleTo.Identity)
- <_Parameter1 Condition=" '$(SignAssembly)' == 'true' And '$(PublicKey)' != '' ">%(InternalsVisibleTo.Identity), PublicKey=$(PublicKey)
+ <_Parameter1 Condition=" '$(SignAssembly)' == 'true' And '$(PublicKey)' != '' ">%(InternalsVisibleTo.Identity),
+ PublicKey=$(PublicKey)
-
+
- true
- $(TargetFramework)
+
+ true
+
+ $(TargetFramework)
- $(TargetFrameworks)
+
+ $(TargetFrameworks)
none
-
+
-
@@ -201,7 +240,8 @@
-
+
@@ -209,17 +249,24 @@
-
-
+
+
-
-
+
+
-
-
+
+
diff --git a/src/Lucene.Net.Replicator/Http/HttpClientBase.cs b/src/Lucene.Net.Replicator/Http/HttpClientBase.cs
index b6e94fb49a..04f0a0fd82 100644
--- a/src/Lucene.Net.Replicator/Http/HttpClientBase.cs
+++ b/src/Lucene.Net.Replicator/Http/HttpClientBase.cs
@@ -1,3 +1,4 @@
+#nullable enable
using Lucene.Net.Diagnostics;
using Lucene.Net.Support;
using Newtonsoft.Json;
@@ -73,7 +74,7 @@ public abstract class HttpClientBase : IDisposable
/// The port to be used to connect on.
/// The path to the replicator on the host.
/// Optional, The HTTP handler stack to use for sending requests, defaults to null.
- protected HttpClientBase(string host, int port, string path, HttpMessageHandler messageHandler = null)
+ protected HttpClientBase(string host, int port, string path, HttpMessageHandler? messageHandler = null)
: this(NormalizedUrl(host, port, path), messageHandler)
{
}
@@ -88,7 +89,7 @@ protected HttpClientBase(string host, int port, string path, HttpMessageHandler
/// The full url, including with host, port and path.
/// Optional, The HTTP handler stack to use for sending requests.
//Note: LUCENENET Specific
- protected HttpClientBase(string url, HttpMessageHandler messageHandler = null)
+ protected HttpClientBase(string url, HttpMessageHandler? messageHandler = null)
: this(url, new HttpClient(messageHandler ?? new HttpClientHandler()) { Timeout = DEFAULT_TIMEOUT })
{
}
@@ -171,27 +172,51 @@ protected virtual void ThrowKnownError(HttpResponseMessage response)
}
///
- /// Internal: Execute a request and return its result.
+ /// Internal: Execute a POST request with custom HttpContent and return its result.
/// The argument is treated as: name1,value1,name2,value2,...
///
- protected virtual HttpResponseMessage ExecutePost(string request, object entity, params string[] parameters)
+ protected virtual HttpResponseMessage ExecutePost(string request, HttpContent content, params string[]? parameters)
{
EnsureOpen();
- //.NET Note: No headers? No ContentType?... Bad use of Http?
- HttpRequestMessage req = new HttpRequestMessage(HttpMethod.Post, QueryString(request, parameters));
+ var req = new HttpRequestMessage(HttpMethod.Post, QueryString(request, parameters))
+ {
+ Content = content
+ };
+
+ // Use SendAsync + GetAwaiter().GetResult() to bridge sync call
+ var resp = httpc.SendAsync(req, HttpCompletionOption.ResponseHeadersRead)
+ .ConfigureAwait(false)
+ .GetAwaiter()
+ .GetResult();
+ VerifyStatus(resp);
+ return resp;
+ }
- req.Content = new StringContent(JToken.FromObject(entity, JsonSerializer.Create())
- .ToString(Formatting.None), Encoding.UTF8, "application/json");
+ ///
+ /// Internal: Execute a POST request asynchronously with custom HttpContent.
+ /// The argument is treated as: name1,value1,name2,value2,...
+ ///
+ protected virtual async Task ExecutePostAsync(string request, HttpContent content, params string[]? parameters)
+ {
+ EnsureOpen();
- return Execute(req);
+ var req = new HttpRequestMessage(HttpMethod.Post, QueryString(request, parameters))
+ {
+ Content = content
+ };
+
+ var resp = await httpc.SendAsync(req).ConfigureAwait(false); // Async call
+ VerifyStatus(resp);
+ return resp;
}
+
///
/// Internal: Execute a request and return its result.
/// The argument is treated as: name1,value1,name2,value2,...
///
- protected virtual HttpResponseMessage ExecuteGet(string request, params string[] parameters)
+ protected virtual HttpResponseMessage ExecuteGet(string request, params string[]? parameters)
{
EnsureOpen();
@@ -200,6 +225,36 @@ protected virtual HttpResponseMessage ExecuteGet(string request, params string[]
return Execute(req);
}
+ ///
+ /// Execute a GET request asynchronously with an array of parameters.
+ ///
+ protected Task ExecuteGetAsync(string action, string[]? parameters, CancellationToken cancellationToken)
+ {
+ EnsureOpen();
+ var url = QueryString(action, parameters);
+ return httpc.GetAsync(url, cancellationToken);
+ }
+
+ ///
+ /// Execute a GET request asynchronously with up to 3 name/value parameters.
+ ///
+ protected Task ExecuteGetAsync(
+ string action,
+ string param1, string value1,
+ string? param2 = null, string? value2 = null,
+ string? param3 = null, string? value3 = null,
+ CancellationToken cancellationToken = default)
+ {
+ EnsureOpen();
+ var url = (param2 == null && param3 == null)
+ ? QueryString(action, param1, value1)
+ : QueryString(action,
+ param1, value1,
+ param2 ?? string.Empty, value2 ?? string.Empty,
+ param3 ?? string.Empty, value3 ?? string.Empty);
+ return httpc.GetAsync(url, cancellationToken);
+ }
+
private HttpResponseMessage Execute(HttpRequestMessage request)
{
//.NET Note: Bridging from Async to Sync, this is not ideal and we could consider changing the interface to be Async or provide Async overloads
@@ -209,7 +264,7 @@ private HttpResponseMessage Execute(HttpRequestMessage request)
return response;
}
- private string QueryString(string request, params string[] parameters)
+ private string QueryString(string request, params string[]? parameters)
{
return parameters is null
? string.Format("{0}/{1}", Url, request)
@@ -256,7 +311,43 @@ public virtual Stream GetResponseStream(HttpResponseMessage response) // LUCENEN
///
public virtual Stream GetResponseStream(HttpResponseMessage response, bool consume) // LUCENENET: This was ResponseInputStream in Lucene
{
+#if FEATURE_HTTPCONTENT_READASSTREAM
+ Stream result = response.Content.ReadAsStream();
+#else
Stream result = response.Content.ReadAsStreamAsync().ConfigureAwait(false).GetAwaiter().GetResult();
+#endif
+
+ if (consume)
+ result = new ConsumingStream(result);
+ return result;
+ }
+
+ ///
+ /// Internal utility: input stream of the provided response asynchronously.
+ ///
+ ///
+ public virtual async Task GetResponseStreamAsync(HttpResponseMessage response, CancellationToken cancellationToken = default)
+ {
+#if FEATURE_HTTPCONTENT_READASSTREAM_CANCELLATIONTOKEN
+ Stream result = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
+#else
+ Stream result = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
+#endif
+ return result;
+ }
+
+ ///
+ /// Internal utility: input stream of the provided response asynchronously, which optionally
+ /// consumes the response's resources when the input stream is exhausted.
+ ///
+ ///
+ public virtual async Task GetResponseStreamAsync(HttpResponseMessage response, bool consume, CancellationToken cancellationToken = default)
+ {
+#if FEATURE_HTTPCONTENT_READASSTREAM_CANCELLATIONTOKEN
+ Stream result = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
+#else
+ Stream result = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
+#endif
if (consume)
result = new ConsumingStream(result);
return result;
@@ -284,7 +375,7 @@ protected virtual T DoAction(HttpResponseMessage response, Func call)
///
protected virtual T DoAction(HttpResponseMessage response, bool consume, Func call)
{
- Exception th = null;
+ Exception? th = null;
try
{
return call();
@@ -315,10 +406,63 @@ protected virtual T DoAction(HttpResponseMessage response, bool consume, Func
}
}
if (Debugging.AssertsEnabled) Debugging.Assert(th != null); // extra safety - if we get here, it means the Func failed
- Util.IOUtils.ReThrow(th);
- return default; // silly, if we're here, IOUtils.reThrow always throws an exception
+ Util.IOUtils.ReThrow(th!);
+ return default!; // silly, if we're here, IOUtils.reThrow always throws an exception
+ }
+
+ ///
+ /// Do a specific async action and validate after the action that the status is still OK,
+ /// and if not, attempt to extract the actual server side exception. Optionally
+ /// release the response at exit, depending on parameter.
+ ///
+ protected virtual async Task DoActionAsync(HttpResponseMessage response, bool consume, Func> call)
+ {
+ Exception? th = null;
+ try
+ {
+ VerifyStatus(response);
+ return await call().ConfigureAwait(false);
+ }
+ catch (Exception t) when (t.IsThrowable())
+ {
+ th = t;
+ }
+ finally
+ {
+ try
+ {
+ VerifyStatus(response);
+ }
+ finally
+ {
+ if (consume)
+ {
+ try
+ {
+ ConsumeQuietly(response);
+ }
+ catch
+ {
+ // ignore on purpose
+ }
+ }
+ }
+ }
+
+ if (Debugging.AssertsEnabled) Debugging.Assert(th != null);
+ Util.IOUtils.ReThrow(th!);
+ return default!; // never reached, rethrow above always throws
+ }
+
+ ///
+ /// Calls the overload passing true to consume.
+ ///
+ protected virtual Task DoActionAsync(HttpResponseMessage response, Func> call)
+ {
+ return DoActionAsync(response, true, call);
}
+
///
/// Disposes this .
/// When called with true, this disposes the underlying .
diff --git a/src/Lucene.Net.Replicator/Http/HttpReplicator.cs b/src/Lucene.Net.Replicator/Http/HttpReplicator.cs
index 5ae21ad06c..305f681fda 100644
--- a/src/Lucene.Net.Replicator/Http/HttpReplicator.cs
+++ b/src/Lucene.Net.Replicator/Http/HttpReplicator.cs
@@ -1,7 +1,11 @@
+#nullable enable
using J2N.IO;
using System;
using System.IO;
using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+
namespace Lucene.Net.Replicator.Http
{
@@ -28,14 +32,14 @@ namespace Lucene.Net.Replicator.Http
///
/// @lucene.experimental
///
- public class HttpReplicator : HttpClientBase, IReplicator
+ public class HttpReplicator : HttpClientBase, IReplicator, IAsyncReplicator
{
///
/// Creates a new with the given host, port and path.
/// for more details.
///
- public HttpReplicator(string host, int port, string path, HttpMessageHandler messageHandler = null)
- : base(host, port, path, messageHandler)
+ public HttpReplicator(string host, int port, string path, HttpMessageHandler? messageHandler = null)
+ : base(host, port, path, messageHandler ?? new HttpClientHandler())
{
}
@@ -44,7 +48,7 @@ public HttpReplicator(string host, int port, string path, HttpMessageHandler mes
/// for more details.
///
//Note: LUCENENET Specific
- public HttpReplicator(string url, HttpMessageHandler messageHandler = null)
+ public HttpReplicator(string url, HttpMessageHandler? messageHandler = null)
: this(url, new HttpClient(messageHandler ?? new HttpClientHandler()) { Timeout = DEFAULT_TIMEOUT })
{
}
@@ -62,13 +66,13 @@ public HttpReplicator(string url, HttpClient client)
///
/// Checks for updates at the remote host.
///
- public virtual SessionToken CheckForUpdate(string currentVersion)
+ public virtual SessionToken? CheckForUpdate(string? currentVersion)
{
- string[] parameters = null;
- if (currentVersion != null)
- parameters = new[] { ReplicationService.REPLICATE_VERSION_PARAM, currentVersion };
+ string[]? parameters = null;
+ if (!string.IsNullOrEmpty(currentVersion))
+ parameters = new[] { ReplicationService.REPLICATE_VERSION_PARAM, currentVersion! };
- HttpResponseMessage response = base.ExecuteGet(ReplicationService.ReplicationAction.UPDATE.ToString(), parameters);
+ HttpResponseMessage response = base.ExecuteGet(ReplicationService.ReplicationAction.UPDATE.ToString(), parameters ?? Array.Empty());
return DoAction(response, () =>
{
using DataInputStream inputStream = new DataInputStream(GetResponseStream(response));
@@ -104,7 +108,98 @@ public virtual void Release(string sessionId)
{
HttpResponseMessage response = ExecuteGet(ReplicationService.ReplicationAction.RELEASE.ToString(), ReplicationService.REPLICATE_SESSION_ID_PARAM, sessionId);
// do not remove this call: as it is still validating for us!
- DoAction