惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

V
Vulnerabilities – Threatpost
Blog — PlanetScale
Blog — PlanetScale
博客园_首页
Y
Y Combinator Blog
The Cloudflare Blog
V
V2EX
Microsoft Security Blog
Microsoft Security Blog
V
Visual Studio Blog
MyScale Blog
MyScale Blog
Martin Fowler
Martin Fowler
T
Tailwind CSS Blog
P
Privacy International News Feed
S
Securelist
T
The Blog of Author Tim Ferriss
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
T
Tor Project blog
P
Proofpoint News Feed
Project Zero
Project Zero
D
Darknet – Hacking Tools, Hacker News & Cyber Security
MongoDB | Blog
MongoDB | Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
H
Help Net Security
美团技术团队
The GitHub Blog
The GitHub Blog
SecWiki News
SecWiki News
酷 壳 – CoolShell
酷 壳 – CoolShell
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Security Latest
Security Latest
NISL@THU
NISL@THU
www.infosecurity-magazine.com
www.infosecurity-magazine.com
S
Secure Thoughts
Cyberwarzone
Cyberwarzone
Scott Helme
Scott Helme
IT之家
IT之家
Last Week in AI
Last Week in AI
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
C
CERT Recently Published Vulnerability Notes
Google DeepMind News
Google DeepMind News
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
P
Privacy & Cybersecurity Law Blog
A
About on SuperTechFans
Microsoft Azure Blog
Microsoft Azure Blog
M
MIT News - Artificial intelligence
Spread Privacy
Spread Privacy
S
Schneier on Security
L
Lohrmann on Cybersecurity
S
Security Affairs
Apple Machine Learning Research
Apple Machine Learning Research
爱范儿
爱范儿
云风的 BLOG
云风的 BLOG

囧克斯

奔四了,写给40岁的自己 有序退网 我和 Vue.js 的十年 我参与《代码之外 Beyond Code》的故事 博客站迁移至 VitePress 的备忘 写给我的奶奶 中文格式化小工具 zhlint 及其开发心得 vue-mark-display:用 markdown 语法轻松撰写幻灯片 我对技术会议的一些看法 VueConf Hangzhou 见闻 第四届 CSSConf CN 见闻 [译]Web 表单的未来 [译]苹果正在做一些他们的程序员明摆着不想要的东西 [译]为什么我不会无偿加班且你也不应该 [译]如果管理是唯一可走的路,那就完蛋了 [译]如何撰写 Git 提交信息 [译]C 程序的原则 Vue 2.0 来了! Weex 近 4 个月的开源之路 Weex 在 JS Runtime 内的多实例管理 我理解的 SPA 我理解的 Flux 架构 【整理】Vue 2.0 自 beta 1 到 beta 4 以来的主要更新 通过一张图走进 Vue 2.0 Code Review for Vue 2.0 Preview Vue 2.0 发布啦! 务实的小而美 Vue.js 1.0.0 发布了! [译]如何成为一名卓越的前端工程师 手机淘宝前端的图片相关工作流程梳理 [译]如何让办公室政治最小化 Vue.js 源码学习笔记 从原型到发布——“团队时间线” 1.0 开发心得 Vue + webpack 项目实践 webcomponents 笔记 之 配置管理 小秀个人的13~14年摄影作品 (共19张) 由今年D2前端论坛想到的 [译]CSS命名神马的真心难 [译]Git 分支的最佳实践 [译]撰写可测试的 JavaScript [译]语义化版本管理 [译]通过HTML5 Canvas API调节图像的亮度和颜色 [译]JavaScript V8性能小贴士 [译]视觉差,走起! [译]Chrome开发者工具中评估性能的五大新特性 精气神儿 细节无微不至,彩屏让人又爱又恨——新老“神机”大对决:Nokia 1050 vs Nokia 1202 秦升拿到红牌之后…… 用Sass重新整理自己的博客主题样式 Connect中间件使用手册 巧用 RequireJS Optimizer 给传统的前端项目打包 编辑器小调查结果 [译]JSLint 文档 烟火——写给蛇年的傲游和我 小秀个人的全年摄影作品 (共15张) 2012年终毫无正能量的总结 标签?ID?还是CLASS? 微创新=伪创新 HTML5峰会归来 分享bookmarklet一则:随意阅读 国际羽联和中国队之间的恶性循环 ZeroClipboard 学习笔记 “思考人生” 听杨东杰弹吉他 学习精髓 网站装修笔记20120426 网站装修笔记20120414 网站装修笔记20120406 分享Typecho插件:百度统计助手 我的得奖感言 网站装修笔记20120331 用CSS3制作尖角标签按钮样式 分享Typecho插件:Markdown 解析器 + 编辑器 分享Typecho皮肤:我的字很大 汇总自己过去的一些HTML5科普文章 html5中的消息通信简介 + 我的新网站开张 网站装修计划 Typed Arrays 是神马? 文明看球 HTML5中的文件处理 之 File Writer API HTML5中的文件处理 之 File API 独生子女、互相等和不耐烦 IndexedDB技术简介(四) IndexedDB技术简介(三) IndexedDB技术简介(二) IndexedDB技术简介(一) 把博客的字体进一步调大,同时去掉了侧边栏 “模仿别人是为了找到自己” 写给我未婚妻的2011年 写给自己的2011年 手把手教你入门EaselJS做HTML5动画 “吃了吧,不吃就浪费了” 写给傲游的2011年 所谓专业 写给HTML5的2011年 对HTML5中LocalStorage的一些使用建议 回味一下我2011年看到的最过瘾的5场足球比赛 浅浅浅谈开饭店被顾客吃出“异物”的用户体验 前端的本职 继续对Mac穷追猛打 之 修改原生按钮文字大小的技巧
用 Koa 写服务体验
勾三股四 · 2015-06-21 · via 囧克斯

Koa

晒一下自己用 Koa next generation web framework for node.js 写的一个 web 服务

这个 web 服务主要是做内容的列表展示和搜索的 (可能说得比较抽象,但确实是 web 服务最常需要做的事情) 主要的文件一共就2个:

  • app.js 主程序
  • lib/model.js 数据层

其中 model.js 是和具体业务逻辑相关的,就不多介绍了,这也不是 Koa 的核心;而 app.js 的代码可以体现 Koa 的很多优点,也使得代码可以写得非常简练而去清晰——这是我自己都完全没有想到的事情


加载资源和相关依赖库 ​

// resources

var koa = require('koa')
var app = koa()

var logger = require('koa-logger')
var route = require('koa-route')

var fs = require('fs')
var path = require('path')
var extname = path.extname

var views = require('co-views')
var render = views('./views', {
  map: { html: 'ejs' }
})

var model = require('./lib/model')

其中:

  1. koa 是最核心的库,appkoa 生成的 web 服务主程序
  2. koa-loggerkoa-route 都是koa官方开发的“中间件”,分别用来打印日志和路由设置,路由设置稍后还会提到
  3. fspath 都是 Node 的官方包,用来进行本地文件和路径相关的处理,辅助性质的
  4. co-views 是用来渲染模板的库,而 render 是它生成的实例,这个用法也跟传统用法不太一样,稍后会提及

Web 服务工作流 ​

// workflow

app.use(logger())

app.use(route.get('/', list))
app.use(route.get('/page/:page', list))
app.use(route.get('/search/:keywords', search))
app.use(route.get('/search/:keywords/:page', search))

app.use(function *(next) {
  if (!this.path.match(/^\/assets\//)) {
    yield* next
    return
  }
  var path = __dirname + this.path
  var fstat = yield stat(path)

  if (fstat.isFile()) {
    this.type = extname(path)
    this.body = fs.createReadStream(path)
  }
})

app.use(function *(next) {
  if (this.needRendered) {
    this.body = yield render(this.templateView, {cache: false, data: this.templateModel})
  }
  yield* next
})


// utils

function stat(file) {
  return function (done) {
    fs.stat(file, done)
  }
}

这部分代码是用来规划服务器工作流的,从请求被接受到响应被发出,整个过程都在这段代码里一览无余。工作流设计的主要的用法是 app.use(...)。里面的参数其实就是一个 generator。

  1. 首先是打开日志
  2. 然后是分发路由,这里可以看到,有首页、列表、搜索、搜索列表 4 种设计,分别对应到了各自的处理方,listsearch 其实都是在利用 lib/model 在生成数据,准备给模板进行渲染。这里的原理也有特殊之处,稍后会看到
  3. 再看紧随其后的两个 app.use,分别是处理静态资源目录 assets 和对模板+数据进行渲染

所以完整的工作流可以理解为:

  1. 请求页面 (列表或搜索) -> logger -> 路由分发 -> listsearch -> 模板渲染 -> 回应
  2. 请求静态资源 -> logger -> 找到对应的 assets 文件 -> 回应

function *() {}yield 是啥? ​

这个其实是 Koa 的精髓所在,在介绍它之前,我们先把 listsearch 的代码也贴出来:

// routes

function *list(page, next) {
  next = arguments[arguments.length - 1]
  this.templateView = 'page'
  this.templateModel = yield model.list({page: page})
  this.needRendered = true
  yield *next
}

function *search(keywords, page, next) {
  next = arguments[arguments.length - 1]
  this.templateView = 'search'
  this.templateModel = yield model.search({keywords: keywords, page: page})
  this.needRendered = true
  yield *next
}

大家会发现,首先 app.use(...)route.get(path, ...) 传入的参数都是一种写得很像函数的东西,但不同之处是函数的写法是 function foo() {...},而这里的写法多了一个星号,即 function *foo() {}。这种写法其实就是 ES6 里的 generator。而 yield 正是配合这个写法的一种语法。

有关 ES6 generator 的基础知识,建议大家来 @兔哥 的这个 ES6 教程网页来学习,这里不做原理方面的赘述。但我想说的是,由于 web 服务的处理本身就是“一层一层”的,并且有些处理是可以同步的,有些是只能异步的,我们不免要精心设计很多中间件并保障它的可扩展性,同时尽量简化异步操作的写法保障它的可读性。

有了 ES6 generator 和 yield 之后,我们的每一层中间件都可以从流程上看成一个以 yield *next 语句切分出来的 “三明治”:

function *(next) {
  // 下一步之前的操作
  yield *next // 进行下一步
  // 所有逻辑处理完之后的补充操作
}

而且这个“下一步”是不介意是不是异步行为,都可以这样简单描述清楚的。

koa workflow

后头看我们设计的整个工作流的实现:

workflow

我们这里的逻辑基于全部是出现在 yield *next 之前的,但是如果你需要在临发出响应之前做点什么,就可以写在其后面了

co-views 的用法 ​

co-views 其实是对通用模板引擎渲染平台 consolidate 的封装,consolidate 应该算是 express.js 时代非常重要的一个库,它支持包括 ejs, mustache, swig 等各种模板渲染并提供统一的 api 调用方法。根据对 co-views 源码的分析,它把 consolidate 统一的 api 又封装成了 return function (done) {...} 的形态,这样源代码中的 yield render(view, model) 就能够融入 generator 的逻辑之中。

值得一提的是,源代码中 yield render(view, model) 这里的 model 传入了一个 {cache: false} 的参数,这会意味着模板不会被缓存,每次修改模板文件之后,在不重启服务的情况下,刷新页面就可以看到最新的效果。这个选项是针对开发环境设置的,为了保障线上环境的运行性能和效率,这个选项应该是不需要的。

lib/model 的用法 ​

同上,我们在 lib/model.js 里封装的 yield model.list({page: page})yield model.search({keywords: keywords, page: page}) 也都会生成形如 return function (done) {...} 的返回值,以融入 generator 的逻辑之中。

最后,监听端口 ​

// listen

app.listen(3000)
console.log('listening on port 3000')

That's it

后记 ​

在首次尝试用 generator 的方式编写 web 服务的时候,我自己一开始总会把 yield 的位置、yield 后面要不要加星号、function 后面要不要加星号、app.use() 的调用顺序这几件事情弄得乱糟糟的,可能还是对 generator 和 koa 的理解不够深入,不过逐渐写着写着,感受到了更多的爽和快感。到最后用如此简单的一个 js 文件完成了全部的功能和逻辑串联,还是觉得很兴奋的。大家如果感兴趣也可以搞来玩一玩,写点自己平时用得到用不到的小玩意儿体验一下:)