13.4 服务器端代码
这个应用程序服务器端部分的流程非常简单,并且是可复用的。它总体目标就是获取来自客户端的SQL查询,在SQL数据库内运行,并以JSON字符串的格式返回结果。为把这个应用程序投入实践,需要精简这个应用程序的流程,并在几种流行的脚本语言如Perl、PHP、Python和Ruby等上复制不同的版本。
13.4.1 处理请求
客户端初始化这个应用程序的服务器端连接,请求执行数据库一段指定的SQL查询。你可以访问这个查询和数据库的名称,通过访问传入这个应用程序的CGI参数来执行它。在上一节中,你已经看到传入服务器端脚本的参数是数据库名称和SQL查询文本。为了解参数是如何获取的,代码清单13-2展示了Ruby版本的服务器端代码。
代码清单13-2 获取传入应用程序的服务器端部分的CGI参数,用Ruby编写
# 引入CGI库
require 'cgi'
# 创建一个新的CGI对象,它会解析传入的CGI参数
cgi = CGI.new
# 捕获查询参数
sql = cgi['sql']
# 从用户那里得到数据库名称,确保没有包含攻击性的字符
d = cgi['db'].gsub(/[^a-zA-Z0-9_-]/, "")
得到数据库名称和查询后,接着连接数据库。这引发了一个问题,该用什么类型的数据库呢?我们决定使用基于SQL的数据库,因为它在Web应用程序开发中被非常普遍的使用。而对于这个应用程序来说,使用的数据库是SQLite。
SQLite是一个很有潜力的SQL数据库实现,非常轻量和快速。为了简洁和速度,它牺牲了一些特性如用户、角色和权限等。这个应用程序非常符合需求。SQLite数据库对应一个单独的文件,所以有多少个这样的文件就可以有多少个数据库。除了非常快之外,SQLite对出于简单的应用程序或者测试的目的而建立数据库的方式也是非常迅速和简便。与其费力安装一个大型数据库(如MySQL、PostgreSQL或者Oracle),不如使用恰到好处的SQLite。
我们探讨的这几种语言(Perl、PHP、Python和Ruby)都有SQLite的一种或多种的支持方式:
l Perl有DBD::SQLite模块。这个模块特别有名,因为开发者决定在模块自身内完全实现SQLite的规范,这就是说,再也不需要额外的下载就能让数据库跑起来。
l PHP5有一个内置的SQLite支持。不幸的是,它只支持SQLite2(对一些应用程序来说不还算很坏),但若要完全兼容不同的代码,你需要安装PHP SQLite 3库。
l Python和Ruby都有SQLite库,官方SQLite的安装就直接跟它们挂钩。Python2.5直接内置SQLite支持(但我们并没有使用它,因为相对较新)。
强烈推荐你在一些小项目中使用SQLite。它的确是建置和执行的好方法,而不需要浪费时间和成本在大型数据库的安装上。
每一种语言中如何连接SQLite数据库都不尽相同。但它们都需要两个步骤:第一步是引入SQLite库(以提供通用的连接函数),第二步是连接到数据库并记录连接状态以备后面之用。代码清单13-3展示了Ruby的实现方法。
代码清单13-3 引入外部的SQLite库并连接到数据库的服务器端代码,Ruby版本
# 引入外部SQLite库
require 'rubygems'
require_gem 'sqlite3-ruby'
# 稍候程序……
# 'd' 需要清晰,确保不会提供带攻击性字符的数据库文件名
d = cgi['db'].gsub(/[^a-zA-Z0-9_-]/, "")
# 连接到SQLite数据库,它只是一个文件而已
# 'd' 包含了我们数据库的名称,称为'wiki'
db = SQLite3::Database.new('../../data/' + d + '.db')
开启了SQLite数据库的连接后,现在你可以执行客户端的查询并获取相应结果了。
13.4.2 执行和格式化SQL
开启数据库的一个连接后,现在你应该可以执行SQL查询了。我们的最终目标是可以把查询获得的结果转换成某种形式,这种形式可以容易地转换成JSON字符串并返回到客户端。SQL结果中最方便的可摘要形式是散列的数组(array of hash),如代码清单13-4所示。散列数组以数据库匹配的每一行来描述,每一行数据的键/值对分别描述了列名称和列的值。
代码清单13-4 服务器返回JSON结构的例子
[
{
title: "HomePage",
author: "John",
content: "Welcome to my wonderful wiki!",
date: "20060324122514"
},
{
title: "Test",
author: "Anonymous",
content: "Lorem ipsum dolem...",
date: "20060321101345"
},
...
]
你所选择语言的不同,导致把SQL结果转换成所需结果的难度也不同。但最常见的情形就是SQL库返回的两个东西:列的名称数组,以及包含所有行数据数组的数组(如代码清单13-5所示)。
代码清单13-5 在Ruby中,查找wiki修订信息而执行SQL查询后返回的数据结构
rows.columns = ["title","author","content","date"]
rows = [
["HomePage","John","Welcome to my wonderful wiki!","20060324122514"],
["Test","Anonymous","Lorem ipsum dolem...","20060321101345"],
...
]
将代码清单13-5所示的SQL结果转换为如代码清单13-4所示的数据结构的过程,是相当棘手的。显然,你需要迭代匹配每一行,创建一个临时的散列(hash),把所有的列数据加进去,然后把这个新散列添加到全局数组中去。Ruby的实现如代码清单13-6所示。
代码清单13-6 在Ruby中如何执行SQL语句并把结果添加到最终的数据结构中(称为r)
# 如果sql有返回的行 (例如一条SELECT语句)
db.query( sql ) do |rows|
# 遍历返回的每一行
rows.each do |row|
# 建立一个临时的散列
tmp = {}
# 强制数组栏目转换为散列的键/值对
for i in 0 .. rows.columns.length-1
tmp[rows.columns[i]] = row[i]
end
# 把行散列添加到已找到的行数组中去
r.push tmp
end
end
已经有了最终的合适数据结构,你可以把它转换成JSON字符串了。本质上,JSON是一种使用兼容JavaScript的对象标识来描述值(字符串和数字)、值的数组和散列(键/值对)的方式。因为你已经小心应对并确保这个数据结构包含的就是数组、散列和字符串,你可以轻松地把它转换成JSON格式的字符串了。
在这个应用程序上使用的所有语言都有JSON串行化(serialization,把原始的数据结构转换成JSON字符串)的实现,而这正是你所需要的。此外,因为大部分JSON格式数据的输出都是十分轻量的,所以输出到浏览器也非常方便。而且,每一门语言的实现到最后都很相似,从而能够简化语言的迁徙过程。
l 每种实现都以库或者模块的形式存在。
l 每种实现都可以转换语言的原始对象(比如字符串、数组和散列)。
l 每种实现都很容易从JSON格式对象的字符串中获取数据。
尽管如此,实现JSON串行化尤为优雅的语言莫过于Ruby了。下面是一个把对象转换成JSON字符串(在载入JSON库之后)并输出到客户端上的例子:
# 把对象(r)转换成JSON字符串,并输出
printr.to_json
强烈推荐你探索一下服务器端实现的代码,并了解它如何处理SQL查询和JSON串行化的。我想你会因为他们的简单而感到非常愉悦。






