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

Generated on Fri May 16 08:32:25 2008 for SecondLife by  doxygen 1.5.5