RESTful API 设计指南——为什么要用(上)

news2024/11/21 2:33:14

引言

在上一篇中:RESTful API 设计指南——开篇词

我们介绍了几个十分有争议的案例:

  • 所有的接口都使用Post请求
  • 不管成功还是失败,HTTP状态码都返回200
  • API命名千奇百怪

本章我们来深入分析一下,为什么不要像案例中所说的那样干?

RESTful 规范能解决这些问题吗?

问题浅析

为什么不要所有的接口都使用POST

我们首先来看一下 GET 和 POST 的区别(来源文章):

  • GET 请求有长度限制(数据放在URL中),而 POST 没有
  • GET 数据都放在 url 中,复制到浏览器可以直接打开(各种分享功能),而 POST 数据放在 body 中,无法分享
  • GET 可以被缓存(CDN),能减少服务器压力,而 POST 请求永远不会被缓存
  • GET 请求会被保存到浏览历史记录中,POST 不可以
  • GET 请求可以使用回退/刷新功能,而 POST 通常不可以
  • GET 请求可以被保存为书签,POST 不行
  • GET 请求不应该包含有敏感数据,因为它可以在地址栏、书签和浏览历史中看到
  • GET 请求仅用于查询数据,而不是修改

image.png

通过上面的对比,我们看到 GET 和 POST 都有各自的使用场景,假设你要做一个分享功能,或者一个博客,那么可以被添加为书签或者能直接在浏览器地址粘贴打开是必须的,否则将会丢失很多从收藏夹过来的阅读量。

所以,在早期的 MVC 时代(前后端在一起),肯定不能所有的请求都是 POST,否则有一些功能将会无法实现。

那么在当下前后端分离的时代呢?后端接口通常是提供给前端使用的,用户不会直接接触到这些后端接口,可以都使用 POST 请求吗?

在前后端分离架构下,团队分工发生了变化:由 Java 干所有事情,变成了前端开发 + 后端开发的组合,这意味分工的精细化,前端除了功能实现,还会关注界面美观和用户体验;后端会关注性能、并发和可用性等一些非功能需求。从这个角度而言 GET 能使用 CDN 缓存,限制 GET 只用来查询,POST用来修改,从而可以通过读写分离机制提升整体服务的性能和可用性就很关键了。

所以,在前后端分离和微服务架构的时代的今天,功能只是整个后端开发中的一部分,有时甚至非功能需求的工作量远远大于功能性需求,所以,不太可能出现所有接口都使用 POST 请求这种情况。

另外,伴随着云计算时代的到来, K8S 和 Docker 容器化部署几乎成为了后端的必备技能之一,而 K8S 其中的一个机制:健康检查就要求服务提供一个 HTTP GET 接口,返回200 OK 代表服务已准备就绪,K8S 才会开放流量给该进程。所以,也不可能只提供 POST 接口的。

当然,微服务中还会伴随各种疑难问题,比如监控、压测、流控、路由和权限等等,可以参考这篇文章了解:来源文章,也没办法只使用 POST 请求。

为什么要区分 HTTP 状态码

在上一篇文章: RESTful API 设计指南——开篇词 中,我们有说过,如果不区分 HTTP 状态码,不管成功还是失败都返回200 OK,这样做最大的问题是:监控系统在一种低效的状态下工作。

原因是监控系统需要解析你的 Body,才能知道是调用方出错还是服务端。这里的监控系统只是一个概念,泛指指基础设施或者第三方监控系统,可以是 nginx、k8s 的 ingress 、api 网关(kong 和 Spring Cloud Gateway等)或者 promethus 等等。

这里我们以大家熟知的 Nginx 举例来说明,它在什么地方会用到 HTTP 状态码。

Nginx是主流的Web服务器之一,除了可以做 HTTP 或 TCP 代理之外,最常用的一个功能就是做负载均衡。

Nginx 中实现负载均衡的是 upstream(ngx_http_upstream_module) 模块,比如下面的例子:

upstream test {
    server 10.78.3.1:80 fail\_timeout=60s max\_fails=2; # Server A
    server 10.78.3.2:80 fail\_timeout=60s max\_fails=2; # Server B
    server 10.78.3.3:80 backup; # Server C
}
  • 配置了3个节点,默认负载均衡算法是轮询,意味着请求会被均摊给 A B C 三台服务器
  • 每个 Server 还可以单独配置策略,比如上面虽然有3台服务器,但是 C 因为指定了 backup ,所以只有当 A B都繁忙时才会被分配请求,正常情况下,它的压力最小

upstream 是一个非常强大的模块,除了轮询负载算法还支持 ip_hash、fair、url_hash 等算法,另外每个节点支持很多配置:

  • down:表示当前的 server 临时不參与负载
  • weight:表示负载的权重,配置的越大,分配到的请求数就越多
  • max_fails :请求失败的次数,当超过最大次数时,返回 proxy_next_upstream 模块定义的错误
  • fail_timeout : max_fails 次失败后,暂停请求的时间,这期间该服务不会收到任何请求

更多关于 upstream模块 的介绍可以参考官网和这篇文章:nginx upstream配置说明负载均衡

upstream 模块通常搭配 http代理模块(ngx_http_proxy_module)使用,用来实现 HTTP 代理转发(还支持4层代理,如TCP和UDP等),比如下面的例子:

http {
    # 负载均衡,名称是 test
    upstream test {
        server 10.78.3.1:80 fail\_timeout=60s max\_fails=2; # Server A
        server 10.78.3.2:80 fail\_timeout=60s max\_fails=2; # Server B
        server 10.78.3.3:80 backup;                       # Server C
    }

    server {
       listen 9998;
       server\_name pub\_api;
       # 转发到 test 负载均衡上
       location / {
           root html;
           proxy\_pass <http://test>;
       }
    }
}

我们配置了一个具有3个节点的 Web 服务器集群,通过 9998 端口统一对外暴露服务,这样上层系统就只需要知道一个 IP 地址,并且当其中某一个节点出问题时,Nginx 会通过负载均衡算法帮我们把请求转发到其他可用的节点上。

那这里和 HTTP 状态码有什么关系呢?

在上面的 location 节点中,我们还可以配置各种超时和重试策略:

location / {
    root html;
    proxy\_connect\_timeout 5s;
    proxy\_read\_timeout 100s;
    proxy\_send\_timeout 70s;
    proxy\_next\_upstream error timeout;
    proxy\_next\_upstream\_timeout 70;
    proxy\_next\_upstream\_tries 1;
    proxy\_pass <http://efast-public>;
}

我们着重看一下失败重试的配置项 proxy_next_upstream。

根据 官方的文档,它的语法如下:

Syntax:proxy\_next\_upstream error | timeout | invalid\_header | http\_500 | http\_502 | http\_503 | http\_504 | http\_403 | http\_404 | http\_429 | non\_idempotent | off ...;
Default:proxy\_next\_upstream error timeout;
Context:http, server, location
  • error:读写出错
  • timeout:超时
  • invalid_header:头信息有误
  • non_idempotent:RFC-2616定义的非幂等HTTP方法(POST、LOCK、PATCH),也可以在失败后重试(默认幂等方法GET、HEAD、PUT、DELETE、OPTIONS、TRACE)
  • off:表示禁用重试
  • http_500:当服务返回 500 错误码时,重试请求
  • http_403:当服务返回 403 错误码时,重试请求

发现了吗?

它可以根据 HTTP 状态码来决定是否需要重试,如果我们不区分 HTTP 状态码,不管成功还是失败,是客户端参数导致的错误,还是服务器内部的错误都返回200的话,我们是没有办法使用 nginx 失败重试的功能的,除非你修改它这个模块的源码!

API命名定义问题

还记得上文提到过的API命名问题吗?

以新增员工为例,因为开发习惯不同,可能会出现这些情况:

http://localhost/employee/save
http://localhost/employee/add
http://localhost/employee/new
http://localhost/employee/xinzeng
http://localhost/employee/append
http://localhost/employee?cmd=add
http://localhost/add_employee
  • 把新增翻译为 save、append、new,或者干脆用拼音
  • 把参数放在 query 中,比如通过 cmd=add 来代表具体的操作。
  • 直接在 path 中拼接2个单词

除了上述问题之外,这个新增员工接口是发 POST 还是 GET 请求?是通过 HTTP 状态码代表操作结果,还是需要解析 json 来判断?如果是解析 json ,那个字段代表成功?等等这些问题,需要我们仔细的查阅它的 API 文档才能下定论。

那么,RESTful API 是如何解决这些的问题的呢?

在《RESTful Web Services》这本书的第4章:面向资源的架构中,为我们做出了解答:统一接口!

客户端与资源之间的所有交互,都是通过为数不多的几个HTTP方法进行的。任何资源都将暴露这些方法的一个子集,而且这些方法无论被哪个资源暴露,都具有相同的意义。

即对于任何资源,增删改查统一使用 HTTP 方法来表示:

  • 发送 GET 请求,代表查询
  • 发送 PUT 请求,代表修改
  • 发送 POST 新增,代表新增
  • 发送 DELETE 请求,代表删除

如下是一个使用 HTTP 方法来代表操作的一个示例:

请求方法URL描述
GET/departments获取所有部门信息
POST/departments新增一个新的部门
GET/departments/{departmentid}获取指定部门详细信息
PUT/departments/{departmentid}更新指定部门信息
DELETE/departments/{departmentid}删除指定部门

所以,按照 RESTful API 规范,新增操作必须发送 POST 请求,至于具体是新增什么,需要由 url 来决定:

POST /employees        # 新增员工
POST /departments      # 新增部门
POST /roles            # 新增角色

至此,增删改查接口命名的问题解决了,在 RESTful API 中,看到 POST 就代表这是一个新增接口,看到 GET 就知道这个接口是用来查询的。至于具体的含义,需要结合资源名(path)来看。另外,资源通常使用名词表示,也就意味着 path 中不应该出现动词,关于这些我们会在后续的文章中详细说明。

总结

本章我们详细介绍了在 HTTP API 设计中可能会遇到的一些典型的非常具有代表性和争议的案例,我们应该极力避免案例中的做法。

对于“所有的接口都使用Post请求”的案例,我们给出了足够多避免这样做的理由,比如 POST 和 GET 的适用的场景不同,健康检查需要提供 GET 请求、不利于读写分离和监控系统配合等。

对于"不管成功还是失败,HTTP状态码都返回200”的案例,我们也通过介绍 Nginx 的 HTTP 代理功能,介绍了微服务时代下,服务和基础设施之间配合的问题。

对于“API命名千奇百怪”的案例,我们介绍了为什么引入 REST 规范能避免该问题,因为 RESTful API 统一了操作接口,增删改查使用 HTTP 方法表示,从此,对于各个团队,大家的玩法都一样了。

在本章,我们介绍了使用 RESTful API 风格的带来的收益和好处,下一章中,我们会介绍一下它的劣势,毕竟,软件世界中没有银弹。


作者简介:一线Coder,公众号《Go和分布式IM》运营者,开源项目: CoffeeChat 、interview-golang 发起人 & 核心开发者,终身程序员。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1232644.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

计算机基础知识54

ORM的介绍 # ORM是什么&#xff1f; 我们在使用Django框架开发web应用的过程中&#xff0c;不可避免地会涉及到数据的管理操作&#xff08;增、删、改、查&#xff09;&#xff0c;而一旦谈到数据的管理操作&#xff0c;就需要用到数据库管理软件&#xff0c;例如mysql、oracle…

TensorFlow实战教程(二十六)-什么是生成对抗网络GAN?基础原理和代码普及

从本专栏开始,作者正式研究Python深度学习、神经网络及人工智能相关知识。前一篇文章分享了Keras实现经典的深度学习文本分类算法,包括LSTM、BiLSTM、BiLSTM+Attention和CNN、TextCNN。这篇文章将详细介绍生成对抗网络GAN的基础知识,包括什么是GAN、常用算法(CGAN、DCGAN、…

【Unity细节】如何调节标签图标的大小(select icon)—标签图标太大遮住了物体

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 秩沅 原创 &#x1f636;‍&#x1f32b;️收录于专栏&#xff1a;unity细节和bug &#x1f636;‍&#x1f32b;️优质专栏 ⭐【…

Qt 基于海康相机的视频绘图

需求 在视频窗口上进行绘图&#xff0c;包括圆&#xff0c;矩形&#xff0c;扇形等 效果&#xff1a; 思路&#xff1a; 自己取图然后转成QImage &#xff0c;再向QWidget 进行渲染&#xff0c;根据以往的经验&#xff0c;无法达到很高的帧率。因此决定使用相机SDK自带的渲染…

利用 React 和 Bootstrap 进行强大的前端开发

文章目录 介绍React 和 Bootstrap设置环境使用 Bootstrap 创建 React 组件React-Bootstrap 组件结论 介绍 创建响应式、交互式和外观引人入胜的 Web 界面是现代前端开发人员的基本技能。幸运的是&#xff0c;借助 React 和 Bootstrap 等工具的出现&#xff0c;制作这些 UI 变得…

目标分割技术-语义分割总览

前言 博主现任高级人工智能工程师&#xff0c;曾发表多篇SCI且获得过多次国际竞赛奖项&#xff0c;理解各类模型原理以及每种模型的建模流程和各类题目分析方法。目的就是为了让零基础快速使用各类代码模型&#xff0c;每一篇文章都包含实战项目以及可运行代码。欢迎大家订阅一…

Simulia 2022 新功能

增材制造 达索系统增材制造解决方案实现了端到端一体化全流程解决方案&#xff0c;可以实现从原材料研究到创成式设计、工艺设计、工艺仿真仿真、并且还延续到增材制造完成后的热处理、线切割等工艺&#xff0c;涵盖了各个方面的内容。 达索系统针对增材制造各个环节在每一个…

python数据结构与算法-06_算法分析

算法复杂度分析 前面我们说了很多次时间复杂度是 O(1), O(n) 啥的&#xff0c;并没有仔细讲解这个 O 符号究竟是什么。 你可以大概理解为操作的次数和数据个数的比例关系。比如 O(1) 就是有限次数操作&#xff0c;O(n) 就是操作正比于你的元素个数。 这一章我们用更严谨的方式…

以makefile的方式在linux上编译代码(小白级别)

作者&#xff1a;爱塔居 作者简介&#xff1a;大四学生&#xff0c;分享自己的学习片段~ 目录 前言 一、创建主要文件 二、makefile 前言 多有不足&#xff0c;以供参考&#xff0c;欢迎大佬们指点。我是在虚拟机上执行的&#xff0c;应该都一样。我用的VirtualBox&#xff0c;…

PHP/Lerv通过经纬度计算距离获取附近商家

实际开发中,常常需要获取用户附近的商家,思路是 获取用户位置(经纬度信息)在数据库中查询在距离范围内的商家 注: 本文章内计算距离所使用地球半径统一为 6378.138 km public function mpa_list($latitude,$longitude,$distance){// $latitude 34.306465;// $longitude 10…

Redis篇---第十一篇

系列文章目录 文章目录 系列文章目录前言一、说说Redis持久化机制二、缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级等问题三、热点数据和冷数据是什么前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章…

Android WMS——输入系统管理(十七)

一、简介 1、工作原理 输入子系统从驱动文件中读取事件后,再封装提交给 IMS,IMS 再发送给 WMS 进行处理。 Android 输入系统的工作原理概括来说,内核将原始事件写入到设备节点中,InputReader 不断地通过 EventHub 将原始事件取出来并翻译加工成 Android 输入事件,…

抖音电商双11官方数据最全汇总!

11月13日&#xff0c;抖音电商数据发布“抖音商城双11好物节”数据报告&#xff0c;展现双11期间平台全域经营情况及大众消费趋势。 报告显示&#xff0c;10月20日至11月11日&#xff0c;抖音电商里的直播间累计直播时长达到5827万小时&#xff0c;挂购物车的短视频播放了1697亿…

引入 requests.codes 模块

在网络应用开发中&#xff0c;处理HTTP请求状态码是一项常见的任务。然而&#xff0c;使用Python的requests库时&#xff0c;我们会发现一个不便之处&#xff1a;requests库没有提供一个方便的方式来管理和引用HTTP请求状态码。 在使用requests库进行HTTP请求时&#xff0c;我…

jenkins传参给robotframework

在做自动化的时候&#xff0c;需要使用jenkins传参给rf&#xff0c;rf根据传来的变量运行&#xff0c;在将变量传递给py脚本文件。特此记录。 一、配置jenkins 构建的命令使用如下格式即可&#xff08;注意空格&#xff09;&#xff1a; cd D:\xxx\test call pybot --variabl…

【数据结构】【版本2.0】【树形深渊】——二叉树入侵

目录 引言 一、树的概念与结构 1.1 树的概念 1.2 树的相关概念 1.3 树的表示 1.4 树在实际中的运用 二、二叉树的概念与结构 2.1 二叉树的概念 2.2 特殊二叉树 满二叉树 完全二叉树 2.3 现实中的二叉树 2.4 二叉树的性质 2.5 二叉树的存储结构 顺序存储 链式…

这篇文章带你了解:如何一次性将Centos中Mysql的数据快速导出!!!

目录 一.数据库导出 1.首先创建文件以.sql结尾的文件 2.打开名mysq的解压目录&#xff0c;导出数据 3.然后在查看即可 4 需要的软件 MobaXterm 一.数据库导出 1.首先创建文件以.sql结尾的文件 通过 touch ssm.sql &#xff08;小编&#xff09; 实际上&#xff1a…

提升办公效率,畅享多功能办公笔记软件Notion for Mac

在现代办公环境中&#xff0c;高效的笔记软件对于提高工作效率至关重要。而Notion for Mac作为一款全能的办公笔记软件&#xff0c;将成为你事业成功的得力助手。 Notion for Mac以其多功能和灵活性而脱颖而出。无论你是需要记录会议笔记、管理项目任务、制定流程指南&#xf…

光谱图像超分辨率综述

光谱图像超分辨率综述 简介 ​ 论文链接&#xff1a;A Review of Hyperspectral Image Super-Resolution Based on Deep Learning UpSample网络框架 1.Front-end Upsampling ​ 在Front-end上采样中&#xff0c;是首先扩大LR图像&#xff0c;然后通过卷积网络对放大图像进行…

Linux安装rabbitMq(亲测可用)解决只能本地访问的问题

安装er https://blog.csdn.net/laterstage/article/details/131513793?spm1001.2014.3001.5501下载mq wget --content-disposition "https://packagecloud.io/rabbitmq/rabbitmq-server/packages/el/7/rabbitmq-server-3.10.0-1.el7.noarch.rpm/download.rpm?distro_v…