『前端工程』—— 用Nginx把前端工程部署到域名的子路径上

3,560

前言

一个产品在A工程中开发,开发完后将A工程部署在域名XXX( www.xxx.com )根路径上,那么在域名XXX后面加上A工程中路由配置的路径,就可以访问A工程中对应的页面。

这个产品在后期的迭代中,有几个新功能不在A工程中开发了,要新起一个B工程来开发。通常一个产品只会对外提供一个域名,于是B工程也要部署在域名XXX上。那么问题来了,在域名XXX后面添加路径只会访问到A工程中的页面,访问不到B工程中的页面。

为了解决这个问题,就要用把B工程部署到域名XXX的子路径上。比如部署到 www.xxx.com/bbb/ 上,然后在 www.xxx.com/bbb/ 后面加上B工程中路由配置的路径,就可以访问B工程中对应的页面。

本专栏用Nginx部署Vue工程作为例子。

一、安装Nginx

打开 Nginx 官网下载地址,点击 nginx / Windows-1.18.0 下载稳定版的 Nginx 。下载完成后,随便找个地方安装,安装完成后打开软件安装目录。Nginx 的主要配置文件都在 conf 文件夹中。

二、常用的Nginx命令

在Nginx软件安装目录的地址栏中输入cmd,打开命令行工具。

以下有一些常用的命令,需要熟悉一下。

  • 启动Nginx start nginx
  • 停止Nginx nginx.exe -s quit
  • 重启Nginx nginx.exe -s reload
  • 检查Nginx配置是否正确 nginx -t

三、跟部署相关的Nginx配置语法

3.1、server块的配置语法

server的作用是在服务器上创建一个虚拟主机。

虚拟主机是指在一台物理主机服务器上划分出多个磁盘空间,每个磁盘空间都是一个虚拟主机,每台虚拟主机都可以对外提供Web服务,并且互不干扰。在外界看来,虚拟主机就是一台独立的服务器主机,这意味着用户能够利用虚拟主机把多个不同域名的网站部署在同一台服务器上,而不必为一个网站单独购买一台服务器,既解决了维护服务器技术的难题,同时又极大地节省了服务器硬件成本和相关的维护费用。

可以简单地把虚拟主机理解成一个虚拟服务器。Nginx默认在 conf/nginx.conf 文件中配置了一个server块,如下所示:

http{
  server{
    listen 80;
    server_name  localhost;
  }
}

启动Nginx,用浏览器中访问 http://localhost:80 ,访问页面如下所示:

在Nginx中每配置一个server块,当启动Nginx时,就会在服务器上创建一个虚拟服务器(为了行文方便将其简称为服务器)并启动它。http块中可以包含多个server块,那么在http块中再配置一个server块,配置内容如下所示:

http{
  server{
    listen 8081;
    server_name  test.com;
  }
}

重启Nginx,用浏览器访问 test.com:8081 ,会发现无法访问。这因为在Host文件中还没把 test.com 指向 127.0.0.1 。

这里推荐一个修改 Host 的工具(Switchhosts!),可以自行百度搜索下载安装,修改好Host文件后,重新用浏览器访问 test.com:8081 ,访问结果会和访问 http://localhost:80 的页面一样。

其中listen的作用就是监听服务的某个端口。server_name的作用是给服务起个名称(域名)。

3.2、include指令在http块中的应用

为了更好管理每个Vue工程对应的部署配置,在 conf 文件夹中创建一个 vhost 文件夹,在 vhost 文件夹中根据每个Vue工程创建对应的部署配置文件,如下图所示:

其中 a.conf 文件代表A工程对应的部署配置文件。

再利用include指令把 conf/vhost 文件夹中每个文件中的server块添加到 conf/nginx.conf 文件中的http块中。

于是在 conf/nginx.conf 文件中的http块中添加如下配置:

http{
  //...
  include vhost/*.conf;
  server_names_hash_bucket_size 64;
}

其中vhost/*.conf表示 vhost 文件下的所有以 .conf 结尾的文件。server_names_hash_bucket_sizeserver_name的存储大小。

3.3、location块的配置语法

在介绍之前先阐述一个URL的构成,一个URL是有协议+域名+端口号+路径+文件名+参数+锚点构成的,如下图所示:

当用一个URL访问一个server块创建的虚拟服务器,这个server块中的location块会用其匹配规则去匹配这个URL的路径部分,如果有一个location块匹配成功后,则执行这个location块中相关的操作。

其语法是location [=|~|~*|^~|@] pattern { ... },其中[=|~|~*|^~|@]是修饰符,pattern是匹配规则,在{ ... }中配置相关的操作。

其中修饰符是非必须的,若没修饰符,仍然可以用pattern(匹配规则)去匹配URL路径部分。修饰符的种类及作用如下表所示:

修饰符作用
=对应的匹配规则是个字符串,表示对URL路径部分进行精确匹配,即该字符串等于URL路径部分。
~对应的匹配规则是个正则,表示匹配时区分大小写。
~*对应的匹配规则是个正则,表示匹配时区分大小写。
^~对应的匹配规则是个字符串,表示匹配时要检测URL路径部分中是否含有该字符串。
@用于定义一个location块,且该块不能被外部访问,只能被Nginx内部指令所访问。

此外修饰符还有另外一个作用,提高location块匹配的优先级,其优先级如下所示:

`[=]` > `[^~]` > `[~/~*]` > 无修饰符

location块匹配的大致流程:

  • 第一步:优先使用=修饰符的location块的匹配规则去匹配,=意味精确匹配,如果匹配上了,该location中的操作马上执行,匹配过程就此结束;

  • 第二步:若没匹配上,接下来去匹配那些匹配规则是字符串的location块,若被多个location块匹配上,取其中匹配规则最长的location块,若该location块的匹配规则前面有^~修饰符,马上执行其中操作,匹配过程结束。若该location块的匹配规则前面没有修饰符,先记录该location块继续往下匹配。

  • 第三步:若匹配规则是字符串的location块没有匹配上,或者匹配上的location块的修饰符不是^~。那么接下来去匹配那些匹配规则是正则的location块。这里要注意如果匹配规则是正则,那么其前面一定要加上 ~~* 修饰符,否则不起作用。 若被多个location块匹配上,则按着location块的定义顺序,取顺序最上面的location块进行匹配,马上执行其中操作,匹配过程结束。

  • 第四步:若匹配规则是正则的location块没匹配上,要去看一下第二步中,若有无修饰符的location块匹配上,马上执行其中操作,匹配过程结束。若没有,匹配过程也照样结束。

关于第二步中,取其中匹配规则最长的location块举个例子说明:

server{
  listen 8081;
  server_name  test.com;
  location ^~ /doc {
    return 001;
  }
  location ^~ /docu {
    return 002;
  }
}

执行命令curl -I test.com:8080/document发起一个网络请求,结果是返回 002 ,如下图所示:

其中^~ /doc^~ /docu都能匹配URL路径部分中/document。因为/docu字符串的长度比/doc长,所以匹配规则为^~ /docu的location块被匹配上。

3.4、root指令和alias指令在location块中的区别

通过URL来访问服务器,一般是要去获取服务器中的资源。在获取资源前,要先知道资源所在文件夹的路径。找到资源所在文件夹后,就可以通过URL中文件名部分去获取对应的资源。

在匹配规则为字符串的location块中,root指令和alias指令所配置的路径皆是资源所在文件夹的路径中的基础路径,把基础路径和URL的路径部分拼接一下,才会得到资源所在文件夹的真正路径。

通过root指令和alias指令定义的基础路径,其拼接规则是不一样的,有区别的,下面用几个例子来阐述一下。

nginx.conf 中 http 块中添加如下配置:

server{
  listen 8081;
  server_name  test.com;
  location ^~ /home {
    root   html;
  }
  location ^~ /list {
    alias  html;
  }
  location ^~ /form/ {
    alias  html;
  }
}

其中html是Nginx安装目录中的html文件夹的路径。

当浏览器访问 test.com:8080/home/index.… ,被匹配规则为 /home 的location块匹配到,用root的值拼接到URL路径/home/前面生成访问服务器资源所在文件夹的路径:html + /home/ = html/home/

当浏览器访问 test.com:8080/list/index.… ,被匹配规则为 /list 的location块匹配到,用alias的值把URL路径/list/中的/list部分替换掉生成访问服务器资源所在文件夹的路径:html/

当浏览器访问 test.com:8080/form/home/i… ,被匹配规则为 /form/ 的location块匹配到,用alias的值把URL路径/form/home/中的/form/部分替换掉生成访问服务器资源所在文件夹的路径:htmlhome/,可以看见URL路径的替换部分/form/和location块的匹配规则/form/一致。

在部署Vue工程时,rootalias一般配置为Vue工程编译打包后生成的文件夹在服务器上存放的路径。

3.5、index指令在location块的应用

index指令的作用时指定当用URL访问服务器某个文件夹时,默认访问文件夹中哪个文件。默认是index.html。

index指令可以配置多个文件路径,用空格隔开。文件路径可以是相对路径或绝对路径,若是绝对路径要放在最后配置。 如果配置多个文件路径,Nginx会根据文件路径的配置顺序从前往后来查找,直到查找到存在的文件。要注意文件的相对路径是相对于index指令所在location块中访问服务器资源所在文件夹的路径。

下面用一个例子来说明:

一般的Vue工程编译打包后都是以index.html,但是在多入口的Vue工程编译打包后会出现多个 .html 文件。比如一个多入口的Vue工程编译打包后的文件目录如下图所示:

其中 a.htmlb.html 文件都为工程的入口文件。把 dist 文件夹放入Nginx安装的根目录中。用Nginx来部署这个工程,那么应该如此配置:

server{
  listen 8081;
  server_name  test.com;
  location / {
    root   dist;
    index a/a.html;
  }
  location ^~ /b {
    root   dist;
    index b.html;
  }
}

3.6、try_files指令在location块的应用

try_files指令的作用是尝试查找文件。其语法是try_files file ... uri;, 其中 file 是文件的相对路径,可以配置多个,uri 是URL路径部分。要注意文件的相对路径是相对于try_files指令所在location块中访问服务器资源所在文件夹的路径。 Nginx会根据文件路径配置的顺序从前往后来查找,并使用第一个找到的文件的配置路径进行请求处理,如果通过配置的文件路径都没有找到对应的文件,则把最后一个配置的uri去匹配对应的location块。

在项目中使用history模式的路由,为了避免通过URL访问到的页面不存在时,返回浏览器自带的404页面。要在location块中增加一行配置,配置如下所示:

location / {
  try_files $uri $uri/ /index.html;
}

其中$uri是URL的路径和文件名部分,例如访问 test.com:8080/doc/index.h…$uri就是/doc/index.html

$uri/会去访问路径下的默认访问文件。例如 doc 文件夹下有 index.html 文件,访问 test.com:8080/doc 时,可以通过$uri/访问 index.html 文件 。

假设一个工程编译打包后只有一个入口文件 index.html, 那么try_files $uri $uri/ /index.html;的作用就是用任何URL访问工程中的页面,最终都会访问到入口文件 index.html,这样当访问工程中的页面不存在时,会访问工程中的404页面,而不会访问浏览器自带的404页面。

3.7、proxy_pass指令在location块的应用

代理有正向代理和反向代理两种,都是用proxy_pass指令来实现。

一句话解释正向代理,正向代理的对象是用户,用户知道访问那个服务器,而服务器不知道是哪个用户访问它。

举个例子来解释正向代理,假如你要访问一个网站,但是你没有权限访问这个网站。此时小明有权限访问这个网站,那么你可以让小明帮忙访问这个网站,把访问结果告诉你。在此过程中,小明就是你的代理,你就是客户端,网站就是服务端,网站只知道小明访问了它而不知道你访问了它。

一句话解释反向代理,反向代理的对象是服务器,用户不知道访问了哪个服务器,而服务器知道那个用户访问它。

举个例子来解释方向代理,一个网站有很多人访问,一个服务器承载不住这么多人的访问,那么就把网站部署到很多台服务器上,这时用一个服务器做代理,假设用户 A,B,C都访问了这个网站,会访问代理服务器,代理服务器通过计算把用户 A,B,C分别导流到不同的服务器上。在此过程中,用户只知道访问了代理服务器,而不知道真正访问的服务器,但真正的服务器可以知道是那个用户访问了它。

在部署项目中,一般用到反向代理。可以用proxy_pass指令实现最简单的反向代理。

例如访问 test.com:8080/doc ,用反向代理访问到 http://127.0.0.1:8001/ 这个服务,其配置如下所示:

server{
  listen 8080;
  server_name  test.com;
  location ^~ /doc {
    proxy_pass: http://127.0.0.1:8001/
  }
}

3.8、rewrite指令在location块的应用

rewrite的作用是重定向。其语法是rewrite regex replacement [flag];

  • regex是匹配URL路径部分的正则表达式;

  • replacement是重定向的地址,如果其值为文件路径时,其路径是相对路径,如果location块中未定义rootalias,且location块所在server块中也没有定义root,则将Nginx安装目录中 html 文件夹的路径作为参照路径。

  • flag 是标志位,在location块中一般使用break,表示执行完rewrite,不会继续匹配其它location块。如果使用last,表示执行完rewrite,会继续匹配其它location块。

四、部署方案

把Vue工程部署到域名的子路径上有三种方案:

  • 利用反向代理来部署;

  • 利用root配置来部署;

  • 利用alias配置来部署。

不管那种部署方案都需要用location块来匹配访问URL的路径部分并做处理。下面分别介绍一下三种部署方案的Nginx配置,至于配置过程中所用到的Nginx语法已在上面介绍了。

4.1、准备工作

  • 假设要部署在域名的子路径/bbb/上,要先更改Vue工程中的配置,且用 Vue CLI3 和 Vue CLI2 搭建的Vue工程,更改的地方不一样。

    • 用Vue CLI3搭建:
      • publicPath的值修改为子路径/bbb/,目的是使编译打包后代码中对资源的引用路径前面加上子路径/bbb/,在加载资源时发出的URL请求能被location块匹配到。在vue.config.js 文件中修改:
        module.exports = {
           publicPath: process.env.NODE_ENV == 'development' ? '/' : '/bbb/',
        }
        
      • 把路由的基路径base的值修改为子路径/bbb/,在src/router/index.js文件中修改:
        const router = new Router({
          //...
          base: process.env.BASE_URL,
        });
        
        process.env.BASE_URL 的值就是publicPath的值。
    • 用Vue CLI2搭建:
      • assetsPublicPath的值修改为子路径/bbb/,在 config/index.js中修改如下配置:
        module.exports = {
          build: {
            assetsPublicPath: '/bbb/',
          }
        }
        
      • 把路由的基路径base的值修改为子路径/bbb/,在src/router/index.js文件中修改:
        const router = new Router({
          //...
          base: /bbb/,
        });
        
  • 在服务器上找个地方,安装一下Nginx,在Nginx安装目录中找到 conf 文件夹,在其中创建一个 vhost 文件夹,专门来管理每个工程的部署配置;

  • 在服务器上找个地方,用git把要部署的工程代码克隆下来,找到工程编译打包后生成的文件夹所在位置,记下其路径。

4.2、利用反向代理来部署

conf/vhost 文件夹下创建 A.conf 来写入A工程部署到域名XXX( www.xxx.com )根路径上的部署配置,如下所示:

# A.conf
server {
  listen 80;
  server_name xxx.com;
  location / {
    root /home/ec2-user/git_src/avue/dist;
    try_files $uri $uri/ /index.html;
    index index.html;
  }
}

其中root所配置是A工程编译打包后生成的文件夹在服务器上的存放路径。

conf/vhost 文件夹下创建 B.conf 来写入B工程部署到端口为8081的服务上的部署配置,如下所示:

server {
  listen 8001;
  location / {
    root /home/ec2-user/git_src/bvue/dist;
    try_files $uri $uri/ /index.html;
    index index.html;
  }
}

conf/vhost/A.conf 文件中添加反向代理的配置,实现访问 www.xxx.com/bbb/ 时,通过反向代理访问 http://10.203.19.74:8081/ ,其中 10.203.19.74 为真实服务器ip地址。这样间接实现了在域名XXX( www.xxx.com )的子路径 /bbb/ 上部署了B工程。反向代理配置如下所示:

# A.conf
server {
  listen 80;
  server_name xxx.com;
  location / {
     root /home/ec2-user/git_src/avue/dist;
     try_files $uri $uri/ /index.html;
     index index.html;
  }
  location ^~ /bbb {
    proxy_pass http://10.203.19.74:8081/;
  }
}

4.3、利用alias配置来部署

conf/vhost/A.conf 文件中写入所示的配置:

# A.conf
server {
  listen 80;
  server_name xxx.com;
  location / {
    root /home/ec2-user/git_src/avue/dist;
    try_files $uri $uri/ /index.html;
    index index.html;
  }
  location ^~ /bbb {
    alias /home/ec2-user/git_src/bvue/dist;
    try_files $uri $uri/ /index.html;
    index index.html;
  }
}
  • root所配置是A工程编译打包后生成的文件夹在服务器上存放的路径;
  • alias所配置是B工程编译打包后生成的文件夹在服务器上存放的路径。

如果location块的匹配规则是字符串且以 / 结尾,那么alias 指令的值最后一定要加 /,否则可加可不加。

此外还要处理一下访问工程中的页面不存在时的场景,例如用 www.xxx.com/bbb/test 访问工程,其中 /test 路径在工程的路由中未定义。按原先的配置,会访问到A工程中的404页面。

这是因为匹配规则为/bbb的location块中try_files指令的最后一个参数引起的。按着前面对try_files指令的介绍,在 try_files 指令执行中没有找到匹配的文件后,会用 /index.html 去匹配其它location块。于是匹配到匹配规则为/的location 块,导致访问B工程中未定义的页面会访问到A工程中404页面。

这里用修饰符 @ 创建一个内部的location块(@router),在这个location块中用rewrite指令来解决这个问题。配置如下所示:

# A.conf
server {
  listen 80;
  server_name xxx.com;
  location / {
    root /home/ec2-user/git_src/avue/dist;
    try_files $uri $uri/ /index.html;
    index index.html;
  }
  location ^~ /bbb {
    alias /home/ec2-user/git_src/bvue/dist;
    try_files $uri $uri/ @router;
    index index.html;
  }
  location @router {
    root /home/ec2-user/git_src/bvue/dist;
    rewrite ^.*$ /index.html break;
  }
}

要注意在location块中使用rewrite指令,要用root指令定义重定向访问服务器资源所在文件夹路径,不然重定向访问的文件的相对路径会相对于Nginx的安装目录中的html文件夹路径。此外rewrite指令的标志位也要选择break,不然执行完rewrite还会继续匹配其它location块,最终还是会匹配到规则为/的location块,导致访问B工程中未定义的页面会访问到A工程中404页面。

4.4、利用root配置来部署

conf/vhost/A.conf 文件中写入如下所示的配置:

# A.conf
server {
  listen 80;
  server_name xxx.com;
  location / {
    root /home/ec2-user/git_src/avue/dist;
    try_files $uri $uri/ /index.html;
    index index.html;
  }
  location ^~ /bbb {
    root /home/ec2-user/git_src/bvue/;
    try_files $uri $uri/ @router;
    index index.html;
  }
  location @router {
  	root /home/ec2-user/git_src/bvue/dist;
  	rewrite ^.*$ /index.html break;
  }
}
  • 匹配规则为/的location块中root所配置是A工程编译打包后生成的文件夹在服务器上存放的路径;
  • 匹配规则为/bbb的location块中 root 所配置是B工程代码所在的文件夹在服务器上存放的路径;
  • @router的location块中的root所配置是B工程编译打包后生成的文件夹在服务器上存放的路径。

同时要把工程编译打包生成的文件夹名称改成和所部署域名的子路径一样。在Vue工程中可以这么配置。

  • 用Vue CLI3搭建:在 vue.config.js 文件中修改outputDirbbb;
    module.exports = {
      outputDir: 'bbb',
    }
    
  • 用Vue CLI2搭建:在 config/index.js 文件中修改如下配置:
    module.exports = {
      build: {
        //...
        assetsRoot: path.resolve(__dirname, '../bbb'),
      }
    }