Skip to content

add tcp keep alive hack to net5 target #560

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Mar 11, 2021
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
2 changes: 1 addition & 1 deletion .github/workflows/buildtest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
- name: Check Format
# don't check formatting on Windows b/c of CRLF issues.
if: matrix.os != 'windows-latest'
run: dotnet format --check --dry-run --exclude ./src/KubernetesClient/generated/
run: dotnet format --check --exclude ./src/KubernetesClient/generated/
- name: Build
run: dotnet build --configuration Release
- name: Test
Expand Down
35 changes: 35 additions & 0 deletions src/KubernetesClient/Kubernetes.ConfigInit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
using System.Net;
using System.Net.Http;
using System.Net.Security;
using System.Net.Sockets;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using k8s.Exceptions;
using k8s.Models;
Expand Down Expand Up @@ -166,6 +168,39 @@ private void AppendDelegatingHandler<T>()
private void CreateHttpClient(DelegatingHandler[] handlers)
{
FirstMessageHandler = HttpClientHandler = CreateRootHandler();


#if NET5_0

// https://github.com/kubernetes-client/csharp/issues/533
// net5 only
// this is a temp fix to attach SocketsHttpHandler to HttpClient in order to set SO_KEEPALIVE
// https://tldp.org/HOWTO/html_single/TCP-Keepalive-HOWTO/
//
// _underlyingHandler is not a public accessible field
// src of net5 HttpClientHandler and _underlyingHandler field defined here
// https://github.com/dotnet/runtime/blob/79ae74f5ca5c8a6fe3a48935e85bd7374959c570/src/libraries/System.Net.Http/src/System/Net/Http/HttpClientHandler.cs#L22
//
// Should remove after better solution

var sh = new SocketsHttpHandler();
sh.ConnectCallback = async (context, token) =>
{
var socket = new Socket(SocketType.Stream, ProtocolType.Tcp)
{
NoDelay = true,
};

socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);

await socket.ConnectAsync(context.DnsEndPoint.Host, context.DnsEndPoint.Port, token).ConfigureAwait(false);
return new NetworkStream(socket, ownsSocket: true);
};

var p = HttpClientHandler.GetType().GetField("_underlyingHandler", BindingFlags.NonPublic | BindingFlags.Instance);
p.SetValue(HttpClientHandler, (sh));
#endif

if (handlers == null || handlers.Length == 0)
{
// ensure we have at least one DelegatingHandler so AppendDelegatingHandler will work
Expand Down
4 changes: 4 additions & 0 deletions src/KubernetesClient/Kubernetes.Watch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,11 @@ public async Task<Watcher<T>> WatchObjectAsync<T>(string path, string @continue
httpResponse.StatusCode));
if (httpResponse.Content != null)
{
#if NET5_0
responseContent = await httpResponse.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
#else
responseContent = await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
#endif
}

ex.Request = new HttpRequestMessageWrapper(httpRequest, responseContent);
Expand Down
9 changes: 6 additions & 3 deletions src/KubernetesClient/Kubernetes.WebSocket.cs
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ protected async Task<WebSocket> StreamConnectAsync(Uri uri, string invocationId
}
#endif

#if NETSTANDARD2_1
#if NETSTANDARD2_1 || NET5_0
if (this.CaCerts != null)
{
webSocketBuilder.ExpectServerCertificate(this.CaCerts);
Expand All @@ -334,7 +334,7 @@ protected async Task<WebSocket> StreamConnectAsync(Uri uri, string invocationId
{
webSocketBuilder.Options.AddSubProtocol(webSocketSubProtocol);
}
#endif // NETSTANDARD2_1
#endif // NETSTANDARD2_1 || NET5_0

// Send Request
cancellationToken.ThrowIfCancellationRequested();
Expand Down Expand Up @@ -366,8 +366,11 @@ protected async Task<WebSocket> StreamConnectAsync(Uri uri, string invocationId
}
else
{
#if NET5_0
var content = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
#else
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);

#endif
// Try to parse the content as a V1Status object
var genericObject = SafeJsonConvert.DeserializeObject<KubernetesObject>(content);
V1Status status = null;
Expand Down
2 changes: 1 addition & 1 deletion src/KubernetesClient/KubernetesClient.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<PackageIconUrl>https://raw.githubusercontent.com/kubernetes/kubernetes/master/logo/logo.png</PackageIconUrl>
<PackageTags>kubernetes;docker;containers;</PackageTags>

<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
<TargetFrameworks>netstandard2.0;netstandard2.1;net5</TargetFrameworks>
<RootNamespace>k8s</RootNamespace>
<SignAssembly>true</SignAssembly>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
Expand Down
4 changes: 2 additions & 2 deletions src/KubernetesClient/WebSocketBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public void CleanupServerCertificateValidationCallback(RemoteCertificateValidati
}
#endif

#if NETSTANDARD2_1
#if NETSTANDARD2_1 || NET5_0
public WebSocketBuilder ExpectServerCertificate(X509Certificate2Collection serverCertificate)
{
Options.RemoteCertificateValidationCallback
Expand All @@ -70,7 +70,7 @@ public WebSocketBuilder SkipServerCertificateValidation()
return this;
}

#endif // NETSTANDARD2_1
#endif // NETSTANDARD2_1 || NET5_0

public virtual async Task<WebSocket> BuildAndConnectAsync(Uri uri, CancellationToken cancellationToken)
{
Expand Down
4 changes: 2 additions & 2 deletions tests/KubernetesClient.Tests/AuthTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ public void BasicAuth()
}
}

#if NETSTANDARD2_1 // The functionality under test, here, is dependent on managed HTTP / WebSocket in .NET Core 2.1 or newer.
#if NETSTANDARD2_1 || NET5_0 // The functionality under test, here, is dependent on managed HTTP / WebSocket in .NET Core 2.1 or newer.
// this test doesn't work on OSX and is inconsistent on windows
[OperatingSystemDependentFact(Exclude = OperatingSystems.OSX | OperatingSystems.Windows)]
public void Cert()
Expand Down Expand Up @@ -338,7 +338,7 @@ public void ExternalCertificate()
}
}
}
#endif // NETSTANDARD2_1
#endif // NETSTANDARD2_1 || NET5_0

[Fact]
public void ExternalToken()
Expand Down
2 changes: 1 addition & 1 deletion tests/KubernetesClient.Tests/KubernetesExecTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#if !NETSTANDARD2_1
#if !NETSTANDARD2_1 || NET5_0
/*
* These tests are only for the netstandard version of the client (there are separate tests for netcoreapp that connect to a local test-hosted server).
*/
Expand Down
4 changes: 2 additions & 2 deletions tests/KubernetesClient.Tests/Mock/MockWebSocketBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#if !NETSTANDARD2_1
#if !NETSTANDARD2_1 || NET5_0
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
Expand Down Expand Up @@ -43,4 +43,4 @@ public override WebSocketBuilder SetRequestHeader(string headerName, string head
}
}

#endif // !NETSTANDARD2_1
#endif // !NETSTANDARD2_1 || NET5_0