利用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+SpringBoot实现负载均衡的示例
Mar 31 Servers
阿里云Nginx配置https实现域名访问项目(图文教程)
Mar 31 Servers
小程序后台PHP版本部署运行 LNMP+WNMP
Apr 01 Servers
制作能在nginx和IIS中使用的ssl证书
Jun 21 Servers
Nginx进程调度问题详解
Sep 25 Servers
nginx中proxy_pass各种用法详解
Nov 07 Servers
Tomcat starup.bat 脚本实现开机自启动
Apr 20 Servers
讨论nginx location 顺序问题
May 30 Servers
使用Nginx+Tomcat实现负载均衡的全过程
May 30 Servers
Apache Kafka 分区重分配的实现原理解析
Jul 15 Servers
Apache自带的ab压力测试工具的实现
Jul 23 Servers
CentOS7 minimal 最小化安装网络设置过程
Dec 24 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
在php中使用sockets:从新闻组中获取文章
2006/10/09 PHP
php for 循环使用的简单实例
2016/06/02 PHP
php 替换文章中的图片路径,下载图片到本地服务器的方法
2018/02/06 PHP
dwz 如何去掉ajaxloading具体代码
2013/05/22 Javascript
JS实现简单的Canvas画图实例
2013/07/04 Javascript
js定时器怎么写?就是在特定时间执行某段程序
2013/10/11 Javascript
javascript 回到顶部效果的实现代码
2014/02/17 Javascript
jquery自定义滚动条插件示例分享
2014/02/21 Javascript
javascript浏览器兼容教程之事件处理
2014/06/09 Javascript
js解决select下拉选不中问题
2014/10/14 Javascript
网页中表单按回车就自动提交的问题的解决方案
2014/11/03 Javascript
JavaScript删除数组元素的方法
2015/03/20 Javascript
原生JS实现图片懒加载(lazyload)实例
2017/06/13 Javascript
node.js 基于cheerio的爬虫工具的实现(需要登录权限的爬虫工具)
2019/04/10 Javascript
微信小程序的线程架构【推荐】
2019/05/14 Javascript
JS造成内存泄漏的几种情况实例分析
2020/03/02 Javascript
vue 移动端记录页面浏览位置的方法
2020/03/11 Javascript
Python实现的数据结构与算法之基本搜索详解
2015/04/22 Python
python读写二进制文件的方法
2015/05/09 Python
Python+django实现文件下载
2016/01/17 Python
使用Python制作微信跳一跳辅助
2018/01/31 Python
Python安装selenium包详细过程
2019/07/23 Python
通过 Django Pagination 实现简单分页功能
2019/11/11 Python
pytorch之ImageFolder使用详解
2020/01/06 Python
pycharm 中mark directory as exclude的用法详解
2020/02/14 Python
python解释器pycharm安装及环境变量配置教程图文详解
2020/02/26 Python
Django框架静态文件处理、中间件、上传文件操作实例详解
2020/02/29 Python
python爬虫开发之urllib模块详细使用方法与实例全解
2020/03/09 Python
python进行参数传递的方法
2020/05/12 Python
Python爬虫小例子——爬取51job发布的工作职位
2020/07/10 Python
销售类个人求职信范文
2013/09/25 职场文书
买卖车协议书
2014/04/21 职场文书
党员学习正风肃纪思想汇报
2014/09/12 职场文书
2015年推普周活动方案
2015/05/06 职场文书
2015年教务处干事工作总结
2015/07/22 职场文书
三八节祝酒词
2015/08/11 职场文书