/** * @file httpcommon.h * @brief Public-facing declarations and definitions of common types * * $LicenseInfo:firstyear=2012&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2012-2013, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ #ifndef _LLCORE_HTTP_COMMON_H_ #define _LLCORE_HTTP_COMMON_H_ /// @package LLCore::HTTP /// /// This library implements a high-level, Indra-code-free (somewhat) client /// interface to HTTP services based on actual patterns found in the viewer /// and simulator. Interfaces are similar to those supplied by the legacy classes /// LLCurlRequest and LLHTTPClient. To that is added a policy scheme that /// allows an application to specify connection behaviors: limits on /// connections, HTTP keepalive, HTTP pipelining, retry-on-error limits, etc. /// /// Features of the library include: /// - Single, private working thread where all transport and processing occurs. /// - Support for multiple consumers running in multiple threads. /// - Scatter/gather (a.k.a. buffer array) model for bulk data movement. /// - Reference counting used for many object instance lifetimes. /// - Minimal data sharing across threads for correctness and low latency. /// /// The public interface is declared in a few key header files: /// - "llcorehttp/bufferarray.h" /// - "llcorehttp/httpcommon.h" /// - "llcorehttp/httphandler.h" /// - "llcorehttp/httpheaders.h" /// - "llcorehttp/httpoptions.h" /// - "llcorehttp/httprequest.h" /// - "llcorehttp/httpresponse.h" /// /// The library is still under development and particular users /// may need access to internal implementation details that are found /// in the _*.h header files. But this is a crutch to be avoided if at /// all possible and probably indicates some interface work is neeeded. /// /// Using the library is fairly easy. Global setup needs a few /// steps: /// /// - libcurl initialization including thread-safely callbacks for SSL: /// . curl_global_init(...) /// . CRYPTO_set_locking_callback(...) /// . CRYPTO_set_id_callback(...) /// - HttpRequest::createService() called to instantiate singletons /// and support objects. /// - HttpRequest::startThread() to kick off the worker thread and /// begin servicing requests. /// /// An HTTP consumer in an application, and an application may have many /// consumers, does a few things: /// /// - Instantiate and retain an object based on HttpRequest. This /// object becomes the portal into runtime services for the consumer. /// - Derive or mixin the HttpHandler class if you want notification /// when requests succeed or fail. This object's onCompleted() /// method is invoked and an instance can be shared across /// requests. /// /// Issuing a request is straightforward: /// - Construct a suitable URL. /// - Configure HTTP options for the request. (optional) /// - Build a list of additional headers. (optional) /// - Invoke one of the requestXXXX() methods (requestGetByteRange, /// requestPost, etc.) on the HttpRequest instance supplying the /// above along with a policy class, a priority and an optional /// pointer to an HttpHandler instance. Work is then queued to /// the worker thread and occurs asynchronously. /// - Periodically invoke the update() method on the HttpRequest /// instance which performs completion notification to HttpHandler /// objects. /// - Do completion processing in your onCompletion() method. /// /// Code fragments. /// /// Initialization. Rather than a poorly-maintained example in /// comments, look in the example subdirectory which is a minimal /// yet functional tool to do GET request performance testing. /// With four calls: /// /// init_curl(); /// LLCore::HttpRequest::createService(); /// LLCore::HttpRequest::startThread(); /// LLCore::HttpRequest * hr = new LLCore::HttpRequest(); /// /// the program is basically ready to issue requests. /// /// HttpHandler. Having started life as a non-indra library, /// this code broke away from the classic Responder model and /// introduced a handler class to represent an interface for /// request responses. This is a non-reference-counted entity /// which can be used as a base class or a mixin. An instance /// of a handler can be used for each request or can be shared /// among any number of requests. Your choice but expect to /// code something like the following: /// /// class AppHandler : public LLCore::HttpHandler /// { /// public: /// virtual void onCompleted(HttpHandle handle, /// HttpResponse * response) /// { /// ... /// } /// ... /// }; /// ... /// handler = new handler(...); /// /// /// Issuing requests. Using 'hr' above, /// /// hr->requestGet(HttpRequest::DEFAULT_POLICY_ID, /// 0, // Priority, not used yet /// url, /// NULL, // options /// NULL, // additional headers /// handler); /// /// If that returns a value other than LLCORE_HTTP_HANDLE_INVALID, /// the request was successfully issued and there will eventally /// be a status delivered to the handler. If invalid is returnedd, /// the actual status can be retrieved by calling hr->getStatus(). /// /// Completing requests and delivering notifications. Operations /// are all performed by the worker thread and will be driven to /// completion regardless of caller actions. Notification of /// completion (success or failure) is done by calls to /// HttpRequest::update() which will invoke handlers for completed /// requests: /// /// hr->update(0); /// // Callbacks into handler->onCompleted() /// /// /// Threads. /// /// Threads are supported and used by this library. The various /// classes, methods and members are documented with thread /// constraints which programmers must follow and which are /// defined as follows: /// /// consumer Any thread that has instanced HttpRequest and is /// issuing requests. A particular instance can only /// be used by one consumer thread but a consumer may /// have many instances available to it. /// init Special consumer thread, usually the main thread, /// involved in setting up the library at startup. /// worker Thread used internally by the library to perform /// HTTP operations. Consumers will not have to deal /// with this thread directly but some APIs are reserved /// to it. /// any Consumer or worker thread. /// /// For the most part, API users will not have to do much in the /// way of ensuring thread safely. However, there is a tremendous /// amount of sharing between threads of read-only data. So when /// documentation declares that an option or header instance /// becomes shared between consumer and worker, the consumer must /// not modify the shared object. /// /// Internally, there is almost no thread synchronization. During /// normal operations (non-init, non-term), only the request queue /// and the multiple reply queues are shared between threads and /// only here are mutexes used. /// #include "linden_common.h" // Modifies curl/curl.h interfaces #include "llsd.h" #include <string> #include <curl/curl.h> #include "boost/noncopyable.hpp" namespace LLCore { /// All queued requests are represented by an HttpHandle value. /// The invalid value is returned when a request failed to queue. /// The actual status for these failures is then fetched with /// HttpRequest::getStatus(). /// /// The handle is valid only for the life of a request. On /// return from any HttpHandler notification, the handle immediately /// becomes invalid and may be recycled for other queued requests. typedef void * HttpHandle; #define LLCORE_HTTP_HANDLE_INVALID (NULL) /// For internal scheduling and metrics, we use a microsecond /// timebase compatible with the environment. typedef U64 HttpTime; /// Error codes defined by the library itself as distinct from /// libcurl (or any other transport provider). enum HttpError { // Successful value compatible with the libcurl codes. HE_SUCCESS = 0, // Intended for HTTP reply codes 100-999, indicates that // the reply should be considered an error by the application. HE_REPLY_ERROR = 1, // Service is shutting down and requested operation will // not be queued or performed. HE_SHUTTING_DOWN = 2, // Operation was canceled by request. HE_OP_CANCELED = 3, // Invalid content range header received. HE_INV_CONTENT_RANGE_HDR = 4, // Request handle not found HE_HANDLE_NOT_FOUND = 5, // Invalid datatype for option/setting HE_INVALID_ARG = 6, // Option hasn't been explicitly set HE_OPT_NOT_SET = 7, // Option not dynamic, must be set during init phase HE_OPT_NOT_DYNAMIC = 8, // Invalid HTTP status code returned by server HE_INVALID_HTTP_STATUS = 9, // Couldn't allocate resource, typically libcurl handle HE_BAD_ALLOC = 10 }; // end enum HttpError /// HttpStatus encapsulates errors from libcurl (easy, multi), HTTP /// reply status codes and internal errors as well. The encapsulation /// isn't expected to completely isolate the caller from libcurl but /// basic operational tests (success or failure) are provided. /// /// Non-HTTP status are encoded as (type, status) with type being /// one of: EXT_CURL_EASY, EXT_CURL_MULTI or LLCORE and status /// being the success/error code from that domain. HTTP status /// is encoded as (status, error_flag). Status should be in the /// range [100, 999] and error_flag is either HE_SUCCESS or /// HE_REPLY_ERROR to indicate whether this should be treated as /// a successful status or an error. The application is responsible /// for making that determination and a range like [200, 299] isn't /// automatically assumed to be definitive. /// /// Examples: /// /// 1. Construct a default, successful status code: /// HttpStatus(); /// /// 2. Construct a successful, HTTP 200 status code: /// HttpStatus(200); /// /// 3. Construct a failed, HTTP 404 not-found status code: /// HttpStatus(404); /// /// 4. Construct a failed libcurl couldn't connect status code: /// HttpStatus(HttpStatus::EXT_CURL_EASY, CURLE_COULDNT_CONNECT); /// /// 5. Construct an HTTP 301 status code to be treated as success: /// HttpStatus(301, HE_SUCCESS); /// /// 6. Construct a failed status of HTTP Status 499 with a custom error message /// HttpStatus(499, "Failed LLSD Response"); struct HttpStatus { typedef unsigned short type_enum_t; HttpStatus() { mDetails = std::shared_ptr<Details>(new Details(LLCORE, HE_SUCCESS)); } HttpStatus(type_enum_t type, short status) { mDetails = std::shared_ptr<Details>(new Details(type, status)); } HttpStatus(int http_status) { mDetails = std::shared_ptr<Details>(new Details(http_status, (http_status >= 200 && http_status <= 299) ? HE_SUCCESS : HE_REPLY_ERROR)); llassert(http_status >= 100 && http_status <= 999); } HttpStatus(int http_status, const std::string &message) { mDetails = std::shared_ptr<Details>(new Details(http_status, (http_status >= 200 && http_status <= 299) ? HE_SUCCESS : HE_REPLY_ERROR)); llassert(http_status >= 100 && http_status <= 999); mDetails->mMessage = message; } HttpStatus(const HttpStatus & rhs) { mDetails = rhs.mDetails; } ~HttpStatus() { } HttpStatus & operator=(const HttpStatus & rhs) { mDetails = rhs.mDetails; return *this; } HttpStatus & clone(const HttpStatus &rhs) { mDetails = std::shared_ptr<Details>(new Details(*rhs.mDetails)); return *this; } static const type_enum_t EXT_CURL_EASY = 0; ///< mStatus is an error from a curl_easy_*() call static const type_enum_t EXT_CURL_MULTI = 1; ///< mStatus is an error from a curl_multi_*() call static const type_enum_t LLCORE = 2; ///< mStatus is an HE_* error code ///< 100-999 directly represent HTTP status codes /// Test for successful status in the code regardless /// of error source (internal, libcurl). /// /// @return 'true' when status is successful. /// operator bool() const { return 0 == mDetails->mStatus; } /// Inverse of previous operator. /// /// @return 'true' on any error condition bool operator !() const { return 0 != mDetails->mStatus; } /// Equality and inequality tests to bypass bool conversion /// which will do the wrong thing in conditional expressions. bool operator==(const HttpStatus & rhs) const { return (*mDetails == *rhs.mDetails); } bool operator!=(const HttpStatus & rhs) const { return ! operator==(rhs); } /// Convert to single numeric representation. Mainly /// for logging or other informal purposes. Also /// creates an ambiguous second path to integer conversion /// which tends to find programming errors such as formatting /// the status to a stream (operator<<). operator U32() const; U32 toULong() const { return operator U32(); } /// And to convert to a hex string. std::string toHex() const; /// Convert status to a string representation. For /// success, returns an empty string. For failure /// statuses, a string as appropriate for the source of /// the error code (libcurl easy, libcurl multi, or /// LLCore itself). std::string toString() const; /// Convert status to a compact string representation /// of the form: "<type>_<value>". The <type> will be /// one of: Core, Http, Easy, Multi, Unknown. And /// <value> will be an unsigned integer. More easily /// interpreted than the hex representation, it's still /// compact and easily searched. std::string toTerseString() const; /// Returns true if the status value represents an /// HTTP response status (100 - 999). bool isHttpStatus() const { return mDetails->mType >= type_enum_t(100) && mDetails->mType <= type_enum_t(999); } /// Returns true if the status is one that will be retried /// internally. Provided for external consumption for cases /// where that logic needs to be replicated. Only applies /// to failed statuses, successful statuses will return false. bool isRetryable() const; /// Returns the currently set status code as a raw number /// short getStatus() const { return mDetails->mStatus; } /// Returns the currently set status type /// type_enum_t getType() const { return mDetails->mType; } /// Returns an optional error message if one has been set. /// std::string getMessage() const { return mDetails->mMessage; } /// Sets an optional error message /// void setMessage(const std::string &message) { mDetails->mMessage = message; } /// Retrieves data about an optionally recorded SSL certificate. LLSD getErrorData() const { return mDetails->mErrorData; } /// Optionally sets an SSL certificate on this status. void setErrorData(LLSD data) { mDetails->mErrorData = data; } private: struct Details { Details(type_enum_t type, short status): mType(type), mStatus(status), mMessage(), mErrorData() {} Details(const Details &rhs) : mType(rhs.mType), mStatus(rhs.mStatus), mMessage(rhs.mMessage), mErrorData(rhs.mErrorData) {} bool operator == (const Details &rhs) const { return (mType == rhs.mType) && (mStatus == rhs.mStatus); } type_enum_t mType; short mStatus; std::string mMessage; LLSD mErrorData; }; std::shared_ptr<Details> mDetails; }; // end struct HttpStatus /// A namespace for several free methods and low level utilities. namespace LLHttp { typedef std::shared_ptr<CURL> CURL_ptr; void initialize(); void cleanup(); CURL_ptr createEasyHandle(); std::string getCURLVersion(); void check_curl_code(CURLcode code, int curl_setopt_option); } } // end namespace LLCore #endif // _LLCORE_HTTP_COMMON_H_