之前模仿百度首页,在获取天气信息这一块,比较好奇,就准备尝试着做一下。这一做牵扯出了很多的知识点,于是我在这里记录下来,方便自己,方便别人,也帮心知天气宣传一下,他们的服务做得真的很好http://www.thinkpage.cn/。
打开上面心知数据的官网,有一个V3版本的开发文档。http://www.thinkpage.cn/doc
首先会有我的信息,这部分包含我的API密钥,我的用户ID。这部分内容要等用户注册登录之后,才能看到具体的信息。
接着,往下一翻发现有一句如何使用JSONP方式调用。
工作中Json一直在用,这里看到JSONP,好家伙听过没玩过,一下子好奇心来了,反正没啥事,就研究呗。
1、jsonp的原理
先说概念:jsonp是动态跨域获取数据的一种方式。
接着我们来说说jsonp的实现。
网上对jsonp的解释有很多,我单纯的将其原理列出,如果诸位看官看的不明白,可以点击文章结尾处的参考文档,或者点击本小节结束时的文档,博采众长,进一步学习这部分知识。
进入正题。
1.1、我们都知道在HTML页面中,由于跨域规则的限制,我们通过ajax无法请求存在其他域中的普通文件。
1.2、但是我们又发现,web页面上引入js文件是不受跨域安全机制的影响的。不仅仅如此,所有有src属性的标签,<script>、<img>、<iframe>都可以突破域的限制。
1.3、恰好此时json数据作为一种格式简单,便于理解的数据交换结构,被大家广泛的使用。关键是json被原生的js支持。
下面补充json对象与json字符串互相转换的方法。
字符串转json对象: var obj = str.parseJSON(); //由JSON字符串转换为JSON对象 或者 var obj = JSON.parse(str); //由JSON字符串转换为JSON对象 json对象转字符串: var last=obj.toJSONString(); //将JSON对象转化为JSON字符 或者 var last=JSON.stringify(obj); //将JSON对象转化为JSON字符
1.4、因此,如果我们可以联想到,在前端直接获取不同域的服务器上的数据,可以通过向目标服务器直接请求一个动态js文件回来(该文件一般以json方式为后缀),之所以是动态的,是因为服务器可以将数据填充到动态文件中。
1.5、发送请求时,使用<script>标签引入的文件是通过get方式获取的,而get请求的后面就可以带参数,这里的参数就是回调函数的名字。
1.6、服务器接受到请求之后,分离出回调函数,再将前端所需要的数据封装成一个对象,以参数的形式给到回调函数中,接着请求返回到前端。
1.7、返回的动态文件因为在<script>标签中,所以会被立即执行,我们在本地写好的回调函数中就可以对返回的数据做处理了。
注意:以下是重点。
我们在html文件中,已经写好了函数的声明,将函数名跟在跨域获取数据的脚本后面作为参数,然后准被发送到服务器。
发送到服务器是通过构造一个script标签来完成的。例子如下:
// 得到航班信息查询结果后的回调函数 var flightHandler = function(data){ alert('你查询的航班结果是:票价 ' + data.price + ' 元,' + '余票 ' + data.tickets + ' 张。'); }; // 提供jsonp服务的URL地址(不管是什么类型的地址,最终生成的返回值都是一段javascript代码) var url = "http://flightQuery.com/jsonp/flightResult.aspx?code=CA1998&callback=flightHandler"; // 创建script标签,设置其属性 var script = document.createElement('script'); script.setAttribute('src',url); // 把script标签加入head,此时调用开始 document.getElementsByTagName('head')[0].appendChild(script);
当请求到达服务器后,会根据参数,制作一个数据出来。再将这个数据以参数的形式,填充到回调函数的参数中。然后服务器将动态生成的js文件返回。
返回后,回调函数会立即执行。此时我们就达到了跨域从后台获取数据的效果。
2、ajax异步使用jsonp
此外,我们还应该知道的是,jQuery也是支持jsonp的,它是通过$.ajax的方式支持jsonp的。
$.ajax({ type: "get",async: false,url: "http://flightQuery.com/jsonp/flightResult.aspx?code=CA1998",dataType: "jsonp",cache: true,jsonp: "callback",//传递给请求处理程序或页面的,用以获得jsonp回调函数名的参数名(一般默认为:callback) jsonpCallback:"flightHandler",//自定义的jsonp回调函数名称,默认为jQuery自动生成的随机函数名,也可以写"?",jQuery会自动为你处理数据 success: function(json){ alert('您查询到航班信息:票价: ' + json.price + ' 元,余票: ' + json.tickets + ' 张。'); },error: function(){ alert('fail'); } });
这里要注意以下几点:
1、jsonp只能以get方式提交。
因为它本身就相当于一个动态添加的script标签,而这种方式发出的请求就是以get方式发出去。所以请求类型为get。
2、cache : true
jQuery会在请求后默认添加一个时间戳,当有了这个时间戳之后,就不会使用缓存。不过在获取心知天气API时,cache这个值可以不设置。
3、请求频率
请求频率要和免费用户的每分钟的次数相匹配,不然会被服务器拒绝服务。
更多内容:请参考http://www.cnblogs.com/dowinning/archive/2012/04/19/json-jsonp-jquery.html
这篇文章有例子,讲的也很详细。
知道了跨域的原理,那么我们接着回到心知数据V3版 API这边,来考虑获取数据。
2、通过加密的方式获取数据
3.1、介绍node使用cryptoJS方式加密数据
API中提到采用node.js的crypto模块加密。
我按照文中的例子做了一个demo,得出的结果与文中结果一致,下面是代码。
var crypto = require("crypto"); var str = "ts=1443079775&ttl=30&uid=U123456789"; var key = "secret"; var result = crypto.createHmac('sha1',key); var final = result.update(str).digest('base64'); console.log(final);
当然也有不用node的测试方法。各位看官可以到https://1024tools.com/hmac 网站中验证上面的数据。
这里补充些关于node.js加密的知识点:
首先,如果要使用上述的方法,本地至少有node.js的环境。我测试时用的是6.9.1的版本。
使用require('crypto')调用加密模块
加密模块需要底层系统提供OpenSSL的支持。它提供了一种安全凭证的封装方式,可以用于HTTPS安全网络以及普通HTTP连接。
crypto.createHash(algorithm)
创建并返回一个hash对象,它是一个指定算法的加密hash,用于生成hash摘要。参数algorithm可选择系统上安装的OpenSSL版本所支持的算法。例如:'sha1','md5','sha256','sha512'等。在近期发行的版本中,openssl list-message-digest-algorithms会显示这些可用的摘要算法。
crypto.createHmac(algorithm,key)
创建并返回一个hmac对象,它是一个指定算法和密钥的加密hmac。参数algorithm可选择OpenSSL支持的算法 - 参见上文的createHash。参数key为hmac所使用的密钥。
hash.digest(encoding='binary') hmac.digest(encoding='binary')
计算所有传入数据的hash摘要。参数encoding(编码方式)可以为'hex','binary' 或者'base64'。
知道了这一步,我们知道加密的流程是什么样了。不知大家是否注意到例子中有一个ts。
这个ts其实就是Unix 的时间戳,是当前时间的非格式化表达。
显然例子中的ts要被替换为当前的时间,这里获取本地时间的方法为:
var myDate = new Date(); var ts = myDate.getTime(); //获取当前时间(从1970.1.1开始的毫秒数)参考文章: http://www.cnblogs.com/carekee/articles/1678041.html
3.2、介绍在web前端使用CryptoJS加密数据
接下来,问题又来了,那么我怎么将加密方式移植到web页面中去呢?
其实呢网上有一个crypto的加密库,它不仅可以在node中使用,也可以在浏览器中以script标签引用的方式使用。下载地址:https://github.com/brix/crypto-js
在这一关,我还是研究了一段时间的。网上有一些方法介绍web端createHmac加密,但是其中<script>引入的用于加密的js文件早已经失效了。所以下面的方法都是我自己摸索出来的。
下面直接写成果,免得大家走弯路。
1、去git上下载crypto-js这个库
下载好后,在crypto-js-develop\src文件夹,我们可以看到所有的与加密相关的文件。
2、在页面中引入cryptoJS的库。
虽然js文件很多,但是我们需要的js文件却并不多。这里你只需要选择与自己加密方式相关的js文件就即可。
<script src="js/core.js"></script> <script src="js/cipher-core.js"></script> <script src="js/enc-base64.js"></script> <script src="js/hmac.js"></script> <script src="js/sha1.js"></script>
可以看到除了两个核心模块,其他的就是我们在加密过程中需要模块。那么接下来,我们就可以在web页面中执行hmac的加密了。str是被加密的字符串,key是密钥。
var result = CryptoJS.HmacSHA1(str,key); var signature = result.toString(CryptoJS.enc.Base64); signature = encodeURI(signature);
4、借助ajax的jsonp方式获取心知天气数据的过程
首先,组装加密后的请求:
var ts = new Date().getTime(); var str = "ts="+ts+"&ttl=30&uid=U75028BE2C"; //uid需要换成自己账户中的uid var result = CryptoJS.HmacSHA1(str,key); //key需要换成自己账户中的key var signature = result.toString(CryptoJS.enc.Base64); signature = encodeURI(signature);
接着,组装url:
var url = "https://api.thinkpage.cn/v3/weather/now.json?location="+ city +"&"+str+"&sig="+signature;
然后,ajax发出请求:
$.ajax({ type: "get",cache: false,url: url,//传递给请求处理程序或页面的,用以获得jsonp回调函数名的参数名(一般默认为:callback) jsonpCallback:"showWeather",//自定义的jsonp回调函数名称,默认为jQuery自动生成的随机函数名,也可以写"?",jQuery会自动为你处理数据 success: function(json){ console.log(json); },error: function(){ alert('fail'); } });
至此,在ajax的回调函数中添加业务逻辑代码,整个jsonp跨域获取数据就ok了,接下来本地测试。心知天气官方需要有域名,并且域名绑定账号才能获取数据,否则连接将一定会被禁掉。
我估计是为了盈利的考虑,一个账号绑定一个域名吧。当然这个地方是我整个文章中唯一不能确认的地方,为什么要这么做。如果各位知道的话,请告诉我。不过需要域名,我就给你一个好了。我们不需要自己搭建一个服务器,或者买一个域名,只需要在本地打一个wamp环境,在host文件中将127.0.0.1映射到例如c.test123.com。然后再将这个域名绑定到你注册心知天气的账号上。
http://www.thinkpage.cn/doc
http://www.cnblogs.com/dowinning/archive/2012/04/19/json-jsonp-jquery.html
http://www.jb51.cc/article/p-rvygneng-bos.htmlhttp://www.jb51.cc/article/p-grchpdlt-mw.htmlhttp://www.cnblogs.com/carekee/articles/1678041.html
http://www.cnblogs.com/worfdream/articles/1956449.html
这编辑器好难用,看来我以后要用MarkDown写博客了。