Merge release/2.1 and aspnet/WebSockets release/2.2
This commit is contained in:
commit
d991c50c82
|
|
@ -14,15 +14,22 @@ resources:
|
|||
phases:
|
||||
- template: .vsts-pipelines/templates/project-ci.yml@buildtools
|
||||
parameters:
|
||||
buildArgs: "/t:CheckUniverse"
|
||||
- phase: DataProtection
|
||||
queue: Hosted VS2017
|
||||
buildArgs: "/t:FastCheck"
|
||||
- phase: RepoBuilds
|
||||
queue:
|
||||
name: Hosted VS2017
|
||||
parallel: 2
|
||||
matrix:
|
||||
DataProtection:
|
||||
_FolderName: DataProtection
|
||||
WebSockets:
|
||||
_FolderName: WebSockets
|
||||
steps:
|
||||
- script: src/DataProtection/build.cmd -ci
|
||||
displayName: Run src/DataProtection/build.cmd
|
||||
- script: src/$(_FolderName)/build.cmd -ci
|
||||
displayName: Run src/$(_FolderName)/build.cmd
|
||||
- task: PublishTestResults@2
|
||||
displayName: Publish test results
|
||||
condition: always()
|
||||
inputs:
|
||||
testRunner: vstest
|
||||
testResultsFiles: 'src/DataProtection/artifacts/logs/**/*.trx'
|
||||
testResultsFiles: 'src/$(_FolderName)/artifacts/logs/**/*.trx'
|
||||
|
|
|
|||
|
|
@ -174,7 +174,3 @@
|
|||
path = modules/Templating
|
||||
url = https://github.com/aspnet/Templating.git
|
||||
branch = release/2.2
|
||||
[submodule "modules/WebSockets"]
|
||||
path = modules/WebSockets
|
||||
url = https://github.com/aspnet/WebSockets.git
|
||||
branch = release/2.2
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<PropertyGroup>
|
||||
<Product>Microsoft ASP.NET Core</Product>
|
||||
<RepositoryRoot>$(MSBuildThisFileDirectory)</RepositoryRoot>
|
||||
<RepositoryUrl>https://github.com/aspnet/Universe</RepositoryUrl>
|
||||
<RepositoryUrl>https://github.com/aspnet/AspNetCore</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)eng\AspNetCore.snk</AssemblyOriginatorKeyFile>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
Universe
|
||||
ASP.NET Core
|
||||
========
|
||||
|
||||
Build infrastructure used to produce the whole ASP.NET Core stack.
|
||||
|
|
@ -102,8 +102,8 @@ RedHat/Fedora (x64) | [Installer (rpm)][redhat-x64-rpm]
|
|||
## Building from source
|
||||
|
||||
```
|
||||
git clone --recursive https://github.com/aspnet/Universe.git
|
||||
cd Universe
|
||||
git clone --recursive https://github.com/aspnet/AspNetCore.git
|
||||
cd AspNetCore
|
||||
./build.cmd
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<Project>
|
||||
<Target Name="CheckForPreviousReleaseArchiveBaseline" BeforeTargets="CheckUniverse">
|
||||
<Target Name="CheckForPreviousReleaseArchiveBaseline" BeforeTargets="FastCheck">
|
||||
<MSBuild Projects="@(ArchiveProjects)"
|
||||
Targets="CheckForPreviousReleaseArchiveBaseline" />
|
||||
</Target>
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@
|
|||
|
||||
<Target Name="_UpdateRepoLockFile">
|
||||
<!-- Copy Korebuild lock file to individual repos to align version if the repo doesn't already have one -->
|
||||
<Message Text="Copying KoreBuild lockfile from Universe to repository $(BuildRepositoryRoot)"/>
|
||||
<Message Text="Copying KoreBuild lockfile from repo to submodule $(BuildRepositoryRoot)"/>
|
||||
<Move SourceFiles="$(RepoLockFile)" DestinationFiles="$(BackupRepoLockFile)" Condition="Exists($(RepoLockFile))" />
|
||||
<Move SourceFiles="$(RepoGlobalJsonFile)" DestinationFiles="$(BackupRepoGlobalJsonFile)" Condition="Exists($(RepoGlobalJsonFile))" />
|
||||
<Copy SourceFiles="$(SourceLockFile)" DestinationFiles="$(RepoLockFile)" />
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@
|
|||
<RepositoryBuildOrder Include="Routing" Order="12" />
|
||||
<RepositoryBuildOrder Include="Diagnostics" Order="12" />
|
||||
<RepositoryBuildOrder Include="Localization" Order="13" />
|
||||
<RepositoryBuildOrder Include="WebSockets" Order="13" />
|
||||
<RepositoryBuildOrder Include="WebSockets" Order="13" RootPath="$(RepositoryRoot)src\WebSockets\" />
|
||||
<RepositoryBuildOrder Include="Security" Order="13" />
|
||||
<RepositoryBuildOrder Include="MetaPackages" Order="13" />
|
||||
<RepositoryBuildOrder Include="Mvc" Order="14" />
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<id>Internal.AspNetCore.Universe.Lineup</id>
|
||||
<version>$version$</version>
|
||||
<authors>Microsoft</authors>
|
||||
<description>This package used to unify ASP.NET Core package versions across all Universe repos. Internal use only.</description>
|
||||
<description>This package used to unify ASP.NET Core package versions across all ASP.NET Core repos. Internal use only.</description>
|
||||
<packageTypes>
|
||||
<packageType name="lineup" />
|
||||
</packageTypes>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<PropertyGroup>
|
||||
<!--
|
||||
This ensures the build number is a time-based number for local builds.
|
||||
This is important for local builds of Universe which need to ensure repo-to-repo
|
||||
This is important for local builds of ASP.NET Core which need to ensure repo-to-repo
|
||||
builds are using new articacts, not ones from the global cache.
|
||||
-->
|
||||
<IncrementalVersion>true</IncrementalVersion>
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
<GeneratedBrandingPropsPath>$(IntermediateDir)branding.g.props</GeneratedBrandingPropsPath>
|
||||
|
||||
<PrepareDependsOn>SetTeamCityBuildNumberToVersion;$(PrepareDependsOn);VerifyPackageArtifactConfig;VerifyExternalDependencyConfig;PrepareOutputPaths</PrepareDependsOn>
|
||||
<CleanDependsOn>$(CleanDependsOn);CleanArtifacts;CleanUniverseArtifacts</CleanDependsOn>
|
||||
<CleanDependsOn>$(CleanDependsOn);CleanArtifacts;CleanRepoArtifacts</CleanDependsOn>
|
||||
<RestoreDependsOn>$(RestoreDependsOn);InstallDotNet</RestoreDependsOn>
|
||||
<CompileDependsOn>$(CompileDependsOn);BuildRepositories</CompileDependsOn>
|
||||
<PackageDependsOn Condition="'$(TestOnly)' != 'true'">$(PackageDependsOn);BuildMetapackages;CheckExpectedPackagesExist</PackageDependsOn>
|
||||
|
|
@ -78,7 +78,7 @@
|
|||
|
||||
<Move SourceFiles="%(SubmoduleGlobalJsonFiles.BackupPath)" DestinationFiles="%(SubmoduleGlobalJsonFiles.Identity)" Condition="Exists(%(SubmoduleGlobalJsonFiles.BackupPath))" />
|
||||
|
||||
<!-- Join required because shipping category is stored in universe (PackageArtifact), but information about package ID and version comes from repos (ArtifactInfo). -->
|
||||
<!-- Join required because shipping category is stored in artifact.props (PackageArtifact), but information about package ID and version comes from repos (ArtifactInfo). -->
|
||||
<RepoTasks.JoinItems
|
||||
Left="@(_Temp)"
|
||||
LeftMetadata="*"
|
||||
|
|
@ -179,7 +179,7 @@
|
|||
</PackNuSpec>
|
||||
</Target>
|
||||
|
||||
<Target Name="CleanUniverseArtifacts">
|
||||
<Target Name="CleanRepoArtifacts">
|
||||
<RemoveDir Directories="$(RepositoryRoot)obj" Condition="Exists('$(RepositoryRoot)obj')" />
|
||||
</Target>
|
||||
|
||||
|
|
@ -261,7 +261,7 @@
|
|||
Condition=" @(ExternalDependency->WithMetadataValue('Version', '')->Count()) != 0 " />
|
||||
</Target>
|
||||
|
||||
<Target Name="CheckUniverse"
|
||||
<Target Name="FastCheck"
|
||||
DependsOnTargets="ComputeGraph;VerifyPackageArtifactConfig;VerifyAllReposHaveNuGetPackageVerifier" />
|
||||
|
||||
<Target Name="CheckExpectedPackagesExist">
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@
|
|||
<Repository Include="SignalR" />
|
||||
<Repository Include="StaticFiles" />
|
||||
<Repository Include="Templating" PatchPolicy="AlwaysUpdateAndCascadeVersions" />
|
||||
<Repository Include="WebSockets" />
|
||||
<Repository Include="WebSockets" RootPath="$(RepositoryRoot)src\WebSockets\" />
|
||||
|
||||
<!-- Test-only repos -->
|
||||
<Repository Include="AuthSamples" PatchPolicy="AlwaysUpdateAndCascadeVersions" />
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ if ! __machine_has docker; then
|
|||
fi
|
||||
|
||||
dockerfile="$DIR/build/docker/$image.Dockerfile"
|
||||
tagname="universe-build-$image"
|
||||
tagname="aspnetcore-build-$image"
|
||||
|
||||
docker build "$(dirname "$dockerfile")" \
|
||||
--build-arg "USER=$(whoami)" \
|
||||
|
|
|
|||
|
|
@ -9,10 +9,10 @@ For full information, see the [official docs for git submodules](https://git-scm
|
|||
|
||||
## Fundamental concept
|
||||
|
||||
The parent repo (aspnet/Universe) stores two pieces of info about each submodule.
|
||||
The parent repo (aspnet/AspNetCore) stores two pieces of info about each submodule.
|
||||
|
||||
1. Where to clone the submodule from. This is stored in the .gitmodules file
|
||||
2. The commit hash of the submodule to use.
|
||||
2. The commit hash of the submodule to use.
|
||||
|
||||
This means you cannot commit a submodule's branch or a tag to the parent repo.
|
||||
Other info may appear in the .gitmodules file, but it is only used when attempting to
|
||||
|
|
@ -22,7 +22,7 @@ Other info may appear in the .gitmodules file, but it is only used when attempti
|
|||
|
||||
By default, submodules will not be present. Use `--recursive` to clone all submodules.
|
||||
|
||||
git clone https://github.com/aspnet/Universe.git --recursive
|
||||
git clone https://github.com/aspnet/AspNetCore.git --recursive
|
||||
|
||||
If you have already cloned, run this to initialize all submodules.
|
||||
|
||||
|
|
@ -53,7 +53,7 @@ Updating all submodules to newer versions can be done like this.
|
|||
Updating just one subumodule.
|
||||
|
||||
git submodule update --remote modules/EntityFrameworkCore/
|
||||
|
||||
|
||||
This uses the remote url and branch info configuration stored in .gitmodules to pull new commits.
|
||||
This does not guarantee the commit is going to be a fast-forward commit.
|
||||
|
||||
|
|
@ -72,7 +72,7 @@ that contains the new commit.
|
|||
git add modules/KestrelhttpServer/
|
||||
git commit -m "Update Kestrel to latest version"
|
||||
|
||||
## PowerShell is slow in aspnet/Universe
|
||||
## PowerShell is slow in aspnet/AspNetCore
|
||||
|
||||
Many users have post-git, and extension that shows git status on the prompt line. Because `git status` with submodules
|
||||
on Windows is very slow, it can make PowerShell unbearable to use.
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
Subproject commit a036f920b8f32446e3524d3eb5a10fbd02f2a37c
|
||||
|
|
@ -87,12 +87,12 @@ if (-not $PSCmdlet.ShouldContinue("Continue?", "This will apply tags to all subm
|
|||
}
|
||||
|
||||
|
||||
$universeTag = Get-PackageVersion $repoRoot
|
||||
New-GitTag $repoRoot $universeTag -WhatIf:$WhatIfPreference
|
||||
$repoTag = Get-PackageVersion $repoRoot
|
||||
New-GitTag $repoRoot $repoTag -WhatIf:$WhatIfPreference
|
||||
|
||||
$tags = @([pscustomobject] @{
|
||||
repo = $(git config remote.origin.url)
|
||||
tag = $universeTag
|
||||
tag = $repoTag
|
||||
commit = $(git rev-parse HEAD)
|
||||
})
|
||||
|
||||
|
|
@ -106,8 +106,8 @@ Get-Submodules $repoRoot | ForEach-Object {
|
|||
|
||||
try {
|
||||
$tag = Get-PackageVersion $_.path
|
||||
if ($tag -ne $universeTag) {
|
||||
Write-Warning "${module}: version ($tag) does not match universe ($universeTag)"
|
||||
if ($tag -ne $repoTag) {
|
||||
Write-Warning "${module}: version ($tag) does not match repo ($repoTag)"
|
||||
}
|
||||
$tags += [pscustomobject] @{
|
||||
repo = $_.remote
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Updates each repo Universe builds to new dependencies.props.
|
||||
Updates each submodule this repo builds to new dependencies.props.
|
||||
.PARAMETER Source
|
||||
The NuGet package source to find the lineup on.
|
||||
.PARAMETER LineupID
|
||||
|
|
@ -71,10 +71,10 @@ try {
|
|||
|
||||
$koreBuildLock = "korebuild-lock.txt"
|
||||
|
||||
$universeKoreBuildLock = (Join-Path $RepoRoot $koreBuildLock)
|
||||
$repoKoreBuildLock = (Join-Path $RepoRoot $koreBuildLock)
|
||||
$submoduleKoreBuildLock = (Join-Path $submodule.path $koreBuildLock)
|
||||
|
||||
Copy-Item $universeKoreBuildLock $submoduleKoreBuildLock -Force
|
||||
Copy-Item $repoKoreBuildLock $submoduleKoreBuildLock -Force
|
||||
|
||||
Write-Verbose "About to update dependencies.props for $($submodule.module)"
|
||||
& .\run.ps1 upgrade deps --source $Source --id $LineupID --version $LineupVersion --deps-file $depsFile
|
||||
|
|
|
|||
|
|
@ -161,7 +161,7 @@ function CreatePR(
|
|||
[string]$gitHubToken) {
|
||||
$hubLocation = Ensure-Hub
|
||||
|
||||
Invoke-Block { git push -f https://$gitHubToken@github.com/$headFork/Universe.git $destinationBranch }
|
||||
Invoke-Block { git push -f https://$gitHubToken@github.com/$headFork/AspNetCore.git $destinationBranch }
|
||||
& $hubLocation pull-request -f -b "${baseFork}:$baseBranch" -h "${headFork}:$destinationBranch" -m $body
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
[Oo]bj/
|
||||
[Bb]in/
|
||||
TestResults/
|
||||
.nuget/
|
||||
_ReSharper.*/
|
||||
packages/
|
||||
artifacts/
|
||||
PublishProfiles/
|
||||
*.user
|
||||
*.suo
|
||||
*.cache
|
||||
*.docstates
|
||||
_ReSharper.*
|
||||
nuget.exe
|
||||
*net45.csproj
|
||||
*k10.csproj
|
||||
*.psess
|
||||
*.vsp
|
||||
*.pidb
|
||||
*.userprefs
|
||||
*DS_Store
|
||||
*.ncrunchsolution
|
||||
*.*sdf
|
||||
*.ipch
|
||||
*.sln.ide
|
||||
/.vs/
|
||||
.testPublish/
|
||||
.build/
|
||||
autobahnreports/
|
||||
.vscode/
|
||||
global.json
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<Project>
|
||||
<Import
|
||||
Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), AspNetCoreSettings.props))\AspNetCoreSettings.props"
|
||||
Condition=" '$(CI)' != 'true' AND '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), AspNetCoreSettings.props))' != '' " />
|
||||
|
||||
<Import Project="version.props" />
|
||||
<Import Project="build\dependencies.props" />
|
||||
<Import Project="build\sources.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<Product>Microsoft ASP.NET Core</Product>
|
||||
<RepositoryUrl>https://github.com/aspnet/WebSockets</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<RepositoryRoot>$(MSBuildThisFileDirectory)</RepositoryRoot>
|
||||
<AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)build\Key.snk</AssemblyOriginatorKeyFile>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<Project>
|
||||
<PropertyGroup>
|
||||
<RuntimeFrameworkVersion Condition=" '$(TargetFramework)' == 'netcoreapp2.0' ">$(MicrosoftNETCoreApp20PackageVersion)</RuntimeFrameworkVersion>
|
||||
<RuntimeFrameworkVersion Condition=" '$(TargetFramework)' == 'netcoreapp2.1' ">$(MicrosoftNETCoreApp21PackageVersion)</RuntimeFrameworkVersion>
|
||||
<RuntimeFrameworkVersion Condition=" '$(TargetFramework)' == 'netcoreapp2.2' ">$(MicrosoftNETCoreApp22PackageVersion)</RuntimeFrameworkVersion>
|
||||
<NETStandardImplicitPackageVersion Condition=" '$(TargetFramework)' == 'netstandard2.0' ">$(NETStandardLibrary20PackageVersion)</NETStandardImplicitPackageVersion>
|
||||
<!-- aspnet/BuildTools#662 Don't police what version of NetCoreApp we use -->
|
||||
<NETCoreAppMaximumVersion>99.9</NETCoreAppMaximumVersion>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"Default": {
|
||||
"rules": [
|
||||
"DefaultCompositeRule"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
WebSockets
|
||||
================
|
||||
|
||||
AppVeyor: [](https://ci.appveyor.com/project/aspnetci/WebSockets/branch/dev)
|
||||
|
||||
Travis: [](https://travis-ci.org/aspnet/WebSockets)
|
||||
|
||||
Contains a managed implementation of the WebSocket protocol, along with server integration components.
|
||||
|
||||
This project is part of ASP.NET Core. You can find samples, documentation and getting started instructions for ASP.NET Core at the [Home](https://github.com/aspnet/home) repo.
|
||||
|
||||
|
||||
## System Requirements
|
||||
|
||||
This repo has a few special system requirements/prerequisites.
|
||||
|
||||
1. Windows IIS Express tests require IIS Express 10 and Windows 8 for WebSockets support
|
||||
2. HttpListener/ASP.NET 4.6 samples require at least Windows 8
|
||||
3. Autobahn Test Suite requires special installation see the README.md in test/AutobahnTestApp
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.26730.10
|
||||
MinimumVisualStudioVersion = 15.0.26730.03
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{2C7947A5-9FBD-4267-97C1-2D726D7B3BAF}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
src\Directory.Build.props = src\Directory.Build.props
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{C45106D0-76C8-4776-A140-F7DD83CA2958}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
test\Directory.Build.props = test\Directory.Build.props
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{9E55FC5B-FD9C-4266-AB24-F3AA649D7C8B}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestServer", "samples\TestServer\TestServer.csproj", "{4E5F5FCC-172C-44D9-BEA0-39098A79CD0B}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "scripts", "scripts", "{19595D64-E42E-46FD-AB2E-BDC870724EE7}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
scripts\UpdateCoreFxCode.ps1 = scripts\UpdateCoreFxCode.ps1
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.WebSockets", "src\Microsoft.AspNetCore.WebSockets\Microsoft.AspNetCore.WebSockets.csproj", "{CDE16880-0374-46FA-8896-99F1B90B4B6F}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.WebSockets.Test", "test\Microsoft.AspNetCore.WebSockets.Test\Microsoft.AspNetCore.WebSockets.Test.csproj", "{5AFA74F5-9B1D-4FC5-815F-EF471F5AC1EF}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.WebSockets.ConformanceTest", "test\Microsoft.AspNetCore.WebSockets.ConformanceTest\Microsoft.AspNetCore.WebSockets.ConformanceTest.csproj", "{74F45408-1959-4FEE-9511-25D40F4913FD}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EchoApp", "samples\EchoApp\EchoApp.csproj", "{421954B0-5C6B-4092-8D4D-EACA4CE60AFB}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutobahnTestApp", "test\AutobahnTestApp\AutobahnTestApp.csproj", "{150DF5A8-87C6-42F7-8886-CE07BFD02FD2}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{92CE12E6-E127-433B-96D3-164C0113EA17}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
build\dependencies.props = build\dependencies.props
|
||||
build\Key.snk = build\Key.snk
|
||||
build\repo.props = build\repo.props
|
||||
build\repo.targets = build\repo.targets
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7A963B09-471B-4D67-B5C0-6039AF0C39EE}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
Directory.Build.props = Directory.Build.props
|
||||
Directory.Build.targets = Directory.Build.targets
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{4E5F5FCC-172C-44D9-BEA0-39098A79CD0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4E5F5FCC-172C-44D9-BEA0-39098A79CD0B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4E5F5FCC-172C-44D9-BEA0-39098A79CD0B}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{4E5F5FCC-172C-44D9-BEA0-39098A79CD0B}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{4E5F5FCC-172C-44D9-BEA0-39098A79CD0B}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{4E5F5FCC-172C-44D9-BEA0-39098A79CD0B}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{4E5F5FCC-172C-44D9-BEA0-39098A79CD0B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4E5F5FCC-172C-44D9-BEA0-39098A79CD0B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{4E5F5FCC-172C-44D9-BEA0-39098A79CD0B}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{4E5F5FCC-172C-44D9-BEA0-39098A79CD0B}.Release|x64.Build.0 = Release|Any CPU
|
||||
{4E5F5FCC-172C-44D9-BEA0-39098A79CD0B}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{4E5F5FCC-172C-44D9-BEA0-39098A79CD0B}.Release|x86.Build.0 = Release|Any CPU
|
||||
{CDE16880-0374-46FA-8896-99F1B90B4B6F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{CDE16880-0374-46FA-8896-99F1B90B4B6F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{CDE16880-0374-46FA-8896-99F1B90B4B6F}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{CDE16880-0374-46FA-8896-99F1B90B4B6F}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{CDE16880-0374-46FA-8896-99F1B90B4B6F}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{CDE16880-0374-46FA-8896-99F1B90B4B6F}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{CDE16880-0374-46FA-8896-99F1B90B4B6F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{CDE16880-0374-46FA-8896-99F1B90B4B6F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{CDE16880-0374-46FA-8896-99F1B90B4B6F}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{CDE16880-0374-46FA-8896-99F1B90B4B6F}.Release|x64.Build.0 = Release|Any CPU
|
||||
{CDE16880-0374-46FA-8896-99F1B90B4B6F}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{CDE16880-0374-46FA-8896-99F1B90B4B6F}.Release|x86.Build.0 = Release|Any CPU
|
||||
{5AFA74F5-9B1D-4FC5-815F-EF471F5AC1EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{5AFA74F5-9B1D-4FC5-815F-EF471F5AC1EF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{5AFA74F5-9B1D-4FC5-815F-EF471F5AC1EF}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{5AFA74F5-9B1D-4FC5-815F-EF471F5AC1EF}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{5AFA74F5-9B1D-4FC5-815F-EF471F5AC1EF}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{5AFA74F5-9B1D-4FC5-815F-EF471F5AC1EF}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{5AFA74F5-9B1D-4FC5-815F-EF471F5AC1EF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{5AFA74F5-9B1D-4FC5-815F-EF471F5AC1EF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{5AFA74F5-9B1D-4FC5-815F-EF471F5AC1EF}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{5AFA74F5-9B1D-4FC5-815F-EF471F5AC1EF}.Release|x64.Build.0 = Release|Any CPU
|
||||
{5AFA74F5-9B1D-4FC5-815F-EF471F5AC1EF}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{5AFA74F5-9B1D-4FC5-815F-EF471F5AC1EF}.Release|x86.Build.0 = Release|Any CPU
|
||||
{74F45408-1959-4FEE-9511-25D40F4913FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{74F45408-1959-4FEE-9511-25D40F4913FD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{74F45408-1959-4FEE-9511-25D40F4913FD}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{74F45408-1959-4FEE-9511-25D40F4913FD}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{74F45408-1959-4FEE-9511-25D40F4913FD}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{74F45408-1959-4FEE-9511-25D40F4913FD}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{74F45408-1959-4FEE-9511-25D40F4913FD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{74F45408-1959-4FEE-9511-25D40F4913FD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{74F45408-1959-4FEE-9511-25D40F4913FD}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{74F45408-1959-4FEE-9511-25D40F4913FD}.Release|x64.Build.0 = Release|Any CPU
|
||||
{74F45408-1959-4FEE-9511-25D40F4913FD}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{74F45408-1959-4FEE-9511-25D40F4913FD}.Release|x86.Build.0 = Release|Any CPU
|
||||
{421954B0-5C6B-4092-8D4D-EACA4CE60AFB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{421954B0-5C6B-4092-8D4D-EACA4CE60AFB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{421954B0-5C6B-4092-8D4D-EACA4CE60AFB}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{421954B0-5C6B-4092-8D4D-EACA4CE60AFB}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{421954B0-5C6B-4092-8D4D-EACA4CE60AFB}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{421954B0-5C6B-4092-8D4D-EACA4CE60AFB}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{421954B0-5C6B-4092-8D4D-EACA4CE60AFB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{421954B0-5C6B-4092-8D4D-EACA4CE60AFB}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{421954B0-5C6B-4092-8D4D-EACA4CE60AFB}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{421954B0-5C6B-4092-8D4D-EACA4CE60AFB}.Release|x64.Build.0 = Release|Any CPU
|
||||
{421954B0-5C6B-4092-8D4D-EACA4CE60AFB}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{421954B0-5C6B-4092-8D4D-EACA4CE60AFB}.Release|x86.Build.0 = Release|Any CPU
|
||||
{150DF5A8-87C6-42F7-8886-CE07BFD02FD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{150DF5A8-87C6-42F7-8886-CE07BFD02FD2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{150DF5A8-87C6-42F7-8886-CE07BFD02FD2}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{150DF5A8-87C6-42F7-8886-CE07BFD02FD2}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{150DF5A8-87C6-42F7-8886-CE07BFD02FD2}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{150DF5A8-87C6-42F7-8886-CE07BFD02FD2}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{150DF5A8-87C6-42F7-8886-CE07BFD02FD2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{150DF5A8-87C6-42F7-8886-CE07BFD02FD2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{150DF5A8-87C6-42F7-8886-CE07BFD02FD2}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{150DF5A8-87C6-42F7-8886-CE07BFD02FD2}.Release|x64.Build.0 = Release|Any CPU
|
||||
{150DF5A8-87C6-42F7-8886-CE07BFD02FD2}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{150DF5A8-87C6-42F7-8886-CE07BFD02FD2}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{4E5F5FCC-172C-44D9-BEA0-39098A79CD0B} = {9E55FC5B-FD9C-4266-AB24-F3AA649D7C8B}
|
||||
{CDE16880-0374-46FA-8896-99F1B90B4B6F} = {2C7947A5-9FBD-4267-97C1-2D726D7B3BAF}
|
||||
{5AFA74F5-9B1D-4FC5-815F-EF471F5AC1EF} = {C45106D0-76C8-4776-A140-F7DD83CA2958}
|
||||
{74F45408-1959-4FEE-9511-25D40F4913FD} = {C45106D0-76C8-4776-A140-F7DD83CA2958}
|
||||
{421954B0-5C6B-4092-8D4D-EACA4CE60AFB} = {9E55FC5B-FD9C-4266-AB24-F3AA649D7C8B}
|
||||
{150DF5A8-87C6-42F7-8886-CE07BFD02FD2} = {C45106D0-76C8-4776-A140-F7DD83CA2958}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {D3542868-F8C6-401B-8071-37FE3C981604}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
@ECHO OFF
|
||||
SET RepoRoot="%~dp0..\.."
|
||||
%RepoRoot%\build.cmd -LockFile %RepoRoot%\korebuild-lock.txt -Path %~dp0 %*
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
repo_root="$DIR/../.."
|
||||
"$repo_root/build.sh" --path "$DIR" --lockfile "$repo_root/korebuild-lock.txt" "$@"
|
||||
Binary file not shown.
|
|
@ -0,0 +1,34 @@
|
|||
<Project>
|
||||
<PropertyGroup>
|
||||
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Label="Package Versions">
|
||||
<InternalAspNetCoreSdkPackageVersion>2.2.0-preview2-20181004.6</InternalAspNetCoreSdkPackageVersion>
|
||||
<MicrosoftAspNetCoreAspNetCoreModulePackageVersion>2.2.0-preview3-35425</MicrosoftAspNetCoreAspNetCoreModulePackageVersion>
|
||||
<MicrosoftAspNetCoreDiagnosticsPackageVersion>2.2.0-preview3-35425</MicrosoftAspNetCoreDiagnosticsPackageVersion>
|
||||
<MicrosoftAspNetCoreHttpExtensionsPackageVersion>2.2.0-preview3-35425</MicrosoftAspNetCoreHttpExtensionsPackageVersion>
|
||||
<MicrosoftAspNetCoreServerHttpSysPackageVersion>2.2.0-preview3-35425</MicrosoftAspNetCoreServerHttpSysPackageVersion>
|
||||
<MicrosoftAspNetCoreServerIISIntegrationPackageVersion>2.2.0-preview3-35425</MicrosoftAspNetCoreServerIISIntegrationPackageVersion>
|
||||
<MicrosoftAspNetCoreServerIntegrationTestingPackageVersion>0.6.0-preview3-35425</MicrosoftAspNetCoreServerIntegrationTestingPackageVersion>
|
||||
<MicrosoftAspNetCoreServerKestrelHttpsPackageVersion>2.2.0-preview3-35425</MicrosoftAspNetCoreServerKestrelHttpsPackageVersion>
|
||||
<MicrosoftAspNetCoreServerKestrelPackageVersion>2.2.0-preview3-35425</MicrosoftAspNetCoreServerKestrelPackageVersion>
|
||||
<MicrosoftAspNetCoreStaticFilesPackageVersion>2.2.0-preview3-35425</MicrosoftAspNetCoreStaticFilesPackageVersion>
|
||||
<MicrosoftAspNetCoreTestingPackageVersion>2.2.0-preview3-35425</MicrosoftAspNetCoreTestingPackageVersion>
|
||||
<MicrosoftExtensionsConfigurationCommandLinePackageVersion>2.2.0-preview3-35425</MicrosoftExtensionsConfigurationCommandLinePackageVersion>
|
||||
<MicrosoftExtensionsLoggingAbstractionsPackageVersion>2.2.0-preview3-35425</MicrosoftExtensionsLoggingAbstractionsPackageVersion>
|
||||
<MicrosoftExtensionsLoggingConsolePackageVersion>2.2.0-preview3-35425</MicrosoftExtensionsLoggingConsolePackageVersion>
|
||||
<MicrosoftExtensionsLoggingPackageVersion>2.2.0-preview3-35425</MicrosoftExtensionsLoggingPackageVersion>
|
||||
<MicrosoftExtensionsLoggingTestingPackageVersion>2.2.0-preview3-35425</MicrosoftExtensionsLoggingTestingPackageVersion>
|
||||
<MicrosoftExtensionsOptionsPackageVersion>2.2.0-preview3-35425</MicrosoftExtensionsOptionsPackageVersion>
|
||||
<MicrosoftNETCoreApp20PackageVersion>2.0.9</MicrosoftNETCoreApp20PackageVersion>
|
||||
<MicrosoftNETCoreApp21PackageVersion>2.1.3</MicrosoftNETCoreApp21PackageVersion>
|
||||
<MicrosoftNETCoreApp22PackageVersion>2.2.0-preview3-27001-02</MicrosoftNETCoreApp22PackageVersion>
|
||||
<MicrosoftNETTestSdkPackageVersion>15.6.1</MicrosoftNETTestSdkPackageVersion>
|
||||
<NETStandardLibrary20PackageVersion>2.0.3</NETStandardLibrary20PackageVersion>
|
||||
<SystemNetWebSocketsWebSocketProtocolPackageVersion>4.5.1</SystemNetWebSocketsWebSocketProtocolPackageVersion>
|
||||
<XunitPackageVersion>2.3.1</XunitPackageVersion>
|
||||
<XunitRunnerVisualStudioPackageVersion>2.4.0</XunitRunnerVisualStudioPackageVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(DotNetPackageVersionPropsPath)" Condition=" '$(DotNetPackageVersionPropsPath)' != '' " />
|
||||
<PropertyGroup Label="Package Versions: Pinned" />
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<Project>
|
||||
<Import Project="dependencies.props" />
|
||||
|
||||
<ItemGroup>
|
||||
<ExcludeFromTest Include="$(RepositoryRoot)test\AutobahnTestApp\*.csproj" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<!-- These properties are use by the automation that updates dependencies.props -->
|
||||
<LineupPackageId>Internal.AspNetCore.Universe.Lineup</LineupPackageId>
|
||||
<LineupPackageVersion>2.2.0-*</LineupPackageVersion>
|
||||
<LineupPackageRestoreSource>https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json</LineupPackageRestoreSource>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<DotNetCoreRuntime Include="$(MicrosoftNETCoreApp20PackageVersion)" />
|
||||
<DotNetCoreRuntime Include="$(MicrosoftNETCoreApp21PackageVersion)" />
|
||||
<DotNetCoreRuntime Include="$(MicrosoftNETCoreApp22PackageVersion)" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<Project>
|
||||
<PropertyGroup>
|
||||
<AutobahnReportDir>$(ArtifactsDir)autobahnreports\</AutobahnReportDir>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="SetReportDirectory" BeforeTargets="Test">
|
||||
<SetEnvironmentVariable Variable="AUTOBAHN_SUITES_REPORT_DIR" Value="$(AutobahnReportDir)" />
|
||||
</Target>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
function has($cmd) { !!(Get-Command $cmd -ErrorAction SilentlyContinue) }
|
||||
|
||||
# Download VCForPython27 if necessary
|
||||
$VendorDir = Join-Path (Get-Location) "vendor"
|
||||
|
||||
if(!(Test-Path $VendorDir)) {
|
||||
mkdir $VendorDir
|
||||
}
|
||||
|
||||
$VirtualEnvDir = Join-Path $VendorDir "virtualenv";
|
||||
$ScriptsDir = Join-Path $VirtualEnvDir "Scripts"
|
||||
$WsTest = Join-Path $ScriptsDir "wstest.exe"
|
||||
|
||||
$VCPythonMsi = Join-Path $VendorDir "VCForPython27.msi"
|
||||
if(!(Test-Path $VCPythonMsi)) {
|
||||
Write-Host "Downloading VCForPython27.msi"
|
||||
Invoke-WebRequest -Uri https://download.microsoft.com/download/7/9/6/796EF2E4-801B-4FC4-AB28-B59FBF6D907B/VCForPython27.msi -OutFile "$VCPythonMsi"
|
||||
}
|
||||
else {
|
||||
Write-Host "Using VCForPython27.msi from Cache"
|
||||
}
|
||||
|
||||
# Install VCForPython27
|
||||
Write-Host "Installing VCForPython27"
|
||||
|
||||
# Launch this way to ensure we wait for msiexec to complete. It's a Windows app so it won't block the console by default.
|
||||
Start-Process msiexec "/i","$VCPythonMsi","/qn","/quiet","/norestart" -Wait
|
||||
|
||||
Write-Host "Installed VCForPython27"
|
||||
|
||||
# Install Python
|
||||
if(!(has python)) {
|
||||
choco install python2
|
||||
}
|
||||
|
||||
if(!(has python)) {
|
||||
throw "Failed to install python2"
|
||||
}
|
||||
|
||||
# Install virtualenv
|
||||
pip install virtualenv
|
||||
|
||||
# Make a virtualenv in .virtualenv
|
||||
virtualenv $VirtualEnvDir
|
||||
|
||||
& "$ScriptsDir\python" --version
|
||||
& "$ScriptsDir\pip" --version
|
||||
|
||||
# Install autobahn into the virtualenv
|
||||
& "$ScriptsDir\pip" install autobahntestsuite
|
||||
|
||||
Write-Host "Using wstest from: '$WsTest'"
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
type -p python
|
||||
python --version
|
||||
|
||||
# Install local virtualenv
|
||||
mkdir .python
|
||||
cd .python
|
||||
curl -OL https://pypi.python.org/packages/d4/0c/9840c08189e030873387a73b90ada981885010dd9aea134d6de30cd24cb8/virtualenv-15.1.0.tar.gz
|
||||
tar xf virtualenv-15.1.0.tar.gz
|
||||
cd ..
|
||||
|
||||
# Make a virtualenv
|
||||
python ./.python/virtualenv-15.1.0/virtualenv.py .virtualenv
|
||||
|
||||
.virtualenv/bin/python --version
|
||||
.virtualenv/bin/pip --version
|
||||
|
||||
# Install autobahn into the virtualenv
|
||||
.virtualenv/bin/pip install autobahntestsuite
|
||||
|
||||
# We're done. The travis config has already established the path to WSTest should be within the virtualenv.
|
||||
ls -l .virtualenv/bin
|
||||
.virtualenv/bin/wstest --version
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<Project>
|
||||
<Import Project="$(DotNetRestoreSourcePropsPath)" Condition="'$(DotNetRestoreSourcePropsPath)' != ''"/>
|
||||
|
||||
<PropertyGroup Label="RestoreSources">
|
||||
<RestoreSources>$(DotNetRestoreSources)</RestoreSources>
|
||||
<RestoreSources Condition="'$(DotNetBuildOffline)' != 'true' AND '$(AspNetUniverseBuildOffline)' != 'true' ">
|
||||
$(RestoreSources);
|
||||
https://dotnet.myget.org/F/dotnet-core/api/v3/index.json;
|
||||
https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json;
|
||||
https://dotnet.myget.org/F/aspnetcore-tools/api/v3/index.json;
|
||||
</RestoreSources>
|
||||
<RestoreSources Condition="'$(DotNetBuildOffline)' != 'true'">
|
||||
$(RestoreSources);
|
||||
https://api.nuget.org/v3/index.json;
|
||||
</RestoreSources>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="..\..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.1.0.0\build\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.props" Condition="Exists('..\..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.1.0.0\build\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.props')" />
|
||||
<Import Project="..\..\packages\Microsoft.Net.Compilers.1.0.0\build\Microsoft.Net.Compilers.props" Condition="Exists('..\..\packages\Microsoft.Net.Compilers.1.0.0\build\Microsoft.Net.Compilers.props')" />
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProductVersion>
|
||||
</ProductVersion>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<ProjectGuid>{72E3AB32-682F-42AF-B7C7-0B777244FF11}</ProjectGuid>
|
||||
<ProjectTypeGuids>{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>AutobahnTestAppAspNet4</RootNamespace>
|
||||
<AssemblyName>AutobahnTestAppAspNet4</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
|
||||
<UseIISExpress>true</UseIISExpress>
|
||||
<IISExpressSSLPort />
|
||||
<IISExpressAnonymousAuthentication />
|
||||
<IISExpressWindowsAuthentication />
|
||||
<IISExpressUseClassicPipelineMode />
|
||||
<UseGlobalApplicationHostFile />
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.1.0.0\lib\net45\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Web.DynamicData" />
|
||||
<Reference Include="System.Web.Entity" />
|
||||
<Reference Include="System.Web.ApplicationServices" />
|
||||
<Reference Include="System.ComponentModel.DataAnnotations" />
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="System.Web.Extensions" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.Web" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="System.Configuration" />
|
||||
<Reference Include="System.Web.Services" />
|
||||
<Reference Include="System.EnterpriseServices" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="packages.config" />
|
||||
<Content Include="EchoSocket.ashx" />
|
||||
<Content Include="wstest-spec.json" />
|
||||
<None Include="Web.Debug.config">
|
||||
<DependentUpon>Web.config</DependentUpon>
|
||||
</None>
|
||||
<None Include="Web.Release.config">
|
||||
<DependentUpon>Web.config</DependentUpon>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Web.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="EchoSocket.ashx.cs">
|
||||
<DependentUpon>EchoSocket.ashx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||
<Import Project="$(VSToolsPath)\WebApplications\Microsoft.WebApplication.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" Condition="false" />
|
||||
<ProjectExtensions>
|
||||
<VisualStudio>
|
||||
<FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}">
|
||||
<WebProjectProperties>
|
||||
<UseIIS>True</UseIIS>
|
||||
<AutoAssignPort>True</AutoAssignPort>
|
||||
<DevelopmentServerPort>29392</DevelopmentServerPort>
|
||||
<DevelopmentServerVPath>/</DevelopmentServerVPath>
|
||||
<IISUrl>http://localhost:29392</IISUrl>
|
||||
<OverrideIISAppRootUrl>True</OverrideIISAppRootUrl>
|
||||
<IISAppRootUrl>http://localhost:29392/EchoSocket.ashx</IISAppRootUrl>
|
||||
<NTLMAuthentication>False</NTLMAuthentication>
|
||||
<UseCustomServer>False</UseCustomServer>
|
||||
<CustomServerUrl>
|
||||
</CustomServerUrl>
|
||||
<SaveServerSettingsInUserFile>False</SaveServerSettingsInUserFile>
|
||||
</WebProjectProperties>
|
||||
</FlavorProperties>
|
||||
</VisualStudio>
|
||||
</ProjectExtensions>
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\..\packages\Microsoft.Net.Compilers.1.0.0\build\Microsoft.Net.Compilers.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.Net.Compilers.1.0.0\build\Microsoft.Net.Compilers.props'))" />
|
||||
<Error Condition="!Exists('..\..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.1.0.0\build\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.1.0.0\build\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.props'))" />
|
||||
</Target>
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
|
|
@ -0,0 +1 @@
|
|||
<%@ WebHandler Language="C#" CodeBehind="EchoSocket.ashx.cs" Class="AutobahnTestAppAspNet4.EchoSocket" %>
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.WebSockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
|
||||
namespace AutobahnTestAppAspNet4
|
||||
{
|
||||
/// <summary>
|
||||
/// Summary description for EchoSocket
|
||||
/// </summary>
|
||||
public class EchoSocket : IHttpHandler
|
||||
{
|
||||
public bool IsReusable => false;
|
||||
|
||||
public void ProcessRequest(HttpContext context)
|
||||
{
|
||||
if (context.IsWebSocketRequest)
|
||||
{
|
||||
context.AcceptWebSocketRequest(async socketContext =>
|
||||
{
|
||||
await Echo(socketContext.WebSocket);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Response.Write("Ready to accept WebSocket request at: " + context.Request.Url.ToString().Replace("https://", "wss://").Replace("http://", "ws://"));
|
||||
context.Response.Flush();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Echo(WebSocket webSocket)
|
||||
{
|
||||
var buffer = new byte[1024 * 4];
|
||||
var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
|
||||
while (!result.CloseStatus.HasValue)
|
||||
{
|
||||
await webSocket.SendAsync(new ArraySegment<byte>(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None);
|
||||
result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
|
||||
}
|
||||
await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("AutobahnTestAppAspNet4")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("AutobahnTestAppAspNet4")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2016")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("72e3ab32-682f-42af-b7c7-0b777244ff11")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Revision and Build Numbers
|
||||
// by using the '*' as shown below:
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!-- For more information on using web.config transformation visit http://go.microsoft.com/fwlink/?LinkId=125889 -->
|
||||
|
||||
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
|
||||
<!--
|
||||
In the example below, the "SetAttributes" transform will change the value of
|
||||
"connectionString" to use "ReleaseSQLServer" only when the "Match" locator
|
||||
finds an attribute "name" that has a value of "MyDB".
|
||||
|
||||
<connectionStrings>
|
||||
<add name="MyDB"
|
||||
connectionString="Data Source=ReleaseSQLServer;Initial Catalog=MyReleaseDB;Integrated Security=True"
|
||||
xdt:Transform="SetAttributes" xdt:Locator="Match(name)"/>
|
||||
</connectionStrings>
|
||||
-->
|
||||
<system.web>
|
||||
<!--
|
||||
In the example below, the "Replace" transform will replace the entire
|
||||
<customErrors> section of your web.config file.
|
||||
Note that because there is only one customErrors section under the
|
||||
<system.web> node, there is no need to use the "xdt:Locator" attribute.
|
||||
|
||||
<customErrors defaultRedirect="GenericError.htm"
|
||||
mode="RemoteOnly" xdt:Transform="Replace">
|
||||
<error statusCode="500" redirect="InternalError.htm"/>
|
||||
</customErrors>
|
||||
-->
|
||||
</system.web>
|
||||
</configuration>
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!-- For more information on using web.config transformation visit http://go.microsoft.com/fwlink/?LinkId=125889 -->
|
||||
|
||||
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
|
||||
<!--
|
||||
In the example below, the "SetAttributes" transform will change the value of
|
||||
"connectionString" to use "ReleaseSQLServer" only when the "Match" locator
|
||||
finds an attribute "name" that has a value of "MyDB".
|
||||
|
||||
<connectionStrings>
|
||||
<add name="MyDB"
|
||||
connectionString="Data Source=ReleaseSQLServer;Initial Catalog=MyReleaseDB;Integrated Security=True"
|
||||
xdt:Transform="SetAttributes" xdt:Locator="Match(name)"/>
|
||||
</connectionStrings>
|
||||
-->
|
||||
<system.web>
|
||||
<compilation xdt:Transform="RemoveAttributes(debug)" />
|
||||
<!--
|
||||
In the example below, the "Replace" transform will replace the entire
|
||||
<customErrors> section of your web.config file.
|
||||
Note that because there is only one customErrors section under the
|
||||
<system.web> node, there is no need to use the "xdt:Locator" attribute.
|
||||
|
||||
<customErrors defaultRedirect="GenericError.htm"
|
||||
mode="RemoteOnly" xdt:Transform="Replace">
|
||||
<error statusCode="500" redirect="InternalError.htm"/>
|
||||
</customErrors>
|
||||
-->
|
||||
</system.web>
|
||||
</configuration>
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
For more information on how to configure your ASP.NET application, please visit
|
||||
http://go.microsoft.com/fwlink/?LinkId=169433
|
||||
-->
|
||||
<configuration>
|
||||
<system.web>
|
||||
<compilation debug="true" targetFramework="4.6.1"/>
|
||||
<httpRuntime targetFramework="4.6.1"/>
|
||||
</system.web>
|
||||
<system.codedom>
|
||||
<compilers>
|
||||
<compiler language="c#;cs;csharp" extension=".cs"
|
||||
type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
|
||||
warningLevel="4" compilerOptions="/langversion:6 /nowarn:1659;1699;1701"/>
|
||||
<compiler language="vb;vbs;visualbasic;vbscript" extension=".vb"
|
||||
type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.VBCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
|
||||
warningLevel="4" compilerOptions="/langversion:14 /nowarn:41008 /define:_MYTYPE=\"Web\" /optionInfer+"/>
|
||||
</compilers>
|
||||
</system.codedom>
|
||||
</configuration>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Microsoft.CodeDom.Providers.DotNetCompilerPlatform" version="1.0.0" targetFramework="net461" />
|
||||
<package id="Microsoft.Net.Compilers" version="1.0.0" targetFramework="net461" developmentDependency="true" />
|
||||
</packages>
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"options": { "failByDrop": false },
|
||||
"outdir": "./bin/wstest-report",
|
||||
"servers": [
|
||||
{
|
||||
"agent": "ASP.NET 4",
|
||||
"url": "ws://localhost:29392/EchoSocket.ashx",
|
||||
"options": { "version": 18 }
|
||||
}
|
||||
],
|
||||
"cases": ["*"],
|
||||
"exclude-cases": ["12.*", "13.*"],
|
||||
"exclude-agent-cases": {}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
|
||||
</startup>
|
||||
</configuration>
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{B7246F23-6A4B-492F-AB61-292AA1A9E9D5}</ProjectGuid>
|
||||
<OutputType>Exe</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>AutobahnTestAppHttpListener</RootNamespace>
|
||||
<AssemblyName>AutobahnTestAppHttpListener</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="App.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.WebSockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AutobahnTestAppHttpListener
|
||||
{
|
||||
class Program
|
||||
{
|
||||
// This app only works on Windows 8+
|
||||
static int Main(string[] args)
|
||||
{
|
||||
using (var listener = StartListener())
|
||||
{
|
||||
if (listener == null)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
var httpUrl = listener.Prefixes.Single();
|
||||
var wsUrl = httpUrl.Replace("http://", "ws://");
|
||||
|
||||
var stopTokenSource = new CancellationTokenSource();
|
||||
var task = Run(listener, wsUrl, stopTokenSource.Token);
|
||||
|
||||
Console.CancelKeyPress += (sender, a) =>
|
||||
{
|
||||
a.Cancel = true;
|
||||
stopTokenSource.Cancel();
|
||||
};
|
||||
|
||||
Console.WriteLine($"HTTP: {httpUrl}");
|
||||
Console.WriteLine($"WS : {wsUrl}");
|
||||
Console.WriteLine("Press Ctrl-C to stop...");
|
||||
|
||||
task.Wait();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static async Task Run(HttpListener listener, string wsUrl, CancellationToken stopToken)
|
||||
{
|
||||
while (!stopToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
var context = await listener.GetContextAsync();
|
||||
|
||||
if (context.Request.IsWebSocketRequest)
|
||||
{
|
||||
var socket = await context.AcceptWebSocketAsync(null);
|
||||
await Echo(socket.WebSocket);
|
||||
}
|
||||
else
|
||||
{
|
||||
using (var writer = new StreamWriter(context.Response.OutputStream))
|
||||
{
|
||||
await writer.WriteLineAsync($"Ready to accept WebSocket request at: {wsUrl}");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Request failed: {ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task Echo(WebSocket webSocket)
|
||||
{
|
||||
var buffer = new byte[1024 * 4];
|
||||
var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
|
||||
while (!result.CloseStatus.HasValue)
|
||||
{
|
||||
await webSocket.SendAsync(new ArraySegment<byte>(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None);
|
||||
result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
|
||||
}
|
||||
await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
|
||||
}
|
||||
|
||||
static HttpListener StartListener()
|
||||
{
|
||||
var port = 49152; // IANA recommends starting at port 49152 for dynamic ports
|
||||
while (port < 65535)
|
||||
{
|
||||
HttpListener listener = new HttpListener();
|
||||
listener.Prefixes.Add($"http://localhost:{port}/");
|
||||
try
|
||||
{
|
||||
listener.Start();
|
||||
return listener;
|
||||
}
|
||||
catch
|
||||
{
|
||||
port++;
|
||||
}
|
||||
}
|
||||
|
||||
Console.Error.WriteLine("Failed to find a free port!");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("AutobahnTestAppHttpListener")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("AutobahnTestAppHttpListener")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2016")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("b7246f23-6a4b-492f-ab61-292aa1a9e9d5")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netcoreapp2.2;net461</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.WebSockets\Microsoft.AspNetCore.WebSockets.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Diagnostics" Version="$(MicrosoftAspNetCoreDiagnosticsPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="$(MicrosoftAspNetCoreServerIISIntegrationPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="$(MicrosoftAspNetCoreServerKestrelPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="$(MicrosoftAspNetCoreStaticFilesPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(MicrosoftExtensionsLoggingConsolePackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
|
||||
namespace EchoApp
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
var host = new WebHostBuilder()
|
||||
.UseKestrel()
|
||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||
.UseIISIntegration()
|
||||
.UseStartup<Startup>()
|
||||
.Build();
|
||||
|
||||
host.Run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:62225/",
|
||||
"sslPort": 0
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"EchoApp": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "http://localhost:5000",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.WebSockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace EchoApp
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
// This method gets called by the runtime. Use this method to add services to the container.
|
||||
// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
}
|
||||
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
|
||||
{
|
||||
loggerFactory.AddConsole(LogLevel.Debug);
|
||||
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
|
||||
app.UseWebSockets();
|
||||
|
||||
app.Use(async (context, next) =>
|
||||
{
|
||||
if (context.WebSockets.IsWebSocketRequest)
|
||||
{
|
||||
var webSocket = await context.WebSockets.AcceptWebSocketAsync();
|
||||
await Echo(context, webSocket, loggerFactory.CreateLogger("Echo"));
|
||||
}
|
||||
else
|
||||
{
|
||||
await next();
|
||||
}
|
||||
});
|
||||
|
||||
app.UseFileServer();
|
||||
}
|
||||
|
||||
private async Task Echo(HttpContext context, WebSocket webSocket, ILogger logger)
|
||||
{
|
||||
var buffer = new byte[1024 * 4];
|
||||
var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
|
||||
LogFrame(logger, result, buffer);
|
||||
while (!result.CloseStatus.HasValue)
|
||||
{
|
||||
// If the client send "ServerClose", then they want a server-originated close to occur
|
||||
string content = "<<binary>>";
|
||||
if (result.MessageType == WebSocketMessageType.Text)
|
||||
{
|
||||
content = Encoding.UTF8.GetString(buffer, 0, result.Count);
|
||||
if (content.Equals("ServerClose"))
|
||||
{
|
||||
await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing from Server", CancellationToken.None);
|
||||
logger.LogDebug($"Sent Frame Close: {WebSocketCloseStatus.NormalClosure} Closing from Server");
|
||||
return;
|
||||
}
|
||||
else if (content.Equals("ServerAbort"))
|
||||
{
|
||||
context.Abort();
|
||||
}
|
||||
}
|
||||
|
||||
await webSocket.SendAsync(new ArraySegment<byte>(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None);
|
||||
logger.LogDebug($"Sent Frame {result.MessageType}: Len={result.Count}, Fin={result.EndOfMessage}: {content}");
|
||||
|
||||
result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
|
||||
LogFrame(logger, result, buffer);
|
||||
}
|
||||
await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
|
||||
}
|
||||
|
||||
private void LogFrame(ILogger logger, WebSocketReceiveResult frame, byte[] buffer)
|
||||
{
|
||||
var close = frame.CloseStatus != null;
|
||||
string message;
|
||||
if (close)
|
||||
{
|
||||
message = $"Close: {frame.CloseStatus.Value} {frame.CloseStatusDescription}";
|
||||
}
|
||||
else
|
||||
{
|
||||
string content = "<<binary>>";
|
||||
if (frame.MessageType == WebSocketMessageType.Text)
|
||||
{
|
||||
content = Encoding.UTF8.GetString(buffer, 0, frame.Count);
|
||||
}
|
||||
message = $"{frame.MessageType}: Len={frame.Count}, Fin={frame.EndOfMessage}: {content}";
|
||||
}
|
||||
logger.LogDebug("Received Frame " + message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,151 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title></title>
|
||||
<style>
|
||||
table { border: 0 }
|
||||
.commslog-data { font-family: Consolas, Courier New, Courier, monospace; }
|
||||
.commslog-server { background-color: red; color: white }
|
||||
.commslog-client { background-color: green; color: white }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>WebSocket Test Page</h1>
|
||||
<p id="stateLabel">Ready to connect...</p>
|
||||
<div>
|
||||
<label for="connectionUrl">WebSocket Server URL:</label>
|
||||
<input id="connectionUrl" />
|
||||
<button id="connectButton" type="submit">Connect</button>
|
||||
</div>
|
||||
<div>
|
||||
<label for="sendMessage">Message to send:</label>
|
||||
<input id="sendMessage" disabled />
|
||||
<button id="sendButton" type="submit" disabled>Send</button>
|
||||
<button id="closeButton" disabled>Close Socket</button>
|
||||
</div>
|
||||
|
||||
<p>Note: When connected to the default server (i.e. the server in the address bar ;)), the message "ServerClose" will cause the server to close the connection. Similarly, the message "ServerAbort" will cause the server to forcibly terminate the connection without a closing handshake</p>
|
||||
|
||||
<h2>Communication Log</h2>
|
||||
<table style="width: 800px">
|
||||
<thead>
|
||||
<tr>
|
||||
<td style="width: 100px">From</td>
|
||||
<td style="width: 100px">To</td>
|
||||
<td>Data</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="commsLog">
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<script>
|
||||
var connectionForm = document.getElementById("connectionForm");
|
||||
var connectionUrl = document.getElementById("connectionUrl");
|
||||
var connectButton = document.getElementById("connectButton");
|
||||
var stateLabel = document.getElementById("stateLabel");
|
||||
var sendMessage = document.getElementById("sendMessage");
|
||||
var sendButton = document.getElementById("sendButton");
|
||||
var sendForm = document.getElementById("sendForm");
|
||||
var commsLog = document.getElementById("commsLog");
|
||||
|
||||
var socket;
|
||||
|
||||
var scheme = document.location.protocol == "https:" ? "wss" : "ws";
|
||||
var port = document.location.port ? (":" + document.location.port) : "";
|
||||
|
||||
connectionUrl.value = scheme + "://" + document.location.hostname + port;
|
||||
|
||||
function updateState() {
|
||||
function disable() {
|
||||
sendMessage.disabled = true;
|
||||
sendButton.disabled = true;
|
||||
closeButton.disabled = true;
|
||||
}
|
||||
function enable() {
|
||||
sendMessage.disabled = false;
|
||||
sendButton.disabled = false;
|
||||
closeButton.disabled = false;
|
||||
}
|
||||
|
||||
connectionUrl.disabled = true;
|
||||
connectButton.disabled = true;
|
||||
|
||||
if (!socket) {
|
||||
disable();
|
||||
} else {
|
||||
switch (socket.readyState) {
|
||||
case WebSocket.CLOSED:
|
||||
stateLabel.innerHTML = "Closed";
|
||||
disable();
|
||||
connectionUrl.disabled = false;
|
||||
connectButton.disabled = false;
|
||||
break;
|
||||
case WebSocket.CLOSING:
|
||||
stateLabel.innerHTML = "Closing...";
|
||||
disable();
|
||||
break;
|
||||
case WebSocket.CONNECTING:
|
||||
stateLabel.innerHTML = "Connecting...";
|
||||
disable();
|
||||
break;
|
||||
case WebSocket.OPEN:
|
||||
stateLabel.innerHTML = "Open";
|
||||
enable();
|
||||
break;
|
||||
default:
|
||||
stateLabel.innerHTML = "Unknown WebSocket State: " + socket.readyState;
|
||||
disable();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closeButton.onclick = function () {
|
||||
if (!socket || socket.readyState != WebSocket.OPEN) {
|
||||
alert("socket not connected");
|
||||
}
|
||||
socket.close(1000, "Closing from client");
|
||||
}
|
||||
|
||||
sendButton.onclick = function () {
|
||||
if (!socket || socket.readyState != WebSocket.OPEN) {
|
||||
alert("socket not connected");
|
||||
}
|
||||
var data = sendMessage.value;
|
||||
socket.send(data);
|
||||
commsLog.innerHTML += '<tr>' +
|
||||
'<td class="commslog-client">Client</td>' +
|
||||
'<td class="commslog-server">Server</td>' +
|
||||
'<td class="commslog-data">' + data + '</td>'
|
||||
'</tr>';
|
||||
}
|
||||
|
||||
connectButton.onclick = function() {
|
||||
stateLabel.innerHTML = "Connecting";
|
||||
socket = new WebSocket(connectionUrl.value);
|
||||
socket.onopen = function (event) {
|
||||
updateState();
|
||||
commsLog.innerHTML += '<tr>' +
|
||||
'<td colspan="3" class="commslog-data">Connection opened</td>' +
|
||||
'</tr>';
|
||||
};
|
||||
socket.onclose = function (event) {
|
||||
updateState();
|
||||
commsLog.innerHTML += '<tr>' +
|
||||
'<td colspan="3" class="commslog-data">Connection closed. Code: ' + event.code + '. Reason: ' + event.reason + '</td>' +
|
||||
'</tr>';
|
||||
};
|
||||
socket.onerror = updateState;
|
||||
socket.onmessage = function (event) {
|
||||
commsLog.innerHTML += '<tr>' +
|
||||
'<td class="commslog-server">Server</td>' +
|
||||
'<td class="commslog-client">Client</td>' +
|
||||
'<td class="commslog-data">' + event.data + '</td>'
|
||||
'</tr>';
|
||||
};
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
|
||||
</startup>
|
||||
</configuration>
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.WebSockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace TestServer
|
||||
{
|
||||
class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
RunEchoServer().Wait();
|
||||
}
|
||||
|
||||
private static async Task RunEchoServer()
|
||||
{
|
||||
HttpListener listener = new HttpListener();
|
||||
listener.Prefixes.Add("http://localhost:12345/");
|
||||
listener.Start();
|
||||
Console.WriteLine("Started");
|
||||
|
||||
while (true)
|
||||
{
|
||||
HttpListenerContext context = listener.GetContext();
|
||||
if (!context.Request.IsWebSocketRequest)
|
||||
{
|
||||
context.Response.Close();
|
||||
continue;
|
||||
}
|
||||
Console.WriteLine("Accepted");
|
||||
|
||||
var wsContext = await context.AcceptWebSocketAsync(null);
|
||||
var webSocket = wsContext.WebSocket;
|
||||
|
||||
byte[] buffer = new byte[1024];
|
||||
WebSocketReceiveResult received = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
|
||||
|
||||
while (received.MessageType != WebSocketMessageType.Close)
|
||||
{
|
||||
Console.WriteLine($"Echoing {received.Count} bytes received in a {received.MessageType} message; Fin={received.EndOfMessage}");
|
||||
// Echo anything we receive
|
||||
await webSocket.SendAsync(new ArraySegment<byte>(buffer, 0, received.Count), received.MessageType, received.EndOfMessage, CancellationToken.None);
|
||||
|
||||
received = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
|
||||
}
|
||||
|
||||
await webSocket.CloseAsync(received.CloseStatus.Value, received.CloseStatusDescription, CancellationToken.None);
|
||||
|
||||
webSocket.Dispose();
|
||||
Console.WriteLine("Finished");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("TestServer")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("TestServer")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2014")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("ffe69337-e3b4-4625-8244-36bd609742ba")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{4E5F5FCC-172C-44D9-BEA0-39098A79CD0B}</ProjectGuid>
|
||||
<OutputType>Exe</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>TestServer</RootNamespace>
|
||||
<AssemblyName>TestServer</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="App.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<Project>
|
||||
<Import Project="..\Directory.Build.props" />
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Internal.AspNetCore.Sdk" PrivateAssets="All" Version="$(InternalAspNetCoreSdkPackageVersion)" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.WebSockets
|
||||
{
|
||||
public class ExtendedWebSocketAcceptContext : WebSocketAcceptContext
|
||||
{
|
||||
public override string SubProtocol { get; set; }
|
||||
public int? ReceiveBufferSize { get; set; }
|
||||
public TimeSpan? KeepAliveInterval { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.WebSockets.Internal
|
||||
{
|
||||
public static class Constants
|
||||
{
|
||||
public static class Headers
|
||||
{
|
||||
public const string Upgrade = "Upgrade";
|
||||
public const string UpgradeWebSocket = "websocket";
|
||||
public const string Connection = "Connection";
|
||||
public const string ConnectionUpgrade = "Upgrade";
|
||||
public const string SecWebSocketKey = "Sec-WebSocket-Key";
|
||||
public const string SecWebSocketVersion = "Sec-WebSocket-Version";
|
||||
public const string SecWebSocketProtocol = "Sec-WebSocket-Protocol";
|
||||
public const string SecWebSocketAccept = "Sec-WebSocket-Accept";
|
||||
public const string SupportedVersion = "13";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.WebSockets.Internal
|
||||
{
|
||||
internal static class HandshakeHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets request headers needed process the handshake on the server.
|
||||
/// </summary>
|
||||
public static readonly IEnumerable<string> NeededHeaders = new[]
|
||||
{
|
||||
Constants.Headers.Upgrade,
|
||||
Constants.Headers.Connection,
|
||||
Constants.Headers.SecWebSocketKey,
|
||||
Constants.Headers.SecWebSocketVersion
|
||||
};
|
||||
|
||||
// Verify Method, Upgrade, Connection, version, key, etc..
|
||||
public static bool CheckSupportedWebSocketRequest(string method, IEnumerable<KeyValuePair<string, string>> headers)
|
||||
{
|
||||
bool validUpgrade = false, validConnection = false, validKey = false, validVersion = false;
|
||||
|
||||
if (!string.Equals("GET", method, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var pair in headers)
|
||||
{
|
||||
if (string.Equals(Constants.Headers.Connection, pair.Key, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (string.Equals(Constants.Headers.ConnectionUpgrade, pair.Value, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
validConnection = true;
|
||||
}
|
||||
}
|
||||
else if (string.Equals(Constants.Headers.Upgrade, pair.Key, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (string.Equals(Constants.Headers.UpgradeWebSocket, pair.Value, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
validUpgrade = true;
|
||||
}
|
||||
}
|
||||
else if (string.Equals(Constants.Headers.SecWebSocketVersion, pair.Key, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (string.Equals(Constants.Headers.SupportedVersion, pair.Value, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
validVersion = true;
|
||||
}
|
||||
}
|
||||
else if (string.Equals(Constants.Headers.SecWebSocketKey, pair.Key, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
validKey = IsRequestKeyValid(pair.Value);
|
||||
}
|
||||
}
|
||||
|
||||
return validConnection && validUpgrade && validVersion && validKey;
|
||||
}
|
||||
|
||||
public static void GenerateResponseHeaders(string key, string subProtocol, IHeaderDictionary headers)
|
||||
{
|
||||
headers[Constants.Headers.Connection] = Constants.Headers.ConnectionUpgrade;
|
||||
headers[Constants.Headers.Upgrade] = Constants.Headers.UpgradeWebSocket;
|
||||
headers[Constants.Headers.SecWebSocketAccept] = CreateResponseKey(key);
|
||||
if (!string.IsNullOrWhiteSpace(subProtocol))
|
||||
{
|
||||
headers[Constants.Headers.SecWebSocketProtocol] = subProtocol;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the Sec-WebSocket-Key request header
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsRequestKeyValid(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
try
|
||||
{
|
||||
byte[] data = Convert.FromBase64String(value);
|
||||
return data.Length == 16;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static string CreateResponseKey(string requestKey)
|
||||
{
|
||||
// "The value of this header field is constructed by concatenating /key/, defined above in step 4
|
||||
// in Section 4.2.2, with the string "258EAFA5- E914-47DA-95CA-C5AB0DC85B11", taking the SHA-1 hash of
|
||||
// this concatenated value to obtain a 20-byte value and base64-encoding"
|
||||
// https://tools.ietf.org/html/rfc6455#section-4.2.2
|
||||
|
||||
if (requestKey == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(requestKey));
|
||||
}
|
||||
|
||||
using (var algorithm = SHA1.Create())
|
||||
{
|
||||
string merged = requestKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||
byte[] mergedBytes = Encoding.UTF8.GetBytes(merged);
|
||||
byte[] hashedBytes = algorithm.ComputeHash(mergedBytes);
|
||||
return Convert.ToBase64String(hashedBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Description>ASP.NET Core web socket middleware for use on top of opaque servers.</Description>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<PackageTags>aspnetcore</PackageTags>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="$(MicrosoftAspNetCoreHttpExtensionsPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="$(MicrosoftExtensionsLoggingAbstractionsPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="$(MicrosoftExtensionsOptionsPackageVersion)" />
|
||||
<PackageReference Include="System.Net.WebSockets.WebSocketProtocol" Version="$(SystemNetWebSocketsWebSocketProtocolPackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,166 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.WebSockets;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.WebSockets.Internal;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
namespace Microsoft.AspNetCore.WebSockets
|
||||
{
|
||||
public class WebSocketMiddleware
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly WebSocketOptions _options;
|
||||
private readonly ILogger _logger;
|
||||
private readonly bool _anyOriginAllowed;
|
||||
private readonly List<string> _allowedOrigins;
|
||||
|
||||
public WebSocketMiddleware(RequestDelegate next, IOptions<WebSocketOptions> options, ILoggerFactory loggerFactory)
|
||||
{
|
||||
if (next == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(next));
|
||||
}
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
_next = next;
|
||||
_options = options.Value;
|
||||
_allowedOrigins = _options.AllowedOrigins.Select(o => o.ToLowerInvariant()).ToList();
|
||||
_anyOriginAllowed = _options.AllowedOrigins.Count == 0 || _options.AllowedOrigins.Contains("*", StringComparer.Ordinal);
|
||||
|
||||
_logger = loggerFactory.CreateLogger<WebSocketMiddleware>();
|
||||
|
||||
// TODO: validate options.
|
||||
}
|
||||
|
||||
[Obsolete("This constructor has been replaced with an equivalent constructor which requires an ILoggerFactory.")]
|
||||
public WebSocketMiddleware(RequestDelegate next, IOptions<WebSocketOptions> options)
|
||||
: this(next, options, NullLoggerFactory.Instance)
|
||||
{
|
||||
}
|
||||
|
||||
public Task Invoke(HttpContext context)
|
||||
{
|
||||
// Detect if an opaque upgrade is available. If so, add a websocket upgrade.
|
||||
var upgradeFeature = context.Features.Get<IHttpUpgradeFeature>();
|
||||
if (upgradeFeature != null && context.Features.Get<IHttpWebSocketFeature>() == null)
|
||||
{
|
||||
var webSocketFeature = new UpgradeHandshake(context, upgradeFeature, _options);
|
||||
context.Features.Set<IHttpWebSocketFeature>(webSocketFeature);
|
||||
|
||||
if (!_anyOriginAllowed)
|
||||
{
|
||||
// Check for Origin header
|
||||
var originHeader = context.Request.Headers[HeaderNames.Origin];
|
||||
|
||||
if (!StringValues.IsNullOrEmpty(originHeader) && webSocketFeature.IsWebSocketRequest)
|
||||
{
|
||||
// Check allowed origins to see if request is allowed
|
||||
if (!_allowedOrigins.Contains(originHeader.ToString(), StringComparer.Ordinal))
|
||||
{
|
||||
_logger.LogDebug("Request origin {Origin} is not in the list of allowed origins.", originHeader);
|
||||
context.Response.StatusCode = StatusCodes.Status403Forbidden;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return _next(context);
|
||||
}
|
||||
|
||||
private class UpgradeHandshake : IHttpWebSocketFeature
|
||||
{
|
||||
private readonly HttpContext _context;
|
||||
private readonly IHttpUpgradeFeature _upgradeFeature;
|
||||
private readonly WebSocketOptions _options;
|
||||
private bool? _isWebSocketRequest;
|
||||
|
||||
public UpgradeHandshake(HttpContext context, IHttpUpgradeFeature upgradeFeature, WebSocketOptions options)
|
||||
{
|
||||
_context = context;
|
||||
_upgradeFeature = upgradeFeature;
|
||||
_options = options;
|
||||
}
|
||||
|
||||
public bool IsWebSocketRequest
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_isWebSocketRequest == null)
|
||||
{
|
||||
if (!_upgradeFeature.IsUpgradableRequest)
|
||||
{
|
||||
_isWebSocketRequest = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
var headers = new List<KeyValuePair<string, string>>();
|
||||
foreach (string headerName in HandshakeHelpers.NeededHeaders)
|
||||
{
|
||||
foreach (var value in _context.Request.Headers.GetCommaSeparatedValues(headerName))
|
||||
{
|
||||
headers.Add(new KeyValuePair<string, string>(headerName, value));
|
||||
}
|
||||
}
|
||||
_isWebSocketRequest = HandshakeHelpers.CheckSupportedWebSocketRequest(_context.Request.Method, headers);
|
||||
}
|
||||
}
|
||||
return _isWebSocketRequest.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<WebSocket> AcceptAsync(WebSocketAcceptContext acceptContext)
|
||||
{
|
||||
if (!IsWebSocketRequest)
|
||||
{
|
||||
throw new InvalidOperationException("Not a WebSocket request."); // TODO: LOC
|
||||
}
|
||||
|
||||
string subProtocol = null;
|
||||
if (acceptContext != null)
|
||||
{
|
||||
subProtocol = acceptContext.SubProtocol;
|
||||
}
|
||||
|
||||
TimeSpan keepAliveInterval = _options.KeepAliveInterval;
|
||||
int receiveBufferSize = _options.ReceiveBufferSize;
|
||||
var advancedAcceptContext = acceptContext as ExtendedWebSocketAcceptContext;
|
||||
if (advancedAcceptContext != null)
|
||||
{
|
||||
if (advancedAcceptContext.ReceiveBufferSize.HasValue)
|
||||
{
|
||||
receiveBufferSize = advancedAcceptContext.ReceiveBufferSize.Value;
|
||||
}
|
||||
if (advancedAcceptContext.KeepAliveInterval.HasValue)
|
||||
{
|
||||
keepAliveInterval = advancedAcceptContext.KeepAliveInterval.Value;
|
||||
}
|
||||
}
|
||||
|
||||
string key = string.Join(", ", _context.Request.Headers[Constants.Headers.SecWebSocketKey]);
|
||||
|
||||
HandshakeHelpers.GenerateResponseHeaders(key, subProtocol, _context.Response.Headers);
|
||||
|
||||
Stream opaqueTransport = await _upgradeFeature.UpgradeAsync(); // Sets status code to 101
|
||||
|
||||
return WebSocketProtocol.CreateFromStream(opaqueTransport, isServer: true, subProtocol: subProtocol, keepAliveInterval: keepAliveInterval);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.WebSockets;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Builder
|
||||
{
|
||||
public static class WebSocketMiddlewareExtensions
|
||||
{
|
||||
public static IApplicationBuilder UseWebSockets(this IApplicationBuilder app)
|
||||
{
|
||||
if (app == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(app));
|
||||
}
|
||||
|
||||
return app.UseMiddleware<WebSocketMiddleware>();
|
||||
}
|
||||
|
||||
public static IApplicationBuilder UseWebSockets(this IApplicationBuilder app, WebSocketOptions options)
|
||||
{
|
||||
if (app == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(app));
|
||||
}
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
return app.UseMiddleware<WebSocketMiddleware>(Options.Create(options));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNetCore.Builder
|
||||
{
|
||||
/// <summary>
|
||||
/// Configuration options for the WebSocketMiddleware
|
||||
/// </summary>
|
||||
public class WebSocketOptions
|
||||
{
|
||||
public WebSocketOptions()
|
||||
{
|
||||
KeepAliveInterval = TimeSpan.FromMinutes(2);
|
||||
ReceiveBufferSize = 4 * 1024;
|
||||
AllowedOrigins = new List<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the frequency at which to send Ping/Pong keep-alive control frames.
|
||||
/// The default is two minutes.
|
||||
/// </summary>
|
||||
public TimeSpan KeepAliveInterval { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the size of the protocol buffer used to receive and parse frames.
|
||||
/// The default is 4kb.
|
||||
/// </summary>
|
||||
public int ReceiveBufferSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Set the Origin header values allowed for WebSocket requests to prevent Cross-Site WebSocket Hijacking.
|
||||
/// By default all Origins are allowed.
|
||||
/// </summary>
|
||||
public IList<string> AllowedOrigins { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AspNetCore.WebSockets
|
||||
{
|
||||
public static class WebSocketsDependencyInjectionExtensions
|
||||
{
|
||||
public static IServiceCollection AddWebSockets(this IServiceCollection services, Action<WebSocketOptions> configure)
|
||||
{
|
||||
return services.Configure(configure);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,231 @@
|
|||
{
|
||||
"AssemblyIdentity": "Microsoft.AspNetCore.WebSockets, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
|
||||
"Types": [
|
||||
{
|
||||
"Name": "Microsoft.AspNetCore.Builder.WebSocketMiddlewareExtensions",
|
||||
"Visibility": "Public",
|
||||
"Kind": "Class",
|
||||
"Abstract": true,
|
||||
"Static": true,
|
||||
"Sealed": true,
|
||||
"ImplementedInterfaces": [],
|
||||
"Members": [
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "UseWebSockets",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "app",
|
||||
"Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder"
|
||||
}
|
||||
],
|
||||
"ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder",
|
||||
"Static": true,
|
||||
"Extension": true,
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "UseWebSockets",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "app",
|
||||
"Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder"
|
||||
},
|
||||
{
|
||||
"Name": "options",
|
||||
"Type": "Microsoft.AspNetCore.Builder.WebSocketOptions"
|
||||
}
|
||||
],
|
||||
"ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder",
|
||||
"Static": true,
|
||||
"Extension": true,
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
}
|
||||
],
|
||||
"GenericParameters": []
|
||||
},
|
||||
{
|
||||
"Name": "Microsoft.AspNetCore.Builder.WebSocketOptions",
|
||||
"Visibility": "Public",
|
||||
"Kind": "Class",
|
||||
"ImplementedInterfaces": [],
|
||||
"Members": [
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "get_KeepAliveInterval",
|
||||
"Parameters": [],
|
||||
"ReturnType": "System.TimeSpan",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "set_KeepAliveInterval",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "value",
|
||||
"Type": "System.TimeSpan"
|
||||
}
|
||||
],
|
||||
"ReturnType": "System.Void",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "get_ReceiveBufferSize",
|
||||
"Parameters": [],
|
||||
"ReturnType": "System.Int32",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "set_ReceiveBufferSize",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "value",
|
||||
"Type": "System.Int32"
|
||||
}
|
||||
],
|
||||
"ReturnType": "System.Void",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Constructor",
|
||||
"Name": ".ctor",
|
||||
"Parameters": [],
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
}
|
||||
],
|
||||
"GenericParameters": []
|
||||
},
|
||||
{
|
||||
"Name": "Microsoft.AspNetCore.WebSockets.ExtendedWebSocketAcceptContext",
|
||||
"Visibility": "Public",
|
||||
"Kind": "Class",
|
||||
"BaseType": "Microsoft.AspNetCore.Http.WebSocketAcceptContext",
|
||||
"ImplementedInterfaces": [],
|
||||
"Members": [
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "get_SubProtocol",
|
||||
"Parameters": [],
|
||||
"ReturnType": "System.String",
|
||||
"Virtual": true,
|
||||
"Override": true,
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "set_SubProtocol",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "value",
|
||||
"Type": "System.String"
|
||||
}
|
||||
],
|
||||
"ReturnType": "System.Void",
|
||||
"Virtual": true,
|
||||
"Override": true,
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "get_ReceiveBufferSize",
|
||||
"Parameters": [],
|
||||
"ReturnType": "System.Nullable<System.Int32>",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "set_ReceiveBufferSize",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "value",
|
||||
"Type": "System.Nullable<System.Int32>"
|
||||
}
|
||||
],
|
||||
"ReturnType": "System.Void",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "get_KeepAliveInterval",
|
||||
"Parameters": [],
|
||||
"ReturnType": "System.Nullable<System.TimeSpan>",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "set_KeepAliveInterval",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "value",
|
||||
"Type": "System.Nullable<System.TimeSpan>"
|
||||
}
|
||||
],
|
||||
"ReturnType": "System.Void",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Constructor",
|
||||
"Name": ".ctor",
|
||||
"Parameters": [],
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
}
|
||||
],
|
||||
"GenericParameters": []
|
||||
},
|
||||
{
|
||||
"Name": "Microsoft.AspNetCore.WebSockets.WebSocketMiddleware",
|
||||
"Visibility": "Public",
|
||||
"Kind": "Class",
|
||||
"ImplementedInterfaces": [],
|
||||
"Members": [
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "Invoke",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "context",
|
||||
"Type": "Microsoft.AspNetCore.Http.HttpContext"
|
||||
}
|
||||
],
|
||||
"ReturnType": "System.Threading.Tasks.Task",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Constructor",
|
||||
"Name": ".ctor",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "next",
|
||||
"Type": "Microsoft.AspNetCore.Http.RequestDelegate"
|
||||
},
|
||||
{
|
||||
"Name": "options",
|
||||
"Type": "Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Builder.WebSocketOptions>"
|
||||
}
|
||||
],
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
}
|
||||
],
|
||||
"GenericParameters": []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netcoreapp2.2</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="TestResources\testCert.pfx" CopyToOutputDirectory="PreserveNewest" CopyToPublishDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.WebSockets\Microsoft.AspNetCore.WebSockets.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(OS)' == 'Windows_NT'">
|
||||
<PackageReference Include="Microsoft.AspNetCore.AspNetCoreModule" Version="$(MicrosoftAspNetCoreAspNetCoreModulePackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Diagnostics" Version="$(MicrosoftAspNetCoreDiagnosticsPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.HttpSys" Version="$(MicrosoftAspNetCoreServerHttpSysPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="$(MicrosoftAspNetCoreServerIISIntegrationPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="$(MicrosoftAspNetCoreServerKestrelPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Https" Version="$(MicrosoftAspNetCoreServerKestrelHttpsPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="$(MicrosoftExtensionsConfigurationCommandLinePackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(MicrosoftExtensionsLoggingConsolePackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Runtime.Loader;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace AutobahnTestApp
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
var scenarioName = "Unknown";
|
||||
var config = new ConfigurationBuilder()
|
||||
.AddCommandLine(args)
|
||||
.Build();
|
||||
|
||||
var builder = new WebHostBuilder()
|
||||
.ConfigureLogging(loggingBuilder => loggingBuilder.AddConsole())
|
||||
.UseConfiguration(config)
|
||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||
.UseIISIntegration()
|
||||
.UseStartup<Startup>();
|
||||
|
||||
if (string.Equals(builder.GetSetting("server"), "Microsoft.AspNetCore.Server.HttpSys", System.StringComparison.Ordinal))
|
||||
{
|
||||
scenarioName = "HttpSysServer";
|
||||
Console.WriteLine("Using HttpSys server");
|
||||
builder.UseHttpSys();
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("ASPNETCORE_PORT")))
|
||||
{
|
||||
// ANCM is hosting the process.
|
||||
// The port will not yet be configured at this point, but will also not require HTTPS.
|
||||
scenarioName = "AspNetCoreModule";
|
||||
Console.WriteLine("Detected ANCM, using Kestrel");
|
||||
builder.UseKestrel();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Also check "server.urls" for back-compat.
|
||||
var urls = builder.GetSetting(WebHostDefaults.ServerUrlsKey) ?? builder.GetSetting("server.urls");
|
||||
builder.UseSetting(WebHostDefaults.ServerUrlsKey, string.Empty);
|
||||
|
||||
Console.WriteLine($"Using Kestrel, URL: {urls}");
|
||||
|
||||
if (urls.Contains(";"))
|
||||
{
|
||||
throw new NotSupportedException("This test app does not support multiple endpoints.");
|
||||
}
|
||||
|
||||
var uri = new Uri(urls);
|
||||
|
||||
builder.UseKestrel(options =>
|
||||
{
|
||||
options.Listen(IPAddress.Loopback, uri.Port, listenOptions =>
|
||||
{
|
||||
if (uri.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
scenarioName = "Kestrel(SSL)";
|
||||
var certPath = Path.Combine(AppContext.BaseDirectory, "TestResources", "testCert.pfx");
|
||||
Console.WriteLine($"Using SSL with certificate: {certPath}");
|
||||
listenOptions.UseHttps(certPath, "testPassword");
|
||||
}
|
||||
else
|
||||
{
|
||||
scenarioName = "Kestrel(NonSSL)";
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
var host = builder.Build();
|
||||
|
||||
AppDomain.CurrentDomain.UnhandledException += (_, a) =>
|
||||
{
|
||||
Console.WriteLine($"Unhandled exception (Scenario: {scenarioName}): {a.ExceptionObject.ToString()}");
|
||||
};
|
||||
|
||||
Console.WriteLine($"Starting Server for Scenario: {scenarioName}");
|
||||
host.Run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:6155/",
|
||||
"sslPort": 44371
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "ManagedSockets"
|
||||
}
|
||||
},
|
||||
"AutobahnTestApp": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "http://localhost:5000",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "ManagedSockets"
|
||||
}
|
||||
},
|
||||
"AutobahnTestApp (SSL)": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "https://localhost:5443",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "ManagedSockets"
|
||||
}
|
||||
},
|
||||
"WebListener": {
|
||||
"commandName": "Project",
|
||||
"commandLineArgs": "--server Microsoft.AspNetCore.Server.HttpSys",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "ManagedSockets"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
# Autobahn Testing
|
||||
|
||||
This application is used to provide the server for the [Autobahn Test Suite](http://autobahn.ws/testsuite) 'fuzzingclient' mode to test. It is a simple echo server that echos each frame received back to the client.
|
||||
|
||||
In order to run these tests you must install CPython 2.7, Pip, and the test suite modules. You must also have
|
||||
the `wstest` executable provided by the Autobahn Suite on the `PATH`. See http://autobahn.ws/testsuite/installation.html#installation for more info
|
||||
|
||||
Once Autobahn is installed, launch this application in the desired configuration (in IIS Express, or using Kestrel directly) from Visual Studio and get the WebSocket URL from the HTTP response. Use that URL in place of `ws://server:1234` and invoke the `scripts\RunAutobahnTests.ps1` script in this project like so:
|
||||
|
||||
```
|
||||
> .\scripts\RunAutobahnTests.ps1 -ServerUrl ws://server:1234
|
||||
```
|
||||
|
||||
By default, all cases are run and the report is written to the `autobahnreports` sub-directory of the directory in which you run the script. You can change either by using the `-Cases` and `-OutputDir` switches, use `.\script\RunAutobahnTests.ps1 -?` for help.
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
using System;
|
||||
using System.Net.WebSockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace AutobahnTestApp
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
|
||||
{
|
||||
app.UseWebSockets();
|
||||
|
||||
var logger = loggerFactory.CreateLogger<Startup>();
|
||||
app.Use(async (context, next) =>
|
||||
{
|
||||
if (context.WebSockets.IsWebSocketRequest)
|
||||
{
|
||||
logger.LogInformation("Received WebSocket request");
|
||||
using (var webSocket = await context.WebSockets.AcceptWebSocketAsync())
|
||||
{
|
||||
await Echo(webSocket, context.RequestAborted);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var wsScheme = context.Request.IsHttps ? "wss" : "ws";
|
||||
var wsUrl = $"{wsScheme}://{context.Request.Host.Host}:{context.Request.Host.Port}{context.Request.Path}";
|
||||
await context.Response.WriteAsync($"Ready to accept a WebSocket request at: {wsUrl}");
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private async Task Echo(WebSocket webSocket, CancellationToken cancellationToken)
|
||||
{
|
||||
var buffer = new byte[1024 * 4];
|
||||
var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), cancellationToken);
|
||||
while (!result.CloseStatus.HasValue)
|
||||
{
|
||||
await webSocket.SendAsync(new ArraySegment<byte>(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, cancellationToken);
|
||||
result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), cancellationToken);
|
||||
}
|
||||
await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
|
@ -0,0 +1,3 @@
|
|||
The password for this is 'testPassword'
|
||||
|
||||
DO NOT EVER TRUST THIS CERT. The private key for it is publicly released.
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
#
|
||||
# RunAutobahnTests.ps1
|
||||
#
|
||||
param([Parameter(Mandatory=$true)][string]$ServerUrl, [string[]]$Cases = @("*"), [string]$OutputDir, [int]$Iterations = 1)
|
||||
|
||||
if(!(Get-Command wstest -ErrorAction SilentlyContinue)) {
|
||||
throw "Missing required command 'wstest'. See README.md in Microsoft.AspNetCore.WebSockets.Server.Test project for information on installing Autobahn Test Suite."
|
||||
}
|
||||
|
||||
if(!$OutputDir) {
|
||||
$OutputDir = Convert-Path "."
|
||||
$OutputDir = Join-Path $OutputDir "autobahnreports"
|
||||
}
|
||||
|
||||
Write-Host "Launching Autobahn Test Suite ($Iterations iteration(s))..."
|
||||
|
||||
0..($Iterations-1) | % {
|
||||
$iteration = $_
|
||||
|
||||
$Spec = Convert-Path (Join-Path $PSScriptRoot "autobahn.spec.json")
|
||||
|
||||
$CasesArray = [string]::Join(",", @($Cases | ForEach-Object { "`"$_`"" }))
|
||||
|
||||
$SpecJson = [IO.File]::ReadAllText($Spec).Replace("OUTPUTDIR", $OutputDir.Replace("\", "\\")).Replace("WEBSOCKETURL", $ServerUrl).Replace("`"CASES`"", $CasesArray)
|
||||
|
||||
$TempFile = [IO.Path]::GetTempFileName()
|
||||
|
||||
try {
|
||||
[IO.File]::WriteAllText($TempFile, $SpecJson)
|
||||
$wstestOutput = & wstest -m fuzzingclient -s $TempFile
|
||||
} finally {
|
||||
if(Test-Path $TempFile) {
|
||||
rm $TempFile
|
||||
}
|
||||
}
|
||||
|
||||
$report = ConvertFrom-Json ([IO.File]::ReadAllText((Convert-Path (Join-Path $OutputDir "index.json"))))
|
||||
|
||||
$report.Server | gm | ? { $_.MemberType -eq "NoteProperty" } | % {
|
||||
$case = $report.Server."$($_.Name)"
|
||||
Write-Host "[#$($iteration.ToString().PadRight(2))] [$($case.behavior.PadRight(6))] Case $($_.Name)"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"options": { "failByDrop": false },
|
||||
"outdir": "OUTPUTDIR",
|
||||
"servers": [
|
||||
{
|
||||
"agent": "Server",
|
||||
"url": "WEBSOCKETURL",
|
||||
"options": { "version": 18 }
|
||||
}
|
||||
],
|
||||
"cases": ["CASES"],
|
||||
"exclude-cases": ["12.*", "13.*"],
|
||||
"exclude-agent-cases": {}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<Project>
|
||||
<Import Project="..\Directory.Build.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<DeveloperBuildTestTfms>netcoreapp2.2</DeveloperBuildTestTfms>
|
||||
<StandardTestTfms>$(DeveloperBuildTestTfms)</StandardTestTfms>
|
||||
|
||||
<StandardTestTfms Condition=" '$(DeveloperBuild)' != 'true' AND '$(OS)' == 'Windows_NT' ">$(StandardTestTfms);net461</StandardTestTfms>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<!--
|
||||
Workaround for "Use executable flags in Microsoft.NET.Test.Sdk" (https://github.com/Microsoft/vstest/issues/792).
|
||||
Remove when fixed.
|
||||
-->
|
||||
<HasRuntimeOutput>true</HasRuntimeOutput>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Internal.AspNetCore.Sdk" PrivateAssets="All" Version="$(InternalAspNetCoreSdkPackageVersion)" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.AspNetCore.WebSockets.ConformanceTest.Autobahn
|
||||
{
|
||||
public class AutobahnCaseResult
|
||||
{
|
||||
public string Name { get; }
|
||||
public string ActualBehavior { get; }
|
||||
|
||||
public AutobahnCaseResult(string name, string actualBehavior)
|
||||
{
|
||||
Name = name;
|
||||
ActualBehavior = actualBehavior;
|
||||
}
|
||||
|
||||
public static AutobahnCaseResult FromJson(JProperty prop)
|
||||
{
|
||||
var caseObj = (JObject)prop.Value;
|
||||
var actualBehavior = (string)caseObj["behavior"];
|
||||
return new AutobahnCaseResult(prop.Name, actualBehavior);
|
||||
}
|
||||
|
||||
public bool BehaviorIs(params string[] behaviors)
|
||||
{
|
||||
return behaviors.Any(b => string.Equals(b, ActualBehavior, StringComparison.Ordinal));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Server.IntegrationTesting;
|
||||
|
||||
namespace Microsoft.AspNetCore.WebSockets.ConformanceTest.Autobahn
|
||||
{
|
||||
public class AutobahnExpectations
|
||||
{
|
||||
private Dictionary<string, Expectation> _expectations = new Dictionary<string, Expectation>();
|
||||
public bool Ssl { get; }
|
||||
public ServerType Server { get; }
|
||||
public string Environment { get; }
|
||||
|
||||
public AutobahnExpectations(ServerType server, bool ssl, string environment)
|
||||
{
|
||||
Server = server;
|
||||
Ssl = ssl;
|
||||
Environment = environment;
|
||||
}
|
||||
|
||||
public AutobahnExpectations Fail(params string[] caseSpecs) => Expect(Expectation.Fail, caseSpecs);
|
||||
public AutobahnExpectations NonStrict(params string[] caseSpecs) => Expect(Expectation.NonStrict, caseSpecs);
|
||||
public AutobahnExpectations OkOrFail(params string[] caseSpecs) => Expect(Expectation.OkOrFail, caseSpecs);
|
||||
|
||||
public AutobahnExpectations Expect(Expectation expectation, params string[] caseSpecs)
|
||||
{
|
||||
foreach (var caseSpec in caseSpecs)
|
||||
{
|
||||
_expectations[caseSpec] = expectation;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
internal void Verify(AutobahnServerResult serverResult, StringBuilder failures)
|
||||
{
|
||||
foreach (var caseResult in serverResult.Cases)
|
||||
{
|
||||
// If this is an informational test result, we can't compare it to anything
|
||||
if (!string.Equals(caseResult.ActualBehavior, "INFORMATIONAL", StringComparison.Ordinal))
|
||||
{
|
||||
Expectation expectation;
|
||||
if (!_expectations.TryGetValue(caseResult.Name, out expectation))
|
||||
{
|
||||
expectation = Expectation.Ok;
|
||||
}
|
||||
|
||||
switch (expectation)
|
||||
{
|
||||
case Expectation.Fail:
|
||||
if (!caseResult.BehaviorIs("FAILED"))
|
||||
{
|
||||
failures.AppendLine($"Case {serverResult.Name}:{caseResult.Name}. Expected 'FAILED', but got '{caseResult.ActualBehavior}'");
|
||||
}
|
||||
break;
|
||||
case Expectation.NonStrict:
|
||||
if (!caseResult.BehaviorIs("NON-STRICT"))
|
||||
{
|
||||
failures.AppendLine($"Case {serverResult.Name}:{caseResult.Name}. Expected 'NON-STRICT', but got '{caseResult.ActualBehavior}'");
|
||||
}
|
||||
break;
|
||||
case Expectation.Ok:
|
||||
if (!caseResult.BehaviorIs("NON-STRICT") && !caseResult.BehaviorIs("OK"))
|
||||
{
|
||||
failures.AppendLine($"Case {serverResult.Name}:{caseResult.Name}. Expected 'NON-STRICT' or 'OK', but got '{caseResult.ActualBehavior}'");
|
||||
}
|
||||
break;
|
||||
case Expectation.OkOrFail:
|
||||
if (!caseResult.BehaviorIs("NON-STRICT") && !caseResult.BehaviorIs("FAILED") && !caseResult.BehaviorIs("OK"))
|
||||
{
|
||||
failures.AppendLine($"Case {serverResult.Name}:{caseResult.Name}. Expected 'FAILED', 'NON-STRICT' or 'OK', but got '{caseResult.ActualBehavior}'");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.AspNetCore.WebSockets.ConformanceTest.Autobahn
|
||||
{
|
||||
public class AutobahnResult
|
||||
{
|
||||
public IEnumerable<AutobahnServerResult> Servers { get; }
|
||||
|
||||
public AutobahnResult(IEnumerable<AutobahnServerResult> servers)
|
||||
{
|
||||
Servers = servers;
|
||||
}
|
||||
|
||||
public static AutobahnResult FromReportJson(JObject indexJson)
|
||||
{
|
||||
// Load the report
|
||||
return new AutobahnResult(indexJson.Properties().Select(AutobahnServerResult.FromJson));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Server.IntegrationTesting;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.AspNetCore.WebSockets.ConformanceTest.Autobahn
|
||||
{
|
||||
public class AutobahnServerResult
|
||||
{
|
||||
public ServerType Server { get; }
|
||||
public bool Ssl { get; }
|
||||
public string Environment { get; }
|
||||
public string Name { get; }
|
||||
public IEnumerable<AutobahnCaseResult> Cases { get; }
|
||||
|
||||
public AutobahnServerResult(string name, IEnumerable<AutobahnCaseResult> cases)
|
||||
{
|
||||
Name = name;
|
||||
|
||||
var splat = name.Split('|');
|
||||
if (splat.Length < 3)
|
||||
{
|
||||
throw new FormatException("Results incorrectly formatted");
|
||||
}
|
||||
|
||||
Server = (ServerType)Enum.Parse(typeof(ServerType), splat[0]);
|
||||
Ssl = string.Equals(splat[1], "SSL", StringComparison.Ordinal);
|
||||
Environment = splat[2];
|
||||
Cases = cases;
|
||||
}
|
||||
|
||||
public static AutobahnServerResult FromJson(JProperty prop)
|
||||
{
|
||||
var valueObj = ((JObject)prop.Value);
|
||||
return new AutobahnServerResult(prop.Name, valueObj.Properties().Select(AutobahnCaseResult.FromJson));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.AspNetCore.WebSockets.ConformanceTest.Autobahn
|
||||
{
|
||||
public class AutobahnSpec
|
||||
{
|
||||
public string OutputDirectory { get; }
|
||||
public IList<ServerSpec> Servers { get; } = new List<ServerSpec>();
|
||||
public IList<string> Cases { get; } = new List<string>();
|
||||
public IList<string> ExcludedCases { get; } = new List<string>();
|
||||
|
||||
public AutobahnSpec(string outputDirectory)
|
||||
{
|
||||
OutputDirectory = outputDirectory;
|
||||
}
|
||||
|
||||
public AutobahnSpec WithServer(string name, string url)
|
||||
{
|
||||
Servers.Add(new ServerSpec(name, url));
|
||||
return this;
|
||||
}
|
||||
|
||||
public AutobahnSpec IncludeCase(params string[] caseSpecs)
|
||||
{
|
||||
foreach (var caseSpec in caseSpecs)
|
||||
{
|
||||
Cases.Add(caseSpec);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public AutobahnSpec ExcludeCase(params string[] caseSpecs)
|
||||
{
|
||||
foreach (var caseSpec in caseSpecs)
|
||||
{
|
||||
ExcludedCases.Add(caseSpec);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public void WriteJson(string file)
|
||||
{
|
||||
File.WriteAllText(file, GetJson().ToString(Formatting.Indented));
|
||||
}
|
||||
|
||||
public JObject GetJson() => new JObject(
|
||||
new JProperty("options", new JObject(
|
||||
new JProperty("failByDrop", false))),
|
||||
new JProperty("outdir", OutputDirectory),
|
||||
new JProperty("servers", new JArray(Servers.Select(s => s.GetJson()).ToArray())),
|
||||
new JProperty("cases", new JArray(Cases.ToArray())),
|
||||
new JProperty("exclude-cases", new JArray(ExcludedCases.ToArray())),
|
||||
new JProperty("exclude-agent-cases", new JObject()));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,197 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Security.Authentication;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Server.IntegrationTesting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.WebSockets.ConformanceTest.Autobahn
|
||||
{
|
||||
public class AutobahnTester : IDisposable
|
||||
{
|
||||
private readonly List<ApplicationDeployer> _deployers = new List<ApplicationDeployer>();
|
||||
private readonly List<DeploymentResult> _deployments = new List<DeploymentResult>();
|
||||
private readonly List<AutobahnExpectations> _expectations = new List<AutobahnExpectations>();
|
||||
private readonly ILoggerFactory _loggerFactory;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public AutobahnSpec Spec { get; }
|
||||
|
||||
public AutobahnTester(ILoggerFactory loggerFactory, AutobahnSpec baseSpec)
|
||||
{
|
||||
_loggerFactory = loggerFactory;
|
||||
_logger = _loggerFactory.CreateLogger("AutobahnTester");
|
||||
|
||||
Spec = baseSpec;
|
||||
}
|
||||
|
||||
public async Task<AutobahnResult> Run(CancellationToken cancellationToken)
|
||||
{
|
||||
var specFile = Path.GetTempFileName();
|
||||
try
|
||||
{
|
||||
// Start pinging the servers to see that they're still running
|
||||
var pingCts = new CancellationTokenSource();
|
||||
var pinger = new Timer(state => Pinger((CancellationToken)state), pingCts.Token, TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(10));
|
||||
|
||||
Spec.WriteJson(specFile);
|
||||
|
||||
// Run the test (write something to the console so people know this will take a while...)
|
||||
_logger.LogInformation("Using 'wstest' from: {WsTestPath}", Wstest.Default.Location);
|
||||
_logger.LogInformation("Now launching Autobahn Test Suite. This will take a while.");
|
||||
var exitCode = await Wstest.Default.ExecAsync("-m fuzzingclient -s " + specFile, cancellationToken, _loggerFactory.CreateLogger("wstest"));
|
||||
if (exitCode != 0)
|
||||
{
|
||||
throw new Exception("wstest failed");
|
||||
}
|
||||
|
||||
pingCts.Cancel();
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (File.Exists(specFile))
|
||||
{
|
||||
File.Delete(specFile);
|
||||
}
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// Parse the output.
|
||||
var outputFile = Path.Combine(Directory.GetCurrentDirectory(), "..", "..", "..", Spec.OutputDirectory, "index.json");
|
||||
using (var reader = new StreamReader(File.OpenRead(outputFile)))
|
||||
{
|
||||
return AutobahnResult.FromReportJson(JObject.Parse(await reader.ReadToEndAsync()));
|
||||
}
|
||||
}
|
||||
|
||||
// Async void! It's OK here because we are running in a timer. We're just using async void to chain continuations.
|
||||
// There's nobody to await this, hence async void.
|
||||
private async void Pinger(CancellationToken token)
|
||||
{
|
||||
try
|
||||
{
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var deployment in _deployments)
|
||||
{
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var resp = await deployment.HttpClient.GetAsync("/ping", token);
|
||||
if (!resp.IsSuccessStatusCode)
|
||||
{
|
||||
_logger.LogWarning("Non-successful response when pinging {url}: {statusCode} {reasonPhrase}", deployment.ApplicationBaseUri, resp.StatusCode, resp.ReasonPhrase);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// We don't want to throw when the token fires, just stop.
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error while pinging servers");
|
||||
}
|
||||
}
|
||||
|
||||
public void Verify(AutobahnResult result)
|
||||
{
|
||||
var failures = new StringBuilder();
|
||||
foreach (var serverResult in result.Servers)
|
||||
{
|
||||
var serverExpectation = _expectations.FirstOrDefault(e => e.Server == serverResult.Server && e.Ssl == serverResult.Ssl);
|
||||
if (serverExpectation == null)
|
||||
{
|
||||
failures.AppendLine($"Expected no results for server: {serverResult.Name} but found results!");
|
||||
}
|
||||
else
|
||||
{
|
||||
serverExpectation.Verify(serverResult, failures);
|
||||
}
|
||||
}
|
||||
|
||||
Assert.True(failures.Length == 0, "Autobahn results did not meet expectations:" + Environment.NewLine + failures.ToString());
|
||||
}
|
||||
|
||||
public async Task DeployTestAndAddToSpec(ServerType server, bool ssl, string environment, CancellationToken cancellationToken, Action<AutobahnExpectations> expectationConfig = null)
|
||||
{
|
||||
var sslNamePart = ssl ? "SSL" : "NoSSL";
|
||||
var name = $"{server}|{sslNamePart}|{environment}";
|
||||
var logger = _loggerFactory.CreateLogger($"AutobahnTestApp:{server}:{sslNamePart}:{environment}");
|
||||
|
||||
var appPath = Helpers.GetApplicationPath("AutobahnTestApp");
|
||||
var configPath = Path.Combine(Directory.GetCurrentDirectory(), "..", "..", "..", "Http.config");
|
||||
var targetFramework =
|
||||
#if NETCOREAPP2_2
|
||||
"netcoreapp2.2";
|
||||
#else
|
||||
#error Target frameworks need to be updated
|
||||
#endif
|
||||
var parameters = new DeploymentParameters(appPath, server, RuntimeFlavor.CoreClr, RuntimeArchitecture.x64)
|
||||
{
|
||||
Scheme = (ssl ? Uri.UriSchemeHttps : Uri.UriSchemeHttp),
|
||||
ApplicationType = ApplicationType.Portable,
|
||||
TargetFramework = targetFramework,
|
||||
EnvironmentName = environment,
|
||||
SiteName = "HttpTestSite", // This is configured in the Http.config
|
||||
ServerConfigTemplateContent = (server == ServerType.IISExpress) ? File.ReadAllText(configPath) : null,
|
||||
};
|
||||
|
||||
var deployer = ApplicationDeployerFactory.Create(parameters, _loggerFactory);
|
||||
var result = await deployer.DeployAsync();
|
||||
_deployers.Add(deployer);
|
||||
_deployments.Add(result);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var handler = new HttpClientHandler();
|
||||
// Win7 HttpClient on NetCoreApp2.2 defaults to TLS 1.0 and won't connect to Kestrel. https://github.com/dotnet/corefx/issues/28733
|
||||
// Mac HttpClient on NetCoreApp2.0 doesn't alow you to set some combinations.
|
||||
// https://github.com/dotnet/corefx/blob/586cffcdfdf23ad6c193a4bf37fce88a1bf69508/src/System.Net.Http/src/System/Net/Http/CurlHandler/CurlHandler.SslProvider.OSX.cs#L104-L106
|
||||
handler.SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls;
|
||||
handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
|
||||
var client = result.CreateHttpClient(handler);
|
||||
|
||||
// Make sure the server works
|
||||
var resp = await RetryHelper.RetryRequest(() =>
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return client.GetAsync(result.ApplicationBaseUri);
|
||||
}, logger, CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, result.HostShutdownToken).Token);
|
||||
resp.EnsureSuccessStatusCode();
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// Add to the current spec
|
||||
var wsUrl = result.ApplicationBaseUri.Replace("https://", "wss://").Replace("http://", "ws://");
|
||||
Spec.WithServer(name, wsUrl);
|
||||
|
||||
var expectations = new AutobahnExpectations(server, ssl, environment);
|
||||
expectationConfig?.Invoke(expectations);
|
||||
_expectations.Add(expectations);
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var deployer in _deployers)
|
||||
{
|
||||
deployer.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.WebSockets.ConformanceTest.Autobahn
|
||||
{
|
||||
public class Executable
|
||||
{
|
||||
private static readonly string _exeSuffix = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".exe" : string.Empty;
|
||||
|
||||
public string Location { get; }
|
||||
|
||||
protected Executable(string path)
|
||||
{
|
||||
Location = path;
|
||||
}
|
||||
|
||||
public static string Locate(string name)
|
||||
{
|
||||
foreach (var dir in Environment.GetEnvironmentVariable("PATH").Split(Path.PathSeparator))
|
||||
{
|
||||
var candidate = Path.Combine(dir, name + _exeSuffix);
|
||||
if (File.Exists(candidate))
|
||||
{
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<int> ExecAsync(string args, CancellationToken cancellationToken, ILogger logger)
|
||||
{
|
||||
var process = new Process()
|
||||
{
|
||||
StartInfo = new ProcessStartInfo()
|
||||
{
|
||||
FileName = Location,
|
||||
Arguments = args,
|
||||
UseShellExecute = false,
|
||||
RedirectStandardError = true,
|
||||
RedirectStandardOutput = true
|
||||
},
|
||||
EnableRaisingEvents = true
|
||||
};
|
||||
var tcs = new TaskCompletionSource<int>();
|
||||
|
||||
using (cancellationToken.Register(() => Cancel(process, tcs)))
|
||||
{
|
||||
process.Exited += (_, __) => tcs.TrySetResult(process.ExitCode);
|
||||
process.OutputDataReceived += (_, a) => LogIfNotNull(logger.LogInformation, "stdout: {0}", a.Data);
|
||||
process.ErrorDataReceived += (_, a) => LogIfNotNull(logger.LogError, "stderr: {0}", a.Data);
|
||||
|
||||
process.Start();
|
||||
|
||||
process.BeginErrorReadLine();
|
||||
process.BeginOutputReadLine();
|
||||
|
||||
return await tcs.Task;
|
||||
}
|
||||
}
|
||||
|
||||
private void LogIfNotNull(Action<string, object[]> logger, string message, string data)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(data))
|
||||
{
|
||||
logger(message, new[] { data });
|
||||
}
|
||||
}
|
||||
|
||||
private static void Cancel(Process process, TaskCompletionSource<int> tcs)
|
||||
{
|
||||
if (process != null && !process.HasExited)
|
||||
{
|
||||
process.Kill();
|
||||
}
|
||||
tcs.TrySetCanceled();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
namespace Microsoft.AspNetCore.WebSockets.ConformanceTest.Autobahn
|
||||
{
|
||||
public enum Expectation
|
||||
{
|
||||
Fail,
|
||||
NonStrict,
|
||||
OkOrFail,
|
||||
Ok
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.AspNetCore.WebSockets.ConformanceTest.Autobahn
|
||||
{
|
||||
public class ServerSpec
|
||||
{
|
||||
public string Name { get; }
|
||||
public string Url { get; }
|
||||
|
||||
public ServerSpec(string name, string url)
|
||||
{
|
||||
Name = name;
|
||||
Url = url;
|
||||
}
|
||||
|
||||
public JObject GetJson() => new JObject(
|
||||
new JProperty("agent", Name),
|
||||
new JProperty("url", Url),
|
||||
new JProperty("options", new JObject(
|
||||
new JProperty("version", 18))));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Microsoft.AspNetCore.WebSockets.ConformanceTest.Autobahn
|
||||
{
|
||||
/// <summary>
|
||||
/// Wrapper around the Autobahn Test Suite's "wstest" app.
|
||||
/// </summary>
|
||||
public class Wstest : Executable
|
||||
{
|
||||
private static Lazy<Wstest> _instance = new Lazy<Wstest>(Create);
|
||||
|
||||
public static readonly string DefaultLocation = LocateWstest();
|
||||
|
||||
public static Wstest Default => _instance.Value;
|
||||
|
||||
public Wstest(string path) : base(path) { }
|
||||
|
||||
private static Wstest Create()
|
||||
{
|
||||
var location = LocateWstest();
|
||||
|
||||
return (location == null || !File.Exists(location)) ? null : new Wstest(location);
|
||||
}
|
||||
|
||||
private static string LocateWstest()
|
||||
{
|
||||
var location = Environment.GetEnvironmentVariable("ASPNETCORE_WSTEST_PATH");
|
||||
if (string.IsNullOrEmpty(location))
|
||||
{
|
||||
location = Locate("wstest");
|
||||
}
|
||||
|
||||
return location;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Server.IntegrationTesting;
|
||||
using Microsoft.AspNetCore.Testing.xunit;
|
||||
using Microsoft.AspNetCore.WebSockets.ConformanceTest.Autobahn;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Testing;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.AspNetCore.WebSockets.ConformanceTest
|
||||
{
|
||||
public class AutobahnTests : LoggedTest
|
||||
{
|
||||
private static readonly TimeSpan TestTimeout = TimeSpan.FromMinutes(3);
|
||||
|
||||
public AutobahnTests(ITestOutputHelper output) : base(output)
|
||||
{
|
||||
}
|
||||
|
||||
// Skip if wstest is not installed for now, see https://github.com/aspnet/WebSockets/issues/95
|
||||
// We will enable Wstest on every build once we've gotten the necessary infrastructure sorted out :).
|
||||
[ConditionalFact]
|
||||
[SkipIfWsTestNotPresent]
|
||||
public async Task AutobahnTestSuite()
|
||||
{
|
||||
// If we're on CI, we want to actually fail if WsTest isn't installed, rather than just skipping the test
|
||||
// The SkipIfWsTestNotPresent attribute ensures that this test isn't skipped on CI, so we just need to check that Wstest is present
|
||||
// And we use Assert.True to provide an error message
|
||||
Assert.True(Wstest.Default != null, $"The 'wstest' executable (Autobahn WebSockets Test Suite) could not be found at '{Wstest.DefaultLocation}'. Run the Build Agent setup scripts to install it or see https://github.com/crossbario/autobahn-testsuite for instructions on manual installation.");
|
||||
|
||||
using (StartLog(out var loggerFactory))
|
||||
{
|
||||
var logger = loggerFactory.CreateLogger<AutobahnTests>();
|
||||
var reportDir = Environment.GetEnvironmentVariable("AUTOBAHN_SUITES_REPORT_DIR");
|
||||
var outDir = !string.IsNullOrEmpty(reportDir) ?
|
||||
reportDir :
|
||||
Path.Combine(AppContext.BaseDirectory, "autobahnreports");
|
||||
|
||||
if (Directory.Exists(outDir))
|
||||
{
|
||||
Directory.Delete(outDir, recursive: true);
|
||||
}
|
||||
|
||||
outDir = outDir.Replace("\\", "\\\\");
|
||||
|
||||
// 9.* is Limits/Performance which is VERY SLOW; 12.*/13.* are compression which we don't implement
|
||||
var spec = new AutobahnSpec(outDir)
|
||||
.IncludeCase("*")
|
||||
.ExcludeCase("9.*", "12.*", "13.*");
|
||||
|
||||
var cts = new CancellationTokenSource();
|
||||
cts.CancelAfter(TestTimeout); // These tests generally complete in just over 1 minute.
|
||||
|
||||
using (cts.Token.Register(() => logger.LogError("Test run is taking longer than maximum duration of {timeoutMinutes:0.00} minutes. Aborting...", TestTimeout.TotalMinutes)))
|
||||
{
|
||||
AutobahnResult result;
|
||||
using (var tester = new AutobahnTester(loggerFactory, spec))
|
||||
{
|
||||
await tester.DeployTestAndAddToSpec(ServerType.Kestrel, ssl: false, environment: "ManagedSockets", cancellationToken: cts.Token);
|
||||
await tester.DeployTestAndAddToSpec(ServerType.Kestrel, ssl: true, environment: "ManagedSockets", cancellationToken: cts.Token);
|
||||
|
||||
// Windows-only WebListener tests
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
if (IsWindows8OrHigher())
|
||||
{
|
||||
// WebListener occasionally gives a non-strict response on 3.2. IIS Express seems to have the same behavior. Wonder if it's related to HttpSys?
|
||||
// For now, just allow the non-strict response, it's not a failure.
|
||||
await tester.DeployTestAndAddToSpec(ServerType.HttpSys, ssl: false, environment: "ManagedSockets", cancellationToken: cts.Token);
|
||||
}
|
||||
}
|
||||
|
||||
result = await tester.Run(cts.Token);
|
||||
tester.Verify(result);
|
||||
}
|
||||
}
|
||||
|
||||
// If it hasn't been cancelled yet, cancel the token just to be sure
|
||||
cts.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsWindows8OrHigher()
|
||||
{
|
||||
const string WindowsName = "Microsoft Windows ";
|
||||
const int VersionOffset = 18;
|
||||
|
||||
if (RuntimeInformation.OSDescription.StartsWith(WindowsName))
|
||||
{
|
||||
var versionStr = RuntimeInformation.OSDescription.Substring(VersionOffset);
|
||||
Version version;
|
||||
if (Version.TryParse(versionStr, out version))
|
||||
{
|
||||
return version.Major > 6 || (version.Major == 6 && version.Minor >= 2);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool IsIISExpress10Installed()
|
||||
{
|
||||
var pf = Environment.GetEnvironmentVariable("PROGRAMFILES");
|
||||
var iisExpressExe = Path.Combine(pf, "IIS Express", "iisexpress.exe");
|
||||
return File.Exists(iisExpressExe) && FileVersionInfo.GetVersionInfo(iisExpressExe).FileMajorPart >= 10;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Microsoft.AspNetCore.WebSockets.ConformanceTest
|
||||
{
|
||||
public class Helpers
|
||||
{
|
||||
public static string GetApplicationPath(string projectName)
|
||||
{
|
||||
var applicationBasePath = AppContext.BaseDirectory;
|
||||
|
||||
var directoryInfo = new DirectoryInfo(applicationBasePath);
|
||||
do
|
||||
{
|
||||
var solutionFileInfo = new FileInfo(Path.Combine(directoryInfo.FullName, "WebSockets.sln"));
|
||||
if (solutionFileInfo.Exists)
|
||||
{
|
||||
return Path.GetFullPath(Path.Combine(directoryInfo.FullName, "test", projectName));
|
||||
}
|
||||
|
||||
directoryInfo = directoryInfo.Parent;
|
||||
}
|
||||
while (directoryInfo.Parent != null);
|
||||
|
||||
throw new Exception($"Solution root could not be found using {applicationBasePath}");
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,17 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netcoreapp2.2</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.IntegrationTesting" Version="$(MicrosoftAspNetCoreServerIntegrationTestingPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Testing" Version="$(MicrosoftAspNetCoreTestingPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="$(MicrosoftExtensionsLoggingPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(MicrosoftExtensionsLoggingConsolePackageVersion)" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNETTestSdkPackageVersion)" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="$(XunitRunnerVisualStudioPackageVersion)" />
|
||||
<PackageReference Include="xunit" Version="$(XunitPackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
using System;
|
||||
using Microsoft.AspNetCore.Testing.xunit;
|
||||
using Microsoft.AspNetCore.WebSockets.ConformanceTest.Autobahn;
|
||||
|
||||
namespace Microsoft.AspNetCore.WebSockets.ConformanceTest
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
|
||||
public class SkipIfWsTestNotPresentAttribute : Attribute, ITestCondition
|
||||
{
|
||||
public bool IsMet => IsOnCi || Wstest.Default != null;
|
||||
public string SkipReason => "Autobahn Test Suite is not installed on the host machine.";
|
||||
|
||||
private static bool IsOnCi =>
|
||||
!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("TEAMCITY_VERSION")) ||
|
||||
string.Equals(Environment.GetEnvironmentVariable("TRAVIS"), "true", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(Environment.GetEnvironmentVariable("APPVEYOR"), "true", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.WebSockets.Test
|
||||
{
|
||||
public class AddWebSocketsTests
|
||||
{
|
||||
[Fact]
|
||||
public void AddWebSocketsConfiguresOptions()
|
||||
{
|
||||
var serviceCollection = new ServiceCollection();
|
||||
|
||||
serviceCollection.AddWebSockets(o =>
|
||||
{
|
||||
o.KeepAliveInterval = TimeSpan.FromSeconds(1000);
|
||||
o.AllowedOrigins.Add("someString");
|
||||
});
|
||||
|
||||
var services = serviceCollection.BuildServiceProvider();
|
||||
var socketOptions = services.GetRequiredService<IOptions<WebSocketOptions>>().Value;
|
||||
|
||||
Assert.Equal(TimeSpan.FromSeconds(1000), socketOptions.KeepAliveInterval);
|
||||
Assert.Single(socketOptions.AllowedOrigins);
|
||||
Assert.Equal("someString", socketOptions.AllowedOrigins[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,350 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNetCore.WebSockets.Test
|
||||
{
|
||||
// This steam accepts writes from one side, buffers them internally, and returns the data via Reads
|
||||
// when requested on the other side.
|
||||
public class BufferStream : Stream
|
||||
{
|
||||
private bool _disposed;
|
||||
private bool _aborted;
|
||||
private bool _terminated;
|
||||
private Exception _abortException;
|
||||
private ConcurrentQueue<byte[]> _bufferedData;
|
||||
private ArraySegment<byte> _topBuffer;
|
||||
private SemaphoreSlim _readLock;
|
||||
private SemaphoreSlim _writeLock;
|
||||
private TaskCompletionSource<object> _readWaitingForData;
|
||||
|
||||
internal BufferStream()
|
||||
{
|
||||
_readLock = new SemaphoreSlim(1, 1);
|
||||
_writeLock = new SemaphoreSlim(1, 1);
|
||||
_bufferedData = new ConcurrentQueue<byte[]>();
|
||||
_readWaitingForData = new TaskCompletionSource<object>();
|
||||
}
|
||||
|
||||
public override bool CanRead
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public override bool CanSeek
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public override bool CanWrite
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
#region NotSupported
|
||||
|
||||
public override long Length
|
||||
{
|
||||
get { throw new NotSupportedException(); }
|
||||
}
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get { throw new NotSupportedException(); }
|
||||
set { throw new NotSupportedException(); }
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
#endregion NotSupported
|
||||
|
||||
/// <summary>
|
||||
/// Ends the stream, meaning all future reads will return '0'.
|
||||
/// </summary>
|
||||
public void End()
|
||||
{
|
||||
_terminated = true;
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
CheckDisposed();
|
||||
// TODO: Wait for data to drain?
|
||||
}
|
||||
|
||||
public override Task FlushAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
|
||||
tcs.TrySetCanceled();
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
Flush();
|
||||
|
||||
// TODO: Wait for data to drain?
|
||||
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if(_terminated)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
VerifyBuffer(buffer, offset, count, allowEmpty: false);
|
||||
_readLock.Wait();
|
||||
try
|
||||
{
|
||||
int totalRead = 0;
|
||||
do
|
||||
{
|
||||
// Don't drain buffered data when signaling an abort.
|
||||
CheckAborted();
|
||||
if (_topBuffer.Count <= 0)
|
||||
{
|
||||
byte[] topBuffer = null;
|
||||
while (!_bufferedData.TryDequeue(out topBuffer))
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
CheckAborted();
|
||||
// Graceful close
|
||||
return totalRead;
|
||||
}
|
||||
WaitForDataAsync().Wait();
|
||||
}
|
||||
_topBuffer = new ArraySegment<byte>(topBuffer);
|
||||
}
|
||||
int actualCount = Math.Min(count, _topBuffer.Count);
|
||||
Buffer.BlockCopy(_topBuffer.Array, _topBuffer.Offset, buffer, offset, actualCount);
|
||||
_topBuffer = new ArraySegment<byte>(_topBuffer.Array,
|
||||
_topBuffer.Offset + actualCount,
|
||||
_topBuffer.Count - actualCount);
|
||||
totalRead += actualCount;
|
||||
offset += actualCount;
|
||||
count -= actualCount;
|
||||
}
|
||||
while (count > 0 && (_topBuffer.Count > 0 || _bufferedData.Count > 0));
|
||||
// Keep reading while there is more data available and we have more space to put it in.
|
||||
return totalRead;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_readLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
|
||||
{
|
||||
// TODO: This option doesn't preserve the state object.
|
||||
// return ReadAsync(buffer, offset, count);
|
||||
return base.BeginRead(buffer, offset, count, callback, state);
|
||||
}
|
||||
|
||||
public override int EndRead(IAsyncResult asyncResult)
|
||||
{
|
||||
// return ((Task<int>)asyncResult).Result;
|
||||
return base.EndRead(asyncResult);
|
||||
}
|
||||
|
||||
public async override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
if (_terminated)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
VerifyBuffer(buffer, offset, count, allowEmpty: false);
|
||||
var registration = cancellationToken.Register(Abort);
|
||||
await _readLock.WaitAsync(cancellationToken);
|
||||
try
|
||||
{
|
||||
int totalRead = 0;
|
||||
do
|
||||
{
|
||||
// Don't drained buffered data on abort.
|
||||
CheckAborted();
|
||||
if (_topBuffer.Count <= 0)
|
||||
{
|
||||
byte[] topBuffer = null;
|
||||
while (!_bufferedData.TryDequeue(out topBuffer))
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
CheckAborted();
|
||||
// Graceful close
|
||||
return totalRead;
|
||||
}
|
||||
await WaitForDataAsync();
|
||||
}
|
||||
_topBuffer = new ArraySegment<byte>(topBuffer);
|
||||
}
|
||||
var actualCount = Math.Min(count, _topBuffer.Count);
|
||||
Buffer.BlockCopy(_topBuffer.Array, _topBuffer.Offset, buffer, offset, actualCount);
|
||||
_topBuffer = new ArraySegment<byte>(_topBuffer.Array,
|
||||
_topBuffer.Offset + actualCount,
|
||||
_topBuffer.Count - actualCount);
|
||||
totalRead += actualCount;
|
||||
offset += actualCount;
|
||||
count -= actualCount;
|
||||
}
|
||||
while (count > 0 && (_topBuffer.Count > 0 || _bufferedData.Count > 0));
|
||||
// Keep reading while there is more data available and we have more space to put it in.
|
||||
return totalRead;
|
||||
}
|
||||
finally
|
||||
{
|
||||
registration.Dispose();
|
||||
_readLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
// Write with count 0 will still trigger OnFirstWrite
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
VerifyBuffer(buffer, offset, count, allowEmpty: true);
|
||||
CheckDisposed();
|
||||
|
||||
_writeLock.Wait();
|
||||
try
|
||||
{
|
||||
if (count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Copies are necessary because we don't know what the caller is going to do with the buffer afterwards.
|
||||
var internalBuffer = new byte[count];
|
||||
Buffer.BlockCopy(buffer, offset, internalBuffer, 0, count);
|
||||
_bufferedData.Enqueue(internalBuffer);
|
||||
|
||||
SignalDataAvailable();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_writeLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
|
||||
{
|
||||
Write(buffer, offset, count);
|
||||
var tcs = new TaskCompletionSource<object>(state);
|
||||
tcs.TrySetResult(null);
|
||||
var result = tcs.Task;
|
||||
if (callback != null)
|
||||
{
|
||||
callback(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public override void EndWrite(IAsyncResult asyncResult) { }
|
||||
|
||||
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
VerifyBuffer(buffer, offset, count, allowEmpty: true);
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<object>();
|
||||
tcs.TrySetCanceled();
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
Write(buffer, offset, count);
|
||||
return Task.FromResult<object>(null);
|
||||
}
|
||||
|
||||
private static void VerifyBuffer(byte[] buffer, int offset, int count, bool allowEmpty)
|
||||
{
|
||||
if (offset < 0 || offset > buffer.Length)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(offset), offset, string.Empty);
|
||||
}
|
||||
if (count < 0 || count > buffer.Length - offset
|
||||
|| (!allowEmpty && count == 0))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(count), count, string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
private void SignalDataAvailable()
|
||||
{
|
||||
// Dispatch, as TrySetResult will synchronously execute the waiters callback and block our Write.
|
||||
Task.Factory.StartNew(() => _readWaitingForData.TrySetResult(null));
|
||||
}
|
||||
|
||||
private Task WaitForDataAsync()
|
||||
{
|
||||
_readWaitingForData = new TaskCompletionSource<object>();
|
||||
|
||||
if (!_bufferedData.IsEmpty || _disposed)
|
||||
{
|
||||
// Race, data could have arrived before we created the TCS.
|
||||
_readWaitingForData.TrySetResult(null);
|
||||
}
|
||||
|
||||
return _readWaitingForData.Task;
|
||||
}
|
||||
|
||||
internal void Abort()
|
||||
{
|
||||
Abort(new OperationCanceledException());
|
||||
}
|
||||
|
||||
internal void Abort(Exception innerException)
|
||||
{
|
||||
Contract.Requires(innerException != null);
|
||||
_aborted = true;
|
||||
_abortException = innerException;
|
||||
Dispose();
|
||||
}
|
||||
|
||||
private void CheckAborted()
|
||||
{
|
||||
if (_aborted)
|
||||
{
|
||||
throw new IOException(string.Empty, _abortException);
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", MessageId = "_writeLock", Justification = "ODEs from the locks would mask IOEs from abort.")]
|
||||
[SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", MessageId = "_readLock", Justification = "Data can still be read unless we get aborted.")]
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// Throw for further writes, but not reads. Allow reads to drain the buffered data and then return 0 for further reads.
|
||||
_disposed = true;
|
||||
_readWaitingForData.TrySetResult(null);
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
private void CheckDisposed()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(GetType().FullName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,172 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNetCore.WebSockets.Test
|
||||
{
|
||||
// A duplex wrapper around a read and write stream.
|
||||
public class DuplexStream : Stream
|
||||
{
|
||||
public BufferStream ReadStream { get; }
|
||||
public BufferStream WriteStream { get; }
|
||||
|
||||
public DuplexStream()
|
||||
: this (new BufferStream(), new BufferStream())
|
||||
{
|
||||
}
|
||||
|
||||
public DuplexStream(BufferStream readStream, BufferStream writeStream)
|
||||
{
|
||||
ReadStream = readStream;
|
||||
WriteStream = writeStream;
|
||||
}
|
||||
|
||||
public DuplexStream CreateReverseDuplexStream()
|
||||
{
|
||||
return new DuplexStream(WriteStream, ReadStream);
|
||||
}
|
||||
|
||||
|
||||
#region Properties
|
||||
|
||||
public override bool CanRead
|
||||
{
|
||||
get { return ReadStream.CanRead; }
|
||||
}
|
||||
|
||||
public override bool CanSeek
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public override bool CanTimeout
|
||||
{
|
||||
get { return ReadStream.CanTimeout || WriteStream.CanTimeout; }
|
||||
}
|
||||
|
||||
public override bool CanWrite
|
||||
{
|
||||
get { return WriteStream.CanWrite; }
|
||||
}
|
||||
|
||||
public override long Length
|
||||
{
|
||||
get { throw new NotSupportedException(); }
|
||||
}
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get { throw new NotSupportedException(); }
|
||||
set { throw new NotSupportedException(); }
|
||||
}
|
||||
|
||||
public override int ReadTimeout
|
||||
{
|
||||
get { return ReadStream.ReadTimeout; }
|
||||
set { ReadStream.ReadTimeout = value; }
|
||||
}
|
||||
|
||||
public override int WriteTimeout
|
||||
{
|
||||
get { return WriteStream.WriteTimeout; }
|
||||
set { WriteStream.WriteTimeout = value; }
|
||||
}
|
||||
|
||||
#endregion Properties
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override int ReadByte()
|
||||
{
|
||||
return ReadStream.ReadByte();
|
||||
}
|
||||
|
||||
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
|
||||
{
|
||||
return ReadStream.BeginRead(buffer, offset, count, callback, state);
|
||||
}
|
||||
|
||||
public override int EndRead(IAsyncResult asyncResult)
|
||||
{
|
||||
return ReadStream.EndRead(asyncResult);
|
||||
}
|
||||
|
||||
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
return ReadStream.ReadAsync(buffer, offset, count, cancellationToken);
|
||||
}
|
||||
|
||||
public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
|
||||
{
|
||||
return ReadStream.CopyToAsync(destination, bufferSize, cancellationToken);
|
||||
}
|
||||
|
||||
#region Read
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
return ReadStream.Read(buffer, offset, count);
|
||||
}
|
||||
|
||||
#endregion Read
|
||||
|
||||
#region Write
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
WriteStream.Write(buffer, offset, count);
|
||||
}
|
||||
public override void WriteByte(byte value)
|
||||
{
|
||||
WriteStream.WriteByte(value);
|
||||
}
|
||||
|
||||
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
|
||||
{
|
||||
return WriteStream.BeginWrite(buffer, offset, count, callback, state);
|
||||
}
|
||||
|
||||
public override void EndWrite(IAsyncResult asyncResult)
|
||||
{
|
||||
WriteStream.EndWrite(asyncResult);
|
||||
}
|
||||
|
||||
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
return WriteStream.WriteAsync(buffer, offset, count, cancellationToken);
|
||||
}
|
||||
|
||||
public override Task FlushAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return WriteStream.FlushAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
WriteStream.Flush();
|
||||
}
|
||||
|
||||
#endregion Write
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
ReadStream.Dispose();
|
||||
WriteStream.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Hosting.Server.Features;
|
||||
|
||||
namespace Microsoft.AspNetCore.Hosting
|
||||
{
|
||||
public static class IWebHostPortExtensions
|
||||
{
|
||||
public static int GetPort(this IWebHost host)
|
||||
{
|
||||
return host.GetPorts().First();
|
||||
}
|
||||
|
||||
public static IEnumerable<int> GetPorts(this IWebHost host)
|
||||
{
|
||||
return host.GetUris()
|
||||
.Select(u => u.Port);
|
||||
}
|
||||
|
||||
public static IEnumerable<Uri> GetUris(this IWebHost host)
|
||||
{
|
||||
return host.ServerFeatures.Get<IServerAddressesFeature>().Addresses
|
||||
.Select(a => new Uri(a));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AspNetCore.WebSockets.Test
|
||||
{
|
||||
public class KestrelWebSocketHelpers
|
||||
{
|
||||
public static IDisposable CreateServer(ILoggerFactory loggerFactory, out int port, Func<HttpContext, Task> app, Action<WebSocketOptions> configure = null)
|
||||
{
|
||||
configure = configure ?? (o => { });
|
||||
Action<IApplicationBuilder> startup = builder =>
|
||||
{
|
||||
builder.Use(async (ct, next) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// Kestrel does not return proper error responses:
|
||||
// https://github.com/aspnet/KestrelHttpServer/issues/43
|
||||
await next();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (ct.Response.HasStarted)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
ct.Response.StatusCode = 500;
|
||||
ct.Response.Headers.Clear();
|
||||
await ct.Response.WriteAsync(ex.ToString());
|
||||
}
|
||||
});
|
||||
builder.UseWebSockets();
|
||||
builder.Run(c => app(c));
|
||||
};
|
||||
|
||||
var configBuilder = new ConfigurationBuilder();
|
||||
configBuilder.AddInMemoryCollection();
|
||||
var config = configBuilder.Build();
|
||||
config["server.urls"] = $"http://127.0.0.1:0";
|
||||
|
||||
var host = new WebHostBuilder()
|
||||
.ConfigureServices(s =>
|
||||
{
|
||||
s.AddWebSockets(configure);
|
||||
s.AddSingleton(loggerFactory);
|
||||
})
|
||||
.UseConfiguration(config)
|
||||
.UseKestrel()
|
||||
.Configure(startup)
|
||||
.Build();
|
||||
|
||||
host.Start();
|
||||
port = host.GetPort();
|
||||
|
||||
return host;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.WebSockets\Microsoft.AspNetCore.WebSockets.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Testing" Version="$(MicrosoftExtensionsLoggingTestingPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="$(MicrosoftAspNetCoreServerKestrelPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Testing" Version="$(MicrosoftAspNetCoreTestingPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNETTestSdkPackageVersion)" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="$(XunitRunnerVisualStudioPackageVersion)" />
|
||||
<PackageReference Include="xunit" Version="$(XunitPackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.WebSockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.WebSockets.Test
|
||||
{
|
||||
public class SendReceiveTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task ClientToServerTextMessage()
|
||||
{
|
||||
const string message = "Hello, World!";
|
||||
|
||||
var pair = WebSocketPair.Create();
|
||||
var sendBuffer = Encoding.UTF8.GetBytes(message);
|
||||
|
||||
await pair.ClientSocket.SendAsync(new ArraySegment<byte>(sendBuffer), WebSocketMessageType.Text, endOfMessage: true, cancellationToken: CancellationToken.None);
|
||||
|
||||
var receiveBuffer = new byte[32];
|
||||
var result = await pair.ServerSocket.ReceiveAsync(new ArraySegment<byte>(receiveBuffer), CancellationToken.None);
|
||||
|
||||
Assert.Equal(WebSocketMessageType.Text, result.MessageType);
|
||||
Assert.Equal(message, Encoding.UTF8.GetString(receiveBuffer, 0, result.Count));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ServerToClientTextMessage()
|
||||
{
|
||||
const string message = "Hello, World!";
|
||||
|
||||
var pair = WebSocketPair.Create();
|
||||
var sendBuffer = Encoding.UTF8.GetBytes(message);
|
||||
|
||||
await pair.ServerSocket.SendAsync(new ArraySegment<byte>(sendBuffer), WebSocketMessageType.Text, endOfMessage: true, cancellationToken: CancellationToken.None);
|
||||
|
||||
var receiveBuffer = new byte[32];
|
||||
var result = await pair.ClientSocket.ReceiveAsync(new ArraySegment<byte>(receiveBuffer), CancellationToken.None);
|
||||
|
||||
Assert.Equal(WebSocketMessageType.Text, result.MessageType);
|
||||
Assert.Equal(message, Encoding.UTF8.GetString(receiveBuffer, 0, result.Count));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ClientToServerBinaryMessage()
|
||||
{
|
||||
var pair = WebSocketPair.Create();
|
||||
var sendBuffer = new byte[] { 0xde, 0xad, 0xbe, 0xef };
|
||||
|
||||
await pair.ClientSocket.SendAsync(new ArraySegment<byte>(sendBuffer), WebSocketMessageType.Binary, endOfMessage: true, cancellationToken: CancellationToken.None);
|
||||
|
||||
var receiveBuffer = new byte[32];
|
||||
var result = await pair.ServerSocket.ReceiveAsync(new ArraySegment<byte>(receiveBuffer), CancellationToken.None);
|
||||
|
||||
Assert.Equal(WebSocketMessageType.Binary, result.MessageType);
|
||||
Assert.Equal(sendBuffer, receiveBuffer.Take(result.Count).ToArray());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ServerToClientBinaryMessage()
|
||||
{
|
||||
var pair = WebSocketPair.Create();
|
||||
var sendBuffer = new byte[] { 0xde, 0xad, 0xbe, 0xef };
|
||||
|
||||
await pair.ServerSocket.SendAsync(new ArraySegment<byte>(sendBuffer), WebSocketMessageType.Binary, endOfMessage: true, cancellationToken: CancellationToken.None);
|
||||
|
||||
var receiveBuffer = new byte[32];
|
||||
var result = await pair.ClientSocket.ReceiveAsync(new ArraySegment<byte>(receiveBuffer), CancellationToken.None);
|
||||
|
||||
Assert.Equal(WebSocketMessageType.Binary, result.MessageType);
|
||||
Assert.Equal(sendBuffer, receiveBuffer.Take(result.Count).ToArray());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ThrowsWhenUnderlyingStreamClosed()
|
||||
{
|
||||
var pair = WebSocketPair.Create();
|
||||
var sendBuffer = new byte[] { 0xde, 0xad, 0xbe, 0xef };
|
||||
|
||||
await pair.ServerSocket.SendAsync(new ArraySegment<byte>(sendBuffer), WebSocketMessageType.Binary, endOfMessage: true, cancellationToken: CancellationToken.None);
|
||||
|
||||
var receiveBuffer = new byte[32];
|
||||
var result = await pair.ClientSocket.ReceiveAsync(new ArraySegment<byte>(receiveBuffer), CancellationToken.None);
|
||||
|
||||
Assert.Equal(WebSocketMessageType.Binary, result.MessageType);
|
||||
|
||||
// Close the client socket's read end
|
||||
pair.ClientStream.ReadStream.End();
|
||||
|
||||
// Assert.Throws doesn't support async :(
|
||||
try
|
||||
{
|
||||
await pair.ClientSocket.ReceiveAsync(new ArraySegment<byte>(receiveBuffer), CancellationToken.None);
|
||||
|
||||
// The exception should prevent this line from running
|
||||
Assert.False(true, "Expected an exception to be thrown!");
|
||||
}
|
||||
catch (WebSocketException ex)
|
||||
{
|
||||
Assert.Equal(WebSocketError.ConnectionClosedPrematurely, ex.WebSocketErrorCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,586 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.WebSockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Testing.xunit;
|
||||
using Microsoft.AspNetCore.WebSockets.Internal;
|
||||
using Microsoft.Extensions.Logging.Testing;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.WebSockets.Test
|
||||
{
|
||||
#if NET461
|
||||
// ClientWebSocket does not support WebSockets on these platforms and OS. Kestrel does support it.
|
||||
[OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, SkipReason = "No WebSockets Client for this platform")]
|
||||
#elif NETCOREAPP2_2
|
||||
// ClientWebSocket has added support for WebSockets on Win7.
|
||||
#else
|
||||
#error Unknown TFM
|
||||
#endif
|
||||
public class WebSocketMiddlewareTests : LoggedTest
|
||||
{
|
||||
[ConditionalFact]
|
||||
public async Task Connect_Success()
|
||||
{
|
||||
using (var server = KestrelWebSocketHelpers.CreateServer(LoggerFactory, out var port, async context =>
|
||||
{
|
||||
Assert.True(context.WebSockets.IsWebSocketRequest);
|
||||
var webSocket = await context.WebSockets.AcceptWebSocketAsync();
|
||||
}))
|
||||
{
|
||||
using (var client = new ClientWebSocket())
|
||||
{
|
||||
await client.ConnectAsync(new Uri($"ws://localhost:{port}/"), CancellationToken.None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
public async Task NegotiateSubProtocol_Success()
|
||||
{
|
||||
using (var server = KestrelWebSocketHelpers.CreateServer(LoggerFactory, out var port, async context =>
|
||||
{
|
||||
Assert.True(context.WebSockets.IsWebSocketRequest);
|
||||
Assert.Equal("alpha, bravo, charlie", context.Request.Headers["Sec-WebSocket-Protocol"]);
|
||||
var webSocket = await context.WebSockets.AcceptWebSocketAsync("Bravo");
|
||||
}))
|
||||
{
|
||||
using (var client = new ClientWebSocket())
|
||||
{
|
||||
client.Options.AddSubProtocol("alpha");
|
||||
client.Options.AddSubProtocol("bravo");
|
||||
client.Options.AddSubProtocol("charlie");
|
||||
await client.ConnectAsync(new Uri($"ws://localhost:{port}/"), CancellationToken.None);
|
||||
|
||||
// The Windows version of ClientWebSocket uses the casing from the header (Bravo)
|
||||
// However, the Managed version seems match the header against the list generated by
|
||||
// the AddSubProtocol calls (case-insensitively) and then use the version from
|
||||
// that list as the value for SubProtocol. This is fine, but means we need to ignore case here.
|
||||
// We could update our AddSubProtocols above to the same case but I think it's better to
|
||||
// ensure this behavior is codified by this test.
|
||||
Assert.Equal("Bravo", client.SubProtocol, ignoreCase: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
public async Task SendEmptyData_Success()
|
||||
{
|
||||
using (var server = KestrelWebSocketHelpers.CreateServer(LoggerFactory, out var port, async context =>
|
||||
{
|
||||
Assert.True(context.WebSockets.IsWebSocketRequest);
|
||||
var webSocket = await context.WebSockets.AcceptWebSocketAsync();
|
||||
|
||||
var serverBuffer = new byte[0];
|
||||
var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(serverBuffer), CancellationToken.None);
|
||||
Assert.True(result.EndOfMessage);
|
||||
Assert.Equal(0, result.Count);
|
||||
Assert.Equal(WebSocketMessageType.Binary, result.MessageType);
|
||||
}))
|
||||
{
|
||||
using (var client = new ClientWebSocket())
|
||||
{
|
||||
await client.ConnectAsync(new Uri($"ws://localhost:{port}/"), CancellationToken.None);
|
||||
var orriginalData = new byte[0];
|
||||
await client.SendAsync(new ArraySegment<byte>(orriginalData), WebSocketMessageType.Binary, true, CancellationToken.None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
public async Task SendShortData_Success()
|
||||
{
|
||||
var orriginalData = Encoding.UTF8.GetBytes("Hello World");
|
||||
using (var server = KestrelWebSocketHelpers.CreateServer(LoggerFactory, out var port, async context =>
|
||||
{
|
||||
Assert.True(context.WebSockets.IsWebSocketRequest);
|
||||
var webSocket = await context.WebSockets.AcceptWebSocketAsync();
|
||||
|
||||
var serverBuffer = new byte[orriginalData.Length];
|
||||
var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(serverBuffer), CancellationToken.None);
|
||||
Assert.True(result.EndOfMessage);
|
||||
Assert.Equal(orriginalData.Length, result.Count);
|
||||
Assert.Equal(WebSocketMessageType.Binary, result.MessageType);
|
||||
Assert.Equal(orriginalData, serverBuffer);
|
||||
}))
|
||||
{
|
||||
using (var client = new ClientWebSocket())
|
||||
{
|
||||
await client.ConnectAsync(new Uri($"ws://localhost:{port}/"), CancellationToken.None);
|
||||
await client.SendAsync(new ArraySegment<byte>(orriginalData), WebSocketMessageType.Binary, true, CancellationToken.None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
public async Task SendMediumData_Success()
|
||||
{
|
||||
var orriginalData = Encoding.UTF8.GetBytes(new string('a', 130));
|
||||
using (var server = KestrelWebSocketHelpers.CreateServer(LoggerFactory, out var port, async context =>
|
||||
{
|
||||
Assert.True(context.WebSockets.IsWebSocketRequest);
|
||||
var webSocket = await context.WebSockets.AcceptWebSocketAsync();
|
||||
|
||||
var serverBuffer = new byte[orriginalData.Length];
|
||||
var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(serverBuffer), CancellationToken.None);
|
||||
Assert.True(result.EndOfMessage);
|
||||
Assert.Equal(orriginalData.Length, result.Count);
|
||||
Assert.Equal(WebSocketMessageType.Binary, result.MessageType);
|
||||
Assert.Equal(orriginalData, serverBuffer);
|
||||
}))
|
||||
{
|
||||
using (var client = new ClientWebSocket())
|
||||
{
|
||||
await client.ConnectAsync(new Uri($"ws://localhost:{port}/"), CancellationToken.None);
|
||||
await client.SendAsync(new ArraySegment<byte>(orriginalData), WebSocketMessageType.Binary, true, CancellationToken.None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
public async Task SendLongData_Success()
|
||||
{
|
||||
var orriginalData = Encoding.UTF8.GetBytes(new string('a', 0x1FFFF));
|
||||
using (var server = KestrelWebSocketHelpers.CreateServer(LoggerFactory, out var port, async context =>
|
||||
{
|
||||
Assert.True(context.WebSockets.IsWebSocketRequest);
|
||||
var webSocket = await context.WebSockets.AcceptWebSocketAsync();
|
||||
|
||||
var serverBuffer = new byte[orriginalData.Length];
|
||||
var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(serverBuffer), CancellationToken.None);
|
||||
int intermediateCount = result.Count;
|
||||
Assert.False(result.EndOfMessage);
|
||||
Assert.Equal(WebSocketMessageType.Text, result.MessageType);
|
||||
|
||||
result = await webSocket.ReceiveAsync(new ArraySegment<byte>(serverBuffer, intermediateCount, orriginalData.Length - intermediateCount), CancellationToken.None);
|
||||
intermediateCount += result.Count;
|
||||
Assert.False(result.EndOfMessage);
|
||||
Assert.Equal(WebSocketMessageType.Text, result.MessageType);
|
||||
|
||||
result = await webSocket.ReceiveAsync(new ArraySegment<byte>(serverBuffer, intermediateCount, orriginalData.Length - intermediateCount), CancellationToken.None);
|
||||
intermediateCount += result.Count;
|
||||
Assert.True(result.EndOfMessage);
|
||||
Assert.Equal(orriginalData.Length, intermediateCount);
|
||||
Assert.Equal(WebSocketMessageType.Text, result.MessageType);
|
||||
|
||||
Assert.Equal(orriginalData, serverBuffer);
|
||||
}))
|
||||
{
|
||||
using (var client = new ClientWebSocket())
|
||||
{
|
||||
await client.ConnectAsync(new Uri($"ws://localhost:{port}/"), CancellationToken.None);
|
||||
await client.SendAsync(new ArraySegment<byte>(orriginalData), WebSocketMessageType.Binary, true, CancellationToken.None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
public async Task SendFragmentedData_Success()
|
||||
{
|
||||
var orriginalData = Encoding.UTF8.GetBytes("Hello World");
|
||||
using (var server = KestrelWebSocketHelpers.CreateServer(LoggerFactory, out var port, async context =>
|
||||
{
|
||||
Assert.True(context.WebSockets.IsWebSocketRequest);
|
||||
var webSocket = await context.WebSockets.AcceptWebSocketAsync();
|
||||
|
||||
var serverBuffer = new byte[orriginalData.Length];
|
||||
var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(serverBuffer), CancellationToken.None);
|
||||
Assert.False(result.EndOfMessage);
|
||||
Assert.Equal(2, result.Count);
|
||||
int totalReceived = result.Count;
|
||||
Assert.Equal(WebSocketMessageType.Binary, result.MessageType);
|
||||
|
||||
result = await webSocket.ReceiveAsync(
|
||||
new ArraySegment<byte>(serverBuffer, totalReceived, serverBuffer.Length - totalReceived), CancellationToken.None);
|
||||
Assert.False(result.EndOfMessage);
|
||||
Assert.Equal(2, result.Count);
|
||||
totalReceived += result.Count;
|
||||
Assert.Equal(WebSocketMessageType.Binary, result.MessageType);
|
||||
|
||||
result = await webSocket.ReceiveAsync(
|
||||
new ArraySegment<byte>(serverBuffer, totalReceived, serverBuffer.Length - totalReceived), CancellationToken.None);
|
||||
Assert.True(result.EndOfMessage);
|
||||
Assert.Equal(7, result.Count);
|
||||
totalReceived += result.Count;
|
||||
Assert.Equal(WebSocketMessageType.Binary, result.MessageType);
|
||||
|
||||
Assert.Equal(orriginalData, serverBuffer);
|
||||
}))
|
||||
{
|
||||
using (var client = new ClientWebSocket())
|
||||
{
|
||||
await client.ConnectAsync(new Uri($"ws://localhost:{port}/"), CancellationToken.None);
|
||||
await client.SendAsync(new ArraySegment<byte>(orriginalData, 0, 2), WebSocketMessageType.Binary, false, CancellationToken.None);
|
||||
await client.SendAsync(new ArraySegment<byte>(orriginalData, 2, 2), WebSocketMessageType.Binary, false, CancellationToken.None);
|
||||
await client.SendAsync(new ArraySegment<byte>(orriginalData, 4, 7), WebSocketMessageType.Binary, true, CancellationToken.None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
public async Task ReceiveShortData_Success()
|
||||
{
|
||||
var orriginalData = Encoding.UTF8.GetBytes("Hello World");
|
||||
using (var server = KestrelWebSocketHelpers.CreateServer(LoggerFactory, out var port, async context =>
|
||||
{
|
||||
Assert.True(context.WebSockets.IsWebSocketRequest);
|
||||
var webSocket = await context.WebSockets.AcceptWebSocketAsync();
|
||||
|
||||
await webSocket.SendAsync(new ArraySegment<byte>(orriginalData), WebSocketMessageType.Binary, true, CancellationToken.None);
|
||||
}))
|
||||
{
|
||||
using (var client = new ClientWebSocket())
|
||||
{
|
||||
await client.ConnectAsync(new Uri($"ws://localhost:{port}/"), CancellationToken.None);
|
||||
var clientBuffer = new byte[orriginalData.Length];
|
||||
var result = await client.ReceiveAsync(new ArraySegment<byte>(clientBuffer), CancellationToken.None);
|
||||
Assert.True(result.EndOfMessage);
|
||||
Assert.Equal(orriginalData.Length, result.Count);
|
||||
Assert.Equal(WebSocketMessageType.Binary, result.MessageType);
|
||||
Assert.Equal(orriginalData, clientBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
public async Task ReceiveMediumData_Success()
|
||||
{
|
||||
var orriginalData = Encoding.UTF8.GetBytes(new string('a', 130));
|
||||
using (var server = KestrelWebSocketHelpers.CreateServer(LoggerFactory, out var port, async context =>
|
||||
{
|
||||
Assert.True(context.WebSockets.IsWebSocketRequest);
|
||||
var webSocket = await context.WebSockets.AcceptWebSocketAsync();
|
||||
|
||||
await webSocket.SendAsync(new ArraySegment<byte>(orriginalData), WebSocketMessageType.Binary, true, CancellationToken.None);
|
||||
}))
|
||||
{
|
||||
using (var client = new ClientWebSocket())
|
||||
{
|
||||
await client.ConnectAsync(new Uri($"ws://localhost:{port}/"), CancellationToken.None);
|
||||
var clientBuffer = new byte[orriginalData.Length];
|
||||
var result = await client.ReceiveAsync(new ArraySegment<byte>(clientBuffer), CancellationToken.None);
|
||||
Assert.True(result.EndOfMessage);
|
||||
Assert.Equal(orriginalData.Length, result.Count);
|
||||
Assert.Equal(WebSocketMessageType.Binary, result.MessageType);
|
||||
Assert.Equal(orriginalData, clientBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
public async Task ReceiveLongData()
|
||||
{
|
||||
var orriginalData = Encoding.UTF8.GetBytes(new string('a', 0x1FFFF));
|
||||
using (var server = KestrelWebSocketHelpers.CreateServer(LoggerFactory, out var port, async context =>
|
||||
{
|
||||
Assert.True(context.WebSockets.IsWebSocketRequest);
|
||||
var webSocket = await context.WebSockets.AcceptWebSocketAsync();
|
||||
|
||||
await webSocket.SendAsync(new ArraySegment<byte>(orriginalData), WebSocketMessageType.Binary, true, CancellationToken.None);
|
||||
}))
|
||||
{
|
||||
using (var client = new ClientWebSocket())
|
||||
{
|
||||
await client.ConnectAsync(new Uri($"ws://localhost:{port}/"), CancellationToken.None);
|
||||
var clientBuffer = new byte[orriginalData.Length];
|
||||
WebSocketReceiveResult result;
|
||||
int receivedCount = 0;
|
||||
do
|
||||
{
|
||||
result = await client.ReceiveAsync(new ArraySegment<byte>(clientBuffer, receivedCount, clientBuffer.Length - receivedCount), CancellationToken.None);
|
||||
receivedCount += result.Count;
|
||||
Assert.Equal(WebSocketMessageType.Binary, result.MessageType);
|
||||
}
|
||||
while (!result.EndOfMessage);
|
||||
|
||||
Assert.Equal(orriginalData.Length, receivedCount);
|
||||
Assert.Equal(WebSocketMessageType.Binary, result.MessageType);
|
||||
Assert.Equal(orriginalData, clientBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
public async Task ReceiveFragmentedData_Success()
|
||||
{
|
||||
var orriginalData = Encoding.UTF8.GetBytes("Hello World");
|
||||
using (var server = KestrelWebSocketHelpers.CreateServer(LoggerFactory, out var port, async context =>
|
||||
{
|
||||
Assert.True(context.WebSockets.IsWebSocketRequest);
|
||||
var webSocket = await context.WebSockets.AcceptWebSocketAsync();
|
||||
|
||||
await webSocket.SendAsync(new ArraySegment<byte>(orriginalData, 0, 2), WebSocketMessageType.Binary, false, CancellationToken.None);
|
||||
await webSocket.SendAsync(new ArraySegment<byte>(orriginalData, 2, 2), WebSocketMessageType.Binary, false, CancellationToken.None);
|
||||
await webSocket.SendAsync(new ArraySegment<byte>(orriginalData, 4, 7), WebSocketMessageType.Binary, true, CancellationToken.None);
|
||||
}))
|
||||
{
|
||||
using (var client = new ClientWebSocket())
|
||||
{
|
||||
await client.ConnectAsync(new Uri($"ws://localhost:{port}/"), CancellationToken.None);
|
||||
var clientBuffer = new byte[orriginalData.Length];
|
||||
var result = await client.ReceiveAsync(new ArraySegment<byte>(clientBuffer), CancellationToken.None);
|
||||
Assert.False(result.EndOfMessage);
|
||||
Assert.Equal(2, result.Count);
|
||||
int totalReceived = result.Count;
|
||||
Assert.Equal(WebSocketMessageType.Binary, result.MessageType);
|
||||
|
||||
result = await client.ReceiveAsync(
|
||||
new ArraySegment<byte>(clientBuffer, totalReceived, clientBuffer.Length - totalReceived), CancellationToken.None);
|
||||
Assert.False(result.EndOfMessage);
|
||||
Assert.Equal(2, result.Count);
|
||||
totalReceived += result.Count;
|
||||
Assert.Equal(WebSocketMessageType.Binary, result.MessageType);
|
||||
|
||||
result = await client.ReceiveAsync(
|
||||
new ArraySegment<byte>(clientBuffer, totalReceived, clientBuffer.Length - totalReceived), CancellationToken.None);
|
||||
Assert.True(result.EndOfMessage);
|
||||
Assert.Equal(7, result.Count);
|
||||
totalReceived += result.Count;
|
||||
Assert.Equal(WebSocketMessageType.Binary, result.MessageType);
|
||||
|
||||
Assert.Equal(orriginalData, clientBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
public async Task SendClose_Success()
|
||||
{
|
||||
string closeDescription = "Test Closed";
|
||||
using (var server = KestrelWebSocketHelpers.CreateServer(LoggerFactory, out var port, async context =>
|
||||
{
|
||||
Assert.True(context.WebSockets.IsWebSocketRequest);
|
||||
var webSocket = await context.WebSockets.AcceptWebSocketAsync();
|
||||
|
||||
var serverBuffer = new byte[1024];
|
||||
var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(serverBuffer), CancellationToken.None);
|
||||
Assert.True(result.EndOfMessage);
|
||||
Assert.Equal(0, result.Count);
|
||||
Assert.Equal(WebSocketMessageType.Close, result.MessageType);
|
||||
Assert.Equal(WebSocketCloseStatus.NormalClosure, result.CloseStatus);
|
||||
Assert.Equal(closeDescription, result.CloseStatusDescription);
|
||||
}))
|
||||
{
|
||||
using (var client = new ClientWebSocket())
|
||||
{
|
||||
await client.ConnectAsync(new Uri($"ws://localhost:{port}/"), CancellationToken.None);
|
||||
await client.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, closeDescription, CancellationToken.None);
|
||||
|
||||
Assert.Equal(WebSocketState.CloseSent, client.State);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
public async Task ReceiveClose_Success()
|
||||
{
|
||||
string closeDescription = "Test Closed";
|
||||
using (var server = KestrelWebSocketHelpers.CreateServer(LoggerFactory, out var port, async context =>
|
||||
{
|
||||
Assert.True(context.WebSockets.IsWebSocketRequest);
|
||||
var webSocket = await context.WebSockets.AcceptWebSocketAsync();
|
||||
|
||||
await webSocket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, closeDescription, CancellationToken.None);
|
||||
}))
|
||||
{
|
||||
using (var client = new ClientWebSocket())
|
||||
{
|
||||
await client.ConnectAsync(new Uri($"ws://localhost:{port}/"), CancellationToken.None);
|
||||
var clientBuffer = new byte[1024];
|
||||
var result = await client.ReceiveAsync(new ArraySegment<byte>(clientBuffer), CancellationToken.None);
|
||||
Assert.True(result.EndOfMessage);
|
||||
Assert.Equal(0, result.Count);
|
||||
Assert.Equal(WebSocketMessageType.Close, result.MessageType);
|
||||
Assert.Equal(WebSocketCloseStatus.NormalClosure, result.CloseStatus);
|
||||
Assert.Equal(closeDescription, result.CloseStatusDescription);
|
||||
|
||||
Assert.Equal(WebSocketState.CloseReceived, client.State);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
public async Task CloseFromOpen_Success()
|
||||
{
|
||||
string closeDescription = "Test Closed";
|
||||
using (var server = KestrelWebSocketHelpers.CreateServer(LoggerFactory, out var port, async context =>
|
||||
{
|
||||
Assert.True(context.WebSockets.IsWebSocketRequest);
|
||||
var webSocket = await context.WebSockets.AcceptWebSocketAsync();
|
||||
|
||||
var serverBuffer = new byte[1024];
|
||||
var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(serverBuffer), CancellationToken.None);
|
||||
Assert.True(result.EndOfMessage);
|
||||
Assert.Equal(0, result.Count);
|
||||
Assert.Equal(WebSocketMessageType.Close, result.MessageType);
|
||||
Assert.Equal(WebSocketCloseStatus.NormalClosure, result.CloseStatus);
|
||||
Assert.Equal(closeDescription, result.CloseStatusDescription);
|
||||
|
||||
await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
|
||||
}))
|
||||
{
|
||||
using (var client = new ClientWebSocket())
|
||||
{
|
||||
await client.ConnectAsync(new Uri($"ws://localhost:{port}/"), CancellationToken.None);
|
||||
await client.CloseAsync(WebSocketCloseStatus.NormalClosure, closeDescription, CancellationToken.None);
|
||||
|
||||
Assert.Equal(WebSocketState.Closed, client.State);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
public async Task CloseFromCloseSent_Success()
|
||||
{
|
||||
string closeDescription = "Test Closed";
|
||||
using (var server = KestrelWebSocketHelpers.CreateServer(LoggerFactory, out var port, async context =>
|
||||
{
|
||||
Assert.True(context.WebSockets.IsWebSocketRequest);
|
||||
var webSocket = await context.WebSockets.AcceptWebSocketAsync();
|
||||
|
||||
var serverBuffer = new byte[1024];
|
||||
var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(serverBuffer), CancellationToken.None);
|
||||
Assert.True(result.EndOfMessage);
|
||||
Assert.Equal(0, result.Count);
|
||||
Assert.Equal(WebSocketMessageType.Close, result.MessageType);
|
||||
Assert.Equal(WebSocketCloseStatus.NormalClosure, result.CloseStatus);
|
||||
Assert.Equal(closeDescription, result.CloseStatusDescription);
|
||||
|
||||
await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
|
||||
}))
|
||||
{
|
||||
using (var client = new ClientWebSocket())
|
||||
{
|
||||
await client.ConnectAsync(new Uri($"ws://localhost:{port}/"), CancellationToken.None);
|
||||
await client.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, closeDescription, CancellationToken.None);
|
||||
Assert.Equal(WebSocketState.CloseSent, client.State);
|
||||
|
||||
await client.CloseAsync(WebSocketCloseStatus.NormalClosure, closeDescription, CancellationToken.None);
|
||||
Assert.Equal(WebSocketState.Closed, client.State);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
public async Task CloseFromCloseReceived_Success()
|
||||
{
|
||||
string closeDescription = "Test Closed";
|
||||
using (var server = KestrelWebSocketHelpers.CreateServer(LoggerFactory, out var port, async context =>
|
||||
{
|
||||
Assert.True(context.WebSockets.IsWebSocketRequest);
|
||||
var webSocket = await context.WebSockets.AcceptWebSocketAsync();
|
||||
|
||||
await webSocket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, closeDescription, CancellationToken.None);
|
||||
|
||||
var serverBuffer = new byte[1024];
|
||||
var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(serverBuffer), CancellationToken.None);
|
||||
Assert.True(result.EndOfMessage);
|
||||
Assert.Equal(0, result.Count);
|
||||
Assert.Equal(WebSocketMessageType.Close, result.MessageType);
|
||||
Assert.Equal(WebSocketCloseStatus.NormalClosure, result.CloseStatus);
|
||||
Assert.Equal(closeDescription, result.CloseStatusDescription);
|
||||
}))
|
||||
{
|
||||
using (var client = new ClientWebSocket())
|
||||
{
|
||||
await client.ConnectAsync(new Uri($"ws://localhost:{port}/"), CancellationToken.None);
|
||||
var clientBuffer = new byte[1024];
|
||||
var result = await client.ReceiveAsync(new ArraySegment<byte>(clientBuffer), CancellationToken.None);
|
||||
Assert.True(result.EndOfMessage);
|
||||
Assert.Equal(0, result.Count);
|
||||
Assert.Equal(WebSocketMessageType.Close, result.MessageType);
|
||||
Assert.Equal(WebSocketCloseStatus.NormalClosure, result.CloseStatus);
|
||||
Assert.Equal(closeDescription, result.CloseStatusDescription);
|
||||
|
||||
Assert.Equal(WebSocketState.CloseReceived, client.State);
|
||||
|
||||
await client.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
|
||||
|
||||
Assert.Equal(WebSocketState.Closed, client.State);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(HttpStatusCode.OK, null)]
|
||||
[InlineData(HttpStatusCode.Forbidden, "")]
|
||||
[InlineData(HttpStatusCode.Forbidden, "http://e.com")]
|
||||
[InlineData(HttpStatusCode.OK, "http://e.com", "http://example.com")]
|
||||
[InlineData(HttpStatusCode.OK, "*")]
|
||||
[InlineData(HttpStatusCode.OK, "http://e.com", "*")]
|
||||
[InlineData(HttpStatusCode.OK, "http://ExAmPLE.cOm")]
|
||||
public async Task OriginIsValidatedForWebSocketRequests(HttpStatusCode expectedCode, params string[] origins)
|
||||
{
|
||||
using (var server = KestrelWebSocketHelpers.CreateServer(LoggerFactory, out var port, context =>
|
||||
{
|
||||
Assert.True(context.WebSockets.IsWebSocketRequest);
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
o =>
|
||||
{
|
||||
if (origins != null)
|
||||
{
|
||||
foreach (var origin in origins)
|
||||
{
|
||||
o.AllowedOrigins.Add(origin);
|
||||
}
|
||||
}
|
||||
}))
|
||||
{
|
||||
using (var client = new HttpClient())
|
||||
{
|
||||
var uri = new UriBuilder(new Uri($"ws://localhost:{port}/"));
|
||||
uri.Scheme = "http";
|
||||
|
||||
// Craft a valid WebSocket Upgrade request
|
||||
using (var request = new HttpRequestMessage(HttpMethod.Get, uri.ToString()))
|
||||
{
|
||||
request.Headers.Connection.Clear();
|
||||
request.Headers.Connection.Add("Upgrade");
|
||||
request.Headers.Upgrade.Add(new System.Net.Http.Headers.ProductHeaderValue("websocket"));
|
||||
request.Headers.Add(Constants.Headers.SecWebSocketVersion, Constants.Headers.SupportedVersion);
|
||||
// SecWebSocketKey required to be 16 bytes
|
||||
request.Headers.Add(Constants.Headers.SecWebSocketKey, Convert.ToBase64String(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }, Base64FormattingOptions.None));
|
||||
|
||||
request.Headers.Add("Origin", "http://example.com");
|
||||
|
||||
var response = await client.SendAsync(request);
|
||||
Assert.Equal(expectedCode, response.StatusCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task OriginIsNotValidatedForNonWebSocketRequests()
|
||||
{
|
||||
using (var server = KestrelWebSocketHelpers.CreateServer(LoggerFactory, out var port, context =>
|
||||
{
|
||||
Assert.False(context.WebSockets.IsWebSocketRequest);
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
o => o.AllowedOrigins.Add("http://example.com")))
|
||||
{
|
||||
using (var client = new HttpClient())
|
||||
{
|
||||
var uri = new UriBuilder(new Uri($"ws://localhost:{port}/"));
|
||||
uri.Scheme = "http";
|
||||
|
||||
using (var request = new HttpRequestMessage(HttpMethod.Get, uri.ToString()))
|
||||
{
|
||||
request.Headers.Add("Origin", "http://notexample.com");
|
||||
|
||||
var response = await client.SendAsync(request);
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
using System;
|
||||
using System.Net.WebSockets;
|
||||
using Microsoft.AspNetCore.WebSockets.Internal;
|
||||
|
||||
namespace Microsoft.AspNetCore.WebSockets.Test
|
||||
{
|
||||
internal class WebSocketPair
|
||||
{
|
||||
public WebSocket ClientSocket { get; }
|
||||
public WebSocket ServerSocket { get; }
|
||||
public DuplexStream ServerStream { get; }
|
||||
public DuplexStream ClientStream { get; }
|
||||
|
||||
public WebSocketPair(DuplexStream serverStream, DuplexStream clientStream, WebSocket clientSocket, WebSocket serverSocket)
|
||||
{
|
||||
ClientStream = clientStream;
|
||||
ServerStream = serverStream;
|
||||
ClientSocket = clientSocket;
|
||||
ServerSocket = serverSocket;
|
||||
}
|
||||
|
||||
public static WebSocketPair Create()
|
||||
{
|
||||
// Create streams
|
||||
var serverStream = new DuplexStream();
|
||||
var clientStream = serverStream.CreateReverseDuplexStream();
|
||||
|
||||
return new WebSocketPair(
|
||||
serverStream,
|
||||
clientStream,
|
||||
clientSocket: WebSocketProtocol.CreateFromStream(clientStream, isServer: false, subProtocol: null, keepAliveInterval: TimeSpan.FromMinutes(2)),
|
||||
serverSocket: WebSocketProtocol.CreateFromStream(serverStream, isServer: true, subProtocol: null, keepAliveInterval: TimeSpan.FromMinutes(2)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<Project>
|
||||
<PropertyGroup>
|
||||
<VersionPrefix>2.2.0</VersionPrefix>
|
||||
<VersionSuffix>rtm</VersionSuffix>
|
||||
<PackageVersion Condition="'$(IsFinalBuild)' == 'true' AND '$(VersionSuffix)' == 'rtm' ">$(VersionPrefix)</PackageVersion>
|
||||
<PackageVersion Condition="'$(IsFinalBuild)' == 'true' AND '$(VersionSuffix)' != 'rtm' ">$(VersionPrefix)-$(VersionSuffix)-final</PackageVersion>
|
||||
<BuildNumber Condition="'$(BuildNumber)' == ''">t000</BuildNumber>
|
||||
<FeatureBranchVersionPrefix Condition="'$(FeatureBranchVersionPrefix)' == ''">a-</FeatureBranchVersionPrefix>
|
||||
<VersionSuffix Condition="'$(VersionSuffix)' != '' And '$(FeatureBranchVersionSuffix)' != ''">$(FeatureBranchVersionPrefix)$(VersionSuffix)-$([System.Text.RegularExpressions.Regex]::Replace('$(FeatureBranchVersionSuffix)', '[^\w-]', '-'))</VersionSuffix>
|
||||
<VersionSuffix Condition="'$(VersionSuffix)' != '' And '$(BuildNumber)' != ''">$(VersionSuffix)-$(BuildNumber)</VersionSuffix>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
Loading…
Reference in New Issue