-
svn+ssh://svn/svn/linden/branches/dpo-3-bug-fixKartic Krishnamurthy authored
NOTE: r63705 is *not* the earliest rev# for dpo-3-bug-fix.
svn+ssh://svn/svn/linden/branches/dpo-3-bug-fixKartic Krishnamurthy authoredNOTE: r63705 is *not* the earliest rev# for dpo-3-bug-fix.
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
llhttpclient.cpp 11.33 KiB
/**
* @file llhttpclient.cpp
* @brief Implementation of classes for making HTTP requests.
*
* Copyright (c) 2006-$CurrentYear$, Linden Research, Inc.
* $License$
*/
#include "linden_common.h"
#include "llhttpclient.h"
#include "llassetstorage.h"
#include "lliopipe.h"
#include "llurlrequest.h"
#include "llbufferstream.h"
#include "llsdserialize.h"
#include "llvfile.h"
#include "llvfs.h"
#include "lluri.h"
#include "message.h"
#include <curl/curl.h>
const F32 HTTP_REQUEST_EXPIRY_SECS = 60.0f;
static std::string gCABundle;
LLHTTPClient::Responder::Responder()
: mReferenceCount(0)
{
}
LLHTTPClient::Responder::~Responder()
{
}
// virtual
void LLHTTPClient::Responder::error(U32 status, const std::string& reason)
{
llinfos << "LLHTTPClient::Responder::error "
<< status << ": " << reason << llendl;
}
// virtual
void LLHTTPClient::Responder::result(const LLSD& content)
{
}
// virtual
void LLHTTPClient::Responder::completedRaw(U32 status, const std::string& reason, const LLChannelDescriptors& channels,
const LLIOPipe::buffer_ptr_t& buffer)
{
LLBufferStream istr(channels, buffer.get());
LLSD content;
if (200 <= status && status < 300)
{
LLSDSerialize::fromXML(content, istr);
/*
const S32 parseError = -1;
if(LLSDSerialize::fromXML(content, istr) == parseError)
{
mStatus = 498;
mReason = "Client Parse Error";
}
*/
}
completed(status, reason, content);
}
// virtual
void LLHTTPClient::Responder::completed(U32 status, const std::string& reason, const LLSD& content)
{
if (200 <= status && status < 300)
{
result(content);
}
else
{
error(status, reason);
}
}
namespace
{
class LLHTTPClientURLAdaptor : public LLURLRequestComplete
{
public:
LLHTTPClientURLAdaptor(LLHTTPClient::ResponderPtr responder)
: mResponder(responder),
mStatus(499), mReason("LLURLRequest complete w/no status")
{
}
~LLHTTPClientURLAdaptor()
{
}
virtual void httpStatus(U32 status, const std::string& reason)
{
mStatus = status;
mReason = reason;
}
virtual void complete(const LLChannelDescriptors& channels,
const buffer_ptr_t& buffer)
{
if (mResponder.get())
{
mResponder->completedRaw(mStatus, mReason, channels, buffer);
}
}
private:
LLHTTPClient::ResponderPtr mResponder;
U32 mStatus;
std::string mReason;
};
class Injector : public LLIOPipe
{
public:
virtual const char* contentType() = 0;
};
class LLSDInjector : public Injector
{
public:
LLSDInjector(const LLSD& sd) : mSD(sd) {}
virtual ~LLSDInjector() {}
const char* contentType() { return "application/xml"; }
virtual EStatus process_impl(const LLChannelDescriptors& channels,
buffer_ptr_t& buffer, bool& eos, LLSD& context, LLPumpIO* pump)
{
LLBufferStream ostream(channels, buffer.get());
LLSDSerialize::toXML(mSD, ostream);
eos = true;
return STATUS_DONE;
}
const LLSD mSD;
};
class RawInjector : public Injector
{
public:
RawInjector(const U8* data, S32 size) : mData(data), mSize(size) {}
virtual ~RawInjector() {}
const char* contentType() { return "application/octet-stream"; }
virtual EStatus process_impl(const LLChannelDescriptors& channels,
buffer_ptr_t& buffer, bool& eos, LLSD& context, LLPumpIO* pump)
{
LLBufferStream ostream(channels, buffer.get());
ostream.write((const char *)mData, mSize); // hopefully chars are always U8s
eos = true;
return STATUS_DONE;
}
const U8* mData;
S32 mSize;
};
class FileInjector : public Injector
{
public:
FileInjector(const std::string& filename) : mFilename(filename) {}
virtual ~FileInjector() {}
const char* contentType() { return "application/octet-stream"; }
virtual EStatus process_impl(const LLChannelDescriptors& channels,
buffer_ptr_t& buffer, bool& eos, LLSD& context, LLPumpIO* pump)
{
LLBufferStream ostream(channels, buffer.get());
llifstream fstream(mFilename.c_str(), std::iostream::binary | std::iostream::out);
fstream.seekg(0, std::ios::end);
U32 fileSize = fstream.tellg();
fstream.seekg(0, std::ios::beg);
char* fileBuffer;
fileBuffer = new char [fileSize];
fstream.read(fileBuffer, fileSize);
ostream.write(fileBuffer, fileSize);
fstream.close();
eos = true;
return STATUS_DONE;
}
const std::string mFilename;
};
class VFileInjector : public Injector
{
public:
VFileInjector(const LLUUID& uuid, LLAssetType::EType asset_type) : mUUID(uuid), mAssetType(asset_type) {}
virtual ~VFileInjector() {}
const char* contentType() { return "application/octet-stream"; }
virtual EStatus process_impl(const LLChannelDescriptors& channels,
buffer_ptr_t& buffer, bool& eos, LLSD& context, LLPumpIO* pump)
{
LLBufferStream ostream(channels, buffer.get());
LLVFile vfile(gVFS, mUUID, mAssetType, LLVFile::READ);
S32 fileSize = vfile.getSize();
U8* fileBuffer;
fileBuffer = new U8 [fileSize];
vfile.read(fileBuffer, fileSize);
ostream.write((char*)fileBuffer, fileSize);
eos = true;
return STATUS_DONE;
}
const LLUUID mUUID;
LLAssetType::EType mAssetType;
};
LLPumpIO* theClientPump = NULL;
}
static void request(
const std::string& url,
LLURLRequest::ERequestAction method,
Injector* body_injector,
LLHTTPClient::ResponderPtr responder,
const LLSD& headers,
const F32 timeout=HTTP_REQUEST_EXPIRY_SECS)
{
if (!LLHTTPClient::hasPump())
{
responder->completed(U32_MAX, "No pump", LLSD());
return;
}
LLPumpIO::chain_t chain;
LLURLRequest *req = new LLURLRequest(method, url);
req->requestEncoding("");
if (headers.isMap())
{
LLSD::map_const_iterator iter = headers.beginMap();
LLSD::map_const_iterator end = headers.endMap();
for (; iter != end; ++iter)
{
std::ostringstream header;
header << iter->first << ": " << iter->second.asString() ;
req->addHeader(header.str().c_str());
}
}
if (!gCABundle.empty())
{
req->checkRootCertificate(true, gCABundle.c_str());
}
req->setCallback(new LLHTTPClientURLAdaptor(responder));
if (method == LLURLRequest::HTTP_POST && gMessageSystem)
{
req->addHeader(llformat("X-SecondLife-UDP-Listen-Port: %d",
gMessageSystem->mPort).c_str());
}
if (method == LLURLRequest::HTTP_PUT || method == LLURLRequest::HTTP_POST)
{
req->addHeader(llformat("Content-Type: %s",
body_injector->contentType()).c_str());
chain.push_back(LLIOPipe::ptr_t(body_injector));
}
chain.push_back(LLIOPipe::ptr_t(req));
theClientPump->addChain(chain, timeout);
}
static void request(
const std::string& url,
LLURLRequest::ERequestAction method,
Injector* body_injector,
LLHTTPClient::ResponderPtr responder,
const F32 timeout=HTTP_REQUEST_EXPIRY_SECS)
{
request(url, method, body_injector, responder, LLSD(), timeout);
}
void LLHTTPClient::get(const std::string& url, ResponderPtr responder, const LLSD& headers, const F32 timeout)
{
request(url, LLURLRequest::HTTP_GET, NULL, responder, headers, timeout);
}
void LLHTTPClient::get(const std::string& url, ResponderPtr responder, const F32 timeout)
{
get(url, responder, LLSD(), timeout);
}
void LLHTTPClient::get(const std::string& url, const LLSD& query, ResponderPtr responder, const LLSD& headers, const F32 timeout)
{
LLURI uri;
uri = LLURI::buildHTTP(url, LLSD::emptyArray(), query);
get(uri.asString(), responder, headers, timeout);
}
void LLHTTPClient::get(const std::string& url, const LLSD& query, ResponderPtr responder, const F32 timeout)
{
get(url, query, responder, LLSD(), timeout);
}
// A simple class for managing data returned from a curl http request.
class LLHTTPBuffer
{
public:
LLHTTPBuffer() { }
static size_t curl_write( void *ptr, size_t size, size_t nmemb, void *user_data)
{
LLHTTPBuffer* self = (LLHTTPBuffer*)user_data;
size_t bytes = (size * nmemb);
self->mBuffer.append((char*)ptr,bytes);
return nmemb;
}
LLSD asLLSD()
{
LLSD content;
if (mBuffer.empty()) return content;
std::istringstream istr(mBuffer);
LLSDSerialize::fromXML(content, istr);
return content;
}
std::string asString()
{
return mBuffer;
}
private:
std::string mBuffer;
};
// This call is blocking! This is probably usually bad. :(
LLSD LLHTTPClient::blockingGet(const std::string& url)
{
llinfos << "blockingGet of " << url << llendl;
// Returns an LLSD map: {status: integer, body: map}
char curl_error_buffer[CURL_ERROR_SIZE];
CURL* curlp = curl_easy_init();
LLHTTPBuffer http_buffer;
// Without this timeout, blockingGet() calls have been observed to take
// up to 90 seconds to complete. Users of blockingGet() already must
// check the HTTP return code for validity, so this will not introduce
// new errors. A 5 second timeout will succeed > 95% of the time (and
// probably > 99% of the time) based on my statistics. JC
curl_easy_setopt(curlp, CURLOPT_NOSIGNAL, 1); // don't use SIGALRM for timeouts
curl_easy_setopt(curlp, CURLOPT_TIMEOUT, 5); // seconds
curl_easy_setopt(curlp, CURLOPT_WRITEFUNCTION, LLHTTPBuffer::curl_write);
curl_easy_setopt(curlp, CURLOPT_WRITEDATA, &http_buffer);
curl_easy_setopt(curlp, CURLOPT_URL, url.c_str());
curl_easy_setopt(curlp, CURLOPT_ERRORBUFFER, curl_error_buffer);
curl_easy_setopt(curlp, CURLOPT_FAILONERROR, 1);
LLSD response = LLSD::emptyMap();
S32 curl_success = curl_easy_perform(curlp);
S32 http_status = 499;
curl_easy_getinfo(curlp,CURLINFO_RESPONSE_CODE, &http_status);
response["status"] = http_status;
if (curl_success != 0
&& http_status != 404) // We expect 404s, don't spam for them.
{
llwarns << "CURL ERROR: " << curl_error_buffer << llendl;
response["body"] = http_buffer.asString();
}
else
{
response["body"] = http_buffer.asLLSD();
}
curl_easy_cleanup(curlp);
return response;
}
void LLHTTPClient::put(const std::string& url, const LLSD& body, ResponderPtr responder, const F32 timeout)
{
request(url, LLURLRequest::HTTP_PUT, new LLSDInjector(body), responder, timeout);
}
void LLHTTPClient::post(const std::string& url, const LLSD& body, ResponderPtr responder, const F32 timeout)
{
request(url, LLURLRequest::HTTP_POST, new LLSDInjector(body), responder, timeout);
}
void LLHTTPClient::post(const std::string& url, const U8* data, S32 size, ResponderPtr responder, const F32 timeout)
{
request(url, LLURLRequest::HTTP_POST, new RawInjector(data, size), responder, timeout);
}
void LLHTTPClient::del(const std::string& url, ResponderPtr responder, const F32 timeout)
{
request(url, LLURLRequest::HTTP_DELETE, NULL, responder, timeout);
}
#if 1
void LLHTTPClient::postFile(const std::string& url, const std::string& filename, ResponderPtr responder, const F32 timeout)
{
request(url, LLURLRequest::HTTP_POST, new FileInjector(filename), responder, timeout);
}
void LLHTTPClient::postFile(const std::string& url, const LLUUID& uuid,
LLAssetType::EType asset_type, ResponderPtr responder, const F32 timeout)
{
request(url, LLURLRequest::HTTP_POST, new VFileInjector(uuid, asset_type), responder, timeout);
}
#endif
void LLHTTPClient::setPump(LLPumpIO& pump)
{
theClientPump = &pump;
}
bool LLHTTPClient::hasPump()
{
return theClientPump != NULL;
}
void LLHTTPClient::setCABundle(const std::string& caBundle)
{
gCABundle = caBundle;
}
namespace boost
{
void intrusive_ptr_add_ref(LLHTTPClient::Responder* p)
{
++p->mReferenceCount;
}
void intrusive_ptr_release(LLHTTPClient::Responder* p)
{
if(p && 0 == --p->mReferenceCount)
{
delete p;
}
}
};