首页

有时发博客,有时发牢骚。

又是一些 Express.js Middleware

实习一个多月,天天对着 Node.js,总该思考点什么了。

Express.js 框架的 Middleware 机制设计得很巧妙。实际上,HTTP 请求是交给下一层的 Connect 处理的,Express.js 在其上丰富了 API。Express.js 的 Middleware 实际上是一套统一接口的处理函数,HTTP 请求被 Node.js 的 HTTP Server 模块接收,由 Connect 负责诸如 POST Body、Querystring、Cookie 之类的解析,然后经由 Express.js 逐个 Middleware 处理,一个 Middleware 处理完就丢给下一个 Middleware,直到到达开发者的业务代码(当然也有的还没到这里已经被返回了,比如静态文件)。

备个忘,近期想抽时间把玩一下的 Express.js (Connect) Middleware

静态文件管理,类似 Ruby on Rails 里的 pipeline。

通过在模板文件里声明需要用到的静态文件(比如 jQuery 等),自动生成相应的 script 和 link 标签。

开发环境下会为每个文件都生成一个标签;生产环境下会把所有文件整合,并配合 MD5 文件名和过期时间来控制缓存。

一个很简单的使用场景的封装:保存一条消息(内部通过 Session 实现),在下一个页面取出。取出过的消息自动销毁。

至于如何把消息显示出来,则是由你自己来负责了。这个 Middleware 实际上只是负责一次性消息的存放和取出管理。

配合 Passport.js 框架来判断用户是否登录,未登录则转跳到指定页面。

同样也是一个经常用到的片段的封装。

好吧,照着Connect 在 GitHub 上的 Wiki 页翻了这么几个 Middleware。TJ 大神是一位神一般的大神(这句话。。。),他开源的项目:Connect、Express、Mongoose、Component、Mocha、Should。没错,从前端到后端、从数据库到单元测试一条龙服务 OTL。

/əʊ'fʌk/

OAuthic For Google APIs

最近在做一个服务,需要调用多个开放平台的 API。

对比了他们各自的 SDK 和一些 OAuth 2.0 请求库之后,决定自己封装一个。

OAuth 2.0 协议本身不复杂,但各家的实现各有千秋(尤其国内,尤其某「类推特」网站)。因此这套库的方式是,设计好统一的 API,然后针对各个不同服务实现具体的代码。

目前已经完成了 Google API新浪微博的封装。使用很简单:

// 准备
var client = require('oauthic-google').client({
    clientId: '你的 Client ID'
  , clientSecret: '你的 Client Secret'
  })
  .token('已有的 Access Token', expiresAt)
  .refresh('你的 Refresh Token', function (token, expiresAt, next) {
    // saveToDb(token)
    return next()
  })
  .expired(function (token) {
    // log(token + ' has expired and could not be refreshed.')
  })

// 请求
client.get('/mirror/v1/timeline', function (err, res, timeline) {
  // ...
})

主要特点:

  • 基于强大的 mikeal/request 库封装,请求接口简洁明了
  • 请求 URL 简化,可以用 /mirror/v1/timeline,也可以用完整的 https://www.googleapis.com/mirror/v1/timeline
  • 其它等你去发掘

Enjoys!

折腾了一下 Terminal

装逼的终端

闲来无事折腾一下工作环境。

之前已经听说 iTerm2 是个好东西了,之前也听说过 zsh 是个好东西了。

动手。

终端应用:iTerm2

下载安装,这个你懂的。

Command + , 打开偏好设定窗口:

  1. Profiles / Window,把 Settings for New Windows 中的 Style 改成 Left of Screen 让它在屏幕左侧显示(在宽屏显示器上效果不错)
  2. Keys,勾选 Hotkey 中的 Show/hide iTerm2 with a system-wide hotkey,然后设定一个热键。我个人偏爱 Command + .

现在按 Command + . 就可以随时调出或者隐藏 iTerm2 了。

配色方案:Solarized Dark

下载,解压,打开 iTerm2 的偏好设定,Profiles / Colors,最下面的 Load Presets ... / Import... 加载 iterm2-colors-solarized/Solarized Dark.itermcolors 配色方案。

Z-Shell

系统自带了 4.0 版的 zsh,但我们可以用 brew install 安装最新的 5.X。

brew install zsh
sudo rm /bin/zsh    # 替换系统自带 zsh
sudo ln -s `brew --prefix zsh`/bin/zsh /bin/zsh
chsh -s /bin/zsh  # 切换系统当前用户的默认 shell 为 zsh

安装完毕,Command + W 关闭 iTerm2 当前窗口,然后按 Command + . 重新打开,此时 shell 已经换成 zsh 了。

oh-my-zsh

这是一套十分强大的 zsh 配置方案。

curl -L https://github.com/robbyrussell/oh-my-zsh/raw/master/tools/install.sh | sh

下载这个字体补丁并安装,给系统中的 Menlo 字体进行增补。

打开 iTerm2 的偏好设定,Profiles / Text,把 Regular Font 和 Non-ASCII Font 都换成 Menlo,大小为 14pt Regular。

编辑 ~/.zshrc 文件:

ZSH_THEME="agnoster"          # 使用 agnoster 主题,很好很强大

DEFAULT_USER="你的用户名"     # 增加这一项,可以隐藏掉路径前面那串用户名

plugins=(git brew node npm)   # 自己按需把要用的 plugin 写上

其中插件可以看这里

另外,建议把末尾的 export PATH 稍微调整一下,比如 Homebrew 就建议 /usr/local/bin 应该优先于 /usr/bin;另外也可以自己加上比如 Ruby Gems 目录 /usr/local/opt/ruby/bin、Node.js NPM 目录 ~/bin 等。

export PATH=/usr/local/bin:/usr/local/sbin/:$HOME/bin:$PATH

关于 Homebrew 的路径,比如 zsh 这个包可以通过 brew --prefix zsh 知道它的目录是 /usr/local/opt/zsh,关于这些链接:

  • /usr/local/opt/zsh 目录 -> /usr/local/Cellar/zsh/版本号 目录
  • /usr/local/bin/zsh 文件 -> /usr/local/Cellar/zsh/版本号/bin/zsh 文件

所以就有了上面那条 chsh -s 命令的写法。

重新打开 iTerm2 窗口,配置完成~

Jekyll + GFM

妈蛋,说 GitHub Pages 跑的 Jekyll 不支持 GFM 的。。。

绕了弯路了。

编辑 _config.yml,加上一句:

markdown: redcarpet

完事儿。


把之前的文章都搬过来了,CSS 样式还要继续优化一下。先测试一段时间,然后再把这个主题开源。

随口说说

好了,主题基本上完成了,这次应该真的是第一篇博客了。

Markdown 真的很激发写作欲望的!

继续谈 RESTful API:关于鉴权

关于 API 的鉴权,已经有很多人讲过了,尝试结合自己的理解总结一下。

OAuth 2.0 定义了一个很周全的认证框架(所以后来从「协议」改称「框架」了),值得参考。

分开两种大情景来说吧。

有用户参与的

什么叫有用户参与的呢?你提供服务,用户使用服务,第三方应用需要你授权它们获取用户数据,就是这么回事。

OAuth 2.0 定义了三种流程:

前两种的特征是,用户点击了第三方应用的授权按钮之后会转跳到服务提供者的页面,让用户确认授权。第三种则是填完用户名和密码后就直接完成授权过程了。考虑到第三方应用的安全无法保证,所以提供商一般是不会向关系不太熟悉的第三方开放第三种方式的。

具体的流程已经有很多资料了,不详说。

没用户参与的

这个好理解,API 的调用者不需要知道当前登录的是谁。

这种情景比较好理解,比如匿名评论,比如访问公共资源,等等。

有三种设计:

  • 完全公开:根本不需要在意谁在调用 API 的。
  • Basic Auth:将 App Key 和 App Secret 作为 HTTP Basic Auth 中的用户名和密码来请求。
  • OAuth 2.0 中的 Client Credentials Grant:类似第二种,不过流程更加规范,首先用 App Key 和 App Secret 来获得 Access Token,然后用 Access Token 来调用其它接口。

后两种,个人觉得需要看情况来选择。如果你的服务就只有两三个接口,显然 OAuth 2.0 增加了一次获取 Access Token 的请求就很多余了。

总结

当然了,不是所有 API 一开始就是为开放平台而设计的。如果只是为了诸如自己的手机客户端设计 API 的话当然不必完全实现 OAuth 规范。不过,设计时尽量贴近、避免使用 OAuth 的命名来做与 OAuth 不一样的事情,以后要做开放也能够避免大改。


传送门:总结一下 RESTful API 的设计

第一篇文章

好吧,这应该就是第一篇文章了。

Jekyll 不支持 GFM 实在是蛋碎,不知道有没有什么变通的方法。

// 这是一段 GFM 才有的代码块
var date = new Date();
window.alert(date)
GET / HTTP/1.1
Host: my.server.com
common block
with
many
lines
a tranditional
block
with suo jin
before

inline

a common inline code that needn't parse.

秘诀在此

再谈 RESTful API:版本化的实现

上一篇只是写了个大概,上课无聊再写写实现之类的事情。

版本化对于 API 的发展是好事。

能够让后端不同版本的 API 在不同进程中运行固然也很理想,但如何在均衡器(比如 nginx)上让不同版本的请求反向代理到不同的后端上呢?这个我也不知道。(突然就知道为什么很多服务都是通过 URL 来区分版本了)


刚刚找到了一段 nginx conf,照着写了一个,不知道行不行

map $http_accept $api_version {
  default                       api_v3;
  ~application/vnd\.bestng\.v3  api_v3;
  ~application/vnd\.bestng\.v2  api_v2;
  ~application/vnd\.bestng\.v1  api_v1;
}

upstream api_v3 {
  server 127.0.0.1:8803;
}

upstream api_v2 {
  server 127.0.0.1:8802;
}

upstream api_v1 {
  server 127.0.0.1:8801;
}

location / {
  proxy_pass http://$api_version;
}

传送门:总结一下 RESTful API 的设计

总结一下 RESTful API 的设计

在伯乐在线看到了一篇不错的文章,结合之前的实践总结一下。

不断编辑中。

URL、Action

  • GET /tickets 获取 ticket 列表
  • GET /tickets/12 获取具体的 ticket 12
  • POST /tickets 新建一个 ticket
  • PUT /tickets/12 更新 ticket 12
  • DELETE /tickets/12 删除 ticket 12
  • GET /tickets/12/messages 获取 ticket 12 的 message 列表
  • GET /tickets/12/messages/5 返回 ticket 12 中的 message 5
  • POST /tickets/12/messages 在 ticket 12 中新建 message
  • POST /gists/12/star 加星
  • DELETE /gists/12/star 取消加星

版本化

请求

GET /resource HTTP/1.1
Host: api.myserver.com
Accept: application/vnd.COMPANY.VERSION+json

返回

HTTP/1.1 200 OK
Content-Type: application/vnd.COMPANY.VERSION+json

对于不支持的版本(比如已被废弃或者不存在),返回 415 Unsupported Media Type 并说明原因。

HTTP/1.1 415 Unsupported Media Type
Content-Type: application/json
Cache-Control: no-store

{
  "code" : 1234,
  "message" : "API v1.0 is deprecated."
}

筛选条件

GET /ticket?state=open HTTP/1.1
Host: api.myserver.com

分页

使用 RFC 5988 定义的 Link Header 来提供分页信息。

值得一提的是,Link Header 在 Web 中也被用于预加载

HTTP/1.1 200 OK
Link: <https://api.github.com/resource?page=2>; rel="next",
      <https://api.github.com/resource?page=5>; rel="last"

其中 rel 可取值:

  • first - 第一页
  • prev - 上一页
  • next - 下一页
  • last - 最后一页
  • up - 上一层

频率限制

HTTP/1.1 200 OK
X-RateLimit-Limit: 5000
X-RateLimit-Remaining: 4999

对于超过频率的请求返回 RFC 6585 中增加的 HTTP/1.1 429 Too Many Requests

鉴权

原则上使用 Bearer 进行鉴权。

GET /resource HTTP/1.1
Host: api.myserver.com
Authorization: Bearer YOUR_ACCESS_TOKEN

关于 Bearer Token 的授予方式,开放 API 使用 OAuth 2.0 认证框架,自用的 API 为了轻量可使用 Access Key 和 Secret Key 通过的 Basic 认证换取 Bearer Token。

POST /token HTTP/1.1
Host: api.myserver.com
Accept: application/vnd.bestng.v1+json
Authorization: Basic ACCESSKEY:SECRETKEY

返回

HTTP/1.1 201 Created
Content-Type: application/vnd.bestng.v1+json
Cache-Control: no-store

{
  "access_token": "SItfD0m6oJ4AAQEAAABMOVP_D4VbBwAASItXF0jB",
  "token_type": "Bearer"
}

时间格式

RFC 1123 中定义了 HTTP 协议中使用的时间格式:

Fri, 14 Jun 2013 02:59:55 GMT

之所以不推荐使用 UNIX Timestamp 或者 JavaScript 时间的原因是,文本时间在保持可读的情况下亦可以方便地被解析:

var time = new Date("Fri, 14 Jun 2013 02:59:55 GMT")
alert(time)    // Fri Jun 14 2013 10:59:55 GMT+0800 (CST)

缓存

ETag、Last-Modified

错误信息

{
  "code" : 1234,
  "message" : "Something bad happened <img src="http://blog.jobbole.com/wp-includes/images/smilies/icon_sad.gif" alt=":-(" class="wp-smiley"> ",
  "description" : "More details about the error here"
}

HTTP Status Code

  • 200 OK - 请求成功
  • 201 Created - 创建成功,多用于 POST
  • 304 Not Modified - 缓存有效
  • 400 Bad Request - 请求格式错误
  • 401 Unauthorized - 未授权
  • 403 Forbidden - 授权有效,但无权访问
  • 404 Not Found - 资源不存在
  • 409 Conflict - 冲突
  • 415 Unsupported Media Type - 不支持的媒体类型,比如错误的版本号
  • 429 Too Many Requests - 请求过多

对 JSONP 的 fallback

当请求中包含了 ?callback= 参数时,假定为 JSONP 请求。

此时 URL 中可包含的参数:

  • v 版本号
  • access_token

请求

GET /resource?callback=jsonp_callback&v=1&access_token=YOUR_ACCESS_TOKEN HTTP/1.1
Host: api.myserver.com

总是返回 HTTP/1.1 200 OK

HTTP/1.1 200 OK
Content-Type: text/javascript

jsonp_callback({
  status_code: 200,    // 真实的 Status Code
  next_page: "http://...",
  response: {
    // 真实的返回数据
  }
})

参考

讨论

欢迎继续探讨。

音协里的一些事

今天要交一篇总结,顺手 PO 上来吧。


关于协会

音协是一个很好玩的协会。

进音协之前,我对摇滚的认识,仅仅停留在日系。纯粹是好奇,大二开学时报了爵士鼓班。那时候我还在犹豫,教我们的会是怎样的人,会有怎样的同学。会不会是很串的人,或者个个都很屌的样子?

结果,开班第一天,同学全是妹纸。

开班,每人发一对鼓棒。原来,节奏不仅仅是咚呲哒呲咚咚哒呲,还有很多的。

我也不知道为什么,自从进音协之后也不太迷恋日系摇滚了。反正现在单曲循环的都是 Beyond。

关于摇滚

在音协里,最疯狂的一件事应该就是 4 月份的摇滚节了。什么叫摇滚?以前在 LIVE 视频里看到的一群人在舞台边缘狂跳、伸手要和乐手握手的场面,这次算是真的亲眼目睹了。华软里的晚会,原来也不一定要有座位、第一排嘉宾、与舞台间隔五米的。华软里的晚会,爵士鼓是可以每个鼓都有声音的。

一群人一起疯狂,而不是坐在台下淡定地看着你疯狂。

有人会觉得,摇滚是噪音;玩摇滚的,是叛逆的少年,不听话。只能说,你没有青春。

专业乐队,上台调试半个小时;屌丝乐队,插线,咦,有声!开始。这是自嘲。PB421 令我见识到了很多。

惭愧的是,不擅长打交道的我直到那次音乐会才认识大家。

关于工作

从没想过会成为这个协会的干事直至部长的,直到爱上了这个协会,然后现在成了宣传部的人。

高中开始一直想拍条很 Rock 很 Rock 的 MV。大学读了影视专业,也帮其他社团做过不少(现在看来很稚嫩)的宣传片了。然而这个愿望直到大一还没实现。

拍一条超酷的 MV,这个就是任期内我最想做的事情了。

关于打鼓

本来只是抱着玩玩的态度进来,没想到认识了这么多朋友,其实「夹 Band」是一件很热血很让人兴奋的事情。

可惜,鼓班从一开始几乎坐不下,到现在只有两三个人了。大家都很忙。 有时候明明只是试一试的事情,到最后竟然离不开。

无论如何,我会继续练习。十分感谢细 Ben 和基炜两位师傅!

结尾

文笔不好,写了两个多小时才把想到的感受写出来。总结成一句话: 我爱音协。


题外话:最近一直忙着各种各样的作业,影视技术笔记第二篇写到一半已经搁置了好久了。会来的。

从汇编分析反推加密算法

之前发过的,转过来博客,顺便整理一下。


原始汇编和我的注释

MOV AL,BYTE PTR DS:[ESI+EDI]  ; FUNC(A)
MOV CL,AL                     ;   C = A
MOV DL,AL                     ;   D = A
SHR CL,1                      ;   C >> 1
AND CL,40                     ;   C = C & 01000000
AND DL,10                     ;   D = D & 00010000
OR  CL,DL                     ;   C = C | D
MOV DL,AL                     ;   D = A
SHR CL,2                      ;   C = C >> 2
AND DL,42                     ;   D = D & 01000010
MOV BL,AL                     ;   B = A
OR  CL,DL                     ;   C = C | D
MOV DL,AL                     ;   D = A
AND DL,0F9                    ;   D = D & 11111001
AND BL,20                     ;   B = B & 00100000
SHL DL,3                      ;   D = D << 3
OR  DL,BL                     ;   D = D | B
AND AL,4                      ;   A = A & 00000100
SHR CL,1                      ;   C = C >> 1
SHL DL,1                      ;   D = D << 1
OR  CL,DL                     ;   C = C | D
OR  CL,AL                     ;   C = C | A
MOV BYTE PTR DS:[ESI+EDI],CL  ; RETURN C

整理

合并等式

C = ((A >> 1) & 01000000) | (A & 00010000)
C = (C >> 2) | (A & 01000010)
D = ((A & 11111001) << 3) | (A & 00100000)
C = (C >> 1) | (D << 1) | (A & 00000100)

将最外层的 | 运算拆分步骤

C = ((A >> 1) & 01000000)
C = C | (A & 00010000)
C = (C >> 2) | (A & 01000010)
C = (C >> 1) | ((A & 11111001) << 4)
C = C | (A & 00100000) << 1
C = C | (A & 00000100)

移动移位运算符

C = (A >> 4) & 00001000
C = C | (A & 00010000) >> 3
C = C | (A & 01000010) >> 1
C = C | (A & 11111001) << 4
C = C | (A & 00100000) << 1
C = C | (A & 00000100)

解开第一行的 A >> 4、解开 & 运算

= (A & 10000000) >> 4
| (A & 01000000) >> 1
| (A & 00100000) << 1
| (A & 00010000) >> 3
| (A & 00001000) << 4
| (A & 00000100)
| (A & 00000010) >> 1
| (A & 00000001) << 4

假如给一个字节的 8 个位按顺序编号 ABCDEFGH 的话,由此不难理解,此算法的本质是将 ABCDEFGH 重排为 ECBHAFDG

最终代码

function encrypt (buffer) {
  var result = new Buffer(buffer.length)

  for (var i = 0; i < buffer.length; i++) {
    result[i] = (buffer[i] & 0x80) >> 4
              | (buffer[i] & 0x40) >> 1
              | (buffer[i] & 0x20) << 1
              | (buffer[i] & 0x10) >> 3
              | (buffer[i] & 0x08) << 4
              | (buffer[i] & 0x04)
              | (buffer[i] & 0x02) >> 1
              | (buffer[i] & 0x01) << 4
  }

  return result
}

用 Node.js 做内容管理系统,一个不错的组合

整理一下备忘

Web 框架

模板引擎

  • Consolidate.js - 准确来说它不是一个模板引擎,它的作用是把一些著名的模板引擎适配成 Express 兼容的接口
  • Handlebars.js - 扩展了 Mustache 语法的模板引擎

数据库操作

  • Mongoose - MongoDb 官方推荐的 ODM 模型引擎

Session 持久化

  • connect-mongo - 使用 MongoDb 为 Express/Connect 提供 Session 持久化储存

用户验证

  • Passport - 提供各种用户验证功能,包括接入/提供第三方登录等
  • Passport-Local Mongoose - 打通 Passport 和 Mongoose 的插件,使用 PBKDF2 算法给密码加盐

其它值得关注的替代方案

  • restify - 如果你的服务只提供纯粹的 API 接入,推荐用这个来替代 Express,针对 API 服务提供了很多便利
  • Hogan.js - Twitter 出品的 Mustache 模板引擎
  • mongoskin - 类 MongoDb CLI 方式操作 MongoDb 的库,阿里出品!

一些同样重要的东西

  • Underscore.js - 一些很常用的 JavaScript 操作的封装
  • Async.js - 功能很全面的异步操作流程库
  • cheerio - 一个提供 jQuery 风格 API 的速度极快的 HTML 解析引擎
  • urllib - 对 Node.js 的模拟 HTTP 请求做了很好的封装,阿里出品!

06/06 补充:我叛逃了。Jade 还是比 Consodilate + Handlebars/Hogan 好用。

影视技术笔记:(一)画面比例

无痛入门篇

所谓比例,就是画面的宽度:高度。

常用的有两种比例:4:3和16:9。

电影有一种更宽的比例:2.39:1。

用力

4:3和16:9属于电视范畴的比例规范。

2.39:1、1.85:1(接近16:9)、1.33:1(接近4:3)是电影范畴的比例规范。

再用力

画面在胶片上的排列是竖排的,很久以前无声电影是1.37:1的,后来在旁边加了条音轨(类似录音带的区域)把胶片的宽度占据了,所以画面的宽度只能减少一点了。

过了些时候,电视被发明出来了。同样是“观看会动的画面”的一种方式,画面比例理所当然要和电影接近的。当然,可能为了各种电子参数的计算方便,比例就用整数比来表示了。

再过了些时候,大家都宅在家看电视,电影业不爽了。有人发现把画面弄宽一点看上去好像很爽的样子,于是各种加宽的电影出来了。

又过了些时候,电视又不爽了,有人折腾出了宽屏电视,把4:3取了个平方变成16:9,不过在那个显像管的年代这个比例似乎没有真正流行起来。个中可能因为4:3的电视已经发展壮大了,技术上难以改变了;也可能是人们觉得把1.85:1的电影上下加上黑边或者左右裁掉一些也无伤大雅。不过DVD中,却是支持16:9的画面的(而不是填黑边~)。

当然还有更宽的,比如好莱坞就喜欢用2.39:1这种超宽比例。

深入

其实比例再发展,在同一种型号的胶片上,一格画面的大小是没变的。

有两种方法在同样大小的胶片上存放画面:一种是裁剪/遮盖掉上下方一定高度的画面;另一种是通过特殊的镜片把画面“压扁”一点。

电影拍摄的时候经常会用到前者,监视器上会有线框标示出2.39:1的画面范围(或者屌丝一点用黑胶带贴一个吧 OTL)。这样录音大哥也可以把麦克风举到线框外面的位置,既尽量让麦克风靠近演员,又能随时跟随镜头不怕麦克风穿帮,嘿嘿~

DVD则采用变形的方式储存宽屏的画面。

再深入

这个还是下一篇结合分辨率说说吧。


回顾上一篇:影视技术笔记:(零)开篇

欢迎通过微博@SINGYUCHAN交流。

影视技术笔记:(零)开篇

写在开始前

1080p?720p?24?25?30?NTSC?PAL?

学影视学到大二了,有时候做作业拍片子的时候会遇到一些纯粹技术方面的问题。

仅仅把我自己知道的写下来,当作笔记,放在这个不知道会不会有人留意到的地方,可能有些地方我也不明白的,欢迎交流!

目录

  1. 画面比例 - 4:3、16:9、2.39:1……
  2. 分辨率/扫描 - 576i、720p、1080p……
  3. 编码 - H.264、MPEG-2……
  4. 色彩空间 - RGB、YCbCr……

写在最后

想到什么写什么,因为我也不知道什么时候会写完,所以直接把最后要写的放这里吧。

妈妈再也不用担心我渲染了!

好吧这是个冷笑话 }