Unable to publish a message to the Pub/Sub Emulator using C# .NET Framework 4.8

233 Views Asked by At

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?

2

There are 2 best solutions below

1
On

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:

System.Net.Sockets.SocketException (10054): An existing connection was forcibly closed by the remote host.

and

Grpc.Core.RpcException : Status(StatusCode="Internal", Detail="Bad gRPC response. Response protocol downgraded to HTTP/1.1.")

This can easily be fixed using an AppContext switch:

AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);

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.

0
On

Using the AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2Support", true); one way to go, however, the 1st line of your exceptions state that there was a mismatch between the server and client communication method (HTTP/1.1 vs HTTP/2). This might be related to the version of your Google Cloud PubSub V1 package, ensure that you are using the latest version for the .NET Framework 4.8 and HTTP/2 to work properly, try installing the most recent release, Version 3.8.0.

Adding this here as well for reference: