Skip to content

Commit f9547f5

Browse files
New TCP fallback example (#3911)
1 parent 9b27364 commit f9547f5

File tree

11 files changed

+279
-1
lines changed

11 files changed

+279
-1
lines changed

.vscode/cspell.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"Idempotency",
2828
"localizable",
2929
"Memberwise",
30+
"middlebox",
3031
"Nagle",
3132
"netstandard",
3233
"Nonmutating",
@@ -37,8 +38,8 @@
3738
"peekable",
3839
"pingable",
3940
"Proto",
40-
"Protoc",
4141
"Protobuf",
42+
"Protoc",
4243
"QUIC",
4344
"reportgenerator",
4445
"retryable",

build/CodeAnalysis.Examples.globalconfig

+7
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@ is_global = true
33
# CA2007: Consider calling ConfigureAwait on the awaited task
44
dotnet_diagnostic.CA2007.severity = none
55

6+
# CA1416: Validate platform compatibility
7+
# QUIC is nominally supported only on Windows, Linux, and macOS.
8+
dotnet_diagnostic.CA1416.severity = none
9+
10+
# CA1848: Use the LoggerMessage delegates
11+
dotnet_diagnostic.CA1848.severity = none
12+
613
# CA1849: Call async methods when in an async method
714
dotnet_diagnostic.CA1849.severity = suggestion
815

examples/slice/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ This folder contains example applications that showcase the IceRPC + Slice integ
2121
| [Retry](./Retry/) | Shows how to use the retry interceptor to retry failed requests. |
2222
| [Secure](./Secure/) | Shows how to secure TCP connections with TLS. |
2323
| [Stream](./Stream/) | Shows how to stream data from a client to a server. |
24+
| [TcpFallback](./TcpFallback/) | Shows how to create client and server applications that communicate over QUIC when possible but can fall back to TCP. |
2425
| [Telemetry](./Telemetry/) | Shows how to use the telemetry interceptor and middleware. |
2526
| [Thermostat](./Thermostat/) | Shows how to send requests via an intermediary server; includes sending requests the "other way around", from a server to a client. |
2627
| [Upload](./Upload/) | Shows how to upload a file from a client to a server by streaming this file. |
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project Sdk="Microsoft.NET.Sdk">
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net8.0</TargetFramework>
6+
<Nullable>enable</Nullable>
7+
<ImplicitUsings>enable</ImplicitUsings>
8+
<!-- Copy the PDBs from the NuGet packages to get file names and line numbers in stack traces. -->
9+
<CopyDebugSymbolFilesFromPackages>true</CopyDebugSymbolFilesFromPackages>
10+
<!-- Enable preview features to use the QUIC transport -->
11+
<EnablePreviewFeatures>True</EnablePreviewFeatures>
12+
</PropertyGroup>
13+
<ItemGroup>
14+
<SliceFile Include="../slice/Greeter.slice" />
15+
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.*" />
16+
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.*" />
17+
<PackageReference Include="IceRpc.Slice.Tools" Version="$(Version)" PrivateAssets="All" />
18+
<PackageReference Include="IceRpc.Slice" Version="$(Version)" />
19+
<PackageReference Include="IceRpc.Logger" Version="$(Version)" />
20+
<PackageReference Include="IceRpc.Transports.Quic" Version="$(Version)" />
21+
</ItemGroup>
22+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Copyright (c) ZeroC, Inc.
2+
3+
using IceRpc;
4+
using IceRpc.Transports.Quic;
5+
using Microsoft.Extensions.Logging;
6+
using System.Net.Security;
7+
using System.Security.Cryptography.X509Certificates;
8+
using VisitorCenter;
9+
10+
using ILoggerFactory loggerFactory = LoggerFactory.Create(builder =>
11+
builder
12+
.AddSimpleConsole()
13+
.AddFilter("IceRpc", LogLevel.Information));
14+
15+
// SSL setup
16+
using var rootCA = new X509Certificate2("../../../../certs/cacert.der");
17+
var clientAuthenticationOptions = new SslClientAuthenticationOptions
18+
{
19+
RemoteCertificateValidationCallback = (sender, certificate, chain, errors) =>
20+
{
21+
using var customChain = new X509Chain();
22+
customChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
23+
customChain.ChainPolicy.DisableCertificateDownloads = true;
24+
customChain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
25+
customChain.ChainPolicy.CustomTrustStore.Add(rootCA);
26+
return customChain.Build((X509Certificate2)certificate!);
27+
}
28+
};
29+
30+
// Use our own factory method (see below) to create a client connection using QUIC with a fallback to TCP.
31+
await using ClientConnection connection = await CreateClientConnectionAsync(
32+
new Uri("icerpc://localhost"),
33+
clientAuthenticationOptions,
34+
loggerFactory.CreateLogger<ClientConnection>());
35+
36+
// Create an invocation pipeline and install the logger interceptor.
37+
Pipeline pipeline = new Pipeline()
38+
.UseLogger(loggerFactory)
39+
.Into(connection);
40+
41+
// Create a greeter proxy with this invocation pipeline.
42+
var greeter = new GreeterProxy(pipeline);
43+
44+
string greeting = await greeter.GreetAsync(Environment.UserName);
45+
46+
Console.WriteLine(greeting);
47+
48+
await connection.ShutdownAsync();
49+
50+
// Creates a client connection connected to the server over QUIC if possible. If the QUIC connection establishment
51+
// fails, fall back to a TCP connection. The caller must dispose the returned connection.
52+
static async Task<ClientConnection> CreateClientConnectionAsync(
53+
Uri serverAddressUri,
54+
SslClientAuthenticationOptions clientAuthenticationOptions,
55+
ILogger logger)
56+
{
57+
#pragma warning disable CA2000 // Dispose objects before losing scope
58+
// This client connection is either returned to the caller after a successful ConnectAsync, or disposed if the
59+
// ConnectAsync fails.
60+
var quicConnection = new ClientConnection(
61+
serverAddressUri,
62+
clientAuthenticationOptions,
63+
multiplexedClientTransport: new QuicClientTransport(),
64+
logger: logger);
65+
#pragma warning restore CA2000
66+
67+
try
68+
{
69+
await quicConnection.ConnectAsync();
70+
return quicConnection;
71+
}
72+
catch (Exception exception)
73+
{
74+
logger.LogInformation(exception, "Failed to connect to server using QUIC, falling back to TCP");
75+
await quicConnection.DisposeAsync();
76+
}
77+
78+
#pragma warning disable CA2000 // Dispose objects before losing scope
79+
return new ClientConnection(serverAddressUri, clientAuthenticationOptions, logger: logger);
80+
#pragma warning restore CA2000
81+
}

examples/slice/TcpFallback/README.md

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# TCP Fallback
2+
3+
This example shows how a client can attempt to connect to a server with QUIC and fallback to TCP when the QUIC
4+
connection establishment fails - for example, because a middlebox between the client and server does not let QUIC
5+
connections through.
6+
7+
The server program creates a server for QUIC and another server for TCP; both servers share the same dispatch pipeline.
8+
9+
The client program first attempts to establish a QUIC connection; if this connection establishment fails, it falls back
10+
to a TCP connection.
11+
12+
You can build the client and server applications with:
13+
14+
``` shell
15+
dotnet build
16+
```
17+
18+
First start the Server program:
19+
20+
```shell
21+
cd Server
22+
dotnet run
23+
```
24+
25+
In a separate terminal, start the Client program:
26+
27+
```shell
28+
cd Client
29+
dotnet run
30+
```
31+
32+
In order to see the fallback to TCP, run the TCP fallback client with the server from the [../Secure] example.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright (c) ZeroC, Inc.
2+
3+
using IceRpc.Features;
4+
using IceRpc.Slice;
5+
using VisitorCenter;
6+
7+
namespace TcpFallbackServer;
8+
9+
/// <summary>A Chatbot is an IceRPC service that implements Slice interface 'Greeter'.</summary>
10+
[SliceService]
11+
internal partial class Chatbot : IGreeterService
12+
{
13+
public ValueTask<string> GreetAsync(string name, IFeatureCollection features, CancellationToken cancellationToken)
14+
{
15+
Console.WriteLine($"Dispatching greet request {{ name = '{name}' }}");
16+
return new($"Hello, {name}!");
17+
}
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright (c) ZeroC, Inc.
2+
3+
using IceRpc;
4+
using IceRpc.Transports.Quic;
5+
using Microsoft.Extensions.Logging;
6+
using System.Net.Security;
7+
using System.Security.Cryptography.X509Certificates;
8+
using TcpFallbackServer;
9+
using VisitorCenter;
10+
11+
using ILoggerFactory loggerFactory = LoggerFactory.Create(builder =>
12+
builder
13+
.AddSimpleConsole()
14+
.AddFilter("IceRpc", LogLevel.Debug));
15+
16+
// Create a router (dispatch pipeline) with the greeter service.
17+
Router router = new Router()
18+
.Map<IGreeterService>(new Chatbot());
19+
20+
// Create two servers that share the same dispatch pipeline.
21+
await using var quicServer = new Server(
22+
router,
23+
new SslServerAuthenticationOptions
24+
{
25+
ServerCertificate = new X509Certificate2("../../../../certs/server.p12")
26+
},
27+
multiplexedServerTransport: new QuicServerTransport(),
28+
logger: loggerFactory.CreateLogger<Server>());
29+
30+
quicServer.Listen();
31+
32+
await using var tcpServer = new Server(
33+
router,
34+
new SslServerAuthenticationOptions
35+
{
36+
ServerCertificate = new X509Certificate2("../../../../certs/server.p12")
37+
},
38+
logger: loggerFactory.CreateLogger<Server>());
39+
40+
tcpServer.Listen();
41+
42+
// Wait until the console receives a Ctrl+C.
43+
await CancelKeyPressed;
44+
await Task.WhenAll(quicServer.ShutdownAsync(), tcpServer.ShutdownAsync());
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project Sdk="Microsoft.NET.Sdk">
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net8.0</TargetFramework>
6+
<Nullable>enable</Nullable>
7+
<ImplicitUsings>enable</ImplicitUsings>
8+
<!-- Copy the PDBs from the NuGet packages to get file names and line numbers in stack traces. -->
9+
<CopyDebugSymbolFilesFromPackages>true</CopyDebugSymbolFilesFromPackages>
10+
<!-- Enable preview features to use the QUIC transport -->
11+
<EnablePreviewFeatures>True</EnablePreviewFeatures>
12+
</PropertyGroup>
13+
<ItemGroup>
14+
<SliceFile Include="../slice/Greeter.slice" />
15+
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.*" />
16+
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.*" />
17+
<PackageReference Include="IceRpc.Slice.Tools" Version="$(Version)" PrivateAssets="All" />
18+
<PackageReference Include="IceRpc.Slice" Version="$(Version)" />
19+
<PackageReference Include="IceRpc.Transports.Quic" Version="$(Version)" />
20+
<Compile Include="../../../common/Program.CancelKeyPressed.cs" Link="Program.CancelKeyPressed.cs" />
21+
<SupportedPlatform Include="Linux" />
22+
<SupportedPlatform Include="macOS" />
23+
<SupportedPlatform Include="Windows" />
24+
</ItemGroup>
25+
</Project>
+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 17
4+
VisualStudioVersion = 17.4.33122.133
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Client", "Client\Client.csproj", "{BBF1199A-46A4-4AE9-AFFE-4D8DD59EB874}"
7+
EndProject
8+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Server", "Server\Server.csproj", "{527EEA4D-77B9-4252-A2CD-C641A25CAD53}"
9+
EndProject
10+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{FBDAF448-665A-4595-98D3-4538C56A666D}"
11+
ProjectSection(SolutionItems) = preProject
12+
README.md = README.md
13+
EndProjectSection
14+
EndProject
15+
Global
16+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
17+
Debug|Any CPU = Debug|Any CPU
18+
Release|Any CPU = Release|Any CPU
19+
EndGlobalSection
20+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
21+
{BBF1199A-46A4-4AE9-AFFE-4D8DD59EB874}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
22+
{BBF1199A-46A4-4AE9-AFFE-4D8DD59EB874}.Debug|Any CPU.Build.0 = Debug|Any CPU
23+
{BBF1199A-46A4-4AE9-AFFE-4D8DD59EB874}.Release|Any CPU.ActiveCfg = Release|Any CPU
24+
{BBF1199A-46A4-4AE9-AFFE-4D8DD59EB874}.Release|Any CPU.Build.0 = Release|Any CPU
25+
{527EEA4D-77B9-4252-A2CD-C641A25CAD53}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
26+
{527EEA4D-77B9-4252-A2CD-C641A25CAD53}.Debug|Any CPU.Build.0 = Debug|Any CPU
27+
{527EEA4D-77B9-4252-A2CD-C641A25CAD53}.Release|Any CPU.ActiveCfg = Release|Any CPU
28+
{527EEA4D-77B9-4252-A2CD-C641A25CAD53}.Release|Any CPU.Build.0 = Release|Any CPU
29+
EndGlobalSection
30+
GlobalSection(SolutionProperties) = preSolution
31+
HideSolutionNode = FALSE
32+
EndGlobalSection
33+
GlobalSection(ExtensibilityGlobals) = postSolution
34+
SolutionGuid = {0B36F1E1-0592-4A15-9981-67BC4A653EC4}
35+
EndGlobalSection
36+
EndGlobal
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Copyright (c) ZeroC, Inc.
2+
3+
module VisitorCenter
4+
5+
/// Represents a simple greeter.
6+
interface Greeter {
7+
/// Creates a personalized greeting.
8+
/// @param name: The name of the person to greet.
9+
/// @returns: The greeting.
10+
greet(name: string) -> string
11+
}

0 commit comments

Comments
 (0)