1- using Lucene . Net . Diagnostics ;
21using Lucene . Net . Support ;
32using System ;
43using System . IO ;
76using System . Net . Http ;
87using System . Threading ;
98using System . Threading . Tasks ;
9+ // ReSharper disable VirtualMemberNeverOverridden.Global
10+ // ReSharper disable MemberCanBePrivate.Global
11+
1012#nullable enable
1113
1214namespace Lucene . Net . Replicator . Http
@@ -173,20 +175,38 @@ protected virtual void ThrowKnownError(HttpResponseMessage response)
173175 }
174176
175177 /// <summary>
176- /// <b>Internal:</b> Execute a request and return its result.
178+ /// <b>Internal:</b> Execute a POST request with custom HttpContent and return its result.
177179 /// The <paramref name="parameters"/> argument is treated as: name1,value1,name2,value2,...
178180 /// </summary>
179181 protected virtual HttpResponseMessage ExecutePost ( string request , HttpContent content , params string [ ] ? parameters )
180182 {
181183 EnsureOpen ( ) ;
182184
183- var req = new HttpRequestMessage ( HttpMethod . Post , QueryString ( request , parameters ) ) ;
184-
185- req . Content = content ;
185+ var req = new HttpRequestMessage ( HttpMethod . Post , QueryString ( request , parameters ) )
186+ {
187+ Content = content
188+ } ;
186189
187190 return Execute ( req ) ;
188191 }
189192
193+ /// <summary>
194+ /// <b>Internal:</b> Execute a POST request asynchronously with custom HttpContent.
195+ /// The <paramref name="parameters"/> argument is treated as: name1,value1,name2,value2,...
196+ /// </summary>
197+ protected virtual async Task < HttpResponseMessage > ExecutePostAsync ( string request , HttpContent content , CancellationToken cancellationToken , params string [ ] ? parameters )
198+ {
199+ EnsureOpen ( ) ;
200+
201+ var req = new HttpRequestMessage ( HttpMethod . Post , QueryString ( request , parameters ) )
202+ {
203+ Content = content
204+ } ;
205+
206+ return await ExecuteAsync ( req , cancellationToken ) . ConfigureAwait ( false ) ;
207+ }
208+
209+
190210 /// <summary>
191211 /// <b>Internal:</b> Execute a request and return its result.
192212 /// The <paramref name="parameters"/> argument is treated as: name1,value1,name2,value2,...
@@ -200,6 +220,16 @@ protected virtual HttpResponseMessage ExecuteGet(string request, params string[]
200220 return Execute ( req ) ;
201221 }
202222
223+ /// <summary>
224+ /// Execute a GET request asynchronously with an array of parameters.
225+ /// </summary>
226+ protected virtual async Task < HttpResponseMessage > ExecuteGetAsync ( string action , string [ ] ? parameters , CancellationToken cancellationToken )
227+ {
228+ EnsureOpen ( ) ;
229+ var req = new HttpRequestMessage ( HttpMethod . Get , QueryString ( action , parameters ) ) ;
230+ return await ExecuteAsync ( req , cancellationToken ) . ConfigureAwait ( false ) ;
231+ }
232+
203233 private HttpResponseMessage Execute ( HttpRequestMessage request )
204234 {
205235 //.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,6 +239,16 @@ private HttpResponseMessage Execute(HttpRequestMessage request)
209239 return response ;
210240 }
211241
242+ // LUCENENET specific - async counterpart to Execute used by ExecuteGetAsync/ExecutePostAsync.
243+ // Uses ResponseHeadersRead so callers can stream large response bodies (e.g. replication files)
244+ // rather than buffering them into memory, and verifies status (matching sync Execute).
245+ private async Task < HttpResponseMessage > ExecuteAsync ( HttpRequestMessage request , CancellationToken cancellationToken )
246+ {
247+ var response = await httpc . SendAsync ( request , HttpCompletionOption . ResponseHeadersRead , cancellationToken ) . ConfigureAwait ( false ) ;
248+ VerifyStatus ( response ) ;
249+ return response ;
250+ }
251+
212252 private string QueryString ( string request , params string [ ] ? parameters )
213253 {
214254 return parameters is null || parameters . Length == 0
@@ -255,7 +295,11 @@ public virtual Stream GetResponseStream(HttpResponseMessage response) // LUCENEN
255295 /// <exception cref="IOException"></exception>
256296 public virtual Stream GetResponseStream ( HttpResponseMessage response , bool consume ) // LUCENENET: This was ResponseInputStream in Lucene
257297 {
298+ #if FEATURE_HTTPCONTENT_READASSTREAM
299+ Stream result = response . Content . ReadAsStream ( ) ;
300+ #else
258301 Stream result = response . Content . ReadAsStreamAsync ( ) . ConfigureAwait ( false ) . GetAwaiter ( ) . GetResult ( ) ;
302+ #endif
259303
260304 if ( consume )
261305 {
@@ -265,6 +309,70 @@ public virtual Stream GetResponseStream(HttpResponseMessage response, bool consu
265309 return result ;
266310 }
267311
312+ /// <summary>
313+ /// Internal utility: input stream of the provided response.
314+ /// The returned stream takes ownership of the response and will dispose it
315+ /// when the stream is disposed.
316+ /// </summary>
317+ /// <exception cref="IOException"></exception>
318+ protected virtual Stream GetResponseStreamWithOwnership ( HttpResponseMessage response )
319+ {
320+ #if FEATURE_HTTPCONTENT_READASSTREAM
321+ Stream result = response . Content . ReadAsStream ( ) ;
322+ #else
323+ Stream result = response . Content . ReadAsStreamAsync ( ) . ConfigureAwait ( false ) . GetAwaiter ( ) . GetResult ( ) ;
324+ #endif
325+ return new ResponseOwningStream ( result , response ) ;
326+ }
327+
328+ /// <summary>
329+ /// Internal utility: input stream of the provided response asynchronously.
330+ /// </summary>
331+ /// <exception cref="IOException"></exception>
332+ public virtual async Task < Stream > GetResponseStreamAsync ( HttpResponseMessage response , CancellationToken cancellationToken = default )
333+ {
334+ #if FEATURE_HTTPCONTENT_READASSTREAM_CANCELLATIONTOKEN
335+ Stream result = await response . Content . ReadAsStreamAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
336+ #else
337+ Stream result = await response . Content . ReadAsStreamAsync ( ) . ConfigureAwait ( false ) ;
338+ #endif
339+ return result ;
340+ }
341+
342+ /// <summary>
343+ /// Internal utility: input stream of the provided response asynchronously, which optionally
344+ /// consumes the response's resources when the input stream is exhausted.
345+ /// </summary>
346+ /// <exception cref="IOException"></exception>
347+ // ReSharper disable once UnusedMember.Global - public API
348+ public virtual async Task < Stream > GetResponseStreamAsync ( HttpResponseMessage response , bool consume , CancellationToken cancellationToken = default )
349+ {
350+ #if FEATURE_HTTPCONTENT_READASSTREAM_CANCELLATIONTOKEN
351+ Stream result = await response . Content . ReadAsStreamAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
352+ #else
353+ Stream result = await response . Content . ReadAsStreamAsync ( ) . ConfigureAwait ( false ) ;
354+ #endif
355+ if ( consume )
356+ result = new ConsumingStream ( result ) ;
357+ return result ;
358+ }
359+
360+ /// <summary>
361+ /// Internal utility: input stream of the provided response asynchronously.
362+ /// The returned stream takes ownership of the response and will dispose it
363+ /// when the stream is disposed.
364+ /// </summary>
365+ /// <exception cref="IOException"></exception>
366+ protected virtual async Task < Stream > GetResponseStreamWithOwnershipAsync ( HttpResponseMessage response , CancellationToken cancellationToken = default )
367+ {
368+ #if FEATURE_HTTPCONTENT_READASSTREAM_CANCELLATIONTOKEN
369+ Stream result = await response . Content . ReadAsStreamAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
370+ #else
371+ Stream result = await response . Content . ReadAsStreamAsync ( ) . ConfigureAwait ( false ) ;
372+ #endif
373+ return new ResponseOwningStream ( result , response ) ;
374+ }
375+
268376 /// <summary>
269377 /// Returns <c>true</c> if this instance was <see cref="Dispose(bool)"/>ed, otherwise
270378 /// returns <c>false</c>. Note that if you override <see cref="Dispose(bool)"/>, you must call
@@ -310,11 +418,56 @@ protected virtual T DoAction<T>(HttpResponseMessage response, bool consume, Func
310418 }
311419 }
312420 }
313- if ( Debugging . AssertsEnabled ) Debugging . Assert ( th != null ) ; // extra safety - if we get here, it means the Func<T> failed
421+
422+ // if (Debugging.AssertsEnabled) Debugging.Assert(th != null); // LUCENENET: Removed assertion because it'll never be null here, ensured by NRT
314423 Util . IOUtils . ReThrow ( th ) ;
315424 return default ! ; // silly, if we're here, IOUtils.reThrow always throws an exception
316425 }
317426
427+ /// <summary>
428+ /// Do a specific async action and validate after the action that the status is still OK,
429+ /// and if not, attempt to extract the actual server side exception. Optionally
430+ /// release the response at exit, depending on <paramref name="consume"/> parameter.
431+ /// </summary>
432+ protected virtual async Task < T > DoActionAsync < T > ( HttpResponseMessage response , bool consume , Func < Task < T > > call )
433+ {
434+ Exception ? th /* = null */ ;
435+ try
436+ {
437+ return await call ( ) . ConfigureAwait ( false ) ;
438+ }
439+ catch ( Exception t ) when ( t . IsThrowable ( ) )
440+ {
441+ th = t ;
442+ }
443+ finally
444+ {
445+ try
446+ {
447+ VerifyStatus ( response ) ;
448+ }
449+ finally
450+ {
451+ if ( consume )
452+ {
453+ ConsumeQuietly ( response ) ;
454+ }
455+ }
456+ }
457+
458+ // if (Debugging.AssertsEnabled) Debugging.Assert(th != null); // LUCENENET: Removed assertion because it'll never be null here, ensured by NRT
459+ Util . IOUtils . ReThrow ( th ) ;
460+ return default ! ; // never reached, rethrow above always throws
461+ }
462+
463+ /// <summary>
464+ /// Calls the overload <see cref="DoActionAsync{T}(HttpResponseMessage, bool, Func{Task{T}})"/> passing <c>true</c> to consume.
465+ /// </summary>
466+ protected virtual Task < T > DoActionAsync < T > ( HttpResponseMessage response , Func < Task < T > > call )
467+ {
468+ return DoActionAsync ( response , true , call ) ;
469+ }
470+
318471 /// <summary>
319472 /// Disposes this <see cref="HttpClientBase"/>.
320473 /// When called with <code>true</code>, this disposes the underlying <see cref="HttpClient"/>.
@@ -350,14 +503,70 @@ private static void ConsumeQuietly(HttpResponseMessage response)
350503 }
351504 }
352505
506+ /// <summary>
507+ /// Wraps a stream and disposes the associated <see cref="HttpResponseMessage"/>
508+ /// when the stream is disposed.
509+ /// </summary>
510+ private sealed class ResponseOwningStream : Stream
511+ {
512+ private readonly Stream input ;
513+ private readonly HttpResponseMessage response ;
514+ private bool disposed ;
515+
516+ public ResponseOwningStream ( Stream input , HttpResponseMessage response )
517+ {
518+ this . input = input ?? throw new ArgumentNullException ( nameof ( input ) ) ;
519+ this . response = response ?? throw new ArgumentNullException ( nameof ( response ) ) ;
520+ }
521+
522+ public override bool CanRead => input . CanRead ;
523+ public override bool CanSeek => input . CanSeek ;
524+ public override bool CanWrite => input . CanWrite ;
525+ public override long Length => input . Length ;
526+ public override long Position
527+ {
528+ get => input . Position ;
529+ set => input . Position = value ;
530+ }
531+
532+ public override void Flush ( ) => input . Flush ( ) ;
533+ public override int Read ( byte [ ] buffer , int offset , int count ) => input . Read ( buffer , offset , count ) ;
534+ public override long Seek ( long offset , SeekOrigin origin ) => input . Seek ( offset , origin ) ;
535+ public override void SetLength ( long value ) => input . SetLength ( value ) ;
536+ public override void Write ( byte [ ] buffer , int offset , int count ) => input . Write ( buffer , offset , count ) ;
537+
538+ public override Task < int > ReadAsync ( byte [ ] buffer , int offset , int count , CancellationToken cancellationToken )
539+ => input . ReadAsync ( buffer , offset , count , cancellationToken ) ;
540+
541+ #if FEATURE_STREAM_READ_SPAN
542+ public override int Read ( Span < byte > buffer ) => input . Read ( buffer ) ;
543+ public override ValueTask < int > ReadAsync ( Memory < byte > buffer , CancellationToken cancellationToken = default )
544+ => input . ReadAsync ( buffer , cancellationToken ) ;
545+ #endif
546+
547+ protected override void Dispose ( bool disposing )
548+ {
549+ if ( ! disposed )
550+ {
551+ if ( disposing )
552+ {
553+ input . Dispose ( ) ;
554+ response . Dispose ( ) ;
555+ }
556+ disposed = true ;
557+ }
558+ base . Dispose ( disposing ) ;
559+ }
560+ }
561+
353562 /// <summary>
354563 /// Wraps a stream and consumes (flushes) and disposes automatically
355564 /// when the last call to a Read overload occurs.
356565 /// </summary>
357566 private class ConsumingStream : Stream
358567 {
359568 private readonly Stream input ;
360- private bool consumed = false ;
569+ private bool consumed /* = false */ ;
361570
362571 public ConsumingStream ( Stream input )
363572 {
0 commit comments