Merge release/2.1 and aspnet/WebSockets release/2.2

This commit is contained in:
Nate McMaster 2018-10-17 15:47:04 -07:00
commit d991c50c82
No known key found for this signature in database
GPG Key ID: A778D9601BD78810
99 changed files with 5618 additions and 40 deletions

View File

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

4
.gitmodules vendored
View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
<Project>
<Target Name="CheckForPreviousReleaseArchiveBaseline" BeforeTargets="CheckUniverse">
<Target Name="CheckForPreviousReleaseArchiveBaseline" BeforeTargets="FastCheck">
<MSBuild Projects="@(ArchiveProjects)"
Targets="CheckForPreviousReleaseArchiveBaseline" />
</Target>

View File

@ -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)" />

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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)" \

View File

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

View File

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

View File

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

View File

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

31
src/WebSockets/.gitignore vendored Normal file
View File

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

View File

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

View File

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

View File

@ -0,0 +1,7 @@
{
"Default": {
"rules": [
"DefaultCompositeRule"
]
}
}

19
src/WebSockets/README.md Normal file
View File

@ -0,0 +1,19 @@
WebSockets
================
AppVeyor: [![AppVeyor](https://ci.appveyor.com/api/projects/status/lk5hyg6gki03hdqe/branch/dev?svg=true)](https://ci.appveyor.com/project/aspnetci/WebSockets/branch/dev)
Travis: [![Travis](https://travis-ci.org/aspnet/WebSockets.svg?branch=dev)](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

View File

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

3
src/WebSockets/build.cmd Normal file
View File

@ -0,0 +1,3 @@
@ECHO OFF
SET RepoRoot="%~dp0..\.."
%RepoRoot%\build.cmd -LockFile %RepoRoot%\korebuild-lock.txt -Path %~dp0 %*

7
src/WebSockets/build.sh Executable file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1 @@
<%@ WebHandler Language="C#" CodeBehind="EchoSocket.ashx.cs" Class="AutobahnTestAppAspNet4.EchoSocket" %>

View File

@ -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);
}
}
}

View File

@ -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")]

View File

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

View File

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

View File

@ -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=\&quot;Web\&quot; /optionInfer+"/>
</compilers>
</system.codedom>
</configuration>

View File

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

View File

@ -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": {}
}

View File

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

View File

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

View File

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

View File

@ -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")]

View File

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

View File

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

View File

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

View File

@ -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);
}
}
}

View File

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

View File

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

View File

@ -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");
}
}
}
}

View File

@ -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")]

View File

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

View File

@ -0,0 +1,7 @@
<Project>
<Import Project="..\Directory.Build.props" />
<ItemGroup>
<PackageReference Include="Internal.AspNetCore.Sdk" PrivateAssets="All" Version="$(InternalAspNetCoreSdkPackageVersion)" />
</ItemGroup>
</Project>

View File

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

View File

@ -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";
}
}
}

View File

@ -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);
}
}
}
}

View File

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

View File

@ -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);
}
}
}
}

View File

@ -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));
}
}
}

View File

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

View File

@ -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);
}
}
}

View File

@ -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": []
}
]
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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);
}
}
}

View File

@ -0,0 +1,3 @@
The password for this is 'testPassword'
DO NOT EVER TRUST THIS CERT. The private key for it is publicly released.

View File

@ -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)"
}
}

View File

@ -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": {}
}

View File

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

View File

@ -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));
}
}
}

View File

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

View File

@ -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));
}
}
}

View File

@ -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));
}
}
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,10 @@
namespace Microsoft.AspNetCore.WebSockets.ConformanceTest.Autobahn
{
public enum Expectation
{
Fail,
NonStrict,
OkOrFail,
Ok
}
}

View File

@ -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))));
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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);
}
}

View File

@ -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]);
}
}
}

View File

@ -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);
}
}
}
}

View File

@ -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);
}
}
}

View File

@ -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));
}
}
}

View File

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

View File

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

View File

@ -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);
}
}
}
}

View File

@ -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);
}
}
}
}
}
}

View File

@ -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)));
}
}
}

View File

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