Nginx(发音同“engine X”)是异步框架的网页服务器,也可以用作反向代理、负载平衡器和HTTP缓存。
本文会讲述如何使用 Nginx 的代理功能帮助进行前端页面的开发。
Web 开发通常使用的是前后端分离的开发模式,即前端和后端分别进行开发,前端通过 Ajax 请求后端的接口,将获取数据将数据渲染到页面上。前端开发会使用脚手架搭建前端开发环境,其底层通常会启动一个本地服务器,通常使用的是 nodejs 的 Express 框架。而后端则是提供接口,一般是放在线上的一个开发用的域名下。
这在开发过程中会导致 跨域 问题,即在一个域名下的网页,是无法通过 Ajax 请求另一个(不同源)域名下的接口 API 的。这是浏览器的同源策略,是浏览器的一个非常重要的安全策略。
解决这个问题的其中一个方案是使用 代理。具体来说,就是在本地启动一个服务器(如 localhost:4000
),发送给该服务器的请求会根据请求路由(比如判断 url 是否有前缀 /api
)进行转发,分别转发到前端开发的服务器(如 localhost:3000
),以及后端服务器(比如 dev.yoursite.com
)。这样通过一个代理服务器,因为请求的 api 都是同一个域名下的,自然就不会造成跨域问题,从而导致请求失败。
下面我们就来讲解如何使用 Nginx 来实现反向代理。
简单认识 Nginx 配置文件
安装好 Nginx 后,我们需要确定下 Nginx 的默认配置文件的位置。执行命令 nginx -t
,该命令会检测 nginx 的默认配置文件语法是否正确,并进行测试,最后输出结果。我们可以从输出中得到默认配置文件所在的位置。
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
还有另外一种方法可以得到默认配置文件的位置,那就是执行 nginx -h
。该命令会输出 nginx 的简易帮助文档,其中的 -c filename
的配置项说明也指出了默认配置项的路径。
-c filename : set configuration file (default: /etc/nginx/nginx.conf)
通过这个文档,我们也可以知道,使用 -c
配置项可以自定义配置文件。如果不指定文件,使用默认配置文件。
下面我们来修改一下 Nginx 到这个默认配置文件 nginx.config
来实现代理功能。
nginx.config
文件的 http
后面的代码块中,应该会有类似下面这行的代码:
include /etc/nginx/conf.d/*.conf;
这行代码的作用是将 /etc/nginx/conf.d
目录下的后缀为 .conf
的文件内容嵌入到引入位置中,作为配置的一部分执行。
如果你是在 macOS 安装的 Nginx,可能会有点不同。我使用 brew 安装的 Nginx 为为 include servers/*;
,对应嵌入的是 servers
目录下的所有文件。
为什么要用到这种嵌入的语法呢?因为这样可以将不同项目需要用到的配置放到不同的配置文件里,好处是可以快速地找到对应项目要修改的配置文件,不用担心不小心修改了其他项目的配置。另外如果全部都直接在 nginx.conf
上修改,会让这个文件变得臃肿,当配置多的时候变得难以配置。这符合设计模式的 单一职责原则。
此外,你可能会奇怪 conf.d
目录的命名为什么要加上 .d
?如果你使用 Linux 过一段时间,你会发现某些目录或文件的末尾会加上一个 d,比如 httpd
、crond
、vsftpd
等。其实这是为了说明这些文件都属于是 daemon(服务)。这里的服务指的是系统的服务,主要分为系统本身需要的服务,以及负责网络的服务。我们的 conf.d 属于后者。
编写 Nginx 配置文件
我们在 conf.d 目录下创建名为 demo.conf
的文件,写入以下内容,然后启动 Nginx。
server {
listen 5000;
server_name localhost;
location / {
proxy_pass http://localhost:3000;
}
location /api/ {
proxy_pass http://localhost:4000;
}
}
该配置启用了 localhost:5000
的服务器,将 localhost:5000
下开头为 /api/
url 请求代理到了 localhost:4000
(后端接口服务器)。其他请求则是代理到 localhost:3000
(前端)。下面我们具体解析一下配置文件里面内容的作用。
listen
设置了服务器的端口号,server_name
则设置了主机名。
location
location
表示进行路由的匹配,如果匹配则执行对应代码块里的操作。location
可以使用 前缀匹配 以及 正则匹配(需要以 ~*
或 ~
开头)。我们这里的配置使用的是前缀匹配。
这里有个点需要注意一下,Nginx 的路由匹配和一般的按顺序匹配第一个的路由匹配方案(比如后端的 gin、前端的 vue-router 的路由匹配方案)不同,nginx 匹配路由的方式为:
- 首先进行前缀匹配,遍历所有的前缀匹配,从中选择前缀匹配最长的;
- 然后会进行正则匹配,在所有正则匹配中,从前往后选择第一个符合的;
- 如果能找到匹配的正则匹配,使用其对应的配置;如果没有,则使用之前找到的那个最长的前缀匹配对应的配置。
所以,当请求为 localhost:5000/api/xx
时, /
和 /api/
都能够前缀匹配。根据规则,虽然位置更靠前的 /
也符合前缀匹配,但 /api
更长,所以最终匹配的是 /api
。
proxy_pass
确定好匹配的 location
后,我们再看看 proxy_pass
又做了什么操作。proxy_pass
用于将请求路由映射到指定的协议和地址。本质是将发送给 Nginx 的请求处理并发送到另一个服务器,然后将返回的数据作为 Nginx的返回数据返回。
-
proxy_pass
后如果使用的是 URI(端口后面至少有一个/
),那么 Nginx 就会 替换 掉 location 匹配的那部分字符。
listen 5000;
server_name localhost;
location /name/ {
proxy_pass http://127.0.0.1/remote/;
}
# localhost:5000/name/fstar
# 会被映射请求为
# 127.0.0.1/remote/fstar
可以看到,/name/ 的部分在映射时被移除(或者说是替换)了。
-
proxy_pass
后如果使用的是不是 URI(端口后没有任何东西),Nginx 会将源请求完全映射到代理服务上:
listen 5000;
server_name localhost;
location /some/path/ {
proxy_pass http://127.0.0.1;
}
# localhost:5000/some/path/x/y
# 会被映射请求为
# 127.0.0.1/some/path/x/y
这里的 /some/path
并没有被移除。
我们的 demo.conf
文件的 proxy_pass
使用的不是 URI,所以是将路由完全映射到另一个服务。
思考题
请问,下面有两段配置(区别是 proxy_pass
结尾是否有 /
),如果请求 /kite/api/xx
,分别会映射为什么?
location /kite/api/ {
proxy_pass http://localhost:5000;
}
location /kite/api/ {
proxy_pass http://localhost:5000/;
}
前面我们讲 proxy_pass
的时候说过,proxy_pass
后面如果不是 URI,会正常转发;如果是 URI,就移除 location
匹配的前缀再进行转发,体现的是替换路由的效果。上面这两个配置的区别就在于末尾的这个 /
,有 /
是 URI,没有的不是 URI,从而导致完全不一样的结果,依次分别为:
http://localhost:5000/kite/api/xx
http://localhost:5000/xx
所以,在写 Nginx 配置的时候,一定要注意端口后面的 /
是否有必要保留。因为它的有无会导致两种截然不同的效果。
文章首发于我的 个人博客网站