Fork me on GitHub

HTTP 报头字段列表

HTTP 报头字段列表

[TOC]

HTTP报头字段 是 HTTP中请求和响应消息的头部部分的组件。他们是定义HTTP事务的操作参数

头部字段在请求行或者响应行发出之后,才会被传送;

首先让我们以RFC 7230为例了解一下 报文格式 Message Format:

1、Message Format 报文格式

1
2
3
4
5
RFC 7230     HTTP/1.1 Message Syntax and Routing      June 2014
HTTP-message = start-line
*( header-field CRLF )(0或多个)
CRLF(回车换行表明头部部分的结束)
[ message-body ]
1.1、start-line 开始行
1
2
3
4
5
6
7
start-line = request-line / status-line

一个HTTP消息可以是客户端发往服务器的一个request请求也可以是服务器响应客户端的response响应。
这个两者的区别就在于 start-line 是 request-line (针对请求) 或者 status-line(针对响应).

在理论上,一个客户端可以接收请求和一个服务端可以接收响应,根据他们的不同的 start-line.但是在实践中
,服务器是被用来实现只希望接收一个请求(响应是作为一个未知名或者不合法的请求的一个解释)和客户端是实现只接受响应的。
1.2、request-line 请求行
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
request-line = method SP(空格) request-target SP HTTP-version CRLF(回车换行)

method token 表明请求资源的请求方法,大小写敏感:

+---------+-------------------------------------------------+-------+
| Method | Description | Sec. |
+---------+-------------------------------------------------+-------+
| GET | Transfer a current representation of the target | 4.3.1 |
| | resource. | |
| | 传输目标资源的当前表示 | |
| HEAD | Same as GET, but only transfer the status line | 4.3.2 |
| | and header section. | |
| | 只传输状态行和头部 | |
| POST | Perform resource-specific processing on the | 4.3.3 |
| | request payload. | |
| | 请求负载中执行指定资源的处理 | |
| PUT | Replace all current representations of the | 4.3.4 |
| | target resource with the request payload. | |
| | 在请求负载中替换目标资源的当前表示 | |
| DELETE | Remove all current representations of the | 4.3.5 |
| | target resource. | |
| | 移除目标资源的所有现在的表示 | |
| CONNECT | Establish a tunnel to the server identified by | 4.3.6 |
| | the target resource. | |
| | 目标资源定义建立的一个连接服务器的通道 | |
| OPTIONS | Describe the communication options for the | 4.3.7 |
| | target resource. | |
| | 描述目标资源的连接参数 | |
| TRACE | Perform a message loop-back test along the path | 4.3.8 |
| | to the target resource. | |
| | 跟随目标资源执行一个消息回环测试 | |
+---------+-------------------------------------------------+-------+

request-target 是当前请求要求获取的资源

接收者会用空格隔开的方式去格式化 request-line 到它的组件里面。不用空格隔开的话,只能有三个组件。
不幸运的是,一些用户代理不能去合理的解压或者解码超文本里面发现的空格。导致了这些不允许的字符被发送到请求的目标那里。

接收者的一个非法 request-line 应该被请求目标回应 400(bad request)错误 或者 301(永久移除);
接收者不应该尝试去自动更正然后没有重定向地处理这个请求,因为非法的 reqest-line 会跟着请求链故意精心的绕过安全过滤策略。

HTTP并不会预先设置一个request-line的长度。服务器如果接收一个比其他任何请求都长的请求的时候,会反馈一个 501 (没有实现)的状态码。一个服务器接收一个比任何URL都长的请求目标是,它希望解析必须回应一个 414 (URL Too long)的状态码。
1.3、status line 状态行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
status-line = HTTP-version SP status-code SP reason-phrase CRLF

响应消息的第一行就是 status-line,由 协议版本 + SP + statusCode + SP + reason-phrase + CRLF 组成。

status-code = 3DIGIT (三个数字)

状态码用来描述服务器尝试理解和满足客户端相应请求的结果。其他的响应信息是用来解释状态码的定义。
状态码是可以扩展的,客户端不用去理解注册的状态码的含义,尽管这样的理解是明显令人向往的。
但是客户端必须懂得这些状态码的分类,也就是第一个数字,不能把一个未被认可的状态码等价于 X00, 客户端不能缓存一个带有未知名状态码的响应消息。

第一个数字代表状态码的分类,其他两个数字没有明显的分类规则,总共有5类状态码:

1XX (Informational信息): 请求被接收,持续处理的过程。
2XX (Successfull成功): 请求被成功接收,解读和接受。
3XX (Redirection重定向): 为了完成请求过程,更多的动作需要去执行。
4XX (Client error 客户端错误): 请求包含错误的语法或者不能被实现。
5XX (Server error 服务器错误): 服务器不能实现一个显然合法的请求。

reason-phrase = *( HTAB / SP / VCHAR / obs-text )

reason-phrase设计的最底层目的是为了提供一个当前状态码的文本描述。客户端可忽略。
1.4、Header Fields 头部字段

每一个头部字段由一个大小写不敏感的字段名字,跟着一个冒号可选的空格字段值可选的尾部空格组成。

1
2
3
4
5
6
7
8
9
header-field = field-name  ":" OWS(可选的空格) field-value OWS

field-name = token
field-value = *( field-content / obs-fold )
field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
field-vchar = VCHAR / obs-text

obs-fold = CRLF 1*( SP / HTAB )
; obsolete line folding
1.5、message body
1
2
3
4
5
6
7
8
9
10
11
12
13
14
 message-body = *OCTET

一个HTTP消息的消息正文是用来携带请求或者响应的负载正文。
消息体是和有效载荷体是一致的,除非一个传输编码已经被完成。

消息中允许的消息主体的规则在请求和响应中是不同的。

request 请求中的 message body的预定义是被 Content-Length 或 Transfer-Encoding 头部字段控制。

请求消息框架是和method的语义无关的。甚至请求方法对消息体没有任何用处。

响应消息中的message body存在与否决定于请求的方法和响应返回的状态码。
HEAD请求不会返回 message body,因为它只是关联响应头部字段。即使存在 message body ,如果方法是GET也是表明将会显示什么值.
所有 1XX 204 304 都不会有 message body.

2、Standard request fields 标准请求头字段

字段名 说明 举例
A-IM 对请求可接受的接口实例化,同accept A-IM: feed
Accept response可接受的媒体类型 Accept: text/html
其他:text/html; q=1.0, text/*; q=0.8, image/gif; q=0.6, image/jpeg; q=0.6, image/*; q=0.5, */*; q=0.1
Accept-Charset 可接受的字符集 Accept-Charset: utf-8
Accept-Encoding 可接受的文件解码格式 Accept-Encoding: gzip, deflate
Accept-Language response可接受的人类语言 Accept-Language: en-US
Accept-Datetime 可接受的时间版本 Accept-Datetime: Thu, 31 May 2007 20:35:00 GMT
Access-Control-Request-Method,
Access-Control-Request-Headers
利用origin发起跨域资源请求 Access-Control-Request-Method: GET
Authorization HTTP认证 Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
Cache-Control 用来指定一个在请求响应链中所有缓存机制必须遵守的一个指令 Cache-Control: no-cache
Connection 当前连接和持续请求的的控制参数。HTTP/2不得用这个方法,默认持续连接 Connection: keep-alive
Connection: Upgrade
Content-Length request body的长度 Content-Length: 348
Content-Type request body的媒体格式(POST 和 PUT适用) Content-Type: application/x-www-form-urlencoded
Cookie 一个被服务器用set-cookie发送的数据 Cookie: $Version=1; Skin=new;
Date message发出的时间 Date: Tue, 15 Nov 1994 08:12:31 GMT
Expect 客户端表明的特殊的服务器要做的行为 Expect: 100-continue

TCP三次握手和四次挥手过程

TCP三次握手和四次挥手过程

[TOC]

1、三次握手

(1)三次握手的详述

首先Client端发送连接请求报文,Server段接受连接后回复ACK报文,并为这次连接分配资源。Client端接收到ACK报文后也向Server段发生ACK报文,并分配资源,这样TCP连接就建立了

img

最初两端的TCP进程都处于CLOSED关闭状态,A主动打开连接,而B被动打开连接;

A、B关闭状态CLOSED——B收听状态LISTEN——A同步已发送状态SYN-SENT——B同步收到状态SYN-RCVD——A、B连接已建立状态ESTABLISHED

  • B的TCP服务器进程先创建传输控制块TCB,准备接受客户进程的连接请求。然后服务器进程就处于LISTEN(收听)状态,等待客户的连接请求。若有,则作出响应。
  • 1**)第一次握手:A的TCP客户进程也是首先创建传输控制块TCB,然后向B发出连接请求报文段,(首部的同步位SYN=1初始序号seq=x)**,(SYN=1的报文段不能携带数据)但要消耗掉一个序号,此时TCP客户进程进入SYN-SENT(同步已发送)状态。
  • 2**)第二次握手:B收到连接请求报文段后,如同意建立连接,则向A发送确认,在确认报文段中(SYN=1,ACK=1,确认号ack=x+1,初始序号seq=y**),测试TCP服务器进程进入SYN-RCVD(同步收到)状态;
  • 3**)第三次握手:TCP客户进程收到B的确认后,要向B给出确认报文段(ACK=1,确认号ack=y+1,序号seq=x+1**)(初始为seq=x,第二个报文段所以要+1),ACK报文段可以携带数据,不携带数据则不消耗序号。TCP连接已经建立,A进入ESTABLISHED(已建立连接)。
  • 当B收到A的确认后,也进入ESTABLISHED状态。
(2)总结三次握手过程:
  • 第一次握手:起初两端都处于CLOSED关闭状态,Client将标志位SYN置为1,随机产生一个值seq=x,并将该数据包发送给Server,Client进入SYN-SENT状态,等待Server确认;
  • 第二次握手:Server收到数据包后由标志位SYN=1得知Client请求建立连接,Server将标志位SYN和ACK都置为1,ack=x+1,随机产生一个值seq=y,并将该数据包发送给Client以确认连接请求,Server进入SYN-RCVD状态,此时操作系统为该TCP连接分配TCP缓存和变量;
  • 第三次握手:Client收到确认后,检查ack是否为x+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=y+1,并且此时操作系统为该TCP连接分配TCP缓存和变量,并将该数据包发送给Server,Server检查ack是否为y+1,ACK是否为1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client和Server就可以开始传输数据。

起初A和B都处于CLOSED状态——B创建TCB,处于LISTEN状态,等待A请求——A创建TCB,发送连接请求(SYN=1,seq=x),进入SYN-SENT状态——B收到连接请求,向A发送确认(SYN=ACK=1,确认号ack=x+1,初始序号seq=y),进入SYN-RCVD状态——A收到B的确认后,给B发出确认(ACK=1,ack=y+1,seq=x+1),A进入ESTABLISHED状态——B收到A的确认后,进入ESTABLISHED状态。

TCB**传输控制块**Transmission Control Block,存储每一个连接中的重要信息,如TCP连接表,到发送和接收缓存的指针,到重传队列的指针,当前的发送和接收序号。

(3)为什么A还要发送一次确认呢?可以二次握手吗?

  答:主要为了防止已失效的连接请求报文段突然又传送到了B,因而产生错误。如A发出连接请求,但因连接请求报文丢失而未收到确认,于是A再重传一次连接请求。后来收到了确认,建立了连接。数据传输完毕后,就释放了连接,A工发出了两个连接请求报文段,其中第一个丢失,第二个到达了B,但是第一个丢失的报文段只是在某些网络结点长时间滞留了,延误到连接释放以后的某个时间才到达B,此时B误认为A又发出一次新的连接请求,于是就向A发出确认报文段,同意建立连接,不采用三次握手,只要B发出确认,就建立新的连接了,此时A不理睬B的确认且不发送数据,则B一致等待A发送数据,浪费资源。

(4)Server端易受到SYN攻击?

服务器端的资源分配是在二次握手时分配的,而客户端的资源是在完成三次握手时分配的,所以服务器容易受到SYN洪泛攻击,SYN攻击就是Client在短时间内伪造大量不存在的IP地址,并向Server不断地发送SYN包,Server则回复确认包,并等待Client确认,由于源地址不存在,因此Server需要不断重发直至超时,这些伪造的SYN包将长时间占用未连接队列,导致正常的SYN请求因为队列满而被丢弃,从而引起网络拥塞甚至系统瘫痪。

防范SYN攻击措施:降低主机的等待时间使主机尽快的释放半连接的占用,短时间受到某IP的重复SYN则丢弃后续请求。

2、四次挥手

(1)四次挥手的详述

​ 假设Client端发起中断连接请求,也就是发送FIN报文。Server端接到FIN报文后,意思是说”我Client端没有数据要发给你了”,但是如果你还有数据没有发送完成,则不必急着关闭Socket,可以继续发送数据。所以你先发送ACK,”告诉Client端,你的请求我收到了,但是我还没准备好,请继续你等我的消息”。这个时候Client端就进入FIN_WAIT状态,继续等待Server端的FIN报文。当Server端确定数据已发送完成,则向Client端发送FIN报文,”告诉Client端,好了,我这边数据发完了,准备好关闭连接了”。Client端收到FIN报文后,”就知道可以关闭连接了,但是他还是不相信网络,怕Server端不知道要关闭,所以发送ACK后进入TIME_WAIT状态,如果Server端没有收到ACK则可以重传。“,Server端收到ACK后,”就知道可以断开连接了”。Client端等待了2MSL后依然没有收到回复,则证明Server端已正常关闭,那好,我Client端也可以关闭连接了。Ok,TCP连接就这样关闭了!

img

数据传输结束后,通信的双方都可释放连接,A和B都处于ESTABLISHED状态。(A、B连接建立状态ESTABLISHED——A终止等待1状态FIN-WAIT-1——B关闭等待状态CLOSE-WAIT——A终止等待2状态FIN-WAIT-2——B最后确认状态LAST-ACK——A时间等待状态TIME-WAIT——B、A关闭状态CLOSED

  • 1)A的应用进程先向其TCP发出连接释放报文段(FIN=1,序号seq=u),并停止再发送数据,主动关闭TCP连接,进入FIN-WAIT-1(终止等待1)状态,等待B的确认。
  • 2)B收到连接释放报文段后即发出确认报文段,(ACK=1,确认号ack=u+1,序号seq=v),B进入CLOSE-WAIT(关闭等待)状态,此时的TCP处于半关闭状态,A到B的连接释放。
  • 3)A收到B的确认后,进入FIN-WAIT-2(终止等待2)状态,等待B发出的连接释放报文段。
  • 4)B没有要向A发出的数据,B发出连接释放报文段(FIN=1,ACK=1,序号seq=w,确认号ack=u+1),B进入LAST-ACK(最后确认)状态,等待A的确认。
  • 5)A收到B的连接释放报文段后,对此发出确认报文段(ACK=1,seq=u+1,ack=w+1),A进入TIME-WAIT(时间等待)状态。此时TCP未释放掉,需要经过时间等待计时器设置的时间2MSL后,A才进入CLOSED状态。
(2)总结四次挥手过程:

起初A和B处于ESTABLISHED状态——A发出连接释放报文段并处于FIN-WAIT-1状态——B发出确认报文段且进入CLOSE-WAIT状态——A收到确认后,进入FIN-WAIT-2状态,等待B的连接释放报文段——B没有要向A发出的数据,B发出连接释放报文段且进入LAST-ACK状态——A发出确认报文段且进入TIME-WAIT状态——B收到确认报文段后进入CLOSED状态——A经过等待计时器时间2MSL后,进入CLOSED状态

(3)为什么A在TIME-WAIT状态必须等待2MSL的时间?

MSL最长报文段寿命Maximum Segment Lifetime,MSL=2

答:  

两个理由:1**)保证A发送的最后一个ACK报文段能够到达B。2)防止“已失效的连接请求报文段”出现在本连接中。**

  • 1)这个ACK报文段有可能丢失,使得处于LAST-ACK状态的B收不到对已发送的FIN+ACK报文段的确认,B超时重传FIN+ACK报文段,而A能在2MSL时间内收到这个重传的FIN+ACK报文段,接着A重传一次确认,重新启动2MSL计时器,最后A和B都进入到CLOSED状态,若A在TIME-WAIT状态不等待一段时间,而是发送完ACK报文段后立即释放连接,则无法收到B重传的FIN+ACK报文段,所以不会再发送一次确认报文段,则B无法正常进入到CLOSED状态。
  • 2)A在发送完最后一个ACK报文段后,再经过2MSL,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失,使下一个新的连接中不会出现这种旧的连接请求报文段。
(4)为什么连接的时候是三次握手,关闭的时候却是四次握手?

答:因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,”你发的FIN报文我收到了”。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。

(5)为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态?

答:虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是我们必须假象网络是不可靠的,有可以最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。

HTTP简介

HTTP简介

[TOC]

Hypertext transfer protocol(简称:HTTP超文本传输协议HTTPWWW(world wide web 万维网)在一个超文本里面包含超链接的数据通信的基础。

HTTP的发展起始于1989年被 Tim Berners-LeeCERN创立。HTTP标准的发展是被IEIF(Internet Engineering Task Force)W3C (world wide web consortium)调整,并在提出 RFCs(request for comments)后达到了顶点。

HTTP的第一个修订版HTTP/1.1,在1997被广泛应用,不过 在 1999 年被 RFC 2616废弃,之后在2014年又出现了 RFC 7230

稍后,在2015年,提出了 继任者HTTP/2(之后的继任者是HTTP/3, 建立在 HTTP/2之上),现在被大部分主要的web服务器和浏览器通过 TSL(Transport Layer Security 传输层安全协议)使用ALPN (Application-Layer Protocol Negotiation 应用层协议协商)扩展支持的。

1、技术介绍

HTTP方法作为一个请求-响应客户端-服务器计算模式。举例,比如一个web浏览器,或者是一个客户端应用程序运行在拥有网站域名的电脑主机上。客户端发起一个 HTTP request 请求,提供资源比如 HTML或者其他资源或代表客户机执行其他行为的一个服务器,返回一个response message响应的消息给客户端。返回的响应信息包含了请求的完整信息状态和或许会包含请求的内容

浏览器就是一个用户代理(user agent 简称 UA)。其他的用户代理包括,搜索提供服务的索引浏览器(web爬虫),语音浏览器,移动apps和其他软件,访问、消耗或者展示 网页内容。

HTTP被设计去允许网络中间件元素去提高或者加强客户端和服务器之间的通信。高并发的网站经常受益在那些传递在上层服务器提高响应时间的web cache(网页缓存)上。网页浏览器缓存之前获取到的资源然后再减少网络拥堵的时候重复使用这些资源。在私有网络边界的HTTP代理服务器能够促进和通过额外的服务器传递消息的客户端的通信。

HTTP是一个设计在互联网协议套件Internet protocol suite)里的一个应用层协议。它的定义内容假一个潜在的可靠的传输层协议transport layer protocol 简称 TLP )和 传输控制协议Transmission Control Protocol 简称 TCP)被普遍应用。然而,HTTP可以被适用于不可靠额协议比如用户数据报协议 (User Dategram Protocol 简称 UDP),比如,HTTPU 通用即用即插简单的服务发现协议 (Simple Service Discovery Protocol 简称 SSDP)

HTTP资源利用URLs 统一资源存放器(Uniform Resource Locators)定义和存放在网络上,利用URL‘s(Uniform Resource identifiers)统一资源标识符 http 和 https。比如:包含所有的可选择的组件:

URLs在HTML 文档中被解码为超链接,所以成为了相关联的超文本文档。

HTTP/1.1是在原来HTTP(HTTP/1.0)的一次版本更新,在HTTP/1.0,每一次请求服务器资源,都要单独发一次请求连接。在HTTP/1.1,可以在页面被发布后,重复使用一个请求连接去请求下载图片,脚本和样式表等。

2、历史

术语 hypertext最早在1985年被Ted Nelson 提出,反而在1930年的 vannevar Bush 的基于微缩胶卷的信息检索和管理的“memex”系统所激励。后来Berners-Lee和他的团队发明的最初的HTTP, 还有Html和相关的网页服务器和基于文本的网页浏览器的相关的技术,终于被认可。

Broners-lee在1989年 第一次提出了 “worldWideWeb”项目,也就是现在著名的 World Wide Web。第一个版本的协议只有一个方法:GET, 可以从一个服务器请一个页面,从服务器返回的一直是一个页面。

第一个注明的HTTP版本是 HTTP V0.9, Dave Raggett 创建HTTP工作小组(HTTP WG),打算扩大协议扩展业务,扩大谈判,丰富媒体元信息,尝试使用一个安全协议,后来通过增加额外的方法和消息头变得更加有效率。RFC 1945官方介绍和发布认证HTTP V1.0 在1996;

HTTP WG 打算在1995年12月发布一些新的标准和对基于开发中的RFC2068的预标准HTTP/1.1提供支持,并在1996早期就被大部分浏览器开发者适用认可。同年三月,预标准 HTTP/1.1 被大部分浏览器支持。

发展历程:

  • RFC 7230, HTTP/1.1: Message Syntax and Routing
  • RFC 7231, HTTP/1.1: Semantics and Content
  • RFC 7232, HTTP/1.1: Conditional Requests
  • RFC 7233, HTTP/1.1: Range Requests
  • RFC 7234, HTTP/1.1: Caching
  • RFC 7235, HTTP/1.1: Authentication

HTTP/2 作为 RFC 7540 在2015年五月发布。

年份 HTTP 版本
1991 0.9
1996 1.0
1997 1.1
2015 2.0
2018 3.0

3、HTTP session 会话

一个HTTP回话是一系列网络请求-响应失误。一个HTTP客户端通过一个特殊的服务器端口(一般是80,偶尔是8080)建立一个TCP 连接 。一个HTTP server 监听这个端口等待客户端发出请求信息。一旦是收到请求,服务器返回一个状态码:比如 “HTTP/1.1 200 OK”和它自己的消息。此消息的正文一般是请求的资源,或者也许可能是一些错误消息或者其他信息。

3.1 持续连接

HTTP/0.91.0,每一次单独请求/响应后,连接会被关闭。在 HTTP/1.1,一个 keep-alive-mechanism 保持存活机制被介绍,多次单个请求可以共用一个连接通道。这样的持续的连接明显地减少请求延迟时间,因为客户端在第一个请求发出去之后不需要重新谈判和TCP进行三次握手的连接。一般的,连接因为TCPslow-start机制变得更加快速。

3.2 HTTP 会话状态

HTTP是一个无状态协议。一个无状态的协议不会要求HTTP 服务器在多个请求中去保存每一个用户的信息或者状态。但是,一些网页应用程序实现状态或者服务器端会话用做HTTP cookie 或者隐藏在网页表单里的变量。

4、HTTP authentication 认证

HTTP 提供多种验证方案如:基本的 访问验证消化访问验证,通过质问-验证机制凭借服务器标识符和在提供其请求内容之前提出一个质问权限的操作。

Authentication realms 身份验证领域

HTTP身份验证规范提供了一个任意的,实现指定的构造,用于划分单独的资源到一个公共的给定的Root URL; 这个realm值是一个字符串,如果存在,一定是和规范的root URL结合来保护空间抵抗挑战。在一个根目录下,可以单独定义不同的认证作用域。

5、Message format 消息格式

客户端和服务器之间的通信靠发送纯文本消息(ASCII)。客户端发送到服务器,服务器发送响应信息;

5.1 request message 请求消息

请求的消息有以下组成部分:

  • 一个 请求命令行 ,比如 (GET /images/logo.png HTTP/1.1)
  • 请求头 request header fields
  • 一个空行
  • 一个可选择的消息正文

使用React v16.9的Lazy-loading懒加载组件,进行路由封装

使用React v16.9的Lazy-loading懒加载组件,进行路由封装

[TOC]

1568696159178

使用SuspenseReact.lazy(),进行代码分割和懒加载组件:

code-splitting

React官网有相关代码分离的说明,这里不再赘述,使用 import() 动态导入会更加有利于代码的分离。

关于import()语法,如果你使用CRA(create-react-app)搭建的项目,可以直接使用这个语法。如果是自己从零开始搭建的项目,需要babel的插件 babel-plugin-syntax-dynamic-import;

1
2
3
yarn add babel-plugin-syntax-dynamic-import
or
npm install babel-plugin-syntax-dynamic-import

.babelrc 或者 babel.config.js 中增加:

1
2
3
{
"plugins": ["syntax-dynamic-import"]
}

React.lazy()

React.lazy()允许你渲染一个动态引入的组件像普通组件一样执行;

React.lazy 可接受一个返回Promise的调用import()function,这个promise最后resolve一个默认导出当前组件。

一般写法:

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
import React, {lazy} from "react";

// 默认后缀 .jsx
const App = lazy(()=> import("../pages/App"));
const About = lazy(()=> import("../pages/About"));

// 路由配置
const routerConfig = [
{
path: '/',
component: App
},
{
path: '/about',
component: About
},
];

function AppRouter() {
return (
<Suspense fallback={<div>Loading...</div>}>
<Router basename="/">
<Switch>
{routerConfig.map((n, index) => {
return <Route path={n.path} exact component={n.component} key={index}></Route>;
})}
</Switch>
</Router>
</Suspense>
);
}

export default AppRouter;

Lazy组件需要反之在 Suspense 组件内部, Suspense 提供一个 fallback 内容填充,当正在懒加载组件的时候,比如加个loading;

我们下面对lazy进行二次封装:

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
// lazy load
const lazyLoad = path =>
lazy(() => {
return new Promise(resolve => setTimeout(resolve, 1 * 1000)).then(() => import(`../pages/${path}`)).catch(() => import('../components/Error'));
});
//这个方法需要注意的一点,这个 path 不能直接传`相对路径`类似 `../pages/App`,
//会找不到组件,所以最好只传过来一个组件名字,然后内部拼接相对路径
//另外在后面加了一个Promise的reject状态处理,如果组件加载错误,将会默认加载Error组件;
import Loading from '@c/Loading';
const App = lazyLoad('App');
const About = lazyLoad('About');

function AppRouter() {
return (
<Suspense fallback={<Loading />}>
{/* 只有当你的应用部署到服务器的二级目录的时候,才需要设置basename */}
{/* <Router basename="/"> */}
<Router basename="/">
<Switch>
{routerConfig.map((n, index) => {
return <Route path={n.path} exact component={n.component} key={index}></Route>;
})}
</Switch>
</Router>
</Suspense>
);
}

export default AppRouter;

这样就完成了懒加载路由的简单封装。

前端面试总结

最近一直在面试,却很少总结,反思了一下,这是不应该的。所以打算认真写一下。

先说下笔试题

Q1:

关于基本类型和==typeof==, 问的还是挺偏的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// number
typeof NaN
// number
typeof 12
// string
typeof "12"
// object
typeof null
// undefined
typeof undefined
// object
typeof Date
// object
typeof {a:1}
// object
typeof [1]
// function
typeof function(){}
// true
null == undefined
// false
NaN == NaN

类型判断这块的东西,比较基础,却容易混乱,接下来让我们深层次了解下这个东西。

img

既然是数据类型判断,无可避免需要先说一下,==基本类型==有哪几种:

  • String
  • Number
  • null
  • undefined
  • Boolean
  • Symbol(Es6)

这六个属于基本数据类型,当然除此之外还有 ==Object== 这个属于引用类型。对象类型是有名字和键值的一个组合。

看到这里我们不禁会问,==arrays==、==functions==、==regular expressions==、==Date==、==Error==等等又算什么。它们只是特殊的Object.

Example

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
console.log(typeof ""); // "string"
console.log(typeof "hello"); // "string"
console.log(typeof String("hello")); // "string"
console.log(typeof new String("hello")); // "object"

console.log(typeof 0); // "number"
console.log(typeof -0); // "number"
console.log(typeof 0xff); // "number"
console.log(typeof -3.142); // "number"
console.log(typeof Infinity); // "number"
console.log(typeof -Infinity); // "number"
console.log(typeof NaN); // "number"
console.log(typeof Number(53)); // "number"
console.log(typeof new Number(53)); // "object"

console.log(typeof true); // "boolean"
console.log(typeof false); // "boolean"
console.log(typeof new Boolean(true)); // "object"

console.log(typeof undefined); // "undefined"

console.log(typeof null); // "object"

console.log(typeof Symbol()); // "symbol"

console.log(typeof []); // "object"
console.log(typeof Array(5)); // "object"

console.log(typeof function () {}); // "function"
console.log(typeof new Function); // "function"

console.log(typeof new Date); // "object"

console.log(typeof /^(.+)$/); // "object"
console.log(typeof new RegExp("^(.+)$")); // "object"

console.log(typeof {}); // "object"
console.log(typeof new Object); // "object"
value typeof
undefined "undefined"
null "object"
true or false "boolean"
all numbers or NaN "number"
all strings "string"
all symbols "symbol"
all functions "function"
all arrays "object"
native objects "object"
host objects dependent on implementation
other objects "object"

你或许经常碰到这句话;

1557903486895

这句话其实是在某些点上是错的。

很多人也许会问,那为什么有些不是对象但是我们依然可以如下调用对象的方法呢?

1
2
3
4
5
6
// getting length property of the string
(“Hello World!”).length
//   getting the character of the string at index 8
(“Another String”)[8]
//  calling Number.prototype.toFixed() method on the number
(53.12345).toFixed(2)

真实的原因是javascript引擎创造了一个wrapped object 和这个原始语句相对应。

1
2
3
4
5
6
7
8
// wrapper object: new String("Hello World!")
(new String("Hello World!")).toLowerCase();

// wrapper object: new String("Another String")
(new String("Another String"))[8];

// wrapper object: new Number(53.12345)
(new Number(53.12345)).toFixed(2);

再次说下类型比较,==null== 和 ==[]==通过==typeof== 判断都返回 ==object==, 此时我们怎么样才能进行深层次的区分呢?

比较精确的类型比较,大概要通过下面几种方法
  • instanceof operator

  • checking the constructor property of the object

  • 使用 toString() 方法检查类型.

Checking for null

1
2
3
4
5
6
function isNull(val){
return val === null
}

console.log(undefined == null); // true
console.log(undefined === null); // false

Checking for NaN

NaN是一个计算导致的结果,一般没有实质意思。前面说过 typeof NaN = number 如果想进一步区分,可以通过 isNaN 或者 es6的 Number.isNaN()

1
2
3
4
5
6
7
8
9
10
11
typeof NaN //number

console.log(isNaN(NaN)); // true
console.log(isNaN(null)); // false
console.log(isNaN(undefined)); // true
console.log(isNaN(Infinity)); // false

console.log(Number.isNaN(NaN)); // true
console.log(Number.isNaN(null)); // false
console.log(Number.isNaN(undefined)); // false
console.log(Number.isNaN(Infinity)); // false

最终用法,兼容es6:

1
2
3
Number.isNaN = Number.isNaN || (function(value) {
return value !== value;
})

Checking for arrays

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

// METHOD 1: constructor property
// Not reliable
function isArray(value) {
return typeof value == 'object' && value.constructor === Array;
}

// METHOD 2: instanceof
// Not reliable since an object's prototype can be changed
// Unexpected results within frames
function isArray(value) {
return value instanceof Array;
}

// METHOD 3: Object.prototype.toString()
// Better option and very similar to ES6 Array.isArray()
function isArray(value) {
return Object.prototype.toString.call(value) === '[object Array]';
}

// METHOD 4: ES6 Array.isArray()
function isArray(value) {
return Array.isArray(value);
}

其实最常用的 Object.prototype.toString.call(data) 是很有用的(apply也可以)。不过我们可以封装一下:

1
2
3
4
5
function type(value){
const regex = /^\[object (\S+?)\]$/;
const matches = Object.prototype.toString.call(value).match(regex) || [];
return (matches[1] || "undefined").toLowerCase();
}

Example

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
47
console.log(type('')); // "string"
console.log(type('hello')); // "string"
console.log(type(String('hello'))); // "string"
console.log(type(new String('hello'))); // "string"

console.log(type(0)); // "number"
console.log(type(-0)); // "number"
console.log(type(0xff)); // "number"
console.log(type(-3.142)); // "number"
console.log(type(Infinity)); // "number"
console.log(type(-Infinity)); // "number"
console.log(type(NaN)); // "number"
console.log(type(Number(53))); // "number"
console.log(type(new Number(53))); // "number"

console.log(type(true)); // "boolean"
console.log(type(false)); // "boolean"
console.log(type(new Boolean(true))); // "boolean"

console.log(type(undefined)); // "undefined"

console.log(type(null)); // "null"

console.log(type(Symbol())); // "symbol"
console.log(type(Symbol.species)); // "symbol"

console.log(type([])); // "array"
console.log(type(Array(5))); // "array"

console.log((function() { return type(arguments) })()); // "arguments"

console.log(type(function() {})); // "function"
console.log(type(new Function)); // "function"

console.log(type(class {})); // "function"

console.log(type({})); // "object"
console.log(type(new Object)); // "object"

console.log(type(/^(.+)$/)); // "regexp"
console.log(type(new RegExp("^(.+)$"))); // "regexp"

console.log(type(new Date)); // "date"
console.log(type(new Set)); // "set"
console.log(type(new Map)); // "map"
console.log(type(new WeakSet)); // "weakset"
console.log(type(new WeakMap)); // "weakmap"

Q2:

考察变量作用域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var name = "Hello body";
var a = {
name: "A",
sayHi: function(){
console.log(this.name)
}
};
var b = {
name: 'B'
};
var sayHi = a.sayHi;
name = "Hello C";

//依次写出返回值
console.log(name);//Hello C
// 此时 this 指向仍是 a
a.sayHi();// A
// 此时 this 指向是 window 也就是全局变量 name
sayHi(); // Hello C
// 改变 this 指向 b
a.sayHi.call(b);// 'B'

这应该是个大部分面试都会问的问题吧。

1557908240751

搞笑一下,言归正传,继续。

Q3:

考察 栈(Stack) && 堆(Heap)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let name = "a";
const a = {
name: name,
php: ['Hello']
}
const b = a;
name = "CC";
b.php = ['world'];

/**
* a = b = {name: 'a', php: [world]}
*/
console.log(a)
console.log(b)

Stack

存储有顺序的数据,所以存储 variables和指向堆内存中的该对象的指针

heap

无序存储数据,放置 objects

1557916548775

扩展知识:

javascript 是一种垃圾回收机制的语言。所谓垃圾回收,就是用完就会注销。

并且是一种==单线程并发==的语言。

Q4:

trim 去除前后空格,实现方法?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const str = " hello world ";
function trim(str){
const regex = /^\s|\s+$/g;
return str.replace(regex, '');
}
console.log(trim(str));// "hello world"

// trimLeft
String.prototype.trimLeft = function(){
return this.replace(/^\s+/g, '');
}
// trimRight
String.prototype.trimRight = function(){
return this.replace(/\s+$/g, '');
}

Q5:

请把“my-name-is-andy”变成驼峰格式?

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
const str = "my-name-is-andy";
// 正则
function formatString(str){
if(!str) return '';
return str.replace(/-(\w+)/g, function(...args){
return args[1] ? args[1].replace(/^\w{1}/g, function($1){
return $1.toUpperCase();
}) : str
})
}

// 第二种
function formatString(str){
if(!str) return '';
if(str.includes('-')){
const arr = str.split('-');
let reuslt = '';
// forEach 循环 n 是不能被重新赋值,如果是object,array 则可以操作增加删除
arr.forEach(function(n,i){
if(i>0){
result += n.substr(0, 1).toUpperCase() + n.substr(1);
}else{
result = n;
}
})
return result;
}
}

扩展知识:

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
47
48
49
50
51
52
53
54
Array.forEach(function callback(currentValue[, index [, array]]){
// your iterator
}[, thisArg])


Example:

//base useage
[1,2,3].forEach((n, i)=>{
console.log(`a[${i}]=${n}`);
});
/*
a[0]=1
a[1]=2
a[2]=3
*/


// thisArg

const items = [1, 2, 3];
function Counter(){
this.sum = 0;
this.count = 0;
}
Counter.prototype.add = function(arr){
arr.forEach(function(n){
this.sum += n;
this.count++;
}, this)
};
const obj = new Counter();
obj.add(items);
console.log(obj.sum);//6
console.log(obj.count);//3


// deep copy a object 深度复制
function copy(obj){
const copy = Object.create(Object.getPrototypeOf(obj));
const propNames = Object.getOwnPropertyNames(obj);

propNames.forEach(function(name){
const desc = Object.getOwnPropertyDescriptor(obj, name);
Object.defineProperty(copy, name, desc);
})
return copy;
}

const obj1 = {a: 1};// {a: 1}
const obj2 = copy(obj1);// {a: 1}
obj1.a = 2;
console.log(obj1);// {a: 2}
console.log(obj2);// {a: 1}

Q6:

执行以下代码,会发送什么,又如何改进?

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
const spans = document.getElementsByTagName('span');// 5个
for(var i=0; i< spans.length; i++){
spans[i].onclick = function(){
alert(i)
}
}
console.log(i)//5
/**
结果是 点击任何一个span 都是 alert(5)
var i = 0 其实是一个全局变量,整个循环结束后 i == spans.length 依然存在
*/

// 改进1 var 更改为 let,这样 i 就是一个当前代码块里的一个局部变量
for(let i=0; i< spans.length; i++){
spans[i].onclick = function(){
alert(i)
}
}
console.log(i)// Uncaught ReferenceError: i is not defined


// 使用闭包 closure functon 这样就避免了最后只读 i
for(let i=0; i<spans.length;i++){
spans[i].onclick = (function(n){
return function(){
alert(n)
}
})(i)
}

Q7:

写出Es6的几种用法

1
2


下是面谈的问题

Q8:

Vue 生命周期实现过程?

1
2


Q9:

vuex中 mumation 和 action 有什么不同? 实现原理是什么?

1
2


Q10:

vue v-if 和 v-show 的区别? Key的作用?

1
2


Q11:

vue keep-alive 用法?

Q12:

vue router 如何返回和刷新?

Q13:

Vue 框架特点

Q14

es6 箭头函数

Q15:

bind 和 call apply 区别

Q16:

Promise 和 async await 区别?

Q17:

移动端适配

1
2


{% include 'gitalk.ejs' %}
  • © 2014-2020 Alex Wong
  • Powered by Hexo Theme Ayer
  • PV: UV:

请我喝杯咖啡吧~~~

支付宝
微信