HTTP协议原理分享

http请求和tcp链接不是一个概念,在一个tcp链接里,可以发送多个http请求,之前的协议版本是不可以这么做的,从http/1.1里面就可以这样做了,tcp链接对应的是多个http请求,而一个http请求肯定是在某个tcp链接里面进行发送的。

快速导航

5层网络模型介绍

互联网的实现分为好几层,每层都有自己的功能,向城市里的高楼一样,每层都需要依赖下一层,对于用户接触到的,只是上面最高一层,当然,如果要了解互联网,就必须从最下层开始自下而上理解每一层的功能。

应用层

构建于TCP协议之上,为应用软件提供服务,应用层也是最高的一层直接面向用户。

  • www万维网

  • FTP文件传输协议

  • DNS协议: 域名与IP的转换

  • 邮件传输

  • DCHP协议

传输层

传输层向用户提供可靠的端到端(End-to-End)服务,主要有两个协议分别是TCP、 UDP协议, 大多数情况下我们使用的是TCP协议,它是一个更可靠的数据传输协议。

  • 协议对比TCP

    • 面向链接: 需要对方主机在线,并建立链接。
    • 面向字节流: 你给我一堆字节流的数据,我给你发送出去,但是每次发送多少是我说了算,每次选出一段字节发送的时候,都会带上一个序号,这个序号就是发送的这段字节中编号最小的字节的编号。
    • 可靠: 保证数据有序的到达对方主机,每发送一个数据就会期待收到对方的回复,如果在指定时间内收到了对方的回复,就确认为数据到达,如果超过一定时间没收到对方回复,就认为对方没收到,在重新发送一遍。
  • 协议对比UDP

    • 面向无链接: 发送的时候不关心对方主机在线,可以离线发送。
    • 面向报文: 一次发送一段数据。
    • 不可靠: 只负责发送出去,至于接收方有没有收到就不管了。

网络层

数据链路层

物理层

http协议发展历史

http是基于TCP/IP之上的应用层协议,也是互联网的基础协议,最新http2协议基于信道复用,分帧传输在传输效率上也有了大幅度的提升

阶段一

http/0.9

只有一个命令GET,对应我们现在的请求GET、POST,没有header等描述数据的信息,服务器发送完毕数据就关闭TCP链接,每个http请求都要经历一次dns域名解析、传输和四次挥手,这样反复创建和断开tcp链接的开销是巨大的,在现在看来这种方式很糟糕。

阶段二

http/1.0

  • 增加了很多命令POST、GET、HEAD
  • 等增status code和header

status code描述服务端处理某一个请求之后它的状态, header是不管发送还是请求一个数据它的描述。

  • 多字符集支持、多部分发送、权限、缓存等。

阶段三

http/1.1

  • 持久链接
  • 管道机制(pipeline)

可以在同一个链接里发送多个请求,但是在服务端对于进来的请求都是要按照顺序进行内容的返回,如果前一个请求很慢,后一个请求很多,它也需要第一个请求发送之后,后一个请求才可以发送,这块在http2里面进行了优化

  • 增加host和其他功能

增加host可以在同一台物理服务器上跑多个web服务,例如一个nodejs的web服务,一个java的web服务

阶段四

http/2

  • 所有数据以二进制传输
  • 同一个链接里面发送多个请求,不在需要按照顺序来
  • 头信息压缩以及推送等提高效率的功能

http三次握手

先清楚一个概念http请求与tcp链接之间的关系,在客户端向服务端请求和返回的过程中,是需要去创建一个TCP connection,因为http是不存在链接这样一个概念的,它只有请求和响应这样一个概念,请求和响应都是一个数据包,中间要通过一个传输通道,这个传输通道就是在TCP里面创建了一个从客户端发起和服务端接收的一个链接,TCP链接在创建的时候是有一个三次握手(三次网络传输)这样一个消耗在的。

客户端与服务器端的一次请求

HTTP - 图1

三次握手时序图

HTTP - 图2

第一次握手: 建立连接,客户端A发送SYN=1、随机产生Seq=client_isn的数据包到服务器B,等待服务器确认。

第二次握手: 服务器B收到请求后确认联机(可以接受数据),发起第二次握手请求,ACK=(A的Seq+1)、SYN=1,随机产生Seq=client_isn的数据包到A。

第三次握手: A收到后检查ACK是否正确,若正确,A会在发送确认包ACK=服务器B的Seq+1、ACK=1,服务器B收到后确认Seq值与ACK值,若正确,则建立连接。

TCP标示:

  • SYN(synchronous建立联机)
  • ACK(acknowledgement 确认)
  • Sequence number(顺序号码)

三次握手数据包详细内容分析

这里采用的是wireshark 官网地址 https://www.wireshark.org/,是一个很好的网络数据包抓取和分析软件。

示例采用的网址http://news.baidu.com/,windows下打开cmd、Mac下打开终端ping下得到ip可以利用wireshark工具进行一次ip地址过滤,只分析指定的数据。

  • 第一次握手,客户端发送一个TCP,标志位为SYN,Seq(序列号)=0,代表客户端请求建立链接,如下图所示

HTTP - 图3

  • 第二次握手,服务器发回数据包,标志位为[SYN, ACK],ACK设置为客户端第一次握手请求的Seq+1,即ACK=0+1=1,在随机产生一个Seq的数据包到客户端。

HTTP - 图4

  • 第三次握手请求,客户端在次发送确认数据包,标识位为ACK,把服务器发来的Seq+1,即ACK=0+1,发送给服务器,服务器成功收到ACK报文段之后,连接就建立成功了。

HTTP - 图5

总结

至于为什么要经过三次握手呢,是为了防止服务端开启一些无用的链接,网络传输是有延时的,中间可能隔着非常远的距离,通过光纤或者中间代理服务器等,客户端发送一个请求,服务端收到之后如果直接创建一个链接,返回内容给到客户端,因为网络传输原因,这个数据包丢失了,客户端就一直接收不到服务器返回的这个数据,超过了客户端设置的时间就关闭了,那么这时候服务端是不知道的,它的端口就会开着等待客户端发送实际的请求数据,服务这个开销也就浪费掉了。

URI/URL/URN

URI

Uniform Resource Identifier/统一资源标志符,用来标示互联网上唯一的信息资源,包括URL和URN。

URL

Uniform Resource Locator/统一资源定位器

URN

永久统一资源定位符,例如资源被移动后如果是URL则会返回404,在URN中资源被移动之后还能被找到,当前还没有什么成熟的使用方案

跨域cors

关于浏览器跨域的原理,一个请求在浏览器端发送出去后,是会收到返回值响应的,只不过浏览器在解析这个请求的响应之后,发现不属于浏览器的同源策略(地址里面的协议、域名和端口号均相同),会进行拦截。如果是在curl里面发送一个请求,都是没有跨域这样一个概念的,下面是例子进行分析:

示例

server.html

在这个html里面采用ajax请求3011这个服务

  1. <html>
  2. <head>
  3. <meta charset="utf-8" />
  4. <title>cors</title>
  5. </head>
  6. <body>
  7. <script>
  8. const xhr = new XMLHttpRequest();
  9.  
  10. xhr.open('GET', 'http://127.0.0.1:3011/');
  11. xhr.send();
  12. </script>
  13. </body>
  14. </html>

server.3011.js

  1. const http = require('http');
  2. const port = 3011;
  3.  
  4. http.createServer((request, response) => {
  5. console.log('request url: ', request.url);
  6.  
  7. response.end('3011 port serve for you');
  8. }).listen(port);
  9.  
  10. console.log('server listening on port ', port);

server.3010.js

对上面定义的server.html模版进行渲染,从而在该模版里调用3011这个端口服务

  1. const http = require('http');
  2. const fs = require('fs');
  3. const port = 3010;
  4.  
  5. http.createServer((request, response) => {
  6. console.log('request url: ', request.url);
  7.  
  8. const html = fs.readFileSync('server.html', 'utf-8');
  9.  
  10. response.writeHead(200, {
  11. 'Content-Type': 'text/html',
  12. });
  13. response.end(html);
  14.  
  15. }).listen(port);
  16.  
  17. console.log('server listening on port ', port);

打开浏览器,地址栏输入http://127.0.0.1:3010/,会看到以下提示not allowed access,但是server.3011.js,会打印出 request url: /,说明浏览器在发送这个请求的时候并不知道服务是不是跨域的,还是会发送请求并接收服务端返回的内容,但是当浏览器接收到响应后在headers里面没有看到 Access-Control-Allow-Origin: 被设置为允许,会把这个请求返回的内容给忽略掉,并且在命令行报下面的错误。

  1. Failed to load http://127.0.0.1:3011/: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://127.0.0.1:3010' is therefore not allowed access.

基于http协议层面的几种解决办法

  • 设置Access-Control-Allow-Origin

    • 设置为*表示,可以接收任意域名的访问
  1. http.createServer((request, response) => {
  2.  
  3. response.writeHead(200, {
  4. 'Access-Control-Allow-Origin': '*'
  5. })
  6. }).listen(port);
  • 也可以设置为特定域名访问
  1. http.createServer((request, response) => {
  2.  
  3. response.writeHead(200, {
  4. 'Access-Control-Allow-Origin': 'http://127.0.0.1:3010/'
  5. })
  6. }).listen(port);
  • 如果有多个域名访问可以在服务端动态设置
  1. http.createServer((request, response) => {
  2. const origin = request.headers.origin;
  3.  
  4. if ([
  5. 'http://127.0.0.1:3010'
  6. ].indexOf(origin) !== -1) {
  7. response.writeHead(200, {
  8. 'Access-Control-Allow-Origin': origin,
  9. })
  10. }
  11. }).listen(port);
  • jsonp

浏览器是允许像link、img、script标签在路径上加载一些内容进行请求,是允许跨域的,那么jsonp的实现原理就是在script标签里面加载了一个链接,去访问服务器的某个请求,返回内容。

  1. <html>
  2. <head>
  3. <meta charset="utf-8" />
  4. <title>cors</title>
  5. </head>
  6. <body>
  7. <!-- <script>
  8. const xhr = new XMLHttpRequest();
  9.  
  10. xhr.open('GET', 'http://127.0.0.1:3011/');
  11. xhr.send();
  12. </script> -->
  13.  
  14. <script src="http://127.0.0.1:3011/"></script>
  15. </body>
  16. </html>

CORS预请求

预请求也是浏览器的一种安全机制,会先发送一个OPTIONS请求给目的站点,与跨域服务器协商可以设置的头部信息,允许的方法和headers信息等。

  • Access-Control-Allow-Origin: 跨域服务器允许的来源地址(跟请求的Origin进行匹配),可以是*或者某个确切的地址,不允许多个地址

  • Access-Control-Allow-Methods: 允许的方法GET、HEAD、POST

  • Access-Control-Allow-Headers: 允许的Content-Type

    • text/plain

    • multipart/form-data

    • application/x-www-form-urlencoded

  • Access-Control-Max-Age: 预请求的返回结果(Access-Control-Allow-Methods和Access-Control-Allow-Headers)可以被缓存的时间,单位秒

  • 请求头限制

  • XMLHttpRequestUpload对象均没有注册任何事件监听器

  • 请求中没有使用ReadableStream对象

修改server.html

  1. <html>
  2. <head>
  3. <meta charset="utf-8" />
  4. <title>cors</title>
  5. </head>
  6. <body>
  7. <script src="https://cdnjs.cloudflare.com/ajax/libs/fetch/2.0.4/fetch.min.js"></script>
  8. <script>
  9. fetch('http://127.0.0.1:3011/', {
  10. method: 'PUT',
  11. headers: {
  12. 'X-Test-Cors': 'test'
  13. }
  14. })
  15. </script>
  16. </body>
  17. </html>

在次运行后浏览器提示如下信息,上面自定义的头X-Test-Cors在跨域请求的时候是不被允许的.

  1. Failed to load http://127.0.0.1:3011/: Request header field X-Test-Cors is not allowed by Access-Control-Allow-Headers in preflight response.
  1. Failed to load http://127.0.0.1:3011/: Method PUT is not allowed by Access-Control-Allow-Methods in preflight response.

修改后端服务server.3011.js处理OPTIONS请求,设置相应的跨域响应头

  1. const http = require('http');
  2. const port = 3011;
  3.  
  4. http.createServer((request, response) => {
  5. response.writeHead(200, {
  6. 'Access-Control-Allow-Origin': '*',
  7. 'Access-Control-Allow-Headers': 'X-Test-Cors',
  8. 'Access-Control-Allow-Methods': 'PUT, DELETE',
  9. 'Access-Control-Max-Age': '1000',
  10. })
  11. response.end('3011');
  12. }).listen(port);
  13.  
  14. console.log('server listening on port ', port);

运行结果

第一次先发送了一个OPTIONS请求询问

HTTP - 图6

第二次发送一个PUT数据请求

HTTP - 图7

缓存头Cache-Control的含义和使用

可缓存性

  • public http经过的任何地方都可以进行缓存

  • private 只有发起请求的这个浏览器才可以进行缓存,如果设置了代理缓存,那么代理缓存是不会生效的

  • no-cache 任何一个节点都不可以缓存

到期

  • max-age= 设置缓存到多少秒过期

  • s-maxage= 会代替max-age,只有在代理服务器(nginx代理服务器)才会生效

  • max-stale= 是发起请求方主动带起的一个头,是代表即便缓存过期,但是在max-stale这个时间内还可以使用过期的缓存,而不需要愿服务器请求新的内容

重新验证

  • must-revalidate 如果max-age设置的内容过期,必须要向服务器请求重新获取数据验证内容是否过期

  • proxy-revalidate 主要用在缓存服务器,指定缓存服务器在过期后重新从原服务器获取,不能从本地获取

其它

  • no-store 本地和代理服务器都不可以存储这个缓存,永远都要从服务器拿body新的内容使用

  • no-transform 主要用于proxy服务器,告诉代理服务器不要随意改动返回的内容

缓存cache-control示例

  • 先思考两个问题?

    • 在页面中引入静态资源文件,为什么静态资源文件改变后,再次发起请求还是之前的内容,没有变化呢?
    • 在使用webpack等一些打包工具时,为什么要加上一串hash码?
  • cache-control.html
  1. <html>
  2. <head>
  3. <meta charset="utf-8" />
  4. <title>cache-control</title>
  5. </head>
  6. <body>
  7. <script src="/script.js"></script>
  8. </body>
  9. </html>
  • cache-control.js

浏览器输入http://localhost:3010/ 加载cache-control.html文件,该文件会请求http://localhost:3010/script.js,在url等于/script.js设置cache-control的max-age进行浏览器缓存

  1. const http = require('http');
  2. const fs = require('fs');
  3. const port = 3010;
  4.  
  5. http.createServer((request, response) => {
  6. console.log('request url: ', request.url);
  7.  
  8. if (request.url === '/') {
  9. const html = fs.readFileSync('./example/cache/cache-control.html', 'utf-8');
  10.  
  11. response.writeHead(200, {
  12. 'Content-Type': 'text/html',
  13. });
  14.  
  15. response.end(html);
  16. } else if (request.url === '/script.js') {
  17. response.writeHead(200, {
  18. 'Content-Type': 'text/javascript',
  19. 'Cache-Control': 'max-age=200'
  20. });
  21.  
  22. response.end("console.log('script load')");
  23. }
  24.  
  25. }).listen(port);
  26.  
  27. console.log('server listening on port ', port);
  • 第一次运行

浏览器运行结果,没有什么问题,正常响应

HTTP - 图8

控制台运行结果

HTTP - 图9

  • 修改cache-control.js返回值
  1. ...
  2. response.writeHead(200, {
  3. 'Content-Type': 'text/javascript',
  4. 'Cache-Control': 'max-age=200'
  5. });
  6.  
  7. response.end("console.log('script load !!!')");
  8. ...
  • 中断上次程序,第二次运行

浏览器运营结果

第二次运行,从memory cahce读取,浏览器控制台并没有打印修改过的内容

HTTP - 图10

控制台运营结果

指请求了/ 并没有请求 /script.js

HTTP - 图11

以上结果浏览器并没有返回给我们服务端修改的结果,这是为什么呢?是因为我们请求的url/script.js没有变,那么浏览器就不会经过服务端的验证,会直接从客户端缓存去读,就会导致一个问题,我们的js静态资源更新之后,不会立即更新到我们的客户端,这也是前端开发中常见的一个问题,我们是希望浏览器去缓存我们的静态资源文件(js、css、img等)我们也不希望服务端内容更新了之后客户端还是请求的缓存的资源, 解决办法也就是我们在做js构建流程时,把打包完成的js文件名上根据它内容hash值加上一串hash码,这样你的js文件或者css文件等内容不变,这样生成的hash码就不会变,反映到页面上就是你的url没有变,如果你的文件内容有变化那么嵌入到页面的文件url就会发生变化,这样就可以达到一个更新缓存的目的,这也是目前前端来说比较常见的一个静态资源方案。

资源验证

如果使用cahce-control浏览器发起一个请求到缓存查找的一个过程流程图

HTTP - 图12

验证头
  • Last-Modified 上次修改时间,配合If-Modified-Since或者If-Unmo dified-Since使用,对比上次修改时间以验证资源是否可用

  • Etag 数据签名,配合If-Match或者If-Non-Match使用,对比资源的签名判断是否使用缓存

通过Set-Cookie设置,下次请求会自动带上,键值对形式可以设置多个

  • max-age或expires设置过期时间

  • Secure只在https发送

  • httpOnly无法通过document.cookie访问

示例

  • cookie.html

控制台打印输出cookie信息

  1. <html>
  2. <head>
  3. <meta charset="utf-8" />
  4. <title>Cookie</title>
  5. </head>
  6. <body>
  7. <script>
  8. console.log(document.cookie);
  9. </script>
  10. </body>
  11. </html>
  • cookie.js

设置两个cookie,a=111 设置过期时间2秒钟,b=222设置httpOnly

  1. const http = require('http');
  2. const fs = require('fs');
  3. const port = 3010;
  4.  
  5. http.createServer((request, response) => {
  6. console.log('request url: ', request.url);
  7.  
  8. if (request.url === '/') {
  9. const html = fs.readFileSync('./cookie.html', 'utf-8');
  10.  
  11. response.writeHead(200, {
  12. 'Content-Type': 'text/html',
  13. 'Set-Cookie': ['a=111;max-age=2', 'b=222; httpOnly'],
  14. });
  15.  
  16. response.end(html);
  17. }
  18.  
  19. }).listen(port);
  20.  
  21. console.log('server listening on port ', port);
  • 返回结果

可以看到当b=222设置了httpOnly之后,js就无法读取到该cookie值,示例中只输出了a=111

HTTP - 图13

如果想要在一个域名的二级域名中共享同一个cookie需要做domain设置

以下例子中,假设test.com是一级域名,设置一些cookie信息,同时设置domain,使得二级域名可以共享,在之后的二级域名例如 a.test.com, b.test.com访问中都可以访问到同一个cookie信息。

  1. const http = require('http');
  2. const fs = require('fs');
  3. const port = 3010;
  4.  
  5. http.createServer((request, response) => {
  6. console.log('request url: ', request.url);
  7.  
  8. if (request.url === '/') {
  9. const html = fs.readFileSync('./cookie.html', 'utf-8');
  10.  
  11. if (request.headers.host === 'test.com') {
  12. response.writeHead(200, {
  13. 'Content-Type': 'text/html',
  14. 'Set-Cookie': ['a=111;max-age=2', 'b=222; httpOnly; domain=test.com'],
  15. });
  16. }
  17.  
  18. response.end(html);
  19. }
  20.  
  21. }).listen(port);
  22.  
  23. console.log('server listening on port ', port);

http长链接

http的请求是在tcp链接之上进行发送,tcp链接分为长链接、短链接的概念,http发送请求的时候会先创建一个tcp链接,在tcp连接上把http请求的内容发送,并接收返回,这个时候一次请求就结束了,浏览器会和服务端商量,要不要把这次tcp链接给关闭到,如果不关闭,这个tcp链接就会一直开着,会有消耗,但是接下去如果还有请求,就可以直接在这个tcp链接上进行发送,那么就不需要经过三次握手这样的一个链接消耗,而如果直接关闭,那么在下次http请求的时候就需要在创建一个tcp链接,长链接是可以设置timeout的,可以设置多长时间在这个tcp链接上没有新的请求就会关闭

http/1.1

http/1.1的链接在tcp上去发送请求是有先后顺序的,例如你有10个请求是不可以并发的在一个tcp链接上去发送,浏览器是可以允许并发的创建一个tcp链接,chrome允许的是6个,一次性的并发,如果你有10个只能等前面6个其中一个完成,新的请求在进去。

http/1.1长链接示例

  • connection.html
  1. <html>
  2. <head>
  3. <meta charset="utf-8" />
  4. <title>Connection</title>
  5. </head>
  6. <body>
  7. <img src="/test1.jpg" alt="" />
  8. <img src="/test2.jpg" alt="" />
  9. <img src="/test3.jpg" alt="" />
  10. <img src="/test4.jpg" alt="" />
  11. <img src="/test5.jpg" alt="" />
  12. <img src="/test6.jpg" alt="" />
  13. <img src="/test7.jpg" alt="" />
  14. <img src="/test8.jpg" alt="" />
  15. </body>
  16. </html>
  • connection.js
  1. const http = require('http');
  2. const fs = require('fs');
  3. const port = 3010;
  4.  
  5. http.createServer((request, response) => {
  6. console.log('request url: ', request.url);
  7.  
  8. const html = fs.readFileSync('./connection.html', 'utf-8');
  9. const img = fs.readFileSync('./test_img.jpg');
  10.  
  11. if (request.url === '/') {
  12. response.writeHead(200, {
  13. 'Content-Type': 'text/html',
  14. });
  15.  
  16. response.end(html);
  17. } else {
  18. response.writeHead(200, {
  19. 'Content-Type': 'image/jpg'
  20. });
  21.  
  22. response.end(img);
  23. }
  24. }).listen(port);
  25.  
  26. console.log('server listening on port ', port);
  • 返回结果

可以看到第一次图片加载时复用了第一次localhost的tcp链接,最后两张图片一直在等待前面的tcp链接完成,有一定的响应等待

HTTP - 图14

http/2

在http/2中有了一个新的概念信道复用,在TCP连接上可以并发的去发送http请求,链接一个网站只需要一个TCP链接(同域的情况下)

HTTP - 图15

数据协商

  1. // todo

csp

Content-Security-Policy内容安全策略,限制资源获取

参考文档 内容安全策略 (CSP) - Web 安全 | MDN

限制方式

  • default-src限制全局

  • 制定资源类型

  1. connect-src manifest-src img-src style-src script-src frame-src font-src media-src ...

参考示例

web领域非常著名的一个攻击方式xss,是通过某些方法在网站里面注入一些别人写好的脚本,窃取一些用户的信息,处于安全考虑不希望执行写在页面里面的一些脚本,可以在返回的headers里面设置Content-Security-Policy。

csp.html

  1. <html>
  2. <head>
  3. <meta charset="utf-8" />
  4. <title>cache-control</title>
  5. </head>
  6. <body>
  7. <script>
  8. console.log('hello world!!!');
  9. </script>
  10. <script src="/script.js"></script>
  11. </body>
  12. </html>

csp.js

在head里设置Content-Security-Policy只能加载http https

  1. const http = require('http');
  2. const fs = require('fs');
  3. const port = 3010;
  4.  
  5. http.createServer((request, response) => {
  6. console.log('request url: ', request.url);
  7.  
  8. if (request.url === '/') {
  9. const html = fs.readFileSync('csp.html', 'utf-8');
  10.  
  11. response.writeHead(200, {
  12. 'Content-Type': 'text/html',
  13. 'Content-Security-Policy': 'default-src http: https',
  14. });
  15.  
  16. response.end(html);
  17. }
  18. }).listen(port);

运行结果

HTTP - 图16

更多的设置方式

  • 限制外链加载的javascript文件只能通过哪些域名加载

只能根据本域名下的js内容进行加载

  1. response.writeHead(200, {
  2. 'Content-Security-Policy': 'default-src \'self\'',
  3. });
  • 限制指定某个网站
  1. response.writeHead(200, {
  2. 'Content-Security-Policy': 'default-src \'self\' https://www.baidu.com/',
  3. });
  • 限制form表单的提交
  1. response.writeHead(200, {
  2. 'Content-Security-Policy': 'default-src \'self\'; form-action \'self\'',
  3. });
  • 内容安全策略如果出现我们不希望的情况,可以让它主动申请向我们服务器发送一个请求进行汇报
  1. // report-uri 跟上服务器的url地址
  2. response.writeHead(200, {
  3. 'Content-Security-Policy': 'default-src \'self\'; report-uri /report',
  4. });
  • 除了在服务端通过headers指定还可以在html里面通过meta标签写

注意:在html标签里通过meta写report-uri是不支持的,建议还用通过headers设置

  1. <meta http-equiv="Content-Security-Policy" content="default-src http: https">

更多内容可参考 CSP的CDN 参考文档 内容安全策略 (CSP) - Web 安全 | MDN

nginx服务配置

nginx出发点就是一个http的服务,一个纯粹做http协议的服务

windows安装可参考以下

nginx: download

Mac安装

  • 安装

brew install nginx

  • 查看版本

nginx -v

  • 安装位置

/usr/local/etc/nginx

  • 启动

sudo nginx

  • 查看 nginx 是否启动成功

在浏览器中访问 http://localhost:8080,如果出现如下界面,则说明启动成功.

HTTP - 图17

  • 关闭nginx

sudo nginx -s stop

  • 重新加载nginx

sudo nginx -s reload

修改hosts文件配置本地域名

hosts位置:

  • Windows C:\windows\system32\drivers\etc\hosts
  • Mac /private/etc/hosts
  • Ubuntu /etc/hosts

vim hosts

  1. ##
  2. # Host Database
  3. #
  4. # localhost is used to configure the loopback interface
  5. # when the system is booting. Do not change this entry.
  6. ##
  7. 127.0.0.1 test.com

保存以上配置即可,127.0.0.1 test.com 在浏览中输入www.test.com域名,就可访问本地指定的网站,仅限于本地。

注意 Nginx中,要做好conf配置,让这些域名有所访问的对象,例如下面Nginx配置缓存处的test.com指向http://127.0.0.1:3010

查看是否配置成功 可以打开cmd ping 一下配置的余名,例如上面配置的

ping test.com

  1. PING test.com (127.0.0.1): 56 data bytes
  2. 64 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.047 ms
  3. 64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.095 ms
  4. 64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.084 ms
  5. 64 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.055 ms

nginx配置缓存

  • levels 是否要创建二级文件夹
  • keys_zone=my_cache:10m 代理缓存查找一个缓存之前要有个地方保存,一个url对应的缓存保存在哪个地方,这个关系是存在内存里的,这里要声明一个内存大小进行保存,my_cache是缓存的名字,在每个server里面可以去设置

修改conf配置,文件目录了 /usr/local/etc/nginx/servers/

vim nginx-cache.conf

  1. proxy_cache_path /var/cache levels=1:2 keys_zone=my_cache:10m;
  2. server {
  3. listen 80;
  4. server_name test.com;
  5. location / {
  6. proxy_cache my_cache;
  7. proxy_pass http://127.0.0.1:3010;
  8. proxy_set_header Host $host; # 设置浏览器请求的host
  9. }
  10. }

nginx-cache.js

  • 以下s-maxage会代替max-age,只有在代理服务器(nginx代理服务器)才会生效
  • 用来指定在发送一个请求时,只有在Vary指定的http的headers是相同的情况下,才会去使用缓存,例如User-Agent,IE、Firefox打开这个页面,CDN/代理服务器就会认为这是不同的页面,将会使用不同的缓存
  1. const http = require('http');
  2. const fs = require('fs');
  3. const port = 3010;
  4.  
  5. const wait = seconds => {
  6. return new Promise((resolve, reject) => {
  7. setTimeout(() => {
  8. resolve();
  9. }, seconds);
  10. })
  11. }
  12.  
  13. http.createServer((request, response) => {
  14. console.log('request url: ', request.url);
  15.  
  16. if (request.url === '/') {
  17. const html = fs.readFileSync('nginx-cache.html', 'utf-8');
  18.  
  19. response.writeHead(200, {
  20. 'Content-Type': 'text/html',
  21. });
  22.  
  23. response.end(html);
  24. } else if (request.url === '/data') {
  25. response.writeHead(200, {
  26. 'Cache-Control': 'max-age=20, s-max-age=20',
  27. 'Vary': 'Test-Cache-Val'
  28. });
  29.  
  30. wait(3000).then(() => response.end("success!"));
  31. }
  32.  
  33. }).listen(port);
  34.  
  35. console.log('server listening on port ', port);

ngxin-cache.html

  1. <html>
  2. <head>
  3. <meta charset="utf-8" />
  4. <title>nginx-cache</title>
  5. <script src="https://cdnjs.cloudflare.com/ajax/libs/fetch/2.0.4/fetch.min.js"></script>
  6. </head>
  7. <body>
  8. <div>
  9. this is nginx-cache, and data is: <span id="data">请等待,数据获取中...</span>
  10. </div>
  11. <script>
  12. fetch('/data', {
  13. headers: {
  14. 'Test-Cache-Val': '123'
  15. }
  16. }).then((res => res.text())).then(text => {
  17. document.getElementById('data').innerText = text;
  18. });
  19. </script>
  20. </body>
  21. </html>

以上就是关于nginx代理服务器的实现实例,具体的Nginx代理服务器缓存还是有很多的功能,比如通过一些脚本让缓存使用内存数据库搜索性能会更高,默认nginx缓存是写在磁盘上的,读写磁盘效率是很低的,还可以通过设置cache key等。

nginx部署https服务

生成public key和private key

/usr/local/etc/nginx/certs目录下执行以下命令

openssl req -x509 -newkey rsa:2048 -nodes -sha256 -keyout localhost-privkey.pem -out localhost-cert.pem
Generating a 2048 bit RSA private key
...............................................................................+++
..............+++
writing new private key to 'localhost-privkey.pem'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:
Email Address []:
基于上面的nginx-cache.conf文件进行修改
proxy_cache_path /var/cache levels=1:2 keys_zone=my_cache:10m;

server {
    listen          443 ssl; # https默认证书
    server_name     test.com;

    # ssl on; # 开启ssl证书
    ssl_certificate_key /usr/local/etc/nginx/certs/localhost-privkey.pem;
    ssl_certificate /usr/local/etc/nginx/certs/localhost-cert.pem;

    location / {
        proxy_cache my_cache;
        proxy_pass http://127.0.0.1:3010;
        proxy_set_header Host $host; # 设置浏览器请求的host
    }
}

注意: nginx: [warn] the "ssl" directive is deprecated, use the "listen … ssl"

http自动跳转https
proxy_cache_path /var/cache levels=1:2 keys_zone=my_cache:10m;

server {
    listen          80 default_server;
    listen          [::]:80 default_server; # [::] 你的ip
    server_name     test.com;
    return 302 https://$server_name$request_uri;
}

server {
    listen          443 ssl; # https默认证书
    server_name     test.com;

    # ssl on; # 开启ssl证书
    ssl_certificate_key /usr/local/etc/nginx/certs/localhost-privkey.pem;
    ssl_certificate /usr/local/etc/nginx/certs/localhost-cert.pem;

    location / {
        proxy_cache my_cache;
        proxy_pass http://127.0.0.1:3010;
        proxy_set_header Host $host; # 设置浏览器请求的host
    }
}

实现http2协议

http2目前只能在https下面才可以

  • 优势:
    • 信道复用
    • 分帧传输
    • Server Push http/1.1协议里是客户端主动请求,服务才会响应,

http2.conf

server {
    listen          443 ssl http2;
    server_name     http2.test.com;
    http2_push_preload on; 

    ssl_certificate_key /usr/local/etc/nginx/certs/localhost-privkey.pem;
    ssl_certificate /usr/local/etc/nginx/certs/localhost-cert.pem;

    location / {
        proxy_pass http://127.0.0.1:30100;
        proxy_set_header Host $host;
    }
}

connection.js 基于http/1.1长链接示例修改

const http = require('http');
const fs = require('fs');
const port = 30100;

http.createServer((request, response) => {
    console.log('request url: ', request.url);

    const html = fs.readFileSync('./connection.html', 'utf-8');
    const img = fs.readFileSync('./test_img.jpg');

    if (request.url === '/') {
        response.writeHead(200, {
            'Content-Type': 'text/html',
            'Connection': 'close',
            'Link': '</test.jpg>; as=image; rel=preload',
        });

        response.end(html);
    } else {
        response.writeHead(200, {
            'Content-Type': 'image/jpg'
        });

        response.end(img);
    }
}).listen(port);

console.log('server listening on port ', port);

connection.html

<html>
    <head>
        <meta charset="utf-8" />
        <title>http2-connection</title>
        <style>
            img {
                width: 100px;
                height: 100px;
            }
        </style>
    </head>
    <body>
        1
        <img src="/test1.jpg" alt="" />
        2
        <img src="/test2.jpg" alt="" />
        3
        <img src="/test3.jpg" alt="" />
        4
        <img src="/test4.jpg" alt="" />
        5
        <img src="/test5.jpg" alt="" />
        6
        <img src="/test6.jpg" alt="" />
        7
        <img src="/test7.jpg" alt="" />
        8
        <img src="/test8.jpg" alt="" />
    </body>
</html>

运行效果,基于http2协议复合浏览器同域策略都在一个TCP上复用

HTTP - 图18

测试http2性能的网站 https://http2.akamai.com/demo/http2-lab.html