-
Notifications
You must be signed in to change notification settings - Fork 7.8k
Enable HTTP persistence when using Session with Invoke-WebRequest and Invoke-RestMethod #19173
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
Changes from 6 commits
a7f576e
c5addae
ac534ef
c722d2c
08e96a1
9dda627
7797a91
e104a79
882be3c
f9e4e65
5f1c8d8
bdc1a2d
49f2d8f
6b9578c
f448308
1c122b8
646aa79
1c7c0fd
b69fe72
18eb46e
6bb5283
7ae5471
b7a5aff
2aee3d8
d747ea2
237aaf7
14487d4
9f73991
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -437,6 +437,11 @@ public virtual string CustomMethod | |
/// </summary> | ||
private string _originalFilePath; | ||
|
||
/// <summary> | ||
/// True if the web session was created specifically by this request | ||
/// </summary> | ||
private bool _noWebSession; | ||
|
||
#endregion Input | ||
|
||
#region Output | ||
|
@@ -578,15 +583,15 @@ protected override void ProcessRecord() | |
// Disable writing to the OutFile. | ||
OutFile = null; | ||
} | ||
|
||
// Detect insecure redirection | ||
if (!AllowInsecureRedirect && response.RequestMessage.RequestUri.Scheme == "https" && response.Headers.Location?.Scheme == "http") | ||
{ | ||
ErrorRecord er = new(new InvalidOperationException(), "InsecureRedirection", ErrorCategory.InvalidOperation, request); | ||
er.ErrorDetails = new ErrorDetails(WebCmdletStrings.InsecureRedirection); | ||
ThrowTerminatingError(er); | ||
ErrorRecord er = new(new InvalidOperationException(), "InsecureRedirection", ErrorCategory.InvalidOperation, request); | ||
er.ErrorDetails = new ErrorDetails(WebCmdletStrings.InsecureRedirection); | ||
ThrowTerminatingError(er); | ||
} | ||
|
||
if (ShouldCheckHttpStatus && !_isSuccess) | ||
{ | ||
string message = string.Format( | ||
|
@@ -837,6 +842,7 @@ internal virtual void ValidateParameters() | |
internal virtual void PrepareSession() | ||
{ | ||
// Make sure we have a valid WebRequestSession object to work with | ||
_noWebSession = WebSession is null; | ||
stevenebutler marked this conversation as resolved.
Show resolved
Hide resolved
|
||
WebSession ??= new WebRequestSession(); | ||
|
||
if (SessionVariable is not null) | ||
|
@@ -894,30 +900,53 @@ internal virtual void PrepareSession() | |
// Store the UserAgent string | ||
WebSession.UserAgent = UserAgent; | ||
} | ||
|
||
if (Proxy is not null) | ||
// Proxy and NoProxy parameters are mutually exclusive | ||
// If NoProxy is provided, WebSession will turn off the proxy | ||
// and if Proxy is provided NoProxy will be turned off | ||
if (NoProxy.IsPresent) | ||
stevenebutler marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
WebProxy webProxy = new(Proxy); | ||
webProxy.BypassProxyOnLocal = false; | ||
if (ProxyCredential is not null) | ||
{ | ||
webProxy.Credentials = ProxyCredential.GetNetworkCredential(); | ||
} | ||
else if (ProxyUseDefaultCredentials) | ||
WebSession.NoProxy = true; | ||
} | ||
else | ||
{ | ||
if (Proxy is not null) | ||
{ | ||
// If both ProxyCredential and ProxyUseDefaultCredentials are passed, | ||
// UseDefaultCredentials will overwrite the supplied credentials. | ||
webProxy.UseDefaultCredentials = true; | ||
WebProxy webProxy = new(Proxy); | ||
webProxy.BypassProxyOnLocal = false; | ||
stevenebutler marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (ProxyCredential is not null) | ||
{ | ||
webProxy.Credentials = ProxyCredential.GetNetworkCredential(); | ||
} | ||
else if (ProxyUseDefaultCredentials) | ||
{ | ||
// If both ProxyCredential and ProxyUseDefaultCredentials are passed, | ||
// UseDefaultCredentials will overwrite the supplied credentials. | ||
webProxy.UseDefaultCredentials = true; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The comment is not correct since the code is in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, you are right. However this is what was there before, which makes me wonder if it's a bug, or just a bad comment? Should we fix the comment or the code? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @CarloToso Could you please look this? |
||
} | ||
// We don't want to update the WebSession unless the proxies are different | ||
stevenebutler marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// as that will require us to create a new HttpClientHandler and lose connection | ||
// persistence | ||
if (!webProxy.Equals(WebSession.Proxy)) | ||
{ | ||
WebSession.Proxy = webProxy; | ||
} | ||
} | ||
|
||
WebSession.Proxy = webProxy; | ||
} | ||
if (MyInvocation.BoundParameters.ContainsKey("SslProtocol")) | ||
stevenebutler marked this conversation as resolved.
Show resolved
Hide resolved
stevenebutler marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
// SslProtocol parameter is a struct and we only want to switch back to the default | ||
// if it is explicitly provided on the command line. Otherwise we keep the value in | ||
// the WebSession from any previous invocation. | ||
WebSession.SslProtocol = SslProtocol; | ||
stevenebutler marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
if (MaximumRedirection > -1) | ||
{ | ||
WebSession.MaximumRedirection = MaximumRedirection; | ||
} | ||
|
||
WebSession.SkipCertificateCheck = SkipCertificateCheck.IsPresent; | ||
|
||
// Store the other supplied headers | ||
if (Headers is not null) | ||
{ | ||
|
@@ -943,57 +972,75 @@ internal virtual void PrepareSession() | |
WebSession.RetryIntervalInSeconds = RetryIntervalSec; | ||
} | ||
} | ||
|
||
internal virtual HttpClient GetHttpClient(bool handleRedirect) | ||
{ | ||
HttpClientHandler handler = new(); | ||
handler.CookieContainer = WebSession.Cookies; | ||
handler.AutomaticDecompression = DecompressionMethods.All; | ||
var handler = WebSession.Handler; | ||
stevenebutler marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
stevenebutler marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// Set the credentials used by this request | ||
if (WebSession.UseDefaultCredentials) | ||
// This indicates GetResponse will handle redirects. | ||
if (handleRedirect || WebSession.MaximumRedirection == 0) | ||
{ | ||
// The UseDefaultCredentials flag overrides other supplied credentials | ||
handler.UseDefaultCredentials = true; | ||
WebSession.AllowAutoRedirect = false; | ||
} | ||
else if (WebSession.Credentials is not null) | ||
else if (WebSession.MaximumRedirection > 0) | ||
{ | ||
handler.Credentials = WebSession.Credentials; | ||
WebSession.MaxAutomaticRedirections = WebSession.MaximumRedirection; | ||
} | ||
|
||
if (NoProxy) | ||
if (handler is not null && WebSession.Changed) | ||
{ | ||
handler.UseProxy = false; | ||
// None of the handler properties can be set after the handler has been used. | ||
// Any options that are different to those used to construct the handler | ||
// will cause the handler to be reconstructed. | ||
WriteWarning("WebSession properties changed: new Http connection will be required"); | ||
stevenebutler marked this conversation as resolved.
Show resolved
Hide resolved
stevenebutler marked this conversation as resolved.
Show resolved
Hide resolved
|
||
handler.Dispose(); | ||
handler = null; | ||
} | ||
else if (WebSession.Proxy is not null) | ||
if (handler is null) | ||
{ | ||
handler.Proxy = WebSession.Proxy; | ||
} | ||
WebSession.Handler = handler = new(); | ||
|
||
if (WebSession.Certificates is not null) | ||
{ | ||
handler.ClientCertificates.AddRange(WebSession.Certificates); | ||
} | ||
handler.CookieContainer = WebSession.Cookies; | ||
handler.AutomaticDecompression = DecompressionMethods.All; | ||
|
||
if (SkipCertificateCheck) | ||
{ | ||
handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator; | ||
handler.ClientCertificateOptions = ClientCertificateOption.Manual; | ||
} | ||
// Set the credentials used by this request | ||
if (WebSession.UseDefaultCredentials) | ||
{ | ||
// The UseDefaultCredentials flag overrides other supplied credentials | ||
handler.UseDefaultCredentials = true; | ||
} | ||
else if (WebSession.Credentials is not null) | ||
{ | ||
handler.Credentials = WebSession.Credentials; | ||
} | ||
|
||
// This indicates GetResponse will handle redirects. | ||
if (handleRedirect || WebSession.MaximumRedirection == 0) | ||
{ | ||
handler.AllowAutoRedirect = false; | ||
} | ||
else if (WebSession.MaximumRedirection > 0) | ||
{ | ||
handler.MaxAutomaticRedirections = WebSession.MaximumRedirection; | ||
} | ||
if (WebSession.NoProxy) | ||
{ | ||
handler.UseProxy = false; | ||
} | ||
else if (WebSession.Proxy is not null) | ||
{ | ||
handler.Proxy = WebSession.Proxy; | ||
} | ||
|
||
handler.SslProtocols = (SslProtocols)SslProtocol; | ||
if (WebSession.Certificates is not null) | ||
{ | ||
handler.ClientCertificates.AddRange(WebSession.Certificates); | ||
} | ||
|
||
HttpClient httpClient = new(handler); | ||
if (WebSession.SkipCertificateCheck) | ||
{ | ||
handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator; | ||
handler.ClientCertificateOptions = ClientCertificateOption.Manual; | ||
} | ||
handler.AllowAutoRedirect = WebSession.AllowAutoRedirect; | ||
if (WebSession.AllowAutoRedirect && WebSession.MaximumRedirection > 0) | ||
{ | ||
handler.MaxAutomaticRedirections = WebSession.MaxAutomaticRedirections; | ||
} | ||
handler.SslProtocols = (SslProtocols)WebSession.SslProtocol; | ||
} | ||
// Only dispose the httpClientHandler if we are not trying to use a persistent one | ||
HttpClient httpClient = new(handler, _noWebSession && string.IsNullOrEmpty(SessionVariable)); | ||
stevenebutler marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// Check timeout setting (in seconds instead of milliseconds as in HttpWebRequest) | ||
httpClient.Timeout = TimeoutSec is 0 ? TimeSpan.FromMilliseconds(Timeout.Infinite) : new TimeSpan(0, 0, TimeoutSec); | ||
|
@@ -1287,7 +1334,7 @@ internal virtual HttpResponseMessage GetResponse(HttpClient client, HttpRequestM | |
requestWithoutRange.Version, | ||
requestWithoutRange.Method, | ||
requestContentLength); | ||
|
||
WriteVerbose(reqVerboseMsg); | ||
|
||
response.Dispose(); | ||
|
@@ -1304,9 +1351,9 @@ internal virtual HttpResponseMessage GetResponse(HttpClient client, HttpRequestM | |
|
||
// If the status code is 429 get the retry interval from the Headers. | ||
// Ignore broken header and its value. | ||
if (response.StatusCode is HttpStatusCode.Conflict && response.Headers.TryGetValues(HttpKnownHeaderNames.RetryAfter, out IEnumerable<string> retryAfter)) | ||
if (response.StatusCode is HttpStatusCode.Conflict && response.Headers.TryGetValues(HttpKnownHeaderNames.RetryAfter, out IEnumerable<string> retryAfter)) | ||
{ | ||
try | ||
try | ||
{ | ||
IEnumerator<string> enumerator = retryAfter.GetEnumerator(); | ||
if (enumerator.MoveNext()) | ||
|
@@ -1319,7 +1366,7 @@ internal virtual HttpResponseMessage GetResponse(HttpClient client, HttpRequestM | |
// Ignore broken header. | ||
} | ||
} | ||
|
||
string retryMessage = string.Format( | ||
CultureInfo.CurrentCulture, | ||
WebCmdletStrings.RetryVerboseMsg, | ||
|
@@ -1389,7 +1436,7 @@ private static Uri CheckProtocol(Uri uri) | |
} | ||
|
||
private string QualifyFilePath(string path) => PathUtils.ResolveFilePath(filePath: path, command: this, isLiteralPath: true); | ||
|
||
private static string FormatDictionary(IDictionary content) | ||
{ | ||
ArgumentNullException.ThrowIfNull(content); | ||
|
@@ -1486,7 +1533,7 @@ internal void SetRequestContent(HttpRequestMessage request, string content) | |
{ | ||
ArgumentNullException.ThrowIfNull(request); | ||
ArgumentNullException.ThrowIfNull(content); | ||
|
||
Encoding encoding = null; | ||
if (ContentType is not null) | ||
{ | ||
|
@@ -1566,7 +1613,7 @@ internal void SetRequestContent(HttpRequestMessage request, MultipartFormDataCon | |
{ | ||
ArgumentNullException.ThrowIfNull(request); | ||
ArgumentNullException.ThrowIfNull(multipartContent); | ||
|
||
// Content headers will be set by MultipartFormDataContent which will throw unless we clear them first | ||
WebSession.ContentHeaders.Clear(); | ||
|
||
|
@@ -1681,7 +1728,6 @@ private void AddMultipartContent(object fieldName, object fieldValue, MultipartF | |
private static StringContent GetMultipartStringContent(object fieldName, object fieldValue) | ||
{ | ||
ContentDispositionHeaderValue contentDisposition = new("form-data"); | ||
|
||
// .NET does not enclose field names in quotes, however, modern browsers and curl do. | ||
contentDisposition.Name = "\"" + LanguagePrimitives.ConvertTo<string>(fieldName) + "\""; | ||
|
||
|
@@ -1736,7 +1782,8 @@ private static string FormatErrorMessage(string error, string contentType) | |
XmlDocument doc = new(); | ||
doc.LoadXml(error); | ||
|
||
XmlWriterSettings settings = new XmlWriterSettings { | ||
XmlWriterSettings settings = new XmlWriterSettings | ||
{ | ||
Indent = true, | ||
NewLineOnAttributes = true, | ||
OmitXmlDeclaration = true | ||
|
@@ -1767,7 +1814,6 @@ private static string FormatErrorMessage(string error, string contentType) | |
{ | ||
// Ignore errors | ||
} | ||
|
||
if (string.IsNullOrEmpty(formattedError)) | ||
{ | ||
// Remove HTML tags making it easier to read | ||
|
Uh oh!
There was an error while loading. Please reload this page.