CORS 实战 专题

本文会代码层面对CORS问题进行剖析

CORS相关相关概念可参考http://www.cnblogs.com/softidea/p/5496719.html

 

ERROR info:

XMLHttpRequest cannot load http://localhost:8080/jsonp/scene1/user/1. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost' is therefore not allowed access.

 

 

JSONP(Javascript Object Notation With Padding)需要客户端和服务器支持

JSONP解释
在解释JSONP之前,我们需要了解下”同源策略“这个概念,这对理解跨域有帮助。
基于安全的原因,浏览器是存在同源策略机制的,同源策略阻止从一个源加载的文档或脚本获取或设置另一个源加载额文档的属性。有点绕,说的简单点就是浏览器限制脚本只能和同协议、同域名、同端口的脚本进行交互。

什么是jsonp格式呢?
API原文:
如果获取的数据文件存放在远程服务器上(域名不同,也就是跨域获取数据),则需要使用jsonp类型。
使用这种类型的话,会创建一个查询字符串参数 callback=? ,这个参数会加在请求的URL后面。
服务器端应当在JSON数据前加上回调函数名,以便完成一个有效的JSONP请求。

意思就是远程服务端需要对返回的数据做下处理,根据客户端提交的callback的参数,返回一个callback(json)的数据,而客户端将会用script的方式处理返回数据,来对json数据做处理。JQuery.getJSON也同样支持jsonp的数据方式调用。

JSONP就是为了解决这一问题的,JSONP是英文JSON with Padding的缩写,是一个非官方的协议。他允许服务端生成script tags返回值客户端,通过JavaScript callback的形式来实现站点访问。JSONP是一种script tag的注入,将server返回的response添加到页面是实现特定功能。
简而言之,JSONP本身不是复杂的东西,就是通过scirpt标签对javascript文档的动态解析绕过了浏览器的同源策略。
JSONP原理及实现
接下来,来实际模拟一个跨域请求的解决方案。后端为spring MVC架构的,前端则通过Ajax进行跨域访问。
1、首先客户端需要注册一个callback(服务端通过该callback(jsonp)可以得到js函数名(jsonpCallback)),然后以JavaScript语
法的方式,生成一个function
2、接下来,将JSON数据直接以入参的方式,放置到function中,这样就生成了一段js语法文档,返回给客户端。
3、最后客户端浏览器动态的解析script标签,并执行返回的JavaScript语法文档片段,此时数据作为参数传入到了预先定义好的
回调函数里(动态执行回调函数)。
这种动态解析js文档和eval函数是类似的。

 

客户端的支持:

This is how I'm using JSONP with Spring MVC, just modify it according to your needs:
on Server Side:
下例中服务器端处理参数名为“callback"的客户端数据,此数据在客户端的JS代码必须存在,可以指定参数值,也不可以不指定,如果不指定,会由浏览器自动生成
类似下面这种格式:

http://localhost:8080/jsonp/scene1/user/1?callback=jQuery3210430263573155826_1502264239363&time=1502265440209&_=1502264239364
@RequestMapping(value="/notes/callback.json", method=RequestMethod.GET)
public void jsonpCallback(@RequestParam("callback") String callback, HttpServletResponse response) {
   response.setContentType("text/javascript; charset=UTF-8");
   PrintWriter out = response.getWriter();
   out.print(callback + "(" + jsonDataString + ")");
}

On client side:

<script src="http://code.jquery.com/jquery-1.6.2.min.js"></script>
<script type="text/javascript">

function yourfunction() {
    jQuery.getJSON("http://localhost:8080/notes/callback.json?callback=?",
        function(data) {
            alert(data.someParam);
        });
}

</script>

 

指定callback参数对应的值:

$.ajax({
    url: "http://tonghuashuo.github.io/test/jsonp.txt",
    dataType: 'jsonp',
    jsonp: "callback",
    jsonpCallback: "dosomething"
})
.done(function(res) {
    console.log("success");
    console.log(res);
})
.fail(function(res) {
    console.log("error");
    console.log(res);
});

后端关键代码(以PHP为例)

$result   = "{'data':'JSONP Works'}"; // 这里省略了数据库查询等操作,直接给出返回值
$callback = $_GET['callback'];        // 最好加上判空和默认值,以防拿不到参数
echo $callback."(".$result.")";

// 返回的结果
// dosomething({"data":"JSONP Works"});

将上述代码放到你本地localhost中,尝试运行一下,顺利的话应该会在浏览器的控制台中得到以下的内容:

> success
> Object {data: "JSONP Works"}

实际发送出来的完整请求长这样:http://tonghuashuo.github.io/test/jsonp.txt?callback=dosomething&_=1471419449018。,后面的随机字符串是jQuery加上的。

区别于常规的 AJAX 请求,这里真正需要关心的参数有以下 3 个

  • dataType: 'jsonp',用于表示这是一个 JSONP 请求。
  • jsonp: 'callback',用于告知服务器根据这个参数获取回调函数的名称,通常约定就叫 callback。
  • jsonpCallback: 'dosomething',指定我们自己的回调函数名称,也是前面callback参数的值。远程服务接受callback参数的值就不再是自动生成的回调名

其中jsonpCallback参数是可以省略的,jQuery 会自动生成一个随机字符串作为函数名,推荐这么做,以减少不必要的命名工作,同时排除潜在的安全隐患。这里由于Github Page没有后台服务,我们只能指定一个名字。注意:省略jsonpCallback的同时,jsonp参数必须指明,不能为false。

jQuery 还支持将jsonp设置为false以避免callback参数出现在请求 URL 中,但这需要前后端配合,前端必须要指定jsonpCallback的值为一个函数名,后端由于无法从请求中获取回调函数的名称,因此也必须固定使用同名的字符串进行拼接。

Spring 4 MVC + JSONP Example with REST, @ResponseBody and ResponseEntity
客户端代码示例:

$("#but1").click(function(){
     $.ajax({
        url:'http://127.0.0.1:8080/DevInfoWeb/get',
        type: "get",
        async: false,
        dataType: "jsonp",
        jsonp: "callbackparam", //服务端用于接收callback调用的function名的参数
        jsonpCallback: "success_jsonpCallback", //callback的function名称,服务端会把名称和data一起传递回来
        success: function(json) {
         alert(json);
        },
        error: function(){alert('Error');}
});
});  

<script language="JavaScript">
$(document).ready(function() {
    $.ajax({
        url:'http://localhost:8080/test/getPopularity',
        dataType:'jsonp',
        success:function(data){
            console.log("返回Json:")
            console.log(data)
        }
    });
});
</script>

http://localhost:8080/test/getPopularity?callback=jsonp

 

 

In this page we will provide Spring 4 MVC and JSONP example with REST, @ResponseBody and ResponseEntity. JSONP is JSON with padding. It supports JavaScript code running in web browser to request data from a server in different domain which is normally prohibited because of same-origin policy. According to this policy a web browser can allow the script code of one web browser to access data from another web browser within the same domain. Same-origin policy is because of web application security model. But this policy is not forced to <script> tag by web browser. From here the role of JSONP comes into picture. JSONP allows to access data from different domain using <script> tag by web browser. If we have a URL as
http://localhost:8080/concretepage-1/book1?callback=myfunction
and if it throws the JSONP response as

myfunction({"bookName":"Godan","writer":"Premchand"});

then in another domain, we can use it as 

<script src=" http://localhost:8080/concretepage-1/book1?callback=myfunction " type="application/javascript"> </script>

In our client code to access JSONP, there should already be a function named myfunction() defined in script code.
To throw the JSONP response using spring, it provides AbstractJsonpResponseBodyAdvice class which is implemented by a class annotated with @ControllerAdvice.

Spring 4 Support for JSONP with AbstractJsonpResponseBodyAdvice
Find the our class which will support JSONP response.
JsonpAdvice.java

@ControllerAdvice
public class JsonpAdvice extends AbstractJsonpResponseBodyAdvice {
    public JsonpAdvice() {
        super("callback");
    }
} 

In the constructor, we need to call super method passing a key. This key will be used in query string of URL while requesting JSONP data. The above method facilitates REST web service to respond JSONP data and also controller method which respond using @ResponseBody and ResponseEntity.
JSONP with Spring REST
Find the bean being used in the example to generate JSON.
Book.java

public class Book {
        private String bookName;
        private String writer;
    public String getBookName() {
        return bookName;
    }
    public void setBookName(String bookName) {
        this.bookName = bookName;
    }
    public String getWriter() {
        return writer;
    }
    public void setWriter(String writer) {
        this.writer = writer;
    }
} 

We are creating a web service which will respond JSONP.
BookService.java

@RestController
class BookService {
    @RequestMapping(value= "/book1", produces = MediaType.APPLICATION_JSON_VALUE)
    Book bookInfo1() {
        Book book = new Book();
        book.setBookName("Godan");
        book.setWriter("Premchand");
        return book;
    }
} 

When we access the URL http://localhost:8080/concretepage-1/book1?callback=functionCall, it will throw the response as

functionCall({"bookName":"Godan","writer":"Premchand"});

JSONP with @ResponseBody and ResponseEntity
Now find a controller in which we have created methods that will return @ResponseBody and ResponseEntity.

@Controller
class BookController {
    @RequestMapping(value ="/book2", produces =MediaType.APPLICATION_JSON_VALUE )
    @ResponseBody
    Book bookInfo2() {
        Book book = new Book();
        book.setBookName("Ramcharitmanas");
        book.setWriter("TulasiDas");
        return book;
    }
    @RequestMapping(value ="/book3", produces =MediaType.APPLICATION_JSON_VALUE )
    public ResponseEntity<Book> bookInfo3() {
        Book book = new Book();
        book.setBookName("Ramayan");
        book.setWriter("Valmiki");
        return ResponseEntity.accepted().body(book);
    }
} 

When we access the URL http://localhost:8080/concretepage-1/book2?callback=functionCall, we will get the output in browser as

functionCall({"bookName":"Ramcharitmanas","writer":"TulasiDas"});

And for the URL http://localhost:8080/concretepage-1/book3?callback=functionCall , the output will be

functionCall({"bookName":"Ramayan","writer":"Valmiki"});

JSONP Client Code
Now we will write client code which can be used in any other domain.
jsonptest.html

<html>
 <head>
   <script>
     function functionCall(data) {
        console.log(data.bookName);
        console.log(data.writer);
        console.log('-----------');
     }
   </script>
  </head>
  <body>
        <!-- Using REST URL-->
        <script src="http://localhost:8080/concretepage-1/book1?callback=functionCall" type="application/javascript"> </script>

    <!--Using @ResponseBody -->
    <script src="http://localhost:8080/concretepage-1/book2?callback=functionCall" type="application/javascript"> </script>

    <!--Using ResponseEntity -->
    <script src="http://localhost:8080/concretepage-1/book3?callback=functionCall" type="application/javascript"> </script>
  </body>
</html> 

Run this page and check the output in console.

在Firefox,chrome,opera,safari,ie9,ie8等高级浏览器直接可以用JSON对象的stringify()和parse()方法。
JSON.stringify(obj)将JSON转为字符串。JSON.parse(string)将字符串转为JSON格式;
上面的转换可以这么写:
var a={"name":"tom","sex":"男","age":"24"};
var b='{"name":"Mike","sex":"女","age":"29"}';
var aToStr=JSON.stringify(a);
var bToObj=JSON.parse(b);
alert(typeof(aToStr)); //string
alert(typeof(bToObj));//object

代码示例:

https://github.com/helloworldtang/cors-demo

参考:
http://www.cnblogs.com/softidea/p/3907113.html

http://blog.csdn.net/u010537398/article/details/52012548

https://en.wikipedia.org/wiki/JSONP

https://stackoverflow.com/questions/10323625/how-to-support-jsonp-with-spring-mvc-and-multiple-response-types

https://tonghuashuo.github.io/blog/jsonp.html

http://www.concretepage.com/spring-4/spring-4-mvc-jsonp-example-with-rest-responsebody-responseentity

http://blog.csdn.net/caiwenfeng_for_23/article/details/45300739

http://www.jianshu.com/p/983642ad125a

http://www.codesnoopy.com/2016/07/09/%E7%94%A8Spring-MVC-4%E5%AE%9E%E7%8E%B0jsonp%E8%B7%A8%E5%9F%9F%E8%B0%83%E7%94%A8/

https://my.oschina.net/u/130771/blog/52912

 

时间: 2024-09-11 20:43:59

CORS 实战 专题的相关文章

本人博客园 重新规划和分类(有待改进)

博客分类:   待分类博文 C基础 C之Bug(已解和未解以后在标题处标注即可) C++基础 C++之Bug(已解和未解以后在标题处标注即可) Objective-C基础 Objective-C之Bug(已解和未解以后在标题处标注即可) Swift基础 Swift之Bug(已解和未解以后在标题处标注即可) iOS开发之     注意:以后文末要注明(Objective-C或者Swift)     理论知识-在开发中可能很少会用到但是须知(比如MRC和ARC)     基础-UI基础(这个需要大致

当下流行架构中的一些技术思考

标签(空格分隔): ArchSummit2016 [toc] 时间:2016.12.2-2016.12.3 地点:北京ArchSummit会场 人物:行业内的讲师&参会人员 总体趋势 ArchSummit全球架构师峰会,在初冬的北京举行.我们一伙人奔着雾霾和技术前沿的理想追逐而去.我先聊聊这次的一个整体情况.两天行程,安排了密密麻麻的专题和演讲,但是总结来看,基本上是云计算.大数据.微服务.和高可用架构实践.相比2015,机器学习和大数据应用的内容开始变多. 具体包含的topic范围包括:(我认

如何把胸罩卖给男人?

摘要: 一个吸引眼球的营销故事有5个营销专业的应届大学生应聘到广东一家女性内衣公司,或许受了网络上流传的可以把梳子卖给和尚吗这个创意的启发,该公司对正式上岗前的业务员有这样 一个吸引眼球的营销故事有5个营销专业的应届大学生应聘到广东一家女性内衣公司,或许受了网络上流传的"可以把梳子卖给和尚吗"这个创意的启发,该公司对正式上岗前的业务员有这样一项测试:把公司的某品牌胸罩推销给在校的男生,并在规定的时间内完成一定的销售任务. 第一个业务员,悄悄走访了几个熟悉的小师弟,都遭到了拒绝.后来灵机

老树新花 说DOS ①

http://www.aliyun.com/zixun/aggregation/13559.html">Windows 95发布之前,PC机的操作系统是DOS的天下.时至今日,Windows已升级至Windows 2003,DOS这个辉煌一时的操作系统早已被大多 数人遗忘.但实际上,DOS在 系统维护及 安装过程中仍然有着特殊的作用,DOS命令也在随着Windows的升级而加强,比如Windows 2003中新增的命令提示符工具就达60多个!这些新增加的DOS命令在实现特定功能时, 往往比

CORS 专题

CORS(跨域资源共享,Cross-Origin Resource Sharing)CORS其实出现时间不短了,它在维基百科上的定义是:跨域资源共享(CORS )是一种网络浏览器的技术规范,它为Web服务器定义了一种方式,允许网页从不同的域访问其资源.而这种访问是被同源策略所禁止的.CORS系统定义了一种浏览器和服务器交互的方式来确定是否允许跨域请求. 它是一个妥协,有更大的灵活性,但比起简单地允许所有这些的要求来说更加安全. 比如,站点 http://domain-a.com 的某 HTML

我的网站专题策划经验实战总结

中介交易 SEO诊断 淘宝客 云主机 技术大厅 10年运营比特邦时,曾经主打专题牌,并收效甚好,当时基于专题策划经验写了网站的专题策划方法与技巧(实战经验总结)一文,受到网编新人宠爱,时隔3年,虽然很少再策划专题,但是内容策划一直是我最爱,故今天再详细啰嗦一下专题策划经验!标题之所以标注"曾经沧海",还是因为忘不掉运营比特邦时候的"荡气回肠"的感觉.虽然ta已进入互联网博物馆!闲话少言,走入正题! 内容型网站(如门户和主题社区),电商网站,企业站大都需要策划专题,或

XML卷之实战锦囊(2)——动态查询

查询功能是我们在网站上见过的最普遍也是最常用的一个功能模块了.以往的信息查询都是连接到数据库的,每一次点击都必须要后台数据库的支持.然而很多情况下用户往往只针对某一部分的数据进行操作,这样不但服务器的负担加重,而且严重的影响用户浏览的速度. 针对这种情况我们需要将用户需要的某一部分数据以XML的方式传递到客户端,用户对这些数据可以很方便的进行操作.既方便了用户,又减轻了服务器数据库的负担.何乐而不为呢!而且这项功能可以通用到其他众多模块,因此添加了这个动态查询功能. 材料: XML卷之动态查询

XML卷之实战锦囊(1)——动态排序

排序功能让我们页面上的数据显的更人性化,是我们在网站上见过的很普遍的一个功能效果了.以往的自动排序都是用大量的脚本代码来完成的,对一般的爱好者来说这是件困难的事情.然而用XML来处理的话就简单多了.让自己的页面更加绚丽,哈哈,您是不是也心动了呢! 材料: XML卷之动态排序 有2个文件:paixu.xml 和 paixu.xsl 作用: 在不刷新页面的情况下更据用户自己的需要对数据重新进行排序显示,有效的提高数据互动功能,让自己的页面更加绚丽多彩. 代码: 以下为引用的内容: paixu.xml

XML卷之实战锦囊(3):动态分页

xml|动态|分页 动机: 为了方便用户查看大批量数据,我们会用到动态分页,因此分页功能是我们在网站上见过的最普遍也是最常用的一个功能模块了.而以往的信息分页都是连接到数据库的,每一次点击都必须要后台数据库的支持.这样不但服务器的负担加重,而且严重的影响用户浏览的速度.试想,如果把分页的功能放到客户端,那会产生什么样的效果呢?呵呵,看看下面的设计吧! . 材料: XML卷之动态分页有2个文件:pages.xml 和 pages.xsl 作用: 把分页的功能放到客户端.在不刷新页面的情况下对数据进