在本文中,我们将为您详细介绍理解RESTful架构的相关知识,并且为您解答关于restful架构的疑问,此外,我们还会提供一些关于Django——RESTful架构、Go语言中的实时通信和RESTfu
在本文中,我们将为您详细介绍理解RESTful架构的相关知识,并且为您解答关于restful 架构的疑问,此外,我们还会提供一些关于Django——RESTful架构、Go语言中的实时通信和RESTful架构、Nodejs RESTFul架构实践、Nodejs RESTFul架构实践之api篇的有用信息。
本文目录一览:- 理解RESTful架构(restful 架构)
- Django——RESTful架构
- Go语言中的实时通信和RESTful架构
- Nodejs RESTFul架构实践
- Nodejs RESTFul架构实践之api篇
理解RESTful架构(restful 架构)
1 什么是REST
REST全称是Representational State Transfer,中文意思是表述性状态转移。 它首次出现在2000年Roy Fielding的博士论文中,Roy Fielding是 HTTP 规范的主要编写者之一。 他在论文中提到:“我这篇文章的写作目的,就是想在符合架构原理的前提下,理解和评估以网络为基础的应用软件的架构设计,得到一个功能强、性能好、适宜通信的架构。REST指的是一组架构约束条件和原则。” 如果一个架构符合REST的约束条件和原则,我们就称它为RESTful架构。
REST本身并没有创造新的技术、组件或服务,而隐藏在RESTful背后的理念就是使用Web的现有特征和能力, 更好地使用现有Web标准中的一些准则和约束。虽然REST本身受Web技术的影响很深, 但是理论上REST架构风格并不是绑定在HTTP上,只不过目前HTTP是唯一与REST相关的实例。 所以我们这里描述的REST也是通过HTTP实现的REST。
2 理解RestFul
要理解RESTful架构,需要理解Representational State Transfer这个词组到底是什么意思,它的每一个词都有些什么涵义。 下面我们结合REST原则,围绕资源展开讨论,从资源的定义、获取、表述、关联、状态变迁等角度,列举一些关键概念并加以解释:
- 资源与URI
- 统一资源接口
- 资源的表述
- 资源的链接
- 状态的转移
2.1 资源与URI
REST全称是表述性状态转移,那究竟指的是什么的表述? 其实指的就是资源。任何事物,只要有被引用到的必要,它就是一个资源。资源可以是实体(例如手机号码),也可以只是一个抽象概念(例如价值) 。下面是一些资源的例子:
- 某用户的手机号码
- 某用户的个人信息
- 最多用户订购的GPRS套餐
- 两个产品之间的依赖关系
- 某用户可以办理的优惠套餐
- 某手机号码的潜在价值
要让一个资源可以被识别,需要有个唯一标识,在Web中这个唯一标识就是URI(Uniform Resource Identifier)。 URI既可以看成是资源的地址,也可以看成是资源的名称。如果某些信息没有使用URI来表示,那它就不能算是一个资源, 只能算是资源的一些信息而已。URI的设计应该遵循可寻址性原则,具有自描述性,需要在形式上给人以直觉上的关联。这里以github网站为例,给出一些还算不错的URI:
- https://github.com/git
- https://github.com/git/git
- https://github.com/git/git/blob/master/block-sha1/sha1.h
- https://github.com/git/git/commit/e3af72cdafab5993d18fae056f87e1d675913d08
- https://github.com/git/git/pulls
- https://github.com/git/git/pulls?state=closed
- https://github.com/git/git/compare/master…next
下面让我们来看看URI设计上的一些技巧:
- 使用_或-来让URI可读性更好
曾经Web上的URI都是冰冷的数字或者无意义的字符串,但现在越来越多的网站使用_或-来分隔一些单词,让URI看上去更为人性化。 例如国内比较出名的开源中国社区,它上面的新闻地址就采用这种风格, 如http://www.oschina.net/news/38119/oschina-translate-reward-plan。
- 使用/来表示资源的层级关系
例如上述/git/git/commit/e3af72cdafab5993d18fae056f87e1d675913d08就表示了一个多级的资源, 指的是git用户的git项目的某次提交记录,又例如/orders/2012/10可以用来表示2012年10月的订单记录。
- 使用?用来过滤资源
很多人只是把?简单的当做是参数的传递,很容易造成URI过于复杂、难以理解。可以把?用于对资源的过滤, 例如/git/git/pulls用来表示git项目的所有推入请求,而/pulls?state=closed用来表示git项目中已经关闭的推入请求, 这种URL通常对应的是一些特定条件的查询结果或算法运算结果。
- ,或;可以用来表示同级资源的关系
有时候我们需要表示同级资源的关系时,可以使用,或;来进行分割。例如哪天github可以比较某个文件在随意两次提交记录之间的差异, 或许可以使用/git/git /block-sha1/sha1.h/compare/e3af72cdafab5993d18fae056f87e1d675913d08; bd63e61bdf38e872d5215c07b264dcc16e4febca作为URI。 不过,现在github是使用…来做这个事情的,例如/git/git/compare/master…next。
2.2 统一资源接口
RESTFul架构应该遵循统一接口原则,统一接口包含了一组受限的预定义的操作,不论什么样的资源,都是通过使用相同的接口进行资源的访问。接口应该使用标准的HTTP方法如GET,PUT和POST,并遵循这些方法的语义。
如果按照HTTP方法的语义来暴露资源,那么接口将会拥有安全性和幂等性的特性,例如GET和HEAD请求都是安全的, 无论请求多少次,都不会改变服务器状态。而GET、HEAD、PUT和DELETE请求都是幂等的,无论对资源操作多少次, 结果总是一样的,后面的请求并不会产生比第一次更多的影响。
下面列出了GET,DELETE,PUT和POST的典型用法:
GET
- 安全且幂等
- 获取表示
- 变更时获取表示(缓存)
- 200(OK) – 表示已在响应中发出
- 204(无内容) – 资源有空表示
- 301(Moved Permanently) – 资源的URI已被更新
- 303(See Other) – 其他(如,负载均衡)
- 304(not modified)- 资源未更改(缓存)
- 400 (bad request)- 指代坏请求(如,参数错误)
- 404 (not found)- 资源不存在
- 406 (not acceptable)- 服务端不支持所需表示
- 500 (internal server error)- 通用错误响应
- 503 (Service Unavailable)- 服务端当前无法处理请求
POST
- 不安全且不幂等
- 使用服务端管理的(自动产生)的实例号创建资源
- 创建子资源
- 部分更新资源
- 如果没有被修改,则不过更新资源(乐观锁)
- 200(OK)- 如果现有资源已被更改
- 201(created)- 如果新资源被创建
- 202(accepted)- 已接受处理请求但尚未完成(异步处理)
- 301(Moved Permanently)- 资源的URI被更新
- 303(See Other)- 其他(如,负载均衡)
- 400(bad request)- 指代坏请求
- 404 (not found)- 资源不存在
- 406 (not acceptable)- 服务端不支持所需表示
- 409 (conflict)- 通用冲突
- 412 (Precondition Failed)- 前置条件失败(如执行条件更新时的冲突)
- 415 (unsupported media type)- 接受到的表示不受支持
- 500 (internal server error)- 通用错误响应
- 503 (Service Unavailable)- 服务当前无法处理请求
PUT
- 不安全但幂等
- 用客户端管理的实例号创建一个资源
- 通过替换的方式更新资源
- 如果未被修改,则更新资源(乐观锁)
- 200 (OK)- 如果已存在资源被更改
- 201 (created)- 如果新资源被创建
- 301(Moved Permanently)- 资源的URI已更改
- 303 (See Other)- 其他(如,负载均衡)
- 400 (bad request)- 指代坏请求
- 404 (not found)- 资源不存在
- 406 (not acceptable)- 服务端不支持所需表示
- 409 (conflict)- 通用冲突
- 412 (Precondition Failed)- 前置条件失败(如执行条件更新时的冲突)
- 415 (unsupported media type)- 接受到的表示不受支持
- 500 (internal server error)- 通用错误响应
- 503 (Service Unavailable)- 服务当前无法处理请求
DELETE
- 不安全但幂等
- 删除资源
- 200 (OK)- 资源已被删除
- 301 (Moved Permanently)- 资源的URI已更改
- 303 (See Other)- 其他,如负载均衡
- 400 (bad request)- 指代坏请求
- 404 (not found)- 资源不存在
- 409 (conflict)- 通用冲突
- 500 (internal server error)- 通用错误响应
- 503 (Service Unavailable)- 服务端当前无法处理请求
下面我们来看一些实践中常见的问题:
- POST和PUT用于创建资源时有什么区别?
POST和PUT在创建资源的区别在于,所创建的资源的名称(URI)是否由客户端决定。 例如为我的博文增加一个java的分类,生成的路径就是分类名/categories/java,那么就可以采用PUT方法。 不过很多人直接把POST、GET、PUT、DELETE直接对应上CRUD,例如在一个典型的rails实现的RESTFul应用中就是这么做的。 我认为,这是因为rails默认使用服务端生成的ID作为URI的缘故,而不少人就是通过rails实践REST的,所以很容易造成这种误解。
- 客户端不一定都支持这些HTTP方法吧?
的确有这种情况,特别是一些比较古老的基于浏览器的客户端,只能支持GET和POST两种方法。 在实践上,客户端和服务端都可能需要做一些妥协。例如rails框架就支持通过隐藏参数_method=DELETE来传递真实的请求方法, 而像Backbone这样的客户端MVC框架则允许传递_method传输和设置X-HTTP-Method-Override头来规避这个问题。
- 统一接口是否意味着不能扩展带特殊语义的方法?
统一接口并不阻止你扩展方法,只要方法对资源的操作有着具体的、可识别的语义即可,并能够保持整个接口的统一性。 像WebDAV就对HTTP方法进行了扩展,增加了LOCK、UPLOCK等方法。而github的API则支持使用PATCH方法来进行issue的更新,例如:
PATCH /repos/:owner/:repo/issues/:number
不过,需要注意的是,像PATCH这种不是HTTP标准方法的,服务端需要考虑客户端是否能够支持的问题。
- 统一资源接口对URI有什么指导意义?
统一资源接口要求使用标准的HTTP方法对资源进行操作,所以URI只应该来表示资源的名称,而不应该包括资源的操作。 通俗来说,URI不应该使用动作来描述。例如,下面是一些不符合统一接口要求的URI:
- GET /getUser/1
- POST /createUser
- PUT /updateUser/1
- DELETE /deleteUser/1
- 如果GET请求增加计数器,这是否违反安全性?
安全性不代表请求不产生副作用,例如像很多API开发平台,都对请求流量做限制。像github,就会限制没有认证的请求每小时只能请求60次。 但客户端不是为了追求副作用而发出这些GET或HEAD请求的,产生副作用是服务端“自作主张”的。 另外,服务端在设计时,也不应该让副作用太大,因为客户端认为这些请求是不会产生副作用的。
- 直接忽视缓存可取吗?
即使你按各个动词的原本意图来使用它们,你仍可以轻易禁止缓存机制。 最简单的做法就是在你的HTTP响应里增加这样一个报头: Cache-control: no-cache。 但是,同时你也对失去了高效的缓存与再验证的支持(使用Etag等机制)。 对于客户端来说,在为一个REST式服务实现程序客户端时,也应该充分利用现有的缓存机制,以免每次都重新获取表示。
- 响应代码的处理有必要吗?
如上图所示,HTTP的响应代码可用于应付不同场合,正确使用这些状态代码意味着客户端与服务器可以在一个具备较丰富语义的层次上进行沟通。 例如,201(“Created”)响应代码表明已经创建了一个新的资源,其URI在Location响应报头里。 假如你不利用HTTP状态代码丰富的应用语义,那么你将错失提高重用性、增强互操作性和提升松耦合性的机会。 如果这些所谓的RESTFul应用必须通过响应实体才能给出错误信息,那么SOAP就是这样的了,它就能够满足了。
2.3 资源的表述
上面提到,客户端通过HTTP方法可以获取资源,是吧? 不,确切的说,客户端获取的只是资源的表述而已。 资源在外界的具体呈现,可以有多种表述(或成为表现、表示)形式,在客户端和服务端之间传送的也是资源的表述,而不是资源本身。 例如文本资源可以采用html、xml、json等格式,图片可以使用PNG或JPG展现出来。 资源的表述包括数据和描述数据的元数据,例如,HTTP头“Content-Type” 就是这样一个元数据属性。
那么客户端如何知道服务端提供哪种表述形式呢?
答案是可以通过HTTP内容协商,客户端可以通过Accept头请求一种特定格式的表述,服务端则通过Content-Type告诉客户端资源的表述形式。
以github为例,请求某组织资源的json格式的表述形式:
假如github也能够支持xml格式的表述格式,那么结果就是这样的:
下面我们来看一些实践上常见的设计:
- 在URI里边带上版本号
有些API在URI里边带上版本号,例如:
- http://api.example.com/1.0/foo
- http://api.example.com/1.2/foo
- http://api.example.com/2.0/foo
如果我们把版本号理解成资源的不同表述形式的话,就应该只是用一个URL,并通过Accept头部来区分,还是以github为例,它的Accept的完整格式是
application/vnd.github[.version].param[+json]
对于v3版本的话,就是Accept: application/vnd.github.v3。对于上面的例子,同理可以使用使用下面的头部:
- Accept: vnd.example-com.foo+json; version=1.0
- Accept: vnd.example-com.foo+json; version=1.2
- Accept: vnd.example-com.foo+json; version=2.0
- 使用URI后缀来区分表述格式
像rails框架,就支持使用/users.xml或/users.json来区分不同的格式。 这样的方式对于客户端来说,无疑是更为直观,但混淆了资源的名称和资源的表述形式。 我个人认为,还是应该优先使用内容协商来区分表述格式。
- 如何处理不支持的表述格式
当服务器不支持所请求的表述格式,那么应该怎么办?若服务器不支持,它应该返回一个HTTP 406响应,表示拒绝处理该请求。下面以github为例,展示了一个请求XML表述资源的结果:
2.4 资源的链接
我们知道REST是使用标准的HTTP方法来操作资源的,但仅仅因此就理解成带CURD的Web数据库架构就太过于简单了。 这种反模式忽略了一个核心概念: “超媒体即应用状态引擎(hypermedia as the engine of application state)”。 超媒体是什么? 当你浏览Web网页时,从一个连接跳到一个页面,再从另一个连接跳到另外一个页面,就是利用了超媒体的概念: 把一个个把资源链接起来.
要达到这个目的,就要求在表述格式里边加入链接来引导客户端。在《RESTFul Web Services》一书中,作者把这种具有链接的特性成为连通性。下面我们具体来看一些例子。
下面展示的是github获取某个组织下的项目列表的请求,可以看到在响应头里边增加Link头告诉客户端怎么访问下一页和最后一页的记录。 而在响应体里边,用url来链接项目所有者和项目地址。
又例如下面这个例子,创建订单后通过链接引导客户端如何去付款。
上面的例子展示了如何使用超媒体来增强资源的连通性。很多人在设计RESTFul架构时,使用很多时间来寻找漂亮的URI,而忽略了超媒体。所以,应该多花一些时间来给资源的表述提供链接,而不是专注于“资源的CRUD”。
2.5 状态的转移
有了上面的铺垫,再讨论REST里边的状态转移就会很容易理解了。 不过,我们先来讨论一下REST原则中的无状态通信原则。初看一下,好像自相矛盾了,既然无状态,何来状态转移一说?
其实,这里说的无状态通信原则,并不是说客户端应用不能有状态,而是指服务端不应该保存客户端状态。
2.5.1 应用状态与资源状态
实际上,状态应该区分应用状态和资源状态,客户端负责维护应用状态,而服务端维护资源状态。 客户端与服务端的交互必须是无状态的,并在每一次请求中包含处理该请求所需的一切信息。 服务端不需要在请求间保留应用状态,只有在接受到实际请求的时候,服务端才会关注应用状态。 这种无状态通信原则,使得服务端和中介能够理解独立的请求和响应。 在多次请求中,同一客户端也不再需要依赖于同一服务器,方便实现高可扩展和高可用性的服务端。
但有时候我们会做出违反无状态通信原则的设计,例如利用Cookie跟踪某个服务端会话状态,常见的像J2EE里边的JSESSIONID。 这意味着,浏览器随各次请求发出去的Cookie是被用于构建会话状态的。 当然,如果Cookie保存的是一些服务器不依赖于会话状态即可验证的信息(比如认证令牌),这样的Cookie也是符合REST原则的。
2.5.2 应用状态的转移
状态转移到这里已经很好理解了, “会话”状态不是作为资源状态保存在服务端的,而是被客户端作为应用状态进行跟踪的。客户端应用状态在服务端提供的超媒体的指引下发生变迁。服务端通过超媒体告诉客户端当前状态有哪些后续状态可以进入。 这些类似“下一页”之类的链接起的就是这种推进状态的作用–指引你如何从当前状态进入下一个可能的状态。
3 总结
现在广东XXX版本、XXX等项目中均使用传统的RPC、SOAP方式的Web服务,而移动南方基地XXXX项目的后台, 虽然采用了JSON格式进行交互,但还是属于RPC风格的。本文从资源的定义、获取、表述、关联、状态变迁等角度, 试图快速理解RESTFul架构背后的概念。RESTFul架构与传统的RPC、SOAP等方式在理念上有很大的不同,希望本文能对各位理解REST有所帮助。
Django——RESTful架构
一、REST简述
来自维基百科的解释:
表现层状态转换(REST,英文:Representational State Transfer)是Roy Thomas Fielding博士于2000年在他的博士论文[1] 中提出来的一种万维网软件架构风格,目的是便于不同软件/程序在网络(例如互联网)中互相传递信息。表现层状态转换是根基于超文本传输协议(HTTP)之上而确定的一组约束和属性,是一种设计提供万维网络服务的软件构建风格。匹配或兼容于这种架构风格(简称为 REST 或 RESTful)的网络服务,允许客户端发出以统一资源标识符访问和操作网络资源的请求,而与预先定义好的无状态操作集一致化。因此表现层状态转换提供了在互联网络的计算系统之间,彼此资源可交互使用的协作性质(interoperability)。相对于其它种类的网络服务,例如 SOAP服务则是以本身所定义的操作集,来访问网络上的资源。
Web 应用程序最重要的 REST 原则是,客户端和服务器之间的交互在请求之间是无状态的。从客户端到服务器的每个请求都必须包含理解请求所必需的信息。如果服务器在请求之间的任何时间点重启,客户端不会得到通知。此外,无状态请求可以由任何可用服务器回答,这十分适合 云计算之类的环境。客户端可以缓存数据以改进性能。在服务器端,应用程序状态和功能可以分为各种资源。资源是一个有趣的概念实体,它向客户端公开。资源的例子有:应用程序对象、数据库记录、算法等等。每个资源都使用 URI (Universal Resource Identifier) 得到一个唯一的地址。所有资源都共享统一的接口,以便在客户端和服务器之间传输状态。使用的是标准的 HTTP 方法,比如 GET、PUT、 POST 和 DELETE。 Hypermedia 是应用程序状态的 引擎,资源表示通过 超链接互联。
二、REST的解析
Fielding将他对互联网软件的架构原则,定名为REST,即Representational State Transfer的缩写。我对这个词组的翻译是"表现层状态转化"。
如果一个架构符合REST原则,就称它为RESTful架构。
要理解RESTful架构,最好的方法就是去理解Representational State Transfer这个词组到底是什么意思,它的每一个词代表了什么涵义。如果你把这个名称搞懂了,也就不难体会REST是一种什么样的设计。
- REST从资源的角度类审视整个网络,它将分布在网络中某个节点的资源通过URL进行标识,客户端应用通过URL来获取资源的表征,获得这些表征致使这些应用转变状态
- REST与技术无关,代表的是一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为“表征状态转移”
- 所有的数据,不过是通过网络获取的还是操作(增删改查)的数据,都是资源,将一切数据视为资源是REST区别与其他架构风格的最本质属性
- 对于REST这种面向资源的架构风格,有人提出一种全新的结构理念,即:面向资源架构(ROA:Resource Oriented Architecture)
三、资源(Resources)
REST的名称"表现层状态转化"中,省略了主语。"表现层"其实指的是"资源"(Resources)的"表现层"。
所谓"资源",就是网络上的一个实体,或者说是网络上的一个具体信息。它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的实在。你可以用一个URI(统一资源定位符)指向它,每种资源对应一个特定的URI。要获取这个资源,访问它的URI就可以,因此URI就成了每一个资源的地址或独一无二的识别符。
所谓"上网",就是与互联网上一系列的"资源"互动,调用它的URI。
四、表现层(Representation)
"资源"是一种信息实体,它可以有多种外在表现形式。我们把"资源"具体呈现出来的形式,叫做它的"表现层"(Representation)。
比如,文本可以用txt格式表现,也可以用HTML格式、XML格式、JSON格式表现,甚至可以采用二进制格式;图片可以用JPG格式表现,也可以用PNG格式表现。
URI只代表资源的实体,不代表它的形式。严格地说,有些网址最后的".html"后缀名是不必要的,因为这个后缀名表示格式,属于"表现层"范畴,而URI应该只代表"资源"的位置。它的具体表现形式,应该在HTTP请求的头信息中用Accept和Content-Type字段指定,这两个字段才是对"表现层"的描述。
五、状态转化(State Transfer)
访问一个网站,就代表了客户端和服务器的一个互动过程。在这个过程中,势必涉及到数据和状态的变化。
互联网通信协议HTTP协议,是一个无状态协议。这意味着,所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生"状态转化"(State Transfer)。而这种转化是建立在表现层之上的,所以就是"表现层状态转化"。
客户端用到的手段,只能是HTTP协议。具体来说,就是HTTP协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。它们分别对应四种基本操作:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源。
六、综述
综合上面的解释,我们总结一下什么是RESTful架构:
(1)每一个URI代表一种资源;
(2)客户端和服务器之间,传递这种资源的某种表现层;
(3)客户端通过四个HTTP动词,对服务器端资源进行操作,实现"表现层状态转化"。
七、误区
RESTful架构有一些典型的设计误区。
最常见的一种设计错误,就是URI包含动词。因为"资源"表示一种实体,所以应该是名词,URI不应该有动词,动词应该放在HTTP协议中。
举例来说,某个URI是 /posts/show/1 ,其中 show 是动词,这个URI就设计错了,正确的写法应该是 /posts/1 ,然后用 GET 方法表示 show 。
如果某些动作是HTTP动词表示不了的,你就应该把动作做成一种资源。比如网上汇款,从账户1向账户2汇款500元,错误的URI是:
POST /accounts/1/transfer/500/to/2
正确的写法是把动词transfer改成名词transaction,资源不能是动词,但是可以是一种服务:
POST /transaction HTTP/1.1
Host: 127.0.0.1
from=1&to=2&amount=500.00
另一个设计误区,就是在URI中加入版本号:
http://www.example.com/app/1.0/foo
http://www.example.com/app/1.1/foo
http://www.example.com/app/2.0/foo
因为不同的版本,可以理解成同一种资源的不同表现形式,所以应该采用同一个URI。版本号可以在HTTP请求头信息的Accept字段中进行区分(参见Versioning REST Services):
Accept: vnd.example-com.foo+json; version=1.0
Accept: vnd.example-com.foo+json; version=1.1
Accept: vnd.example-com.foo+json; version=2.0
八、RESTful API设计
8.1 协议
API与用户的通信协议,总是使用HTTPs协议。
8.2 域名
应该尽量将API部署在专用域名之下。
https://api.example.com
如果确定API很简单,不会有进一步扩展,可以考虑放在主域名下。
https://example.org/api/
8.3 版本(Versioning)
应该将API的版本号放入URL。
https://api.example.com/v1/
另一种做法是,将版本号放在HTTP头信息中,但不如放入URL方便和直观。Github采用这种做法。
8.4 路径(Endpoint)
路径又称"终点"(endpoint),表示API的具体网址。
在RESTful架构中,每个网址代表一种资源(resource),所以网址中不能有动词,只能有名词,而且所用的名词往往与数据库的表格名对应。一般来说,数据库中的表都是同种记录的"集合"(collection),所以API中的名词也应该使用复数。
举例来说,有一个API提供动物园(zoo)的信息,还包括各种动物和雇员的信息,则它的路径应该设计成下面这样。
- https://api.example.com/v1/zoos
- https://api.example.com/v1/animals
- https://api.example.com/v1/employees
8.5 HTTP动词
对于资源的具体操作类型,由HTTP动词表示。
常用的HTTP动词有下面五个(括号里是对应的SQL命令)。
- GET(SELECT):从服务器取出资源(一项或多项)。
- POST(CREATE):在服务器新建一个资源。
- PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
- PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。
- DELETE(DELETE):从服务器删除资源。
还有两个不常用的HTTP动词。
- HEAD:获取资源的元数据。
- OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的。
下面是一些例子。
- GET /zoos:列出所有动物园
- POST /zoos:新建一个动物园
- GET /zoos/ID:获取某个指定动物园的信息
- PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息)
- PATCH /zoos/ID:更新某个指定动物园的信息(提供该动物园的部分信息)
- DELETE /zoos/ID:删除某个动物园
- GET /zoos/ID/animals:列出某个指定动物园的所有动物
- DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物
8.6 过滤信息(Filtering)
如果记录数量很多,服务器不可能都将它们返回给用户。API应该提供参数,过滤返回结果。
下面是一些常见的参数。
- ?limit=10:指定返回记录的数量
- ?offset=10:指定返回记录的开始位置。
- ?page=2&per_page=100:指定第几页,以及每页的记录数。
- ?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。
- ?animal_type_id=1:指定筛选条件
参数的设计允许存在冗余,即允许API路径和URL参数偶尔有重复。比如,GET /zoo/ID/animals 与 GET /animals?zoo_id=ID 的含义是相同的。
8.7 状态码(Status Codes)
服务器向用户返回的状态码和提示信息,常见的有以下一些(方括号中是该状态码对应的HTTP动词)。
- 200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。
- 201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
- 202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)
- 204 NO CONTENT - [DELETE]:用户删除数据成功。
- 400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。
- 401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。
- 403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。
- 404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
- 406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
- 410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。
- 422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。
- 500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。
状态码的完全列表参见这里。
8.8 错误处理(Error handling)
如果状态码是4xx,就应该向用户返回出错信息。一般来说,返回的信息中将error作为键名,出错信息作为键值即可。
{ error: "Invalid API key" }
8.9 返回结果
针对不同操作,服务器向用户返回的结果应该符合以下规范。
- GET /collection:返回资源对象的列表(数组)
- GET /collection/resource:返回单个资源对象
- POST /collection:返回新生成的资源对象
- PUT /collection/resource:返回完整的资源对象
- PATCH /collection/resource:返回完整的资源对象
- DELETE /collection/resource:返回一个空文档
8.10 Hypermedia API
RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。
比如,当用户向api.example.com的根目录发出请求,会得到这样一个文档。
{"link": { "rel": "collection https://www.example.com/zoos", "href": "https://api.example.com/zoos", "title": "List of zoos", "type": "application/vnd.yourformat+json" }}
上面代码表示,文档中有一个link属性,用户读取这个属性就知道下一步该调用什么API了。rel表示这个API与当前网址的关系(collection关系,并给出该collection的网址),href表示API的路径,title表示API的标题,type表示返回类型。
Hypermedia API的设计被称为HATEOAS。Github的API就是这种设计,访问api.github.com会得到一个所有可用API的网址列表。
{ "current_user_url": "https://api.github.com/user", "authorizations_url": "https://api.github.com/authorizations", // ... }
从上面可以看到,如果想获取当前用户的信息,应该去访问api.github.com/user,然后就得到了下面结果。
{ "message": "Requires authentication", "documentation_url": "https://developer.github.com/v3" }
上面代码表示,服务器给出了提示信息,以及文档的网址。
8.11 其他
(1)API的身份认证应该使用OAuth 2.0框架。
(2)服务器返回的数据格式,应该尽量使用JSON,避免使用XML。
参考资料:
阮一峰:http://www.ruanyifeng.com/blog/2011/09/restful.html
YUAN先生: https://www.cnblogs.com/yuanchenqi/articles/8742684.html
维基百科: https://en.wikipedia.org/wiki/Representational_state_transfer
Go语言中的实时通信和RESTful架构
随着互联网技术的飞速发展和人们对实时通信的需求不断增加,实时通信架构和restful架构也逐渐成为目前最流行的应用程序架构之一。而
一、实时通信
实时通信是指通过互联网实现实时即时通信的技术,主要包括即时消息、视频通话、音频通话等。在实时通信应用领域,对于性能的要求非常高,需要能够实时响应用户的请求,并及时推送消息,保证数据的准确性和时效性。
Go语言在实时通信方面有着很好的表现,其协程模型使得并发编程变得非常简单,可以轻松实现实时消息推送等操作。同时,Go语言在处理底层网络通信时,也有着很强的优势,使用原生的Socket和TCP协议进行网络通信,基于Goroutine的高并发模型能够实现每秒百万级的数据处理速度,针对实时通信特别合适。
在开发实时通信应用时,可以使用Go语言的WebSocket来实现长连接通信,WebSocket是一种双向通信协议,允许客户端和服务器之间建立持久性的连接,可以快速传输大量数据,适用于实时应用场景。在Go语言中,使用github.com/gorilla/websocket库可轻松实现WebSocket的使用。
立即学习“go语言免费学习笔记(深入)”;
此外,Go语言也支持使用HTTP/2协议进行通信,与HTTP/1.x不同,HTTP/2采用二进制协议帧进行通信,可同时处理多个请求,相比HTTP/1.x协议,能够大幅提高数据传输效率和响应速度。
二、RESTful架构
RESTful架构是一种基于HTTP/HTTPS协议的网络应用程序架构,使用统一的接口、缓存、分层系统等原则来构建Web服务。RESTful架构不仅为应用程序提供了高性能的支持,还具有轻量级、可扩展性好的特点。
Go语言天生的HTTP库包中就已经提供了完整的HTTP服务器和客户端支持,同时Go语言的URL路由器gorilla/mux库也为开发者提供了非常方便的路由功能,可以快速构建RESTful的Web服务。
在使用Go语言实现RESTful架构时,可以采用如下几种方式:
1.标准库http包
Go语言的标准库中已经内置了HTTP服务和客户端功能,可以通过net/http包快速构建RESTful程序,实现GET、POST、PUT、DELETE等HTTP请求,同时也可通过gorilla/mux库实现更高级的路由功能。
2.Gin
Gin是基于Go语言的轻量级Web应用框架,采用了类似于ExpressJS的Api编写方式,非常适合构建Web Api和微服务。Gin使用高效的httprouter路由库和标准的HTTP错误处理机制,可快速开发RESTful应用。
3.Echo
Echo是一个高性能、可扩展、灵活的Web框架,可快速构建RESTful应用。Echo使用高效的httprouter路由器和快速的HTTP服务器,发布后包体积小且可运行与独立的二进制文件中。
总结
实时通信和RESTful架构是目前最流行的应用程序架构之一。Go语言以其优秀的并发处理和高性能特性,成为了实时通信和RESTful架构的首选语言之一。选择Go语言来实现应用程序,无论是实时通信还是RESTful架构,都将为应用程序的性能和效率提供有力保证。
以上就是Go语言中的实时通信和RESTful架构的详细内容,更多请关注php中文网其它相关文章!
Nodejs RESTFul架构实践
Nodejs RESTFul架构实践
准备
如果你不了解http协议,先看一下https://github.com/i5ting/node-http
什么是REST?
RESTful架构,就是目前最流行的一种互联网软件架构。它结构清晰、符合标准、易于理解、扩展方便,所以正得到越来越多网站的采用。
以下内容摘自阮一峰的文章:
一、起源
REST这个词,是Roy Thomas Fielding在他2000年的博士论文中提出的。
二、名称
Fielding将他对互联网软件的架构原则,定名为REST,即Representational State Transfer的缩写。我对这个词组的翻译是”表现层状态转化”。 如果一个架构符合REST原则,就称它为RESTful架构。
三、资源(Resources)
REST的名称”表现层状态转化”中,省略了主语。”表现层”其实指的是”资源”(Resources)的”表现层”。
四、表现层(Representation)
“资源”是一种信息实体,它可以有多种外在表现形式。我们把”资源”具体呈现出来的形式,叫做它的”表现层”(Representation)。
五、状态转化(State Transfer)
访问一个网站,就代表了客户端和服务器的一个互动过程。在这个过程中,势必涉及到数据和状态的变化。
六、综述
综合上面的解释,我们总结一下什么是RESTful架构:
(1)每一个URI代表一种资源; (2)客户端和服务器之间,传递这种资源的某种表现层; (3)客户端通过四个HTTP动词,对服务器端资源进行操作,实现”表现层状态转化”。
说的通俗一点,改变url写法,让它带有状态,即语义化
更多详见:
http://www.ruanyifeng.com/blog/2011/09/restful.html
http://www.restapitutorial.com/lessons/whatisrest.html
请求方法
一般会严格要求请求方法及其释义,下面给出常用的请求方法
如果请求头中存在
X-HTTP-Method-Override
或参数中存在_method
(拥有更高权重),且值为GET
,POST
,PUT
,DELETE
,PATCH
,OPTION
,HEAD
之一,则视作相应的请求方式进行处理GET
,DELETE
,HEAD
方法,参数风格为标准的GET
风格的参数,如url?a=1&b=2
POST
,PUT
,PATCH
,OPTION
方法默认情况下请求实体会被视作标准 json 字符串进行处理,当然,依旧推荐设置头信息的
Content-Type
为application/json
在一些特殊接口中(会在文档中说明),可能允许
Content-Type
为application/x-www-form-urlencoded
或者multipart/form-data
,此时请求实体会被视作标准POST
风格的参数进行处理
关于方法语义的说明:
OPTIONS
用于获取资源支持的所有 HTTP 方法HEAD
用于只获取请求某个资源返回的头信息GET
用于从服务器获取某个资源的信息完成请求后返回状态码
200 OK
完成请求后需要返回被请求的资源详细信息
POST
用于创建新资源创建完成后返回状态码
201 Created
完成请求后需要返回被创建的资源详细信息
PUT
用于完整的替换资源或者创建指定身份的资源,比如创建 id 为 123 的某个资源如果是创建了资源,则返回
201 Created
如果是替换了资源,则返回
200 OK
完成请求后需要返回被修改的资源详细信息
PATCH
用于局部更新资源完成请求后返回状态码
200 OK
完成请求后需要返回被修改的资源详细信息
DELETE
用于删除某个资源完成请求后返回状态码
204 No Content
上面是比较常见的,估计大部分人最常用的是2个,get和post,具体每个怎么玩,下面会给出实例
最常见的增删改查
以前大家都认为管理信息系统就是crud,认为没有啥技术含量,哎,真正能把crud写明白其实也不是一件容易的事儿
七个路由,见app/routes/users.js
其中4个路由是crud
GET /users[/] => user.list()
POST /users[/] => user.create()
PATCH /users/:id => user.update()
DELETE /users/:id => user.destroy()
另外3个是页面渲染用的
GET /users/new => user.new()
GET /users/:id => user.show()
GET /users/:id/edit => user.edit()
那么我们先来看一下crud对应的请求方法
get用于请求列表
post用于创建
patch用于更新,局部更新资源
delete用于删除
对比上一节的内容,你会发现他们的含义貌似真的对了,唯一可能有争议是更新,有的人用put有的用patch,推荐patch
以前做java的时候会认为,创建、删除和更新都用post,查询和搜索用get,这样做没问题,只是不符合rest风格而已。
很多人和我讨论过到该不该rest,我的回答是
客户端 API 开发总结
express代码分层
默认的express生成器生成的只有routes和views文件夹,相对比较简单,做大型应用怎么能少了mvc呢?
于是我仿照rails写了如下代码分层
routes 路由层,只有url和中间件,不包含任何逻辑
controllers 业务逻辑控制层
models 模型层
views 视图层
如下图
路由层
上代码
var express = require(''express'');
var router = express.Router();
var $ = require(''../controllers/users_controller'');
// -- custom
/**
* Auto generate RESTful url routes.
*
* URL routes:
*
* GET /users[/] => user.list()
* GET /users/new => user.new()
* GET /users/:id => user.show()
* GET /users/:id/edit => user.edit()
* POST /users[/] => user.create()
* PATCH /users/:id => user.update()
* DELETE /users/:id => user.destroy()
*
*/
router.get(''/new'', $.new);
router.get(''/:id/edit'', $.edit);
router.route(''/'')
.get($.list)
.post($.create);
router.route(''/:id'')
.patch($.update)
.get($.show)
.delete($.destroy);
module.exports = router;
和普通的路由代码一样,稍微改造了一下,统一了地址,应该是更清晰了。
这里只是引入了users_controller
文件,完成请求地址和业务处理代码的映射而已。
这里还要讨论一个问题,每次增加一个接口就要加一个路由会比较烦,而且要在app.js里配置,能不能自动加载呢?比如app/routes目录下的所有js都可以挂载到app上
答案是可以的,使用我写的mount-routes
即可,示例如下:
var express = require(''express'')
var app = express()
var mount = require(''mount-routes'');
// 简单用法,加载app/routes下的所有路由
// mount(app);
// 带路径的用法,加载routes2目录下的所有路由
// mount(app, ''routes2'');
// 带路径的用法并且可以打印出路有表
mount(app, ''routes2'', true);
// start server
app.listen(23018)
更多内容见
mount-routes
控制层
/**
* Created by sang on 01/06/14.
*/
var User = require(''../models/user'');
首先,控制层是控制业务处理的,所以它和模型层打交道比较多,同时控制视图如何展示
exports.list = function (req, res, next) {
console.log(req.method + '' /users => list, query: '' + JSON.stringify(req.query));
User.getAll(function(err, users){
console.log(users);
res.render(''users/index'', {
users : users
})
});
};
请求所有列表,很简单,获取所有用户即可
exports.new = function (req, res, next) {
console.log(req.method + '' /users/new => new, query: '' + JSON.stringify(req.query));
res.render(''users/new'', {
user : {
"_action" : "new"
}
})
};
新建用户,实际是render视图而已,没有啥逻辑,user参数是为了我生成代码方便用的,无他
exports.show = function (req, res, next) {
console.log(req.method + '' /users/:id => show, query: '' + JSON.stringify(req.query) +
'', params: '' + JSON.stringify(req.params));
var id = req.params.id;
User.getById(id, function(err, user) {
console.log(user);
res.render(''users/show'', {
user : user
})
});
};
同new,是render视图代码
exports.edit = function (req, res, next) {
console.log(req.method + '' /users/:id/edit => edit, query: '' + JSON.stringify(req.query) +
'', params: '' + JSON.stringify(req.params));
var id = req.params.id;
User.getById(id, function (err, user) {
console.log(user);
user._action = ''edit'';
res.render(''users/edit'', {
user : user
})
});
};
同new,是render视图代码
exports.create = function (req, res, next) {
console.log(req.method + '' /users => create, query: '' + JSON.stringify(req.query) +
'', params: '' + JSON.stringify(req.params) + '', body: '' + JSON.stringify(req.body));
User.create({name: req.body.name,password: req.body.password}, function (err, user) {
console.log(user);
res.render(''users/show'', {
user : user
})
});
};
这段是创建用户的代码,根据post参数,保存入库,跳转到展示详情页面
exports.update = function (req, res, next) {
console.log(req.method + '' /users/:id => update, query: '' + JSON.stringify(req.query) +
'', params: '' + JSON.stringify(req.params) + '', body: '' + JSON.stringify(req.body));
var id = req.params.id;
User.updateById(id,{name: req.body.name,password: req.body.password}, function (err, user) {
console.log(user);
res.json({
data:{
redirect : ''/users/'' + id
},
status:{
code : 0,
msg : ''delete success!''
}
});
});
};
和创建类似,它是根据id来更新内容
exports.destroy = function (req, res, next) {
console.log(req.method + '' /users/:id => destroy, query: '' + JSON.stringify(req.query) +
'', params: '' + JSON.stringify(req.params) + '', body: '' + JSON.stringify(req.body));
var id = req.params.id;
User.deleteById(id, function (err) {
console.log(err);
res.json({
data:{},
status:{
code : 0,
msg : ''delete success!''
}
});
});
};
和创建类似,它是根据id来删除内容
以上代码形式都是一样的
exports.xxxx = function (req, res, next) {
...
}
他实际上connect中间件的标准写法,如果你熟悉express,可以非常简单的上手,看了这些代码,你一定很好奇,User模型是怎么工作的,而且mongoose里并没有这些方法啊,下面会详细说明
模型层
我们的模型层使用的是比较传统的mongoose,如果需要promise库,可以搭配bluebird
/**
* Created by alfred on 01/06/14.
*/
var mongoose = require(''mongoose'');
var Schema = mongoose.Schema;
var MongooseDao = require(''mongoosedao'');
var userSchema = new Schema(
{"name":"String","password":"String"}
);
var User = mongoose.model(''User'', userSchema);
var UserDao = new MongooseDao(User);
module.exports = UserDao;
这实际是最简单的定义mongoose模型的方法,我们唯一做的改进是增加了MongooseDao
DAO是java的概念,是data access object,即数据访问对象,就是传说的crud方法
你只要知道模型就好,为啥每个crud都要写呢?那得多烦啊
var UserDao = new MongooseDao(User);
这样就可以给user增加了基本的crud方法
所以在controller里我们看到了如下代码
User.getAll(function(err, users){
User.getById(id, function(err, user) {
User.create({name: req.body.name,password: req.body.password}, function (err, user) {
User.updateById(id,{name: req.body.name,password: req.body.password}, function (err, user) {
User.deleteById(id, function (err) {
这5个方法,完美的完成crud的所有操作,是不是很爽?至少少写了很多代码
而且当你想扩展的时候,你可以使用User.model
来操作mongoose对象
比如login,我需要在User模型增加is_exist方法
var mongoose = require(''mongoose'');
var Schema = mongoose.Schema;
var MongooseDao = require(''mongoosedao'');
var userSchema = new Schema(
{"name":"String","password":"String"}
);
userSchema.methods.is_exist = function(cb) {
var query;
query = {
username: this.username,
password: this.password
};
return this.model(''User'').findOne(query, cb);
};
var User = mongoose.model(''User'', userSchema);
var UserDao = new MongooseDao(User);
module.exports = UserDao;
这就是mongoose里的实例方法,static方法也是一样,你可以玩各种花样
然后控制层
exports.login = function (req, res, next) {
username = req.body.username;
password = req.body.password;
user = new User.model({
username: username,
password: password
});
return user.is_exist(function(err, usr) {
。。。
});
}
是不是很简单?
视图层
视图层我们采用express默认的jade,无论各位怎么看,jade都可圈可点
1)extends 方式使用布局 2)include 复用模型代码 3)block 复用块代码
以new.jade和edit.jade为例,它们具有代表性
new.jade
extends ../layouts/layout
block content
h1 New user
include user
a(href=''/users'') Back
edit.jade
extends ../layouts/layout
block content
h1 Editing user
include user
a(href=''/users/#{ user._id}'') Show
span |
a(href=''/users'') Back
首先要说明的是include用法,include类似于partial概念,可以包含一个jade作为一部分
jade里有一个约定,你include了谁,它就要把这个对象传进去
所以user.jade里才是我们复用的重点
- var _action = user._action == ''edit'' ? ''#'' : ''/users/''
- var _method = user._action == ''edit'' ? "" : "post"
- var _type = user._action == ''edit'' ? "button" : "submit"
- var onClick = user._action == ''edit'' ? "click_edit(''user-" + user._action + "-form'',''/users/" + user._id + "/'')" : ""
form(id=''user-#{ user._action}-form'',action="#{_action}", method="#{_method}",role=''form'')
each n in [''user.name'',''user.password'']
- m = eval(n);
div()
label #{n.split(''.'')[1]} #{m}
br
input(type=''text'',name="#{n.split(''.'')[1]}" ,value="#{ m == undefined ? '''' : m }")
div()
input(type=''#{_type}'',value=''Submit'',onClick=''#{onClick}'')
这段是为了复用写的代码,可读性不强,但可以说明include用法,对于代码而言,达到了一定的复用
实际上我们自己jade的适合要尽可能的拆分成小块去复用,当需求变得时候更容易应变。提醒强迫症患者,当心物极必反,不要刻意去强求
总结一下
RESTful架构大势所趋,代码写的标准了让人觉得赞
Expressjs下可以写的很规范,可以有做大应用,可以有很好的分层
展示了MVC + Routes的标准rest写法
使用了几个开源代码
mount-routes
MongooseDao
完成了以上工作,我们还要继续反思一下,既然rest是标准,写法很固定,是不是可以量产呢?答案是可以的,我展示的所有代码是根据一条命令搞定的
moag user name:string password:string
这就是我目前在写的一个开源项目,待可用的时候会公开的,敬请期待
欢迎关注我的公众号【node全栈】
Nodejs RESTFul架构实践之api篇
why token based auth?
此段摘自
http://zhuanlan.zhihu.com/FrontendMagazine/19920223
英文原文
http://code.tutsplus.com/tutorials/token-based-authentication-with-angularjs-nodejs–cms-22543
在讨论了关于基于 token 认证的一些基础知识后,我们接下来看一个实例。看一下下面的几点,然后我们会仔细的分析它:
多个终端,比如一个 web 应用,一个移动端等向 API 发送特定的请求。
类似 https://api.yourexampleapp.com 这样的请求发送到服务层。如果很多人使用了这个应用,需要多个服务器来响应这些请求操作。
这时,负载均衡被用于平衡请求,目的是达到最优化的后端应用服务。当你向https://api.yourexampleapp.com 发送请求,最外层的负载均衡会处理这个请求,然后重定向到指定的服务器。
一个应用可能会被部署到多个服务器上(server-1, server-2, …, server-n)。当有请求发送到https://api.yourexampleapp.com 时,后端的应用会拦截这个请求头部并且从认证头部中提取到 token 信息。使用这个 token 查询数据库。如果这个 token 有效并且有请求终端数据所必须的许可时,请求会继续。如果无效,会返回 403 状态码(表明一个拒绝的状态)。
基于 token 的认证在解决棘手的问题时有几个优势:
Client Independent Services 。在基于 token 的认证,token 通过请求头传输,而不是把认证信息存储在 session 或者 cookie 中。这意味着无状态。你可以从任意一种可以发送 HTTP 请求的终端向服务器发送请求。
CDN 。在绝大多数现在的应用中,view 在后端渲染,HTML 内容被返回给浏览器。前端逻辑依赖后端代码。这中依赖真的没必要。而且,带来了几个问题。比如,你和一个设计机构合作,设计师帮你完成了前端的 HTML,CSS 和 JavaScript,你需要拿到前端代码并且把它移植到你的后端代码中,目的当然是为了渲染。修改几次后,你渲染的 HTML 内容可能和设计师完成的代码有了很大的不同。在基于 token 的认证中,你可以开发完全独立于后端代码的前端项目。后端代码会返回一个 JSON 而不是渲染 HTML,并且你可以把最小化,压缩过的代码放到 CDN 上。当你访问 web 页面,HTML 内容由 CDN 提供服务,并且页面内容是通过使用认证头部的 token 的 API 服务所填充。
No Cookie-Session (or No CSRF) 。CSRF 是当代 web 安全中一处痛点,因为它不会去检查一个请求来源是否可信。为了解决这个问题,一个 token 池被用在每次表单请求时发送相关的 token。在基于 token 的认证中,已经有一个 token 应用在认证头部,并且 CSRF 不包含那个信息。
Persistent Token Store 。当在应用中进行 session 的读,写或者删除操作时,会有一个文件操作发生在操作系统的temp 文件夹下,至少在第一次时。假设有多台服务器并且 session 在第一台服务上创建。当你再次发送请求并且这个请求落在另一台服务器上,session 信息并不存在并且会获得一个“未认证”的响应。我知道,你可以通过一个粘性 session 解决这个问题。然而,在基于 token 的认证中,这个问题很自然就被解决了。没有粘性 session 的问题,因为在每个发送到服务器的请求中这个请求的 token 都会被拦截。
这些就是基于 token 的认证和通信中最明显的优势。基于 token 认证的理论和架构就说到这里。下面上实例。
这段本来想自己写,不过自己写也这些内容,节省点时间
jwt加密和解密
JWT 代表 JSON Web Token ,它是一种用于认证头部的 token 格式。这个 token 帮你实现了在两个系统之间以一种安全的方式传递信息。出于教学目的,我们暂且把 JWT 作为“不记名 token”。一个不记名 token 包含了三部分:header,payload,signature。
header 是 token 的一部分,用来存放 token 的类型和编码方式,通常是使用 base-64 编码。
payload 包含了信息。你可以存放任一种信息,比如用户信息,产品信息等。它们都是使用 base-64 编码方式进行存储。 signature 包括了 header,payload 和密钥的混合体。密钥必须安全地保存储在服务端。
nodejs实现的jwt代码
http://github.com/auth0/node-jsonwebtoken
主要3个方法
jwt.sign
jwt.verify
jwt.decode
需要小心的密钥在多线程或集群下的处理。
加解密一个对象的时间,远远比查询数据库的代价小,唯一可能有的是token有效期的校验,代价极其小。
优雅之写法
授权获取token
在app/routes/api/index.js里
// auth
router.post(''/auth'', function(req, res, next) {
User.one({username: req.body.username},function(err, user){
if (err) throw err;
console.log(user);
if (!user) {
res.json({ success: false, message: ''认证失败,用户名找不到'' });
} else if (user) {
// 检查密码
if (user.password != req.body.password) {
res.json({ success: false, message: ''认证失败,密码错误'' });
} else {
// 创建token
var token = jwt.sign(user, ''app.get(superSecret)'', {
''expiresInMinutes'': 1440 // 设置过期时间
});
// json格式返回token
res.json({
success: true,
message: ''Enjoy your token!'',
token: token
});
}
}
});
});
post请求
地址http://127.0.0.1:3019/api/auth
参数”username=sang&password=000000”
测试
curl -d "username=sang&password=000000" http://127.0.0.1:3019/api/auth
返回
{"success":true,"message":"Enjoy your token!","token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiI1NTc4MzJkZjk0ZTFjN2YyMDJmYTVlNGUiLCJ1c2VybmFtZSI6InNhbmciLCJwYXNzd29yZCI6IjAwMDAwMCIsImF2YXRhciI6IiIsInBob25lX251bWJlciI6IiIsImFkZHJlc3MiOiIiLCJfX3YiOjB9.Wv5za6GpJSMi346o625_8FxfoM4dJ1cWNuezG10zQG4"}%
路由处理
在app/routes/api/groups.js
里
var express = require(''express'');
var router = express.Router();
var $ = require(''../../controllers/groups_controller'');
var $middlewares = require(''mount-middlewares'');
router.get(''/list'', $middlewares.check_api_token, $.api.list);
module.exports = router;
核心代码
router.get(''/list'', $middlewares.check_api_token, $.api.list);
说明
使用了$middlewares.check_api_token中间件
核心业务逻辑在$.api.list
和其他的express路由用法一样,无他
中间件$middlewares.check_api_token
/*!
* Moajs Middle
* Copyright(c) 2015-2019 Alfred Sang <shiren1118@126.com>
* MIT Licensed
*/
var jwt = require(''jsonwebtoken'');//用来创建和确认用户信息摘要
// 检查用户会话
module.exports = function(req, res, next) {
console.log(''检查post的信息或者url查询参数或者头信息'');
//检查post的信息或者url查询参数或者头信息
var token = req.body.token || req.query.token || req.headers[''x-access-token''];
// 解析 token
if (token) {
// 确认token
jwt.verify(token, ''app.get(superSecret)'', function(err, decoded) {
if (err) {
return res.json({ success: false, message: ''token信息错误.'' });
} else {
// 如果没问题就把解码后的信息保存到请求中,供后面的路由使用
req.api_user = decoded;
console.dir(req.api_user);
next();
}
});
} else {
// 如果没有token,则返回错误
return res.status(403).send({
success: false,
message: ''没有提供token!''
});
}
};
这个很容易解释,只要参数有token或者头信息里有x-access-token,我们就认定它是一个api接口,
校验通过了,就把token的decode对象,也就是之前加密的用户对象返回来,保存为req.api_user
业务代码
在app/controllers/groups_controller.js
里
exports.api = {
list: function (req, res, next) {
console.log(req.method + '' /groups => list, query: '' + JSON.stringify(req.query));
var user_id = req.api_user._id;
Group.query({ower_id: user_id}, function(err, groups){
console.log(groups);
res.json({
data:{
groups : groups
},
status:{
code : 0,
msg : ''success''
}
})
});
}
}
让scaffold生成代码和api共存,清晰明了
说明一下
req.api_user是$middlewares.check_api_token里赋值的
写一个下查询接口,返回json即可
测试接口
然后让我们来测试一下
get请求
url = http://127.0.0.1:3019/api/groups/list
参数token
curl http://127.0.0.1:3019/api/groups/list\?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiI1NTc4MzJkZjk0ZTFjN2YyMDJmYTVlNGUiLCJ1c2VybmFtZSI6InNhbmciLCJwYXNzd29yZCI6IjAwMDAwMCIsImF2YXRhciI6IiIsInBob25lX251bWJlciI6IiIsImFkZHJlc3MiOiIiLCJfX3YiOjB9.Wv5za6GpJSMi346o625_8FxfoM4dJ1cWNuezG10zQG4
{"data":{"groups":[{"_id":"557d32a282f9ddcc76a540e8","name":"sjkljkl","desc":"2323","ower_id":"557832df94e1c7f202fa5e4e","users":"","is_public":"","__v":0},{"_id":"557d32b082f9ddcc76a540e9","name":"sjkljkl","desc":"2323","ower_id":"557832df94e1c7f202fa5e4e","users":"","is_public":"","__v":0},{"_id":"557d32f082f9ddcc76a540ea","name":"sjkljkl","desc":"2323","ower_id":"557832df94e1c7f202fa5e4e","users":"","is_public":"","__v":0},{"_id":"557d33804f5905de78e1c25a","name":"sjkljkl","desc":"2323","ower_id":"557832df94e1c7f202fa5e4e","users":"","is_public":"","__v":0},{"_id":"557d33984f5905de78e1c25b","name":"anan","desc":"2323","ower_id":"557832df94e1c7f202fa5e4e","users":"2323","is_public":"232","__v":0}]},"status":{"code":0,"msg":"success"}}
模型,查询以及其他
模型,查询以及其他,沿用之前的东西,仍然以mongoosedao为主
one
all
query
基本上够用了
如果还想玩的更high一点,可以增加一个service层,把多个model的操作放到里面。
总结
以后写api,可以这样玩
1) 在app/routes/api/
目录下建立对应的api文件,比如groups.js,topics.js,users.js等
2) 然后在对应的controller里,增加
exports.api = {
aa:function(req, res, next){
var user_id = req.api_user._id;
},
bb:function(req, res, next){
var user_id = req.api_user._id;
}
}
3) 简单写点模型的查询方法就可以了
是不是很简单?
使用mount-routes自动挂载routes
使用mongoosedao更简单的接口
如果以后再提供生成器呢?
想想就很美好,美好就继续美好吧~
补一下
Nodejs RESTFul架构实践 http://nodeonly.com/2015/06/09/expressjs-rest.html
全文完
欢迎关注我的公众号【node全栈】
关于理解RESTful架构和restful 架构的介绍现已完结,谢谢您的耐心阅读,如果想了解更多关于Django——RESTful架构、Go语言中的实时通信和RESTful架构、Nodejs RESTFul架构实践、Nodejs RESTFul架构实践之api篇的相关知识,请在本站寻找。
本文标签: