@yangfch3
2016-02-18T15:09:10.000000Z
字数 6796
阅读 3688
JavaScript
只要协议、域名、端口有任何一个不同,都被视为不同的域。
跨域问题的出现是因为浏览器的安全机制导致的,防止 AJAX
形式的跨站恶意脚本攻击,常见出现跨域问题的情景是:
font-face
字体下载AJAX
跨域请求资源例如:aspx
、php
、jsp
等服务器端处理程序,都有相应的组件能访问 URL
获取与处理 response
,我们可以在服务器端写一个小程序用于获取外域的数据,再对数据进行相应的处理作为返回值,当我们请求这个服务器端程序对应的 URL
时,服务器作为中间人代为请求数据然后返回。
例子:梨山宾馆的天气数据获取方法
浏览器端:AJAX 请求服务器端 aspx 程序
var xhr;
var rootURL = "http://www.lishanguesthouse.com.tw/";
if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest();
} else if (window.ActiveXObject()) {
xhr = new ActiveXObject("Microsoft XMLHTTP");
}
var url=rootURL + "GetWeather.ashx";
xhr.onreadystatechange = success;
xhr.open("POST", url, true);
xhr.send();
function success() {
if (xhr.readyState == 4 && xhr.status == 200) {
var str = xhr.responseText;
str = '(' + str + ')';
var obj = eval(str);
var weatherIcon = document.getElementById("weatherIcon");
var liTemp = document.getElementById("temperature");
var iconNum = obj['img1'];
var tempVal = obj['temp1'];
var weatherIconUrl;
if (iconNum.length==1) {
weatherIconUrl = rootURL+"/Images/weatherIcon/0" + iconNum + ".png";
} else {
weatherIconUrl = rootURL+"/Images/weatherIcon/" + iconNum + ".png";
}
weatherIcon.style.backgroundImage = "url('" + weatherIconUrl + "')";
liTemp.innerHTML = tempVal+" ℃";
}
}
服务器端的 aspx
处理程序:请求 HTML
页面,正则处理,抽取数据,生成 JSON 格式数据,返回
<%@ WebHandler Language="C#" Class="GetWeather" %>
using System;
using System.IO;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
public class GetWeather : IHttpHandler {
public void ProcessRequest (HttpContext context) {
//首先获取天气图标的编号,用正则表达式匹配,天气图标数据从中国天气网API获取
string weatherIconUrl = "http://www.weather.com.cn/data/cityinfo/101340401.html";
//根据Url返回对应网页的数据
string weatherIconPage = GetPage(weatherIconUrl);
//获得img1的索引
int weatherIconIndex = weatherIconPage.IndexOf("img1");
//获得img1对应的天气图标的编号
string weatherIconNum = weatherIconPage.Substring(weatherIconIndex + 8, 1);
//以下部分是把网页的table中的气温数据取出来
string tempUrl = "http://www.cwb.gov.tw/V7/observe/24real/Data/C0F86.htm";
string tempPage = GetPage(tempUrl);
int tableStartIndex = tempPage.IndexOf("<table");
int tableEndIndex = tempPage.IndexOf("</table>");
string tempTable = tempPage.Substring(tableStartIndex, tableEndIndex - tableStartIndex + 8);
Regex regExp = new Regex(@"<tr[^>]*>[\s\S]*?<\/tr>");
MatchCollection matches = regExp.Matches(tempTable);
string firstTr = matches[1].Value.ToString();
Regex reg = new Regex(@"<td[^>]*>[\s\S]*?<\/td>");
MatchCollection tdMatches = reg.Matches(firstTr);
string firstTd = tdMatches[0].Value.ToString();
string[] seperators = new string[] { "<", ">" };
string[] components = firstTd.Split(seperators, StringSplitOptions.RemoveEmptyEntries);
string temp1 = components[1];
string weatherJson = @"{'img1':'" + weatherIconNum + "','temp1':'" + temp1 + "'}";
context.Response.Write(weatherJson);
context.Response.End();
}
public string GetPage(string url)
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
Stream responseStream = response.GetResponseStream();
string page = string.Empty;
StreamReader streamReader = new StreamReader(responseStream, Encoding.UTF8);
page = streamReader.ReadToEnd();
return page;
}
public bool IsReusable {
get {
return false;
}
}
}
在同源域名下架设一个代理服务器来转发,JavaScript
负责把请求发送到代理服务器:
'/proxy?url=http://www.sina.com.cn'
代理服务器再把结果返回,这样就遵守了浏览器的同源策略。
用脚趾头也能想到这个方法的缺点:你有这个预算搭建代理服务器么?你有这个能力在别人家的域名下架设代理服务器么?
Flash
是强大的,虽然安全性与性能问题饱受人诟病。
随着 Flash
的式微,已经极少采用。
Javascript
资源JSONP
支持,只支持 GET
请求JSONP
采用在网页中动态插入 script
元素的做法(避开 AJAX
请求,直接请求外部脚本资源),向服务器请求脚本文件。使用 GET
的方式发送请求,并需要在 URL
后面加上参数 ?callback=foo
(具体见各个 API
提供方的调用说明),服务器会将 JSON
数据作为一个对象参数放到回调函数 foo
内,返回脚本。
服务器端需要的相应机制:针对特定 GET
请求的 URL
,请求数据,将数据作为对象参数传入指定的回调函数,构建包含回调函数的脚本,返回脚本,JSONP
请求完成。
JSONP 作用过程:
在脚本程序内准备好返回数据的处理函数(需要预先知道返回的数据的格式)
foo(data){...}
创建
script
标签,向目标域目标 URL 发起请求,一般需要添加查询参数类似?callback=foo
。服务器处理请求,返回数据作为参数的回调函数的脚本文件
浏览器加载好
JSONP
请求的脚本,执行预先准备好的回调函数
实例:服务器端
<script type="text/javascript">
function dosomething(jsondata){
// 处理获得的json数据
}
</script>
<script src="http://example.com/data.php?callback=dosomething"></script>
实例:服务器端处理
<?php
$callback = $_GET['callback'];//得到回调函数名
$data = array('a','b','c');//要返回的数据
echo $callback.'('.json_encode($data).')';//输出
?>
优点:对数据格式无限制 ,服务器可自定义接收多种请求方式:GET
POST
OPTIONS
PUT
(JSONP
只支持 GET
请求),可以得到更详细的错误信息,部署更有针对性的错误处理代码
缺点:需要服务器开放请求许可,决定权仍旧在服务器手里,浏览器需支持 HTML5
,在老式浏览器下表现不佳
CORS
的原理其实很简单,就是自动增加一条 HTTP
头信息的查询,询问服务器端,当前请求的域名是否在许可名单之中,以及可以使用哪些 HTTP
动作。
可见,跨域能否成功,取决于对方服务器是否愿意给你设置一个正确的 Access-Control-Allow-Origin
,决定权始终在对方手中。
简单跨域请求 的请求头与响应头信息(GET
、HEAD
以及 Content-Type
类型
为 application/x-www-form-urlencoded
、multipart/form-data
和 text/plain
的 post
请求):
// AJAX Request Header
Origin: http://www.example.com
// AJAX Response Header
Access-Control-Allow-Origin: http://www.example.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER
Access-Control-Max-Age: 1728000
需要预检的复杂跨域请求 的请求头与响应头信息(PUT
、DELETE
以及 其他类型如 application/json
的 POST
请求)。
在发送AJAX请求之前,浏览器会先发送一个 OPTIONS
请求(称为 preflighted
请求)到这个 URL
上,询问目标服务器是否接受:
OPTIONS /path/to/resource HTTP/1.1
Host: bar.com
Origin: http://my.com
Access-Control-Request-Method: POST
服务器必须响应并明确指出允许的Method:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://my.com
Access-Control-Allow-Methods: POST, GET, PUT, OPTIONS
Access-Control-Max-Age: 86400
浏览器确认服务器响应的 Access-Control-Allow-Methods
头确实包含将要发送的 AJAX
请求的 Method
,才会继续发送 AJAX
,否则,抛出一个错误。
// AJAX Prefilght Request Header
OPTIONS /resources/post-here/ HTTP/1.1
Host: www.google.com
Origin: http://www.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER
// AJAX Response Header for Preflight Request
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://www.example.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER
Access-Control-Max-Age: 1728000
// AJAX Request after Passed Preflight
...
解释:
Access-Control-Allow-Origin
为 *
或域名时说明允许跨域请求Access-Control-Request-Headers
为允许的自定义请求头Access-Control-Max-Age
为预检过期时间,超过此时间需要再次预检CORS
机制默认不发送 cookie
和 HTTP
认证信息,除非在 Ajax
请求中打开 withCredentials
属性。
var request = new XMLHttpRequest();
request.withCredentials = true;
同时,服务器返回 HTTP
头信息时,也必须打开 Access-Control-Allow-Credentials
选项。否则,浏览器会忽略服务器返回的回应。
Access-Control-Allow-Credentials: true
由于整个过程都是浏览器自动后台完成,不用用户参与,所以对于开发者来说,使用 Ajax
跨域请求与同域请求没有区别,代码完全一样。但是,这需要服务器的支持,所以在使用 CORS
之前,要查看一下所请求的网站是否支持。
document.domain
属性可写,但不能设置为当前 URL 中不包含的域,通过设置 domain
属性相同可实现框架间不同子域页面的 JavaScript
通信
// example.com
document.domain = 'example.com';
// b.example.com as a frame in example.com
document.domain = 'example.com';
// 可以使用程序从 example.com 访问
do sth Cross-domain
document.domain
只能往上级域名方向设置,设置为非上级域名会报错,不能设置为顶级域名
// a.example.com
document.domain = 'example.com'; // 'example.com'
document.domian = 'b.example.com'; // 'Uncaught DOMException: ...'
利用的机制是框架间的 window.name
共享机制,利用 window.name
来传输数据。
此方法,也需要两个不同域的页面相互配合。
window.postMessage(message,targetOrigin)
方法是 HTML5
新引进的特性,可以使用它来向其它的 window
对象发送消息,无论这个 window
对象是属于同源或不同源。
IE8+ 已支持。
此方法也需要两个不同域的页面相互配合!