• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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