diff --git a/include/QHttpEngine/QProxyHandler b/include/QHttpEngine/QProxyHandler new file mode 100644 index 0000000..e256793 --- /dev/null +++ b/include/QHttpEngine/QProxyHandler @@ -0,0 +1 @@ +#include "qproxyhandler.h" diff --git a/include/QHttpEngine/qproxyhandler.h b/include/QHttpEngine/qproxyhandler.h new file mode 100644 index 0000000..eb06626 --- /dev/null +++ b/include/QHttpEngine/qproxyhandler.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2016 Nathan Osman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef QHTTPENGINE_QPROXYHANDLER_H +#define QHTTPENGINE_QPROXYHANDLER_H + +#include + +#include + +#include "qhttpengine_global.h" + +class QHTTPENGINE_EXPORT QProxyHandlerPrivate; + +/** + * @brief Handler that routes HTTP requests to an upstream server + */ +class QHTTPENGINE_EXPORT QProxyHandler : public QHttpHandler +{ + Q_OBJECT + +public: + + /** + * @brief Create a new proxy handler + */ + QProxyHandler(const QHostAddress &address, quint16 port, QObject *parent = 0); + +protected: + + /** + * @brief Reimplementation of QHttpHandler::process() + */ + virtual void process(QHttpSocket *socket, const QString &path); + +private: + + QProxyHandlerPrivate *const d; +}; + +#endif // QHTTPENGINE_QPROXYHANDLER_H diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a204bce..255ee94 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -15,6 +15,7 @@ set(SRC qlocalauth.cpp qlocalfile.cpp qobjecthandler.cpp + qproxyhandler.cpp ) if(WIN32) diff --git a/src/qproxyhandler.cpp b/src/qproxyhandler.cpp new file mode 100644 index 0000000..453347e --- /dev/null +++ b/src/qproxyhandler.cpp @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2016 Nathan Osman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include +#include +#include + +#include + +#include "qproxyhandler_p.h" + +QProxyHandlerPrivate::QProxyHandlerPrivate(QObject *parent, const QHostAddress &address, quint16 port) + : QObject(parent), + address(address), + port(port) +{ +} + +QByteArray QProxyHandlerPrivate::methodToString(QHttpSocket::Method method) +{ + switch (method) { + case QHttpSocket::OPTIONS: return "OPTIONS"; + case QHttpSocket::GET: return "GET"; + case QHttpSocket::HEAD: return "HEAD"; + case QHttpSocket::POST: return "POST"; + case QHttpSocket::PUT: return "PUT"; + case QHttpSocket::DELETE: return "DELETE"; + case QHttpSocket::TRACE: return "TRACE"; + case QHttpSocket::CONNECT: return "CONNECT"; + } +} + +QProxyHandler::QProxyHandler(const QHostAddress &address, quint16 port, QObject *parent) + : QHttpHandler(parent), + d(new QProxyHandlerPrivate(this, address, port)) +{ +} + +void QProxyHandler::process(QHttpSocket *socket, const QString &path) +{ + // Parent the socket to the proxy + socket->setParent(this); + + // Build the request for the upstream server + QUrl url(QString("http://%s:%d/%s").arg(d->address.toString()).arg(d->port).arg(path)); + QNetworkRequest request(url); + + // Copy all of the request headers + for (auto i = socket->headers().constBegin(); i != socket->headers().constEnd(); ++i) { + request.setRawHeader(i.key(), i.value()); + } + + // Begin the request + QNetworkReply *reply = d->networkAccessManager.sendCustomRequest( + request, + d->methodToString(socket->method()) + ); + reply->setParent(this); + + // Copy all of the response headers when they arrive + connect(reply, &QNetworkReply::metaDataChanged, [socket, reply]() { + foreach (QByteArray headerName, reply->rawHeaderList()) { + socket->setHeader(headerName, reply->rawHeader(headerName)); + } + socket->writeHeaders(); + }); + + // When data arrives from upstream, send it downstream + connect(reply, &QNetworkReply::readyRead, [socket, reply]() { + socket->write(reply->readAll()); + }); + + // When data arrives from downstream, send it upstream + connect(socket, &QHttpSocket::readyRead, [socket, reply]() { + reply->write(socket->readAll()); + }); + + // When either end disconnects, sever everything and tear it down + auto teardown = [socket, reply]() { + socket->deleteLater(); + reply->deleteLater(); + }; + connect(reply, &QNetworkReply::finished, teardown); + connect(socket, &QHttpSocket::aboutToClose, teardown); +} diff --git a/src/qproxyhandler_p.h b/src/qproxyhandler_p.h new file mode 100644 index 0000000..1346003 --- /dev/null +++ b/src/qproxyhandler_p.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2016 Nathan Osman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef QHTTPENGINE_QPROXYHANDLERPRIVATE_H +#define QHTTPENGINE_QPROXYHANDLERPRIVATE_H + +#include +#include +#include + +#include + +class QProxyHandlerPrivate : public QObject +{ + Q_OBJECT + +public: + + explicit QProxyHandlerPrivate(QObject *parent, const QHostAddress &address, quint16 port); + + QByteArray methodToString(QHttpSocket::Method method); + + QNetworkAccessManager networkAccessManager; + + QHostAddress address; + quint16 port; +}; + +#endif // QHTTPENGINE_QPROXYHANDLERPRIVATE_H