6. 设置二级域名虚拟主机
在某某云 ☁️ 上购买了域名之后,就可以配置虚拟主机了,一般配置的路径在 域名管理 -> 解析 -> 添加记录
中添加二级域名,配置后某某云会把二级域名也解析到我们配置的服务器 IP 上,然后我们在 Nginx 上配置一下虚拟主机的访问监听,就可以拿到从这个二级域名过来的请求了。
现在我自己的服务器上配置了一个 fe 的二级域名,也就是说在外网访问 fe.sherlocked93.club
的时候,也可以访问到我们的服务器了。
由于默认配置文件 /etc/nginx/nginx.conf
的 http 模块中有一句 include /etc/nginx/conf.d/*.conf
也就是说 conf.d
文件夹下的所有 *.conf
文件都会作为子配置项被引入配置文件中。为了维护方便,我在 /etc/nginx/conf.d
文件夹中新建一个 fe.sherlocked93.club.conf
:
# /etc/nginx/conf.d/fe.sherlocked93.club.conf server { listen 80; server_name fe.sherlocked93.club; location / { root /usr/share/nginx/html/fe; index index.html; } } 复制代码
然后在 /usr/share/nginx/html
文件夹下新建 fe 文件夹,新建文件 index.html
,内容随便写点,改完 nginx -s reload
重新加载,浏览器中输入 fe.sherlocked93.club
,发现从二级域名就可以访问到我们刚刚新建的 fe 文件夹:
7. 配置反向代理
反向代理是工作中最常用的服务器功能,经常被用来解决跨域问题,下面简单介绍一下如何实现反向代理。
首先进入 Nginx 的主配置文件:
vim /etc/nginx/nginx.conf 复制代码
为了看起来方便,把行号显示出来 :set nu
(个人习惯),然后我们去 http
模块的 server
块中的 location /
,增加一行将默认网址重定向到最大学习网站 Bilibili 的 proxy_pass
配置 🤓 :
改完保存退出,nginx -s reload
重新加载,进入默认网址,那么现在就直接跳转到 B 站了,实现了一个简单的代理。
实际使用中,可以将请求转发到本机另一个服务器上,也可以根据访问的路径跳转到不同端口的服务中。
比如我们监听 9001
端口,然后把访问不同路径的请求进行反向代理:
- 把访问
http://127.0.0.1:9001/edu
的请求转发到http://127.0.0.1:8080
- 把访问
http://127.0.0.1:9001/vod
的请求转发到http://127.0.0.1:8081
这种要怎么配置呢,首先同样打开主配置文件,然后在 http 模块下增加一个 server 块:
server { listen 9001; server_name *.sherlocked93.club; location ~ /edu/ { proxy_pass http://127.0.0.1:8080; } location ~ /vod/ { proxy_pass http://127.0.0.1:8081; } } 复制代码
反向代理还有一些其他的指令,可以了解一下:
proxy_set_header
:在将客户端请求发送给后端服务器之前,更改来自客户端的请求头信息。proxy_connect_timeout
:配置Nginx与后端代理服务器尝试建立连接的超时时间。proxy_read_timeout
:配置Nginx向后端服务器组发出read请求后,等待相应的超时时间。proxy_send_timeout
:配置Nginx向后端服务器组发出write请求后,等待相应的超时时间。proxy_redirect
:用于修改后端服务器返回的响应头中的Location和Refresh。
8. 跨域 CORS 配置
关于简单请求、非简单请求、跨域的概念,前面已经介绍过了,还不了解的可以看看前面的讲解。现在前后端分离的项目一统天下,经常本地起了前端服务,需要访问不同的后端地址,不可避免遇到跨域问题。
要解决跨域问题,我们来制造一个跨域问题。首先和前面设置二级域名的方式一样,先设置好 fe.sherlocked93.club
和 be.sherlocked93.club
二级域名,都指向本云服务器地址,虽然对应 IP 是一样的,但是在 fe.sherlocked93.club
域名发出的请求访问 be.sherlocked93.club
域名的请求还是跨域了,因为访问的 host 不一致(如果不知道啥原因参见前面跨域的内容)。
8.1 使用反向代理解决跨域
在前端服务地址为 fe.sherlocked93.club
的页面请求 be.sherlocked93.club
的后端服务导致的跨域,可以这样配置:
server { listen 9001; server_name fe.sherlocked93.club; location / { proxy_pass be.sherlocked93.club; } } 复制代码
这样就将对前一个域名 fe.sherlocked93.club
的请求全都代理到了 be.sherlocked93.club
,前端的请求都被我们用服务器代理到了后端地址下,绕过了跨域。
这里对静态文件的请求和后端服务的请求都以 fe.sherlocked93.club
开始,不易区分,所以为了实现对后端服务请求的统一转发,通常我们会约定对后端服务的请求加上 /apis/
前缀或者其他的 path 来和对静态资源的请求加以区分,此时我们可以这样配置:
# 请求跨域,约定代理后端服务请求path以/apis/开头 location ^~/apis/ { # 这里重写了请求,将正则匹配中的第一个分组的path拼接到真正的请求后面,并用break停止后续匹配 rewrite ^/apis/(.*)$ /$1 break; proxy_pass be.sherlocked93.club; # 两个域名之间cookie的传递与回写 proxy_cookie_domain be.sherlocked93.club fe.sherlocked93.club; } 复制代码
这样,静态资源我们使用 fe.sherlocked93.club/xx.html
,动态资源我们使用 fe.sherlocked93.club/apis/getAwo
,浏览器页面看起来仍然访问的前端服务器,绕过了浏览器的同源策略,毕竟我们看起来并没有跨域。
也可以统一一点,直接把前后端服务器地址直接都转发到另一个 server.sherlocked93.club
,只通过在后面添加的 path 来区分请求的是静态资源还是后端服务,看需求了。
8.2 配置 header 解决跨域
当浏览器在访问跨源的服务器时,也可以在跨域的服务器上直接设置 Nginx,从而前端就可以无感地开发,不用把实际上访问后端的地址改成前端服务的地址,这样可适性更高。
比如前端站点是 fe.sherlocked93.club
,这个地址下的前端页面请求 be.sherlocked93.club
下的资源,比如前者的 fe.sherlocked93.club/index.html
内容是这样的:
<html> <body> <h1>welcome fe.sherlocked93.club!!<h1> <script type='text/javascript'> var xmlhttp = new XMLHttpRequest() xmlhttp.open("GET", "http://be.sherlocked93.club/index.html", true); xmlhttp.send(); </script> </body> </html> 复制代码
打开浏览器访问 fe.sherlocked93.club/index.html
的结果如下:
很明显这里是跨域请求,在浏览器中直接访问
http://be.sherlocked93.club/index.html
是可以访问到的,但是在 fe.sherlocked93.club
的 html 页面访问就会出现跨域。
在 /etc/nginx/conf.d/
文件夹中新建一个配置文件,对应二级域名 be.sherlocked93.club
:
# /etc/nginx/conf.d/be.sherlocked93.club.conf server { listen 80; server_name be.sherlocked93.club; add_header 'Access-Control-Allow-Origin' $http_origin; # 全局变量获得当前请求origin,带cookie的请求不支持* add_header 'Access-Control-Allow-Credentials' 'true'; # 为 true 可带上 cookie add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; # 允许请求方法 add_header 'Access-Control-Allow-Headers' $http_access_control_request_headers; # 允许请求的 header,可以为 * add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range'; if ($request_method = 'OPTIONS') { add_header 'Access-Control-Max-Age' 1728000; # OPTIONS 请求的有效期,在有效期内不用发出另一条预检请求 add_header 'Content-Type' 'text/plain; charset=utf-8'; add_header 'Content-Length' 0; return 204; # 200 也可以 } location / { root /usr/share/nginx/html/be; index index.html; } } 复制代码
然后 nginx -s reload
重新加载配置。这时再访问 fe.sherlocked93.club/index.html
结果如下,请求中出现了我们刚刚配置的 Header:
解决了跨域问题。
9. 开启 gzip 压缩
gzip 是一种常用的网页压缩技术,传输的网页经过 gzip 压缩之后大小通常可以变为原来的一半甚至更小(官网原话),更小的网页体积也就意味着带宽的节约和传输速度的提升,特别是对于访问量巨大大型网站来说,每一个静态资源体积的减小,都会带来可观的流量与带宽的节省。
百度可以找到很多检测站点来查看目标网页有没有开启 gzip 压缩,在下随便找了一个 <网页GZIP压缩检测> 输入掘金 juejin.im
来偷窥下掘金有没有开启 gzip。
这里可以看到掘金是开启了 gzip 的,压缩效果还挺不错,达到了 52% 之多,本来 34kb
的网页体积,压缩完只需要 16kb
,可以想象网页传输速度提升了不少。
9.1 Nginx 配置 gzip
使用 gzip 不仅需要 Nginx 配置,浏览器端也需要配合,需要在请求消息头中包含 Accept-Encoding: gzip
(IE5 之后所有的浏览器都支持了,是现代浏览器的默认设置)。一般在请求 html 和 css 等静态资源的时候,支持的浏览器在 request 请求静态资源的时候,会加上 Accept-Encoding: gzip
这个 header,表示自己支持 gzip 的压缩方式,Nginx 在拿到这个请求的时候,如果有相应配置,就会返回经过 gzip 压缩过的文件给浏览器,并在 response 相应的时候加上 content-encoding: gzip
来告诉浏览器自己采用的压缩方式(因为浏览器在传给服务器的时候一般还告诉服务器自己支持好几种压缩方式),浏览器拿到压缩的文件后,根据自己的解压方式进行解析。
先来看看 Nginx 怎么进行 gzip 配置,和之前的配置一样,为了方便管理,还是在 /etc/nginx/conf.d/
文件夹中新建配置文件 gzip.conf
:
# /etc/nginx/conf.d/gzip.conf gzip on; # 默认off,是否开启gzip gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript; # 上面两个开启基本就能跑起了,下面的愿意折腾就了解一下 gzip_static on; gzip_proxied any; gzip_vary on; gzip_comp_level 6; gzip_buffers 16 8k; # gzip_min_length 1k; gzip_http_version 1.1; 复制代码
稍微解释一下:
- gzip_types:要采用 gzip 压缩的 MIME 文件类型,其中 text/html 被系统强制启用;
- gzip_static:默认 off,该模块启用后,Nginx 首先检查是否存在请求静态文件的 gz 结尾的文件,如果有则直接返回该
.gz
文件内容; - gzip_proxied:默认 off,nginx做为反向代理时启用,用于设置启用或禁用从代理服务器上收到相应内容 gzip 压缩;
- gzip_vary:用于在响应消息头中添加
Vary:Accept-Encoding
,使代理服务器根据请求头中的Accept-Encoding
识别是否启用 gzip 压缩; - gzip_comp_level:gzip 压缩比,压缩级别是 1-9,1 压缩级别最低,9 最高,级别越高压缩率越大,压缩时间越长,建议 4-6;
- gzip_buffers:获取多少内存用于缓存压缩结果,16 8k 表示以 8k*16 为单位获得;
- gzip_min_length:允许压缩的页面最小字节数,页面字节数从header头中的
Content-Length
中进行获取。默认值是 0,不管页面多大都压缩。建议设置成大于 1k 的字节数,小于 1k 可能会越压越大; - gzip_http_version:默认 1.1,启用 gzip 所需的 HTTP 最低版本;
这个配置可以插入到 http 模块整个服务器的配置里,也可以插入到需要使用的虚拟主机的 server
或者下面的 location
模块中,当然像上面我们这样写的话就是被 include 到 http 模块中了。
其他更全的配置信息可以查看 <官网文档ngx_http_gzip_module>,配置前是这样的:
配置之后 response 的 header 里面多了一个 Content-Encoding: gzip
,返回信息被压缩了:
注意了,一般 gzip 的配置建议加上 gzip_min_length 1k
,不加的话:
由于文件太小,gzip 压缩之后得到了 -48% 的体积优化,压缩之后体积还比压缩之前体积大了,所以最好设置低于 1kb
的文件就不要 gzip 压缩了 🤪
9.2 Webpack 的 gzip 配置
当前端项目使用 Webpack 进行打包的时候,也可以开启 gzip 压缩:
// vue-cli3 的 vue.config.js 文件 const CompressionWebpackPlugin = require('compression-webpack-plugin') module.exports = { // gzip 配置 configureWebpack: config => { if (process.env.NODE_ENV === 'production') { // 生产环境 return { plugins: [new CompressionWebpackPlugin({ test: /\.js$|\.html$|\.css/, // 匹配文件名 threshold: 10240, // 文件压缩阈值,对超过10k的进行压缩 deleteOriginalAssets: false // 是否删除源文件 })] } } }, ... } 复制代码
由此打包出来的文件如下图:
这里可以看到某些打包之后的文件下面有一个对应的 .gz
经过 gzip
压缩之后的文件,这是因为这个文件超过了 10kb
,有的文件没有超过 10kb
就没有进行 gzip
打包,如果你期望压缩文件的体积阈值小一点,可以在 compression-webpack-plugin
这个插件的配置里进行对应配置。
那么为啥这里 Nginx 已经有了 gzip 压缩,Webpack 这里又整了个 gzip 呢,因为如果全都是使用 Nginx 来压缩文件,会耗费服务器的计算资源,如果服务器的 gzip_comp_level
配置的比较高,就更增加服务器的开销,相应增加客户端的请求时间,得不偿失。
如果压缩的动作在前端打包的时候就做了,把打包之后的高压缩等级文件作为静态资源放在服务器上,Nginx 会优先查找这些压缩之后的文件返回给客户端,相当于把压缩文件的动作从 Nginx 提前给 Webpack 打包的时候完成,节约了服务器资源,所以一般推介在生产环境应用 Webpack 配置 gzip 压缩。