利用Nginx代理如何解决前端跨域问题详析


Posted in Servers onApril 02, 2021

前言

Nginx(发音同“engine X”)是异步框架的网页服务器,也可以用作反向代理、负载平衡器和HTTP缓存。

本文将讲述如何使用 Nginx 在 Web 前后端分离开发中实现路由的转发。

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 匹配路由的方式为:

  1. 首先进行前缀匹配,遍历所有的前缀匹配,从中选择前缀匹配最长的;
  2. 然后会进行正则匹配,在所有正则匹配中,从前往后选择第一个符合的;
  3. 如果能找到匹配的正则匹配,使用其对应的配置;如果没有,则使用之前找到的那个最长的前缀匹配对应的配置。

所以,当请求为 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 配置的时候,一定要注意端口后面的 / 是否有必要保留。因为它的有无会导致两种截然不同的效果。

参考文章

总结

到此这篇关于利用Nginx代理如何解决前端跨域问题的文章就介绍到这了,更多相关Nginx代理解决前端跨域内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Servers 相关文章推荐
Nginx同一个域名配置多个项目的实现方法
Mar 31 Servers
Nginx服务器添加Systemd自定义服务过程解析
Mar 31 Servers
Nginx服务器添加Systemd自定义服务过程解析
Mar 31 Servers
解析在浏览器地址栏输入一个URL后发生了什么
Jun 21 Servers
使用Nginx搭载rtmp直播服务器的方法
Oct 16 Servers
图文详解nginx日志切割的实现
Jan 18 Servers
nginx日志格式分析和修改
Apr 28 Servers
在容器中使用nginx搭建上传下载服务器
May 11 Servers
nginx lua 操作 mysql
May 15 Servers
nginx 添加http_stub_status_module模块
May 25 Servers
vscode内网访问服务器的方法
Jun 28 Servers
Apache SkyWalking 监控 MySQL Server 实战解析
Sep 23 Servers
Nginx URL重写rewrite机制原理及使用实例
Apr 01 #Servers
nginx限制并发连接请求数的方法
Apr 01 #Servers
Nginx已编译的nginx-添加新模块
Nginx下配置Https证书详细过程
详解Nginx启动失败的几种错误处理
Apr 01 #Servers
Nginx 根据URL带的参数转发的实现
Apr 01 #Servers
Nginx Rewrite使用场景及配置方法解析
You might like
类的另类用法--数据的封装
2006/10/09 PHP
php中simplexml_load_string使用实例分享
2014/02/13 PHP
浅析ThinkPHP的模板输出功能
2014/07/01 PHP
php验证手机号码
2015/11/11 PHP
老生常谈PHP数组函数array_merge(必看篇)
2017/05/25 PHP
TP(thinkPHP)框架多层控制器和多级控制器的使用示例
2018/06/13 PHP
PHP+mysql防止SQL注入的方法小结
2019/04/27 PHP
解决tp5在nginx下修改配置访问的问题
2019/10/16 PHP
jquery插件 cluetip 关键词注释
2010/01/12 Javascript
javascript写的日历类(基于pj)
2010/12/28 Javascript
简单的两种Extjs formpanel加载数据的方式
2013/11/09 Javascript
jquery操作select大全
2014/04/25 Javascript
js 去除字符串第一位逗号的方法
2014/06/07 Javascript
jQuery中find()方法用法实例
2015/01/07 Javascript
2016年最热门的15 款代码语法高亮工具,美化你的代码
2016/01/06 Javascript
使用Jasmine和Karma对AngularJS页面程序进行测试
2016/03/05 Javascript
jQuery实现二维码扫描功能
2017/01/09 Javascript
详解ES6 Symbol 的用途
2018/10/14 Javascript
vue路由的配置和页面切换详解
2020/09/09 Javascript
解决antd 表单设置默认值initialValue后验证失效的问题
2020/11/02 Javascript
Python中用于计算对数的log()方法
2015/05/15 Python
Python与R语言的简要对比
2017/11/14 Python
python正则表达式匹配不包含某几个字符的字符串方法
2019/07/23 Python
Python threading.local代码实例及原理解析
2020/03/16 Python
详解Python 最短匹配模式
2020/07/29 Python
Python HTMLTestRunner如何下载生成报告
2020/09/04 Python
Expedia丹麦:全球领先的旅游网站
2018/03/18 全球购物
颇特女士:NET-A-PORTER(直邮中国)
2020/07/11 全球购物
医药工作岗位求职信分享
2013/12/31 职场文书
母亲80寿诞答谢词
2014/01/16 职场文书
年会活动策划方案
2014/01/23 职场文书
酒店总经理助理岗位职责
2014/02/01 职场文书
2014年高中教师工作总结
2014/12/19 职场文书
描述鲁迅的名言整理,一生受用
2019/08/08 职场文书
大学生军训心得体会5篇
2019/08/15 职场文书
Keras多线程机制与flask多线程冲突的解决方案
2021/05/28 Python