网络寻租

Programmer, Gamer, Hacker

Oauth个人总结

| Comments

image

最近因为工作需要, 回头看懂了oauth的整个过程和实现, 因为人的记忆系统是神经反应网络, 不适合做系统性的记忆, 留存一份文档索引是非常有必要的。

什么是oauth?

在IT行业,经常会有这样的一种情况: 一个网站保存了用户的信息(比如一个照片记录网站photos.example.net), 用户希望利用另外一个网站(priter.example.net)来打印这些照片, 但是因为安全性的考虑, 用户不想提供用户名和密码给它, 希望只是提供一个受限的访问权限。 oauth就是为了实现这样的功能产生的。

oauth本身是一个标准, 它指定了一系列的操作规范, 服务提供方(就是上面的photos.example.net)提供一系列的API, 任何需要访问用户资源的第三方网站或者应用, 遵循这套规范, 向用户请求权限, 然后用户授权了之后, 这些第三方网站就能够获得需要的信息, 同时兼顾了用户的安全需求。

oauth的过程

oauth的流程基于http协议, 通过POST请求实现数据通讯。首先定义一下涉及到的几个角色(引用维基百科):

  • 服务提供方(server): 用户使用服务提供方来存储受保护的资源,如照片,视频,联系人列表。
  • 用户(resource owner): 存放在服务提供方的受保护的资源的拥有者。
  • 客户端(client): 要访问服务提供方资源的第三方应用,通常是网站,如提供照片打印服务的网站。在认证过程之前,客户端要向服务提供者申请客户端标识。

整体流程分成3个步骤:

  • client向服务器获得request_token, 包含token和secret, token用来作标识, secret用来做后续的通讯验证。
  • client把resource owner导向到server, 让resource owner授权, 获得一个verifier, 通过后server又把用户重导向到client。
  • client利用获得的verifier, 向server申请一个新的access_token, 申请成功后,就可以利用它来访问resource owner的受限资源。

具体过程如下图: image

为了安全起见, 这些通讯操作都应该在https下面进行。

疑问

下面是我在看rfc的时候遇到的一些疑问, 以及我对它们的解答。

在第二步的时候, 如何跳转回client?

几个地方, 可以在网站创建consumer_key的时候设置默认callback, 以及在申请request_token的时候传入参数oauth_callback, 就我用linkedin API的时候, 在authorize过程传入oauth_callback

nonce的作用?

文档在rfc3.3, client生成的一个随机字符串, 让服务器记忆, 防止有中间人记录了通讯, 重复进行这样的请求来做攻击。 每次给server的请求, 都会生成一个新的nonce。 为了防止产生大量的nonce给服务器带来负担, 服务器会考虑利用时间来给出限制(具体如何做我也不是很明白, 猜测就是做一个延时吧)。

为什么要分离 request_tokenaccess_token, 而不是只用request_token?

恩, 不是很清晰地明白具体会引发什么安全隐患, 个人猜测: request_token在resource owner, client, server之间通讯会有安全隐患, 真正做访问的只有一个access_token, 获得access_token只在client和server之间发生一次, 这样限制一下更安全?

如何通过token和secret让服务器知道自己的?

用”HMAC-SHA1”, “RSA-SHA1”, “PLAINTEXT”三种方式验证, 利用secret, 根据请求的数据, 生成一个oauth_signature section3.4, server会通过它来验证通讯。

consumer_keyrequest_tokenaccess_token里面的token以及secret, verifier它们是如何生成的?

根据我看oauth-plugin的实现, 最后追溯到的是一个随机字串生成器, 看起来只要服务器生成后记住就可以了。

可能的安全问题

rfc里面列出来很多安全性的考虑, 我因为只关心具体的使用, 就不看它们了, 等有担忧的时候再看。

oauth2.0

根据Eran的说法,oauth2.0状况不妙,facebooke和google做的还可以,那么我先不跟踪它了。

你想要多快乐,就能多快乐

| Comments

image

我们每个人都有开心和沮丧的时候。

工资涨了, 和朋友一起去外面玩, 很久没有见面的老朋友来拜访, 遇到这些事情, 我们会开心;

丢失了手机, 被老板或父母教训, 不小心摔坏了心爱的杯子, 遇到这些事情, 我们会沮丧;

我们总是想让自己更开心一些, 但是生活中往往那些让你郁闷的事情发生的更多一些, 要不然就是平平淡淡, 上学放学, 上班下班, 吃饭睡觉。

不过在我们身边, 往往有一类人, 他们的生活不是很好, 或许一直都混得很惨, 读书掉车尾, 工作天天被老板骂, 或者在街上摆小摊, 不过他们一直保留着笑容, 好像天天都很快乐, 情绪一直很high, 不像我们大多数, 摆着一副 “我很烦, 不要来惹我“的表情。

他们到底用了什么魔法? 能够在生活不如意的时候, 保持快乐?

抱着这样的疑问, 你可能上前去问: “请问你, 为什么这么快乐呢?”

她可能给出一些匪夷所思解答: “因为今天天空云彩很漂亮啊~”

你可能又问: “但是今天你又被老板骂耶。。”

她可能好像一点都不在意的样子: “又不是第一天了, 都习惯啦。。”

这个时候, 你估计只有摇摇头, 摆摆手, 当她脑子里缺根筋了。

不过且慢, 我们需要的是快乐, 如果我们可以模拟这样的状况, 那么我们不就掌握了快乐的诀窍吗?

假如, 我们遇到不开心的事情, 可以摆摆手把问题撇到一边, 说, “生活总是会遇到不开心的事情啦~”;

假如, 我们可以因为各种小事而兴奋半天: “今天又是一个大晴天耶!”;

那么我们应该要更快乐的吧。

不过大家可能会说: “遇到这样那样的事情, 怎么可能摆摆手就过去了嘛, 还有这些无关紧要的事情, 我怎么可能开心得起来。。”

但是有人就能够开心得起来, 那么, 我们是否可以怀疑一下:

事情发生了也就发生了, 可能是好事也是坏事。但是因为我们的认知状况不同, 会产生完全不一样的情感反应!

也就是说, 我们可以通过改变自己对事物的解读方式, 改变我们的情感反应, 我们自己的情感, 是可以被自己操控的

从理论上, 我们可以做到, 想让自己多快乐, 就能多快乐, 不需要改变周围的事物, 只是改变自己的认知, 来达到让自己更快乐的目的。

我们每天辛苦工作, 休息日还要加班, 很多时候为了更好的房子, 更好的服装, 可以去更多的地方玩, 这些的目的, 都是为了让我们更快乐, 但是每个人的能力不同, 我们认为自己需要的物质条件, 可能永远都无法获得, 我们可能永远都没有办法在居住的城市里面买到一套房子。

但是如果我们只需要改变自己对事物的看法, 就能获得快乐, 那么就完全可以放下辛苦工作, 只赚一点点生活费, 也能生活得很快乐了。

那么我们可以尝试一下, 遇到事情, 用能够给自己带来快乐的方式解读:

被老板骂, 很开心, 因为: “老板关心我的工作, 而不是把我炒鱿鱼, 还来教训我, 这是一个多么好的反思自己工作方式的机会呀~”

丢了手机, 很开心, 因为: “还好遇到的是小偷, 如果硬抢, 那么我丢的可不一定只是手机了, 可能手也没有了。。”

不小心摔坏了心爱的杯子, 很开心, 因为: “旧的不去, 新的不来, 我有机会用到更喜欢的杯子了!”

听起来很阿Q, 但是我们获得了快乐的能力, 可以不在执着于一些我们不真正需要的东西了, 因为我们能够让自己, 想要多快乐, 就能多快乐!

恩, 还没有完, 我们是否可以进一步想想, 这样我们也能够想让自己多不快乐, 就多不快乐, 为什么要这样呢? 因为情感只是我们的工具(待续)。

老书换新封

| Comments

博客已然数月没有更新了,作为博客的主人我很惭愧,现在改头换面重新开张,在此我小小地列一下来龙去脉。

原先的博客系统是本人折腾的产物,写文档采用ReST格式,页面采用静态更新,回复全部交由Disqus打理。 虽磕磕碰碰年许,还是凑合能用。不过时代发展迅速,Markdown成为主流,与之相比,ReST显得太重了,毕竟,它主要的目标还是在于文档编写。

我逐渐产生了移植博客系统到Octopress的打算,由于历史成本,一直未能顺利完成,折腾了许久,再加上自己开发的系统设计考虑不周,发布过程比较痛苦,写博客这件事算是耽搁下来了。 原以为写博客系统和写文章可以一起搞定,但最后还是又验证了那个谚语:一个人同时只能做好一件事。 博客系统和写博文混杂起来对心智负担甚重,还是先专注写文章吧。

回首已多月,最后我终于下定决心投入时间整顿它们,全部移植到Octopress上去。 从ReST转移到Markdown耗费了一些功夫,有Pandoc在,工作还算顺利,有些许的格式混乱,容我以后时间充裕时再慢慢调整吧, 若您看到什么格式混乱的地方,还请包涵,这些我都会在将来的几个月里面慢慢整理完毕的~

黑客马拉松活动小记

| Comments

image

活动

上周的周末(5/5-5/6), 我们参加了 黑客马拉松 活动, 简单点的介绍, 就是一群程序员/美工/产品经理, 花费1整天的时间, 鼓捣出一个酷的东西。 黑客的文化在于创造, 分享, 进步, hackathon的活动是黑客文化的一个体现。

过程

参与活动可以考虑预先组队和现场组队, 我们是预先组好的, 4个人: 我(GuruDigger), 蔡金(GuruDiggger), 吴杰蔚(住隔壁, 交大硕士), 周航(EMC云存储), 准备做的产品, 是我的一个Idea: 豆瓣match , 通过豆瓣上面的喜好资料, 来寻找志同道合的对象。

一天时间非常的紧张, 我们提前一天的晚上, 小聚了一下, 讨论了一下产品, 以及具体的分工。 我负责web前端和数据挖掘算法, 吴杰蔚考虑推荐算法, 周航负责爬豆瓣数据, 蔡金负责页面设计。

黑客马拉松举办的场地是在江湾体育场的创智天地, 我们早上9点开始, 到了现场, 我们抢了一个靠近门的位置, 没有理会现场的举办方, 直接开工了。

我把网站的架子弄起来(rails), 我们在如何让ubuntu默认安装的mysql可以让其他机器访问到这点上花费了比较多的时间, 然后上午的时间就结束了。

中午在现场吃饭, 也是社交的时间, 到处找人聊天, 以及认识新朋友。 平时很少有机会聊的一些话题。

下午的时候, 我遇到了技术问题: 基本上的时间都花费在了如何让豆瓣用户登录这一块上面。 我本来采用的是 omniauth-douban , 花费了大量时间, 流程还是没有走通顺, 后来换了道哥的 douban-ruby , 看懂了豆瓣oauth的逻辑, 总算实现了登录和替用户发豆邮的功能。 周航这边导数据也遇到了一些问题, 不过总算都搞定了。

开发期间, 抽空去看了一下其他团队。 团队采用的技术区别还是比较大, ruby, python, java, node.js, objective-C, 还有一组是用.net的, 现场学习新的技术。。 采用的开发工具也百花齐放, eclipse, Xcode, emacs, vim, sublime, textmate… 如果是非技术的人过来, 看大家的电脑屏幕, 是搞不清楚大家在做什么的。

就做的东西而言, 大多数还是以做产品为导向, 有做团购搜索的, 有做青年旅社查找应用的, 有做平台的, 都是大东西啊。 让我眼前一亮的只有一个: 基于js+webgl的3D小游戏。 引擎是组里面一个做游戏的牛人现场写的。 我觉得这个产品算是最符合黑客精神的了。

晚上我的精力不足, 效率低下, 不过把网站的整体流程跑出来了。 10点钟我们离开场地回去, 我和蔡金整理好UI, 然后我把整体流程跑顺, 处理bug, 我弄到了4点才去睡觉。 不过, 产品的完成度总算是能够应付第二天的展示了。

第二天7点半我起床, 和蔡金一起去场地。 这一天都是展示日, 没有我什么事情, 就在下面休息。 最后的结果是, 我们的产品得到了三等奖, 应该算是理所应当的吧。 第一名是Chop, 一个聊天的产品。

image

小结

就开发而言。 我觉得有些可以总结的:

  • Hackathon活动一天都应该处于一个集中精神做事情的状态, 我前一天没有休息好, 造成这一天精力不充沛, 时间效率不高。
  • 为了更好的产能, 可以做的是提前把技术细节都准备好, 现场做的只是拼装, 但是我觉得这样就太作弊了, 不算是好的行为。 毕竟现场搞定问题才是意义所在。
  • 开发的时候, 因为分工做好了, 沟通链减少很多, 基本上都在全力开发中。
  • 一天的时间非常紧张, 项目计划需要弄好。 我们还是错误预估了工作量, 预期要做的根据喜好推荐用户的功能没有做出来, 只能通过部分的随机来模拟一个效果。如果还有下次的活动, 一定要考虑清楚工作量。
  • 成功的很大因素靠产品设计, 在这点上面蔡金考虑产品考虑得很好。 直截了当地解决问题。

然后就是本次活动:

  • 黑客精神应该是用技术的方式巧妙解决问题。 参与者做的大都是产品导向, 解决问题的方式也不是很开创性质的。 只能算是做出来一个东西吧。
  • 奇怪的评委, VC或者公司大员, 有人还提问“你们的盈利模式是什么”(我把这个当成一个笑话来看), 如果这个活动是以产品开发为主题还差不多。 但是不符合“黑客马拉松”的气氛。
  • 和硅谷双线举办的效果不好, 基本上是双方分开做自己的事情。 这样就没有太多的意义了。
  • 我觉得应该得到第一名的那个3D游戏连第二轮都没有进入, 在这样的评委组成下, 意料以外情理之中。

我觉得让开发者们时不时地聚在一起, 花费一天两天的时间做一个小原型的活动很有意义, 但是不应该像本次活动一样办成一个大会形式了。 如果下次还有类似的活动, 我还是会去的。 然后我会考虑做一个更有黑客精神的东西。

TDD 测试驱动开发

| Comments

TDD是这样一种开发方式,引用至wikipedia,以及中文:

利用测试来驱动软件程序的设计和实现。

image 具体内容我就不多说了,网络上面的资料很多。

我没有试过TDD,比较像样的测试也没有做过,所以没有什么权力来评价TDD,

考虑到我们公司现有的开发流程--谈需求,开发代码,测试,验收,

以及我们公司经常出现需求变更的状况,TDD可能可用,下次开发时要实际应用一下。

Unicorn配置

| Comments

什么是unicorn?

unicorn 是ruby下面的一个基于Rack的HTTP server. 类似的工具有 passenger, thin 等。

unicorn简单的使用方式

在你的rails项目下面, 直接执行下面的代码就可以了

unicorn_rails

unicorn的原理

它的工作模式是master/worker多进程模式。 简单地说, 首先建立一个master进程, 然后fork出来worker进程。

worker进程处理进来的请求, master负责管控, 当worker消耗内存过多, 或者相应时间太长, 杀掉worker进程。

这里是一篇github使用他们的文档:

https://github.com/blog/517-unicorn

unicorn详细配置

一般来说, 按照这个架构方式:

nginx负责端口映射, 从80端口映射到本地unix socket, 然后unicorn按照daemon方式执行。

设置nginx

nginx只需要设置一下端口转发就可以了。 (对于rails, 另外提供静态资源服务)

server
{
    listen 80;
    server_name doubanmash.com;
    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

设置unicorn

基本摘抄 上面github的配置 , 文件保存为./config/unicorn.rb, 稍微解释一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# -*- coding: utf-8 -*-
rails_env = ENV['RAILS_ENV'] || 'production'
# 需要设置一下rail的路径
RAILS_ROOT = "/rails/path"

# 设置生产和开发环境下面跑的worker数量
worker_processes (rails_env == 'production' ? 16 : 4)

# rails环境是需要预先加载的, 节省时间和内存
preload_app true

# 每个请求最长的响应时间, 超过了就杀掉worker
timeout 30

# 监听端口设置, 可以设置成unix socket或者tcp, 这里是用tcp, 因为开发环境可以直接看网站
# listen '/data/github/current/tmp/sockets/unicorn.sock', :backlog => 2048
listen 8080, backlog: 2048

before_fork do |server, worker|
  ##
  # 这里是实现重启的时候无缝衔接的代码。
  # 首先unicorn提供了这样一个机制:
  # 当我们发送 USR2 信号给master的时候, unicorn就会把旧的pidfile加上.oldbin后缀,
  # 然后启动一个新的master, 新的master也会fork worker出来。
  #
  # 下面的代码就是当新的master起来的时候, 检查oldbin这个文件, 告诉旧的master退出(发送QUIT信号)。
  # 这样我们保证了无缝重启。

  old_pid = RAILS_ROOT + '/tmp/pids/unicorn.pid.oldbin'
  if File.exists?(old_pid) && server.pid != old_pid
    begin
      Process.kill("QUIT", File.read(old_pid).to_i)
    rescue Errno::ENOENT, Errno::ESRCH
      # someone else did our job for us
    end
  end
end


after_fork do |server, worker|
  ##
  # fork了之后, 原先开启的socket就不能用了, 重新开启
  ActiveRecord::Base.establish_connection
  # Redis 和 Memcached 的连接是按需的, 不需要重新开启
end

信号是外界给unicorn发命令的方式, 我们利用发信号来控制unicorn。 上面的配置中无缝重启的部分利用到了这个机制。 最好看看 unicorn 信号文档

更多的配置, 可以见 unicorn 配置文档

具体使用

设置完成后, 在命令行下面执行

bundle exec unicorn_rails -c ./config/unicorn.rb -D

如果是生产环境

bundle exec unicorn_rails -c ./config/unicorn.rb -D -E production

当新版本上线, 需要重启的时候, 执行

kill -USR2 `cat ${RAILS_ROOT}/tmp/pids/unicorn.pid`

如何监控

好像有的时候, unicorn master会出现内存泄漏的状况, 还需要一个进程来监控它。 有人推荐我用 god , 不过我还没有评测过。

性能和易用性

没有评测过, 等我有时间的时候考虑一下。

结论

听说rails的初学者去用passenger, 熟悉了一些之后会用unicorn, 我对为什么要用它并没有什么太多的感受。 听说应该是它的可配置性, 以及比较好的性能吧。

Pjax是什么以及为什么推荐大家用

| Comments

image

什么是pjax?

现在很多网站(facebook, twitter)都支持这样的一种浏览方式, 当你点击一个站内的链接的时候, 不是做页面跳转, 而是只是站内页面刷新。 这样的用户体验, 比起整个页面都闪一下来说, 好很多。

其中有一个很重要的组成部分, 这些网站的ajax刷新是支持浏览器历史的, 刷新页面的同时, 浏览器地址栏位上面的地址也是会更改, 用浏览器的回退功能也能够回退到上一个页面。

那么如果我们想要实现这样的功能, 我们如何做呢?

我发现pjax提供了一个脚本支持这样的功能。

pjax项目地址在 https://github.com/defunkt/jquery-pjax 。 实际的效果见: http://pjax.heroku.com/ 没有勾选pjax的时候, 点击链接是跳转的。 勾选了之后, 链接都是变成了ajax刷新。

为什么要用pjax?

pjax有好几个好处:

  • 用户体验提升。

    页面跳转的时候人眼需要对整个页面作重新识别, 刷新部分页面的时候, 只需要重新识别其中一块区域。自从我在自己的网站 GuruDigger 上面采用了pjax技术后, 不由觉得访问其他只有页面跳转的网站难受了许多。 同时, 由于刷新部分页面的时候提供了一个loading的提示, 以及在刷新的时候旧页面还是显示在浏览器中, 用户能够容忍更长的页面加载时间。

  • 极大地减少带宽消耗和服务器消耗。

    由于只是刷新部分页面, 大部分的请求(css/js)都不会重新获取, 网站带有用户登录信息的外框部分都不需要重新生成了。 虽然我没有具体统计这部分的消耗, 我估计至少有40%以上的请求, 30%以上的服务器消耗被节省了。

坏处我觉得也有:

  • IE6等历史浏览器的支持

    虽然我没有实际测试, 但是由于pjax利用到了新的标准, 旧的浏览器兼容会有问题。 不过pjax本身支持fallback, 当发现浏览器不支持该功能的时候, 会回到原始的页面跳转上面去。

  • 复杂的服务器端支持

    服务器端需要根据过来的请求, 判断是作全页面渲染还是部分页面渲染, 相对来说系统复杂度增大了。 不过对于设计良好的服务器代码, 支持这样的功能不会有太大的问题。

综合起来, 由于用户体验和资源利用率的提升, 坏处是可以完全得到弥补的。 我强烈推荐大家使用。

如何使用pjax?

直接看 官方文档 就可以了。

我觉得做技术的人要养成看一手的技术资料的习惯。

有一个rails针对pjax的 gem插件 可以直接使用。 也有 django的支持

pjax的原理

为了能够处理问题, 我们需要能够理解pjax的运作方式。 pjax的代码只有一个文件: https://github.com/defunkt/jquery-pjax/blob/master/jquery.pjax.js

如果有能力, 可以自己去看一遍。 我这里解释一下原理。

首先, 我们在html里面指定, 需要做pjax的链接内容是哪些, 以及点击之后需要更新的部分(放在data-pjax属性里面):

1
$('a[data-pjax]').pjax()

当加载了pjax脚本之后, 它会拦截这些链接的事件, 然后包装成一个ajax请求, 发送给服务器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$.fn.pjax = function( container, options ) {
  return this.live('click.pjax', function(event){
    handleClick(event, container, options)
  })
}

function handleClick(event, container, options) {
  $.pjax($.extend({}, defaults, options))
  ...
  event.preventDefault()
}
var pjax = $.pjax = function( options ) {
  ...
  pjax.xhr = $.ajax(options)
}

这个请求带有X-PJAX的HEADER标识, 服务器在收到这样的请求的时候, 就知道只需要渲染部分页面返回就可以了。

1
2
xhr.setRequestHeader('X-PJAX', 'true')
xhr.setRequestHeader('X-PJAX-Container', context.selector)

pjax接受到返回的请求之后, 更新data-pjax指定的区域, 同时也会更新浏览器的地址。

1
2
3
4
5
6
options.success = function(data, status, xhr) {
  var container = extractContainer(data, xhr, options)
  ...
  if (container.title) document.title = container.title
  context.html(container.contents)
}

为了能够支持浏览器的后退, 利用到了history的api, 记录下来对应的信息,

1
2
3
4
5
6
7
8
9
10
11
pjax.state = {
  id: options.id || uniqueId(),
  url: container.url,
  container: context.selector,
  fragment: options.fragment,
  timeout: options.timeout
}

if (options.push || options.replace) {
  window.history.replaceState(pjax.state, container.title, container.url)
}

当浏览器后退的时候, 拦截事件, 根据记录的历史信息, 产生一个新的ajax请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
$(window).bind('popstate', function(event){
  var state = event.state
  if (state && state.container) {
    var container = $(state.container)
    if (container.length) {
      ...
      var options = {
        id: state.id,
        url: state.url,
        container: container,
        push: false,
        fragment: state.fragment,
        timeout: state.timeout,
        scrollTo: false
      }

      if (contents) {
        // pjax event is deprecated
        $(document).trigger('pjax', [null, options])
        container.trigger('pjax:start', [null, options])
        // end.pjax event is deprecated
        container.trigger('start.pjax', [null, options])

        container.html(contents)
        pjax.state = state

        container.trigger('pjax:end', [null, options])
        // end.pjax event is deprecated
        container.trigger('end.pjax', [null, options])
      } else {
        $.pjax(options)
      }
      ...
    }
  }
}

为了支持fallback, 一个是在加载的时候判断浏览器是否支持history push state API:

1
2
3
4
5
// Is pjax supported by this browser?
$.support.pjax =
  window.history && window.history.pushState && window.history.replaceState
  // pushState isn't reliable on iOS until 5.
  && !navigator.userAgent.match(/((iPod|iPhone|iPad).+\bOS\s+[1-4]|WebApps\/.+CFNetwork)/)

另一个是当发现请求一段时间没有回复的时候(可以设置参数timeout), 直接做页面跳转。

1
2
3
4
5
6
7
8
9
options.beforeSend = function(xhr, settings) {
  if (settings.timeout > 0) {
    timeoutTimer = setTimeout(function() {
      if (fire('pjax:timeout', [xhr, options]))
        xhr.abort('timeout')
    }, settings.timeout)

    // Clear timeout setting so jquerys internal timeout isn't invoked
    settings.timeout = 0

结论

既然都看到这里了, 你为什么不去实际使用一下pjax呢? 有那么多好处, 我觉得几乎所有网站都应该采用pjax。 赶紧用起来吧!

Rails介绍

| Comments

我现在算是一个rails程序员了, 作为一门技术的使用者, 必要的时候要向其他人推广和介绍这门技术, 下面是我准备的slide, 不定期更新。

Rspec

| Comments

image

我现在在做的 GuruDigger 项目一直没有用测试框架, 最近我做了几次大的重构, 结果出现了许多bug, 为了能够保证以后项目不会在重构中挂掉, 我还是需要把测试给整起来.

rails的测试框架用得比较多的是rspec.

rspec的原理

我们有一段代码:

1
2
3
4
5
def exp number n
  result = 1
  n.times.each{result *= number}
  result
end

我们需要给出一个单元测试. rspec的写法是这样的:

1
2
3
4
5
6
7
describe exp do
  it "should work" do
    exp(2, 3).should == 8
    exp(5, 3).should == 125
    exp(1, 3).should == 1
  end
end

我们来解释一下. 里面的describe和it是什么意思呢? rspec里面的测试是采用描述性的方式进行的. describe说明具体描述的是什么东西, it指代这个东西它的行为应该是怎么样. 上面的代码一方面做好了测试, 另外一方面也直观地描述了这个方法需要做的事情, 符合人类直觉.

然后, 我们看具体验证的部分. 和其他单元测试框架的 assert_equal 函数不同, rspec-expectations 修改了Kernel, 给了一个should方法. 这样让原先的外层函数调用, 变成了内层的方法调用, 造成的结果就是写起来超级直观. 这个算是ruby比较常用的套路了.

should的写法可以去看 rspec-expectations的文档.

https://github.com/rspec/rspec-mocks 这一块不太容易懂, 需要看看.

factory_girl

factory_girl是取代rails默认生成测试数据的yml格式的一种写法, 原生ruby, 写起来比较舒服和能够嵌入ruby代码.

1
2
3
4
5
6
7
8
9
10
11
# 定义一个对象
FactoryGirl.define do
  factory :user do
    first_name 'John'
    last_name  'Doe'
    admin false
  end
end

# 用到的时候build一下就好.
user = FactoryGirl.build(:user)

网上也有很详细的教程:

https://github.com/thoughtbot/factory_girl/blob/master/GETTING_STARTED.md

rspec和rails整合

上面是rspec做测试的部分, 下面我们看如何和rails整合. 其实文档都全了, 我觉得大家还是直接去看官方的文档就好:

https://github.com/rspec/rspec-rails

重点是测试的几个类型.

rspec加上spork

rspec跑一遍下来超级慢, 于是就有了 spork 这样的东西. 原理就是先跑一个服务器, 加载好对应的环境. 然后需要执行测试的时候, 就通知这个服务器开始测试. 服务器会fork一下, 执行对应的测试.

如何使用上面的文档都有. 重点是几步:

  • 用spork —bootstrap初始化spec_helper.rb, 把每次fork需要做的事情填到对应的方法里面去.
  • 跑服务器, 执行spork
  • 跑测试. 执行rspec —drb

配置

这里有别人贴出来的配置, 还是挺复杂的, 需要搞搞清楚. 但是搞清楚了, 开发起来你会发现非常舒服(前提是你的机器够好…) https://gist.github.com/1191428

一些资料

rspec书籍: http://pragprog.com/book/achbd/the-rspec-book

rspec cheetsheet: http://cheat.errtheblog.com/s/rspec/

capybara cheetsheet: https://gist.github.com/428105 http://cheat.errtheblog.com/s/rspec_shoulda/

rspec最佳实践: http://eggsonbread.com/2010/03/28/my-rspec-best-practices-and-tips/

Guard

| Comments

image

介绍

guard 是一个自动监控文件夹变更, 执行特定操作的工具. 如果你有这样的需求, 就可以用它.

guard可以和rails整合, 这里 是介绍.

使用方法

比如, 简单地监控当前文件夹里面的变更, 如果变了就重新编译一下, 我们先装好一个插件: guard-rake 新建一个文件夹,里面2个文件:

Guardfile :

guard 'rake', :task => 'default' do
  watch(/(.*)/) 
end

Rakefile:

task :default do
  sh 'cpp xxx.c -o hello'
  sh './hello'
end

然后在这个文件夹里面执行:

guard

每次这个文件夹里面出现了文件变更, 就会重新执行一下default, 编译执行一下代码, 省去了每次自己点击的操作了.

原理

我们使用一个工具的时候一般来说最好还是需要知道一下这个工具的基本工作原理, 不然遇到问题的话也比较好解决.

底层根据操作系统采用对应的文件系统监控API(Listener). 在linux下面是调用 inotify.

guard的插件, 需要继承Guard, 实现run_on_change/initialize/start/stop 这些API就可以了.

guard本身定义了一套DSL. 在Guardfile里面写的东西, 就会连接到对应的插件上面去. 比如上面的例子, guard ‘rake’, 下面就会调用guard-rake写的一个插件定义类.

livereload

guard有各种各样的插件, 这里介绍一个神奇的livereload. 它的作用是:

在做rails开发的时候, 每次更新了一个源文件, 我们需要手动去刷新一下网站页面. 它能够让你的网页自动刷新.

原理:

guard维护一个本地的服务器. 网页内嵌有一个js, 连接上这个服务器.

当修改文件的时候, guard能够获知修改, 通知所有连上来的客户端, 本地有更新. js接收到更新, 会去自动刷新网页.

具体安装需要几个: guard-livereload 作为guard插件. rack-livereload 作为rails rack的一个中间件, 用来嵌入livereload的js.