我正在寻找一种方法来在我的基于PHP的Web应用程序,数据库和CMS中围绕默认函数包装API .
我环顾四周,发现了几个"skeleton"框架 . 除了我的问题中的答案,还有Tonic,我喜欢的REST框架,因为它非常轻量级 .
我最喜欢REST,因为它简单,并且希望基于它创建一个API架构 . 我试图了解基本原则并且还没有完全理解它 . 因此,一些问题 .
1. Am I understanding it right?
说我有一个资源“用户” . 我可以像这样设置一些URI:
/api/users when called with GET, lists users
/api/users when called with POST, creates user record
/api/users/1 when called with GET, shows user record
when called with PUT, updates user record
when called with DELETE, deletes user record
到目前为止,这是RESTful架构的正确表示吗?
2. I need more verbs
理论上创建,更新和删除可能就足够了,但在实践中我需要更多的动词 . 我意识到这些内容可以嵌入到更新请求中,但它们是具有特定返回代码的特定操作,我不想将它们全部放入一个操作中 .
用户示例中的一些想法是:
activate_login
deactivate_login
change_password
add_credit
我将如何表达RESTful URL架构中的操作?
我的直觉是对像这样的URL进行GET调用
/api/users/1/activate_login
并期望返回状态代码 .
但是,这偏离了使用HTTP动词的想法 . 你怎么看?
3. How to return error messages and codes
REST的美丽很大一部分源于其使用标准HTTP方法 . 如果出错,我会发出一个包含3xx,4xx或5xx错误状态代码的标头 . 对于详细的错误描述,我可以使用正文(对吗?) . 到现在为止还挺好 . 但是,在描述出错的地方(例如"failed to connect to database"或"database login wrong")更详细地传输 proprietary error code 的方法是什么?如果我把它与信息一起放入体内,我必须在事后解析它 . 这种东西有标准的 Headers 吗?
4. How to do authentication
-
遵循REST原则的基于API密钥的身份验证是什么样的?
-
在对REST客户端进行身份验证时,是否存在强烈反对使用会话的问题,除此之外,它是否公然违反了REST原则? :)(这里只有半开玩笑,基于会话的身份验证可以很好地与我现有的基础架构配合使用 . )
10 回答
re 1 :到目前为止看起来很好 . 请记住在"Location:"标头中返回新创建的用户的URI,作为对POST的响应的一部分,以及"201 Created"状态代码 .
re 2 :通过GET激活是一个坏主意,包括URI中的动词是一种设计气味 . 您可能要考虑在GET上返回表单 . 在Web应用程序中,这将是一个带有提交按钮的HTML表单;在API用例中,您可能希望返回包含PUT的URI的表示以激活帐户 . 当然,您也可以在对/ users的POST响应中包含此URI . 使用PUT将确保您的请求是幂等的,即如果客户端不确定成功,它可以安全地再次发送 . 一般来说,考虑一下你可以将动词转化为哪些资源("nounification of verbs") . 问问自己,您的具体行动与哪种方法最为接近 . 例如 . change_password - > PUT;停用 - >可能是DELETE; add_credit - >可能是POST或PUT . 通过将客户端包含在表示中,将客户端指向适当的URI .
re 3. Don 't invent new status codes, unless you believe they'如此通用,值得全球标准化 . 尽量使用最合适的状态代码(在RFC 2616中阅读所有这些代码) . 在响应正文中包含其他信息 . 如果你真的,确实想要发明一个新的状态代码,那就再想一想;如果您仍然相信,请确保至少选择正确的类别(1xx - > OK,2xx - > informational,3xx - >重定向; 4xx->客户端错误,5xx - >服务器错误) . 我是否提到发明新的状态代码是一个坏主意?
re 4. 如果以任何可能的方式,请使用HTTP中内置的身份验证框架 . 查看Google在GData中进行身份验证的方式 . 通常,don 't put API keys in your URIs. Try to avoid sessions to enhance scalability and support caching - if the response to a request differs because of something that has happened before, you' ve通常将自己绑定到特定的服务器进程实例 . 将会话状态转换为客户端状态(例如,使其成为后续请求的一部分)或通过将其转换为(服务器)资源状态使其明确,即为其提供自己的URI,要好得多 .
对于您所说的示例,我将使用以下内容:
activate_login
POST /users/1/activation
deactivate_login
DELETE /users/1/activation
更改密码
PUT /passwords
(这假定用户已通过身份验证)add_credit
POST /credits
(这假设用户已通过身份验证)对于错误,您将以您收到请求的格式返回正文中的错误,因此如果您收到:
DELETE /users/1.xml
您将以XML格式发送响应,对于JSON等也是如此......
对于身份验证,您应该使用http身份验
当你没有't know how the new resource URI would look like (you create new user, application would assign the new user it'的id时使用帖子,PUT用于更新或创建你知道它们将如何表示的资源(例如:PUT /myfiles/thisismynewfile.txt)
返回错误邮件正文中的描述
您可以使用HTTP身份验证(如果足够的话)Web服务应该是状态
关于REST返回码:混合HTTP协议代码和REST结果是 wrong .
但是,我看到很多实现混合它们,许多开发人员可能不同意我的观点 .
HTTP返回码与
HTTP Request
本身相关 . REST调用是使用超文本传输协议请求完成的,它的工作级别低于调用的REST方法本身 . REST是一种概念/方法,其输出是业务/逻辑结果,而HTTP结果代码是传输结果 .例如,当您调用/ users /时返回“404 Not found”是混淆的,因为它可能意味着:
URI错误(HTTP)
找不到用户(REST)
“403 Forbidden / Access Denied”可能表示:
需要特殊许可 . 浏览器可以通过询问用户/密码来处理它 . (HTTP)
服务器上配置了错误的访问权限 . (HTTP)
您需要进行身份验证(REST)
该列表可能会继续“500服务器错误”(Apache / Nginx HTTP抛出错误或REST中的业务约束错误)或其他HTTP错误等...
从代码中,很难理解什么是失败原因,HTTP(传输)故障或REST(逻辑)故障 .
如果物理上成功执行了HTTP请求,它应始终返回200个代码,无论是否找到记录 . 因为找到了URI资源并由http服务器处理 . 是的,它可能会返回一个空集 . 是否有可能收到一个空的网页,其中200为http结果,对吗?
取而代之的是,您可以返回200个HTTP代码,只返回带有空数组/对象的JSON,或者使用bool结果/成功标志来通知执行的操作状态 .
此外,一些互联网服务提供商可能会拦截您的请求并返回404 http代码 . 这并不意味着您的数据未找到,但在传输级别出现问题 .
从Wiki:
1. 你've got the right idea about how to design your resources, IMHO. I wouldn'改变了一件事 .
2. 而不是尝试使用更多动词来扩展HTTP,而不是根据基本的HTTP方法和资源考虑您的建议动词可以减少到什么 . 例如,您可以设置以下资源而不是
activate_login
动词:/api/users/1/login/active
这是一个简单的布尔值 . 要激活登录,只需PUT
那里有'true'或1或其他的文件 . 要取消激活,PUT
文档为空或表示0或false .同样,要更改或设置密码,只需将
PUT
s设为/api/users/1/password
即可 .每当你需要添加一些东西(如信用卡)时,请考虑
POST
s . 例如,您可以使用/api/users/1/credits
这样的资源执行POST
,其中包含要添加的信用数量的正文 . 可以使用相同资源上的PUT
覆盖值而不是添加 . 主体中带负数的POST
将减去,依此类推 .3. 我'd strongly advise against extending the basic HTTP status codes. If you can' t找到一个与您的情况完全匹配的,选择最接近的一个并将错误详细信息放在响应正文中 . 另外,请记住HTTP标头是可扩展的;您的应用程序可以定义您喜欢的所有自定义标头 . 例如,我工作的一个应用程序可能会在多种情况下返回
404 Not Found
. 我们只是添加了一个新的 HeadersX-Status-Extended
,其中包含我们的专有状态代码扩展,而不是让客户端解析响应主体 . 所以您可能会看到如下响应:这样,像Web浏览器这样的HTTP客户端仍然知道如何处理常规404代码,而更复杂的HTTP客户端可以选择查看
X-Status-Extended
标头以获取更具体的信息 .4. 对于身份验证,如果可以,我建议使用HTTP身份验证 . 但恕我直言,对你来说更容易's nothing wrong with using cookie-based authentication if that' .
REST基础知识
REST具有统一的接口约束,该约束声明REST客户端必须依赖于标准而不是实际REST服务的应用程序特定细节,因此REST客户端不会因轻微更改而中断,并且可能是可重用的 .
因此,REST客户端和REST服务之间存在 Contract . 如果您使用HTTP作为基础协议,则以下标准是 Contract 的一部分:
HTTP 1.1
方法定义
状态代码定义
缓存控制头
接受和内容类型标头
auth标头
IRI(utf8 URI)
身体(挑一个)
注册的特定于应用程序的MIME类型,例如maze+xml
供应商特定的MIME类型,例如vnd.github+json
通用MIME类型
特定于应用程序的RDF词汇,例如ld+json&hydra,schema.org
应用程序特定的配置文件,例如hal+json&profile link param(我猜)
超链接
应该包含什么(选择一个)
发送link headers
发送超媒体响应,例如html,atom xml,hal json,ld json&hydra等...
语义
使用IANA链接关系以及可能的自定义链接关系
使用特定于应用程序的RDF词汇表
REST具有无状态约束,它声明REST服务和客户端之间的通信必须是无状态的 . 这意味着REST服务无法维护客户端状态,因此您无法拥有服务器端会话存储 . 您必须验证每个请求 . 因此,例如HTTP基本身份验证(HTTP标准的一部分)是可以的,因为它会在每次请求时发送用户名和密码 .
回答你的问题
仅仅提及,客户端不关心IRI结构,他们关心语义,因为它们遵循具有链接关系或链接数据(RDF)属性的链接 .
IRI唯一重要的是,单个IRI必须只识别一个资源 . 允许单个资源(如用户)拥有许多不同的IRI .
为什么我们使用像
/users/123/password
这样的漂亮IRI非常简单;只需阅读IRI就可以更容易地在服务器上编写路由逻辑 .activate_login -> PUT /login/active true deactivate_login -> PUT /login/active false change_password -> PUT /user/xy/password "newpass" add_credit -> POST /credit/raise {details: {}}
(由于无状态约束,从REST角度来看,登录没有意义 . )
HTTP状态标头是您的标准标头 . 我认为其他一切都应该在身体里 . 单个标头不足以描述详细的多语言错误消息 .
如果用户使用主客户端授予访问权限,则第三方客户端将获取访问令牌 . 之后,第三方客户端会向每个请求发送访问令牌 . 有更复杂的解决方案,例如您可以对每个请求进行签名等 . 有关详细信息,请查看OAuth手册 .
相关文献
Architectural Styles and the Design of Network-based Software Architectures
罗伯特·托马斯·菲尔丁(REST作者)的论文
2000年,加州大学欧文分校
Third Generation Web APIs - Bridging the Gap between REST and Linked Data
Markus Lanthaler的论文(JSON-LD的合着者和Hydra的作者)
2014年,奥地利格拉茨科技大学
我建议(作为第一遍),
PUT
应仅用于更新现有实体 .POST
应该用于创建新的 . 即对我不合适 . 但是,第一部分的其余部分(重新使用动词)看似合乎逻辑 .
简单地说,你完全是落后的 .
您不应该从您应该使用的URL接近这个 . 一旦确定了系统所需的资源以及如何表示这些资源以及资源和应用程序状态之间的交互,URL将“免费”实现 .
引用Roy Fielding
人们总是从URI开始并认为这是解决方案,然后他们倾向于错过REST架构中的关键概念,特别是如上所述,“这里的失败意味着带外信息正在推动交互而不是超文本 . “
说实话,很多人看到一堆URI和一些GET,PUT和POST,并认为REST很容易 . REST并不容易 . RPC over HTTP很容易,通过HTTP有效负载来回移动数据blob简单 . 然而,REST不仅限于此 . REST与协议无关 . HTTP非常受欢迎,适用于REST系统 .
REST存在于媒体类型,它们的定义以及应用程序如何通过超文本(链接,有效)驱动这些资源可用的操作 .
关于REST系统中的媒体类型有不同的观点 . 一些人喜欢特定于应用程序的有效载荷,而另一些人喜欢将现有媒体类型提升到适合应用程序的角色 . 例如,一方面,您具有适合您的应用程序的特定XML模式,而不是使用XHTML等代表,可能通过微格式和其他机制 .
我认为,这两种方法都有它们的位置,XHTML在与人为驱动和机器驱动的Web重叠的场景中运行良好,而前者,更具体的数据类型,我觉得更好地促进机器到机器的交互 . 我发现商品格式的提升可能使内容谈判变得非常困难 . “application / xml yourresource”作为媒体类型比“application / xhtml xml”更具体,因为后者可以应用于许多有效载荷,这些有效载荷可能是也可能不是机器客户端实际感兴趣的东西,也不能确定内省 .
但是,XHTML在人类网络中的工作非常好(显然),Web浏览器和渲染非常重要 .
您的应用程序将指导您做出这些决定 .
设计REST系统的部分过程是发现系统中的第一类资源,以及支持主资源操作所需的衍生支持资源 . 一旦发现资源,那么这些资源的表示,以及状态图显示由于下一个挑战而在表示中通过超文本的资源流 .
回想一下,在超文本系统中,资源的每个表示都将实际资源表示与资源可用的状态转换相结合 . 将每个资源视为图中的节点,其中链接是将该节点留给其他状态的行 . 这些链接不仅告知客户可以做什么,而且还要告知客户需要做什么(因为良好的链接结合了所需的URI和媒体类型) .
例如,您可能有:
您的文档将讨论名为“users”的rel字段以及“application / xml youruser”的媒体类型 .
这些链接看起来似乎是多余的,它们都是相同的URI,几乎就是这样 . 但他们不是 .
这是因为对于“用户”关系,该链接正在讨论用户集合,您可以使用统一接口来处理集合(GET以检索所有这些,删除以删除所有这些,等等)
如果您发布到此URL,则需要传递“application / xml usercollection”文档,该文档可能只包含文档中的单个用户实例,因此您可以添加用户,也可以不添加多个用户实例 . . 也许您的文档会建议您只需传递单个用户类型,而不是集合 .
您可以查看应用程序执行搜索所需的内容,如“搜索”链接及其媒体类型所定义 . 搜索媒体类型的文档将告诉您这种行为的表现以及结果的预期结果 .
但是,这里的内容是URI本身基本上不重要 . 应用程序控制URI,而不是客户端 . 除了一些“入口点”之外,您的客户还应该依赖应用程序提供的URI来完成其工作 .
客户需要知道如何操纵和解释媒体类型,但不需要关心它的去向 .
这两个链接在客户眼中在语义上是相同的:
所以,专注于你的资源 . 关注应用程序中的状态转换以及最佳实现方式 .
详细,但从HTTP 1.1方法规范复制http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
9.3 GET
GET方法意味着检索由Request-URI标识的任何信息(以实体的形式) . 如果Request-URI引用数据生成过程,则生成的数据应作为响应中的实体而不是过程的源文本返回,除非该文本恰好是过程的输出 .
如果请求消息包括If-Modified-Since,If-Unmodified-Since,If-Match,If-None-Match或If-Range头字段,则GET方法的语义变为“条件GET” . 条件GET方法请求仅在条件头字段描述的情况下传送实体 . 条件GET方法旨在通过允许刷新缓存的实体而不需要多个请求或传输客户端已经拥有的数据来减少不必要的网络使用 .
如果请求消息,则GET方法的语义变为“部分GET”包括Range标头字段 . 部分GET请求仅传输实体的一部分,如第14.35节所述 . 部分GET方法旨在通过允许完成部分检索的实体而不传输客户端已经拥有的数据来减少不必要的网络使用 .
当且仅当它满足第13节中描述的HTTP缓存要求时,对GET请求的响应才是可缓存的 .
有关用于表单的安全注意事项,请参见第15.1.3节 .
9.5 POST
POST方法用于请求源服务器接受请求中包含的实体作为Request-URI中Request-URI标识的资源的新下级 . POST旨在允许统一的方法来涵盖以下功能:
POST方法执行的实际功能由服务器确定,通常依赖于Request-URI . 发布的实体从属于该URI,其方式与文件从属于包含它的目录相同,新闻文章从属于发布它的新闻组,或者记录从属于数据库 .
POST方法执行的操作可能不会生成可由URI标识的资源 . 在这种情况下,200(OK)或204(No Content)是适当的响应状态,具体取决于响应是否包括描述结果的实体 .
如果在源服务器上创建了资源,则响应应该是201(已创建)并包含描述请求状态的实体,并引用新资源和Location头(请参阅第14.30节) .
除非响应包含适当的Cache-Control或Expires头字段,否则对此方法的响应不可缓存 . 但是,303(请参阅其他)响应可用于指示用户代理检索可缓存资源 .
POST请求必须遵守8.2节中规定的消息传输要求 .
有关安全性的考虑,请参见第15.1.3节 .
9.6 PUT
PUT方法请求将所包含的实体存储在提供的Request-URI下 . 如果Request-URI引用已经存在的资源,则封闭的实体应该被视为驻留在源服务器上的实体的修改版本 . 如果Request-URI未指向现有资源,并且该URI能够被请求用户代理定义为新资源,则源服务器可以使用该URI创建资源 . 如果创建了新资源,则源服务器必须通过201(已创建)响应通知用户代理 . 如果修改了现有资源,则应该发送200(OK)或204(No Content)响应代码以指示请求成功完成 . 如果无法使用Request-URI创建或修改资源,则应该给出适当的错误响应,以反映问题的性质 . 实体的接收者绝不能忽略它不理解或实现的任何Content- *(例如Content-Range)头,并且在这种情况下必须返回501(未实现)响应 .
如果请求通过缓存并且Request-URI标识一个或多个当前缓存的实体,那么这些条目应该被视为陈旧 . 对此方法的响应不可缓存 .
POST和PUT请求之间的根本区别体现在Request-URI的不同含义上 . POST请求中的URI标识将处理所包含实体的资源 . 该资源可能是数据接受过程,某些其他协议的网关或接受注释的单独实体 . 相反,PUT请求中的URI标识请求附带的实体 - 用户代理知道URI的用途,并且服务器不得尝试将请求应用于其他资源 . 如果服务器希望将请求应用于不同的URI,
它必须发送301(永久移动)响应;然后,用户代理可以自己决定是否重定向请求 .
单个资源可以由许多不同的URI标识 . 例如,文章可能具有用于标识“当前版本”的URI,该URI与标识每个特定版本的URI分开 . 在这种情况下,对一般URI的PUT请求可能会导致原始服务器定义其他几个URI .
HTTP / 1.1没有定义PUT方法如何影响原始服务器的状态 .
PUT请求必须遵守8.2节中规定的消息传输要求 .
除非为特定实体标头另外指定,否则PUT请求中的实体标头应该应用于由PUT创建或修改的资源 .
9.7删除
DELETE方法请求源服务器删除Request-URI标识的资源 . 可以通过源服务器上的人为干预(或其他方式)覆盖此方法 . 客户无法保证即使从源服务器返回的状态代码表明操作已成功完成,操作也已执行 . 但是,服务器不应该指示成功,除非在给出响应时,它打算删除资源或将其移动到不可访问的位置 .
如果响应包括描述状态的实体,则成功响应应为200(OK),如果操作尚未执行,则应为202(已接受);如果操作已颁布但响应不包括,则应为204(无内容)一个实体 .
如果请求通过缓存并且Request-URI标识一个或多个当前缓存的实体,那么这些条目应该被视为陈旧 . 对此方法的响应不可缓存 .
我几天后才注意到这个问题,但我觉得我可以添加一些见解 . 我希望这对你的RESTful冒险有所帮助 .
Point 1: Am I understanding it right?
你明白了 . 这是RESTful架构的正确表示 . 您可以在Wikipedia中找到以下矩阵,非常有助于定义您的名词和动词:
处理 Collection URI时: http://example.com/resources/
GET :列出集合的成员,并使用其成员URI进行进一步导航 . 例如,列出所有待售汽车 .
PUT :含义定义为"replace the entire collection with another collection" .
POST :在集合中创建一个新条目,集合将自动分配ID . 创建的ID通常包含在此操作返回的数据中 .
DELETE :含义定义为"delete the entire collection" .
处理 Member URI时: http://example.com/resources/7HOU57Y
GET :检索以适当的MIME类型表示的集合的已寻址成员的表示形式 .
PUT :更新集合的已寻址成员或使用指定的ID创建它 .
POST :将被寻址的成员视为一个集合,并创建一个新的下属 .
DELETE :删除该集合的已寻址成员 .
Point 2: I need more verbs
通常,当您认为自己需要更多动词时,实际上可能意味着需要重新识别您的资源 . 请记住,在REST中,您始终在资源或资源集合上执行操作 . 您选择的资源对于您的API定义非常重要 .
Activate/Deactivate Login :如果要创建新会话,则可能需要将"the session"视为资源 . 要创建新会话,请使用POST到
http://example.com/sessions/
并使用正文中的凭据 . 要使它过期,请使用PUT或DELETE(可能取决于您是否打算保留会话历史记录)到http://example.com/sessions/SESSION_ID
.Change Password: 这次资源是"the user" . 你需要一个PUT到
http://example.com/users/USER_ID
与身体中的旧密码和新密码 . 您正在"the user"资源上执行操作,更改密码只是更新请求 . 它与关系数据库中的UPDATE语句非常相似 .这违背了一个非常核心的REST原则:HTTP动词的正确用法 . 任何GET请求都不应该留下任何副作用 .
例如,GET请求永远不应该在数据库上创建会话,返回带有新会话ID的cookie,或者在服务器上留下任何残留 . GET动词类似于数据库引擎中的SELECT语句 . 请记住,使用相同参数请求时,对具有GET动词的任何请求的响应应该是可缓存的,就像您请求静态网页时一样 .
Point 3: How to return error messages and codes
将4xx或5xx HTTP状态代码视为错误类别 . 您可以详细说明正文中的错误 .
Failed to Connect to Database: / Incorrect Database Login :通常,您应该对这些类型的错误使用500错误 . 这是服务器端错误 . 客户没有做错任何事 . 500错误通常被视为"retryable" . 即客户端可以重试相同的确切请求,并且一旦服务器的故障得到解决,它就会成功 . 指定正文中的详细信息,以便客户端能够为我们的人类提供一些上下文 .
另一类错误是4xx系列,这通常表明客户端做错了什么 . 特别是,这类错误通常向客户端表明不需要按原样重试请求,因为它将继续永久失败 . 即,客户端需要在重试此请求之前更改某些内容 . 例如,“未找到资源”(HTTP 404)或“格式错误的请求”(HTTP 400)错误将属于此类别 .
Point 4: How to do authentication
正如第1点所指出的,您可能想要考虑创建会话,而不是对用户进行身份验证 . 您将返回一个新的“会话ID”,以及相应的HTTP状态代码(200:授予访问权限或403:访问被拒绝) .
然后,您将询问您的RESTful服务器:“您能获取此会话ID的资源吗?” .
没有经过身份验证的模式 - REST是无状态的:您创建会话,要求服务器使用此会话ID作为参数为您提供资源,并在注销时丢弃或使会话失效 .