History

Add XMLHttpRequest

Ryan Huffman committed 12 years ago

Commit 7d7e0121 parent 2bc605e2 files · +361 −0Browse files

Showing the changes to libraries/script-engine/src/XMLHttpRequestClass.cpp (1 of 2 files). Show all

Unified · Split

libraries/script-engine/src/XMLHttpRequestClass.cpp+233−0
@@ -0,0 +1,233 @@
1+//
2+// XMLHttpRequestClass.cpp
3+// libraries/script-engine/src/
4+//
5+// Created by Ryan Huffman on 5/2/14.
6+// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
7+//
8+// This class is an implementation of the XMLHttpRequest object for scripting use. It provides a near-complete implementation
9+// of the class described in the Mozilla docs: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest
10+//
11+// Distributed under the Apache License, Version 2.0.
12+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
13+//
14+
15+#include <QEventLoop>
16+
17+#include "XMLHttpRequestClass.h"
18+
19+XMLHttpRequestClass::XMLHttpRequestClass(QScriptEngine* engine) :
20+ _engine(engine),
21+ _async(true),
22+ _url(),
23+ _method(""),
24+ _responseType(""),
25+ _manager(this),
26+ _request(),
27+ _reply(NULL),
28+ _sendData(NULL),
29+ _rawResponseData(),
30+ _responseData(""),
31+ _onTimeout(QScriptValue::NullValue),
32+ _onReadyStateChange(QScriptValue::NullValue),
33+ _readyState(XMLHttpRequestClass::UNSENT),
34+ _errorCode(QNetworkReply::NoError),
35+ _timeout(0),
36+ _timer(this),
37+ _numRedirects(0) {
38+
39+ _timer.setSingleShot(true);
40+}
41+
42+XMLHttpRequestClass::~XMLHttpRequestClass() {
43+ if (_reply) { delete _reply; }
44+ if (_sendData) { delete _sendData; }
45+}
46+
47+QScriptValue XMLHttpRequestClass::constructor(QScriptContext* context, QScriptEngine* engine) {
48+ return engine->newQObject(new XMLHttpRequestClass(engine));
49+}
50+
51+QScriptValue XMLHttpRequestClass::getStatus() const {
52+ if (_reply) {
53+ return QScriptValue(_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt());
54+ }
55+ return QScriptValue(0);
56+}
57+
58+QString XMLHttpRequestClass::getStatusText() const {
59+ if (_reply) {
60+ return _reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();
61+ }
62+ return "";
63+}
64+
65+void XMLHttpRequestClass::abort() {
66+ _abortRequest();
67+}
68+
69+void XMLHttpRequestClass::setRequestHeader(const QString& name, const QString& value) {
70+ _request.setRawHeader(QByteArray(name.toLatin1()), QByteArray(value.toLatin1()));
71+}
72+
73+void XMLHttpRequestClass::requestMetaDataChanged() {
74+ QVariant redirect = _reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
75+
76+ // If this is a redirect, abort the current request and start a new one
77+ if (redirect.isValid() && _numRedirects < MAXIMUM_REDIRECTS) {
78+ _numRedirects++;
79+ _abortRequest();
80+
81+ QUrl newUrl = _url.resolved(redirect.toUrl().toString());
82+ _request.setUrl(newUrl);
83+ _doSend();
84+ }
85+}
86+
87+void XMLHttpRequestClass::requestDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) {
88+ if (_readyState == OPENED && bytesReceived > 0) {
89+ _setReadyState(HEADERS_RECEIVED);
90+ _setReadyState(LOADING);
91+ }
92+}
93+
94+QScriptValue XMLHttpRequestClass::getAllResponseHeaders() const {
95+ if (_reply) {
96+ QList<QNetworkReply::RawHeaderPair> headerList = _reply->rawHeaderPairs();
97+ QByteArray headers;
98+ for (int i = 0; i < headerList.size(); i++) {
99+ headers.append(headerList[i].first);
100+ headers.append(": ");
101+ headers.append(headerList[i].second);
102+ headers.append("\n");
103+ }
104+ return QString(headers.data());
105+ }
106+ return QScriptValue("");
107+}
108+
109+QScriptValue XMLHttpRequestClass::getResponseHeader(const QString& name) const {
110+ if (_reply && _reply->hasRawHeader(name.toLatin1())) {
111+ return QScriptValue(QString(_reply->rawHeader(name.toLatin1())));
112+ }
113+ return QScriptValue::NullValue;
114+}
115+
116+void XMLHttpRequestClass::_setReadyState(ReadyState readyState) {
117+ if (readyState != _readyState) {
118+ _readyState = readyState;
119+ if (_onReadyStateChange.isFunction()) {
120+ _onReadyStateChange.call(QScriptValue::NullValue);
121+ }
122+ }
123+}
124+
125+void XMLHttpRequestClass::open(const QString& method, const QString& url, bool async, const QString& username,
126+ const QString& password) {
127+ if (_readyState == UNSENT) {
128+ _async = async;
129+ _url.setUrl(url);
130+ if (!username.isEmpty()) {
131+ _url.setUserName(username);
132+ }
133+ if (!password.isEmpty()) {
134+ _url.setPassword(password);
135+ }
136+ _request.setUrl(_url);
137+ _method = method;
138+ _setReadyState(OPENED);
139+ }
140+}
141+
142+void XMLHttpRequestClass::send() {
143+ send(QString::Null());
144+}
145+
146+void XMLHttpRequestClass::send(const QString& data) {
147+ if (_readyState == OPENED && !_reply) {
148+ if (!data.isNull()) {
149+ _sendData = new QBuffer(this);
150+ _sendData->setData(data.toUtf8());
151+ }
152+
153+ _doSend();
154+
155+ if (!_async) {
156+ QEventLoop loop;
157+ connect(this, SIGNAL(requestComplete()), &loop, SLOT(quit()));
158+ loop.exec();
159+ }
160+ }
161+}
162+
163+void XMLHttpRequestClass::_doSend() {
164+ _reply = _manager.sendCustomRequest(_request, _method.toLatin1(), _sendData);
165+
166+ _connectToReply(_reply);
167+
168+ if (_timeout > 0) {
169+ _timer.start(_timeout);
170+ connect(&_timer, SIGNAL(timeout()), this, SLOT(requestTimeout()));
171+ }
172+}
173+
174+void XMLHttpRequestClass::requestTimeout() {
175+ if (_onTimeout.isFunction()) {
176+ _onTimeout.call(QScriptValue::NullValue);
177+ }
178+ _abortRequest();
179+ _errorCode = QNetworkReply::TimeoutError;
180+ _setReadyState(DONE);
181+ emit requestComplete();
182+}
183+
184+void XMLHttpRequestClass::requestError(QNetworkReply::NetworkError code) {
185+}
186+
187+void XMLHttpRequestClass::requestFinished() {
188+ disconnect(&_timer, SIGNAL(timeout()), this, SLOT(requestTimeout()));
189+
190+ _errorCode = _reply->error();
191+ if (_errorCode == QNetworkReply::NoError) {
192+ _rawResponseData.append(_reply->readAll());
193+
194+ if (_responseType == "json") {
195+ _responseData = _engine->evaluate("(" + QString(_rawResponseData.data()) + ")");
196+ if (_responseData.isError()) {
197+ _engine->clearExceptions();
198+ _responseData = QScriptValue::NullValue;
199+ }
200+ } else if (_responseType == "arraybuffer") {
201+ _responseData = QScriptValue(_rawResponseData.data());
202+ } else {
203+ _responseData = QScriptValue(QString(_rawResponseData.data()));
204+ }
205+ }
206+ _setReadyState(DONE);
207+ emit requestComplete();
208+}
209+
210+void XMLHttpRequestClass::_abortRequest() {
211+ // Disconnect from signals we don't want to receive any longer.
212+ disconnect(&_timer, SIGNAL(timeout()), this, SLOT(requestTimeout()));
213+ if (_reply) {
214+ _disconnectFromReply(_reply);
215+ _reply->abort();
216+ delete _reply;
217+ _reply = NULL;
218+ }
219+}
220+
221+void XMLHttpRequestClass::_connectToReply(QNetworkReply* reply) {
222+ connect(reply, SIGNAL(finished()), this, SLOT(requestFinished()));
223+ connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestError(QNetworkReply::NetworkError)));
224+ connect(reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(requestDownloadProgress(qint64, qint64)));
225+ connect(reply, SIGNAL(metaDataChanged()), this, SLOT(requestMetaDataChanged()));
226+}
227+
228+void XMLHttpRequestClass::_disconnectFromReply(QNetworkReply* reply) {
229+ disconnect(reply, SIGNAL(finished()), this, SLOT(requestFinished()));
230+ disconnect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestError(QNetworkReply::NetworkError)));
231+ disconnect(reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(requestDownloadProgress(qint64, qint64)));
232+ disconnect(reply, SIGNAL(metaDataChanged()), this, SLOT(requestMetaDataChanged()));
233+}