本章导读

  • 这章开始写写一些小技巧,或者叫小功能
  • 目前很流行前后端分离
  • 即使不分离,如果需要用ajax调用,但是调用服务不在同域
  • 这个时候,nginx都可以帮上忙

开始前先看一个chrome的报错:

:8080/#/auth/login:1 Access to XMLHttpRequest at ‘http://localhost:8830/extinterface/loginExt’ from origin ‘http://localhost:8080’ has been blocked by CORS policy: Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header is present on the requested resource.

再看看不修改前端代码(比如js),也不修改java代码(假设后端是java实现的),只修改下nginx的配置:

http {
	include       mime.types;
	add_header 'Access-Control-Allow-Origin' '*';
	add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
	add_header 'Access-Control-Allow-Credentials' 'true';
	default_type  application/octet-stream;
	
	map $http_origin $corsHost {
		default 0;
		"~http://130.51.23.17:8080" http://130.51.23.17:8080;
	}
}

本章要点

  • 首先要了解跨域,不同的ip,或者不同的域名,当然算跨域
  • 如果同样的ip,不同的端口,算跨域吗?当然算的
  • 解决的方法有很多,nginx只是其中一种
  • nginx解决,也有两种配置方法,一种是反向代理,把不同域的a域名反向代理成同域的b域名
  • 另一种是设置header,使得服务端允许跨域
  • 这里说的服务端允许跨域,不是说修改服务端的代码(当然也可以这么说,但是修改代码要重新编译发布,太麻烦),而是通过nginx来访问服务端,而中间的nginx设置了一下header
  • 如果是允许了跨域,可能会导致一些安全的问题,所以最好服务端应该在内网,并且有防火墙保护的

了解跨域以及产生原因

跨域是指a页面想获取b页面资源,如果a、b页面的协议、域名、端口、子域名不同,或是a页面为ip地址,b页面为域名地址,所进行的访问行动都是跨域的,而浏览器为了安全问题一般都限制了跨域访问,也就是不允许跨域请求资源。

注意:跨域限制访问,其实是浏览器的限制。理解这一点很重要。所以,当用java(或者其他语言)调用RESTful api,从来不会报什么跨域错误。

跨域情况如下:

url说明是否跨域
http://www.cnblogs.com/a.js,http://www.a.com/b.js不同域名
http://www.a.com/lab/a.jshttp://www.a.com/script/b.js同一域名下不同文件夹
http://www.a.com:8000/a.jshttp://www.a.com/b.js同一域名,不同端口
http://www.a.com/a.jshttps://www.a.com/b.js同一域名,不同协议
http://www.a.com/a.jshttp://70.32.92.74/b.js域名和域名对应ip
http://www.a.com/a.jshttp://script.a.com/b.js主域相同,子域不同是(cookie不可访问)
http://www.a.com/a.jshttp://a.com/b.js同一域名,不同二级域名(同上)

跨域的常见解决方法

目前来讲没有不依靠服务器端来解决跨域请求资源的技术,也就是说,如果服务端不允许跨域请求,那么单靠客户端无论怎么弄,都是不行的。

  • 1.jsonp 需要目标服务器配合一个callback函数。

  • 2.window.name+iframe 需要目标服务器响应window.name

  • 3.window.location.hash+iframe 同样需要目标服务器作处理。

  • 4.html5的 postMessage+ifrme 这个也是需要目标服务器或者说是目标页面写一个postMessage,主要侧重于前端通讯。

  • 5.CORS 需要服务器设置header :Access-Control-Allow-Origin。

  • 6.nginx反向代理 这个方法一般很少有人提及,但是他可以不用目标服务器配合,不过需要你搭建一个中转nginx服务器,用于转发请求。

方法一:add_header

当出现403跨域错误的时候 No ‘Access-Control-Allow-Origin’ header is present on the requested resource,需要给Nginx服务器配置响应的header参数

解决方案

只需要在Nginx的配置文件中配置以下参数:

location / {  
    add_header Access-Control-Allow-Origin *;
    add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
    add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';

    if ($request_method = 'OPTIONS') {
        return 204;
    }
} 

上面配置代码即可解决问题了,不想深入研究的,看到这里就可以.

解释

1. Access-Control-Allow-Origin

服务器默认是不被允许跨域的。给Nginx服务器配置Access-Control-Allow-Origin *后,表示服务器可以接受所有的请求源(Origin),即接受所有跨域的请求。

2. Access-Control-Allow-Headers

是为了防止出现以下错误:

Request header field Content-Type is not allowed by Access-Control-Allow-Headers in preflight response.

这个错误表示当前请求Content-Type的值不被支持。其实是我们发起了"application/json"的类型请求导致的。这里涉及到一个概念:预检请求(preflight request),请看下面"预检请求"的介绍。

3. Access-Control-Allow-Methods

是为了防止出现以下错误:

Content-Type is not allowed by Access-Control-Allow-Headers in preflight response.

4.给OPTIONS 添加 204的返回

是为了处理在发送POST请求时Nginx依然拒绝访问的错误

发送"预检请求"时,需要用到方法 OPTIONS ,所以服务器需要允许该方法。

预检请求(preflight request)

其实上面的配置涉及到了一个W3C标准:CROS,全称是跨域资源共享 (Cross-origin resource sharing),它的提出就是为了解决跨域请求的。

跨域资源共享(CORS)标准新增了一组 HTTP 首部字段,允许服务器声明哪些源站有权限访问哪些资源。另外,规范要求,对那些可能对服务器数据产生副作用的HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨域请求。服务器确认允许之后,才发起实际的 HTTP 请求。在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证(包括 Cookies 和 HTTP 认证相关数据)。
其实Content-Type字段的类型为application/json的请求就是上面所说的搭配某些 MIME 类型的 POST 请求,CORS规定,Content-Type不属于以下MIME类型的,都属于预检请求:

application/x-www-form-urlencoded
multipart/form-data
text/plain

所以 application/json的请求 会在正式通信之前,增加一次"预检"请求,这次"预检"请求会带上头部信息

Access-Control-Request-Headers: Content-Type:
OPTIONS /api/test HTTP/1.1
Origin: http://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type
… 省略了一些

服务器回应时,返回的头部信息如果不包含Access-Control-Allow-Headers: Content-Type则表示不接受非默认的的Content-Type。即出现以下错误:

Request header field Content-Type is not allowed by Access-Control-Allow-Headers in preflight response.

方法二:反向代理

上面已经说到,禁止跨域问题其实是浏览器的一种安全行为,而现在的大多数解决方案都是用标签可以跨域访问的这个漏洞或者是技巧去完成,但都少不了目标服务器做相应的改变,而我最近遇到了一个需求是,目标服务器不能给予我一个header,更不可以改变代码返回个script,所以前5种方案都被我否决掉。最后因为我的网站是我自己的主机,所以我决定搭建一个nginx并把相应代码部署在它的下面,由页面请求本域名的一个地址,转由nginx代理处理后返回结果给页面,而且这一切都是同步的。

关于nginx的一些基本配置和安装请看我的另一篇博客,下面直接讲解如何配置一个反向代理。
https://blog.csdn.net/ouyida3/article/details/86763553

首先找到nginx.conf或者nginx.conf.default 或者是default里面的这部份。

其中server代表启动的一个服务,location 是一个定位规则。

location /{   #所有以/开头的地址,实际上是所有请求
  root  html     #去请求../html文件夹里的文件,其中..的路径在nginx里面有定义,安装的时候会有默认路径,详见另一篇博客
  index  index.html index.htm  #首页响应地址
}

从上面可以看出location是nginx用来路由的入口,所以我们接下来要在location里面完成我们的反向代理。

假如我们我们是 www.a.com/html/msg.html 想请求 www.b.com/api/?method=1&para=2;

我们的ajax:

var url = 'http://www.b.com/api/msg?method=1&para=2';
<br>$.ajax({
type: "GET",
url:url,
success: function(res){..},
....
})

上面的请求必然会遇到跨域问题,这时我们需要修改一下我们的请求url,让请求发在nginx的一个url下。

var url = 'http://www.b.com/api/msg?method=1&para=2'var proxyurl = 'msg?method=1&para=2'//假如实际地址是 www.c.com/proxy/html/api/msg?method=1&para=2; www.c.com是nginx主机地址
 $.ajax({
type: "GET",
url:proxyurl,
success: function(res){..},
....
})  

再在刚才的路径中匹配到这个请求,我们在location下面再添加一个location。

location ^~/proxy/html/{
  rewrite ^/proxy/html/(.*)$ /$1 break;
  proxy_pass http://www.b.com/;
}

以下做一个解释:

1.’^~ /proxy/html/ ’

就像上面说的一样是一个匹配规则,用于拦截请求,匹配任何以 /proxy/html/开头的地址,匹配符合以后,停止往下搜索正则。

2.rewrite ^/proxy/html/(.*)$ /$1 break;

代表重写拦截进来的请求,并且只能对域名后边的除去传递的参数外的字符串起作用,例如www.c.com/proxy/html/api/msg?method=1&para=2重写。只对/proxy/html/api/msg重写。

rewrite后面的参数是一个简单的正则 ^/proxy/html/(.*)$ ,$1代表正则中的第一个(),$2代表第二个()的值,以此类推。

break代表匹配一个之后停止匹配。

3.proxy_pass

既是把请求代理到其他主机,其中 http://www.b.com/ 写法和 http://www.b.com写法的区别如下:

  • 不带/
location /html/
{
  proxy_pass http://b.com:8300; 
}
  • 带/
location /html/ 
{ 
    proxy_pass http://b.com:8300/; 
}

上面两种配置,区别只在于proxy_pass转发的路径后是否带 “/”。

针对情况1,如果访问url = http://server/html/test.jsp, 则被nginx代理后,请求路径会便问http://proxy_pass/html/test.jsp,将test/ 作为根路径,请求test/路径下的资源。

针对情况2,如果访问url = http://server/html/test.jsp, 则被nginx代理后,请求路径会变为 http://proxy_pass/test.jsp,直接访问server的根资源。

修改配置后重启nginx代理就成功了。

完结。

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐