├── .gitignore
├── README.md
├── errors
├── README.md
├── client.js
├── grpc_server.js
└── mali_server.js
├── hello_world_dynamic
├── greeter_client.js
├── greeter_server.js
└── gs.js
├── hello_world_static
├── greeter_client.js
├── greeter_server.js
├── helloworld_grpc_pb.js
├── helloworld_pb.js
└── package.json
├── package-lock.json
├── package.json
├── protos
├── any.proto
├── errorexample.proto
├── helloworld.proto
├── hw2.proto
├── push.proto
├── redischat.proto
├── route_guide.proto
├── secret.proto
├── status.proto
└── user.proto
├── push
├── client.js
├── lotsofclients.js
├── push_service.js
├── server.js
├── update_source.js
├── widget.json
└── widget_source.js
├── redis-chat
├── client.js
├── server_grpc.js
└── server_mali.js
├── route_guide
├── feature_db.js
├── route_guide_client.js
├── route_guide_db.json
├── route_guide_db_notes.json
├── route_guide_server.js
├── route_utils.js
└── server.js
├── secret_service
├── client.js
├── encrpyt.js
├── save.js
├── secret_data.json
├── secret_service.js
└── server.js
└── user_service
├── server.js
├── test
└── user.test.js
├── user.js
├── user_db.json
└── user_service.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 |
11 | # Directory for instrumented libs generated by jscoverage/JSCover
12 | lib-cov
13 |
14 | # Coverage directory used by tools like istanbul
15 | coverage
16 |
17 | # nyc test coverage
18 | .nyc_output
19 |
20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
21 | .grunt
22 |
23 | # node-waf configuration
24 | .lock-wscript
25 |
26 | # Compiled binary addons (http://nodejs.org/api/addons.html)
27 | build/Release
28 |
29 | # Dependency directories
30 | node_modules
31 | jspm_packages
32 |
33 | # Optional npm cache directory
34 | .npm
35 |
36 | # Optional REPL history
37 | .node_repl_history
38 |
39 | user_db_write.json
40 | secret_db_write.json
41 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Mali Examples
2 |
3 | A repository containing small examples to illustrate the use of Mali for creating gRPC applications.
4 |
5 | ## Installation
6 |
7 | ```sh
8 | $ npm install
9 | ```
10 |
11 | ## Examples
12 |
13 | * [Hello World](#helloworld) - The stock gRPC Hello World example
14 | * [Route Guide](#routeguide) - The stock gRPC Route Guide tutorial
15 | * [User Service](#userservice) - Sample "User" service
16 | * [Secret Service](#secretservice) - Process stream of "secret" requests, and consume errors
17 | * [Push Server](#push) - Push server example
18 | * [Errors Example](#errors) - Example demonstrating sending errors to client
19 |
20 | ### Hello World
21 |
22 | The stock [gRPC Hello World example](https://github.com/grpc/grpc/tree/master/examples/node)
23 | converted to use Mali for server.
24 | Both dynamic and static code generated examples are provided.
25 | With dynamic we create a server from protocol buffer definition file.
26 | With static we create the server from pre-generated Node.js code.
27 | The client is unchanged from the gRPC client sample code.
28 |
29 | #### Run the server
30 |
31 | ```sh
32 | $ # from this directory
33 | $ node ./hello_world_dynamic/greeter_server.js
34 | $ # OR
35 | $ node ./hello_world_static/greeter_server.js
36 | ```
37 |
38 | #### Run the client
39 |
40 | ```sh
41 | $ # from this directory
42 | $ node ./hello_world_dynamic/greeter_client.js
43 | $ # OR
44 | $ node ./hello_world_static/greeter_client.js
45 | ```
46 |
47 | ### Route Guide
48 |
49 | The stock [gRPC Route Guide tutorial](http://www.grpc.io/docs/tutorials/basic/node.html)
50 | converted to use Mali for server with some notable changes:
51 |
52 | * We use the `route_guide_db.json` as the database source.
53 | * All data is asynchronously queried to the file using streaming to demonstrate
54 | asynchronous interaction with a "database".
55 | * Similarly Notes are written to `route_guide_db_notes.json` file to simulate
56 | asynchronous I/O interactions. This file is truncated at startup to get
57 | a clean slate.
58 | * Use of [logger](https://github.com/malijs/logger) middleware.
59 |
60 | The client is unchanged from the gRPC client sample code.
61 |
62 | #### Run the server
63 |
64 | ```sh
65 | $ # from within route_guide directory
66 | $ node ./server.js
67 | ```
68 |
69 | #### Run the client
70 |
71 | ```sh
72 | $ # from this directory
73 | $ node ./route_guide/route_guide_client.js --db_path=./route_guide/route_guide_db.json
74 | ```
75 |
76 | ### User service
77 |
78 | A simple "User" service that demonstrates usage of [apikey](https://github.com/malijs/apikey), [logger](https://github.com/malijs/logger) and [tojson](https://github.com/malijs/tojson)
79 | middleware. As well since currently Protocol Buffers (and therefore gRPC) doesn't support
80 | serializing variable JSON data (the user "metadata" field in this case),
81 | this example shows how to serialize the property by converting it to bytes.
82 | This is not the most efficient solution but the best we can do right now.
83 |
84 | #### Run the server
85 |
86 | ```sh
87 | $ # from within user_service directory
88 | $ node ./server.js
89 | ```
90 |
91 | #### Run the tests for the service
92 |
93 | ```sh
94 | $ # from this directory
95 | $ npm run test-user
96 | ```
97 |
98 | ### Secret service
99 |
100 | A simple service that asynchronously processes a stream of requests and collects
101 | the errors and returns the number of succeeded and the failed details.
102 |
103 | #### Run the server
104 |
105 | ```sh
106 | $ # from within secret_service directory
107 | $ node ./server.js
108 | ```
109 |
110 | #### Run the client
111 |
112 | ```sh
113 | $ # from within secret_service directory
114 | $ node ./client.js
115 | ```
116 |
117 | ### Push server
118 |
119 | gRPC can be used to implement a service that sends updates to all connected clients. This can be achieved using response stream calls. In this contrieved example clients open a stream response connection using `syncWidgets` call, which returns all "updated" widgets since some timestamp. Afterwards as widgets are updated the server sends the updates to all the connected clients. The server abstracts and mocks a simple widget store which can be either another service (ie widget service) or a database. Similarly it abstracts and mocks an update notification mechanism that notifies it of updated widgets (for example incoming messages from AMQP or some other messanging system).
120 |
121 | #### Run the server
122 |
123 | ```sh
124 | $ # from within examples directory
125 | $ node ./push/server.js
126 | ```
127 |
128 | #### Run the client
129 |
130 | ```sh
131 | $ # from within examples directory
132 | $ node ./push/client.js
133 | ```
134 |
135 | #### Run an app that makes lots of concurrent client requests
136 |
137 | ```sh
138 | $ # from within examples directory
139 | $ node ./push/lotsofclients.js
140 | ```
141 |
142 | ### Errors example
143 |
144 | Demonstrates different ways of comminicating erros to client. [Read more](errors/README.md).
145 |
146 | #### Run the server
147 |
148 | To run the traditional gRPC implementation:
149 |
150 | ```sh
151 | $ # from within examples directory
152 | $ node ./errors/grpc_server.js
153 | ```
154 |
155 | Mali implementation:
156 |
157 | ```sh
158 | $ # from within examples directory
159 | $ node ./errors/mali_server.js
160 | ```
161 |
162 | #### Run the client
163 |
164 | Experiment with different calls to explore.
165 |
166 | ```sh
167 | $ # from within examples directory
168 | $ node ./errors/client.js
169 | ```
170 |
--------------------------------------------------------------------------------
/errors/README.md:
--------------------------------------------------------------------------------
1 | # Comminicating Errors
2 |
3 | This section will cover different ways for communicating errors from server to client.
4 | We will compare traditional gRPC implementations and Mali version.
5 |
6 | ## Service Definition
7 |
8 | ```protobuf
9 | syntax = "proto3";
10 |
11 | package ErrorExample;
12 |
13 | import "status.proto";
14 |
15 | service SampleService {
16 | rpc GetWidget (WidgetRequest) returns (Widget) {}
17 | rpc GetWidget2 (WidgetRequest) returns (Widget) {}
18 | rpc ListWidgets (WidgetRequest) returns (stream WidgetStreamObject) {}
19 | rpc CreateWidgets (stream Widget) returns (WidgetResult) {}
20 | rpc SyncWidgets (stream WidgetStreamObject) returns (stream WidgetStreamObject) {}
21 | }
22 |
23 | message Widget {
24 | string name = 1;
25 | }
26 |
27 | message WidgetStreamObject {
28 | Widget widget = 1;
29 | google.rpc.Status error = 2;
30 | }
31 |
32 | message WidgetRequest {
33 | int32 id = 1;
34 | }
35 |
36 | message WidgetResult {
37 | int32 created = 1;
38 | }
39 | ```
40 |
41 | ## UNARY
42 |
43 | With gRPC with use the callback in our handler to response with an error to the client.
44 |
45 | ```js
46 | function getWidget (call, fn) {
47 | const { id } = call.request
48 | if (id && id % 2 === 0) {
49 | return fn(new Error('boom!'))
50 | }
51 | fn(null, { name: `w${id}` })
52 | }
53 | ```
54 |
55 | On client side:
56 |
57 | ```js
58 | // change id to 4 to cause error
59 | client.getWidget({ id: 0 }, (err, data) => {
60 | if (err) {
61 | console.error('Error: %s', err)
62 | return fn()
63 | }
64 | console.log(data)
65 | })
66 | ```
67 |
68 | With Mali the server implementation becomes just a matter of throwing an error:
69 |
70 | ```js
71 | async function getWidget (ctx) {
72 | const { id } = ctx.req
73 | if (id && id % 2 === 0) {
74 | throw new Error('boom!')
75 | }
76 | ctx.res = { name: `w${id}` }
77 | }
78 | ```
79 |
80 | If `app.silent` is the default `false` this will log the error in the server application.
81 | We can explicitly set the response to an error which will also communicate the error to the client, but circumvent the error logging.
82 |
83 | ```js
84 | async function getWidget (ctx) {
85 | const { id } = ctx.req
86 | if (id && id % 2 === 0) {
87 | ctx.res = new Error('boom!')
88 | } else {
89 | ctx.res = { name: `w${id}` }
90 | }
91 | }
92 | ```
93 |
94 | ## REQUEST STREAM
95 |
96 | Similarly with request stream in gRPC server implementation we use the callback to respond either with a response or an error:
97 |
98 | ```js
99 | function createWidgets (call, fn) {
100 | let created = 0
101 | call.on('data', d => created++)
102 | call.on('end', () => {
103 | if (created && created % 2 === 0) {
104 | return fn(new Error('boom!'))
105 | }
106 | fn(null, { created })
107 | })
108 | }
109 | ```
110 |
111 | Client implementation:
112 |
113 | ```js
114 | const call = client.createWidgets((err, res) => {
115 | if (err) {
116 | console.error('Error: %s', err)
117 | return fn()
118 | }
119 | console.log(res)
120 | })
121 |
122 | const widgets = [
123 | { name: 'w1' },
124 | { name: 'w2' },
125 | { name: 'w3' }
126 | ]
127 |
128 | widgets.forEach(w => call.write(w))
129 | call.end()
130 | ```
131 |
132 | With Mali if becomes a matter of returning a Promise that's either resolved with the final response or rejected with an error:
133 |
134 | ```js
135 | async function createWidgets (ctx) {
136 | ctx.res = new Promise((resolve, reject) => {
137 | // using Highland.js
138 | hl(ctx.req)
139 | .toArray(a => {
140 | const created = a.length
141 | if (created && created % 2 === 0) {
142 | return reject(new Error(`boom ${created}!`))
143 | }
144 | resolve({ created })
145 | })
146 | })
147 | }
148 | ```
149 |
150 | Alternatively, similar to `UNARY` calls, we can resolve with an error to explicitly return an error and circumvent the error logging within the application.
151 |
152 | ## RESPONSE STREAM
153 |
154 | With response stream calls we can `emit` an error to the response stream. However this would cause a stop to the request. Sometimes this is not desireble if we can detect and control errorous conditions and want to contirnue streaming. In such scenarios we need to setup are responses to include error data. Reviewing our call definition:
155 |
156 | ```protobuf
157 | rpc ListWidgets (WidgetRequest) returns (stream WidgetStreamObject) {}
158 | ```
159 |
160 | and our response type:
161 |
162 | ```
163 | message WidgetStreamObject {
164 | Widget widget = 1;
165 | google.rpc.Status error = 2;
166 | }
167 | ```
168 |
169 | If there was an error in processing the request on a perticular instance of the stream and we want to send that to the client but continue on serving the rest of the request, we can just set the `error` property of the payload. Here we use Google API's [RPC status](https://github.com/googleapis/googleapis/blob/master/google/rpc/status.proto) proto definition to define the error field.
170 |
171 | Our gRPC server implementation can look something like the following:
172 |
173 | ```js
174 | function listWidgets (call) {
175 | const widgets = [
176 | { name: 'w1' },
177 | { name: 'w2' },
178 | { name: 'w3' },
179 | new Error('boom!'),
180 | { name: 'w4' },
181 | new Error('Another boom!'),
182 | { name: 'w5' },
183 | { name: 'w6' }
184 | ]
185 |
186 | _.each(widgets, w => {
187 | if (w instanceof Error) {
188 | const { message } = w
189 | call.write({ error: { message } })
190 | } else {
191 | call.write({ widget: w })
192 | }
193 | })
194 | call.end()
195 | }
196 | ```
197 |
198 | On client:
199 |
200 | ```js
201 | const call = client.listWidgets({ id: 8 })
202 |
203 | call.on('data', d => {
204 | if (d.widget) {
205 | console.log(d.widget)
206 | } else if (d.error) {
207 | console.log('Data error: %s', d.error.message)
208 | }
209 | })
210 |
211 | call.on('error', err => {
212 | console.error('Client error: %s', err)
213 | })
214 |
215 | call.on('end', () => console.log('done!'))
216 | ```
217 |
218 | With Mali we set the response to a stream that's piped to the client. We can use stream utilities such as [Highland.js](http://highlandjs.org/), or others to work with the stream data.
219 |
220 | ```js
221 | async function listWidgets (ctx) {
222 | const widgets = [
223 | { name: 'w1' },
224 | { name: 'w2' },
225 | { name: 'w3' },
226 | new Error('boom!'),
227 | { name: 'w4' },
228 | new Error('Another boom!'),
229 | { name: 'w5' },
230 | { name: 'w6' }
231 | ]
232 |
233 | ctx.res = hl(widgets)
234 | .map(w => {
235 | if (w instanceof Error) {
236 | const { message } = w
237 | return { error: { message } }
238 | } else {
239 | return { widget: w }
240 | }
241 | })
242 | }
243 | ```
244 |
245 | ## DUPLEX
246 |
247 | We can take the same approach with duplex streams.
248 |
249 | gRPC server implementation:
250 |
251 | ```js
252 | function syncWidgets (call) {
253 | let counter = 0
254 | call.on('data', d => {
255 | counter++
256 | if (d.widget) {
257 | console.log('data: %s', d.widget.name)
258 | call.write({ widget: { name: d.widget.name.toUpperCase() } })
259 | } else if (d.error) {
260 | console.error('Error: %s', d.error.message)
261 | }
262 | if (counter % 4 === 0) {
263 | call.write({ error: { message: `Boom ${counter}!` } })
264 | }
265 | })
266 | call.on('end', () => {
267 | call.end()
268 | })
269 | }
270 | ```
271 |
272 | Client:
273 |
274 | ```js
275 | const call = client.syncWidgets()
276 |
277 | call.on('data', d => {
278 | if (d.widget) {
279 | console.log(d.widget)
280 | } else if (d.error) {
281 | console.log('Data error: %s', d.error.message)
282 | }
283 | })
284 |
285 | call.on('error', err => {
286 | console.error('Client error: %s', err)
287 | })
288 |
289 | const widgets = [
290 | { name: 'w1' },
291 | new Error('Client Boom 1'),
292 | { name: 'w2' },
293 | { name: 'w3' },
294 | { name: 'w4' },
295 | new Error('Client Boom 2'),
296 | { name: 'w5' }
297 | ]
298 |
299 | widgets.forEach(w => {
300 | if (w instanceof Error) {
301 | const { message } = w
302 | call.write({ error: { message } })
303 | } else {
304 | call.write({ widget: w })
305 | }
306 | })
307 | call.end()
308 | ```
309 |
310 | With Mali we can use [mississippi](https://npmjs.com/package/mississippi) stream utility to ietrate over the stream and supply response data. In case of an error we set the `error` property in the payload appropriately.
311 |
312 | ```js
313 | async function syncWidgets (ctx) {
314 | let counter = 0
315 | miss.each(ctx.req, (d, next) => {
316 | counter++
317 | if (d.widget) {
318 | console.log('data: %s', d.widget.name)
319 | ctx.res.write({ widget: { name: d.widget.name.toUpperCase() } })
320 | } else if (d.error) {
321 | console.error('Error: %s', d.error.message)
322 | }
323 | if (counter % 4 === 0) {
324 | ctx.res.write({ error: { message: `Boom ${counter}!` } })
325 | }
326 | next()
327 | }, () => {
328 | ctx.res.end()
329 | })
330 | }
331 | ```
332 |
--------------------------------------------------------------------------------
/errors/client.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const async = require('async')
3 | const protoLoader = require('@grpc/proto-loader')
4 | const grpc = require('grpc')
5 |
6 | const PROTO_PATH = path.resolve(__dirname, '../protos/errorexample.proto')
7 | const HOSTPORT = '0.0.0.0:50051'
8 |
9 | const pd = protoLoader.loadSync(PROTO_PATH)
10 | const loaded = grpc.loadPackageDefinition(pd)
11 | const client = new loaded.ErrorExample.SampleService(HOSTPORT, grpc.credentials.createInsecure())
12 |
13 | function getWidgetOK (fn) {
14 | client.getWidget({ id: 0 }, (err, data) => {
15 | if (err) {
16 | console.error('Error: %s', err)
17 | return fn()
18 | }
19 | console.log(data)
20 | fn()
21 | })
22 | }
23 |
24 | function getWidgetError (fn) {
25 | client.getWidget({ id: 4 }, (err, data) => {
26 | if (err) {
27 | console.error('Error: %s', err)
28 | return fn()
29 | }
30 | console.log(data)
31 | fn()
32 | })
33 | }
34 |
35 | function listWidgets (fn) {
36 | const call = client.listWidgets({ id: 3 })
37 | call.on('data', d => {
38 | console.log(d)
39 | })
40 | call.on('error', err => {
41 | console.error('Error: %s', err)
42 | })
43 | call.on('end', fn)
44 | }
45 |
46 | function listWidgetsError (fn) {
47 | const call = client.listWidgets({ id: 8 })
48 | call.on('data', d => {
49 | if (d.widget) {
50 | console.log(d.widget)
51 | } else if (d.error) {
52 | console.log('Data error: %s', d.error.message)
53 | }
54 | })
55 | call.on('error', err => {
56 | console.error('Client error: %s', err)
57 | })
58 | call.on('end', fn)
59 | }
60 |
61 | function createWidgets (fn) {
62 | const call = client.createWidgets((err, res) => {
63 | if (err) {
64 | console.error('Error: %s', err)
65 | return fn()
66 | }
67 | console.log(res)
68 | })
69 |
70 | const widgets = [{ name: 'w1' }, { name: 'w2' }, { name: 'w3' }]
71 |
72 | widgets.forEach(w => call.write(w))
73 | call.end()
74 | fn()
75 | }
76 |
77 | function createWidgetsError (fn) {
78 | const call = client.createWidgets((err, res) => {
79 | if (err) {
80 | console.error('Error: %s', err)
81 | return fn()
82 | }
83 | console.log(res)
84 | })
85 |
86 | const widgets = [{ name: 'w1' }, { name: 'w2' }, { name: 'w3' }, { name: 'w4' }]
87 |
88 | widgets.forEach(w => call.write(w))
89 | call.end()
90 | }
91 |
92 | function syncWidgets (fn) {
93 | const call = client.syncWidgets()
94 | call.on('data', d => {
95 | if (d.widget) {
96 | console.log(d.widget)
97 | } else if (d.error) {
98 | console.log('Data error: %s', d.error.message)
99 | }
100 | })
101 | call.on('error', err => {
102 | console.error('Client error: %s', err)
103 | })
104 | const widgets = [{ name: 'w1' }, { name: 'w2' }, { name: 'w3' }]
105 |
106 | widgets.forEach(w => call.write({ widget: w }))
107 | call.end()
108 | fn()
109 | }
110 |
111 | function syncWidgetsError (fn) {
112 | const call = client.syncWidgets()
113 | call.on('data', d => {
114 | if (d.widget) {
115 | console.log(d.widget)
116 | } else if (d.error) {
117 | console.log('Data error: %s', d.error.message)
118 | }
119 | })
120 | call.on('error', err => {
121 | console.error('Client error: %s', err)
122 | })
123 | const widgets = [
124 | { name: 'w1' },
125 | new Error('Client Boom 1'),
126 | { name: 'w2' },
127 | { name: 'w3' },
128 | { name: 'w4' },
129 | new Error('Client Boom 2'),
130 | { name: 'w5' }
131 | ]
132 |
133 | widgets.forEach(w => {
134 | if (w instanceof Error) {
135 | const { message } = w
136 | call.write({ error: { message } })
137 | } else {
138 | call.write({ widget: w })
139 | }
140 | })
141 | call.end()
142 | fn()
143 | }
144 |
145 | function main () {
146 | async.series(
147 | [
148 | getWidgetOK,
149 | getWidgetError,
150 | listWidgets,
151 | listWidgetsError,
152 | createWidgets,
153 | createWidgetsError,
154 | syncWidgets,
155 | syncWidgetsError
156 | ],
157 | () => {
158 | console.log('done!')
159 | }
160 | )
161 | }
162 |
163 | main()
164 |
--------------------------------------------------------------------------------
/errors/grpc_server.js:
--------------------------------------------------------------------------------
1 | const _ = require('lodash')
2 | const protoLoader = require('@grpc/proto-loader')
3 | const grpc = require('grpc')
4 | const path = require('path')
5 |
6 | const PROTO_PATH = path.resolve(__dirname, '../protos/errorexample.proto')
7 | const HOSTPORT = '0.0.0.0:50051'
8 |
9 | const pd = protoLoader.loadSync(PROTO_PATH)
10 | const loaded = grpc.loadPackageDefinition(pd)
11 | const example = loaded.ErrorExample
12 |
13 | function listWidgets (call) {
14 | const widgets = [
15 | { name: 'w1' },
16 | { name: 'w2' },
17 | { name: 'w3' },
18 | new Error('boom!'),
19 | { name: 'w4' },
20 | new Error('Another boom!'),
21 | { name: 'w5' },
22 | { name: 'w6' }
23 | ]
24 |
25 | _.each(widgets, w => {
26 | if (w instanceof Error) {
27 | const { message } = w
28 | call.write({ error: { message } })
29 | } else {
30 | call.write({ widget: w })
31 | }
32 | })
33 | call.end()
34 | }
35 |
36 | function getWidget (call, fn) {
37 | const { id } = call.request
38 | if (id && id % 2 === 0) {
39 | return fn(new Error('boom!'))
40 | }
41 | fn(null, { name: `w${id}` })
42 | }
43 |
44 | function createWidgets (call, fn) {
45 | let created = 0
46 | call.on('data', d => created++)
47 | call.on('end', () => {
48 | if (created && created % 2 === 0) {
49 | return fn(new Error('boom!'))
50 | }
51 | fn(null, { created })
52 | })
53 | }
54 |
55 | function syncWidgets (call) {
56 | let counter = 0
57 | call.on('data', d => {
58 | counter++
59 | if (d.widget) {
60 | console.log('data: %s', d.widget.name)
61 | call.write({ widget: { name: d.widget.name.toUpperCase() } })
62 | } else if (d.error) {
63 | console.error('Error: %s', d.error.message)
64 | }
65 | if (counter % 4 === 0) {
66 | call.write({ error: { message: `Boom ${counter}!` } })
67 | }
68 | })
69 | call.on('end', () => {
70 | call.end()
71 | })
72 | }
73 |
74 | function main () {
75 | const server = new grpc.Server()
76 | server.addService(example.SampleService.service, {
77 | getWidget,
78 | getWidget2: getWidget,
79 | listWidgets,
80 | createWidgets,
81 | syncWidgets
82 | })
83 | server.bind(HOSTPORT, grpc.ServerCredentials.createInsecure())
84 | server.start()
85 | }
86 |
87 | main()
88 |
--------------------------------------------------------------------------------
/errors/mali_server.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const hl = require('highland')
3 | const Mali = require('mali')
4 | const miss = require('mississippi')
5 |
6 | const PROTO_PATH = path.resolve(__dirname, '../protos/errorexample.proto')
7 | const HOSTPORT = '0.0.0.0:50051'
8 |
9 | async function getWidget (ctx) {
10 | const { id } = ctx.req
11 | if (id && id % 2 === 0) {
12 | throw new Error('boom!')
13 | }
14 | ctx.res = { name: `w${id}` }
15 | }
16 |
17 | async function getWidget2 (ctx) {
18 | const { id } = ctx.req
19 | if (id && id % 2 === 0) {
20 | ctx.res = new Error('boom!')
21 | } else {
22 | ctx.res = { name: `w${id}` }
23 | }
24 | }
25 |
26 | async function listWidgets (ctx) {
27 | const widgets = [
28 | { name: 'w1' },
29 | { name: 'w2' },
30 | { name: 'w3' },
31 | new Error('boom!'),
32 | { name: 'w4' },
33 | new Error('Another boom!'),
34 | { name: 'w5' },
35 | { name: 'w6' }
36 | ]
37 |
38 | ctx.res = hl(widgets)
39 | .map(w => {
40 | if (w instanceof Error) {
41 | const { message } = w
42 | return { error: { message } }
43 | } else {
44 | return { widget: w }
45 | }
46 | })
47 | }
48 |
49 | async function createWidgets (ctx) {
50 | ctx.res = new Promise((resolve, reject) => {
51 | hl(ctx.req)
52 | .toArray(a => {
53 | const created = a.length
54 | if (created && created % 2 === 0) {
55 | return reject(new Error(`boom ${created}!`))
56 | }
57 | resolve({ created })
58 | })
59 | })
60 | }
61 |
62 | async function syncWidgets (ctx) {
63 | let counter = 0
64 | miss.each(ctx.req, (d, next) => {
65 | counter++
66 | if (d.widget) {
67 | console.log('data: %s', d.widget.name)
68 | ctx.res.write({ widget: { name: d.widget.name.toUpperCase() } })
69 | } else if (d.error) {
70 | console.error('Error: %s', d.error.message)
71 | }
72 | if (counter % 4 === 0) {
73 | ctx.res.write({ error: { message: `Boom ${counter}!` } })
74 | }
75 | next()
76 | }, () => {
77 | ctx.res.end()
78 | })
79 | }
80 |
81 | function main () {
82 | const app = new Mali(PROTO_PATH)
83 | app.use({
84 | getWidget,
85 | getWidget2,
86 | listWidgets,
87 | createWidgets,
88 | syncWidgets
89 | })
90 | app.start(HOSTPORT)
91 | console.log(`Greeter service running @ ${HOSTPORT}`)
92 | }
93 |
94 | main()
95 |
--------------------------------------------------------------------------------
/hello_world_dynamic/greeter_client.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * Copyright 2015, Google Inc.
4 | * All rights reserved.
5 | *
6 | * Redistribution and use in source and binary forms, with or without
7 | * modification, are permitted provided that the following conditions are
8 | * met:
9 | *
10 | * * Redistributions of source code must retain the above copyright
11 | * notice, this list of conditions and the following disclaimer.
12 | * * Redistributions in binary form must reproduce the above
13 | * copyright notice, this list of conditions and the following disclaimer
14 | * in the documentation and/or other materials provided with the
15 | * distribution.
16 | * * Neither the name of Google Inc. nor the names of its
17 | * contributors may be used to endorse or promote products derived from
18 | * this software without specific prior written permission.
19 | *
20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 | *
32 | */
33 |
34 | var path = require('path')
35 | const protoLoader = require('@grpc/proto-loader')
36 | const grpc = require('grpc')
37 |
38 | const PROTO_PATH = path.resolve(__dirname, '../protos/helloworld.proto')
39 |
40 | const pd = protoLoader.loadSync(PROTO_PATH)
41 | const loaded = grpc.loadPackageDefinition(pd)
42 | const hello_proto = loaded.helloworld
43 |
44 | function main () {
45 | var client = new hello_proto.Greeter('localhost:50051', grpc.credentials.createInsecure())
46 | var user
47 | if (process.argv.length >= 3) {
48 | user = process.argv[2]
49 | } else {
50 | user = 'world'
51 | }
52 | client.sayHello({ name: user }, function (err, response) {
53 | console.log('Greeting:', response.message)
54 | })
55 | }
56 |
57 | main()
58 |
--------------------------------------------------------------------------------
/hello_world_dynamic/greeter_server.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const Mali = require('mali')
3 |
4 | const PROTO_PATH = path.resolve(__dirname, '../protos/helloworld.proto')
5 | const HOSTPORT = '0.0.0.0:50051'
6 |
7 | /**
8 | * Implements the SayHello RPC method.
9 | */
10 | function sayHello (ctx) {
11 | ctx.res = { message: 'Hello ' + ctx.req.name }
12 | }
13 |
14 | /**
15 | * Starts an RPC server that receives requests for the Greeter service at the
16 | * sample server port
17 | */
18 | function main () {
19 | const app = new Mali(PROTO_PATH, 'Greeter')
20 | app.use({ sayHello })
21 | app.start(HOSTPORT)
22 | console.log(`Greeter service running @ ${HOSTPORT}`)
23 | }
24 |
25 | main()
26 |
--------------------------------------------------------------------------------
/hello_world_dynamic/gs.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const Mali = require('mali')
3 | const hl = require('highland')
4 |
5 | const PROTO_PATH = path.resolve(__dirname, '../protos/hw2.proto')
6 | const HOSTPORT = '0.0.0.0:50051'
7 |
8 | const streamData = [{
9 | message: 'Hello Bob'
10 | },
11 | {
12 | message: 'Hello Kate'
13 | },
14 | {
15 | message: 'Hello Jim'
16 | },
17 | {
18 | message: 'Hello Sara'
19 | }
20 | ]
21 |
22 | /**
23 | * Implements the SayHello RPC method.
24 | */
25 | async function sayHello (ctx) {
26 | console.dir(ctx.metadata, { depth: 3, colors: true })
27 | console.log(`got sayHello request name: ${ctx.req.name}`)
28 | ctx.res = { message: 'Hello ' + ctx.req.name }
29 | console.log(`set sayHello response: ${ctx.res.message}`)
30 | }
31 |
32 | async function sayHellos (ctx) {
33 | console.dir(ctx.metadata, { depth: 3, colors: true })
34 | console.log(`got sayHellos request name: ${ctx.req.name}`)
35 | ctx.res = hl(streamData)
36 | console.log(`done sayHellos`)
37 | }
38 |
39 | async function sayHelloCs (ctx) {
40 | console.dir(ctx.metadata, { depth: 3, colors: true })
41 | console.log('got sayHelloCs')
42 | let counter = 0
43 | return new Promise((resolve, reject) => {
44 | hl(ctx.req)
45 | .map(message => {
46 | counter++
47 | if (message && message.name) {
48 | return message.name.toUpperCase()
49 | }
50 | return ''
51 | })
52 | .collect()
53 | .toCallback((err, result) => {
54 | if (err) return reject(err)
55 | console.log(`done sayHelloCs counter ${counter}`)
56 | ctx.response.res = { message: 'Hello ' + counter }
57 | resolve()
58 | })
59 | })
60 | }
61 |
62 | async function sayHelloBidi (ctx) {
63 | console.log('got sayHelloBidi')
64 | console.dir(ctx.metadata, { depth: 3, colors: true })
65 | let counter = 0
66 | ctx.req.on('data', d => {
67 | counter++
68 | ctx.res.write({ message: 'Hello ' + d.name })
69 | })
70 | ctx.req.on('end', () => {
71 | console.log(`done sayHelloBidi counter ${counter}`)
72 | ctx.res.end()
73 | })
74 | }
75 |
76 | /**
77 | * Starts an RPC server that receives requests for the Greeter service at the
78 | * sample server port
79 | */
80 | function main () {
81 | const app = new Mali(PROTO_PATH, 'Greeter')
82 | app.use({ sayHello, sayHellos, sayHelloCs, sayHelloBidi })
83 | app.start(HOSTPORT)
84 | console.log(`Greeter service running @ ${HOSTPORT}`)
85 | }
86 |
87 | main()
88 |
--------------------------------------------------------------------------------
/hello_world_static/greeter_client.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * Copyright 2015, Google Inc.
4 | * All rights reserved.
5 | *
6 | * Redistribution and use in source and binary forms, with or without
7 | * modification, are permitted provided that the following conditions are
8 | * met:
9 | *
10 | * * Redistributions of source code must retain the above copyright
11 | * notice, this list of conditions and the following disclaimer.
12 | * * Redistributions in binary form must reproduce the above
13 | * copyright notice, this list of conditions and the following disclaimer
14 | * in the documentation and/or other materials provided with the
15 | * distribution.
16 | * * Neither the name of Google Inc. nor the names of its
17 | * contributors may be used to endorse or promote products derived from
18 | * this software without specific prior written permission.
19 | *
20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 | *
32 | */
33 |
34 | var messages = require('./helloworld_pb');
35 | var services = require('./helloworld_grpc_pb');
36 |
37 | var grpc = require('grpc');
38 |
39 | function main() {
40 | var client = new services.GreeterClient('localhost:50051',
41 | grpc.credentials.createInsecure());
42 | var request = new messages.HelloRequest();
43 | var user;
44 | if (process.argv.length >= 3) {
45 | user = process.argv[2];
46 | } else {
47 | user = 'world';
48 | }
49 | request.setName(user);
50 | client.sayHello(request, function(err, response) {
51 | console.log('Greeting:', response.getMessage());
52 | });
53 | }
54 |
55 | main();
56 |
--------------------------------------------------------------------------------
/hello_world_static/greeter_server.js:
--------------------------------------------------------------------------------
1 | const Mali = require('mali')
2 |
3 | const messages = require('./helloworld_pb')
4 | const services = require('./helloworld_grpc_pb')
5 |
6 | const HOSTPORT = '0.0.0.0:50051'
7 |
8 | /**
9 | * Implements the SayHello RPC method.
10 | */
11 | function sayHello (ctx) {
12 | const reply = new messages.HelloReply()
13 | reply.setMessage('Hello ' + ctx.req.getName())
14 | ctx.res = reply
15 | }
16 |
17 | /**
18 | * Starts an RPC server that receives requests for the Greeter service at the
19 | * sample server port
20 | */
21 | function main () {
22 | const app = new Mali(services, 'Greeter')
23 | app.use({ sayHello })
24 | app.start(HOSTPORT)
25 | console.log(`Greeter service running @ ${HOSTPORT}`)
26 | }
27 |
28 | main()
29 |
--------------------------------------------------------------------------------
/hello_world_static/helloworld_grpc_pb.js:
--------------------------------------------------------------------------------
1 | // GENERATED CODE -- DO NOT EDIT!
2 |
3 | // Original file comments:
4 | // Copyright 2015 gRPC authors.
5 | //
6 | // Licensed under the Apache License, Version 2.0 (the "License");
7 | // you may not use this file except in compliance with the License.
8 | // You may obtain a copy of the License at
9 | //
10 | // http://www.apache.org/licenses/LICENSE-2.0
11 | //
12 | // Unless required by applicable law or agreed to in writing, software
13 | // distributed under the License is distributed on an "AS IS" BASIS,
14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | // See the License for the specific language governing permissions and
16 | // limitations under the License.
17 | //
18 | 'use strict';
19 | var grpc = require('grpc');
20 | var helloworld_pb = require('./helloworld_pb.js');
21 |
22 | function serialize_HelloReply(arg) {
23 | if (!(arg instanceof helloworld_pb.HelloReply)) {
24 | throw new Error('Expected argument of type HelloReply');
25 | }
26 | return new Buffer(arg.serializeBinary());
27 | }
28 |
29 | function deserialize_HelloReply(buffer_arg) {
30 | return helloworld_pb.HelloReply.deserializeBinary(new Uint8Array(buffer_arg));
31 | }
32 |
33 | function serialize_HelloRequest(arg) {
34 | if (!(arg instanceof helloworld_pb.HelloRequest)) {
35 | throw new Error('Expected argument of type HelloRequest');
36 | }
37 | return new Buffer(arg.serializeBinary());
38 | }
39 |
40 | function deserialize_HelloRequest(buffer_arg) {
41 | return helloworld_pb.HelloRequest.deserializeBinary(new Uint8Array(buffer_arg));
42 | }
43 |
44 |
45 | // The greeting service definition.
46 | var GreeterService = exports.GreeterService = {
47 | // Sends a greeting
48 | sayHello: {
49 | path: '/helloworld.Greeter/SayHello',
50 | requestStream: false,
51 | responseStream: false,
52 | requestType: helloworld_pb.HelloRequest,
53 | responseType: helloworld_pb.HelloReply,
54 | requestSerialize: serialize_HelloRequest,
55 | requestDeserialize: deserialize_HelloRequest,
56 | responseSerialize: serialize_HelloReply,
57 | responseDeserialize: deserialize_HelloReply,
58 | },
59 | };
60 |
61 | exports.GreeterClient = grpc.makeGenericClientConstructor(GreeterService);
--------------------------------------------------------------------------------
/hello_world_static/helloworld_pb.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @fileoverview
3 | * @enhanceable
4 | * @public
5 | */
6 | // GENERATED CODE -- DO NOT EDIT!
7 |
8 | var jspb = require('google-protobuf');
9 | var goog = jspb;
10 | var global = Function('return this')();
11 |
12 | goog.exportSymbol('proto.helloworld.HelloReply', null, global);
13 | goog.exportSymbol('proto.helloworld.HelloRequest', null, global);
14 |
15 | /**
16 | * Generated by JsPbCodeGenerator.
17 | * @param {Array=} opt_data Optional initial data array, typically from a
18 | * server response, or constructed directly in Javascript. The array is used
19 | * in place and becomes part of the constructed object. It is not cloned.
20 | * If no data is provided, the constructed object will be empty, but still
21 | * valid.
22 | * @extends {jspb.Message}
23 | * @constructor
24 | */
25 | proto.helloworld.HelloRequest = function(opt_data) {
26 | jspb.Message.initialize(this, opt_data, 0, -1, null, null);
27 | };
28 | goog.inherits(proto.helloworld.HelloRequest, jspb.Message);
29 | if (goog.DEBUG && !COMPILED) {
30 | proto.helloworld.HelloRequest.displayName = 'proto.helloworld.HelloRequest';
31 | }
32 |
33 |
34 | if (jspb.Message.GENERATE_TO_OBJECT) {
35 | /**
36 | * Creates an object representation of this proto suitable for use in Soy templates.
37 | * Field names that are reserved in JavaScript and will be renamed to pb_name.
38 | * To access a reserved field use, foo.pb_, eg, foo.pb_default.
39 | * For the list of reserved names please see:
40 | * com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS.
41 | * @param {boolean=} opt_includeInstance Whether to include the JSPB instance
42 | * for transitional soy proto support: http://goto/soy-param-migration
43 | * @return {!Object}
44 | */
45 | proto.helloworld.HelloRequest.prototype.toObject = function(opt_includeInstance) {
46 | return proto.helloworld.HelloRequest.toObject(opt_includeInstance, this);
47 | };
48 |
49 |
50 | /**
51 | * Static version of the {@see toObject} method.
52 | * @param {boolean|undefined} includeInstance Whether to include the JSPB
53 | * instance for transitional soy proto support:
54 | * http://goto/soy-param-migration
55 | * @param {!proto.helloworld.HelloRequest} msg The msg instance to transform.
56 | * @return {!Object}
57 | */
58 | proto.helloworld.HelloRequest.toObject = function(includeInstance, msg) {
59 | var f, obj = {
60 | name: msg.getName()
61 | };
62 |
63 | if (includeInstance) {
64 | obj.$jspbMessageInstance = msg;
65 | }
66 | return obj;
67 | };
68 | }
69 |
70 |
71 | /**
72 | * Deserializes binary data (in protobuf wire format).
73 | * @param {jspb.ByteSource} bytes The bytes to deserialize.
74 | * @return {!proto.helloworld.HelloRequest}
75 | */
76 | proto.helloworld.HelloRequest.deserializeBinary = function(bytes) {
77 | var reader = new jspb.BinaryReader(bytes);
78 | var msg = new proto.helloworld.HelloRequest;
79 | return proto.helloworld.HelloRequest.deserializeBinaryFromReader(msg, reader);
80 | };
81 |
82 |
83 | /**
84 | * Deserializes binary data (in protobuf wire format) from the
85 | * given reader into the given message object.
86 | * @param {!proto.helloworld.HelloRequest} msg The message object to deserialize into.
87 | * @param {!jspb.BinaryReader} reader The BinaryReader to use.
88 | * @return {!proto.helloworld.HelloRequest}
89 | */
90 | proto.helloworld.HelloRequest.deserializeBinaryFromReader = function(msg, reader) {
91 | while (reader.nextField()) {
92 | if (reader.isEndGroup()) {
93 | break;
94 | }
95 | var field = reader.getFieldNumber();
96 | switch (field) {
97 | case 1:
98 | var value = /** @type {string} */ (reader.readString());
99 | msg.setName(value);
100 | break;
101 | default:
102 | reader.skipField();
103 | break;
104 | }
105 | }
106 | return msg;
107 | };
108 |
109 |
110 | /**
111 | * Class method variant: serializes the given message to binary data
112 | * (in protobuf wire format), writing to the given BinaryWriter.
113 | * @param {!proto.helloworld.HelloRequest} message
114 | * @param {!jspb.BinaryWriter} writer
115 | */
116 | proto.helloworld.HelloRequest.serializeBinaryToWriter = function(message, writer) {
117 | message.serializeBinaryToWriter(writer);
118 | };
119 |
120 |
121 | /**
122 | * Serializes the message to binary data (in protobuf wire format).
123 | * @return {!Uint8Array}
124 | */
125 | proto.helloworld.HelloRequest.prototype.serializeBinary = function() {
126 | var writer = new jspb.BinaryWriter();
127 | this.serializeBinaryToWriter(writer);
128 | return writer.getResultBuffer();
129 | };
130 |
131 |
132 | /**
133 | * Serializes the message to binary data (in protobuf wire format),
134 | * writing to the given BinaryWriter.
135 | * @param {!jspb.BinaryWriter} writer
136 | */
137 | proto.helloworld.HelloRequest.prototype.serializeBinaryToWriter = function (writer) {
138 | var f = undefined;
139 | f = this.getName();
140 | if (f.length > 0) {
141 | writer.writeString(
142 | 1,
143 | f
144 | );
145 | }
146 | };
147 |
148 |
149 | /**
150 | * Creates a deep clone of this proto. No data is shared with the original.
151 | * @return {!proto.helloworld.HelloRequest} The clone.
152 | */
153 | proto.helloworld.HelloRequest.prototype.cloneMessage = function() {
154 | return /** @type {!proto.helloworld.HelloRequest} */ (jspb.Message.cloneMessage(this));
155 | };
156 |
157 |
158 | /**
159 | * optional string name = 1;
160 | * @return {string}
161 | */
162 | proto.helloworld.HelloRequest.prototype.getName = function() {
163 | return /** @type {string} */ (jspb.Message.getFieldProto3(this, 1, ""));
164 | };
165 |
166 |
167 | /** @param {string} value */
168 | proto.helloworld.HelloRequest.prototype.setName = function(value) {
169 | jspb.Message.setField(this, 1, value);
170 | };
171 |
172 |
173 |
174 | /**
175 | * Generated by JsPbCodeGenerator.
176 | * @param {Array=} opt_data Optional initial data array, typically from a
177 | * server response, or constructed directly in Javascript. The array is used
178 | * in place and becomes part of the constructed object. It is not cloned.
179 | * If no data is provided, the constructed object will be empty, but still
180 | * valid.
181 | * @extends {jspb.Message}
182 | * @constructor
183 | */
184 | proto.helloworld.HelloReply = function(opt_data) {
185 | jspb.Message.initialize(this, opt_data, 0, -1, null, null);
186 | };
187 | goog.inherits(proto.helloworld.HelloReply, jspb.Message);
188 | if (goog.DEBUG && !COMPILED) {
189 | proto.helloworld.HelloReply.displayName = 'proto.helloworld.HelloReply';
190 | }
191 |
192 |
193 | if (jspb.Message.GENERATE_TO_OBJECT) {
194 | /**
195 | * Creates an object representation of this proto suitable for use in Soy templates.
196 | * Field names that are reserved in JavaScript and will be renamed to pb_name.
197 | * To access a reserved field use, foo.pb_, eg, foo.pb_default.
198 | * For the list of reserved names please see:
199 | * com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS.
200 | * @param {boolean=} opt_includeInstance Whether to include the JSPB instance
201 | * for transitional soy proto support: http://goto/soy-param-migration
202 | * @return {!Object}
203 | */
204 | proto.helloworld.HelloReply.prototype.toObject = function(opt_includeInstance) {
205 | return proto.helloworld.HelloReply.toObject(opt_includeInstance, this);
206 | };
207 |
208 |
209 | /**
210 | * Static version of the {@see toObject} method.
211 | * @param {boolean|undefined} includeInstance Whether to include the JSPB
212 | * instance for transitional soy proto support:
213 | * http://goto/soy-param-migration
214 | * @param {!proto.helloworld.HelloReply} msg The msg instance to transform.
215 | * @return {!Object}
216 | */
217 | proto.helloworld.HelloReply.toObject = function(includeInstance, msg) {
218 | var f, obj = {
219 | message: msg.getMessage()
220 | };
221 |
222 | if (includeInstance) {
223 | obj.$jspbMessageInstance = msg;
224 | }
225 | return obj;
226 | };
227 | }
228 |
229 |
230 | /**
231 | * Deserializes binary data (in protobuf wire format).
232 | * @param {jspb.ByteSource} bytes The bytes to deserialize.
233 | * @return {!proto.helloworld.HelloReply}
234 | */
235 | proto.helloworld.HelloReply.deserializeBinary = function(bytes) {
236 | var reader = new jspb.BinaryReader(bytes);
237 | var msg = new proto.helloworld.HelloReply;
238 | return proto.helloworld.HelloReply.deserializeBinaryFromReader(msg, reader);
239 | };
240 |
241 |
242 | /**
243 | * Deserializes binary data (in protobuf wire format) from the
244 | * given reader into the given message object.
245 | * @param {!proto.helloworld.HelloReply} msg The message object to deserialize into.
246 | * @param {!jspb.BinaryReader} reader The BinaryReader to use.
247 | * @return {!proto.helloworld.HelloReply}
248 | */
249 | proto.helloworld.HelloReply.deserializeBinaryFromReader = function(msg, reader) {
250 | while (reader.nextField()) {
251 | if (reader.isEndGroup()) {
252 | break;
253 | }
254 | var field = reader.getFieldNumber();
255 | switch (field) {
256 | case 1:
257 | var value = /** @type {string} */ (reader.readString());
258 | msg.setMessage(value);
259 | break;
260 | default:
261 | reader.skipField();
262 | break;
263 | }
264 | }
265 | return msg;
266 | };
267 |
268 |
269 | /**
270 | * Class method variant: serializes the given message to binary data
271 | * (in protobuf wire format), writing to the given BinaryWriter.
272 | * @param {!proto.helloworld.HelloReply} message
273 | * @param {!jspb.BinaryWriter} writer
274 | */
275 | proto.helloworld.HelloReply.serializeBinaryToWriter = function(message, writer) {
276 | message.serializeBinaryToWriter(writer);
277 | };
278 |
279 |
280 | /**
281 | * Serializes the message to binary data (in protobuf wire format).
282 | * @return {!Uint8Array}
283 | */
284 | proto.helloworld.HelloReply.prototype.serializeBinary = function() {
285 | var writer = new jspb.BinaryWriter();
286 | this.serializeBinaryToWriter(writer);
287 | return writer.getResultBuffer();
288 | };
289 |
290 |
291 | /**
292 | * Serializes the message to binary data (in protobuf wire format),
293 | * writing to the given BinaryWriter.
294 | * @param {!jspb.BinaryWriter} writer
295 | */
296 | proto.helloworld.HelloReply.prototype.serializeBinaryToWriter = function (writer) {
297 | var f = undefined;
298 | f = this.getMessage();
299 | if (f.length > 0) {
300 | writer.writeString(
301 | 1,
302 | f
303 | );
304 | }
305 | };
306 |
307 |
308 | /**
309 | * Creates a deep clone of this proto. No data is shared with the original.
310 | * @return {!proto.helloworld.HelloReply} The clone.
311 | */
312 | proto.helloworld.HelloReply.prototype.cloneMessage = function() {
313 | return /** @type {!proto.helloworld.HelloReply} */ (jspb.Message.cloneMessage(this));
314 | };
315 |
316 |
317 | /**
318 | * optional string message = 1;
319 | * @return {string}
320 | */
321 | proto.helloworld.HelloReply.prototype.getMessage = function() {
322 | return /** @type {string} */ (jspb.Message.getFieldProto3(this, 1, ""));
323 | };
324 |
325 |
326 | /** @param {string} value */
327 | proto.helloworld.HelloReply.prototype.setMessage = function(value) {
328 | jspb.Message.setField(this, 1, value);
329 | };
330 |
331 |
332 | goog.object.extend(exports, proto.helloworld);
--------------------------------------------------------------------------------
/hello_world_static/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mali-examples-hello-world-dynamic",
3 | "version": "0.1.0",
4 | "description": "Hello world example for Mali",
5 | "main": "greeter_server.js",
6 | "author": {
7 | "name": "Bojan D.",
8 | "email": "dbojan@gmail.com"
9 | },
10 | "license": "MIT",
11 | "dependencies": {
12 | "google-protobuf": "^3.1.1",
13 | "grpc": "^1.0.1",
14 | "mali": "file:../../mali"
15 | },
16 | "devDependencies": {
17 | "babel-eslint": "^7.1.1",
18 | "standard": "^8.5.0"
19 | },
20 | "standard": {
21 | "parser": "babel-eslint"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mali-examples",
3 | "version": "0.1.0",
4 | "description": "Mali examples",
5 | "author": {
6 | "name": "Bojan D.",
7 | "email": "dbojan@gmail.com"
8 | },
9 | "dependencies": {
10 | "@grpc/proto-loader": "^0.5.0",
11 | "JSONStream": "^1.3.5",
12 | "ascallback": "^1.1.1",
13 | "async": "^3.1.0",
14 | "bcrypt": "^3.0.6",
15 | "chance": "^1.1.0",
16 | "create-grpc-error": "^0.1.0",
17 | "delay": "^4.3.0",
18 | "destroy": "^1.0.4",
19 | "google-protobuf": "^3.9.1",
20 | "grpc": "^1.23.3",
21 | "highland": "3.0.0-beta.7",
22 | "ioredis": "^4.14.0",
23 | "json-schema-faker": "^0.5.0-rc9",
24 | "lodash": "^4.17.15",
25 | "mali": "^0.19.0",
26 | "mali-apikey": "^0.1.0",
27 | "mali-logger": "^0.2.2",
28 | "mali-tojson": "^0.2.0",
29 | "minimist": "^1.2.3",
30 | "mississippi": "^4.0.0",
31 | "p-finally": "^2.0.0",
32 | "p-times": "^2.1.0",
33 | "pify": "^4.0.0",
34 | "uuid": "^3.3.3"
35 | },
36 | "devDependencies": {
37 | "ava": "^2.3.0",
38 | "grpc-caller": "^0.13.0",
39 | "sprom": "^3.0.0",
40 | "standard": "^14.1.0"
41 | },
42 | "scripts": {
43 | "test-user": "ava user_service/test/*.test.js -v"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/protos/any.proto:
--------------------------------------------------------------------------------
1 | // from https://github.com/google/protobuf/blob/master/src/google/protobuf/any.proto
2 | //
3 | // Protocol Buffers - Google's data interchange format
4 | // Copyright 2008 Google Inc. All rights reserved.
5 | // https://developers.google.com/protocol-buffers/
6 | //
7 | // Redistribution and use in source and binary forms, with or without
8 | // modification, are permitted provided that the following conditions are
9 | // met:
10 | //
11 | // * Redistributions of source code must retain the above copyright
12 | // notice, this list of conditions and the following disclaimer.
13 | // * Redistributions in binary form must reproduce the above
14 | // copyright notice, this list of conditions and the following disclaimer
15 | // in the documentation and/or other materials provided with the
16 | // distribution.
17 | // * Neither the name of Google Inc. nor the names of its
18 | // contributors may be used to endorse or promote products derived from
19 | // this software without specific prior written permission.
20 | //
21 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 |
33 | syntax = "proto3";
34 |
35 | package google.protobuf;
36 |
37 | option csharp_namespace = "Google.Protobuf.WellKnownTypes";
38 | option go_package = "github.com/golang/protobuf/ptypes/any";
39 | option java_package = "com.google.protobuf";
40 | option java_outer_classname = "AnyProto";
41 | option java_multiple_files = true;
42 | option objc_class_prefix = "GPB";
43 |
44 | // `Any` contains an arbitrary serialized protocol buffer message along with a
45 | // URL that describes the type of the serialized message.
46 | //
47 | // Protobuf library provides support to pack/unpack Any values in the form
48 | // of utility functions or additional generated methods of the Any type.
49 | //
50 | // Example 1: Pack and unpack a message in C++.
51 | //
52 | // Foo foo = ...;
53 | // Any any;
54 | // any.PackFrom(foo);
55 | // ...
56 | // if (any.UnpackTo(&foo)) {
57 | // ...
58 | // }
59 | //
60 | // Example 2: Pack and unpack a message in Java.
61 | //
62 | // Foo foo = ...;
63 | // Any any = Any.pack(foo);
64 | // ...
65 | // if (any.is(Foo.class)) {
66 | // foo = any.unpack(Foo.class);
67 | // }
68 | //
69 | // Example 3: Pack and unpack a message in Python.
70 | //
71 | // foo = Foo(...)
72 | // any = Any()
73 | // any.Pack(foo)
74 | // ...
75 | // if any.Is(Foo.DESCRIPTOR):
76 | // any.Unpack(foo)
77 | // ...
78 | //
79 | // Example 4: Pack and unpack a message in Go
80 | //
81 | // foo := &pb.Foo{...}
82 | // any, err := ptypes.MarshalAny(foo)
83 | // ...
84 | // foo := &pb.Foo{}
85 | // if err := ptypes.UnmarshalAny(any, foo); err != nil {
86 | // ...
87 | // }
88 | //
89 | // The pack methods provided by protobuf library will by default use
90 | // 'type.googleapis.com/full.type.name' as the type URL and the unpack
91 | // methods only use the fully qualified type name after the last '/'
92 | // in the type URL, for example "foo.bar.com/x/y.z" will yield type
93 | // name "y.z".
94 | //
95 | //
96 | // JSON
97 | // ====
98 | // The JSON representation of an `Any` value uses the regular
99 | // representation of the deserialized, embedded message, with an
100 | // additional field `@type` which contains the type URL. Example:
101 | //
102 | // package google.profile;
103 | // message Person {
104 | // string first_name = 1;
105 | // string last_name = 2;
106 | // }
107 | //
108 | // {
109 | // "@type": "type.googleapis.com/google.profile.Person",
110 | // "firstName": ,
111 | // "lastName":
112 | // }
113 | //
114 | // If the embedded message type is well-known and has a custom JSON
115 | // representation, that representation will be embedded adding a field
116 | // `value` which holds the custom JSON in addition to the `@type`
117 | // field. Example (for message [google.protobuf.Duration][]):
118 | //
119 | // {
120 | // "@type": "type.googleapis.com/google.protobuf.Duration",
121 | // "value": "1.212s"
122 | // }
123 | //
124 | message Any {
125 | // A URL/resource name that uniquely identifies the type of the serialized
126 | // protocol buffer message. The last segment of the URL's path must represent
127 | // the fully qualified name of the type (as in
128 | // `path/google.protobuf.Duration`). The name should be in a canonical form
129 | // (e.g., leading "." is not accepted).
130 | //
131 | // In practice, teams usually precompile into the binary all types that they
132 | // expect it to use in the context of Any. However, for URLs which use the
133 | // scheme `http`, `https`, or no scheme, one can optionally set up a type
134 | // server that maps type URLs to message definitions as follows:
135 | //
136 | // * If no scheme is provided, `https` is assumed.
137 | // * An HTTP GET on the URL must yield a [google.protobuf.Type][]
138 | // value in binary format, or produce an error.
139 | // * Applications are allowed to cache lookup results based on the
140 | // URL, or have them precompiled into a binary to avoid any
141 | // lookup. Therefore, binary compatibility needs to be preserved
142 | // on changes to types. (Use versioned type names to manage
143 | // breaking changes.)
144 | //
145 | // Note: this functionality is not currently available in the official
146 | // protobuf release, and it is not used for type URLs beginning with
147 | // type.googleapis.com.
148 | //
149 | // Schemes other than `http`, `https` (or the empty scheme) might be
150 | // used with implementation specific semantics.
151 | //
152 | string type_url = 1;
153 |
154 | // Must be a valid serialized protocol buffer of the above specified type.
155 | bytes value = 2;
156 | }
157 |
--------------------------------------------------------------------------------
/protos/errorexample.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package ErrorExample;
4 |
5 | import "status.proto";
6 |
7 | service SampleService {
8 | rpc GetWidget (WidgetRequest) returns (Widget) {}
9 | rpc GetWidget2 (WidgetRequest) returns (Widget) {}
10 | rpc ListWidgets (WidgetRequest) returns (stream WidgetStreamObject) {}
11 | rpc CreateWidgets (stream Widget) returns (WidgetResult) {}
12 | rpc SyncWidgets (stream WidgetStreamObject) returns (stream WidgetStreamObject) {}
13 | }
14 |
15 | message Widget {
16 | string name = 1;
17 | }
18 |
19 | message WidgetStreamObject {
20 | Widget widget = 1;
21 | google.rpc.Status error = 2;
22 | }
23 |
24 | message WidgetRequest {
25 | int32 id = 1;
26 | }
27 |
28 | message WidgetResult {
29 | int32 created = 1;
30 | }
31 |
--------------------------------------------------------------------------------
/protos/helloworld.proto:
--------------------------------------------------------------------------------
1 | // Copyright 2015, Google Inc.
2 | // All rights reserved.
3 | //
4 | // Redistribution and use in source and binary forms, with or without
5 | // modification, are permitted provided that the following conditions are
6 | // met:
7 | //
8 | // * Redistributions of source code must retain the above copyright
9 | // notice, this list of conditions and the following disclaimer.
10 | // * Redistributions in binary form must reproduce the above
11 | // copyright notice, this list of conditions and the following disclaimer
12 | // in the documentation and/or other materials provided with the
13 | // distribution.
14 | // * Neither the name of Google Inc. nor the names of its
15 | // contributors may be used to endorse or promote products derived from
16 | // this software without specific prior written permission.
17 | //
18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 |
30 | syntax = "proto3";
31 |
32 | option java_multiple_files = true;
33 | option java_package = "io.grpc.examples.helloworld";
34 | option java_outer_classname = "HelloWorldProto";
35 | option objc_class_prefix = "HLW";
36 |
37 | package helloworld;
38 |
39 | // The greeting service definition.
40 | service Greeter {
41 | // Sends a greeting
42 | rpc SayHello (HelloRequest) returns (HelloReply) {}
43 | }
44 |
45 | // The request message containing the user's name.
46 | message HelloRequest {
47 | string name = 1;
48 | }
49 |
50 | // The response message containing the greetings
51 | message HelloReply {
52 | string message = 1;
53 | }
54 |
--------------------------------------------------------------------------------
/protos/hw2.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package helloworld;
4 |
5 | // The greeting service definition.
6 | service Greeter {
7 | // Sends a greeting
8 | rpc SayHello (HelloRequest) returns (HelloReply) {}
9 | rpc SayHelloCS (stream HelloRequest) returns (HelloReply) {}
10 | rpc SayHellos (HelloRequest) returns (stream HelloReply) {}
11 | rpc SayHelloBidi (stream HelloRequest) returns (stream HelloReply) {}
12 | }
13 |
14 | // The request message containing the user's name.
15 | message HelloRequest {
16 | string name = 1;
17 | }
18 |
19 | // The response message containing the greetings
20 | message HelloReply {
21 | string message = 1;
22 | }
23 |
--------------------------------------------------------------------------------
/protos/push.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package PushExample;
4 |
5 | service PushService {
6 | rpc SyncWidgets (SyncRequest) returns (stream Widget) {}
7 | }
8 |
9 | message SyncRequest {
10 | string id = 1;
11 | int32 timestamp = 2;
12 | }
13 |
14 | message Widget {
15 | string id = 1;
16 | string data = 2;
17 | }
18 |
--------------------------------------------------------------------------------
/protos/redischat.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package redischat;
4 |
5 | service RedisChat {
6 | rpc Scan (ChatRequest) returns (stream ChatResponse) {}
7 | }
8 |
9 | message ChatRequest {
10 | string key = 1;
11 | }
12 |
13 | message ChatResponse {
14 | string key = 1;
15 | }
16 |
--------------------------------------------------------------------------------
/protos/route_guide.proto:
--------------------------------------------------------------------------------
1 | // Copyright 2015, Google Inc.
2 | // All rights reserved.
3 | //
4 | // Redistribution and use in source and binary forms, with or without
5 | // modification, are permitted provided that the following conditions are
6 | // met:
7 | //
8 | // * Redistributions of source code must retain the above copyright
9 | // notice, this list of conditions and the following disclaimer.
10 | // * Redistributions in binary form must reproduce the above
11 | // copyright notice, this list of conditions and the following disclaimer
12 | // in the documentation and/or other materials provided with the
13 | // distribution.
14 | // * Neither the name of Google Inc. nor the names of its
15 | // contributors may be used to endorse or promote products derived from
16 | // this software without specific prior written permission.
17 | //
18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 |
30 | syntax = "proto3";
31 |
32 | option java_multiple_files = true;
33 | option java_package = "io.grpc.examples.routeguide";
34 | option java_outer_classname = "RouteGuideProto";
35 | option objc_class_prefix = "RTG";
36 |
37 | package routeguide;
38 |
39 | // Interface exported by the server.
40 | service RouteGuide {
41 | // A simple RPC.
42 | //
43 | // Obtains the feature at a given position.
44 | //
45 | // A feature with an empty name is returned if there's no feature at the given
46 | // position.
47 | rpc GetFeature(Point) returns (Feature) {}
48 |
49 | // A server-to-client streaming RPC.
50 | //
51 | // Obtains the Features available within the given Rectangle. Results are
52 | // streamed rather than returned at once (e.g. in a response message with a
53 | // repeated field), as the rectangle may cover a large area and contain a
54 | // huge number of features.
55 | rpc ListFeatures(Rectangle) returns (stream Feature) {}
56 |
57 | // A client-to-server streaming RPC.
58 | //
59 | // Accepts a stream of Points on a route being traversed, returning a
60 | // RouteSummary when traversal is completed.
61 | rpc RecordRoute(stream Point) returns (RouteSummary) {}
62 |
63 | // A Bidirectional streaming RPC.
64 | //
65 | // Accepts a stream of RouteNotes sent while a route is being traversed,
66 | // while receiving other RouteNotes (e.g. from other users).
67 | rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
68 | }
69 |
70 | // Points are represented as latitude-longitude pairs in the E7 representation
71 | // (degrees multiplied by 10**7 and rounded to the nearest integer).
72 | // Latitudes should be in the range +/- 90 degrees and longitude should be in
73 | // the range +/- 180 degrees (inclusive).
74 | message Point {
75 | int32 latitude = 1;
76 | int32 longitude = 2;
77 | }
78 |
79 | // A latitude-longitude rectangle, represented as two diagonally opposite
80 | // points "lo" and "hi".
81 | message Rectangle {
82 | // One corner of the rectangle.
83 | Point lo = 1;
84 |
85 | // The other corner of the rectangle.
86 | Point hi = 2;
87 | }
88 |
89 | // A feature names something at a given point.
90 | //
91 | // If a feature could not be named, the name is empty.
92 | message Feature {
93 | // The name of the feature.
94 | string name = 1;
95 |
96 | // The point where the feature is detected.
97 | Point location = 2;
98 | }
99 |
100 | // A RouteNote is a message sent while at a given point.
101 | message RouteNote {
102 | // The location from which the message is sent.
103 | Point location = 1;
104 |
105 | // The message to be sent.
106 | string message = 2;
107 | }
108 |
109 | // A RouteSummary is received in response to a RecordRoute rpc.
110 | //
111 | // It contains the number of individual points received, the number of
112 | // detected features, and the total distance covered as the cumulative sum of
113 | // the distance between each point.
114 | message RouteSummary {
115 | // The number of points received.
116 | int32 point_count = 1;
117 |
118 | // The number of known features passed while traversing the route.
119 | int32 feature_count = 2;
120 |
121 | // The distance covered in metres.
122 | int32 distance = 3;
123 |
124 | // The duration of the traversal in seconds.
125 | int32 elapsed_time = 4;
126 | }
127 |
--------------------------------------------------------------------------------
/protos/secret.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package Secret;
4 |
5 | service SecretService {
6 | rpc ProcessSecrets (stream SecretRequest) returns (ProcessResult) {}
7 | }
8 |
9 | message SecretRequest {
10 | string id = 1;
11 | string secret = 2;
12 | }
13 |
14 | message ProcessResult {
15 | int32 success = 1;
16 | repeated FailedResut failed = 2;
17 | }
18 |
19 | message FailedResut {
20 | string id = 1;
21 | string message = 2;
22 | }
23 |
--------------------------------------------------------------------------------
/protos/status.proto:
--------------------------------------------------------------------------------
1 | // from https://github.com/googleapis/googleapis/blob/master/google/rpc/status.proto
2 | // Copyright 2017 Google Inc.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | syntax = "proto3";
17 |
18 | package google.rpc;
19 |
20 | import "any.proto";
21 |
22 | option go_package = "google.golang.org/genproto/googleapis/rpc/status;status";
23 | option java_multiple_files = true;
24 | option java_outer_classname = "StatusProto";
25 | option java_package = "com.google.rpc";
26 | option objc_class_prefix = "RPC";
27 |
28 |
29 | // The `Status` type defines a logical error model that is suitable for different
30 | // programming environments, including REST APIs and RPC APIs. It is used by
31 | // [gRPC](https://github.com/grpc). The error model is designed to be:
32 | //
33 | // - Simple to use and understand for most users
34 | // - Flexible enough to meet unexpected needs
35 | //
36 | // # Overview
37 | //
38 | // The `Status` message contains three pieces of data: error code, error message,
39 | // and error details. The error code should be an enum value of
40 | // [google.rpc.Code][google.rpc.Code], but it may accept additional error codes if needed. The
41 | // error message should be a developer-facing English message that helps
42 | // developers *understand* and *resolve* the error. If a localized user-facing
43 | // error message is needed, put the localized message in the error details or
44 | // localize it in the client. The optional error details may contain arbitrary
45 | // from https://github.com/googleapis/googleapis/blob/master/google/rpc/status.proto
46 | //
47 | // information about the error. There is a predefined set of error detail types
48 | // in the package `google.rpc` that can be used for common error conditions.
49 | //
50 | // # Language mapping
51 | //
52 | // The `Status` message is the logical representation of the error model, but it
53 | // is not necessarily the actual wire format. When the `Status` message is
54 | // exposed in different client libraries and different wire protocols, it can be
55 | // mapped differently. For example, it will likely be mapped to some exceptions
56 | // in Java, but more likely mapped to some error codes in C.
57 | //
58 | // # Other uses
59 | //
60 | // The error model and the `Status` message can be used in a variety of
61 | // environments, either with or without APIs, to provide a
62 | // consistent developer experience across different environments.
63 | //
64 | // Example uses of this error model include:
65 | //
66 | // - Partial errors. If a service needs to return partial errors to the client,
67 | // it may embed the `Status` in the normal response to indicate the partial
68 | // errors.
69 | //
70 | // - Workflow errors. A typical workflow has multiple steps. Each step may
71 | // have a `Status` message for error reporting.
72 | //
73 | // - Batch operations. If a client uses batch request and batch response, the
74 | // `Status` message should be used directly inside batch response, one for
75 | // each error sub-response.
76 | //
77 | // - Asynchronous operations. If an API call embeds asynchronous operation
78 | // results in its response, the status of those operations should be
79 | // represented directly using the `Status` message.
80 | //
81 | // - Logging. If some API errors are stored in logs, the message `Status` could
82 | // be used directly after any stripping needed for security/privacy reasons.
83 | message Status {
84 | // The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code].
85 | int32 code = 1;
86 |
87 | // A developer-facing error message, which should be in English. Any
88 | // user-facing error message should be localized and sent in the
89 | // [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client.
90 | string message = 2;
91 |
92 | // A list of messages that carry the error details. There is a common set of
93 | // message types for APIs to use.
94 | repeated google.protobuf.Any details = 3;
95 | }
96 |
--------------------------------------------------------------------------------
/protos/user.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package User;
4 |
5 | service UserService {
6 | rpc GetUser (Id) returns (User) {}
7 | rpc ListUsers (Empty) returns (stream User) {}
8 | rpc CreateUser (NewUser) returns (User) {}
9 | }
10 |
11 | message User {
12 | string id = 1;
13 | string email = 2;
14 | string dateOfBirth = 3;
15 | bytes metadata = 4;
16 | }
17 |
18 | message NewUser {
19 | string email = 1;
20 | string dateOfBirth = 2;
21 | string password = 3;
22 | bytes metadata = 4;
23 | }
24 |
25 | message Id {
26 | string id = 1;
27 | }
28 |
29 | message Empty {
30 | }
31 |
--------------------------------------------------------------------------------
/push/client.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const caller = require('grpc-caller')
3 | const Chance = require('chance')
4 | const chance = new Chance()
5 |
6 | const PROTO_PATH = path.resolve(__dirname, '../protos/push.proto')
7 | const HOSTPORT = '0.0.0.0:50051'
8 |
9 | async function main () {
10 | const client = caller(HOSTPORT, PROTO_PATH, 'PushService')
11 | const id = chance.guid()
12 | const data = { id, timestamp: new Date().getTime() }
13 | const call = await client.syncWidgets(data)
14 | call.on('data', data => {
15 | console.log(`client ${id} got widget %j`, data)
16 | })
17 | }
18 |
19 | main()
20 |
--------------------------------------------------------------------------------
/push/lotsofclients.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const caller = require('grpc-caller')
3 | const Chance = require('chance')
4 | const chance = new Chance()
5 |
6 | const PROTO_PATH = path.resolve(__dirname, '../protos/push.proto')
7 | const HOSTPORT = '0.0.0.0:50051'
8 | const NUM_CLIENTS = 100
9 |
10 | async function main () {
11 | for (let i = 0; i < NUM_CLIENTS; i++) {
12 | let delay = chance.integer({ min: 100, max: 10000 })
13 | setTimeout(async() => {
14 | const client = caller(HOSTPORT, PROTO_PATH, 'PushService')
15 | const id = chance.guid()
16 | const data = { id, timestamp: new Date().getTime() }
17 | const call = await client.syncWidgets(data)
18 | call.on('data', data => {
19 | console.log(`client ${id} got widget %j`, data)
20 | })
21 | }, delay)
22 | }
23 | }
24 |
25 | main()
26 |
--------------------------------------------------------------------------------
/push/push_service.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const _ = require('lodash')
3 | const hl = require('highland')
4 | const Mali = require('mali')
5 | const logger = require('mali-logger')
6 | const first = require('ee-first')
7 |
8 | const { getUpdatedWidgets } = require('./widget_source')
9 | const { WidgetUpdater } = require('./update_source')
10 |
11 | const PROTO_PATH = path.resolve(__dirname, '../protos/push.proto')
12 | const HOSTPORT = '0.0.0.0:50051'
13 |
14 | let app = null
15 | const updater = new WidgetUpdater()
16 |
17 | // all of our connected clients hashed by their id
18 | const clients = {}
19 |
20 | /*
21 | * Handler of client disocnnect.
22 | * Removes the call object from our client registry
23 | */
24 | function disconnectHandler (req, res) {
25 | const ee = first([
26 | [res, 'close', 'end', 'error']
27 | ], err => {
28 | console.log(`client ${req.id} disconnected`)
29 | if (err) {
30 | console.error(`client ${req.id} errored ${err}`)
31 | }
32 | delete clients[req.id]
33 | ee.cancel()
34 | })
35 | }
36 |
37 | function writeWidget (id, widget) {
38 | if (clients[id]) {
39 | clients[id].write(widget)
40 | }
41 | }
42 |
43 | async function syncWidgets (ctx) {
44 | const req = ctx.req
45 | const id = req.id
46 | console.log(`client ${id} connected`)
47 | const widgets = await getUpdatedWidgets(ctx.since)
48 | clients[id] = ctx.call
49 | ctx.res = hl(widgets).each(w => writeWidget(id, w))
50 | disconnectHandler(req, ctx.call)
51 | }
52 |
53 | /**
54 | * Handler of widget update message
55 | */
56 | function updateHandler (widget) {
57 | const ids = _.keys(clients)
58 | _.each(ids, id => writeWidget(id, widget))
59 | }
60 |
61 | /**
62 | * Just periodically logs a status message consisting of number of connected clients.
63 | */
64 | function status () {
65 | setInterval(() => {
66 | const n = _.keys(clients).length
67 | console.log(`connected clients: ${n}`)
68 | }, 30000)
69 | }
70 |
71 | function main () {
72 | app = new Mali(PROTO_PATH, 'PushService')
73 | app.use(logger())
74 | app.use({ syncWidgets })
75 | app.start(HOSTPORT)
76 |
77 | updater.on('widget_updated', updateHandler)
78 | updater.start()
79 |
80 | status()
81 |
82 | console.log(`Push Service service running @ ${HOSTPORT}`)
83 | }
84 |
85 | function shutdown (err) {
86 | if (err) {
87 | console.error(err)
88 | }
89 |
90 | const ids = _.keys(clients)
91 | ids.forEach(id => clients[id].end())
92 |
93 | updater.stop()
94 | app.close().then(() => process.exit())
95 | }
96 |
97 | process.on('uncaughtException', shutdown)
98 | process.on('SIGINT', shutdown)
99 | process.on('SIGTERM', shutdown)
100 |
101 | main()
102 |
--------------------------------------------------------------------------------
/push/server.js:
--------------------------------------------------------------------------------
1 | require('./push_service.js')
2 |
--------------------------------------------------------------------------------
/push/update_source.js:
--------------------------------------------------------------------------------
1 | /*
2 | * This mock some kind of widget update mechanism which notifies the main widget
3 | * service of updated widgets. For example something like AMQP or some other
4 | * messanging system.
5 | */
6 |
7 | const EventEmitter = require('events')
8 | const _ = require('lodash')
9 | const util = require('util')
10 | const setTimeoutPromise = util.promisify(setTimeout)
11 |
12 | const { getWidget } = require('./widget_source')
13 |
14 | class WidgetUpdater extends EventEmitter {
15 | async emitWidget (w) {
16 | if (!w) {
17 | w = await getWidget()
18 | }
19 | this.emit('widget_updated', w)
20 | }
21 |
22 | async loop () {
23 | while (this.do) {
24 | await setTimeoutPromise(_.random(1000, 10000))
25 | this.emitWidget()
26 | }
27 | }
28 |
29 | start () {
30 | this.do = true
31 | this.loop()
32 | }
33 |
34 | stop () {
35 | this.do = false
36 | }
37 | }
38 |
39 | module.exports = {
40 | WidgetUpdater
41 | }
42 |
--------------------------------------------------------------------------------
/push/widget.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "object",
3 | "properties": {
4 | "id": {
5 | "type": "string",
6 | "chance": "guid"
7 | },
8 | "data": {
9 | "type": "string",
10 | "maxLength": 30,
11 | "minLength":""
12 | }
13 | },
14 | "required": ["id", "data"]
15 | }
16 |
--------------------------------------------------------------------------------
/push/widget_source.js:
--------------------------------------------------------------------------------
1 | /*
2 | * This mock some kind of "widget store", which can be either anther service or a
3 | * database. We can either get all updated widgets since some timestamp, or
4 | * get a random widget
5 | */
6 |
7 | const jsf = require('json-schema-faker')
8 | const _ = require('lodash')
9 | const pTimes = require('p-times')
10 | const Chance = require('chance')
11 | const chance = new Chance()
12 |
13 | jsf.extend('chance', () => chance)
14 | const schema = require('./widget.json')
15 |
16 | async function getWidget () {
17 | return jsf.resolve(schema)
18 | }
19 |
20 | async function getUpdatedWidgets (since) {
21 | return pTimes(_.random(2, 10), getWidget)
22 | }
23 |
24 | module.exports = {
25 | getUpdatedWidgets,
26 | getWidget
27 | }
28 |
--------------------------------------------------------------------------------
/redis-chat/client.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | const protoLoader = require('@grpc/proto-loader')
3 | const grpc = require('grpc')
4 |
5 | const PROTO_PATH = path.resolve(__dirname, '../protos/redischat.proto')
6 |
7 | const pd = protoLoader.loadSync(PROTO_PATH)
8 | const loaded = grpc.loadPackageDefinition(pd)
9 | const redischat = loaded.redischat
10 |
11 | function main () {
12 | const client = new redischat.RedisChat('localhost:50051', grpc.credentials.createInsecure())
13 | const call = client.scan({ key: 'foo:*' })
14 | let count = 0
15 |
16 | call.on('data', function (d) {
17 | count++
18 | console.log(d)
19 | })
20 |
21 | call.on('end', () => {
22 | console.log(`count: ${count}`)
23 | })
24 | }
25 |
26 | main()
27 |
--------------------------------------------------------------------------------
/redis-chat/server_grpc.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const grpc = require('grpc')
3 | const protoLoader = require('@grpc/proto-loader')
4 | const Redis = require('ioredis')
5 | const { Transform } = require('stream')
6 |
7 | const redis = new Redis()
8 |
9 | const PROTO_PATH = path.resolve(__dirname, '../protos/redischat.proto')
10 | const HOSTPORT = '0.0.0.0:50051'
11 |
12 | const packageDefinition = protoLoader.loadSync(PROTO_PATH)
13 | const redischat = grpc.loadPackageDefinition(packageDefinition).redischat
14 |
15 | function scan (call) {
16 | var key = call.request.key
17 |
18 | const upper = new Transform({
19 | writableObjectMode: true,
20 | readableObjectMode: true,
21 | transform (keys, encoding, callback) {
22 | for (const key of keys) {
23 | console.log(`sending: ${key}`)
24 | call.write({ key })
25 | }
26 | callback()
27 | }
28 | })
29 |
30 | redis
31 | .scanStream({ match: key })
32 | .pipe(upper)
33 | .on('finish', () => {
34 | call.end()
35 | })
36 | }
37 |
38 | function setupRedisData () {
39 | for (let i = 0; i < 20; i++) {
40 | redis.set(`foo:${i}`, `bar${i}`)
41 | }
42 | }
43 |
44 | function getServer () {
45 | setupRedisData()
46 |
47 | const server = new grpc.Server()
48 | server.addService(redischat.RedisChat.service, {
49 | scan
50 | })
51 | return server
52 | }
53 | var routeServer = getServer()
54 | routeServer.bind(HOSTPORT, grpc.ServerCredentials.createInsecure())
55 | routeServer.start()
56 |
--------------------------------------------------------------------------------
/redis-chat/server_mali.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const Mali = require('mali')
3 | const Redis = require('ioredis')
4 | const { Transform } = require('stream')
5 |
6 | const redis = new Redis()
7 |
8 | const PROTO_PATH = path.resolve(__dirname, '../protos/redischat.proto')
9 | const HOSTPORT = '0.0.0.0:50051'
10 |
11 | const upper = new Transform({
12 | writableObjectMode: true,
13 | readableObjectMode: true,
14 | transform (keys, encoding, callback) {
15 | for (const key of keys) {
16 | console.log(`sending: ${key}`)
17 | this.push({ key })
18 | }
19 | callback()
20 | }
21 | })
22 |
23 | function scan (ctx) {
24 | ctx.res = redis.scanStream({ match: ctx.req.key }).pipe(upper)
25 | }
26 |
27 | function setupRedisData () {
28 | for (let i = 0; i < 20; i++) {
29 | redis.set(`foo:${i}`, `bar${i}`)
30 | }
31 | }
32 |
33 | function main () {
34 | setupRedisData()
35 |
36 | const app = new Mali(PROTO_PATH, 'RedisChat')
37 | app.use({ scan })
38 | app.start(HOSTPORT)
39 | console.log(`RedisChat service running @ ${HOSTPORT}`)
40 | }
41 |
42 | main()
43 |
--------------------------------------------------------------------------------
/route_guide/feature_db.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Represents an feature / route db interface
3 | * We're just doing fs calls to files here to represent
4 | * async actions using promises and streams
5 | */
6 |
7 | const hl = require('highland')
8 | const JSONStream = require('JSONStream')
9 | const _ = require('lodash')
10 | const pify = require('pify')
11 | const fs = require('fs')
12 | const pfs = pify(fs)
13 |
14 | /**
15 | * Get a feature object at the given point, or creates one if it does not exist.
16 | * @param {point} point The point to check
17 | * @return {feature} The feature object at the point. Note that an empty name
18 | * indicates no feature
19 | */
20 | async function checkFeature (point) {
21 | return new Promise((resolve, reject) => {
22 | const input = fs.createReadStream('route_guide_db.json')
23 |
24 | hl(input)
25 | .through(JSONStream.parse('*'))
26 | .find(feature =>
27 | feature.location.latitude === point.latitude &&
28 | feature.location.longitude === point.longitude)
29 | .toCallback((err, result) => {
30 | if (err) {
31 | return reject(err)
32 | }
33 | resolve(result)
34 | })
35 | })
36 | }
37 |
38 | async function getFeaturesListStream () {
39 | return fs
40 | .createReadStream('route_guide_db.json')
41 | .pipe(JSONStream.parse('*'))
42 | }
43 |
44 | async function getNote (key) {
45 | const input = fs.createReadStream('route_guide_db_notes.json')
46 |
47 | return hl(input)
48 | .through(JSONStream.parse('*'))
49 | .find(v => v && v.key === key)
50 | .toPromise(Promise)
51 | }
52 |
53 | async function putNote (key, value) {
54 | const file = await pfs.readFile('route_guide_db_notes.json', 'utf8')
55 | let all = file ? JSON.parse(file) : []
56 | if (!Array.isArray(all)) {
57 | console.warn('notes file is not an array')
58 | all = []
59 | }
60 | const existing = _.find(all, v => v && v.key === key)
61 | if (existing) {
62 | existing.value.push(value)
63 | } else {
64 | const data = { key, value: [value] }
65 | all.push(data)
66 | }
67 | const str = JSON.stringify(all)
68 | await pfs.writeFile('route_guide_db_notes.json', str, 'utf8')
69 |
70 | return value
71 | }
72 |
73 | exports.checkFeature = checkFeature
74 | exports.getFeaturesListStream = getFeaturesListStream
75 | exports.getNote = getNote
76 | exports.putNote = putNote
77 |
--------------------------------------------------------------------------------
/route_guide/route_guide_client.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * Copyright 2015, Google Inc.
4 | * All rights reserved.
5 | *
6 | * Redistribution and use in source and binary forms, with or without
7 | * modification, are permitted provided that the following conditions are
8 | * met:
9 | *
10 | * * Redistributions of source code must retain the above copyright
11 | * notice, this list of conditions and the following disclaimer.
12 | * * Redistributions in binary form must reproduce the above
13 | * copyright notice, this list of conditions and the following disclaimer
14 | * in the documentation and/or other materials provided with the
15 | * distribution.
16 | * * Neither the name of Google Inc. nor the names of its
17 | * contributors may be used to endorse or promote products derived from
18 | * this software without specific prior written permission.
19 | *
20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 | *
32 | */
33 |
34 | var async = require('async')
35 | var fs = require('fs')
36 | var parseArgs = require('minimist')
37 | var path = require('path')
38 | var _ = require('lodash')
39 | const protoLoader = require('@grpc/proto-loader')
40 | const grpc = require('grpc')
41 |
42 | var PROTO_PATH = path.resolve(__dirname, '../protos/route_guide.proto')
43 | const pd = protoLoader.loadSync(PROTO_PATH)
44 | const loaded = grpc.loadPackageDefinition(pd)
45 | var routeguide = loaded.routeguide
46 | var client = new routeguide.RouteGuide('localhost:50051',
47 | grpc.credentials.createInsecure())
48 |
49 | var COORD_FACTOR = 1e7
50 |
51 | /**
52 | * Run the getFeature demo. Calls getFeature with a point known to have a
53 | * feature and a point known not to have a feature.
54 | * @param {function} callback Called when this demo is complete
55 | */
56 | function runGetFeature (callback) {
57 | var next = _.after(2, callback)
58 |
59 | function featureCallback (error, feature) {
60 | if (error) {
61 | callback(error)
62 | return
63 | }
64 | if (feature.name === '') {
65 | console.log('Found no feature at ' +
66 | feature.location.latitude / COORD_FACTOR + ', ' +
67 | feature.location.longitude / COORD_FACTOR)
68 | } else {
69 | console.log('Found feature called "' + feature.name + '" at ' +
70 | feature.location.latitude / COORD_FACTOR + ', ' +
71 | feature.location.longitude / COORD_FACTOR)
72 | }
73 | next()
74 | }
75 | var point1 = {
76 | latitude: 409146138,
77 | longitude: -746188906
78 | }
79 | var point2 = {
80 | latitude: 0,
81 | longitude: 0
82 | }
83 | client.getFeature(point1, featureCallback)
84 | client.getFeature(point2, featureCallback)
85 | }
86 |
87 | /**
88 | * Run the listFeatures demo. Calls listFeatures with a rectangle containing all
89 | * of the features in the pre-generated database. Prints each response as it
90 | * comes in.
91 | * @param {function} callback Called when this demo is complete
92 | */
93 | function runListFeatures (callback) {
94 | var rectangle = {
95 | lo: {
96 | latitude: 400000000,
97 | longitude: -750000000
98 | },
99 | hi: {
100 | latitude: 420000000,
101 | longitude: -730000000
102 | }
103 | }
104 | console.log('Looking for features between 40, -75 and 42, -73')
105 | var call = client.listFeatures(rectangle)
106 | call.on('data', function (feature) {
107 | console.log('Found feature called "' + feature.name + '" at ' +
108 | feature.location.latitude / COORD_FACTOR + ', ' +
109 | feature.location.longitude / COORD_FACTOR)
110 | })
111 | call.on('end', callback)
112 | }
113 |
114 | /**
115 | * Run the recordRoute demo. Sends several randomly chosen points from the
116 | * pre-generated feature database with a variable delay in between. Prints the
117 | * statistics when they are sent from the server.
118 | * @param {function} callback Called when this demo is complete
119 | */
120 | function runRecordRoute (callback) {
121 | var argv = parseArgs(process.argv, {
122 | string: 'db_path'
123 | })
124 | fs.readFile(path.resolve(argv.db_path), function (err, data) {
125 | if (err) {
126 | callback(err)
127 | return
128 | }
129 | var feature_list = JSON.parse(data)
130 |
131 | var num_points = 10
132 | var call = client.recordRoute(function (error, stats) {
133 | if (error) {
134 | callback(error)
135 | return
136 | }
137 | console.log('Finished trip with', stats.point_count, 'points')
138 | console.log('Passed', stats.feature_count, 'features')
139 | console.log('Travelled', stats.distance, 'meters')
140 | console.log('It took', stats.elapsed_time, 'seconds')
141 | callback()
142 | })
143 | /**
144 | * Constructs a function that asynchronously sends the given point and then
145 | * delays sending its callback
146 | * @param {number} lat The latitude to send
147 | * @param {number} lng The longitude to send
148 | * @return {function(function)} The function that sends the point
149 | */
150 | function pointSender (lat, lng) {
151 | /**
152 | * Sends the point, then calls the callback after a delay
153 | * @param {function} callback Called when complete
154 | */
155 | return function (callback) {
156 | console.log('Visiting point ' + lat / COORD_FACTOR + ', ' +
157 | lng / COORD_FACTOR)
158 | call.write({
159 | latitude: lat,
160 | longitude: lng
161 | })
162 | _.delay(callback, _.random(500, 1500))
163 | }
164 | }
165 | var point_senders = []
166 | for (var i = 0; i < num_points; i++) {
167 | var rand_point = feature_list[_.random(0, feature_list.length - 1)]
168 | point_senders[i] = pointSender(rand_point.location.latitude,
169 | rand_point.location.longitude)
170 | }
171 | async.series(point_senders, function () {
172 | call.end()
173 | })
174 | })
175 | }
176 |
177 | /**
178 | * Run the routeChat demo. Send some chat messages, and print any chat messages
179 | * that are sent from the server.
180 | * @param {function} callback Called when the demo is complete
181 | */
182 | function runRouteChat (callback) {
183 | var call = client.routeChat()
184 | call.on('data', function (note) {
185 | console.log('Got message "' + note.message + '" at ' +
186 | note.location.latitude + ', ' + note.location.longitude)
187 | })
188 |
189 | call.on('end', callback)
190 |
191 | var notes = [{
192 | location: {
193 | latitude: 0,
194 | longitude: 0
195 | },
196 | message: 'First message'
197 | }, {
198 | location: {
199 | latitude: 0,
200 | longitude: 1
201 | },
202 | message: 'Second message'
203 | }, {
204 | location: {
205 | latitude: 1,
206 | longitude: 0
207 | },
208 | message: 'Third message'
209 | }, {
210 | location: {
211 | latitude: 0,
212 | longitude: 0
213 | },
214 | message: 'Fourth message'
215 | }]
216 | for (var i = 0; i < notes.length; i++) {
217 | var note = notes[i]
218 | console.log('Sending message "' + note.message + '" at ' +
219 | note.location.latitude + ', ' + note.location.longitude)
220 | call.write(note)
221 | }
222 | call.end()
223 | }
224 |
225 | /**
226 | * Run all of the demos in order
227 | */
228 | function main () {
229 | async.series([
230 | runGetFeature,
231 | runListFeatures,
232 | runRecordRoute,
233 | runRouteChat
234 | ])
235 | }
236 |
237 | if (require.main === module) {
238 | main()
239 | }
240 |
241 | exports.runGetFeature = runGetFeature
242 |
243 | exports.runListFeatures = runListFeatures
244 |
245 | exports.runRecordRoute = runRecordRoute
246 |
247 | exports.runRouteChat = runRouteChat
248 |
--------------------------------------------------------------------------------
/route_guide/route_guide_db.json:
--------------------------------------------------------------------------------
1 | [{
2 | "location": {
3 | "latitude": 407838351,
4 | "longitude": -746143763
5 | },
6 | "name": "Patriots Path, Mendham, NJ 07945, USA"
7 | }, {
8 | "location": {
9 | "latitude": 408122808,
10 | "longitude": -743999179
11 | },
12 | "name": "101 New Jersey 10, Whippany, NJ 07981, USA"
13 | }, {
14 | "location": {
15 | "latitude": 413628156,
16 | "longitude": -749015468
17 | },
18 | "name": "U.S. 6, Shohola, PA 18458, USA"
19 | }, {
20 | "location": {
21 | "latitude": 419999544,
22 | "longitude": -740371136
23 | },
24 | "name": "5 Conners Road, Kingston, NY 12401, USA"
25 | }, {
26 | "location": {
27 | "latitude": 414008389,
28 | "longitude": -743951297
29 | },
30 | "name": "Mid Hudson Psychiatric Center, New Hampton, NY 10958, USA"
31 | }, {
32 | "location": {
33 | "latitude": 419611318,
34 | "longitude": -746524769
35 | },
36 | "name": "287 Flugertown Road, Livingston Manor, NY 12758, USA"
37 | }, {
38 | "location": {
39 | "latitude": 406109563,
40 | "longitude": -742186778
41 | },
42 | "name": "4001 Tremley Point Road, Linden, NJ 07036, USA"
43 | }, {
44 | "location": {
45 | "latitude": 416802456,
46 | "longitude": -742370183
47 | },
48 | "name": "352 South Mountain Road, Wallkill, NY 12589, USA"
49 | }, {
50 | "location": {
51 | "latitude": 412950425,
52 | "longitude": -741077389
53 | },
54 | "name": "Bailey Turn Road, Harriman, NY 10926, USA"
55 | }, {
56 | "location": {
57 | "latitude": 412144655,
58 | "longitude": -743949739
59 | },
60 | "name": "193-199 Wawayanda Road, Hewitt, NJ 07421, USA"
61 | }, {
62 | "location": {
63 | "latitude": 415736605,
64 | "longitude": -742847522
65 | },
66 | "name": "406-496 Ward Avenue, Pine Bush, NY 12566, USA"
67 | }, {
68 | "location": {
69 | "latitude": 413843930,
70 | "longitude": -740501726
71 | },
72 | "name": "162 Merrill Road, Highland Mills, NY 10930, USA"
73 | }, {
74 | "location": {
75 | "latitude": 410873075,
76 | "longitude": -744459023
77 | },
78 | "name": "Clinton Road, West Milford, NJ 07480, USA"
79 | }, {
80 | "location": {
81 | "latitude": 412346009,
82 | "longitude": -744026814
83 | },
84 | "name": "16 Old Brook Lane, Warwick, NY 10990, USA"
85 | }, {
86 | "location": {
87 | "latitude": 402948455,
88 | "longitude": -747903913
89 | },
90 | "name": "3 Drake Lane, Pennington, NJ 08534, USA"
91 | }, {
92 | "location": {
93 | "latitude": 406337092,
94 | "longitude": -740122226
95 | },
96 | "name": "6324 8th Avenue, Brooklyn, NY 11220, USA"
97 | }, {
98 | "location": {
99 | "latitude": 406421967,
100 | "longitude": -747727624
101 | },
102 | "name": "1 Merck Access Road, Whitehouse Station, NJ 08889, USA"
103 | }, {
104 | "location": {
105 | "latitude": 416318082,
106 | "longitude": -749677716
107 | },
108 | "name": "78-98 Schalck Road, Narrowsburg, NY 12764, USA"
109 | }, {
110 | "location": {
111 | "latitude": 415301720,
112 | "longitude": -748416257
113 | },
114 | "name": "282 Lakeview Drive Road, Highland Lake, NY 12743, USA"
115 | }, {
116 | "location": {
117 | "latitude": 402647019,
118 | "longitude": -747071791
119 | },
120 | "name": "330 Evelyn Avenue, Hamilton Township, NJ 08619, USA"
121 | }, {
122 | "location": {
123 | "latitude": 412567807,
124 | "longitude": -741058078
125 | },
126 | "name": "New York State Reference Route 987E, Southfields, NY 10975, USA"
127 | }, {
128 | "location": {
129 | "latitude": 416855156,
130 | "longitude": -744420597
131 | },
132 | "name": "103-271 Tempaloni Road, Ellenville, NY 12428, USA"
133 | }, {
134 | "location": {
135 | "latitude": 404663628,
136 | "longitude": -744820157
137 | },
138 | "name": "1300 Airport Road, North Brunswick Township, NJ 08902, USA"
139 | }, {
140 | "location": {
141 | "latitude": 407113723,
142 | "longitude": -749746483
143 | },
144 | "name": ""
145 | }, {
146 | "location": {
147 | "latitude": 402133926,
148 | "longitude": -743613249
149 | },
150 | "name": ""
151 | }, {
152 | "location": {
153 | "latitude": 400273442,
154 | "longitude": -741220915
155 | },
156 | "name": ""
157 | }, {
158 | "location": {
159 | "latitude": 411236786,
160 | "longitude": -744070769
161 | },
162 | "name": ""
163 | }, {
164 | "location": {
165 | "latitude": 411633782,
166 | "longitude": -746784970
167 | },
168 | "name": "211-225 Plains Road, Augusta, NJ 07822, USA"
169 | }, {
170 | "location": {
171 | "latitude": 415830701,
172 | "longitude": -742952812
173 | },
174 | "name": ""
175 | }, {
176 | "location": {
177 | "latitude": 413447164,
178 | "longitude": -748712898
179 | },
180 | "name": "165 Pedersen Ridge Road, Milford, PA 18337, USA"
181 | }, {
182 | "location": {
183 | "latitude": 405047245,
184 | "longitude": -749800722
185 | },
186 | "name": "100-122 Locktown Road, Frenchtown, NJ 08825, USA"
187 | }, {
188 | "location": {
189 | "latitude": 418858923,
190 | "longitude": -746156790
191 | },
192 | "name": ""
193 | }, {
194 | "location": {
195 | "latitude": 417951888,
196 | "longitude": -748484944
197 | },
198 | "name": "650-652 Willi Hill Road, Swan Lake, NY 12783, USA"
199 | }, {
200 | "location": {
201 | "latitude": 407033786,
202 | "longitude": -743977337
203 | },
204 | "name": "26 East 3rd Street, New Providence, NJ 07974, USA"
205 | }, {
206 | "location": {
207 | "latitude": 417548014,
208 | "longitude": -740075041
209 | },
210 | "name": ""
211 | }, {
212 | "location": {
213 | "latitude": 410395868,
214 | "longitude": -744972325
215 | },
216 | "name": ""
217 | }, {
218 | "location": {
219 | "latitude": 404615353,
220 | "longitude": -745129803
221 | },
222 | "name": ""
223 | }, {
224 | "location": {
225 | "latitude": 406589790,
226 | "longitude": -743560121
227 | },
228 | "name": "611 Lawrence Avenue, Westfield, NJ 07090, USA"
229 | }, {
230 | "location": {
231 | "latitude": 414653148,
232 | "longitude": -740477477
233 | },
234 | "name": "18 Lannis Avenue, New Windsor, NY 12553, USA"
235 | }, {
236 | "location": {
237 | "latitude": 405957808,
238 | "longitude": -743255336
239 | },
240 | "name": "82-104 Amherst Avenue, Colonia, NJ 07067, USA"
241 | }, {
242 | "location": {
243 | "latitude": 411733589,
244 | "longitude": -741648093
245 | },
246 | "name": "170 Seven Lakes Drive, Sloatsburg, NY 10974, USA"
247 | }, {
248 | "location": {
249 | "latitude": 412676291,
250 | "longitude": -742606606
251 | },
252 | "name": "1270 Lakes Road, Monroe, NY 10950, USA"
253 | }, {
254 | "location": {
255 | "latitude": 409224445,
256 | "longitude": -748286738
257 | },
258 | "name": "509-535 Alphano Road, Great Meadows, NJ 07838, USA"
259 | }, {
260 | "location": {
261 | "latitude": 406523420,
262 | "longitude": -742135517
263 | },
264 | "name": "652 Garden Street, Elizabeth, NJ 07202, USA"
265 | }, {
266 | "location": {
267 | "latitude": 401827388,
268 | "longitude": -740294537
269 | },
270 | "name": "349 Sea Spray Court, Neptune City, NJ 07753, USA"
271 | }, {
272 | "location": {
273 | "latitude": 410564152,
274 | "longitude": -743685054
275 | },
276 | "name": "13-17 Stanley Street, West Milford, NJ 07480, USA"
277 | }, {
278 | "location": {
279 | "latitude": 408472324,
280 | "longitude": -740726046
281 | },
282 | "name": "47 Industrial Avenue, Teterboro, NJ 07608, USA"
283 | }, {
284 | "location": {
285 | "latitude": 412452168,
286 | "longitude": -740214052
287 | },
288 | "name": "5 White Oak Lane, Stony Point, NY 10980, USA"
289 | }, {
290 | "location": {
291 | "latitude": 409146138,
292 | "longitude": -746188906
293 | },
294 | "name": "Berkshire Valley Management Area Trail, Jefferson, NJ, USA"
295 | }, {
296 | "location": {
297 | "latitude": 404701380,
298 | "longitude": -744781745
299 | },
300 | "name": "1007 Jersey Avenue, New Brunswick, NJ 08901, USA"
301 | }, {
302 | "location": {
303 | "latitude": 409642566,
304 | "longitude": -746017679
305 | },
306 | "name": "6 East Emerald Isle Drive, Lake Hopatcong, NJ 07849, USA"
307 | }, {
308 | "location": {
309 | "latitude": 408031728,
310 | "longitude": -748645385
311 | },
312 | "name": "1358-1474 New Jersey 57, Port Murray, NJ 07865, USA"
313 | }, {
314 | "location": {
315 | "latitude": 413700272,
316 | "longitude": -742135189
317 | },
318 | "name": "367 Prospect Road, Chester, NY 10918, USA"
319 | }, {
320 | "location": {
321 | "latitude": 404310607,
322 | "longitude": -740282632
323 | },
324 | "name": "10 Simon Lake Drive, Atlantic Highlands, NJ 07716, USA"
325 | }, {
326 | "location": {
327 | "latitude": 409319800,
328 | "longitude": -746201391
329 | },
330 | "name": "11 Ward Street, Mount Arlington, NJ 07856, USA"
331 | }, {
332 | "location": {
333 | "latitude": 406685311,
334 | "longitude": -742108603
335 | },
336 | "name": "300-398 Jefferson Avenue, Elizabeth, NJ 07201, USA"
337 | }, {
338 | "location": {
339 | "latitude": 419018117,
340 | "longitude": -749142781
341 | },
342 | "name": "43 Dreher Road, Roscoe, NY 12776, USA"
343 | }, {
344 | "location": {
345 | "latitude": 412856162,
346 | "longitude": -745148837
347 | },
348 | "name": "Swan Street, Pine Island, NY 10969, USA"
349 | }, {
350 | "location": {
351 | "latitude": 416560744,
352 | "longitude": -746721964
353 | },
354 | "name": "66 Pleasantview Avenue, Monticello, NY 12701, USA"
355 | }, {
356 | "location": {
357 | "latitude": 405314270,
358 | "longitude": -749836354
359 | },
360 | "name": ""
361 | }, {
362 | "location": {
363 | "latitude": 414219548,
364 | "longitude": -743327440
365 | },
366 | "name": ""
367 | }, {
368 | "location": {
369 | "latitude": 415534177,
370 | "longitude": -742900616
371 | },
372 | "name": "565 Winding Hills Road, Montgomery, NY 12549, USA"
373 | }, {
374 | "location": {
375 | "latitude": 406898530,
376 | "longitude": -749127080
377 | },
378 | "name": "231 Rocky Run Road, Glen Gardner, NJ 08826, USA"
379 | }, {
380 | "location": {
381 | "latitude": 407586880,
382 | "longitude": -741670168
383 | },
384 | "name": "100 Mount Pleasant Avenue, Newark, NJ 07104, USA"
385 | }, {
386 | "location": {
387 | "latitude": 400106455,
388 | "longitude": -742870190
389 | },
390 | "name": "517-521 Huntington Drive, Manchester Township, NJ 08759, USA"
391 | }, {
392 | "location": {
393 | "latitude": 400066188,
394 | "longitude": -746793294
395 | },
396 | "name": ""
397 | }, {
398 | "location": {
399 | "latitude": 418803880,
400 | "longitude": -744102673
401 | },
402 | "name": "40 Mountain Road, Napanoch, NY 12458, USA"
403 | }, {
404 | "location": {
405 | "latitude": 414204288,
406 | "longitude": -747895140
407 | },
408 | "name": ""
409 | }, {
410 | "location": {
411 | "latitude": 414777405,
412 | "longitude": -740615601
413 | },
414 | "name": ""
415 | }, {
416 | "location": {
417 | "latitude": 415464475,
418 | "longitude": -747175374
419 | },
420 | "name": "48 North Road, Forestburgh, NY 12777, USA"
421 | }, {
422 | "location": {
423 | "latitude": 404062378,
424 | "longitude": -746376177
425 | },
426 | "name": ""
427 | }, {
428 | "location": {
429 | "latitude": 405688272,
430 | "longitude": -749285130
431 | },
432 | "name": ""
433 | }, {
434 | "location": {
435 | "latitude": 400342070,
436 | "longitude": -748788996
437 | },
438 | "name": ""
439 | }, {
440 | "location": {
441 | "latitude": 401809022,
442 | "longitude": -744157964
443 | },
444 | "name": ""
445 | }, {
446 | "location": {
447 | "latitude": 404226644,
448 | "longitude": -740517141
449 | },
450 | "name": "9 Thompson Avenue, Leonardo, NJ 07737, USA"
451 | }, {
452 | "location": {
453 | "latitude": 410322033,
454 | "longitude": -747871659
455 | },
456 | "name": ""
457 | }, {
458 | "location": {
459 | "latitude": 407100674,
460 | "longitude": -747742727
461 | },
462 | "name": ""
463 | }, {
464 | "location": {
465 | "latitude": 418811433,
466 | "longitude": -741718005
467 | },
468 | "name": "213 Bush Road, Stone Ridge, NY 12484, USA"
469 | }, {
470 | "location": {
471 | "latitude": 415034302,
472 | "longitude": -743850945
473 | },
474 | "name": ""
475 | }, {
476 | "location": {
477 | "latitude": 411349992,
478 | "longitude": -743694161
479 | },
480 | "name": ""
481 | }, {
482 | "location": {
483 | "latitude": 404839914,
484 | "longitude": -744759616
485 | },
486 | "name": "1-17 Bergen Court, New Brunswick, NJ 08901, USA"
487 | }, {
488 | "location": {
489 | "latitude": 414638017,
490 | "longitude": -745957854
491 | },
492 | "name": "35 Oakland Valley Road, Cuddebackville, NY 12729, USA"
493 | }, {
494 | "location": {
495 | "latitude": 412127800,
496 | "longitude": -740173578
497 | },
498 | "name": ""
499 | }, {
500 | "location": {
501 | "latitude": 401263460,
502 | "longitude": -747964303
503 | },
504 | "name": ""
505 | }, {
506 | "location": {
507 | "latitude": 412843391,
508 | "longitude": -749086026
509 | },
510 | "name": ""
511 | }, {
512 | "location": {
513 | "latitude": 418512773,
514 | "longitude": -743067823
515 | },
516 | "name": ""
517 | }, {
518 | "location": {
519 | "latitude": 404318328,
520 | "longitude": -740835638
521 | },
522 | "name": "42-102 Main Street, Belford, NJ 07718, USA"
523 | }, {
524 | "location": {
525 | "latitude": 419020746,
526 | "longitude": -741172328
527 | },
528 | "name": ""
529 | }, {
530 | "location": {
531 | "latitude": 404080723,
532 | "longitude": -746119569
533 | },
534 | "name": ""
535 | }, {
536 | "location": {
537 | "latitude": 401012643,
538 | "longitude": -744035134
539 | },
540 | "name": ""
541 | }, {
542 | "location": {
543 | "latitude": 404306372,
544 | "longitude": -741079661
545 | },
546 | "name": ""
547 | }, {
548 | "location": {
549 | "latitude": 403966326,
550 | "longitude": -748519297
551 | },
552 | "name": ""
553 | }, {
554 | "location": {
555 | "latitude": 405002031,
556 | "longitude": -748407866
557 | },
558 | "name": ""
559 | }, {
560 | "location": {
561 | "latitude": 409532885,
562 | "longitude": -742200683
563 | },
564 | "name": ""
565 | }, {
566 | "location": {
567 | "latitude": 416851321,
568 | "longitude": -742674555
569 | },
570 | "name": ""
571 | }, {
572 | "location": {
573 | "latitude": 406411633,
574 | "longitude": -741722051
575 | },
576 | "name": "3387 Richmond Terrace, Staten Island, NY 10303, USA"
577 | }, {
578 | "location": {
579 | "latitude": 413069058,
580 | "longitude": -744597778
581 | },
582 | "name": "261 Van Sickle Road, Goshen, NY 10924, USA"
583 | }, {
584 | "location": {
585 | "latitude": 418465462,
586 | "longitude": -746859398
587 | },
588 | "name": ""
589 | }, {
590 | "location": {
591 | "latitude": 411733222,
592 | "longitude": -744228360
593 | },
594 | "name": ""
595 | }, {
596 | "location": {
597 | "latitude": 410248224,
598 | "longitude": -747127767
599 | },
600 | "name": "3 Hasta Way, Newton, NJ 07860, USA"
601 | }]
602 |
--------------------------------------------------------------------------------
/route_guide/route_guide_db_notes.json:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/malijs/examples/a2238e5fc60ed32059292e1cb766fb7f1ac8e470/route_guide/route_guide_db_notes.json
--------------------------------------------------------------------------------
/route_guide/route_guide_server.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const hl = require('highland')
3 | const _ = require('lodash')
4 | const Mali = require('mali')
5 | const pify = require('pify')
6 | const fs = pify(require('fs'))
7 | const logger = require('mali-logger')
8 | const asCallback = require('ascallback')
9 | const pFinally = require('p-finally')
10 | const miss = require('mississippi')
11 |
12 | const db = require('./feature_db')
13 | const { getDistance, pointKey } = require('./route_utils')
14 |
15 | const PROTO_PATH = path.resolve(__dirname, '../protos/route_guide.proto')
16 | const HOSTPORT = '0.0.0.0:50051'
17 |
18 | /**
19 | * getFeature request handler. Gets a request with a point, and responds with a
20 | * feature object indicating whether there is a feature at that point.
21 | * @param {Object} ctx Context or point
22 | */
23 | async function getFeature (ctx) {
24 | const point = ctx.req
25 | let feature = await db.checkFeature(point)
26 | if (!feature) {
27 | feature = {
28 | name: '',
29 | location: point
30 | }
31 | }
32 |
33 | ctx.res = feature
34 | }
35 |
36 | /**
37 | * listFeatures request handler. Gets a request with two points, and responds
38 | * with a stream of all features in the bounding box defined by those points.
39 | */
40 | async function listFeatures (ctx) {
41 | const lo = ctx.req.lo
42 | const hi = ctx.req.hi
43 |
44 | const left = _.min([lo.longitude, hi.longitude])
45 | const right = _.max([lo.longitude, hi.longitude])
46 | const top = _.max([lo.latitude, hi.latitude])
47 | const bottom = _.min([lo.latitude, hi.latitude])
48 |
49 | const input = await db.getFeaturesListStream()
50 |
51 | const ret = hl(input).filter(feature => {
52 | if (feature.name === '') {
53 | return false
54 | }
55 | if (feature.location.longitude >= left &&
56 | feature.location.longitude <= right &&
57 | feature.location.latitude >= bottom &&
58 | feature.location.latitude <= top) {
59 | return true
60 | }
61 | })
62 |
63 | ctx.res = ret
64 | }
65 |
66 | async function statsMapper (point) {
67 | let feature = await db.checkFeature(point)
68 | if (!feature) {
69 | feature = {
70 | name: '',
71 | location: point
72 | }
73 | }
74 | return {
75 | point,
76 | feature
77 | }
78 | }
79 |
80 | // to be used because we don't have highland asyncWrapper
81 | function statsMappedCb (point, fn) {
82 | asCallback(Promise.resolve(statsMapper(point)), fn)
83 | }
84 |
85 | /**
86 | * recordRoute handler. Gets a stream of points, and responds with statistics
87 | * about the "trip": number of points, number of known features visited, total
88 | * distance traveled, and total time spent.
89 | */
90 | async function recordRoute (ctx) {
91 | // TODO we need to wait for Highland flatReduce
92 | // to be able to use async reduce iterator
93 | // for now we'll just async map to get features if present
94 | // TODO wrapAsync is not present in highland beta.3 yet
95 | // So use callback style using statsMappedCb
96 |
97 | const iv = {
98 | point_count: 0,
99 | feature_count: 0,
100 | distance: 0,
101 | previous: null
102 | }
103 |
104 | const mapper = hl.wrapCallback(statsMappedCb)
105 |
106 | const startTime = process.hrtime()
107 |
108 | return new Promise((resolve, reject) => {
109 | hl(ctx.req)
110 | .map(mapper)
111 | .series()
112 | .reduce((memo, data) => {
113 | memo.point_count += 1
114 | if (data.feature && data.feature.name) {
115 | memo.feature_count += 1
116 | }
117 |
118 | if (memo.previous != null) {
119 | memo.distance += getDistance(memo.previous, data.point)
120 | }
121 | memo.previous = data.point
122 | return memo
123 | }, iv)
124 | .toCallback((err, r) => {
125 | if (err) {
126 | return reject(err)
127 | }
128 |
129 | ctx.res = {
130 | point_count: r.point_count,
131 | feature_count: r.feature_count,
132 | // Cast the distance to an integer
133 | distance: r.distance | 0,
134 | // End the timer
135 | elapsed_time: process.hrtime(startTime)[0]
136 | }
137 | resolve()
138 | })
139 | })
140 | }
141 |
142 | async function handleNode (ctx, note) {
143 | const key = pointKey(note.location)
144 | const existing = await db.getNote(key)
145 | if (existing) {
146 | _.each(existing.value, n => ctx.res.write(n))
147 | }
148 |
149 | return db.putNote(key, note)
150 | }
151 |
152 | function handleNoteCb (ctx, note, fn) {
153 | asCallback(handleNode(ctx, note), fn)
154 | }
155 |
156 | /**
157 | * routeChat handler. Receives a stream of message/location pairs, and responds
158 | * with a stream of all previous messages at each of those locations.
159 | */
160 | async function routeChat (ctx) {
161 | const p = _.partial(handleNoteCb, ctx)
162 | miss.each(ctx.req, p, () => {
163 | ctx.res.end()
164 | })
165 | }
166 |
167 | let app
168 |
169 | function main () {
170 | fs.truncate('route_guide_db_notes.json', 0).then(() => {
171 | app = new Mali(PROTO_PATH, 'RouteGuide')
172 | app.use(logger())
173 | app.use({
174 | getFeature,
175 | listFeatures,
176 | recordRoute,
177 | routeChat
178 | })
179 | app.start(HOSTPORT)
180 | console.log(`Route Guide service running @ ${HOSTPORT}`)
181 | })
182 | }
183 |
184 | function shutdown (err) {
185 | if (err) {
186 | console.error(err)
187 | }
188 |
189 | const p = fs
190 | .truncate('route_guide_db_notes.json', 0)
191 | .then(app.close())
192 |
193 | pFinally(p, () => process.exit())
194 | }
195 |
196 | process.on('uncaughtException', shutdown)
197 | process.on('SIGINT', shutdown)
198 | process.on('SIGTERM', shutdown)
199 |
200 | main()
201 |
--------------------------------------------------------------------------------
/route_guide/route_utils.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Random route / feature utils
3 | */
4 |
5 | const COORD_FACTOR = 1e7
6 |
7 | function toRadians (num) {
8 | return num * Math.PI / 180
9 | }
10 |
11 | /**
12 | * Calculate the distance between two points using the "haversine" formula.
13 | * This code was taken from http://www.movable-type.co.uk/scripts/latlong.html.
14 | * @param start The starting point
15 | * @param end The end point
16 | * @return The distance between the points in meters
17 | */
18 | function getDistance (start, end) {
19 | var lat1 = start.latitude / COORD_FACTOR
20 | var lat2 = end.latitude / COORD_FACTOR
21 | var lon1 = start.longitude / COORD_FACTOR
22 | var lon2 = end.longitude / COORD_FACTOR
23 | var R = 6371000 // metres
24 | var φ1 = toRadians(lat1)
25 | var φ2 = toRadians(lat2)
26 | var Δφ = toRadians(lat2 - lat1)
27 | var Δλ = toRadians(lon2 - lon1)
28 |
29 | var a = Math.sin(Δφ / 2) * Math.sin(Δφ / 2) +
30 | Math.cos(φ1) * Math.cos(φ2) *
31 | Math.sin(Δλ / 2) * Math.sin(Δλ / 2)
32 | var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
33 |
34 | return R * c
35 | }
36 |
37 | /**
38 | * Turn the point into a dictionary key.
39 | * @param {point} point The point to use
40 | * @return {string} The key for an object
41 | */
42 | function pointKey (point) {
43 | return point.latitude + ' ' + point.longitude
44 | }
45 |
46 | exports.toRadians = toRadians
47 | exports.getDistance = getDistance
48 | exports.pointKey = pointKey
49 |
--------------------------------------------------------------------------------
/route_guide/server.js:
--------------------------------------------------------------------------------
1 | require('./route_guide_server.js')
2 |
--------------------------------------------------------------------------------
/secret_service/client.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const path = require('path')
3 | const caller = require('grpc-caller')
4 | const hl = require('highland')
5 | const JSONStream = require('JSONStream')
6 |
7 | const DBFILE = 'secret_data.json'
8 | const PROTO_PATH = path.resolve(__dirname, '../protos/secret.proto')
9 | const HOSTPORT = '0.0.0.0:50051'
10 | const client = caller(HOSTPORT, PROTO_PATH, 'SecretService')
11 |
12 | function main () {
13 | const call = client.processSecrets((err, result) => {
14 | if (err) console.log(err)
15 | console.dir(result)
16 | process.exit(0)
17 | })
18 |
19 | const input = fs.createReadStream(DBFILE)
20 |
21 | hl(input)
22 | .through(JSONStream.parse('*'))
23 | .each(d => call.write(d))
24 | .done(() => call.end())
25 | }
26 |
27 | main()
28 |
--------------------------------------------------------------------------------
/secret_service/encrpyt.js:
--------------------------------------------------------------------------------
1 | const pify = require('pify')
2 | const bcrypt = pify(require('bcrypt'))
3 | const Chance = require('chance')
4 | const chance = new Chance()
5 |
6 | module.exports = function (secret, iterations = 10) {
7 | const fakeError = chance.bool({ likelihood: 5 })
8 | if (fakeError) throw new Error('Error encrypting secret')
9 | if (!secret || typeof secret !== 'string') throw new Error('Secret required')
10 |
11 | return bcrypt.hash(secret, iterations)
12 | }
13 |
--------------------------------------------------------------------------------
/secret_service/save.js:
--------------------------------------------------------------------------------
1 | const os = require('os')
2 | const pify = require('pify')
3 | const fs = require('fs')
4 | const appendFile = pify(fs.appendFile)
5 | const Chance = require('chance')
6 | const chance = new Chance()
7 |
8 | const DB_WRITE_FILE = 'secret_db_write.json'
9 |
10 | async function save (id, data) {
11 | if (!id || typeof id !== 'string') throw new Error('ID required')
12 | if (!data || typeof data !== 'string') throw new Error('Data required')
13 |
14 | const fakeError = chance.bool({ likelihood: 5 })
15 | if (fakeError) throw new Error('Error saving data')
16 |
17 | await appendFile(DB_WRITE_FILE, id.concat(':', data, os.EOL), 'utf8')
18 | return id
19 | }
20 |
21 | module.exports = save
22 |
--------------------------------------------------------------------------------
/secret_service/secret_data.json:
--------------------------------------------------------------------------------
1 | [{
2 | "id": "ef0e786c-7c25-45be-9b7b-e3c58a042253",
3 | "secret": "SVh60gAIPG5j"
4 | }, {
5 | "id": "a7e7fc04-6b38-4f72-bcb7-520ce6094dc1",
6 | "secret": "vUUtsPK"
7 | }, {
8 | "id": "d324689f-a035-47ed-a228-ee74ee57b66c",
9 | "secret": "aQbBqKdp"
10 | }, {
11 | "id": "9a792bfd-a2bc-4c0e-ae73-960409a006f3",
12 | "secret": "377T01"
13 | }, {
14 | "id": "00d4b971-a676-4729-a83f-604be781cf55",
15 | "secret": "vB2bKoWNxFDp"
16 | }, {
17 | "id": "c9297e25-e32a-4964-a546-96078bec58a7",
18 | "secret": "6k8Y6VrL"
19 | }, {
20 | "id": "049645cd-66d6-46d7-ad6f-c1bef5f419bf",
21 | "secret": "ACb03hSdo"
22 | }, {
23 | "id": "836ce97b-dcd1-4f14-aaef-daed0d5752c7",
24 | "secret": "1TT1fIG0EZ"
25 | }, {
26 | "id": "7ecc84bb-42af-492a-af63-db70a6f59ce7",
27 | "secret": ""
28 | }, {
29 | "id": "9e54f369-dfc1-4372-a414-4f5e11ef1f1f",
30 | "secret": "OStVB0"
31 | }, {
32 | "id": "85269203-04ca-42ff-9f82-dea5f40699b8",
33 | "secret": "neNagxcW9b"
34 | }, {
35 | "id": "42e9c023-04bb-4906-a954-6ad2c07faf3e",
36 | "secret": "2UzKw3BIypsx"
37 | }, {
38 | "id": "75768ab9-db07-4bba-8db4-166402ae6de4",
39 | "secret": "TAeyLfL8Ypb"
40 | }, {
41 | "id": "b3284f05-c247-41ea-aff2-885722963344",
42 | "secret": "M5O3XwAsBfnY"
43 | }, {
44 | "id": "7bdab4fd-ba0a-488a-b85f-863fa915d364",
45 | "secret": "PTNrFF9R"
46 | }, {
47 | "id": "ad4c73de-7322-4e33-9253-c449b9d94111",
48 | "secret": "RZy5BKlKK"
49 | }, {
50 | "id": "5a66e979-ba06-4f8f-8ae9-78166542b5cf",
51 | "secret": "U6djbdHqNm"
52 | }, {
53 | "id": "73ed5f3b-a6f2-412a-bcf7-b3e5675b2b81",
54 | "secret": "HZv2ADGm31z"
55 | }, {
56 | "id": "98304014-dd8c-406e-bb25-77ab00c08723",
57 | "secret": "PncZbnuAVKlX"
58 | }, {
59 | "id": "baa6227f-9ee4-4e98-b939-94dc867ac5bc",
60 | "secret": "TpQwKOO"
61 | }, {
62 | "id": "e3b11a70-4a06-4d34-a34e-b71ecea797a7",
63 | "secret": "H6gTe1KF"
64 | }, {
65 | "id": "5fb6247a-3775-497c-a224-ab8c79dc4647",
66 | "secret": "eB55cCoKk6"
67 | }, {
68 | "id": "01f2f044-b136-4f7f-bac9-513e8f743b66",
69 | "secret": "1XCnfmN9jwf"
70 | }, {
71 | "id": "b6a75d41-bdb9-490d-853a-bdd68a32307e",
72 | "secret": "r1eK4IYN"
73 | }, {
74 | "id": "a5a30f7e-2b51-4e7c-814c-f00caa8d906b",
75 | "secret": "TyuJzv"
76 | }, {
77 | "id": "37b7af3e-9683-4f01-a624-587baaff9c33",
78 | "secret": "5aW4hDOPPf8"
79 | }, {
80 | "id": "9cb3ebf0-760f-4767-99bf-be5bbf4cbb35",
81 | "secret": "CIf677w"
82 | }, {
83 | "id": "a5b8aa22-4551-47ec-afd6-3822cb4bf6dc",
84 | "secret": "sHWM2yi44dMN"
85 | }, {
86 | "id": "db80ac67-1812-4810-a896-643487691a8d",
87 | "secret": "tFS9y9BhXMj"
88 | }, {
89 | "id": "411f839d-1886-4cbf-aa37-60e8197a444a",
90 | "secret": "miZyHc85"
91 | }, {
92 | "id": "356f8690-a6df-4880-bd93-7273767a316a",
93 | "secret": "zwhlJMT43jBJ"
94 | }, {
95 | "id": "cb641d7f-08c6-4141-adfd-b450b58da3e8",
96 | "secret": "X6CvrzX2X"
97 | }, {
98 | "id": "4d398844-1b73-46c8-b5d5-23f38ff310b3",
99 | "secret": "g7lSVCH9"
100 | }, {
101 | "id": "5cd77ba7-64f2-47d6-bfbe-7eb605e03170",
102 | "secret": "6VZwWJe8OXD"
103 | }, {
104 | "id": "4f510c82-1912-46fb-bcbd-8d0e076a1616",
105 | "secret": "z7hle6Fh"
106 | }, {
107 | "id": "1d52711d-82fe-469b-89cb-95682e6ecc64",
108 | "secret": "xQikx8G"
109 | }, {
110 | "id": "12fca923-e434-4d98-bf7c-cae3e0f5cb70",
111 | "secret": "g8JdXvsfi2A4"
112 | }, {
113 | "id": "2447e5c0-3062-480b-b9c6-f7451b10f582",
114 | "secret": "ygUywUDp"
115 | }, {
116 | "id": "1048f3a8-c6f0-4526-8747-3f835b29b1f6",
117 | "secret": "uAVKIsdcZbn"
118 | }, {
119 | "id": "58dcf39e-1e17-40f1-8411-a268683932e2",
120 | "secret": "t4zOddoZ4G"
121 | }, {
122 | "id": "064a4281-96c1-42ed-a074-7fdbb06ae5a8",
123 | "secret": "Nj9PF0orfrh"
124 | }, {
125 | "id": "e5928bbb-1872-49d9-9618-bdf5b42f04e4",
126 | "secret": "YvqCb8aE"
127 | }, {
128 | "id": "a09aca86-9d0b-4a3e-8408-718f747c41ba",
129 | "secret": "6rZLH9ArYg"
130 | }, {
131 | "id": "d56adad8-b7fa-44ee-b296-9b642fa7b12f",
132 | "secret": "YxqIsdGL"
133 | }, {
134 | "id": "48f4e260-96eb-4445-8443-84cd9eeee4b2",
135 | "secret": ""
136 | }, {
137 | "id": "b0fd63b6-64b2-4251-bd09-c0d800da874b",
138 | "secret": "3SFwWrZLHX"
139 | }, {
140 | "id": "bcb92e69-177c-4101-b124-f5c6fe8b1d97",
141 | "secret": "5hu06MjgPf"
142 | }, {
143 | "id": "e44802a0-2211-4fb4-89d2-d6a8baad60b2",
144 | "secret": "84rRewHQ"
145 | }, {
146 | "id": "aaf358d0-a947-42e0-8386-a0d8f851d759",
147 | "secret": "knBQxx0"
148 | }, {
149 | "id": "b2d18d4b-ab00-4aaf-859c-f9f1f7729b3d",
150 | "secret": "Y3SF8iv"
151 | }]
152 |
--------------------------------------------------------------------------------
/secret_service/secret_service.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const hl = require('highland')
3 | const asCallback = require('ascallback')
4 | const Mali = require('mali')
5 | const logger = require('mali-logger')
6 | const pify = require('pify')
7 | const fs = require('fs')
8 | const truncate = pify(fs.truncate)
9 |
10 | const encrypt = require('./encrpyt')
11 | const save = require('./save')
12 |
13 | const PROTO_PATH = path.resolve(__dirname, '../protos/secret.proto')
14 | const HOSTPORT = '0.0.0.0:50051'
15 |
16 | async function processSecretAsync (id, secret) {
17 | const data = await encrypt(secret)
18 | const r = await save(id, data)
19 | return r
20 | }
21 |
22 | // to be used because we don't have highland async wrapper
23 | function processSecretCb (data, fn) {
24 | asCallback(processSecretAsync(data.id, data.secret), (err, r) => {
25 | if (err) err.id = data.id
26 | fn(err, r)
27 | })
28 | }
29 |
30 | async function processSecrets (ctx) {
31 | const processSercret = hl.wrapCallback(processSecretCb)
32 |
33 | const failed = []
34 | return new Promise((resolve, reject) => {
35 | hl(ctx.req)
36 | .map(processSercret)
37 | .parallel(10)
38 | .errors((err, push) => {
39 | if (err.id) {
40 | failed.push({ id: err.id, message: err.message })
41 | push(null)
42 | } else push(err)
43 | })
44 | .compact()
45 | .reduce(m => ++m, 0)
46 | .toCallback((err, success) => {
47 | if (err) return reject(err)
48 | ctx.res = { success, failed }
49 | resolve()
50 | })
51 | })
52 | }
53 |
54 | let app
55 |
56 | function main () {
57 | app = new Mali(PROTO_PATH, 'SecretService')
58 |
59 | app.use(logger())
60 | app.use({ processSecrets })
61 |
62 | app.start(HOSTPORT)
63 | console.log(`Secret service running @ ${HOSTPORT}`)
64 | }
65 |
66 | async function shutdown (err) {
67 | if (err) console.error(err)
68 |
69 | await truncate('secret_db_write.json', 0)
70 | await app.close()
71 | process.exit()
72 | }
73 |
74 | process.on('uncaughtException', shutdown)
75 | process.on('SIGINT', shutdown)
76 | process.on('SIGTERM', shutdown)
77 |
78 | main()
79 |
--------------------------------------------------------------------------------
/secret_service/server.js:
--------------------------------------------------------------------------------
1 | require('./secret_service.js')
2 |
--------------------------------------------------------------------------------
/user_service/server.js:
--------------------------------------------------------------------------------
1 | require('./user_service.js')
2 |
--------------------------------------------------------------------------------
/user_service/test/user.test.js:
--------------------------------------------------------------------------------
1 | import test from 'ava'
2 | import path from 'path'
3 | import caller from 'grpc-caller'
4 | import sprom from 'sprom'
5 |
6 | import User from '../user'
7 |
8 | const users = require('../user_db.json')
9 |
10 | const PROTO_PATH = path.resolve(__dirname, '../../protos/user.proto')
11 | const HOSTPORT = '0.0.0.0:50051'
12 | const client = caller(HOSTPORT, PROTO_PATH, 'UserService')
13 |
14 | test('fail with wrong auth info', async t => {
15 | t.plan(5)
16 | const err = await t.throwsAsync(client.getUser({ id: '1d78202b-23cf-4d1e-92ac-2d2f76278a7d' }, { apikey: '654321' }))
17 | t.truthy(err)
18 | t.is(err.message, '16 UNAUTHENTICATED: Not Authorized')
19 | const md = err.metadata.getMap()
20 | t.is(md.type, 'AUTH')
21 | t.is(md.code, 'INVALID_APIKEY')
22 | })
23 |
24 | test('fail with wrong api key', async t => {
25 | t.plan(5)
26 | const err = await t.throwsAsync(client.getUser({ id: '1d78202b-23cf-4d1e-92ac-2d2f76278a7d' }, { Authorization: 'apikey 654322' }))
27 | t.truthy(err)
28 | t.is(err.message, '16 UNAUTHENTICATED: Not Authorized')
29 | const md = err.metadata.getMap()
30 | t.is(md.type, 'AUTH')
31 | t.is(md.code, 'INVALID_APIKEY')
32 | })
33 |
34 | test('get existing user', async t => {
35 | t.plan(2)
36 | const response = await client.getUser({ id: '1d78202b-23cf-4d1e-92ac-2d2f76278a7d' }, { Authorization: 'apikey 654321' })
37 | t.truthy(response)
38 | const user = new User(response)
39 | t.deepEqual(user.metadata, {
40 | foo: 'bar',
41 | active: true
42 | })
43 | })
44 |
45 | test('get all users', async t => {
46 | const nusers = users.length
47 | t.plan(nusers + 1)
48 | // need to set empty object for "query" arg / param
49 | const call = client.listUsers({}, { Authorization: 'apikey 654321' })
50 | let counter = 0
51 | call.on('data', (data) => {
52 | const u = new User(data)
53 | t.truthy(u.id)
54 | counter++
55 | })
56 |
57 | await sprom(call)
58 | t.is(counter, nusers)
59 | })
60 |
61 | test('create user', async t => {
62 | t.plan(5)
63 | const data = {
64 | email: 'test@test.com',
65 | dateOfBirth: new Date('1/1/2000').toISOString(),
66 | password: 's3crE+1',
67 | metadata: new Buffer(JSON.stringify({foo: 'bar'}))
68 | }
69 |
70 | const ret = await client.createUser(data, { Authorization: 'apikey 654321' })
71 | t.truthy(ret)
72 | t.truthy(ret.id)
73 | const r = new User(ret)
74 | t.truthy(r.metadata)
75 | t.truthy(r.metadata.foo)
76 | t.is(r.metadata.foo, 'bar')
77 | })
78 |
--------------------------------------------------------------------------------
/user_service/user.js:
--------------------------------------------------------------------------------
1 | const _ = require('lodash')
2 | const hl = require('highland')
3 | const JSONStream = require('JSONStream')
4 | const pify = require('pify')
5 | const fs = require('fs')
6 | const appendFile = pify(fs.appendFile)
7 | const uuid = require('uuid')
8 |
9 | const DBFILE = 'user_db.json'
10 | const DB_WRITE_FILE = 'user_db_write.json'
11 |
12 | class User {
13 | constructor (props) {
14 | _.forOwn(props, (v, k) => { this[k] = v })
15 |
16 | if (this.metadata && _.isEmpty(this.metadata)) {
17 | delete this.metadata
18 | }
19 |
20 | if (typeof this.metadata === 'string') {
21 | this.metadata = JSON.parse(new Buffer(this.metadata, 'base64').toString())
22 | } else if (Buffer.isBuffer(this.metadata)) {
23 | this.metadata = JSON.parse(this.metadata.toString())
24 | }
25 |
26 | if (typeof this.dateOfBirth === 'string') {
27 | this.dateOfBirth = new Date(this.dateOfBirth)
28 | }
29 |
30 | if (!this.id) {
31 | this.id = uuid()
32 | }
33 | }
34 |
35 | toObject () {
36 | const ret = {}
37 | _.forOwn(this, (v, k) => { ret[k] = v })
38 | return _.cloneDeep(ret)
39 | }
40 |
41 | toJSON () {
42 | const ret = this.toObject()
43 | delete ret.password
44 |
45 | if (this.metadata) {
46 | ret.metadata = new Buffer(JSON.stringify(this.metadata))
47 | }
48 |
49 | if (this.dateOfBirth instanceof Date) {
50 | ret.dateOfBirth = this.dateOfBirth.toISOString()
51 | }
52 |
53 | return ret
54 | }
55 |
56 | // simulates write
57 | // to a different, untracked file, so we don't mess with git
58 | async save () {
59 | const doc = this.toObject()
60 | await appendFile(DB_WRITE_FILE, JSON.stringify(doc), 'utf8')
61 | return this
62 | }
63 |
64 | static async findById (id) {
65 | return new Promise((resolve, reject) => {
66 | const input = fs.createReadStream(DBFILE)
67 |
68 | hl(input)
69 | .through(JSONStream.parse('*'))
70 | .find(u => u.id === id)
71 | .toCallback((err, data) => {
72 | if (err) {
73 | return reject(err)
74 | }
75 | resolve(new User(data))
76 | })
77 | })
78 | }
79 |
80 | static list () {
81 | return hl(fs.createReadStream(DBFILE))
82 | .through(JSONStream.parse('*'))
83 | .map(o => new User(o))
84 | }
85 | }
86 |
87 | module.exports = User
88 |
--------------------------------------------------------------------------------
/user_service/user_db.json:
--------------------------------------------------------------------------------
1 | [{
2 | "id": "1d78202b-23cf-4d1e-92ac-2d2f76278a7d",
3 | "email": "danderson0@fastcompany.com",
4 | "password": "JldKZO5Mqp3m",
5 | "dateOfBirth": "1994-05-12T22:15:42Z",
6 | "metadata": {
7 | "foo": "bar",
8 | "active": true
9 | }
10 | }, {
11 | "id": "83219b96-bc2f-477e-92b5-0f54459c471c",
12 | "email": "pbennett1@nifty.com",
13 | "password": "wLhZWdkTeFe",
14 | "dateOfBirth": "1999-12-16T02:41:19Z",
15 | "metadata": {
16 | "age": 20,
17 | "flag": "something"
18 | }
19 | }, {
20 | "id": "a3a0b364-cce2-4451-ac81-301c46cedba4",
21 | "email": "jgilbert2@domainmarket.com",
22 | "password": "CeWqV0q0j",
23 | "dateOfBirth": "1999-09-03T16:14:29Z"
24 | }, {
25 | "id": "a4523882-e052-4e4a-8129-7952f9878dbb",
26 | "email": "rflores3@ifeng.com",
27 | "password": "fhu55UIeQu80",
28 | "dateOfBirth": "1974-04-17T20:37:33Z",
29 | "metadata": {
30 | "company": "ACME Inc"
31 | }
32 | }, {
33 | "id": "70b01689-66fd-4c71-b250-b3ffa758fc8b",
34 | "email": "syoung4@seesaa.net",
35 | "password": "wh34ls0Y",
36 | "dateOfBirth": "1980-09-29T02:40:01Z"
37 | }, {
38 | "id": "2563eef4-a801-44f8-a0f9-8d30b0db2115",
39 | "email": "troberts5@so-net.ne.jp",
40 | "password": "sZBhJUjd62g9",
41 | "dateOfBirth": "1988-02-16T12:22:50Z",
42 | "metadata": {
43 | "token": "86a08c9c-fad3-4e58-bacf-54f1d94aafc5"
44 | }
45 | }, {
46 | "id": "b6d377b8-d0c7-4591-b955-c84338385689",
47 | "email": "sclark6@google.com.au",
48 | "password": "kgYSq6",
49 | "dateOfBirth": "1999-06-05T23:20:02Z"
50 | }, {
51 | "id": "e14a3d80-f347-437d-a9e7-f00bdd1d196d",
52 | "email": "cwheeler7@goo.gl",
53 | "password": "DHrQL2sXEwA",
54 | "dateOfBirth": "1971-12-08T08:43:45Z",
55 | "metadata": {
56 | "likes": ["movies", "music"]
57 | }
58 | }, {
59 | "id": "5af03282-d112-4f71-ad19-6b8fae5c6803",
60 | "email": "vbryant8@economist.com",
61 | "password": "ihBIRr",
62 | "dateOfBirth": "1996-07-01T20:08:29Z"
63 | }, {
64 | "id": "5fe3cbbc-ed70-40b0-88f4-ed17173ea28b",
65 | "email": "khamilton9@gnu.org",
66 | "password": "Tk7CtJH",
67 | "dateOfBirth": "1984-11-14T05:07:04Z"
68 | }, {
69 | "id": "00bb3792-0244-4008-8aa8-2809a8104c10",
70 | "email": "shuntera@nps.gov",
71 | "password": "EtkJByv2",
72 | "dateOfBirth": "1978-06-28T18:37:39Z"
73 | }]
74 |
--------------------------------------------------------------------------------
/user_service/user_service.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const hl = require('highland')
3 | const Mali = require('mali')
4 | const grpc = require('grpc')
5 |
6 | const createError = require('create-grpc-error')
7 | const apikey = require('mali-apikey')
8 | const logger = require('mali-logger')
9 | const toJSON = require('mali-tojson')
10 | const User = require('./user')
11 |
12 | const PROTO_PATH = path.resolve(__dirname, '../protos/user.proto')
13 | const HOSTPORT = '0.0.0.0:50051'
14 |
15 | let app
16 | const API_KEY = '654321'
17 | const apiKeyErrorMetadata = { type: 'AUTH', code: 'INVALID_APIKEY' }
18 |
19 | async function getUser (ctx) {
20 | const user = await User.findById(ctx.req.id)
21 | ctx.res = user
22 | }
23 |
24 | async function listUsers (ctx) {
25 | const users = await User.list()
26 | ctx.res = hl(users).map(u => u.toJSON())
27 | }
28 |
29 | async function createUser (ctx) {
30 | const user = new User(ctx.req)
31 | ctx.res = await user.save()
32 | }
33 |
34 | async function checkAPIKey (key, ctx, next) {
35 | const err = createError('Not Authorized', grpc.status.UNAUTHENTICATED, apiKeyErrorMetadata)
36 | if (key !== API_KEY) throw err
37 | await next()
38 | }
39 |
40 | function main () {
41 | app = new Mali(PROTO_PATH, 'UserService')
42 |
43 | app.use(logger())
44 | app.use(
45 | apikey(
46 | { error: { metadata: apiKeyErrorMetadata, code: grpc.status.UNAUTHENTICATED } },
47 | checkAPIKey
48 | )
49 | )
50 | app.use(toJSON())
51 |
52 | app.use({
53 | getUser,
54 | listUsers,
55 | createUser
56 | })
57 |
58 | app.start(HOSTPORT)
59 | console.log(`User service running @ ${HOSTPORT}`)
60 | }
61 |
62 | async function shutdown (err) {
63 | if (err) console.error(err)
64 | await app.close()
65 | process.exit()
66 | }
67 |
68 | process.on('uncaughtException', shutdown)
69 | process.on('SIGINT', shutdown)
70 | process.on('SIGTERM', shutdown)
71 |
72 | main()
73 |
--------------------------------------------------------------------------------