gRPC-Web : Using gRPC over browser application

Previously, we created a gRPC server in .NET and connected it using a Flutter Desktop client. In this blog post, we would attempt to connect a web application running on a browser to the gRPC server. We will look at the problems, and the solutions we have, particularly, the gRPC-Web .Net Microsoft have come out with.

Why we need gRPC-Web

Without special workarounds, a browser based application fails to connect to a gRPC Server. In order to understand why it fails, let us take a step backwards and understand what exactly is gRPC is about.

gRPC is an Open source Remote Procedure Call framework build on top of two already established modern technologies – HTTP/2 and Protocol Buffers (Protobuf).

HTTP/2 is the successor of HTTP/1.1 and has greatly influenced the performance of web applications. Previously, with HTTP/1.1, resources are loaded one by one, blocking all succeeding request until response received for the preceding one. Browsers, in order to gain performance benefits, open 5/6 TCP Connections for downloading resources at the same time. But this comes at the performance hits for creating and maintaining excess TCP connections. Http/2 improved this by support multiplexing.

With Http/2, a single TCP connections can be used to send multiple streams of data without blocking each other. Http/2 also supports additional features like Server push and Header compression which greatly influenced its success.

gRPC combined the advances made by HTTP/2 with protocol buffer serialization. It also allowed streaming of data from client to server (and vice versa). In order to ensure/track success of entire streamed message, it introduced a new status message gRPC Status, which is send as a trailing header to the existing HTTP/2 message.

However, the current browser API’s including XHR and Fetch doesn’t support this new additional flag. This, along with inability to streaming binary data, severely restricts the support for gRPC natively in a browser application.

How does gRPC-web solve this problem ?

In simplest terms, gRPC-web solved this problem by modifying the HTTP/2 Data Frame. The gRPC Status is included in the message. The binary message is usually base64 encoded to allow streaming over HTTP/2. The basic idea here is to have the browser send the regular HTTP requests, but introduce a proxy which sits between the client and server. The proxy converts the requests send by the client to property gRPC format, and send it to the server. Similarly, the response from the Server is converted to something which the client browser can understand.

As a .Net Developer, one way to achieve this is using a proxy like the Envoy which sits between the Server and the Client. The other options is to use implementations of gRPC web via the middle-ware provided by Grpc.AspNetCore.Web.

In this blog post, we will address the later. We will use the Microsoft Implementation of gRPC-web to update the gRPC Server, which we created in the previous blog entry to support gRPC-web requests.

Configure gRPC-Web in Server

We will consider the gRPC server which we build in the previous example. We will then add support for handling gRPC-web in the server. This is done using middleware. As the first step install the Grpc.AspNetCore.Web package.

Install-Package Grpc.AspNetCore.Web

We need to now ensure the gRPC-web middleware is used by the server application. This is done using the UseGrpcWeb() and EnableGrpcWeb() methods.

var app = builder.Build();

app.UseGrpcWeb();
app.MapGrpcService<InstrumentService>()
    .EnableGrpcWeb(); ;

The UseGrpcWeb() call ensures the middleware is added to the pipeline. Do note that this needs to be added after routing and before endpoints.

The second method call, EnableGrpcWeb() specifies that the particular Service (in this case InstrumentService) supports gRPC-Web. The EnableGrpcWeb() allows you to configure individual services. Alternatively, you could enable gRPC-web for all gRPC services in the application using the overload for UseGrpcWeb().

// To enable it as default
app.UseGrpcWeb(new GrpcWebOptions { DefaultEnabled =  true});

While this two method calls ensure the middleware is enlisted to handle gRPC calls, there is one more step required on the Server. We would need to configure CORS to ensure that the browser application is allowed to make calls to a different domain than the one which serves the application.

// Use CORS to access different domain
builder.Services.AddCors(opt => opt.AddPolicy("AllowAll", builder =>
{
    builder.AllowAnyOrigin()
               .AllowAnyMethod()
               .AllowAnyHeader()
               .WithExposedHeaders("Grpc-Status", "Grpc-Message", "Grpc-Encoding", "Grpc-Accept-Encoding");
}));

We have now successfully configured our Server API to handle gRPC-web calls.

Configure Client for gRPC-web

The next obvious step is to configure our client application to make calls to the gRPC server. In this example, we are using an MVC Web application.

As the first step, we would need to include the ‘.proto’ file used by the server application in the client application. Following is the protocol buffer definition we had previously created for the server.

syntax = "proto3";

option csharp_namespace = "GrpcDemoServer";

package greet;

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply);
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings.
message HelloReply {
  string message = 1;
}


service Instrument{
	rpc ReadStatus (ReadStatusRequest) returns (ReadStatusResponse);
	rpc Subscribe (RawDataRequest) returns (stream RawDataResponse);
}

message ReadStatusRequest {
	int32 id = 1;
}

message ReadStatusResponse{
	string status = 1;
}


message RawDataRequest{
	int32 maxItems = 1;
}

message RawDataResponse{
	int32 id  = 1;
	string description = 2;
}

Once the protocol buffer definitions are include in the application, we need to include 3 nuget packages which would help us in generating the gRPC Stub classes for client.

Grpc.Net.Client
Grpc.Net.Client.Web
Grpc.Tools

Select the .proto file from the solution explorer, and choose Properties from the Right click menu. Configure the build action as Protocol Compiler and gRPC Stub classes as Client Only. Building the solution now would generate the required Stub Classes needed for the client.

Head over to your desired controller and use the stub classes to make the gRPC Call. In this example, I have a DemoController explosing an endpoint as following.

[HttpGet]
[Route("GetInstrumentStatus")]
public async Task<IActionResult> GetInstrumentStatus(int instrumentId)
{
    var channel = GrpcChannel.ForAddress("https://localhost:7280", new GrpcChannelOptions
    {
        HttpHandler = new GrpcWebHandler(new HttpClientHandler())
    });

    var client = new Instrument.InstrumentClient(channel);
    var response = await client.ReadStatusAsync(new ReadStatusRequest { Id = instrumentId });
    return Ok(response.Status);
}

The above code creates gRPC Channel for the server address. We then creates an instance of Instrument.InstrumentClient to invoke the ReadStatusAsync method exposed by our gRPC server.

Run your application and watch how it was easy to enable your browser application to take advantages of the gRPC communication. Do not forget to compare the performance with REST.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s