跨域解决方案之JSONP,CORS

何为跨域

ajax出现,能够让页面无刷新的同时调用服务,浏览器出于安全方面的考虑,在进行Ajax 访问的时候不允许XMLHttpRequest跨域调用其他页面的对象。这里指的就是不允许XMLHttpRequest跨域调用服务,所谓跨域是指如果服务的URL中协议 hostname、端口号与当前页面的这三个参数中有一个不同的话则视为跨域,如下情况

如上的各种组合还有好多情况,那么我们如何才能绕过浏览器的跨域限制 正常地访问服务呢。

在flash或者silverlight中,只要服务的页面下放入 跨域策略文件就可以允许不同域下flash/silverlight的页面来调用,内容大致如下

<cross-domain-policy> 
<allow-access-from domain="*" to-ports="507" /> 
<allow-access-from domain="*.example.com" to-ports="507,516" /> 
<allow-access-from domain="*.example2.com" to-ports="516-523" /> 
<allow-access-from domain="www.example2.com" to-ports="507,516-523" /> 
<allow-access-from domain="www.example3.com" to-ports="*" /> 
</cross-domain-policy> 

关于Flash 的跨域情况 在这里不说解释,本文重点介绍Javascript的情况。

JSONP

虽然浏览器默认禁止了跨域访问,但并不禁止在页面中引用其他域的JS文件,并可以自由执行引入的JS文件中的function(包括操作cookie、Dom等等)。根据这一点,可以方便地通过创建script节点的方法来实现完全跨域的通信。这种方式叫JSONP解决方案。

利用动态的script来请求服务,服务端返回函数的调用的形式,比如callback(“hello world”);前端如果实现已经定义好了一个callback 的函数既可以获得helloworld;下面开始具体实现。

服务端使用NodeJS+express实现。新建express项目,app.js如下

var express = require('express');
var http = require('http');
var path = require('path');
var app = express();
app.set('port', process.env.PORT || 3000);
app.set('port1', process.env.PORT || 3002);
app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')))

        function add(a,b){
            return a+b;
        } 
app.get('/add', function(req,res){
    var a=parseInt(req.query.a);
    var b=parseInt(req.query.b);
     res.send("addresult("+add(a,b)+")");
});

http.createServer(app).listen(app.get('port'), function(){
  console.log('Express server listening on port ' + app.get('port'));
});
http.createServer(app).listen(app.get('port1'), function(){
  console.log('Express server listening on port ' + app.get('port1'));
});

为了方便演示,这里的跨域具体是指端口号不同,并且在一个文件里面开了两个server,一个3000端口,一个3002端口,这里的服务主要是实现一个加法的功能,读取两个参数,然后进行加法运算,最后返回结果用addresult包起来,比如前端传入过来的参数分别为a=3,b=7,则返回的参数为addresult(7) app.use(express.static(path.join(__dirname, 'public')))是将当前目录下的public 文件件配置为静态目录,接下来在public文件夹下新建crossdomain.html文件,关键代码如下。

var   $=function(id){
               return document.getElementById(id);
   }                
       var a=$('a').value;
       var b=$('b').value;
       var querystring="a="+a+"&b="+b;
       var script=document.createElement('script');
       script.async=true;
       script.src="http://localhost:3000/add?"+querystring;
       document.body.appendChild(script);

以上$为自定义的函数,根据ID 获取元素,然后从文本框中读取值。作为服务的参数传入。接下来要定义一个名字为addresult的函数来接受返回结果:

function addresult (data) {
    // body... 
    $("result").innerHTML=a;
}

启动程序输入 http://localhost:3002/crossdomain.html,这里是在3002上的页面调用的是3000端口上的服务。视为跨域,运行之后在文本框中输入两个值我们发现实现了跨域调用。
以上是实现了一个JSONP的精简版。当然还有些问题要考虑,比如回调函数名字的问题也可以作为参数来传入。第二个问题就是要在服务执行完毕或者请求失败之后 删除动态创建的script标签。

CORS

随着HTML5标准的 不断完善,也推出了一种新的跨域解决方案Cross-Origin Resource Sharing (CORS),现在已经是W3C指定的标准。允许 XMLHttpRequest 进行跨域调用,支持CORS的浏览器有

  • Chrome 3+
  • Firefox 3.5+
  • Opera 12+
  • Safari 4+
  • Internet Explorer 8+ (IE 8下为 XDomainRequest对象)

这里需要服务端和客户端配合 ,要求服务端返回的返回头中必须包含:

Access-Control-Allow-Origin: //发起请求的XMLHttpRequest所在的域
Access-Control-Allow-Methods: //支持的请求的方法
Access-Control-Allow-Credentials:true

同时在请求服务的时候需要将xhr的withCredentials设置为true;如

var xhr = new XMLHttpRequest();
xhr.open("get", "url", true);
xhr.withCredentials = true; //设置这个参数是为了允许cookie发送

这里请求的时候会发两次http请求,第一次是option请求,判断服务端时候只是CORS 以及支持哪些行为(post,get,put等)如果满足条件话则继续进行服务请求,同样这里也以NodeJS为例写个服务端。代码如下

function multiply(a,b){
        return a*b;
}
app.get("/multiply",function(req,res){
    console.log(res.query);
    var a=parseInt(req.query.a);
    var b=parseInt(req.query.b);
    res.set({
              'Access-Control-Allow-Origin': req.headers.origin,
              'Access-Control-Allow-Credentials': true,
              'Access-Control-Allow-Methods':'POST, GET, PUT, DELETE, OPTIONS'
            });
});

客户端的带代码如下

    var a=$('a').value;
    var b=$('b').value;
    var querystring="c="+a+"&d="+b;
    var xhr = new XMLHttpRequest();
        xhr.open("get", "http://localhost:3000/multiply?"+querystring, true);
        xhr.withCredentials = true;//经测试这个貌似没有什么作用
        xhr.onload =function(){
        var data = xhr.responseText;
        $("result").innerHTML="这两个数的乘积为"+data;
    }
xhr.send();  

其他的跨域解决方案。还有比如window.name,iframe,img标签等方式

当然使用代理也是比较给力的,所谓代理就是相当于请求先发到当前页面所在的webserver上,在webserver上的编写一个程序转发请求到另一个本来要打算访问的服务器上,同时也可以将结果返回到浏览器端,这样浏览器就间接地访问了服务。同时代理 又分为 正向和反向两部分。。。。。额,话题扯远了。。

本博文的用到的Demo的源码

HTML5 Geolocation API-获取设备的地理位置

地理位置API介绍

地理位置API能够神奇地定位出你正待在世界的什么地方,并且在你允许的情况下,将你的位置信息分享给你信任的人,关于内部的定位原理,主要通过三种手段:

  1. IP地址
  2. GPS定位
  3. MAC地址
  4. GSM基站网络
  5. 用户定义的地址位置。

地理位置获取流程:

1、用户打开需要获取地理位置的web应用。
2、应用向浏览器请求地理位置,浏览器弹出询问窗口询问用户是否共享地理位置。
3、假设用户允许,浏览器从设别查询相关信息。
4、浏览器将相关信息发送到一个信任的位置服务器,服务器返回具体的地理位置。

监测浏览器支持情况

如果浏览器支持地理位置API话,在全局的 navigator对象上回有一个名字为geolocation的属性,反之,navigator 对象上该属性为undefined 。于是可以编写如下函数

function suport_geolocation(){
    return !!navigator.geolocation
}

或者使用一个专门监测HTML5 特性的库Modernizr,提供的方法来监测浏览器是否支持地理位置API,如下

if(Modernizr.geolocation){

}
else{

}

getCurrentPosition函数

这个函数是通过navigator.geolocation对象来调用的,所以在脚本中需要先取得此对象。这个函数接受一个必选参数和两个可选参数。

void getCurrentPosition(

        in PositionCallback successCallBack,
        in optional PositionErrorCallback errorCallback,
        in optional PositionOptios options
);

. successCallBack:必选参数,为浏览器指明位置数据可用时应调用的函数,即收到实际位置信息并进行处理的地方。
. errorCallback:可选参数,出错处理
. options:可选参数,用来调整HTML5 Geolocation服务的数据收集方式。

示例

navigator.geolocation.getCurrentPosition(updateLocation,handleeLocationError);

这里updateLocation就是接收位置信息并进行重的函数,handleeLocationError是进行错误处理的函数。updateLocation只接受一个参数:位置对象。这个对象包含坐标(coords)和一个获取位置数据时的时间戳。以下是坐标的主要特性:

  • latitude(纬度)
  • longitude(经度)
  • accuracy(准确度)

    function updateLocation(position) {
            var latitude = position.coords.latitude;
            var longitude = position.coords.longitude;
    
            if (!latitude || !longitude) {
                document.getElementById("status").innerHTML = "HTML5 Geolocation is supported in your browser, but location is currently not available.";
                return;
            }
    
            document.getElementById("latitude").innerHTML = latitude;
            document.getElementById("longitude").innerHTML = longitude;
        }
    

    handleeLocationError()函数

HTML5定义了一些错误编号

  • PERMISSION_DENIED(错误编号为1)——用户选择拒绝浏览器获得其位置信息。
  • POSITION_UNAVAILABLE(错误编号为2)——尝试获取用户位置数据失败。
  • TIMEOUT(错误编号为3)——设置了可选的timeout值,获取用户位置超时。

    function handleeLocationError(error)
       {
           switch (error.code) {
               case 0: alert(error.message); break;
               case 1: alert(error.message); break;
               case 2: alert(error.message); break;
               case 3: alert(error.message); break;
           }
       }
    

options:可选的地理定位请求特性

  • enableHighAccuracy:如果启用该参数,则通知浏览器启用HTML5 Geolocation服务的高精度模式,参数的默认值为false.
  • timeout:可选值,单位为ms,告诉浏览器计算当前位置所允许的最长时间。默认值为Infinity,即为无穷大或无限制。
  • maximumAge:这个值表示浏览器重新计算位置的时间间隔。它也是一个以ms为单位的值,默认为零,这意味着浏览器每次请求时必须立即重新计算位置。

watchPosition函数

navigator.geolocation.watchPosition(updateLocation,handleLocationError);

监听位置的变化,如果位置发生变化则调用updateLocation函数,如果程序不再需要接收用户的位置信息,则可以调用navigator.geolocation.clearWatch(watchId),例子如下

var watchId = navigator.geolocation.watchPosition(updateLocation, handleeLocationError);
navigator.geolocation.clearWatch(watchId);

很多情况下,获得的位置在地图上在来展示,可视化地展示出我们所在的位置,参见demo