Skip to content

Commit ca7d842

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

File tree

7 files changed

+352
-0
lines changed

7 files changed

+352
-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: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
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 <qurl.h>
12+
13+
#include "response.hpp"
14+
15+
namespace qs::http {
16+
17+
HttpClient::HttpClient(QObject* parent)
18+
: QObject(parent)
19+
, mManager(new QNetworkAccessManager(this)) {}
20+
21+
QNetworkRequest HttpClient::createRequest(const QString& url, const QJSValue& headers) {
22+
QNetworkRequest req;
23+
24+
req.setUrl(QUrl(url));
25+
26+
if (headers.isObject()) {
27+
QJSValueIterator iter(headers);
28+
while (iter.hasNext()) {
29+
iter.next();
30+
req.setRawHeader(iter.name().toUtf8(), iter.value().toString().toUtf8());
31+
}
32+
}
33+
34+
return req;
35+
}
36+
37+
void HttpClient::connectReply(QNetworkReply* reply, const QJSValue& callback) {
38+
QJSEngine* engine = qjsEngine(this);
39+
40+
if (!engine) {
41+
return;
42+
}
43+
if (callback.isCallable()) {
44+
connect(reply, &QNetworkReply::finished, this, [reply, engine, callback]() mutable {
45+
reply->deleteLater();
46+
auto* resp = new HttpResponse(reply);
47+
auto jsResp = engine->newQObject(resp);
48+
QJSEngine::setObjectOwnership(resp, QJSEngine::JavaScriptOwnership);
49+
50+
callback.call(QJSValueList() << jsResp);
51+
});
52+
}
53+
}
54+
55+
void HttpClient::request(
56+
const QString& url,
57+
const QString& verb,
58+
const QJSValue& options,
59+
const QJSValue& callback
60+
) {
61+
QJSValue headers;
62+
QByteArray body;
63+
64+
if (options.isObject()) {
65+
if (options.hasOwnProperty("headers")) {
66+
headers = options.property("headers");
67+
}
68+
if (options.hasOwnProperty("body")) {
69+
auto bodyProp = options.property("body");
70+
body = bodyProp.toVariant().toByteArray();
71+
}
72+
}
73+
74+
auto req = this->createRequest(url, headers);
75+
auto* reply = this->mManager->sendCustomRequest(req, verb.toUtf8(), body);
76+
77+
this->connectReply(reply, callback);
78+
}
79+
80+
void HttpClient::get(const QString& url, const QJSValue& options, const QJSValue& callback) {
81+
QJSValue headers;
82+
83+
if (options.isObject()) {
84+
if (options.hasOwnProperty("headers")) {
85+
headers = options.property("headers");
86+
}
87+
}
88+
89+
auto req = this->createRequest(url, headers);
90+
auto* reply = this->mManager->get(req);
91+
92+
this->connectReply(reply, callback);
93+
}
94+
95+
void HttpClient::post(
96+
const QString& url,
97+
const QVariant& body,
98+
const QJSValue& options,
99+
const QJSValue& callback
100+
) {
101+
auto bodyBytes = body.toByteArray();
102+
QJSValue headers;
103+
104+
if (options.isObject()) {
105+
if (options.hasOwnProperty("headers")) {
106+
headers = options.property("headers");
107+
}
108+
}
109+
110+
auto req = this->createRequest(url, headers);
111+
auto* reply = this->mManager->post(req, bodyBytes);
112+
113+
this->connectReply(reply, callback);
114+
}
115+
116+
void HttpClient::put(
117+
const QString& url,
118+
const QVariant& body,
119+
const QJSValue& options,
120+
const QJSValue& callback
121+
) {
122+
auto bodyBytes = body.toByteArray();
123+
QJSValue headers;
124+
125+
if (options.isObject()) {
126+
if (options.hasOwnProperty("headers")) {
127+
headers = options.property("headers");
128+
}
129+
}
130+
131+
auto req = this->createRequest(url, headers);
132+
auto* reply = this->mManager->put(req, bodyBytes);
133+
134+
this->connectReply(reply, callback);
135+
}
136+
137+
void HttpClient::del(const QString& url, const QJSValue& options, const QJSValue& callback) {
138+
QJSValue headers;
139+
140+
if (options.isObject()) {
141+
if (options.hasOwnProperty("headers")) {
142+
headers = options.property("headers");
143+
}
144+
}
145+
146+
auto req = this->createRequest(url, headers);
147+
auto* reply = this->mManager->deleteResource(req);
148+
149+
this->connectReply(reply, callback);
150+
}
151+
152+
void HttpClient::head(const QString& url, const QJSValue& options, const QJSValue& callback) {
153+
QJSValue headers;
154+
155+
if (options.isObject()) {
156+
if (options.hasOwnProperty("headers")) {
157+
headers = options.property("headers");
158+
}
159+
}
160+
161+
auto req = this->createRequest(url, headers);
162+
auto* reply = this->mManager->head(req);
163+
164+
this->connectReply(reply, callback);
165+
}
166+
167+
} // 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);
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 <qnetworkrequest.h>
9+
#include <qobject.h>
10+
#include <qqmlinfo.h>
11+
12+
namespace qs::http {
13+
14+
HttpResponse::HttpResponse(QNetworkReply* reply, QObject* parent): QObject(parent) {
15+
if (reply) {
16+
mStatus = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
17+
mUrl = reply->url();
18+
mData = reply->readAll();
19+
20+
auto headersList = reply->rawHeaderList();
21+
for (auto& header: headersList) {
22+
this->mHeadersMap.insert(
23+
QString::fromUtf8(header),
24+
QString::fromUtf8(reply->rawHeader(header))
25+
);
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

src/http/response.hpp

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#pragma once
2+
3+
#include <qcontainerfwd.h>
4+
#include <qjsonobject.h>
5+
#include <qjsonvalue.h>
6+
#include <qnetworkreply.h>
7+
#include <qobject.h>
8+
#include <qqmlintegration.h>
9+
#include <qstringview.h>
10+
#include <qtmetamacros.h>
11+
#include <qurl.h>
12+
13+
namespace qs::http {
14+
15+
class HttpResponse: public QObject {
16+
Q_OBJECT;
17+
QML_UNCREATABLE("HttpResponse can only be created by HttpClient");
18+
Q_PROPERTY(int status READ status CONSTANT);
19+
20+
public:
21+
explicit HttpResponse(QNetworkReply* reply, QObject* parent = nullptr);
22+
23+
Q_INVOKABLE [[nodiscard]] bool success() const {
24+
return this->mStatus >= 200 && this->mStatus < 300;
25+
}
26+
Q_INVOKABLE [[nodiscard]] QUrl url();
27+
Q_INVOKABLE [[nodiscard]] QString text();
28+
Q_INVOKABLE [[nodiscard]] QJsonValue json();
29+
Q_INVOKABLE [[nodiscard]] QByteArray arrayBuffer();
30+
Q_INVOKABLE [[nodiscard]] QVariantMap headers();
31+
Q_INVOKABLE [[nodiscard]] QString header(const QString& name);
32+
33+
[[nodiscard]] int status() const { return this->mStatus; }
34+
35+
private:
36+
int mStatus;
37+
QUrl mUrl;
38+
QByteArray mData;
39+
QVariantMap mHeadersMap;
40+
};
41+
42+
} // namespace qs::http

0 commit comments

Comments
 (0)