grpc / grpc-dotnet

gRPC for .NET
Apache License 2.0
4.2k stars 770 forks source link

Does it support HTTP / 2 Connection Preface? #1647

Closed m-yukio closed 2 years ago

m-yukio commented 2 years ago

What version of gRPC and what language are you using?

What operating system (Linux, Windows,...) and version?

What runtime / compiler are you using (e.g. .NET Core SDK version dotnet --info)

What did you do?

This is the gRPC Go log on the server side.

2022/03/18 16:57:52 server listening at 192.168.200.3:50051
2022/03/18 16:58:12 http2: Framer 0xc0002a2000: wrote SETTINGS len=6, settings: MAX_FRAME_SIZE=16384
WARNING: 2022/03/18 16:58:12 [core] grpc: Server.Serve failed to create ServerTransport:  connection error: desc = "transport: http2Server.HandleStreams received bogus greeting from client: \"POST /helloworld.Greeter\""

Below is the gRPC Go code that printed the error. internal/transport/http2_server.go

292 // Check the validity of client preface.
293 preface := make([]byte, len(clientPreface))
294 if _, err := io.ReadFull(t.conn, preface); err != nil {
295     // In deployments where a gRPC server runs behind a cloud load balancer
296     // which performs regular TCP level health checks, the connection is
297     // closed immediately by the latter.  Returning io.EOF here allows the
298     // grpc server implementation to recognize this scenario and suppress
299     // logging to reduce spam.
300     if err == io.EOF {
301         return nil, io.EOF
302     }
303     return nil, connectionErrorf(false, err, "transport: http2Server.HandleStreams failed to receive the preface from client: %v", err)
304 }
305 if !bytes.Equal(preface, clientPreface) {
306     return nil, connectionErrorf(false, nil, "transport: http2Server.HandleStreams received bogus greeting from client: %q", preface)
307 }

What did you expect to see?

What did you see instead?

It doesn't seem to support "3.4. HTTP / 2 Connection Preface". https://httpwg.org/http2-spec/draft-ietf-httpbis-http2bis.html#name-http-2-connection-preface

The gRPC core library supports the following points.

https://github.com/grpc/grpc/blob/master/src/core/ext/transport/chttp2/transport/chttp2_transport.cc#L492

The gRPC-Go supports the following points.

https://github.com/grpc/grpc-go/blob/master/internal/transport/http2_client.go#L368

JamesNK commented 2 years ago

Looks like the client sent an HTTP/1.1 request to the server. The server is rejecting it because it doesn't start with the HTTP/2 preface.

Unity might not support HTTP/2. I'm not a Unity expert and I'm not sure the right person for you to talk to.

m-yukio commented 2 years ago

If you comment out the HttpHandler setting in GrpcChannelOptions with the following code

        string certPath = System.IO.Path.Combine(Application.streamingAssetsPath, "example.crt");
        string keyPath = System.IO.Path.Combine(Application.streamingAssetsPath, "example.key");
        string caPath = System.IO.Path.Combine(Application.streamingAssetsPath, "root_ca.crt");
        HttpClientHandler handler = new HttpClientHandler();
        handler.ServerCertificateCustomValidationCallback = (HttpRequestMessage request, X509Certificate2 certificate, X509Chain certificateChain, SslPolicyErrors policy) =>
        {
            Debug.Log("handler.ServerCertificateCustomValidationCallback");
            return true;
        };
        handler.ClientCertificates.Add(new X509Certificate2(certPath));
        Grpc.Core.ChannelBase channel = GrpcChannel.ForAddress("https://192.168.200.3:50051", new GrpcChannelOptions
        {
            HttpHandler = handler
        });

I got the following error in Unity, so I thought that HTTP / 2 support was possible.

PlatformNotSupportedException: gRPC requires extra configuration on .NET implementations that don't support gRPC over HTTP/2. An HTTP provider must be specified using GrpcChannelOptions.HttpHandler.The configured HTTP provider must either support HTTP/2 or be configured to use gRPC-Web. See https://aka.ms/aspnet/grpc/netstandard for details.
Grpc.Shared.HttpHandlerFactory.CreatePrimaryHandler () (at <93d755b016cc4513ac20ab2598098c88>:0)
Grpc.Net.Client.GrpcChannel.CreateInternalHttpInvoker (System.Net.Http.HttpMessageHandler handler) (at <93d755b016cc4513ac20ab2598098c88>:0)
Grpc.Net.Client.GrpcChannel..ctor (System.Uri address, Grpc.Net.Client.GrpcChannelOptions channelOptions) (at <93d755b016cc4513ac20ab2598098c88>:0)
Grpc.Net.Client.GrpcChannel.ForAddress (System.Uri address, Grpc.Net.Client.GrpcChannelOptions channelOptions) (at <93d755b016cc4513ac20ab2598098c88>:0)
Grpc.Net.Client.GrpcChannel.ForAddress (System.String address, Grpc.Net.Client.GrpcChannelOptions channelOptions) (at <93d755b016cc4513ac20ab2598098c88>:0)
GrpcTest.RunHelloWorld (System.Boolean useNet) (at Assets/Scripts/GrpcTest.cs:70)
GrpcTest.RunNet () (at Assets/Scripts/GrpcTest.cs:23)
UnityEngine.Events.InvokableCall.Invoke () (at /Users/bokken/buildslave/unity/build/Runtime/Export/UnityEvent/UnityEvent.cs:178)
UnityEngine.Events.UnityEvent.Invoke () (at /Users/bokken/buildslave/unity/build/Runtime/Export/UnityEvent/UnityEvent/UnityEvent_0.cs:58)
UnityEngine.UI.Button.Press () (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/UI/Core/Button.cs:70)
UnityEngine.UI.Button.OnPointerClick (UnityEngine.EventSystems.PointerEventData eventData) (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/UI/Core/Button.cs:114)
UnityEngine.EventSystems.ExecuteEvents.Execute (UnityEngine.EventSystems.IPointerClickHandler handler, UnityEngine.EventSystems.BaseEventData eventData) (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/ExecuteEvents.cs:57)
UnityEngine.EventSystems.ExecuteEvents.Execute[T] (UnityEngine.GameObject target, UnityEngine.EventSystems.BaseEventData eventData, UnityEngine.EventSystems.ExecuteEvents+EventFunction`1[T1] functor) (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/ExecuteEvents.cs:272)
UnityEngine.EventSystems.EventSystem:Update() (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/EventSystem.cs:501)

I was thinking of setting HttpClientHandler to HttpHandler in GrpcChannelOptions when communicating via HTTP / 2 instead of HTTP / 1.1. Is my understanding wrong?

JamesNK commented 2 years ago

"The configured HTTP provider must either support HTTP/2 or be configured to use gRPC-Web."

That's the important part from the error message. HttpClientMessageHandler on that platform - Unity - might not support HTTP/2. Like I said, I'm not a Unity expert.

m-yukio commented 2 years ago

Thank you.

mikerochip commented 2 years ago

Found this through Twitter! I have a feeling this issue should be closed since Unity isn't an official dotnet runtime.

@m-yukio you might want to reconsider using a NuGet client directly in Unity,. The client you're referencing (https://github.com/GlitchEnzo/NuGetForUnity) is a user-created thing, and it doesn't seem to have the best support (the original author hasn't contributed in years, CI is constantly broken, install instructions point to a bad link). NuGet is intended for .Net Framework, .Net Core, and .Net applications. Unity's .net runtime isn't one of those; it's a custom fork of mono, so even if a package compiles, the runtime may fail (which is what happened in your case). If you really want a NuGet client in Unity this might be more reliable https://github.com/xoofx/UnityNuGet since it's upm compatible and points to a curated list of NuGet packages that work in Unity, but the server for that is pointing to the author's personal azure instance so you still have to proceed with caution if you're planning on making daily builds. Honestly there just isn't a really reliable way to make NuGet work in Unity; I've talked to the upm devs about this a couple years ago and that was the direction they chose.

If you really want NuGet packages though; I hate to say it but I'd honestly recommend downloading directly off NuGet, then vendor the package into your project, and do lots of testing. Official MS docs are a bit outdated, but they actually recommend this approach (https://docs.microsoft.com/en-us/visualstudio/gamedev/unity/unity-scripting-upgrade#add-packages-from-nuget-to-a-unity-project).

m-yukio commented 2 years ago

Thank you. Please refer to the contents explained and try it.

m-yukio commented 2 years ago

I confirmed it with Wireshark, but it seems that it is not communicating with HTTP / 2. It seems that you should use SocketsHttpHandler instead of HttpClientHandler, but it doesn't seem to be implemented in Unity? It did not exist in netstandard (2.1.0.0) in the Visual Studio assembly browser.

mikerochip commented 2 years ago

So since this is a Unity problem and this package isn't targeting Unity as a supported platform (again, no nuget package targets Unity, it is luck if it works), i highly recommend going through Unity channels.

If your team is already assigned a Unity account manager, you should talk to them, and if you don't know who your TAM is, ask your team. Unity also has an internal slack that you could get on if you ask your TAM. I've always had the most success discussing roadmap issues that way instead of going straight to forums.

If you don't have a TAM (or don't have a team) then your best bet is the forums. Here is some info about .net support https://forum.unity.com/threads/unity-future-net-development-status.1092205/

You could also try their roadmap site but I haven't had a lot of success with that personally https://unity.com/roadmap/unity-platform

m-yukio commented 2 years ago

Thank you. I'll try to tell Unity about the problem.

JamesNK commented 2 years ago

Closing as answered.

koala2000 commented 2 years ago

Hi @m-yukio ,

Hope you are doing well. Have you ever solved the issue? Thanks a lot.

RubenGarcia commented 1 year ago

I am also interested in any updates regarding this issue.

RubenGarcia commented 1 year ago

Depenting on your platform https://github.com/Cysharp/YetAnotherHttpHandler maybe a solution for you.

mikerochip commented 1 year ago

I would not recommend protobuf or grpc for Unity projects. Getting it integrated is like fighting gravity - too complicated, too many steps to make it work. It is a brittle workflow. There are other options.

If you don't need the performance then you should really just keep it simple and use json

If you need the performance then you should use something with better Unity support. I would recommend MessagePack and MagicOnion. MessagePack is used in lots of very popular server technologies (like Redis) and enjoys excellent C# and Unity support. If you have C# on the server, then you can also define your message schema in C# code and share that code with your server.

If you're in a position to choose your server communication format, I would go this way.

https://github.com/MessagePack-CSharp/MessagePack-CSharp

https://github.com/Cysharp/MagicOnion

These are also created by the creator/owner of CySharp, neuecc. He specializes in creating libraries that work in both Unity and latest .NET