Support Unix-Socket in WebCmdlets (#19343)

This commit is contained in:
CarloToso 2023-07-25 01:44:32 +02:00 committed by GitHub
parent 8a89fad7df
commit e8ce6a5b1f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 257 additions and 16 deletions

View File

@ -1050,6 +1050,7 @@ ubuntu22.04
uint
un-versioned
unicode
UnixSocket
unregister-event
unregister-packagesource
unregister-psrepository

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View 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
```

View 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 = @()
}

View 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()
}
}

View 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();
}
}
}

View 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>