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
19XMLHttpRequestClass::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
42XMLHttpRequestClass::~XMLHttpRequestClass() {
43 if (_reply) { delete _reply; }
44 if (_sendData) { delete _sendData; }
45}
46
47QScriptValue XMLHttpRequestClass::constructor(QScriptContext* context, QScriptEngine* engine) {
48 return engine->newQObject(new XMLHttpRequestClass(engine));
49}
50
51QScriptValue XMLHttpRequestClass::getStatus() const {
52 if (_reply) {
53 return QScriptValue(_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt());
54 }
55 return QScriptValue(0);
56}
57
58QString XMLHttpRequestClass::getStatusText() const {
59 if (_reply) {
60 return _reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();
61 }
62 return "";
63}
64
65void XMLHttpRequestClass::abort() {
66 _abortRequest();
67}
68
69void XMLHttpRequestClass::setRequestHeader(const QString& name, const QString& value) {
70 _request.setRawHeader(QByteArray(name.toLatin1()), QByteArray(value.toLatin1()));
71}
72
73void 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
87void XMLHttpRequestClass::requestDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) {
88 if (_readyState == OPENED && bytesReceived > 0) {
89 _setReadyState(HEADERS_RECEIVED);
90 _setReadyState(LOADING);
91 }
92}
93
94QScriptValue 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
109QScriptValue 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
116void XMLHttpRequestClass::_setReadyState(ReadyState readyState) {
117 if (readyState != _readyState) {
118 _readyState = readyState;
119 if (_onReadyStateChange.isFunction()) {
120 _onReadyStateChange.call(QScriptValue::NullValue);
121 }
122 }
123}
124
125void 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
142void XMLHttpRequestClass::send() {
143 send(QString::Null());
144}
145
146void 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
163void 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
174void 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
184void XMLHttpRequestClass::requestError(QNetworkReply::NetworkError code) {
185}
186
187void 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
210void 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
221void 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
228void 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}