mirror of
https://github.com/PowerShell/PowerShell.git
synced 2024-11-23 01:34:19 +08:00
Support Unix-Socket in WebCmdlets (#19343)
This commit is contained in:
parent
8a89fad7df
commit
e8ce6a5b1f
@ -1050,6 +1050,7 @@ ubuntu22.04
|
||||
uint
|
||||
un-versioned
|
||||
unicode
|
||||
UnixSocket
|
||||
unregister-event
|
||||
unregister-packagesource
|
||||
unregister-psrepository
|
||||
|
@ -1161,8 +1161,9 @@ function Publish-PSTestTools {
|
||||
$tools = @(
|
||||
@{ Path="${PSScriptRoot}/test/tools/TestAlc"; Output="library" }
|
||||
@{ Path="${PSScriptRoot}/test/tools/TestExe"; Output="exe" }
|
||||
@{ Path="${PSScriptRoot}/test/tools/WebListener"; Output="exe" }
|
||||
@{ Path="${PSScriptRoot}/test/tools/TestService"; Output="exe" }
|
||||
@{ Path="${PSScriptRoot}/test/tools/UnixSocket"; Output="exe" }
|
||||
@{ Path="${PSScriptRoot}/test/tools/WebListener"; Output="exe" }
|
||||
)
|
||||
|
||||
$Options = Get-PSOptions -DefaultToNew
|
||||
|
@ -11,6 +11,7 @@ using System.Management.Automation;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Sockets;
|
||||
using System.Security;
|
||||
using System.Security.Authentication;
|
||||
using System.Security.Cryptography;
|
||||
@ -366,14 +367,9 @@ namespace Microsoft.PowerShell.Commands
|
||||
[Parameter(Mandatory = true, ParameterSetName = "CustomMethodNoProxy")]
|
||||
[Alias("CM")]
|
||||
[ValidateNotNullOrEmpty]
|
||||
public virtual string CustomMethod
|
||||
{
|
||||
get => _custommethod;
|
||||
public virtual string CustomMethod { get => _customMethod; set => _customMethod = value.ToUpperInvariant(); }
|
||||
|
||||
set => _custommethod = value.ToUpperInvariant();
|
||||
}
|
||||
|
||||
private string _custommethod;
|
||||
private string _customMethod;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the PreserveHttpMethodOnRedirect property.
|
||||
@ -381,6 +377,13 @@ namespace Microsoft.PowerShell.Commands
|
||||
[Parameter]
|
||||
public virtual SwitchParameter PreserveHttpMethodOnRedirect { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the UnixSocket property.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
[ValidateNotNullOrEmpty]
|
||||
public virtual UnixDomainSocketEndPoint UnixSocket { get; set; }
|
||||
|
||||
#endregion Method
|
||||
|
||||
#region NoProxy
|
||||
@ -1019,6 +1022,8 @@ namespace Microsoft.PowerShell.Commands
|
||||
WebSession.MaximumRedirection = MaximumRedirection;
|
||||
}
|
||||
|
||||
WebSession.UnixSocket = UnixSocket;
|
||||
|
||||
WebSession.SkipCertificateCheck = SkipCertificateCheck.IsPresent;
|
||||
|
||||
// Store the other supplied headers
|
||||
|
@ -7,6 +7,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Sockets;
|
||||
using System.Security.Authentication;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading;
|
||||
@ -33,6 +34,7 @@ namespace Microsoft.PowerShell.Commands
|
||||
private bool _noProxy;
|
||||
private bool _disposed;
|
||||
private TimeSpan _connectionTimeout;
|
||||
private UnixDomainSocketEndPoint? _unixSocket;
|
||||
|
||||
/// <summary>
|
||||
/// Contains true if an existing HttpClient had to be disposed and recreated since the WebSession was last used.
|
||||
@ -144,6 +146,8 @@ namespace Microsoft.PowerShell.Commands
|
||||
|
||||
internal TimeSpan ConnectionTimeout { set => SetStructVar(ref _connectionTimeout, value); }
|
||||
|
||||
internal UnixDomainSocketEndPoint UnixSocket { set => SetClassVar(ref _unixSocket, value); }
|
||||
|
||||
internal bool NoProxy
|
||||
{
|
||||
set
|
||||
@ -195,7 +199,18 @@ namespace Microsoft.PowerShell.Commands
|
||||
|
||||
private HttpClient CreateHttpClient()
|
||||
{
|
||||
HttpClientHandler handler = new();
|
||||
SocketsHttpHandler handler = new();
|
||||
|
||||
if (_unixSocket is not null)
|
||||
{
|
||||
handler.ConnectCallback = async (context, token) =>
|
||||
{
|
||||
Socket socket = new(AddressFamily.Unix, SocketType.Stream, ProtocolType.IP);
|
||||
await socket.ConnectAsync(_unixSocket).ConfigureAwait(false);
|
||||
|
||||
return new NetworkStream(socket, ownsSocket: false);
|
||||
};
|
||||
}
|
||||
|
||||
handler.CookieContainer = Cookies;
|
||||
handler.AutomaticDecompression = DecompressionMethods.All;
|
||||
@ -204,9 +219,9 @@ namespace Microsoft.PowerShell.Commands
|
||||
{
|
||||
handler.Credentials = Credentials;
|
||||
}
|
||||
else
|
||||
else if (UseDefaultCredentials)
|
||||
{
|
||||
handler.UseDefaultCredentials = UseDefaultCredentials;
|
||||
handler.Credentials = CredentialCache.DefaultCredentials;
|
||||
}
|
||||
|
||||
if (_noProxy)
|
||||
@ -220,13 +235,12 @@ namespace Microsoft.PowerShell.Commands
|
||||
|
||||
if (Certificates is not null)
|
||||
{
|
||||
handler.ClientCertificates.AddRange(Certificates);
|
||||
handler.SslOptions.ClientCertificates = new X509CertificateCollection(Certificates);
|
||||
}
|
||||
|
||||
if (_skipCertificateCheck)
|
||||
{
|
||||
handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
|
||||
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
|
||||
handler.SslOptions.RemoteCertificateValidationCallback = delegate { return true; };
|
||||
}
|
||||
|
||||
handler.AllowAutoRedirect = _allowAutoRedirect;
|
||||
@ -235,9 +249,9 @@ namespace Microsoft.PowerShell.Commands
|
||||
handler.MaxAutomaticRedirections = MaximumRedirection;
|
||||
}
|
||||
|
||||
handler.SslProtocols = (SslProtocols)_sslProtocol;
|
||||
handler.SslOptions.EnabledSslProtocols = (SslProtocols)_sslProtocol;
|
||||
|
||||
// Check timeout setting (in seconds instead of milliseconds as in HttpWebRequest)
|
||||
// Check timeout setting (in seconds)
|
||||
return new HttpClient(handler)
|
||||
{
|
||||
Timeout = _connectionTimeout
|
||||
|
@ -4465,6 +4465,26 @@ Describe 'Invoke-WebRequest and Invoke-RestMethod support Cancellation through C
|
||||
}
|
||||
}
|
||||
|
||||
Describe "Web cmdlets Unix Sockets tests" -Tags "CI", "RequireAdminOnWindows" {
|
||||
BeforeAll {
|
||||
$unixSocket = Get-UnixSocketName
|
||||
$WebListener = Start-UnixSocket $unixSocket
|
||||
}
|
||||
|
||||
It "Execute Invoke-WebRequest with -UnixSocket" {
|
||||
$uri = Get-UnixSocketUri
|
||||
$result = Invoke-WebRequest $uri -UnixSocket $unixSocket
|
||||
$result.StatusCode | Should -Be "200"
|
||||
$result.Content | Should -Be "Hello World Unix Socket."
|
||||
}
|
||||
|
||||
It "Execute Invoke-RestMethod with -UnixSocket" {
|
||||
$uri = Get-UnixSocketUri
|
||||
$result = Invoke-RestMethod $uri -UnixSocket $unixSocket
|
||||
$result | Should -Be "Hello World Unix Socket."
|
||||
}
|
||||
}
|
||||
|
||||
Describe 'Invoke-WebRequest and Invoke-RestMethod support OperationTimeoutSeconds' -Tags "CI", "RequireAdminOnWindows" {
|
||||
BeforeAll {
|
||||
$oldProgress = $ProgressPreference
|
||||
|
17
test/tools/Modules/UnixSocket/README.md
Normal file
17
test/tools/Modules/UnixSocket/README.md
Normal file
@ -0,0 +1,17 @@
|
||||
# UnixSocket Module
|
||||
|
||||
A PowerShell module for managing the UnixSocket App.
|
||||
|
||||
## Running UnixSocket
|
||||
|
||||
```powershell
|
||||
Import-Module .\build.psm1
|
||||
Publish-PSTestTools
|
||||
$Listener = Start-UnixSocket
|
||||
```
|
||||
|
||||
## Stopping UnixSocket
|
||||
|
||||
```powershell
|
||||
Stop-UnixSocket
|
||||
```
|
17
test/tools/Modules/UnixSocket/UnixSocket.psd1
Normal file
17
test/tools/Modules/UnixSocket/UnixSocket.psd1
Normal file
@ -0,0 +1,17 @@
|
||||
@{
|
||||
ModuleVersion = '1.0.0'
|
||||
GUID = '86471f04-5b94-4136-a299-caf98464a06b'
|
||||
Author = 'PowerShell'
|
||||
Description = 'An UnixSocket Server for testing purposes'
|
||||
RootModule = 'UnixSocket.psm1'
|
||||
RequiredModules = @()
|
||||
FunctionsToExport = @(
|
||||
'Get-UnixSocket'
|
||||
'Get-UnixSocketName'
|
||||
'Get-UnixSocketUri'
|
||||
'Start-UnixSocket'
|
||||
'Stop-UnixSocket'
|
||||
)
|
||||
AliasesToExport = @()
|
||||
CmdletsToExport = @()
|
||||
}
|
125
test/tools/Modules/UnixSocket/UnixSocket.psm1
Normal file
125
test/tools/Modules/UnixSocket/UnixSocket.psm1
Normal file
@ -0,0 +1,125 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
Class UnixSocket
|
||||
{
|
||||
[System.Management.Automation.Job]$Job
|
||||
|
||||
UnixSocket () { }
|
||||
|
||||
[String] GetStatus()
|
||||
{
|
||||
return $this.Job.JobStateInfo.State
|
||||
}
|
||||
}
|
||||
|
||||
[UnixSocket]$UnixSocket
|
||||
|
||||
function Get-UnixSocket
|
||||
{
|
||||
[CmdletBinding(ConfirmImpact = 'Low')]
|
||||
[OutputType([UnixSocket])]
|
||||
param()
|
||||
|
||||
process
|
||||
{
|
||||
return [UnixSocket]$Script:UnixSocket
|
||||
}
|
||||
}
|
||||
|
||||
function Start-UnixSocket
|
||||
{
|
||||
[CmdletBinding(ConfirmImpact = 'Low')]
|
||||
[OutputType([UnixSocket])]
|
||||
param([string] $socketPath)
|
||||
|
||||
process
|
||||
{
|
||||
$runningListener = Get-UnixSocket
|
||||
if ($null -ne $runningListener -and $runningListener.GetStatus() -eq 'Running')
|
||||
{
|
||||
return $runningListener
|
||||
}
|
||||
|
||||
$initTimeoutSeconds = 25
|
||||
$appExe = (Get-Command UnixSocket).Path
|
||||
$initCompleteMessage = 'Now listening on'
|
||||
$sleepMilliseconds = 100
|
||||
|
||||
$Job = Start-Job {
|
||||
$path = Split-Path -Parent (Get-Command UnixSocket).Path -Verbose
|
||||
Push-Location $path -Verbose
|
||||
'appEXE: {0}' -f $using:appExe
|
||||
$env:ASPNETCORE_ENVIRONMENT = 'Development'
|
||||
& $using:appExe $using:socketPath
|
||||
}
|
||||
|
||||
$Script:UnixSocket = [UnixSocket]@{
|
||||
Job = $Job
|
||||
}
|
||||
|
||||
# Count iterations of $sleepMilliseconds instead of using system time to work around possible CI VM sleep/delays
|
||||
$sleepCountRemaining = $initTimeoutSeconds * 1000 / $sleepMilliseconds
|
||||
do
|
||||
{
|
||||
Start-Sleep -Milliseconds $sleepMilliseconds
|
||||
$initStatus = $Job.ChildJobs[0].Output | Out-String
|
||||
$isRunning = $initStatus -match $initCompleteMessage
|
||||
$sleepCountRemaining--
|
||||
}
|
||||
while (-not $isRunning -and $sleepCountRemaining -gt 0)
|
||||
|
||||
if (-not $isRunning)
|
||||
{
|
||||
$jobErrors = $Job.ChildJobs[0].Error | Out-String
|
||||
$jobOutput = $Job.ChildJobs[0].Output | Out-String
|
||||
$jobVerbose = $Job.ChildJobs[0].Verbose | Out-String
|
||||
$Job | Stop-Job
|
||||
$Job | Remove-Job -Force
|
||||
$message = 'UnixSocket did not start before the timeout was reached.{0}Errors:{0}{1}{0}Output:{0}{2}{0}Verbose:{0}{3}' -f ([System.Environment]::NewLine), $jobErrors, $jobOutput, $jobVerbose
|
||||
throw $message
|
||||
}
|
||||
return $Script:UnixSocket
|
||||
}
|
||||
}
|
||||
|
||||
function Stop-UnixSocket
|
||||
{
|
||||
[CmdletBinding(ConfirmImpact = 'Low')]
|
||||
[OutputType([Void])]
|
||||
param()
|
||||
|
||||
process
|
||||
{
|
||||
$Script:UnixSocket.Job | Stop-Job -PassThru | Remove-Job
|
||||
$Script:UnixSocket = $null
|
||||
}
|
||||
}
|
||||
|
||||
function Get-UnixSocketName {
|
||||
[CmdletBinding()]
|
||||
[OutputType([string])]
|
||||
param ()
|
||||
|
||||
process {
|
||||
return [System.IO.Path]::Join([System.IO.Path]::GetTempPath(), [System.IO.Path]::ChangeExtension([System.IO.Path]::GetRandomFileName(), "sock"))
|
||||
}
|
||||
}
|
||||
|
||||
function Get-UnixSocketUri {
|
||||
[CmdletBinding()]
|
||||
[OutputType([Uri])]
|
||||
param ()
|
||||
|
||||
process {
|
||||
$runningListener = Get-UnixSocket
|
||||
if ($null -eq $runningListener -or $runningListener.GetStatus() -ne 'Running')
|
||||
{
|
||||
return $null
|
||||
}
|
||||
$Uri = [System.UriBuilder]::new()
|
||||
$Uri.Host = '127.0.0.0'
|
||||
|
||||
return [Uri]$Uri.ToString()
|
||||
}
|
||||
}
|
27
test/tools/UnixSocket/UnixSocket.cs
Normal file
27
test/tools/UnixSocket/UnixSocket.cs
Normal file
@ -0,0 +1,27 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using static Microsoft.AspNetCore.Hosting.WebHostBuilderKestrelExtensions;
|
||||
|
||||
namespace UnixSocket
|
||||
{
|
||||
public static class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
WebApplicationBuilder builder = WebApplication.CreateBuilder();
|
||||
builder.WebHost.ConfigureKestrel(options =>
|
||||
{
|
||||
options.ListenUnixSocket(args[0]);
|
||||
});
|
||||
|
||||
var app = builder.Build();
|
||||
app.MapGet("/", () => "Hello World Unix Socket.");
|
||||
|
||||
app.Run();
|
||||
}
|
||||
}
|
||||
}
|
14
test/tools/UnixSocket/UnixSocket.csproj
Normal file
14
test/tools/UnixSocket/UnixSocket.csproj
Normal file
@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<Import Project="..\..\Test.Common.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<Description>A very simple ASP.NET Core app to provide an UnixSocket server for testing.</Description>
|
||||
<AssemblyName>UnixSocket</AssemblyName>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TieredCompilation>true</TieredCompilation>
|
||||
<TieredCompilationQuickJit>true</TieredCompilationQuickJit>
|
||||
<RuntimeIdentifiers>win7-x86;win7-x64;osx-x64;linux-x64</RuntimeIdentifiers>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
Loading…
Reference in New Issue
Block a user