Ajax技术——跨域解决方案及抓取领域应用

1Ajax定义

Ajax:(Asynchronous JavaScript and XML)用JavaScript执行异步网络请求。

1.1Ajax技术带来的改变

  • 一次HTTP请求对应一个页面
  • 异步数据刷新

Ajax技术包含的内容:

  1. 使用CSS和XHTML来表示。
  2. 使用DOM模型来交互和动态显示。
  3. 使用XMLHttpRequest来和服务器进行异步通信。
  4. 使用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简单请求

CORS

当ajax通过浏览器发出跨域请求,浏览器收到响应后检查header中Access-Control-Allow-Origin是否包含当前域(origin)。如果有,则跨域成功。

2预检测请求
PUT、DELETE以及其他类型如application/json的POST请求,将会进入以下流程。
先发出OPTIONS请求询问服务端支持的请求方式;
确认成功后,进行数据的请求。

cors

请参考https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS

2.3JS-Native客户端代理

原理很简单,因为浏览器有同源限制,然后客户端、后端并没有,我们是可以在代理对象(客户端或后端集群)上收到远程服务的响应。
因此,通过将前端的请求头和请求body等参数封装后,提交给客户端进行抓取,从而达到跨域的目的。

代理请求的模型.png
            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
image.png

遇到这样的问题想查看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芝麻分接口获取

image.png

18年中旬,基本完成支付宝、淘宝电商抓取的全部技术栈,其中对前端抓取最常见的就是ajax的跨域请求。


image.png

芝麻分的请求就是通过jsonp进行跨域的,支付宝服务端支持callback参数,通过自定义function name可以在js端收到请求响应回调。
我们注册了一个名为mtopjsonp1的方法用来接受js对象。

3.2花呗接口获取

ajax请求的代码,其中有一次jsonp请求和一次CORS简单跨域请求(响应头包含:access-control-allow-credentials: true),最后的结果会在客户端保存。


image.png

花呗的请求结果:


image.png

CORS跨域二次请求方案在老版本的芝麻分和借呗,后端接口提供过,首先会发送一次option请求获取后端支持跨域的操作get、post等,然后通过相应的请求获取数据。

当然以上讨论的都是前端抓取的技术方案,如果在接口已经破解的情况下,只需要客户端或后端拿到cookie后,直接对服务端发起请求就可以拿到数据了,后端用python实现回更简单。

你可能感兴趣的:(Ajax技术——跨域解决方案及抓取领域应用)