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 passwordGetFastLoginToken
: 9 dots patternGetRFIDToken
: 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!