天天看点

《RESTful Web Services》学习笔记

《RESTful Web Services》Leonard Ricbardson & Sam Ruby

前言

每个Web应用(包括每个网站)都是一个服务。

Web 服务就是为机器设计的网站。 

REST: Representational State Transfer(表示性状态转移)

我们引入了面向资源的架构(Resource-Oriented Architecture, ROA)作为用于设计REST式Web服务(RESTful web services)的一组切合实际的原则。

从HTTP0.9中我们可以看到可寻址性(addressablity)和无状态性(statelessness)-正是这两条基本设计原则,另HTTP较其同类更加优秀,并得以延伸到今天如此巨大的规模。

万维网(World Wide Web)是一个简单而灵活的分布式编程环境。

为人类使用而设计的 human web, 跟为软件程序调用而设计的 programmable web 没有本质区别。

第1章  Programmable Web 及其分类

programmable web 跟 human web 的主要不同在于:programmable web 返回的不是令人赏心悦目的HTML页面,而是冷漠刻板的XML文档。

Web服务客户端必须自动从HTTP响应中提取含义,并根据含义决定下一个动作。

programmable web 是基于 HTTP 和 XML 技术的。

HTTP:信封里的文档

HTTP 是一种基于文档的协议。客户端把文档放在信封里,然后发给服务器,作为回应,服务器把响应文档放在信封里,然后发给客户端。

方法信息

作用域信息

常见的Web服务架构主要有三种:REST 式架构、RPC 式架构和 REST-RPC 混合架构

REST式、面向资源的架构

REST式意味着,方法信息(method information)都在 HTTP 方法(HTTP method)里;面向资源的架构(ROA)意味着,作用域信息(scoping information)都在 URI 里。

RPC 式架构

RPC式架构意味着:方法信息和作用域信息都在信封或报头里。

REST 式服务为不同的作用域信息暴露不同的URI;而RPC式服务一般为每个“文档处理器”暴露一个URI。

REST-RPC 混合架构

由于 HTTP 工作方式的原因,任何采用普通 HTTP 并暴露多个 URI 的 RPC 式服务,往往最终成为 REST 式架构或混合架构。

Human Web 是基于 Programmable Web 的

只要 Web 服务客户端是基于 Web 的,它就是 Web 服务。

Programmable Web 涉及的技术

HTTP

对于一个 REST 式 Web 服务,它会在 HTTP 方法里寻找方法信息,在 URI 里寻找作用域信息;而 RPC 式 Web 服务则往往忽略 HTTP 方法,在 URI、HTTP 报头或实体主体里寻找方法信息和

作用域信息。

URI

一个 REST 式面向资源的服务为客户端可能操作的每一则数据暴露一个URI;一个 REST-RPC 混合服务,为客户端可能进行的每一个操作暴露一个 URI(比如取数据用一个 URI,删除数据用一

个 URI);一个 RPC 式服务,为每个处理远程调用的进程暴露一个 URI,一般来说这样的 URI 只有一个,即服务的“端点(endpoint)”。

XML-RPC

XML-RPC 是一种用于表达“函数调用及其返回值”的数据机构格式,它是专门用于 RPC 式 Web 服务的。

SOAP

跟 HTTP 一样,SOAP 也是一种信封格式,只不过它是基于 XML 的。基本上,现在每个采用 SOAP 的 Web 服务都属于 RPC 式架构。

WS-*

这些标准定义各种特定用途的 SOAP 报头。

WSDL

WSDL(Web Service Description Language, Web 服务描述语言) 是一套用于描述 SOAP Web 服务的 XML 词汇。

WADL

WADL(Web Application Description Language, Web 应用描述语言) 是一套用于描述 REST 式 Web 服务的 XML 词汇。

第2章  编写 Web 服务客户端

Web 服务就是网站

示例2-1:用 Yahoo!Web 服务搜索 Web

#!/usr/bin/ruby

# yahoo-web-search.rb

require 'open-uri'

require 'rexml/document'

require 'cgi'

BASE_URI = 'http://api.search.yahoo.com/WebSearchService/V1/webSearch'

def print_page_titles(term)

# 获取资源(一个包含搜索结果的 XML 文档)

term = CGI::escape(term)

xml = open(BASE_URI + "?appid=restbook&query=#{term}").read

# 把上述 XML 文档解析为一个数据结构

document = REXML::Document.new(xml)

# 用 XPath 在数据结构里寻找相关信息

REXML::XPath.each(document, '/ResultSet/Result/Title/[]') do |title|

puts title

end

end

(puts "Usage: #{$0} [search term]"; exit) if ARGV.empty?

print_page_title(ARGV.join(' '))

del.icio.us: 示例应用

服务器应该是理想主义的,客户端必须是实用主义的-这是 Postel 法则“严以律己,宽以待人”的一个变形。

Ruby: rest-open-uri 和 net/http

示例2-4:一个采用 open-uri 的 del.icio.us 客户端

#!/usr/bin/ruby -w

# delicious-open-uri.rb

require 'rubygems'

require 'open-uri'

require 'rexml/document'

# 获取一个 del.icio.us 用户的最近书签,并把每个书签打印出来。

def print_my_recent_bookmarks(username, password)

# 发送 HTTPS 请求

response = open('https://api.del.icio.us/v1/posts/recent', :http_basic_authentication => [username, password])

# 把返回的实体主体作为 XML 文档来读取

xml = response.read

# 把 XML 文档解析为一个数据结构

document = REXML::Document.new(xml)

# 对于每个书签...

REXML::XPath.each(document, "/posts/post") do |e|

# 打印书签的描述和网址

puts "#{e.attributes['description']}: #{e.attributes['href']}"

end

end

# 主程序

username, password = ARGV

unless username and password

puts "Usage: #{$0} [username] [password]"

exit

end

print_my_recent_bookmarks(username, password)

Python: httplib2

示例2-5:一个用 Python 编写的 del.icio.us 客户端

#!/usr/bin/python2.5

# delicious-httplib2.py

import sys

from xml.etree import ElementTree

import httplib2

# 获取一个 del.icio.us 用户的最近书签,并把每个书签打印出来

def print_my_recent_bookmarks(username, password)

client = httplib2.Http(".cache")

client.add_credentials(username, password)

# 发出 HTTP 请求,并获取响应和实体主体

response, xml = client.request('https://api.del.icio.us/v1/posts/recent')

# 把 XML 格式的实体主体转换成一个数据结构

doc = ElementTree.fromstring(xml)

# 打印出每个书签的信息

for post in doc.findall('post'):

print "%s: %s" % (post.attrib['description'], post.attrib['href'])

# 主程序

if len(sys.argv) != 3:

print "Usage: %s [username] [password]" % sys.argv[0]

sys.exit()

username, password = sys.argv[1:]

print_my_recent_bookmarks(username, password)

Java: HttpClient

C#: System.Web.HTTPWebRequest

PHP: libcurl

JavaScript: XMLHttpRequest

命令行:curl

$curl https://username:[email protected]/v1/posts/recent

用 XML 解析器处理响应

有两种基本的 XML 解析策略:基于文档的策略(DOM 等树式解析器)和基于事件的策略(SAX 及拖式解析器)

Ruby: REXML

示例2-9:采用 REXML 的 SAX 解析器

#!/usr/bin/ruby -w

# delicious-sax.rb

require 'open-uri'

require 'rexml/parsers/sax2parser'

def print_my_recent_bookmarks(username, password)

# 发出 HTTPS 请求,并把响应实体主体作为 XML 文档来读取

xml = open('https://api.del.icio.us/v1/posts/recent', :http_basic_authentication => [username, password])

# 创建一个 SAX 解析器来解析 XML 实体主体

parser = REXML::Parsers::SAX2Parser.new(xml)

# 当 SAX 解析器遇到 "post" 标签时...

parser.listen(:start_element, ["post"]) do |uri, tag, fqtag, attributes|

# 打印出该标签信息

puts "#{attributes['description']}: #{attributes['href']}"

end

# 让解析器开始解析 XML 实体主体

parser.parse

end

# 主程序

username, password = ARGV

unless username and password

puts "Usage: #{$0} [username] [password]"

exit

end

print_my_recent_bookmarks(username, password)

示例2-10:采用 REXML 的 拖式解析器

Pyton: ElementTree

Java: javax.xml、Xerces 或 XMLPull

C#: System.Xml.XmlReader

PHP

JavaScript: responseXML

JSON Parsers: 处理序列化数据

示例2-13:用 JavaScript 演示 JSON

<!-- json-demo.html -->

<!-- 在实际应用中,你可以把json.js保存在本地而不是每次都从 json.org 获取 -->

<script type="text/javascript" src="http://www.json.org/json.js">

</script>

<script type="text/javascript">

array = [3, "three"]

alert("Converted array to JSON string: '" + array.toJSONString() + "'")

json = "[4, \"four\"]"

alert("Converted JSON '" + json + "' into array:")

array2 = json.parseJSON()

for(i = 0; i < array2.length; i++)

{

alert("Element #" + i + " is " + array2[i])

}

</script>

示例2-14:Ruby 语言的 JSON 示例

# json-demo.rb

require 'rubygems'

require 'json'

[3, "three"].to_json

JSON.parse('[4, "four"]'

JSON 适用于表达数据结构;XML 和 HTML 专门用于表达文档

WADL 简化客户端的编写

Web 服务描述语言(Web Application Description Language, WADL)

示例2-6:一个用 Ruby/WADL 编写的 del.icio.us 客户端

#!/usr/bin/ruby

# delicious-wadl-ruby.rb

require 'wadl'

if ARGV.size != 2

puts "Usage: #{$0} [username] [password]"

exit

end

username, password = ARGV

# 载入 WADL 文档里的一个应用

delicious = WADL::Application.from_wadl(open("delicious.wadl"))

# 给出认证信息

service = delicious.v1.with_basic_auth(username, password)

begin

# 找出最近书签功能

recent_posts = service.posts.recent

# 对于每个最近书签

recent_posts.get.representation.each_by_param('post') do |post|

# 打印出描述和网址

puts "#{post.attributes['description']}: #{post.attributes['href']}"

end

rescue WADL::Faults::AuthorizationRequired

puts "Invaild authentication information!"

end

第3章  REST 式服务有什么特别不同?

介绍Simple Storage Service

资源

REST 式 S3 服务提供三种资源:

* 桶列表(https://s3.amazonaws.com/),这种类型的资源只有一个

* 一个特定的桶(https://s3.amazonaws.com/{name-of-bucket}/),这种类型的资源最多可以有100个

* 某个桶里的一个特定的 S3 对象(https://s3.amazonaws.com/{name-of-bucket}/{name-of-object}),这种类型的资源数量不限

表3-1:S3 资源及其方法

GET HEAD PUT DELETE

桶列表(/) 列出所有的桶 - - -

一个桶(/{bucket}) 列出桶里的对象 - 创建桶 删除桶

一个对象(/{bucket}/{object}) 获取对象的值及元数据 获取对象的元数据 设置对象的值及元数据 删除对象

HTTP 响应代码

一个 S3 客户端

#!/usr/bin/ruby -w

# S3lib.rb

# 发送 HTTP 请求和解析响应所需的库

require 'rubygems'

require 'rest-open-uri'

require 'rexml/document'

# 对请求进行签名所需的库

require 'openssl'

require 'digest/sha1'

require 'base64'

require 'uri'

module S3 # 一个容纳所有代码的模块的开头

module Authorized

  # 输入公共标识符合密钥

@@access_key_id = ''

@@secret_access_key = ''

if @@public_key.empty? or @@private_key.empty?

raise "You need to set your S3 keys."

end

HOST = 'https://s3.amazonaws.com/'

end

# S3::BucketList 类

# 桶列表

class BucketList

include Authorized

# 获取该用户的所有桶

def get

buckets = []

# 向桶列表的 URI 发送 GET 请求,并读取返回的 XML 文档

doc = REXML::Document.new(open(HOST).read)

# 对于每个桶

REXML::XPath.each(doc, "//Bucket/Name") do |e|

# 创建一个 Bucket 对象,并把它添加到列表中

buckets << Bucket.new(e.text) if e.text

end

return buckets

end

end

# S3::Bucket 类

# 一个 S3 桶

class Bucket

include Authorized

attr_accessor :name

def initialize(name)

@name = name

end

# 桶的 URI 等于服务的根 URI 加上桶名

def uri

HOST + URI.escape(name)

end

# 在 S3 上保存这个桶

def put(acl_policy=nil)

# 设置 HTTP 方法和访问策略

args = {:method => :put}

args["x-amz-acl"] = acl_policy if acl_policy

# 向该桶的 URI 发送 PUT 请求

open(uri, args)

return self

end

# 删除该桶,若桶不为空,则删除失败,返回409("Conflict")

def delete

open(uri, :method => :delete)

end

# 获取桶的(全部或部分)对象

def get(options={})

# 获取桶的基准 URI

uri = uri()

suffix = '?'

# 对用户提供的每个选项

options.each do |param, value|

# 如果属于某个 S3 选项

if [:Prefix, :Marker, :Delimiter, :MaxKeys].member? :param

# 把它附加到 URI 上

uri << suffix << param.to_s << '=' << URI.escape(value)

suffix = '&'

end

end

# 发送 GET 请求,并读取 XML 文档

doc = REXML::Document.new(open(uri).read)

there_are_more = REXML::XPath.first(doc, "//IsTruncated").text == "true"

# 构件一个 S3::Object 对象的列表

objects = []

# 对于桶里的每个 S3 对象

REXML::XPath.each(doc, "//Contents/Key") do |e|

# 构造一个 S3::Object 对象,并把它添加到列表里

objects << Object.new(self, e.text) if e.text

end

return objects, there_are_more

end

end

# S3::Object 类

# 跟某个桶关联的一个具有值和元数据的 S3 对象

class Object

include Authorized

# 对象所在桶

attr_reader :bucket

# 对象名称

attr_accessor :name

# 元数据和值

attr_writer :metadata, :value

def initialize(bucket, name, value=nil, metadata=nil)

@bucket, @name, @value, @metadata = bucket, name, value, metadata

end

def uri

@bucket.uri + '/' + URI.escape(name)

end

# 获取对象的元数据hash

def metadata

# 如果没有元数据

unless @metadata

# 向对象的 URI 发送一个 HEAD 请求,并从响应的 HTTP 报头里读取元数据

begin

store_metadata(open(uri, :method => :head).meta)

rescue OpenURI::HTTPError => e

if e.io.status == ["404", "Not Found"]

# 假如没有元数据是因为对象不存在,这不算错误

@metadata = {}

else

# 其他情况,作错误处理

raise e

end

end

end

return @metadata

end

# 获取对象的值和元数据

def value

# 如果没有值

unless @value

# 向对象的 URI 发送 GET 请求

response = open(uri)

# 读取元数据

store_metadata(response.meta) unless @metadata

# 从实体主体里读取值

@value = response.read

end

return @value

end

# 在 S3 上保存对象

def put(acl_policy=nil)

# 以原始元数据副本开始,或者以空 hash 开始

args = @metadata ? @metadata.clone : {}

# 设置 HTTP 方法、实体主体及一些 HTTP 报头

args[:method] = :put

args["x-amz-acl"] = acl_policy if acl_policy

if @value

args["Content-Length"] = @value.size.to_s

args[:body] = @value

end

# 发送 PUT 请求

open(uri, args)

return self

end

# 删除对象

def delete

# 发送 DELETE 请求

open(uri, :method => :delete)

end

private

# 根据 HTTP 响应报头生成 S3 对象的元数据

def store_metadata(new_metadata)

@metadata = {}

new_metadata.each do |h, v|

if RELEVANT_HEADERS.member?(h) || h.index('x-amz-meta') == 0

@metadata[h] = v

end

end

end

RELEVANT_HEADERS = ['content-type', 'content-disposition', 'content-range', 'x-amz-missing-meta']

end

# 重写 Authorized 模块 

第4章  面向资源的架构

REST 并不是一种架构,而是一组设计原则。

什么是资源?

任何事物,只要具有被引用的必要,它就是一个资源。

URIs

资源必须至少有一个 URI, URI 既是资源的名称,也是资源的地址。

URI 应具有描述性。

资源及其 URI 应该给人以直觉上的关联。

URI 应具有一定的结构。

URI 跟资源的关系

可寻址性

无状态性

应用状态 VS 资源状态

前者应该保存在客户端,后者应该保存在服务端。

当你使用搜索引擎时,你的搜索请求和当前页码就属于应用状态,各个客户端应当自己管理自己的应用状态。

表示

表示是关于一个资源的状态的任何有用信息。

表示的选择

为一个资源的各个表示设置不同的 URI。

建议尽量把更多信息放在 URI 里,放在请求元数据里的信息越少越好。因为 URI 可以传播,而元数据则不能。

链接与连通性

将超媒体作为应用状态的引擎。

这种具有链接的特性为连通性。

资源之间相互链接的程度越强,资源之间的关系就越清晰。

统一接口

获取一个资源的表示:GET

创建一个新资源:PUT

修改资源:PUT

删除资源:DELETE

获取一个只包含元数据的表示:HEAD

查看一个资源支持哪些HTTP方法:OPTIONS

HTTP Allow 报头:Allow: GET, HEAD // 表示该资源支持 GET 和 HEAD 请求

POST

创建从属资源

如果客户端负责决定新资源采用什么 URI,那就用 PUT,如果是服务器负责新资源采用什么 URI,那就用 POST。

附加到资源状态

重载 POST:不符合统一接口

安全性和幂等性

安全性:GET 和 HEAD 都是读取数据的请求,不改变服务器状态

幂等性:无论对该资源做多少次这种操作,结果总是一样的。PUT 和 DELETE 操作都是幂等的。

POST 既不是安全的,也不是幂等的。向某个“工厂”资源发出两个相同的 POST 请求,很可能会导致创建两个具有相同信息的从属资源。

第5章  设计只读的面向资源的服务

资源设计

面向对象程序的标准设计方法是把系统分解为一个个功能部件,即其中的名词。

一个对象就是一个事物。每个名词都有自己的类和自己的方法。

编程语言里的一个类可以暴露无数个方法,并给这些方法取任意名称。但一个资源只暴露一个统一接口,最多支持六种 HTTP 方法。

统一接口意味着,在面向对象设计里被视为动词的事物,在面向资源的设计里必需被视为对象。

根据需求创建只读资源

1. 规划数据集

2. 把数据集划分为资源

对于其中每个资源:

3. 用 URI 为该资源命名

4. 暴露一个统一接口的子集

5. 设计来自客户端的表示

6. 设计发给客户端的表示

7. 用超链接和表单把该资源与已有资源联系起来

8. 考虑有哪些典型的事件经过

9. 考虑可能出现哪些错误情况

把数据集划分为资源

一个资源就是任何值得作为超链接目标的事物。

服务暴露的资源可分为三类:

为特别目的专门预定义的一次性资源(如主页)

服务暴露的每一个对象所对应的资源

代表在数据集上执行算法的结果的资源

要理解“把算法暴露为资源”这一方式,不要从动作方面来考虑,而要从该动作的结果方面来考虑。

命名资源

URI 设计有三条基本原则:

1. 用路径变量来表达层次机构:/parent/child

2. 用逗号或分号表达非层次结构:/parent/child1;child2,当次序有关紧要时用逗号,否则用分号。

3. 用查询变量来表达算法的输入,例如:/search?q=jellyfish&start=20

第6章  设计可读写的面向资源的服务

将用户账户作为资源

暴露账户的 URI: https://maps.example.com/user/{user-name}

用户自定义地点也将成为一个资源

其URI 为:/usr/{user-name}/{planet}/{latitude},{longitude}/{placename}

第7章  一个服务的实现

列表(list)和列表中的项(item)是普遍存在的两种资源。

用户列表资源:/users

用户资源:/usrs/{username}

书签列表资源:/usrs/{username}/bookmarks

书签资源:/usrs/{username}/bookmarks/{bookmarkname}

标签列表资源:/usrs/{username}/tags

标签资源:/usrs/{username}/{tagname}

日历资源:/usrs/{username}/calendar #用于查阅书签发布历史

最近书签列表资源:/recent #查询所有最近发布的书签

最近发布的某一书签资源:/recent/{tagname}

Rails代码实现

第8章  REST 和 ROA 最佳实践

客户端无法直接获取一个资源,只能获取该资源的一个表示

表示应当是可寻址的

建议给每个资源的不同表示分别分配不同的 URI。

可寻址的重要性

无状态的重要性

统一接口的重要性

连通性的重要性

事务

把事务暴露为资源

假如遇到无法适应统一接口的动作,就把它本身暴露为资源。

在 URI 里放入版本信息,如 /v1/users/leonardr

摘要认证:

质询:(这是你通过认证所需的全部信息,假如你知道正确的用户名和密码的话)

401 Unauthorized

WWW-Authenticate: Digest realm="My Private Data",

qop="auth",

nonce="0cc175b9c0jk0j9k8k8jd3455kk87d7s8f" #随机数

opaque="920eb5ffeelad2hod9fg7f8fg9g9g90f"

示例8-2:构造摘要

#!/usr/bin/ruby

# calculate-http-digest.rb

require 'md5'

# 原请求里的信息

METHOD="GET"

PATH="/resource.html"

# 质询里的信息

REALM="My Private Data"

NONCE="0cc175b9c0jk0j9k8k8jd3455kk87d7s8f"

OPAQUE="920eb5ffeelad2hod9fg7f8fg9g9g90f"

QOP="auth"

# 客户端已知信息

NC="00000001"

CONONCE="4a8a08kkdhifiwhlwejiwpjoe809877wkk"

USER="Alibaba"

PASSWORD="open sesame"

# 计算摘要

ha1 = MD5::hexdigest("#{USER}:#{REALM}:#{PASSWORD}")

ha2 = MD5::hexdigest("#{METHOD}:#{PATH}")

ha3 = MD5::hexdigest("#{ha1}:#{NONCE}:#{NC}:#{CNONCE}:#{QOP}:#{ha2}")

puts ha3

# 237898773mklslajfwohlaldafjoiwh

#客户端发送摘要(我知道正确的用户名和密码,证明在这里)

GET /resource.html HTTP/1.1

Host: www.example.com

Authorization: Digest username="Alibaba",

realm="My Private Data",

nonce="0cc175b9c0jk0j9k8k8jd3455kk87d7s8f"

uri="/resource.html",

qop=auth,

nc=00000001,

cnonce="4a8a08kkdhifiwhlwejiwpjoe809877wkk",

response="237898773mklslajfwohlaldafjoiwh", # 摘要

opaque="920eb5ffeelad2hod9fg7f8fg9g9g90f"

压缩

客户端在 Accept-Encoding 中指出可接受的压缩算法

服务器在 Content-Encoding 中使用的压缩算法

Loop-Before-You-Leap请求

用来防止客户端徒劳地向服务器发送巨大的表示

PUT /filestore/myfile.txt HTTP/1.1

Host: example.com

Content-length: 524288000

Expect: 100-continue

部分 GET

第9章  服务的技术构件

表示格式

超媒体

超媒体分为:链接和表单

表单分为:应用表单和资源表单

应用表单告诉客户端如何处理应用状态,如搜索引擎

资源表单告诉客户端如何构造一个表示来更新资源状态,如 PUT

URI 模板

WADL

Web 应用描述语言(Web Application Description Language, WADL)是一种用于“表达 HTTP 资源的行为”的 XML 词汇。

第10章  面向资源的架构 VS 大 Web 服务

第11章  将 Ajax 应用作为 REST 客户端

Ajax 应用就是在 Web 浏览器里运行的 Web 服务客户端。 

一个 del.icio.us 示例

<!DOCTYPE HTML PUTBLIC "-//W3C//DTD HTML 4.0

Transitional//EN" "http://www.w3.org/TR/REC-html140/transitional.dtd">

<!--delicious-ajax.html-->

<html>

<head>

<title>JavaScript del.icio.us</title>

</head>

<body>

<h1>JavaScript del.icio.us example</h1>

<p>Enter your del.icio.us account information, and I'll fetch and display your most recent bookmarks.</p>

<form οnsubmit="callDecious(); return false;">

Username:<input id="username" type="text" /><br />

Password:<input id="password" type="password" /><br />

</form>

<div id="message"></div>

<ul id="links"></ul>

<script type="text/javascript">

function setMessage(newValue) {

message = document.getElementById("message");

message.firstChild.textContent = newValue;

}

function callDelicious() {

// 向浏览器申请获得向外部 Web 服务发送请求的权限

try {

if(netscape.security.PrivilegeManager.enablePrivilege)

netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");

} catch (e) {

alert("Sorry, browser security settings won't let this program run.");

return;

}

// 获取用户输入的账户信息

var username = document.getElementById("username").value;

var password = document.getElementById("password").value;

// 清空链接列表

var links = document.getElementById("links");

while(links.firstChild)

links.removeChild(links.firstChild)

setMessage("Please wait...");

// 发送请求

request = new XMLHttpRequest();

request.open("GET", "http://api.del.icio.us/v1/posts/recent", true, username, password);

request.onreadystatechange = populateLinkList;

request.send(null);

// 当 HTTP 请求完成时调用

function populateLinkList() {

if(request.readyState != 4) // 请求尚未完成

return;

setMessage("Request complete.");

if(netscape.security.PrivilegeManager.enablePrivilege)

netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead"); // Netscape中申请向外部 Web 发送请求的权限

// 在表示中找到所有的“post”标签

posts = request.responseXML.getElementsByTagName("post");

setMessate(posts.length + " link(s) found:");

//对于 XML 文档中的每一个“post”标签...

for(var i = 0; i < posts.length; i++) {

post = posts[i];

// 创建一个指向适当 URI 的链接

var link = document.createElement("a");

var description = post.getAttribute('description');

link.setAttribute("href", post.getAttribute('href'));

link.appendChild(document.createTextNode(description));

// 把链接放在一个"li"标签上

var listItem = document.createElement("li");

listItem.appendChild(link);

// 把"li"放入"ul"

links.appendChild(listItem);

}

}

}

</script>

</body>

</html>

发送请求

request = new XMLHttpRequest(); // 创建 XMLHttpRequest 对象

request.open([HTTP method], [URI], true, [Basic auth username], [Basci auth password]); // true 表示异步请求

request.onReadyStateChange = [Name of handler function];

request.send([Entity-body]); // POST 或 PUT 时,参数为实体主体,其他方法为 null

处理响应

XMLHttpRequest实例的属性:

Status 数字状态码

responseXML 由解析响应文档得到的 DOM 文档

responseText 响应文档的原始文本串,当返回为 JSON 时很有用

getResponseHeader 得到响应报头的方法

示例11-6:一个处理 JSON 表示的 Ajsx 客户端

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"

"http://www.w3.org/TR/REC-html40/transitional.dtd">

<!--yahoo-ajax.html-->

<!--一个使用 Yahoo! Web 服务的 JSON 输出特性的 Ajax 应用-->

<html>

<head>

<title>Javascript Yahoo! JSON</title>

</head>

<body>

<h1>Javascript Yahoo! JSON example</h1>

<p id="message">Loading pictures of body elephants!</p>

<div id="image">

</div>

<script type="text/javascript">

function formatImage(result) {

var image = document.getElementById("images");

items = result["ResultSet"]["Result"];

document.getElementById("message").firstChild.textContent = items.length + " body elephant pictures:";

for(var i = 0; i < items.length; i++) {

image = items[i];

// 创建一个链接

var link = document.createElement("a");

link.setAttribute("href", image["ClickUrl"]);

// 在链接中放置一个缩略图

var img = document.createElement("img");

var thumbnail = image["Thumbnail"];

img.setAttribute("src", thumbnail["Url"]);

img.setAttribute("width", thumbnail["Width"]);

img.setAttribute("width", thumbnail["Height"]);

img.setAttribute("width", thumbnail["Title"]);

link.appendChild(img);

images.appendChild(link);

}

}

</script>

<script type="text/javascript" src="http://api.search.yahoo.com/ImageSearchService/V1/imageSearch?appid=restbook&query=body

+elephant&output=json&callback=formatImage" />

</body>

</html>

示例11-7:跨浏览器的 XMLHttpRequest 封装函数

function createXMLHttpRequest() {

if(typeof XMLHttpRequest != "undefined") {

return new XMLHttpRequest();

} else if(typeof ActiveXObject != "undefined") {

return new ActiveXObject("Microsoft.XMLHTTP");

} else {

throw new Error("XMLHttpRequest not supported");

}

}

Prototype库

Dojo库

颠覆浏览器安全模型:让来自一个域名的代码向另一个域名发送 HTTP 请求

请求代理

示例11-10:用 ProxyPass设置代理

SSLProxyEngine on

ProxyRequests Off # Don't act as an open proxy

RewriteEngine On RewriteRule^apis/delicious/v1/(.*)$https://api.del.icio.us/v1/$1 [P]

request.open("GET", "https://api.del.icio.us/v1/posts/recent", true, username, password);

改为:

request.open("GET", "https://example.com/apis/delicious/v1/posts/recent", true, username, password);

JavaScript on Demand

<script type="text/javascript" src="http://api.search.yahoo.com/ImageSearchService/V1/imageSearch?appid=restbook&query=body+elephant&output=json&callback=formatImage" 

/>

动态 script 标签

JSONscriptRequest 库

<form οnsubmit="callYahoo(); return false;">

What would you like to see?<input id="query" type="text" /><br />

<input type="submit" value="Fetch pictures from Yahoo! Image Search" />

</form>

<script type="text/javascript" src="jsr_class.js"></script>

<script type="text/javascript">

function callYahoo() {

var query = document.getElementById("query").value;

var uri = "http://api.search.yahoo.com/ImageSearchService/V1/imageSearch" + 

"?query=" + escape(query) +

"&appid=restbook&output=json&callback=formatImages";

alert(uri);

var request = new JSONscriptRequest(uri);

request.buildScriptTag();

request.addScriptTag();

}

</script>

第12章  REST 式服务框架

Ruby on Rails

Restlet (Java)

示例12-3:Restlet 实现 Web 服务

Django (Python)

示例12-10: Django 实现 Web 服务

----个人广告----

本人符江涛,在今年6月硕士毕业,目前在找工作,熟悉领域:全栈开发,项目经验:母鸡行网站http://www.destpact.com,微信:jiangtao95zy

继续阅读