Skip to content

Commit

Permalink
Merge pull request #108 from Cysharp/feature/DowngradeHttpVersion
Browse files Browse the repository at this point in the history
Allow downgrading of HTTP version
  • Loading branch information
mayuki authored Dec 16, 2024
2 parents e50334c + a455fb2 commit a7338d3
Show file tree
Hide file tree
Showing 17 changed files with 232 additions and 265 deletions.
30 changes: 15 additions & 15 deletions src/YetAnotherHttpHandler/NativeHttpHandlerCore.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
using System;
using System.Buffers;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Net;
using System.Text;
using System.IO.Pipelines;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -197,7 +192,7 @@ private unsafe void Initialize(YahaNativeContext* ctx, NativeClientSettings sett

public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (YahaEventSource.Log.IsEnabled()) YahaEventSource.Log.Info($"HttpMessageHandler.SendAsync: {request.RequestUri}");
if (YahaEventSource.Log.IsEnabled()) YahaEventSource.Log.Info($"HttpMessageHandler.SendAsync: {request.RequestUri}; Method={request.Method}; Version={request.Version}");

var requestContext = Send(request, cancellationToken);

Expand Down Expand Up @@ -300,14 +295,19 @@ private unsafe RequestContext UnsafeSend(YahaContextSafeHandle ctxHandle, YahaRe
}

// Set HTTP version
var version = request.Version switch
{
var v when v == HttpVersion.Version10 => YahaHttpVersion.Http10,
var v when v == HttpVersion.Version11 => YahaHttpVersion.Http11,
var v when v == HttpVersionShim.Version20 => YahaHttpVersion.Http2,
_ => throw new NotSupportedException($"Unsupported HTTP version '{request.Version}'"),
};
NativeMethods.yaha_request_set_version(ctx, reqCtx, version);
// NOTE: Following reasons are why we should ignore HttpRequestMessage.Version.
// - If the request version is specified, Hyper expects it to match the version provided by the server, so specifying HttpVersion.Version20 will cause an error even when connecting to an HTTP/1 server over HTTPS.
// - The official .NET default behavior allows downgrading the HTTP request version.
// - If http2_only is not specified or false in Hyper, the version is determined by ALPN negotiation in TLS, so there is no need to set the version of the request.
// - If the server supports HTTP/2, HTTP/2 is selected by ALPN or h2c is used with http2_only, so there is no need to specify the request version.
//var version = request.Version switch
//{
// var v when v == HttpVersion.Version10 => YahaHttpVersion.Http10,
// var v when v == HttpVersion.Version11 => YahaHttpVersion.Http11,
// var v when v == HttpVersionShim.Version20 => YahaHttpVersion.Http2,
// _ => throw new NotSupportedException($"Unsupported HTTP version '{request.Version}'"),
//};
//NativeMethods.yaha_request_set_version(ctx, reqCtx, version);

// Prepare body channel
NativeMethods.yaha_request_set_has_body(ctx, reqCtx, request.Content != null);
Expand Down Expand Up @@ -415,7 +415,7 @@ private static unsafe bool OnServerCertificateVerification(IntPtr callbackState,
var certificateDer = new ReadOnlySpan<byte>(certificateDerPtr, (int)certificateDerLength);
if (YahaEventSource.Log.IsEnabled()) YahaEventSource.Log.Trace($"OnServerCertificateVerification: State=0x{callbackState:X}; ServerName={serverName}; CertificateDer.Length={certificateDer.Length}; Now={now}");

var onServerCertificateVerification = (ServerCertificateVerificationHandler)GCHandle.FromIntPtr(callbackState).Target;
var onServerCertificateVerification = (ServerCertificateVerificationHandler?)GCHandle.FromIntPtr(callbackState).Target;
Debug.Assert(onServerCertificateVerification != null);
if (onServerCertificateVerification == null)
{
Expand Down

This file was deleted.

This file was deleted.

11 changes: 0 additions & 11 deletions test/YetAnotherHttpHandler.Test/Helpers/Testing/ITestCondition.cs

This file was deleted.

This file was deleted.

This file was deleted.

49 changes: 0 additions & 49 deletions test/YetAnotherHttpHandler.Test/Helpers/Testing/SkippedTestCase.cs

This file was deleted.

This file was deleted.

61 changes: 61 additions & 0 deletions test/YetAnotherHttpHandler.Test/Http1Test.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,67 @@ public async Task FailedToConnect_VersionMismatch()
Assert.Contains("'HTTP_1_1_REQUIRED' (0xd)", ex.Message);
}

[Fact]
public async Task Request_Version_20_Http1OnlyServer_Secure()
{
// Arrange
using var httpHandler = new Cysharp.Net.Http.YetAnotherHttpHandler()
{
// We need to verify server certificate.
SkipCertificateVerification = false,
RootCertificates = File.ReadAllText("./Certificates/localhost.crt")
};
var httpClient = new HttpClient(httpHandler);
await using var server = await LaunchServerAsync<TestServerForHttp1AndHttp2>(TestWebAppServerListenMode.SecureHttp1Only, builder =>
{
// Use self-signed certificate for testing purpose.
builder.WebHost.ConfigureKestrel(options =>
{
options.ConfigureHttpsDefaults(options =>
{
options.ServerCertificate = new X509Certificate2("Certificates/localhost.pfx");
});
});
});
var request = new HttpRequestMessage(HttpMethod.Get, $"{server.BaseUri}/")
{
Version = HttpVersion.Version20,
VersionPolicy = HttpVersionPolicy.RequestVersionOrLower, // Allow downgrade to HTTP/1.1. This is the default behavior on all .NET versions.
};

// Act
var response = await httpClient.SendAsync(request);
var responseBody = await response.Content.ReadAsStringAsync();

// Assert
Assert.Equal(HttpVersion.Version11, response.Version);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("__OK__", responseBody);
}

[Fact]
public async Task Request_Version_Downgrade()
{
// Arrange
using var httpHandler = new Cysharp.Net.Http.YetAnotherHttpHandler();
var httpClient = new HttpClient(httpHandler);
await using var server = await LaunchServerAsync<TestServerForHttp1AndHttp2>(TestWebAppServerListenMode.InsecureHttp1Only);
var request = new HttpRequestMessage(HttpMethod.Get, $"{server.BaseUri}/")
{
Version = HttpVersion.Version20,
VersionPolicy = HttpVersionPolicy.RequestVersionOrLower, // Allow downgrade to HTTP/1.1. This is the default behavior on all .NET versions.
};

// Act
var response = await httpClient.SendAsync(request);
var responseBody = await response.Content.ReadAsStringAsync();

// Assert
Assert.Equal(HttpVersion.Version11, response.Version);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("__OK__", responseBody);
}

[Fact]
public async Task Get_Ok()
{
Expand Down
Loading

0 comments on commit a7338d3

Please sign in to comment.