Skip to content

Commit eb8fb44

Browse files
committed
docs: add documentation for streams and TLS
Signed-off-by: NorthBlue333 <[email protected]>
1 parent be9b43a commit eb8fb44

File tree

3 files changed

+249
-59
lines changed

3 files changed

+249
-59
lines changed

README.md

Lines changed: 217 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@
66

77
The `@loopback/grpc` component enables LoopBack 4 as a [gRPC](https://grpc.io/) Server. Also it provides with a gRPC decorator to define your RPC Method implementations from your Application Controllers.
88

9+
### Features
10+
11+
- Handles unary, client streaming, server streaming and bidirectional streaming calls
12+
- Provides options for TLS and mTLS
13+
914
## Installation
1015

1116
Install the `@loopback/grpc` component in your LoopBack 4 Application.
@@ -18,10 +23,10 @@ $ npm install --save @loopback/grpc
1823

1924
```js
2025
import {Application} from '@loopback/core';
21-
import {GrpcComponent, Config} from '@loopback/grpc';
22-
import {GreeterCtrl} from './controllers/greeter/greeter.ctrl';
26+
import {GrpcComponent, GrpcComponentConfig} from '@loopback/grpc';
27+
import {GreeterController} from './controllers/greeter.controller';
2328
// Grpc Configurations are optional.
24-
const config: Config.Component = {
29+
const config: GrpcComponentConfig = {
2530
/* Optional Configs */
2631
};
2732
// Pass the optional configurations
@@ -30,13 +35,15 @@ const app = new Application({
3035
});
3136
// Add Grpc as Component
3237
app.component(GrpcComponent);
33-
// Bind GreeterCtrl to the LoopBack Application
34-
app.controller(GreeterCtrl);
38+
// Bind GreeterController to the LoopBack Application
39+
// only if your Boot Mixins do not load this directory
40+
// see https://loopback.io/doc/en/lb4/Booting-an-Application.html
41+
// app.controller(GreeterController);
3542
// Start App
3643
app.start();
3744
```
3845

39-
## Grpc auto-generated code
46+
## gRPC auto-generated code
4047

4148
The `@loopback/grpc` extension provides you with auto-generated interfaces and configurations for strict development.
4249

@@ -46,71 +53,245 @@ Example:
4653

4754
```sh
4855
- app
56+
| - protos
57+
| | - greeter.proto
58+
| - protos-ts
4959
| - controllers
50-
| | - greeter
51-
| | | - greeter.proto
52-
| | | - greeter.ctrl.ts
60+
| | - greeter.controller.ts
5361
```
5462

5563
Once you start your app for first time it will automatically create your typescript interfaces from the `greeter.proto` file.
5664

5765
```sh
5866
- app
67+
| - protos
68+
| | - greeter.proto
69+
| - protos-ts
70+
| | - greeter.ts <--- Auto-generated
5971
| - controllers
60-
| | - greeter
61-
| | | - greeter.proto
62-
| | | - greeter.proto.ts <--- Auto-generated
63-
| | | - greeter.ctrl.ts
72+
| | - greeter.controller.ts
6473
```
6574

6675
Once your interfaces and configurations are created, you can start building your controller logic.
6776

68-
## Grpc Controller
77+
NB: you can also manually generate your interfaces from \_.proto files by creating a `.js` file at the root of your project like below. A good practice would be to add the directory containing your generated `.ts` files to `.gitignore` and to add `node generate.js` to the command `npm run build`.
6978

70-
The `@loopback/grpc` component provides you with a handy decorator to implement GRPC Methods within your LoopBack controllers.
79+
```ts
80+
const GrpcGenerator = require('@loopback/grpc').GrpcGenerator;
81+
const path = require('path');
82+
const fs = require('fs');
7183

72-
`app/controllers/greeter/greeter.ctrl.ts`
84+
function formatWithColor(message, color) {
85+
return `\x1b[${color}m${message}\x1b[0m`;
86+
}
7387

74-
```js
75-
import {grpc} from '@loopback/grpc';
76-
import {Greeter, HelloRequest, HelloReply} from '/greeter.proto';
77-
/**
78-
* @class GreeterCtrl
79-
* @description Implements grpc proto service
80-
**/
81-
export class GreeterCtrl implements Greeter.Service {
82-
// Tell LoopBack that this is a Service RPC implementation
83-
@grpc(Greeter.SayHello)
84-
sayHello(request: HelloRequest): HelloReply {
85-
return {message: 'Hello ' + request.name};
88+
const protoTsPath = path.join(process.cwd(), 'src', 'protos-ts');
89+
try {
90+
fs.statSync(protoTsPath);
91+
} catch (e) {
92+
fs.mkdirSync(protoTsPath);
93+
}
94+
95+
const generator = new GrpcGenerator({
96+
protoPattern: '**/protos/**/*.proto',
97+
// additionalArgs: '--experimental_allow_proto3_optional',
98+
tsOutputPath: protoTsPath,
99+
protoPath: path.join(process.cwd(), 'src', 'protos'),
100+
generate: true,
101+
load: false,
102+
});
103+
104+
console.log(formatWithColor('Generating proto.ts files from *.proto...', 33));
105+
generator.execute();
106+
console.log(formatWithColor('All proto.ts files have been generated', 32));
107+
```
108+
109+
## gRPC Controller
110+
111+
The `@loopback/grpc` component provides you with a handy decorator to implement gRPC Methods within your LoopBack controllers. The decorator will automatically map the correct calls from the file descriptor, the method name and the controller name. If you want an other suffix than `(Ctrl|Controller)`, you can use the argument `controllerNameRegex`.
112+
113+
`app/controllers/greeter.controller.ts`
114+
115+
```ts
116+
import {
117+
sendUnaryData,
118+
ServerDuplexStream,
119+
ServerReadableStream,
120+
ServerUnaryCall,
121+
ServerWritableStream,
122+
} from '@grpc/grpc-js';
123+
import {
124+
getSpecsFromMethodDefinitionAndProtoMetadata,
125+
grpc,
126+
} from '../../../decorators/grpc.decorator';
127+
import BaseController from '../../../grpc.controller';
128+
import {
129+
GreeterServer,
130+
GreeterService,
131+
protoMetadata,
132+
TestRequest,
133+
TestResponse,
134+
} from './greeter';
135+
136+
export default class GreeterController extends BaseController
137+
implements GreeterServer {
138+
// Tell LoopBack that this is a Service RPC implementation
139+
@grpc(
140+
getSpecsFromMethodDefinitionAndProtoMetadata(
141+
GreeterService.unaryTest,
142+
protoMetadata.fileDescriptor,
143+
),
144+
)
145+
async unaryTest(
146+
call: ServerUnaryCall<TestRequest, TestResponse>,
147+
callback: sendUnaryData<TestResponse>,
148+
): Promise<void> {
149+
callback(null, {
150+
message: 'Hello ' + call.request.name,
151+
});
152+
}
153+
154+
@grpc(
155+
getSpecsFromMethodDefinitionAndProtoMetadata(
156+
GreeterService.clientStreamTest,
157+
protoMetadata.fileDescriptor,
158+
),
159+
)
160+
async clientStreamTest(
161+
call: ServerReadableStream<TestRequest, TestResponse>,
162+
callback: sendUnaryData<TestResponse>,
163+
): Promise<void> {
164+
const names = await new Promise<string[]>((resolve, reject) => {
165+
const names: string[] = [];
166+
call.on('data', (chunk: TestRequest) => names.push(chunk.name));
167+
call.on('error', (err) => reject(err));
168+
call.on('end', () => resolve(names));
169+
});
170+
callback(null, {
171+
message: names.join(' '),
172+
});
173+
}
174+
175+
@grpc(
176+
getSpecsFromMethodDefinitionAndProtoMetadata(
177+
GreeterService.serverStreamTest,
178+
protoMetadata.fileDescriptor,
179+
),
180+
)
181+
async serverStreamTest(
182+
call: ServerWritableStream<TestRequest, TestResponse>,
183+
): Promise<void> {
184+
const req = call.request.name.split(' ');
185+
const sleep = (seconds = 0) =>
186+
new Promise((resolve) => setTimeout(resolve, seconds));
187+
const writeAfterSleep = (payload: TestResponse, seconds = 0) =>
188+
(async () => {
189+
await sleep(seconds);
190+
call.write(payload);
191+
})();
192+
193+
const promises: Promise<void>[] = [];
194+
promises.push(
195+
writeAfterSleep({
196+
message: req[0],
197+
}),
198+
);
199+
for (let i = 1; i < req.length; i++) {
200+
promises.push(
201+
writeAfterSleep(
202+
{
203+
message: req[i],
204+
},
205+
i * 200,
206+
),
207+
);
208+
if (i === req.length - 1) {
209+
promises.push(
210+
(async () => {
211+
await sleep((i + 1) * 200);
212+
call.end();
213+
})(),
214+
);
215+
}
86216
}
217+
await Promise.all(promises);
218+
}
219+
220+
@grpc(
221+
getSpecsFromMethodDefinitionAndProtoMetadata(
222+
GreeterService.bidiStreamTest,
223+
protoMetadata.fileDescriptor,
224+
),
225+
)
226+
async bidiStreamTest(
227+
call: ServerDuplexStream<TestRequest, TestResponse>,
228+
): Promise<void> {
229+
await new Promise<string[]>((resolve, reject) => {
230+
call.on('data', (chunk: TestRequest) =>
231+
call.write({
232+
message: `Got ${chunk.name} !`,
233+
}),
234+
);
235+
call.on('error', (err) => reject(err));
236+
call.on('end', () => call.end());
237+
});
238+
}
87239
}
88240
```
89241

90242
## Proto Example
91243

92-
`app/controllers/greeter/greeter.proto`
244+
`app/protos/greeter.proto`
93245

94-
```txt
246+
```proto
95247
syntax = "proto3";
96248
package greeterpackage;
97249
98250
service Greeter {
99251
// Sends a greeting
100-
rpc SayHello (HelloRequest) returns (HelloReply) {}
252+
rpc UnaryTest (TestRequest) returns (TestResponse) {}
253+
rpc ClientStreamTest (stream TestRequest) returns (TestResponse) {}
254+
rpc ServerStreamTest (TestRequest) returns (stream TestResponse) {}
255+
rpc BidiStreamTest (stream TestRequest) returns (stream TestResponse) {}
101256
}
102257
103258
// The request message containing the user's name.
104-
message HelloRequest {
259+
message TestRequest {
105260
string name = 1;
106261
}
107262
108263
// The response message containing the greetings
109-
message HelloReply {
264+
message TestResponse {
110265
string message = 1;
111266
}
112267
```
113268

269+
## Setting up TLS and mTLS
270+
271+
```ts
272+
import {GrpcComponentConfig} from '@loopback/grpc';
273+
274+
const grpcConfig: GrpcComponentConfig = {
275+
server: {
276+
tls: {
277+
rootCertPath: 'path/to/root/cert',
278+
keyCertPairPaths: [
279+
{
280+
privateKeyPath: 'path/to/private/key/for/server',
281+
certChainPath: 'path/to/cert/chain/for/server',
282+
},
283+
],
284+
},
285+
// set it to false/undefined to disable client certificate verification
286+
checkClientCertificate: true,
287+
},
288+
};
289+
```
290+
291+
### Client side integration
292+
293+
Tests can be a good starting point for your client side integration.
294+
114295
## Contribute
115296

116297
Get started by either downloading this project or cloning it as follows:
@@ -122,17 +303,16 @@ $ cd loopback4-extension-grpc && npm install
122303

123304
## Contributions
124305

125-
* [Guidelines](https://github.com/strongloop/loopback-next/wiki/Contributing#guidelines)
126-
* [Join the team](https://github.com/strongloop/loopback-next/issues/110)
306+
- [Guidelines](https://github.com/strongloop/loopback-next/wiki/Contributing#guidelines)
307+
- [Join the team](https://github.com/strongloop/loopback-next/issues/110)
127308

128309
## Tests
129310

130311
run `npm test` from the root folder.
131312

132313
## Todo
133314

134-
* Watch for proto changes.
135-
* Server/Client Streams
315+
- Maybe watch for proto changes.
136316

137317
## Contributors
138318

0 commit comments

Comments
 (0)