代码地址如下:
http://www.demodashi.com/demo/12932.html

一、简介

    koa是由Express原班人马打造的,致力于成为一个更小、更富有表现力、更健壮的Web框架,Koa不定制路由,无冗余的中间件,开发设计方案趋向定制化,所以很适合对业务和技术有灵活要求的web场景。


二、应用

    由于restful、加解密、跨域、参数解析、中间件等比较基础,且文档丰富,本小节将直接跳过,侧重于分享以下几点:


  1. 1、路由转发时,如何利用钩子函数机制做到controller层业务解耦
  2. 2、在socket通信中如何动态加载protobuf进行数据格式交换
  3. 3、如何基于websocket绑定相同的端口
  4. 4、如何利用c++编写node扩展库

  • 2.1 业务解耦

    中间件及钩子函数机制皆为业务解耦的有效实现方式,其中中间件模式因其实现方便而应用广泛, 如koa、express、sails中都曾大量用到,

而钩子函数机制在node生态中被大量用到ORM对数据库的操作,如mongoose、waterline,鲜有在controller层的广泛应用,本小节则尝试分享

一个简易的Hooks实现方式,并应用在koa框架中。

编写koa-hooks, 并提交到npm

  1. const hooks = require('hooks')
  2. class ApiHooks {
  3. constructor(ctx, next, cb) {
  4. this._ctx = ctx
  5. this._next = next
  6. this._cb = cb
  7. this._listenerTree = {}
  8. this.addListenerTree()
  9. }
  10. addListenerTree() {
  11. for (let fn in hooks) {
  12. this[fn] = hooks[fn]
  13. }
  14. }
  15. addHooks(listeners) {
  16. const self = this
  17. try {
  18. listeners.map(listener => {
  19. const [method, hooksFn] = listener.split('.')
  20. if(hooksFn.match('before')) self.addFn(method, hooksFn, 'pre')
  21. if(hooksFn.match('after')) self.addFn(method, hooksFn, 'post')
  22. })
  23. } catch (err) {
  24. console.log('err:', err)
  25. }
  26. }
  27. addFn(method, hooksFn, hook) {
  28. const self = this
  29. self[hook](method, async (next) => {
  30. await self[hooksFn](self._ctx, next, self._cb)
  31. })
  32. }
  33. }
  34. module.exports = ApiHooks

编写一个restful风格接口/v1/verb/get,继承ApiHooks, 添加对应的钩子函数beforeVerbCheckLogin实现登录检查

  1. /**
  2. * Created by Joseph on 18/09/2017.
  3. */
  4. const Api = require('koa-hooks').Api
  5. const VerbService = require('../../services/verb.js')
  6. class VerbApi extends Api {
  7. constructor(ctx, next, cb) {
  8. super(ctx, next, cb)
  9. this.addHooks([
  10. 'verbGetOnThisRequest.beforeVerbCheckLogin',
  11. 'verbPostOnThisRequest.beforeVerbCheckLogin',
  12. 'verbPutOnThisRequest.beforeVerbCheckLogin',
  13. 'verbDeleteOnThisRequest.beforeVerbCheckLogin',
  14. ])
  15. }
  16. async beforeVerbCheckLogin(ctx, next, cb) {
  17. const data = await VerbService.beforeVerbCheckLogin(ctx, next)
  18. data ? cb(ctx, data) : await next()
  19. }
  20. async verbGetOnThisRequest(ctx, next, cb) {
  21. const data = await VerbService.verbGetOnThisTest(ctx, next)
  22. data ? cb(ctx, data) : await next()
  23. }
  24. async verbPostOnThisRequest(ctx, next, cb) {
  25. const data = await VerbService.verbPostOnThisTest(ctx, next)
  26. data ? cb(ctx, data) : await next()
  27. }
  28. async verbPutOnThisRequest(ctx, next, cb) {
  29. const data = await VerbService.verbPutOnThisTest(ctx, next)
  30. data ? cb(ctx, data) : await next()
  31. }
  32. async verbDeleteOnThisRequest(ctx, next, cb) {
  33. const data = await VerbService.verbDeleteOnThisTest(ctx, next)
  34. data ? cb(ctx, data) : await next()
  35. }
  36. }
  37. module.exports = (ctx, next, cb) => new VerbApi(ctx, next, cb)

启动服务,请求接口http://127.0.0.1:3000/v1/verb/get,可以发现此钩子函数已经生效

注释掉//'verbGetOnThisRequest.beforeVerbCheckLogin', 再次请求接口,可以发现在需求变动情况对源码修改极少,代码可维护性提升


  • 2.2 protobuf数据协议

    protobuf是谷歌开源的是一种轻便高效的结构化数据存储格式, 且平台无关、语言无关、可扩展,通常用在tcp编程对数据传输要求较高的场

景,protobuf兼有json的可读性,且传输效率远大于json、xml等,非常适合流式数据交换。

A) 根据文件名及message动态加载protobuf

  1. const protobuf = require('protobufjs')
  2. const protoPath = '/Users/dreamboad/Projects/koa-service/message/'
  3. class Proto {
  4. async loadByName(protoName, messageName, obj, type) {
  5. return new Promise((resolve, reject) => {
  6. protobuf.load(`${protoPath}${protoName}.proto`, (err, root) => {
  7. if (err) {
  8. return console.log(err) || resolve()
  9. }
  10. const data = root.lookupType(`${protoName}.${messageName}`)
  11. if (type === 'encode' && data.verify(obj)) {
  12. return console.log('encode err') || resolve()
  13. }
  14. switch (type) {
  15. case 'decode':
  16. return resolve(data.toObject(data.decode(obj), { objects: true }))
  17. case 'encode':
  18. return resolve(data.encode(data.create(obj) || '').finish())
  19. }
  20. })
  21. })
  22. }
  23. async deserialize(protoName, messageName, obj) {
  24. return await this.loadByName(protoName, messageName, obj, 'decode')
  25. }
  26. async serialize(protoName, messageName, obj) {
  27. return await this.loadByName(protoName, messageName, obj, 'encode')
  28. }
  29. }
  30. module.exports = new Proto()

B) 编写soket client

  1. /**
  2. * 1、动态加载protobuf
  3. * 2、socket数据流断包、粘包处理(TODO)
  4. * 3、心跳机制、及断线重连
  5. */
  6. const net = require('net')
  7. const [HOST, PORT] = ['127.0.0.1', 9999]
  8. const client = new net.Socket()
  9. const connection = () => {
  10. client.connect(PORT, HOST, () => { console.log('CONNECTED TO: ' + HOST + ':' + PORT)})
  11. }
  12. client.on('data', (data) => {
  13. console.log(`${HOST}:${PORT} CONNECT DATA: `, data)
  14. })
  15. client.on('error', (e) => {
  16. console.log(`${HOST}:${PORT} CONNECT ERROR: ` + e)
  17. })
  18. client.on('timeout', (e) => {
  19. console.log(`${HOST}:${PORT} CONNECT TIMEOUT: ` + e)
  20. })
  21. client.on('end', (e) => {
  22. console.log(`${HOST}:${PORT} CONNECT END: ` + e)
  23. })
  24. client.on('close', (e) => {
  25. console.log(`${HOST}:${PORT} CONNECT CLOSE: ` + e)
  26. if (client.destroyed) {
  27. client.destroy()
  28. }
  29. setTimeout(connection, 3000)
  30. })
  31. process.on('exit', () => {
  32. client.destroy()
  33. client.on('close', () => {
  34. console.log('Connection closed')
  35. })
  36. })
  37. // 连接 客户端
  38. module.exports = { connection, client }

C) 在soket通信中序列化/反序列化json数据

  1. /**
  2. * 序列化、反序列化
  3. */
  4. const crypto = require('crypto')
  5. const Proto = require('./protobuf')
  6. class SocketProto {
  7. async doTranslation(obj, protoName, messageName, operation) {
  8. try {
  9. switch (operation) {
  10. case 'decode':
  11. return await Proto.deserialize(obj, protoName, messageName)
  12. case 'encode':
  13. return await Proto.serialize(obj, protoName, messageName)
  14. }
  15. } catch (error) {
  16. console.log(error)
  17. }
  18. }
  19. async decode(obj, protoName, messageName) {
  20. return await this.doTranslation(obj, protoName, messageName, 'decode')
  21. }
  22. async encode(obj, protoName, messageName) {
  23. return await this.doTranslation(obj, protoName, messageName, 'encode')
  24. }
  25. }
  26. module.exports = new SocketProto()

D) 连接服务器,读写流式数据,并用proto解析

  1. const { connection, client } = require('./socket_client')
  2. const SocketProto = require('./socket_protobuf')
  3. const config = require('../config/').msgIdConfig
  4. connection()
  5. const writer = module.exports.writer = async (protoName, messageName, obj) => {
  6. const w = await SocketProto.encode(protoName, messageName, obj)
  7. return client.write(w)
  8. }
  9. const reader = module.exports.reader = async (protoName, messageName, obj) => {
  10. const r = await SocketProto.decode(protoName, messageName, obj)
  11. return r
  12. }
  13. client.on('data', (buf) => {
  14. chooseFnByMsg('', 'basemsg', buf)
  15. })
  16. const chooseFnByMsg = (msgId, type, obj) => {
  17. if (msgId) {
  18. if (!config[msgId] || !config[msgId].req || !config[msgId].res) {
  19. return console.log('noting to do: ', msgId)
  20. }
  21. }
  22. switch (type) {
  23. case 'basemsg':
  24. return reader(config.head.res.pName, config.head.res.mName, obj)
  25. case 'write':
  26. return writer(config[msgId].req.pName, config[msgId].req.mName, obj)
  27. case 'read':
  28. return reader(config[msgId].res.pName, config[msgId].res.mName, obj)
  29. default:
  30. console.log('noting to do default: ', msgId)
  31. break
  32. }
  33. }
  34. chooseFnByMsg(1, 'write', { Field: "String" })
  35. module.exports = chooseFnByMsg

E) server及client分别在终端打印结果

  • 2.3 websocket

A) koa server

  1. const app = new Koa()
  2. // web socket
  3. const server = require('http').Server(app.callback())
  4. const io = require('socket.io')(server)
  5. io.on('connection', client => {
  6. console.log('new connection:')
  7. client.on('news', (data, cb) => {
  8. console.log('news:', data)
  9. })
  10. client.on('disconnect', () => {
  11. console.log('disconnect:')
  12. })
  13. })

B) websocket client

  1. const client = require('socket.io-client').connect('http://localhost:3000')
  2. client.emit('news', "hello world")

  • 2.1 C++插件

    IO异步及高并发是Node的优势,但若在需要密集计算、集成基于C++的第三方SDK等场景时,Node的劣势则显现出来,此时可以基于node-gyp来嵌入集成C++解决以上等问题。

A) 安装node-gyp

  1. cnpm install -g node-gyp

A) 编辑binding.gyp、C++、Node调用模块

  1. {
  2. "targets": [
  3. {
  4. "target_name": "demo",
  5. "sources": ["src/demo.cc"]
  6. },
  7. {
  8. "target_name": "test_params_nocb",
  9. "sources": ["src/test_params_nocb.cc"]
  10. },
  11. {
  12. "target_name": "test_function_nocb",
  13. "sources": ["src/test_function_nocb.cc"]
  14. },
  15. {
  16. "target_name": "test_params_function_nocb",
  17. "sources": ["src/test_params_function_nocb.cc"]
  18. }
  19. ]
  20. }
  1. // test_function_nocb.cc
  2. #include <node.h>
  3. namespace demo {
  4. using v8::Function;
  5. using v8::FunctionCallbackInfo;
  6. using v8::Isolate;
  7. using v8::Local;
  8. using v8::Null;
  9. using v8::Object;
  10. using v8::String;
  11. using v8::Value;
  12. void RunCallback(const FunctionCallbackInfo<Value>& args) {
  13. Isolate* isolate = args.GetIsolate();
  14. Local<Function> cb = Local<Function>::Cast(args[0]);
  15. Local<Value> argv[1] = { String::NewFromUtf8(isolate, "hello world") };
  16. cb->Call(Null(isolate), 1, argv);
  17. }
  18. void Init(Local<Object> exports, Local<Object> module) {
  19. NODE_SET_METHOD(module, "exports", RunCallback);
  20. }
  21. NODE_MODULE(test_function_nocb, Init)
  22. } // namespace demo
  1. module.exports.embeddedProxy = (cb, params) => {
  2. return new Promise((resolve, reject) => {
  3. try {
  4. return cb((data) => { resolve(data) }, params)
  5. } catch (err) {
  6. return resolve({ data: "调用失败", code: -1 })
  7. }
  8. })
  9. }

C) 编译C++

  1. node-gyp configure
  2. node-gyp build



D) 定义路由并调用接口


项目文件目录结构截图

三、参考

代码地址如下:
http://www.demodashi.com/demo/12932.html

注:本文著作权归作者,由demo大师代发,拒绝转载,转载需要作者授权

Node.js进阶篇-koa、钩子函数、websocket、嵌入式开发的更多相关文章

  1. 2. web前端开发分享-css,js进阶篇

    一,css进阶篇: 等css哪些事儿看了两三遍之后,需要对看过的知识综合应用,这时候需要大量的实践经验, 简单的想法:把qq首页全屏另存为jpg然后通过ps工具切图结合css转换成html,有无从下手 ...

  2. web前端开发分享-css,js进阶篇

    一,css进阶篇: 等css哪些事儿看了两三遍之后,需要对看过的知识综合应用,这时候需要大量的实践 经验, 简单的想法:把qq首页全屏另存为jpg然后通过ps工具切图结合css转换成html,有无 从 ...

  3. Node.js学习准备篇

    这里写个Node.js 准备篇包含内容有node.js 的安装,命令行运行node.js 文件,使用webStrom 编写 node.js 时有提示功能,并用webStrom 运行 Node.js 其 ...

  4. js进阶 13 jquery动画函数有哪些

    js进阶 13 jquery动画函数有哪些 一.总结 一句话总结: 二.jquery动画函数有哪些 原生JavaScript编写动画效果代码比较复杂,而且还需要考虑兼容性.通过jQuery,我们使用简 ...

  5. Node.js自学笔记之回调函数

    写在前面:如果你是一个前端程序员,你不懂得像PHP.Python或Ruby等动态编程语言,然后你想创建自己的服务,那么Node.js是一个非常好的选择.这段时间对node.js进行了简单的学习,在这里 ...

  6. Node.js进阶:5分钟入门非对称加密方法

    前言 刚回答了SegmentFault上一个兄弟提的问题<非对称解密出错>.这个属于Node.js在安全上的应用,遇到同样问题的人应该不少,基于回答的问题,这里简单总结下. 非对称加密的理 ...

  7. Node.js 入门篇

    Node.js 使用C++开发的. Node.js是一个事件驱动服务端JavaScript环境,只要能够安装相应的模块包,就可以开发出需要的服务端程序,如HTTP服务端程序.Socket程序等. No ...

  8. 基于Unix Socket的可靠Node.js HTTP代理实现(支持WebSocket协议)

    实现代理服务,最常见的便是代理服务器代理相应的协议体请求源站,并将响应从源站转发给客户端.而在本文的场景中,代理服务及源服务采用相同技术栈(Node.js),源服务是由代理服务fork出的业务服务(如 ...

  9. Node.js系列基础学习-----回调函数,异步

    Node.js基础学习 Node.js回调函数 Node.js异步编程的直接体现就是回调,异步编程依托回调来实现,但不是异步.回调函数在完成任务后就会被调用,Node有很多的回调函数,其所有的API都 ...

随机推荐

  1. yii2.0在model里自定义数据表

    无需多言,直接撸代码 class Zhuanjia extends \yii\db\ActiveRecord { public static function tableName() { return ...

  2. MySQL InnoDB MVCC深度分析

    关于MySQL的InnoDB的MVCC原理,很多朋友都能说个大概: 每行记录都含有两个隐藏列,分别是记录的创建时间与删除时间 每次开启事务都会产生一个全局自增ID 在RR隔离级别下 INSERT -& ...

  3. SDL安装小结

    SDL是一个基于C的简易实现,安装过程中也多亏了,各位大神的助攻,这里简单mark一下遇到的问题,以备查找: 关于VS的版本:目前文档里确定支持的VS为2008到2013,我的VS是2013,2015 ...

  4. 2、Flask实战第2天:URL传参

    当我们访问网站/的时候,会执行hell_world函数,并把这个函数的返回值返回给浏览器,这样浏览器就显示hello world了 @app.route('/') def hello_world(): ...

  5. Difference between [0-9], [[:digit:]] and \d

    Yes, it is [[:digit:]] ~ [0-9] ~ \d (where ~ means aproximate).In most programming languages (where ...

  6. 【可持久化Trie】模板

    总算找到个能看懂的了,orz Lavender. #define INF 2147483647 #define N 100001 #define MAXBIT 31 int root[N],ch[N* ...

  7. WebService综述

    一.序言 大家或多或少都听过WebService(Web服务),有一段时间很多计算机期刊.书籍和网站都大肆的提及和宣传WebService技术,其中不乏很多吹嘘和做广告的成分.但是不得不承认的是Web ...

  8. 利用Cain+wireshark进行协议分析

    Cain抓包指南 1.简介: 在开发测试工作中经常有捕抓设备间通信报文的需求,但有时候被抓包的设备并不直接和进行抓包的主机或设备进行通信,因此会达不到想要的效果.解决该问题的常见方法有: (1).为被 ...

  9. 64945e3dtw1dii6vfdr19j.jpg(PNG 图像,1497x929 像素)

    64945e3dtw1dii6vfdr19j.jpg(PNG 图像,1497x929 像素)

  10. DataRow 数组转化成DataTable

    #region 封装DataTable DataTable dt = null; if (newRows.Length > 0) { dt = newRows[0].Table.Clone(); ...