2012年5月9日 星期三

怎么样才算是 RESTful?读 REST in Practice

最近 O’Reilly 搞活动,我就半价买了一本《REST in Practice》(Kindle 版链接)。对于 O’Reilly 的书,我通常会对比 O’Reilly 打折后的价钱和 Kindle 版的价格,通常是那家更便宜就在那家买,但图表或代码比较多的我就会坚持买 O’Reilly 的版本,因为 PDF 能够最好地保存这些格式。

回到 REST 的话题上。尽管这个概念 2000 年就被提出来了,2007 年成为了一个热词,随后越来越多的服务都宣称自己是 RESTful 的,但是到底真么做才是真正的 REST 我从来没有自习学习过。由于 2007 年的时候 Ruby on Rails 也十分热门,所以我以为 Rails 风格的 CRUD API 就是 REST 了,同时对于外界关于「什么算是 REST 什么不算是 REST」的争论没怎么关心过。

记得之前老赵提到过《REST in Practice》是本好书,所以我就收藏到 wish list 里面了,在 O’Reilly 打折时就下狠心买下来,然后看看争论得如此多的 REST 到底是什么东东。书中提到 Richardson 的 REST 成熟度模型,通过这个模型你可以理解什么是 REST 什么不是 REST。这个模型是这样子的:

第 0 级服务:只使用一个 URI 作为一个服务端口,也只使用一个 HTTP 方法传输数据。大多数 WS-* 服务都是这个级别的,XML-RPC 和 POX 也是。这种做法相当于把 HTTP 这个应用层协议降级为传输层协议用。HTTP 头和有效载荷是完全隔离的,HTTP 头只用于保证传输,不涉及业务逻辑;有效载荷包含全部业务逻辑,因此 API 可以无视 HTTP 头中的任何信息。

第 1 级服务:使用多个 URI,不同的 URI 代表不同的调用入口,但只使用同一个 HTTP 方法传输数据。

第 2 级服务:使用多个 URI,不同的 URI 代表不同的资源,同时使用多个 HTTP 方法操作这些资源,例如使用 POST/GET/PUT/DELET 分别进行 CRUD 操作。这时候 HTTP 头和有效载荷都包含业务逻辑,例如 HTTP 方法对应 CRUD 操作,HTTP 状态码对应操作结果的状态。我们现在看到的大多数所谓 RESTful API 做到的也就是这个级别。

第 3 级服务:使用超媒体(hypermedia)作为应用状态引擎。要解释这个概念先要解释什么是超媒体:

我们已经知道什么是多媒体(multimedia),以及什么是超文本(hypertext)。其中超文本特有的优势是拥有超链接(hyperlink)。如果我们把超链接引入到多媒体当中去,那就得到了超媒体,因此关键角色还是超链接。使用超媒体作为应用引擎状态,意思是应用引擎的状态变更由客户端访问不同的超媒体资源驱动。

举个例子来说,用户在论坛的帖子列表点击超链接进入某个帖子,这时候浏览器作为一个客户端就会去 GET 这个帖子的链接 URI,应用状态就切换为显示某个帖子了。接着用户输入回复提交表单,浏览器就会根据 form 上面的 action 属性 POST 内容给目标 URI,应用状态就再一次发生切换,完成了回复的存储。

使用超媒体与前面第 1 级、第 2 级的显著区别是,客户端不再和 URI 紧耦合。在第 1 级或者第 2 级的应用里面,客户端都需要知道资源使用的 URI 模版(如 /orders/{id}),然后要操作什么样的资源就生成什么样的 URI。超媒体客户端只知道入口 URI,之后的每一个 URI 都是通过超链接获得的。

还是用上述论坛例子来解释,假若这个论坛通过 Atom 协议支持非浏览器的客户端访问。客户端是不需要知道论坛帖子的 URI 模版的,因为客户端可以通过帖子列表的 Atom 获得帖子的超链接,然后在用户选择浏览帖子时获取对应 URI 的内容。获取回来的结果不会带有 form,但会带有 <link rel="reply" />,通过这个 link 客户端又知道了用户提交的回复应该发往哪个 URI。

说完 Richardson 的成熟度模型,说说 REST 这篇论文作者 Roy Fielding 的回应。Roy Fielding 说「只有使用了超媒体的才能算是 REST」。简单来说,他认为第 3 级成熟度以外的都不算 REST。我个人的看法是,我支持 Roy Fielding 对 REST 的严格定义,我也认为一个真正使用了超媒体实现应用状态引擎的服务非常了不起,但是在普通 CRUD API 能满足需求的情况下我觉得使用 CRUD API 也可以。

这本书后面还讲到了如何使用缓存来增加系统健壮性,如何使用 Atom 和 Atom 发布协议,如何使用 OpenID 和 OAuth 等认证授权技术。因为这本书我还没看完,所以这些内容我也不好做评价。

这本书总体来说写得不错,但就是废话稍微多了一点点。如果你跳着章节地去看,可能就不会觉得那是废话了,因为你挑选出来阅读的部分都包含所有的信息。但如果你像我这样从头到尾地阅读,就会觉得作者怎么前面提到过的事情后面还要再提及。

此外这本书提供的大量 .NET 和 Java 代码对我来说没什么意义。我觉得这本书更多是写给做企业服务的人看的,希望他们的思维模式能够从企业环境里常见的 WS-* 跳出来,同时希望证明给他们看实现 REST 是多么简单的事情。这对于天天研究互联网服务的我来说没什么必要,况且我也很久没写过 .NET 代码了,所以各式各样的示例代码给我无视掉了。我觉得我知道这件事情 WCF 能做也就足够了。

2012年4月24日 星期二

把 Blogger 服务的博客都迁移到 Disqus 评论了

一直使用 Blogger 的服务,因为我觉得使用起来比较放心,相信 Blogger(实际上是 Google)能够保证数据不会丢失。我的第一需求是要能专心写文章,第二需求是前端在有需要的情况下能够定制,这两点 Blogger 都能很好地满足到,所以在大家都忙于搭建 WordPress 时我还是坚守在 Blogger 上。因此,我不需要面对因为自己的失误,或者宿主环境的问题,而导致 WordPress 数据丢失的风险。

尽管对 Blogger 没什么好抱怨的,不过看到 Disqus 提供的评论服务还是觉得挺有吸引力的。看到越来越多的大网站使用 Disqus,相信越来越多的用户会拥有 Disqus 账号或能登录到 Disqus 的第三方账号,我就开始考虑迁移到 Disqus。背后的主要原因是,Blogger 评论支持的账号系统不多,不是很 geek 的人通常不会拥有它所支持的账号,而不登录的话就难以形成对话。(谁会去看自己匿名发表的评论是否有回复?)我希望我的评论区域能够形成对话,就好像在博客园那样子,大家都使用博客园账号互相回复讨论问题,因此我决定换成 Disqus 试试。

将 Disqus 部署到 Blogger 比我想象中的要容易。尽管 Blogger 不像 WordPress 那样允许你随意改后端代码,但 Disqus 早就准备好了前端解决方案——只要是有评论的页面,Disqus 就会将 Blogger 原有的评论隐藏掉,然后把自己显示出来。轻松跟着 Disqus 的 Blogger 迁移向导做,点几下就能把 Blogger 的评论替换为 Disqus。

完成功能迁移后,接着就可以进行内容迁移了。Disqus 也知道 Blogger 用户原本可能已经拥有评论了,所以它可以帮你把内容都迁移过来,只不过账号是不可能对应上了(就算该评论作者在 Disqus 有对应账号)。尽管 Disqus 写的是 24 小时内能够完成内容迁移,但实际上我等了几天也没见它有动静,后来不再在意这件事了,却发现迁移完成了。因此,如果你要做类似的迁移,我建议你先迁移内容,然后再迁移功能,这样能够做到无缝切换。

2012年2月8日 星期三

如何帮助别人改变坏习惯

最近为了写一个 slides 而重新看了一遍 The Mystery Method,然后又重新学习了一遍我已经学习过并且忘记掉的知识。其中让我觉得比较有意义的一条是关于如何训练别人的行为模式的,尽管背后的道理源自对动物行为模式的训练。

如果你的狗总喜欢跳到你身上来,你该怎么办?你可以打它骂它,但它无法理解你的情绪,所以它只会把这理解为关注,也就是你在乎它,因此它会继续跳到你身上来,以便获取更多的关注。你可以把硬币放在一个罐子里,然后摇晃那个罐子让狗因为恐惧而走开,但久而久之它会理解到你和罐子存在关系,因此把你和厌恶联系在一起,这显然也不是你想要的结果。正确的做法是,先通过摇晃罐子让它从你身上下来,然后让它做一个简单容易做到的动作,例如跟它说「sit」,等它坐下来后在拍拍它给它一些奖励。这样子它就会把厌恶和错误的行为联系起来,但不会把厌恶跟你联系起来,因为你还是会奖励它的。

尽管 The Mystery Method 把这个方法用于训练别人的服从性,不过如果你想帮助身边的朋友改掉坏习惯这也是适用的。可能你觉得,既然大家都是朋友那么平等理性地沟通就可以了,只要对方知道这是个缺点他就可以改掉。然而事实不是这样的,可能沟通并不一定能够很好地传达你的意思,让对方有足够的决心去改变;可能对方一早就知道这个缺点的存在,但是他的意识无法扭转无意识的行为习惯,久而久之他就会合理化自己的行为,为自己无意识的行为辩论。

关于意识和无意识,我喜欢用 The Happiness Hypothesis 里面的那个比喻——无意识是大象,有力量,能够做重复性劳动,但是智慧不是很高;意识是骑象人,力量比不过大象,而且还懒惰,但智商足够高。这个比喻的一个要点就是,人永远不能跟象较劲。象看到了香蕉就要冲过去,而人看到了香蕉就在悬崖边上,这时候人想拉住象是不可能的。人应该使用自己的聪明才智防止这种情况的发生,例如训练象看到香蕉不要那么冲动。

显然你不希望在帮助对方改掉坏习惯的过程中让对方讨厌你,尤其是无意识讨厌你同时意识到应该感激你。前面已经说了,人是不能跟象较劲的,所以如果你有幸让一个人无意识讨厌你,那么无论他多么明白事理,他也无法让自己喜欢上你。为此,当你需要帮助别人改变习惯的时候,不一定需要跟对方的象骑者沟通。如果对方足够聪明,也足够信任你,他会允许你直接训练他的象,并且他也能理解你在做什么。

2012年1月31日 星期二

Google Search Plus 把用户选择放到了内容质量的前面

Google 发布了 Search, plus Your World,简称 Google Search+,带来最大的改变包括 3 点:

  1. 跟个人相关的搜索结果:你跟 Google+ 上好友发表过的链接和照片都会出现在搜索结果内,包括通过 Circle 限制为非公开的。
  2. Google+ Profile:如果搜索人名,自动完成和搜索结果都会出现对应的 Google+ Profile。
  3. 相关 Google+ Profile:如果 Google+ 上有 Profile 跟你搜索的关键字相关,会出现在搜索结果页右侧。

其中最直接影响搜索结果排名的是第 1 点,后面两点都只是往搜索结果里插入少数 Google+ Profile 条目而已。这第 1 点可以说是把用户选择放到了内容质量前面来,使得过去完全针对内容的搜索引擎优化手段变得需要同时考虑针对用户做优化,也就是引入营销手段。

在过去,搜索引擎优化考虑的是内容质量。理论上来说,如果内容质量足够高,别人就会频繁引用你的页面,这样你的页面自然应当排到前面去。但是有了 Google Search+ 之后游戏规则彻底改变了,就算你的页面内容十分小众,只要它被某人发布到 Google+ 上去了,它就会出现在此人好友的搜索结果上。这时候,被多少人 +1 就显得比内容如何更重要。

这个变化可以说是由作者投票变成了读者投票。过去 PageRank 是以对待论文的方式来对待页面,被引用的次数越多,引用来源越重要,这篇论文也就越重要。然而,能够影响论文引用的只有论文作者,有多少读者读过一篇论文后受益是不知道的,除非他为此再写一篇论文。现在 Google Search+ 的做法可以说是把页面对读者的影响力也计算在内了。

跟作者投票不一样的是,读者投票的影响是跟个人相关的。只有离你近的读者投票才会影响到你的搜索结果,离你远的读者投票对你影响不明显(除非你指定搜索他的信息)。因此,如果要让你的内容通过搜索引擎展示给特定的受众看,你不一定再需要把内容优化为其他作者会认同的样子,只要你有营销手段让受众中的一部分先认同你的内容,搜索引擎就会让这种认同扩散出去。

尽管已经有很多社会化营销手段涉及到 Facebook 和 Twitter,Google Search+ 暂时还不支持这两者,所有社会化信息仅来自 Google+。无论这是 Google 有意的恶性竞争行为,还是技术上和商务上暂时没把对 Facebook 和 Twitter 的支持做好,现在你要影响 Google 搜索结果中的社会化成分就必须通过 Google+。估计很快网站就会重视页面上嵌入的 +1 按钮,并且使用各种方式去鼓励用户点击那个按钮。

2012年1月18日 星期三

如何设计大规模 JavaScript 应用 (Part 1 - 概览)

背景

我一直很关注如何设计大规模的 JavaScript 应用,因为我一直在做的都是大规模的 JavaScript 应用。从百度 Hi 网页版到百度地图,从 Yahoo Search Direct 到豌豆荚客户端。好吧……Yahoo Search Direct 本身的规模不大,但作为一个网页插件它要能跑在任何宿主页 面上,其复杂度也不低。在大规模 JavaScript 应用开发和维护的过程中,有两个问题尤其值得关注:设计和性能。前者是必须在开发阶段之前做好的,开发开始后就来不及改了,只能事后重构;后者则更多发生在开发的中后期,等 profiling 结果出来了,再针对瓶颈做优化。在这个文章系列中,我们的关注点是设计。

尽管我一直在写跟设计相关的文章,不过现在看来我过去写下的文章都有点山寨了,也不够 update。在我看到 Large-scale JavaScript Application Architecture 这个幻灯片后,我决定重新写一个系列来说说我在大规模 JavaScript 应用方面的经验,也包括一些设想。

如果说大规模 JavaScript 应用设计方面有什么核心原则的话,我觉得核心原则就是这一条:永远不要尝试构建大规模应用。构建小应用,保证它们的可测性,然后把它们组装成大应用。因此,我觉得在讨论任何设计模式之前,我们先要讨论一下如何把大应用拆分成小应用。

“The secret to building large apps is never build large apps. Break your applications into small pieces. Then, assemble those testable, bite-sized pieces into your big application”

- Justin Meyer

模块化

CommonJS Modules

先说最理想的模块化方式,那就是 CommonJS Modules。一个模块系统至少要能解决两个问题:依赖项的加载、私有作用域和公有导出成员的区分。CommonJS Modules 通过 requireexports 很好地实现了上述两个功能。下面是一个 CommonJS Modules 模块定义和使用的例子:

util.js

var util = {
    extend: function(target, source) {
        /* implementation */
    }
};
util.extend(exports, util);

feature.js

var util = require('util');
var features = {
    core: {
        start: function() {
            /* implementation */
        }
    }
};
util.extend(exports,features);

app.js

var features = require('features');
features.core.start();
AMD

考虑到浏览器里面没有 CommonJS 的 Modules 接口,也不可能完整实现这样的 Modules 接口,所以就有了 AMD (Asynchronous Module Definition) 这样的解决方案。AMD 使用 define 函数定义模块,要求模块提前声明依赖项,然后通过回调加载模块,解决了浏览器无法同步加载模块的问题。下面是一个 AMD 模块定义和使用的例子:

util.js

define('util', [], function() {
    return {
        extend: function(target, source) {
            /* implementation */
        }
    };
};

feature.js

define('features', ['util'], function(util) {
    return {
        core: {
            start: function() {
                /* implementation */
            }
        }
    };
});

app.js

define('app', ['feature'], function(feature) {
    features.core.start();
});
CommonJS Modules/Wrappings

考虑到 AMD 的写法跟 CommonJS Modules 的写法区别十分之大,要把已有的 CommonJS Module 写法模块改为兼容 AMD 不容易,所以又有人设计一些改动不那么大的写法,如 AMD 工厂函数的 function(require, exports, ...) {...} 签名,或 CommonJS Modules/Wrappings (意思是 Modules 的 Wrappings)。由于浏览器必须异步加载依赖项,所以这些写法只能通过对工厂函数源代码做静态分析提前找出依赖项,在异步加载好之后再执行工厂函数。这样做的坏处是工厂函数内部对 require 的调用缺乏灵活性。下面是一个 CommonJS Modules/Wrappings 模块定义和使用的例子:

util.js

define(function(require, exports, module) {
    var util = {
        extend: function(target, source) {
            /* implementation */
        }
    };
    util.extend(exports, util);
});

feature.js

define(function(require, exports, module) {
    var util = require('util');
    var features = {
        core: {
            start: function() {
                /* implementation */
            }
        }
    };
    util.extend(exports,features);
});

app.js

define(function(require, exports, module) {
    var features = require('features');
    features.core.start();
});
UMD

尽管 Node.js 写好的 CommonJS Modules 模块可以通过 CommonJS Modules/Wrapping 包装一下使得它能用在浏览器内,尽管包装过的模块通过 r.js 也能用于 Node.js 环境下,不过这不是完美的解决方案。因此,又有人提出 UMD (Universal Module Definition),希望提供跨平台的模块定义方案。

UMD 现在还没有定稿,不同的人提出了不同的解决方案。最全面的方案同时支持 AMD 和 Node.js,顺便还把传统的浏览器脚本顺序加载模式也兼容了。具体的做法就是判断环境中是否存在 AMD 所依赖的 define,如果存在的话就使用 AMD 加载,不存在的话就使用别的方式模拟 AMD 加载。

util.js

(function (root, factory) {
    if (typeof exports === 'object') {
        module.exports = factory();
    } else if (typeof define === 'function' && define.amd) {
        define('util', [], factory);
    } else {
        root.util = factory();
    }
})(this, function() {
    return {
        extend: function(target, source) {
            /* implementation */
        }
    };
});

feature.js

(function (root, factory) {
    if (typeof exports === 'object') {
        module.exports = factory(require('util'));
    } else if (typeof define === 'function' && define.amd) {
        define('feature', ['util'], factory);
    } else {
        root.feature = factory(root.util);
    }
})(this, function(util) {
    return features = {
        core: {
            start: function() {
                /* implementation */
            }
        }
    };
    util.extend(exports,features);
});

app.js

(function (root, factory) {
    if (typeof exports === 'object') {
        module.exports = factory(require('feature'));
    } else if (typeof define === 'function' && define.amd) {
        define('app', ['feature'], factory);
    } else {
        root.app = factory(root.feature);
    }
})(this, function(feature) {
    var features = require('features');
    features.core.start();
});

小结

在这篇文章里面,我们了解了大规模 JavaScript 应用设计的核心原则:使用模块化的方式把大应用分解为小应用来编写和维护。同时我们也看了不同的模块定义方式,我们可以针对平台来选择一种合适的方式,也可以选择通用的方式但需要维护更多的代码。

在有了模块的概念之后,我们就可以讨论具体的设计模式了。这个系列接下来将会深入讨论每一个对大规模 JavaScript 应用设计有用的设计模式。如果你对这个系列的文章感兴趣,可以选择订阅本博客。

2012年1月12日 星期四

Covariant(协变)与 Contravariant(逆变)

今天为了解释某个问题而提到协变和逆变,发现每次解释这两个概念都会忘掉它们的本质,然后要重新看看定义,重新消化一下才能说明白。所以我决定把自己对协变和逆变的理解写下来,以免将来再次忘掉。

我知道 .NET 的用户喜欢用 delegate TResult Func<in T, out TResult>(T arg); 来解释协变逆变,我则喜欢把 Func 的签名简写为 Haskell 签名形式。也就是说,把 Func<T, TResult> 写成 f :: a -> b 的形式;把 Func<T1, T2, Result> 写成 f :: a -> b -> c 的形式。

其实无论是协变还是逆变,本质都是一样的:对于签名为 f :: A -> B 的函数,实际可接受的参数范围为 ASub,实际可返回的参数范围为 BSub。这个很容易理解吧?任何时候子类的实例都可以当做超类实例来使用,无论是接受还是返回。

协变和逆变用于描述高阶函数签名,如 f :: (X -> Y) -> Z。那上面的 f :: A -> B 做模版,我们可以把 (X -> Y) 看做 A,把 Z 看做 B。应用同样的逻辑,函数实际可接受的参数范围是 (X -> Y) 的子类,实际可返回的参数范围是 Z 的子类。对于后者我们没什么疑问,但 (X -> Y) 的子类到底是什么呢?它的所谓「子类」应该是 (XSuper -> YSub)

为什么说 (X -> Y) 的「子类」应该是 (XSuper -> YSub) 呢?因为子类在能力上应该完整覆盖超类的能力,因此如果对方要求你提供一个函数,这个函数接受 X 类型返回 Y 类型,你提供的函数至少要能接受 X 的超类而返回必须是 Y 的子类。这时候 X 是逆变参数(类型可以更宽松),而 Y 是协变参数(类型可以更严格)。

一般来说,如果把「类型可以更严格」看做协变的话,函数的返回类型一定可以协变,非高阶函数的参数也可以协变,高阶函数的非函数参数同样可以协变。把「类型可以更宽松」看做逆变的话,只有高阶函数中的函数参数中会出现逆变,也就是作为参数的参数出现。那么参数的参数的参数呢?也就是说高阶函数的参数仍然是高阶函数,那会怎么样呢?这个大家可以尝试自行分析,盯住 f :: ((X -> Y) -> Z) -> W 看一会儿,再不停类比上文的 f :: A -> B,或许你就明白了。

2012年1月7日 星期六

世界顶级黑客自传:Ghost in the Wires

上个星期终于把 Ghost in the Wires 看完了,现在就抽时间来写写书评吧。尽管我一向懒得写书评,不过这本书真的是超级推荐!

最初我是在 Audible 上面买了这本书的有声读物,然后发现朗读得超级好,尤其是 Kevin 知道危险逼近时心中的独白——「Fuuuuuuuck!」朗读的语气掌握得非常好。后来我决定买 Kindle 版来仔细读一下,因为有声读物不会专心听,只能把我到大意。随后我边听边读把整本书看完了。

这本书整体上分为 4 大部分。第 1 部分讲述 Kevin Mitnick 是如何入行的——他父亲和叔叔都是商人,这使得他很小的时候就意识到通过言语可以驱使他人做一些对自己有利的事情。同时他又是个超级 geek,从小喜欢在书店里面看一些稀奇古怪的书,例如说如何伪造身份让自己凭空消失,或者是如何获取他人的驾驶记录、资产记录、信用报告、银行信息、非公开电话号码。

由于这些爱好的驱使,他接触到了电话玩家(phone phreak),也就是一群喜欢研究电话系统和拿电话系统做实验的人。然后他开始学习如何打免费电话(Steve Jobs 和 Steve Wozniak 也做个类似的事情),并且如同研究黑箱电路一样通过不停地打电话探索电话公司内部的组织形式。

后来由于同伴的出卖(这又是因为 Kevin 爱在同伴面前耍小聪明),Kevin 被捕并被起诉。在这个过程中,公诉人和媒体想尽办法抹黑 Kevin,给他安上各种奇怪的罪名,例如黑进 NSA、切断假释官电话、篡改法官信用报表等等,其中最莫名其妙的是说他由于喜欢明星 Kristy McNichol 而切断她的电话服务。(Kevin 独白:有见过因为喜欢一个人而切断她电话的吗?)正是由于这段经历,导致 Kevin 后来被 FBI 追捕时选择了逃亡,因为他认定政府是邪恶的,只会想往他头上加更多的罪名。

第 2 部分讲 Kevin 假释后的生活。尽管他的假释条款规定了他不能再进行违背道德的黑客行为,也不能再跟别的黑客有所接触,但为了调查弟弟死亡的真相,他还是忍不住黑进电话公司并且监听被怀疑对象的电话。随后他又回到了黑客道路上来。这时候他碰到了一位神秘黑客 Eric,看样子跟娱乐圈混得很近,不用工作也能过得很滋润,而且跟 Kevin 一样对电话系统有着深入的了解。最最重要的是,Eric 知道 Kevin 不知道的一种设备,叫做 SAS,可以用来全自动地监听任意线路。

Kevin 一方面对 Eric 有所怀疑,觉得他可能是卧底或者是线人;另一方面又受不住 SAS 的诱惑,希望搞清楚这套系统是怎么用的。最终他假装电话公司员工,向 SAS 制造商要到了完整的 SAS 设计手册,里面的资料比电话公司拥有的 SAS 用户手册还要丰富,包括用户用不到的调试指令。随后 Kevin 要想办法绕过 SAS 的安全策略来监听别人。

SAS 如此强大的设备自然有相对严谨的安全策略——你可以拨号给 SAS 让它监听一条线路,SAS 也能帮你监听这条线路,但监听的结果不会输出到你拨入 SAS 的线路,只会输出到你指定的回拨线路,问题就在于每一台 SAS 都限制了回拨线路,你不能让它回拨到任意一个号码上。Kevin 想了一个很聪明的方法来绕过这个限制:他先用线路 A 拨入到 SAS 的回拨线路上,然后用线路 B 向 SAS 发送指令。SAS 要回拨时它自然会接上回拨线路,实际上就接到了线路 A 上,这时候 Kevin 就对着话筒哼一个线路就绪音(就是我们平时拿起听筒听到的声音),SAS 以为线路就绪了就开始拨号,实际上线路早就被接通了。

电话公司发现有人入侵了,安全小组就联通 FBI 一起侦察,而 Kevin 发现自己被监听以后,就想办法监听电话公司安全小组以及 FBI 的通话,以便搞清楚到底对方都收集到多少证据了,自己是否还有机会脱身。在如此几个回合后,Kevin 决定是时候人间蒸发了,于是就开始了他的逃亡之旅。

第 3 部分讲 Kevin 逃亡的过程。他先换了一个临时假身份来到了丹佛,然后再换一个长期假身份。每一次他都先要把出生证明和驾驶证骗到手,然后再去办社会保障号码卡。由于他用的都是真是存在的同龄人身份,简历上印的学历也是真实的,所以他能够找到工作赚钱生活下去。在这个过程中,他还弄到了一叠盖好章的出生证明!这意味着将来他想换身份就更容易了,直接把想要克隆的身份填写到出生证明上面就可以了,然后去考驾照和申请补发社会保障号码卡。

第 4 部分讲 Kevin 最终被捕的过程。Kevin 一直做足安全措施,包括使用克隆的手机号码,拨号时利用先前已经黑下来的电话公司交换机设置多重呼叫转移使得拨号源头难以跟踪,为此 FBI 一直都抓不到他。最后他黑了安全专家 Tsutomu Shimomura。对方是个高傲的黑客,因为被黑而决定帮助 FBI 抓住 Kevin,FBI 为此也给了他很多平民在合法程序上不可能获得的特权,例如监听电话和网络。最终 Kevin 被捕,同时公诉人再次想往他头上套上最严重的罪名,结果反而导致了 Free Kevin 活动的活跃。

媒体最初一直把 Kevin Mitnick 渲染为大黑客,说他只要对着电话吹口哨就能发射核导弹。现在由于政府玩大了,风向就发生了变化,人们开始支持 Free Kevin——不是说他无罪,而是说他应该在合理的范围内承担他的罪名。他盗窃了大量的源代码和信用卡信息,但是他没有利用这些信息赚钱,所以造成的损失也就是各大公司少赚了一份源代码授权费用而已,因此应该根据这部分损失来做出判决。

公诉人和法官当然不这样想,但 Kevin 竟然找到了一份极具参考价值的判例——一位税局官员曾经处于好奇心而获取了众多议员的税务信息,最终因为他没有因此获利而被轻判,理由是他只是好奇心驱使而已。这个判例使得 Kevin 获得了合理的对待,他只需要赔偿源代码授权费的损失就可以了,而且赔偿金额还要根据他未来几年的赚钱能力调整。考虑到假释条款上规定他不能使用电脑、不能接触电话、不能做技术咨询……基本上他就只能去麦当劳煎牛肉了,法官判他 $4000 多一些的赔偿。

假释期满后,Steve Wozniak 亲自给他送了一台最新款的 PowerBook G4,然后他又回到了他的黑客世界。只不过这次他是开咨询公司,在道德的范围内,在客户的要求下,去黑客户的公司,帮助客户找出他们的漏洞。

总的来说,如果你对社会工程学有点好奇心,或者想知道美国政府是如何不小心把 Kevin Mitnick 搞成头号黑客的,Ghost in the Wires 能够很好地满足你的好奇心。里面有技术细节,但非常少,就算你完全看不懂也不影响对情节的理解。

P.S. 这本书里面的每一章开头都有一段密文,有兴趣的人可以自己尝试去破解,懒得破解的可以搜索相关的文章。