Line data Source code
1 : /**
2 : Copyright (c) 2023 Stappler LLC <admin@stappler.dev>
3 :
4 : Permission is hereby granted, free of charge, to any person obtaining a copy
5 : of this software and associated documentation files (the "Software"), to deal
6 : in the Software without restriction, including without limitation the rights
7 : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 : copies of the Software, and to permit persons to whom the Software is
9 : furnished to do so, subject to the following conditions:
10 :
11 : The above copyright notice and this permission notice shall be included in
12 : all copies or substantial portions of the Software.
13 :
14 : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 : THE SOFTWARE.
21 : **/
22 :
23 : #include "XLNetworkController.h"
24 : #include "SPNetworkContext.h"
25 : #include "XLNetworkRequest.h"
26 :
27 : namespace STAPPLER_VERSIONIZED stappler::xenolith::network {
28 :
29 : struct ControllerHandle {
30 : using Context = stappler::network::Context<Interface>;
31 :
32 : Rc<Request> request;
33 : Handle *handle;
34 : Context context;
35 : };
36 :
37 : struct Controller::Data final : thread::ThreadInterface<Interface> {
38 : using Context = stappler::network::Context<Interface>;
39 :
40 : Application *_application = nullptr;
41 : Controller *_controller = nullptr;
42 : String _name;
43 : Bytes _signKey;
44 :
45 : std::thread _thread;
46 :
47 : Mutex _mutexQueue;
48 : Mutex _mutexFree;
49 :
50 : CURLM *_handle = nullptr;
51 :
52 : memory::PriorityQueue<Rc<Request>> _pending;
53 :
54 : std::atomic_flag _shouldQuit;
55 : Map<String, void *> _sharegroups;
56 :
57 : Map<CURL *, ControllerHandle> _handles;
58 : NetworkCapabilities _capabilities = NetworkCapabilities::None;
59 :
60 : Data(Application *app, Controller *c, StringView name, Bytes &&signKey);
61 : virtual ~Data();
62 :
63 : bool init();
64 : void invalidate();
65 :
66 : virtual void threadInit() override;
67 : virtual bool worker() override;
68 : virtual void threadDispose() override;
69 :
70 : void *getSharegroup(StringView);
71 :
72 : void onUploadProgress(Handle *, int64_t total, int64_t now);
73 : void onDownloadProgress(Handle *, int64_t total, int64_t now);
74 : bool onComplete(Handle *, bool success);
75 :
76 : void sign(NetworkHandle &, Context &) const;
77 :
78 : void pushTask(Rc<Request> &&handle);
79 : void wakeup();
80 :
81 : bool prepare(Handle &handle, Context *ctx, const Callback<bool(CURL *)> &onBeforePerform);
82 : bool finalize(Handle &handle, Context *ctx, const Callback<bool(CURL *)> &onAfterPerform);
83 : };
84 :
85 : SPUNUSED static void registerNetworkCallback(Application *, void *, Function<void(NetworkCapabilities)> &&);
86 : SPUNUSED static void unregisterNetworkCallback(Application *, void *);
87 :
88 : XL_DECLARE_EVENT(Controller, "network::Controller", onNetworkCapabilities);
89 :
90 21 : Controller::Data::Data(Application *app, Controller *c, StringView name, Bytes &&signKey)
91 21 : : _application(app), _controller(c), _name(name.str<Interface>()), _signKey(move(signKey)) {
92 :
93 21 : }
94 :
95 42 : Controller::Data::~Data() {
96 42 : }
97 :
98 21 : bool Controller::Data::init() {
99 21 : registerNetworkCallback(_application, this, [this] (NetworkCapabilities cap) {
100 21 : _application->performOnMainThread([this, cap] {
101 21 : _capabilities = cap;
102 21 : Controller::onNetworkCapabilities(_controller, int64_t(toInt(_capabilities)));
103 21 : }, this);
104 21 : });
105 21 : _thread = std::thread(Controller::Data::workerThread, this, nullptr);
106 21 : return true;
107 : }
108 :
109 0 : void Controller::Data::invalidate() {
110 0 : unregisterNetworkCallback(_application, this);
111 0 : }
112 :
113 21 : void Controller::Data::threadInit() {
114 21 : _shouldQuit.test_and_set();
115 21 : _pending.setQueueLocking(_mutexQueue);
116 21 : _pending.setFreeLocking(_mutexFree);
117 :
118 21 : thread::ThreadInfo::setThreadInfo(_name);
119 :
120 21 : _handle = curl_multi_init();
121 21 : }
122 :
123 1382 : bool Controller::Data::worker() {
124 2764 : if (!_shouldQuit.test_and_set()) {
125 21 : return false;
126 : }
127 :
128 : do {
129 1361 : if (!_pending.pop_direct([&, this] (memory::PriorityQueue<Rc<Handle>>::PriorityType type, Rc<Request> &&it) {
130 21 : auto h = curl_easy_init();
131 21 : auto networkHandle = const_cast<Handle *>(&it->getHandle());
132 21 : auto i = _handles.emplace(h, ControllerHandle{move(it), networkHandle}).first;
133 :
134 21 : auto sg = i->second.handle->getSharegroup();
135 21 : if (!sg.empty()) {
136 0 : i->second.context.share = getSharegroup(sg);
137 : }
138 :
139 21 : i->second.context.userdata = this;
140 21 : i->second.context.curl = h;
141 21 : i->second.context.origHandle = networkHandle;
142 :
143 21 : i->second.context.origHandle->setDownloadProgress([this, h = networkHandle] (int64_t total, int64_t now) -> int {
144 21 : onDownloadProgress(h, total, now);
145 21 : return 0;
146 : });
147 :
148 21 : i->second.context.origHandle->setUploadProgress([this, h = networkHandle] (int64_t total, int64_t now) -> int {
149 0 : onUploadProgress(h, total, now);
150 0 : return 0;
151 : });
152 :
153 21 : if (i->second.handle->shouldSignRequest()) {
154 0 : sign(*networkHandle, i->second.context);
155 : }
156 :
157 21 : prepare(*networkHandle, &i->second.context, nullptr);
158 :
159 21 : curl_multi_add_handle(reinterpret_cast<CURLM *>(_handle), h);
160 21 : })) {
161 1340 : break;
162 : }
163 : } while (0);
164 :
165 1361 : int running = 0;
166 1361 : auto err = curl_multi_perform(reinterpret_cast<CURLM *>(_handle), &running);
167 1361 : if (err != CURLM_OK) {
168 0 : log::error("CURL", toString("Fail to perform multi: ", err));
169 0 : return false;
170 : }
171 :
172 1361 : int timeout = 16;
173 1361 : if (running == 0) {
174 193 : timeout = 1000;
175 : }
176 :
177 1361 : err = curl_multi_poll(reinterpret_cast<CURLM *>(_handle), NULL, 0, timeout, nullptr);
178 1361 : if (err != CURLM_OK) {
179 0 : log::error("CURL", toString("Fail to poll multi: ", err));
180 0 : return false;
181 : }
182 :
183 1361 : struct CURLMsg *msg = nullptr;
184 : do {
185 1382 : int msgq = 0;
186 1382 : msg = curl_multi_info_read(reinterpret_cast<CURLM *>(_handle), &msgq);
187 1382 : if (msg && (msg->msg == CURLMSG_DONE)) {
188 21 : CURL *e = msg->easy_handle;
189 21 : curl_multi_remove_handle(reinterpret_cast<CURLM *>(_handle), e);
190 :
191 21 : auto it = _handles.find(e);
192 21 : if (it != _handles.end()) {
193 21 : it->second.context.code = msg->data.result;
194 21 : auto ret = finalize(*it->second.handle, &it->second.context, nullptr);
195 21 : if (!onComplete(it->second.handle, ret)) {
196 0 : _handles.erase(it);
197 0 : return false;
198 : }
199 21 : _handles.erase(it);
200 : }
201 :
202 21 : curl_easy_cleanup(e);
203 : }
204 1382 : } while (msg);
205 :
206 1361 : return true;
207 : }
208 :
209 21 : void Controller::Data::threadDispose() {
210 21 : if (_handle) {
211 21 : for (auto &it : _handles) {
212 0 : curl_multi_remove_handle(reinterpret_cast<CURLM *>(_handle), it.first);
213 0 : it.second.context.code = CURLE_FAILED_INIT;
214 0 : finalize(*it.second.handle, &it.second.context, nullptr);
215 0 : curl_easy_cleanup(it.first);
216 : }
217 :
218 21 : curl_multi_cleanup(reinterpret_cast<CURLM *>(_handle));
219 :
220 21 : for (auto &it : _sharegroups) {
221 0 : curl_share_cleanup((CURLSH *)it.second);
222 : }
223 :
224 21 : _handles.clear();
225 21 : _sharegroups.clear();
226 :
227 21 : _handle = nullptr;
228 : }
229 21 : }
230 :
231 0 : void *Controller::Data::getSharegroup(StringView name) {
232 0 : auto it = _sharegroups.find(name);
233 0 : if (it != _sharegroups.end()) {
234 0 : return it->second;
235 : }
236 :
237 0 : auto sharegroup = curl_share_init();
238 0 : curl_share_setopt(reinterpret_cast<CURLSH *>(sharegroup), CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE);
239 0 : curl_share_setopt(reinterpret_cast<CURLSH *>(sharegroup), CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);
240 0 : curl_share_setopt(reinterpret_cast<CURLSH *>(sharegroup), CURLSHOPT_SHARE, CURL_LOCK_DATA_PSL);
241 :
242 0 : _sharegroups.emplace(name.str<Interface>(), sharegroup);
243 0 : return sharegroup;
244 : }
245 :
246 0 : void Controller::Data::onUploadProgress(Handle *handle, int64_t total, int64_t now) {
247 0 : _application->performOnMainThread([handle, total, now] {
248 0 : auto req = handle->getReqeust();
249 0 : req->notifyOnUploadProgress(total, now);
250 0 : });
251 0 : _application->wakeup();
252 0 : }
253 :
254 21 : void Controller::Data::onDownloadProgress(Handle *handle, int64_t total, int64_t now) {
255 21 : _application->performOnMainThread([handle, total, now] {
256 21 : auto req = handle->getReqeust();
257 21 : req->notifyOnDownloadProgress(total, now);
258 21 : });
259 21 : _application->wakeup();
260 21 : }
261 :
262 21 : bool Controller::Data::onComplete(Handle *handle, bool success) {
263 21 : _application->performOnMainThread([handle, success] {
264 21 : auto req = handle->getReqeust();
265 21 : req->notifyOnComplete(success);
266 21 : });
267 :
268 21 : _application->wakeup();
269 21 : return true;
270 : }
271 :
272 0 : void Controller::Data::sign(NetworkHandle &handle, Context &ctx) const {
273 0 : String date = Time::now().toHttp<Interface>();
274 0 : StringStream message;
275 :
276 0 : message << handle.getUrl() << "\r\n";
277 0 : message << "X-ApplicationName: " << _application->getInfo().bundleName << "\r\n";
278 0 : message << "X-ApplicationVersion: " << _application->getInfo().applicationVersionCode << "\r\n";
279 0 : message << "X-ClientDate: " << date << "\r\n";
280 0 : message << "User-Agent: " << _application->getInfo().userAgent << "\r\n";
281 :
282 0 : auto msg = message.str();
283 0 : auto sig = string::Sha512::hmac(msg, _signKey);
284 :
285 0 : ctx.headers = curl_slist_append(ctx.headers, toString("X-ClientDate: ", date).data());
286 0 : ctx.headers = curl_slist_append(ctx.headers, toString("X-Stappler-Sign: ", base64url::encode<Interface>(sig)).data());
287 :
288 0 : if (!_application->getInfo().userAgent.empty()) {
289 0 : handle.setUserAgent(_application->getInfo().userAgent);
290 : }
291 0 : }
292 :
293 21 : void Controller::Data::pushTask(Rc<Request> &&handle) {
294 21 : _pending.push(0, false, move(handle));
295 21 : curl_multi_wakeup(_handle);
296 21 : }
297 :
298 0 : void Controller::Data::wakeup() {
299 0 : curl_multi_wakeup(_handle);
300 0 : }
301 :
302 21 : bool Controller::Data::prepare(Handle &handle, Context *ctx, const Callback<bool(CURL *)> &onBeforePerform) {
303 21 : if (!handle.prepare(ctx)) {
304 0 : return false;
305 : }
306 :
307 21 : return stappler::network::prepare(*handle.getData(), ctx, onBeforePerform);
308 : }
309 :
310 21 : bool Controller::Data::finalize(Handle &handle, Context *ctx, const Callback<bool(CURL *)> &onAfterPerform) {
311 21 : auto ret = stappler::network::finalize(*handle.getData(), ctx, onAfterPerform);
312 21 : return handle.finalize(ctx, ret);
313 : }
314 :
315 21 : Controller::Controller(Application *app, StringView name, Bytes &&signKey) {
316 21 : _data = new Data(app, this, name, move(signKey));
317 21 : _data->init();
318 21 : }
319 :
320 42 : Controller::~Controller() {
321 21 : _data->_shouldQuit.clear();
322 21 : curl_multi_wakeup(_data->_handle);
323 21 : _data->_thread.join();
324 21 : delete _data;
325 42 : }
326 :
327 21 : void Controller::initialize(Application *) {
328 :
329 21 : }
330 :
331 21 : void Controller::invalidate(Application *) {
332 :
333 21 : }
334 :
335 3846 : void Controller::update(Application *, const UpdateTime &t) {
336 :
337 3846 : }
338 :
339 42 : Application *Controller::getApplication() const {
340 42 : return _data->_application;
341 : }
342 :
343 0 : StringView Controller::getName() const {
344 0 : return _data->_name;
345 : }
346 :
347 21 : void Controller::run(Rc<Request> &&handle) {
348 21 : _data->pushTask(move(handle));
349 21 : }
350 :
351 0 : void Controller::setSignKey(Bytes &&value) {
352 :
353 0 : }
354 :
355 0 : bool Controller::isNetworkOnline() const {
356 0 : return (_data->_capabilities & NetworkCapabilities::Internet) != NetworkCapabilities::None;
357 : }
358 :
359 0 : NetworkCapabilities Controller::getNetworkCapabilities() const {
360 0 : return _data->_capabilities;
361 : }
362 :
363 :
364 : }
|