Skip to content

Gateway

At ONO we use gRPC for our server/client communications. It has worked great for us but it is still a pretty young protocol and many services still rely on traditional HTTP. That's why we created the gateway.

Why not HTTP

gRPC is a fast and powerful, binary encoded protocol with great tooling and a large ecosystem. It enables messages to be streamed by the client, the server or both, unlike HTTP. The interface is defined in a protobuf3 file which has many advantages:

  • It is self-documenting
  • It is strongly typed
  • Server interfaces and client stubs are automatically generated for all supported languages

In fact, you can inspect our protobuf3 files here. These fully describe our APIs, they are self-documenting.

In contrast, HTTP would have required manual documentation, a lot of extra effort to make sure our APIs are consistent across clients and servers, no auto-generated boilerplate code and no streaming requests.

What is the gateway

The gateway bridges the gap between HTTP and gRPC. It is essentially a translation layer:

Our APIs (defined by the protobuf3 files) are translated to and from JSON. A gRPC procedure named X can also be made using HTTP POST request: POST /grpc/X.

How to use it

Assume localhost is the server.

The server exposes two ports:

  • 7575: gRPC
  • 7576: HTTP

Since we want to consume the APIs via HTTP, we'll need to use port 7576.

There's a gRPC procedure called GetEmptyDrawers (you can look it up here, it's in onoll.proto). It takes a message GetEmptyDrawersRequest and returns a message GetEmptyDrawersResponse. The former is defined like so:

message GetEmptyDrawersRequest {
    string pluginPayload = 15;
}

the pluginPayload field is used for inter-plugin communication, we can ignore that. Therefore the request message is basically empty. Now let's perform this request in HTTP:

$ curl -s -X POST localhost:7576/grpc/GetEmptyDrawers | json_pp
{
   "drawers" : [
      {
         "height" : 0.0294296875,
         "id" : 25,
         "name" : "C25",
         "slot" : {
            "height" : 3.72,
            "id" : 92,
...

looks like it worked! The json_pp at the end is just to format the JSON response object in a human-readable format.

But what about procedures that have a non-empty request body? It's basically the same thing as REST APIs, the gateway will take care of the translation: set header Content-Type to be application/json and use a JSON object as the request body.

Let's try procedure GetDrawerFromName. It takes a message GetDrawerFromNameRequest and returns a message GetDrawerFromNameResponse. We're not really interested in the latter right now but we need to know how the request message is made so we can create our JSON object:

message GetDrawerFromNameRequest {
    string name = 1;

    string pluginPayload = 15;
}

Again, we can ignore field pluginPayload but name is interesting: it's a string. Based on the procedure name and the request object, it's pretty straightforward that this procedure acts as a query: we provide it a string and it returns a drawer that has that name, if it exists. In fact, here's the response message:

message GetDrawerFromNameResponse {
    Drawer drawer = 1;

    string pluginPayload = 15;
}

Remember, all these definitions can be looked up in the reference section.

Let's try performing this request using HTTP then:

$ curl -s -X POST localhost:7576/grpc/GetDrawerFromName \ 
-H "Content-Type: application/json" --data '{"name": "C1"}' | json_pp
{
   "drawer" : {
      "description" : "materiale elettrico",
      "height" : 0.134125926167739,
      "id" : 1,
      "name" : "C1",
      "products" : [
         {
            "code" : "W2DH5qD7tods95gnm3sK",
            "drawer" : {
...

Now that's cool!

Authentication

If authentication is enabled on your server, you'll need to acquire a JWT token using one of three authentication methods, and you'll need to include that in all your requests.

These are the three procedures that you can use to acquire a token:

  • GetAccessToken: email and password
  • GetFastLoginToken: 9 dots pattern
  • GetRFIDToken: rfid code

Once you have your token, you'll have to put it in the Authorization header of every request. The syntax is the same for REST APIs JWT tokens:

Authorization: Bearer <token_goes_here>

Let's try making a request with no token on a server on which authentication is enabled:

$ curl -s -X POST localhost:7576/grpc/GetRacks | json_pp
{
   "code": 16,
   "error": "running interceptor 'withAuthUnary': missing or invalid JWT token",
   "message": "running interceptor 'withAuthUnary': missing or invalid JWT token"
}

As expected, our request is blocked. Let's get a token!

$ curl -s -X POST localhost:7576/grpc/GetAccessToken \
-H "Content-Type: application/json" \
--data '{"email": "ozzy.osbourne@tormec.com", "password": "crazytrain"}'
| json_pp
{
   "operator" : {
      "email" : "ozzy.osbourne@tormec.com",
      "id" : 1,
      "password" : "$2a$11$uGb3T8tjh4vV..."
   },
   "token" : "eyJhbGciOiJIUzI1NiI..."
}

Let's try again the request above but this time we're going to include our token:

$ curl -s -X POST localhost:7576/grpc/GetRacks \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiI..." \
| json_pp
{
   "racks" : [
      {
         "id" : 1
      }
   ]
}

Looks like it works!