Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 27 additions & 6 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,16 @@ on:
env:
CONFIGURATION: Release
DOTNET_VERSION: 11.0.x
OUTPUT_FOLDER: net11.0
jobs:
build-and-deploy:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Setup .NET
uses: actions/setup-dotnet@v5
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
- name: Install Node.js
uses: actions/setup-node@v6
with:
node-version: 26.x
- name: Install Package libmsquic
run: |
sudo apt update
Expand All @@ -38,10 +35,34 @@ jobs:
dotnet test ./tests/TestWebApplication.Tests --no-build -c ${{ env.CONFIGURATION }}
dotnet test ./tests/CHttp.Api.Tests --no-build -c ${{ env.CONFIGURATION }}
dotnet test ./tests/CHttpServer.Tests --no-build -c ${{ env.CONFIGURATION }}

build-and-test-node:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Setup .NET
uses: actions/setup-dotnet@v5
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
- name: Install Node.js
uses: actions/setup-node@v6
with:
node-version: 26.x
- name: Install Package libmsquic
run: |
sudo apt update
sudo apt install -y libmsquic
shell: sh
- name: Publish VSCE
run: |
pushd ./src/VSCodeExt/
npm install
npm run vsce-deploy-win-x64
npm run vsce-deploy-linux-x64
popd
popd
- name: Browser CHttp Server Tests
run: |
dotnet build ./tests/CHttpServer.BrowserTests -c ${{ env.CONFIGURATION }}
./tests/CHttpServer.BrowserTests/bin/${{ env.CONFIGURATION }}/${{ env.OUTPUT_FOLDER }}/playwright.ps1 install --with-deps chromium
dotnet test ./tests/CHttpServer.BrowserTests -c ${{ env.CONFIGURATION }}
shell: pwsh
3 changes: 2 additions & 1 deletion CHttpTools.slnx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
<Solution>
<Folder Name="/Server/">
<Project Path="src/CHttpServer/CHttpServer/CHttpServer.csproj" />
<Project Path="src/CHttpServer/TestCHttpServerApplication/TestCHttpServerApplication.csproj" DefaultStartup="true" />
<Project Path="src/CHttpServer/TestCHttpServerApplication/TestCHttpServerApplication.csproj" />
<Project Path="tests/CHttpServer.Tests/CHttpServer.Tests.csproj" />
<Project Path="tests/CHttpServer.BrowserTests/CHttpServer.BrowserTests.csproj" />
</Folder>
<Project Path="src/CHttp.Api/CHttp.Api.csproj" />
<Project Path="src/CHttp/CHttp.csproj" />
Expand Down
4 changes: 4 additions & 0 deletions src/CHttpServer/CHttpServer/Http3/Http3Connection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,10 @@ private async Task TryWriteGoAwayAsync()
{
// Connection already aborted by the client.
}
catch (QuicException ex)
{
Debug.WriteLine(ex);
}
}

private async Task HandleStreamAsync<TContext>(
Expand Down
20 changes: 6 additions & 14 deletions src/CHttpServer/CHttpServer/RequestHeaderCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -139,17 +139,12 @@ private bool TrySetKnownHeader(ReadOnlySpan<byte> rawKey, ReadOnlySpan<byte> raw
{
Span<char> utf16Value = stackalloc char[rawValue.Length];
Encoding.Latin1.GetChars(rawValue, utf16Value);
if (_hostValue.Count > 0 && utf16Value.SequenceEqual(_hostValue[0].AsSpan()))
{
key = "Host";
value = _hostValue;
return true;
}
if (_hostValue.Count == 0 || !utf16Value.SequenceEqual(_hostValue[0].AsSpan()))
_hostValue = new StringValues(utf16Value.ToString());

_hostValue = new StringValues(utf16Value.ToString());
_isHostValueSet = true;
key = "Host";
value = _hostValue;
_isHostValueSet = true;
return true;
}

Expand All @@ -164,12 +159,9 @@ private bool TrySetKnownHeader(string key, ReadOnlySpan<byte> rawValue)
{
Span<char> utf16Value = stackalloc char[rawValue.Length];
Encoding.Latin1.GetChars(rawValue, utf16Value);
if (_hostValue.Count > 0 && utf16Value.SequenceEqual(_hostValue[0].AsSpan()))
{
key = "Host";
return true;
}
_hostValue = new StringValues(utf16Value.ToString());
if (_hostValue.Count == 0 || !utf16Value.SequenceEqual(_hostValue[0].AsSpan()))
_hostValue = new StringValues(utf16Value.ToString());

_isHostValueSet = true;
return true;
}
Expand Down
Binary file modified tests/CHttp.Tests/testCert.pfx
Binary file not shown.
33 changes: 33 additions & 0 deletions tests/CHttpServer.BrowserTests/CHttpServer.BrowserTests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net11.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<TestingPlatformDotnetTestSupport>true</TestingPlatformDotnetTestSupport>
<UseMicrosoftTestingPlatformRunner>true</UseMicrosoftTestingPlatformRunner>
<EnablePreviewFeatures>true</EnablePreviewFeatures>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="11.0.0-preview.5.26302.115" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.7.0" />
<PackageReference Include="Microsoft.Playwright.Xunit.v3" Version="1.61.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="4.0.0-pre.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="xunit.v3.mtp-v2" Version="4.0.0-pre.128" />

</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\CHttpServer.Tests\CHttpServer.Tests.csproj" />
</ItemGroup>

<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>

</Project>
67 changes: 67 additions & 0 deletions tests/CHttpServer.BrowserTests/Http2BrowserTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using Microsoft.Playwright;

namespace CHttpServer.BrowserTests;

public class Http2BrowserTests(Http2TestFixture testFixture) : IClassFixture<Http2TestFixture>
{
private readonly Http2TestFixture _fixture = testFixture;
private readonly string _url = $"https://127.0.0.1:{testFixture.Port}";

[Fact]
public async Task Http2Protocol()
{
var page = await _fixture.Browser.NewPageAsync();
await page.GotoAsync($"{_url}/protocol");
Assert.Contains("HTTP/2", await page.ContentAsync());
await page.CloseAsync();
}

[Fact]
public async Task Sse()
{
var page = await _fixture.Browser.NewPageAsync();
await page.GotoAsync($"{_url}/html");
await page.ClickAsync("body > button:nth-child(3)");
var sse = await page.WaitForSelectorAsync("#sse", new PageWaitForSelectorOptions() { State = WaitForSelectorState.Visible, Timeout = 10_000 });
Assert.NotNull(sse);
var content = await sse.InnerHTMLAsync();
Assert.Contains("sse 0", content);
Assert.Contains("sse 1", content);
await page.CloseAsync();
}

[Fact]
public async Task FormPost()
{
var page = await _fixture.Browser.NewPageAsync();
await page.GotoAsync($"{_url}/html");

// Intercept all requests ("**/*" is a glob pattern for everything)
await page.RouteAsync("**/*", async route =>
{
// Let the request continue normally
await route.ContinueAsync();
});

await page.RunAndWaitForResponseAsync(async () =>
{
await page.ClickAsync("body > form > button");
}, $"{_url}/protocol");
Assert.Contains("HTTP/2", await page.ContentAsync());
await page.CloseAsync();
}

[Fact]
public async Task GetFetchJs()
{
var page = await _fixture.Browser.NewPageAsync();
await page.GotoAsync($"{_url}/html");
await page.ClickAsync("body > button:nth-child(2)");
var output = await page.WaitForSelectorAsync("#output", new PageWaitForSelectorOptions() { State = WaitForSelectorState.Visible, Timeout = 10_000 });
Assert.NotNull(output);
var content = await output.InnerHTMLAsync();
Assert.Contains("\"some content\"", content);
await page.CloseAsync();
}
}

37 changes: 37 additions & 0 deletions tests/CHttpServer.BrowserTests/Http2TestFixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System.Runtime.Versioning;
using CHttpServer.Tests;
using Microsoft.Playwright;

namespace CHttpServer.BrowserTests;

public sealed class Http2TestFixture : IAsyncDisposable
{
internal int Port => 7294;

public TestServer Server { get; private set; }

public IPlaywright PlaywrightHost { get; private set; }

public IBrowserContext Browser { get; private set; }

[SupportedOSPlatform("linux")]
[SupportedOSPlatform("windows")]
public Http2TestFixture()
{
Server = new TestServer();
Server.RunAsync(Port, false, useHttp3: false).GetAwaiter().GetResult();
PlaywrightHost = Playwright.CreateAsync().GetAwaiter().GetResult();
Browser = PlaywrightHost.Chromium.LaunchPersistentContextAsync("/tmp/chrome-profile-integrationtest", new()
{
Headless = true,
Args = ["--ignore-certificate-errors-spki-list=5QveYGg8xaCnnZWvkC9Y6v9lQVmF2BCozvds6Cn6F6k="]
}).GetAwaiter().GetResult();
}

public async ValueTask DisposeAsync()
{
await Browser.DisposeAsync();
PlaywrightHost.Dispose();
await Server.DisposeAsync();
}
}
68 changes: 68 additions & 0 deletions tests/CHttpServer.BrowserTests/Http3BrowserTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using Microsoft.Playwright;

namespace CHttpServer.BrowserTests;

public class Http3BrowserTests(Http3TestFixture testFixture) : IClassFixture<Http3TestFixture>
{
private readonly Http3TestFixture _fixture = testFixture;
private readonly string _url = $"https://127.0.0.1:{testFixture.Port}";

[Fact]
public async Task Http3Protocol()
{
for (int i = 0; i < 10; i++)
{
var page = await _fixture.Browser.NewPageAsync();
await page.GotoAsync($"{_url}/protocol");
Assert.Contains("HTTP/3", await page.ContentAsync());
await page.CloseAsync();
}
}

[Fact]
public async Task Sse()
{
var page = await _fixture.Browser.NewPageAsync();
await page.GotoAsync($"{_url}/html");
await page.ClickAsync("body > button:nth-child(3)");
var sse = await page.WaitForSelectorAsync("#sse", new PageWaitForSelectorOptions() { State = WaitForSelectorState.Visible, Timeout = 10_000 });
Assert.NotNull(sse);
var content = await sse.InnerHTMLAsync();
Assert.Contains("sse 0", content);
Assert.Contains("sse 1", content);
await page.CloseAsync();
}

[Fact]
public async Task FormPost()
{
for (int i = 0; i < 10; i++)
{
var page = await _fixture.Browser.NewPageAsync();
await page.GotoAsync($"{_url}/html");
await page.RunAndWaitForResponseAsync(async () =>
{
await page.ClickAsync("body > form > button");
}, $"{_url}/protocol");
Assert.Contains("HTTP/3", await page.ContentAsync());
await page.CloseAsync();
}
}

[Fact]
public async Task GetFetchJs()
{
for (int i = 0; i < 10; i++)
{
var page = await _fixture.Browser.NewPageAsync();
await page.GotoAsync($"{_url}/html");
await page.ClickAsync("body > button:nth-child(2)");
var output = await page.WaitForSelectorAsync("#output", new PageWaitForSelectorOptions() { State = WaitForSelectorState.Visible, Timeout = 10_000 });
Assert.NotNull(output);
var content = await output.InnerHTMLAsync();
Assert.Contains("\"some content\"", content);
await page.CloseAsync();
}
}
}

37 changes: 37 additions & 0 deletions tests/CHttpServer.BrowserTests/Http3TestFixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System.Runtime.Versioning;
using CHttpServer.Tests;
using Microsoft.Playwright;

namespace CHttpServer.BrowserTests;

public sealed class Http3TestFixture : IAsyncDisposable
{
internal int Port => 7295;

public TestServer Server { get; private set; }

public IPlaywright PlaywrightHost { get; private set; }

public IBrowserContext Browser { get; private set; }

[SupportedOSPlatform("linux")]
[SupportedOSPlatform("windows")]
public Http3TestFixture()
{
Server = new TestServer();
Server.RunAsync(Port, false, useHttp3: true).GetAwaiter().GetResult();
PlaywrightHost = Playwright.CreateAsync().GetAwaiter().GetResult();
Browser = PlaywrightHost.Chromium.LaunchPersistentContextAsync("/tmp/chrome-profile-integrationtest", new()
{
Headless = true,
Args = [$"--origin-to-force-quic-on=127.0.0.1:{Port}", "--ignore-certificate-errors-spki-list=5QveYGg8xaCnnZWvkC9Y6v9lQVmF2BCozvds6Cn6F6k="]
}).GetAwaiter().GetResult();
}

public async ValueTask DisposeAsync()
{
await Browser.DisposeAsync();
PlaywrightHost.Dispose();
await Server.DisposeAsync();
}
}
3 changes: 1 addition & 2 deletions tests/CHttpServer.Tests/Http3/Http3IntegrationTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Net;
using System.Net.Http.Json;
using System.Text;

namespace CHttpServer.Tests.Http3;
Expand Down Expand Up @@ -347,7 +346,7 @@ public async Task TestServerSideEvents()
var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, TestContext.Current.CancellationToken);
Assert.True(response.IsSuccessStatusCode);
var content = await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken);
Assert.Equal("data: some content\n\ndata: some content\n\n", content);
Assert.Equal("data: sse 0\n\ndata: sse 1\n\n", content);
}
}

Loading
Loading