From 3e1886a84e132627f27e8f4dc0bf9e6a22e19fa5 Mon Sep 17 00:00:00 2001 From: antao Date: Thu, 9 May 2019 16:26:50 +0800 Subject: [PATCH] Add the forward() method to the framework --- examples/simple_example/ForwardCtrl.cc | 8 +++++ examples/simple_example/ForwardCtrl.h | 12 +++++++ lib/inc/drogon/HttpAppFramework.h | 19 ++++++++++++ lib/inc/drogon/HttpResponse.h | 6 +++- lib/src/HttpAppFrameworkImpl.cc | 43 ++++++++++++++++++++++++++ lib/src/HttpAppFrameworkImpl.h | 9 +++++- lib/src/HttpClientImpl.cc | 12 +++---- lib/src/HttpClientImpl.h | 1 + lib/src/HttpControllersRouter.cc | 13 +++++--- lib/src/HttpResponseImpl.cc | 2 +- lib/src/HttpResponseImpl.h | 22 +++++++++++++ 11 files changed, 131 insertions(+), 16 deletions(-) create mode 100644 examples/simple_example/ForwardCtrl.cc create mode 100644 examples/simple_example/ForwardCtrl.h diff --git a/examples/simple_example/ForwardCtrl.cc b/examples/simple_example/ForwardCtrl.cc new file mode 100644 index 00000000..16b29132 --- /dev/null +++ b/examples/simple_example/ForwardCtrl.cc @@ -0,0 +1,8 @@ +#include "ForwardCtrl.h" +void ForwardCtrl::asyncHandleHttpRequest(const HttpRequestPtr &req, std::function &&callback) +{ + req->setPath("/repos/an-tao/drogon/git/refs/heads/master"); + app().forward(req, [callback = std::move(callback)](const HttpResponsePtr &resp) { + callback(resp); + },"https://api.github.com"); +} \ No newline at end of file diff --git a/examples/simple_example/ForwardCtrl.h b/examples/simple_example/ForwardCtrl.h new file mode 100644 index 00000000..b741dc51 --- /dev/null +++ b/examples/simple_example/ForwardCtrl.h @@ -0,0 +1,12 @@ +#pragma once +#include +using namespace drogon; +class ForwardCtrl:public drogon::HttpSimpleController +{ +public: + virtual void asyncHandleHttpRequest(const HttpRequestPtr& req, std::function &&callback) override; + PATH_LIST_BEGIN + //list path definitions here; + PATH_ADD("/forward",Get); + PATH_LIST_END +}; diff --git a/lib/inc/drogon/HttpAppFramework.h b/lib/inc/drogon/HttpAppFramework.h index 4f8db756..54e267a7 100755 --- a/lib/inc/drogon/HttpAppFramework.h +++ b/lib/inc/drogon/HttpAppFramework.h @@ -379,6 +379,25 @@ public: DrClassMap::setSingleInstance(filterPtr); } + /// Forward the http request + /** + * The @param hostString is the address where the request is forwarded. The following strings are + * valid for the @param hostString: + * + * https://www.baidu.com + * http://www.baidu.com + * https://127.0.0.1:8080/ + * http://127.0.0.1 + * http://[::1]:8080/ + * + * NOTE: + * If the @param hostString is empty, the request is handled by the same application, so in this condition + * one should modify the path of the @param req before forwarding to avoid infinite loop processing. + * + * This method can be used to implement reverse proxy or redirection on the server side. + */ + virtual void forward(const HttpRequestPtr &req, std::function &&callback, const std::string &hostString = "") = 0; + ///Get information about the handlers registered to drogon /** * The first item of std::tuple in the return value represents the path pattern of the handler; diff --git a/lib/inc/drogon/HttpResponse.h b/lib/inc/drogon/HttpResponse.h index 6293aeab..f7f6358d 100755 --- a/lib/inc/drogon/HttpResponse.h +++ b/lib/inc/drogon/HttpResponse.h @@ -70,6 +70,10 @@ public: virtual const std::string &getHeader(const std::string &key, const std::string &defaultVal = std::string()) const = 0; virtual const std::string &getHeader(std::string &&key, const std::string &defaultVal = std::string()) const = 0; + /// Remove the header identified by the @param key. + virtual void removeHeader(const std::string &key) = 0; + virtual void removeHeader(std::string &&key) = 0; + /// Get all headers of the response virtual const std::unordered_map &headers() const = 0; const std::unordered_map &getHeaders() const { return headers(); } @@ -132,7 +136,7 @@ public: /// For more details, see the wiki pages, the "View" section. static HttpResponsePtr newHttpViewResponse(const std::string &viewName, const HttpViewData &data = HttpViewData()); /// Create a response that returns a 302 Found page, redirecting to another page located in the @param location. - static HttpResponsePtr newLocationResponse(const std::string &location); + static HttpResponsePtr newRedirectionResponse(const std::string &location); /// Create a response that returns a file to the client. /** * @param fullPath is the full path to the file. diff --git a/lib/src/HttpAppFrameworkImpl.cc b/lib/src/HttpAppFrameworkImpl.cc index 55fbec75..4124efbe 100755 --- a/lib/src/HttpAppFrameworkImpl.cc +++ b/lib/src/HttpAppFrameworkImpl.cc @@ -918,3 +918,46 @@ void HttpAppFrameworkImpl::createDbClient(const std::string &dbType, } } #endif + +void HttpAppFrameworkImpl::forward(const HttpRequestImplPtr &req, std::function &&callback, const std::string &hostString) +{ + if (hostString.empty()) + { + onAsyncRequest(req, std::move(callback)); + } + else + { + /// A tiny implementation of a reverse proxy. + static std::unordered_map clientsMap; + HttpClientImplPtr clientPtr; + static std::mutex mtx; + { + std::lock_guard lock(mtx); + auto iter = clientsMap.find(hostString); + if (iter != clientsMap.end()) + { + clientPtr = iter->second; + } + else + { + clientPtr = std::make_shared(trantor::EventLoop::getEventLoopOfCurrentThread() ? trantor::EventLoop::getEventLoopOfCurrentThread() : getLoop(), + hostString); + clientsMap[hostString] = clientPtr; + } + } + clientPtr->sendRequest(req, [callback = std::move(callback)](ReqResult result, const HttpResponsePtr &resp) { + if (result == ReqResult::Ok) + { + resp->removeHeader("server"); + resp->removeHeader("date"); + resp->removeHeader("content-length"); + resp->removeHeader("transfer-encoding"); + callback(resp); + } + else + { + callback(HttpResponse::newNotFoundResponse()); + } + }); + } +} diff --git a/lib/src/HttpAppFrameworkImpl.h b/lib/src/HttpAppFrameworkImpl.h index b5dae0bf..b406ba82 100644 --- a/lib/src/HttpAppFrameworkImpl.h +++ b/lib/src/HttpAppFrameworkImpl.h @@ -90,12 +90,19 @@ public: resp->setStatusCode(k404NotFound); _custom404 = resp; } - + const HttpResponsePtr &getCustom404Page() { return _custom404; } + virtual void forward(const HttpRequestPtr &req, std::function &&callback, const std::string &hostString = "") override + { + forward(std::dynamic_pointer_cast(req), std::move(callback), hostString); + } + + void forward(const HttpRequestImplPtr &req, std::function &&callback, const std::string &hostString); + virtual void registerBeginningAdvice(const std::function &advice) override { getLoop()->runInLoop(advice); diff --git a/lib/src/HttpClientImpl.cc b/lib/src/HttpClientImpl.cc index 820ae55e..d36ba2de 100644 --- a/lib/src/HttpClientImpl.cc +++ b/lib/src/HttpClientImpl.cc @@ -306,20 +306,16 @@ void HttpClientImpl::onRecvMessage(const trantor::TcpConnectionPtr &connPtr, tra { auto resp = responseParser->responseImpl(); responseParser->reset(); - assert(!_pipeliningCallbacks.empty()); - auto &type = resp->getHeaderBy("content-type"); - if (type.find("application/json") != std::string::npos) - { - resp->parseJson(); - } - if (resp->getHeaderBy("content-encoding") == "gzip") { resp->gunzip(); } - + if (type.find("application/json") != std::string::npos) + { + resp->parseJson(); + } auto cb = std::move(_pipeliningCallbacks.front()); _pipeliningCallbacks.pop(); cb(ReqResult::Ok, resp); diff --git a/lib/src/HttpClientImpl.h b/lib/src/HttpClientImpl.h index 05e6ac15..7fe05ce2 100644 --- a/lib/src/HttpClientImpl.h +++ b/lib/src/HttpClientImpl.h @@ -50,4 +50,5 @@ class HttpClientImpl : public HttpClient, public std::enable_shared_from_this HttpClientImplPtr; } // namespace drogon diff --git a/lib/src/HttpControllersRouter.cc b/lib/src/HttpControllersRouter.cc index e2e77ad9..18d9c386 100644 --- a/lib/src/HttpControllersRouter.cc +++ b/lib/src/HttpControllersRouter.cc @@ -24,12 +24,15 @@ namespace drogon { static void doWhenNoHandlerFound(const HttpRequestImplPtr &req, - const std::function &callback) + std::function &&callback) { if (req->path() == "/" && !HttpAppFrameworkImpl::instance().getHomePage().empty()) { - auto resp = drogon::HttpResponse::newLocationResponse("/" + HttpAppFrameworkImpl::instance().getHomePage()); - callback(resp); + // auto resp = drogon::HttpResponse::newRedirectionResponse("/" + HttpAppFrameworkImpl::instance().getHomePage()); + // callback(resp); + // Redirect on the server side + req->setPath("/" + HttpAppFrameworkImpl::instance().getHomePage()); + drogon::app().forward(req, std::move(callback)); return; } auto resp = drogon::HttpResponse::newNotFoundResponse(); @@ -282,13 +285,13 @@ void HttpControllersRouter::route(const HttpRequestImplPtr &req, else { //No handler found - doWhenNoHandlerFound(req, callback); + doWhenNoHandlerFound(req, std::move(callback)); } } else { //No handler found - doWhenNoHandlerFound(req, callback); + doWhenNoHandlerFound(req, std::move(callback)); } } diff --git a/lib/src/HttpResponseImpl.cc b/lib/src/HttpResponseImpl.cc index 6bd10a32..e9ed9552 100755 --- a/lib/src/HttpResponseImpl.cc +++ b/lib/src/HttpResponseImpl.cc @@ -66,7 +66,7 @@ HttpResponsePtr HttpResponse::newNotFoundResponse() return notFoundResp; } } -HttpResponsePtr HttpResponse::newLocationResponse(const std::string &location) +HttpResponsePtr HttpResponse::newRedirectionResponse(const std::string &location) { auto res = std::make_shared(); res->setStatusCode(k302Found); diff --git a/lib/src/HttpResponseImpl.h b/lib/src/HttpResponseImpl.h index 11cc4cc8..26b29084 100755 --- a/lib/src/HttpResponseImpl.h +++ b/lib/src/HttpResponseImpl.h @@ -122,6 +122,19 @@ public: return getHeaderBy(key, defaultVal); } + virtual void removeHeader(const std::string &key) override + { + auto field = key; + transform(field.begin(), field.end(), field.begin(), ::tolower); + removeHeaderBy(field); + } + + virtual void removeHeader(std::string &&key) override + { + transform(key.begin(), key.end(), key.begin(), ::tolower); + removeHeaderBy(key); + } + virtual const std::unordered_map &headers() const override { return _headers; @@ -137,6 +150,11 @@ public: return iter->second; } + void removeHeaderBy(const std::string &lowerKey) + { + _headers.erase(lowerKey); + } + virtual void addHeader(const std::string &key, const std::string &value) override { _fullHeaderString.reset(); @@ -346,6 +364,7 @@ public: if (!reader->parse(_bodyPtr->data(), _bodyPtr->data() + _bodyPtr->size(), _jsonPtr.get(), &errs)) { LOG_ERROR << errs; + LOG_DEBUG << "body: " << *_bodyPtr; _jsonPtr.reset(); } } @@ -375,7 +394,10 @@ public: { auto gunzipBody = utils::gzipDecompress(_bodyPtr); if (gunzipBody) + { + removeHeader("content-encoding"); _bodyPtr = gunzipBody; + } } protected: