diff options
| author | Monty Brandenberg <monty@lindenlab.com> | 2013-09-17 21:55:44 +0000 | 
|---|---|---|
| committer | Monty Brandenberg <monty@lindenlab.com> | 2013-09-17 21:55:44 +0000 | 
| commit | dab920c26b36e032876592ca827a3a31f067a9ba (patch) | |
| tree | c9ac4bd267144c0fdb609f9f0837a71999c92749 /indra | |
| parent | 99c60b83f15a3c40ecf0e5f144cfc060b37c4cf8 (diff) | |
SH-4492 Create a useful README for llcorehttp.
First edit complete.  Use the library in 15 minutes.  Describe the
code.  Refinements to the initial try.  Describe that code.  Still
to do:  more refinements, how to choose a policy class, FAQ.
Diffstat (limited to 'indra')
| -rw-r--r-- | indra/llcorehttp/README.Linden | 467 | 
1 files changed, 467 insertions, 0 deletions
| diff --git a/indra/llcorehttp/README.Linden b/indra/llcorehttp/README.Linden new file mode 100644 index 0000000000..e5ff824388 --- /dev/null +++ b/indra/llcorehttp/README.Linden @@ -0,0 +1,467 @@ + + + +1.  HTTP fetching in 15 Minutes + +    Let's start with a trivial working example.  You'll need a throwaway +    build of the viewer.  And we'll use indra/newview/llappviewer.cpp as +    our host. + +    Add some needed headers: + + +        #include "httpcommon.h" +        #include "httprequest.h" +        #include "httphandler.h" + + +    You'll need to derive a class from HttpHandler (not HttpHandle). +    This is used to deliver notifications of HTTP completion to your +    code.  Place it near the top, before LLDeferredTaskList, say: + + +        class MyHandler : public LLCore::HttpHandler +        { +        public: +            MyHandler() +            : LLCore::HttpHandler() +            {} + +            virtual void onCompleted(LLCore::HttpHandle /* handle */, +                                     LLCore::HttpResponse * /* response */) +            { +                LL_INFOS("Hack") << "It is happening again." << LL_ENDL; + +                delete this;    // Last statement +            } +        }; + + +    Add some statics up there as well: + + +        // Our request object.  Allocate during initialiation. +        static LLCore::HttpRequest * my_request(NULL); + +        // The policy class for HTTP traffic. +        // Use HttpRequest::DEFAULT_POLICY_ID, but DO NOT SHIP WITH THIS VALUE!! +        static LLCore::HttpRequest::policy_t my_policy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + +        // Priority for HTTP requests.  Use 0U. +        static LLCore::HttpRequest::priority_t my_priority(0U); + + +    In LLAppViewer::init() after mAppCoreHttp.init(), create a request object: + + +        my_request = new LLCore::HttpRequest(); + + +    In LLAppViewer::mainLoop(), just before entering the while loop, +    we'll kick off one HTTP request: + + +        // Construct a handler object (we'll use the heap this time): +        MyHandler * my_handler = new MyHandler; + +        // Issue a GET request to 'http://www.example.com/' kicking off +        // all the I/O, retry logic, etc. +        LLCore::HttpHandle handle; +        handle = my_request->requestGet(my_policy, +                                        my_priority, +                                        "http://www.example.com/", +                                        NULL, +                                        NULL, +                                        my_handler); +        if (LLCORE_HTTP_HANDLE_INVALID == handle) +        { +            LL_WARNS("Hack") << "Failed to launch HTTP request.  Try again." +                             << LL_ENDL; +        } + + +    Finally, arrange to periodically call update() on the request object +    to find out when the request completes.  This will be done by +    calling the onCompleted() method with status information and +    response data from the HTTP operation.  Add this to the +    LLAppViewer::idle() method after the ping: + + +        my_request->update(0); + + +    That's it.  Build it, run it and watch the log file.  You should get +    the "It is happening again." message indicating that the HTTP +    operation completed in some manner. + + +2.  What Does All That Mean + +    MyHandler/HttpHandler.  This class replaces the Responder-style in +    legacy code.  One method is currently defined.  It is used for all +    request completions, successful or failed: + + +        void onCompleted(LLCore::HttpHandle /* handle */, +                         LLCore::HttpResponse * /* response */); + + +    The onCompleted() method is invoked as a callback during calls to +    HttpRequest::update().  All I/O is completed asynchronously in +    another thread.  But notifications are polled by calling update() +    and invoking a handler for completed requests. + +    In this example, the invocation also deletes the handler (which is +    never referenced by the llcorehttp code again).  But other +    allocation models are possible including handlers shared by many +    requests, stack-based handlers and handlers mixed in with other, +    unrelated classes. + +    LLCore::HttpRequest().  Instances of this class are used to request +    all major functions of the library.  Initialization, starting +    requests, delivering final notification of completion and various +    utility operations are all done via instances.  There is one very +    important rule for instances: + +        Request objects may NOT be shared between threads. + +    my_priority.  The APIs support the idea of priority ordering of +    requests but it hasn't been implemented and the hope is that this +    will become useless and removed from the interface.  Use 0U except +    as noted. + +    my_policy.  This is an important one.  This library attempts to +    manage TCP connection usage more rigorously than in the past.  This +    is done by issuing requests to a queue that has various settable +    properties.  These establish connection usage for the queue as well +    as how queues compete with one another.  (This is patterned after +    class-based queueing used in various networking stacks.)  Several +    classes are pre-defined.  Deciding when to use an existing class and +    when to create a new one will determine what kind of experience +    users have.  We'll pick up this question in detail below. + +    requestGet().  Issues an ordinary HTTP GET request to a given URL +    and associating the request with a policy class, a priority and an +    response handler.  Two additional arguments, not used here, allow +    for additional headers on the request and for per-request options. +    If successful, the call returns a handle whose value is other than +    LLCORE_HTTP_HANDLE_INVALID.  The HTTP operation is then performed +    asynchronously by another thread without any additional work by the +    caller.  If the handle returned is invalid, you can get the status +    code by calling my_request->getStatus(). + +    update().  To get notification that the request has completed, a +    call to update() will invoke onCompleted() methods. + + +3.  Refinements, Necessary and Otherwise + +    MyHandler::onCompleted().  You'll want to do something useful with +    your response.  Distinguish errors from successes and getting the +    response body back in some form. + +    Add a new header: + + +        #include "bufferarray.h" + + +    Replace the existing MyHandler::onCompleted() definition with: + + +        virtual void onCompleted(LLCore::HttpHandle /* handle */, +                                 LLCore::HttpResponse * response) +        { +            LLCore::HttpStatus status = response->getStatus(); +            if (status) +            { +                // Successful request.  Try to fetch the data +                LLCore::BufferArray * data = response->getBody(); + +                if (data && data->size()) +                { +                    // There's some data.  A BufferArray is a linked list +                    // of buckets.  We'll create a linear buffer and copy +                    // it into it. +                    size_t data_len = data->size(); +                    char * data_blob = new char [data_len + 1]; +                    data->read(0, data_blob, data_len); +                    data_blob[data_len] = '\0'; + +                    // Process the data now in NUL-terminated string. +                    // Needs more scrubbing but this will do. +                    LL_INFOS("Hack") << "Received:  " << data_blob << LL_ENDL; + +                    // Free the temporary data +                    delete [] data_blob; +                } +            } +            else +            { +                // Something went wrong.  Translate the status to +                // a meaningful message. +                LL_WARNS("Hack") << "HTTP GET failed.  Status:  " +                                 << status.toTerseString() +                                 << ", Reason:  " << status.toString() +                                 << LL_ENDL; +            }            + +            delete this;    // Last statement +        } + + +    HttpHeaders.  The header file "httprequest.h" documents the expected +    important headers that will go out with the request.  You can add to +    these by including an HttpHeaders object with the requestGet() call. +    These are typically setup once as part of init rather than +    dynamically created. + +    Add another header: + + +        #include "httpheaders.h" + + +    In LLAppViewer::mainLoop(), add this alongside the allocation of +    my_handler: + + +        // Additional headers for all requests +        LLCore::HttpHeaders * my_headers = new LLCore::HttpHeaders(); +        my_headers->append("Accept", "text/html, application/llsd+xml"); + + +    HttpOptions.  Options are similar and include a mix of value types. +    One interesting per-request option is the trace setting.  This +    enables various debug-type messages in the log file that show the +    progress of the request through the library.  It takes values from +    zero to three with higher values giving more verbose logging.  We'll +    use '2' and this will also give us a chance to verify that +    HttpHeaders works as expected. + +    Same as above, a new header: + + +        #include "httpoptions.h" + + +    And in LLAppView::mainLoop(): + + +        // Special options for requests +        LLCore::HttpOptions * my_options = new LLCore::HttpOptions(); +        my_options->setTrace(2); + + +    Now let's put that all together into a more complete requesting +    sequence.  Replace the existing invocation of requestGet() with this +    slightly more elaborate block: + + +        LLCore::HttpHandle handle; +        handle = my_request->requestGet(my_policy, +                                        my_priority, +                                        "http://www.example.com/", +                                        my_options, +                                        my_headers, +                                        my_handler); +        if (LLCORE_HTTP_HANDLE_INVALID == handle) +        { +             LLCore::HttpStatus status = my_request->getStatus(); + +             LL_WARNS("Hack") << "Failed to request HTTP GET.  Status:  " +                              << status.toTerseString() +                              << ", Reason:  " << status.toString() +                              << LL_ENDL; + +             delete my_handler;    // No longer needed. +             my_handler = NULL; +        } + + +    Build, run and examine the log file.  You'll get some new data with +    this run.  First, you should get the www.example.com home page +    content: + + +---------------------------------------------------------------------------- +2013-09-17T20:26:51Z INFO: MyHandler::onCompleted: Received:  <!doctype html> +<html> +<head> +    <title>Example Domain</title> + +    <meta charset="utf-8" /> +    <meta http-equiv="Content-type" content="text/html; charset=utf-8" /> +    <meta name="viewport" content="width=device-width, initial-scale=1" /> +    <style type="text/css"> +    body { +        background-color: #f0f0f2; +        margin: 0; +        padding: 0; +        font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; +         +    } +    div { +        width: 600px; +        margin: 5em auto; +        padding: 50px; +        background-color: #fff; +        border-radius: 1em; +    } +    a:link, a:visited { +        color: #38488f; +        text-decoration: none; +    } +    @media (max-width: 700px) { +        body { +            background-color: #fff; +        } +        div { +            width: auto; +            margin: 0 auto; +            border-radius: 0; +            padding: 1em; +        } +    } +    </style>     +</head> + +<body> +<div> +    <h1>Example Domain</h1> +    <p>This domain is established to be used for illustrative examples in documents. You may use this +    domain in examples without prior coordination or asking for permission.</p> +    <p><a href="http://www.iana.org/domains/example">More information...</a></p> +</div> +</body> +</html> +---------------------------------------------------------------------------- + + +    You'll also get a detailed trace of the HTTP operation itself.  Note +    the HEADEROUT line which shows the additional header added to the +    request. + + +---------------------------------------------------------------------------- +HttpService::processRequestQueue: TRACE, FromRequestQueue, Handle:  086D3148 +HttpLibcurl::addOp: TRACE, ToActiveQueue, Handle:  086D3148, Actives:  0, Readies:  0 +HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle:  086D3148, Type:  TEXT, Data:  About to connect() to www.example.com port 80 (#0)  +HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle:  086D3148, Type:  TEXT, Data:    Trying 93.184.216.119...  +HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle:  086D3148, Type:  TEXT, Data:  Connected to www.example.com (93.184.216.119) port 80 (#0)  +HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle:  086D3148, Type:  TEXT, Data:  Connected to www.example.com (93.184.216.119) port 80 (#0)  +HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle:  086D3148, Type:  HEADEROUT, Data:  GET / HTTP/1.1  Host: www.example.com  Accept-Encoding: deflate, gzip  Connection: keep-alive  Keep-alive: 300  Accept: text/html, application/llsd+xml +HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle:  086D3148, Type:  HEADERIN, Data:  HTTP/1.1 200 OK   +HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle:  086D3148, Type:  HEADERIN, Data:  Accept-Ranges: bytes   +HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle:  086D3148, Type:  HEADERIN, Data:  Cache-Control: max-age=604800   +HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle:  086D3148, Type:  HEADERIN, Data:  Content-Type: text/html   +HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle:  086D3148, Type:  HEADERIN, Data:  Date: Tue, 17 Sep 2013 20:26:56 GMT   +HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle:  086D3148, Type:  HEADERIN, Data:  Etag: "3012602696"   +HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle:  086D3148, Type:  HEADERIN, Data:  Expires: Tue, 24 Sep 2013 20:26:56 GMT   +HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle:  086D3148, Type:  HEADERIN, Data:  Last-Modified: Fri, 09 Aug 2013 23:54:35 GMT   +HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle:  086D3148, Type:  HEADERIN, Data:  Server: ECS (ewr/1590)   +HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle:  086D3148, Type:  HEADERIN, Data:  X-Cache: HIT   +HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle:  086D3148, Type:  HEADERIN, Data:  x-ec-custom-error: 1   +HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle:  086D3148, Type:  HEADERIN, Data:  Content-Length: 1270   +HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle:  086D3148, Type:  HEADERIN, Data:     +HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle:  086D3148, Type:  DATAIN, Data:  256 Bytes +HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle:  086D3148, Type:  TEXT, Data:  Connection #0 to host www.example.com left intact  +HttpLibcurl::completeRequest: TRACE, RequestComplete, Handle:  086D3148, Status:  Http_200 +HttpOperation::addAsReply: TRACE, ToReplyQueue, Handle:  086D3148 +---------------------------------------------------------------------------- + + +4.  What Does All That Mean, Part 2 + +    HttpStatus.  The HttpStatus object encodes errors from libcurl, the +    library itself and HTTP status values.  It does this to avoid +    collapsing all non-HTTP error into a single '499' HTTP status and to +    make errors distinct. + +    To aid programming, the usual bool conversions are available so that +    you can write 'if (status)' and the expected thing will happen +    whether it's an HTTP, libcurl or library error.  There's also +    provision to override the treatment of HTTP errors (making 404 a +    success, say). + +    Share data, don't copy it.  The library was started with the goal of +    avoiding data copies as much as possible.  Instead, read-only data +    sharing across threads with atomic reference counts is used for a +    number of data types.  These currently are: + +        * BufferArray.  Linked list of data blocks/HTTP bodies. +        * HttpHeaders.  Shared headers for both requests and responses. +        * HttpOptions.  Request-only data modifying HTTP behavior. +        * HttpResponse.  HTTP response description given to onCompleted. + +    Using objects of these types requires a few rules: + +        * Constructor always gives a reference to caller. +        * References are dropped with release() not delete. +        * Additional references may be taken out with addRef(). +        * Unless otherwise stated, once an object is shared with another +          thread it should be treated as read-only.  There's no +          synchronization on the objects themselves. + +    HttpResponse.  You'll encounter this mainly in onCompleted() methods. +    Commonly-used interfaces on this object: + +        * getStatus() to return the final status of the request. +        * getBody() to retrieve the response body which may be NULL or +          zero-length. +        * getContentType() to return the value of the 'Content-Type' +          header or an empty string if none was sent. + +    This is a reference-counted object so you can call addRef() on it +    and hold onto the response for an arbitrary time.  But you'll +    usually just call a few methods and return from onCompleted() whose +    caller will release the object. + +    BufferArray.  The core data representation for request and response +    bodies.  In HTTP responses, it's fetched with the getBody() method +    and may be NULL or non-NULL but zero length.  All successful data +    handling should check both conditions before attempting to fetch +    data from the object.  Data access model uses simple read/write +    semantics: + +        * append() +        * size() +        * read() +        * write() + +    There is a more sophisticated stream adapter that extends these +    methods and will be covered below.  So, one way to retrieve data +    from a request is as follows: + + +        LLCore::BufferArray * data = response->getBody(); +        if (data && data->size()) +        { +            size_t data_len = data->size(); +            char * data_blob = new char [data_len + 1]; +            data->read(0, data_blob, data_len); + + +    HttpOptions and HttpResponse.  Really just simple containers of POD +    and std::string pairs.  But reference counted and the rule about not +    modifying after sharing must be followed.  You'll have the urge to +    change options dynamically at some point.  And you'll try to do that +    by just writing new values to the shared object.  And in tests +    everything will appear to work.  Then you ship and people in the +    real world start hitting read/write races in strings and then crash. + +    HttpHandle.  Uniquely identifies a request and can be used to +    identify it in an onCompleted() method or cancel it if it's still +    queued.  But as soon as a request's onCompleted() invocation +    returns, the handle becomes invalid and may be reused immediately +    for new requests.  Don't hold on to handles after notification. + + +5.  And Still More Refinements + + +6.  Choosing a Policy Class + + +7.  FAQ + | 
