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;
posted @ 2024-05-20 01:49  Edmond辉仔  阅读(32)  评论(0编辑  收藏  举报