REST API 设计最佳实践:如何正确使用 HTTP 状态码?

简介: 本文分享在设计 REST API 时的最佳实践。关于设计优秀REST API 的一些建议、提示和指导,帮助您让消费者(以及开发人员)满意。我们都应该努力使API变得易于使用。无论是对于消费者,还是我们自己的开发人员同伴。希望这篇文章能帮助你学到一些技巧,并激发出构建更好REST API的方法。

总的来说,HTTP协议出现以来Web服务也就存在了。但是,自从云计算出现后,才成为实现客户端与服务和数据交互的普遍方法。

作为一名开发者,我很幸运能够在工作中使用一些仍然存在的SOAP服务。但是,我主要接触的是REST,这是一种基于资源的API和Web服务开发架构风格。在我的职业生涯中有很大一部分时间都参与了构建、设计和使用API 的项目。我见过的大多数API 都“声称” 是 “符合REST原则”的——意味着遵循 REST 架构的原则和约束。但是,我也曾遇到过一些让 REST 蒙羞的 API 例子,错误使用 HTTP 状态码、纯文本响应、不一致的模式、插入端点中动词...

因此我决定写篇文章分享一下,在设计 REST API 时的最佳实践。以下是关于设计优秀REST API 的一些建议、提示和指导,帮助您让消费者(以及开发人员)满意。


1. 学习 HTTP 基础知识

如果你想构建一个设计良好的REST API,那么你必须了解HTTP协议的基本知识。我坚信这将帮助你做出正确的设计选择。Mozilla Developer Network文档上关于HTTP概述是一个相当全面的参考资料,尽管如此,在REST API设计方面,以下是将HTTP应用于RESTful设计的简要说明:

  • HTTP具有动词(操作或方法):最常见的是GET、POST、PUT、PATCH和DELETE。

  • REST以资源为导向,资源由URI表示:/library/

  • 端点(endpoint)是动词和URI的组合,例如:GET: /books/

  • 端点可以理解为对资源执行的操作。例如:POST: /books/可能意味着“创建一本新书”。

  • 高一层次来看,动词映射到CRUD操作:GET表示读取,POST表示创建,PUT和PATCH表示更新,DELETE表示删除

  • 响应状态由其状态码指定:1xx 表示信息, 2xx 表示成功, 3xx 表示重定向, 4xx 表示客户端错误 和5xx 表示服务器错误

当然你还可以使用其他 HTTP 协议提供给 REST API 设计的功能 ,但这些都必须牢记在心里。


2. 不要返回纯文本

尽管并非强制规定的,但大多数REST API通常约定使用JSON作为数据格式。然而,仅返回包含JSON格式字符串的响应体是不够好的。您还应该指定Content-Type标头。它必须设置为application/json值。

在处理应用程序/编程客户端(例如,通过Python中的requests库与您的API交互的另一个服务/API)时,这一点尤为重要——其中一些客户端依赖于此标头来准确解码响应。


3. 不要在 URI 中使用动词

到目前为止,如果您已经理解了基本概念,那么您会开始意识到在URI中放置动词是不符合RESTful的,这是因为HTTP动词应该足以准确描述正在对资源执行的操作。

示例:假设您要提供一个端点来生成和检索一本书的封面。我将注意到:param 是一个URI参数(如ID或缩写)的占位符,你第一个想法可能是创建类似于这个的端点:

GET: /books/:slug/generateBookCover/

但是,在这里GET方法在语法上足以说明我们正在获取(“GET”)一本书的封面。所以,让我们只使用:

GET: /books/:slug/bookCover/

同样,对于创建新书的端点:

#Don’t do this
POST: /books/createNewBook/
#Do this
POST: /books/


4. 使用复数名词表示资源

我们应该使用 /book/:id/ (单数) 还是 /books/:id/ (复数)?我个人建议使用复数形式。为什么?因为它非常适合所有类型的端点。

我可以看到 GET /book/2/ 是没问题的。但是 GET /book/ 呢?我们是在获取图书馆里唯一的那本书、其中几本还是全部?为了避免这种模棱两可的情况,让我们保持一致(💡软件职业建议!)并在所有地方都使用复数:

GET: /books/2/
POST: /books/
...


5. 在响应体中返回错误详情

当API服务器处理错误时,将错误详细信息包含在JSON主体中可以帮助使用者进行调试,这是是非常方便的,如果您还能说明哪些字段受到了错误的影响,那就更好了!

{
    "error": "Invalid payload.",
    "detail": {
        "name": "This field is required."
    }
}


6. 特别关注 HTTP 状态码

这一点非常重要,如果你从这篇文章中只记住一件事,那可能就是它了。

你的API最糟糕的事情莫过于返回一个带有200 OK状态码的错误响应。

这是最差的语义,相反,应该返回一个能准确描述错误类型的有意义HTTP状态码。尽管如此,你可能还在想:“但我按照您推荐的方式,在响应体中发送了错误详细信息,那么问题出在哪里呢?”

让我给你讲个故事吧。曾经我不得不集成一个API,它对每个响应都返回200 OK,并通过status字段来表示请求是否成功:

{
    "status": "success",
    "data": {}
}

尽管HTTP状态码返回200 OK,但我不能完全确定它有没有处理我的请求失败。

实际上,API可以返回如下响应:

HTTP/1.1 200 OK
Content-Type: text/html{
    "status": "failure",
    "data": {
        "error": "Expected at least three items in the list."
    }
}

因此,我必须检查状态代码和临时状态字段,以确保一切正常后才能读取数据。太烦人了!

这种设计真的很糟糕,因为它破坏了API与其使用者之间的信任关系,你会担心API可能在欺骗你。所有这些都极不符合RESTful风格。那么你应该怎么做呢?利用HTTP状态码,并且只在响应体中提供错误详细信息。

HTTP/1.1 400 Bad Request
Content-Type: application/json{
    "error": "Expected at least three items in the list."
}


7. 你应该始终保持一致地使用 HTTP 状态码

一旦你掌握了HTTP状态码,就应该力求始终如一地使用它们。例如,如果你选择某个POST端点返回201 Created,那么对于每个POST端点都应使用相同的HTTP状态码。为什么?因为消费者不应该担心在哪种情况下哪个方法在哪个端点上会返回哪个状态码。

所以,请保持可预测性(一致性)。如果必须偏离约定,请在某处用大标志记录下来。通常,我遵循以下模式:

GET: 200 OK
PUT: 200 OK
POST: 201 Created
PATCH: 200 OK
DELETE: 204 No Content


8. 不要嵌套资源

您可能已经注意到,REST API处理的是资源。检索资源列表或单个实例非常简单,但是,当处理相关资源时会发生什么呢?例如,假设我们想要检索特定作者(名为Cagan)的书籍列表。基本上有两个选择。

第一个选项是将books资源嵌套在authors资源下面,例如:

GET: /authors/Cagan/books/

一些架构师推荐这种约定,因为它确实表示了作者与其书籍之间的一对多关系。但是,现在不再清楚您请求的是哪种类型的资源。 是作者吗?还是书籍?...而且扁平化总比嵌套好,所以肯定有更好的方法... 确实如此!我个人建议使用查询字符串参数直接过滤books资源:

GET: /books?author=Cagan

这显然意味着:“获取所有名为Cagan 的作者所写的书”,对吧。


9. 优雅地处理尾部斜杠

关于URI是否应该有尾随斜杠/实际上并不是一个值得争论的问题,你只需要选择其中一种方式(即带或不带尾随斜杠),坚持使用它,并在客户端使用错误约定时优雅地重定向。

讲个故事吧! 有一天,当我将REST API集成到我的一个项目中时,每次调用都收到HTTP 500内部错误。我所使用的端点看起来像这样:

POST: /buckets

当时我非常生气,怎么也想不明白究竟哪里出了问题。最后,原来是因为缺少了尾随斜杠导致服务器出错!于是,我开始使用:

POST: /buckets/

然后一切都顺利进行了。API没有修复,但希望您可以防止消费者遇到此类问题。专业提示:大多数基于网络的框架(Angular、React等)都有一个选项可以优雅地重定向至带或不带尾随斜杠的URL版本。找到那个选项并尽早激活。


10. 利用查询字符串进行筛选和分页

大多数情况下,一个简单的端点无法满足各种复杂的业务场景。您的用户可能希望检索满足特定条件的项目,或者一次只检索少量数据以提高性能,这正是过滤和分页功能所设计的目标。

通过过滤,消费者可以指定返回项目应具有哪些参数(或属性)。分页允许用户逐步获取数据集。最简单类型的分页就是按页码进行分页,它由pagepage size确定。现在问题来了:如何将这样的功能融入REST API?

我的答案是:使用查询字符串(querystring)。

我认为使用查询字符串实现分页非常明显。它看起来像这样:

GET: /books?page=1&page_size=10

但对于过滤来说可能不那么明显。首先,你可能会想做类似以下操作以仅检索已发布书籍列表:

GET: /books/published/

设计问题:published 不是资源!相反,它是您要检索数据所具备特征。此类内容应放在查询字符串中。因此最后, 用户可以像这样获取“包含20个项目、已发布书籍第二页”:

GET: /books?published=true&page=2&page_size=10

美观且清晰易懂,不是吗?


11. 了解401未授权和403禁止之间的区别

如果我每看到一次开发人员甚至有经验的架构师搞砸这个问题就能得到一个25美分硬币……在处理REST API中的安全错误时,很容易弄混错误是与身份验证还是授权(又称权限)相关 - 我以前总是遇到这种情况。根据不同情况,以下是我的备忘单,用于了解我正在处理什么问题:

  • 消费者没有提供身份验证凭据吗?他们的SSO令牌是否无效/超时?👉 401 未授权。

  • 消费者正确地进行了身份验证,但他们没有访问资源所需的权限/适当的许可吗?👉 403 禁止。


12. 充分利用 HTTP 202 Accepted

我认为202 Accepted是一个非常方便的替代201 Created的选项。它基本上意味着:

我,服务器,已经理解了你的请求。虽然我还没有创建资源(尚未),但这没问题。

有两个主要场景,我觉得202 Accepted特别适用:

  1. 如果资源将在未来处理后被创建 — 例如:在某个工作/流程完成之后。

  2. 如果资源以某种方式已经存在,但这不应被视为错误。


13. 使用专门针对REST API的网络框架

作为最后一个最佳实践,让我们讨论这个问题:如何在您的API中实际应用最佳实践?大多数时候,您希望建立一个快速的API,以便一些服务可以相互交互。Python开发者会选择Flask,JavaScript开发者会选择Node(Express),然后他们会实现一些简单的路由来处理HTTP请求。

这种方法的问题在于,通常情况下,框架并不是针对构建REST API服务器而设计的。例如,Flask和Express都是两个非常灵活的框架,但它们并没有专门为帮助您构建REST API而制定。因此,在API中应用最佳实践需要采取额外措施。而且大多数时候, 懒惰或缺乏时间意味着你不会付出努力——从而使你的消费者面临一个古怪的API。

解决方案很简单:使用合适工具完成任务。

各种语言中已经出现了新框架, 它们专门用于构建REST APIs。它们能够帮助您轻松遵循最佳做法,并提高生产力。

在Python中, 我找到过其中之一优秀API框架就是Falcon。它与Flask一样简单易用,速度很快,非常适合在几分钟内构建REST API。

如果您更喜欢使用Django,那么首选就是Django REST框架。虽然它不如其他框架直观,但功能非常强大。在Node中,Restify似乎也是一个很好的选择,尽管我还没有尝试过。我强烈建议您试一试这些框架,它们将帮助您构建美观、优雅且设计精良的REST API。


结束语

我们都应该努力使API变得易于使用。无论是对于消费者,还是我们自己的开发人员同伴。我希望这篇文章能帮助你学到一些技巧,并激发出构建更好REST API的方法。对我来说,这只是归结为良好的语义、简单性和常识。


【Eolink 翻译】,Eolink Apikit = API 管理 + Mock + 自动化测试 + 异常监控 + 团队协作的一站式 API 生产平台,是一个跨平台(Windows、Mac、Linux、Browsers...)的 API 开发测试工具,支持 REST、Websocket、gRPC、TCP、UDP、SOAP等协议。

微光计划20230704 无.jpg

相关文章
|
3天前
|
存储 安全 Java
Spring Boot 编写 API 的 10条最佳实践
本文总结了 10 个编写 Spring Boot API 的最佳实践,包括 RESTful API 设计原则、注解使用、依赖注入、异常处理、数据传输对象(DTO)建模、安全措施、版本控制、文档生成、测试策略以及监控和日志记录。每个实践都配有详细的编码示例和解释,帮助开发者像专业人士一样构建高质量的 API。
|
19天前
|
监控 数据管理 测试技术
API接口自动化测试深度解析与最佳实践指南
本文详细介绍了API接口自动化测试的重要性、核心概念及实施步骤,强调了从明确测试目标、选择合适工具、编写高质量测试用例到构建稳定测试环境、执行自动化测试、分析测试结果、回归测试及集成CI/CD流程的全过程,旨在为开发者提供一套全面的技术指南,确保API的高质量与稳定性。
|
21天前
|
API
获取网页状态码[可指定地域]免费API接口教程
该接口用于获取指定网址的访问状态码,支持从国内、香港、美国等地域节点访问。通过POST或GET请求,需提供用户ID、KEY及目标网址等参数。返回结果包括状态码和信息提示。 示例:https://cn.apihz.cn/api/wangzhan/getcode.php?id=88888888&key=88888888&type=1&url=www.apihz.cn。
|
25天前
|
监控 安全 API
深入浅出:构建高效RESTful API的最佳实践
在数字化时代,API已成为连接不同软件和服务的桥梁。本文将带你深入了解如何设计和维护一个高效、可扩展且安全的RESTful API。我们将从基础概念出发,逐步深入到高级技巧,让你能够掌握创建优质API的关键要素。无论你是初学者还是有经验的开发者,这篇文章都将为你提供实用的指导和启示。让我们一起探索API设计的奥秘,打造出色的后端服务吧!
|
1月前
|
SQL 缓存 测试技术
构建高性能RESTful API:最佳实践与避坑指南###
—— 本文深入探讨了构建高性能RESTful API的关键技术要点,从设计原则、状态码使用、版本控制到安全性考虑,旨在为开发者提供一套全面的最佳实践框架。通过避免常见的设计陷阱,本文将指导你如何优化API性能,提升用户体验,确保系统的稳定性和可扩展性。 ###
59 12
|
1月前
|
安全 Java API
告别SimpleDateFormat:Java 8日期时间API的最佳实践
在Java开发中,处理日期和时间是一个基本而重要的任务。传统的`SimpleDateFormat`类因其简单易用而被广泛采用,但它存在一些潜在的问题,尤其是在多线程环境下。本文将探讨`SimpleDateFormat`的局限性,并介绍Java 8引入的新的日期时间API,以及如何使用这些新工具来避免潜在的风险。
34 5
|
1月前
|
数据可视化 API 索引
ES常见Index API操作最佳实践!
【10月更文挑战第21天】
90 1
ES常见Index API操作最佳实践!
|
1月前
|
JSON API 开发者
构建高效API:后端开发中的RESTful最佳实践####
在数字化时代,API作为不同系统间通信的桥梁,其重要性日益凸显。本文将深入探讨RESTful API的设计原则与最佳实践,通过实际案例分析,揭示如何构建高效、可维护且易于使用的API接口,助力后端开发者提升项目质量与用户体验。 ####
|
1月前
|
JSON 缓存 API
构建高效RESTful API的最佳实践
【10月更文挑战第34天】在数字时代的浪潮中,后端开发扮演着至关重要的角色。本文将带你深入探索如何构建高效的RESTful API,从设计原则到实际编码技巧,再到性能优化和错误处理,我们将一一解锁这些技能。你将学会如何打造一个既优雅又强大的后端服务,让你的应用程序在激烈的市场竞争中脱颖而出。那么,让我们一起踏上这段精彩的旅程吧!
35 2
|
2月前
|
机器学习/深度学习 PyTorch 算法框架/工具
揭秘深度学习中的微调难题:如何运用弹性权重巩固(EWC)策略巧妙应对灾难性遗忘,附带实战代码详解助你轻松掌握技巧
【10月更文挑战第1天】深度学习中,模型微调虽能提升性能,但常导致“灾难性遗忘”,即模型在新任务上训练后遗忘旧知识。本文介绍弹性权重巩固(EWC)方法,通过在损失函数中加入正则项来惩罚对重要参数的更改,从而缓解此问题。提供了一个基于PyTorch的实现示例,展示如何在训练过程中引入EWC损失,适用于终身学习和在线学习等场景。
128 4
揭秘深度学习中的微调难题:如何运用弹性权重巩固(EWC)策略巧妙应对灾难性遗忘,附带实战代码详解助你轻松掌握技巧

热门文章

最新文章

下一篇
DataWorks