109 changes: 109 additions & 0 deletions libs/qhttp/private/httpwriter.hxx
@@ -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__
52 changes: 52 additions & 0 deletions libs/qhttp/private/qhttpbase.hpp
@@ -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
196 changes: 196 additions & 0 deletions libs/qhttp/private/qhttpclient_private.hpp
@@ -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
57 changes: 57 additions & 0 deletions libs/qhttp/private/qhttpclientrequest_private.hpp
@@ -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
51 changes: 51 additions & 0 deletions libs/qhttp/private/qhttpclientresponse_private.hpp
@@ -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
90 changes: 90 additions & 0 deletions libs/qhttp/private/qhttpserver_private.hpp
@@ -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
175 changes: 175 additions & 0 deletions libs/qhttp/private/qhttpserverconnection_private.hpp
@@ -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
51 changes: 51 additions & 0 deletions libs/qhttp/private/qhttpserverrequest_private.hpp
@@ -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
62 changes: 62 additions & 0 deletions libs/qhttp/private/qhttpserverresponse_private.hpp
@@ -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
126 changes: 126 additions & 0 deletions libs/qhttp/private/qsocket.hpp
@@ -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__
114 changes: 114 additions & 0 deletions libs/qhttp/qhttpabstracts.cpp
@@ -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
///////////////////////////////////////////////////////////////////////////////
190 changes: 190 additions & 0 deletions libs/qhttp/qhttpabstracts.hpp
@@ -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
284 changes: 284 additions & 0 deletions libs/qhttp/qhttpclient.cpp
@@ -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
///////////////////////////////////////////////////////////////////////////////
182 changes: 182 additions & 0 deletions libs/qhttp/qhttpclient.hpp
@@ -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
98 changes: 98 additions & 0 deletions libs/qhttp/qhttpclientrequest.cpp
@@ -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
///////////////////////////////////////////////////////////////////////////////
67 changes: 67 additions & 0 deletions libs/qhttp/qhttpclientrequest.hpp
@@ -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
66 changes: 66 additions & 0 deletions libs/qhttp/qhttpclientresponse.cpp
@@ -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
///////////////////////////////////////////////////////////////////////////////
78 changes: 78 additions & 0 deletions libs/qhttp/qhttpclientresponse.hpp
@@ -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
221 changes: 221 additions & 0 deletions libs/qhttp/qhttpfwd.hpp
@@ -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
118 changes: 118 additions & 0 deletions libs/qhttp/qhttpserver.cpp
@@ -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
///////////////////////////////////////////////////////////////////////////////
131 changes: 131 additions & 0 deletions libs/qhttp/qhttpserver.hpp
@@ -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
217 changes: 217 additions & 0 deletions libs/qhttp/qhttpserverconnection.cpp
@@ -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
///////////////////////////////////////////////////////////////////////////////
87 changes: 87 additions & 0 deletions libs/qhttp/qhttpserverconnection.hpp
@@ -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
81 changes: 81 additions & 0 deletions libs/qhttp/qhttpserverrequest.cpp
@@ -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
///////////////////////////////////////////////////////////////////////////////