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
Enable HTTP persistence when using Session with Invoke-WebRequest and Invoke-RestMethod #19173
Enable HTTP persistence when using Session with Invoke-WebRequest and Invoke-RestMethod #19173
Conversation
@microsoft-github-policy-service agree |
src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/WebRequestSession.cs
Outdated
Show resolved
Hide resolved
Maybe you could do something like this for WebRequestSession.cs (it's just an example I think it can be done better) internal int MaxAutomaticRedirections
{
get => _maxAutomaticRedirections;
set => _maxAutomaticRedirections = SetVariable(typeof(int), _maxAutomaticRedirections, value);
}
internal dynamic SetVariable(Type type, object oldValue, object newValue)
{
dynamic convertedOldValue = Convert.ChangeType(oldValue, type);
dynamic convertedNewValue = Convert.ChangeType(newValue, type);
if (convertedOldValue != convertedNewValue)
{
_changed = true;
return convertedNewValue;
}
else
{
return convertedOldValue;
}
} |
...t.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs
Outdated
Show resolved
Hide resolved
// build the cookie jar | ||
Cookies = new CookieContainer(); | ||
// Build the cookie jar | ||
_cookies = new CookieContainer(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I understand the code correctly you can revert _cookies -> Cookies (and the same for all the others in WebRequestSession())
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could, but it would go through a longer code path checking for change when we already know it's changed. Is that the preferred option?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think @iSazonov will answer to this question
...t.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs
Outdated
Show resolved
Hide resolved
...t.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/WebProxy.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/WebProxy.cs
Show resolved
Hide resolved
src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/WebRequestSession.cs
Outdated
Show resolved
Hide resolved
I used a couple of generics to do something similar. I'm not a fan of using dynamic since it turns off lots of type checking. private void SetClassVar<T>(ref T oldValue, T newValue) where T : class
{
if (oldValue == newValue)
{
return;
}
_changed = true;
oldValue = newValue;
}
private void SetStructVar<T>(ref T oldValue, T newValue) where T : struct
{
if (oldValue.Equals(newValue))
{
return;
}
_changed = true;
oldValue = newValue;
} Let me know if there's a better way. I wasn't able to use a single generic for both structs and classes but I'm not an expert in this area and someone else may have a better suggestion. |
@stevenebutler you could look into adding some tests to WebCmdlets.Tests.ps1 |
...t.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs
Outdated
Show resolved
Hide resolved
...t.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/WebRequestSession.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/WebRequestSession.cs
Outdated
Show resolved
Hide resolved
You would either have to accept that persistence would not work if the user
supplied any client handler impacting parameters to iwr or you would have
to check every assignment to the handler for logical equivalence before
attempting to set it with this approach.
You could argue that to get persistence they should clean up the script
parameters. I was going for maximum performance with least script change
effort and maximum compatibility with this PR.
|
src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/WebRequestSession.cs
Outdated
Show resolved
Hide resolved
...t.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs
Outdated
Show resolved
Hide resolved
...t.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs
Outdated
Show resolved
Hide resolved
...t.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs
Show resolved
Hide resolved
...t.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/WebProxy.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/WebRequestSession.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/WebRequestSession.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/WebRequestSession.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/WebRequestSession.cs
Outdated
Show resolved
Hide resolved
* Move GetHttpClient body to WebRequestSession * Make internal properties setter only * Implement IDisposable for WebRequestSession * Add tests for persistence * Fix GetHashCode() / Equals() on WebProxy
$caught = $true | ||
} | ||
$Warnings | Should -be $connWarning | ||
$caught | should -BeTrue |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$caught | should -BeTrue | |
$caught | Should -BeTrue |
} catch { | ||
$caught = $true | ||
} | ||
$Warnings | Should -be $connWarning |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$Warnings | Should -be $connWarning | |
$Warnings | Should -Be $connWarning |
|
||
It 'Connection persistence maintained' { | ||
$Uri = Get-WebListenerUrl | ||
$null = Invoke-WebRequest -Uri $uri -SessionVariable Session -wv warnings |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$null = Invoke-WebRequest -Uri $uri -SessionVariable Session -wv warnings | |
$null = Invoke-WebRequest -Uri $uri -SessionVariable Session -WarningVariable Warnings |
Could you please, always use variables with the same capitalization in the tests
} | ||
|
||
It 'Connection persistence outperforms non-persistence' { | ||
$Uri = Get-WebListenerUrl -Https |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$Uri = Get-WebListenerUrl -Https | |
$uri = Get-WebListenerUrl -Https |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have addressed the naming issues in the latest commit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can remove the W.I.P label
...t.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs
Outdated
Show resolved
Hide resolved
As per review request by @iSazonov
...t.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs
Show resolved
Hide resolved
...t.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs
Outdated
Show resolved
Hide resolved
...t.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs
Show resolved
Hide resolved
{ | ||
// 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 comment
The reason will be displayed to describe this comment to others. Learn more.
The comment is not correct since the code is in else
branch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The 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 comment
The reason will be displayed to describe this comment to others. Learn more.
@CarloToso Could you please look this?
...t.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs
Show resolved
Hide resolved
test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1
Show resolved
Hide resolved
test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1
Outdated
Show resolved
Hide resolved
test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1
Outdated
Show resolved
Hide resolved
$null = Invoke-WebRequest $uri -WebSession $Session -Proxy 'http://localhost:8080' -WarningVariable warnings | ||
$warnings | Should -Be $connWarning | ||
} | ||
} | ||
} | ||
|
||
Describe "Invoke-RestMethod tests" -Tags "Feature", "RequireAdminOnWindows" { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have to duplicate the tests for Invoke-RestMethod cmdlet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's get it right for IWR then I'll look at duplicating.
test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1
Outdated
Show resolved
Hide resolved
Co-authored-by: Ilya <darpa@yandex.ru>
Comment improvements Changed reconnection warning to verbose message Moved verbose message text to resource Updated tests
// This indicates GetResponse will handle redirects. | ||
SetStructVar(ref _allowAutoRedirect, !(handleRedirect || MaximumRedirection == 0)); | ||
// Do not auto redirect if the the caller does not want it, or maximum redirections is 0 | ||
SetStructVar(ref _allowAutoRedirect, !(doNotRedirect || MaximumRedirection == 0)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the change from handleRedirect to doNotRedirect or doNotHandleRedirect could be confusing. Maybe you can find a new name that always makes sense. Maybe cmdletHandleRedirect
or something better
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is handleRedirect from the perspective of cmdlet, but do not from the perspective of the websession's httpclient. I'm not sure what else to call it. Have made the change you suggested to doNotHandleRedirect in the WebSession.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think doNotHandleRedirect
is better but it's still not the best (sorry I deleted the suggestion, I was thinking of a more fitting name), let's keep thinking about it and use doNotHandleRedirect
in the meantime
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about supressHttpClientRedirects
?
Also, could you take a look at @iSazonov 's comment regarding the comment around UseDefaultCredentials. It looks wrong based on the code, but I'm not sure if the code or comment is ultimately correct.
// 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 comment
The reason will be displayed to describe this comment to others. Learn more.
I gave it a quick look and it seems the code could be wrong, not only there but every time we use UseDefaultCredentials
, when I have some time I will open a tracking issue to investigate.
supressHttpClientRedirects
might work, what do you think @iSazonov?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe more short disableRedirect
.
This PR has Quantification details
Why proper sizing of changes matters
Optimal pull request sizes drive a better predictable PR flow as they strike a
What can I do to optimize my changes
How to interpret the change counts in git diff output
Was this comment helpful? 👍 :ok_hand: :thumbsdown: (Email) |
@stevenebutler The PR contains too many commits and comments. I suggest to close the PR and open new one. Also maybe squash commits - it seems the history is not so useful. |
I am closing this PR in favour of #19249 |
PR Summary
This PR addresses 12764 by using persistent HTTP connections when Invoke-WebRequest uses the -Session variable. It does this by
PR Context
This PR makes a considerable difference to performance when using Invoke-WebRequest or Invoke-RestMethod with sessions when multiple requests are made to the same host. This is particularly evident for HTTPS requests, as each HTTPS connection can take hundreds of milliseconds to establish.
This PR as written passes existing tests in
WebCmdlets.Tests.ps1
and additional WebSession tests that verify the HttpClient is rebuilt when appropriate. This PR is submitted as WIP for feedback following advice on #12764The PR is attempting to provide as much backwards compatibility as possible at the expense of the complexity of tracking how the HttpClient used in the WebSession was constructed and recreating the HttpClient in the case it would have been constructed differently between invocations. Some feedback on whether this is a reasonable approach would help shape the PR.
PR Checklist
.h
,.cpp
,.cs
,.ps1
and.psm1
files have the correct copyright headerWIP:
or[ WIP ]
to the beginning of the title (theWIP
bot will keep its status check atPending
while the prefix is present) and remove the prefix when the PR is ready.(which runs in a different PS Host).