如果想了解更多与Nginx相关的内容,可以查看Nginx专栏中的文章: Nginx

视频教程:26-Nginx中浏览器缓存的相关概念

1. 静态资源的缓存处理

1.1 什么是缓存

缓存(Cache)在计算机科学中指的是一种访问速度比普通随机存取存储器(RAM)更快的特殊高速存储器。缓存通常使用静态随机存取存储器(SRAM)技术,而不是动态随机存取存储器(DRAM),因为SRAM具有更快的访问速度,尽管SRAM的成本较高且集成度较低

缓存的设置是现代计算机系统提高性能的关键因素之一。缓存的工作原理是利用程序访问的局部性原理,即程序在一段时间内倾向于访问相对集中的内存区域。通过将频繁使用的数据和指令存储在缓存中,计算机可以减少访问较慢主存(通常使用DRAM)的次数,从而显著提高数据访问速度和整体系统性能

1.2 什么是Web缓存

Web缓存是指存储在Web服务器和客户端(如浏览器)之间的Web资源的副本。它的主要目的是减少网络带宽的使用,提高页面加载速度,减轻服务器负载,并改善用户体验

当用户访问一个网站时,浏览器会根据一定的缓存机制(如HTTP缓存头信息,如Cache-Control、Expires、ETag等)来判断是否可以直接使用本地缓存的资源,还是需要向服务器发送请求以获取最新的资源。如果缓存机制判断资源没有更新(例如,通过比较ETag或Last-Modified时间戳),则浏览器会直接使用本地缓存的副本,从而避免了不必要的网络请求和数据传输

只有当服务器明确标识资源已经更新(例如,通过发送新的ETag值或更新Last-Modified时间戳),浏览器才会发送请求以获取更新的资源。这种机制确保了用户始终能够访问到最新的内容,同时又能充分利用缓存的优势来提高性能

除了浏览器端的缓存,Web缓存还可以存在于其他位置,如代理服务器、CDN(内容分发网络)节点等。这些缓存机制共同工作,以实现更高效的网络通信和更快的Web体验

1.3 Web缓存的种类

1.3.1 客户端缓存

客户端缓存主要是指在用户浏览器中实现的缓存机制,它用于存储用户访问过的Web资源,以便在后续访问时能够快速加载页面

  1. 浏览器缓存:
    • 内存缓存:存储在浏览器的内存中,用于快速访问最近使用的数据。内存缓存通常用于存储体积小的资源,如CSS文件和JavaScript文件
    • 磁盘缓存:存储在用户的硬盘上,用于保存体积较大的资源,如图像、视频和其他媒体文件
    • 离线缓存:通过Service Workers和Cache API实现,允许网站在用户设备上存储大量数据,即使在离线状态下也能访问
  2. HTTP缓存:
    • 强缓存:通过设置HTTP响应头中的Cache-Control和Expires字段,浏览器可以直接从本地缓存中加载资源,而无需向服务器发送请求
    • 协商缓存:通过设置Last-Modified和ETag字段,浏览器在发送请求时会带上If-Modified-Since和If-None-Match头,服务器根据这些信息判断资源是否更新,从而决定是否返回新的资源

1.3.2 服务端缓存

服务端缓存主要是指在服务器端实现的缓存机制,它用于存储和复用服务器生成的动态内容,以减轻服务器负载并提高响应速度

  1. 代理服务器缓存:
    • 透明代理缓存:位于客户端和服务器之间,无需客户端或服务器进行任何特别的配置即可工作。它监听和缓存经过它的所有请求
    • 反向代理缓存:位于服务器端,通常用于缓存服务器生成的动态内容。反向代理可以减轻后端服务器的负载,并提供更好的性能
  2. CDN(内容分发网络)缓存:
    • CDN提供商在全球范围内部署了多个缓存服务器,用于存储和分发静态内容,如图像、CSS、JavaScript文件等。CDN可以显著减少内容传输延迟,提高全球用户的访问速度
  3. 应用层缓存:
    • 在应用程序内部实现的缓存机制,可以缓存计算结果、会话数据、配置信息等。应用层缓存通常使用内存数据存储,如Redis、Memcached等
  4. 网关缓存:
    • 网关缓存通常用于企业环境中,它位于内部网络和互联网之间,可以缓存从互联网请求的资源,减少对外部网络的依赖

1.4 为什么要用浏览器缓存

  1. 减少服务器负载:当浏览器缓存了网页资源后,用户在下次访问相同页面时,可以直接从本地缓存加载这些资源,而不需要再次从服务器请求。这样可以显著减少服务器的请求次数,减轻服务器的负载
  2. 提高页面加载速度:从本地缓存加载资源比从服务器下载要快得多。这意味着网页可以更快地显示内容,从而提升用户体验
  3. 节省带宽:由于资源是从本地缓存而非服务器加载,因此可以减少网络传输的数据量,节省带宽资源
  4. 减少延迟:从本地缓存加载资源可以减少因网络延迟导致的加载时间,特别是在移动设备或网络条件较差的环境中

1.5 浏览器缓存的执行流程

我们先来认识下HTTP协议中和页面缓存相关的字段

Header说明
Expires指定缓存资源的过期日期和时间。浏览器在请求资源时,会检查当前时间是否超过了Expires中指定的时间。如果超过了,则认为缓存已过期,需要重新从服务器获取资源
Cache-Control设置和缓存相关的配置信息。这是一个更灵活和强大的缓存控制机制,可以设置多种指令,如max-age(资源在缓存中有效的秒数)、no-cache(每次使用资源前都需要重新验证)、no-store(禁止缓存)、public(缓存对所有用户可见)、private(缓存仅对单个用户可见)等。
Last-Modified指示资源的最后修改时间。浏览器在下次请求资源时,可以发送一个If-Modified-Since头部,包含上次获取资源的Last-Modified值。服务器会检查资源是否在指定时间后修改过,如果没有修改,则返回304 Not Modified状态码,浏览器可以使用本地缓存;如果修改过,则返回最新的资源
ETag请求变量的实体标签的当前值,通常是一个资源的唯一标识符,如文件的MD5值或哈希值。类似于Last-Modified,浏览器在下次请求时可以发送一个If-None-Match头部,包含上次获取资源的ETag值。服务器会检查资源的ETag是否与指定值匹配,如果不匹配,则返回最新的资源;如果匹配,则返回304 Not Modified状态码,浏览器可以使用本地缓存

以下是浏览器缓存的执行流程示意图

在这里插入图片描述

  1. 首次请求:用户通过浏览器首次请求某个资源,此时浏览器中没有该资源的缓存,因此需要向服务器发送请求
  2. 服务器响应:服务器接收请求后,返回资源以及相应的HTTP响应头,包括缓存相关信息(如Expires, Cache-Control, ETag, Last-Modified等)。浏览器将这些信息存储在缓存中
  3. 重复请求:当用户再次请求同一资源时,浏览器首先检查本地缓存中是否有该资源的副本
  4. 缓存命中:如果找到了缓存副本,浏览器会检查缓存是否过期(例如,通过Expires或Cache-Control中的max-age)
  5. 缓存有效:如果缓存未过期,浏览器直接从缓存中读取资源,无需再向服务器发送请求
  6. 缓存过期:如果缓存过期,浏览器需要进一步检查资源是否在服务器上有更新
  7. 验证更新:浏览器可以通过发送带有ETag或Last-Modified头的条件请求(如If-None-Match或If-Modified-Since)来询问服务器资源是否有更新
  8. 资源未变:如果服务器确认资源自缓存以来未更改,它会返回304 Not Modified状态码,指示浏览器继续使用缓存中的版本
  9. 资源已变:如果资源有更新,服务器将返回新的资源以及相应的HTTP响应头,浏览器会将新资源存储在缓存中以供未来使用

1.6 浏览器强缓存和弱缓存的区别

视频教程:28-浏览器强缓存和弱缓存的效果演示

强缓存和弱缓存的区别可以参考本文的 浏览器缓存的执行流程 示意图

1.6.1 强缓存(Strong Cache)

强缓存是指当缓存未过期时,浏览器直接从本地读取缓存,不会向服务器发送请求


强缓存主要通过以下HTTP头部字段来控制:

  • Expires:指定缓存资源失效的日期和时间。当当前时间小于Expires值时,缓存有效
  • Cache-Control:提供更细粒度的缓存控制
    • max-age:缓存资源在客户端缓存的最大时间(秒)
    • no-cache:表示不使用本地缓存,每次请求都需要向服务器发送请求以验证缓存
    • no-store:表示不存储任何缓存
    • must-revalidate:表示资源在过期前可以命中强缓存,过期后必须向服务器发送请求以验证缓存

1.6.2 弱缓存(Weak Cache)

弱缓存是指当缓存过期后,浏览器会向服务器发送请求以验证缓存是否仍然有效


弱缓存主要通过以下HTTP头部字段来控制:

  • Last-Modified: 表示资源最后修改的时间
  • ETag: 资源的实体标签,通常是资源的唯一标识符,用于验证资源是否发生变化
  • If-Modified-Since: 请求头部字段,用于发送Last-Modified值,询问服务器资源是否在此时间之后有修改
  • If-None-Match: 请求头部字段,用于发送ETag值,询问服务器资源是否与此ETag不匹配

当弱缓存命中时,浏览器会发送带有If-Modified-Since或If-None-Match头的请求到服务器。服务器会根据这些头部字段检查资源是否有更新:

  • 如果资源未发生变化(Last-Modified未更新或ETag未更改),服务器返回304 Not Modified状态码,浏览器继续使用本地缓存
  • 如果资源已发生变化,服务器返回新的资源内容,浏览器更新本地缓存

弱缓存机制允许服务器控制缓存的有效性,确保用户始终获取最新的资源,同时减少了不必要的数据传输

2. Nginx中与浏览器缓存相关的指令

如果需要利用Nginx进行浏览器缓存的相关设置,就需要用到如下指令

2.1 expires指令

expires指令用来控制页面缓存的作用,可以通过expires指令控制HTTP响应头中的Expires和Cache-Control字段

语法默认值位置
expires [modified] time;expires off;http、server、location
expires epoch | max | off;
参数描述
time可以是整数或负数,指定资源的过期时间(以秒为单位)。如果是负数,Cache-Control 将被设置为 no-cache;如果是0,Cache-Control 将被设置为 max-age=time
epoch将 Expires 设置为 1970-01-01 00:00:01 GMT,并将 Cache-Control 设置为 no-cache
max将 Expires 设置为 2037-12-31 23:59:59 GMT,并将 Cache-Control 设置为 max-age=315360000(大约10年)
off默认不缓存

2.2 add_header指令(Cache-Control)

add_header指令用于添加指定的响应头和响应值

语法默认值位置
add_header name value [always];—http、server、location

Cache-Control作为响应头信息,可以设置如下值

Cache-control: must-revalidate
Cache-control: no-cache
Cache-control: no-store
Cache-control: no-transform
Cache-control: public
Cache-control: private
Cache-control: proxy-revalidate
Cache-Control: max-age=<seconds>
Cache-control: s-maxage=<seconds>
指令说明
must-revalidate缓存必须在使用之前验证其有效性,如果资源已过期,则必须重新从源服务器获取
no-cache缓存前必须先验证资源的有效性,如果资源已过期,则必须重新从源服务器获取
no-store不缓存请求或响应的任何部分,确保敏感信息不会被存储在缓存中
no-transform代理服务器不得更改响应的内容格式,例如不得压缩或转换媒体类型
public响应可以被任何缓存存储,包括中间代理和客户端缓存
private响应只能被特定用户缓存,不能被共享缓存(如代理服务器)存储
proxy-revalidate要求中间缓存服务器在提供缓存之前必须重新验证响应的有效性
max-age=<秒>指定响应的最大缓存时间(以秒为单位),如果响应的 Age 大于 max-age,则缓存必须视为过期
s-maxage=<秒>类似于 max-age,但仅适用于共享缓存(如代理服务器),对私有缓存无效

3. Nginx的跨域问题

3.1 本域和同源策略

同源策略规定,一个域下的 JavaScript 脚本不能直接访问或读取另一个域的资源,也不能直接向另一个域发起请求

在了解什么是跨域之前,我们需要先了解两个概念:本域和同源策略

  • 本域:同协议、同域名、同端口
  • 同源策略:为了保护用户隐私和防止恶意网站窃取数据,浏览器默认只允许与本域的接口进行交互

在这里插入图片描述

3.2 跨域的概念

当浏览器发出一个请求时,只要请求URL的协议、域名、端口三者之间任意一个与当前页面URL不同,就称为跨域

当前页面URL请求URL是否跨域原因
http://www.test.comhttp://www.test.com/index.html否同源(协议、域名、端口都相同)
http😕/www.test.comhttps😕/www.test.com/index.html是协议不同
http://www.test.comhttp://www.baidu.com是域名不同
http://www.test.com:8080http://www.test.com:8088是端口不同

3.3 跨域问题案例演示

本次演示使用的是Windows操作系统,在Linux操作系统上的操作步骤相差无几


出现跨域问题会发生什么呢,我们通过一个案例演示一下

3.3.1 新建HTML文件

在nginx的html目录下新建一个名为crossOrigin.html的文件

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>跨域问题演示</title>
    <script src="https://unpkg.com/axios@1.9.0/dist/axios.min.js"></script>
    <style>
        body {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif;
            background-color: #f5f7fa;
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            padding: 20px;
        }

        .container {
            background-color: white;
            border-radius: 12px;
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
            padding: 40px;
            text-align: center;
        }

        button {
            background-color: #007bff;
            color: white;
            border: none;
            padding: 10px 20px;
            border-radius: 5px;
            cursor: pointer;
            transition: background-color 0.3s ease;
        }

        button:hover {
            background-color: #0056b3;
        }

        .result {
            margin-top: 20px;
            padding: 15px;
            background-color: #e9ecef;
            border-radius: 5px;
            display: none; /* Initially hide the result */
        }
    </style>
</head>
<body>

<div class="container">
    <button id="btn">获取数据</button>
    <div id="result" class="result"></div>
</div>

<script>
    document.getElementById('btn').addEventListener('click', function() {
        const resultElement = document.getElementById('result');
        resultElement.style.display = 'none'; // Hide result before new request

        axios.get('http://127.0.0.1:6250/getUser')
            .then(function(response) {
                if (response.data) { // Check if data exists
                    resultElement.textContent = JSON.stringify(response.data, null, 2);
                    resultElement.style.display = 'block'; // Show result only if there is data
                }
            })
            .catch(function(error) {
                console.error('Error fetching data:', error);
            });
    });
</script>

</body>
</html>

3.3.2 修改nginx.conf文件

在监听80端口的server块中添加以下location块

location /crossorigin {
    alias html;
    index crossOrigin.html;
}

在这里插入图片描述

填写一个新的server块(将127.0.0.1改成你的服务器的IP地址)

server {
    listen 6250;
    server_name 127.0.0.1;
    location /getUser {
        default_type application/json;
        return 200 '{"id":1,"name":"TOM","age":18}';
    }
}

修改nginx.conf文件后记得重载配置文件

3.3.3 通过浏览器访问

在浏览器访问localhost/crossorigin网址(将localhost改成nginx.conf文件中对应的server_name)

localhost/crossorigin

打开浏览器的控制台后点击获取数据按钮,可以看到控制台出现了Access to XMLHttpRequest at 'http://127.0.0.1:6250/getUser' from origin 'http://localhost' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.信息

在这里插入图片描述

3.4 解决方案

使用add_header指令添加一些头信息

语法默认值位置
add_header name value…http、server、location

如果解决跨域问题,需要添加两个头信息,一个是Access-Control-Allow-Origin,另一个是Access-Control-Allow-Methods

  • Access-Control-Allow-Origin:直译过来是允许跨域访问的源地址信息,可以配置多个(多个用逗号分隔),也可以使用*代表所有源
  • Access-Control-Allow-Methods:直译过来是允许跨域访问的请求方式,值可以为 GET POST PUT DELETE…,可以全部设置,也可以根据需要设置,多个用逗号分隔

在location块中添加以下信息

# 允许所有来源的跨域请求
add_header Access-Control-Allow-Origin *;

# 允许的HTTP方法
add_header Access-Control-Allow-Methods GET,POST,PUT,DELETE;

在这里插入图片描述

重载nginx.conf配置文件,再次在浏览器访问localhost/crossorigin网址(将localhost改成nginx.conf文件中对应的server_name)

localhost/crossorigin

可以发现数据已经成功获取了,响应头中也多了Access-Control-Allow-Origin和Access-Control-Allow-Methods两个属性

在这里插入图片描述

4. 静态资源防盗链

视频教程:33-Nginx静态资源盗链的效果展示

视频教程中用的是Linux操作系统,本次演示使用的是Windows操作系统,整体步骤相差无几

4.1 什么是资源盗链

资源盗链指的是资源不在自己服务器上,而是通过技术手段,绕过别人的限制,将别人的内容放到自己页面上最终展示给用户,以此来盗取大网站的空间和流量,简而言之就是用别人的东西成就自己的网站

4.2 静态资源防盗链演示

FangDaoLian

在Nginx的html目录下新建一个名为FangDaoLian.html的文件,文件内容如下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>静态资源防盗链示例</title>
</head>
<body>
    <h1>静态资源防盗链示例</h1>
    <p>以下图片来自京东和百度</p>
    
    <!-- 京东图片 -->
    <h2>京东图片</h2>
    <img src="https://img14.360buyimg.com/n7/jfs/t1/101062/37/2153/254169/5dcbd410E6d10ba22/4ddbd212be225fcd.jpg" alt="京东图片">
    
    <!-- 百度图片 -->
    <h2>百度图片</h2>
    <img src="https://pics7.baidu.com/feed/cf1b9d16fdfaaf516f7e2011a7cda1e8f11f7a1a.jpeg?token=551979a23a0995e5e5279b8fa1a48b34&s=BD385394D2E963072FD48543030030BB" alt="百度图片">
    
</body>
</html>

接着往nginx.conf文件中添加以下内容

server {
    listen 8765;
    server_name localhost;

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

浏览器访问http://localhost:8765/网址

http://localhost:8765/

在这里插入图片描述

可以发现,京东图片成功显示出来了,而百度图片没有显示出来。按下F12打开浏览器的控制台,可以发现加载百度图片的请求被拒绝了

在这里插入图片描述

在浏览器中直接输入百度图片的URL,是可以查看到图片的,在本地的HTML文件中引入百度图片也可以查看,但是通过Nginx访问HTML文件后百度的图片就不显示了,说明百度图片确实做了防盗链处理

4.3 防盗链的实现原理

4.3.1 Referer请求头

在了解防盗链的实现原理之前,我们需要先学习一个名为Referer的HTTP的头信息。当浏览器向web服务器发送请求的时候,一般都会带上Referer请求头,告诉服务器该请求是从哪个页面链接过来的


我们访问CSDN官网,按下F12快捷键打开浏览器控制台,打开网络一栏后,刷新页面,查看某个请求的标头信息,可以看到请求携带的Referer信息(CSDN上的图片也做了防盗链处理)

在这里插入图片描述

在本地直接打开HTML文件时,如果HTML文件发起请求获取网络资源文件,请求中不会有Referer头信息,可以用本文的静态资源防盗链演示章节提供的FangDaoLian.html文件进行测试

在这里插入图片描述

后台服务器可以根据获取到Referer信息来判断请求是否来源于信任的网站

  • 如果请求来源于信任的网站,则放行请求
  • 如果请求不是来源于信任的网站,则返回403(服务端拒绝访问)状态码

4.3.2 valid_referers指令

nginx会自动将referer请求头和valid_referers后面的内容进行匹配,如果匹配到了就将$invalid_referer变量置0,如果没有匹配到,则将·$invalid_referer·变量置为1,匹配的过程中不区分大小写

语法默认值位置
valid_referers none | blocked | server_names | string …server、location
  • none:如果Header中的Referer为空,允许访问

  • blocked:在Header中的Referer不为空,但是该值被防火墙或代理进行伪装过,如不带"http://" 、"https://"等协议头的资源,允许访问

  • server_names:指定具体的域名或者IP

  • string:支持正则表达式和*的字符串,如果是正则表达式,需要以~开头表示


示例如下

# 定义一个location块,匹配以.png、.jpg或.gif结尾的URL路径
location ~* \.(png|jpg|gif)$ {
    # 设置允许的引用来源列表
    valid_referers none blocked www.baidu.com 192.168.200.222 www.example.org;
    
    # 如果请求的Referer不在允许列表中($invalid_referer为真)
    if ($invalid_referer) {
        # 返回403 Forbidden状态码,拒绝访问
        return 403;
    }
    
    # 设置根目录为/usr/local/nginx/html
    root /usr/local/nginx/html;
}

4.3.3 实战案例

我们动手实现一下防盗链,在FangDaoLian.html文件中新增一个img标签

<!-- 本地图片 -->
<h2>本地图片</h2>
<img src="http://127.0.0.1:8765/LiangNianBan.jpg" alt="本地图片">	

在这里插入图片描述

在nginx的html目录下新建一个名为images的目录,将以下图片粘贴到images目录中,并将图片命名为LiangNianBan.jpg

LiangNianBan.jpg

在这里插入图片描述

在location块中添加防盗链相关的内容

# 定义一个location块,匹配以.png、.jpg或.gif结尾的URL路径
location ~ \.(png|jpg|gif)$ {
    # 设置允许的引用来源列表
    valid_referers none blocked www.baidu.com 192.168.200.222 www.example.org;
    
    # 如果请求的Referer不在允许列表中(invalid_referer为真)
    if ($invalid_referer) {
        # 返回403 Forbidden状态码,拒绝访问
        return 403;
    }
    
    # 设置根目录为html/images
    root html/images;
}

在这里插入图片描述

添加完成后重载nginx.conf配置文件


浏览器直接访问localhost:8765/LiangNianBan.jpg网址,图片能够正常显示

localhost:8765/LiangNianBan.jpg

在这里插入图片描述

如果访问http://localhost:8765/网址,通过nginx返回的HTML文件间接访问图片文件,可以发现图片加载不出来了

http://localhost:8765/

在这里插入图片描述

如果在valid_referers中添加localhost域名,可以发现图片能够显示出来了

注意:localhost和127.0.0.1在nginx.conf文件中并不等价

在这里插入图片描述

4.4 针对目录进行防盗链

如果有很多图片,或者说是有很多静态资源,该如何批量进行防盗链呢

location /images {
           valid_referers none blocked www.baidu.com 192.168.200.222;
           if ($invalid_referer){
                return 403;
           }
           root /usr/local/nginx/html;
}

这样我们可以对一个目录下的所有资源进行防盗链操作,但是Referer的限制比较粗,比如随意加一个Referer,上面的方式是无法进行限制的,这个问题该如何解决呢

此处我们需要用到 Nginx 的第三方模块ngx_http_accesskey_module实现防盗链,至于如何在 Nginx 中使用第三方模块的功能,我们在后面的 Nginx 的模块篇再进行详细的讲解

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐