(如果你按照下面的步骤来却碰到了问题,你可以查看 Github 上的完整示例应用。)
我们的示例应用由两个 Ruby 源文件组成:
evernote_config.rb, 包括了一些配置信息。
en_oauth.rb, 应用的源代码。
配置过程
evernote_config.rb 看起来像这样:
在顶部的两个 require 语句是针对 Ruby OAuth gem的,紧接着是evernote_oauth gem。这个 gem 依赖于 oauth gem 并且实现了整个印象笔记云 API,还有附加的 OAuth 相关的功能。
在这些下面,是 Consumer Key 和 Consumer Secret 的值(它们需要被设置为你的印象笔记云 API Key 的相应的值)。
最后,我们有一个 boolean 变量 SANDBOX,它用于控制我们的应用是否要连接到沙盒开发服务器。如果值为 false,那我们的应用会连接到印象笔记的产品环境。
示例应用的组件
在 en_oauth.rb 中,我们会看到应用的几个不同部分:before 过滤器,helpers 函数,get路由和一对简单的 ERB 模板。
首先,让我们先来看一下“前言”:
首先,我们引入 Sinatra gem 并启用基于cookie的 sessions(Sinatra 的一部分)。其次,我们将当前目录引入$LOAD_PATH中(这样我们可以载入我们的配置文件)。最后,我们载入配置文件。相当清晰直观,对吧?
现在,开始进入示例应用的主要内容:
before
当应用初始化的时候这个 filter 开始运行,并检查OAUTH_CONSUMER_KEY 和 OAUTH_CONSUMER_SECRET 常量是否已经在 evernote_config.rb 中设置好。如果任一常量被初始化为空字符串,那么就会显示错误消息:
helpers
一个要在应用中用到的简单的 Ruby 函数的集合。其中许多就是表示我们从应用的 routes 部分抽象出来的变量。这部分也恰巧包括了对印象笔记云 API 的调用。让我们来逐个快速看一下:
auth_token 存储了我们要调用印象笔记云 API 时使用的认证 token。因为我们的 web 应用是无状态的,所以这个值被储存在了 Sinatra 的session中:
client 代表EvernoteOAuth::Client的实例。它实例化的时候要用到auth_token(如果我们有的话),OAUTH_CONSUMER_KEY,OAUTH_CONSUMER_SECRET 和 SANDBOX。
user_store 和 note_store 是印象笔记云 API 的两个服务:前者处理用户和帐户信息,后者处理用户的笔记、笔记本等等。
en_user 代表从印象笔记云 API 上获取的一个印象笔记 User 实例。
notebooks 包括了调用 NoteStore.listNotebooks() 时印象笔记云 API 返回的 Notebook 对象的一个集合
total_note_count 是我们的应用中唯一一个运行逻辑不像简单返回一个值那么简单的函数,所以我们应当对其多做一些解释:
开始是一个 filter,一个新的(空的) NoteFilter 的实例。因为我们想要查询整个帐户,我们不需要添加任何附加的过滤条件(在这里查看完整的 NoteFilter 定义)。
接下来,我们调用 findNoteCounts,传入我们的auth_token 和 filter 作为参数(false 参数意味着我们不想获取来自帐户废纸篓中的笔记)。findNoteCounts 返回一个 NoteCollectionCounts 的实例——它是一个哈希表,表中将笔记本的 GUID 映射到该笔记本的笔记条数。遍历我们所有的笔记本,我们用笔记本的 GUID,在counts(我们的 NoteCollectionCounts实例)中获取对应笔记本的笔记条数,然后返回所有这些笔记的条数和。
get 路由
示例中的每一个 get 的实例都表示一个 HTTP 请求跟一个对应的 URL 模式。我们的示例应用总共有7个路由。其中的4个用于直接处理 OAuth 的实现,我们将在下一部分来讨论它。剩下的3个是:
我们的站点根目录,这个路由就是用于显示 index 模板(在 en_auth.rb的底部定义为 @@index)并提示用户开始 OAuth 认证流程。
在这个路由中,我们首先清空 session 对象(它用于在无状态的 web 交互中跟踪用户进度和状态),然后将用户重定向到 / 这样他们可以再次开始授权过程。
这个方法收集了关于用户帐户的各种信息(使用定义在示例的 helpers 的方法),将它们添加到当前的 session 对象并渲染 index 模板。如果出现问题,那么代码的 rescue 块会被触发执行,同时错误原因会被填入 @last_error 变量,变量会被发送给 error 模板(也在 en_auth.rb 底部定义)。
OAuth
正如大多数 OAuth 流程一样,我们的认证过程有以下几个步骤:
- 从印象笔记云 API 上获取一个请求 token。这个 token 用于在下一步请求一个认证 token。
- 使用请求 token,将用户转到印象笔记的站点,在那里他们将用自己的用户名密码完成登录并授权我们的应用访问他们的帐户。
- 印象笔记的网站会将用户重定向到我们预定义的回调 URL 并附加上我们将要在调用印象笔记云 API 时使用的认证 token。
这个过程在我们的应用中已经在某种程度上自动化了(至少在用户交互方面)。这也就是说,上面的每一个步骤完成的时候,下一个步骤就会通过 Sinatra 的 redirect 功能被自动调用。
这里,我们向印象笔记云 API 获取我们的临时请求 token。
首先,我们取出由 request 对象的 url 成员定义的当前 URL 地址,移除掉 requesttoken 部分并将其用 callback 来代替以形成我们的回调 URL。然后,我们调用 client.authentication_request_token 来请求我们的 token(并发送 callback_url 变量来通知印象笔记云 API 将响应发送到哪里)。如果一切顺利,token 的值会被填入 session[:request_token] 然后用户会被定向到 /authorize:
一旦我们确认了 session[:request_token]存在,那我们就从请求 toekn 中取出 authorize_url 并将用户定向到提示使用印象笔记账号登陆并授权应用访问帐户的页面。如果 session[:request_token] 还未赋值,用户会收到错误消息并被提示重新开始授权。
在成功验证印象笔记帐户并授权我们的应用之后,用户会被定向到 /callback(我们在 /requesttoken 路由中定义了):
当用户被转到我们的回调 URL 之后,我们会验证:
- URL 中参数
oauth_verifier 已赋值。
request_token 的值已经在 session 中。
如果以上两点有有一个是 true,我们就会在 session 中添加参数 oauth_verifier。如果以上两点全不是 true,那么会给用户提示错误信息。
接下来,我们取出用于调用 API 的访问 token 并将其添加进 session 中。如果访问 token 不存在,则会显示错误信息。如果存在,那么我们会把用户转到我们前面定义的 /list 路由。假定一切都如所描述的能正常工作,用户就会看到他们的用户名,帐户中的笔记条数跟帐户中每一个笔记本的名称。
模板
示例应用使用了两个 ERB 模板:index 和 error:
很显然,这是一个非常简单的模板。首先,我们要使用户能通过印象笔记 API 完成认证。无论如何这个都会显示。如果 session[:notebooks] 已定义了,那么我们有理由确认用户的数据能被印象笔记云 API 获取到了。之后我们会显示每一条数据:
比 index 更简单的是 error,它显示 @last_error的值并提示用户清空session重新开始授权。