一网打尽面试中的http相关知识

171 阅读30分钟

web网络通信相关技术

HTTP 概述

HTTP 是一种能够获取如 HTML 这样的网络资源的 protocol(通讯协议)。它是在 Web 上进行数据交换的基础,是一种 client-server 协议,也就是说,请求通常是由像浏览器这样的接受方发起的。一个完整的 Web 文档通常是由不同的子文档拼接而成的,像是文本、布局描述、图片、视频、脚本等等。

客户端和服务端通过交换各自的消息(与数据流正好相反)进行交互。由像浏览器这样的客户端发出的消息叫做 request,被服务端响应的消息叫做 response

HTTP 被设计于 20 世纪 90 年代初期,是一种可扩展的协议。它是应用层的协议,通过TCP,或者是TLS-加密的 TCP 连接来发送,理论上任何可靠的传输协议都可以使用。因为其良好的扩展性,时至今日,它不仅被用来传输超文本文档,还用来传输图片、视频或者向服务器发送如 HTML 表单这样的信息。HTTP 还可以根据网页需求,仅获取部分 Web 文档内容更新网页。

基于 HTTP 的最常用 API 是XMLHttpRequest API,可用于在user agent和服务器之间交换数据。 现代Fetch API提供相同的功能,具有更强大和灵活的功能集。

HTTP 头部

Accept

Accept 请求头用来告知(服务器)客户端可以处理的内容类型,这种内容类型用MIME 类型来表示。借助内容协商机制, 服务器可以从诸多备选项中选择一项进行应用,并使用 Content-Type 应答头通知客户端它的选择。浏览器会基于请求的上下文来为这个请求头设置合适的值,比如获取一个 CSS 层叠样式表时值与获取图片、视频或脚本文件时的值是不同的。

Accept: <MIME_type>/<MIME_subtype>
Accept: <MIME_type>/*
Accept: */*

// Multiple types, weighted with the quality value syntax:
Accept: text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8
<MIME_type>/<MIME_subtype>

单一精确的 MIME 类型,例如text/html.

<MIME_type>/*

一类 MIME 类型,但是没有指明子类。image/* 可以用来指代 image/pngimage/svgimage/gif 以及任何其他的图片类型。

*/*

任意类型的 MIME 类型

;q= (q 因子权重)

值代表优先顺序,用相对质量价值表示,又称作权重。

Accept-Charset

Accept-Charset 请求头用来告知(服务器)客户端可以处理的字符集类型。 借助内容协商机制,服务器可以从诸多备选项中选择一项进行应用, 并使用Content-Type 应答头通知客户端它的选择。浏览器通常不会设置此项值,因为每种内容类型的默认值通常都是正确的,但是发送它会更有利于识别。

Accept-Charset: <charset>

// Multiple types, weighted with the quality value syntax:
Accept-Charset: utf-8, iso-8859-1;q=0.5

Accept-Encoding

HTTP 请求头 Accept-Encoding 会将客户端能够理解的内容编码方式——通常是某种压缩算法——进行通知(给服务端)。通过内容协商的方式,服务端会选择一个客户端提议的方式,使用并在响应头 Content-Encoding 中通知客户端该选择。

Accept-Encoding: gzip
Accept-Encoding: compress
Accept-Encoding: deflate
Accept-Encoding: br
Accept-Encoding: identity
Accept-Encoding: *

gzip

表示采用 Lempel-Ziv coding (LZ77) 压缩算法,以及 32 位 CRC 校验的编码方式。

compress

采用 Lempel-Ziv-Welch (LZW) 压缩算法。

deflate

采用 zlib 结构和 deflate 压缩算法。

br

表示采用 Brotli 算法的编码方式。

identity

用于指代自身(例如:未经过压缩和修改)。除非特别指明,这个标记始终可以被接受。

*

匹配其他任意未在该请求头字段中列出的编码方式。假如该请求头字段不存在的话,这个值是默认值。它并不代表任意算法都支持,而仅仅表示算法之间无优先次序。

Accept-Language

Accept-Language 请求头允许客户端声明它可以理解的自然语言,以及优先选择的区域方言。借助内容协商机制,服务器可以从诸多备选项中选择一项进行应用, 并使用 Content-Language 应答头通知客户端它的选择。浏览器会基于其用户界面语言为这个请求头设置合适的值,即便是用户可以进行修改,但是这种情况极少发生(因为可增加指纹独特性,通常也不被鼓励)(译者注:通常只在测试网站的多语言支持时手动修改它;或为进一步减少指纹独特性,改为最常见的英文)。

Accept-Language: <language>
Accept-Language: *
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2

用含有两到三个字符的字符串表示的语言码或完整的语言标签。除了语言本身之外,还会包含其他方面的信息,显示在中划线("-")后面。最常见的额外信息是国家或地区变种(如"en-US")或者表示所用的字母系统(如"sr-Lat")。其他变种诸如拼字法("de-DE-1996")等通常不被应用在这种场合。

*

任意语言;"*" 表示通配符(wildcard)。

Accept-Patch

服务器使用 HTTP 响应头 Accept-Patch 通知浏览器请求的媒体类型 (media-type) 可以被服务器理解。

Accept-Patch: application/example, text/example
Accept-Patch: text/example;charset=utf-8
Accept-Patch: application/merge-patch+json

Accept-Ranges

服务器使用 HTTP 响应头 Accept-Ranges 标识自身支持范围请求 (partial requests)。字段的具体值用于定义范围请求的单位。

当浏览器发现Accept-Ranges头时,可以尝试继续中断了的下载,而不是重新开始。

Accept-Ranges: bytes //不支持任何范围请求单位,由于其等同于没有返回此头部,因此很少使用
Accept-Ranges: none //范围请求的单位是 bytes(字节)

Age

Age 消息头里包含对象在缓存代理中存贮的时长,以秒为单位。

Age: 24

Allow

Allow 首部字段用于枚举资源所支持的 HTTP 方法的集合。

若服务器返回状态码 405 Method Not Allowed,则该首部字段亦需要同时返回给客户端。如果 Allow 首部字段的值为空,说明资源不接受使用任何 HTTP 方法的请求。这是可能的,比如服务器需要临时禁止对资源的任何访问。

Allow: GET, POST, HEAD

Access-Control-Max-Age

Access-Control-Max-Age 这个响应头表示 preflight request (预检请求)的返回结果(即 Access-Control-Allow-MethodsAccess-Control-Allow-Headers 提供的信息) 可以被缓存多久。

Access-Control-Max-Age: 600 //以秒为单位将预检请求的结果缓存 10 分钟

Authorization

HTTP 协议中的 Authorization 请求消息头含有服务器用于验证用户代理身份的凭证,通常会在服务器返回401 Unauthorized 状态码以及WWW-Authenticate 消息头之后在后续请求中发送此消息头。

Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l

Cache-Control

Cache-Control 通用消息头字段,被用于在 http 请求和响应中,通过指定指令来实现缓存机制。缓存指令是单向的,这意味着在请求中设置的指令,不一定被包含在响应中。

Cache-control: must-revalidate
Cache-control: no-cache
Cache-control: no-store
Cache-control: no-transform
Cache-control: public
Cache-control: private
Cache-control: proxy-revalidate
Cache-Control: max-age=<seconds>
Cache-control: s-maxage=<seconds>

public

表明响应可以被任何对象(包括:发送请求的客户端,代理服务器,等等)缓存,即使是通常不可缓存的内容。(例如:1.该响应没有max-age指令或Expires消息头;2. 该响应对应的请求方法是 POST 。)

private

表明响应只能被单个用户缓存,不能作为共享缓存(即代理服务器不能缓存它)。私有缓存可以缓存响应内容,比如:对应用户的本地浏览器。

no-cache

在发布缓存副本之前,强制要求缓存把请求提交给原始服务器进行验证 (协商缓存验证)。

no-store

缓存不应存储有关客户端请求或服务器响应的任何内容,即不使用任何缓存。

max-age=<seconds>

设置缓存存储的最大周期,超过这个时间缓存被认为过期 (单位秒)。与Expires相反,时间是相对于请求的时间。

Connection

Connection 头(header) 决定当前的事务完成后,是否会关闭网络连接。如果该值是“keep-alive”,网络连接就是持久的,不会关闭,使得对同一个服务器的请求可以继续在该连接上完成。

Connection: keep-alive
Connection: close

Clear-Site-Data

Clear-Site-Data 响应头,表示清除当前请求网站有关的浏览器数据(cookie,存储,缓存)。它让 Web 开发人员对浏览器本地存储的数据有更多控制能力。

// 单个参数
Clear-Site-Data: "cache"
// 多个参数 (用逗号分隔)
Clear-Site-Data: "cache", "cookies"
// 通配
Clear-Site-Data: "*"

"cache"

表示服务端希望删除本 URL 原始响应的本地缓存数据(即 :浏览器缓存,请参阅 HTTP 缓存)。根据浏览器的不同,可能还会清除预渲染页面,脚本缓存,WebGL 着色器缓存或地址栏建议等内容。

"cookies"

表示服务端希望删除 URL 响应的所有 cookie。 HTTP 身份验证凭据也会被清除。会影响整个主域,包括子域。所以 https://example.com 以及 https://stage.example.com 的 Cookie 都会被清除。

"storage"

表示服务端希望删除 URL 原响应的所有 DOM 存储。这包括存储机制,如

"executionContexts"

表示服务端希望浏览器重新加载本请求 (Location.reload).

"*" (通配符)

表示服务端希望清除原请求响应的所有类型的数据。如果在此头的未来版本中添加了更多数据类型,它们也将被涉及。

登出

如果用户退出您的网站或服务,您可能希望删除本地存储的数据。您可以通过在 https://example.com/logout 的响应头增加 Clear-Site-Data,以达到目的:

Clear-Site-Data: "cache", "cookies", "storage", "executionContexts"

清除 cookie

如果它在 https://example.com/clear-cookies 的响应头中出现,则同一域 https://example.com 和所有子域(如 https://stage.example.com 等)中的所有 Cookie,将都被清除。

Clear-Site-Data: "cookies"

Content-Disposition

在常规的 HTTP 应答中,Content-Disposition 响应头指示回复的内容该以何种形式展示,是以内联的形式(即网页或者页面的一部分),还是以附件的形式下载并保存到本地。

作为消息主体中的消息头

在 HTTP 场景中,第一个参数或者是 inline(默认值,表示回复中的消息体会以页面的一部分或者整个页面的形式展示),或者是 attachment(意味着消息体应该被下载到本地;大多数浏览器会呈现一个“保存为”的对话框,将 filename 的值预填为下载后的文件名,假如它存在的话)。

Content-Disposition: inline
Content-Disposition: attachment
Content-Disposition: attachment; filename="filename.jpg"

作为 multipart body 中的消息头

在 HTTP 场景中。第一个参数总是固定不变的 form-data;附加的参数不区分大小写,并且拥有参数值,参数名与参数值用等号 ('=') 连接,参数值用双引号括起来。参数之间用分号 (';') 分隔。

Content-Disposition: form-data
Content-Disposition: form-data; name="fieldName"
Content-Disposition: form-data; name="fieldName"; filename="filename.jpg"

Content-Encoding

Content-Encoding 列出了对当前实体消息(消息荷载)应用的任何编码类型,以及编码的顺序。它让接收者知道需要以何种顺序解码该实体消息才能获得原始荷载格式。 Content-Encoding 主要用于在不丢失原媒体类型内容的情况下压缩消息数据。

Content-Encoding: gzip
Content-Encoding: compress
Content-Encoding: deflate
Content-Encoding: br

// 多个,按应用的编码顺序列出
Content-Encoding: deflate, gzip

Content-Language

Content-Language 是一个 entity header(实体消息首部),用来说明访问者希望采用的语言或语言组合,这样的话用户就可以根据自己偏好的语言来定制不同的内容。

Content-Language: de-DE
Content-Language: en-US
Content-Language: de-DE, en-CA

Content-Length

Content-Length 是一个实体消息首部,用来指明发送给接收方的消息主体的大小,即用十进制数字表示的八位元组的数目。

Content-Length: <length> //消息的长度,用十进制数字表示的八位字节的数目。

Content-Type

Content-Type 实体头部用于指示资源的 MIME 类型 media type

在响应中,Content-Type 标头告诉客户端实际返回的内容的内容类型。浏览器会在某些情况下进行 MIME 查找,并不一定遵循此标题的值; 为了防止这种行为,可以将标题 X-Content-Type-Options 设置为 nosniff

Content-Type: text/html; charset=utf-8
Content-Type: multipart/form-data; boundary=something

Cookie

Cookie 是一个 HTTP 请求标头,其中含有先前由服务器通过 Set-Cookie 标头投放或通过 JavaScript 的 Document.cookie 方法设置,然后存储到客户端的 HTTP cookie

Cookie: <cookie-list>
Cookie: name=value
Cookie: name=value; name2=value2; name3=value3

Date

Date 是一个通用首部,其中包含了报文创建的日期和时间。

Date: <day-name>, <day> <month> <year> <hour>:<minute>:<second> GMT

ETag

**ETag**HTTP 响应头是资源的特定版本的标识符。这可以让缓存更高效,并节省带宽,因为如果内容没有改变,Web 服务器不需要发送完整的响应。而如果内容发生了变化,使用 ETag 有助于防止资源的同时更新相互覆盖(“空中碰撞”)。

ETag: W/"<etag_value>"
ETag: "<etag_value>"

Expires

Expires 响应头包含日期/时间, 即在此时候之后,响应过期。

无效的日期,比如 0,代表着过去的日期,即该资源已经过期。

如果在Cache-Control响应头设置了 "max-age" 或者 "s-max-age" 指令,那么 Expires 头会被忽略。

Expires: Wed, 21 Oct 2015 07:28:00 GMT

Last-Modified

Last-Modified 是一个响应首部,其中包含源头服务器认定的资源做出修改的日期及时间。 它通常被用作一个验证器来判断接收到的或者存储的资源是否彼此一致。由于精确度比 ETag 要低,所以这是一个备用机制。包含有 If-Modified-SinceIf-Unmodified-Since 首部的条件请求会使用这个字段。

Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT

User-Agent

User-Agent 首部包含了一个特征字符串,用来让网络协议的对端来识别发起请求的用户代理软件的应用类型、操作系统、软件开发商以及版本号。

HTTP请求方法

GET

HTTP GET 方法请求指定的资源。使用 GET 的请求应该只用于获取数据。

POST

HTTP POST 方法 发送数据给服务器。请求主体的类型由 Content-Type 首部指定。

PUT 和POST方法的区别是,PUT 方法是幂等的:连续调用一次或者多次的效果相同(无副作用)。连续调用同一个 POST 可能会带来额外的影响,比如多次提交订单。

PUT

HTTP PUT 请求方法使用请求中的负载创建或者替换目标资源。

delete

HTTP DELETE 请求方法用于删除指定的资源。

HTTP常见状态码

RFC 规定 HTTP 的状态码为「三位数」,第一个数字定义了响应的类别,被分为五类:

  • 1xx: 代表请求已被接受,需要继续处理。
  • 2xx: 表示成功状态。
  • 3xx: 重定向状态。
  • 4xx: 客户端错误。
  • 5xx: 服务器端错误。

1xx 信息类

  接受的请求正在处理,信息类状态码。

2xx 成功

  • 200 OK 表示从客户端发来的请求在服务器端被正确请求。
  • 204 No content,表示请求成功,但没有资源可返回。
  • 206 Partial Content,该状态码表示客户端进行了范围请求,而服务器成功执行了这部分的 GET 请求 响应报文中包含由 「Content-Range」 指定范围的实体内容。

3xx 重定向

  • 301 moved permanently,永久性重定向,表示资源已被分配了新的 URL,这时应该按 Location 首部字段提示的 URI 重新保存。
  • 302 found,临时性重定向,表示资源临时被分配了新的 URL。
  • 304 资源未修改,拿缓存的资源

301 和 302 都会在响应头里使用字段 Location 指明后续要跳转的 URI,最终的效果很相似,浏览器都会重定向到新的 URI。两者的根本区别在于语义,一个是“永久”,一个是“临时”。

比如,你的网站升级到了 HTTPS,原来的 HTTP 不打算用了,这就是“永久”的,所以要配置 301 跳转,把所有的 HTTP 流量都切换到 HTTPS。

  304 Not Modified 是一个比较有意思的状态码,它用于 If-Modified-Since 等条件请求,表示资源未修改,用于缓存控制。它不具有通常的跳转含义,但可以理解成“重定向已到缓存的文件”(即“缓存重定向”)。

4xx 客户端错误

  • 400 bad request,通用的错误码,请求报文存在语法错误。
  • 403 forbidden,表示对请求资源的访问被服务器拒绝,服务器经常访问资源。
  • 404 not found,表示在服务器上没有找到请求的资源。
  • 405 Method Not Allowed,服务器禁止使用该方法,客户端可以通过options方法来查看服务器允许的访问方法

5xx 服务的错误

  • 500 internal sever error,表示服务器端在执行请求时发生了错误。
  • 502 Bad Gateway,服务器自身是正常的,访问的时候出了问题,具体啥错误我们不知道。
  • 503 service unavailable,表明服务器暂时处于超负载或正在停机维护,无法处理请求。

HTTP缓存

http缓存指的是: 当客户端向服务器请求资源时,会先抵达浏览器缓存,如果浏览器有“要请求资源”的副本,就可以直接从浏览器缓存中提取而不是从原始服务器中提取这个资源。常见的http缓存只能缓存get请求响应的资源,对于其他类型的响应则无能为力。

强缓存

在 Chrome 中,强缓存又分为 Disk Cache (存放在硬盘中)和 Memory Cache (存放在内存中),存放的位置是由浏览器控制的。是否强缓存由 Expires、Cache-Control 和 Pragma 3 个 Header 属性共同来控制。

Expires

Expires 的值是一个 HTTP 日期,在浏览器发起请求时,会根据系统时间和 Expires 的值进行比较,如果系统时间超过了 Expires 的值,缓存失效。由于和系统时间进行比较,所以当系统时间和服务器时间不一致的时候,会有缓存有效期不准的问题。Expires 的优先级在三个 Header 属性中是最低的。

Cache-Control

Cache-Control 是 HTTP/1.1 中新增的属性,在请求头和响应头中都可以使用,常用的属性值如有:

max-age:单位是秒,缓存时间计算的方式是距离发起的时间的秒数,超过间隔的秒数缓存失效 no-cache:不使用强缓存,需要与服务器验证缓存是否新鲜,进入协商缓存 no-store:禁止使用缓存(包括协商缓存),每次都向服务器请求最新的资源 private:专用于个人的缓存,中间代理、CDN 等不能缓存此响应 public:响应可以被中间代理、CDN 等缓存 must-revalidate:在缓存过期前可以使用,过期后必须向服务器验证

Pragma(优先级高)

Pragma 只有一个属性值,就是 no-cache ,效果和 Cache-Control 中的 no-cache 一致,不使用强缓存,需要与服务器验证缓存是否新鲜,在 3 个头部属性中的优先级最高。

协商缓存

ETag/If-None-Match(优先级高)

首次请求的时候响应头会返回一个,ETAG,内容是一串hash值,此时刷新之后,发现状态码变成304,此时的请求头上会有一个If-None-Match属性,内容与上次请求的ETAG值一样。即通过对比两个属性的值。

Last-Modified/If-Modified-Since(有时间精度问题,或者文件修改,但内容没改,就不会走缓存)

Last-Modified/If-Modified-Since 的值代表的是文件的最后修改时间,第一次请求服务端会把资源的最后修改时间放到 Last-Modified 响应头中,第二次发起请求的时候,请求头会带上上一次响应头中的 Last-Modified 的时间,并放到 If-Modified-Since 请求头属性中,服务端根据文件最后一次修改时间和 If-Modified-Since 的值进行比较,如果相等,返回 304 ,并加载浏览器缓存。

HTTPS

HTTPS (安全的HTTP)是 HTTP 协议的加密版本。它通常使用 SSL 或者 TLS 来加密客户端和服务器之间所有的通信。这安全的链接允许客户端与服务器安全地交换敏感的数据,例如网上银行或者在线商城等涉及金钱的操作。

HTTPS协议的主要作用可以分为两种:一种是建立一个信息安全通道,来保证数据传输的安全;另一种就是确认网站的真实性。

1.客户端发起HTTPS请求

用户在浏览器里输入一个https网址,然后连接到server的443端口

2.服务端的配置

采用HTTPS协议的服务器必须要有一套数字证书,可以自己制作,也可以向组织申请,区别就是自己颁发的证书需要客户端验证通过,才可以继续访问,而使用受信任的公司申请的证书则不会弹出提示页面(startssl就是个不错的选择,有1年的免费服务)。

这套证书其实就是一对公钥和私钥,如果对公钥和私钥不太理解,可以想象成一把钥匙和一个锁头,只是全世界只有你一个人有这把钥匙,你可以把锁头给别人,别人可以用这个锁把重要的东西锁起来,然后发给你,因为只有你一个人有这把钥匙,所以只有你才能看到被这把锁锁起来的东西。

3.传送证书

这个证书其实就是公钥,只是包含了很多信息,如证书的颁发机构,过期时间等等。

4.客户端解析证书

这部分工作是有客户端的TLS来完成的,首先会验证公钥是否有效,比如颁发机构,过期时间等等,如果发现异常,则会弹出一个警告框,提示证书存在问题。

如果证书没有问题,那么就生成一个随机值,然后用证书对该随机值进行加密,就好像上面说的,把随机值用锁头锁起来,这样除非有钥匙,不然看不到被锁住的内容。

5.传送加密信息

这部分传送的是用证书加密后的随机值,目的就是让服务端得到这个随机值,以后客户端和服务端的通信就可以通过这个随机值来进行加密解密了。

6.服务端解密信息

服务端用私钥解密后,得到了客户端传过来的随机值(私钥),然后把内容通过该值进行对称加密,所谓对称加密就是,将信息和私钥通过某种算法混合在一起,这样除非知道私钥,不然无法获取内容,而正好客户端和服务端都知道这个私钥,所以只要加密算法够彪悍,私钥够复杂,数据就够安全。

7.传输加密后的信息

这部分信息是服务段用私钥加密后的信息,可以在客户端被还原。

8.客户端解密信息

客户端用之前生成的私钥解密服务段传过来的信息,于是获取了解密后的内容,整个过程第三方即使监听到了数据,也束手无策。

Https与Http区别

1、HTTPS的服务器需要到CA申请证书,以证明自己服务器的用途;

2、HTTP信息是明文传输,HTTPS信息是加密传输;

3、HTTP默认端口是80,HTTPS默认端口号是443;

4、https采用混合加密算法(对称加密和非对称加密)

5、https 比 http 协议更加安全,但是https协议传输效率没有http效率搞。

HTTP与HTTPS是完全不同的连接方式,HTTPS集合了加密传输,身份认证,更加的安全。

HTTPS底层协议其实就是使用SSL协议,也是使用对称加密进行传输(只不过不需要自己生成密钥,浏览器自动生成的。是使用混合算法进行加密的,就是对称加密与非对称加密混合使用的。)

加密数据之后也能防止篡改数据,如果数据被篡改了数据,解密就会失败,所以可比避免数据被篡改。

在微信小程序里面都限制只能有https协议、搜索引擎排名都对https优先收录。

AJAX发送请求

AJAX 是异步的 JavaScript 和 XML(Asynchronous JavaScript And XML),它可以使用 JSON,XML,HTML 和 text 文本等格式发送和接收数据.

实现步骤:

第一步:创建网络请求的AJAX对象(使用XMLHttpRequest)

第二步:监听XMLHttpRequest对象状态的变化,或者监听onload事件(请求完成时触发);

第三步:配置网络请求(通过open方法), open方法可以传入两个参数;

参数一: method(请求的方式: get, post …)

参数二: url(请求的地址)

第四步:发送send网络请求;

// 1.创建网络请求对象
const xhr = new XMLHttpRequest()

// 2.监听对象状态的变化
xhr.addEventListener("readystatechange", function() {
  // 拿到网络请求的结果
  console.log(xhr.response)
})

// 3.配置网络请求
// 参数一: 请求的方式; 参数二: 要请求的url地址
xhr.open("get", "http://192.168.0.110:1888")

// 4.发生网络请求
xhr.send()

使用 Fetch发送请求

Fetch API 提供了一个 JavaScript 接口,用于访问和操纵 HTTP 管道的一些具体部分,例如请求和响应。它还提供了一个全局 fetch() 方法,该方法提供了一种简单,合理的方式来跨网络异步获取资源。

这种功能以前是使用 XMLHttpRequest 实现的。Fetch 提供了一个更理想的替代方案,可以很容易地被其他技术使用,例如 Service Workers。Fetch 还提供了专门的逻辑空间来定义其他与 HTTP 相关的概念,例如 CORS 和 HTTP 的扩展。

fetch('http://example.com/movies.json')
  .then(response => response.json())
  .then(data => console.log(data));

WebSocket

为什么需要 WebSocket?

初次接触 WebSocket 的人,都会问同样的问题:我们已经有了 HTTP 协议,为什么还需要另一个协议?它能带来什么好处?

答案很简单,因为 HTTP 协议有一个缺陷:通信只能由客户端发起。

这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。我们只能使用"轮询":每隔一段时候,就发出一个询问,了解服务器有没有新的信息。最典型的场景就是聊天室。

轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。因此,WebSocket 就是这样发明的。

简介

它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。

其他特点包括:

(1)建立在 TCP 协议之上,服务器端的实现比较容易。

(2)与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。

(3)数据格式比较轻量,性能开销小,通信高效。

(4)可以发送文本,也可以发送二进制数据。

(5)没有同源限制,客户端可以与任意服务器通信。

(6)协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。

ws://example.com:80/some/path

客户端的简单示例

const ws = new WebSocket("wss://echo.websocket.org");

ws.onopen = function(evt) { 
  console.log("Connection open ..."); 
  ws.send("Hello WebSockets!");
};

ws.onmessage = function(evt) {
  console.log( "Received Message: " + evt.data);
  ws.close();
};

ws.onclose = function(evt) {
  console.log("Connection closed.");
};

客户端的 API

1.WebSocket 构造函数

WebSocket 对象作为一个构造函数,用于新建 WebSocket 实例。执行下面语句之后,客户端就会与服务器进行连接。

const ws = new WebSocket('ws://localhost:8080');

2.webSocket.readyState

readyState属性返回实例对象的当前状态,共有四种。

  • CONNECTING:值为0,表示正在连接。
  • OPEN:值为1,表示连接成功,可以通信了。
  • CLOSING:值为2,表示连接正在关闭。
  • CLOSED:值为3,表示连接已经关闭,或者打开连接失败。

3.webSocket.onopen

实例对象的onopen属性,用于指定连接成功后的回调函数。

ws.onopen = function () {
  ws.send('Hello Server!');
}

如果要指定多个回调函数,可以使用addEventListener方法。

ws.addEventListener('open', function (event) {
  ws.send('Hello Server!');
});

4.webSocket.onclose

实例对象的onclose属性,用于指定连接关闭后的回调函数。

ws.onclose = function(event) {
  const code = event.code;
  const reason = event.reason;
  const wasClean = event.wasClean;
  // handle close event
};

ws.addEventListener("close", function(event) {
  const code = event.code;
  const reason = event.reason;
  const wasClean = event.wasClean;
  // handle close event
});

5.webSocket.onmessage

实例对象的onmessage属性,用于指定收到服务器数据后的回调函数。

ws.onmessage = function(event) {
  const data = event.data;
  // 处理数据
};

ws.addEventListener("message", function(event) {
  const data = event.data;
  // 处理数据
});

6.webSocket.send()

实例对象的send()方法用于向服务器发送数据。

ws.send('your message');

7.webSocket.bufferedAmount

实例对象的bufferedAmount属性,表示还有多少字节的二进制数据没有发送出去。它可以用来判断发送是否结束。

const data = new ArrayBuffer(10000000);
socket.send(data);

if (socket.bufferedAmount === 0) {
  // 发送完毕
} else {
  // 发送还没结束
}

8.webSocket.onerror

实例对象的onerror属性,用于指定报错时的回调函数。

socket.onerror = function(event) {
  // handle error event
};

socket.addEventListener("error", function(event) {
  // handle error event
});

服务端的实现(node)

Socket.IO

Socket.IO是一个WebSocket库,包括了客户端的js和服务器端的nodejs,它会自动根据浏览器从WebSocket、AJAX长轮询、Iframe流等等各种方式中选择最佳的方式来实现网络实时应用,非常方便和人性化

npm i -S socket.io

//服务端
const express = require('express')
const http = require('http')
const app = express()
const server = http.createServer(app)
const io = require('socket.io')(server)
server.listen(3000)
app.use(express.static('www'))

// 监听客户端的连接事件
io.on('connection', socket => {
  socket.on('message', msg => {
    console.log('接受到客户端发过来的消息:' + msg);
    socket.send('服务器说:' + msg)
  })
})

<!--客户端-->
<script>
import { io } from "socket.io-client";
let socket = io.connect('http://localhost:3000/')
socket.on('connect', () => {
  console.log('连接成功')
  socket.emit('message','hello')
});

socket.on('message', msg => {
  console.log(msg)
});

// 断开连接
socket.on('disconnect', () => {
  console.log('断开连接')
});
</script>

// 全局广播
io.emit('message','全局广播');
// 向除了自己外的所有人广播
socket.broadcast.emit('message', msg);

跨域调用方式

同源策略

跨域问题其实就是浏览器的同源策略所导致的。

同源策略是一个重要的安全策略,它用于限制一个origin的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介。

同源:protocol(协议)、domain(域名)、port(端口)三者一致

解决跨域

1.CORS

跨域资源共享CORS 是一种机制,它使用额外的 HTTP头来告诉浏览器 让运行在一个 origin (domain) 上的 Web 应用被准许访问来自不同源服务器上的指定的资源。当一个资源从与该资源本身所在的服务器**「不同的域、协议或端口」请求一个资源时,资源会发起一个「跨域 HTTP 请求」**。

node中的解决案例

app.use(async (ctx, next) => {
  ctx.set("Access-Control-Allow-Origin", ctx.headers.origin);
  ctx.set("Access-Control-Allow-Credentials"true);
  ctx.set("Access-Control-Request-Method""PUT,POST,GET,DELETE,OPTIONS");
  ctx.set(
    "Access-Control-Allow-Headers",
    "Origin, X-Requested-With, Content-Type, Accept, cc"
  );
  if (ctx.method === "OPTIONS") {
    ctx.status = 204;
    return;
  }
  await next();
});

为了方便也可以直接使用中间件

const cors = require("koa-cors");
app.use(cors());

2.cli 工具中的代理

Webpack

webpack中可以配置proxy来快速获得接口代理的能力

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  entry: {
    index"./index.js"
  },
  output: {
    filename"bundle.js",
    path: path.resolve(__dirname, "dist")
  },
  devServer: {
    port8000,
    proxy: {
      "/api": {
        target"http://localhost:8080"
      }
    }
  },
  plugins: [
    new HtmlWebpackPlugin({
      filename"index.html",
      template"webpack.html"
    })
  ]
};
Vue-cli
module.exports = {
  devServer: {
    port8000,
    proxy: {
      "/api": {
        target"http://localhost:8080"
      }
    }
  }
};

3.Nginx 反向代理

Nginx 则是通过反向代理的方式,(这里也需要自定义一个域名)这里就是保证我当前域,能获取到静态资源和接口,不关心是怎么获取的

配置下 hosts

127.0.0.1 local.test

配置 nginx

server {
        listen 80;
        server_name local.test;
        location /api {
            proxy_pass http://localhost:8080;
        }
        location / {
            proxy_pass http://localhost:8000;
        }
}

4.JSONP

JSONP 主要就是利用了 script 标签没有跨域限制的这个特性来完成的。

仅支持 GET 方法,如果想使用完整的 REST 接口,请使用 CORS 或者其他代理方式。

「流程解析」

1.前端定义解析函数(例如 jsonpCallback=function(){....})

2.通过 params 形式包装请求参数,并且声明执行函数(例如 cb=jsonpCallback)

3.后端获取前端声明的执行函数(jsonpCallback),并以带上参数并调用执行函数的方式传递给前端。

「使用示例」

const Koa = require("koa");
const fs = require("fs");
const app = new Koa();

app.use(async (ctx, next) => {
  if (ctx.path === "/api/jsonp") {
    const { cb, msg } = ctx.query;
    ctx.body = `${cb}(${JSON.stringify({ msg })})`;
    return;
  }
});

app.listen(8080);
<script type="text/javascript">
  window.jsonpCallback = function(res) {
    console.log(res);
  };
</script>
<script
  src="http://localhost:8080/api/jsonp?msg=hello&cb=jsonpCallback"
  type="text/javascript"
></script>

5.Websocket

WebSocket 规范定义了一种 API,可在网络浏览器和服务器之间建立“套接字”连接。简单地说:客户端和服务器之间存在持久的连接,而且双方都可以随时开始发送数据。这种方式本质没有使用了 HTTP 的响应头, 因此也没有跨域的限制。

<script>
  let socket = new WebSocket("ws://localhost:8080");
  socket.onopen = function() {
    socket.send("message");
  };
  socket.onmessage = function(e) {
    console.log(e.data);
  };
</script>
const WebSocket = require("ws");
const server = new WebSocket.Server({ port8080 });
server.on("connection"function(socket) {
  socket.on("message"function(data) {
    socket.send(data);
  });
});

6.window.postMessage

「window.postMessage()」 方法可以安全地实现跨源通信。通常,对于两个不同页面的脚本,只有当执行它们的页面位于具有相同的协议(通常为 https),端口号(443 为 https 的默认值),以及主机 (两个页面的模数 Document.domain设置为相同的值) 时,这两个脚本才能相互通信。「window.postMessage()」 方法提供了一种受控机制来规避此限制,只要正确的使用,这种方法就很安全。

用途

1.页面和其打开的新窗口的数据传递

2.多窗口之间消息传递

3.页面与嵌套的 iframe 消息传递

otherWindow: 其他窗口的一个引用,比如 iframe 的 contentWindow 属性、执行window.open返回的窗口对象、或者是命名过或数值索引的window.frames

message: 将要发送到其他 window 的数据。

targetOrigin: 通过窗口的 origin 属性来指定哪些窗口能接收到消息事件.

transfer(可选) : 是一串和 message 同时传递的 Transferable 对象. 这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权

示例

index.html

<iframe
  src="http://localhost:8080"
  frameborder="0"
  id="iframe"
  onload="load()"
></iframe>
<script>
  function load() {
    iframe.contentWindow.postMessage("秋风的笔记""http://localhost:8080");
    window.onmessage = e => {
      console.log(e.data);
    };
  }
</script>

another.html

<div>hello</div>
<script>
  window.onmessage = e => {
    console.log(e.data); // 秋风的笔记
    e.source.postMessage(e.data, e.origin);
  };
</script>

vue3中配置跨域

在根目录下创建vue.config.js

module.exports = {
    devServer: {
        proxy: 'http://192.168.31.252:80'//服务器域名,80端口是默认的,可以不用配置
    }
}