Sites

Menu

ngRPC is in alpha and may have breaking changes before stabilizing. For more information on maturity stages see Maturity stages.

ngRPC (alpha)

ngRPC is a low-level lightweight gRPC client that we bundle with the Worker SDK. This means:

  • You can use it out of the box on all of our supported platforms.
  • It doesn’t need any heavyweight dependencies, such as the full gRPC library.
  • It automatically uses any custom memory allocation that you have configured.

You can use it to communciate with the Platform SDK, or any other gRPC services you might host yourself.

However, it is very low-level, meaning that you need to be able to serialize and deserialize the payload data it sends and receives yourself. In gRPC, these payloads are in the Google Protocol Buffers format (“protobuf“).

You can implement (de)serialization in a number of ways:

  • Manually in your own code. This would only be convenient for very small APIs or proofs-of-concept.
  • Use one of the official protobuf libraries to perform the encoding.
  • Use one of the many third-party protobuf libraries.
  • Write your own code generator, which can take .proto files as input and outputs appropriate serialization/deserialization functions. (You can implement this most easily as a plugin for the official protobuf compiler.)

Flow

An Ngrpc_Client object represents a connection to a specific gRPC server. From an Ngrpc_Client, you can create one or more Ngrpc_Call objects, which correspond to individual remote procedure calls (RPCs) - in other words, method invocations. The lifetimes of these Ngrpc_Calls can overlap arbitrarily, although typically you only use one at a time, as the operations on it are blocking.

The flow for each Ngrpc_Call is to:

  1. Create the call object (Ngrpc_MakeCall).
  2. Send request data (Ngrpc_Send).
  3. Receive response data (Ngrpc_Receive).
  4. Finish the call and check the final status (Ngrpc_FinishCall).
  5. Destroy the call object, releasing any associated resources (Ngrpc_DestroyCall).

Currently, we support only “unary“ (non-streaming) RPCs, so there should just be a single call to Ngrpc_Send and likewise for Ngrpc_Receive.

If an error occurs in a Ngrpc_Send or Ngrpc_Receive (indicated by their return values), you should finish the call (Ngrpc_FinishCall), which returns detailed error information: a status code and a message. The status code corresponds to the standard gRPC codes.

Secure connection

ngRPC can connect over either unsecured TCP, or TCP with TLS for security. The latter requires you to provide one or more root certificates in PEM format, which we validate the server’s certificate against.

Metadata

You can associate additional data in the form of key-value pairs to any Ngrpc_Call as part of the Ngrpc_CallParameters used when creating it. The interpretation of this metadata depends on the particular gRPC service.

One common use for this is to provide an authentication token, with key authorization and value Bearer, followed by a space and the token. For example, the Platform SDK uses this method, with an OAuth 2 access token.

Limitations

  • Use of a single Ngrpc_Client and its associated Ngrpc_Calls is not thread-safe. You must use synchronization to avoid concurrent access from multiple threads.
  • Only blocking (synchronous) operations are supported.
  • Only unary (non-streaming) RPCs are supported.
  • No support for cancellation.
  • No support for client certificates.

Example using manual serialization

This example talks to a hypothetical gRPC service with the following definition:

syntax = "proto3";

package geometry;

service GeometryService {
  rpc CalculateCircleArea(CalculateCircleAreaRequest) returns (CalculateCircleAreaResponse) {}
}

message CalculateCircleAreaRequest {
  double radius = 1;
}

message CalculateCircleAreaResponse {
  double area = 1;
}

As the RPC is very simple, the code below uses manual serialization of the protobuf messages.

double radius = 2.5;
printf("Radius = %f\n", radius);

Ngrpc_Parameters grpc_params = {0};
grpc_params.connect_timeout_ms = 5000;
Ngrpc_Client* grpc = Ngrpc_Create("localhost", 50051, &grpc_params);
Ngrpc_Status create_status = Ngrpc_GetStatus(grpc);
if (create_status.code != NGRPC_STATUS_CODE_OK) {
  fprintf(stderr, "Failed to create gRPC client:\n  %s\n", create_status.message);
  Ngrpc_Destroy(grpc);
  exit(EXIT_FAILURE);
}

Ngrpc_CallParameters call_params = {0};
call_params.timeout_ms = 5000;
Ngrpc_Call* call =
    Ngrpc_MakeCall(grpc, "/geometry.GeometryService/CalculateCircleArea", &call_params);

uint8_t payload[9];  // To hold CalculateCircleAreaRequest message in binary protobuf format.
payload[0] = 011;    // Field ID 1, wire type 1 (64-bit).
memcpy(payload + 1, &radius, 8);
if (Ngrpc_Send(call, payload, sizeof payload)) {
  Ngrpc_Buffer response = Ngrpc_Receive(call);
  if (response.buffer) {
    if (response.length == 9 && response.buffer[0] == 011) {
      double area;
      memcpy(&area, response.buffer + 1, 8);
      printf("Area = %f\n", area);
    } else {
      printf("Unexpected response format.\n");
    }
  } else {
    printf("Receive failed.\n");
  }
} else {
  printf("Send failed.\n");
}

Ngrpc_Status result = Ngrpc_FinishCall(call);
printf("Status code: %s (%d)\n", Ngrpc_StatusCodeToString(result.code), result.code);
printf("Status message: %s\n", result.message);

Ngrpc_DestroyCall(call);

Ngrpc_Destroy(grpc);


————
2019-09-26 Page updated with editorial review

Search results

Was this page helpful?

Thanks for letting us know!

Thanks for your feedback

Need more help? Ask on the forums