├── .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 | --------------------------------------------------------------------------------