Web领域的实时推送技术,也叫称作Realtime技术。这种技术使高达的目的是为用户不需刷新浏览器就得收获实时更新。它装有广泛的运场景,比如在线聊天室、在线客服系统、评论系统、WebIM等。

正文主要读者

WebSocket简介

谈到Web实时推送,就不得不说WebSocket。在WebSocket出现前,很多网站为贯彻实时推送技术,通常以的方案是轮询(Polling)和Comet技术,Comet又可划分为寡栽实现方式,一种植是加上轮询机制,一栽名叫流技术,这半种植方法实在是对准轮询技术之改善,这些方案带来格外显的短处,需要由浏览器对服务器出HTTP
request,大量吃服务器带富和资源。面对这种景象,HTML5概念了WebSocket协议,能重好之节约服务器资源和牵动富并落实真正含义及之实时推送。

WebSocket商谈本质上是一个根据TCP的合计,它由通信协议和编程API组成,WebSocket能够在浏览器与服务器之间建立双向连接,以基于事件的章程,赋予浏览器实时通信能力。既然是双向通信,就意味着服务器端和客户端好而且发送并应请求,而不再如HTTP的请与应。

为成立一个WebSocket连接,客户端浏览器首先要朝着服务器发起一个HTTP请求,这个要和普通的HTTP请求例外,包含了有附加头信息,其中附加头信息”Upgrade:
WebSocket”表明这是一个提请协议升级之HTTP请求,服务器端解析这些附加的条信息然后发对信息返回给客户端,客户端和劳动器端的WebSocket连接就立起了,双方便好透过这个连续通道自由之传递信息,并且是连续会不停有直到客户端还是服务器端的某部同着主动的关连接。

一个独立WebSocket客户端请求头:

图片 1

前方说到WebSocket是HTML5面临新增的同一栽通信协议,这意味着部分一直版本浏览器(主要是IE10之下版本)并无具有这功效,
由此百度统计的当众数据展示,IE8目前仍以33%底市场份额占据榜首,好当chrome浏览器市场份额逐年升高,现在为超26%底市场份额位居第二,同时微软以来揭晓停止针对IE6的技术支持并提示用户更新到新本子浏览器,这个已为不少前端工程师也底峰疼的浏览器有望退出历史舞台,再加上几乎所有的智能手机浏览器还支持HTML5,所以让WebSocket的实战意义非常益,但是无论如何,我们实际上的类型蒙,仍然要考虑没有版本浏览器的相当方案:在支持WebSocket的浏览器中应用新技巧,而在匪支持WebSocket的浏览器里启用Comet来收取发送信息。

引言

WebSocket实战

本文将为差不多人口在线聊天应用作为实例场景,我们先来规定这个聊天应用之主干要求。

REST是什么

急需分析

1、兼容不支持WebSocket的低位版本浏览器。
2、允许客户端起同等的用户称。
3、进入聊天室后可见见眼前在线的用户以及在线人数。
4、用户上线或脱,所有在线的客户端应该实时更新。
5、用户发送信息,所有客户端实时接受。

在其实的开销进程中,为了以WebSocket接口构建Web应用,我们先是用构建一个实现了
WebSocket规范的服务端,服务端的兑现不让平台与支出语言的限量,只需要遵守WebSocket规范即可,目前一度冒出了片较成熟的WebSocket服务端实现,比如本文使用的Node.js+Socket.IO。为什么选用是方案吧?先来大概介绍下她们少。

  联合接口

Node.js

Node.js采用C++语言编写而改为,它不是Javascript应用,而是一个Javascript的运行环境,据Node.js创始人Ryan
Dahl回忆,他最初梦想采取Ruby来描写Node.js,但是后来发觉Ruby虚拟机的习性不可知满足他的要求,后来客尝使用V8引擎,所以选择了C++语言。

Node.js支持的体系包括*nux、Windows,这意味着程序员可以编制系统级或者服务器端的Javascript代码,交给Node.js来解释实施。Node.js的Web开发框架Express,可以辅助程序员快速建立web站点,从2009年落地至今天,Node.js的成长的速显然,其发展前景获得了技术社区的充分肯定。

    依据资源

Socket.IO

Socket.IO是一个开源的WebSocket库,它经过Node.js实现WebSocket服务端,同时为提供客户端JS库。Socket.IO支持为事件为根基的实时双向通讯,它可以干活于其他平台、浏览器还是运动装备。

Socket.IO支持4栽协议:WebSocket、htmlfile、xhr-polling、jsonp-polling,它见面活动根据浏览器选择切合之报导方式,从而被开发者可以聚焦到职能的兑现而无是阳台的兼容性,同时Socket.IO具有对的稳定性与总体性。

    通过特色来操作资源

编码实现

先上演示效果图:

图片 2

可以点击这里翻开在线演示。整个开发进程非常简单,下面简单记录了开支步骤:

    起描述的消息

安装Node.js

基于自己之操作系统,去Node.js官网下载安装即可。如果成功安装。在指令执行输入node -vnpm -v该力所能及顾相应的版本号。

<pre>
node -v
v0.10.26
npm -v
1.4.6
</pre>

    超媒体即用状态引擎(HATEOAS)

搭建WebSocket服务端

其一环节我们尽量的考虑实际生产环境,把WebSocket后端平服务多建成一个线上可用域名访问的劳务,如果你是当本地开发条件,可以转换资金地ip地址,或者使一个虚拟域名指向当地ip。

事先上及公的干活目录,比如
/workspace/wwwroot/plhwin/realtime.plhwin.com,新建一个叫作也
package.json的文本,内容如下:
<pre>
{
“name”: “realtime-server”,
“version”: “0.0.1”,
“description”: “my first realtime server”,
“dependencies”: {}
}
</pre>

搭下去使用npm命令安装expresssocket.io
<pre>
npm install –save express
npm install –save socket.io
</pre>
安成功后,应该好观看工作目录下生成了一个称呼也node_modules的文本夹,里面分别是expresssocket.io,接下去好开修服务端的代码了,新建一个文本:index.js

<pre>
var app = require(‘express’)();
var http = require(‘http’).Server(app);
var io = require(‘socket.io’)(http);

app.get(‘/’, function(req, res){
res.send(‘<h1>Welcome Realtime Server</h1>’);
});

http.listen(3000, function(){
console.log(‘listening on *:3000’);
});
</pre>

指令执行运行node index.js,如果一切顺利,你当会相返回的listening on *:3000字样,这说明服务都成搭建了。此时浏览器中开辟http://localhost:3000应该可以见见正常的逆页面。

如果你想只要受服务运作在线上服务器,并且可通过域名访问的言辞,可以动用Nginx做代理,再nginx.conf中添加如下配置,然后以域名(比如:realtime.plhwin.com)解析及服务器IP即可。
<pre>
server
{
listen 80;
server_name realtime.plhwin.com;
location / {
proxy_pass http://127.0.0.1:3000;
}
}
</pre>

完上述步骤,http://realtime.plhwin.com:3000的后端服务就是正常搭建了。

图片 3

  无状态

服务端代码实现

面前说到之index.js运作于服务端,之前的代码只是一个大概的WebServer欢迎内容,让咱们把WebSocket服务端完整的兑现代码加入进来,整个服务端就得处理客户端的呼吁了。完整的index.js代码如下:

<pre>
var app = require(‘express’)();
var http = require(‘http’).Server(app);
var io = require(‘socket.io’)(http);

app.get(‘/’, function(req, res){
res.send(‘<h1>Welcome Realtime Server</h1>’);
});

//在线用户
var onlineUsers = {};
//当前在线人数
var onlineCount = 0;

io.on(‘connection’, function(socket){
console.log(‘a user connected’);

//监听新用户加入
socket.on('login', function(obj){
    //将新加入用户的唯一标识当作socket的名称,后面退出的时候会用到
    socket.name = obj.userid;

    //检查在线列表,如果不在里面就加入
    if(!onlineUsers.hasOwnProperty(obj.userid)) {
        onlineUsers[obj.userid] = obj.username;
        //在线人数+1
        onlineCount++;
    }

    //向所有客户端广播用户加入
    io.emit('login', {onlineUsers:onlineUsers, onlineCount:onlineCount, user:obj});
    console.log(obj.username+'加入了聊天室');
});

//监听用户退出
socket.on('disconnect', function(){
    //将退出的用户从在线列表中删除
    if(onlineUsers.hasOwnProperty(socket.name)) {
        //退出用户的信息
        var obj = {userid:socket.name, username:onlineUsers[socket.name]};

        //删除
        delete onlineUsers[socket.name];
        //在线人数-1
        onlineCount--;

        //向所有客户端广播用户退出
        io.emit('logout', {onlineUsers:onlineUsers, onlineCount:onlineCount, user:obj});
        console.log(obj.username+'退出了聊天室');
    }
});

//监听用户发布聊天内容
socket.on('message', function(obj){
    //向所有客户端广播发布的消息
    io.emit('message', obj);
    console.log(obj.username+'说:'+obj.content);
});

});

http.listen(3000, function(){
console.log(‘listening on *:3000’);
});
</pre>

  可缓存

客户端代码实现

进去客户端工作目录/workspace/wwwroot/plhwin/demo.plhwin.com/chat,新建一个index.html:

<!DOCTYPE html>
<html>
<head>
<meta charset=”utf-8″>
<meta name=”format-detection” content=”telephone=no”/>
<meta name=”format-detection” content=”email=no”/>
<meta content=”width=device-width, initial-scale=1.0,
maximum-scale=1.0, minimum-scale=1.0, user-scalable=0″
name=”viewport”>
<title>多人数聊天室</title>
<link rel="stylesheet" type="text/css" href="./style.css" />

<script src="http://realtime.plhwin.com:3000/socket.io/socket.io.js"></script>
</head>
<body>

请先输入你在聊天室的昵称


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

</html>

方的html内容本身并未什么好说的,我们要看中的4个文件要:

1、realtime.plhwin.com:3000/socket.io/socket.io.js
2、style.css
3、json3.min.js
4、client.js

第1独JS是Socket.IO提供的客户端JS文件,在前方安装服务端的步调中,当npm安装完毕socket.io并加建筑起WebServer后,这个JS文件就可以健康访问了。

第2个style.css文件没什么好说的,就是样式文件而已。

第3个JS只以IE8以下版本的IE浏览器中加载,目的是给这些没有版本的IE浏览器也克处理json,这是一个开源之JS,详见:http://bestiejs.github.io/json3/

第4个client.js大凡完好的客户端的政工逻辑实现代码,它的情节如下:

<pre>
(function () {
var d = document,
w = window,
p = parseInt,
dd = d.documentElement,
db = d.body,
dc = d.compatMode == ‘CSS1Compat’,
dx = dc ? dd: db,
ec = encodeURIComponent;

w.CHAT = {
    msgObj:d.getElementById("message"),
    screenheight:w.innerHeight ? w.innerHeight : dx.clientHeight,
    username:null,
    userid:null,
    socket:null,
    //让浏览器滚动条保持在最低部
    scrollToBottom:function(){
        w.scrollTo(0, this.msgObj.clientHeight);
    },
    //退出,本例只是一个简单的刷新
    logout:function(){
        //this.socket.disconnect();
        location.reload();
    },
    //提交聊天消息内容
    submit:function(){
        var content = d.getElementById("content").value;
        if(content != ''){
            var obj = {
                userid: this.userid,
                username: this.username,
                content: content
            };
            this.socket.emit('message', obj);
            d.getElementById("content").value = '';
        }
        return false;
    },
    genUid:function(){
        return new Date().getTime()+""+Math.floor(Math.random()*899+100);
    },
    //更新系统消息,本例中在用户加入、退出的时候调用
    updateSysMsg:function(o, action){
        //当前在线用户列表
        var onlineUsers = o.onlineUsers;
        //当前在线人数
        var onlineCount = o.onlineCount;
        //新加入用户的信息
        var user = o.user;

        //更新在线人数
        var userhtml = '';
        var separator = '';
        for(key in onlineUsers) {
            if(onlineUsers.hasOwnProperty(key)){
                userhtml += separator+onlineUsers[key];
                separator = '、';
            }
        }
        d.getElementById("onlinecount").innerHTML = '当前共有 '+onlineCount+' 人在线,在线列表:'+userhtml;

        //添加系统消息
        var html = '';
        html += '<div class="msg-system">';
        html += user.username;
        html += (action == 'login') ? ' 加入了聊天室' : ' 退出了聊天室';
        html += '</div>';
        var section = d.createElement('section');
        section.className = 'system J-mjrlinkWrap J-cutMsg';
        section.innerHTML = html;
        this.msgObj.appendChild(section);   
        this.scrollToBottom();
    },
    //第一个界面用户提交用户名
    usernameSubmit:function(){
        var username = d.getElementById("username").value;
        if(username != ""){
            d.getElementById("username").value = '';
            d.getElementById("loginbox").style.display = 'none';
            d.getElementById("chatbox").style.display = 'block';
            this.init(username);
        }
        return false;
    },
    init:function(username){
        //客户端根据时间和随机数生成uid,这样使得聊天室用户名称可以重复。实际项目中,如果是需要用户登录,那么直接采用用户的uid来做标识就可以
        this.userid = this.genUid();
        this.username = username;

        d.getElementById("showusername").innerHTML = this.username;
        this.msgObj.style.minHeight = (this.screenheight - db.clientHeight + this.msgObj.clientHeight) + "px";
        this.scrollToBottom();

        //连接websocket后端服务器
        this.socket = io.connect('ws://realtime.plhwin.com:3000');

        //告诉服务器端有用户登录
        this.socket.emit('login', {userid:this.userid, username:this.username});

        //监听新用户登录
        this.socket.on('login', function(o){
            CHAT.updateSysMsg(o, 'login');  
        });

        //监听用户退出
        this.socket.on('logout', function(o){
            CHAT.updateSysMsg(o, 'logout');
        });

        //监听消息发送
        this.socket.on('message', function(obj){
            var isme = (obj.userid == CHAT.userid) ? true : false;
            var contentDiv = '<div>'+obj.content+'</div>';
            var usernameDiv = ''+obj.username+'';

            var section = d.createElement('section');
            if(isme){
                section.className = 'user';
                section.innerHTML = contentDiv + usernameDiv;
            } else {
                section.className = 'service';
                section.innerHTML = usernameDiv + contentDiv;
            }
            CHAT.msgObj.appendChild(section);
            CHAT.scrollToBottom();  
        });

    }
};
//通过“回车”提交用户名
d.getElementById("username").onkeydown = function(e) {
    e = e || event;
    if (e.keyCode === 13) {
        CHAT.usernameSubmit();
    }
};
//通过“回车”提交信息
d.getElementById("content").onkeydown = function(e) {
    e = e || event;
    if (e.keyCode === 13) {
        CHAT.submit();
    }
};

})();
</pre>

迄今所有的编码开发工作总体到位了,在浏览器中打开http://demo.plhwin.com/chat/就可以看到功能了,后续我会将演示代码提交到Github上。

本例只是一个略的Demo,留下2个关于项目扩大的想想:

1、假设是一个在线客服系统,里面来过多的营业所用你的服务,每个店自己的用户可经过一个直属URL地址上该商家之聊天室,聊天是相当的,每个企业得以新建多只客服人员,每个客服人员可以和客户端的差不多单用户聊天。

2、又比方是一个当线WebIM系统,实现类似微信,qq的功力,客户端好见到好友在线状态,在线列表,添加好友,删除好友,新建群组等,消息的发送除了支持中心的契外,还能支持表情、图片与文书。

发生趣味之同学可以继承深入钻研。

  C-S架构

  子系统

  按需编码(可选)

REST快速提示

  使HTTP动词表示有含义

  合理的资源名

  XML和JSON

  开创适当粒度的资源

  设想连通性

定义

  幂等性

  安全

HTTP动词

  GET

  PUT

  POST

  PUT和POST的开创于

  DELETE

资源命名

  资源URI示例

  资源命名的反例

  复数

返表征

  资源通过链接的而是发现性(HATEOAS续)

    无限小化链接推荐

    链接格式

  卷入响应

  拍卖跨域问题

    支持CORS

    支持JSONP

询问,过滤和分页

  结果限制

    因此范围标记进行限

    故此字符串查询参数进行界定

    冲范围的应

  分页

  结果的过滤和排序

    过滤

    排序

服务版本管理

  经内容商支持版本管理

  当没点名版本时,返回什么版本?

  恳请不支持的本子

  嘿时该创建一个新本子?

    破坏性的修改

    非破坏性的改动

  版本控制应以啊级别出现?

  使用Content-Location来增长响应

  带有Content-Type的链接

  追寻有支持的本子

    自家应当又支持小只版?

    弃用

    本身哪告客户端给弃用的资源?

日子/时间拍卖

  Body内容遭之日子/时间序列化

  HTTP
Headers中之日子/时间序列化

保护服务的安康

  身份验证

  传输安全

  授权

  应用程序安全

缓存和可伸缩性

  ETag Header

HTTP状态码(前10)

叠加资源

  书籍

  网站

 

本文主要读者

  该最佳实践文档适用于对RESTful
Web服务感兴趣之开发人员,该服务吗超多单劳务之零件提供了较高的可靠性与一致性。按照本文的点拨,可快捷、广泛、公开地啊内外部客户采用。

  本文中之指点原则一致适用于工程师等,他们要下这些根据最佳实践标准开发之劳务。虽然他们更关心缓存、代理规则、监听和康宁等相关地方,但是该文档能作为同一份包含所有品种服务之总指南。

  另外,通过由这些点原则,管理人员了解及开创公共的、提供高稳定的劳务所急需花的奋力,他们吗可从中受益。

 

引言

  现今早已来大气有关RESTful
Web服务至上实践的有关材料(详见本文最后之连锁文献有)。由于撰文之时日各异,许多资料遭受的始末是矛盾的。此外,想如果经过查看文献来打探这种劳动的前进是无极端长之。为了打探RESTful这同定义,至少需要查阅三届五依照有关文献,而本文将能帮助你加快这无异于进程——摒弃多余的议论,最大化地提炼出REST的特等实践和规范。

  与其说REST是平等学标准,REST更像是平种口径的汇。除了六个基本点之格外就是没外的正儿八经了。实际上,虽然有所谓的“最佳实践”和正式,但这些事物还和教斗争一样,在持续地演变。

  本文围绕REST的泛问题提出了看法与仿食谱式的议论,并经介绍部分简便的背景知识对创建真实处境下之优先生产条件被相同的REST服务提供文化。本文收集了自其它渠道的音,经历了一次次的败后不断改进。

  但对于REST模式是否定比SOAP好用本发生较生争(反之亦然),也许在少数情况下仍待创造SOAP服务。本文在提及SOAP时并未花较生篇幅来讨论她的对立优点。相反由于技术以及行业在不断进步,我们将继续坚持我们的设–REST是即刻筹web服务的超级艺术。

  第一有的概述REST的含义、设计则及它的超常规的处在。第二片罗列了有的多少贴士来记忆REST的劳动理念。之后的部分则会又透地啊web服务创建人员提供部分细节之支撑以及座谈,来贯彻一个会明白亮在生环境面临的大质量REST服务。

 

REST是什么?

  REST架构方式讲述了六栽设计则。这些用于架构的筹划则,最早是由Roy
Fielding在外的博士论文中提出并定义了RESTful风格。(详见http://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm)

  六单统筹则分别是:

  • 联合接口
  • 无状态
  • 可缓冲
  • C-S架构
  • 分段系统
  • 按需编码

  以下是这些计划则的详尽座谈:

统一接口

  统一接口准则定义了客户端以及服务端之间的接口,简化和分手了框架结构,这样一来每个片还可独立演化。以下是接口统一之季独标准化:

  基于资源

  不同资源需要因此URI来唯一标识。返回给客户端的性状和资源本身在概念上有所不同,例如服务端不见面直接传送一个数据库资源,然而,一些HTML、XML或JSON数据会亮部分数据库记录,如用芬兰语来发表要用UTF-8编码则使基于请求和服务器实现之底细来支配。

  通过特征来操作资源

  当客户端收到包含元数据的资源的特点时,在产生权力的气象下,客户端都掌握的足够的音信,可以对服务端的资源拓展删改。

  自描述的信

  每条消息都富含足够的数量用于确认消息该如何处理。例如要由网络媒体类型(已知道之要MIME类型)来确认要调用哪个解析器。响应同样也表明了其的缓存能力。

  超媒体即祭状态引擎(HATEOAS)

  客户端通过body内容、查询串参数、请求头和URI(资源名称)来传送状态。服务端通过body内容,响应码和响应头传送状态为客户端。这项技术于称超媒体(或超文本链接)。

  除了上述内容外,HATEOS也代表,必要的当儿链接也可被含有在回去的body(或头部)中,以供URI来找对象自我还是提到对象。下文将针对这个进行再详尽的阐述。

  统一接口是每个REST服务计划时的必备准则。

无状态

  正如REST是REpresentational State
Transfer的缩写,无状态好重要。本质上,这表明了拍卖要所要的状态已包含在请我里,也出或是URI的相同局部、查询串参数、body或头部。URI能够唯一标识每个资源,body中也富含了资源的转态(或转态变更情况)。之后,服务器将展开拍卖,将有关的状态或资源通过头部、状态与应body传递给客户端。

  从事我们马上同样行当的大多数人还习惯使用容器来编程,容器被生一个“会话”的概念,用于在多单HTTP请求下维持状态。在REST中,如果要以差不多个请求下保持用户状态,客户端必须概括客户端的装有消息来好请求,必要常常再发送请求。自从服务端不需保障、更新或传递会话状态后,无状态性得到了重复可怜的延展。此外,负载均衡器无需担心和管状态系统间的对话。

  所以状态及资源间有啊区别?服务器对状态,或者说是应用状态,所关心的点是于眼前对话或要被如果到位请求所用的数码。而资源,或者说是资源状态,则是概念了资源特点的数据,例如存储于数据库被的数量。由此可见,应用状态是是就客户端与要的改观而变更之数额。相反,资源状态对于发出请求的客户端的话是无转换的。

  在网络下之某同特定岗位上摆一个回按钮,是以其愿意您会随一定之逐条来操作为?其实是因她违反了无状态的准。有许多未遵循无状态原则的案例,例如3-Legged
OAuth,API调用速度限制等。但还是要尽可能确保服务器被不需要在差不多只请求下维持利用状态。

可缓存

  于万维网上,客户端好缓存页面的应内容。因此应都应隐式或显式的概念为可缓存的,若不足缓存则使避免客户端在数要后用旧数据还是污染数据来响应。管理得当的缓存会部分地还是全地除了客户端以及服务端之间的互相,进一步改进性与延展性。

C-S架构

  统一接口使得客户端和服务端相互分开。关注分离意味什么?打个如,客户端不需要仓储数据,数据都养于服务端内部,这样让客户端代码的可移植性得到了晋级;而服务端不需考虑用户接口及用户状态,这样一来服务端将越简明好拓展。只要接口不改变,服务端和客户端可单独地展开研发及替换。

分层系统

  客户端通常无法表明自己是直接或者间接与端服务器进行连接。中介服务器可以经启用负载均衡或供共享缓存来提升系统的延展性。分层时同样要考虑安全策略。

按需编码(可选)

  服务端通过传输可实施逻辑给客户端,从而为那临时拓展和定制功能。相关的例子有编译组件Java
applets和客户端脚本JavaScript。

  遵从上述原则,与REST架构风格保持一致,能吃各种分布式超媒体系统有着梦想之自然属性,比如高性能,延展性,简洁,可变性,可视化,可移植性和可靠性。

  提示:REST架构中的设计则遭到,只有本需编码为可选项。如果某服务违反了任何随意一宗则,严格意思上无克称之为RESTful风格。

 

REST快速提示

  (根据地方提到的六独规格)不管在技术上是匪是RESTful的,这里来一些近乎REST概念的建议。遵循它,可以兑现重新好、更使得的劳务:

运HTTP动词表示有意思

  任何API的使用者能发送GET、POST、PUT和DELETE请求,它们非常特别程度明显了所让告的目的。同时,GET请求不克更改任何秘密的资源数量。测量和跟踪仍可能来,但惟独见面更新数据而非会见更新由URI标识的资源数量。

客观之资源名

  合理之资源名称或路径(如/posts/23如果未是/api?type=posts&id=23)可以另行显眼一个央的目的。使用URL查询串来过滤数据是老好之法,但切莫应有用于固定资源名称。

  适当的资源名称为服务端请求提供上下文,增加服务端API的可理解性。通过URI名称分层地查看资源,可以让使用者提供一个和谐之、容易掌握的资源层次,以在她们之应用程序上应用。资源名称应当是名词,避免为动词。使用HTTP方法来指定要的动作有,能为工作越的清。

XML和JSON

  建议默认支持json,并且,除非花费很震惊,否则就算以支持json和xml。在优秀图景下,让使用者仅经过反扩展名.xml和.json来切换类型。此外,对于支撑ajax风格的用户界面,一个深受包裹的应是杀有帮助的。提供一个让装进的应,在默认的或者发生独立放展名的状下,例如:.wjson和.wxml,表明客户端请求一个给装进的json或xml响应(请参见下的包装响应)。

  “标准”中对json的渴求充分少。并且这些要求才是语法性质的,无关内容格式和布局。换句话说,REST服务端调用的json响应是商的同样局部——在规范中尚无有关描述。更多关于json数据格式可以以http://www.json.org/上找到。

  关于REST服务受到xml的下,xml的科班以及预约除了运用语法正确的签以及文本外没有任何的作用。特别地,命名空间不是吧未该是深受利用在REST服务端的前后文中。xml的回重新类似于json——简单、容易看,没有模式和命名空间的底细表现——仅仅是数码以及链接。如果它们比马上还复杂的话,参看本节的第一段落——使用xml的老本是震惊之。鉴于我们的涉,很少有人利用xml作为响应。在其让统统淘汰之前,这是最后一个只是给一定之地方。

创适当粒度的资源

  同开始,系统被模拟底层应用程序域或数据库架构的API更爱受创造。最终,你见面希望以这些服务都构成至一块——利用基本上宗底层资源减少通信量。在开立独立的资源后再次创更甚粒度的资源,比由更怪之一块集中创建于充分粒度的资源更容易有。从局部稍之轻定义之资源开始,创建CRUD(增删查改)功能,可以要资源的创办变得重新便于。随后,你得创建这些根据用例和削减通信量的资源。

设想连通性

  REST的原理之一即是连通性——通过超媒体链接实现。当于应中归链接时,api变的又有从描述性,而在尚未她常服务端依然可用。至少,接口本身可以吗客户端提供什么样寻找数据的参考。此外,在通过POST方法创建资源时,还可以采取头位置包含一个链接。对于响应中支持分页的集结,”first”、
“last”、”next”、和”prev”链接至少是蛮管用的。

 

定义

幂等性

  不要从字面意思来理解什么是幂等性,恰恰相反,这同某些功能紊乱的世界无关。下面是来维基百科的说明:

于处理器是中,术语幂当用于更周全地描述一个操作,一破还是累实践该操作发生的结果是一律的。根据使用的上下文,这说不定发例外之含义。例如,在措施或者子例程调用拥有副作用的景下,意味着当首先调用之后吃修改的状态呢维持无转移。

  从REST服务端的角度来拘禁,由于操作(或服务端调用)是幂等的,客户端好为此更的调用而生相同的结果——在编程语言中操作像是一个”setter”(设置)方法。换句话说,就是动多单同之乞求与应用单个请求效果一样。注意,当幂等操作以服务器上生相同之结果(副作用),响应本身可能是殊之(例如当差不多单请求中,资源的状态恐怕会见转)。

  PUT和DELETE方法吃定义也凡幂等的。查看http请求中delete动词的警示信息,可以参见下文的DELETE部分。GET、HEAD、OPTIO和TRACE方法从被定义也安全之道后,也叫定义也幂等的。参照下关于安全的段落。

安全

  来自维基百科:

一些方式(例如GET、HEAD、OPTIONS和TRACE)被定义也平安之法门,这意味其仅仅吃用来信息寻找,而无克改变服务器的状态。换句话说,它们不见面发生副作用,除了相对来说无害的震慑使日志、缓存、横幅广告或计数服务等。任意的GET请求,不考虑下状态的上下文,都吃看是平安的。

  总之,安全意味着调用的方无会见滋生副作用。因此,客户端可屡屡用安全的恳求而非用担心对服务端产生其他副作用。这代表服务端必须遵从GET、HEAD、OPTIONS和TRACE操作的安康概念。否则,除了针对消费端产生模糊外,它还会招Web缓存,搜索引擎和另活动代理的题目——这将在服务器上有意想不到的究竟。

  根据定义,安全操作是幂等的,因为她于服务器上发出相同的结果。

  安全的不二法门给实现呢单纯读操作。然而,安全并无意味着服务器必须每次都归相同之应。

 

HTTP动词

  Http动词主要按“统一接口”规则,并提供被咱相应的根据名词的资源的动作。最要害还是太常用之http动词(或者称方法,这样称呼可能更恰当些)有POST、GET、PUT和DELETE。这些分别指向应于创建、读取、更新和去(CRUD)操作。也发生众多其它的动词,但是用效率比较没有。在这些使比较少的法中,OPTIONS和HEAD往往用得更多。

GET

  HTTP的GET方法用于检索(或读取)资源的数额。在正确的求路径下,GET方法会返回一个xml或者json格式的数据,以及一个200之HTTP响应代码(表示是返回结果)。在错误情况下,它通常返回404(不存)或400(错误的求)。

  例如:

*  GET http://www.example.com/customers/12345*
  GET http://www.example.com/customers/12345/orders
  GET http://www.example.com/buckets/sample

  按照HTTP的设计规范,GET(以及附带的HEAD)请求单用于读取数据而非移多少。因此,这种利用方式给认为是高枕无忧之。也就是说,它们的调用没有数修改或污染之高风险——调用1软和调用10浅或无给调用的法力一样。此外,GET(以及HEAD)是幂等的,这意味使用多个一律的伸手和利用单个的求最终都负有一致的结果。

  不要通过GET暴露不安全之操作——它当永远都未能够修改服务器上的其他资源。

PUT

  PUT通常被用来创新资源。通过PUT请求一个已经解之资源URI时,需要在呼吁的body中隐含对原有资源的更新数据。

  不过,在资源ID是出于客服端而休服务端提供的状况下,PUT同样好叫用来创造资源。换句话说,如果PUT请求的URI中富含的资源ID值在服务器上未存在,则用于创造资源。同时呼吁的body中须含有要创的资源的数额。有人当就会时有发生歧义,所以只有真的需要,使用这种措施来创造资源应该被慎用。

  或者我们吧可以body中提供由客户端定义之资源ID然后使用POST来创造新的资源——假设请求的URI中不含要开创的资源ID(参见下POST的有些)。

  例如:

*  PUT http://www.example.com/customers/12345*
  PUT http://www.example.com/customers/12345/orders/98765
  PUT http://www.example.com/buckets/secret\_stuff

  当以PUT操作更新成功时,会回到200(或者返回204,表示回去的body中未含其他内容)。如果以PUT请求创建资源,成功返回的HTTP状态码是201。响应的body是可选的——如果提供的口舌将见面耗费又多之带来富。在开立资源时莫必要通过头部的位置返回链接,因为客户端就设置了资源ID。请参见下的归值部分。

  PUT不是一个安全之操作,因为它见面窜(或创办)服务器上之状态,但其是幂等的。换句话说,如果您使用PUT创建或者更新资源,然后又调用,资源还有而状态不会见发生变化。

  例如,如果当资源增量计数器中调用PUT,那么这个调用方法就是不再是幂等的。这种状态有时候会时有发生,且可能得验证她是勿幂等性的。不过,建议维持PUT请求的幂等性。并强烈建议非幂等性的请求使用POST。

POST

  POST请求时吃用来创造新的资源,特别是于用来创造于属于资源。从属于资源就属于外资源(如慈父资源)的资源。换句话说,当创建一个新资源时,POST请求发送给父资源,服务端负责用新资源以及老子资源开展关联,并分配一个ID(新资源的URI),等等。

  例如:

  POST http://www.example.com/customers
  POST http://www.example.com/customers/12345/orders

  当创建成功时,返回HTTP状态码201,并顺便一个位置头信息,其中蕴涵指向最先创建的资源的链接。

  POST请求既未是安全的又未是幂等的,因此其给定义为非幂等性资源要。使用简单单相同的POST请求很可能会见招创建两独饱含相同信息之资源。

PUT和POST的创立于

  总之,我们建议使用POST来创造资源。当由客户端来支配新资源具有何等URI(通过资源名称或者ID)时,使用PUT:即如果客户端知道URI(或资源ID)是呀,则针对该URI使用PUT请求。否则,当由服务器或劳务端来支配创造的资源的URI时虽采取POST请求。换句话说,当客户端在创立之前未理解(或无法知道)结果的URI时,使用POST请求来创造新的资源。

DELETE

  DELETE很爱懂。它给用来冲URI标识删除资源。

  例如:

  DELETE http://www.example.com/customers/12345
  DELETE http://www.example.com/customers/12345/orders
  DELETE http://www.example.com/buckets/sample

  当去成功时,返回HTTP状态码200(表示对),同时会有意无意一个应体body,body中恐怕包含了删除项之数(这会占用部分网带来富),或者封装的应(参见下的返回值)。也得回来HTTP状态码204(表示管内容)表示没有响应体。总之,可以回状态码204意味着没有响应体,或者返回状态码200而且附带JSON风格的响应体。

  根据HTTP规范,DELETE操作是幂等的。如果你对一个资源进行DELETE操作,资源就受移除了。在资源及屡次调用DELETE最终致使的结果还一模一样:即资源为移除了。但假如将DELETE的操作用于计数器(资源中),则DETELE将不再是幂等的。如前所陈述,只要数据没有吃更新,统计和测量的用法依然只是吃看是幂等的。建议非幂等性的资源要使用POST操作。

  然而,这里来一个有关DELETE幂等性的警戒。在一个资源上第二破调整用DELETE往往会回来404(未找到),因为拖欠资源就给移除了,所以找不交了。这使得DELETE操作不再是幂等的。如果资源是自从数据库中除去而未是叫概括地记为去,这种情形用适当让步。

  下表总结发生了重在HTTP的道与资源URI,以及推荐的返回值:

HTTP请求

/customers

/customers/{id}

GET

200(正确),用户列表。使用分页、排序和过滤大导航列表。

200(正确),查找单个用户。如果ID没有找到或ID无效则赶回404(未找到)。

PUT

404(未找到),除非你想在周集合中更新/替换每个资源。

200(正确)或204(无内容)。如果无找到ID或ID无效则归404(未找到)。

POST

201(创建),带有链接到/customers/{id}的职位头信息,包含新的ID。

404(未找到)

DELETE

404(未找到),除非你想抹所有集合——通常不深受允许。

200(正确)。如果无找到ID或ID无效则归404(未找到)。

 

资源命名

  除了当地行使HTTP动词,在开创一个足以知道的、易于使的Web服务API时,资源命名可以说凡是极其富有争议和最好要紧之概念。一个吓的资源命名,它所对应之API看起重直观并且爱使。相反,如果命名不好,同样的API会于人口感觉到异常笨而难以掌握以及动用。当您需要吗你的新API创建资源URL时,这里发出局部小技巧值得借鉴。

  从本质上说话,一个RESTFul
API最终还好吃概括地作是一律积URI的集结,HTTP调用这些URI以及有之所以JSON和(或)XML表示的资源,它们中产生过多分包了互关系的链接。RESTful的可是寻址能力要靠URI。每个资源都有好的地方或URI——服务器会提供的各国一个实用之音都可视作资源来明。统一接口的规范有地经URI和HTTP动词的组成来解决,并符合利用标准和预约。

  以控制你系统中如下的资源时,使用名词来定名这些资源,而非是故动词或动作来命名。换句话说,一个RESTful
URI应该提到到一个有血有肉的资源,而非是关联到一个动作。另外,名词还有所局部动词没有的特性,这吗是外一个强烈的元素。

  一些资源的事例:

  • 系统的用户
  • 生注册之学科
  • 一个用户帖子的时光轴
  • 关注其他用户的用户
  • 平等篇有关骑马的篇章

  服务套件中之每个资源最少发生一个URI来标识。如果这URI能表示一定的意思并且会尽量描述其所表示的资源,那么她便是一个太好的命名。URI应该享有可预测性和分支结构,这将推增进它们的可理解性和可用性的:可预测指的是资源应该和名称保持一致;而分指的是数码有所涉上之布局。这并非REST规则或正式,但是它加重了针对性API的定义。

  RESTful
API是提供于消费端的。URI的称谓以及布局应当以它所抒发的义传达给消费者。通常我们十分不便知晓数码的界线是啊,但是自从君的数达你应该十分有或失掉尝尝找到要回去给客户端的多少是什么。API是也客户端而设计之,而非是吧你的数。

  假设我们今天如讲述一个包客户、订单,列表项,产品相当力量的订单系统。考虑一下我们该怎么来描述在这个服务受到所关联到的资源的URIs:

资源URI示例

  为了在系统受插入(创建)一个新的用户,我们得以采用:

  POST http://www.example.com/customers

 

  读取编号为33245底用户信息:

  GET http://www.example.com/customers/33245

  使用PUT和DELETE来请求相同之URI,可以创新和去数据。

 

  下面是针对性成品有关的URI的有些提议:

  POST http://www.example.com/products

  用于创造新的出品。

 

  GET|PUT|DELETE http://www.example.com/products/66432

  分别用于读取、更新、删除编号为66432之活。

 

  那么,如何呢用户创建一个初的订单也?

  一栽方案是:

  POST http://www.example.com/orders

  这种方法得以为此来创造订单,但欠相应的用户数量。

  

  以咱们想呢用户创建一个订单(注意之间的涉嫌),这个URI可能不足够直观,下面这URI则还鲜明一些:

  POST http://www.example.com/customers/33245/orders

  现在咱们理解她是啊编号33245之用户创建一个订单。

 

  那下面这请返回的凡呀吧?

  GET http://www.example.com/customers/33245/orders

  可能是一个编号也33245的用户所开创或者持有的订单列表。注意:我们得遮挡对拖欠URI进行DELETE或PUT请求,因为其的操作对象是一个凑。

 

  继续深入,那下面是URI的要又意味着什么啊?

  POST http://www.example.com/customers/33245/orders/8769/lineitems

  可能是(为编号33245之用户)增加一个编号也8769的订单条目。没错!如果下GET方式要是URI,则会回这个订单的持有条条框框。但是,如果这些条款与用户信息无关,我们将会晤提供POST
www.example.com/orders/8769/lineitems
这个URI。

  从返回的这些条款来拘禁,指定的资源或会见生出多只URIs,所以我们可能为需而提供这么一个URI
GET
http://www.example.com/orders/8769
,用来当非理解用户ID的情景下基于订单ID来询问订单。

 

  更进一步:

  GET http://www.example.com/customers/33245/orders/8769/lineitems/1

  可能单回跟个订单被之率先个条文。

  现在你应当清楚啊是分开层结构了。它们并无是严的条条框框,只是为保在你的服务遭遇这些强制的构造会还爱被用户所理解。与有着软件开发中的技术一样,命名是打响之第一。

  

  多扣一些API的示范并学会控制这些技能,和您的队友一起来宏观而API资源的URIs。这里来一些APIs的例证:

  • Twitter: https://dev.twitter.com/docs/api
  • Facebook: http://developers.facebook.com/docs/reference/api/
  • LinkedIn: https://developer.linkedin.com/apis

资源命名的反例

  前面我们已经讨论过局部老少咸宜的资源命名的例证,然而有时有反面的例子也颇有教育意义。下面是有未极端具有RESTful风格的资源URIs,看起较散乱。这些还是荒谬的例子! 

  首先,一些serivices往往用单一的URI来指定服务接口,然后通过查询参数来指定HTTP请求的动作。例如,要创新编号12345的用户信息,带有JSON
body的请求或是这么:

  GET
http://api.example.com/services?op=update\_customer&id=12345&format=json

  尽管地方URL中之”services”的是节点是一个名词,但以此URL不是打说的,因为对持有的恳求而言,该URI的层级结构都是相同的。此外,它以GET作为HTTP动词来推行一个更新操作,这简直就是相反人类(甚至是危险的)。

  下面是另外一个创新用户的操作的事例:

  GET http://api.example.com/update\_customer/12345

  以及她的一个变种:

  GET http://api.example.com/customers/12345/update

  你见面时时看看于另外开发者的劳务套件中发出广大这样的用法。可以望,这些开发者试图去创造RESTful的资源名称,而且都起矣部分前进。但是你依然会分辨出URL中之动词短语。注意,在斯URL中我们无需”update”这个词,因为咱们得以凭借HTTP动词来就操作。下面这个URL正好说明了即一点:

  PUT http://api.example.com/customers/12345/update

  这个请又有PUT和”update”,这会针对顾客产生迷惑!这里的”update”指的是一个资源为?因此,这里我们费些口舌也是意在而能够掌握……

复数

  让咱来讨论一下复数和“单数”的争论…还没听说过?但这种争议确实在,事实上它可概括为是题材……

  以公的层级结构被URI节点是否要被取名为单数或复数形式吗?举个例,你用来查找用户资源的URI的命名是否需要像下这样:

  GET http://www.example.com/customer/33245

  或者:

  GET http://www.example.com/customers/33245

  两种方法还尚未问题,但常见我们还见面挑选用复数命名,以让你的API
URI在具有的HTTP方法吃保持一致。原因是根据这样同样栽考虑:customers是劳动套件中的一个成团,而ID33245之这用户则是这个集中之中间一个。

  按照此规则,一个采用复数形式之差不多节点的URI会是这样(注意粗体部分):

  GET
http://www.example.com/**customers**/33245/**orders**/8769/**lineitems**/1

  “customers”、“orders”以及“lineitems”这些URI节点都应用的凡复数形式。

  这象征你的每个根资源就需要少独着力的URL就可以了,一个用于创造集合内之资源,另一个用来因标识符获取、更新与去资源。例如,以customers为例,创建资源可以动用下的URL进行操作:

  POST http://www.example.com/customers

  而读取、更新与去资源,使用下的URL操作:

  GET|PUT|DELETE http://www.example.com/customers/{id}

  正使前方提到的,给一定的资源或来差不多单URI,但作为一个极度小之圆的增删改查功能,利用有限个简易的URI来拍卖就足够了。

  或许你晤面咨询:是否以有些情况下复数没有意思?嗯,事实上是这样的。当没有汇概念的时(此时复数没有意思)。换句话说,当资源只出一个的情景下,使用单数资源名称为是足以的——即一个单纯的资源。例如,如果来一个纯粹的一体化安排资源,你可以采用一个单数名称来表示:

  GET|PUT|DELETE http://www.example.com/configuration

  注意这里少configuration的ID以及HTTP动词POST的用法。假设每个用户产生一个配置来说,那么这URL会是如此:

  GET|PUT|DELETE
http://www.example.com/customers/12345/configuration

  同令人瞩目这里没有点名configuration的ID,以及从未让定POST动词的用法。在及时简单单例子中,可能也会有人认为利用POST是实用之。好吧…

 

归来表征

  正而前提到的,RESTful接口支持多资源特点,包括JSON和XML,以及让打包的JSON和XML。建议JSON作为默认表征,不过服务端应该允许客户端指定其他表征。

  对于客户端请求的特点格式,我们得以以Accept头通过文件扩展名来展开点名,也得以由此query-string等其余方式来指定。理想图景下,服务端可以支持有这些方法。但是,现在专业更赞成被通过类似于文件扩展名的计来进展点名。因此,建议服务端至少要支持下文件扩展名的不二法门,例如“.json”,“.xml”以及它的包版本“.wjon”,“.wxml”。

  通过这种艺术,在URI中指定返回表征的格式,可以增强URL的可见性。例如,GET
http://www.example.com/customers.xml
拿回customer列表的XML格式的特点。同样,GET
http://www.example.com/customers.json
拿返回一个JSON格式的表征。这样,即使是在极度基础之客户端(例如“curl”),服务使起来也会愈加便利。推荐应用这种办法。

  此外,当url中绝非含格式说明时,服务端应该归默认格式的特点(假设为JSON)。例如:

  GET http://www.example.com/customers/12345

  GET http://www.example.com/customers/12345.json

  以上两者返回的ID为12345之customer数据全为JSON格式,这是劳务端的默认格式。

  GET http://www.example.com/customers/12345.xml

  如果服务端支持的话,以上要返回的ID为12345底customer数据也XML格式。如果该服务器无支持XML格式的资源,将回到一个HTTP
404底荒唐。

  使用HTTP
Accept头被广泛认为是同等种植更优雅的不二法门,并且可HTTP的标准和意义,客户端可通过这种方法来喻HTTP服务端它们可是支撑之数据类型有什么样。但是,为了使Accept头,服务端要而支持封装和无封装的应,你要兑现自定义的项目——因为这些格式不是正规的品种。这大大加了客户端以及劳务端的错综复杂。请参见RFC
2616的14.1节关于Accept头的详细信息(http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1)。使用文件扩展名来指定数量格式是绝简便易行直接的点子,用最好少的字符就可就,并且支持脚本操作——无需下HTTP头。

  通常当我们关系REST服务,跟XML是毫不相关的。即使服务端支持XML,也几乎从不丁提议在REST中动用XML。XML的规范与公约在REST中未太适用。特别是它们并命名空间还不曾,就再也不欠在RESTful服务体系中利用了。这仅会使业务变得又复杂。所以回来的XML看起重如JSON,它大概好读,没有模式和命名空间的界定,换句话来说是管标准的,易于解析。

资源通过链接的但是发现性(HATEOAS续)

  REST指导原则之一(根据统一接口规范)是application的状态通过hypertext(超文本)来传。这就是我们普通所说的Hypertext
As The Engine of Application State
(即HATEOAS,用超文本来当应用程序状态机),我们以“REST是什么”一如既往节省中吗涉了。

  根据Roy
Fielding在他的博客中之描述(http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertextdriven),REST接口中极其要紧之片段是超文本的用。此外,他尚指出,在为出其他有关的音讯前,一个API应该是可用和而了解的。也就是说,一个API应当可以由此该链接导航及多少的逐一部分。不建议才回纯数据。

  不过当下之业界先驱们连不曾经常用这种做法,这反映了HATEOAS仅仅在成熟度模型中之使用率还胜似。纵观众多的服务体系,它们多返回重新多的数目,而回到的链接却非常少(或者没)。这是背Fielding的REST约定的。Fielding说:“信息之各一个可是寻址单元都携一个地点……查询结果应当呈现吧一个带有摘要信息之链接清单,而休是目标往往组。”

  另一方面,简单粗暴地以全方位链接集合返回会大大影响网络带来富。在实际情形被,根据所欲的尺码还是行使情况,API接口的通信量要因服务器响应中超文本链接所蕴藏的“摘要”数量来平衡。

  同时,充分利用HATEOAS可能会见大增实现的复杂,并对服务客户端有显著的负责,这一定给降低了客户端与服务器端开发人员的生产力。因此,当务之急是设平衡超链接服务推行与现有可用资源之间的问题。

  超链接太小化的做法是以极其充分限度地压缩客户端和服务器之间的耦合的又,提高劳动端的可用性、可操纵性和可理解性。这些最为小化建议是:通过POST创建资源并自GET请求返回集合,对于生分页的事态后我们见面提到。

绝小化链接推荐

  于create的用例中,新建资源的URI(链接)应该于Location响应头中归,且应中心是拖欠的——或者仅包含新建资源的ID。

  对于自服务端返回的特色集合,每个表征应该以它们的链接集合中带走一个极其小之“自身”链接属性。为了好分页操作,其它的链接可以在一个独自的链接集合中回到,必要时得涵盖“第一页”、“上一样页”、“下一样页”、“最后一页”等消息。

  参照下文链接格式片的例证获取更多信息。

链接格式

  参照整个链接格式的专业,建议遵守一些近乎Atom、AtomPub或Xlink的作风。JSON-LD也对,但连从未吃广大采用(如果已经为用过)。目前标准最普遍的点子是行使含”rel”元素与含资源整体URI的”href”元素的Atom链接格式,不含其他身份验证或询问字符串参数。”rel”元素得以涵盖标准值”alternate”、”related”、”self”、”enclosure”和”via”,还有分页链接的“第一页”、“上同样页”、“下一样页”,“最后一页”。在需要经常得以自定义并加上应用其。

  一些XML
Atom格式的定义对用JSON格式表示的链接来说是杯水车薪的。例如,METHOD属性对于一个RESTful资源来说是免欲的,因为对此一个加的资源,在享有支持之HTTP方法(CRUD行为)中,资源的URI都是千篇一律之——所以单独列有这些是尚未必要之。

  让咱选一些切实的例证来更印证这或多或少。下面是调用创建新资源的呼吁后的响应:

  POST http://api.example.com/users

  下面是作应头集合中蕴含创建新资源的URI的Location部分:

HTTP/1.1 201 CREATED 
Status: 201 
Connection: close 
Content-Type: application/json; charset=utf-8 
Location: http://api.example.com/users/12346

  返回的body可以吗空,或者隐含一个叫包裹的应(见下文封装响应)。

  下面的例子通过GET请求获取一个请勿含分页的风味集合的JSON响应:

{
  "data": [
    {
      "user_id": "42",
      "name": "Bob",
      "links": [
        {
          "rel": "self",
          "href": "http://api.example.com/users/42"
        }
      ]
    },
    {
      "user_id": "22",
      "name": "Frank",
      "links": [
        {
          "rel": "self",
          "href": "http://api.example.com/users/22"
        }
      ]
    },
    {
      "user_id": "125",
      "name": "Sally",
      "links": [
        {
          "rel": "self",
          "href": "http://api.example.com/users/125"
        }
      ]
    }
  ]
}

  注意,links数组中的各国一样桩都蕴含一个对准“自身(self)”的链接。该数组还可能还含有其他关系,如children、parent等。

  最后一个例证是经GET请求获取一个暗含分页的特色集合的JSON响应(每页显示3起),我们为闹第三页的数量:

{
  "data": [
    {
      "user_id": "42",
      "name": "Bob",
      "links": [
        {
          "rel": "self",
          "href": "http://api.example.com/users/42"
        }
      ]
    },
    {
      "user_id": "22",
      "name": "Frank",
      "links": [
        {
          "rel": "self",
          "href": "http://api.example.com/users/22"
        }
      ]
    },
    {
      "user_id": "125",
      "name": "Sally",
      "links": [
        {
          "rel": "self",
          "href": "http://api.example.com/users/125"
        }
      ]
    }
  ],
  "links": [
    {
      "rel": "first",
      "href": "http://api.example.com/users?offset=0&limit=3"
    },
    {
      "rel": "last",
      "href": "http://api.example.com/users?offset=55&limit=3"
    },
    {
      "rel": "previous",
      "href": "http://api.example.com/users?offset=3&limit=3"
    },
    {
      "rel": "next",
      "href": "http://api.example.com/users?offset=9&limit=3"
    }
  ]
}

  于斯例子中,响应中用于分页的links集合中之各一样码都带有一个对“自身(self)”的链接。这里可能还会见发出一些事关到聚集的外链接,但都跟分页本身无关。简而言之,这里发出半点只地方含有links。一个便是data对象被所蕴含的聚众(这个呢是接口要回来给客户端的多寡表征集合),其中的每一样宗至少要包一个针对“自身(self)”的links集合;另一个则是一个单独的靶子links,其中包同分页相关的链接,该有的情适用于所有集合。

  对于经过POST请求创建资源的气象,需要以应头着隐含一个涉新建对象链接的Location

装进响应

   服务器可以以应中并且返回HTTP状态码和body。有很多JavaScript框架没有将HTTP状态响应码返回给最终之开发者,这往往会导致客户端无法根据状态码来确定具体的行为。此外,虽然HTTP规范中产生特别多种响应码,但是频繁就发生个别客户端会关切这些——通常大家只是以乎”success”、”error”或”failture”。因此,将应内容和响应状态码封装于含蓄响应信息的特色着,是发出必不可少之。

  OmniTI
实验室有这样一个提议,它被誉为JSEND响应。更多信息请参见http://labs.omniti.com/labs/jsend。另外一个提案是由于Douglas
Crockford提出的,可以查看此http://www.json.org/JSONRequest.html。

  这些提案在实践中并不曾完全含所有的情状。基本上,现在极度好的做法是据以下属性封装常规(非JSONP)响应:

  • code——包含一个平头类的HTTP响应状态码。
  • status——包含文本:”success”,”fail”或”error”。HTTP状态响应码在500-599里头吧”fail”,在400-499以内也”error”,其它都为”success”(例如:响应状态码为1XX、2XX与3XX)。
  • message——当状态值为”fail”和”error”时有效,用于展示错误信息。参照国际化(il8n)标准,它好分包信息号或者编码,可以只有含有其中一个,或者又含有并为此分隔符隔开。
  • data——包含响应的body。当状态值为”fail”或”error”时,data就含错误原因还是大名称。

  下面是一个返回success的包响应:

{
  "code": 200,
  "status": "success",
  "data": {
    "lacksTOS": false,
    "invalidCredentials": false,
    "authToken": "4ee683baa2a3332c3c86026d"
  }
}

  返回error的包装响应:

{
  "code": 401,
  "status": "error",
  "message": "token is invalid",
  "data": "UnauthorizedException"
}

  这点儿个包响应对应的XML如下:

<response>
    <code>200</code>
    <status>success</status>
    <data class="AuthenticationResult">
        <lacksTOS>false</lacksTOS>
        <invalidCredentials>false</invalidCredentials>
        <authToken>1.0|idm|idm|4ee683baa2a3332c3c86026d</authToken>
    </data>
</response>

  和:

<response>
    <code>401</code>
    <status>error</status>
    <message>token is invalid</message>
    <data class="string">UnauthorizedException</data>
</response>

处理跨域问题

   我们且听说过关于浏览器的同源策略要同源性需求。它借助的是浏览器只能请时正在显示的站点的资源。例如,如果手上在显示的站点是www.Example1.com,则该站点不可知对www.Example.com倡导呼吁。显然这会潜移默化站点访问服务器的措施。

  时生点儿单给大规模接受之支撑跨域请求的章程:JSONP和跨域资源共享(CORS)。JSONP或“填充的JSON”是同样种植使模式,它提供了一个艺术要来自不同域中之服务器的数额。其行事办法是从服务器返回任意的JavaScript代码,而不是JSON。客户端的响应由JavaScript解析器进行分析,而无是直解析JSON数据。另外,CORS是如出一辙种web浏览器的艺标准,它吗web服务器定义了一如既往种植艺术,从而允许服务器的资源得以为免同域的网页访问。CORS被看做是JSONP的新型替代品,并且可以被抱有现代浏览器支持。因此,不建议采用JSONP。任何动静下,推荐选择CORS。

支持CORS

  于服务端实现CORS很粗略,只待以发送响应时顺便HTTP头,例如: 

Access-Control-Allow-Origin: *

  只有以数量是官使用的景况下才见面拿做客来源设置也”*”。大多数状况下,Access-Control-Allow-Origin头应该指定哪些域好发起一个CORS请求。只有用跨域访问的URL才装CORS头。

Access-Control-Allow-Origin: http://example.com:8080
http://foo.example.com

  以上Access-Control-Allow-Origin头中,被装也单纯允许被信赖的地域可以看。

Access-Control-Allow-Credentials: true

  只以用经常才下方面这header,因为只要用户既报到的话,它见面同时发送cookies/sessions。

  这些headers可以透过web服务器、代理来拓展部署,或者从服务器本身发送。不引进在服务端实现,因为生无灵敏。或者,可以运用方面的第二栽办法,在web服务器上布置一个用空格分隔的地面的列表。更多关于CORS的情节可以参见这里:http://enable-cors.org/。

支持JSONP

  JSONP通过利用GET请求避开浏览器的限,从而实现对具有服务之调用。其工作规律是央求方在伸手的URL上添加一个字符串查询参数(例如:jsonp=”jsonp_callback”),其中“jsonp”参数的价是JavaScript函数称作,该函数在起响应返回时用会见于调用。

  由于GET请求中绝非含呼吁求体,JSONP在应用时有着严重的局限性,因此数据必须通过字符串查询参数来传递。同样的,为了支持PUT,POST和DELETE方法,HTTP方法必须为通过字符串查询参数来传递,类似_method=POST这种形式。像这样的HTTP方法传送方式是休引进以的,这会叫服务处于安全风险之中。

  JSONP通常以一些请勿支持CORS的老旧浏览器被采用,如果只要改成成为支持CORS的,会潜移默化总体服务器的架。或者我们吧足以经代理来贯彻JSONP。总之,JSONP正在吃CORS所取代,我们应有尽量地行使CORS。

  为了以服务端支持JSONP,在JSONP字符串查询参数传递时,响应必须要实施以下这些操作:

  1. 响应体必须封装成一个参数传递给jsonp中指定的JavaScript函数(例如:jsonp_callback(“<JSON
    response body>”))。
  2. 一味返回HTTP状态码200(OK),并且将真正的状态作为JSON响应中的同片归。

  另外,响应体中不时要含有响应头。这让JSONP回调方法要依据响应体来规定响应处理方式,因为她自身无法获知真实的响应头和状态值。

  下面的事例是比照上述方法封装的一个回error状态的jsonp(注意:HTTP的应状态是200):

jsonp_callback("{'code':'404', 'status':'error','headers':[],'message':'resource XYZ not
found','data':'NotFoundException'}")

  成功创建后底应类似于如此(HTTP的应状态仍是200):

jsonp_callback("{'code':'201', 'status':'error','headers':
[{'Location':'http://www.example.com/customers/12345'}],'data':'12345'}")

 

查询,过滤与分页

  对于大数据集,从带宽的角度来拘禁,限制返回的数据量是甚主要的。而自从UI处理的角度来拘禁,限制数据量也同要,因为UI通常只能展现大数额集中的一模一样多少有数据。在数据集的增长速度不确定的状态下,限制默认返回的数据量是怪有必不可少之。以Twitter为条例,要博有用户之推文(通过个人主页的辰轴),如果无专门指定,请求默认只见面回来20长长的记下,尽管系统最多可回去200长记下。

  除了限制返回的数据量,我们尚索要考虑什么对天意据集进行“分页”或下拉滚动操作。创建数量的“页码”,返回大数额列表的都了解片段,然后标出数据的“前同一页”和“后同样页”——这同表现给称之为分页。此外,我们或吗急需指定响应中将包含哪些字段或性质,从而限制返回值的数码,并且我们愿意最后能够通过一定值来进行查询操作,并对回到值进行排序。

  有少种植要的主意来以限制查询结果和实施分页操作。首先,我们得以成立一个目方案,它可以页码为导向(请求中而吃起各一样页的记录数及页码),或者为记录也导向(请求中一直让出第一久记下与最终一长达记下)来规定返回值的开始位置。举个例子,这简单种植方法分别代表:“给出第五页(假设每页有20修记下)的记录”,或“给有第100暨第120久之笔录”。

  服务端将依据运作机制来展开切分。有些UI工具,比如Dojo
JSON会选择模仿HTTP规范使用字节范围。如果服务端支持out of
box(即开箱即用效应),则前端UI工具和后端服务中无需另移,这样使起来会杀有利。

  下文将介绍一种植方法,既能支持Dojo这样的分页模式(在请求头中受起记录之克),也能够支撑以字符串查询参数。这样一来服务端将移得尤为灵敏,既好用类似Dojo一样先进的UI工具集,也堪下简易直接的链接和标签,而无论是需再为这多复杂的付出工作。但倘若服务不直支持UI功能,可以设想不要以请求头中于起记录范围。

  要专门指出的凡,我们并无引进以备服务遭遇动用查询、过滤与分页操作。并无是有着资源还默认支持这些操作,只有少数特定的资源才支撑。服务与资源的文档应当说明如何接口支持这些扑朔迷离的功用。

结果限制

  “给有第3至第55长达的记录”,这种求数据的主意与HTTP的字节范围规范更平等,因此我们得用它来标识Range
header。而“从第2久记下开始,给闹极多20长达记下”这种艺术再次爱阅读与掌握,因此我们便会因此字符串查询参数的法子来表示。

  综上所述,推荐既支持下HTTP Range
header,也支撑使用字符串查询参数——offset(偏移量)和limit(限制),然后以服务端对响应结果开展限定。注意,如果以支持这片栽方式,那么字符串查询参数的优先级要超越Range
header。

  这里你恐怕会见来个谜:“这半栽办法效果相似,但是返的数目不完全一致。这会无会见吃人口歪曲呢?”恩…这是片独问题。首先要回的是,这实在会被人歪曲。关键是,字符串查询参数看起越清晰易懂,在构建与剖析时进一步惠及。而Range
header则再次多是由机器来使(偏向于底层),它进一步吻合HTTP使用规范。

  总之,解析Range
header的劳作会追加复杂度,相应的客户端在构建请求时也亟需开展一些处理。而采取单独的limit和offset参数会更为爱懂与构建,并且不待对开发人员有还多的渴求。

所以范围标记进行限

  当用HTTP header而无是字符串查询参数来赢得记录之限时,Ranger
header应该经过以下内容来指定范围: 

  Range: items=0-24

  注意记录是从0开始的连年字段,HTTP规范着证了什么样使Range
header来请求字节。也就是说,如果要要数据集中的率先漫长记下,范围该从0开始算从。上述的请将会晤回去前25单记录,假而数据汇总至少有25长长的记下。

  而以服务端,通过检查请求的Range
header来确定拖欠归哪些记录。只要Range
header存在,就见面发一个简便的正则表达式(如”items=(\d+)-(\d+)”)对其展开解析,来博要寻找的范围值。

故字符串查询参数进行限

  字符串查询参数为当Range
header的替代选择,它采用offset和limit作为参数叫做,其中offset代表要询问的首先长记下编号(与上述的用来范围标记的items第一单数字同样),limit代表记录的极可怜条数。下面的例证返回的结果与上述用范围标记的例子一样:

  GET http://api.example.com/resources?offset=0&limit=25

  Offset参数的价值与Range
header中的切近,也是从0开始计。Limit参数的值是返回记录之最好充分数额。当字符串查询参数中莫指定limit时,服务端应当被来一个缺乏省之顶深limit值,不过这些参数的采取都用在文档中开展验证。

据悉范围之响应

  对一个冲范围之请求来说,无论是通过HTTP的Range
header还是通过字符串查询参数,服务端都应有发生一个Content-Range
header来响应,以表明返回记录的条数和总记录数:

  Content-Range: items 0-24/66

  注意这里的毕竟记录数(如本例中的66)不是从0开始算计的。如果如呼吁数据集中之最终几修记下,Content-Range
header的情应是这般:

  Content-Range: items 40-65/66

  根据HTTP的正规,如果响应时总记录数未知或不便计算,也堪用星号(”*”)来代替(如本例中之66)。本例中响应头也只是这般描写:

  *Content-Range: items 40-65/**

  不过假如专注,Dojo或局部别的UI工具或未支持该符号。

分页

  上述方式经过请求方指定数据集的限量来界定返回结果,从而实现分页功能。上面的例子中一共发66长条记下,如果各页25长记下,要显第二页数据,Range
header的始末如下:

  Range: items=25-49

  同样,用字符串查询参数表示如下:

  GET …?offset=25&limit=25

  服务端会相应地回一组数,附带的Content-Range header内容如下:

  Content-Range: 25-49/66

  在大多数气象下,这种分页方式都并未问题。但偶尔会发出这种场面,就是若回来的记录数据无法直接代表成数据汇总的行号。还有即使是发出头数据集的变快,不断会出新的数码插入到数量集中,这样必然会招分页出现问题,一些又的数据可能会见油然而生在不同的页中。

  按日期排列的数据集(例如Twitter
feed)就是同一栽常见的图景。虽然你要么好对数据开展分页,但偶尔用”after”或”before”这样的关键字连跟Range
header(或者和字符串查询参数offset和limit)配合来促成分页,看起会愈加简洁易亮。

  例如,要博取给定时间戳的前方20漫长评论:

  GET
http://www.example.com/remarks/home\_timeline?after=&lt;timestamp&gt; 

  Range: items=0-19

  GET
http://www.example.com/remarks/home\_timeline?before=&lt;timestamp&gt; 

*  Range: items=0-19*

  用字符串查询参数表示也:

  GET
http://www.example.com/remarks/home\_timeline?after=&lt;timestamp&gt;&offset=0&limit=20 

*  GET
http://www.example.com/remarks/home\_timeline?before=&lt;timestamp&gt;&offset=0&limit=20*

  有关以不同景象对时间戳的格式化处理,请参见下文的“日期/时间处理”。

  如果请时无点名要回来的数据范围,服务端返回了平组默认数据或者限的不过老数据集,那么服务端同时也该以回到结果受到蕴含Content-Range
header来和客户端进行确认。以地方个人主页的岁月轴为例,无论客户端是不是指定了Range
header,服务端每次都只是回20长长的记下。此时,服务端响应的Content-Range
header应该包含如下内容:

  Content-Range: 0-19/4125

  或 *Content-Range: 0-19/**

结果的过滤跟排序

  针对返回结果,还用考虑怎样当服务端对数码开展过滤跟排,以及哪随指定的逐条对子数据开展搜寻。这些操作可以同分页、结果限制,以及字符串查询参数filter和sort等竞相结合,可以实现强大的数据检索功能。

  再强调平等不好,过滤与排序都是繁体的操作,不需默认提供被所有的资源。下文将介绍如何资源用提供过滤和排序。

过滤

  于本文中,过滤被定义也“通过一定的基准来规定要使回到的多少,从而减少返回的多寡”。如果服务端支持一模仿完整的比运算符和复杂的规范配合,过滤操作以移得一定复杂。不过我们便会以有简约的表达式,如starts-with(以…开始)或contains(包含)来展开匹配,以保证返回数据的完整性。

  以我们初步谈论过滤的字符串查询参数之前,必须事先了解怎么而采取单个参数而非是大半只字符串查询参数。从根本上来说是为着减小参数名称的冲突。我们曾经发生offsetlimitsort(见下文)参数了。如果可能的口舌还会见来jsonpformat标识符,或许还见面发afterbefore参数,这些还是以本文惨遭提到了之字符串查询参数。字符串查询中应用的参数越多,就越可能引致参数名称的冲突,而动单个过滤参数则会以闯的可能性降低到低。

  此外,从服务端也异常轻就透过单个的filter参数来判定请求方是否需要多少过滤效果。如果查询需要的复杂度增加,单个参数将再有灵活性——可以好立平等效功能完全的询问语法(详见下文OData注释或访问http://www.odata.org)。

  通过引入一组大的、公认的分隔符,用于过滤的表达式可以为死直观的花样为采用。用这些分隔符来设置过滤查询参数的价,这些分隔符所创建的参数名/值对会更容易地为服务端解析并加强多少查询的性。目前一度部分分隔符包括用来分隔每个过滤短语的竖线(”|”)和用来分隔参数名为及价值的对仗冒号(”::”)。这套分隔符足够唯一,并可大多数状,同时用其来构建的字符串查询参数为越来越爱了解。下面将就此一个概括的事例来介绍其的用法。假设我们怀念如果吃名也“Todd”的用户们发送请求,他们停下在丹佛,有着“Grand
Poobah”之称。用字符串查询参数实现之乞求URI如下:

  GET
http://www.example.com/users?filter="name::todd|city::denver|title::grand
poobah”

  双冒号(”::”)分隔符将属于性名和价值分开,这样属性值就可知包含空格——服务端能重复易地打属于性值中分析出分隔符。

  注意查询参数名/值对遭到的性质名要和服务端返回的特性名相匹配。

  简单而使得。有关大小写敏感的题目,要因具体情况来拘禁,但看来,在并非关心大小写的动静下,过滤效果可充分好地运转。若查询参数名/值对遭到之属性值未知,你吧足以为此星号(”*”)来代替。

  除了简单的表达式和通配符之外,若一旦进行重新扑朔迷离的查询,你得使引入运算符。在这种情景下,运算符本身吗是属性值的等同片段,能够让服务端解析,而无是成为属性名的平部分。当得复杂的query-language-style(查询语言风格)功能时,可参照Open
Data Protocol (OData) Filter System Query
Option说明遭到之询问概念(详见http://www.odata.org/documentation/uriconventions#FilterSystemQueryOption)。

排序

  排序决定了从服务端返回的记录之次第。也就是对响应中之大多修记下进行排序。

  同样,我们这里仅考虑有比较简单的气象。推荐应用排序字符串查询参数,它涵盖了扳平组用分隔符分隔的属性名。具体做法是,默认对每个属性名以升序排列,如果属于性名有前缀”-“,则随降序排列。用竖线(”|”)分隔每个属性名,这跟眼前过滤效果受到的参数名/值对之做法无异于。

  举个例,如果我们纪念循用户之姓氏和称进行升序排序,而针对雇佣时间开展降序排序,请求将是这么的:

  GET
http://www.example.com/users?sort=last\_name|first\_name|-hire\_date

  再次强调一下,查询参数名/值对受之性能名要和服务端返回的性名相匹配。此外,由于排序操作比较复杂,我们就对亟待之资源提供排序功能。如果需要的话也得以客户端对有些的资源聚合进行排。

 

劳版本管理

   坦率地提,一说及本就会见受丁觉着甚拮据,很麻烦,不绝好,甚至会让丁觉得难受——因为当时会追加API的复杂度,并同时可能会见指向客户端起局部影响。因此,在API的统筹着如果尽量避免多单例外之本子。

  不支持版本,不以版本控制作为糟糕之API设计之倚重。如果你在APIs的计划性被引入版本,这迟早还见面为您捉狂。由于返回的数额经过JSON来呈现,客户端会由于不同的本要接受及不同的性质。这样即使会见设有有的题目,如由内容己以及认证规则者改变了一个都在的性之义。

  当然,我们无法避免API可能于好几时刻要转移返回数据的格式和内容,而立即吗用招致消费端的一部分转变,我们相应避免进行一些要的调。将API进行版本化管理是免这种根本转变的平种植有效方式。

经内容商支持版本管理

  以往,版本管理通过URI本身的本子号来形成,客户端在恳求的URI中标明要获取的资源的版本号。事实上,许多老大商家要Twitter、Yammer、Facebook、Google等不时于她们的URI里使用版本号。甚至像WSO2这样的API管理工具也会见当其的URLs中求版本号。

  面向REST原则,版本管理技术飞速发展。因为它不分包HTTP规范着置放的header,也无支持不过当一个初的资源要概念叫引入时才应添加新URI的理念——即版本不是表现形式的变更。另一个反对之理是资源URI是不见面随时间改变之,资源就是资源。

  URI应该能够简单地辨识资源——而不是她的“形状”(状态)。另一个虽是得指定响应的格式(表征)。还有有HTTP
headers:Accept 和 Content-Type。Accept
header允许客户端指定所企望或者会支持的响应的传媒类型(一栽或多)。Content-Type
header可分别被客户端与服务端用来指定要或响应的多少格式。

  例如,要抱一个user的JSON格式的数量:

  #Request:

  GET http://api.example.com/users/12345
  Accept: application/json; version=1

  #Response:

  HTTP/1.1 200 OK
  Content-Type: application/json; version=1

  {“id”:”12345″, “name”:”Joe DiMaggio”}

  现在,我们对同资源要版本2底数码:

  #Request:

  GET http://api.example.com/users/12345
  Accept: application/json; version=2

  #Response:

  HTTP/1.1 200 OK
  Content-Type: application/json; version=2

  {“id”:”12345″, “firstName”:”Joe”, “lastName”:”DiMaggio”}

  Accept
header被用来代表所梦想之应格式(以及示例中之版本号),注意上述两独一样的URI是什么样做到以不同的版本被分辨资源的。或者,如果客户端需要一个XML格式的数据,可以将Accept
header设置为”application/xml”,如果需要的话也可带一个点名的版本号。

  由于Accept
header可以叫装置也允许多媒体类型,在应请求时,服务器将把响应的Content-Type
header设置也最匹配配客户端请求内容的花色。更多信息可参照http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.Html

  例如:

  #Request

  GET http://api.example.com/users/12345

  Accept: application/json; version=1, application/xml; version=1

  上述呼吁中,假设服务器支持JSON
和XML格式的请求,或者少种植都支持,那么用由服务器来控制最终回到哪种类型的多少。但无论服务器选择啊一样栽,都见面以应中包含Content-Type
header。

  例如,如果服务器返回application/xml格式的数目,结果是:

  #Response

  HTTP/1.1 200 OK
  Content-Type: application/xml; version=1

  <user>
    <id>12345</id>
    <name>Joe DiMaggio</name>
  </user>

  为了验证Content-Type在发送数据给服务器时的用处,这里吃闹一个就此JSON格式创建新用户之事例:

  #Request

  POST http://api.example.com/users
  Content-Type: application/json;version=1

  {“name”:”Marco Polo”}

  或者,调用版本2之接口:

  #Request

  POST http://api.example.com/users
  Content-Type: application/json;version=2

  {“firstName”:”Marco”, “lastName”:”Polo”}

当没有点名版本时,返回什么版本?

  并不需要在各一个请求被还指定版本号。由于HTTP
content-negotiation(内容商)遵循类型的“最佳匹配”方式,所以你的API也当遵照这或多或少。根据这同一口径,当客户端从未点名版本时,API应当返回所支撑之顶早版本。

  还是这例子,获取一个user的JSON格式的数额:

  #Request

  GET http://api.example.com/users/12345
  Accept: application/json

  #Response

  HTTP/1.1 200 OK
  Content-Type: application/json; version=1

  {“id”:”12345″, “name”:”Joe DiMaggio”}

  相应地,当为POST方式向服务器发送数据时,如果服务器支持多个不同版本,而求时又没有点名版本,和方的事例一样——服务器会将最为小/最早版本的数包含在body中。为了进行求证,下面的例证以JSON格式请求一个蕴含多本资源的服务器,来创造一个新用户(预期会回去版本1):

  #Request

  POST http://api.example.com/users
  Content-Type: application/json

  {“name”:”Marco Polo”}

  #Response

  HTTP/1.1 201 OK
  Content-Type: application/json; version=1
  Location: http://api.example.com/users/12345

  {“id”:”12345″, “name”:”Marco Polo”}

请不支持之本子

  当求一个休支持之本号时(包含在API生命周期中就不复存在的资源版本),API应当返回一个荒谬的HTTP状态码406(表示不让奉)。此外,API还该返回一个包含Content-Type:
application/json的响应体,其中带有一个JSON数组,用于证明该服务器支持之种类。

  #Request

  GET http://api.example.com/users/12345
  Content-Type: application/json; version=999

  #Response

  HTTP/1.1 406 NOT ACCEPTABLE 

  Content-Type: application/json

  [“application/json; version=1”, “application/json; version=2”,
“application/xml; version=1”, “application/xml; version=2”]

嗬时应该创建一个初本子?

  API开发被的诸多者都见面打破约定,并最后对客户端有有不良影响。如果你切莫确定API的改动会带什么的结局,保险起见最好考虑采取本控制。当你当考虑提供一个初本子是否适用时,或者考虑对现有的返表征进行修改是否必然能满足急需并吃客户端所接受时,有这么几独因素使考虑。

破坏性的改

  • 变更属性名(例如将”name”改成为”firstName”)
  • 删去属性
  • 改属性之数据类型(例如将numeric变为string,
    boolean变为bit/numeric,string 变为 datetime等等)
  • 改变验证规则
  • 当Atom样式的链接中,修改”rel”的价
  • 每当存活的工作流中引入必要资源
  • 反资源的概念/意图;概念/意图或资源状态的义不同为其原本的意思。例如:
    • 一个content
      type是text/html的资源,之前表示的是具备支持之媒体类型的一个”links”集合,而初的text/html则意味着的凡用户输入的“web浏览器表单”。
    • 一个暗含”endTime”参数的API,对资源”…/users/{id}/exams/{id}”表达的义是学生在老大时间付诸试卷,而初的意义则是考的约定了时。
  • 经添加新的字段来改变现有的资源。将鲜独资源统一为一个并弃用旧的资源。
    • 起这么简单单资源”…/users/{id}/dropboxBaskets/{id}/messages/{id}”和”…/users/{id}/dropboxBaskets/{id}/messages/{id}/readStatus”。新需要是将readStatus资源的特性放到单独的message资源面临,并弃用readStatus资源。这将导致messages资源遭受指向readStatus资源的链接给移除。

  虽然上面列有的连无完美,但它们深受闹了有晤针对客户端起破坏性影响之变类型,这时用考虑提供一个初资源或新本子。

非破坏性的改

  • 当回到的JSON中补充加新属性
  • 丰富指向任何资源的”link”
  • 添加content-type支持之初格式
  • 添加content-language支持的初格式
  • 是因为API的开创者和买主都设拍卖不同的casing,因此casing的成形无关紧要

版本控制应于啊级别出现?

  建议针对性单个的资源拓展版本控制。对API的局部改,如修改工作流,也许要过多个资源的版本控制,以这个来防护对客户端起破坏性的震慑。

采用Content-Location来增长响应

  可选。见RDF(Resource Description Framework,即资源描述框架)规范。

带有Content-Type的链接

  Atom风格的链接支持”type”属性。提供足够的信以便客户端可本着一定的版本和内容类型进行调用。

搜寻有支持的本

自当以支持小只本子?

  维护多个不等之版会吃工作转移得烦、复杂、容易出错,而且代价高,对于任何给定的资源,你应该支持非超过2只本子。

弃用

  Deprecated(弃用)的目的是因此来证实资源对API仍然可用,但于明天见面无有并转移得不可用。专注:弃用的时长将由弃用政策决定——这里连无吃有概念。

本人怎么样告客户端给弃用的资源?

  许多客户端将来走访的资源或在新本子引入后会于废弃掉,因此,他们用来雷同栽方式来发现及监理他们的应用程序对遗弃用资源的用。当呼吁一个弃用资源时,API应该正常应,并蕴藏一个布尔档次的自定义Header
“Deprecated”。以下用一个例证来进展认证。

  #Request

  GET http://api.example.com/users/12345
  Accept: application/json
  Content-Type: application/json; version=1

  #Response

  HTTP/1.1 200 OK
  Content-Type: application/json; version=1
  Deprecated: true
  {“id”:”12345”, “name”:”Joe DiMaggio”}

 

日期/时间拍卖

  如果没妥善地、一致地处理好日期及时来说,这将成为一个要命累。我们常常会面遇上时区的问题,而且由于日期在JSON中凡坐字符串的格式在的,如果不指定统一之格式,那么解析日期为会见是一个问题。

  于接口内部,服务端应该因为UTC或GMT时间来存储、处理与缓存时间戳。这将中缓解日期及时空的题材。

Body内容被的日期/时间序列化

  有一个简约的办法可缓解这些题材——在字符串中尽用平等的格式,包括时间片(带有时区信息)。ISO8601时间格式是一个对的化解方案,它用了截然增强的年月格式,包括小时、分钟、秒和秒的小数部分(例如yyyy-MM-dd’T’HH:mm:ss.SSS’Z’)。建议于REST服务之body内容中(请求和应均包括)使用ISO8601代表所有的日期格式。

  顺便取一下,对于那些基于JAVA的劳务以来,DateAdapterJ库使用DateAdapter,Iso8601TimepointAdapter和HttpHeaderTimestampAdapter类可以非常容易地剖析及格式化ISO8601日期以及时,以及HTTP
1.1
header(RFC1123)格式。可以自https://github.com/tfredrich/DateAdapterJ下载。

  对于那些创建基于浏览器的用户界面来说,ECMAScript5规范一开始便带有了JavaScript解析及创造ISO8601日期的情,所以其应有改成我们所说之主流浏览器所遵循的方。当然,如果你若支持那些不能自动解析日期的旧版浏览器,可以运用JavaStript库或正则表达式。这里出几只可分析和创办ISO8601时间之JavaStript库:

  http://momentjs.com/

  http://www.datejs.com/

HTTP Headers中的日期/时间序列化

  然而上述提议才适用于HTTP请求或响应内容遭之JSON和XML内容,HTTP规范针对HTTP
headers使用外一样栽不同的格式。在被RFC1123重给之RFC822中指出,该格式包括了各种日期、时间跟date-time格式。不过,建议始终用时戳格式,在公的request
headers中它看起像这么:

  Sun, 06 Nov 1994 08:49:37 GMT

  不过,这种格式没有设想毫秒或者秒的十进制小数。Java的SimpleDataFormat的格式串是:”EEE,
dd MMM yyyy HH:mm:ss ‘GMT'”。

 

护服务之平安

  Authentication(身份验证)指的是认同给定的要是于服务都掌握的某(或某个系统)发出之,且请求者是他协调所声明的不胜人。Authentication是为验证请求者的真正身份,而authorization(授权)是为求证请求者有权力去实践为呼吁的操作。

  本质上,这个历程是这般的:

  1. 客户端发起一个要,将authentication的token(身份证明令牌)包含在X-Authentication
    header中,或者将token叠加在伸手的查询串参数中。
  2. 服务器对authorization
    token(授权令牌)进行自我批评,并进行验证(有效且非过),并因令牌内容分析或者加载认证中心。
  3. 服务器调用授权服务,提供验证中心、被呼吁资源与必备之操作许可。
  4. 若果授权通过了,服务器将会延续健康运行。

  上面第三步的付出可能会见较好,但是只要如果存在一个而缓存的权柄控制列表(ACL),那么在起远程请求前,可以以地方创建一个授权客户端来缓存最新的ACLs。

身份验证

  时最好好之做法是采用OAuth身份验证。强烈推荐OAuth2,不过它们仍处于草案状态。或者选择OAuth1,它了可胜任。在好几情况下吧得以选取3-Legged
OAuth。更多关于OAuth的业内好查看此http://oauth.net/documentation/spec/。

  OpenID是一个增大选择。不过建议以OpenID作为一个附加的身份验证选项,以OAuth为主。更多关于OpenID的专业好翻此http://openid.net/developers/specs/。

传安全

  所有的辨证都应当采取SSL。OAuth2需要授权服务器和access
token(访问令牌)来利用TLS(安全传输层协议)。

  以HTTP和HTTPS之间切换会带来平安隐患,最好之做法是有着简报默认都采用TLS。

授权

  对劳务之授权和针对性其他应用程序的授权一样,没有其它区别。它根据这样一个题材:“主体是否针对加的资源起要的许可?”这里让出了简单的老三桩数据(主体,资源同许可),因此特别爱构造一个支撑这种概念的授权服务。其中重点是给给予资源访问许可的人口还是体系。使用这些相似概念,就好啊各级一个主题构建一个缓存访问控制列表(ALC)。

应用程序安全

  对RESTful服务以来,开发一个安然无恙的web应用适用同的标准化。

  • 于服务器上证实所有输入。接受“已清楚”的是的输入并驳回错误的输入。
  • 防止SQL和NoSQL注入。
  • 采取library如微软的Anti-XSS或OWASP的AntiSammy来对出口的数码进行编码。
  • 用信息之长限制在规定的字段长度内。
  • 劳动应该只展示一般的错误信息。
  • 考虑工作逻辑攻击。例如,攻击者可超越了多步骤的预订流程来预订产品如果无论是需输入信用卡信息吗?
  • 本着可疑之运动记录日志。

  RESTful安全要专注的地方:

  • 证明数据的JSON和XML格式。
  • HTTP动词应该受拘在允许的道被。例如,GET请求不可知去除一个实体。GET用来读取实体而DELETE用来删除实体。
  • 顾race
    conditions(竞争原则——由于个别单或基本上只经过竞争下无克吃同时做客的资源,使得这些过程产生或因日子达推进的次序因一旦产出问题)。

  API网关可用于监视、限制及操纵对API的拜会。以下内容可由于网关或RESTful服务实现。

  • 蹲点API的应用情况,并了解如何活动是正规的,哪些是非正常的。
  • 限定API的行使,使恶意用户不能够停少一个API服务(DOS攻击),并且有力量阻止恶意的IP地址。
  • 将API密钥存储在加密底平安密钥库中。

 

缓存和可伸缩性

  通过以网层级消除通过远距离调用来获取请求的数,缓存提高了系统的可是扩展性。服务通过当应中安装headers来增进缓存的能力。遗憾之凡,HTTP
1.0中及缓存相关的headers与HTTP
1.1不比,因此服务器如果而支持有限栽版本。下表给起了GET请求而支持缓存所必须的最好少headers集合,并给来了适度的描述。

HTTP Header

描述

示例

Date

一呼百应返回的日期与岁月(RFC1123格式)。

Date: Sun, 06 Nov 1994 08:49:37 GMT

Cache-Control

一呼百应可吃缓存的绝深秒数(最酷age值)。如果响应不支持缓存,值吗no-cache。

Cache-Control: 360

Cache-Control: no-cache

Expires

假定给出了最大age值,该时间穿(RFC1123格式)表示的凡响应过期的工夫,也就算是Date(例如当前日期)加上最要命age值。如果响应不支持缓存,该headers不有。

Expires: Sun, 06 Nov 1994 08:49:37 GMT

Pragma

当Cache-Control为no-cache时,该header的价为吃安装也no-cahche。否则,不有。

Pragma: no-cache

Last-Modified

资源本身最后吃涂改的时空戳(RFC1123格式)。

Last-Modified: Sun, 06 Nov1994 08:49:37 GMT

  为了简化,这里选出一个响应中的headers集合的例子。这是一个简短的对准资源进行GET请求的响应,缓存时长为同样天(24时):

  Cache-Control: 86400
  Date: Wed, 29 Feb 2012 23:01:10 GMT
  Last-Modified: Mon, 28 Feb 2011 13:10:14 GMT
  Expires: Thu, 01 Mar 2012 23:01:10 GMT

  下面是一个类似之例证,不过缓存被全然禁用:

  Cache-Control: no-cache
  Pragma: no-cache

ETag Header

  ETag
header对于证明缓存数据的初老程度很有因此,同时也推动条件的读取和换代操作(分别吗GET和PUT)。它的价值是一个任意字符串,用来表示回到数据的版。不过,对于返回数据的异格式,它为得以不同——JSON格式响应的ETag与同等资源XML格式响应的ETag会不同。ETag
header的价好像带有格式的底层域对象的哈希表(例如Java中之Obeject.hashcode())一样简单。建议吗每个GET(读)操作返回一个ETag
header。另外,确保用双引号包含ETag的值,例如:

  ETag: “686897696a7c876b7e”

 

HTTP状态码(前10)

  以下是由RESTful服务或者API返回的卓绝常用之HTTP状态码,以及有关于她广泛用法的略说明。其它HTTP状态码不顶经常应用,它们或更与众不同,要么更高级。大多数劳动套件只支持这些常用的状态码,甚至光支持中的一致有的,并且其都能健康办事。

  200 (OK) —— 通常的成功状态。表示成功的极其普遍代码。

  201 (CREATED) ——(通过POST或PUT)创建成功。通过安装Location
header来含有一个针对性最新创建的资源的链接。

  204 (NO CONTENT)
—— 封装过之应没有利用,或body中并未其余内容经常(如DELETE),使用该状态。

  304 (NOT MODIFIED)
—— 用于产生原则的GET调用的响应,以压缩带宽的采取。
如果下该状态,那么得也GET调用设置Date、Content-Location和ETag
headers。不带有响应体。

  400 (BAD REQUEST)
—— 用于履行要时或许惹无效状态的一般错误代码。如域名无效错误、数据丢失等。

  401 (UNAUTHORIZED)
—— 用于缺少认证token或说明token无效的错误代码。

  403 (FORBIDDEN)
—— 未授权的用户执行操作,没有权力访问资源,或者是因为某些原因资源不可用(如时间限制等),使用该错误码。

  404 (NOT FOUND)
—— 无论资源存不在,无论是否有401、403的限定,当呼吁的资源找不交常,出于安全因素考虑,服务器都得以用该错误码来遮掩。

  409 (CONFLICT)
—— 每当执行要或会见招资源撞时常用。例如,存在重新的实业,当不支持级联删除时去根对象。

  500 (INTERNAL SERVER ERROR)
—— 当服务器抛来异常时,捕捉到的貌似错误。

 

叠加资源

书籍

  REST API Design Rulebook,Mark Masse, 2011, O’Reilly Media, Inc.

  RESTful Web Services, Leonard Richardson and Sam Ruby, 2008,
O’Reilly Media, Inc.

*  RESTful Web Services Cookbook, Subbu Allamaraju, 2010, O’Reilly
Media, Inc.*

  REST in Practice: Hypermedia and Systems Architecture, Jim Webber,
et al., 2010, O’Reilly Media, Inc.

  APIs: A Strategy Guide, Daniel Jacobson; Greg Brail; Dan Woods,
2011, O’Reilly Media, Inc.

网站

  http://www.restapitutorial.com
http://www.toddfredrich.com
  http://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm
  http://www.json.org/
https://github.com/tfredrich/DateAdapterJ
  http://openid.net/developers/specs/
  http://oauth.net/documentation/spec/
  http://www.json.org/JSONRequest.html
http://labs.omniti.com/labs/jsend
  http://enable-cors.org/
  http://www.odata.org/documentation/uri-conventions#FilterSystemQueryOption
  http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven
  https://developer.linkedin.com/apis
  http://developers.facebook.com/docs/reference/api/
  https://dev.twitter.com/docs/api
http://momentjs.com/
  http://www.datejs.com/

 

每当原本翻译的底蕴及经过改动:http://blog.csdn.net/huayuqa/article/details/62237010

英文原稿下载:RESTful Best Practices-v1
2.pdf