04--JS04--进阶:jsonp、promise、axios、xml.open
JavaScript04:拓展进阶
一. jsonp
# 0 前提:浏览器同源策略 限制跨域
浏览器的同源策略,浏览器的基本安全策略
不允许去不同的url地址获取数据,即域名、端口、协议必须相同
# 请求访问实质是可以的,只是返回的数据被浏览器拦截,不接受而已
# 1 访问服务器的目的
1.加载图片
2.加载css样式
3.加载JavaScript代码
# 上述三种情况,浏览器是不做跨域限制的, cdn加速就基于这个做出来的
4.获取数据 # 会被限制跨域
# 2 jsonp原理
为了解决浏览器跨域问题,jQuery提供了jsonp请求
核心是利用浏览器不限制,加载JS代码的特性
把正常的Ajax请求,伪装成一个JS代码的加载
针对jsonp请求,服务器返回的就不是普通的json数据了
而是返回JS代码(代码是函数调用的形式),浏览器一加载该JS代码,就把数据提取出来了
# 3 jsonp格式
在网页端,如果见到了服务器返回的数据是:
xxxxxxxxxxdjsfkldasjfkldasjklfjadsklfjasdlkj( {json数据} )
并在Preview里面,可以像看到json一样去调试
# 这就是jsonp,依然是Ajax请求
这个数据就是JS代码,就是函数调用的形式,xxxx函数(参数)
# 4 jsonp实现逻辑
在发送请求的时候,带上一个callback字符串(就是json数据外面包裹的字符串),该字符串自动发送给服务器
服务器返回数据的时候,会带上该callback字符串,在抓包中看到的就是这样的效果:
在Python中,还原一下该效果
首先, 在flask中,必须接收到前端发送的callback,然后在返回数据的时候,需要用前端发送的callback字符串,将数据包裹
@app.route("/process_jsonp", methods=["GET"])
def process_jsonp():
# 获取回调字符串
cb = request.args.get("cb")
print(cb)
data = {
"name": "alex",
"age": 18
}
import json
# 用回调字符串将真实要返回的数据包裹起来
# 如果不包裹起來。前端ajax中的success将无法获取到数据
return cb + "("+json.dumps(data)+")"
前端在发送ajax的时候. 需要指定 dataType为jsonp,以及自由配置回调函数的参数名
$(function(){
$.ajax({
url: "/process_jsonp",
method:"get",
// 典型:京东
dataType: "jsonp", // 响应数据的类型
// 执行逻辑是 请求服务上的一个js,然后会自动执行加载该js,将js函数内的东西,丢给success
jsonp:"cb", // 指定jsonp callback字符串的名称 传递给服务器的时候,会自动带上cb=xxxxxx 服务器端接收cb即可
// 不指定,jQuery默认为 callback=xxxx
success: function(data){ // 此时data可以直接收取到数据
console.log(data);
}
});
});
抓包效果:
服务器处理cb时的效果:
抓包中. 看到的服务器返回的数据
success中接收到的数据效果
1.1 处理jsonp
eg: 京东的jsonp
import requests
import json
import re
url = 'https://api.m.jd.com/?h5st=20240521145348311%3Bgm9g9m9y6it5nzg3%3Bb5216%3Btk03w335519b218nHkBCWI02NV1W7LRF3FKRLRD7I8llRhH0fYX97Es3kVJm7iFF8SchDM4eHDab70LjdQ7iIV-8xve8%3Bb528c020f7beadb0937be1457efeea1d6ca8baab5af9263f7f805b025b158af9%3B4.7%3B1716274428311%3BTKmWvNErj_dMOwloFYUUjixDWFuqOmgTfhGHNCl77jXL02dvyK8yjaB77FndZMga3_zGxNW5ToZwemW3lVmophGTixlY5Q3iJ6IEEe43b-8n6qHK-TLZuCbxiwcSFYOeBpH43Gvw7iMa9vhWEVsSYRnpUdmY1YiqD4WR4TSiXGwyHRDEjCCv6YcetHKuamAN-tMvAwScsP4kKNXMwDjK33y2IGgbgsAfa2fV3NG9kLVKbTrkcgaaP5sOLg17qAArrj5Gt46lZt-I04Cz-3MzWk2-CdGmYOeJ1j5Ok_tadIckFg4CY53VYs6qiz_Kv1PhWs5RggE7nDk8PeheJO0dl8zjLad9Prk3hGJ0DQIeqffFGvzEemLTD52YgeDqWQHLXbk3&appid=www-jd-com&body=%7B%22page%22%3A6%2C%22pagesize%22%3A25%2C%22area%22%3A%2222_1930_0_0%22%2C%22source%22%3A%22pc-home%22%7D&clientVersion=1.0.0&client=pc&functionId=pc_home_feed&t=1716274428308&uuid=76161171.170589621565883669607.1705896215.1715672930.1716274404.6&loginType=3&x-api-eid-token=jdd03GGYIH52VQ7AW4SEK6MWU3Y3PWQOO2IPLENOKBZ4FRP7MFRKLBPKKQCN5LBJI2K53VJJPL2376GQNLKBGVPNRRMWN4MAAAAMPOYJVHWIAAAAADBB4UH4EVCAYVIX&callback=jsonpMore2Goods&_=1716274428312'
headers = {
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36",
"Referer":"https://www.jd.com/"
}
response = requests.get(url, headers=headers)
# 得到这样一个类似的字符串: 'haha({"name": "alex", "age": 18})',处理成json很容易的
### 方式一:正则匹配
jsonp_re = re.compile('\((?P<data>.*)\)', re.S)
data = jsonp_re.search(response.text).group('data')
### 方式二:根据url参数callback=jsonpMore2Goods,直接看得到callback字符串的值,直接进行替换处理
data = response.text.replace("jsonpMore2Goods", "", 1).strip("()")
### 方式三:字符串截取
data = response.text.strip("jsonpMore2Goods(").strip(")")
### 方式四:甚至可以直接将 url参数callback,赋值为空,直接得到data
data = response.text.strip("()") # 有些需要去掉括号,有些不用
data = json.loads(data)
print(data)
二. ES6中的promise
https://blog.csdn.net/qq_53669554/article/details/131598219
具体执行过程和推理过程. 请看视频. 这里很饶腾.
### 死亡回调:
本次请求,必须建立在上一次请求成功的基础之上
前端有很多请求,会一层层套娃进去,eg: 验证 验证码 --> 登录 --> 数据
### 1 ES6的promise
是为了解决这些死亡回调的,Promise对象 表示异步操作,最终的完成(或失败)以及其结果值
一个Promise是一个代理,它代表一个在创建promise时,不一定已知的 值
它允许你将处理程序,与 异步操作的最终成功值 或 失败原因 关联起来
使得异步方法可以像同步方法一样返回值
通过 Promise 的then方法,串联的处理程序将被调用
设计一个函数,专门为了发送请求的,保证请求有结果 # promise v.保证、承诺
把原本 一层一层的嵌套 调用,变成了链式的调用
### 2 promise的状态 3个状态
pending : 等待状态
调用promise时,一开始就呈现出等待状态,遇到resolve或者reject之前,都处于这个状态,且可以改变
但如果确定了状态(fulfilled/reject),则结果将永远不变,不能再次修改
fulfilled : 成功状态
在执行了resolve后,promise则会从pedding变成fulfilled
后续会进入.then 的回调函数中,在回调函数的第一个参数函数中可以获取到值
rejected : 失败状态
在执行了reject后 或 异步操作依次时,promise状态会变成rejected
rejected函数传递的参数,可以在.then的第二个参数函数中获取得到,或者是在.catch获取到
但如果是程序上的错误,得通过.catch函数去拿到失败消息,在.then中是获取不了的
### 3 promise的主要方法
# 1 promise的then方法
then方法是异步执行的,是等promise中的异步操作执行完成 再执行的
# 1.1 then方法的参数
该回调函数有两个参数(函数)
一个是用于处理Promise 解决时(resolve) 的回调函数
一个是用于处理Promise 拒绝时(rejected)的回调函数 // 可选
# 1.2 then方法的返回值
无返回时 或者 return的是个普通数据,默认 返回的值是一个promise对象
要进行下一次的嵌套请求,也可以返回新的promise对象
就可以直接.then,在链式第一个promise对象后面
# 2 promise的catch方法
用于注册在Promise对象拒绝时(rejected)的回调函数,同时也可以用来捕获代码异常或者出错
.then中产生reject的状态或者异常能在.catch 或者下一个.then中捕获 需要分场合使用:一般异常用.catch,拒绝状态用.then
一般最好用.catch 在最后去捕获异常,能捕获到上面最先报错的信息
# eg:
var p = new Promise((resolve, reject) => {
// resolve("拒绝");
console.log("----打印:");
throw new Error("抛出错误"); // 若前面已经成功执行resolve,这一句就不能改变promise状态,因为状态已经决定了
});
# catch获取异常
p.catch((error) => {
console.log(error); // "拒绝"
});
# .then的第二个参数 获取异常 不建议这种
p.then(
(res) => {},
(rej) => {
console.log("----打印:", rej); // ----打印: 拒绝
}
);
// eg:常用的promise
function send(url){
return new Promise(function(resolve, reject){ // resolve v.解决 处理 reject v.拒绝、否决
console.log("我要发送ajax了", url)
// 用定时器代表异步操作,一段时间后才有结果值
setTimeout(function(){
console.log("我发送ajax回来了")
// 成功了, 要去处理返回值
resolve("数据", url); // reslove 传递的参数,在后一个的 .then 第一个参数(回调函数) 能直接获取到
}, 3000); // 注意 传递的参数,该回调函数的形参 要对应上
});
}
send("www.baidu.com").then(function(data){
console.log("我要处理数据了啊", data); // data: "数据" url没有获取到,要在该回调函数形参上,定义了,才能获取
return send("www.google.com"); // 返回了新的promise对象(send)
}).then(function(data, url){ // data: "数据" url: "www.google.com"
console.log("我又来处理数据了", data);
}) // 中间可以链式调用很多层...
.catch(function(data){
console.log('前面一层层的请求失败时,都走这里 处理 reject的')
});
三. axios
由于jquery有严重的地狱回调逻辑,再加上jquery的性能逐年跟不上市场节奏,很多前端工程师采用axios来发送ajax
相比jQuery,axios更加灵活,且容易使用
更加美丽的是:axios是用promise做的,所以更加贴合大前端的项目需求
axios 和 jQuery ,发送ajax ,底层都是对原生JS的 XMLHttpRequest对象的封装
<script src="/static/axios.min.js"></script>
<script>
window.onload = function(){
axios.post("/movies", {"page": 10086}).then(function(resp){
console.log(resp.data); // axios请求 返回的是一个包含其他参数的响应对象
})
}
</script>
// axios 默认发送json
axios
.get('/index', {}) // 发送get请求
.post("/movies", {"page": 10086}) // url, json数据
.then(function(resp){ // 回调函数,处理返回的数据
console.log(resp.data);
})
axios为了更加适应大前端,它默认发送和接收的数据就是json
所以在浏览器抓包时:
直接就是request payload,这对于前端工程师而言,爽爆了。
四. axios拦截器(重点)
在前端,能看到有些网站会对每次请求都添加加密信息,或者每次返回数据的时候,都有解密逻辑
那此时,思考:不可能每次请求,都要程序员去手动写加密逻辑. 例如:
window.onload = function(){
// 加密一堆数据
axios.post("/movies", 加密数据的json数据).then(function(resp){
明文 = 解密(resp.data);
console.log(明文);
})
// 加密一堆数据
axios.post("/movies", {"page": 10086}).then(function(resp){
明文 = 解密(resp.data);
console.log(明文);
})
}
这样很麻烦,axios提供了拦截器(你写的请求 和 axios之间,有一个中间件),进行批量处理,一次性处理好这种问题
// 故重点:大多数的JS逆向 数据的加解密入口,都在axios拦截器中!!!
axios.interceptors.request.use(function(config){ // 拦截所有请求 interceptors n.拦截器
console.log("我是拦截器. 我可以对数据进行加密");
console.log(config)
return config;
}, function(error){
return Promise.reject(error);
});
axios.interceptors.response.use(function(response){ // 拦截所有响应
console.log("我是响应回来之后拦截器. 我可以对数据进行解密")
return response.data;
}, function(error){
return Promise.reject(error);
});
这样. 对于业务层的代码而言就简单很多了
window.onload = function(){
// 加密的逻辑拦截器,帮我完成了
axios.post("/movies", {"page": 10086}).then(function(data){
// 解密的逻辑拦截器帮我完成了
console.log(data);
})
// 加密的逻辑拦截器,帮我完成了
axios.post("/movies", {"page": 10086}).then(function(data){
// 解密的逻辑拦截器帮我完成了
console.log(data);
})
}
五 补充
1.重写XMLHttpRequest.open
# 前提
1.axios和jQuery发送ajax请求,底层都是对原生JS的 XMLHttpRequest对象的封装
2.XMLHttpRequest对象 发送请求时,大致是分两步走,分别是
先xhr.open(url, ...),与服务器的url地址,建立一个通道
再xhr.send(数据, ...),真正发送真的Ajax请求
# 情形: eg:http://www.fangdi.com.cn/old_house/old_house.html
发送请求时,一开始url是正常的,然后再接着执行一行代码,url就莫名其妙加上了 一段参数 MmEwMD: 4mB6...
你也找不到 该参数 咋添加上去的
大概率,就是 在XMLHttpRequest对象.open(url, ...)时,动了手脚,重写了open方法
XMLHttpRequest.prototype.open = function(){...}
# JS逆向瑞树常用这种,在url上加上参数 防爬措施
控制台(输入):XMLHttpRequest.prototype.open 一点进去
function _$7g() {
_$1F();
arguments[1] = _$0H(arguments[1]);
return _$pL[_$c5[32]](this, arguments);
}
2.local storage、session storage、cookie
# 浏览器上的 Application ---> Storage中 cookie、Local storage、Session storage三者的区别
### 相同点:
三者都是在开发中 浏览器,临时存储客户端会话信息或者数据的方法
### 不同点:
# 一.存储的时间有效期不同
1.cookie的有效期是可以设置的 # 默认 关闭浏览器后失效
2.sessionStorage的有效期是仅保持在当前页面 # 关闭当前会话页或者浏览器后就会失效
3.localStorage的有效期是在不进行手动删除的情况下是一直有效的 # 一直存在
# 二.存储的大小不同
1.cookie的存储是4kb左右,存储量较小,一般页面最多存储20条左右信息
2.localStorage和sessionStorage的存储容量是5Mb (官方介绍,可能和浏览器有部分差异性)
# 三.与服务端的通信
1.cookie会参与到与服务端的通信中,一般会携带在http请求的头部中,例如一些关键密匙验证等
2.localStorage和sessionStorage是单纯的前端存储,不参与与服务端的通信
### 操作方法
# 存 sessionStorage一样
localStorage.setItem('数据名', '数据值');
# 取
let data = localStorage.getItem('数据名');
# cookie的存 取
document.cookie="username=John Doe";
var x = document.cookie;