/** * * @file HttpClient.h * * @author An Tao * * Copyright 2018, An Tao. All rights reserved. * https://github.com/an-tao/drogon * Use of this source code is governed by the MIT license * that can be found in the License file. * * Drogon * */ #pragma once #include #include #include #include #include #include #include #include #include #include #include "drogon/HttpBinder.h" #ifdef __cpp_impl_coroutine #include #endif namespace drogon { class HttpClient; using HttpClientPtr = std::shared_ptr; #ifdef __cpp_impl_coroutine namespace internal { struct HttpRespAwaiter : public CallbackAwaiter { HttpRespAwaiter(HttpClient *client, HttpRequestPtr req, double timeout) : client_(client), req_(std::move(req)), timeout_(timeout) { } void await_suspend(std::coroutine_handle<> handle); private: HttpClient *client_; HttpRequestPtr req_; double timeout_; }; } // namespace internal #endif /// Asynchronous http client /** * HttpClient implementation object uses the HttpAppFramework's event loop by * default, so you should call app().run() to make the client work. * Each HttpClient object establishes a persistent connection with the server. * If the connection is broken, the client attempts to reconnect * when calling the sendRequest method. * * Using the static mathod newHttpClient(...) to get shared_ptr of the object * implementing the class, the shared_ptr is retained in the framework until all * response callbacks are invoked without fear of accidental deconstruction. * */ class DROGON_EXPORT HttpClient : public trantor::NonCopyable { public: /** * @brief Send a request asynchronously to the server * * @param req The request sent to the server. * @param callback The callback is called when the response is received from * the server. * @param timeout In seconds. If the response is not received within the * timeout, the callback is called with `ReqResult::Timeout` and an empty * response. The zero value by default disables the timeout. * * @note * The request object is altered(some headers are added to it) before it is * sent, so calling this method with a same request object in different * thread is dangerous. * Please be careful when using timeout on an non-idempotent request. */ virtual void sendRequest(const HttpRequestPtr &req, const HttpReqCallback &callback, double timeout = 0) = 0; /** * @brief Send a request asynchronously to the server * * @param req The request sent to the server. * @param callback The callback is called when the response is received from * the server. * @param timeout In seconds. If the response is not received within * the timeout, the callback is called with `ReqResult::Timeout` and an * empty response. The zero value by default disables the timeout. * * @note * The request object is altered(some headers are added to it) before it is * sent, so calling this method with a same request object in different * thread is dangerous. * Please be careful when using timeout on an non-idempotent request. */ virtual void sendRequest(const HttpRequestPtr &req, HttpReqCallback &&callback, double timeout = 0) = 0; /** * @brief Send a request synchronously to the server and return the * response. * * @param req * @param timeout In seconds. If the response is not received within the * timeout, the `ReqResult::Timeout` and an empty response is returned. The * zero value by default disables the timeout. * * @return std::pair * @note Never call this function in the event loop thread of the * client (partially in the callback function of the asynchronous * sendRequest method), otherwise the thread will be blocked forever. * Please be careful when using timeout on an non-idempotent request. */ std::pair sendRequest(const HttpRequestPtr &req, double timeout = 0) { assert(!getLoop()->isInLoopThread() && "Deadlock detected! Calling a sync API from the same loop as " "the HTTP client processes on will deadlock the event loop"); std::promise> prom; auto f = prom.get_future(); sendRequest( req, [&prom](ReqResult r, const HttpResponsePtr &resp) { prom.set_value({r, resp}); }, timeout); return f.get(); } #ifdef __cpp_impl_coroutine /** * @brief Send a request via coroutines to the server and return an * awaiter what could be `co_await`-ed to retrieve the response * (HttpResponsePtr) * * @param req * @param timeout In seconds. If the response is not received within the * timeout, A `std::runtime_error` with the message "Timeout" is thrown. * The zero value by default disables the timeout. * * @return internal::HttpRespAwaiter. Await on it to get the response */ internal::HttpRespAwaiter sendRequestCoro(HttpRequestPtr req, double timeout = 0) { return internal::HttpRespAwaiter(this, std::move(req), timeout); } #endif /// Set the pipelining depth, which is the number of requests that are not /// responding. /** * If this method is not called, the default depth value is 0 which means * the pipelining is disabled. For details about pipelining, see * rfc2616-8.1.2.2 */ virtual void setPipeliningDepth(size_t depth) = 0; /// Enable cookies for the client /** * @param flag if the parameter is true, all requests sent by the client * carry the cookies set by the server side. Cookies are disabled by * default. */ virtual void enableCookies(bool flag = true) = 0; /// Add a cookie to the client /** * @note * These methods are independent of the enableCookies() method. Whether the * enableCookies() is called with true or false, the cookies added by these * methods will be sent to the server. */ virtual void addCookie(const std::string &key, const std::string &value) = 0; /// Add a cookie to the client /** * @note * These methods are independent of the enableCookies() method. Whether the * enableCookies() is called with true or false, the cookies added by these * methods will be sent to the server. */ virtual void addCookie(const Cookie &cookie) = 0; /** * @brief Set the user_agent header, the default value is 'DrogonClient' if * this method is not used. * * @param userAgent The user_agent value, if it is empty, the user_agent * header is not sent to the server. */ virtual void setUserAgent(const std::string &userAgent) = 0; /** * @brief Creaet a new HTTP client which use ip and port to connect to * server * * @param ip The ip address of the HTTP server * @param port The port of the HTTP server * @param useSSL if the parameter is set to true, the client connects to the * server using HTTPS. * @param loop If the loop parameter is set to nullptr, the client uses the * HttpAppFramework's event loop, otherwise it runs in the loop identified * by the parameter. * @param useOldTLS If the parameter is set to true, the TLS1.0/1.1 are * eanbled for HTTPS. * @param validateCert If the parameter is set to true, the client validates * the server certificate when SSL handshaking. * @return HttpClientPtr The smart pointer to the new client object. * @note: The ip parameter support for both ipv4 and ipv6 address */ static HttpClientPtr newHttpClient(const std::string &ip, uint16_t port, bool useSSL = false, trantor::EventLoop *loop = nullptr, bool useOldTLS = false, bool validateCert = true); /// Get the event loop of the client; virtual trantor::EventLoop *getLoop() = 0; /// Get the number of bytes sent or received virtual size_t bytesSent() const = 0; virtual size_t bytesReceived() const = 0; virtual std::string host() const = 0; std::string getHost() const { return host(); } virtual uint16_t port() const = 0; uint16_t getPort() const { return port(); } virtual bool secure() const = 0; bool onDefaultPort() const { if (secure()) return port() == 443; return port() == 80; } /** * @brief Set the client certificate used by the HTTP connection * * @param cert Path to the certificate * @param key Path to the certificate's private key * @note this method has no effect if the HTTP client is communicating via * unencrypted HTTP */ virtual void setCertPath(const std::string &cert, const std::string &key) = 0; /** * @brief Supplies command style options for `SSL_CONF_cmd` * * @param sslConfCmds options for SSL_CONF_cmd * @note this method has no effect if the HTTP client is communicating via * unencrypted HTTP * @code * addSSLConfigs({{"-dhparam", "/path/to/dhparam"}, {"-strict", ""}}); * @endcode */ virtual void addSSLConfigs( const std::vector> &sslConfCmds) = 0; /// Create a Http client using the hostString to connect to server /** * * @param hostString this parameter must be prefixed by 'http://' or * 'https://'. * * Examples for hostString: * @code https://www.baidu.com http://www.baidu.com https://127.0.0.1:8080/ http://127.0.0.1 http://[::1]:8080/ //IPv6 address must be enclosed in [], rfc2732 @endcode * * @param loop If the loop parameter is set to nullptr, the client uses the * HttpAppFramework's event loop, otherwise it runs in the loop identified * by the parameter. * * @param useOldTLS If the parameter is set to true, the TLS1.0/1.1 are * enabled for HTTPS. * @note * * @param validateCert If the parameter is set to true, the client validates * the server certificate when SSL handshaking. * * @note Don't add path and parameters in hostString, the request path and * parameters should be set in HttpRequestPtr when calling the sendRequest() * method. * */ static HttpClientPtr newHttpClient(const std::string &hostString, trantor::EventLoop *loop = nullptr, bool useOldTLS = false, bool validateCert = true); virtual ~HttpClient() { } protected: HttpClient() = default; }; #ifdef __cpp_impl_coroutine inline void internal::HttpRespAwaiter::await_suspend( std::coroutine_handle<> handle) { assert(client_ != nullptr); assert(req_ != nullptr); client_->sendRequest( req_, [handle = std::move(handle), this](ReqResult result, const HttpResponsePtr &resp) { if (result == ReqResult::Ok) setValue(resp); else { std::stringstream ss; ss << result; setException( std::make_exception_ptr(std::runtime_error(ss.str()))); } handle.resume(); }, timeout_); } #endif } // namespace drogon