1Ajax定义
Ajax:(Asynchronous JavaScript and XML)用JavaScript执行异步网络请求。
1.1Ajax技术带来的改变
- 一次HTTP请求对应一个页面
- 异步数据刷新
Ajax技术包含的内容:
- 使用CSS和XHTML来表示。
- 使用DOM模型来交互和动态显示。
- 使用XMLHttpRequest来和服务器进行异步通信。
- 使用javascript来绑定和调用。
1.2利用XMLHttpRequest发送请求
(function () {
function success(text) {
alert('success' + text);
}
function fail(code) {
alert('fail' + code);
}
var request = new XMLHttpRequest(); // 新建XMLHttpRequest对象
request.onreadystatechange = function () { // 状态发生变化时,函数被回调
if (request.readyState === 4) { // 成功完成
// 判断响应结果:
if (request.status === 200) {
// 成功,通过responseText拿到响应的文本:
success(request.responseText);
} else {
// 失败,根据响应码判断失败原因:
fail(request.status);
}
} else {
// HTTP请求还在继续...
}
}
// 发送请求
request.open('GET', '/api/categories');
// request.open('POST', '/api/categories');
request.send();
alert('请求已发送,请等待响应...');
})();
从中我们可以看出XMLHttpRequest对象主要包括以下属性:
- onreadystatechange 回调函数
- readyState 网络发起和结束的状态
0: 请求未初始化
1: 服务器连接已建立
2: 请求已接收
3: 请求处理中
4: 请求已完成,且响应已就绪 - status 网络响应的状态
200: "OK"
404: 未找到页面 - open 规定请求的类型、URL 以及是否异步处理请求
- send 将请求发送到服务器,post可支持入参
2AJAX跨域请求的问题
因为浏览器的同源策略导致的,默认情况下,JavaScript在发送AJAX请求时,URL的域名必须和当前页面完全一致。
一致的含义:
- 域名 www.example.com和example.com不同
- 协议 http和https不同
- 端口号 :80和:8080不同
那么问题就来了,不是所有的资源或数据都会缓存在当前同源服务器下面,即使通过代理或缓存服务器的策略。
如何解决跨域访问的问题?
由于技术实现和应用场景原因,存在许多js跨域请求的解决方案,下面说我应用过的几种解决方案。
2.1JSONP
特点:
- 只能用GET请求
- 返回JavaScript
原理:利用了浏览器允许跨域引用JavaScript资源(场景,例如引用CDN下的第三方js依赖)。
//动态添加标签
var js = document.createElement('script'),
head = document.getElementsByTagName('head')[0];
js.src = 'http://example.com/abc.js';
head.appendChild(js);
执行以上方法后,浏览器拉取http://example.com/abc.js
源码:
refreshPrice({"0000001":{"code": "0000001", ... });
在当前JS中增加回调函数
function refreshPrice(data) {
var p = document.getElementById('test-jsonp');
p.innerHTML = '当前价格:' +
data['0000001'].name +': ' +
data['0000001'].price + ';' +
data['1399001'].name + ': ' +
data['1399001'].price;
}
这样,我们就通过JSONP完成了跨域请求,得到了返回。
一般,我们采用JQuery提供的方式可以较为方便的实现jsonp。前提是,对应的源有相应的后端实现,即提供GET方式的请求和规范的JavaScript返回。
值得注意的是,通过get请求的目标源文件采用的是fetch方式,而不是XMLHttpRequest对象,所以在浏览器network中XHR里找不到该请求。JSONP真正意义上,不属于ajax的技术范畴。
2.2CORS
CORS:全称Cross-Origin Resource Sharing,是HTML5规范定义的如何跨域访问资源(前提是,浏览器支持HTML5)。
1简单请求
当ajax通过浏览器发出跨域请求,浏览器收到响应后检查header中Access-Control-Allow-Origin是否包含当前域(origin)。如果有,则跨域成功。
2预检测请求
PUT、DELETE以及其他类型如application/json的POST请求,将会进入以下流程。
先发出OPTIONS请求询问服务端支持的请求方式;
确认成功后,进行数据的请求。
请参考https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS
2.3JS-Native客户端代理
原理很简单,因为浏览器有同源限制,然后客户端、后端并没有,我们是可以在代理对象(客户端或后端集群)上收到远程服务的响应。
因此,通过将前端的请求头和请求body等参数封装后,提交给客户端进行抓取,从而达到跨域的目的。
mxSaveRequest({//此处调用js-native接口mxSaveRequest将请求发送给客户端
itemName: "bindQuery-" + e + ".json",
type: "GET",
url: "https://zht.alipay.com/asset/bindQuery.json?_input_charset=utf-8&providerType=" + e + "&t=" + (new Date).getTime() + "&ctoken=" + (0, y.getCookie)("ctoken"),
headers: {
Accept: "*/*",
"Accept-Language": "en-US,en;q=0.8,zh;q=0.6,zh-CN;q=0.4,ja;q=0.2,zh-TW;q=0.2",
"Cache-Control": "max-age=0",
"X-Requested-With": "XMLHttpRequest",
Host: "zht.alipay.com",
Referer: "https://zht.alipay.com/asset/bankList.htm",
"User-Agent": navigator.userAgent
},
encoding: "GBK"
})
客户端提供mxSaveRequest的端能力代码
@NotProguard
@JavascriptInterface
public void mxSaveRequest(final String str) {//注入js对象用于接受跨域请求的参数
Logger.json(str);
WebViewECV3Presenter.this.handler.post(new Runnable() {
public void run() {
WebViewECV3Presenter.call(WebViewECV3Presenter.this, str);
}
});
}
static /* synthetic */ void call(final WebViewECV3Presenter webViewECV3Presenter, final String str) {
try {
new Thread(new Runnable() {
public void run() {
try {
JsBaseRequest jsBaseRequest = JsBaseRequest.getJsRequest(str);
byte[] response =
WebViewECV3Presenter.getbytes(webViewECV3Presenter, jsBaseRequest);//http request获取请求的接口
WebViewECV3Presenter.a(webViewECV3Presenter,/*call back result*/ response, jsBaseRequest);//put
// maps jsreq.itemName
WebViewECV3Presenter.callback(webViewECV3Presenter, response, jsBaseRequest);//回调js请求的接口,给js回调的数据需要base64加密
} catch (Throwable e) {
ErrorHandle.b("sendRequest fail", e);
}
}
}).start();
} catch (Throwable e) {
ErrorHandle.b("sendRequest fail1", e);
}
}
//请求的数据结构
public class JsBaseRequest {
public String type = "";
public String url = "";
public String headers = "";
public String data = "";
public String itemName = "";
public String encoding = "UTF-8";
public int failCode = ErrorCode.INFO_CODE_BASE;
public String responseId = "";
public static JsBaseRequest getJsRequest(String str) {
JsBaseRequest jsBaseRequest = new JsBaseRequest();
JSONObject jSONObject = null;
try {
jSONObject = new JSONObject(str);
} catch (JSONException e) {
e.printStackTrace();
}
jsBaseRequest.type = jSONObject.optString("type");
jsBaseRequest.url = jSONObject.optString("url");
jsBaseRequest.headers = jSONObject.optString("headers");
jsBaseRequest.data = jSONObject.optString("data");
jsBaseRequest.itemName = jSONObject.optString("itemName");
jsBaseRequest.responseId = jSONObject.optString("responseId");
if (jSONObject.has("encoding")) {
jsBaseRequest.encoding = jSONObject.optString("encoding");
if (jsBaseRequest.encoding.equalsIgnoreCase("UTF8")) {
jsBaseRequest.encoding = "UTF-8";
}
}
if (jSONObject.has("failCode")) {
jsBaseRequest.failCode = jSONObject.optInt("failCode");
}
return jsBaseRequest;
}
}
//callback JS
webViewECV3Presenter.webViewECV3Fragment.loadUrl("requestFinishCallback('" + jSONObject.toString() + "')");
2.3常见的问题
2.3.1身份凭证
Fetch(JSONP形式) 与 CORS 的一个有趣的特性是——可以基于 HTTP cookies 和 HTTP 认证信息发送身份凭证。一般而言对于跨域 XMLHttpRequest 或 Fetch 请求,浏览器不会发送身份凭证信息——即cookies。如果要发送凭证信息,需要设置 XMLHttpRequest 的某个特殊标志位——withCredentials。
var invocation = new XMLHttpRequest();
var url = 'http://bar.other/resources/credentialed-content/';
function callOtherDomain(){
if(invocation) {
invocation.open('GET', url, true);
invocation.withCredentials = true;
invocation.onreadystatechange = handler;
invocation.send();
}
}
2.3.2使用浏览器插件net-internals
遇到这样的问题想查看header的详细参数,请使用插件。
2.3.3携带cookie的修改参数
ajax跨域中直接设置请求头是不允许,我们只能在发送前修改cookie
document.cookie = 'domain=m.taobao.com';
$.ajax({
url: trust_url,
method: 'get',
xhrFields: {
withCredentials: true
},
crossDomain: true,
success: function (res) {
alert("success\n" + JSON.stringify(res));
alipayAuth(res);
},
error: function (res) {
alert("fail");
}
});
3抓取的应用场景——芝麻分、花呗破解方案
3.1芝麻分接口获取
18年中旬,基本完成支付宝、淘宝电商抓取的全部技术栈,其中对前端抓取最常见的就是ajax的跨域请求。
芝麻分的请求就是通过jsonp进行跨域的,支付宝服务端支持callback参数,通过自定义function name可以在js端收到请求响应回调。
我们注册了一个名为mtopjsonp1的方法用来接受js对象。
3.2花呗接口获取
ajax请求的代码,其中有一次jsonp请求和一次CORS简单跨域请求(响应头包含:access-control-allow-credentials: true),最后的结果会在客户端保存。
花呗的请求结果:
CORS跨域二次请求方案在老版本的芝麻分和借呗,后端接口提供过,首先会发送一次option请求获取后端支持跨域的操作get、post等,然后通过相应的请求获取数据。
当然以上讨论的都是前端抓取的技术方案,如果在接口已经破解的情况下,只需要客户端或后端拿到cookie后,直接对服务端发起请求就可以拿到数据了,后端用python实现回更简单。