diff --git a/include/eepp/network/http.hpp b/include/eepp/network/http.hpp index 3bcf0d061..9bf2f74b8 100644 --- a/include/eepp/network/http.hpp +++ b/include/eepp/network/http.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -37,7 +38,8 @@ class EE_API Http : NonCopyable { Put, ///< The PUT method replaces all current representations of the target resource with the request payload. Delete, ///< The DELETE method deletes the specified resource. Options, ///< The OPTIONS method is used to describe the communication options for the target resource. - Patch ///< The PATCH method is used to apply partial modifications to a resource. + Patch, ///< The PATCH method is used to apply partial modifications to a resource. + Connect ///< The CONNECT method starts two-way communications with the requested resource. It can be used to open a tunnel. }; /** @return Method from a method name string. */ @@ -133,6 +135,15 @@ class EE_API Http : NonCopyable { /** Set the maximun number of redirects allowed if follow redirect is enabled. */ void setMaxRedirects( unsigned int maxRedirects ); + /** Sets the request proxy */ + void setProxy( const URI& uri ); + + /** @return The request proxy */ + const URI& getProxy() const; + + /** @return Is a proxy is need to be used */ + bool isProxied() const; + /** Definition of the current progress callback * @param http The http client * @param request The http request @@ -160,7 +171,7 @@ class EE_API Http : NonCopyable { ** This is used internally by Http before sending the ** request to the web server. ** @return String containing the request, ready to be sent */ - std::string prepare() const; + std::string prepare(const Http& http) const; // Types typedef std::map FieldTable; @@ -179,6 +190,7 @@ class EE_API Http : NonCopyable { ProgressCallback mProgressCallback; ///< Progress callback unsigned int mMaxRedirections; ///< Maximun number of redirections allowed mutable unsigned int mRedirectionCount; ///< Number of redirections followed by the request + URI mProxy; ///< Proxy information }; /** @brief Define a HTTP response */ @@ -224,6 +236,9 @@ class EE_API Http : NonCopyable { ConnectionFailed = 1001 ///< Connection with server failed }; + /** @return The status string */ + static const char * statusToString( const Status& status ); + /** @brief Default constructor ** Constructs an empty response. */ Response(); @@ -388,6 +403,12 @@ class EE_API Http : NonCopyable { /** @return The host port */ const unsigned short& getPort() const; + + /** @return If the HTTP client uses SSL/TLS */ + const bool& isSSL() const; + + /** @return The URI from the schema + hostname + port */ + URI getURI() const; private: class AsyncRequest : public Thread { public: @@ -411,6 +432,7 @@ class EE_API Http : NonCopyable { bool mStreamOwned; IOStream * mStream; }; + friend class AsyncRequest; ThreadLocalPtr mConnection; ///< Connection to the host IpAddress mHost; ///< Web host address @@ -419,6 +441,9 @@ class EE_API Http : NonCopyable { std::list mThreads; Mutex mThreadsMutex; bool mIsSSL; + URI mProxy; + + Http(const std::string& host, unsigned short port, bool useSSL, URI proxy); void removeOldThreads(); diff --git a/src/eepp/network/http.cpp b/src/eepp/network/http.cpp index 7de52558b..40d5277fd 100644 --- a/src/eepp/network/http.cpp +++ b/src/eepp/network/http.cpp @@ -64,6 +64,8 @@ Http::Request::Method Http::Request::methodFromString( std::string methodString return Method::Options; } else if ( "patch" == methodString ) { return Method::Patch; + } else if ( "connect" == methodString ) { + return Method::Connect; } else { return Method::Get; } @@ -144,6 +146,18 @@ void Http::Request::setMaxRedirects(unsigned int maxRedirects) { mMaxRedirections = maxRedirects; } +void Http::Request::setProxy(const URI& uri) { + mProxy = uri; +} + +const URI& Http::Request::getProxy() const { + return mProxy; +} + +bool Http::Request::isProxied() const { + return !mProxy.empty(); +} + void Http::Request::setProgressCallback(const Http::Request::ProgressCallback& progressCallback) { mProgressCallback = progressCallback; } @@ -160,7 +174,7 @@ const bool &Http::Request::isCancelled() const { return mCancel; } -std::string Http::Request::prepare() const { +std::string Http::Request::prepare(const Http& http) const { std::ostringstream out; // Convert the method to its string representation @@ -174,10 +188,18 @@ std::string Http::Request::prepare() const { case Delete: method = "DELETE"; break; case Options: method = "OPTIONS"; break; case Patch: method = "PATCH"; break; + case Connect: method = "CONNECT"; break; } // Write the first line containing the request type - out << method << " " << mUri << " "; + if ( mProxy.empty() ) { + out << method << " " << mUri << " "; + } else { + URI uri = http.getURI(); + uri.setPathEtc( mUri ); + out << method << " " << uri.toString() << " "; + } + out << "HTTP/" << mMajorVersion << "." << mMinorVersion << "\r\n"; // Write fields @@ -208,6 +230,44 @@ const std::string& Http::Request::getField(const std::string& field) const { } } +const char * Http::Response::statusToString( const Http::Response::Status& status ) { + switch ( status ) { + // 2xx: success + case Ok: return "OK"; + case Created: return "Created"; + case Accepted: return "Accepted"; + case NoContent: return "No Content"; + case ResetContent: return "Reset Content"; + case PartialContent: return "Partial Content"; + + // 3xx: redirection + case MultipleChoices: return "Multiple Choices"; + case MovedPermanently: return "Moved Permanently"; + case MovedTemporarily: return "Moved Temporarily"; + case NotModified: return "Not Modified"; + + // 4xx: client error + case BadRequest: return "BadRequest"; + case Unauthorized: return "Unauthorized"; + case Forbidden: return "Forbidden"; + case NotFound: return "Not Found"; + case RangeNotSatisfiable: return "Range Not Satisfiable"; + + // 5xx: server error + case InternalServerError: return "Internal Server Error"; + case NotImplemented: return "Not Implemented"; + case BadGateway: return "Bad Gateway"; + case ServiceNotAvailable: return "Service Not Available"; + case GatewayTimeout: return "Gateway Timeout"; + case VersionNotSupported: return "Version Not Supported"; + + // 10xx: Custom codes + case InvalidResponse: return "Invalid Response"; + case ConnectionFailed: return "Connection Failed"; + default: return ""; + } +} + Http::Response::Response() : mStatus (ConnectionFailed), mMajorVersion(0), @@ -358,6 +418,15 @@ Http::Http(const std::string& host, unsigned short port, bool useSSL) : setHost(host, port, useSSL); } +Http::Http(const std::string & host, unsigned short port, bool useSSL, URI proxy) : + mConnection( NULL ), + mHostName(host), + mPort(port), + mProxy(proxy) +{ + setHost(host, port, useSSL); +} + Http::~Http() { std::list::iterator itt; @@ -409,7 +478,12 @@ void Http::setHost(const std::string& host, unsigned short port, bool useSSL) { if (!mHostName.empty() && (*mHostName.rbegin() == '/')) mHostName.erase(mHostName.size() - 1); - mHost = IpAddress(mHostName); + if ( !mProxy.empty() ) { + mHost = IpAddress(mProxy.getHost()); + sameHost = false; + } else { + mHost = IpAddress(mHostName); + } // If the new host is different to the last set host // and there's an open connection to the host, we close @@ -429,12 +503,19 @@ Http::Response Http::sendRequest(const Http::Request& request, Time timeout) { } Http::Response Http::downloadRequest(const Http::Request& request, IOStream& writeTo, Time timeout) { + if ( mProxy.empty() && request.isProxied() ) { + Http http( mHostName, mPort, mIsSSL, request.getProxy() ); + return http.downloadRequest( request, writeTo, timeout ); + } + if ( 0 == mHost.toInteger() ) { return Response(); } if ( NULL == mConnection ) { - TcpSocket * Conn = mIsSSL ? SSLSocket::New( mHostName, request.getValidateCertificate(), request.getValidateHostname() ) : TcpSocket::New(); + TcpSocket * Conn = ( mProxy.empty() ? mIsSSL : ( SSLSocket::isSupported() && mProxy.getScheme() == "https" ) ) ? + SSLSocket::New( mHostName, request.getValidateCertificate(), request.getValidateHostname() ) : + TcpSocket::New(); mConnection = Conn; } @@ -445,9 +526,9 @@ Http::Response Http::downloadRequest(const Http::Request& request, IOStream& wri Response received; // Connect the socket to the host - if (mConnection->connect(mHost, mPort, timeout) == Socket::Done) { + if (mConnection->connect(mHost, mProxy.empty() ? mPort : mProxy.getPort(), timeout) == Socket::Done) { // Convert the request to string and send it through the connected socket - std::string requestStr = toSend.prepare(); + std::string requestStr = toSend.prepare(*this); if (!requestStr.empty()) { // Send it through the socket @@ -743,6 +824,12 @@ Http::Request Http::prepareFields(const Http::Request& request) { toSend.setField("Connection", "close"); } + if (!mProxy.empty()) { + toSend.setField("Accept", "*/*"); + + toSend.setField("Proxy-connection", "close"); + } + return std::move(toSend); } @@ -797,4 +884,12 @@ const unsigned short& Http::getPort() const { return mPort; } +const bool& Http::isSSL() const { + return mIsSSL; +} + +URI Http::getURI() const { + return URI( String::format( "%s://%s:%d", mIsSSL ? "https" : "http", mHostName.c_str(), mPort ) ); +} + }} diff --git a/src/examples/http_request/http_request.cpp b/src/examples/http_request/http_request.cpp index 4ff57477b..6b5c5fe25 100644 --- a/src/examples/http_request/http_request.cpp +++ b/src/examples/http_request/http_request.cpp @@ -5,11 +5,13 @@ void printResponseHeaders( Http::Response& response ) { Http::Response::FieldTable headers = response.getHeaders(); - std::cout << "\r\nHeaders: " << std::endl; + std::cout << "HTTP/" << response.getMajorHttpVersion() << "." << response.getMinorHttpVersion() << " " << response.getStatus() << " " << Http::Response::statusToString( response.getStatus() ) << std::endl; for ( auto&& head : headers ) { - std::cout << "\t" << head.first << ": " << head.second << std::endl; + std::cout << head.first << ": " << head.second << std::endl; } + + std::cout << std::endl; } EE_MAIN_FUNC int main (int argc, char * argv []) { @@ -17,14 +19,16 @@ EE_MAIN_FUNC int main (int argc, char * argv []) { args::HelpFlag help(parser, "help", "Display this help menu", {'h', "help"}); args::ValueFlag postData(parser, "data", "HTTP POST data", {'d', "data"}); args::ValueFlagList headers(parser, "header", "Pass custom header(s) to server", {'H', "header"}); - args::Flag head(parser, "head", "Show document info", {'I',"head"}); + args::Flag includeHead(parser, "include", "Include protocol response headers in the output", {'i',"include"}); args::Flag insecure(parser, "insecure", "Allow insecure server connections when using SSL", {'k',"insecure"}); args::Flag location(parser, "location", "Follow redirects", {'L',"location"}); args::ValueFlag maxRedirs(parser, "max-redirs", "Maximum number of redirects allowed", {"max-redirs"}); args::ValueFlag output(parser, "file", "Write to file instead of stdout", {'o', "output"}); + args::ValueFlag proxy(parser, "proxy", "[protocol://]host[:port] Use this proxy", {'x', "proxy"}); args::Flag progress(parser, "progress", "Show current progress of a download", {'p',"progress"}); + args::Flag verbose(parser, "verbose", "Make the operation more talkative", {'v',"verbose"}); args::ValueFlag requestMethod(parser, "request", "Specify request command to use", {'X', "request"}); - args::Positional url(parser, "url", "The url to request"); + args::Positional url(parser, "URL", "The URL to request"); try { parser.ParseCLI(argc, argv); @@ -127,6 +131,11 @@ EE_MAIN_FUNC int main (int argc, char * argv []) { request.setMaxRedirects(maxRedirs.Get()); } + // Set the proxy for the request + if ( proxy ) { + request.setProxy( URI( proxy.Get() ) ); + } + if ( !output ) { // Send the request Http::Response response = http.sendRequest(request); @@ -134,16 +143,14 @@ EE_MAIN_FUNC int main (int argc, char * argv []) { // Check the status code and display the result Http::Response::Status status = response.getStatus(); + if ( includeHead ) + printResponseHeaders(response); + if ( status == Http::Response::Ok ) { - if ( head ) { - printResponseHeaders(response); - - std::cout << std::endl << "Body: " << std::endl; - } - std::cout << response.getBody() << std::endl; } else { std::cout << "Error " << status << std::endl << response.getStatusDescription() << std::endl; + std::cout << response.getBody() << std::endl; } } else { std::string path( output.Get() ); @@ -171,13 +178,13 @@ EE_MAIN_FUNC int main (int argc, char * argv []) { // Download the request response into a file Http::Response response = http.downloadRequest(request, path, Seconds(5)); - if ( head ) + if ( includeHead ) printResponseHeaders(response); } } } - if ( head ) + if ( verbose ) MemoryManager::showResults(); return EXIT_SUCCESS;