I'm not able to publish a message to the Google Pub/Sub emulator using C# .NET Framework 4.8 . In production, the code works. This is just happening with the gcloud emulator.
Also, the same code works with C# .NET 8.0 and the gcloud emulator. However, I need the code to work with .NET Framework 4.8.
Both are using the same packages:
Google.Api.Gax = 4.4.0
Google.Cloud.PubSub.V1 = 3.8.0
gcloud is updated to the latest version:
>gcloud version
Google Cloud SDK 455.0.0
beta 2023.11.10
bq 2.0.98
core 2023.11.10
gsutil 5.27
pubsub-emulator 0.8.10
I run the emulator with this command:
gcloud beta emulators pubsub start --project=my-project --host-port=localhost:8085
Then create a topic called my-topic
.
I create the environmental variable:
setx PUBSUB_EMULATOR_HOST "localhost:8085"
C# .NET Framework 4.8 Code:
using Google.Api.Gax;
using Google.Cloud.PubSub.V1;
using System;
using System.Threading.Tasks;
namespace PubSubDotNetTest
{
internal class Program
{
static void Main(string[] args)
{
AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2Support", true);
Subscriber().GetAwaiter().GetResult();
}
static async Task Subscriber()
{
Console.WriteLine("Starting publisher...");
var projectId = "my-project";
var topicId = "my-topic";
PublisherServiceApiClient publisherService = await new PublisherServiceApiClientBuilder
{
EmulatorDetection = EmulatorDetection.EmulatorOrProduction
}.BuildAsync();
TopicName topicName = new TopicName(projectId, topicId);
PublisherClient publisher = await new PublisherClientBuilder
{
TopicName = topicName,
EmulatorDetection = EmulatorDetection.EmulatorOrProduction
}.BuildAsync();
await publisher.PublishAsync("Hello, Pubsub1");
await publisher.ShutdownAsync(TimeSpan.FromSeconds(15));
}
}
}
It comes back with this exception:
Unhandled Exception: Grpc.Core.RpcException: Status(StatusCode="Internal", Detail="Bad gRPC response. Response protocol downgraded to HTTP/1.1.")
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Google.Api.Gax.Grpc.ApiCallRetryExtensions.<>c__DisplayClass0_0`2.<<WithRetry>b__0>d.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Google.Cloud.PubSub.V1.Tasks.ForwardingAwaiter`1.GetResult() in /_/apis/Google.Cloud.PubSub.V1/Google.Cloud.PubSub.V1/Tasks/ForwardingAwaiter.cs:line 41
at Google.Cloud.PubSub.V1.Tasks.Extensions.<>c__DisplayClass5_0`1.<<ConfigureAwaitHideErrors>g__Inner|0>d.MoveNext() in /_/apis/Google.Cloud.PubSub.V1/Google.Cloud.PubSub.V1/Tasks/TaskHelper.cs:line 174
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Google.Cloud.PubSub.V1.Tasks.ForwardingAwaiter`1.GetResult() in /_/apis/Google.Cloud.PubSub.V1/Google.Cloud.PubSub.V1/Tasks/ForwardingAwaiter.cs:line 41
at Google.Cloud.PubSub.V1.PublisherClientImpl.<PublishAsync>d__32.MoveNext() in /_/apis/Google.Cloud.PubSub.V1/Google.Cloud.PubSub.V1/PublisherClientImpl.cs:line 247
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at PubSubDotNetTest.Program.<Subscriber>d__1.MoveNext() in C:\MyPath\Program.cs:line 37
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
at PubSubDotNetTest.Program.Main(String[] args) in C:\MyPath\Program.cs:line 12
This is shown in the emulator console when the exception is thrown:
[pubsub] Nov 21, 2023 10:37:46 AM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
[pubsub] INFO: Detected non-HTTP/2 connection.
[pubsub] Nov 21, 2023 10:37:46 AM io.gapi.emulators.netty.NotFoundHandler handleRequest
[pubsub] INFO: Unknown request URI: /google.pubsub.v1.Publisher/Publish
[pubsub] Nov 21, 2023 10:37:46 AM io.gapi.emulators.netty.NotFoundHandler handleRequest
[pubsub] INFO: Unknown request URI: /google.pubsub.v1.Publisher/Publish
[pubsub] Nov 21, 2023 10:37:46 AM io.gapi.emulators.netty.NotFoundHandler handleRequest
[pubsub] INFO: Unknown request URI: /google.pubsub.v1.Publisher/Publish
[pubsub] Nov 21, 2023 10:37:47 AM io.gapi.emulators.netty.NotFoundHandler handleRequest
[pubsub] INFO: Unknown request URI: /google.pubsub.v1.Publisher/Publish
[pubsub] Nov 21, 2023 10:37:52 AM io.gapi.emulators.netty.NotFoundHandler handleRequest
[pubsub] INFO: Unknown request URI: /google.pubsub.v1.Publisher/Publish
I tried adding this line, as the first line in the Main method:
AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
But it didn't resolve the problem.
I also tried this code:
static async Task Subscriber()
{
Console.WriteLine("Starting publisher...");
var projectId = "project-id";
var topicId = "topic-id";
string emulatorHostAndPort = Environment.GetEnvironmentVariable("PUBSUB_EMULATOR_HOST");
PublisherServiceApiClient publisherService = await new PublisherServiceApiClientBuilder
{
Endpoint = emulatorHostAndPort,
ChannelCredentials = ChannelCredentials.Insecure
}.BuildAsync();
TopicName topicName = new TopicName(projectId, topicId);
PublisherClient publisher = await new PublisherClientBuilder
{
TopicName = topicName,
Endpoint = emulatorHostAndPort,
ChannelCredentials = ChannelCredentials.Insecure
}.BuildAsync();
await publisher.PublishAsync("Hello, Pubsub1");
await publisher.ShutdownAsync(TimeSpan.FromSeconds(15));
}
But it comes back with the same exception.
C# .NET 8.0 Code:
using Google.Api.Gax;
using Google.Cloud.PubSub.V1;
Console.WriteLine("Starting publisher...");
var projectId = "my-project";
var topicId = "my-topic";
PublisherServiceApiClient publisherService = await new PublisherServiceApiClientBuilder
{
EmulatorDetection = EmulatorDetection.EmulatorOrProduction
}.BuildAsync();
TopicName topicName = new TopicName(projectId, topicId);
PublisherClient publisher = await new PublisherClientBuilder
{
TopicName = topicName,
EmulatorDetection = EmulatorDetection.EmulatorOrProduction
}.BuildAsync();
await publisher.PublishAsync("Hello, Pubsub2");
await publisher.ShutdownAsync(TimeSpan.FromSeconds(15));
This works well, and my Pub/Sub emulator receives the message.
How can I get this working with .NET Framework 4.8?
See
Grpc.Net.Client and .NET Core 3.1 page
section of the .NET's "Emulator support" documentation:https://cloud.google.com/dotnet/docs/reference/help/emulators#grpcnetclient-and-net-core-31
As of GAX v4, the default gRPC implementation (where available) is Grpc.Net.Client. (See the transport selection documentation for more details.) By default, .NET Core 3.1 does not support unencrypted HTTP/2 connections - whereas this is required for emulator connections. Without any additional code, .NET Core 3.1 connections to the emulator will fail with a variety of error messages, including:
and
This can easily be fixed using an AppContext switch:
This code is deliberately not included in the client libraries, as it is an application-wide switch that you should consider carefully before enabling. In most cases we expect that it's safe and appropriate when testing an application against an emulator, but that's not a decision that the client libraries can reasonably take for themselves. This switch is not required when running .NET 6.