1 /*
2 * Copyright (c) 2023 Unionman Technology Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "JQHttpServer.h"
17
18 // Qt lib import
19 #include <QBuffer>
20 #include <QEventLoop>
21 #include <QFile>
22 #include <QImage>
23 #include <QJsonDocument>
24 #include <QJsonValue>
25 #include <QMetaObject>
26 #include <QPointer>
27 #include <QSemaphore>
28 #include <QThread>
29 #include <QThreadPool>
30 #include <QTimer>
31
32 #include <QtConcurrent>
33
34 #include <QLocalServer>
35 #include <QLocalSocket>
36 #include <QTcpServer>
37 #include <QTcpSocket>
38 #ifndef QT_NO_SSL
39 #include <QSslCertificate>
40 #include <QSslConfiguration>
41 #include <QSslKey>
42 #include <QSslSocket>
43 #endif
44
45 #define JQHTTPSERVER_SESSION_PROTECTION(functionName, ...) \
46 auto thisP = this; \
47 if (!(thisP) || ((contentLength_ < -1) || (waitWrittenByteCount_ < -1))) { \
48 qDebug() << QStringLiteral("JQHttpServer::Session::") + functionName + ": current session this is null"; \
49 return __VA_ARGS__; \
50 }
51
52 static QString replyTextFormat("HTTP/1.1 %1 OK\r\n"
53 "Content-Type: %2\r\n"
54 "Content-Length: %3\r\n"
55 "Access-Control-Allow-Origin: *\r\n"
56 "\r\n"
57 "%4");
58
59 static QString replyRedirectsFormat("HTTP/1.1 %1 OK\r\n"
60 "Content-Type: %2\r\n"
61 "Content-Length: %3\r\n"
62 "Access-Control-Allow-Origin: *\r\n"
63 "\r\n"
64 "%4");
65
66 static QString replyFileFormat("HTTP/1.1 %1 OK\r\n"
67 "Content-Disposition: attachment;filename=%2\r\n"
68 "Content-Length: %3\r\n"
69 "Access-Control-Allow-Origin: *\r\n"
70 "\r\n");
71
72 static QString replyImageFormat("HTTP/1.1 %1\r\n"
73 "Content-Type: image\r\n"
74 "Content-Length: %2\r\n"
75 "Access-Control-Allow-Origin: *\r\n"
76 "\r\n");
77
78 static QString replyBytesFormat("HTTP/1.1 %1 OK\r\n"
79 "Content-Type: application/octet-stream\r\n"
80 "Content-Length: %2\r\n"
81 "Access-Control-Allow-Origin: *\r\n"
82 "\r\n");
83
84 static QString replyOptionsFormat("HTTP/1.1 200 OK\r\n"
85 "Allow: OPTIONS, GET, POST, PUT, HEAD\r\n"
86 "Access-Control-Allow-Methods: OPTIONS, GET, POST, PUT, HEAD\r\n"
87 "Content-Length: 0\r\n"
88 "Access-Control-Allow-Origin: *\r\n"
89 "\r\n");
90
91 // Session
Session(const QPointer<QIODevice> & tcpSocket)92 JQHttpServer::Session::Session(const QPointer<QIODevice>& tcpSocket)
93 :ioDevice_(tcpSocket), timerForClose_(new QTimer)
94 {
95 timerForClose_->setInterval(30L * 1000L);
96
97 connect(ioDevice_.data(), &QIODevice::readyRead, [this]() {
98 if (this->timerForClose_->isActive()) {
99 timerForClose_->stop();
100 }
101
102 const auto && data = this->ioDevice_->readAll();
103
104 this->buffer_.append(data);
105
106 this->InspectionBufferSetup1();
107
108 timerForClose_->start();
109 });
110
111 connect(ioDevice_.data(), &QIODevice::bytesWritten, [this](const qint64& bytes) {
112 this->waitWrittenByteCount_ -= bytes;
113
114 if (this->waitWrittenByteCount_ == 0) {
115 this->deleteLater();
116 return;
117 }
118
119 if (!ioDeviceForReply_.isNull()) {
120 if (ioDeviceForReply_->atEnd()) {
121 ioDeviceForReply_->deleteLater();
122 ioDeviceForReply_.clear();
123 } else {
124 ioDevice_->write(ioDeviceForReply_->read(512L * 1024L));
125 }
126 }
127
128 if (this->timerForClose_->isActive()) {
129 timerForClose_->stop();
130 }
131
132 timerForClose_->start();
133 });
134
135 connect(timerForClose_.data(), &QTimer::timeout, this, &QObject::deleteLater);
136 }
137
~Session()138 JQHttpServer::Session::~Session()
139 {
140 if (!ioDevice_.isNull()) {
141 delete ioDevice_.data();
142 }
143 }
144
requestUrlPath() const145 QString JQHttpServer::Session::requestUrlPath() const
146 {
147 JQHTTPSERVER_SESSION_PROTECTION("requestUrlPath", {});
148 QString result;
149 const auto indexForQueryStart = requestUrl_.indexOf("?");
150 if (indexForQueryStart >= 0) {
151 result = requestUrl_.mid(0, indexForQueryStart);
152 } else {
153 result = requestUrl_;
154 }
155
156 if (result.startsWith("//")) {
157 result = result.mid(1);
158 }
159
160 if (result.endsWith("/")) {
161 result = result.mid(0, result.size() - 1);
162 }
163
164 result.replace("%5B", "[");
165 result.replace("%5D", "]");
166 result.replace("%7B", "{");
167 result.replace("%7D", "}");
168 result.replace("%5E", "^");
169 return result;
170 }
171
requestUrlPathSplitToList() const172 QStringList JQHttpServer::Session::requestUrlPathSplitToList() const
173 {
174 auto list = this->requestUrlPath().split("/");
175
176 while (!list.isEmpty() && list.first().isEmpty()) {
177 list.pop_front();
178 }
179
180 while (!list.isEmpty() && list.last().isEmpty()) {
181 list.pop_back();
182 }
183
184 return list;
185 }
186
requestUrlQuery() const187 QMap<QString, QString> JQHttpServer::Session::requestUrlQuery() const
188 {
189 const auto indexForQueryStart = requestUrl_.indexOf("?");
190 if (indexForQueryStart < 0) {
191 return {};
192 }
193
194 QMap<QString, QString> result;
195
196 auto lines = QUrl::fromEncoded(requestUrl_.mid(indexForQueryStart + 1).toUtf8()).toString().split("&");
197
198 for (const auto& line_ : lines) {
199 auto line = line_;
200 line.replace("%5B", "[");
201 line.replace("%5D", "]");
202 line.replace("%7B", "{");
203 line.replace("%7D", "}");
204 line.replace("%5E", "^");
205 qDebug() << "line_:" << line_;
206 auto indexOf = line.indexOf("=");
207 if (indexOf > 0) {
208 result[line.mid(0, indexOf)] = line.mid(indexOf + 1);
209 }
210 }
211
212 return result;
213 }
214
ReplyText(const QString & replyData,const int & httpStatusCode)215 void JQHttpServer::Session::ReplyText(const QString& replyData, const int& httpStatusCode)
216 {
217 JQHTTPSERVER_SESSION_PROTECTION("replyText");
218
219 if (alreadyReply_) {
220 qDebug() << "JQHttpServer::Session::replyText: already reply";
221 return;
222 }
223
224 if (QThread::currentThread() != this->thread()) {
225 QMetaObject::invokeMethod(
226 this, "replyText", Qt::QueuedConnection, Q_ARG(QString, replyData), Q_ARG(int, httpStatusCode));
227 return;
228 }
229
230 alreadyReply_ = true;
231
232 if (ioDevice_.isNull()) {
233 qDebug() << "JQHttpServer::Session::replyText: error1";
234 this->deleteLater();
235 return;
236 }
237
238 const auto && data = replyTextFormat
239 .arg(QString::number(httpStatusCode), "text;charset=UTF-8",
240 QString::number(replyData.toUtf8().size()), replyData)
241 .toUtf8();
242
243 waitWrittenByteCount_ = data.size();
244 ioDevice_->write(data);
245 }
246
ReplyRedirects(const QUrl & targetUrl,const int & httpStatusCode)247 void JQHttpServer::Session::ReplyRedirects(const QUrl& targetUrl, const int& httpStatusCode)
248 {
249 JQHTTPSERVER_SESSION_PROTECTION("replyRedirects");
250
251 if (alreadyReply_) {
252 qDebug() << "JQHttpServer::Session::replyRedirects: already reply";
253 return;
254 }
255
256 if (QThread::currentThread() != this->thread()) {
257 QMetaObject::invokeMethod(
258 this, "replyRedirects", Qt::QueuedConnection, Q_ARG(QUrl, targetUrl), Q_ARG(int, httpStatusCode));
259 return;
260 }
261
262 alreadyReply_ = true;
263
264 if (ioDevice_.isNull()) {
265 qDebug() << "JQHttpServer::Session::replyRedirects: error1";
266 this->deleteLater();
267 return;
268 }
269
270 const auto && buffer
271 = QString("<head>\n<meta http-equiv=\"refresh\" content=\"0;URL=%1/\" />\n</head>").arg(targetUrl.toString());
272
273 const auto && data = replyRedirectsFormat
274 .arg(QString::number(httpStatusCode), "text;charset=UTF-8",
275 QString::number(buffer.toUtf8().size()), buffer)
276 .toUtf8();
277
278 waitWrittenByteCount_ = data.size();
279 ioDevice_->write(data);
280 }
281
ReplyJsonObject(const QJsonObject & jsonObject,const int & httpStatusCode)282 void JQHttpServer::Session::ReplyJsonObject(const QJsonObject& jsonObject, const int& httpStatusCode)
283 {
284 JQHTTPSERVER_SESSION_PROTECTION("replyJsonObject");
285
286 if (alreadyReply_) {
287 qDebug() << "JQHttpServer::Session::replyJsonObject: already reply";
288 return;
289 }
290
291 if (QThread::currentThread() != this->thread()) {
292 QMetaObject::invokeMethod(
293 this, "replyJsonObject", Qt::QueuedConnection, Q_ARG(QJsonObject, jsonObject), Q_ARG(int, httpStatusCode));
294 return;
295 }
296
297 alreadyReply_ = true;
298
299 if (ioDevice_.isNull()) {
300 qDebug() << "JQHttpServer::Session::replyJsonObject: error1";
301 this->deleteLater();
302 return;
303 }
304
305 const auto && data = QJsonDocument(jsonObject).toJson(QJsonDocument::Compact);
306 const auto && data2 = replyTextFormat
307 .arg(QString::number(httpStatusCode), "application/json;charset=UTF-8",
308 QString::number(data.size()), QString(data))
309 .toUtf8();
310
311 waitWrittenByteCount_ = data2.size();
312 ioDevice_->write(data2);
313 }
314
ReplyJsonArray(const QJsonArray & jsonArray,const int & httpStatusCode)315 void JQHttpServer::Session::ReplyJsonArray(const QJsonArray& jsonArray, const int& httpStatusCode)
316 {
317 JQHTTPSERVER_SESSION_PROTECTION("replyJsonArray");
318
319 if (alreadyReply_) {
320 qDebug() << "JQHttpServer::Session::replyJsonArray: already reply";
321 return;
322 }
323
324 if (QThread::currentThread() != this->thread()) {
325 QMetaObject::invokeMethod(
326 this, "replyJsonArray", Qt::QueuedConnection, Q_ARG(QJsonArray, jsonArray), Q_ARG(int, httpStatusCode));
327 return;
328 }
329
330 alreadyReply_ = true;
331
332 if (ioDevice_.isNull()) {
333 qDebug() << "JQHttpServer::Session::replyJsonArray: error1";
334 this->deleteLater();
335 return;
336 }
337
338 const auto && data = QJsonDocument(jsonArray).toJson(QJsonDocument::Compact);
339 const auto && data2 = replyTextFormat
340 .arg(QString::number(httpStatusCode), "application/json;charset=UTF-8",
341 QString::number(data.size()), QString(data))
342 .toUtf8();
343
344 waitWrittenByteCount_ = data2.size();
345 ioDevice_->write(data2);
346 }
347
ReplyFile(const QString & filePath,const int & httpStatusCode)348 void JQHttpServer::Session::ReplyFile(const QString& filePath, const int& httpStatusCode)
349 {
350 JQHTTPSERVER_SESSION_PROTECTION("replyFile");
351
352 if (alreadyReply_) {
353 qDebug() << "JQHttpServer::Session::replyFile: already reply";
354 return;
355 }
356
357 if (QThread::currentThread() != this->thread()) {
358 QMetaObject::invokeMethod(
359 this, "replyFile", Qt::QueuedConnection, Q_ARG(QString, filePath), Q_ARG(int, httpStatusCode));
360 return;
361 }
362
363 alreadyReply_ = true;
364
365 if (ioDevice_.isNull()) {
366 qDebug() << "JQHttpServer::Session::replyFile: error1";
367 this->deleteLater();
368 return;
369 }
370
371 ioDeviceForReply_.reset(new QFile(filePath));
372 QPointer<QFile> file = (qobject_cast<QFile*>(ioDeviceForReply_.data()));
373
374 if (!file->open(QIODevice::ReadOnly)) {
375 qDebug() << "JQHttpServer::Session::replyFile: open file error:" << filePath;
376 ioDeviceForReply_.clear();
377 this->deleteLater();
378 return;
379 }
380
381 const auto && data
382 = replyFileFormat
383 .arg(QString::number(httpStatusCode), QFileInfo(filePath).fileName(), QString::number(file->size()))
384 .toUtf8();
385
386 waitWrittenByteCount_ = data.size() + file->size();
387 ioDevice_->write(data);
388 }
389
ReplyImage(const QImage & image,const int & httpStatusCode)390 void JQHttpServer::Session::ReplyImage(const QImage& image, const int& httpStatusCode)
391 {
392 JQHTTPSERVER_SESSION_PROTECTION("replyImage");
393
394 if (alreadyReply_) {
395 qDebug() << "JQHttpServer::Session::replyImage: already reply";
396 return;
397 }
398
399 if (QThread::currentThread() != this->thread()) {
400 QMetaObject::invokeMethod(
401 this, "replyImage", Qt::QueuedConnection, Q_ARG(QImage, image), Q_ARG(int, httpStatusCode));
402 return;
403 }
404
405 alreadyReply_ = true;
406
407 if (ioDevice_.isNull()) {
408 qDebug() << "JQHttpServer::Session::replyImage: error1";
409 this->deleteLater();
410 return;
411 }
412
413 auto buffer = new QBuffer;
414
415 if (!buffer->open(QIODevice::ReadWrite)) {
416 qDebug() << "JQHttpServer::Session::replyImage: open buffer error";
417 delete buffer;
418 this->deleteLater();
419 return;
420 }
421
422 if (!image.save(buffer, "PNG")) {
423 qDebug() << "JQHttpServer::Session::replyImage: save image to buffer error";
424 delete buffer;
425 this->deleteLater();
426 return;
427 }
428
429 ioDeviceForReply_.reset(buffer);
430 ioDeviceForReply_->seek(0);
431
432 const auto && data
433 = replyImageFormat.arg(QString::number(httpStatusCode), QString::number(buffer->buffer().size())).toUtf8();
434
435 waitWrittenByteCount_ = data.size() + buffer->buffer().size();
436 ioDevice_->write(data);
437 }
438
ReplyImage(const QString & imageFilePath,const int & httpStatusCode)439 void JQHttpServer::Session::ReplyImage(const QString& imageFilePath, const int& httpStatusCode)
440 {
441 JQHTTPSERVER_SESSION_PROTECTION("replyImage");
442
443 if (alreadyReply_) {
444 qDebug() << "JQHttpServer::Session::replyImage: already reply";
445 return;
446 }
447
448 if (QThread::currentThread() != this->thread()) {
449 QMetaObject::invokeMethod(
450 this, "replyImage", Qt::QueuedConnection, Q_ARG(QString, imageFilePath), Q_ARG(int, httpStatusCode));
451 return;
452 }
453
454 alreadyReply_ = true;
455
456 if (ioDevice_.isNull()) {
457 qDebug() << "JQHttpServer::Session::replyImage: error1";
458 this->deleteLater();
459 return;
460 }
461
462 auto buffer = new QFile(imageFilePath);
463
464 if (!buffer->open(QIODevice::ReadWrite)) {
465 qDebug() << "JQHttpServer::Session::replyImage: open buffer error";
466 delete buffer;
467 this->deleteLater();
468 return;
469 }
470
471 ioDeviceForReply_.reset(buffer);
472 ioDeviceForReply_->seek(0);
473
474 const auto && data = replyImageFormat.arg(
475 QString::number(httpStatusCode),
476 QString::number(buffer->size()))
477 .toUtf8();
478
479 waitWrittenByteCount_ = data.size() + buffer->size();
480 ioDevice_->write(data);
481 }
482
ReplyBytes(const QByteArray & bytes,const int & httpStatusCode)483 void JQHttpServer::Session::ReplyBytes(const QByteArray& bytes, const int& httpStatusCode)
484 {
485 JQHTTPSERVER_SESSION_PROTECTION("replyBytes");
486
487 if (QThread::currentThread() != this->thread()) {
488 QMetaObject::invokeMethod(
489 this, "replyBytes", Qt::QueuedConnection, Q_ARG(QByteArray, bytes), Q_ARG(int, httpStatusCode));
490 return;
491 }
492
493 if (alreadyReply_) {
494 qDebug() << "JQHttpServer::Session::replyBytes: already reply";
495 return;
496 }
497 alreadyReply_ = true;
498
499 if (ioDevice_.isNull()) {
500 qDebug() << "JQHttpServer::Session::replyBytes: error1";
501 this->deleteLater();
502 return;
503 }
504
505 auto buffer = new QBuffer;
506 buffer->setData(bytes);
507
508 if (!buffer->open(QIODevice::ReadWrite)) {
509 qDebug() << "JQHttpServer::Session::replyBytes: open buffer error";
510 delete buffer;
511 this->deleteLater();
512 return;
513 }
514
515 ioDeviceForReply_.reset(buffer);
516 ioDeviceForReply_->seek(0);
517
518 const auto && data
519 = replyBytesFormat.arg(QString::number(httpStatusCode), QString::number(buffer->buffer().size())).toUtf8();
520
521 waitWrittenByteCount_ = data.size() + buffer->buffer().size();
522 ioDevice_->write(data);
523 }
524
ReplyOptions()525 void JQHttpServer::Session::ReplyOptions()
526 {
527 JQHTTPSERVER_SESSION_PROTECTION("replyOptions");
528
529 if (alreadyReply_) {
530 qDebug() << "JQHttpServer::Session::replyOptions: already reply";
531 return;
532 }
533
534 if (QThread::currentThread() != this->thread()) {
535 QMetaObject::invokeMethod(this, "replyOptions", Qt::QueuedConnection);
536 return;
537 }
538
539 alreadyReply_ = true;
540
541 if (ioDevice_.isNull()) {
542 qDebug() << "JQHttpServer::Session::replyOptions: error1";
543 this->deleteLater();
544 return;
545 }
546
547 const auto && data2 = replyOptionsFormat.toUtf8();
548
549 waitWrittenByteCount_ = data2.size();
550 ioDevice_->write(data2);
551 }
552
InspectionBufferSetupHelper()553 void JQHttpServer::Session::InspectionBufferSetupHelper()
554 {
555 auto index = buffer_.indexOf(':');
556 if (index <= 0) {
557 qDebug() << "JQHttpServer::Session::inspectionBuffer: error4";
558 this->deleteLater();
559 return;
560 }
561 auto headerData = buffer_.mid(0, splitFlagIndex);
562 buffer_.remove(0, splitFlagIndex + 2L);
563 const auto && key = headerData.mid(0, index);
564 auto value = headerData.mid(index + 1);
565 if (value.startsWith(' ')) {
566 value.remove(0, 1);
567 }
568 requestHeader_[key] = value;
569 if (key.toLower() == "content-length") {
570 contentLength_ = value.toLongLong();
571 }
572 }
573
574
InspectionBufferSetup1()575 void JQHttpServer::Session::InspectionBufferSetup1()
576 {
577 if (headerAcceptedFinish_) {
578 this->InspectionBufferSetup2();
579 return;
580 }
581 forever {
582 static QByteArray splitFlag("\r\n");
583
584 auto splitFlagIndex = buffer_.indexOf(splitFlag);
585
586 // 没有获取到分割标记,意味着数据不全
587 if (splitFlagIndex == -1) {
588 // 没有获取到 method 但是缓冲区内已经有了数据,这可能是一个无效的连接
589 if (requestMethod_.isEmpty() && (buffer_.size() > 4)) {
590 this->deleteLater();
591 }
592 return;
593 }
594
595 if (requestMethod_.isEmpty() && (splitFlagIndex == 0)) {
596 this->deleteLater();
597 return;
598 }
599
600 if (requestMethod_.isEmpty()) {
601 auto requestLineDatas = buffer_.mid(0, splitFlagIndex).split(' ');
602 buffer_.remove(0, splitFlagIndex + 2L);
603
604 if (requestLineDatas.size() != 3) {
605 qDebug() << "JQHttpServer::Session::inspectionBuffer: error2";
606 this->deleteLater();
607 return;
608 }
609
610 requestMethod_ = requestLineDatas.at(0);
611 requestUrl_ = requestLineDatas.at(1);
612 requestCrlf_ = requestLineDatas.at(2);
613
614 if ((requestMethod_ != "GET") && (requestMethod_ != "OPTIONS") && (requestMethod_ != "POST")
615 && (requestMethod_ != "PUT")) {
616 qDebug() << "JQHttpServer::Session::inspectionBuffer: error3:" << requestMethod_;
617 this->deleteLater();
618 return;
619 }
620 } else if (splitFlagIndex == 0) {
621 buffer_.remove(0, 2);
622
623 headerAcceptedFinish_ = true;
624
625 if ((requestMethod_.toUpper() == "GET") || (requestMethod_.toUpper() == "OPTIONS")
626 || ((requestMethod_.toUpper() == "POST") && ((contentLength_ > 0) ? (!buffer_.isEmpty()) : (true)))
627 || ((requestMethod_.toUpper() == "PUT") && ((contentLength_ > 0) ? (!buffer_.isEmpty()) : (true)))) {
628 this->InspectionBufferSetup2();
629 }
630 } else {
631 InspectionBufferSetupHelper();
632 }
633 }
634 }
635
InspectionBufferSetup2()636 void JQHttpServer::Session::InspectionBufferSetup2()
637 {
638 requestBody_ += buffer_;
639 buffer_.clear();
640
641 if (!handleAcceptedCallback_) {
642 qDebug() << "JQHttpServer::Session::inspectionBuffer: error4";
643 this->deleteLater();
644 return;
645 }
646
647 if ((contentLength_ != -1) && (requestBody_.size() != contentLength_)) {
648 return;
649 }
650
651 handleAcceptedCallback_(this);
652 }
653
654 // AbstractManage
AbstractManage(const int & handleMaxThreadCount)655 JQHttpServer::AbstractManage::AbstractManage(const int& handleMaxThreadCount)
656 {
657 handleThreadPool_.reset(new QThreadPool);
658 serverThreadPool_.reset(new QThreadPool);
659
660 handleThreadPool_->setMaxThreadCount(handleMaxThreadCount);
661 serverThreadPool_->setMaxThreadCount(1);
662 }
663
~AbstractManage()664 JQHttpServer::AbstractManage::~AbstractManage()
665 {
666 this->StopHandleThread();
667 }
668
Begin()669 bool JQHttpServer::AbstractManage::Begin()
670 {
671 if (QThread::currentThread() != this->thread()) {
672 qDebug() << "JQHttpServer::Manage::listen: error: listen from other thread";
673 return false;
674 }
675
676 if (this->IsRunning()) {
677 qDebug() << "JQHttpServer::Manage::close: error: already running";
678 return false;
679 }
680
681 return this->StartServerThread();
682 }
683
Close()684 void JQHttpServer::AbstractManage::Close()
685 {
686 if (!this->IsRunning()) {
687 qDebug() << "JQHttpServer::Manage::close: error: not running";
688 return;
689 }
690
691 emit readyToClose();
692
693 if (serverThreadPool_->activeThreadCount()) {
694 this->StopServerThread();
695 }
696 }
697
StartServerThread()698 bool JQHttpServer::AbstractManage::StartServerThread()
699 {
700 QSemaphore semaphore;
701
702 QtConcurrent::run(serverThreadPool_.data(), [&semaphore, this]() {
703 QEventLoop eventLoop;
704 QObject::connect(this, &AbstractManage::readyToClose, &eventLoop, &QEventLoop::quit);
705
706 if (!this->OnStart()) {
707 semaphore.release(1);
708 }
709
710 semaphore.release(1);
711
712 eventLoop.exec();
713
714 this->OnFinish();
715 });
716
717 semaphore.acquire(1);
718
719 return this->IsRunning();
720 }
721
StopHandleThread()722 void JQHttpServer::AbstractManage::StopHandleThread()
723 {
724 handleThreadPool_->waitForDone();
725 }
726
StopServerThread()727 void JQHttpServer::AbstractManage::StopServerThread()
728 {
729 serverThreadPool_->waitForDone();
730 }
731
newSession(const QPointer<Session> & session)732 void JQHttpServer::AbstractManage::newSession(const QPointer<Session>& session)
733 {
734 session->setHandleAcceptedCallback(
735 [this](const QPointer<JQHttpServer::Session>& session) { this->HandleAccepted(session); });
736
737 auto session_ = session.data();
738 connect(session.data(), &QObject::destroyed, [this, session_]() {
739 this->mutex_.lock();
740 this->availableSessions_.remove(session_);
741 this->mutex_.unlock();
742 });
743 availableSessions_.insert(session.data());
744 }
745
HandleAccepted(const QPointer<Session> & session)746 void JQHttpServer::AbstractManage::HandleAccepted(const QPointer<Session>& session)
747 {
748 QtConcurrent::run(handleThreadPool_.data(), [this, session]() {
749 if (!this->httpAcceptedCallback_) {
750 qDebug() << "JQHttpServer::Manage::handleAccepted: error, httpAcceptedCallback_ is nullptr";
751 return;
752 }
753
754 this->httpAcceptedCallback_(session);
755 });
756 }
757
758 // TcpServerManage
TcpServerManage(const int & handleMaxThreadCount)759 JQHttpServer::TcpServerManage::TcpServerManage(const int& handleMaxThreadCount)
760 : AbstractManage(handleMaxThreadCount)
761 {
762 }
763
~TcpServerManage()764 JQHttpServer::TcpServerManage::~TcpServerManage()
765 {
766 if (this->IsRunning()) {
767 this->Close();
768 }
769 }
770
Listen(const QHostAddress & address,const quint16 & port)771 bool JQHttpServer::TcpServerManage::Listen(const QHostAddress& address, const quint16& port)
772 {
773 listenAddress_ = address;
774 listenPort_ = port;
775
776 return this->Begin();
777 }
778
IsRunning()779 bool JQHttpServer::TcpServerManage::IsRunning()
780 {
781 return !tcpServer_.isNull();
782 }
783
OnStart()784 bool JQHttpServer::TcpServerManage::OnStart()
785 {
786 mutex_.lock();
787
788 tcpServer_ = new QTcpServer;
789
790 mutex_.unlock();
791
792 QObject::connect(tcpServer_.data(), &QTcpServer::newConnection, [this]() {
793 auto socket = this->tcpServer_->nextPendingConnection();
794
795 this->newSession(new Session(socket));
796 });
797
798 if (!tcpServer_->Listen(QHostAddress::Any, listenPort_)) {
799 mutex_.lock();
800
801 delete tcpServer_.data();
802 tcpServer_.clear();
803
804 mutex_.unlock();
805
806 return false;
807 }
808
809 return true;
810 }
811
OnFinish()812 void JQHttpServer::TcpServerManage::OnFinish()
813 {
814 this->mutex_.lock();
815
816 tcpServer_->Close();
817 delete tcpServer_.data();
818 tcpServer_.clear();
819
820 this->mutex_.unlock();
821 }
822
823 // SslServerManage
824 #ifndef QT_NO_SSL
825 namespace JQHttpServer {
826
827 class SslServerHelper : public QTcpServer {
incomingConnection(qintptr socketDescriptor)828 void incomingConnection(qintptr socketDescriptor) final
829 {
830 onIncomingConnectionCallback_(socketDescriptor);
831 }
832
833 public:
834 std::function<void(qintptr socketDescriptor)> onIncomingConnectionCallback_;
835 };
836
837 }
838
SslServerManage(const int & handleMaxThreadCount)839 JQHttpServer::SslServerManage::SslServerManage(const int& handleMaxThreadCount)
840 : AbstractManage(handleMaxThreadCount)
841 {
842 }
843
~SslServerManage()844 JQHttpServer::SslServerManage::~SslServerManage()
845 {
846 if (this->IsRunning()) {
847 this->Close();
848 }
849 }
850
Listen(const QHostAddress & address,const quint16 & port,const QString & crtFilePath,const QString & keyFilePath,const QList<QPair<QString,bool>> & caFileList)851 bool JQHttpServer::SslServerManage::Listen(const QHostAddress& address, const quint16& port, const QString& crtFilePath,
852 const QString& keyFilePath, const QList<QPair<QString, bool>>& caFileList)
853 {
854 listenAddress_ = address;
855 listenPort_ = port;
856
857 QFile fileForCrt(crtFilePath);
858 if (!fileForCrt.open(QIODevice::ReadOnly)) {
859 qDebug() << "SslServerManage::listen: error: can not open file:" << crtFilePath;
860 return false;
861 }
862
863 QFile fileForKey(keyFilePath);
864 if (!fileForKey.open(QIODevice::ReadOnly)) {
865 qDebug() << "SslServerManage::listen: error: can not open file:" << keyFilePath;
866 return false;
867 }
868
869 QSslCertificate sslCertificate(fileForCrt.readAll(), QSsl::Pem);
870 QSslKey sslKey(fileForKey.readAll(), QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey);
871
872 QList<QSslCertificate> caCertificates;
873 for (const auto& caFile : caFileList) {
874 QFile fileForCa(caFile.first);
875 if (!fileForCa.open(QIODevice::ReadOnly)) {
876 qDebug() << "SslServerManage::listen: error: can not open file:" << caFile.first;
877 return false;
878 }
879
880 caCertificates.push_back(QSslCertificate(fileForCa.readAll(), (caFile.second) ? (QSsl::Pem) : (QSsl::Der)));
881 }
882
883 sslConfiguration_.reset(new QSslConfiguration);
884 sslConfiguration_->setPeerVerifyMode(QSslSocket::VerifyNone);
885 sslConfiguration_->setLocalCertificate(sslCertificate);
886 sslConfiguration_->setPrivateKey(sslKey);
887 sslConfiguration_->setProtocol(QSsl::TlsV1_2);
888 sslConfiguration_->setCaCertificates(caCertificates);
889
890 qDebug() << "sslCertificate:" << sslCertificate;
891 qDebug() << "sslKey:" << sslKey;
892 qDebug() << "caCertificates:" << caCertificates;
893
894 return this->Begin();
895 }
896
IsRunning()897 bool JQHttpServer::SslServerManage::IsRunning()
898 {
899 return !tcpServer_.isNull();
900 }
901
OnStart()902 bool JQHttpServer::SslServerManage::OnStart()
903 {
904 mutex_.lock();
905
906 tcpServer_ = new SslServerHelper;
907
908 mutex_.unlock();
909
910 tcpServer_->onIncomingConnectionCallback_ = [this](qintptr socketDescriptor) {
911 auto sslSocket = new QSslSocket;
912
913 sslSocket->setSslConfiguration(*sslConfiguration_);
914
915 QObject::connect(
916 sslSocket, &QSslSocket::encrypted, [this, sslSocket]() { this->newSession(new Session(sslSocket)); });
917 sslSocket->setSocketDescriptor(socketDescriptor);
918 sslSocket->startServerEncryption();
919 };
920
921 if (!tcpServer_->Listen(QHostAddress::AnyIPv6, listenPort_)) {
922 mutex_.lock();
923
924 delete tcpServer_.data();
925 tcpServer_.clear();
926
927 mutex_.unlock();
928
929 return false;
930 }
931
932 return true;
933 }
934
OnFinish()935 void JQHttpServer::SslServerManage::OnFinish()
936 {
937 this->mutex_.lock();
938
939 tcpServer_->Close();
940 delete tcpServer_.data();
941 tcpServer_.clear();
942
943 this->mutex_.unlock();
944 }
945 #endif
946
947 // LocalServerManage
LocalServerManage(const int & handleMaxThreadCount)948 JQHttpServer::LocalServerManage::LocalServerManage(const int& handleMaxThreadCount)
949 : AbstractManage(handleMaxThreadCount)
950 {
951 }
952
~LocalServerManage()953 JQHttpServer::LocalServerManage::~LocalServerManage()
954 {
955 if (this->IsRunning()) {
956 this->Close();
957 }
958 }
959
Listen(const QString & name)960 bool JQHttpServer::LocalServerManage::Listen(const QString& name)
961 {
962 listenName_ = name;
963
964 return this->Begin();
965 }
966
IsRunning()967 bool JQHttpServer::LocalServerManage::IsRunning()
968 {
969 return !localServer_.isNull();
970 }
971
OnStart()972 bool JQHttpServer::LocalServerManage::OnStart()
973 {
974 mutex_.lock();
975
976 localServer_ = new QLocalServer;
977
978 mutex_.unlock();
979
980 QObject::connect(localServer_.data(), &QLocalServer::newConnection,
981 [this]() { this->newSession(new Session(this->localServer_->nextPendingConnection())); });
982
983 if (!localServer_->Listen(listenName_)) {
984 mutex_.lock();
985
986 delete localServer_.data();
987 localServer_.clear();
988
989 mutex_.unlock();
990
991 return false;
992 }
993
994 return true;
995 }
996
OnFinish()997 void JQHttpServer::LocalServerManage::OnFinish()
998 {
999 this->mutex_.lock();
1000
1001 localServer_->Close();
1002 delete localServer_.data();
1003 localServer_.clear();
1004
1005 this->mutex_.unlock();
1006 }
1007