rest put post 区别模式get,put,post,delete含义与区别

也谈 GET 和 POST 的区别-ASP.NET-第七城市
也谈 GET 和 POST 的区别
上个月,博客园精华区有篇文章《 GET 和 POST 有什么区别?及为什么网上的多数答案都是错的 》,文中和回复多是对以下两个问题进行了深究:
Url 是否隐藏数据在我看来这两者都不是重点,特写此文予以讨论。我们先来看些基本概念:HTTP 基本概念HTTP Request MethodsGET、POST 专业名称是 HTTP Request Methods。但 HTTP Request Methods 不只是 GET 和 POST,完整列表如下:
PATCH REST 使用前四个:GET、POST、PUT、DELETE。因些这四个也是经常被一块提及的,将这四个作为关键字进行搜索,你会得到更深入的结果。在一般的 Web 开发中,GET 和 POST 用得最多,网上对这两的讨论也是最多,往往又很肤浅的。更多信息请查看:&&& http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html&&& http://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol&&& http://zh.wikipedia.org/wiki/RESTSafe Methods(安全方法)RFC 2616 中定义如下(后面翻译):Implementors should be aware that the software represents the user in their interactions over the Internet, and should be careful to allow the user to be aware of any actions they might take which may have an unexpected significance to themselves or others.In particular, the convention has been established that the GET and HEAD methods SHOULD NOT have the significance of taking an action other than retrieval. These methods ought to be considered "safe". This allows user agents to represent other methods, such as POST, PUT and DELETE, in a special way, so that the user is made aware of the fact that a possibly unsafe action is being requested.Naturally, it is not possible to ensure that the server does not generate side-effects as a result of performing a GET in fact, some dynamic resources consider that a feature. The important distinction here is that the user did not request the side-effects, so therefore cannot be held accountable for them.网址:http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1维基百科中的说明(基本上是对 RFC 2616翻译):开发者应当意识到他们的软件代表了用户在因特网上进行交互,并且应当告知用户,他们正在进行的操作可能对他们自身或者其他人有未曾预料的重要影响。特别地,对于GET和HEAD方法而言,除了进行获取资源信息外,这些请求不应当再有任何其他意义。也就是说,这些方法应当被认为是“安全的”。客户端应当使用其他“非安全”方法,例如POST,PUT及DELETE来以特殊的方式(通常是按钮而不是超链接)使得客户能够意识到可能要负的责任(例如一个按钮带来的资金交易)或者被告知正在请求的操作可能是不安全的(例如某个文件将被上传或删除)。但是,不能想当然地认为服务器在处理某个GET请求时不会产生任何副作用。事实上,很多动态资源会把这作为其特性。这里重要的区别在于用户并没有请求这一副作用,因此不应由用户为这些副作用承担责任。这部分读起来比较晦涩,不要着急,读完后面的再回头看就好理解了。来源网址:http://zh.wikipedia.org/wiki/超文本传输协议#.E5.AE.89.E5.85.A8.E6.96.B9.E6.B3.95GET 与 POST 的区别综上所述,可总结如下:
GET 仅用来获取查看信息,不能改变服务器信息。
POST 用来改变服务器信息。 这里说的改变,包括增加、修改和删除。这是 HTTP 协议中的要求,众多浏览器和浏览器插件都遵守这些约定。如果你的代码不按照这约定来,可能会出现严重的后果。使用 GET 改变服务器信息的严重后果假定你编写的 Web 程序或网站允许 GET 提交的修改,比如允许用户通过以下 Url 直接删除编写为 1024 的订单:&&
~/orders/delete/1024那么在订单的管理(或列表)页面,你可能会定义一个删除连接如下:&&& &a href="/orders/delete/1024"&删除&/a&当然不会这么简单,一般都会在删除之前会提示用户一下,加上确认提示脚本:&&& &a href="/orders/delete/1024" onclick="return confirm('确实要删除吗?')"&删除&/a&(说明:我在这里只是示简单例下,添加确认删除还是建议使用 Unobtrusive JavaScript 方式,可以使用 jQuery。)很多开发人员以为这样就万事大吉了,有了确认提示,也不怕误删。但问题就恰恰出在这里,2005年时,谷歌发布了一款浏览器加速插件:Google Web Accelerator(以下简称 GWA),让这种问题严重的暴露了出来。GWA 通过多种技术来加速,其中一种就是页面预先加载:比如你在查看我这篇文章的时候,GWA 可能把我前一篇或其它文章预先在后台下载,这样你在点击时,就节省了时间,起到了加速的效果。GWA 的预先加载是根据当前页面中的链接来的,根据 HTTP 的协议,点击链接时使用 GET 方法获取信息,因些不会对服务器造成影响。因此 GWA 会放心的加载当前页面链接对应的网页。当然也可能会加速上面提到的订单删除链接,GWA 会无视你的确认删除脚本,直接从后台把 "/orders/delete/1024" 载入,也就意味着 1024 订单已经被删除了。GWA 发布后,很多网站出现了很多莫名其妙的问题,数据无故丢失,商品自动加入了用户的购物车,用户无端地被扣款…一时问题很严重,后来发现的原因的所在,就是网站开发者没有遵守 HTTP 约定,没有弄明白 GET 和 POST 的区别。可以查看以下文章深入了解这段历史:&&&/cantrell/archives/2005/06/what_have_we_le.html&&&/articles//google-web-accelerator-offers-web-developers-an-important-opportunity而如今,谷歌发布的 Chrome 浏览器,类似的加速功能集成了进去,你可以在 设置 - 显示高级设置 里面看得到:所以,对服务器有改变的一定要用 POST,GWA 和类似的插件不会提交 POST 表单加速的。删除、查看用户信息收费(比如人才网、婚恋网)、加入购物车等操作还是放在 POST 表单中用 Button 来吧。再回头读维基百科中对 Safe Methods 的说明,相信你会明白很多。注意:但也不是所有对服务器有改变的都要用 POST,比如你点击本文下面的 前一篇博文链接 ,我的文章访问量可能+1,对服务器有所改变,但这种改变是轻微的,影响不大(相对删除、扣款来说),可以放心的使用链接(GET 方式)。基它一些区别:
使用 GET 表单查询,查询结果页面可以收藏;POST 表单不行。
向服务器发送文件只能使用 POST 表单。能想到的大致这些吧。感言之前,我曾学习 ASP.NET 多年,但对 HTTP 几乎一无所知,WebForm 封装了一切:
不用去考虑 POST、GET,只需知道 Postback;
不用多考虑值来回传递,因为有 ViewState;
不用关心 Html,因为有服务端控件。更悲哀的是,我有很长一段时间都认为一个页面上只能有一个 Form。后来做了一段时间 WinForm 后,开始学习 ASP.NET MVC,开始逐步了解 Html、Http 等等,也开始知道了 Post-Redirect-Get 模式等等。看到很多人浅浅讨论 GET 和 POST,感到很无奈,WebForm 误人啊。要想进步,还是学学 ASP.NET MVC 吧!&(本人知识有限,如文中有错误或不妥之处,请指正,非常感谢!)MangoDB&介绍及&简单语法&(转)
是一个基于分布式
文件存储的数据库,介于关系数据库和非关系数据库之间,是非关系数据库当中功能最丰富,最像关系数据库的。MongoDB最大的特点是他支持的查询语言非
常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。MongoDB支持
RUBY,PYTHON,JAVA,C++,PHP,C#等多种语言。
MongoDB是高性能开源文档数据库,也是目前最受关注的NoSQL技术之一,以敏捷、可扩展和对企业应用友好(支持事务,一致性和数据完整性保
证,有大企业应用案例)而著称。有人甚至认为LAMP中的M应该用MongoDB取代MySQL,其火热程度可见一斑。使用MongoDB的公司包括
Foursquare, Craiglist,
迪士尼,SAP,Intuit,EA等,国内淘宝、大众点评、视觉中国等公司有应用。
基本查询:
构造查询数据。
db.test.findOne()
"_id" : ObjectId("4fd58ecbb9ac507e96276f1a"),
"name" : "stephen",
"age" : 35,
"genda" : "male",
"email" : ""
--多条件查询。下面的示例等同于SQL语句的where name
= "stephen" and age = 35
db.test.find({"name":"stephen","age":35})
&&& { "_id" :
ObjectId("4fd58ecbb9ac507e96276f1a"), "name" : "stephen", "age" :
35, "genda" : "male", "email" : "" }
--返回指定的文档键值对。下面的示例将只是返回name和age键值对。
db.test.find({}, {"name":1,"age":1})
&&&{ "_id" :
ObjectId("4fd58ecbb9ac507e96276f1a"), "name" : "stephen", "age" :
--指定不返回的文档键值对。下面的示例将返回除name之外的所有键值对。
db.test.find({}, {"name":0})
&&& { "_id" :
ObjectId("4fd58ecbb9ac507e96276f1a"), "age" : 35, "genda" : "male",
"email" : "" }
查询条件:
MongoDB提供了一组比较操作符:$lt/$lte/$gt/$gte/$ne,依次等价于&/&=/&/&=/!=。
--下面的示例返回符合条件age &= 18
&& age &= 40的文档。
db.test.find({"age":{"$gte":18, "$lte":40}})
&&& { "_id" :
ObjectId("4fd58ecbb9ac507e96276f1a"), "name" : "stephen", "age" :
35,"genda" : "male", "email" : "" }
--下面的示例返回条件符合name !=
"stephen1"
db.test.find({"name":{"$ne":"stephen1"}})
&&& { "_id" :
ObjectId("4fd58ecbb9ac507e96276f1a"), "name" : "stephen", "age" :
35,"genda" : "male", "email" : "" }
--$in等同于SQL中的in,下面的示例等同于SQL中的in
("stephen","stephen1")
db.test.find({"name":{"$in":["stephen","stephen1"]}})
&&& { "_id" :
ObjectId("4fd58ecbb9ac507e96276f1a"), "name" : "stephen", "age" :
35,"genda" : "male", "email" : ""
--和SQL不同的是,MongoDB的in
list中的数据可以是不同类型。这种情况可用于不同类型的别名场景。
db.test.find({"name":{"$in":["stephen",123]}})
&&& { "_id" :
ObjectId("4fd58ecbb9ac507e96276f1a"), "name" : "stephen", "age" :
35,"genda" : "male", "email" : ""
--$nin等同于SQL中的not
in,同时也是$in的取反。如:
db.test.find({"name":{"$nin":["stephen2","stephen1"]}})
&&& { "_id" :
ObjectId("4fd58ecbb9ac507e96276f1a"), "name" : "stephen", "age" :
35,"genda" : "male", "email" : "" }
--$or等同于SQL中的or,$or所针对的条件被放到一个数组中,每个数组元素表示or的一个条件。
--下面的示例等同于name = "stephen1" or
db.test.find({"$or": [{"name":"stephen1"}, {"age":35}]})
&&& { "_id" :
ObjectId("4fd58ecbb9ac507e96276f1a"), "name" : "stephen", "age" :
35,"genda" : "male", "email" : ""
--下面的示例演示了如何混合使用$or和$in。
db.test.find({"$or": [{"name":{"$in":["stephen","stephen1"]}},
{"age":36}]})
&&& { "_id" :
ObjectId("4fd58ecbb9ac507e96276f1a"), "name" : "stephen", "age" :
35,"genda" : "male", "email" : ""
--$not表示取反,等同于SQL中的not。
db.test.find({"name": {"$not":
{"$in":["stephen2","stephen1"]}}})
&&& { "_id" :
ObjectId("4fd58ecbb9ac507e96276f1a"), "name" : "stephen", "age" :
35,"genda" : "male", "email" : "" }
null数据类型的查询:
--在进行值为null数据的查询时,所有值为null,以及不包含指定键的文档均会被检索出来。
db.test.find({"x":null})
&&& { "_id" :
ObjectId("4fd59d30b9ac507e96276f1b"), "x" : null }
&&& { "_id" :
ObjectId("4fd59d49b9ac507e96276f1c"), "y" : 1 }
--需要将null作为数组中的一个元素进行相等性判断,即便这个数组中只有一个元素。
--再有就是通过$exists判断指定键是否存在。
db.test.find({"x": {"$in": [null], "$exists":true}})
&&& { "_id" :
ObjectId("4fd59d30b9ac507e96276f1b"), "x" : null }
正则查询:
--MongoDB中使用了Perl规则的正则语法。如:
db.test.find()
&&& { "_id" :
ObjectId("4fd59ed7b9ac507e96276f1d"), "name" : "stephen" }
&&& { "_id" :
ObjectId("4fd59edbb9ac507e96276f1e"), "name" : "stephen1" }
--i表示忽略大小写
db.test.find({"name":/stephen?/i})
&&& { "_id" :
ObjectId("4fd59ed7b9ac507e96276f1d"), "name" : "stephen" }
&&& { "_id" :
ObjectId("4fd59edbb9ac507e96276f1e"), "name" : "stephen1"
数组数据查询:
--基于数组的查找。
db.test.find()
&&& { "_id" :
ObjectId("4fd5a177b9ac507e96276f1f"), "fruit" : [ "apple",
"banana", "peach" ] }
&&& { "_id" :
ObjectId("4fd5a18cb9ac507e96276f20"), "fruit" : [ "apple",
"kumquat","orange" ] }
&&& { "_id" :
ObjectId("4fd5a1f0b9ac507e96276f21"), "fruit" : [ "cherry",
"banana","apple" ] }
--数组中所有包含banana的文档都会被检索出来。
db.test.find({"fruit":"banana"})
&&& { "_id" :
ObjectId("4fd5a177b9ac507e96276f1f"), "fruit" : [ "apple",
"banana", "peach" ] }
&&& { "_id" :
ObjectId("4fd5a1f0b9ac507e96276f21"), "fruit" : [ "cherry",
"banana","apple" ] }
&&& --检索数组中需要包含多个元素的情况,这里使用$all。下面的示例中,数组中必须同时包含apple和banana,但是他们的顺序无关紧要。
db.test.find({"fruit": {"$all": ["banana","apple"]}})
&&& { "_id" :
ObjectId("4fd5a177b9ac507e96276f1f"), "fruit" : [ "apple",
"banana", "peach" ] }
&&& { "_id" :
ObjectId("4fd5a1f0b9ac507e96276f21"), "fruit" : [ "cherry",
"banana", "apple" ] }&
--下面的示例表示精确匹配,即被检索出来的文档,fruit值中的数组数据必须和查询条件完全匹配,即不能多,也不能少,顺序也必须保持一致。
db.test.find({"fruit":["apple","banana","peach"]})
&&& { "_id" :
ObjectId("4fd5a177b9ac507e96276f1f"), "fruit" : [ "apple",
"banana", peach" ] }&
--下面的示例将匹配数组中指定下标元素的值。数组的起始下标是0。
db.test.find({"fruit.2":"peach"})
&&& { "_id" :
ObjectId("4fd5a177b9ac507e96276f1f"), "fruit" : [ "apple",
"banana", peach" ] }&
&&& --可以通过$size获取数组的长度,但是$size不能和比较操作符联合使用。
db.test.find({"fruit": {$size : 3}})
&&& { "_id" :
ObjectId("4fd5a177b9ac507e96276f1f"), "fruit" : [ "apple",
"banana", "peach" ] }
&&& { "_id" :
ObjectId("4fd5a18cb9ac507e96276f20"), "fruit" : [ "apple",
"kumquat","orange" ] }
&&& { "_id" :
ObjectId("4fd5a1f0b9ac507e96276f21"), "fruit" : [ "cherry",
"banana","apple" ] }&
--如果需要检索size &
n的结果,不能直接使用$size,只能是添加一个额外的键表示数据中的元素数据,在操作数据中的元素时,需要同时更新size键的值。
--为后面的实验构造数据。
db.test.update({}, {"$set": {"size":3}},false,true)
db.test.find()
&&& { "_id" :
ObjectId("4fd5a18cb9ac507e96276f20"), "fruit" : [ "apple",
"kumquat", "orange" ], "size" : 3 }
&&& { "_id" :
ObjectId("4fd5a1f0b9ac507e96276f21"), "fruit" : [ "cherry",
"banana", "apple" ], "size" : 3 }&
--每次添加一个新元素,都要原子性的自增size一次。
test.update({},{"$push":
{"fruit":"strawberry"},"$inc":{"size":1}},false,true)
db.test.find()
&&& { "_id" :
ObjectId("4fd5a18cb9ac507e96276f20"), "fruit" : [ "apple",
"kumquat", "orange", "strawberry" ], "size" : 4 }
&&& { "_id" :
ObjectId("4fd5a1f0b9ac507e96276f21"), "fruit" : [ "cherry",
"banana", "apple", "strawberry" ], "size" : 4 }
--通过$slice返回数组中的部分数据。"$slice":2表示数组中的前两个元素。
db.test.find({},{"fruit": {"$slice":2}, "size":0})
&&& { "_id" :
ObjectId("4fd5a18cb9ac507e96276f20"), "fruit" : [ "apple",
"kumquat" ]}
&&& { "_id" :
ObjectId("4fd5a1f0b9ac507e96276f21"), "fruit" : [ "cherry",
"banana" ]}&
&&& --通过$slice返回数组中的部分数据。"$slice":-2表示数组中的后两个元素。
db.test.find({},{"fruit": {"$slice":-2}, "size":0})
&&& { "_id" :
ObjectId("4fd5a18cb9ac507e96276f20"), "fruit" : [ "orange",
"strawberry" ] }
&&& { "_id" :
ObjectId("4fd5a1f0b9ac507e96276f21"), "fruit" : [ "apple",
"strawberry" ] }
--$slice :
[2,1],表示从第二个2元素开始取1个,如果获取数量大于2后面的元素数量,则取后面的全部数据。
db.test.find({},{"fruit": {"$slice":[2,1]}, "size":0})
&&& { "_id" :
ObjectId("4fd5a18cb9ac507e96276f20"), "fruit" : [ "orange" ]
&&& { "_id" :
ObjectId("4fd5a1f0b9ac507e96276f21"), "fruit" : [ "apple" ] }
内嵌文档查询:
--为后面的示例构造测试数据。
db.test.find()
&&& { "_id" :
ObjectId("4fd5ada3b9ac507e96276f22"), "name" : { "first" : "Joe",
"last" : "He" }, "age" : 45 }
--当嵌入式文档为数组时,需要$elemMatch操作符来帮助定位某一个元素匹配的情况,否则嵌入式文件将进行全部的匹配。
--即检索时需要将所有元素都列出来作为查询条件方可。
db.test.findOne()
"_id" : ObjectId("4fd5af76b9ac507e96276f23"),
"comments" : [
&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&
"author" : "joe",
&&&&&&&&&&&&&&&&&&&&&&&&
"score" : 3
&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&
"author" : "mary",
&&&&&&&&&&&&&&&&&&&&&&&&
"score" : 6
&&&&&&&&&&&&&&&&
db.test.find({"comments": {"$elemMatch":
{"author":"joe","score":{"$gte":3}}}}
&&& { "_id" :
ObjectId("4fd5af76b9ac507e96276f23"), "comments" : [ { "author" :
"joe", "score" : 3 }, { "author" : "mary", "score" : 6 } ] }
数据库使用游标来返回find()的执行结果,客户端对游标可以进行有效的控制,如:限定结果集的数量、跳过部分结果、基于任意键的任意方向的排序等。
下面的例子将用于准备测试数据。
db.testtable.remove()
&&& & for (i
= 0; i & 10; ++i) {
db.testtable.insert({x:i})
我们可以通过cursor提供的hasNext()方法判断是否还有未读取的数据,再通过next()方法读取结果集中的下一个文档。如:
&&& & var c =
db.testtable.find()
&&& & while
(c.hasNext()) {
print(c.next().x)
当调用find()的时候,shell并不立即查询数据库,而是等待真正开始要求获得结果的时候才发送查询,这样在执行之前可以给查询附加额外的选项。几乎所有的游标方法都返回本身,因此可以像下面这样将游标的方法链式组合起来。如:
&&& & var c1
= db.testtable.find().sort({"x":1}).limit(1).skip(4);
&&& & var c2
= db.testtable.find().limit(1).sort({"x":1}).skip(4);
&&& & var c3
= db.testtable.find().skip(4).limit(1).sort({"x":1});
此时,查询并未执行,所有这些函数都是在构造查询,当执行下面的语句时,查询将被真正执行,
c.hasNext()
查询被发送到服务器,MongoDB服务器每次将返回一批数据,当本批被全部迭代后再从服务器读取下一批数据,直至查询结果需要的数据被全部迭代。
对于上面的示例,limit(1)表示输出结果仅为一个,如果小于1,则不输出,即limit(n)函数限定的是最多输出结果。skip(4)表示跳过查
询结果中的前4个文档,如果结果小于4,则不会返回任何文档。sort({"x":1})用于设定排序条件,即按照x键以升序(1)的方式排序,如果需要
降序排序可以改为:sort({"x":-1})。sort也可以支持多键排序,如:sort({username:1,
age:-1})即先按照username进行升序排序,如果username的值相同,再以age键进行降序排序。这里需要指出的是,如果skip过多
的文档,将会导致性能问题。
原文地址:
/stephen-liu74/archive//2553803.html
http://blog.csdn.net/yczz/article/details/5978800
/think_fish/p/3422307.html
已投稿到:
以上网友发言只代表其个人观点,不代表新浪网的观点或立场。482,381 一月 独立访问用户
您目前处于:
虚拟研讨会:如何设计好的RESTful API?
虚拟研讨会:如何设计好的RESTful API?
相关厂商内容
相关赞助商
全球软件开发大会,4月23-25日,北京,!
什么是好的RESTful API?相信每个人都有自己的评判标准。那么,您认为一个好的RESTful API应该具有哪些特征呢?
安全是恒久的话题,对于基于WSDL和SOAP的Web Service,我们有WS-Security这样的安全规范来指导实现认证、授权、身份管理等安全需求。那么,RESTful API有无成熟可用规范或实现框架呢?如何保证RESTful API的安全性呢?
如何对RESTful API进行版本控制,请分享您认为实用的做法?
HTTP1.1规范中给出的动词对于设计RESTful API够用吗?您在实际项目中会扩展自己的动词吗?在什么情况下需要扩展?
今年5月份发布的JAX-RS 2.0规范对于RSTfulAPI的设计最有价值的特性是哪个(些)? 它(们)用于解决什么问题?
能否为InfoQ的读者们推荐一款实用的RESTful API开发框架,并说明您的推介理由。
HTTP2.0规范正在制定当中,您对它的期待是什么?
InfoQ:什么是好的RESTful API?相信每个人都有自己的评判标准。那么,您认为一个好的RESTful API应该具有哪些特征呢?
李锟:一个好的RESTful API,应该具备以下特征:
这个API应该是对浏览器友好的,能够很好地融入Web,而不是与Web格格不入。
浏览器是最常见和最通用的REST客户端。好的RESTful API应该能够使用浏览器+HTML完成所有的测试(不需要使用编程语言)。这样的API还可以很方便地使用各种自动化的Web功能测试、性能测试工具来做测试。Web前端应用(基于浏览器的RIA应用、移动App等等)也可以很方便地将多个RESTful API的功能组合起来,建造Mashup类的应用。
这个API中所包含的资源和对于资源的操作,应该是直观和容易理解的,并且符合HTTP协议的要求。
REST开发又被称作&面向资源的开发&,这说明对于资源的抽象,是设计RESTful API的核心内容。RESTful API建模的过程与面向对象建模类似,是以名词为核心的。这些名词就是资源,任何可命名的抽象概念都可以定义为一个资源。而HTTP协议并不是一种传输协议,它实际提供了一个操作资源的统一接口。对于资源的任何操作,都应该映射到HTTP的几个有限的方法(常用的有GET/POST/PUT/DELETE四个方法,还有不常用的PATCH/HEAD/OPTIONS方法)上面。所以RESTful API建模的过程,可以看作是具有统一接口约束的面向对象建模过程。
按照HTTP协议的规定,GET方法是安全且幂等的,POST方法是既不安全也不幂等的(可以用来作为所有写操作的通配方法),PUT、DELETE方法都是不安全但幂等的。将对资源的操作合理映射到这四个方法上面,既不过度使用某个方法(例如过度使用GET方法或POST方法),也不添加过多的操作以至于HTTP的四个方法不够用。
如果发现资源上的操作过多,以至于HTTP的方法不够用,应该考虑设计出更多的资源。设计出更多资源(以及相应的URI)对于RESTful API来说并没有什么害处。
这个API应该是松耦合的。
RESTful API的设计包括了三个循序渐进、由低到高的层次:资源抽象、统一接口、超文本驱动。正是这三个层次确保了RESTful API的松耦合性。
当设计面向互联网的API时,松耦合变成了一种&必须有&的强需求。紧耦合的API非常脆弱,一旦公布出去,服务器端和客户端都无法持续进化。尤其是服务器端,公布出去的接口根本不敢改,改了之后,几乎所有客户端应用立即无法正常工作。REST这种架构风格就是紧耦合API的解毒剂,这个话题可以谈的很深,这里就不展开了。感兴趣的读者可以参考《REST实战》。
这个API中所使用的表述格式应该是常见的通用格式
在RESTful API中,对于资源的操作,是通过在服务器端-客户端之间传递资源的表述来间接完成的。资源的表述可以有很多种格式,并且在响应和请求中的资源表述格式也会有所不同。GET/POST响应中的资源表述格式,常见的有HTML、XML、JSON;POST/PUT请求中的资源表述格式,常见的有标准的HTML表单参数、XML、JSON。
这些常见表述格式,处理起来非常容易,有大量的框架和库提供支持。所以除非有很合理的要求,通常不需要使用自定义的私有格式。
使用HTTP响应状态代码来表达各种出错情况
HTTP响应状态代码,是HTTP协议这个统一接口中用来表达出错情况的标准机制。响应状态代码分成两部分:status code和reason phase。两部分都是可定制的,也可以使用标准的status code,只定制reason phase。
如果一个所谓的&RESTful API&对于任何请求都返回200 OK响应,在响应的消息体中返回出错情况信息,这种做法显然不符合&确保操作语义的可见性&这个REST架构风格的基本要求。
这个API应该对于HTTP缓存是友好的
充分利用好HTTP缓存是RESTful API可伸缩性的根本。HTTP协议是一个分层的架构,从两端的user agent到origin server之间,可以插入很多中间组件。而在整个HTTP通信链条的很多位置,都可以设置缓存。HTTP协议内建有很好的缓存机制,可以分成过期模型和验证模型两套缓存机制。如果API设计者完全没有考虑过如何利用HTTP缓存,那么这个API的可伸缩性会有很多问题。
李建业:首先说明一下,对REST这个概念,我一般把它理解为REST风格的架构,但是现在实践中最为广泛认知的是HTTP,而它是REST的一个实现,所以RESTful API也可以不太严格的指基于HTTP的API&&当然,即使是不严格的时候,API本身也应该力求遵循REST架构风格。
我认为,一个RESTful API最重要的一点应该是&&&尽可能少的先验信息&,这一条也同时是我判断一个好的RESTful API的标准。
比如HTTP动词,在实践中,大家可能会常常纠结于有效利用 HTTP 动词,但这却并不是特别重要的事情&&除非你理解这么做的价值。HTTP 动词最重要的地方在于它是标准阐明了的行为,也就是说,如果我们的&客户端&遵循约定,那么就不必要发明新的动词,也就不必增加&先验信息&;但是,所谓&先验信息&,针对的是客户端&&对API来说就是调用者,对于一些企业内部系统,或者一些传统系统,由于&资源&很稳定,对资源的操作也很稳定,这些系统的&调用客户端&不是浏览器而是另一个系统,此时如果强制对应到HTTP动词,反而会变成额外的&先验信息&,这时我就不会太拘泥HTTP动词,自己制定一套动词放在参数中也可以接受&&只要动词不变化,这个系统依然是REST风格的。
再比如Response里面的Content-Type,这个有时会被新手忽略,但这其实很重要,因为一般涉及到系统间协同的API,往往不会使用普通的文本,比较常见的是使用json表达复杂结构,而这与通常的缺省理解不同(缺省一般会认为是text/plain和text/html),所以如果在API中忘记用Content-Type进行区分的话,后续对多种类型的客户端接入的支持就会变成陷阱(我们多次遇到过这个问题)。而如果一开始就检查是否增加先验知识(缺省Content-Type为plain或者允许指定Content-Type),那这一困难就可以避免了。
丁雪丰:首先,应该正确地使用HTTP的统一接口,比如HTTP的动词,如果不分青红皂白清一色POST那显然还有改进的余地;
其次,资源有合适的粒度,可以从三个方面来评判资源的粒度是否合理&&网络的效率、表述的大小以及客户端使用时的易用程度;
最后,是表述的设计,除了表述的正文内容,还有其中的URI和链接,这些都是评判一个RESTful API好坏的标准。
马钧:在我看来,一个好的API标准,就是能尽量利用到HTTP协议的特性,将HTTP当成一种转移协议,而不是传输协议。包括但不限于:利用HTTP的各种动词来明确操作;包含有内容协商,可以根据请求头提供的参数选择一个资源最合适的媒体类型、语言、字符集和编码的表现;使用不同的返回代码来描述各种状态。但实际上见到过的很多声称RESTful API,包括国内的和国外的,能符合这些条件的并不多。提供的API是我见到过的较为不错的RESTful API,可以作为范例参考。
InfoQ:安全是恒久的话题,对于基于WSDL和SOAP的Web Service,我们有WS-Security这样的安全规范来指导实现认证、授权、身份管理等安全需求。那么,RESTful API有无成熟可用规范或实现框架呢?如何保证RESTful API的安全性呢?
李锟:保证RESTful API的安全性,主要包括三大方面:
a) 对客户端做身份认证
b) 对敏感的数据做加密,并且防止篡改
c) 身份认证之后的授权
对客户端做身份认证,有几种常见的做法:
在请求中加签名参数
为每个接入方分配一个密钥,并且规定一种签名的计算方法。要求接入方的请求中必须加上签名参数。这个做法是最简单的,但是需要确保接入方密钥的安全保存,另外还要注意防范replay攻击。其优点是容易理解与实现,缺点是需要承担安全保存密钥和定期更新密钥的负担,而且不够灵活,更新密钥和升级签名算法很困难。
使用标准的HTTP身份认证机制
HTTP Basic身份认证安全性较低,必须与HTTPS配合使用。HTTP Digest身份认证可以单独使用,具备中等程度的安全性。
HTTP Digest身份认证机制还支持插入用户自定义的加密算法,这样可以进一步提高API的安全性。不过插入自定义加密算法在面向互联网的API中用的不是很多。
这个做法需要确保接入方&安全域-用户名-密码&三元组信息的安全保存,另外还要注意防范replay攻击。
优点:基于标准,得到了广泛的支持(大量HTTP服务器端、客户端库)。在服务器端做HTTP身份认证的职责可以由Web Server(例如Nginx)、App Server(例如Tomcat)、安全框架(例如Spring Security)来承担,对应用开发者来说是透明的。HTTP身份认证机制(RFC 2617)非常好地体现了&分离关注点&的设计原则,而且保持了操作语义的可见性。
缺点:这类基于简单用户名+密码机制的安全性不可能高于基于非对称密钥的机制(例如数字证书)。
使用OAuth协议做身份认证
OAuth协议适用于为外部应用授权访问本站资源的情况。其中的加密机制与HTTP Digest身份认证相比,安全性更高。需要注意,OAuth身份认证与HTTP Digest身份认证之间并不是相互取代的关系,它们的适用场景是不同的。OAuth协议更适合于为面向最终用户维度的API提供授权,例如获取隶属于用户的微博信息等等。如果API并不是面向最终用户维度的,例如像七牛云存储这样的存储服务,这并非是OAuth协议的典型适用场景。
对敏感的数据做加密,并且防止篡改,常见的做法有:
部署SSL基础设施(即HTTPS),敏感数据的传输全部基于SSL。
仅对部分敏感数据做加密(例如预付费卡的卡号+密码),并加入某种随机数作为加密盐,以防范数据被篡改。
身份认证之后的授权,主要是由应用来控制。通常应该实现某种基于角色+用户组的授权机制,这方面的框架有不少(例如Spring Security),不过大多数开发团队还是喜欢自己来实现相关功能。
李建业:我不认为安全是RESTful API需要考虑的问题,事实上我觉得这是两个正交的问题。当然,如果使用RESTful API来提供认证、授权和身份管理,那也算是双方有关系,但是这和其它风格的API设计所要考虑的问题似乎没什么区别,不值得特别注意。
但是在具体设计层面,这两者的&正交点&上似乎确实有些问题,因为REST是一个推崇状态无关原则的架构风格,而认证和授权通常基于第三方解决方案,所以往往会出现违背有状态约束的问题,这个地方我也没有特别的想法,当然这个困难和原问题关系不大。
至于WS-族的协议,我不太了解,不太能参与讨论。
丁雪丰:对于RESTful API,常见的安全措施都是可以继续使用的。例如,为了防篡改,可以对全部参数进行签名;为了防范重放攻击可以在请求中增加一次性的Token,或者短时间内有效的Token;对内容加密可以实现数据防泄露&&;对于DDoS攻击,各种HTTP流量清洗策略,都可以继续发挥作用,因为这就是基本的HTTP请求。
在授权和认证方面,OAuth 2.0已经基本成熟了,并且得到了广泛地应用。如果可以,接入第三方账户体系是个不错的选择,比如Google和Facebook的,国内的当然也有几个候选。
马钧:个人认为RESTful的安全性分为几个层次,在安全要求较高的场合,可以通过HTTPs这样的加密协议来保证网络层的安全,应用层的安全可以通过OAuth实现认证,而对于资源的访问授权,则只能依靠应用程序来实现了。
InfoQ:如何对RESTful API进行版本控制,请分享您认为实用的做法?
李锟:一个比较简单实用的做法是直接在URI中插入版本号,这样做允许多个版本的API并行运行。
另一个做法是在HTTP请求中加入自定义头信息,标明使用的版本号。不过这个做法其实对浏览器不够友好,简单地使用浏览器+HTML无法测试。
李建业:目前比较好的方式还是在uri设计中添加版本信息,其它方法都不如这个实用。
丁雪丰:个人认为最好的版本化,就是没有明显的版本。在对已发布的服务进行变更时,要尽量做到兼容,其中包括URI、链接和各种不同的表述的兼容,最关键的就是在扩展时不能破坏现有的客户端。例如,要变更一个参数,可以选择同时兼容新旧两种输入,或者保持老参数不动,提供一个新的参数,在文档中必须做出说明,不推荐新用户再继续使用之前的参数。
如果必须要进行不兼容的变更,那么可以选择标记不同的版本号,这时可以选择在路径或参数中增加版本信息。也有做法是增加HTTP标头,只是在调用时会稍有不便,推荐前两种方法。
马钧:RESTfulAPI的版本升级,尽量兼容之前的版本,保证原有的API都能正常工作,可以通过HTTP 301转跳到新的资源。另外一种实用的做法就是在url中保留版本号,同时提供多个版本供客户端使用,如
InfoQ:HTTP1.1规范中给出的动词对于设计RESTful API够用吗?您在实际项目中会扩展自己的动词吗?在什么情况下需要扩展?
李锟:这个问题取决于设计者如何看待和设计资源。如果资源抽象做的很好,对于某个资源的任何操作,通常都能够映射到CRUD四个类别中。CRUD四个类别对于操作资源来说,绝大多数情况下是完备的。HTTP的GET/POST/PUT/DELETE四个方法,对于CRUD四个类别的操作来说是足够的,映射关系是Create-POST/Retrieve-GET/Update-PUT/Delete-DELETE。
我们通常不会选择创建自己的动词,这样做对于客户端开发者来说,需要更多的学习成本。如果在资源上定义的操作过多,我们会选择拆分出更多的资源。
李建业:一般是够用的,有时一些&不够用&的场景是由于我们没有设计出合理的资源,比如批量操作。但是,正如之前所说的那样,对于某些内部的、传统的(因此模型稳定且已知)系统,API提供者和调用者会有自已的固定动词表,此时没必要拘泥。另外,我不建议扩展动词,一旦扩展了动词,其实已经破坏了我之前说的*&尽可能少的先验信息&*,那么,扩展动词和重新设计动词的成本差别不大。基于这个考虑,我建议尽可能保持动词不变,除非你想重新设计动词表。
丁雪丰:一般情况下,常用的HTTP动词是够用的,并没有出现一定要自己扩展动词的情况。其实,最常用的也就是GET、POST、DELETE和PUT,而HEAD、OPTIONS、TRACE则基本用不太到。如果出现一时找不到合适的动词,安全幂等的操作用GET,其他都可以用POST,在设计资源时稍加考虑即可。
马钧:在我的实际项目中,只用到了POST,PUT,DELETE,GET这四个动词。
InfoQ:今年5月份发布的JAX-RS 2.0规范对于RSTfulAPI的设计最有价值的特性是哪个(些)? 它(们)用于解决什么问题?
李锟:REST开发框架RESTEasy项目负责人Bill Burke,去年。
我同意Bill在文章中的观点,在JAX-RS 2.0增加的内容中,最重要的三部分为:
a) Client API&&用来规范化JAX-RS客户端的开发方式。
b) Server-side Asynchronous HTTP&&用来实现服务器端推送功能,而不需要依靠低效的轮询方式。
c) Filters and Interceptors&&用来分离关注点,将鉴权、日志等逻辑与业务逻辑分离开,更好地实现代码重用。
这三部分的内容对于开发者来说都很有用。遵循JAX-RS规范做开发,可以确保服务器端以及客户端代码的可移植性。
李建业:我个人关注异步API这部分,主要是因为流式服务将会越来越多,那将大量需要这类支持。
InfoQ:能否为InfoQ的读者推荐一款实用的RESTful API开发框架,并说明您的推介理由。
李锟:这个问题我就不详细回答了。不同的编程语言有不同的REST开发框架,对于REST的支持程度也不同。开发RESTful API的需求范围很广,可选择的开发框架的范围也很广。保持多样性是繁荣生态环境的基础。像Java就有支持JAX-RS规范的Jersey、RESTEasy、Restlet、Apache CXF,和不支持JAX-RS规范的Spring MVC等等很多框架。这些框架目前都做的不错。我对框架的选择没有倾向性。RESTful API设计的最佳实践应该是通用的,而不是必须依赖某种特定的开发框架。
李建业:不好意思,这个我不太重视,没法推荐,不过我可以解释一下为什么对RESTful API框架不感冒的原因。
REST作为一个架构风格,对我们的系统开发有很大影响,但是这些影响一般是针对架构(例如状态无关)或者设计(例如资源识别)上的,所以一旦涉及到具体实现,主要工作就基本结束了,此时开发框架能做的事也就只有简化编程了(相较而言,有的框架还能起到引导设计的作用),而由于RESTful会抽象动词,所以实现层面中和API规范相关的工作本来就不多,那么框架的价值就更小了。
当然,我们也不可能直接基于servlet/rakc/wsgi来开发,不过一般的编程语言都会提供一些简单的url route/match策略,我们使用这些就足够了。另外,有些框架能帮我们生成全部的动词支持,但这也未必是好事,我一般倾向于按需实现&&用到了再支持,这就更不需要太关注开发框架对RESTful的支持了。
丁雪丰:由于本人是Spring的拥护者,工作中也一直在使用Spring,所以在选择框架时会更多地倾向Spring MVC(并不是说别的框架不好,这里有些个人主观的成份)。如果一定要选择其他框架,也要选择能够方便与Spring集成的框架。如果在项目中已经使用了Spring,那么没有什么理由不选择Spring MVC,鉴于目前Spring在各种项目中的高出镜率,相信一般情况下都会选择Spring MVC。
REST的成熟度模型中,第三层就是HATEOAS,Spring目前还提供了Spring Hateoas子项目,对链接、资源等方面的支持都做了一定的增强。
马钧:我目前在实际项目中使用的是Spray,这是一个开源的 REST/HTTP 工具包和底层网络 IO 包,基于 Scala 和 Akka 构建。轻量级、异步、非堵塞、基于 actor 模式、模块化和可测试是Spray的特点。
InfoQ:HTTP2.0规范正在制定当中,您对它的期待是什么?
李锟:我的期待包括两个方面:应该做的和不应该做的。
HTTP/2.0规范应该做的:
与HTTP/1.1协议保持兼容。兼容的含义是说两者可以并存,客户端应用可以根据服务器端的能力,自由地选择使用HTTP/2.0还是HTTP/1.1,而且选择过程对应用来说是透明的。
改进HTTP协议(作为资源的统一接口)之中操作语义表达方式的语法,提高网络传输效率。
更好地模块化,这样HTTP/2.0协议的实现能够更好地模块化。应用程序可根据需要选择适当的模块,而不是要么全有、要么全无。
废弃掉HTTP/1.1协议中一些很少有人用到的部分,例如采用管道(pipelining)方式发送请求。
增加更多的动词,以适应除CRUD之外的其他场景。
HTTP/2.0规范不应该做的:
HTTP/2.0协议不应该把底层的数据加密机制(即SSL)作为必选项。
HTTP/2.0协议不应该背离REST架构风格的约束,尤其是要确保操作语义对于中间组件的可见性。
在上面这两个方面,Roy Fileidng曾经与SPDY协议设计者Mike Belshe发生过激烈争论,详情请看:
李建业:对此规范关注不多,不知道会不会有对于流的支持,目前我所知道的只有chunk方式进行简单的支持,但是真正的流需要区分数据通道和控制通道&&哪怕是逻辑上的区分,这样就直接对REST风格产生了很大冲击,考虑到流式服务在未来的发展潜力,我特别期待业界在这方面有所进展。
丁雪丰:HTTP 2.0很大程度上是借鉴了Google的SPDY,就我而言,首先,希望这个规范能做到与HTTP 1.1的兼容,使用者如果只认识1.1,那么2.0能优雅&降级&;其次,希望2.0能带来更好的性能,SPDY在这方面还是有所改进的,希望HTTP 2.0能再接再厉;最后,希望这个规范能在最终定稿时附带一个最佳实践,正确引导人们合理地使用HTTP 2.0。
马钧:没研究过,估计即使出来,1.1还有很长的生命周期,不会很快被取代。
关于专家们
李锟:从2003年开始尝试Ajax开发,意识到这种开发方式的优点后,次年(2004年)在知名技术论坛JavaEye上与技术爱好者深入讨论过相关问题。他也是轻量级Java开发框架的拥护者,曾参与JavaEye论坛组织的《J2EE Development without EJB》一书的翻译。在2007年Ruby on Rails 1.2版支持REST开发之后,他对搞清楚REST的来龙去脉产生了浓厚兴趣,同年组织翻译了Roy T. Fielding的博士论文《Architectural Styles and the Design of Network-based Software Architectures》(中文版名为《架构风格与基于网络的软件架构设计》)。在2011年又翻译了《REST实战》一书,目前仍然致力于在国内普及对于REST架构风格的深入理解,以及相关实战经验的交流。目前主要感兴趣的技术领域包括DDD、DSL、SOA、REST。
李建业:2002年毕业,之后一直从事软件开发,涉及办公自动化、电信网管/增值业务系统以及互联网;2009年12月加入淘宝的广告应用开发团队;从 2011年底开始,关注软件研发本身,涉及运维和测试相关工作,目前关注持续集成。李建业还经常受邀出席一些技术大会并发表主题演讲,比如InfoQ,Ruby大会等。
丁雪丰:一线攻城师一枚,InfoQ中文站小编,常年混迹于各种社区,业余时间写作翻译、汉化软件,《RESTfulWebServices Cookbook中文版》、《Spring攻略》等多部图书的译者。
马钧:全端程序员,15年以上多种程序编程经验,爱好开源软件,积极参加各类社区活动,《REST实战》等技术图书的译者。
告诉我们您的想法
允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p
当有人回复此评论时请E-mail通知我
Re: 分享的不错
Re: 分享的不错
Xiaoguang Huo
允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p
当有人回复此评论时请E-mail通知我
允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p
当有人回复此评论时请E-mail通知我
Cal Leeming
赞助商链接
InfoQ每周精要
通过个性化定制的新闻邮件、RSS Feeds和InfoQ业界邮件通知,保持您对感兴趣的社区内容的时刻关注。
<及所有内容,版权所有 &#169;
C4Media Inc.
服务器由 提供, 我们最信赖的ISP伙伴。
北京创新网媒广告有限公司
京ICP备号-7

我要回帖

更多关于 tomcat put delete 的文章

 

随机推荐