Giter Club home page Giter Club logo

grpcwebsocketbridge's Introduction

GrpcWebSocketBridge

Yet Another gRPC over HTTP/1 using WebSocket implementation, primarily targets .NET platform.

Supported features

  • Unary
  • DuplexStreaming
  • ClientStreaming
  • ServerStreaming (Only supported in Always WebSocket mode)

Limitations

  • Deadline not supported
  • Request Trailer not supported

Implementation details

  • Unary: Porting grpc-web (Binary Mode) of Grpc.Net.Client.Web/Grpc.AspNetCore.Server.Web communicating over HTTP/1.
  • Streaming: Custom protocols based on grpc-web's wire-protocol for communication via WebSocket.

Requirements

Server

  • gRPC server built on ASP.NET Core
    • .NET 6 or later
    • Web server listening on HTTP/1
    • When using with MagicOnion, use MagicOnion 4.5.0 or later

Client

  • .NET 6 or later (Console, Blazor WebAssembly)
  • Unity 2021.3 or later
    • We strongly recommend a newer version when trying WebGL builds, as older versions have behavioural issues.

How to run a sample project

The repository contains sample projects. You can run a simple sample app (Unity WebGL) as follows.

Getting started

Server

Install GrpcWebSocketBridge.AspNetCore package

NuGet package needs to be installed on the server. Add GrpcWebSocketBridge.AspNetCore package to your project.

dotnet add package GrpcWebSocketBridge.AspNetCore

Enable GrpcWebSocketBridge on the server (.NET 6)

Enable middleware as a bridge and register a service to use WebSockets.

If the application and API server are different Origins, you need to register the CORS services to use CORS.

builder.Services.AddGrpc();
builder.Services.AddCors(options =>
{
    options.AddDefaultPolicy(policy =>
    {
        // WARN: Do not apply following policies to your production.
        //       If not configured carefully, it may cause security problems.
        policy.AllowAnyMethod();
        policy.AllowAnyOrigin();
        policy.AllowAnyHeader();

        // NOTE: "grpc-status" and "grpc-message" headers are required by gRPC. so, we need expose these headers to the client.
        policy.WithExposedHeaders("grpc-status", "grpc-message");
    });
});

Warning

In this example, all are set to accept, but CORS policies should be configured with care to avoid security issues.

Add CORS, WebSockets, GrpcWebSocketRequestRoutingEnabler and GrpcWebSocketBridge middleware.

var app = builder.Build();

// Configure the HTTP request pipeline.

// Enable CORS, WebSocket, GrpcWebSocketRequestRoutingEnabler
// NOTE: These need to be called before `UseRouting`.  
app.UseCors();
app.UseWebSockets();
app.UseGrpcWebSocketRequestRoutingEnabler();

app.UseRouting();

// NOTE: `UseGrpcWebSocketBridge` must be called after calling `UseRouting`.
app.UseGrpcWebSocketBridge();

app.MapGrpcService<GreeterService>();
app.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");

app.Run();

Client (.NET 6 or later; Blazor WebAssembly)

Add Grpc.Net.Client and GrpcWebSocketBridge.Client package to your project.

Use GrpcWebSocketBridge handler with GrpcChannel

You need to change your code to use WebSocket instead of HTTP/2 for gRPC channels.

var channel = GrpcChannel.ForAddress("https://localhost:5000");

Change the code to use GrpcWebSocketBridgeHandler as follows:

var channel = GrpcChannel.ForAddress("https://localhost:5000", new GrpcChannelOptions()
{
    HttpHandler = new GrpcWebSocketBridgeHandler()
});

Client (Unity)

Install the Unity package for GrpcWebSocketBridge, available from the GitHub Release page.

For gRPC-related and dependent libraries, extract and add assemblies for netstandard2.1 from the following NuGet packages

  • Grpc.Core.Api
  • Grpc.Net.Client (Unity WebGL requires installation of a Cysharp custom-built version)
  • Grpc.Net.Common (Unity WebGL requires installation of a Cysharp custom-built version)
  • Microsoft.Extensions.Logging.Abstractions
  • System.Buffers
  • System.Diagnostics.DiagnosticSource
  • System.IO.Pipelines
  • System.Memory
  • System.Runtime.CompilerServices.Unsafe
  • System.Threading.Tasks.Extensions

Note

If you want to build your application for WebGL, you need to install custom versions of Grpc.Net.Client and Grpc.Net.Common by Cysharp. Those assemblies are available from GitHub Release page.

Use GrpcWebSocketBridge handler with GrpcChannel

You need to change your code to use WebSocket with GrpcWebSocketBridgeHandler instead of HTTP/2 for gRPC channels.

var channel = new Channel("localhost", 5000);

Change the code to use GrpcWebSocketBridgeHandler as follows:

var channel = GrpcChannel.ForAddress("https://localhost:5000", new GrpcChannelOptions()
{
    HttpHandler = new GrpcWebSocketBridgeHandler()
});

If you want to keep channels in your application code, it is recommended to use the Grpc.Core.ChannelBase class instead of the Grpc.Core.Channel class. It is the base class for all channels.

Disable SynchronizationContext (WebGL)

If SynchronisationContext exists, a code path using ThreadPool will occur and WebGL will stop working because ThreadPool cannot be used. Therefore, SynchronisationContext must be set to null under WebGL.

Warning

If null is set in a non-WebGL environment, the operation will stop because it cannot return to the main thread after await, so it should only be applied in a WebGL environment.

#if UNITY_WEBGL && !UNITY_EDITOR
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void InitializeSynchronizationContext()
{
    SynchronizationContext.SetSynchronizationContext(null);
}
#endif

Troubleshooting

Cannot connect to the server.

  • Listening on HTTP/1 is required.
    • WebSocket connects via HTTP/1; connecting to a port where the Protocols setting of Kestreal is Http2 only will result in an error.
  • Connecting to an API server on a different domain to the server you are serving a HTML.
    • When connecting to an API server with a different domain CORS in ASP.NET Core needs to be configured.

License

MIT License

Copyright (c) Cysharp, Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

GrpcWebSocketBridge includes and derives from some third party source codes. They are under the original license.

================================================================================
src/GrpcWebSocketBridge.Client/External/System.Memory/
src/GrpcWebSocketBridge.Shared/External/System.Memory/
================================================================================
The MIT License (MIT)

Copyright (c) .NET Foundation and Contributors

All rights reserved.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

================================================================================
src/External/Grpc.Net.Client/
src/External/Grpc.Net.Common/
src/GrpcWebSocketBridge.AspNetCore/External/Grpc.AspNetCore.Web/
src/GrpcWebSocketBridge.Client/External/Grpc.AspNetCore.Web.Internal/
src/GrpcWebSocketBridge.Client/External/Grpc.Shared/
src/GrpcWebSocketBridge.Shared/GrpcWebSocketBufferReader.cs
================================================================================
Copyright 2019 The gRPC Authors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

GrpcWebSocketBridge includes and derives from some third party source codes. They are under the original license.

grpcwebsocketbridge's People

Contributors

dependabot[bot] avatar gavar avatar github-actions[bot] avatar guitarrapc avatar mayuki avatar wmltogether avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

grpcwebsocketbridge's Issues

Why MagicOnionSampleApp.Shared is Targeting netstandard 2.0?

Hello,

I am looking at MagicOnion for first time so please excuse my ignorance.

Do we need to target netstandard 2.0 everywhere where we add package MagicOnion.Abstractions?

My intent is to use MagicOnion with BlazorWebAssembly and MAUI both?

Can we use MagicOnion Client in MAUI apps?

Thanks

1.1.0 broke MagicOnionService

Env:

Unity 2021.3.5.f1

Problem:

After 1.1.0 MagicOnionService (not StreamingHub) stopped working with error on client - "No grpc-status found on response".

How to reproduce:

Add any Service to MagicOnion sample and try using

var service = MagicOnionClient.Create<IMyService>(channel); 
await service.MyMethod();

Hack fix:

In 1.1.0 some api has been changed to native api where possible if unity can support it.

Somehow returning TrailingHeadersHelpers.TrailingHeaders to previous version fixes the problem.

How to set up envoy?

grpc-web settings do not allow communication. Is there any other way?

envoy error :
upstream connect error or disconnect/reset before headers. reset reason: connection termination

WebGL biuld doesn't work

This method in Grpc.Net.Client.Internal.OperatingSystem
private OperatingSystem() { this.IsBrowser = RuntimeInformation.IsOSPlatform(OSPlatform.Create("browser")); }
is affected by this bug:
https://issuetracker.unity3d.com/issues/notsupportedexception-thrown-when-calling-any-member-of-system-dot-runtime-dot-interopservices-dot-runtimeinformation-in-webgl
and throw exception when calling GrpcChannel.ForAddress
Since this project already use modified Grpc.Net.Client only for WebGL I recommend to change this line to:
this.IsBrowser = true;

GrpcWebSocketBridge: Buffer is not large enough for header

Hello Team

The issue still occurs.

Environment: .Net 8, MagicOnion 6.0, GrpcWebSocketBridge 6.0
Network: Client->Cloudflare-Firewall->Proxy-Gprc

Observations: We see MagicOnion returning a success message
Request finished HTTP/2 POST https://www.fixme.com/IPortfoliosGrpcService/GetAllEntitiesAsync - 200 null application/grpc 20.9603ms

The grpc websocket bridge seems to be broken at the hightlighted code below.

ExceptionDetail.InnerException.Message
Buffer is not large enough for header
ExceptionDetail.InnerException.Source
GrpcWebSocketBridge.Client
ExceptionDetail.InnerException.TargetSite
Void MoveNext()
ExceptionDetail.InnerException.Type
System.InvalidOperationException
ExceptionDetail.Message
Status(StatusCode="Internal", Detail="Error starting gRPC call. InvalidOperationException: Buffer is not large enough for header", DebugException="System.InvalidOperationException: Buffer is not large enough for header")
ExceptionDetail.Source
System.Private.CoreLib
ExceptionDetail.Status._typeTag
Status
ExceptionDetail.Status.DebugException._typeTag
InvalidOperationException
ExceptionDetail.Status.DebugException.Data
[]
ExceptionDetail.Status.DebugException.HelpLink

ExceptionDetail.Status.DebugException.HResult
-2146233079
ExceptionDetail.Status.DebugException.InnerException

ExceptionDetail.Status.DebugException.Message
Buffer is not large enough for header
ExceptionDetail.Status.DebugException.Source
GrpcWebSocketBridge.Client
ExceptionDetail.Status.DebugException.StackTrace
at Grpc.Net.Client.Web.Internal.GrpcWebResponseStream.ReadAsync(Memory1 data, CancellationToken cancellationToken) at Grpc.Net.Client.Internal.StreamExtensions.ReadMessageAsync[TResponse](Stream responseStream, GrpcCall call, Func2
deserializer, String grpcEncoding, Boolean singleMessage, CancellationToken cancellationToken)
at Grpc.Net.Client.Internal.GrpcCall2.ReadMessageAsync(Stream responseStream, String grpcEncoding, Boolean singleMessage, CancellationToken cancellationToken) at Grpc.Net.Client.Internal.GrpcCall2.RunCall(HttpRequestMessage request, Nullable`1 timeout)
ExceptionDetail.Status.DebugException.TargetSite
Void MoveNext()
ExceptionDetail.Status.Detail
Error starting gRPC call. InvalidOperationException: Buffer is not large enough for header
ExceptionDetail.Status.StatusCode
Internal
ExceptionDetail.StatusCode
Internal
ExceptionDetail.TargetSite
Void Throw()
ExceptionDetail.Trailers
[]

MagicOnion 6.0 and GrpcWebSocketBridge: Buffer is not large enough for header

Just Updated to MagicOnion 6.0 and using latest GrpcWebSocketBridge. We get the following error.
It works fine with previous releas of magic onion which is 5.1.8.

{
"@t": "2024-02-01T15:08:21.0248904Z",
"@mt": "Status(StatusCode="Internal", Detail="Error starting gRPC call. InvalidOperationException: Buffer is not large enough for header", DebugException="System.InvalidOperationException: Buffer is not large enough for header")",
"@m": "Status(StatusCode="Internal", Detail="Error starting gRPC call. InvalidOperationException: Buffer is not large enough for header", DebugException="System.InvalidOperationException: Buffer is not large enough for header")",
"@i": "c845d5d8",
"@l": "Error",
"@x": "Grpc.Core.RpcException: Status(StatusCode="Internal", Detail="Error starting gRPC call. InvalidOperationException: Buffer is not large enough for header", DebugException="System.InvalidOperationException: Buffer is not large enough for header")\r\n ---> System.InvalidOperationException: Buffer is not large enough for header\r\n at Grpc.Net.Client.Web.Internal.GrpcWebResponseStream.ReadAsync(Memory1 data, CancellationToken cancellationToken)\r\n at Grpc.Net.Client.Internal.StreamExtensions.ReadMessageAsync[TResponse](Stream responseStream, GrpcCall call, Func2 deserializer, String grpcEncoding, Boolean singleMessage, CancellationToken cancellationToken)\r\n at Grpc.Net.Client.Internal.GrpcCall2.ReadMessageAsync(Stream responseStream, String grpcEncoding, Boolean singleMessage, CancellationToken cancellationToken)\r\n at Grpc.Net.Client.Internal.GrpcCall2.RunCall(HttpRequestMessage request, Nullable1 timeout)\r\n --- End of inner exception stack trace ---\r\n at MagicOnion.Client.ResponseContextRaw2.FromRawResponseToResponseAsync()\r\n at MagicOnion.Client.ResponseContextRaw2.WaitResponseAsync()\r\n at Insights.Core.Client.Grpc.LoggingFilter.SendAsync(RequestContext context, Func2 next)\r\n at MagicOnion.Client.Internal.RawMethodInvoker4.InvokeUnaryCore(MagicOnionClientBase client, String path, TRequest request, Func2 requestMethod)\r\n at MagicOnion.UnaryResult1.UnwrapResponse()\r\n at Insights.Core.Client.Grpc.GrpcClients.EquitiesGrpcServiceClient.GetAllEntitiesAsync(String userId, Int64 lastSyncStamp)", "Application": "InsightsMaui", "ApplicationName": "Insights Logging", "ApplicationType": "Native", "BusinessEntityId": null, "ConnectionId": "0HN136QGA9ABU", "Env": null, "ExceptionDetail": { "HResult": -2146233088, "InnerException": { "HResult": -2146233079, "Message": "Buffer is not large enough for header", "Source": "GrpcWebSocketBridge.Client", "TargetSite": "Void MoveNext()", "Type": "System.InvalidOperationException" }, "Message": "Status(StatusCode=\"Internal\", Detail=\"Error starting gRPC call. InvalidOperationException: Buffer is not large enough for header\", DebugException=\"System.InvalidOperationException: Buffer is not large enough for header\")", "Source": "System.Private.CoreLib", "Status": { "DebugException": { "Data": [], "HResult": -2146233079, "HelpLink": null, "InnerException": null, "Message": "Buffer is not large enough for header", "Source": "GrpcWebSocketBridge.Client", "StackTrace": " at Grpc.Net.Client.Web.Internal.GrpcWebResponseStream.ReadAsync(Memory1 data, CancellationToken cancellationToken)\r\n at Grpc.Net.Client.Internal.StreamExtensions.ReadMessageAsync[TResponse](Stream responseStream, GrpcCall call, Func2 deserializer, String grpcEncoding, Boolean singleMessage, CancellationToken cancellationToken)\r\n at Grpc.Net.Client.Internal.GrpcCall2.ReadMessageAsync(Stream responseStream, String grpcEncoding, Boolean singleMessage, CancellationToken cancellationToken)\r\n at Grpc.Net.Client.Internal.GrpcCall2.RunCall(HttpRequestMessage request, Nullable1 timeout)",
"TargetSite": "Void MoveNext()",
"_typeTag": "InvalidOperationException"
},
"Detail": "Error starting gRPC call. InvalidOperationException: Buffer is not large enough for header",
"StatusCode": "Internal",
"_typeTag": "Status"
},
"StatusCode": "Internal",
"TargetSite": "Void Throw()",
"Trailers": [],
"Type": "Grpc.Core.RpcException"
},
"InstanceId": "0e9734ddc4ab45f590949d546bac6532",
"MachineName": "ICAN-SEN",
"Origin": "Client",
"ProcessId": 61688,
"ProcessName": "InsightsMauiApp",
"RequestId": "0HN136QGA9ABU:00000005",
"RequestPath": "/clientlog",
"SourceContext": "Insights.Core.Client.Grpc.GrpcClients.EquitiesGrpcServiceClient",
"ThreadId": 1
}

Channel is not available in Android MAUI application

Original Issue #26


Here is my test results.

Environment: Windows 11. (All nugets latest, magiconion and grpcwebsocketbridge.
Application: Net MAUI Blazor Hybrid

Connection from Windows: Successful with new upgrade.

Case 1: Connecting from Windows MAUI App to MagicOnionGrpcServer

Channel Configuration code:

GrpcChannel channel?;
 if (OperatingSystem.IsWindows())
 {
     var httpHandler = new GrpcWebSocketBridgeHandler();  **//Note there was no need to forceWebSocketMode, though it works with that as sell**
     channel = GrpcChannel.ForAddress(baseUri, new GrpcChannelOptions
     {
         HttpHandler = httpHandler,
         MaxReceiveMessageSize = 20 * 1024 * 1024, // 10 MB
         MaxSendMessageSize = 10 * 1024 * 1024, // 10 MB
     });
 }

Results: Working as expected.

Case 2: Connecting from Android MAUI App to MagicOnionGrpcServer (Same App but running Android Build)

if (OperatingSystem.IsAndroid())
{
    var httpHandler = new GrpcWebSocketBridgeHandler(true); //Does not work with any either true or false for forceWebSocket
    channel = GrpcChannel.ForAddress(baseUri, new GrpcChannelOptions
    {
        HttpHandler = httpHandler,
        MaxReceiveMessageSize = 20 * 1024 * 1024, // 10 MB
        MaxSendMessageSize = 10 * 1024 * 1024, // 10 MB
    });
}
[chromium] [INFO:CONSOLE(1)] "The channel configuration isn't valid on Android devices. The channel is configured to use HttpClientHandler and Android's native HTTP/2 library. gRPC isn't fully supported by Android's native HTTP/2 library and it can cause runtime errors. To fix this problem, either configure the channel to use SocketsHttpHandler, or add <UseNativeHttpHandler>false</UseNativeHttpHandler> to the app's project file. For more information, see https://aka.ms/aspnet/grpc/android.
[chromium]    at Grpc.Net.Client.GrpcChannel.CreateInternalHttpInvoker(HttpMessageHandler handler) in /_/src/Grpc.Net.Client/GrpcChannel.cs:line 501
[chromium]    at Grpc.Net.Client.GrpcChannel..ctor(Uri address, GrpcChannelOptions channelOptions) in /_/src/Grpc.Net.Client/GrpcChannel.cs:line 161
[chromium]    at Grpc.Net.Client.GrpcChannel.ForAddress(Uri address, GrpcChannelOptions channelOptions) in /_/src/Grpc.Net.Client/GrpcChannel.cs:line 697
[chromium]    at Grpc.Net.Client.GrpcChannel.ForAddress(String address, GrpcChannelOptions channelOptions) in /_/src/Grpc.Net.Client/GrpcChannel.cs:line 667
[chromium]    at Insights.Services.Client.NativeGrpcClientChannel.GetGrpcChannel(String baseUri) in D:\Trade\InsightsOnDaprAspire\src\Services\Client\NativeGrpcClientChannel.cs:line 45
[chromium]    at Insights.Core.Client.Grpc.GrpcClients.EquitiesGrpcServiceClient.GetClient() in D:\Trade\InsightsOnDaprAspire\src\Core\Client\Grpc\GrpcClients\EquitiesGrpcServiceClient.cs:line 68
[chromium]    at Insights.Core.Client.Grpc.GrpcClients.EquitiesGrpcServiceClient..ctor(IGrpcClient nativeGrpcClient, IConfiguration configuration, ILogger`1 logger, IBlazorUserManager blazorUserManager) in D:\Trade\InsightsOnDaprAspire\src\Core\Client\Grpc\GrpcClients\EquitiesGrpcServiceClient.cs:line 31
[chromium]    at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Constructor(Object obj, IntPtr* args)
[chromium]    at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr)
[chromium]    at System.Reflection.MethodBaseInvoker.InvokeWithFewArgs(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
[chromium]    at System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
[chromium]    at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
[chromium]    at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2[[Microsoft.Extensions.DependencyInjection.ServiceLookup.RuntimeResolverContext, Microsoft.Extensions.DependencyInjection, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60],[System.Object, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].VisitCallSiteMain(ServiceCallSite callSite, RuntimeResolverContext argument)
[chromium]    at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitRootCache(ServiceCallSite callSite, RuntimeResolverContext context)
[chromium]    at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
[chromium]    at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2[[Microsoft.Extensions.DependencyInjection.ServiceLookup.RuntimeResolverContext, Microsoft.Extensions.DependencyInjection, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60],[System.Object, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].VisitCallSite(ServiceCallSite callSite, RuntimeResolverContext argument)
[chromium]    at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
[chromium]    at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2[[Microsoft.Extensions.DependencyInjection.ServiceLookup.RuntimeResolverContext, Microsoft.Extensions.DependencyInjection, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60],[System.Object, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].VisitCallSiteMain(ServiceCallSite callSite, RuntimeResolverContext argument)
[chromium]    at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitRootCache(ServiceCallSite callSite, RuntimeResolverContext context)
[chromium]    at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2[[Microsoft.Extensions.DependencyInjection.ServiceLookup.RuntimeResolverContext, Microsoft.Extensions.DependencyInjection, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60],[System.Object, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].VisitCallSite(ServiceCallSite callSite, RuntimeResolverContext argument)
[chromium]    at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
[chromium]    at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2[[Microsoft.Extensions.DependencyInjection.ServiceLookup.RuntimeResolverContext, Microsoft.Extensions.DependencyInjection, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60],[System.Object, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].VisitCallSiteMain(ServiceCallSite callSite, RuntimeResolverContext argument)
[chromium]    at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitRootCache(ServiceCallSite callSite, RuntimeResolverContext context)
[chromium]    at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2[[Microsoft.Extensions.DependencyInjection.ServiceLookup.RuntimeResolverContext, Microsoft.Extensions.DependencyInjection, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60],[System.Object, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].VisitCallSite(ServiceCallSite callSite, RuntimeResolverContext argument)
[chromium]    at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
[chromium]    at Microsoft.Extensions.DependencyInjection.ServiceProvider.CreateServiceAccessor(ServiceIdentifier serviceIdentifier)
[chromium]    at System.Collections.Concurrent.ConcurrentDictionary`2[[Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceIdentifier, Microsoft.Extensions.DependencyInjection, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60],[Microsoft.Extensions.DependencyInjection.ServiceProvider.ServiceAccessor, Microsoft.Extensions.DependencyInjection, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]].GetOrAdd(ServiceIdentifier key, Func`2 valueFactory)
[chromium]    at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(ServiceIdentifier serviceIdentifier, ServiceProviderEngineScope serviceProviderEngineScope)
[chromium]    at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
[chromium]    at Microsoft.AspNetCore.Components.ComponentFactory.<>c__DisplayClass9_0.<CreatePropertyInjector>g__Initialize|1(IServiceProvider serviceProvider, IComponent component)
[chromium]    at Microsoft.AspNetCore.Components.ComponentFactory.InstantiateComponent(IServiceProvider serviceProvider, Type componentType, IComponentRenderMode callerSpecifiedRenderMode, Nullable`1 parentComponentId)
[chromium]    at Microsoft.AspNetCore.Components.RenderTree.Renderer.InstantiateChildComponentOnFrame(RenderTreeFrame[] frames, Int32 frameIndex, Int32 parentComponentId)
[chromium]    at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.InitializeNewComponentFrame(DiffContext& diffContext, Int32 frameIndex)
[chromium]    at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.InitializeNewSubtree(DiffContext& diffContext, Int32 frameIndex)
[chromium]    at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.InsertNewFrame(DiffContext& diffContext, Int32 newFrameIndex)
[chromium]    at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.AppendDiffEntriesForRange(DiffContext& diffContext, Int32 oldStartIndex, Int32 oldEndIndexExcl, Int32 newStartIndex, Int32 newEndIndexExcl)
[chromium]    at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.ComputeDiff(Renderer renderer, RenderBatchBuilder batchBuilder, Int32 componentId, ArrayRange`1 oldTree, ArrayRange`1 newTree)
[chromium]    at Microsoft.AspNetCore.Components.Rendering.ComponentState.RenderIntoBatch(RenderBatchBuilder batchBuilder, RenderFragment renderFragment, Exception& renderFragmentException)
[chromium]    at Microsoft.AspNetCore.Components.RenderTree.Renderer.RenderInExistingBatch(RenderQueueEntry renderQueueEntry)
[chromium]    at Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessRenderQueue()", source: https://0.0.0.0/_framework/blazor.webview.js (1)
[GPUAUX] [AUX]GuiExtAuxCheckAuxPath:663: Null anb

Result: Does not work on MAUI Android App. with above configuration.

Workaround/Perhaps right way to create channel specific to MAUI

After a little research and guidance from a reply of @JamesNK, I figured out that correct way to create a channel on MAUI app should be.

if (OperatingSystem.IsAndroid())
{
    var httpHandler = new GrpcWebSocketBridgeHandler(true); //Works with both true/false
    channel = GrpcChannel.ForAddress(baseUri, new GrpcChannelOptions
    {
        HttpClient = new HttpClient(httpHandler),   //Observe that we are passing HttpClient with GrpcWebSocketBridgeHandler
        MaxReceiveMessageSize = 20 * 1024 * 1024, // 10 MB
        MaxSendMessageSize = 10 * 1024 * 1024, // 10 MB
    });
}

I belive current build 1.3 has fixed the existing issues and the issue specific to MAUI relates to the way we need to provision the channel is specific to Android.

Thanks for such great tools and libraries.

Love from New Delhi!

Originally posted by @Sen-Gupta in #26 (comment)

Error: The underlying HTTP transport must be a SocketsHttpHandler

This exception is thrown when creating the channel:

System.InvalidOperationException: Channel is configured with an HTTP transport doesn't support client-side load balancing or connectivity state tracking. The underlying HTTP transport must be a SocketsHttpHandler with no SocketsHttpHandler.ConnectCallback configured. The HTTP transport must be configured on the channel using GrpcChannelOptions.HttpHandler.
   at Grpc.Net.Client.GrpcChannel.ValidateHttpHandlerSupportsConnectivity()
   at Grpc.Net.Client.GrpcChannel..ctor(Uri address, GrpcChannelOptions channelOptions)
   at Grpc.Net.Client.GrpcChannel.ForAddress(Uri address, GrpcChannelOptions channelOptions)
   at Grpc.Net.Client.GrpcChannel.ForAddress(String address, GrpcChannelOptions channelOptions)
   at Program.<Main>$(String[] args) in D:\FFT\Program.cs:line 24

Link to the source that is throwing the exception:
https://github.com/grpc/grpc-dotnet/blob/master/src/Grpc.Net.Client/GrpcChannel.cs#L411

To reproduce use this Program.cs and .csproj

using Grpc.Core;
using Grpc.Net.Client;
using Grpc.Net.Client.Balancer;
using Grpc.Net.Client.Configuration;
using GrpcWebSocketBridge.Client;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;


var services = new ServiceCollection();
services.AddSingleton<ResolverFactory>(new WSSResolverFactory());

using var channel = GrpcChannel.ForAddress("wss://myserver.com:443", new GrpcChannelOptions
{
  HttpHandler = new GrpcWebSocketBridgeHandler(),
  ServiceProvider = services.BuildServiceProvider(),
  Credentials = ChannelCredentials.SecureSsl,
});

internal class WSSResolverFactory : ResolverFactory
{
  public override string Name => "wss";

  public override Resolver Create(ResolverOptions options)
  {
    return new WSSResolver(options.Address, options.DefaultPort, options.LoggerFactory);
  }
}

internal class WSSResolver : PollingResolver
{
  private readonly Uri _address;
  private readonly int _port;

  public WSSResolver(Uri address, int defaultPort, ILoggerFactory loggerFactory)
      : base(loggerFactory)
  {
    _address = address;
    _port = defaultPort;
  }

  protected override async Task ResolveAsync(CancellationToken cancellationToken)
  {
    var address = new BalancerAddress(_address.Host, _port);
    Listener(ResolverResult.ForResult(new[] { address }));
  }
}
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
		<OutputType>Exe</OutputType>
		<TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Google.Protobuf" Version="3.26.0" />
    <PackageReference Include="Grpc.Core" Version="2.46.6" />
    <PackageReference Include="Grpc.Net.Client" Version="2.61.0" />
    <PackageReference Include="Grpc.Tools" Version="2.62.0">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
    <PackageReference Include="GrpcWebSocketBridge.Client" Version="1.2.2" />
    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
  </ItemGroup>
</Project>

How to use it with yarp?

my yarp configuration.:

        "ReverseProxy": {
          "Routes": {
            "seserver-statements": {
              "ClusterId": "seserver-statements-cluster",
              "Match": {
                "Path": "/statements/{**catch-all}"
              }
            },
            "seserver-auth": {
              "ClusterId": "seserver-auth-cluster",
              "Match": {
                "Path": "/auth/{**catch-all}"
              }
            }
          },
          "Clusters": {
            "seserver-statements-cluster": {
              "Destinations": {
                "statements": {
                  "Address": "http://seserver-statements"
                }
              }
            },
            "seserver-auth-cluster": {
              "Destinations": {
                "auth": {
                  "Address": "http://seserver-auth"
                }
              }
            }
          }
        }

two servers both use grpcwebsocketbridge. but when i call the test function like this:

        var channel = GrpcChannel.ForAddress("http://localhost:5104/statements", new GrpcChannelOptions()
        {
            HttpHandler = new GrpcWebSocketBridgeHandler()
        });

        var client = new Test.TestClient(channel);
        var response = await client.HelloWorldAsync(new HelloWorldRequest { Message = "Hello World" });

       Console.WriteLine(response.Message);

it return 404. all applications use .net 7

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.