Skip to content

Commit c4392a4

Browse files
http: add http client
1 parent e9bad67 commit c4392a4

File tree

7 files changed

+407
-0
lines changed

7 files changed

+407
-0
lines changed

src/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,5 @@ add_subdirectory(services)
3333
if (BLUETOOTH)
3434
add_subdirectory(bluetooth)
3535
endif()
36+
37+
add_subdirectory(http)

src/http/CMakeLists.txt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
qt_add_library(quickshell-http STATIC
2+
client.cpp
3+
response.cpp
4+
)
5+
6+
qt_add_qml_module(quickshell-http
7+
URI Quickshell.Http
8+
VERSION 0.1
9+
DEPENDENCIES QtQml
10+
)
11+
12+
install_qml_module(quickshell-http)
13+
14+
target_link_libraries(quickshell-http PRIVATE
15+
Qt::Qml
16+
Qt::Network
17+
)
18+
19+
qs_module_pch(quickshell-http)
20+
21+
target_link_libraries(quickshell PRIVATE quickshell-http)

src/http/client.cpp

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
#include "client.hpp"
2+
3+
#include <qjsengine.h>
4+
#include <qjsvalue.h>
5+
#include <qjsvalueiterator.h>
6+
#include <qnetworkaccessmanager.h>
7+
#include <qnetworkreply.h>
8+
#include <qnetworkrequest.h>
9+
#include <qobject.h>
10+
#include <qstringview.h>
11+
#include <qtimer.h>
12+
#include <qurl.h>
13+
14+
#include "response.hpp"
15+
16+
namespace qs::http {
17+
18+
HttpClient::HttpClient(QObject* parent)
19+
: QObject(parent)
20+
, mManager(new QNetworkAccessManager(this)) {}
21+
22+
QNetworkRequest HttpClient::createRequest(const QString& url, const QJSValue& headers) {
23+
QNetworkRequest req;
24+
25+
req.setUrl(QUrl(url));
26+
27+
if (headers.isObject()) {
28+
QJSValueIterator iter(headers);
29+
while (iter.hasNext()) {
30+
iter.next();
31+
req.setRawHeader(iter.name().toUtf8(), iter.value().toString().toUtf8());
32+
}
33+
}
34+
35+
return req;
36+
}
37+
38+
void HttpClient::connectReply(QNetworkReply* reply, const QJSValue& callback, int timeout) {
39+
if (timeout > 0) {
40+
auto* timer = new QTimer(reply);
41+
timer->setSingleShot(true);
42+
timer->setInterval(timeout);
43+
connect(timer, &QTimer::timeout, reply, &QNetworkReply::abort);
44+
timer->start();
45+
}
46+
47+
QJSEngine* engine = qjsEngine(this);
48+
49+
if (!engine) {
50+
return;
51+
}
52+
if (callback.isCallable()) {
53+
connect(reply, &QNetworkReply::finished, this, [reply, engine, callback]() mutable {
54+
reply->deleteLater();
55+
auto* resp = new HttpResponse(reply);
56+
auto jsResp = engine->newQObject(resp);
57+
QJSEngine::setObjectOwnership(resp, QJSEngine::JavaScriptOwnership);
58+
59+
callback.call(QJSValueList() << jsResp);
60+
});
61+
}
62+
}
63+
64+
void HttpClient::request(
65+
const QString& url,
66+
const QString& verb,
67+
const QJSValue& options,
68+
const QJSValue& callback
69+
) {
70+
QJSValue headers;
71+
QByteArray body;
72+
int timeout = -1;
73+
74+
if (options.isObject()) {
75+
if (options.hasOwnProperty("headers")) {
76+
headers = options.property("headers");
77+
}
78+
if (options.hasOwnProperty("body")) {
79+
auto bodyProp = options.property("body");
80+
body = bodyProp.toVariant().toByteArray();
81+
}
82+
if (options.hasOwnProperty("timeout")) {
83+
auto timeoutProp = options.property("timeout");
84+
if (timeoutProp.isNumber()) {
85+
timeout = static_cast<int>(timeoutProp.toNumber());
86+
}
87+
}
88+
}
89+
90+
auto req = this->createRequest(url, headers);
91+
auto* reply = this->mManager->sendCustomRequest(req, verb.toUtf8(), body);
92+
93+
this->connectReply(reply, callback, timeout);
94+
}
95+
96+
void HttpClient::get(const QString& url, const QJSValue& options, const QJSValue& callback) {
97+
QJSValue headers;
98+
int timeout = -1;
99+
100+
if (options.isObject()) {
101+
if (options.hasOwnProperty("headers")) {
102+
headers = options.property("headers");
103+
}
104+
if (options.hasOwnProperty("timeout")) {
105+
auto timeoutProp = options.property("timeout");
106+
if (timeoutProp.isNumber()) {
107+
timeout = static_cast<int>(timeoutProp.toNumber());
108+
}
109+
}
110+
}
111+
112+
auto req = this->createRequest(url, headers);
113+
auto* reply = this->mManager->get(req);
114+
115+
this->connectReply(reply, callback, timeout);
116+
}
117+
118+
void HttpClient::post(
119+
const QString& url,
120+
const QVariant& body,
121+
const QJSValue& options,
122+
const QJSValue& callback
123+
) {
124+
auto bodyBytes = body.toByteArray();
125+
QJSValue headers;
126+
int timeout = -1;
127+
128+
if (options.isObject()) {
129+
if (options.hasOwnProperty("headers")) {
130+
headers = options.property("headers");
131+
}
132+
if (options.hasOwnProperty("timeout")) {
133+
auto timeoutProp = options.property("timeout");
134+
if (timeoutProp.isNumber()) {
135+
timeout = static_cast<int>(timeoutProp.toNumber());
136+
}
137+
}
138+
}
139+
140+
auto req = this->createRequest(url, headers);
141+
auto* reply = this->mManager->post(req, bodyBytes);
142+
143+
this->connectReply(reply, callback, timeout);
144+
}
145+
146+
void HttpClient::put(
147+
const QString& url,
148+
const QVariant& body,
149+
const QJSValue& options,
150+
const QJSValue& callback
151+
) {
152+
auto bodyBytes = body.toByteArray();
153+
QJSValue headers;
154+
int timeout = -1;
155+
156+
if (options.isObject()) {
157+
if (options.hasOwnProperty("headers")) {
158+
headers = options.property("headers");
159+
}
160+
if (options.hasOwnProperty("timeout")) {
161+
auto timeoutProp = options.property("timeout");
162+
if (timeoutProp.isNumber()) {
163+
timeout = static_cast<int>(timeoutProp.toNumber());
164+
}
165+
}
166+
}
167+
168+
auto req = this->createRequest(url, headers);
169+
auto* reply = this->mManager->put(req, bodyBytes);
170+
171+
this->connectReply(reply, callback, timeout);
172+
}
173+
174+
void HttpClient::del(const QString& url, const QJSValue& options, const QJSValue& callback) {
175+
QJSValue headers;
176+
int timeout = -1;
177+
178+
if (options.isObject()) {
179+
if (options.hasOwnProperty("headers")) {
180+
headers = options.property("headers");
181+
}
182+
if (options.hasOwnProperty("timeout")) {
183+
auto timeoutProp = options.property("timeout");
184+
if (timeoutProp.isNumber()) {
185+
timeout = static_cast<int>(timeoutProp.toNumber());
186+
}
187+
}
188+
}
189+
190+
auto req = this->createRequest(url, headers);
191+
auto* reply = this->mManager->deleteResource(req);
192+
193+
this->connectReply(reply, callback, timeout);
194+
}
195+
196+
void HttpClient::head(const QString& url, const QJSValue& options, const QJSValue& callback) {
197+
QJSValue headers;
198+
int timeout = -1;
199+
200+
if (options.isObject()) {
201+
if (options.hasOwnProperty("headers")) {
202+
headers = options.property("headers");
203+
}
204+
if (options.hasOwnProperty("timeout")) {
205+
auto timeoutProp = options.property("timeout");
206+
if (timeoutProp.isNumber()) {
207+
timeout = static_cast<int>(timeoutProp.toNumber());
208+
}
209+
}
210+
}
211+
212+
auto req = this->createRequest(url, headers);
213+
auto* reply = this->mManager->head(req);
214+
215+
this->connectReply(reply, callback, timeout);
216+
}
217+
218+
} // namespace qs::http

src/http/client.hpp

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#pragma once
2+
3+
#include <qcontainerfwd.h>
4+
#include <qjsvalue.h>
5+
#include <qnetworkaccessmanager.h>
6+
#include <qnetworkreply.h>
7+
#include <qnetworkrequest.h>
8+
#include <qobject.h>
9+
#include <qqmlintegration.h>
10+
#include <qtmetamacros.h>
11+
12+
namespace qs::http {
13+
14+
class HttpClient: public QObject {
15+
Q_OBJECT;
16+
QML_ELEMENT;
17+
QML_SINGLETON;
18+
19+
public:
20+
explicit HttpClient(QObject* parent = nullptr);
21+
22+
QNetworkRequest createRequest(const QString& url, const QJSValue& headers);
23+
void connectReply(QNetworkReply* reply, const QJSValue& callback, int timeout);
24+
25+
Q_INVOKABLE void request(
26+
const QString& url,
27+
const QString& verb,
28+
const QJSValue& options,
29+
const QJSValue& callback = QJSValue()
30+
);
31+
Q_INVOKABLE void
32+
get(const QString& url, const QJSValue& options, const QJSValue& callback = QJSValue());
33+
Q_INVOKABLE void post(
34+
const QString& url,
35+
const QVariant& body,
36+
const QJSValue& options,
37+
const QJSValue& callback = QJSValue()
38+
);
39+
Q_INVOKABLE void
40+
put(const QString& url,
41+
const QVariant& body,
42+
const QJSValue& options,
43+
const QJSValue& callback = QJSValue());
44+
// cannot use delete since it's a reserved keyword
45+
Q_INVOKABLE void
46+
del(const QString& url, const QJSValue& options, const QJSValue& callback = QJSValue());
47+
Q_INVOKABLE void
48+
head(const QString& url, const QJSValue& options, const QJSValue& callback = QJSValue());
49+
50+
private:
51+
QNetworkAccessManager* mManager;
52+
};
53+
54+
} // namespace qs::http

src/http/modules.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
name = "Quickshell.Http"
2+
description = "HTTP fetch API"
3+
headers = [
4+
"client.hpp",
5+
"response.hpp",
6+
]
7+
-----
8+
Quickshell's HTTP module.

src/http/response.cpp

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#include "response.hpp"
2+
3+
#include <qcontainerfwd.h>
4+
#include <qjsonarray.h>
5+
#include <qjsondocument.h>
6+
#include <qjsonparseerror.h>
7+
#include <qjsonvalue.h>
8+
#include <qnetworkreply.h>
9+
#include <qnetworkrequest.h>
10+
#include <qobject.h>
11+
#include <qqmlinfo.h>
12+
13+
namespace qs::http {
14+
15+
HttpResponse::HttpResponse(QNetworkReply* reply, QObject* parent): QObject(parent) {
16+
this->mStatus = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
17+
this->mStatusText = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toString();
18+
this->mUrl = reply->url();
19+
this->mData = reply->readAll();
20+
21+
auto headersList = reply->rawHeaderList();
22+
for (auto& header: headersList) {
23+
this->mHeadersMap.insert(
24+
QString::fromUtf8(header),
25+
QString::fromUtf8(reply->rawHeader(header))
26+
);
27+
}
28+
}
29+
30+
QUrl HttpResponse::url() { return this->mUrl; }
31+
32+
QString HttpResponse::text() { return QString::fromUtf8(this->mData); }
33+
34+
QJsonValue HttpResponse::json() {
35+
QJsonParseError error;
36+
auto json = QJsonDocument::fromJson(this->mData, &error);
37+
38+
if (error.error != QJsonParseError::NoError) {
39+
qmlWarning(this) << "Failed to deserialize json: " << error.errorString();
40+
return QJsonValue::Undefined;
41+
}
42+
43+
if (json.isArray()) {
44+
return json.array();
45+
}
46+
47+
return json.object();
48+
}
49+
50+
QByteArray HttpResponse::arrayBuffer() { return this->mData; }
51+
52+
QVariantMap HttpResponse::headers() { return this->mHeadersMap; }
53+
54+
QString HttpResponse::header(const QString& name) {
55+
return this->mHeadersMap.value(name).toString();
56+
}
57+
58+
} // namespace qs::http

0 commit comments

Comments
 (0)