Rest vs. GRPC for Micro Services

This question is not as simple as it appears. I think grpc is an obvious choice for micro service interface however when it comes to client access there are times when http will be chosen over grpc for a variety of reasons. I would just like to know what others are seeing out there. Frankly both Envoy grpc- json transcoding feature, and grpc-gateway seem a bit brittle to me for production use. With grpc-gateway I think I know what its doing but with json transcoding not really sure how thats implemented or what the options are for customizing it. There is one page of documentation and not much response about it. Also note that grpc-gateway is not apache 2.0 license and no one is really committed to maintaining it long term. As Istio users we are all kind of committed to Envoy via the sidecar pattern so its really quite a serious matter to know what the plans are for json transcoding support. The tickets get closed with any level of support. There is quite a bit involved with supporting grpc. Streaming and GRPC codes etc. Now one thing I have heard is that just dont dont mix protocols in this way. But how then to support clients that cant or won’t use grpc? Do you build a giant rest service and proxy all the calls? . Of course I would prefer end to end GRPC. But how many places are doing end to end grpc? I would like to know the experiences of others in terms of external facing api gateway? What are you using and is it working for you. Please share your experiences with these options. Thanks.

Have you seen the gRPC HTTP/1.1 bridge: https://www.envoyproxy.io/docs/envoy/latest/configuration/http_filters/grpc_http1_bridge_filter? Seems slightly less brittle to me.

1 Like

No I had never heard of this option. I will definitely take a look.

Yes I will update soon on this

@Steven_O_brien I haven’t used it yet, but possibly plan to. Can you update this thread with your results?

Here is working example for grpc-http bridge:

This approach is used by Lyft the creators of Envoy for surfacing their grpc services to clients.

create an EnvoyFilter

apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: echo-filtering
spec:
workloadLabels:
app: echo
filters:

  • listenerMatch:
    portNumber: 9009
    listenerType: SIDECAR_INBOUND
    insertPosition:
    index: BEFORE
    relativeTo: envoy.router
    filterName: envoy.grpc_http1_bridge
    filterType: HTTP
    filterConfig:
    services:
  • com.test.echo.EchoService

Then access as shown below:

5 byte header. first byte zero and then 4 bytes bigindian for length.

Note: Code below is scrollable.

public static void echoHttpTest() throws java.io.IOException {

System.out.println("echoHttpTest");

CloseableHttpClient httpClient = HttpClients.createDefault();

HttpPost postRequest = new HttpPost("http://10.10.10.10:” + "31380" + "/com.test.EchoService/echo");

//Set the API media type in http content-type header
postRequest.addHeader("content-type", "application/grpc");

String message = "hello";
byte[] zeroByte = {0};
byte[] messageBytes = EchoRequest
        .newBuilder()
        .setMessage(message)
        .build()
        .toByteArray();

byte[] lengthBytes = ByteBuffer.allocate(4).putInt(messageBytes.length).array();

ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write(zeroByte);
baos.write(lengthBytes);
baos.write(messageBytes);

ByteArrayEntity bae = new ByteArrayEntity(baos.toByteArray());
postRequest.setEntity(bae);

//Send the request; It will immediately return the response in HttpResponse object if any
HttpResponse response = httpClient.execute(postRequest);

int statusCode = response.getStatusLine().getStatusCode();
if (statusCode >= 300) {
    throw new RuntimeException("Failed with HTTP error code : " + statusCode);
}

Header statusHeader = response.getFirstHeader("grpc-status");
System.out.println("grpc-status: " + (statusHeader != null ? statusHeader.getValue() : "n/a"));
Header msgHeader = response.getFirstHeader("grpc-message");
System.out.println("grpc-messsage: " + (msgHeader != null ? msgHeader.getValue() : "n/a"));

System.out.println("----------------------------------------");
System.out.println(response.getStatusLine());
System.out.println(response.getLastHeader("Content-Encoding"));
System.out.println(response.getLastHeader("Content-Length"));
System.out.println("----------------------------------------");

HttpEntity entity = response.getEntity();


//HttpEntity entity = response.getEntity();
InputStream in = entity.getContent();

ByteBuffer header = ByteBuffer.allocate(5);
System.out.println("header ");
int n =0;
try {
    n = in.read(header.array());
} catch(Exception e) {
    System.out.println(e.getMessage());
}

if (n != 5) {
    System.out.println("n="+n);
    throw new RuntimeException("expected 5 bytes, got " + n);
}
if (header.get(0) != 0) {
    throw new RuntimeException("unexpected compressed payload");
}
int size = header.getInt(1);

ByteBuffer payload = ByteBuffer.allocate(size);
n = in.read(payload.array());
if (n != size) {
    throw new RuntimeException("expected " + size + " bytes, got " + n);
}

EchoResponse echoResponse = EchoResponse.parseFrom(payload.array());
System.out.println("HTTP: " + echoResponse.getMessage());

httpClient.getConnectionManager().shutdown();

}

Nice, thanks for the working example.