跨域失败?摆好姿势就可以

请求跨域问题的产生原因是浏览器的同源策略(Same origin policy),这是有Netscape提出的一个著名的安全策略,对于浏览器而言,它提供了最基本也是最核心的安全功能。它所指的同源是域名、协议、端口都相同。

从wiki百科上截了个例子说明:

从图中可以看出,三者只要任何一个不相同,都会导致Failure.

什么样的姿势跨域,才能成功的跨域?

贴上一份简单的node代码,用以说明服务端的情况:

var http = require(‘http‘);
var fs = require(‘fs‘);

var reg = /(^|\?|&)callback=\w*/;

var MINE_TYPE = {
  ‘css‘: ‘text/css‘,
  ‘html‘: ‘text/html‘,
  ‘js‘: ‘text/javascript‘,
  ‘txt‘:‘text/plain‘
};

http.createServer(function(req,res){
  var _url = req.url;
  var data = _url.indexOf(‘callback‘) >=0 ? req.url.match(reg)[0].substr(10)+‘("XMLHttpRequest is success")‘:‘XMLHttpRequest is success‘;
  res.writeHead(200,{‘Content-Type‘:MINE_TYPE[‘txt‘],‘Access-Control-Allow-Origin‘:‘http://192.168.1.162:9988‘});
  res.end(data);
}).listen(3003,‘192.168.1.162‘);

function _server(port){
  http.createServer(function(req,res){
    var pathname = req.url.substr(1);
    var _ext = pathname.split(‘.‘).pop();
    fs.readFile(pathname,‘utf-8‘,function(err,data){
  	res.writeHead(200,{‘Content-Type‘:MINE_TYPE[_ext]});
  	res.end(data)
    })
  }).listen(port,‘192.168.1.162‘);
}

_server(8899);
_server(9988);

先以普通的姿势来跨一次(本文调试使用端口条件处理同源):

html:

<script type="text/javascript" src="http://cdn.bootcss.com/jquery/3.1.0/jquery.js"></script>
<div class="xmlhttprequset">发送请求</div>

js:

$(‘.xmlhttprequset‘).click(function(){
    $.get(‘http://192.168.1.162:3003‘,function(res){
        console.log(res);
    })
})

我们在浏览器url框输入192.168.1.162:8899/test.html和192.168.1.162:9988/test.html打开两个页面,分别点击页面上的"发送请求",得到以下结果:

XMLHttpRequest cannot load http://192.168.1.162:3003/. No ‘Access-Control-Allow-Origin‘ header is present on the requested resource. Origin ‘http://192.168.1.162:port‘ is therefore not allowed access.

看到这个错误,心里一如既往的爽...那么爽完之后,该解决错误了,最常见的是jsonp处理跨域问题:

js:

$(‘.xmlhttprequset‘).click(function(){
  $.ajax({
    type:‘get‘,
    dataType:‘jsonp‘,
    url:‘http://192.168.1.162:3003‘,
    success:function(res){
      console.log(res);
    }
  });
})

于是,我们就能够在控制台看到打印出来的东西了...

那么我不想使用jsonp呢,能否实现跨域呢?答案是肯定的。而且还可以允许9988端口跨域请求,对8899端口进行"丑拒",就是这么任性~ 办法就是在response头配置"Access-Control-Allow-Origin"参数的值来控制给不给跨,不给跨,给跨,跨... ←_← 这真的只是回声.

于是我们将server.js代码改了改:

http.createServer(function(req,res){
  var _url = req.url;
  var data = _url.indexOf(‘callback‘) >=0 ? req.url.match(reg)[0].substr(10)+‘("success")‘:‘success‘;
  res.writeHead(200,{‘Content-Type‘:MINE_TYPE[‘txt‘],‘Access-Control-Allow-Origin‘:‘http://192.168.1.162:9988‘});
  res.end(data);
}).listen(3003,‘192.168.1.162‘);

然后将jsonp请求的代码换回get请求的代码,重启这个js,切换到浏览器,刷新2个页面,再次点击请求,效果如下:

:9988

success

:8899

XMLHttpRequest cannot load http://192.168.1.162:3003/. The ‘Access-Control-Allow-Origin‘ header has a value ‘http://192.168.1.162:9988‘ that is not equal to the supplied origin. Origin ‘http://192.168.1.162:8899‘ is therefore not allowed access.
// 缺德的3003,拒也就拒了,还喷了句:你又不是9988,我爱的是9988...  强行暴击

那么现在姿势明确了,两个姿势:正常请求+response设置允许域,jsonp。

对比两个方法,我们可以看出,前者讲究的是"你情我愿,两情相悦",我给了你家里的钥匙,你想来就来,离开的时候还能够满心欢喜,我不给别人家里的钥匙,他只能在门外懵逼;后者是"霸王硬上弓",管你同不同意,我背个大锤子来,你不开门是吧,我砸个它,强行进门,出去后我照样能够得到满足...(这很大一部分问题在门的质量,你要是厚厚的大铁门,人家没砸破,就被some叔叔按倒在地上了呢,而这个问题也正是jsonp得以实现的原理)

jsonp 实现原理

jsonp通过添加一个<script>标签,将该标签的src指向请求资源的接口,并且需要在请求中带上一个callback参数,script的src是不受浏览器的同源策略限制的,所以只要后端将数据包装在这个callback的方法里返回即可,于是我们有了这段代码:

server.js:

var data = _url.indexOf(‘callback‘) >=0 ? req.url.match(reg)[0].substr(10)+‘("success")‘:‘success‘;
// 如果包含callback参数,则将参数以callback的值为方法名包装成一个执行函数,否则直接返回数据

jsonp在jQuery中的实现为:

var oldCallbacks = [],
	rjsonp = /(=)\?(?=&|$)|\?\?/;

// 默认jsonp设置
jQuery.ajaxSetup( {
	jsonp: "callback",
	jsonpCallback: function() {
		var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( nonce++ ) );
		this[ callback ] = true;
		return callback;
	}
} );

jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {

	var callbackName, overwritten, responseContainer,
		jsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ?
			"url" :
			typeof s.data === "string" &&
				( s.contentType || "" )
					.indexOf( "application/x-www-form-urlencoded" ) === 0 &&
				rjsonp.test( s.data ) && "data"
		);

	// jsonp 判断
	if ( jsonProp || s.dataTypes[ 0 ] === "jsonp" ) {

		// 生成 callback 名称
		callbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ?
			s.jsonpCallback() :
			s.jsonpCallback;

		// url处理 加入 callback
		if ( jsonProp ) {
			s[ jsonProp ] = s[ jsonProp ].replace( rjsonp, "$1" + callbackName );
		} else if ( s.jsonp !== false ) {
			s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName;
		}

		// 方法执行后取数据
		s.converters[ "script json" ] = function() {
			if ( !responseContainer ) {
				jQuery.error( callbackName + " was not called" );
			}
			return responseContainer[ 0 ];
		};

		// Force json dataType
		s.dataTypes[ 0 ] = "json";

		// 添加callback这个方法
		overwritten = window[ callbackName ];
		window[ callbackName ] = function() {
			responseContainer = arguments;
		};

		// 毁尸灭迹处理
		jqXHR.always( function() {

			// 如果之前不存在这个方法,删除
			if ( overwritten === undefined ) {
				jQuery( window ).removeProp( callbackName );

			// 如果之前就存在这个方法,恢复
			} else {
				window[ callbackName ] = overwritten;
			}

			//
			if ( s[ callbackName ] ) {

				// 确保安全处理,不影响其他项
				s.jsonpCallback = originalSettings.jsonpCallback;

				// 预留着
				oldCallbacks.push( callbackName );
			}

			// 如果是个函数,携带数据调用
			if ( responseContainer && jQuery.isFunction( overwritten ) ) {
				overwritten( responseContainer[ 0 ] );
			}

			responseContainer = overwritten = undefined;
		} );

		// 归为script
		return "script";
	}
} );

// jsonp实现,生成script级之后移除
jQuery.ajaxTransport( "script", function( s ) {

	// 这个函数指明了只处理跨域请求
	if ( s.crossDomain ) {
		var script, callback;
		return {
			send: function( _, complete ) {
				script = jQuery( "<script>" ).prop( {
					charset: s.scriptCharset,
					src: s.url
				} ).on(
					"load error",
					callback = function( evt ) {
						script.remove();
						callback = null;
						if ( evt ) {
							complete( evt.type === "error" ? 404 : 200, evt.type );
						}
					}
				);

				// 使用原生DOM操作避免一些 domManip ajax 问题
				document.head.appendChild( script[ 0 ] );
			},
			abort: function() {
				if ( callback ) {
					callback();
				}
			}
		};
	}
} );

简单过一遍,就是这样了...然后扔出整理后的,不加各种判断的,简单的,只有20行代码的实现,帮助理解上面这么长长的一堆代码:

js:

function getInfo(url, callback, _callback){
    url += url.indexOf(‘?‘)>=0 ? ‘&callback=‘ + callback : ‘?callback=‘+callback;  // url处理
    var overWriteContent;
    var script = document.createElement(‘script‘);
    script.src = url;
    overWritten = window.dataBack;
    window[callback] = function(){
      overWriteContent = arguments[0];
    };                                                          //  生成callback方法,挂在window下
    document.head.appendChild(script);                          //  添加script标签
    script.onload = function(e){
      document.head.removeChild(script);                        //  删除script标签
      if(overWritten === undefined) delete window[callback];    //  销毁window下的callback方法
      if(e.type === ‘load‘){
        _callback(overWriteContent);                            //  带上数据执行回调
      } else{
        console.error(‘error:failed to load the resource‘);
      }
    }
}

学海无涯苦作舟,宝宝心里苦啊...       如有不正之处,感谢指正,同时欢迎大伙伴、小伙伴们交流讨论~

水平较渣,不喜勿喷,谢谢!

时间: 2024-10-14 11:36:31

跨域失败?摆好姿势就可以的相关文章

谷歌浏览器设置跨域失败

在进行前端开发设置谷歌浏览器跨域时遇到了问题,百度上说的方法不管怎么设置就是不能跨域,在死怼中发现了可以这样设置跨域(详见三) 总结三种方法: 一.49版本以前的设置: 在桌面chrome快捷方式的属性中的目标输入框添加   --disable-web-security  与前面字符之间有空格 二.49版本以后的设置: 1.在电脑上新建一个目录,例如:C:\MyChromeDevUserData 2.在属性页面中的目标输入框里加上   --disable-web-security --user-

AJAX - 跨域

一.jsonp 其实我本以为jsonp能够做到利用AJAX任意访问别人的程序代码,但是我发现实际并不是我想象的那样,因为jsonp要改动服务器端的代码.别人的服务器端代码怎么改啊?除非别人愿意,否则你还是不能用AJAX获取别人的数据. Ajax直接请求普通文件存在跨域无权限访问的问题,甭管你是静态页面.动态网页.web服务.WCF,只要是跨域请求,一律不准:其实jsonp的原理就是远程执行js. <script type="text/javascript" src="远

js实现跨域(jsonp, iframe+window.name, iframe+window.domain, iframe+window.postMessage)

一.浏览器同源策略 首先我们需要了解一下浏览器的同源策略,关于同源策略可以仔细看看知乎上的一个解释.传送门 总之:同协议,domain(或ip),同端口视为同一个域,一个域内的脚本仅仅具有本域内的权限,可以理解为本域脚本只能读写本域内的资源,而无法访问其它域的资源.这种安全限制称为同源策略. ( 现代浏览器在安全性和可用性之间选择了一个平衡点.在遵循同源策略的基础上,选择性地为同源策略"开放了后门". 例如img script style等标签,都允许垮域引用资源.) 下表给出了相对 

JS跨域笔记

什么是跨域,跨域是指不同域之间相互访问,只要协议.域名.端口有任何一个不同,都被当作是不同的域. 对于端口和协议的不同,只能通过后台来解决,前台是无能为力的. 受浏览器同源策略的限制,本域的js不能操作其他域的页面对象(比如DOM).同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能, 这里需要明确的一点是:所谓的域跟js的存放服务器没有关系,比如baidu.com的页面加载了google.com的js,那么此js的所在域是baidu.com而不是go

Ajax 跨域 异步 CORS

HTTP access control (CORS) 核心在于使用定制(添加新的header)HTTP header让浏览器和服务器有更多的相互了解,从而决定一个请求或者响应成功还是失败 对于一个简单的请求,没有定制header并且body是text/plain的话,该请求发送的时候会带上一个header叫origin,包含了发送请求的(prototype,domin,和port),服务器就可以根据这个来决定是否响应: Origin: http://www.haha.com 当服务器响应的时候,

CDN惹的祸:记一次使用OSS设置跨域资源共享(CORS)不生效的问题

原文: https://www.lastupdate.net/4669.html 昨天H5组的开发反馈了一个问题,说浏览器收不到跨域的配置,提示:Failed to load https://nnmjstore.xxx.com/records/34e38a6b-0aaf-4bc3-af73-1d9dffcdb6f8_cdhzmj_15: No 'Access-Control-Allow-Origin' header is present on the requested resource. Or

SpringBoot:处理跨域请求

一.跨域背景 1.1 何为跨域? Url的一般格式: 协议 + 域名(子域名 + 主域名) + 端口号 + 资源地址 示例: https://www.dustyblog.cn:8080/say/Hello 是由 https + www + dustyblog.cn + 8080 + say/Hello 组成. 只要协议,子域名,主域名,端口号这四项组成部分中有一项不同,就可以认为是不同的域,不同的域之间互相访问资源,就被称之为跨域. 1.2 一次正常的请求 Controller层代码: @Req

NET Core 3.1 API项目 Cors跨域

1.配置 CORS 跨域 在Startup 的 ConfigureServices中添加 #region CORS //跨域第一种方法,先注入服务,声明策略,然后再下边app中配置开启中间件 services.AddCors(c => { //一般采用这种方法 c.AddPolicy("LimitRequests", policy => { policy .WithOrigins("http://127.0.0.1:1818", "http:/

跨域cors方法(jsonp,document.domain,document.name)及iframe性质

这里说的js跨域是指通过js在不同的域之间进行数据传输或通信,比如用ajax向一个不同的域请求数据,或者通过js获取页面中不同域的框架中(iframe)的数据.只要协议.域名.端口有任何一个不同,都被当作是不同的域. 下表给出了相对 http://store.company.com/dir/page.html 同源检测的结果: 要解决跨域的问题,我们可以使用以下几种方法: 1.通过jsonp跨域[解决ajax跨域] 在js中,我们直接用XMLHttpRequest请求不同域上的数据时,是不可以的