QML 中的 XMLHttpRequest 对象

QML 中的 XMLHttpRequest 对象

QML 中的 XMLHttpRequest 没有同源限制,并且可以读写本地文件。

代码如下:

function saveText(filename, contentText) {
    var xhr = new XMLHttpRequest;
    xhr.onreadystatechange = function() {
        if (xhr.readyState == XMLHttpRequest.HEADERS_RECEIVED) {
            console.log(xhr.getAllResponseHeaders());
        } else if (xhr.readyState == XMLHttpRequest.DONE) {
            console.log(xhr.getAllResponseHeaders());
        }
    };
    xhr.open("PUT", filename);
    xhr.send(contentText.toString());
}

但是 QML 中只实现了 XMLHttpRequest Level 1 标准。

如下源码来自 Qtqml 模块,此函数的功能就是过滤 setRequestHeader 设置的请求头。

其中 COOKIECOOKIE2 等不能被设置,更多被过滤的请求头,查看下面的源码。

ReturnedValue QQmlXMLHttpRequestCtor::method_setRequestHeader(CallContext *ctx)
{
    Scope scope(ctx);
    Scoped<QQmlXMLHttpRequestWrapper> w(scope, ctx->thisObject().as<QQmlXMLHttpRequestWrapper>());
    if (!w)
        V4THROW_REFERENCE("Not an XMLHttpRequest object");
    QQmlXMLHttpRequest *r = w->d()->request;

    if (ctx->argc() != 2)
        V4THROW_DOM(DOMEXCEPTION_SYNTAX_ERR, "Incorrect argument count");

    if (r->readyState() != QQmlXMLHttpRequest::Opened || r->sendFlag())
        V4THROW_DOM(DOMEXCEPTION_INVALID_STATE_ERR, "Invalid state");

    QString name = ctx->args()[0].toQStringNoThrow();
    QString value = ctx->args()[1].toQStringNoThrow();

    // ### Check that name and value are well formed

    QString nameUpper = name.toUpper();
    if (nameUpper == QLatin1String("ACCEPT-CHARSET") ||
        nameUpper == QLatin1String("ACCEPT-ENCODING") ||
        nameUpper == QLatin1String("CONNECTION") ||
        nameUpper == QLatin1String("CONTENT-LENGTH") ||
        nameUpper == QLatin1String("COOKIE") ||
        nameUpper == QLatin1String("COOKIE2") ||
        nameUpper == QLatin1String("CONTENT-TRANSFER-ENCODING") ||
        nameUpper == QLatin1String("DATE") ||
        nameUpper == QLatin1String("EXPECT") ||
        nameUpper == QLatin1String("HOST") ||
        nameUpper == QLatin1String("KEEP-ALIVE") ||
        nameUpper == QLatin1String("REFERER") ||
        nameUpper == QLatin1String("TE") ||
        nameUpper == QLatin1String("TRAILER") ||
        nameUpper == QLatin1String("TRANSFER-ENCODING") ||
        nameUpper == QLatin1String("UPGRADE") ||
        nameUpper == QLatin1String("USER-AGENT") ||
        nameUpper == QLatin1String("VIA") ||
        nameUpper.startsWith(QLatin1String("PROXY-")) ||
        nameUpper.startsWith(QLatin1String("SEC-")))
        return Encode::undefined();

    r->addHeader(name, value);

    return Encode::undefined();
}

如有需求,可参照 XMLHttpRequest 接口可以设计一个支持更多必要功能的 C++ 类。

W3C XMLHttpRequest 标准

XMLHttpRequest 的 W3C 接口描述如下:

[NoInterfaceObject]
interface XMLHttpRequestEventTarget : EventTarget {
    // event handlers
    attribute EventHandler onloadstart;
    attribute EventHandler onprogress;
    attribute EventHandler onabort;
    attribute EventHandler onerror;
    attribute EventHandler onload;
    attribute EventHandler ontimeout;
    attribute EventHandler onloadend;
};

interface XMLHttpRequestUpload : XMLHttpRequestEventTarget {

};

enum XMLHttpRequestResponseType {
    "",
    "arraybuffer",
    "blob",
    "document",
    "json",
    "text"
};

[Constructor]
interface XMLHttpRequest : XMLHttpRequestEventTarget {
    // event handler
    attribute EventHandler onreadystatechange;

    // states
    const unsigned short UNSENT = 0;
    const unsigned short OPENED = 1;
    const unsigned short HEADERS_RECEIVED = 2;
    const unsigned short LOADING = 3;
    const unsigned short DONE = 4;
    readonly attribute unsigned short readyState;

    // request
    void open(ByteString method, [EnsureUTF16] DOMString url);
    void open(ByteString method, 
             [EnsureUTF16] DOMString url, 
             boolean async, 
             optional [EnsureUTF16] DOMString? username = null, 
             optional [EnsureUTF16] DOMString? password = null);
    void setRequestHeader(ByteString header, ByteString value);
    attribute unsigned long timeout;
    attribute boolean withCredentials;
    readonly attribute XMLHttpRequestUpload upload;
    void send(optional (ArrayBufferView or Blob or Document or [EnsureUTF16] DOMString or FormData)? data = null);
    void abort();

    // response
    readonly attribute unsigned short status;
    readonly attribute ByteString statusText;
    ByteString? getResponseHeader(ByteString header);
    ByteString getAllResponseHeaders();
    void overrideMimeType(DOMString mime);
    attribute XMLHttpRequestResponseType responseType;
    readonly attribute any response;
    readonly attribute DOMString responseText;
    readonly attribute Document? responseXML;
};

已完成 qyvlik/HttpRequest

qml-fetch

仿制 node-fetch 的接口, Qt 5.13.2

需要使用到 Qt 的一个特性:Promise,从 Qt 5.12 开始支持 New_Features_in_Qt_5.12

// file is qml-fetch.js
function createResponse(xhr) {
    let res = {};
    function headersParser() {
        let headersRaw = {};
        let lowerCaseHeaders = {};
        let rawHeaderArray = xhr.getAllResponseHeaders().split("\n");
        for(let i in rawHeaderArray) {
            let rawHeader = rawHeaderArray[i];
            let headerItem = rawHeader.split(":");
            let name = headerItem[0].trim();
            let value = headerItem[1].trim();
            let lowerName = name.toLowerCase();
            headersRaw[name] = value;
            lowerCaseHeaders [lowerName] = value;
        }
        return {
            "headersRaw": headersRaw,
            "lowerCaseHeaders": lowerCaseHeaders
        };
    }
    res.headers = {
        __alreayParse : false,
        raw: function() {
            if (!res.headers.__alreayParse) {
                let {headersRaw, lowerCaseHeaders} = headersParser();
                res.headers.__alreayParse = true;
                res.headers.__headersRaw = headersRaw;
                res.headers.__lowerCaseHeaders = lowerCaseHeaders;
            }
            return res.headers.__headersRaw;
        },
        get: function(headerName) {
            if (!res.headers.__alreayParse) {
                let {headersRaw, lowerCaseHeaders} = headersParser();
                res.headers.__alreayParse = true;
                res.headers.__headersRaw = headersRaw;
                res.headers.__lowerCaseHeaders = lowerCaseHeaders;
            }
            return res.headers.__lowerCaseHeaders[headerName.toLowerCase()];
        }
    };
    res.json = function() {
        if(res.__json) {
            return res.__json;
        }
        return res.__json = JSON.parse(xhr.responseText);
    }
    res.text = function() {
        if (res.__text) {
            return res.__text;
        }
        return res.__text = xhr.responseText;
    }
    res.arrayBuffer = function() {
        if (res.__arrayBuffer) {
            return res.__arrayBuffer;
        }
        return res.__arrayBuffer = new Uint8Array(xhr.response);
    }
    res.ok = (xhr.status >= 200 && xhr.status < 300);
    res.status = xhr.status;
    res.statusText = xhr.statusText;
    return res;
}
function fetch(url, options) {
    return new Promise(function(resolve, reject) {
        let requestUrl = "";
        let method = "";
        let headers = {};
        let body;
        let timeout;
        if (typeof url === 'string') {
            requestUrl = url;
            method = "GET";
            if (options) {
                requestUrl = options['url'];
                method = options['method'];
                headers = options['headers'];
                body = options['body'];
                timeout = options['timeout'];
            }
        } else {
            let optionsObj = url;
            requestUrl = optionsObj['url'];
            method = optionsObj['method'];
            headers = optionsObj['headers'];
            body = optionsObj['body'];
            timeout = optionsObj['timeout'];
        }
        let closureData = {
            'abort': false
        };
        let xhr = new XMLHttpRequest;

        // https://github.com/qt/qtdeclarative/blob/v5.13.2/src/qml/qml/qqmlxmlhttprequest.cpp#L1560
        // when call xhr.abort, methods will call as follow
        // 1. in cpp, call destroyNetwork (qml xmlhttprequest abort not call QNetworkReply::abort)
        // 2. onreadystatechange, the xhr.readyState will be set as DONE
        // 3. onerror
        if (timeout && fetch.setTimeout && fetch.clearTimeout) {
            let timeId = fetch.setTimeout(()=>{
              fetch.clearTimeout(timeId);
              if (xhr.readyState !== XMLHttpRequest.DONE && !closureData.abort) {
                  closureData.abort = true;
                  xhr.abort();
              }
          }, timeout * 1000);
        }

        // must set responseType to arraybuffer, then the xhr.response type will be ArrayBuffer
        // but responseType not effect the responseText
        // https://github.com/qt/qtdeclarative/blob/v5.13.2/src/qml/qml/qqmlxmlhttprequest.cpp#L2014
        // responseType value as follow: `text`, `arraybuffer`, `json`, `document`
        xhr.responseType = 'arraybuffer';

        // https://github.com/qt/qtdeclarative/blob/v5.13.2/src/qml/qml/qqmlxmlhttprequest.cpp#L1582
        // callback value as follow: `onreadystatechange`, `onerror`, `onload`, `onloadend`
        xhr.onreadystatechange = function() {
            // readyState as follow: UNSENT, OPENED, HEADERS_RECEIVED, LOADING, DONE
            if(!closureData.abort && xhr.readyState === XMLHttpRequest.DONE) {
                try {
                    resolve(createResponse(xhr));
                } catch(error) {
                    reject(error);
                }
            }
        };

        xhr.onerror = function() {
            if(xhr.readyState === XMLHttpRequest.DONE) {
                let error = {
                    "message": closureData.abort ? "XMLHttpRequest abort": "XMLHttpRequest statusText:" + xhr.statusText,
                    "res": createResponse(xhr)
                }
                reject(error);
            }
        }

        xhr.open(method, requestUrl);

        //        if (nameUpper == QLatin1String("ACCEPT-CHARSET") ||
        //            nameUpper == QLatin1String("ACCEPT-ENCODING") ||
        //            nameUpper == QLatin1String("CONNECTION") ||
        //            nameUpper == QLatin1String("CONTENT-LENGTH") ||
        //            nameUpper == QLatin1String("COOKIE") ||
        //            nameUpper == QLatin1String("COOKIE2") ||
        //            nameUpper == QLatin1String("CONTENT-TRANSFER-ENCODING") ||
        //            nameUpper == QLatin1String("DATE") ||
        //            nameUpper == QLatin1String("EXPECT") ||
        //            nameUpper == QLatin1String("HOST") ||
        //            nameUpper == QLatin1String("KEEP-ALIVE") ||
        //            nameUpper == QLatin1String("REFERER") ||
        //            nameUpper == QLatin1String("TE") ||
        //            nameUpper == QLatin1String("TRAILER") ||
        //            nameUpper == QLatin1String("TRANSFER-ENCODING") ||
        //            nameUpper == QLatin1String("UPGRADE") ||
        //            nameUpper == QLatin1String("USER-AGENT") ||
        //            nameUpper == QLatin1String("VIA") ||
        //            nameUpper.startsWith(QLatin1String("PROXY-")) ||
        //            nameUpper.startsWith(QLatin1String("SEC-")))
        //            RETURN_UNDEFINED();

        for(let iter in headers) {
            xhr.setRequestHeader(iter, headers[iter]);
        }

        if("GET" === method || "HEAD" === method) {
            xhr.send();
        } else {
            xhr.send(body);
        }
    });
}

qml xmlhttprequest 下载处理二进制文件

qml xmlhttprequest download and handle binary file

使用上诉的 qml-fetch 代码。设置 xhr.responseTypearraybuffer, xhr.response 返回的是 arraybuffer

如下的代码,其中 hexdump 函数是将二进制内容转换成 hex 格式的文本,进行输出,可以配合 linux 的 hexdump 命令进行使用,验证 qml 中的二进制文件内容和实际的文件内容是否相匹配。

import Qt.Quick 2.13
import "qml-fetch.js" as QmlFetch

Item {
	function hexdump(uint8array) {
	    let count = 0;
	    let line = "";
	    let lineCount = 0;
	    let content = "";
	    for(let i=0; i<uint8array.byteLength; i++) {
	        let c = uint8array[i];
	        let hex =  c.toString(16).padStart (2, "0");
	        line += hex + " ";
	        count++;
	        if (count === 16) {
	            let lineCountHex = (lineCount).toString (16).padStart (7, "0") + "0";
	            content += lineCountHex + " " + line + "\n";
	            line = "";
	            count = 0;
	            lineCount++;
	        }
	    }
	    if(line) {
	        let lineCountHex = (lineCount).toString (16).padStart (7, "0") + "0";
	        content += lineCountHex + " " + line + "\n";
	        line = "";
	        //            count = 0;
	        lineCount++;
	    }
	    content+= (lineCount).toString (16).padStart (7, "0") + count.toString(16) +"\n";
	    return content;
	}
    Component.onCompleted: {
        let fetch = QmlFetch.fetch;
        fetch("https://avatars0.githubusercontent.com/u/6630355")
        .then((res)=>{
        	let arrayBuffer = res.arrayBuffer();
        	let hex = hexdump(arrayBuffer);
        	console.info(hex);
        })
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值