nginx解决跨域的实际应用
随着前后端分离成为主流,跨域请求已成为每个 Web 开发者必须面对的问题。传统的解决跨域的 JSONP 技术局限昭著,为此市面上出现了无数的跨域解决方案。本文将具体介绍如何利用反向代理服务器 Nginx 来简单、优雅地实现跨域。
背景
跨域请求由浏览器的“同源策略”造成,是必然会遇到的问题。目前主流的解决方案是在服务器端进行 CORS 配置,允许指定域的跨域请求。但是这样需要服务端的配置配合,一旦后端无法修改,前端就无能为力了。
使用 Nginx 作为反向代理服务器,可以轻松地在前端工程中解决跨域问题,无需后端进行任何配置,只需要在前端请求地址进行简单改造,通过 Nginx 转发请求即可实现跨域。这种方式成本低、可控性好,非常适合前后端分离的项目。
本文将具体介绍 Nginx 的反向代理配置,以及如何在前端代码中使用,从而完美解决跨域请求,使前后端开发真正解耦合。
Nginx配置
在下载安装了Nginx后,以mac为例,配置文件的目录如下:
nginx.conf是主配置文件,里面会做一些的全局的配置,如错误日志的目录等等。里面会引用servers目录下的其它配置文件,这里一般放一些具体的配置。
http区块下可以包含多个server区块,各个server监听不同的域名和端口号。
#user nobody; # worker进程的用户
worker_processes 1; # worker进程的数量
error_log /var/log/nginx/error.log; # 所有的错误写入的文件,在其它区块里还可以进一步设置
events {
worker_connections 1024; # worker进程能够接受并发连接的最大数
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
gzip on;
gzip_min_length 1k;
gzip_buffers 4 16k;
gzip_http_version 1.1;
gzip_comp_level 9;
gzip_types text/plain application/x-javascript text/css application/xml text/javascript application/javascript application/json;
gzip_disable "MSIE [1-6]\.";
gzip_vary on;
server {
listen 48635;
server_name localhost;
# /匹配到的路由,会在root指定的html文件夹查找index.html或index.htm文件
location / {
root html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
include servers/*;
}
location
location命令只能在server区块下定义,指定了路由匹配规则,语法如下:
location [修改符] uri {...}
location @name {...} # 具名的路由
location不加修饰符时是普通的根据前缀匹配,加修饰符时根据修饰符的不同可分为精确匹配和正则匹配。
location的修饰符有四种:
=
精确匹配~
正则匹配,有大小写~*
正则匹配,不区分大小写^~
优先于正则的普通前缀匹配
举个例子:
location = /api 精确匹配/api的路由
location ~ /api 匹配/api开头的路由,~加*可以匹配/Api这样的路由
loicaton ^~ /api是前缀匹配,但比正则匹配的优先级更高。
整体的优先级顺序从高到低是:
精确匹配(=) > 高优先级前缀匹配(^~) > 正则匹配(~ ~*) > 普通前缀匹配
try_files
try_files可用在server下,但经常是用在location里。常用的用法是根据给的参数顺序进行尝试,将匹配到的处理传递给一个具名的location。
location / {
try_files @uri @uri/ @mongrel;
}
location @mongrel {
proxy_pass http://apiserver;
}
反向代理
相信很多人已经了解了正向代理与反向代理的不同,这里做个简单的比较。
正向与反向
不管是正向还是反向,代理是介于用户与目标服务器之间的桥梁。
"正向代理"主要解决客户端IP访问受限(如:VPN)和网络安全性(如:防火墙)的问题,典型的场景就是梯子。
"反向代理"(Reverse Proxy)主要解决服务器稳定性和资源利用的问题(加强网络数据处理能力、提高网络的灵活性和可用性), 典型的场景是负载均衡。
解决跨域需要的配置
出现跨域问题通常是因为请求了非同源的接口,为了解决跨域,我们使用Nginx作为反向代理,终结来自客户端的请求,并向上游服务器打开一个新的请求。代理到上游服务器的配置中,最重要的是proxy_pass指令。
proxy_pass
该指令接收一个参数,表示url请求会被转换。例如将uri请求代理到apserver上的/newuri。
location /uri {
proxy_pass http://apiserver/newuri;
}
proxy_pass转发有两个例外情况:location 定义的正则表达式不会发生转换;location内有rewrite也不会发生转换。
location ~ ^/local {
proxy_pass http://apiserver/foreign; // /local会直接传递到上游服务器,而不会转成/foreign
}
location / {
rewrite /(.*)$ /index.php?page=$1 break;
proxy_pass http://apiserver/index; // 前面的rewrite改变了uri,此处的转换不再发生
}
此外proxy_pass在上游服务器后加不加/
是不同的。
location /api {
proxy_pass http://apiserver/new_api; # localhost/api会被转换成 apiserver/new_api
}
location /api {
proxy_pass http://apiserver/new_api/; # 此处加了/,/api会被截断,localhost/api被转换成 apiserver/
}
add_header与proxy_ser_header
proxy_set_header是Nginx设置头部信息给到上游服务器,add_header是Nginx设置响应请求头给浏览器。
在处理跨域问题时,浏览器只会与Nginx打交道,所以add_header一般设置如下
add_header Access-Control-Allow-Origin http://localhost:8090 always;
add_header Access-Control-Allow-Headers "Accept,Accept-Encoding,Accept-Language,Connection,Content-Length,Content-Type,Host,Origin,Referer,User-Agent";
add_header Access-Control-Allow-Methods "GET,POST,PUT,DELETE,OPTIONS";
add_header Access-Control-Allow-Credentials true;
上游服务器处理请求时,有些情况下需要读取头部信息,这里就需要proxy_set_header进行设置。
proxy_set_header Host $host; # 将原始Host头传递给上游而不是Nginx自己的hostname
proxy_set_header X-Forwarded-For $remote_addr; # 传递原始客户端的IP
proxy_set_header User-Agent $http_user_agent; # 传递原始客户端的UA信息
proxy_set_header Authorizaton ""; # 删除客户端请求的敏感头信息
proxy_set_header X-Proxy-ID $server_name; # 设置自定义头部
需要注意的是,proxy_set_header 会覆盖之前设置的同名头。可以通过添加标志来附加头值而不是替换。在进行反向代理配置时,合理设置请求头可以帮助后端服务器获得原始的客户端信息,提高安全性和日志分析效果。
例子
下面通过一个例子具体看下Nginx如何配置来实现跨域的问题。原有系统部署在ip_a:31023,内部直接请求了ip_b:8000 和 ip_c:31298。因为域名和端口号都不相同,自然会有跨域问题。
现在第一步是启动一个Nginx,作为原服务的配置文件部署在ip_a下,所以系统访问Nginx不存在跨域的问题。第二步修改请求接口的方式,从直接请求改为请求代理服务器。第三步是修改Nginx配置,通过不同的uri将请求代理到不同的上游服务器中。
在Nginx的配置里,server要监听ip_a的端口号,同时利用location、add_header、proxy_set_header、proxy_pass等配置项共同完成。
server {
listen 31023;
server_name localhost;
location /emop {
add_header Access-Control-Allow-Origin http://ip_a:31023 always;
add_header Access-Control-Allow-Headers "Accept,Accept-Encoding,Accept-Language,Connection,Content-Length,Content-Type,Host,Origin,Referer,User-Agent";
add_header Access-Control-Allow-Methods "GET,POST,PUT,DELETE,OPTIONS";
add_header Access-Control-Allow-Credentials true;
proxy_pass http://ip_b:8000;
}
location /omp {
add_header Access-Control-Allow-Origin http://ip_a:31023 always;
add_header Access-Control-Allow-Headers "Accept,Accept-Encoding,Accept-Language,Connection,Content-Length,Content-Type,Host,Origin,Referer,User-Agent";
add_header Access-Control-Allow-Methods "GET,POST,PUT,DELETE,OPTIONS";
add_header Access-Control-Allow-Credentials true;
proxy_pass http://ip_c:31298;
}
}
上面就是通过Nginx反向代理解决跨域的简单配置。在实例使用过程中有几点注意事项:
- Nginx默认会把header中包含下划线的参数给去掉,导致获取不到该参数。解决方法是在server区块下增加
underscores_in_headers on;
- Nginx允许使用
if
进行逻辑判断,但不能像其它编程语言一样进行if嵌套。例如需要cond_a和cond_b都满足的时候进行proxy_pass,下面的做法是通不过语法检查的。
location /api {
if (cond_a) {
if (cond_b) {
proxy_pass http://apiserver/api;
}
}
}
通常这种情况下可以通过设置变量来进行绕行。满足cond_a时设置变量a,满足cond_b时设置变量b,然后设置变量c将a和b粘合起来。当c的设置达到cond_a&&cond_b时执行相应的操作。
location /api {
set $a 0;
if (cond_a) set $a 1;
set $b 0;
if (cond_b) set $b 1;
set $c "${a}${b}";
if ($c = 11) {
proxy_pass http://apiserver/api;
}
}
本地开发时使用Nginx代理
上面介绍了在实际生产环境如何通过配置反向代理实现跨域,那么在本地开发时如何解决跨域的问题呢?一种方法是通过禁用浏览器的web-security在浏览器层绕过跨域,另一种方法就是同样利用Nginx,整体的配置与上面是类似的。
假设本地服务启动之后运行在localhost:8080上。通过修改Nginx配置文件指定Nginx启动后监听的端口号,假设是8090。
第一步将代码中/mop和/omp的请求交给Nginx处理。
第二步Nginx配置中监听/
中路径,将请求转给本地服务,这样才能正常打开页面。
server {
listen 8090;
server_name localhost;
...
location / {
proxy_pass http://localhost:8080;
}
}
第三步与前面类似,将不同url请求转发到相应的上游服务器中,不再赘述。
总结
本文介绍了Nginx中的一些指令的配置,通过配置不同的头部和proxy_pass可以达到解决跨域的效果。在实例使用中,还有一些文章中没有介经到的点,例如日志的配置、upstream、反向代理服务器的性能调优等。同样利用Nginx还可以实现流量染色、灰度等场景,大家有兴趣的话也可以研究一下。