Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Invoke-WebRequest and Invoke-RestMethod: Rename TimeoutSec to ConnectionTimeoutSeconds (with alias) and add OperationTimeoutSeconds #19558

Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#nullable enable

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
Expand All @@ -26,17 +27,19 @@ public class BasicHtmlWebResponseObject : WebResponseObject
/// Initializes a new instance of the <see cref="BasicHtmlWebResponseObject"/> class.
/// </summary>
/// <param name="response">The response.</param>
/// <param name="perReadTimeout">Time permitted between reads or Timeout.InfiniteTimeSpan for no timeout.</param>
/// <param name="cancellationToken">Cancellation token.</param>
public BasicHtmlWebResponseObject(HttpResponseMessage response, CancellationToken cancellationToken) : this(response, null, cancellationToken) { }
public BasicHtmlWebResponseObject(HttpResponseMessage response, TimeSpan perReadTimeout, CancellationToken cancellationToken) : this(response, null, perReadTimeout, cancellationToken) { }

/// <summary>
/// Initializes a new instance of the <see cref="BasicHtmlWebResponseObject"/> class
/// with the specified <paramref name="contentStream"/>.
/// </summary>
/// <param name="response">The response.</param>
/// <param name="contentStream">The content stream associated with the response.</param>
/// <param name="perReadTimeout">Time permitted between reads or Timeout.InfiniteTimeSpan for no timeout.</param>
/// <param name="cancellationToken">Cancellation token.</param>
public BasicHtmlWebResponseObject(HttpResponseMessage response, Stream? contentStream, CancellationToken cancellationToken) : base(response, contentStream, cancellationToken)
public BasicHtmlWebResponseObject(HttpResponseMessage response, Stream? contentStream, TimeSpan perReadTimeout, CancellationToken cancellationToken) : base(response, contentStream, perReadTimeout, cancellationToken)
{
InitializeContent(cancellationToken);
InitializeRawContent(response);
Expand Down Expand Up @@ -157,7 +160,7 @@ protected void InitializeContent(CancellationToken cancellationToken)
// Fill the Content buffer
string? characterSet = WebResponseHelper.GetCharacterSet(BaseResponse);

Content = StreamHelper.DecodeStream(RawContentStream, characterSet, out Encoding encoding, cancellationToken);
Content = StreamHelper.DecodeStream(RawContentStream, characterSet, out Encoding encoding, perReadTimeout, cancellationToken);
Encoding = encoding;
}
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,12 @@ internal override void ProcessResponse(HttpResponseMessage response)
ArgumentNullException.ThrowIfNull(response);
ArgumentNullException.ThrowIfNull(_cancelToken);

TimeSpan perReadTimeout = ConvertTimeoutSecondsToTimeSpan(OperationTimeoutSeconds);
Stream baseResponseStream = StreamHelper.GetResponseStream(response, _cancelToken.Token);

if (ShouldWriteToPipeline)
{
using BufferingStreamReader responseStream = new(baseResponseStream, _cancelToken.Token);
using BufferingStreamReader responseStream = new(baseResponseStream, perReadTimeout, _cancelToken.Token);

// First see if it is an RSS / ATOM feed, in which case we can
// stream it - unless the user has overridden it with a return type of "XML"
Expand All @@ -96,8 +97,7 @@ internal override void ProcessResponse(HttpResponseMessage response)
{
// Try to get the response encoding from the ContentType header.
string? characterSet = WebResponseHelper.GetCharacterSet(response);

string str = StreamHelper.DecodeStream(responseStream, characterSet, out Encoding encoding, _cancelToken.Token);
string str = StreamHelper.DecodeStream(responseStream, characterSet, out Encoding encoding, perReadTimeout, _cancelToken.Token);

string encodingVerboseName;
try
Expand Down Expand Up @@ -139,12 +139,12 @@ internal override void ProcessResponse(HttpResponseMessage response)
}
}
else if (ShouldSaveToOutFile)
{
{
string outFilePath = WebResponseHelper.GetOutFilePath(response, _qualifiedOutFile);

WriteVerbose(string.Create(System.Globalization.CultureInfo.InvariantCulture, $"File Name: {Path.GetFileName(_qualifiedOutFile)}"));

StreamHelper.SaveStreamToFile(baseResponseStream, outFilePath, this, response.Content.Headers.ContentLength.GetValueOrDefault(), _cancelToken.Token);
StreamHelper.SaveStreamToFile(baseResponseStream, outFilePath, this, response.Content.Headers.ContentLength.GetValueOrDefault(), perReadTimeout, _cancelToken.Token);
}

if (!string.IsNullOrEmpty(StatusCodeVariable))
Expand Down Expand Up @@ -349,18 +349,20 @@ public enum RestReturnType

internal class BufferingStreamReader : Stream
{
internal BufferingStreamReader(Stream baseStream, CancellationToken cancellationToken)
internal BufferingStreamReader(Stream baseStream, TimeSpan perReadTimeout, CancellationToken cancellationToken)
{
_baseStream = baseStream;
_streamBuffer = new MemoryStream();
_length = long.MaxValue;
_copyBuffer = new byte[4096];
_perReadTimeout = perReadTimeout;
_cancellationToken = cancellationToken;
}

private readonly Stream _baseStream;
private readonly MemoryStream _streamBuffer;
private readonly byte[] _copyBuffer;
private readonly TimeSpan _perReadTimeout;
private readonly CancellationToken _cancellationToken;

public override bool CanRead => true;
Expand Down Expand Up @@ -395,7 +397,7 @@ public override int Read(byte[] buffer, int offset, int count)
// If we don't have enough data to fill this from memory, cache more.
// We try to read 4096 bytes from base stream every time, so at most we
// may cache 4095 bytes more than what is required by the Read operation.
int bytesRead = _baseStream.ReadAsync(_copyBuffer, 0, _copyBuffer.Length, _cancellationToken).GetAwaiter().GetResult();
int bytesRead = _baseStream.ReadAsync(_copyBuffer.AsMemory(), _perReadTimeout, _cancellationToken).GetAwaiter().GetResult();

if (_streamBuffer.Position < _streamBuffer.Length)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,11 +266,25 @@ public abstract class WebRequestPSCmdlet : PSCmdlet, IDisposable
public virtual SwitchParameter DisableKeepAlive { get; set; }

/// <summary>
/// Gets or sets the TimeOut property.
/// Gets or sets the ConnectionTimeoutSeconds property.
/// </summary>
/// <remarks>
/// This property applies to sending the request and receiving the response headers only.
/// </remarks>
[Alias("TimeoutSec")]
[Parameter]
[ValidateRange(0, int.MaxValue)]
public virtual int ConnectionTimeoutSeconds { get; set; }

/// <summary>
/// Gets or sets the OperationTimeoutSeconds property.
/// </summary>
/// <remarks>
/// This property applies to each read operation when receiving the response body.
/// </remarks>
[Parameter]
[ValidateRange(0, int.MaxValue)]
public virtual int TimeoutSec { get; set; }
public virtual int OperationTimeoutSeconds { get; set; }

/// <summary>
/// Gets or sets the Headers property.
Expand Down Expand Up @@ -570,7 +584,7 @@ protected override void ProcessRecord()
string respVerboseMsg = contentLength is null
? string.Format(CultureInfo.CurrentCulture, WebCmdletStrings.WebResponseNoSizeVerboseMsg, response.Version, contentType)
: string.Format(CultureInfo.CurrentCulture, WebCmdletStrings.WebResponseVerboseMsg, response.Version, contentLength, contentType);

WriteVerbose(respVerboseMsg);

bool _isSuccess = response.IsSuccessStatusCode;
Expand Down Expand Up @@ -621,12 +635,19 @@ protected override void ProcessRecord()
string detailMsg = string.Empty;
try
{
string error = StreamHelper.GetResponseString(response, _cancelToken.Token);
// We can't use ReadAsStringAsync because it doesn't have per read timeouts
TimeSpan perReadTimeout = ConvertTimeoutSecondsToTimeSpan(OperationTimeoutSeconds);
string characterSet = WebResponseHelper.GetCharacterSet(response);
var responseStream = StreamHelper.GetResponseStream(response, _cancelToken.Token);
int initialCapacity = (int)Math.Min(contentLength ?? StreamHelper.DefaultReadBuffer, StreamHelper.DefaultReadBuffer);
var bufferedStream = new WebResponseContentMemoryStream(responseStream, initialCapacity, this, contentLength, perReadTimeout, _cancelToken.Token);
string error = StreamHelper.DecodeStream(bufferedStream, characterSet, out Encoding encoding, perReadTimeout, _cancelToken.Token);
detailMsg = FormatErrorMessage(error, contentType);
}
catch
catch (Exception ex)
{
// Catch all
er.ErrorDetails = new ErrorDetails(ex.ToString());
}

if (!string.IsNullOrEmpty(detailMsg))
Expand Down Expand Up @@ -666,7 +687,7 @@ protected override void ProcessRecord()

ThrowTerminatingError(er);
}
finally
finally
{
_cancelToken?.Dispose();
_cancelToken = null;
Expand Down Expand Up @@ -970,7 +991,7 @@ internal virtual void PrepareSession()
}
else
{
webProxy.UseDefaultCredentials = ProxyUseDefaultCredentials;
webProxy.UseDefaultCredentials = ProxyUseDefaultCredentials;
}

// We don't want to update the WebSession unless the proxies are different
Expand Down Expand Up @@ -1020,7 +1041,7 @@ internal virtual void PrepareSession()
WebSession.RetryIntervalInSeconds = RetryIntervalSec;
}

WebSession.TimeoutSec = TimeoutSec;
WebSession.ConnectionTimeout = ConvertTimeoutSecondsToTimeSpan(ConnectionTimeoutSeconds);
}

internal virtual HttpClient GetHttpClient(bool handleRedirect)
Expand Down Expand Up @@ -1388,6 +1409,9 @@ internal virtual void UpdateSession(HttpResponseMessage response)
#endregion Virtual Methods

#region Helper Methods

internal static TimeSpan ConvertTimeoutSecondsToTimeSpan(int timeout) => timeout > 0 ? TimeSpan.FromSeconds(timeout) : Timeout.InfiniteTimeSpan;

private Uri PrepareUri(Uri uri)
{
uri = CheckProtocol(uri);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,24 +72,36 @@ public class WebResponseObject

#endregion Properties

#region Protected Fields

/// <summary>
/// Time permitted between reads or Timeout.InfiniteTimeSpan for no timeout.
/// </summary>
protected TimeSpan perReadTimeout;

#endregion Protected Fields

#region Constructors

/// <summary>
/// Initializes a new instance of the <see cref="WebResponseObject"/> class.
/// </summary>
/// <param name="response">The Http response.</param>
/// <param name="perReadTimeout">Time permitted between reads or Timeout.InfiniteTimeSpan for no timeout.</param>
/// <param name="cancellationToken">The cancellation token.</param>
public WebResponseObject(HttpResponseMessage response, CancellationToken cancellationToken) : this(response, null, cancellationToken) { }
public WebResponseObject(HttpResponseMessage response, TimeSpan perReadTimeout, CancellationToken cancellationToken) : this(response, null, perReadTimeout, cancellationToken) { }

/// <summary>
/// Initializes a new instance of the <see cref="WebResponseObject"/> class
/// with the specified <paramref name="contentStream"/>.
/// </summary>
/// <param name="response">Http response.</param>
/// <param name="contentStream">The http content stream.</param>
/// <param name="perReadTimeout">Time permitted between reads or Timeout.InfiniteTimeSpan for no timeout.</param>
/// <param name="cancellationToken">The cancellation token.</param>
public WebResponseObject(HttpResponseMessage response, Stream? contentStream, CancellationToken cancellationToken)
public WebResponseObject(HttpResponseMessage response, Stream? contentStream, TimeSpan perReadTimeout, CancellationToken cancellationToken)
{
this.perReadTimeout = perReadTimeout;
SetResponse(response, contentStream, cancellationToken);
InitializeContent();
InitializeRawContent(response);
Expand Down Expand Up @@ -149,7 +161,7 @@ private void SetResponse(HttpResponseMessage response, Stream? contentStream, Ca
}

int initialCapacity = (int)Math.Min(contentLength, StreamHelper.DefaultReadBuffer);
RawContentStream = new WebResponseContentMemoryStream(st, initialCapacity, cmdlet: null, response.Content.Headers.ContentLength.GetValueOrDefault(), cancellationToken);
RawContentStream = new WebResponseContentMemoryStream(st, initialCapacity, cmdlet: null, response.Content.Headers.ContentLength.GetValueOrDefault(), perReadTimeout, cancellationToken);
}

// Set the position of the content stream to the beginning
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.IO;
using System.Management.Automation;
using System.Net.Http;
using System.Threading;

namespace Microsoft.PowerShell.Commands
{
Expand Down Expand Up @@ -35,7 +36,7 @@ public InvokeWebRequestCommand() : base()
internal override void ProcessResponse(HttpResponseMessage response)
{
ArgumentNullException.ThrowIfNull(response);

TimeSpan perReadTimeout = ConvertTimeoutSecondsToTimeSpan(OperationTimeoutSeconds);
Stream responseStream = StreamHelper.GetResponseStream(response, _cancelToken.Token);
if (ShouldWriteToPipeline)
{
Expand All @@ -45,8 +46,9 @@ internal override void ProcessResponse(HttpResponseMessage response)
StreamHelper.ChunkSize,
this,
response.Content.Headers.ContentLength.GetValueOrDefault(),
perReadTimeout,
_cancelToken.Token);
WebResponseObject ro = WebResponseHelper.IsText(response) ? new BasicHtmlWebResponseObject(response, responseStream, _cancelToken.Token) : new WebResponseObject(response, responseStream, _cancelToken.Token);
WebResponseObject ro = WebResponseHelper.IsText(response) ? new BasicHtmlWebResponseObject(response, responseStream, perReadTimeout, _cancelToken.Token) : new WebResponseObject(response, responseStream, perReadTimeout, _cancelToken.Token);
ro.RelationLink = _relationLink;
WriteObject(ro);

Expand All @@ -63,7 +65,7 @@ internal override void ProcessResponse(HttpResponseMessage response)

WriteVerbose(string.Create(System.Globalization.CultureInfo.InvariantCulture, $"File Name: {Path.GetFileName(_qualifiedOutFile)}"));

StreamHelper.SaveStreamToFile(responseStream, outFilePath, this, response.Content.Headers.ContentLength.GetValueOrDefault(), _cancelToken.Token);
StreamHelper.SaveStreamToFile(responseStream, outFilePath, this, response.Content.Headers.ContentLength.GetValueOrDefault(), perReadTimeout, _cancelToken.Token);
}
}

Expand Down
Loading