Build from scratch Node.js Enterprise web server (12): remote call

Time:2021-1-25

About remote call

Remote call (RPC) mainly refers to the call of process between servers or clusters. Through remote call, we can get through the data and functions between different systems, or extract and establish a common logic to provide services. The two ends of remote call are also known as the client and server of remote call. Generally, they are many to many, and need to introduce registration and discovery mechanism for governance

Build from scratch Node.js  Enterprise web server (12): remote call

Governance mechanism is usually adapted to local conditions and can be based onZooKeeperConstruction, this chapter will not start.

gRPC

gPRCGoogle open source is a cross language high-performance RPC framework, the underlying useprotobufData exchange has been widely used in Google, Nafi, Cisco and other enterprises. The following is the call process of grpc. The client first performs DNS resolution, and then directly connects to the server according to the resolution result, or obtains the server information from the load balancing / governance service, and then connects:

Build from scratch Node.js  Enterprise web server (12): remote call

This chapter will be based on the work done in the previous chapterhost1-tech/nodejs-server-examples – 11-scheduleAdd remote call function to realize the input and output of a simple message. Now go to the project root directory to install grpc related modules

$yarn add @ grpc / grpc JS @ grpc / proto loader # local installation of @ grpc / grpc JS, @ grpc / proto loader
# ...
info Direct dependencies
├─ @grpc/[email protected]
└─ @grpc/[email protected]
# ...

Plus remote call

adopt.protoThe file defines the remote interface and writes the grpc client and server based on the definition

$MKDIR Src / RPC # new Src / RPC to store remote call logic

$MKDIR Src / RPC / echo # new Src / RPC / echo

$tree SRC - L 1 # show SRC directory content structure
src
├── config
├── controllers
├── middlewares
├── models
├── moulds
├── rpc
├── schedules
├── server.js
├── services
└── utils
// src/rpc/echo/def.proto
syntax = "proto3";

service Echo {
  rpc Get(EchoRequest) returns (EchoResponse) {}
}

message EchoRequest { string message = 1; }

message EchoResponse { string message = 1; }
// src/rpc/echo/client.js
const { resolve } = require('path');
const { promisify } = require('util');
const protoLoader = require('@grpc/proto-loader');
const grpc = require('@grpc/grpc-js');
const { rpc } = require('../../config');

class EchoClient {
  grpcClient;

  async init() {
    const grpcObject = grpc.loadPackageDefinition(
      await protoLoader.load(resolve(__dirname, 'def.proto'))
    );

    this.grpcClient = new grpcObject.Echo(
      `${rpc.domain}:${rpc.port}`,
      grpc.credentials.createInsecure()
    );
  }

  get = async ({ s, logger }) => {
    const { grpcClient } = this;

    const { message } = await promisify(
      grpcClient.get.bind(grpcClient, { message: s })
    )();

    logger.info('Echo/Get Invoked');

    return { message };
  };
}

let client;
module.exports = async () => {
  if (!client) {
    client = new EchoClient();
    await client.init();
  }
  return client;
};
// src/rpc/echo/server.js
const { resolve } = require('path');
const { callbackify } = require('util');
const protoLoader = require('@grpc/proto-loader');
const grpc = require('@grpc/grpc-js');

class EchoServer {
  grpcServer;

  async init() {
    const grpcObject = grpc.loadPackageDefinition(
      await protoLoader.load(resolve(__dirname, 'def.proto'))
    );

    this.grpcServer.addService(grpcObject.Echo.service, this);
  }

  get = callbackify(async (call) => {
    const { message } = call.request;
    return { message };
  });
}

let server;
module.exports = async (grpcServer) => {
  if (!server) {
    server = new EchoServer();
    Object.assign(server, { grpcServer });
    await server.init();
  }
  return server;
};
// src/config/index.js
// ...
const config = {
  //Default configuration
  default: {
    // ...
+    
+    rpc: {
+      domain: 'localhost',
+      port: process.env.PORT_RPC || 9001,
+    },
  },
  // ...
};
// ...

Open grpc log output and initialize:

# .env
LOG_LEVEL='debug'
+
+GRPC_TRACE='all'
+GRPC_VERBOSITY='DEBUG'
// src/utils/logger.js
// ...
+const GRPC_LOGGER_REGEXP = /^.+Z\s+\|\s+/;
+
+function grpcLogger(logger, level = 'debug') {
+  const verbosities = ['debug', 'info', 'error'];
+
+  return {
+    error(severity, message) {
+      if (typeof severity != 'number') {
+        message = severity;
+        severity = 0;
+      }
+
+      if (typeof message != 'string') {
+        message = String(message || '');
+      }
+
+      logger[verbosities[severity] || level](
+        message.replace(GRPC_LOGGER_REGEXP, '')
+      );
+    },
+  };
+}
+
module.exports = logger;

-Object.assign(module.exports, { logging });
+Object.assign(module.exports, { logging, grpcLogger });
// src/rpc/index.js
const { promisify } = require('util');
const grpc = require('@grpc/grpc-js');
const { rpc } = require('../config');
const logger = require('../utils/logger');
const echoClient = require('./echo/client');
const echoServer = require('./echo/server');
const { grpcLogger } = logger;

module.exports = async function initRpc() {
  grpc.setLogger(grpcLogger(logger.child({ type: 'rpc' }), 'debug'));

  // init rpc servers
  const grpcServer = new grpc.Server();
  await echoServer(grpcServer);

  await promisify(grpcServer.bindAsync.bind(grpcServer))(
    `0.0.0.0:${rpc.port}`,
    grpc.ServerCredentials.createInsecure()
  );
  grpcServer.start();

  // init rpc clients
  await echoClient();
};
// src/server.js
const express = require('express');
const { resolve } = require('path');
const { promisify } = require('util');
const initMiddlewares = require('./middlewares');
const initControllers = require('./controllers');
const initSchedules = require('./schedules');
+const initRpc = require('./rpc');
const logger = require('./utils/logger');

const server = express();
const port = parseInt(process.env.PORT || '9000');
const publicDir = resolve('public');
const mouldsDir = resolve('src/moulds');

async function bootstrap() {
+  await initRpc();
  server.use(await initMiddlewares());
  server.use(express.static(publicDir));
  server.use('/moulds', express.static(mouldsDir));
  server.use(await initControllers());
  server.use(errorHandler);
  await initSchedules();
  await promisify(server.listen.bind(server, port))();
  logger.info(`> Started on port ${port}`);
}
// ...

Add grpc client logger and control layer entry:

// src/middlewares/trace.js
const { v4: uuid } = require('uuid');
const morgan = require('morgan');
const onFinished = require('on-finished');
const logger = require('../utils/logger');
const { logging } = logger;

module.exports = function traceMiddleware() {
  return [
    morgan('common', { skip: () => true }),
    (req, res, next) => {
      req.uuid = uuid();
      req.logger = logger.child({ uuid: req.uuid });
      req.loggerSql = req.logger.child({ type: 'sql' });
      req.logging = logging(req.loggerSql, 'info');
+      req.loggerRpc = req.logger.child({ type: 'rpc' });

      onFinished(res, () => {
        // ...
      });

      next();
    },
  ];
};
// src/controllers/echo.js
const { Router } = require('express');
const cc = require('../utils/cc');
const rpcEchoClient = require('../rpc/echo/client');

class EchoController {
  rpcEchoClient;

  async init() {
    this.rpcEchoClient = await rpcEchoClient();

    const router = Router();
    router.get('/', this.get);
    return router;
  }

  get = cc(async (req, res) => {
    const { s = '' } = req.query;
    const message = await this.rpcEchoClient.get({ s, logger: req.loggerRpc });
    res.send({ success: true, message });
  });
}

module.exports = async () => {
  const c = new EchoController();
  return await c.init();
};
// src/controllers/index.js
const { Router } = require('express');
const shopController = require('./shop');
const chaosController = require('./chaos');
const healthController = require('./health');
const loginController = require('./login');
const csrfController = require('./csrf');
+const echoController = require('./echo');

module.exports = async function initControllers() {
  const router = Router();
  router.use('/api/shop', await shopController());
  router.use('/api/chaos', await chaosController());
  router.use('/api/health', await healthController());
  router.use('/api/login', await loginController());
  router.use('/api/csrf', await csrfController());
+  router.use('/api/echo', await echoController());
  return router;
};

visithttp://localhost:9000/api/echo?s=Hello%20RPCYou can see the effect:

Build from scratch Node.js  Enterprise web server (12): remote call

At the same time, you can see sufficient grpc logs on the command line

# ...
08:20:52.320Z DEBUG 12-rpc: dns_resolver | Resolver constructed for target dns:0.0.0.0:9001 (type=rpc)
08:20:52.321Z DEBUG 12-rpc: dns_resolver | Resolution update requested for target dns:0.0.0.0:9001 (type=rpc)
08:20:52.321Z DEBUG 12-rpc: dns_resolver | Returning IP address for target dns:0.0.0.0:9001 (type=rpc)
08:20:52.322Z DEBUG 12-rpc: server | Attempting to bind 0.0.0.0:9001 (type=rpc)
08:20:52.324Z DEBUG 12-rpc: server | Successfully bound 0.0.0.0:9001 (type=rpc)
08:20:52.327Z DEBUG 12-rpc: resolving_load_balancer | dns:localhost:9001 IDLE -> IDLE (type=rpc)
08:20:52.327Z DEBUG 12-rpc: connectivity_state | dns:localhost:9001 IDLE -> IDLE (type=rpc)
08:20:52.327Z DEBUG 12-rpc: dns_resolver | Resolver constructed for target dns:localhost:9001 (type=rpc)
# ...

Source code of this chapter

host1-tech/nodejs-server-examples – 12-rpc

Read more

Build from scratch Node.js Enterprise web server (zero): static services
Build from scratch Node.js Enterprise web server (1): interface and layering
Build from scratch Node.js Enterprise web server (2): Verification
Build from scratch Node.js Enterprise web server (3): Middleware
Build from scratch Node.js Enterprise web server (4): exception handling
Build from scratch Node.js Enterprise web server (5): database access
Build from scratch Node.js Enterprise web server (6): session
Build from scratch Node.js Enterprise web server (7): authentication login
Build from scratch Node.js Enterprise web server (8): network security
Build from scratch Node.js Enterprise web server (9): configuration items
Build from scratch Node.js Enterprise web server (x): log
Build from scratch Node.js Enterprise web server (11): timed tasks
Build from scratch Node.js Enterprise web server (12): remote call
Build from scratch Node.js Enterprise web server (XIII): breakpoint debugging and performance analysis
Build from scratch Node.js Enterprise web server (14): automated testing
Build from scratch Node.js Enterprise web server (XV): summary and Prospect

Recommended Today

020_CSS3

catalog How to learn CSS What is CSS History of development quick get start Advantages of CSS Three ways to import CSS Expansion: two ways of writing external style selector Basic selector Hierarchy selector Structure pseudo class selector attribute selectors Beautify web page elements Why beautify web pages Span label: for the text that needs […]