0%

前端知识点滴

swagger-client

swagger-client 是一个 js 包,可以读取并解析 Swagger 2.0 和 OpenAPI 3 的 json 文档并生成可以对文档中定义的 API 进行调用的 client,简化 api 请求过程。

文件下载

服务端响应的 Header 中可以设置 Content-Disposition: attachment; filename=test.txt 指定文件名称,一般设置 Content-Type: application/octet-stream 对于小的文本文件也可以设置为 Content-Type: text/plain; charset=utf-8 告诉浏览器可以直接在浏览器窗口打开。

更新依赖

npm-check-updates 可以帮助更新 package.json 中的所有依赖,其仍是通过 npm update 实现。

1
2
npm i -g npm-check-updates
npm-check-updates -u

Node.js 下载图片到本地

1
2
3
4
5
6
7
8
9
function downloadPic(src, dest) {
request(src)
.pipe(fs.createWriteStream(dest))
.on("close", function () {
console.log("pic saved!");
});
}
// 使用方式:
downloadPic(imgList[0], "./catpics/1.jpg");

Node.js 教程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import * as FormData from "form-data";
import { request } from "http";
import { createReadStream } from "fs";

const readStream = createReadStream("./photo.jpg");

const form = new FormData();
form.append("photo", readStream);
form.append("firstName", "Marcin");
form.append("lastName", "Wanago");

const req = request(
{
host: "localhost",
port: "5000",
path: "/upload",
method: "POST",
headers: form.getHeaders(),
},
(response) => {
console.log(response.statusCode); // 200
}
);

form.pipe(req);

Node.js 流式转存图片

下载的同时开始上传到七牛云:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import * as qiniu from 'qiniu';
const config: any = new qiniu.conf.Config();
config.zone = qiniu.zone.Zone_z2;
const formUploader = new qiniu.form_up.FormUploader(config);
const putExtra = new qiniu.form_up.PutExtra();

async uploadFiles(urls: any[], type: string, token?: string) {
if (!token) {
token = await get_token();
}
let keys = {};
for (let url of urls) {
let requestUrl = url.url.trim();
if (!requestUrl || !/(https?|ftp|file):\/\/\w+\.\w+/.test(requestUrl)) {
keys[url.id] = null;
continue
}
const uploadFunc = async () => {
return new Promise((resolve, reject) => {
let readableStream = request(requestUrl);
const key = `images/xxx_${Date.now()}.jpg`;
try {
formUploader.putStream(
token,
key,
readableStream,
putExtra,
(respErr, respBody, respInfo) => {
if (respInfo.statusCode == 200) {
return resolve(respBody && respBody.key);
} else {
return resolve(null);
}
}
);
} catch (e) {
return resolve(null);
}
})
}
const key = await uploadFunc();
this.app.logger.info('key: ', key);
keys[url.id] = key;
}
return keys;
}

从 Html 文本中匹配所有 img 的地址

  1. 从字符串中匹配出所有的 img 标签
1
2
3
4
5
const str =
'this is test string <img src="http:baidu.com/test.jpg" width=\'50\' > 1 and the end <img src="所有地址也能匹配.jpg" /> 33! <img src="/uploads/attached/image/20120426/20120426225658_92565.png" alt="" />';
const imgReg = /<img.*?(?:>|\/>)/gi;
const srcReg = /src=[\'\"]?([^\'\"]*)[\'\"]?/i;
const arr = str.match(imgReg); // arr 为包含所有img标签的数组
  1. 从数组中获取到所有的 src 地址
1
2
3
4
5
for (var i = 0; i < arr.length; i++) {
const src = arr[i].match(srcReg);
//获取图片地址
console.log("图片地址" + (i + 1) + ":" + src[1]);
}
  1. 需要 cookie 是因为 HTTP 协议本身是无状态的,但是需要用户登录的网站显然是需要记录用户的登录状态给后端使用的,而这个状态应该对同一个域(同源)下的所有请求都有效,所以设计了 cookie,cookie 的设置和发送一般是不需要前端人员介入的(当然前端是可以使用 js 增删改查 cookie 的),设置是浏览器根据 HTTP 响应头中的 set-cookie 信息自动设置,发送也是浏览器根据 HTTP 请求的 URL 自动匹配后添加的。
  2. HTTP 请求,Cookie 的使用过程:server 通过 HTTP Response 中的 “Set-Cookie: header” 把 cookie 发送给 client;client(用户的浏览器) 把 cookie 通过 HTTP Request 中的 “Cookie: header” 发送给 server;每次 HTTP 请求,Cookie 都会被发送。
  3. HTTP 请求发送 Cookies 的条件:本地已经缓存有 cookies;根据请求的 URL 来匹配 cookies 的 domain、path 属性,如果都符合才会发送。
  4. 如下为 HTTP 响应头中设置 cookie 的示例,Expires 和 Max-Age 同时设置时后者优先级高,如果均未设置则默认有效期为 session,当浏览器关闭时失效,设置 Max-Age 为 0 使 cookie 立即失效即删除 cookie,HttpOnly 指明只允许浏览器自动处理,不允许使用 JavaScrit 操作该 cookie,Path 指定对哪些 Path 自动发送该 cookie,默认为 / 代表对所有路径有效,设置 domain 为顶级域名时可以在各子域名之间共享 cookie,如 Set-Cookie: name=value; domain=mydomain.com ,则可以在 sub1.mydomain.com/sub2.mydomain.com/subsub.subdomain.mydomain.com 之间共享该 cookie,无论最初的 set-cookie 响应头源自哪个 sub domain:
1
2
Set-Cookie: KEYCLOAK_SESSION=umstor/fe2ff4f2-6b3c-4d03-aaf1-0e79f09568c5/9e4adcbc-cfec-4f57-9eb9-4bcc16877a23; Version=1; Expires=Sat, 30-Nov-2019 14:52:40 GMT; Max-Age=36000; Path=/keycloak/realms/umstor/
Set-Cookie: KEYCLOAK_REMEMBER_ME=; Version=1; Comment=Expiring cookie; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Max-Age=0; Path=/keycloak/realms/umstor/; HttpOnly
  1. session 是服务端记录用户登录状态的机制,只不过需要借助 cookie 机制在浏览器每次发起请求时带上 session id。
  2. 使用 cookie 不是必须的,也可以将信息存在 localStorage 并通过 url 或 formdata 传输也是可行的,cookie 只不过因为是在协议层面支持因而使用起来可以更方便一些(自动设置和发送)。

前端的各种存储

  1. cookie 能够携带的数据较少,容量在 4KB 左右,在 HTTP Header 中携带太多数据会对性能有一定影响,因此引入了各种 storage,所以现在的 Web 程序中 cookie 的使用已经少了很多。
  2. localStorage 和 sessionStorage 的大小均在 5M 左右,均有同源策略,仅在客户端保存,不像 cookie 通过协议的支持自动在客户端和服务端之间传输,需要编程人员自行通过 URL 或者请求体(响应体)进行传输并解析。
  3. localStorage 的存储是永久的,除非人为删除否则一直存在;sessionStorage 与存储数据的脚本所在的标签页的有效期是相同的,一旦窗口或者标签页被关闭,那么所有通过 sessionStorage 存储的数据也会被删除。
  4. localStorage: 在同一个浏览器内,同源文档之间共享 localStorage 数据,可以互相读取、覆盖。
  5. sessionStorage: 与 localStorage 一样需要同一浏览器同源文档这一条件。不仅如此,sessionStorage 的作用域还被限定在了窗口中,也就是说,只有同一浏览器、同一窗口的同源文档(如同源的 iframe)才能共享数据。例如你在浏览器中打开了两个相同地址的页面 A、B, 虽然这两个页面的源完全相同,但是他们还是不能共享数据,因为他们是不同窗口中的。但是如果是一个窗口中,有两个同源的 iframe 元素的话,这两个 iframe 的 sessionStorage 是可以互通的
  6. IndexedDB 是相比 localStorage 和 sessionStorage 除了提供了更大的存储空间(一般来说不少于 250MB,甚至没有上限),还提供查找接口,还能建立索引,支持事务。
  7. IndexedDB 操作时不会锁死浏览器,用户依然可以进行其他操作,这与 LocalStorage 形成对比,后者的操作是同步的。异步设计是为了防止大量数据的读写,拖慢网页的表现。
  8. 就数据库类型而言,IndexedDB 不属于关系型数据库(不支持 SQL 查询语句),更接近 NoSQL 数据库。
  9. WebSQL 相比 IndexedDB 支持 SQL 查询,但是目前只有谷歌浏览器支持,火狐不支持。
  10. cache storage 是配合 PWA 使用的。

Chrome 控制台记录 XMLHttpRequest 请求

image.png

关于 Token 的认识

  • 一个 Token 就是一些信息的集合;
  • 在 Token 中包含足够多的信息,以便在后续请求中减少查询数据库的几率;
  • 服务端需要对 cookie 和 HTTP Authrorization Header 进行 Token 信息的检查;
  • 基于上一点,你可以用一套 token 认证代码来面对浏览器类客户端和非浏览器类客户端;
  • 因为 token 是被签名的,所以我们可以认为一个可以解码认证通过的 token 是由我们系统发放的,其中带的信息是合法有效的;
  • Token 无法撤销的问题,一种解决方法是发放有效期较短的 Token,另一种是仍然使用 session 与浏览器端交互;
  • Token 与 session 可以同时使用,只不过面向不同的场景,Token 可以作为第三方进行 API 调用的认证方式,而 session 可作为前端交互时的选择;
  • 当然也可以只用 Token 作为前端交互时的认证方式,但要面临 Token 无法撤销的问题;
  • 如果你的用户数据可能需要和第三方共享,或者允许第三方调用 API 接口,用 Token;
  • 在 Web 应用中,别再把 JWT 当做 session 使用,绝大多数情况下,传统的 cookie-session 机制工作得更好;
  • JWT 适合一次性的命令认证,颁发一个有效期极短的 JWT,即使暴露了危险也很小,由于每次操作都会生成新的 JWT,因此也没必要保存 JWT,真正实现无状态。

OAtuh 与 OpenID

OAuth 的本意是一个应用允许另一个应用在用户授权的情况下访问自己的数据,OAuth 的设计本意更倾向于授权而非认证(当然授权用户信息就间接实现了认证)。OpenID 用于身份认证,允许你以同一个账户在多个网站登陆。它仅仅是为你的合法身份背书,当你以 Facebook 账号登陆某个站点之后,该站点无权访问你的在 Facebook 上的数据。OAuth 用于授权,允许被授权方访问授权方的用户数据。两者往往一起使用,实现认证和授权过程。

单点登录的实现方式

  • 同域下不同站点的 SSO(跨站点):两个站点如果在同域下,那么它们之间是可以共享 cookie 的。简单的说就是这种同域下不同站点的 sso 实现可以通过 cookie 来实现,当用户访问这个域下面的任意站点时,浏览器都会将这个 cookie 发送给站点对应的系统。
  • 同域但不同子域名的 SSO(跨子域): 通过在设置 cookie 时指定 domain,设置 domain 为顶级域名时可以在各子域名之间共享 cookie,如 Set-Cookie: name=value; domain=mydomain.com。
  • 不同域的 SSO(跨域):一种是简单的笨方法,使用 cookie,在各个应用之间重定向;一种是使用单独的 SSO 服务器。
  • 使用单独的认证服务器已经是较为通用的方式。
  • 参考:SSO 三种情况的实现方式

访问资源时省略 URL 中的协议

便于自适应同时支持 http 和 https 的站点:

1
<img src="//remotesite.com/image1.jpg" />

长短连接与长短轮询

HTTP 是无状态的,因此 HTTP 连接的说法是不合适的,由于 HTTP 基于 TCP 实现,所以提到连接应该指的是 TCP 连接。HTTP 协议是基于请求 / 响应模式的,因此只要服务端给了响应,本次 HTTP 请求就结束了,没有所谓的连接一说。TCP 连接是一个双向的通道,它是可以保持一段时间不关闭的,因此 TCP 连接才有真正的长连接和短连接这一说。
现在用的基本上都是 HTTP/1.1 协议,请求头中 Connection 都是 keep-alive。而且 HTTP 协议文档上也提到了,HTTP/1.1 默认是长连接,也就是默认 Connection 的值就是 keep-alive。
需要注意的是,HTTP/2 中则不会处理 Connection: keep-alive 的设置,这是因为在 HTTP/1.1 中虽然 keep-alive 是默认值,但是可以通过设置 keep-live: close 关闭长连接,在 HTTP/2 中默认也是长连接但却不允许在 HTTP 层去关闭,所以在 HTTP/2 的请求头中看不到 Connection: keep-alive 这样的设置,也不会对 keep-live: close 这样的设置作出处理。
只有服务端能够针对 Connection 请求头的设置作出处理才算是真正实现了连接保持。由于连接保持是 HTTP1.1 协议的一部分,所以只要实现了 HTTP1.1 协议的服务端都是可以进行连接保持的。
连接保持的主要作用是为了进行多个 HTTP 请求的复用,因为 TCP 连接的创建和销毁都是有成本的。
长连接并不是永久连接,如果一段时间内(具体的时间长短,是可以在 header 当中进行设置的,也就是所谓的超时时间),这个连接没有 HTTP 请求发出的话,那么这个长连接就会被断掉,否则的话,TCP 连接将会越来越多,直到把服务器的 TCP 连接数量撑爆到上限为止。
长轮询和短轮询最大的区别是,短轮询去服务端查询的时候,不管数据有没有变化,服务端立即返回。而长轮询则不是,在长轮询中,服务器如果检测到数据没有变化的话,将会把当前请求挂起一段时间(这个时间也叫作超时时间,一般是几十秒)。在这个时间里,服务器会去检测数据有没有变化,检测到变化就立即返回,否则就一直等到超时为止。
不管是长轮询还是短轮询,都不太适用于客户端数量太多的情况,因为每个服务器所能承载的 TCP 连接数是有上限的,这种轮询很容易把连接数顶满。
WebSocket 或者 SSE 是应对客户数并发量较多的更好的选择。SSE 与 WebSocket 对比:使用 WebSocket 和 SSE 实现 HTTP 服务器推送
一个 TCP 连接是否为长连接,是通过设置 HTTP 的 Connection Header 来决定的,而且是需要两边都设置才有效。而一种轮询方式是否为长轮询,是根据服务端的处理方式来决定的,与客户端没有关系。连接的长短是通过协议来规定和实现的。而轮询的长短,是服务器通过编程的方式手动挂起请求来实现的。

HTTP/2 与 HTTP/1.1

  • 一般而言,浏览器只允许同时与同一个服务端建立最多 6 个 TCP 连接,所以通过浏览器查看控制台时往往会看到会有 6 个请求同时进行,这是 HTTP/1.1 的情况,因为无法进行多路复用,所以需要建立多个连接同时请求多个资源,另外对于每一个 TCP 连接来说,由于服务器必须按接受请求的顺序发送响应的规则限制,那么假设浏览器在一个(tcp)连接上发送了两个请求,那么服务器必须等第一个请求响应完毕才能发送第二个响应,会造成请求的阻塞,无法有效利用带宽。
  • HTTP/2 中引入了流的概念实现多路复用,一个域名只需要一个 TCP 连接完成所有的 HTTP 请求和响应,也不会有多个请求之间的阻塞,极大提高了效率。
  • HTTP/2 引入的服务端推送特性也允许服务端在客户端发出一个 HTTP 请求(例如 index.html )后,主动推送可能相关的内容(例如 index.html 里需要引用的 js 和 css 文件)到客户端,减少了客户端发起请求的次数,提高效率。
  • HTTP/2 的头部压缩,二进制编码等新的特性也能提高传输效率。
  • 虽然 HTTP/2 的实现与 TLS 并无强行关联,但是一般代理服务器(例如 Nginx )只允许 HTTPS 模式下使用 HTTP/2 ;Go 语言的标准库实现的 HTTP/2 协议默认也要求使用 HTTPS(虽然也可以绕过)。
  • HTTP/2: the difference between HTTP/1.1, benefits and how to use itHTTP/2 协议详解HTTP/2HTTP/1.1 vs HTTP/2: What’s the Difference?

Content-Encoding: gzip

服务端可以将静态文件进行 gzip 压缩后,在响应头中设置 Content-Encoding: gzip ,告知浏览器需通过解压缩来获取 Content-Type: text/html; charset=UTF-8 中指定类型的文件。一般用于加快传输速度。

Token 刷新机制

Token 由于无法撤销,所以有效期不宜太长,当 Token 到期后,需要获取新的 Token。Token 刷新的几种方式:

  • 用户登录后返回一个 Token,Token 有两个属性,过期时间和刷新时间,前端存储 Token,Token 过期后,在刷新时间过期之前使用该 Token 获取新的 Token;
  • 用户登录后返回 Refresh Token 和 Access Token,Refresh Token 有效期更长,Access Token 过期后,使用 Refresh Token 获取新的 Access Token,后端服务校验 Access Token 不需要访问 Auth Server,而通过 Refresh Token 获取 Access Token 时需要访问 Auth Server。
  • 参考:前后端分离中的无痛刷新 token 机制
本文到此结束  感谢您的阅读