llhttpclient.cpp

Go to the documentation of this file.
00001 
00032 #include "linden_common.h"
00033 
00034 #include "llhttpclient.h"
00035 
00036 #include "llassetstorage.h"
00037 #include "lliopipe.h"
00038 #include "llurlrequest.h"
00039 #include "llbufferstream.h"
00040 #include "llsdserialize.h"
00041 #include "llvfile.h"
00042 #include "llvfs.h"
00043 #include "lluri.h"
00044 
00045 #include "message.h"
00046 #include <curl/curl.h>
00047 
00048 const F32 HTTP_REQUEST_EXPIRY_SECS = 60.0f;
00049 static std::string gCABundle;
00050 
00051 
00052 LLHTTPClient::Responder::Responder()
00053         : mReferenceCount(0)
00054 {
00055 }
00056 
00057 LLHTTPClient::Responder::~Responder()
00058 {
00059 }
00060 
00061 // virtual
00062 void LLHTTPClient::Responder::error(U32 status, const std::string& reason)
00063 {
00064         llinfos << "LLHTTPClient::Responder::error "
00065                 << status << ": " << reason << llendl;
00066 }
00067 
00068 // virtual
00069 void LLHTTPClient::Responder::result(const LLSD& content)
00070 {
00071 }
00072 
00073 // virtual 
00074 void LLHTTPClient::Responder::completedRaw(U32 status, const std::string& reason, const LLChannelDescriptors& channels,
00075                                                                 const LLIOPipe::buffer_ptr_t& buffer)
00076 {
00077         LLBufferStream istr(channels, buffer.get());
00078         LLSD content;
00079 
00080         if (isGoodStatus(status))
00081         {
00082                 LLSDSerialize::fromXML(content, istr);
00083 /*
00084                 const S32 parseError = -1;
00085                 if(LLSDSerialize::fromXML(content, istr) == parseError)
00086                 {
00087                         mStatus = 498;
00088                         mReason = "Client Parse Error";
00089                 }
00090 */
00091         }
00092         
00093         completed(status, reason, content);
00094 }
00095 
00096 // virtual
00097 void LLHTTPClient::Responder::completed(U32 status, const std::string& reason, const LLSD& content)
00098 {
00099         if(isGoodStatus(status))
00100         {
00101                 result(content);
00102         }
00103         else
00104         {
00105                 error(status, reason);
00106         }
00107 }
00108 
00109 
00110 namespace
00111 {
00112         class LLHTTPClientURLAdaptor : public LLURLRequestComplete
00113         {
00114         public:
00115                 LLHTTPClientURLAdaptor(LLHTTPClient::ResponderPtr responder)
00116                         : mResponder(responder),
00117                                 mStatus(499), mReason("LLURLRequest complete w/no status")
00118                 {
00119                 }
00120                 
00121                 ~LLHTTPClientURLAdaptor()
00122                 {
00123                 }
00124 
00125                 virtual void httpStatus(U32 status, const std::string& reason)
00126                 {
00127                         mStatus = status;
00128                         mReason = reason;
00129                 }
00130 
00131                 virtual void complete(const LLChannelDescriptors& channels,
00132                                                                 const buffer_ptr_t& buffer)
00133                 {
00134                         if (mResponder.get())
00135                         {
00136                                 mResponder->completedRaw(mStatus, mReason, channels, buffer);
00137                         }
00138                 }
00139 
00140         private:
00141                 LLHTTPClient::ResponderPtr mResponder;
00142                 U32 mStatus;
00143                 std::string mReason;
00144         };
00145         
00146         class Injector : public LLIOPipe
00147         {
00148         public:
00149                 virtual const char* contentType() = 0;
00150         };
00151 
00152         class LLSDInjector : public Injector
00153         {
00154         public:
00155                 LLSDInjector(const LLSD& sd) : mSD(sd) {}
00156                 virtual ~LLSDInjector() {}
00157 
00158                 const char* contentType() { return "application/xml"; }
00159 
00160                 virtual EStatus process_impl(const LLChannelDescriptors& channels,
00161                         buffer_ptr_t& buffer, bool& eos, LLSD& context, LLPumpIO* pump)
00162                 {
00163                         LLBufferStream ostream(channels, buffer.get());
00164                         LLSDSerialize::toXML(mSD, ostream);
00165                         eos = true;
00166                         return STATUS_DONE;
00167                 }
00168 
00169                 const LLSD mSD;
00170         };
00171 
00172         class RawInjector : public Injector
00173         {
00174         public:
00175                 RawInjector(const U8* data, S32 size) : mData(data), mSize(size) {}
00176                 virtual ~RawInjector() {}
00177 
00178                 const char* contentType() { return "application/octet-stream"; }
00179 
00180                 virtual EStatus process_impl(const LLChannelDescriptors& channels,
00181                         buffer_ptr_t& buffer, bool& eos, LLSD& context, LLPumpIO* pump)
00182                 {
00183                         LLBufferStream ostream(channels, buffer.get());
00184                         ostream.write((const char *)mData, mSize);  // hopefully chars are always U8s
00185                         eos = true;
00186                         return STATUS_DONE;
00187                 }
00188 
00189                 const U8* mData;
00190                 S32 mSize;
00191         };
00192         
00193         class FileInjector : public Injector
00194         {
00195         public:
00196                 FileInjector(const std::string& filename) : mFilename(filename) {}
00197                 virtual ~FileInjector() {}
00198 
00199                 const char* contentType() { return "application/octet-stream"; }
00200 
00201                 virtual EStatus process_impl(const LLChannelDescriptors& channels,
00202                         buffer_ptr_t& buffer, bool& eos, LLSD& context, LLPumpIO* pump)
00203                 {
00204                         LLBufferStream ostream(channels, buffer.get());
00205 
00206                         llifstream fstream(mFilename.c_str(), std::iostream::binary | std::iostream::out);
00207             fstream.seekg(0, std::ios::end);
00208             U32 fileSize = fstream.tellg();
00209             fstream.seekg(0, std::ios::beg);
00210                         char* fileBuffer;
00211                         fileBuffer = new char [fileSize];
00212             fstream.read(fileBuffer, fileSize);
00213             ostream.write(fileBuffer, fileSize);
00214                         fstream.close();
00215                         eos = true;
00216                         return STATUS_DONE;
00217                 }
00218 
00219                 const std::string mFilename;
00220         };
00221         
00222         class VFileInjector : public Injector
00223         {
00224         public:
00225                 VFileInjector(const LLUUID& uuid, LLAssetType::EType asset_type) : mUUID(uuid), mAssetType(asset_type) {}
00226                 virtual ~VFileInjector() {}
00227 
00228                 const char* contentType() { return "application/octet-stream"; }
00229 
00230                 virtual EStatus process_impl(const LLChannelDescriptors& channels,
00231                         buffer_ptr_t& buffer, bool& eos, LLSD& context, LLPumpIO* pump)
00232                 {
00233                         LLBufferStream ostream(channels, buffer.get());
00234                         
00235                         LLVFile vfile(gVFS, mUUID, mAssetType, LLVFile::READ);
00236                         S32 fileSize = vfile.getSize();
00237                         U8* fileBuffer;
00238                         fileBuffer = new U8 [fileSize];
00239             vfile.read(fileBuffer, fileSize);
00240             ostream.write((char*)fileBuffer, fileSize);
00241                         eos = true;
00242                         return STATUS_DONE;
00243                 }
00244 
00245                 const LLUUID mUUID;
00246                 LLAssetType::EType mAssetType;
00247         };
00248 
00249         
00250         LLPumpIO* theClientPump = NULL;
00251 }
00252 
00253 static void request(
00254         const std::string& url,
00255         LLURLRequest::ERequestAction method,
00256         Injector* body_injector,
00257         LLHTTPClient::ResponderPtr responder,
00258     const LLSD& headers,
00259         const F32 timeout=HTTP_REQUEST_EXPIRY_SECS)
00260 {
00261         if (!LLHTTPClient::hasPump())
00262         {
00263                 responder->completed(U32_MAX, "No pump", LLSD());
00264                 return;
00265         }
00266         LLPumpIO::chain_t chain;
00267 
00268         LLURLRequest *req = new LLURLRequest(method, url);
00269         req->requestEncoding("");
00270 
00271     // Insert custom headers is the caller sent any
00272     if (headers.isMap())
00273     {
00274         LLSD::map_const_iterator iter = headers.beginMap();
00275         LLSD::map_const_iterator end  = headers.endMap();
00276 
00277         for (; iter != end; ++iter)
00278         {
00279             std::ostringstream header;
00280             //if the header is "Pragma" with no value
00281             //the caller intends to force libcurl to drop
00282             //the Pragma header it so gratuitously inserts
00283             //Before inserting the header, force libcurl
00284             //to not use the proxy (read: llurlrequest.cpp)
00285             if ((iter->first == "Pragma") && (iter->second.asString() == ""))
00286             {
00287                 req->useProxy(FALSE);
00288             }
00289             header << iter->first << ": " << iter->second.asString() ;
00290             llinfos << "header = " << header.str() << llendl;
00291             req->addHeader(header.str().c_str());
00292         }
00293     }
00294         if (!gCABundle.empty())
00295         {
00296                 req->checkRootCertificate(true, gCABundle.c_str());
00297         }
00298         req->setCallback(new LLHTTPClientURLAdaptor(responder));
00299 
00300         if (method == LLURLRequest::HTTP_POST  &&  gMessageSystem)
00301         {
00302                 req->addHeader(llformat("X-SecondLife-UDP-Listen-Port: %d",
00303                                                                 gMessageSystem->mPort).c_str());
00304         }
00305         
00306         if (method == LLURLRequest::HTTP_PUT || method == LLURLRequest::HTTP_POST)
00307         {
00308                 req->addHeader(llformat("Content-Type: %s",
00309                                                                 body_injector->contentType()).c_str());
00310 
00311                 chain.push_back(LLIOPipe::ptr_t(body_injector));
00312         }
00313         chain.push_back(LLIOPipe::ptr_t(req));
00314 
00315         theClientPump->addChain(chain, timeout);
00316 }
00317 
00318 static void request(
00319         const std::string& url,
00320         LLURLRequest::ERequestAction method,
00321         Injector* body_injector,
00322         LLHTTPClient::ResponderPtr responder,
00323         const F32 timeout=HTTP_REQUEST_EXPIRY_SECS)
00324 {
00325     request(url, method, body_injector, responder, LLSD(), timeout);
00326 }
00327 
00328 void LLHTTPClient::get(const std::string& url, ResponderPtr responder, const LLSD& headers, const F32 timeout)
00329 {
00330         request(url, LLURLRequest::HTTP_GET, NULL, responder, headers, timeout);
00331 }
00332 
00333 void LLHTTPClient::get(const std::string& url, ResponderPtr responder, const F32 timeout)
00334 {
00335         get(url, responder, LLSD(), timeout);
00336 }
00337 
00338 void LLHTTPClient::get(const std::string& url, const LLSD& query, ResponderPtr responder, const LLSD& headers, const F32 timeout)
00339 {
00340         LLURI uri;
00341         
00342         uri = LLURI::buildHTTP(url, LLSD::emptyArray(), query);
00343         get(uri.asString(), responder, headers, timeout);
00344 }
00345 
00346 void LLHTTPClient::get(const std::string& url, const LLSD& query, ResponderPtr responder, const F32 timeout)
00347 {
00348         get(url, query, responder, LLSD(), timeout);
00349 }
00350 
00351 // A simple class for managing data returned from a curl http request.
00352 class LLHTTPBuffer
00353 {
00354 public:
00355         LLHTTPBuffer() { }
00356 
00357         static size_t curl_write( void *ptr, size_t size, size_t nmemb, void *user_data)
00358         {
00359                 LLHTTPBuffer* self = (LLHTTPBuffer*)user_data;
00360                 
00361                 size_t bytes = (size * nmemb);
00362                 self->mBuffer.append((char*)ptr,bytes);
00363                 return nmemb;
00364         }
00365 
00366         LLSD asLLSD()
00367         {
00368                 LLSD content;
00369 
00370                 if (mBuffer.empty()) return content;
00371                 
00372                 std::istringstream istr(mBuffer);
00373                 LLSDSerialize::fromXML(content, istr);
00374                 return content;
00375         }
00376 
00377         std::string asString()
00378         {
00379                 return mBuffer;
00380         }
00381 
00382 private:
00383         std::string mBuffer;
00384 };
00385 
00386 // This call is blocking! This is probably usually bad. :(
00387 LLSD LLHTTPClient::blockingGet(const std::string& url)
00388 {
00389         llinfos << "blockingGet of " << url << llendl;
00390 
00391         // Returns an LLSD map: {status: integer, body: map}
00392         char curl_error_buffer[CURL_ERROR_SIZE];
00393         CURL* curlp = curl_easy_init();
00394 
00395         LLHTTPBuffer http_buffer;
00396 
00397         // Without this timeout, blockingGet() calls have been observed to take
00398         // up to 90 seconds to complete.  Users of blockingGet() already must 
00399         // check the HTTP return code for validity, so this will not introduce
00400         // new errors.  A 5 second timeout will succeed > 95% of the time (and 
00401         // probably > 99% of the time) based on my statistics. JC
00402         curl_easy_setopt(curlp, CURLOPT_NOSIGNAL, 1);   // don't use SIGALRM for timeouts
00403         curl_easy_setopt(curlp, CURLOPT_TIMEOUT, 5);    // seconds
00404 
00405         curl_easy_setopt(curlp, CURLOPT_WRITEFUNCTION, LLHTTPBuffer::curl_write);
00406         curl_easy_setopt(curlp, CURLOPT_WRITEDATA, &http_buffer);
00407         curl_easy_setopt(curlp, CURLOPT_URL, url.c_str());
00408         curl_easy_setopt(curlp, CURLOPT_ERRORBUFFER, curl_error_buffer);
00409         curl_easy_setopt(curlp, CURLOPT_FAILONERROR, 1);
00410 
00411         LLSD response = LLSD::emptyMap();
00412 
00413         S32 curl_success = curl_easy_perform(curlp);
00414 
00415         S32 http_status = 499;
00416         curl_easy_getinfo(curlp,CURLINFO_RESPONSE_CODE, &http_status);
00417 
00418         response["status"] = http_status;
00419 
00420         if (curl_success != 0 
00421                 && http_status != 404)  // We expect 404s, don't spam for them.
00422         {
00423                 llwarns << "CURL ERROR: " << curl_error_buffer << llendl;
00424                 
00425                 response["body"] = http_buffer.asString();
00426         }
00427         else
00428         {
00429                 response["body"] = http_buffer.asLLSD();
00430         }
00431         
00432         curl_easy_cleanup(curlp);
00433 
00434         return response;
00435 }
00436 
00437 void LLHTTPClient::put(const std::string& url, const LLSD& body, ResponderPtr responder, const F32 timeout)
00438 {
00439         request(url, LLURLRequest::HTTP_PUT, new LLSDInjector(body), responder, timeout);
00440 }
00441 
00442 void LLHTTPClient::post(const std::string& url, const LLSD& body, ResponderPtr responder, const F32 timeout)
00443 {
00444         request(url, LLURLRequest::HTTP_POST, new LLSDInjector(body), responder, timeout);
00445 }
00446 
00447 void LLHTTPClient::post(const std::string& url, const U8* data, S32 size, ResponderPtr responder, const F32 timeout)
00448 {
00449         request(url, LLURLRequest::HTTP_POST, new RawInjector(data, size), responder, timeout);
00450 }
00451 
00452 void LLHTTPClient::del(const std::string& url, ResponderPtr responder, const F32 timeout)
00453 {
00454         request(url, LLURLRequest::HTTP_DELETE, NULL, responder, timeout);
00455 }
00456 
00457 #if 1
00458 void LLHTTPClient::postFile(const std::string& url, const std::string& filename, ResponderPtr responder, const F32 timeout)
00459 {
00460         request(url, LLURLRequest::HTTP_POST, new FileInjector(filename), responder, timeout);
00461 }
00462 
00463 void LLHTTPClient::postFile(const std::string& url, const LLUUID& uuid,
00464                                                         LLAssetType::EType asset_type, ResponderPtr responder, const F32 timeout)
00465 {
00466         request(url, LLURLRequest::HTTP_POST, new VFileInjector(uuid, asset_type), responder, timeout);
00467 }
00468 #endif
00469 
00470 void LLHTTPClient::setPump(LLPumpIO& pump)
00471 {
00472         theClientPump = &pump;
00473 }
00474 
00475 bool LLHTTPClient::hasPump()
00476 {
00477         return theClientPump != NULL;
00478 }
00479 
00480 void LLHTTPClient::setCABundle(const std::string& caBundle)
00481 {
00482         gCABundle = caBundle;
00483 }
00484 
00485 namespace boost
00486 {
00487         void intrusive_ptr_add_ref(LLHTTPClient::Responder* p)
00488         {
00489                 ++p->mReferenceCount;
00490         }
00491         
00492         void intrusive_ptr_release(LLHTTPClient::Responder* p)
00493         {
00494                 if(p && 0 == --p->mReferenceCount)
00495                 {
00496                         delete p;
00497                 }
00498         }
00499 };

Generated on Thu Jul 1 06:08:42 2010 for Second Life Viewer by  doxygen 1.4.7