​ 把一些前后端概念性比较强的理念放在这里,不放代码,不提供案例,仅供理解核心概念参考。

  

Restful

restful架构:

每一个URI代表一种资源

客户端与服务器之间传递这种资源的表现层

客户端通过四个http动词对服务器端资源进行操作,实现表现层状态转化

restful中一般包含以下数据:

接口地址、请求方法、请求参数:即传输参数时要带什么参数、携带什么字段,规则是什么、返回内容:一般为json或者xml形式,错误代码

具体来说

域名:应该将api部署在专用域名之下,如https://api.example.com api很简单时考虑放在主域名下 https://example.org/api/

版本:应该将api的版本好放入URL中:https://api.example.com/v1/

路径: 每个路径/终点代表一种资源,所以api中不能有动词,只能有名词,而且所用名词往往与数据库的表名对应,如https://api.example.com/v1/zoos https://api.example.com/v1/animals

http动词:对于资源的不同操作类型使用不同的http方法

过滤信息:如果资源数据很多,服务器不可能都将它们返回给用户,api提供参数,过滤返回结果。?limit=10:指定返回记录的数量 ?offset:指定返回记录的开始位置 ?page=2&per——page=100 指定第几页,以及每页的记录数 ?sortby=name&order=asc:指定返回结果按照哪个属性排序以及排序顺序 ?animal——type——id=1:指定筛选条件

状态码:服务器向用户返回的状态码和提示信息

错误处理:如果状态码时4**,应该向用户返回错误信息,并将error作为键名

返回结果:不同的请求应该返回指定解构的数据,比如数组或者包含完整信息的对象

其他:api的身份认证应该使用oauth2.0框架、服务器返回的数据格式应该尽量使用json,避免使用xml

Swagger

swagger是rest API生成工具,swagger是一个完整的框架,用于生成、描述、调用和可视化restfulapi的web服务

swagger-parser

解析和验证后端生成的swagger文档,用来生成前端的接口类型

https://github.com/APIDevTools/swagger-parser

swagger-typescript-api

根据swagger文档生成ts类型

https://github.com/acacode/swagger-typescript-api

insomnia

Insomnia是一个跨平台的REST客户端,类似于postman,一般用于测试

Insomnia页面主要由4大块构成:菜单栏、接口栏、请求栏和响应栏,看起来更加清晰简洁

可以设置三级的配置文件,用于多个不同环境的配置。

首先最高级配置是Base Environment ,第二优先级配置是Sub Enviroments,在接口栏中创建文件夹,可以建立针对这个文件的环境,这是第三级配置

Insomnia会自动保存接口的访问结果,请求和响应数据都会完整的保存下来,需要的时候就可以再点开看。使用postman是不会保存请求的,有时候没有网络(比如使用内网时)就很不方便查看了。

iosomnia的缺点:

内置方法:

postman可以使用Pre-request Script 在请求前先定义好一些变量。Insomnia不支持java script,只能使用他内部的一些方法,或者下载插件。但是针对大多数的请求是可以满足的。比如加密、解密、获取时间等。

postman

apifox

Oauth

Authentication vs. Authorization

身份验证和授权是管理员用来保护系统和信息的两个重要信息安全过程。 【身份验证】验证用户或服务的身份,而【授权】确定其访问权限。 尽管这两个术语听起来都一样,但它们在保护应用程序和数据中扮演着单独但同样重要的角色。 了解差异至关重要。 结合在一起,它们决定了系统的安全性。 除非您正确地配置了身份验证和授权,否则您将没有安全的解决方案

身份验证(AuthN)是一个验证某人或某物(与系统中存储的信息相匹配)就是(系统中的)这个人或物的过程。 技术系统通常使用某种形式的身份验证来确保访问应用程序或其数据的访问。 例如,当您需要访问在线网站或服务时,通常必须输入用户名和密码。 然后,在幕后,它将您输入的用户名和密码与其数据库中的记录进行了比较。 如果您提交的信息匹配,则系统假定您是有效的用户,并且授予您访问的访问。 在此示例中,系统身份验证假定只有您才能知道正确的用户名和密码。 因此,它通过使用只有您知道的东西的原理来验证您

身份验证的目的是验证某人或某物是他们声称的是谁或什么。 有多种形式的身份验证。 例如,艺术界拥有确认绘画或雕塑的过程和机构是特定艺术家的作品。 同样,政府使用不同的身份验证技术来保护其货币免于伪造。 通常,身份验证保护有价值的项目,并且在信息时代,它保护系统和数据

身份验证是验证用户或服务的身份的过程。 基于此信息,系统然后为用户提供适当的访问。 例如,假设我们有两个人在咖啡店露西亚(Lucia)和拉胡尔(Rahul)工作。 露西亚(Lucia)是咖啡店经理,而拉胡尔(Rahul)是咖啡师。 咖啡店使用销售点(POS)系统,服务员和咖啡师可以下订单进行准备。 在此示例中,POS 将使用一些过程来验证 Lucia 或 Rahul 的身份,然后才能访问系统。 例如,它可能会要求他们提供用户名和密码,或者他们可能需要在指纹读取器上扫描拇指。 由于咖啡店需要确保访问其 POS,因此使用该系统的员工需要通过身份验证过程来验证其身份。

常见的身份验证类型

系统可以使用多种机制来验证用户。 通常,要验证您的身份,身份验证过程使用: - 您知道的东西 - 您拥有的东西或您是某物 密码和安全问题是两个您知道类别的身份验证因素。 由于只有您知道您的密码或特定安全问题集的答案,因此系统使用此假设来授予您访问。 另一种常见的身份验证因素使用您拥有的东西。 USB 安全令牌和手机等物理设备属于此类别。 例如,当您访问系统并通过 SMS 或应用程序向您发送一次性 PIN(OTP)时,它可以验证您的身份,因为它是您的设备。 最后类型的身份验证因素使用了您的事物。 生物识别身份验证机制属于这一类别。 由于诸如指纹之类的个人物理特征是独特的,因此使用这些因素来验证个体是一种安全的身份验证机制。

授权是确定用户或服务访问级别的安全过程。 在技术中,我们使用授权授予用户或服务许可,以访问某些数据或执行特定操作。 如果我们重新访问咖啡店的例子,Rahul 和 Lucia 在咖啡店中扮演着不同的角色。 由于拉胡尔(Rahul)是咖啡师,他只能放置并查看命令。 另一方面,露西亚(Lucia)担任经理,也可能可以访问日常销售总额。 由于 Rahul 和 Lucia 在咖啡店有不同的工作,因此该系统将使用其经过验证的身份为每个用户提供个人权限。 重要的是要注意身份验证和授权之间的区别。 身份验证在允许用户访问之前验证用户(Lucia),并确定系统授予他们访问的授权(查看销售信息)

常见授权类型

授权系统以多种形式存在于典型的技术环境中。 例如,访问控制列表(ACL)确定哪些用户或服务可以访问特定的数字环境。 他们通过根据用户的授权级别执行允许或拒绝规则来完成此访问控制。 例如,在任何系统上,通常都有一般用户,超级用户或管理员。 如果标准用户想进行影响其安全性的更改,则 ACL 可能会拒绝访问权限。 另一方面,管理员有权更改安全性,因此 ACL 将允许他们这样做。 授权的另一种常见类型是访问数据。 在任何企业环境中,您通常都具有具有不同灵敏度的数据。 例如,您可能会在公司网站上找到的公共数据,只有员工才能访问的内部数据以及只有少数个人才能访问的机密数据。 在此示例中,授权确定哪些用户可以访问各种信息类型

如前所述,身份验证和授权听起来可能相似,但是每个人在保护系统和数据中起着不同的作用。 不幸的是,人们通常会互换使用这两个术语,因为他们都指系统访问。 但是,它们是不同的过程。 简而言之,一个人在授予用户或服务访问权限之前验证用户或服务的身份,而另一个则确定访问权限后可以做什么。

说明两个术语之间差异的最佳方法是一个简单的示例。 假设您决定去参观朋友的家。 到达后,您敲门,您的朋友打开它。 她认识您(身份验证)并向您致意。 正如您的朋友对您的身份验证一样,她现在很乐意让您进入她的家。 但是,根据您的关系,您可以做些事情,而其他您无法做的事情(授权)。 例如,您可以进入厨房区域,但您不能进入她的私人办公室。 换句话说,您有权进入厨房,但禁止使用她的私人办公室。

身份验证和授权相似,因为它们是提供访问权限的基础过程的两个部分。 因此,两个术语在信息安全性上通常会混淆,因为它们共享相同的 “auth” 缩写。 身份验证和授权在他们俩都利用身份的方式上也相似。 例如,一个人在授予访问权限之前先验证身份,而另一个则使用此验证的身份来控制访问。 云计算中的身份验证和授权 安全是任何云计算解决方案中的重要组件。 由于这些服务提供了共享的访问模型,其中一切都在同一平台上运行,因此他们需要分开并保护客户系统和数据。 云服务提供商使用身份验证和授权来实现这些安全目标。 实际上,云计算平台无法通过其共享资源模型提供规模经济,而无需认证和授权。 例如,当用户尝试访问特定的云服务时,系统将提示他们进行某种形式的身份验证。 这个挑战可能要求他们输入用户名和密码,或使用其他身份验证因素,例如接受应用程序上的通知。 一旦用户成功身份验证,云平台将使用授权来确保用户只能访问其系统和数据。 如果没有身份验证和授权,则不可能在同一平台上分离客户环境。

身份验证和授权都取决于身份。 由于您无法在识别用户或服务授权之前授权,因此始终在授权之前进行身份验证。 同样,我们可以参考我们的咖啡店示例来说明这一点。 如前所述,咖啡师只能创建和查看订单,而经理也可以访问每日销售数据。 如果 POS 系统无法识别哪个用户正在访问系统,则无法提供正确的访问级别。 身份验证提供了控制访问的验证身份授权所需的需求。 当 Rahul 或 Lucia 登录系统时,应用程序知道谁签名了,以及它应该为其身份分配的角色

人们经常互换使用术语访问控制和授权。 尽管许多授权政策构成了访问控制的一部分,但访问控制是授权的组成部分。 访问控制使用授权过程来授予或拒绝对系统或数据的访问。 换句话说,授权定义了用户或服务可以访问的内容的策略。 访问控制执行这些政策。 如果我们比较身份验证和访问控制,则身份验证和授权之间的比较仍然适用。 身份验证验证用户的身份,访问控制使用此身份来授予或拒绝访问。

https://www.cnblogs.com/eddyz/p/17191576.html

Okta

Okta 成立于 2009 年,是一家总部位于美国旧金山的身份识别与访问管理解决方案提供商

Okta 是最早开发单点登录技术的厂商,凭借创始人对市场独到的趋势判断和创新性的技术,Okta 的单点登录产品迅速获得市场和用户的认可,并赢得了大量投资人的青睐。在 2010-2015 年期间 Okta 共融资 7 轮,融资总金额达 2.3 亿美元。2017 年 Okta 在美国纳斯纳克成功上市。

目前,Okta 员工数量达 5030 人,2022 年员工高速增长。

Auth0

https://juejin.cn/post/7176273575484096571

// index.js
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import { Auth0Provider } from "@auth0/auth0-react";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <Auth0Provider
    domain={process.env.REACT_APP_AUTH0_DOMAIN}
    clientId={process.env.REACT_APP_CLIENT_ID}
    redirectUri={window.location.origin}
  >
    <App />
  </Auth0Provider>
);

GraphQL

GraphQL是一种用于API的查询语言。对API提供了一套易于理解的完整描述,使客户端能够准确获得需要的数据

使用GraphQL后端将不再产出api,与前端约定一套schema,用来生成接口文档,前端通过schema进行自己期望的请求

GraphQL并没有和特定的数据库或者存储引擎绑定,而是依靠现有的代码和数据支撑。一个GraphQL是通过定义类型和类型上的字段来创建的,然后给每个类型的字段提供解析函数

目前很多语言也提供了对GraphQL的支持,比如javascript/nodejs,Java、php、Ruby、Python、Go、C#等

与Restful接口相比

当今很多页面需要向多个资源发送请求,有的页面甚至发3个以上的请求,而且请求之间可能存在依赖关系,后一个请求需要根据前一个请求的结果进行下一步

传统restful:

数据请求冗余:一个用户的信息可能包含id、昵称、年龄、性别、头像、等级等很多字段,但是有时我们只需要用户的一两个字段而不得不请求到所有的数据,其他的数据是无用的,或者需要获得一个用户列表时,需要调用多个接口,给后端造成麻烦

restful接口改动麻烦:restful一旦要改变API,改变APIurl、增减数量、前后端都需改变

GraphQL:

减少数据和请求冗余:正如官网所说,请求的数据不多也不少,节省带宽,获取多个资源可以只用一个请求

schema格式化:通过schema限制了req和rep的格式和类型,且自动生成文档,减少前后端沟通成本

接口校验:

接口变动,维护与文档:客户端只声明数据,不需要具体格式,API也无需划分版本,只要服务端包含请求方的数据,不需要更改接口

劣势:学习成本,对后端的要求高,这也是graphql推广难的原因,后端需要重新构建

简言之:

restful一个接口返回一个资源,graphql一次获取多个资源

restful用url定义资源,graphql用类型定义资源

优点:

1.强类型文档。GraphQL 本身首先是一门语言,而且是强类型语言,目前社区已有各种语言与 GraphQL 转换的工具。实践中,前端配合自动生成工具和 TypeScript 能确切知道每个 Query 每个变量每个属性的类型,省去了以往 RESTful API 项目中前端手动声明类型的麻烦。使用如果后端使用 TypeGraphQL 或者 NestJS 这类框架,能直接从 TypeSCript 的 Class 生成文档(schema)。

2.自动聚合。以往 RESTful API 的每个接口的数据都是预先设计好的,前端展示的数据和后端返回的数据有时匹配不上,复杂的业务往往需要一层 BFF 层来做数据聚合。GraphQL 的返回数据是由前端决定的,能有效降低前后端的数据耦合程度,并且减少接口调用次数。

3.更细致的鉴权。RESTful API 的鉴权颗粒度只停留在路由上,但是 GraphQL 能控制每个字段的访问权限。(讲道理 RESTful 也能,无非实现起来麻烦点)

缺点:

1.迁移成本高。 2.生态不完善。如果后端要用 GraphQL 的话目前几乎只能选 Node.js 了。其他语言生态都不完善,缺少 dataloader 、federation 等关键包库。

坑:

1.著名的 N+1 问题。对于后端来说,需要对每一个列表查询进行优化避免 N + 1 。目前流行的解决方案是 dataloader 。

2.每个前端项目只能有一张 GraphQL schema 。这使得后端必须部署一个网关来整个各个微服务,目前几乎只能用 Apollo Federation 来解决这个问题。

3.Subscriptions 。Apollo 实现了 Subscriptions 功能来帮助服务器主动发送消息。但是实践下来发现这个功能还是比较简陋的,不合适微服务架构和集群部署。

GraphQL中的基本概念

操作类型

​ query:查询:获取数据,查

​ mutation:修改,对数据进行变更,增删改

​ substription:订阅:当数据发生更改时进行消息推送

​ GraphQL在query查询语句时是并行执行的,在mutation变更时是线性执行的,一个接着一个

数据类型

​ GraphQL中定义了两种类型:对象类型和标量类型

​ 对象类型是用户在schema中定义的type

​ GraphQL中定义了标量类型,String、Int、Float、Boolean、ID,此外,用户还可以自己定义自己的标量类型

​ 参数后通常跟!表示参数不能为空

​ GraphQL服务器接收到query请求后,从Root Query开始查找,找到对象类型后到它的解析函数Resolver获取内容,如果返回的是标量内容则结束获取,直到获取最后一个标量类型

模式schema

​ 在schema中定义了字段的类型,数据的结构,即接口数据请求的规则,

​ schema使用强类型模式语法,称为模式描述语言

解析函数Resolver

​ 前端请求到达后端后,由Resolver函数提供数据,

​ 解析函数接受四个参数

​ parent:当前上一个解析函数的返回值

​ args:查询函数中传入的参数

​ context:提供给所有解析器的上下文信息

​ info:一个保存与当前查询相关的字段特定信息

请求格式:

​ GraphQL是通过http来发送请求的

异常处理

​ 查询错误:返回状态码200,返回data字段为null,如query语法错误时

​ HTTP错误:返回码4** 或5**,如token失效等

​ 网络异常:fetch方法异常,如无网络

​ JSON解析异常:response.json方法异常

openAPi-to-GraphQL

转换openAPI接口为GraphQL

cli

npm i -g openapi-to-graphql-cli

## openapi-to-graphql <OAS JSON file path or remote url> [options]

在代码中引入

npm i -s openapi-to-graphql

使用

const { createGraphQLSchema } = require("openapi-to-graphql");
// load or construct OAS (const oas = ...)
const { schema, report } = await createGraphQLSchema(oas);

https://github.com/IBM/openapi-to-graphql

SSE

HTTP 协议无法做到服务器主动推送信息。但是,有一种变通方法,就是服务器向客户端声明,接下来要发送的是流信息

也就是说,发送的不是一次性的数据包,而是一个数据流,会连续不断地发送过来。这时,客户端不会关闭连接,会一直等着服务器发过来的新的数据流,视频播放就是这样的例子。本质上,这种通信就是以流信息的方式,完成一次用时很长的下载。

SSE 就是利用这种机制,使用流信息向浏览器推送信息。它基于 HTTP 协议,目前除了 IE/Edge,其他浏览器都支持。

SSE 与 WebSocket 作用相似,都是建立浏览器与服务器之间的通信渠道,然后服务器向浏览器推送信息。

总体来说,WebSocket 更强大和灵活。因为它是全双工通道,可以双向通信;SSE 是单向通道,只能服务器向浏览器发送,因为流信息本质上就是下载。如果浏览器向服务器发送信息,就变成了另一次 HTTP 请求。

SSE 也有自己的优点。

  • SSE 使用 HTTP 协议,现有的服务器软件都支持。WebSocket 是一个独立协议。
  • SSE 属于轻量级,使用简单;WebSocket 协议相对复杂。
  • SSE 默认支持断线重连,WebSocket 需要自己实现。
  • SSE 一般只用来传送文本,二进制数据需要编码后传送,WebSocket 默认支持传送二进制数据。
  • SSE 支持自定义发送的消息类型。

https://www.ruanyifeng.com/blog/2017/05/server-sent_events.html

Websocket

Http协议是无状态的,只能由客户端主动发起,服务端再被动响应,服务端无法向客户端主动推送内容,并且一旦服务器响应结束,链接就会断开(见注解部分),所以无法进行实时通信。WebSocket协议正是为解决客户端与服务端实时通信而产生的技术

WebSocket协议本质上是一个基于tcp的协议,它是先通过HTTP协议发起一条特殊的http请求进行握手后,如果服务端支持WebSocket协议,则会进行协议升级。WebSocket会使用http协议握手后创建的tcp链接,和http协议不同的是,WebSocket的tcp链接是个长链接(不会断开),所以服务端与客户端就可以通过此TCP连接进行实时通信。

WebSocket是html5规范中的一个部分,它借鉴了socket这种思想,为web应用程序客户端和服务端之间(注意是客户端服务端)提供了一种全双工通信机制。同时,它又是一种新的应用层协议,WebSocket协议是为了提供web应用程序和服务端全双工通信而专门制定的一种应用层协议,

websocket的特点:

  • 1)建立在 TCP 协议之上,服务器端的实现比较容易;
  • 2)与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器;
  • 3)数据格式比较轻量,性能开销小,通信高效;
  • 4)可以发送文本,也可以发送二进制数据;
  • 5)没有同源限制,客户端可以与任意服务器通信;
  • 6)协议标识符是ws(如果加密,则为wss),服务器网址就是 URL,形如:ws://example.com:80/some/path。

websocket断码测试

刷新浏览器页面 1001 终端离开, 可能因为服务端错误, 也可能因为浏览器正从打开连接的页面跳转离开.
关闭浏览器tab页面 1001 终端离开, 可能因为服务端错误, 也可能因为浏览器正从打开连接的页面跳转离开.
关闭浏览器, 所有标签页都会关闭。 1001 可以发现。无论是刷新,关闭tab页面还是关闭浏览器,错误码都是1001
ws.close() 1005 主动调用close, 不传递错误码。对服务端来说,也是异常断开。 1005表示没有收到预期的状态码.
ws.close(1000) 1000 正常的关闭,客户端必需传递正确的错误原因码。 原因码不是随便填入的。 比如 ws.close(1009) Failed to execute 'close' on 'WebSocket': The code must be either 1000, or between 3000 and 4999. 1009 is neither.

连接过程

当web程序执行到new WebSocket(url)接口时,browser就开始与url对应的webserver建立握手连接过程

首先建立http连接,也就是Browser与Websocket服务器通过tcp三次握手建立连接,如果这个过程失败就后面的过程就不会执行,直接收到错误。

在tcp建立成功之后,Browser通过http协议发送websocket支持的版本号,协议的版本号、主机等信息发送给服务器。

服务器收到之后确认信息,正确的话就接受本次握手连接,并给出响应的数据恢复,回复的数据也采用http传输

浏览器收到服务器回复的数据包之后数据包和信息都没有问题,就表示连接成功,触发onopen消息。即升级为websocket连接

升级时发送请求头

Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: sN9cRrP/n9NdMgdcy2VJFQ==
Sec-WebSocket-Version: 13 

前两行表示升级为websocket请求

Sec-WebSocket-Key 是由浏览器随机生成的,提供基本的防护,防止恶意或者无意的连接。

Sec-WebSocket-Version 表示 WebSocket 的版本,最初 WebSocket 协议太多,不同厂商都有自己的协议版本,不过现在已经定下来了。如果服务端不支持该版本,需要返回一个 Sec-WebSocket-Versionheader,里面包含服务端支持的版本号。

响应信息

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat 
  1. Sec-WebSocket-Accept 这个则是经过服务器确认,并且加密过后的 Sec-WebSocket-Key
  2. Sec-WebSocket-Protocol 则是表示最终使用的协议。
let socket = new WebSocket('wss://')

socket.onopen = function(e) {
	socket.send('a')
}

socket.onmessage = function(event) {
	console.log(event.data)
}

我们需要在 url 中使用特殊的协议 ws 创建 new WebSocket

同样也有一个加密的 wss:// 协议。类似于 WebSocket 中的 HTTPS。

wss:// 协议不仅是被加密的,而且更可靠。

因为 ws:// 数据不是加密的,对于任何中间人来说其数据都是可见的。并且,旧的代理服务器不了解 WebSocket,它们可能会因为看到“奇怪的” header 而中止连接。

另一方面,wss:// 是基于 TLS 的 WebSocket,类似于 HTTPS 是基于 TLS 的 HTTP),传输安全层在发送方对数据进行了加密,在接收方进行解密。因此,数据包是通过代理加密传输的。它们看不到传输的里面的内容,且会让这些数据通过。

websocket、长轮询、轮询、sse的区别

短轮询(Polling)的实现思路就是浏览器端每隔几秒钟向服务器端发送http请求,服务端在收到请求后,不论是否有数据更新,都直接进行响应。在服务端响应完成,就会关闭这个Tcp连接

长轮询(Long-Polling)客户端发送请求后服务器端不会立即返回数据,服务器端会阻塞请求连接不会立即断开,直到服务器端有数据更新或者是连接超时才返回,客户端才再次发出请求新建连接、如此反复从而获取最新数据。

还有一种长轮询是当我们在页面中嵌入一个iframe并设置其src时,服务端就可以通过长连接“源源不断”地向客户端输出内容。 例如,我们可以向客户端返回一段script标签包裹的javascript代码,该代码就会在iframe中执行。因此,如果我们预先在iframe的父页面中定义一个处理函数process(),而在每次有新数据需要推送时,在该连接响应中写入。那么iframe中的这段代码就会调用父页面中预先定义的process()函数。

SSE(Server-sent-Event):Server-SentHTML5提出一个标准。由客户端发起与服务器之间创建TCP连接,然后并维持这个连接,直到客户端或服务器中的任何一方断开,ServerSent使用的是"问"+"答"的机制,连接创建后浏览器会周期性地发送消息至服务器询问,是否有自己的消息。其实现原理类似于我们在上一节中提到的基于iframe的长连接模式。 HTTP响应内容有一种特殊的content-type —— text/event-stream,该响应头标识了响应内容为事件流,客户端不会关闭连接,而是等待服务端不断得发送响应结果。 SSE规范比较简单,主要分为两个部分:浏览器中的EventSource对象,以及服务器端与浏览器端之间的通讯协议

Web Sockets定义了一种在通过一个单一的 socket 在网络上进行全双工通讯的通道。仅仅是传统的 HTTP 通讯的一个增量的提高,尤其对于实时、事件驱动的应用来说是一个飞跃。

# 轮询(Polling) 长轮询(Long-Polling) Websocket sse
通信协议 http http tcp http
触发方式 client(客户端) client(客户端) client、server(客户端、服务端) client、server(客户端、服务端)
优点 兼容性好容错性强,实现简单 比短轮询节约资源 全双工通讯协议,性能开销小、安全性高,可扩展性强 实现简便,开发成本低
缺点 安全性差,占较多的内存资源与请求数 安全性差,占较多的内存资源与请求数 传输数据需要进行二次解析,增加开发成本及难度 只适用高级浏览器
延迟 非实时,延迟取决于请求间隔 同短轮询 实时 非实时,默认3秒延迟,延迟可自定义

总结:

兼容性:短轮询>长轮询>长连接SSE>WebSocket

性能:WebSocket>长连接SSE>长轮询>短轮询

服务端推送:WebSocket>长连接SSE>长轮询

BFF

BFF是(Backends For Frontends)单词的缩写,主要是用于服务前端的后台应用程序,来解决多访问终端业务耦合问题。

传统的应用程序内提供的接口是有业务针对性的,这种类型的接口如果独立出来再提供给别的系统再次使用是一件比较麻烦的事情,设计初期的高耦合就决定了这一点。

如果我们的接口同时提供给web移动端使用,移动端仅用来采集数据以及数据的展示,而web端大多数场景是用来管理数据,因为不同端点的业务有所不同每一个端的接口复用度不会太高。

针对多端共用服务接口的场景,我们将基础的数据服务BFF进行了分离数据服务仅提供数据的增删改查,并不过多涉及业务的判断处理,所有业务判断处理都交给BFF来把控,遇到的一些业务逻辑异常也同样由BFF格式化处理后展示给访问端点。

这种设计方式同样存在一定的问题,虽然基础服务BFF进行了分离,我们只需要在BFF层面进行业务判断处理,但是多个端共用一个BFF,也会导致代码编写复杂度增高代码可阅读性降低多端业务耦合

如果我们为每一个端点都提供一个BFF,每个端点的BFF处理自身的业务逻辑,需要数据时从基础服务内获取,然后在接口返回之前进行组装数据用于实例化返回对象。

这样基础服务如果有新功能添加,BFF几乎不会受到影响,而我们如果后期把App端点进行拆分成AndroidIOS时我们只需要将app-bff进行拆分为android-bffios-bff,基础服务同样也不会受到影响

优缺点

优点:

(1)可以降低沟通成本:后端同学追求解耦,希望客户端应用和内部微服务不耦合,通过引入BFF这中间层,使得两边可以独立变化

(2)多端应用适配:展示不同的(或更少量的)数据,比如PC端页面设计的API需要支持移动端,发现现有接口从设计到实现都与桌面UI展示需求强相关,无法简单适应移动端的展示需求 ,就好比PC端一个新闻推荐接口,接口字段PC端都需要,而移动端呢H5不需要,这个时候根据不同终端在BFF层做调整,同时也可以进行不同的(或更少的)API调用(聚合)来减少http请求

  当你在设计 API 时,会因为不同终端存在不同的区分,它们对服务端提供的 API 访问也各有其特点,需要做一些区别处理。这个时候如果考虑在原有的接口上进行修改,会因为修改导致耦合,破坏其单一的职责。

BFF的痛点

(1)重复开发:每个设备开发一个 BFF 应用,也会面临一些重复开发的问题展示,增加开发成本

(2)维护问题:需要维护各种 BFF 应用。以往前端也不需要关心并发,现在并发压力却集中到了 BFF 上

(3)链路复杂:流程变得繁琐,BFF引入后,要同时走前端、服务端的研发流程,多端发布、互相依赖,导致流程繁琐

(4)浪费资源: BFF层多了,资源占用就成了问题,会浪费资源,除非有弹性伸缩扩容

在微服务架构设计中,BFF起到了一个业务聚合的关键作用,可以 通过openfeignrestTemplate调用基础服务来获取数据,将获取到的数据进行组装返回结果对象,BFF解决了业务场景问题,也同样带来了一些问题,如下所示:

  • 响应时间延迟(服务如果是内网之间访问,延迟时间较低)
  • 编写起来较为浪费时间(因为在基础服务上添加的一层转发,所以会多写一部分代码)
  • 业务异常处理(统一格式化业务异常的返回内容)
  • 分布式事务(微服务的通病)

前后端跨域9种方式

浏览器的同源策略/SOP(Same origin policy)是一种约定,所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。

非同源的两个对象,Cookie、LocalStorage 和 IndexDB 无法读取2、 DOM 和 Js对象无法获得3、 AJAX 请求不能发送

对于前后端分离,跨域是需要解决的问题

跨域解决方法

1.jsonp(JSON with Padding)

浏览器允许在html页面中再通过相应的标签从不同域名下加载静态资源,而被浏览器允许,基于此原理,我们可以通过动态创建script,再请求一个带参网址实现跨域通信。

手撸jsonp

<script>
        let jsonp=(url,data={},callback='callback')=>{
            //准备好带有padding的请求url
        let dataStr=url.indexOf('?')=== -1?'?':'&'
        // console.log(dataStr);
        for(let key in data){
            dataStr +=`${key}=${data[key]}&`
        }
        dataStr +=`callback=`+callback

        //构造 script
        let oScript=document.createElement('script')
        oScript.src=url+dataStr
        //appendChild () 方法可向节点的子节点列表的末尾添加新的子节点
        document.body.appendChild(oScript)

        window[callback]=(data)=>{
            console.log(data);
        }
    }

    jsonp('https://photo.sina.cn/aj/index?a=1',{
            page:1,
            cate:'recommend'
        })       
</script>

2.跨域资源共享(CORS)

服务端设置Access-Control-Allow-Origin即可,前端无须设置,若要带cookie请求:前后端都需要设置。CORS也已经成为主流的跨域解决方案。

相关请求头:access-control-allow-headers,access-control-allow-method,access-control-allow-credentials

CORS是一种基于http头的机制,该机制通过允许服务器标识除了它自己以外的其他origin(域、协议或者端口),这样浏览器可以从这里加载资源。

如果想所有网站都能跨域请求: 后端设置 access-control-allow-origion:*

指定网站进行跨域:设置 access-control-allow-origion 为自己网站具体的协议 + 域名 + 端口号

带上cookie: 前端 xmlhttprequest 设置 withCredentials:true,服务端同时设置 access-control-allow-credentials

3.Nodejs中间件/Nginx代理跨域

通过启一个代理服务器,实现数据的转发,也可以通过设置cookieDomainRewrite参数修改响应头中cookie中域名,实现当前域的cookie写入,方便接口登录认证。

利用node + express + http-proxy-middleware搭建一个proxy代理服务器。

var express = require('express');
var app = express();

var proxy = require('http-proxy-middleware');

app.use('/', proxy({
    // 代理跨域目标接口
    target: 'http://www.demo2.com:8080',
    changeOrigin: true,

    // 修改响应头信息,实现跨域并允许带cookie    
      onProxyRes: function(proxyRes, req, res) {
          res.header('Access-Control-Allow-Origin', 'http://www.domain1.com');
          res.header('Access-Control-Allow-Credentials', 'true');
      },

      // 修改响应信息中的cookie域名
      cookieDomainRewrite: 'www.demo1.com'  // 可以为false,表示不修改
      })
    );

实现思路:通过nginx配置一个代理服务器(域名与demo1相同,端口不同)做跳板机,反向代理访问demo2接口,并且可以顺便修改cookie中demo信息,方便当前域cookie写入,实现跨域登录。

4.WebSocket协议跨域

WebSocket protocol是HTML5一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯,是server push技术的一种很好的实现。

原生WebSocket API使用起来不太方便,我们使用Socket.io,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容。

5.XMLHttpRequest

HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一,

6.axios

其他

window.name + iframe跨域

document.domain + iframe跨域

location.hash + iframe跨域

https://docs.qq.com/doc/BqI21X2yZIht1bfJDW0BNxAF0cqHEb1racd94Lv8jM4Uatg11

各种跨域方式对比

实现CORS的关键是服务器,只要服务器实现了CORS接口,就可以跨源通信了

浏览器将CORS请求分为两类,简单请求和复杂请求

简单请求是指http方法为get、post、head三种之一,或者http头部信息不超过以下字段

对于简单请求,请求时自动在请求头信息中添加Origin字段,说明本次请求是来自哪个源

非简单请求是对服务器有特殊要求的请求,比如请求方法是PUT或者DELETE,或者cotent-type字段的类型是application/json

对于非简单请求的CORS请求,会在正式通信之前增加一次HTTP查询请求,称为预检请求

浏览器会先询问服务器当前的域名是否在服务器的许可名单之中,以及可使用哪些HTTP动词和头字段

预检请求的请求信息

OPTIONS /cors HTTP/1.1

预检请求使用的请求方法是OPTIONS,表示这个请求是用来询问的。

一旦服务器通过了预检请求,以后每次浏览器正常的CORS请求就会像简单请求一样,会有一个Origin头部信息

CORS比jsonp更强大,JSONP只支持GET请求,CORS支持所有类型的HTTP请求。

JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求。

跨域验证

方案一:cookie/session认证

用户填写用户名和密码,发送给服务器

局限:

无法数据共享。如果A网站和B网站是同一家公司的

方式二:JWT(Json Web Token)是最流行的跨域验证方案

JWT的原理是服务器认证以后,生成一个json对象,发回给用户。用户与服务器通信都靠这个对象。为了防止用户篡改数据,服务端生成对象时会加上签名。

JWT是一个很长的字符串,中间用点分隔成3个部分。JWT的三个部分依次是头部Header、负载Payload、签名Signature。

头部定义签名的算法和令牌类型(默认为JWT),负载包含实际需要传递的数据,其中定义了七个字段

iss:签发人,exp:过期时间 sub:主题。aud:受众 nbf:生效时间 iat:签发时间 jti:编号

签名是对前两部分的签名,防止数据篡改

使用方式:

客户端收到服务器返回的JWT,可以存储在cookie中,也可以存储在localstorage中,

客户端每次与服务器通信都带上JWT,把JWT放在http请求的头信息Authorization字段里面,就可以跨域访问。

JWT的使用特点:

JWT默认是不加密的,也可以加密,不加密时不能讲秘密数据写入JWT,加密时生成原生token用密钥加密。

JWT不仅可以用于验证,也可以用于交换信息。有效使用JWT可以降低服务器查询数据库的次数

如果你觉得我的文章对你有帮助的话,希望可以推荐和交流一下。欢迎關注和 Star 本博客或者关注我的 Github