| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| /** @file httpwriter.hxx | ||
| * | ||
| * @copyright (C) 2016 | ||
| * @date 2016.05.26 | ||
| * @version 1.0.0 | ||
| * @author amir zamani <azadkuh@live.com> | ||
| * | ||
| */ | ||
|
|
||
| #ifndef __QHTTP_HTTPWRITER_HXX__ | ||
| #define __QHTTP_HTTPWRITER_HXX__ | ||
|
|
||
| #include "qhttpbase.hpp" | ||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
| namespace qhttp { | ||
| namespace details { | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
|
|
||
| // usage in client::QHttpRequest, server::QHttpResponse | ||
| template<class TBase, class TImpl> | ||
| class HttpWriter : public TBase | ||
| { | ||
| public: | ||
| bool addHeader(const QByteArray &field, const QByteArray &value) { | ||
| if ( ifinished ) | ||
| return false; | ||
|
|
||
| TBase::iheaders.insert(field.toLower(), value); | ||
| return true; | ||
| } | ||
|
|
||
| bool writeHeader(const QByteArray& field, const QByteArray& value) { | ||
| if ( ifinished ) | ||
| return false; | ||
|
|
||
| QByteArray buffer = QByteArray(field) | ||
| .append(": ") | ||
| .append(value) | ||
| .append("\r\n"); | ||
|
|
||
| isocket.writeRaw(buffer); | ||
| return true; | ||
| } | ||
|
|
||
| bool writeData(const QByteArray& data) { | ||
| if ( ifinished ) | ||
| return false; | ||
|
|
||
| ensureWritingHeaders(); | ||
| isocket.writeRaw(data); | ||
| return true; | ||
| } | ||
|
|
||
| bool endPacket(const QByteArray& data) { | ||
| if ( !writeData(data) ) | ||
| return false; | ||
|
|
||
| isocket.flush(); | ||
| ifinished = true; | ||
| return true; | ||
| } | ||
|
|
||
| void ensureWritingHeaders() { | ||
| if ( ifinished || iheaderWritten ) | ||
| return; | ||
|
|
||
| TImpl* me = static_cast<TImpl*>(this); | ||
| isocket.writeRaw(me->makeTitle()); | ||
| writeHeaders(); | ||
|
|
||
| iheaderWritten = true; | ||
| } | ||
|
|
||
| void writeHeaders(bool doFlush = false) { | ||
| if ( ifinished || iheaderWritten ) | ||
| return; | ||
|
|
||
| if ( TBase::iheaders.keyHasValue("connection", "keep-alive") ) | ||
| ikeepAlive = true; | ||
| else | ||
| TBase::iheaders.insert("connection", "close"); | ||
|
|
||
| TImpl* me = static_cast<TImpl*>(this); | ||
| me->prepareHeadersToWrite(); | ||
|
|
||
| TBase::iheaders.forEach([this](const auto& cit) { | ||
| this->writeHeader(cit.key(), cit.value()); | ||
| }); | ||
|
|
||
| isocket.writeRaw("\r\n"); | ||
| if ( doFlush ) | ||
| isocket.flush(); | ||
| } | ||
|
|
||
| public: | ||
| QSocket isocket; | ||
|
|
||
| bool ifinished = false; | ||
| bool iheaderWritten = false; | ||
| bool ikeepAlive = false; | ||
| }; | ||
|
|
||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
| } // namespace details | ||
| } // namespace qhttp | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
| #endif // __QHTTP_HTTPWRITER_HXX__ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| /** base classes for private implementations. | ||
| * https://github.com/azadkuh/qhttp | ||
| * | ||
| * @author amir zamani | ||
| * @version 2.0.0 | ||
| * @date 2014-07-11 | ||
| */ | ||
|
|
||
| #ifndef QHTTPBASE_HPP | ||
| #define QHTTPBASE_HPP | ||
|
|
||
| #include "qhttpfwd.hpp" | ||
|
|
||
| #include "qsocket.hpp" | ||
| #include <QHostAddress> | ||
| #include <QBasicTimer> | ||
|
|
||
| #include "http-parser/http_parser.h" | ||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
| namespace qhttp { | ||
| namespace details { | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
|
|
||
| struct HttpBase | ||
| { | ||
| QString iversion; | ||
| THeaderHash iheaders; | ||
| }; // struct HttpBase | ||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
|
|
||
| struct HttpRequestBase : public HttpBase | ||
| { | ||
| QUrl iurl; | ||
| THttpMethod imethod; | ||
| }; // HttpRequestBase | ||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
|
|
||
| struct HttpResponseBase : public HttpBase | ||
| { | ||
| TStatusCode istatus = ESTATUS_BAD_REQUEST; | ||
|
|
||
| HttpResponseBase() { iversion = "1.1"; } | ||
| }; // HttpResponseBase | ||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
| } // namespace details | ||
| } // namespace qhttp | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
| #endif // QHTTPBASE_HPP |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,196 @@ | ||
| /** private imeplementation. | ||
| * https://github.com/azadkuh/qhttp | ||
| * | ||
| * @author amir zamani | ||
| * @version 2.0.0 | ||
| * @date 2014-07-11 | ||
| */ | ||
|
|
||
| #ifndef QHTTPCLIENT_PRIVATE_HPP | ||
| #define QHTTPCLIENT_PRIVATE_HPP | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
|
|
||
| #include "qhttpclient.hpp" | ||
| #include "httpparser.hxx" | ||
| #include "qhttpclientrequest_private.hpp" | ||
| #include "qhttpclientresponse_private.hpp" | ||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
| namespace qhttp { | ||
| namespace client { | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
|
|
||
| class QHttpClientPrivate : | ||
| public details::HttpResponseParser<QHttpClientPrivate> | ||
| { | ||
| Q_DECLARE_PUBLIC(QHttpClient) | ||
|
|
||
| public: | ||
| explicit QHttpClientPrivate(QHttpClient* q) : q_ptr(q) { | ||
| QObject::connect( | ||
| q_func(), &QHttpClient::disconnected, | ||
| [this](){ release(); } | ||
| ); | ||
|
|
||
| QHTTP_LINE_DEEPLOG | ||
| } | ||
|
|
||
| virtual ~QHttpClientPrivate() { | ||
| QHTTP_LINE_DEEPLOG | ||
| } | ||
|
|
||
| void release() { | ||
| // if socket drops and http_parser can not call messageComplete, | ||
| // dispatch the ilastResponse | ||
| finalizeConnection(); | ||
|
|
||
| isocket.disconnectAllQtConnections(); | ||
| isocket.release(); | ||
|
|
||
| if ( ilastRequest ) { | ||
| ilastRequest->deleteLater(); | ||
| ilastRequest = nullptr; | ||
| } | ||
|
|
||
| if ( ilastResponse ) { | ||
| ilastResponse->deleteLater(); | ||
| ilastResponse = nullptr; | ||
| } | ||
|
|
||
| // must be called! or the later http_parser_execute() may fail | ||
| http_parser_init(&iparser, HTTP_RESPONSE); | ||
| } | ||
|
|
||
| void initializeSocket() { | ||
| if ( isocket.isOpen() ) { | ||
| // no need to reconnect. do nothing and simply return | ||
| if ( ikeepAlive ) | ||
| return; | ||
|
|
||
| // close previous connection now! | ||
| // instead being called by emitted disconnected signal | ||
| release(); | ||
| } | ||
|
|
||
| ikeepAlive = false; | ||
|
|
||
| // create a tcp connection | ||
| if ( isocket.ibackendType == ETcpSocket ) { | ||
| initTcpSocket(); | ||
|
|
||
| } else if ( isocket.ibackendType == ELocalSocket ) { | ||
| initLocalSocket(); | ||
| } | ||
| } | ||
|
|
||
| public: | ||
| int messageBegin(http_parser* parser); | ||
| int url(http_parser*, const char*, size_t) { | ||
| return 0; // not used in parsing incoming respone. | ||
| } | ||
| int status(http_parser* parser, const char* at, size_t length) ; | ||
| int headerField(http_parser* parser, const char* at, size_t length); | ||
| int headerValue(http_parser* parser, const char* at, size_t length); | ||
| int headersComplete(http_parser* parser); | ||
| int body(http_parser* parser, const char* at, size_t length); | ||
| int messageComplete(http_parser* parser); | ||
|
|
||
| protected: | ||
| void onConnected() { | ||
| iconnectingTimer.stop(); | ||
|
|
||
| if ( itimeOut > 0 ) | ||
| itimer.start(itimeOut, Qt::CoarseTimer, q_func()); | ||
|
|
||
| if ( ireqHandler ) | ||
| ireqHandler(ilastRequest); | ||
| else | ||
| q_func()->onRequestReady(ilastRequest); | ||
| } | ||
|
|
||
| void onReadyRead() { | ||
| while ( isocket.bytesAvailable() > 0 ) { | ||
| char buffer[4097] = {0}; | ||
| size_t readLength = (size_t) isocket.readRaw(buffer, 4096); | ||
|
|
||
| parse(buffer, readLength); | ||
| } | ||
| } | ||
|
|
||
| void finalizeConnection() { | ||
| if ( ilastResponse == nullptr ) | ||
| return; | ||
|
|
||
| ilastResponse->d_func()->finalizeSending([this]{ | ||
| emit ilastResponse->end(); | ||
| }); | ||
| } | ||
|
|
||
| private: | ||
| void initTcpSocket() { | ||
| QTcpSocket* sok = new QTcpSocket(q_func()); | ||
| isocket.itcpSocket = sok; | ||
|
|
||
| QObject::connect( | ||
| sok, &QTcpSocket::connected, | ||
| [this](){ onConnected(); } | ||
| ); | ||
| QObject::connect( | ||
| sok, &QTcpSocket::readyRead, | ||
| [this](){ onReadyRead(); } | ||
| ); | ||
| QObject::connect( | ||
| sok, &QTcpSocket::bytesWritten, | ||
| [this](qint64){ | ||
| const auto& ts = isocket.itcpSocket; | ||
| if ( ts->bytesToWrite() == 0 && ilastRequest ) | ||
| emit ilastRequest->allBytesWritten(); | ||
| }); | ||
| QObject::connect( | ||
| sok, &QTcpSocket::disconnected, | ||
| q_func(), &QHttpClient::disconnected | ||
| ); | ||
| } | ||
|
|
||
| void initLocalSocket() { | ||
| QLocalSocket* sok = new QLocalSocket(q_func()); | ||
| isocket.ilocalSocket = sok; | ||
|
|
||
| QObject::connect( | ||
| sok, &QLocalSocket::connected, | ||
| [this](){ onConnected(); } | ||
| ); | ||
| QObject::connect( | ||
| sok, &QLocalSocket::readyRead, | ||
| [this](){ onReadyRead(); } | ||
| ); | ||
| QObject::connect( | ||
| sok, &QLocalSocket::bytesWritten, | ||
| [this](qint64){ | ||
| const auto* ls = isocket.ilocalSocket; | ||
| if ( ls->bytesToWrite() == 0 && ilastRequest ) | ||
| emit ilastRequest->allBytesWritten(); | ||
| }); | ||
| QObject::connect( | ||
| sok, &QLocalSocket::disconnected, | ||
| q_func(), &QHttpClient::disconnected | ||
| ); | ||
| } | ||
|
|
||
| protected: | ||
| QHttpClient* const q_ptr; | ||
|
|
||
| QHttpRequest* ilastRequest = nullptr; | ||
| QHttpResponse* ilastResponse = nullptr; | ||
| TRequstHandler ireqHandler; | ||
| TResponseHandler irespHandler; | ||
|
|
||
| QBasicTimer iconnectingTimer; | ||
| }; | ||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
| } // namespace client | ||
| } // namespace qhttp | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
|
|
||
| #endif // QHTTPCLIENT_PRIVATE_HPP |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| /** private imeplementation. | ||
| * https://github.com/azadkuh/qhttp | ||
| * | ||
| * @author amir zamani | ||
| * @version 2.0.0 | ||
| * @date 2014-07-11 | ||
| */ | ||
|
|
||
| #ifndef QHTTPCLIENT_REQUEST_PRIVATE_HPP | ||
| #define QHTTPCLIENT_REQUEST_PRIVATE_HPP | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
| #include "httpwriter.hxx" | ||
| #include "qhttpclient.hpp" | ||
| #include "qhttpclientrequest.hpp" | ||
|
|
||
| #include <QTcpSocket> | ||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
| namespace qhttp { | ||
| namespace client { | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
| class QHttpRequestPrivate : | ||
| public details::HttpWriter<details::HttpRequestBase, QHttpRequestPrivate> | ||
| { | ||
| Q_DECLARE_PUBLIC(QHttpRequest) | ||
|
|
||
| public: | ||
| explicit QHttpRequestPrivate(QHttpClient* cli, QHttpRequest* q) : q_ptr(q), iclient(cli) { | ||
| QHTTP_LINE_DEEPLOG | ||
| } | ||
|
|
||
| virtual ~QHttpRequestPrivate() { | ||
| QHTTP_LINE_DEEPLOG | ||
| } | ||
|
|
||
| void initialize() { | ||
| iversion = "1.1"; | ||
|
|
||
| isocket.ibackendType = iclient->backendType(); | ||
| isocket.itcpSocket = iclient->tcpSocket(); | ||
| isocket.ilocalSocket = iclient->localSocket(); | ||
| } | ||
|
|
||
| QByteArray makeTitle(); | ||
|
|
||
| void prepareHeadersToWrite(); | ||
|
|
||
| protected: | ||
| QHttpRequest* const q_ptr; | ||
| QHttpClient* const iclient; | ||
| }; | ||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
| } // namespace client | ||
| } // namespace qhttp | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
| #endif // QHTTPCLIENT_REQUEST_PRIVATE_HPP |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| /** private imeplementation. | ||
| * https://github.com/azadkuh/qhttp | ||
| * | ||
| * @author amir zamani | ||
| * @version 2.0.0 | ||
| * @date 2014-07-11 | ||
| */ | ||
|
|
||
| #ifndef QHTTPCLIENT_RESPONSE_PRIVATE_HPP | ||
| #define QHTTPCLIENT_RESPONSE_PRIVATE_HPP | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
|
|
||
| #include "httpreader.hxx" | ||
| #include "qhttpclient.hpp" | ||
| #include "qhttpclientresponse.hpp" | ||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
| namespace qhttp { | ||
| namespace client { | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
| class QHttpResponsePrivate : | ||
| public details::HttpReader<details::HttpResponseBase> | ||
| { | ||
| Q_DECLARE_PUBLIC(QHttpResponse) | ||
| QHttpResponse* const q_ptr; | ||
|
|
||
| public: | ||
| explicit QHttpResponsePrivate(QHttpClient* cli, QHttpResponse* q) | ||
| : q_ptr(q), iclient(cli) { | ||
| QHTTP_LINE_DEEPLOG | ||
| } | ||
|
|
||
| virtual ~QHttpResponsePrivate() { | ||
| QHTTP_LINE_DEEPLOG | ||
| } | ||
|
|
||
| void initialize() { | ||
| } | ||
|
|
||
| public: | ||
| QString icustomStatusMessage; | ||
|
|
||
| protected: | ||
| QHttpClient* const iclient; | ||
| }; | ||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
| } // namespace client | ||
| } // namespace qhttp | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
| #endif // QHTTPCLIENT_RESPONSE_PRIVATE_HPP |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| /** private imeplementation. | ||
| * https://github.com/azadkuh/qhttp | ||
| * | ||
| * @author amir zamani | ||
| * @version 2.0.0 | ||
| * @date 2014-07-11 | ||
| */ | ||
|
|
||
| #ifndef QHTTPSERVER_PRIVATE_HPP | ||
| #define QHTTPSERVER_PRIVATE_HPP | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
|
|
||
| #include "qhttpserver.hpp" | ||
| #include "qhttpserverconnection.hpp" | ||
| #include "qhttpserverrequest.hpp" | ||
| #include "qhttpserverresponse.hpp" | ||
|
|
||
| #include <QTcpServer> | ||
| #include <QLocalServer> | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
| namespace qhttp { | ||
| namespace server { | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
|
|
||
| class QHttpServerPrivate | ||
| { | ||
| public: | ||
| template<class TServer> | ||
| class BackendServer : public TServer | ||
| { | ||
| public: | ||
| QHttpServer* iserver; | ||
|
|
||
| explicit BackendServer(QHttpServer* s) : TServer(s), iserver(s) { | ||
| } | ||
|
|
||
| protected: | ||
| // if it's a QTcpServer | ||
| virtual void incomingConnection(qintptr socketDescriptor) { | ||
| iserver->incomingConnection(socketDescriptor); | ||
| } | ||
|
|
||
| // if it's a QLocalServer | ||
| virtual void incomingConnection(quintptr socketDescriptor) { | ||
| iserver->incomingConnection((qintptr) socketDescriptor); | ||
| } | ||
| }; | ||
|
|
||
| using TTcpServer = QScopedPointer<BackendServer<QTcpServer>>; | ||
| using TLocalServer = QScopedPointer<BackendServer<QLocalServer>>; | ||
|
|
||
| public: | ||
| quint32 itimeOut = 0; | ||
| TServerHandler ihandler = nullptr; | ||
|
|
||
| TBackend ibackend = ETcpSocket; | ||
|
|
||
| TTcpServer itcpServer; | ||
| TLocalServer ilocalServer; | ||
|
|
||
| public: | ||
| explicit QHttpServerPrivate() { | ||
| QHTTP_LINE_DEEPLOG | ||
| } | ||
|
|
||
| virtual ~QHttpServerPrivate() { | ||
| QHTTP_LINE_DEEPLOG | ||
| } | ||
|
|
||
| void initialize(TBackend backend, QHttpServer* parent) { | ||
| ibackend = backend; | ||
|
|
||
| if ( ibackend == ETcpSocket ) { | ||
| itcpServer.reset( new BackendServer<QTcpServer>(parent) ); | ||
| ilocalServer.reset( nullptr ); | ||
|
|
||
| } else if ( ibackend == ELocalSocket ) { | ||
| itcpServer.reset( nullptr ); | ||
| ilocalServer.reset( new BackendServer<QLocalServer>(parent) ); | ||
| } | ||
| } | ||
|
|
||
| }; | ||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
| } // namespace server | ||
| } // namespace qhttp | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
|
|
||
| #endif // QHTTPSERVER_PRIVATE_HPP |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,175 @@ | ||
| /** private imeplementation. | ||
| * https://github.com/azadkuh/qhttp | ||
| * | ||
| * @author amir zamani | ||
| * @version 2.0.0 | ||
| * @date 2014-07-11 | ||
| */ | ||
|
|
||
| #ifndef QHTTPSERVER_CONNECTION_PRIVATE_HPP | ||
| #define QHTTPSERVER_CONNECTION_PRIVATE_HPP | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
|
|
||
| #include "qhttpserverconnection.hpp" | ||
| #include "httpparser.hxx" | ||
| #include "qhttpserverrequest.hpp" | ||
| #include "qhttpserverresponse.hpp" | ||
|
|
||
| #include "private/qhttpserverrequest_private.hpp" | ||
| #include "private/qhttpserverresponse_private.hpp" | ||
|
|
||
| #include <QBasicTimer> | ||
| #include <QFile> | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
| namespace qhttp { | ||
| namespace server { | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
| class QHttpConnectionPrivate : | ||
| public details::HttpRequestParser<QHttpConnectionPrivate> | ||
| { | ||
| Q_DECLARE_PUBLIC(QHttpConnection) | ||
|
|
||
| public: | ||
| explicit QHttpConnectionPrivate(QHttpConnection* q) : q_ptr(q) { | ||
|
|
||
| QObject::connect( | ||
| q_func(), &QHttpConnection::disconnected, | ||
| [this](){ release(); } | ||
| ); | ||
|
|
||
| QHTTP_LINE_DEEPLOG | ||
| } | ||
|
|
||
| virtual ~QHttpConnectionPrivate() { | ||
| QHTTP_LINE_DEEPLOG | ||
| } | ||
|
|
||
| void createSocket(qintptr sokDesc, TBackend bend) { | ||
| isocket.ibackendType = bend; | ||
|
|
||
| if ( bend == ETcpSocket ) { | ||
| initTcpSocket(sokDesc); | ||
|
|
||
| } else if ( bend == ELocalSocket ) { | ||
| initLocalSocket(sokDesc); | ||
| } | ||
| } | ||
|
|
||
| void release() { | ||
| // if socket drops and http_parser can not call | ||
| // messageComplete, dispatch the ilastRequest | ||
| finalizeConnection(); | ||
|
|
||
| isocket.disconnectAllQtConnections(); | ||
| isocket.release(); | ||
|
|
||
| if ( ilastRequest ) { | ||
| ilastRequest->deleteLater(); | ||
| ilastRequest = nullptr; | ||
| } | ||
|
|
||
| if ( ilastResponse ) { | ||
| ilastResponse->deleteLater(); | ||
| ilastResponse = nullptr; | ||
| } | ||
|
|
||
| q_func()->deleteLater(); | ||
| } | ||
|
|
||
| public: | ||
| void onReadyRead() { | ||
| while ( isocket.bytesAvailable() > 0 ) { | ||
| char buffer[4097] = {0}; | ||
| size_t readLength = (size_t) isocket.readRaw(buffer, 4096); | ||
|
|
||
| parse(buffer, readLength); | ||
| } | ||
| } | ||
|
|
||
| void finalizeConnection() { | ||
| if ( ilastRequest == nullptr ) | ||
| return; | ||
|
|
||
| ilastRequest->d_func()->finalizeSending([this]{ | ||
| emit ilastRequest->end(); | ||
| }); | ||
| } | ||
|
|
||
| public: | ||
| int messageBegin(http_parser* parser); | ||
| int url(http_parser* parser, const char* at, size_t length); | ||
| int status(http_parser*, const char*, size_t) { | ||
| return 0; // not used in parsing incoming request. | ||
| } | ||
| int headerField(http_parser* parser, const char* at, size_t length); | ||
| int headerValue(http_parser* parser, const char* at, size_t length); | ||
| int headersComplete(http_parser* parser); | ||
| int body(http_parser* parser, const char* at, size_t length); | ||
| int messageComplete(http_parser* parser); | ||
|
|
||
| private: | ||
| void initTcpSocket(qintptr sokDesc) { | ||
| QTcpSocket* sok = new QTcpSocket( q_func() ); | ||
| isocket.itcpSocket = sok; | ||
| sok->setSocketDescriptor(sokDesc); | ||
|
|
||
| QObject::connect( | ||
| sok, &QTcpSocket::readyRead, | ||
| [this](){ onReadyRead(); } | ||
| ); | ||
| QObject::connect( | ||
| sok, &QTcpSocket::bytesWritten, | ||
| [this](){ | ||
| auto btw = isocket.itcpSocket->bytesToWrite(); | ||
| if ( btw == 0 && ilastResponse ) | ||
| emit ilastResponse->allBytesWritten(); | ||
| }); | ||
| QObject::connect( | ||
| sok, &QTcpSocket::disconnected, | ||
| q_func(), &QHttpConnection::disconnected, | ||
| Qt::QueuedConnection | ||
| ); | ||
| } | ||
|
|
||
| void initLocalSocket(qintptr sokDesc) { | ||
| QLocalSocket* sok = new QLocalSocket( q_func() ); | ||
| isocket.ilocalSocket = sok; | ||
| sok->setSocketDescriptor(sokDesc); | ||
|
|
||
| QObject::connect( | ||
| sok, &QLocalSocket::readyRead, | ||
| [this](){ onReadyRead(); } | ||
| ); | ||
| QObject::connect( | ||
| sok, &QLocalSocket::bytesWritten, | ||
| [this](){ | ||
| auto btw = isocket.ilocalSocket->bytesToWrite(); | ||
| if ( btw == 0 && ilastResponse ) | ||
| emit ilastResponse->allBytesWritten(); | ||
| }); | ||
| QObject::connect( | ||
| sok, &QLocalSocket::disconnected, | ||
| q_func(), &QHttpConnection::disconnected, | ||
| Qt::QueuedConnection | ||
| ); | ||
| } | ||
|
|
||
| protected: | ||
| QHttpConnection* const q_ptr; | ||
|
|
||
| QByteArray itempUrl; | ||
|
|
||
| // Since there can only be one request/response pair per connection at any | ||
| // time even with pipelining. | ||
| QHttpRequest* ilastRequest = nullptr; | ||
| QHttpResponse* ilastResponse = nullptr; | ||
|
|
||
| TServerHandler ihandler = nullptr; | ||
|
|
||
| }; | ||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
| } // namespace server | ||
| } // namespace qhttp | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
| #endif // QHTTPSERVER_CONNECTION_PRIVATE_HPP |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| /** private imeplementation. | ||
| * https://github.com/azadkuh/qhttp | ||
| * | ||
| * @author amir zamani | ||
| * @version 2.0.0 | ||
| * @date 2014-07-11 | ||
| */ | ||
|
|
||
| #ifndef QHTTPSERVER_REQUEST_PRIVATE_HPP | ||
| #define QHTTPSERVER_REQUEST_PRIVATE_HPP | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
|
|
||
| #include "httpreader.hxx" | ||
| #include "qhttpserverrequest.hpp" | ||
| #include "qhttpserverconnection.hpp" | ||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
| namespace qhttp { | ||
| namespace server { | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
| class QHttpRequestPrivate : | ||
| public details::HttpReader<details::HttpRequestBase> | ||
| { | ||
| protected: | ||
| Q_DECLARE_PUBLIC(QHttpRequest) | ||
| QHttpRequest* const q_ptr; | ||
|
|
||
| public: | ||
| explicit QHttpRequestPrivate(QHttpConnection* conn, QHttpRequest* q) : q_ptr(q), iconnection(conn) { | ||
| QHTTP_LINE_DEEPLOG | ||
| } | ||
|
|
||
| virtual ~QHttpRequestPrivate() { | ||
| QHTTP_LINE_DEEPLOG | ||
| } | ||
|
|
||
| void initialize() { | ||
| } | ||
|
|
||
| public: | ||
| QString iremoteAddress; | ||
| quint16 iremotePort = 0; | ||
|
|
||
| QHttpConnection* const iconnection = nullptr; | ||
| }; | ||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
| } // namespace server | ||
| } // namespace qhttp | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
| #endif // QHTTPSERVER_REQUEST_PRIVATE_HPP |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| /** private imeplementation. | ||
| * https://github.com/azadkuh/qhttp | ||
| * | ||
| * @author amir zamani | ||
| * @version 2.0.0 | ||
| * @date 2014-07-11 | ||
| */ | ||
|
|
||
| #ifndef QHTTPSERVER_RESPONSE_PRIVATE_HPP | ||
| #define QHTTPSERVER_RESPONSE_PRIVATE_HPP | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
| #include "httpwriter.hxx" | ||
| #include "qhttpserverresponse.hpp" | ||
| #include "qhttpserver.hpp" | ||
| #include "qhttpserverconnection.hpp" | ||
|
|
||
| #include <QDateTime> | ||
| #include <QLocale> | ||
| #include <QTcpSocket> | ||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
| namespace qhttp { | ||
| namespace server { | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
| class QHttpResponsePrivate : | ||
| public details::HttpWriter<details::HttpResponseBase, QHttpResponsePrivate> | ||
| { | ||
| Q_DECLARE_PUBLIC(QHttpResponse) | ||
|
|
||
| public: | ||
| explicit QHttpResponsePrivate(QHttpConnection* conn, QHttpResponse* q) | ||
| : q_ptr(q), iconnection(conn) { | ||
| QHTTP_LINE_DEEPLOG | ||
| } | ||
|
|
||
| virtual ~QHttpResponsePrivate() { | ||
| QHTTP_LINE_DEEPLOG | ||
| } | ||
|
|
||
| void initialize() { | ||
| isocket.ibackendType = iconnection->backendType(); | ||
| isocket.ilocalSocket = iconnection->localSocket(); | ||
| isocket.itcpSocket = iconnection->tcpSocket(); | ||
|
|
||
| QObject::connect(iconnection, &QHttpConnection::disconnected, | ||
| q_func(), &QHttpResponse::deleteLater); | ||
| } | ||
|
|
||
| QByteArray makeTitle(); | ||
|
|
||
| void prepareHeadersToWrite(); | ||
|
|
||
| protected: | ||
| QHttpResponse* const q_ptr; | ||
| QHttpConnection* const iconnection; | ||
| }; | ||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
| } // namespace server | ||
| } // namespace qhttp | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
| #endif // QHTTPSERVER_RESPONSE_PRIVATE_HPP |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,126 @@ | ||
| /** @file qsocket.hpp | ||
| * | ||
| * @copyright (C) 2016 | ||
| * @date 2016.05.26 | ||
| * @version 1.0.0 | ||
| * @author amir zamani <azadkuh@live.com> | ||
| * | ||
| */ | ||
|
|
||
| #ifndef __QHTTP_SOCKET_HPP__ | ||
| #define __QHTTP_SOCKET_HPP__ | ||
|
|
||
| #include "qhttpfwd.hpp" | ||
|
|
||
| #include <QTcpSocket> | ||
| #include <QLocalSocket> | ||
| #include <QUrl> | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
| namespace qhttp { | ||
| namespace details { | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
|
|
||
| /** an adapter for different socket types. | ||
| * the main purpose of QHttp was to create a small HTTP server with ability to | ||
| * support UNIX sockets (QLocalSocket) | ||
| */ | ||
| class QSocket | ||
| { | ||
| public: | ||
| void close() { | ||
| if ( itcpSocket ) | ||
| itcpSocket->close(); | ||
|
|
||
| if ( ilocalSocket ) | ||
| ilocalSocket->close(); | ||
| } | ||
|
|
||
| void release() { | ||
| close(); | ||
| if ( itcpSocket ) | ||
| itcpSocket->deleteLater(); | ||
|
|
||
| if ( ilocalSocket ) | ||
| ilocalSocket->deleteLater(); | ||
|
|
||
| itcpSocket = nullptr; | ||
| ilocalSocket = nullptr; | ||
| } | ||
|
|
||
| void flush() { | ||
| if ( itcpSocket ) | ||
| itcpSocket->flush(); | ||
|
|
||
| else if ( ilocalSocket ) | ||
| ilocalSocket->flush(); | ||
| } | ||
|
|
||
| bool isOpen() const { | ||
| if ( ibackendType == ETcpSocket && itcpSocket ) | ||
| return itcpSocket->isOpen() | ||
| && itcpSocket->state() == QTcpSocket::ConnectedState; | ||
|
|
||
| else if ( ibackendType == ELocalSocket && ilocalSocket ) | ||
| return ilocalSocket->isOpen() | ||
| && ilocalSocket->state() == QLocalSocket::ConnectedState; | ||
|
|
||
| return false; | ||
| } | ||
|
|
||
| void connectTo(const QUrl& url) { | ||
| if ( ilocalSocket ) | ||
| ilocalSocket->connectToServer(url.path()); | ||
| } | ||
|
|
||
| void connectTo(const QString& host, quint16 port) { | ||
| if ( itcpSocket ) | ||
| itcpSocket->connectToHost(host, port); | ||
| } | ||
|
|
||
| qint64 readRaw(char* buffer, int maxlen) { | ||
| if ( itcpSocket ) | ||
| return itcpSocket->read(buffer, maxlen); | ||
|
|
||
| else if ( ilocalSocket ) | ||
| return ilocalSocket->read(buffer, maxlen); | ||
|
|
||
| return 0; | ||
| } | ||
|
|
||
| void writeRaw(const QByteArray& data) { | ||
| if ( itcpSocket ) | ||
| itcpSocket->write(data); | ||
|
|
||
| else if ( ilocalSocket ) | ||
| ilocalSocket->write(data); | ||
| } | ||
|
|
||
| qint64 bytesAvailable() { | ||
| if ( itcpSocket ) | ||
| return itcpSocket->bytesAvailable(); | ||
|
|
||
| else if ( ilocalSocket ) | ||
| return ilocalSocket->bytesAvailable(); | ||
|
|
||
| return 0; | ||
| } | ||
|
|
||
| void disconnectAllQtConnections() { | ||
| if ( itcpSocket ) | ||
| QObject::disconnect(itcpSocket, 0, 0, 0); | ||
|
|
||
| if ( ilocalSocket ) | ||
| QObject::disconnect(ilocalSocket, 0, 0, 0); | ||
| } | ||
|
|
||
| public: | ||
| TBackend ibackendType = ETcpSocket; | ||
| QTcpSocket* itcpSocket = nullptr; | ||
| QLocalSocket* ilocalSocket = nullptr; | ||
| }; // class QSocket | ||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
| } // namespace details | ||
| } // namespace qhttp | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
| #endif // __QHTTP_SOCKET_HPP__ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,114 @@ | ||
| #include "qhttpabstracts.hpp" | ||
| #include "http-parser/http_parser.h" | ||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
| namespace qhttp { | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
| #if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0)) | ||
| # error "to compile QHttp classes, Qt 5.0 or later is needed." | ||
| #endif | ||
|
|
||
| #define HTTP_STATUS_MAP(XX) \ | ||
| XX(100, "Continue") \ | ||
| XX(101, "Switching Protocols") \ | ||
| /* RFC 2518) obsoleted by RFC 4918 */ \ | ||
| XX(102, "Processing") \ | ||
| XX(200, "OK") \ | ||
| XX(201, "Created") \ | ||
| XX(202, "Accepted") \ | ||
| XX(203, "Non-Authoritative Information") \ | ||
| XX(204, "No Content") \ | ||
| XX(205, "Reset Content") \ | ||
| XX(206, "Partial Content") \ | ||
| /* RFC 4918 */ \ | ||
| XX(207, "Multi-Status") \ | ||
| XX(300, "Multiple Choices") \ | ||
| XX(301, "Moved Permanently") \ | ||
| XX(302, "Moved Temporarily") \ | ||
| XX(303, "See Other") \ | ||
| XX(304, "Not Modified") \ | ||
| XX(305, "Use Proxy") \ | ||
| XX(307, "Temporary Redirect") \ | ||
| XX(400, "Bad Request") \ | ||
| XX(401, "Unauthorized") \ | ||
| XX(402, "Payment Required") \ | ||
| XX(403, "Forbidden") \ | ||
| XX(404, "Not Found") \ | ||
| XX(405, "Method Not Allowed") \ | ||
| XX(406, "Not Acceptable") \ | ||
| XX(407, "Proxy Authentication Required") \ | ||
| XX(408, "Request Time-out") \ | ||
| XX(409, "Conflict") \ | ||
| XX(410, "Gone") \ | ||
| XX(411, "Length Required") \ | ||
| XX(412, "Precondition Failed") \ | ||
| XX(413, "Request Entity Too Large") \ | ||
| XX(414, "Request-URI Too Large") \ | ||
| XX(415, "Unsupported Media Type") \ | ||
| XX(416, "Requested Range Not Satisfiable") \ | ||
| XX(417, "Expectation Failed") \ | ||
| /* RFC 2324 */ \ | ||
| XX(418, "I\"m a teapot") \ | ||
| /* RFC 4918 */ \ | ||
| XX(422, "Unprocessable Entity") \ | ||
| /* RFC 4918 */ \ | ||
| XX(423, "Locked") \ | ||
| /* RFC 4918 */ \ | ||
| XX(424, "Failed Dependency") \ | ||
| /* RFC 4918 */ \ | ||
| XX(425, "Unordered Collection") \ | ||
| /* RFC 2817 */ \ | ||
| XX(426, "Upgrade Required") \ | ||
| XX(500, "Internal Server Error") \ | ||
| XX(501, "Not Implemented") \ | ||
| XX(502, "Bad Gateway") \ | ||
| XX(503, "Service Unavailable") \ | ||
| XX(504, "Gateway Time-out") \ | ||
| XX(505, "HTTP Version not supported") \ | ||
| /* RFC 2295 */ \ | ||
| XX(506, "Variant Also Negotiates") \ | ||
| /* RFC 4918 */ \ | ||
| XX(507, "Insufficient Storage") \ | ||
| XX(509, "Bandwidth Limit Exceeded") \ | ||
| /* RFC 2774 */ \ | ||
| XX(510, "Not Extended") | ||
|
|
||
| #define PATCH_STATUS_CODES(n,s) {n, s}, | ||
| static struct { | ||
| int code; | ||
| const char* message; | ||
| } g_status_codes[] { | ||
| HTTP_STATUS_MAP(PATCH_STATUS_CODES) | ||
| }; | ||
| #undef PATCH_STATUS_CODES | ||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
|
|
||
| const char* | ||
| Stringify::toString(TStatusCode code) { | ||
| size_t count = sizeof(g_status_codes) / sizeof(g_status_codes[0]); | ||
| for ( size_t i = 0; i < count; i++ ) { | ||
| if ( g_status_codes[i].code == code ) | ||
| return g_status_codes[i].message; | ||
| } | ||
|
|
||
| return nullptr; | ||
| } | ||
|
|
||
| const char* | ||
| Stringify::toString(THttpMethod method) { | ||
| return http_method_str(static_cast<http_method>(method)); | ||
| } | ||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
|
|
||
| QHttpAbstractInput::QHttpAbstractInput(QObject* parent) : QObject(parent) { | ||
| } | ||
|
|
||
| QHttpAbstractOutput::QHttpAbstractOutput(QObject *parent) : QObject(parent) { | ||
| } | ||
|
|
||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
| } // namespace qhttp | ||
| /////////////////////////////////////////////////////////////////////////////// |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,190 @@ | ||
| /** interfaces of QHttp' incomming and outgoing classes. | ||
| * https://github.com/azadkuh/qhttp | ||
| * | ||
| * @author amir zamani | ||
| * @version 2.0.0 | ||
| * @date 2014-07-11 | ||
| */ | ||
|
|
||
| #ifndef QHTTPABSTRACTS_HPP | ||
| #define QHTTPABSTRACTS_HPP | ||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
| #include "qhttpfwd.hpp" | ||
|
|
||
| #include <QObject> | ||
| #include <functional> | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
| namespace qhttp { | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
|
|
||
| /** a utility class to give the string representation of qhttp types. */ | ||
| class QHTTP_API Stringify | ||
| { | ||
| public: | ||
| /** returns the standard message for an HTTP status code. */ | ||
| static const char* toString(TStatusCode); | ||
|
|
||
| /** returns the standars name of an HTTP method. */ | ||
| static const char* toString(THttpMethod); | ||
| }; | ||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
|
|
||
| /** an interface for input (incoming) HTTP packets. | ||
| * server::QHttpRequest or client::QHttpResponse inherit from this class. */ | ||
| class QHTTP_API QHttpAbstractInput : public QObject | ||
| { | ||
| Q_OBJECT | ||
|
|
||
| public: | ||
| /** Return all the headers in the incomming packet. | ||
| * This returns a reference. If you want to store headers | ||
| * somewhere else, where the request may be deleted, | ||
| * make sure you store them as a copy. | ||
| * @note All header names are <b>lowercase</b> . */ | ||
| virtual const THeaderHash& headers() const=0; | ||
|
|
||
| /** The HTTP version of the packet. | ||
| * @return A string in the form of "x.x" */ | ||
| virtual const QString& httpVersion() const=0; | ||
|
|
||
| /** If this packet was successfully received. | ||
| * Set before end() has been emitted, stating whether | ||
| * the message was properly received. This is false | ||
| * until the receiving the full request has completed. */ | ||
| virtual bool isSuccessful() const=0; | ||
|
|
||
| signals: | ||
| /** Emitted when new body data has been received. | ||
| * @param data Received data. | ||
| * @note This may be emitted zero or more times depending on the transfer type. | ||
| * @see onData(); | ||
| */ | ||
| void data(QByteArray data); | ||
|
|
||
| /** Emitted when the incomming packet has been fully received. | ||
| * @note The no more data() signals will be emitted after this. | ||
| * @see onEnd(); | ||
| */ | ||
| void end(); | ||
|
|
||
| public: | ||
| /** optionally set a handler for data() signal. | ||
| * @param dataHandler a std::function or lambda handler to receive incoming data. | ||
| * @note if you set this handler, the data() signal won't be emitted anymore. | ||
| */ | ||
| template<class Func> | ||
| void onData(Func f) { | ||
| QObject::connect(this, &QHttpAbstractInput::data, f); | ||
| } | ||
|
|
||
|
|
||
| /** optionally set a handler for end() signal. | ||
| * @param endHandler a std::function or lambda handler to receive end notification. | ||
| * @note if you set this handler, the end() signal won't be emitted anymore. | ||
| */ | ||
| template<class Func> | ||
| void onEnd(Func f) { | ||
| QObject::connect(this, &QHttpAbstractInput::end, f); | ||
| } | ||
|
|
||
| public: | ||
| /** tries to collect all the incoming data internally. | ||
| * @note if you call this method, data() signal won't be emitted and | ||
| * onData() will have no effect. | ||
| * | ||
| * @param atMost maximum acceptable incoming data. if the incoming data | ||
| * exceeds this value, the connection won't read any more data and | ||
| * end() signal will be emitted. | ||
| * default value (-1) means read data as "content-length" or unlimited if | ||
| * the body size is unknown. | ||
| */ | ||
| virtual void collectData(int atMost = -1) =0; | ||
|
|
||
| /** returns the collected data requested by collectData(). */ | ||
| virtual const QByteArray& collectedData()const =0; | ||
|
|
||
|
|
||
| public: | ||
| virtual ~QHttpAbstractInput() = default; | ||
|
|
||
| explicit QHttpAbstractInput(QObject* parent); | ||
|
|
||
| Q_DISABLE_COPY(QHttpAbstractInput) | ||
| }; | ||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
|
|
||
| /** an interface for output (outgoing) HTTP packets. | ||
| * server::QHttpResponse or client::QHttpRequest inherit from this class. */ | ||
| class QHTTP_API QHttpAbstractOutput : public QObject | ||
| { | ||
| Q_OBJECT | ||
|
|
||
| public: | ||
| /** changes the HTTP version string ex: "1.1" or "1.0". | ||
| * version is "1.1" set by default. */ | ||
| virtual void setVersion(const QString& versionString)=0; | ||
|
|
||
| /** helper function. @sa addHeader */ | ||
| template<typename T> | ||
| void addHeaderValue(const QByteArray &field, T value); | ||
|
|
||
| /** adds an HTTP header to the packet. | ||
| * @note this method does not actually write anything to socket, just prepares the headers(). */ | ||
| virtual void addHeader(const QByteArray& field, const QByteArray& value)=0; | ||
|
|
||
| /** returns all the headers that already been set. */ | ||
| virtual THeaderHash& headers()=0; | ||
|
|
||
| /** writes a block of data into the HTTP packet. | ||
| * @note headers are written (flushed) before any data. | ||
| * @warning after calling this method add a new header, set staus code, set Url have no effect! */ | ||
| virtual void write(const QByteArray &data)=0; | ||
|
|
||
| /** ends (finishes) the outgoing packet by calling write(). | ||
| * headers and data will be flushed to the underlying socket. | ||
| * | ||
| * @sa write() */ | ||
| virtual void end(const QByteArray &data = QByteArray())=0; | ||
|
|
||
| signals: | ||
| /** Emitted when all the data has been sent. | ||
| * this signal indicates that the underlaying socket has transmitted all | ||
| * of it's buffered data. */ | ||
| void allBytesWritten(); | ||
|
|
||
| /** Emitted when the packet is finished and reports if it was the last packet. | ||
| * if it was the last packet (google for "Connection: keep-alive / close") | ||
| * the http connection (socket) will be closed automatically. */ | ||
| void done(bool wasTheLastPacket); | ||
|
|
||
| public: | ||
| virtual ~QHttpAbstractOutput() = default; | ||
|
|
||
| protected: | ||
| explicit QHttpAbstractOutput(QObject* parent); | ||
|
|
||
| Q_DISABLE_COPY(QHttpAbstractOutput) | ||
| }; | ||
|
|
||
| template<> inline void | ||
| QHttpAbstractOutput::addHeaderValue<int>(const QByteArray &field, int value) { | ||
| addHeader(field, QString::number(value).toLatin1()); | ||
| } | ||
|
|
||
| template<> inline void | ||
| QHttpAbstractOutput::addHeaderValue<size_t>(const QByteArray &field, size_t value) { | ||
| addHeader(field, QString::number(value).toLatin1()); | ||
| } | ||
|
|
||
| template<> inline void | ||
| QHttpAbstractOutput::addHeaderValue<QString>(const QByteArray &field, QString value) { | ||
| addHeader(field, value.toUtf8()); | ||
| } | ||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
| } // namespace qhttp | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
| #endif // QHTTPABSTRACTS_HPP |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,284 @@ | ||
| #include "private/qhttpclient_private.hpp" | ||
|
|
||
| #include <QTimerEvent> | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
| namespace qhttp { | ||
| namespace client { | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
| QHttpClient::QHttpClient(QObject *parent) | ||
| : QObject(parent), d_ptr(new QHttpClientPrivate(this)) { | ||
| QHTTP_LINE_LOG | ||
| } | ||
|
|
||
| QHttpClient::QHttpClient(QHttpClientPrivate &dd, QObject *parent) | ||
| : QObject(parent), d_ptr(&dd) { | ||
| QHTTP_LINE_LOG | ||
| } | ||
|
|
||
| QHttpClient::~QHttpClient() { | ||
| QHTTP_LINE_LOG | ||
| } | ||
|
|
||
| quint32 | ||
| QHttpClient::timeOut() const { | ||
| return d_func()->itimeOut; | ||
| } | ||
|
|
||
| void | ||
| QHttpClient::setTimeOut(quint32 t) { | ||
| d_func()->itimeOut = t; | ||
| } | ||
|
|
||
| bool | ||
| QHttpClient::isOpen() const { | ||
| return d_func()->isocket.isOpen(); | ||
| } | ||
|
|
||
| void | ||
| QHttpClient::killConnection() { | ||
| Q_D(QHttpClient); | ||
|
|
||
| d->iconnectingTimer.stop(); | ||
| d->itimer.stop(); | ||
| d->isocket.close(); | ||
| } | ||
|
|
||
| TBackend | ||
| QHttpClient::backendType() const { | ||
| return d_func()->isocket.ibackendType; | ||
| } | ||
|
|
||
| QTcpSocket* | ||
| QHttpClient::tcpSocket() const { | ||
| return d_func()->isocket.itcpSocket; | ||
| } | ||
|
|
||
| QLocalSocket* | ||
| QHttpClient::localSocket() const { | ||
| return d_func()->isocket.ilocalSocket; | ||
| } | ||
|
|
||
| void | ||
| QHttpClient::setConnectingTimeOut(quint32 timeout) { | ||
| Q_D(QHttpClient); | ||
|
|
||
| if ( timeout == 0 ) { | ||
| d->iconnectingTimer.stop(); | ||
|
|
||
| } else { | ||
| d->iconnectingTimer.start(timeout, | ||
| Qt::CoarseTimer, | ||
| this | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| bool | ||
| QHttpClient::request(THttpMethod method, QUrl url, | ||
| const TRequstHandler &reqHandler, | ||
| const TResponseHandler &resHandler) { | ||
| Q_D(QHttpClient); | ||
|
|
||
| d->ireqHandler = nullptr; | ||
| d->irespHandler = nullptr; | ||
|
|
||
| // if url is a local file (UNIX socket) the host could be empty! | ||
| if ( !url.isValid() || url.isEmpty() /*|| url.host().isEmpty()*/ ) | ||
| return false; | ||
|
|
||
| // process handlers | ||
| if ( resHandler ) { | ||
| d->irespHandler = resHandler; | ||
|
|
||
| if ( reqHandler ) | ||
| d->ireqHandler = reqHandler; | ||
| else | ||
| d->ireqHandler = [](QHttpRequest* req) ->void { | ||
| req->addHeader("connection", "close"); | ||
| req->end(); | ||
| }; | ||
| } | ||
|
|
||
| auto requestCreator = [this, method, url]() { | ||
| // create request object | ||
| if ( d_ptr->ilastRequest ) | ||
| d_ptr->ilastRequest->deleteLater(); | ||
|
|
||
| d_ptr->ilastRequest = new QHttpRequest(this); | ||
| QObject::connect(d_ptr->ilastRequest, &QHttpRequest::done, [this](bool wasTheLastPacket){ | ||
| d_ptr->ikeepAlive = !wasTheLastPacket; | ||
| }); | ||
|
|
||
| d_ptr->ilastRequest->d_ptr->imethod = method; | ||
| d_ptr->ilastRequest->d_ptr->iurl = url; | ||
| }; | ||
|
|
||
| // connecting to host/server must be the last thing. (after all function handlers and ...) | ||
| // check for type | ||
| if ( url.scheme().toLower() == QLatin1String("file") ) { | ||
| d->isocket.ibackendType = ELocalSocket; | ||
| d->initializeSocket(); | ||
|
|
||
| requestCreator(); | ||
|
|
||
| if ( d->isocket.isOpen() ) | ||
| d->onConnected(); | ||
| else | ||
| d->isocket.connectTo(url); | ||
|
|
||
| } else { | ||
| d->isocket.ibackendType = ETcpSocket; | ||
| d->initializeSocket(); | ||
|
|
||
| requestCreator(); | ||
|
|
||
| if ( d->isocket.isOpen() ) | ||
| d->onConnected(); | ||
| else | ||
| d->isocket.connectTo(url.host(), url.port(80)); | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| void | ||
| QHttpClient::timerEvent(QTimerEvent *e) { | ||
| Q_D(QHttpClient); | ||
|
|
||
| if ( e->timerId() == d->itimer.timerId() ) { | ||
| killConnection(); | ||
|
|
||
| } else if ( e->timerId() == d->iconnectingTimer.timerId() ) { | ||
| d->iconnectingTimer.stop(); | ||
| emit connectingTimeOut(); | ||
| } | ||
| } | ||
|
|
||
| void | ||
| QHttpClient::onRequestReady(QHttpRequest *req) { | ||
| emit httpConnected(req); | ||
| } | ||
|
|
||
| void | ||
| QHttpClient::onResponseReady(QHttpResponse *res) { | ||
| emit newResponse(res); | ||
| } | ||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
|
|
||
| // if server closes the connection, ends the response or by any other reason | ||
| // the socket disconnects, then the irequest and iresponse instances may have | ||
| // been deleted. In these situations reading more http body or emitting end() | ||
| // for incoming request are not possible: | ||
| // if ( ilastRequest == nullptr ) | ||
| // return 0; | ||
|
|
||
|
|
||
|
|
||
| int | ||
| QHttpClientPrivate::messageBegin(http_parser*) { | ||
| itempHeaderField.clear(); | ||
| itempHeaderValue.clear(); | ||
|
|
||
| return 0; | ||
| } | ||
|
|
||
| int | ||
| QHttpClientPrivate::status(http_parser* parser, const char* at, size_t length) { | ||
| if ( ilastResponse ) | ||
| ilastResponse->deleteLater(); | ||
|
|
||
| ilastResponse = new QHttpResponse(q_func()); | ||
| ilastResponse->d_func()->istatus = static_cast<TStatusCode>(parser->status_code); | ||
| ilastResponse->d_func()->iversion = QString("%1.%2") | ||
| .arg(parser->http_major) | ||
| .arg(parser->http_minor); | ||
| ilastResponse->d_func()->icustomStatusMessage = QString::fromUtf8(at, length); | ||
|
|
||
| return 0; | ||
| } | ||
|
|
||
| int | ||
| QHttpClientPrivate::headerField(http_parser*, const char* at, size_t length) { | ||
| if ( ilastResponse == nullptr ) | ||
| return 0; | ||
|
|
||
| // insert the header we parsed previously | ||
| // into the header map | ||
| if ( !itempHeaderField.isEmpty() && !itempHeaderValue.isEmpty() ) { | ||
| // header names are always lower-cased | ||
| ilastResponse->d_func()->iheaders.insert( | ||
| itempHeaderField.toLower(), | ||
| itempHeaderValue.toLower() | ||
| ); | ||
| // clear header value. this sets up a nice | ||
| // feedback loop where the next time | ||
| // HeaderValue is called, it can simply append | ||
| itempHeaderField.clear(); | ||
| itempHeaderValue.clear(); | ||
| } | ||
|
|
||
| itempHeaderField.append(at, length); | ||
| return 0; | ||
| } | ||
|
|
||
| int | ||
| QHttpClientPrivate::headerValue(http_parser*, const char* at, size_t length) { | ||
|
|
||
| itempHeaderValue.append(at, length); | ||
| return 0; | ||
| } | ||
|
|
||
| int | ||
| QHttpClientPrivate::headersComplete(http_parser*) { | ||
| if ( ilastResponse == nullptr ) | ||
| return 0; | ||
|
|
||
| // Insert last remaining header | ||
| ilastResponse->d_func()->iheaders.insert( | ||
| itempHeaderField.toLower(), | ||
| itempHeaderValue.toLower() | ||
| ); | ||
|
|
||
| if ( irespHandler ) | ||
| irespHandler(ilastResponse); | ||
| else | ||
| q_func()->onResponseReady(ilastResponse); | ||
|
|
||
| return 0; | ||
| } | ||
|
|
||
| int | ||
| QHttpClientPrivate::body(http_parser*, const char* at, size_t length) { | ||
| if ( ilastResponse == nullptr ) | ||
| return 0; | ||
|
|
||
| ilastResponse->d_func()->ireadState = QHttpResponsePrivate::EPartial; | ||
|
|
||
| if ( ilastResponse->d_func()->icollectRequired ) { | ||
| if ( !ilastResponse->d_func()->append(at, length) ) { | ||
| // forcefully dispatch the ilastResponse | ||
| finalizeConnection(); | ||
| } | ||
|
|
||
| return 0; | ||
| } | ||
|
|
||
| emit ilastResponse->data(QByteArray(at, length)); | ||
| return 0; | ||
| } | ||
|
|
||
| int | ||
| QHttpClientPrivate::messageComplete(http_parser*) { | ||
| if ( ilastResponse == nullptr ) | ||
| return 0; | ||
|
|
||
| // response is done | ||
| finalizeConnection(); | ||
| return 0; | ||
| } | ||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
| } // namespace client | ||
| } // namespace qhttp | ||
| /////////////////////////////////////////////////////////////////////////////// |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,182 @@ | ||
| /** HTTP client class. | ||
| * https://github.com/azadkuh/qhttp | ||
| * | ||
| * @author amir zamani | ||
| * @version 2.0.0 | ||
| * @date 2014-07-11 | ||
| */ | ||
|
|
||
| #ifndef QHTTPCLIENT_HPP | ||
| #define QHTTPCLIENT_HPP | ||
|
|
||
| // configured by src.pro | ||
| #if defined(QHTTP_HAS_CLIENT) | ||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
| #include "qhttpfwd.hpp" | ||
|
|
||
| #include <QTcpSocket> | ||
| #include <QUrl> | ||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
| namespace qhttp { | ||
| namespace client { | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
| using TRequstHandler = std::function<void (QHttpRequest*)>; | ||
| using TResponseHandler = std::function<void (QHttpResponse*)>; | ||
|
|
||
| /** a simple and async HTTP client class which sends a request to an HTTP server and parses the | ||
| * corresponding response. | ||
| * This class internally handles the memory management and life cycle of QHttpRequest and | ||
| * QHttpResponse instances. you do not have to manually delete or keep their pointers. | ||
| * in fact the QHttpRequest and QHttpResponse object will be deleted when the internal socket | ||
| * disconnects. | ||
| */ | ||
| class QHTTP_API QHttpClient : public QObject | ||
| { | ||
| Q_OBJECT | ||
|
|
||
| Q_PROPERTY(quint32 timeOut READ timeOut WRITE setTimeOut) | ||
|
|
||
| public: | ||
| explicit QHttpClient(QObject *parent = nullptr); | ||
|
|
||
| virtual ~QHttpClient(); | ||
|
|
||
| /** tries to connect to a HTTP server. | ||
| * when the connection is made, the reqHandler will be called | ||
| * and when the response is ready, resHandler will be called. | ||
| * @note httpConnected() and newResponse() won't be emitted. | ||
| * | ||
| * @param method an HTTP method, ex: GET, POST, ... | ||
| * @param url specifies server's address, port and optional path and query strings. | ||
| * if url starts with socket:// the request will be made on QLocalSocket, otherwise | ||
| * normal QTcpSocket will be used. | ||
| * @param resHandler response handler (a lambda, std::function object, ...) | ||
| * @return true if the url is valid or false (no connection will be made). | ||
| */ | ||
| bool request(THttpMethod method, QUrl url, | ||
| const TRequstHandler& reqHandler, | ||
| const TResponseHandler& resHandler); | ||
|
|
||
| /** tries to connect to a HTTP server. | ||
| * when the connection is made, a default request handler is called automatically ( | ||
| * simply calls req->end()) and when the response is ready, resHandler will be called. | ||
| * @note httpConnected() and newResponse() won't be emitted. | ||
| * | ||
| * @param method an HTTP method, ex: GET, POST, ... | ||
| * @param url specifies server's address, port and optional path and query strings. | ||
| * @param resHandler response handler (a lambda, std::function object, ...) | ||
| * @return true if the url is valid or false (no connection will be made). | ||
| */ | ||
| inline bool request(THttpMethod method, QUrl url, const TResponseHandler& resHandler) { | ||
| return request(method, url, nullptr, resHandler); | ||
| } | ||
|
|
||
| /** tries to connect to a HTTP server. | ||
| * when the connection is made, creates and emits a QHttpRequest instance | ||
| * by @sa httpConnected(QHttpRequest*). | ||
| * @note both httpConnected() and newResponse() may be emitted. | ||
| * | ||
| * @param method an HTTP method, ex: GET, POST, ... | ||
| * @param url specifies server's address, port and optional path and query strings. | ||
| * @return true if the url is valid or false (no connection will be made). | ||
| */ | ||
| inline bool request(THttpMethod method, QUrl url) { | ||
| return request(method, url, nullptr, nullptr); | ||
| } | ||
|
|
||
| /** checks if the connetion to the server is open. */ | ||
| bool isOpen() const; | ||
|
|
||
| /** forcefully close the connection. */ | ||
| void killConnection(); | ||
|
|
||
|
|
||
| /** returns time-out value [mSec] for ESTABLISHED connections (sockets). | ||
| * @sa setTimeOut(). */ | ||
| quint32 timeOut()const; | ||
|
|
||
| /** set time-out for ESTABLISHED connections in miliseconds [mSec]. | ||
| * each (already opened) connection will be forcefully closed after this timeout. | ||
| * a zero (0) value disables timer for new connections. */ | ||
| void setTimeOut(quint32); | ||
|
|
||
| /** set a time-out [mSec] for making a new connection (make a request). | ||
| * if connecting to server takes more than this time-out value, | ||
| * the @sa timedOut(quint32) signal will be emitted and connection will be killed. | ||
| * 0 (default) timeout value means to disable this timer. | ||
| */ | ||
| void setConnectingTimeOut(quint32); | ||
|
|
||
| template<class Handler> | ||
| void setConnectingTimeOut(quint32 timeout, Handler&& func) { | ||
| setConnectingTimeOut(timeout); | ||
| QObject::connect(this, &QHttpClient::connectingTimeOut, | ||
| std::forward<Handler&&>(func) | ||
| ); | ||
| } | ||
|
|
||
| /** returns the backend type of this client. */ | ||
| TBackend backendType() const; | ||
|
|
||
| /** returns tcp socket of the connection if backend() == ETcpSocket. */ | ||
| QTcpSocket* tcpSocket() const; | ||
|
|
||
| /** returns local socket of the connection if backend() == ELocalSocket. */ | ||
| QLocalSocket* localSocket() const; | ||
|
|
||
| signals: | ||
| /** emitted when a new HTTP connection to the server is established. | ||
| * if you overload onRequestReady this signal won't be emitted. | ||
| * @sa onRequestReady | ||
| * @sa QHttpRequest | ||
| */ | ||
| void httpConnected(QHttpRequest* req); | ||
|
|
||
| /** emitted when a new response is received from the server. | ||
| * if you overload onResponseReady this signal won't be emitted. | ||
| * @sa onResponseReady | ||
| * @sa QHttpResponse | ||
| */ | ||
| void newResponse(QHttpResponse* res); | ||
|
|
||
| /** emitted when the HTTP connection drops or being disconnected. */ | ||
| void disconnected(); | ||
|
|
||
| /// emitted when fails to connect to a HTTP server. @sa setConnectingTimeOut() | ||
| void connectingTimeOut(); | ||
|
|
||
|
|
||
| protected: | ||
| /** called when a new HTTP connection is established. | ||
| * you can overload this method, the default implementaion only emits connected(). | ||
| * @param req use this request instance for assinging the | ||
| * request headers and sending optional body. | ||
| * @see httpConnected(QHttpRequest*) | ||
| */ | ||
| virtual void onRequestReady(QHttpRequest* req); | ||
|
|
||
| /** called when a new response is received from the server. | ||
| * you can overload this method, the default implementaion only emits newResponse(). | ||
| * @param res use this instance for reading incoming response. | ||
| * @see newResponse(QHttpResponse*) | ||
| */ | ||
| virtual void onResponseReady(QHttpResponse* res); | ||
|
|
||
| protected: | ||
| explicit QHttpClient(QHttpClientPrivate&, QObject*); | ||
|
|
||
| void timerEvent(QTimerEvent*) override; | ||
|
|
||
| Q_DECLARE_PRIVATE(QHttpClient) | ||
| Q_DISABLE_COPY(QHttpClient) | ||
| QScopedPointer<QHttpClientPrivate> d_ptr; | ||
| }; | ||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
| } // namespace client | ||
| } // namespace qhttp | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
| #endif // QHTTP_HAS_CLIENT | ||
| #endif // define QHTTPCLIENT_HPP |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,98 @@ | ||
| #include "private/qhttpclientrequest_private.hpp" | ||
| #include "qhttpclient.hpp" | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
| namespace qhttp { | ||
| namespace client { | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
| QHttpRequest::QHttpRequest(QHttpClient* cli) | ||
| : QHttpAbstractOutput(cli) , d_ptr(new QHttpRequestPrivate(cli, this)) { | ||
| d_ptr->initialize(); | ||
| QHTTP_LINE_LOG | ||
| } | ||
|
|
||
| QHttpRequest::QHttpRequest(QHttpRequestPrivate& dd, QHttpClient* cli) | ||
| : QHttpAbstractOutput(cli) , d_ptr(&dd) { | ||
| d_ptr->initialize(); | ||
| QHTTP_LINE_LOG | ||
| } | ||
|
|
||
| QHttpRequest::~QHttpRequest() { | ||
| QHTTP_LINE_LOG | ||
| } | ||
|
|
||
| void | ||
| QHttpRequest::setVersion(const QString &versionString) { | ||
| d_func()->iversion = versionString; | ||
| } | ||
|
|
||
| void | ||
| QHttpRequest::addHeader(const QByteArray &field, const QByteArray &value) { | ||
| d_func()->addHeader(field, value); | ||
| } | ||
|
|
||
| THeaderHash& | ||
| QHttpRequest::headers() { | ||
| return d_func()->iheaders; | ||
| } | ||
|
|
||
| void | ||
| QHttpRequest::write(const QByteArray &data) { | ||
| d_func()->writeData(data); | ||
| } | ||
|
|
||
| void | ||
| QHttpRequest::end(const QByteArray &data) { | ||
| Q_D(QHttpRequest); | ||
|
|
||
| if ( d->endPacket(data) ) | ||
| emit done(!d->ikeepAlive); | ||
| } | ||
|
|
||
| QHttpClient* | ||
| QHttpRequest::connection() const { | ||
| return d_func()->iclient; | ||
| } | ||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
| QByteArray | ||
| QHttpRequestPrivate::makeTitle() { | ||
|
|
||
| QByteArray title; | ||
| title.reserve(512); | ||
| title.append(qhttp::Stringify::toString(imethod)) | ||
| .append(" "); | ||
|
|
||
| QByteArray path = iurl.path(QUrl::FullyEncoded).toLatin1(); | ||
| if ( path.size() == 0 ) | ||
| path = "/"; | ||
| title.append(path); | ||
|
|
||
| if ( iurl.hasQuery() ) | ||
| title.append("?").append(iurl.query(QUrl::FullyEncoded).toLatin1()); | ||
|
|
||
|
|
||
| title.append(" HTTP/") | ||
| .append(iversion.toLatin1()) | ||
| .append("\r\n"); | ||
|
|
||
| return title; | ||
| } | ||
|
|
||
| void | ||
| QHttpRequestPrivate::prepareHeadersToWrite() { | ||
|
|
||
| if ( !iheaders.contains("host") ) { | ||
| quint16 port = iurl.port(); | ||
| if ( port == 0 ) | ||
| port = 80; | ||
|
|
||
| iheaders.insert("host", | ||
| QString("%1:%2").arg(iurl.host()).arg(port).toLatin1() | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
| } // namespace client | ||
| } // namespace qhttp | ||
| /////////////////////////////////////////////////////////////////////////////// |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| /** HTTP request from a client. | ||
| * https://github.com/azadkuh/qhttp | ||
| * | ||
| * @author amir zamani | ||
| * @version 2.0.0 | ||
| * @date 2014-07-11 | ||
| */ | ||
|
|
||
| #ifndef QHTTPCLIENT_REQUEST_HPP | ||
| #define QHTTPCLIENT_REQUEST_HPP | ||
|
|
||
| // configured by src.pro | ||
| #if defined(QHTTP_HAS_CLIENT) | ||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
| #include "qhttpabstracts.hpp" | ||
| #include <QUrl> | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
| namespace qhttp { | ||
| namespace client { | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
| /** a class for building a new HTTP request. | ||
| * the life cycle of this class and the memory management is handled by QHttpClient. | ||
| * @sa QHttpClient | ||
| */ | ||
| class QHTTP_API QHttpRequest : public QHttpAbstractOutput | ||
| { | ||
| Q_OBJECT | ||
|
|
||
| public: | ||
| virtual ~QHttpRequest(); | ||
|
|
||
| public: // QHttpAbstractOutput methods: | ||
| /** @see QHttpAbstractOutput::setVersion(). */ | ||
| void setVersion(const QString& versionString) override; | ||
|
|
||
| /** @see QHttpAbstractOutput::addHeader(). */ | ||
| void addHeader(const QByteArray& field, const QByteArray& value) override; | ||
|
|
||
| /** @see QHttpAbstractOutput::headers(). */ | ||
| THeaderHash& headers() override; | ||
|
|
||
| /** @see QHttpAbstractOutput::write(). */ | ||
| void write(const QByteArray &data) override; | ||
|
|
||
| /** @see QHttpAbstractOutput::end(). */ | ||
| void end(const QByteArray &data = QByteArray()) override; | ||
|
|
||
| public: | ||
| /** returns parent QHttpClient object. */ | ||
| QHttpClient* connection() const; | ||
|
|
||
| protected: | ||
| explicit QHttpRequest(QHttpClient*); | ||
| explicit QHttpRequest(QHttpRequestPrivate&, QHttpClient*); | ||
| friend class QHttpClient; | ||
|
|
||
| Q_DECLARE_PRIVATE(QHttpRequest) | ||
| QScopedPointer<QHttpRequestPrivate> d_ptr; | ||
| }; | ||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
| } // namespace client | ||
| } // namespace qhttp | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
| #endif // QHTTP_HAS_CLIENT | ||
| #endif // define QHTTPCLIENT_REQUEST_HPP |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| #include "private/qhttpclientresponse_private.hpp" | ||
| #include "qhttpclient.hpp" | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
| namespace qhttp { | ||
| namespace client { | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
| QHttpResponse::QHttpResponse(QHttpClient *cli) | ||
| : QHttpAbstractInput(cli), d_ptr(new QHttpResponsePrivate(cli, this)) { | ||
| d_ptr->initialize(); | ||
| QHTTP_LINE_LOG | ||
| } | ||
|
|
||
| QHttpResponse::QHttpResponse(QHttpResponsePrivate &dd, QHttpClient *cli) | ||
| : QHttpAbstractInput(cli), d_ptr(&dd) { | ||
| d_ptr->initialize(); | ||
| QHTTP_LINE_LOG | ||
| } | ||
|
|
||
| QHttpResponse::~QHttpResponse() { | ||
| QHTTP_LINE_LOG | ||
| } | ||
|
|
||
| TStatusCode | ||
| QHttpResponse::status() const { | ||
| return d_func()->istatus; | ||
| } | ||
|
|
||
| const QString& | ||
| QHttpResponse::statusString() const { | ||
| return d_func()->icustomStatusMessage; | ||
| } | ||
|
|
||
| const QString& | ||
| QHttpResponse::httpVersion() const { | ||
| return d_func()->iversion; | ||
| } | ||
|
|
||
| const THeaderHash& | ||
| QHttpResponse::headers() const { | ||
| return d_func()->iheaders; | ||
| } | ||
|
|
||
| bool | ||
| QHttpResponse::isSuccessful() const { | ||
| return d_func()->isuccessful; | ||
| } | ||
|
|
||
| void | ||
| QHttpResponse::collectData(int atMost) { | ||
| d_func()->collectData(atMost); | ||
| } | ||
|
|
||
| const QByteArray& | ||
| QHttpResponse::collectedData() const { | ||
| return d_func()->icollectedData; | ||
| } | ||
|
|
||
| QHttpClient* | ||
| QHttpResponse::connection() const { | ||
| return d_func()->iclient; | ||
| } | ||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
| } // namespace client | ||
| } // namespace qhttp | ||
| /////////////////////////////////////////////////////////////////////////////// |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| /** HTTP response received by client. | ||
| * https://github.com/azadkuh/qhttp | ||
| * | ||
| * @author amir zamani | ||
| * @version 2.0.0 | ||
| * @date 2014-07-11 | ||
| */ | ||
|
|
||
| #ifndef QHTTPCLIENT_RESPONSE_HPP | ||
| #define QHTTPCLIENT_RESPONSE_HPP | ||
|
|
||
| // configured by src.pro | ||
| #if defined(QHTTP_HAS_CLIENT) | ||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
|
|
||
| #include "qhttpabstracts.hpp" | ||
|
|
||
| #include <QUrl> | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
| namespace qhttp { | ||
| namespace client { | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
| /** a class for reading incoming HTTP response from a server. | ||
| * the life cycle of this class and the memory management is handled by QHttpClient. | ||
| * @sa QHttpClient | ||
| */ | ||
| class QHTTP_API QHttpResponse : public QHttpAbstractInput | ||
| { | ||
| Q_OBJECT | ||
|
|
||
| public: | ||
| virtual ~QHttpResponse(); | ||
|
|
||
| public: // QHttpAbstractInput methods: | ||
| /** @see QHttpAbstractInput::headers(). */ | ||
| const THeaderHash& headers() const override; | ||
|
|
||
| /** @see QHttpAbstractInput::httpVersion(). */ | ||
| const QString& httpVersion() const override; | ||
|
|
||
| /** @see QHttpAbstractInput::isSuccessful(). */ | ||
| bool isSuccessful() const override; | ||
|
|
||
| /** @see QHttpAbstractInput::collectData(). */ | ||
| void collectData(int atMost = -1) override; | ||
|
|
||
| /** @see QHttpAbstractInput::collectedData(). */ | ||
| const QByteArray& collectedData()const override; | ||
|
|
||
|
|
||
| public: | ||
| /** The status code of this response. */ | ||
| TStatusCode status() const ; | ||
|
|
||
| /** The server status message as string. | ||
| * may be slightly different than: @code qhttp::Stringify::toString(status()); @endcode | ||
| * depending on implementation of HTTP server. */ | ||
| const QString& statusString() const; | ||
|
|
||
| /** returns parent QHttpClient object. */ | ||
| QHttpClient* connection() const; | ||
|
|
||
| protected: | ||
| explicit QHttpResponse(QHttpClient*); | ||
| explicit QHttpResponse(QHttpResponsePrivate&, QHttpClient*); | ||
| friend class QHttpClientPrivate; | ||
|
|
||
| Q_DECLARE_PRIVATE(QHttpResponse) | ||
| QScopedPointer<QHttpResponsePrivate> d_ptr; | ||
| }; | ||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
| } // namespace client | ||
| } // namespace qhttp | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
| #endif // QHTTP_HAS_CLIENT | ||
| #endif // define QHTTPCLIENT_RESPONSE_HPP |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,221 @@ | ||
| /** forward declarations and general definitions of the QHttp. | ||
| * https://github.com/azadkuh/qhttp | ||
| * | ||
| * @author amir zamani | ||
| * @version 2.0.0 | ||
| * @date 2014-07-11 | ||
| */ | ||
|
|
||
| #ifndef QHTTPFWD_HPP | ||
| #define QHTTPFWD_HPP | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
| #include <QHash> | ||
| #include <QString> | ||
| #include <QtGlobal> | ||
|
|
||
| #include <functional> | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
| // Qt | ||
| class QTcpServer; | ||
| class QTcpSocket; | ||
| class QLocalServer; | ||
| class QLocalSocket; | ||
|
|
||
| // http_parser | ||
| struct http_parser_settings; | ||
| struct http_parser; | ||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
| namespace qhttp { | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
|
|
||
| /// QHash/QMap iterators are incompatibility with range for | ||
| template<class Iterator, class Func> | ||
| void for_each(Iterator first, Iterator last, Func&& f) { | ||
| while ( first != last ) { | ||
| f( first ); | ||
| ++first; | ||
| } | ||
| } | ||
|
|
||
| /** A map of request or response headers. */ | ||
| class THeaderHash : public QHash<QByteArray, QByteArray> | ||
| { | ||
| public: | ||
| /** checks for a header item, regardless of the case of the characters. */ | ||
| bool has(const QByteArray& key) const { | ||
| return contains(key.toLower()); | ||
| } | ||
|
|
||
| /** checks if a header has the specified value ignoring the case of the characters. */ | ||
| bool keyHasValue(const QByteArray& key, const QByteArray& value) const { | ||
| if ( !contains(key) ) | ||
| return false; | ||
|
|
||
| const QByteArray& v = QHash<QByteArray, QByteArray>::value(key); | ||
| return qstrnicmp(value.constData(), v.constData(), v.size()) == 0; | ||
| } | ||
|
|
||
| template<class Func> | ||
| void forEach(Func&& f) const { | ||
| for_each(constBegin(), constEnd(), f); | ||
| } | ||
| }; | ||
|
|
||
|
|
||
| /** Request method enumeration. | ||
| * @note Taken from http_parser.h */ | ||
| enum THttpMethod { | ||
| EHTTP_DELETE = 0, ///< DELETE | ||
| EHTTP_GET = 1, ///< GET | ||
| EHTTP_HEAD = 2, ///< HEAD | ||
| EHTTP_POST = 3, ///< POST | ||
| EHTTP_PUT = 4, ///< PUT | ||
| /* pathological */ | ||
| EHTTP_CONNECT = 5, ///< CONNECT | ||
| EHTTP_OPTIONS = 6, ///< OPTIONS | ||
| EHTTP_TRACE = 7, ///< TRACE | ||
| /* webdav */ | ||
| EHTTP_COPY = 8, ///< COPY | ||
| EHTTP_LOCK = 9, ///< LOCK | ||
| EHTTP_MKCOL = 10, ///< MKCOL | ||
| EHTTP_MOVE = 11, ///< MOVE | ||
| EHTTP_PROPFIND = 12, ///< PROPFIND | ||
| EHTTP_PROPPATCH = 13, ///< PROPPATCH | ||
| EHTTP_SEARCH = 14, ///< SEARCH | ||
| EHTTP_UNLOCK = 15, ///< UNLOCK | ||
| EHTTP_BIND = 16, ///< BIND | ||
| EHTTP_REBIND = 17, ///< REBIND | ||
| EHTTP_UNBIND = 18, ///< UNBIND | ||
| EHTTP_ACL = 19, ///< ACL | ||
| /* subversion */ | ||
| EHTTP_REPORT = 20, ///< REPORT | ||
| EHTTP_MKACTIVITY = 21, ///< MKACTIVITY | ||
| EHTTP_CHECKOUT = 22, ///< CHECKOUT | ||
| EHTTP_MERGE = 23, ///< MERGE | ||
| /* upnp */ | ||
| EHTTP_MSEARCH = 24, ///< M-SEARCH | ||
| EHTTP_NOTIFY = 25, ///< NOTIFY | ||
| EHTTP_SUBSCRIBE = 26, ///< SUBSCRIBE | ||
| EHTTP_UNSUBSCRIBE = 27, ///< UNSUBSCRIBE | ||
| /* RFC-5789 */ | ||
| EHTTP_PATCH = 28, ///< PATCH | ||
| EHTTP_PURGE = 29, ///< PURGE | ||
| /* CalDAV */ | ||
| EHTTP_MKCALENDAR = 30, ///< MKCALENDAR | ||
| /* RFC-2068, section 19.6.1.2 */ | ||
| EHTTP_LINK = 31, ///< LINK | ||
| EHTTP_UNLINK = 32, ///< UNLINK | ||
| }; | ||
|
|
||
| /** HTTP status codes. */ | ||
| enum TStatusCode { | ||
| ESTATUS_CONTINUE = 100, | ||
| ESTATUS_SWITCH_PROTOCOLS = 101, | ||
| ESTATUS_OK = 200, | ||
| ESTATUS_CREATED = 201, | ||
| ESTATUS_ACCEPTED = 202, | ||
| ESTATUS_NON_AUTHORITATIVE_INFORMATION = 203, | ||
| ESTATUS_NO_CONTENT = 204, | ||
| ESTATUS_RESET_CONTENT = 205, | ||
| ESTATUS_PARTIAL_CONTENT = 206, | ||
| ESTATUS_MULTI_STATUS = 207, | ||
| ESTATUS_MULTIPLE_CHOICES = 300, | ||
| ESTATUS_MOVED_PERMANENTLY = 301, | ||
| ESTATUS_FOUND = 302, | ||
| ESTATUS_SEE_OTHER = 303, | ||
| ESTATUS_NOT_MODIFIED = 304, | ||
| ESTATUS_USE_PROXY = 305, | ||
| ESTATUS_TEMPORARY_REDIRECT = 307, | ||
| ESTATUS_BAD_REQUEST = 400, | ||
| ESTATUS_UNAUTHORIZED = 401, | ||
| ESTATUS_PAYMENT_REQUIRED = 402, | ||
| ESTATUS_FORBIDDEN = 403, | ||
| ESTATUS_NOT_FOUND = 404, | ||
| ESTATUS_METHOD_NOT_ALLOWED = 405, | ||
| ESTATUS_NOT_ACCEPTABLE = 406, | ||
| ESTATUS_PROXY_AUTHENTICATION_REQUIRED = 407, | ||
| ESTATUS_REQUEST_TIMEOUT = 408, | ||
| ESTATUS_CONFLICT = 409, | ||
| ESTATUS_GONE = 410, | ||
| ESTATUS_LENGTH_REQUIRED = 411, | ||
| ESTATUS_PRECONDITION_FAILED = 412, | ||
| ESTATUS_REQUEST_ENTITY_TOO_LARGE = 413, | ||
| ESTATUS_REQUEST_URI_TOO_LONG = 414, | ||
| ESTATUS_REQUEST_UNSUPPORTED_MEDIA_TYPE = 415, | ||
| ESTATUS_REQUESTED_RANGE_NOT_SATISFIABLE = 416, | ||
| ESTATUS_EXPECTATION_FAILED = 417, | ||
| ESTATUS_INTERNAL_SERVER_ERROR = 500, | ||
| ESTATUS_NOT_IMPLEMENTED = 501, | ||
| ESTATUS_BAD_GATEWAY = 502, | ||
| ESTATUS_SERVICE_UNAVAILABLE = 503, | ||
| ESTATUS_GATEWAY_TIMEOUT = 504, | ||
| ESTATUS_HTTP_VERSION_NOT_SUPPORTED = 505 | ||
| }; | ||
|
|
||
| /** The backend of QHttp library. */ | ||
| enum TBackend { | ||
| ETcpSocket = 0, ///< client / server work on top of TCP/IP stack. (default) | ||
| ESslSocket = 1, ///< client / server work on SSL/TLS tcp stack. (not implemented yet) | ||
| ELocalSocket = 2 ///< client / server work on local socket (unix socket). | ||
| }; | ||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
| namespace server { | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
| class QHttpServer; | ||
| class QHttpConnection; | ||
| class QHttpRequest; | ||
| class QHttpResponse; | ||
|
|
||
| // Privte classes | ||
| class QHttpServerPrivate; | ||
| class QHttpConnectionPrivate; | ||
| class QHttpRequestPrivate; | ||
| class QHttpResponsePrivate; | ||
|
|
||
| using TServerHandler = std::function<void (QHttpRequest*, QHttpResponse*)>; | ||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
| } // namespace server | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
| namespace client { | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
| class QHttpClient; | ||
| class QHttpRequest; | ||
| class QHttpResponse; | ||
|
|
||
| // Private classes | ||
| class QHttpClientPrivate; | ||
| class QHttpRequestPrivate; | ||
| class QHttpResponsePrivate; | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
| } // namespace client | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
| #ifdef Q_OS_WIN | ||
| # if defined(QHTTP_EXPORT) | ||
| # define QHTTP_API __declspec(dllexport) | ||
| # else | ||
| # define QHTTP_API __declspec(dllimport) | ||
| # endif | ||
| #else | ||
| # define QHTTP_API | ||
| #endif | ||
|
|
||
|
|
||
| #if QHTTP_MEMORY_LOG > 0 | ||
| # define QHTTP_LINE_LOG fprintf(stderr, "%s(): obj = %p @ %s[%d]\n",\ | ||
| __FUNCTION__, this, __FILE__, __LINE__); | ||
| #else | ||
| # define QHTTP_LINE_LOG | ||
| #endif | ||
|
|
||
| #if QHTTP_MEMORY_LOG > 1 | ||
| # define QHTTP_LINE_DEEPLOG QHTTP_LINE_LOG | ||
| #else | ||
| # define QHTTP_LINE_DEEPLOG | ||
| #endif | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
| } // namespace qhttp | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
| #endif // define QHTTPFWD_HPP |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,118 @@ | ||
| #include "private/qhttpserver_private.hpp" | ||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
| namespace qhttp { | ||
| namespace server { | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
|
|
||
| QHttpServer::QHttpServer(QObject *parent) | ||
| : QObject(parent), d_ptr(new QHttpServerPrivate) { | ||
| } | ||
|
|
||
| QHttpServer::QHttpServer(QHttpServerPrivate &dd, QObject *parent) | ||
| : QObject(parent), d_ptr(&dd) { | ||
| } | ||
|
|
||
| QHttpServer::~QHttpServer() { | ||
| stopListening(); | ||
| } | ||
|
|
||
| bool | ||
| QHttpServer::listen(const QString &socketOrPort, const TServerHandler &handler) { | ||
| Q_D(QHttpServer); | ||
|
|
||
| bool isNumber = false; | ||
| quint16 tcpPort = socketOrPort.toUShort(&isNumber); | ||
| if ( isNumber && tcpPort > 0 ) | ||
| return listen(QHostAddress::Any, tcpPort, handler); | ||
|
|
||
| d->initialize(ELocalSocket, this); | ||
| d->ihandler = handler; | ||
| return d->ilocalServer->listen(socketOrPort); | ||
| } | ||
|
|
||
| bool | ||
| QHttpServer::listen(const QHostAddress& address, quint16 port, const qhttp::server::TServerHandler& handler) { | ||
| Q_D(QHttpServer); | ||
|
|
||
| d->initialize(ETcpSocket, this); | ||
| d->ihandler = handler; | ||
| return d->itcpServer->listen(address, port); | ||
| } | ||
|
|
||
| bool | ||
| QHttpServer::isListening() const { | ||
| const Q_D(QHttpServer); | ||
|
|
||
| if ( d->ibackend == ETcpSocket && d->itcpServer ) | ||
| return d->itcpServer->isListening(); | ||
|
|
||
| else if ( d->ibackend == ELocalSocket && d->ilocalServer ) | ||
| return d->ilocalServer->isListening(); | ||
|
|
||
| return false; | ||
| } | ||
|
|
||
| void | ||
| QHttpServer::stopListening() { | ||
| Q_D(QHttpServer); | ||
|
|
||
| if ( d->itcpServer ) | ||
| d->itcpServer->close(); | ||
|
|
||
| if ( d->ilocalServer ) { | ||
| d->ilocalServer->close(); | ||
| QLocalServer::removeServer( d->ilocalServer->fullServerName() ); | ||
| } | ||
| } | ||
|
|
||
| quint32 | ||
| QHttpServer::timeOut() const { | ||
| return d_func()->itimeOut; | ||
| } | ||
|
|
||
| void | ||
| QHttpServer::setTimeOut(quint32 newValue) { | ||
| d_func()->itimeOut = newValue; | ||
| } | ||
|
|
||
| TBackend | ||
| QHttpServer::backendType() const { | ||
| return d_func()->ibackend; | ||
| } | ||
|
|
||
| QTcpServer* | ||
| QHttpServer::tcpServer() const { | ||
| return d_func()->itcpServer.data(); | ||
| } | ||
|
|
||
| QLocalServer* | ||
| QHttpServer::localServer() const { | ||
| return d_func()->ilocalServer.data(); | ||
| } | ||
|
|
||
| void | ||
| QHttpServer::incomingConnection(qintptr handle) { | ||
| QHttpConnection* conn = new QHttpConnection(this); | ||
| conn->setSocketDescriptor(handle, backendType()); | ||
| conn->setTimeOut(d_func()->itimeOut); | ||
|
|
||
| emit newConnection(conn); | ||
|
|
||
| Q_D(QHttpServer); | ||
| if ( d->ihandler ) | ||
| QObject::connect(conn, &QHttpConnection::newRequest, d->ihandler); | ||
| else | ||
| incomingConnection(conn); | ||
| } | ||
|
|
||
| void | ||
| QHttpServer::incomingConnection(QHttpConnection *connection) { | ||
| QObject::connect(connection, &QHttpConnection::newRequest, | ||
| this, &QHttpServer::newRequest); | ||
| } | ||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
| } // namespace server | ||
| } // namespace qhttp | ||
| /////////////////////////////////////////////////////////////////////////////// |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,131 @@ | ||
| /** HTTP server class. | ||
| * https://github.com/azadkuh/qhttp | ||
| * | ||
| * @author amir zamani | ||
| * @version 2.0.0 | ||
| * @date 2014-07-11 | ||
| */ | ||
|
|
||
| #ifndef QHTTPSERVER_HPP | ||
| #define QHTTPSERVER_HPP | ||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
| #include "qhttpfwd.hpp" | ||
|
|
||
| #include <QObject> | ||
| #include <QHostAddress> | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
| namespace qhttp { | ||
| namespace server { | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
|
|
||
| /** The QHttpServer class is a fast, async (non-blocking) HTTP server. */ | ||
| class QHTTP_API QHttpServer : public QObject | ||
| { | ||
| Q_OBJECT | ||
|
|
||
| Q_PROPERTY(quint32 timeOut READ timeOut WRITE setTimeOut) | ||
|
|
||
| public: | ||
| /** construct a new HTTP Server. */ | ||
| explicit QHttpServer(QObject *parent = nullptr); | ||
|
|
||
| virtual ~QHttpServer(); | ||
|
|
||
| /** starts a TCP or Local (unix domain socket) server. | ||
| * if you provide a server handler, the newRequest() signal won't be emitted. | ||
| * | ||
| * @param socketOrPort could be a tcp port number as "8080" or a unix socket name as | ||
| * "/tmp/sample.socket" or "sample.socket". | ||
| * @param handler optional server handler (a lambda, std::function, ...) | ||
| * @return false if listening fails. | ||
| */ | ||
| bool listen(const QString& socketOrPort, | ||
| const TServerHandler& handler = nullptr); | ||
|
|
||
| /** starts a TCP server on specified address and port. | ||
| * if you provide a server handler, the newRequest() signal won't be emitted. | ||
| * | ||
| * @param address listening address as QHostAddress::Any. | ||
| * @param port listening port. | ||
| * @param handler optional server handler (a lambda, std::function, ...) | ||
| * @return false if listening fails. | ||
| */ | ||
| bool listen(const QHostAddress& address, quint16 port, | ||
| const TServerHandler& handler = nullptr); | ||
|
|
||
| /** @overload listen() */ | ||
| bool listen(quint16 port) { | ||
| return listen(QHostAddress::Any, port); | ||
| } | ||
|
|
||
| /** returns true if server successfully listens. @sa listen() */ | ||
| bool isListening() const; | ||
|
|
||
| /** closes the server and stops from listening. */ | ||
| void stopListening(); | ||
|
|
||
| /** returns timeout value [mSec] for open connections (sockets). | ||
| * @sa setTimeOut(). */ | ||
| quint32 timeOut()const; | ||
|
|
||
| /** set time-out for new open connections in miliseconds [mSec]. | ||
| * new incoming connections will be forcefully closed after this time out. | ||
| * a zero (0) value disables timer for new connections. */ | ||
| void setTimeOut(quint32); | ||
|
|
||
| /** returns the QHttpServer's backend type. */ | ||
| TBackend backendType() const; | ||
|
|
||
| signals: | ||
| /** emitted when a client makes a new request to the server if you do not override | ||
| * incomingConnection(QHttpConnection *connection); | ||
| * @sa incommingConnection(). */ | ||
| void newRequest(QHttpRequest *request, QHttpResponse *response); | ||
|
|
||
| /** emitted when a new connection comes to the server if you do not override | ||
| * incomingConnection(QHttpConnection *connection); | ||
| * @sa incomingConnection(); */ | ||
| void newConnection(QHttpConnection* connection); | ||
|
|
||
| protected: | ||
| /** returns the tcp server instance if the backend() == ETcpSocket. */ | ||
| QTcpServer* tcpServer() const; | ||
|
|
||
| /** returns the local server instance if the backend() == ELocalSocket. */ | ||
| QLocalServer* localServer() const; | ||
|
|
||
|
|
||
| /** is called when server accepts a new connection. | ||
| * you can override this function for using a thread-pool or ... some other reasons. | ||
| * | ||
| * the default implementation just connects QHttpConnection::newRequest signal. | ||
| * @note if you override this method, the signal won't be emitted by QHttpServer. | ||
| * (perhaps, you do not need it anymore). | ||
| * | ||
| * @param connection New incoming connection. */ | ||
| virtual void incomingConnection(QHttpConnection* connection); | ||
|
|
||
| /** overrides QTcpServer::incomingConnection() to make a new QHttpConnection. | ||
| * override this function if you like to create your derived QHttpConnection instances. | ||
| * | ||
| * @note if you override this method, incomingConnection(QHttpConnection*) or | ||
| * newRequest(QHttpRequest *, QHttpResponse *) signal won't be called. | ||
| * | ||
| * @see example/benchmark/server.cpp to see how to override. | ||
| */ | ||
| virtual void incomingConnection(qintptr handle); | ||
|
|
||
| private: | ||
| explicit QHttpServer(QHttpServerPrivate&, QObject *parent); | ||
|
|
||
| Q_DECLARE_PRIVATE(QHttpServer) | ||
| Q_DISABLE_COPY(QHttpServer) | ||
| QScopedPointer<QHttpServerPrivate> d_ptr; | ||
| }; | ||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
| } // namespace server | ||
| } // namespace qhttp | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
| #endif // define QHTTPSERVER_HPP |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,217 @@ | ||
| #include "private/qhttpserverconnection_private.hpp" | ||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
| namespace qhttp { | ||
| namespace server { | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
| QHttpConnection::QHttpConnection(QObject *parent) | ||
| : QObject(parent), d_ptr(new QHttpConnectionPrivate(this)) { | ||
| QHTTP_LINE_LOG | ||
| } | ||
|
|
||
| QHttpConnection::QHttpConnection(QHttpConnectionPrivate& dd, QObject* parent) | ||
| : QObject(parent), d_ptr(&dd) { | ||
| QHTTP_LINE_LOG | ||
| } | ||
|
|
||
| void | ||
| QHttpConnection::setSocketDescriptor(qintptr sokDescriptor, TBackend backendType) { | ||
| d_ptr->createSocket(sokDescriptor, backendType); | ||
| } | ||
|
|
||
| QHttpConnection::~QHttpConnection() { | ||
| QHTTP_LINE_LOG | ||
| } | ||
|
|
||
| void | ||
| QHttpConnection::setTimeOut(quint32 miliSeconds) { | ||
| if ( miliSeconds != 0 ) { | ||
| d_func()->itimeOut = miliSeconds; | ||
| d_func()->itimer.start(miliSeconds, Qt::CoarseTimer, this); | ||
| } | ||
| } | ||
|
|
||
| void | ||
| QHttpConnection::killConnection() { | ||
| d_func()->isocket.close(); | ||
| } | ||
|
|
||
| TBackend | ||
| QHttpConnection::backendType() const { | ||
| return d_func()->isocket.ibackendType; | ||
| } | ||
|
|
||
| QTcpSocket* | ||
| QHttpConnection::tcpSocket() const { | ||
| return d_func()->isocket.itcpSocket; | ||
| } | ||
|
|
||
| QLocalSocket* | ||
| QHttpConnection::localSocket() const { | ||
| return d_func()->isocket.ilocalSocket; | ||
| } | ||
|
|
||
| void | ||
| QHttpConnection::onHandler(const TServerHandler &handler) { | ||
| d_func()->ihandler = handler; | ||
| } | ||
|
|
||
| void | ||
| QHttpConnection::timerEvent(QTimerEvent *) { | ||
| killConnection(); | ||
| } | ||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
|
|
||
| // if user closes the connection, ends the response or by any other reason the | ||
| // socket disconnects, then the irequest and iresponse instances may have | ||
| // been deleted. In these situations reading more http body or emitting end() | ||
| // for incoming request are not possible: | ||
| // if ( ilastRequest == nullptr ) | ||
| // return 0; | ||
|
|
||
|
|
||
| int | ||
| QHttpConnectionPrivate::messageBegin(http_parser*) { | ||
| itempUrl.clear(); | ||
| itempUrl.reserve(128); | ||
|
|
||
| if ( ilastRequest ) | ||
| ilastRequest->deleteLater(); | ||
|
|
||
| ilastRequest = new QHttpRequest(q_func()); | ||
| return 0; | ||
| } | ||
|
|
||
| int | ||
| QHttpConnectionPrivate::url(http_parser*, const char* at, size_t length) { | ||
| Q_ASSERT(ilastRequest); | ||
|
|
||
| itempUrl.append(at, length); | ||
| return 0; | ||
| } | ||
|
|
||
| int | ||
| QHttpConnectionPrivate::headerField(http_parser*, const char* at, size_t length) { | ||
| if ( ilastRequest == nullptr ) | ||
| return 0; | ||
|
|
||
| // insert the header we parsed previously | ||
| // into the header map | ||
| if ( !itempHeaderField.isEmpty() && !itempHeaderValue.isEmpty() ) { | ||
| // header names are always lower-cased | ||
| ilastRequest->d_func()->iheaders.insert( | ||
| itempHeaderField.toLower(), | ||
| itempHeaderValue.toLower() | ||
| ); | ||
| // clear header value. this sets up a nice | ||
| // feedback loop where the next time | ||
| // HeaderValue is called, it can simply append | ||
| itempHeaderField.clear(); | ||
| itempHeaderValue.clear(); | ||
| } | ||
|
|
||
| itempHeaderField.append(at, length); | ||
| return 0; | ||
| } | ||
|
|
||
| int | ||
| QHttpConnectionPrivate::headerValue(http_parser*, const char* at, size_t length) { | ||
| if ( ilastRequest == nullptr ) | ||
| return 0; | ||
|
|
||
| itempHeaderValue.append(at, length); | ||
| return 0; | ||
| } | ||
|
|
||
| int | ||
| QHttpConnectionPrivate::headersComplete(http_parser* parser) { | ||
| if ( ilastRequest == nullptr ) | ||
| return 0; | ||
|
|
||
| ilastRequest->d_func()->iurl = QUrl(itempUrl); | ||
|
|
||
| // set method | ||
| ilastRequest->d_func()->imethod = | ||
| static_cast<THttpMethod>(parser->method); | ||
|
|
||
| // set version | ||
| ilastRequest->d_func()->iversion = QString("%1.%2") | ||
| .arg(parser->http_major) | ||
| .arg(parser->http_minor); | ||
|
|
||
| // Insert last remaining header | ||
| ilastRequest->d_func()->iheaders.insert( | ||
| itempHeaderField.toLower(), | ||
| itempHeaderValue.toLower() | ||
| ); | ||
|
|
||
| // set client information | ||
| if ( isocket.ibackendType == ETcpSocket ) { | ||
| ilastRequest->d_func()->iremoteAddress = isocket.itcpSocket->peerAddress().toString(); | ||
| ilastRequest->d_func()->iremotePort = isocket.itcpSocket->peerPort(); | ||
|
|
||
| } else if ( isocket.ibackendType == ELocalSocket ) { | ||
| ilastRequest->d_func()->iremoteAddress = isocket.ilocalSocket->fullServerName(); | ||
| ilastRequest->d_func()->iremotePort = 0; // not used in local sockets | ||
| } | ||
|
|
||
| if ( ilastResponse ) | ||
| ilastResponse->deleteLater(); | ||
| ilastResponse = new QHttpResponse(q_func()); | ||
|
|
||
| if ( parser->http_major < 1 || parser->http_minor < 1 ) | ||
| ilastResponse->d_func()->ikeepAlive = false; | ||
|
|
||
| // close the connection if response was the last packet | ||
| QObject::connect(ilastResponse, &QHttpResponse::done, [this](bool wasTheLastPacket){ | ||
| ikeepAlive = !wasTheLastPacket; | ||
| if ( wasTheLastPacket ) { | ||
| isocket.flush(); | ||
| isocket.close(); | ||
| } | ||
| }); | ||
|
|
||
| // we are good to go! | ||
| if ( ihandler ) | ||
| ihandler(ilastRequest, ilastResponse); | ||
| else | ||
| emit q_ptr->newRequest(ilastRequest, ilastResponse); | ||
|
|
||
| return 0; | ||
| } | ||
|
|
||
| int | ||
| QHttpConnectionPrivate::body(http_parser*, const char* at, size_t length) { | ||
| if ( ilastRequest == nullptr ) | ||
| return 0; | ||
|
|
||
| ilastRequest->d_func()->ireadState = QHttpRequestPrivate::EPartial; | ||
|
|
||
| if ( ilastRequest->d_func()->icollectRequired ) { | ||
| if ( !ilastRequest->d_func()->append(at, length) ) { | ||
| // forcefully dispatch the ilastRequest | ||
| finalizeConnection(); | ||
| } | ||
|
|
||
| return 0; | ||
| } | ||
|
|
||
| emit ilastRequest->data(QByteArray(at, length)); | ||
| return 0; | ||
| } | ||
|
|
||
| int | ||
| QHttpConnectionPrivate::messageComplete(http_parser*) { | ||
| if ( ilastRequest == nullptr ) | ||
| return 0; | ||
|
|
||
| // request is done | ||
| finalizeConnection(); | ||
| return 0; | ||
| } | ||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
| } // namespace server | ||
| } // namespace qhttp | ||
| /////////////////////////////////////////////////////////////////////////////// |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| /** HTTP connection class. | ||
| * https://github.com/azadkuh/qhttp | ||
| * | ||
| * @author amir zamani | ||
| * @version 2.0.0 | ||
| * @date 2014-07-11 | ||
| */ | ||
|
|
||
| #ifndef QHTTPSERVER_CONNECTION_HPP | ||
| #define QHTTPSERVER_CONNECTION_HPP | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
| #include "qhttpfwd.hpp" | ||
|
|
||
| #include <QObject> | ||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
| namespace qhttp { | ||
| namespace server { | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
| /** a HTTP connection in the server. | ||
| * this class controls the HTTP connetion and handles life cycle and the memory management | ||
| * of QHttpRequest and QHttpResponse instances autoamtically. | ||
| */ | ||
| class QHTTP_API QHttpConnection : public QObject | ||
| { | ||
| Q_OBJECT | ||
|
|
||
| public: | ||
| virtual ~QHttpConnection(); | ||
|
|
||
| /** set an optional timer event to close the connection. */ | ||
| void setTimeOut(quint32 miliSeconds); | ||
|
|
||
| /** forcefully kills (closes) a connection. */ | ||
| void killConnection(); | ||
|
|
||
| /** optionally set a handler for connection class. | ||
| * @note if you set this handler, the newRequest() signal won't be emitted. | ||
| */ | ||
| void onHandler(const TServerHandler& handler); | ||
|
|
||
| /** returns the backend type of the connection. */ | ||
| TBackend backendType() const; | ||
|
|
||
| /** returns connected socket if the backend() == ETcpSocket. */ | ||
| QTcpSocket* tcpSocket() const; | ||
|
|
||
| /** returns connected socket if the backend() == ELocalSocket. */ | ||
| QLocalSocket* localSocket() const; | ||
|
|
||
| /** creates a new QHttpConnection based on arguments. */ | ||
| static | ||
| QHttpConnection* create(qintptr sokDescriptor, TBackend backendType, QObject* parent) { | ||
| QHttpConnection* conn = new QHttpConnection(parent); | ||
| conn->setSocketDescriptor(sokDescriptor, backendType); | ||
| return conn; | ||
| } | ||
|
|
||
| signals: | ||
| /** emitted when a pair of HTTP request and response are ready to interact. | ||
| * @param req incoming request by the client. | ||
| * @param res outgoing response to the client. | ||
| */ | ||
| void newRequest(QHttpRequest* req, QHttpResponse* res); | ||
|
|
||
| /** emitted when the tcp/local socket, disconnects. */ | ||
| void disconnected(); | ||
|
|
||
| protected: | ||
| explicit QHttpConnection(QObject *parent); | ||
| explicit QHttpConnection(QHttpConnectionPrivate&, QObject *); | ||
|
|
||
| void setSocketDescriptor(qintptr sokDescriptor, TBackend backendType); | ||
| void timerEvent(QTimerEvent*) override; | ||
|
|
||
| Q_DISABLE_COPY(QHttpConnection) | ||
| Q_DECLARE_PRIVATE(QHttpConnection) | ||
| QScopedPointer<QHttpConnectionPrivate> d_ptr; | ||
|
|
||
| friend class QHttpServer; | ||
| }; | ||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
| } // namespace server | ||
| } // namespace qhttp | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
| #endif // #define QHTTPSERVER_CONNECTION_HPP |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| #include "private/qhttpserverrequest_private.hpp" | ||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
| namespace qhttp { | ||
| namespace server { | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
| QHttpRequest::QHttpRequest(QHttpConnection *conn) | ||
| : QHttpAbstractInput(conn), d_ptr(new QHttpRequestPrivate(conn, this)) { | ||
| d_ptr->initialize(); | ||
| QHTTP_LINE_LOG | ||
| } | ||
|
|
||
| QHttpRequest::QHttpRequest(QHttpRequestPrivate &dd, QHttpConnection *conn) | ||
| : QHttpAbstractInput(conn), d_ptr(&dd) { | ||
| d_ptr->initialize(); | ||
| QHTTP_LINE_LOG | ||
| } | ||
|
|
||
| QHttpRequest::~QHttpRequest() { | ||
| QHTTP_LINE_LOG | ||
| } | ||
|
|
||
| THttpMethod | ||
| QHttpRequest::method() const { | ||
| return d_func()->imethod; | ||
| } | ||
|
|
||
| const QString | ||
| QHttpRequest::methodString() const { | ||
| return http_method_str(static_cast<http_method>(d_func()->imethod)); | ||
| } | ||
|
|
||
| const QUrl& | ||
| QHttpRequest::url() const { | ||
| return d_func()->iurl; | ||
| } | ||
|
|
||
| const QString& | ||
| QHttpRequest::httpVersion() const { | ||
| return d_func()->iversion; | ||
| } | ||
|
|
||
| const THeaderHash& | ||
| QHttpRequest::headers() const { | ||
| return d_func()->iheaders; | ||
| } | ||
|
|
||
| const QString& | ||
| QHttpRequest::remoteAddress() const { | ||
| return d_func()->iremoteAddress; | ||
| } | ||
|
|
||
| quint16 | ||
| QHttpRequest::remotePort() const { | ||
| return d_func()->iremotePort; | ||
| } | ||
|
|
||
| bool | ||
| QHttpRequest::isSuccessful() const { | ||
| return d_func()->isuccessful; | ||
| } | ||
|
|
||
| void | ||
| QHttpRequest::collectData(int atMost) { | ||
| d_func()->collectData(atMost); | ||
| } | ||
|
|
||
| const QByteArray& | ||
| QHttpRequest::collectedData() const { | ||
| return d_func()->icollectedData; | ||
| } | ||
|
|
||
| QHttpConnection* | ||
| QHttpRequest::connection() const { | ||
| return d_ptr->iconnection; | ||
| } | ||
|
|
||
| /////////////////////////////////////////////////////////////////////////////// | ||
| } // namespace server | ||
| } // namespace qhttp | ||
| /////////////////////////////////////////////////////////////////////////////// |