Merge branch 'master' of git://github.com/AlericInglewood/SingularityViewer
Conflicts: indra/llmessage/llregionpresenceverifier.cpp indra/llmessage/llregionpresenceverifier.h
This commit is contained in:
@@ -4,20 +4,31 @@ project(llmessage)
|
||||
|
||||
include(00-Common)
|
||||
include(LLCommon)
|
||||
include(AIStateMachine)
|
||||
include(LLMath)
|
||||
include(LLMessage)
|
||||
include(LLVFS)
|
||||
include(LLXML)
|
||||
|
||||
include_directories (${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
include_directories(
|
||||
${LLCOMMON_INCLUDE_DIRS}
|
||||
${AISTATEMACHINE_INCLUDE_DIRS}
|
||||
${LLMATH_INCLUDE_DIRS}
|
||||
${LLMESSAGE_INCLUDE_DIRS}
|
||||
${LLVFS_INCLUDE_DIRS}
|
||||
${LLXML_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
set(llmessage_SOURCE_FILES
|
||||
aicurl.cpp
|
||||
aicurleasyrequeststatemachine.cpp
|
||||
aicurlthread.cpp
|
||||
aihttpheaders.cpp
|
||||
aihttptimeoutpolicy.cpp
|
||||
debug_libcurl.cpp
|
||||
llhttpclient.cpp
|
||||
llares.cpp
|
||||
llareslistener.cpp
|
||||
llassetstorage.cpp
|
||||
@@ -29,15 +40,10 @@ set(llmessage_SOURCE_FILES
|
||||
llchainio.cpp
|
||||
llcircuit.cpp
|
||||
llclassifiedflags.cpp
|
||||
aicurl.cpp
|
||||
debug_libcurl.cpp
|
||||
aicurlthread.cpp
|
||||
lldatapacker.cpp
|
||||
lldispatcher.cpp
|
||||
llfiltersd2xmlrpc.cpp
|
||||
llhost.cpp
|
||||
llhttpclient.cpp
|
||||
llhttpclientadapter.cpp
|
||||
llhttpnode.cpp
|
||||
llhttpsender.cpp
|
||||
llinstantmessage.cpp
|
||||
@@ -101,6 +107,13 @@ set(llmessage_SOURCE_FILES
|
||||
set(llmessage_HEADER_FILES
|
||||
CMakeLists.txt
|
||||
|
||||
aicurl.h
|
||||
aicurleasyrequeststatemachine.h
|
||||
aicurlprivate.h
|
||||
aicurlthread.h
|
||||
aihttpheaders.h
|
||||
aihttptimeoutpolicy.h
|
||||
debug_libcurl.h
|
||||
llares.h
|
||||
llareslistener.h
|
||||
llassetstorage.h
|
||||
@@ -114,10 +127,6 @@ set(llmessage_HEADER_FILES
|
||||
llcircuit.h
|
||||
llclassifiedflags.h
|
||||
llcurl.h
|
||||
aicurl.h
|
||||
debug_libcurl.h
|
||||
aicurlprivate.h
|
||||
aicurlthread.h
|
||||
lldatapacker.h
|
||||
lldbstrings.h
|
||||
lldispatcher.h
|
||||
@@ -126,8 +135,6 @@ set(llmessage_HEADER_FILES
|
||||
llfollowcamparams.h
|
||||
llhost.h
|
||||
llhttpclient.h
|
||||
llhttpclientinterface.h
|
||||
llhttpclientadapter.h
|
||||
llhttpnode.h
|
||||
llhttpnodeadapter.h
|
||||
llhttpsender.h
|
||||
@@ -221,7 +228,6 @@ if (LL_TESTS)
|
||||
include(Tut)
|
||||
|
||||
SET(llmessage_TEST_SOURCE_FILES
|
||||
# llhttpclientadapter.cpp
|
||||
llmime.cpp
|
||||
llnamevalue.cpp
|
||||
lltrustedmessageservice.cpp
|
||||
@@ -236,7 +242,8 @@ if (LL_TESTS)
|
||||
${LLVFS_LIBRARIES}
|
||||
${LLMATH_LIBRARIES}
|
||||
${LLCOMMON_LIBRARIES}
|
||||
${GOOGLEMOCK_LIBRARIES}
|
||||
${GOOGLEMOCK_LIBRARIES}
|
||||
${LLXML_LIBRARIES}
|
||||
)
|
||||
|
||||
LL_ADD_INTEGRATION_TEST(
|
||||
|
||||
@@ -55,9 +55,15 @@
|
||||
#include "lltimer.h" // ms_sleep
|
||||
#include "llproxy.h"
|
||||
#include "llhttpstatuscodes.h"
|
||||
#ifdef CWDEBUG
|
||||
#include <libcwd/buf2str.h>
|
||||
#endif
|
||||
#include "aihttpheaders.h"
|
||||
#include "aihttptimeoutpolicy.h"
|
||||
#include "aicurleasyrequeststatemachine.h"
|
||||
|
||||
//==================================================================================
|
||||
// Debug Settings
|
||||
//
|
||||
|
||||
bool gNoVerifySSLCert;
|
||||
|
||||
//==================================================================================
|
||||
// Local variables.
|
||||
@@ -247,7 +253,7 @@ void ssl_init(void)
|
||||
CRYPTO_set_dynlock_destroy_callback(&ssl_dyn_destroy_function);
|
||||
need_renegotiation_hack = (0x10001000UL <= ssleay);
|
||||
llinfos << "Successful initialization of " <<
|
||||
SSLeay_version(SSLEAY_VERSION) << " (0x" << std::hex << SSLeay() << ")." << llendl;
|
||||
SSLeay_version(SSLEAY_VERSION) << " (0x" << std::hex << SSLeay() << std::dec << ")." << llendl;
|
||||
}
|
||||
|
||||
// Cleanup OpenSSL library thread-safety.
|
||||
@@ -316,7 +322,7 @@ void initCurl(void (*flush_hook)())
|
||||
}
|
||||
|
||||
llinfos << "Successful initialization of libcurl " <<
|
||||
version_info->version << " (0x" << std::hex << version_info->version_num << "), (" <<
|
||||
version_info->version << " (0x" << std::hex << version_info->version_num << std::dec << "), (" <<
|
||||
version_info->ssl_version;
|
||||
if (version_info->libz_version)
|
||||
{
|
||||
@@ -416,123 +422,20 @@ void setCAPath(std::string const& path)
|
||||
CertificateAuthority_w->path = path;
|
||||
}
|
||||
|
||||
// THREAD-SAFE
|
||||
std::string strerror(CURLcode errorcode)
|
||||
{
|
||||
// libcurl is thread safe, no locking needed.
|
||||
return curl_easy_strerror(errorcode);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// class Responder
|
||||
//
|
||||
|
||||
Responder::Responder(void) : mReferenceCount(0)
|
||||
{
|
||||
DoutEntering(dc::curl, "AICurlInterface::Responder() with this = " << (void*)this);
|
||||
}
|
||||
|
||||
Responder::~Responder()
|
||||
{
|
||||
DoutEntering(dc::curl, "AICurlInterface::Responder::~Responder() with this = " << (void*)this << "; mReferenceCount = " << mReferenceCount);
|
||||
llassert(mReferenceCount == 0);
|
||||
}
|
||||
|
||||
void Responder::setURL(std::string const& url)
|
||||
{
|
||||
// setURL is called from llhttpclient.cpp (request()), before calling any of the below (of course).
|
||||
// We don't need locking here therefore; it's a case of initializing before use.
|
||||
mURL = url;
|
||||
}
|
||||
|
||||
// Called with HTML header.
|
||||
// virtual
|
||||
void Responder::completedHeader(U32, std::string const&, LLSD const&)
|
||||
{
|
||||
// Nothing.
|
||||
}
|
||||
|
||||
// Called with HTML body.
|
||||
// virtual
|
||||
void Responder::completedRaw(U32 status, std::string const& reason, LLChannelDescriptors const& channels, LLIOPipe::buffer_ptr_t const& buffer)
|
||||
{
|
||||
LLSD content;
|
||||
LLBufferStream istr(channels, buffer.get());
|
||||
if (!LLSDSerialize::fromXML(content, istr))
|
||||
{
|
||||
llinfos << "Failed to deserialize LLSD. " << mURL << " [" << status << "]: " << reason << llendl;
|
||||
}
|
||||
|
||||
// Allow derived class to override at this point.
|
||||
completed(status, reason, content);
|
||||
}
|
||||
|
||||
void Responder::fatalError(std::string const& reason)
|
||||
{
|
||||
llwarns << "Responder::fatalError(\"" << reason << "\") is called (" << mURL << "). Passing it to Responder::completed with fake HTML error status and empty HTML body!" << llendl;
|
||||
completed(U32_MAX, reason, LLSD());
|
||||
}
|
||||
|
||||
// virtual
|
||||
void Responder::completed(U32 status, std::string const& reason, LLSD const& content)
|
||||
{
|
||||
// HTML status good?
|
||||
if (200 <= status && status < 300)
|
||||
{
|
||||
// Allow derived class to override at this point.
|
||||
result(content);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Allow derived class to override at this point.
|
||||
errorWithContent(status, reason, content);
|
||||
}
|
||||
}
|
||||
|
||||
// virtual
|
||||
void Responder::errorWithContent(U32 status, std::string const& reason, LLSD const&)
|
||||
{
|
||||
// Allow derived class to override at this point.
|
||||
error(status, reason);
|
||||
}
|
||||
|
||||
// virtual
|
||||
void Responder::error(U32 status, std::string const& reason)
|
||||
{
|
||||
llinfos << mURL << " [" << status << "]: " << reason << llendl;
|
||||
}
|
||||
|
||||
// virtual
|
||||
void Responder::result(LLSD const&)
|
||||
{
|
||||
// Nothing.
|
||||
}
|
||||
|
||||
// Friend functions.
|
||||
|
||||
void intrusive_ptr_add_ref(Responder* responder)
|
||||
{
|
||||
responder->mReferenceCount++;
|
||||
}
|
||||
|
||||
void intrusive_ptr_release(Responder* responder)
|
||||
{
|
||||
if (--responder->mReferenceCount == 0)
|
||||
{
|
||||
delete responder;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace AICurlInterface
|
||||
//==================================================================================
|
||||
|
||||
|
||||
//==================================================================================
|
||||
// Local implementation.
|
||||
//
|
||||
|
||||
namespace AICurlPrivate {
|
||||
|
||||
#if defined(CWDEBUG) || defined(DEBUG_CURLIO)
|
||||
// CURLOPT_DEBUGFUNCTION function.
|
||||
extern int debug_callback(CURL*, curl_infotype infotype, char* buf, size_t size, void* user_ptr);
|
||||
#endif
|
||||
|
||||
//static
|
||||
LLAtomicU32 Stats::easy_calls;
|
||||
LLAtomicU32 Stats::easy_errors;
|
||||
@@ -640,7 +543,7 @@ char* CurlEasyHandle::getTLErrorBuffer(void)
|
||||
return tldata.mCurlErrorBuffer;
|
||||
}
|
||||
|
||||
void CurlEasyHandle::setErrorBuffer(void)
|
||||
void CurlEasyHandle::setErrorBuffer(void) const
|
||||
{
|
||||
char* error_buffer = getTLErrorBuffer();
|
||||
if (mErrorBuffer != error_buffer)
|
||||
@@ -653,9 +556,13 @@ void CurlEasyHandle::setErrorBuffer(void)
|
||||
mErrorBuffer = NULL;
|
||||
}
|
||||
}
|
||||
if (mErrorBuffer)
|
||||
{
|
||||
mErrorBuffer[0] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
CURLcode CurlEasyHandle::getinfo_priv(CURLINFO info, void* data)
|
||||
CURLcode CurlEasyHandle::getinfo_priv(CURLINFO info, void* data) const
|
||||
{
|
||||
setErrorBuffer();
|
||||
return check_easy_code(curl_easy_getinfo(mEasyHandle, info, data));
|
||||
@@ -703,12 +610,12 @@ CURLMcode CurlEasyHandle::remove_handle_from_multi(AICurlEasyRequest_wat& curl_e
|
||||
return res;
|
||||
}
|
||||
|
||||
void intrusive_ptr_add_ref(ThreadSafeCurlEasyRequest* threadsafe_curl_easy_request)
|
||||
void intrusive_ptr_add_ref(ThreadSafeBufferedCurlEasyRequest* threadsafe_curl_easy_request)
|
||||
{
|
||||
threadsafe_curl_easy_request->mReferenceCount++;
|
||||
}
|
||||
|
||||
void intrusive_ptr_release(ThreadSafeCurlEasyRequest* threadsafe_curl_easy_request)
|
||||
void intrusive_ptr_release(ThreadSafeBufferedCurlEasyRequest* threadsafe_curl_easy_request)
|
||||
{
|
||||
if (--threadsafe_curl_easy_request->mReferenceCount == 0)
|
||||
{
|
||||
@@ -795,22 +702,22 @@ void CurlEasyRequest::setoptString(CURLoption option, std::string const& value)
|
||||
setopt(option, value.c_str());
|
||||
}
|
||||
|
||||
void CurlEasyRequest::setPost(AIPostFieldPtr const& postdata, S32 size)
|
||||
void CurlEasyRequest::setPost(AIPostFieldPtr const& postdata, U32 size)
|
||||
{
|
||||
llassert_always(postdata->data());
|
||||
|
||||
Dout(dc::curl, "POST size is " << size << " bytes: \"" << libcwd::buf2str(postdata->data(), size) << "\".");
|
||||
DoutCurl("POST size is " << size << " bytes: \"" << libcwd::buf2str(postdata->data(), size) << "\".");
|
||||
setPostField(postdata); // Make sure the data stays around until we don't need it anymore.
|
||||
|
||||
setPost_raw(size, postdata->data());
|
||||
}
|
||||
|
||||
void CurlEasyRequest::setPost_raw(S32 size, char const* data)
|
||||
void CurlEasyRequest::setPost_raw(U32 size, char const* data)
|
||||
{
|
||||
if (!data)
|
||||
{
|
||||
// data == NULL when we're going to read the data using CURLOPT_READFUNCTION.
|
||||
Dout(dc::curl, "POST size is " << size << " bytes.");
|
||||
DoutCurl("POST size is " << size << " bytes.");
|
||||
}
|
||||
|
||||
// The server never replies with 100-continue, so suppress the "Expect: 100-continue" header that libcurl adds by default.
|
||||
@@ -821,19 +728,14 @@ void CurlEasyRequest::setPost_raw(S32 size, char const* data)
|
||||
addHeader("Keep-alive: 300");
|
||||
}
|
||||
setopt(CURLOPT_POSTFIELDSIZE, size);
|
||||
setopt(CURLOPT_POSTFIELDS, data);
|
||||
}
|
||||
|
||||
ThreadSafeCurlEasyRequest* CurlEasyRequest::get_lockobj(void)
|
||||
{
|
||||
return static_cast<ThreadSafeCurlEasyRequest*>(AIThreadSafeSimpleDC<CurlEasyRequest>::wrapper_cast(this));
|
||||
setopt(CURLOPT_POSTFIELDS, data); // Implies CURLOPT_POST
|
||||
}
|
||||
|
||||
//static
|
||||
size_t CurlEasyRequest::headerCallback(char* ptr, size_t size, size_t nmemb, void* userdata)
|
||||
{
|
||||
CurlEasyRequest* self = static_cast<CurlEasyRequest*>(userdata);
|
||||
ThreadSafeCurlEasyRequest* lockobj = self->get_lockobj();
|
||||
ThreadSafeBufferedCurlEasyRequest* lockobj = self->get_lockobj();
|
||||
AICurlEasyRequest_wat lock_self(*lockobj);
|
||||
return self->mHeaderCallback(ptr, size, nmemb, self->mHeaderCallbackUserData);
|
||||
}
|
||||
@@ -850,7 +752,7 @@ void CurlEasyRequest::setHeaderCallback(curl_write_callback callback, void* user
|
||||
size_t CurlEasyRequest::writeCallback(char* ptr, size_t size, size_t nmemb, void* userdata)
|
||||
{
|
||||
CurlEasyRequest* self = static_cast<CurlEasyRequest*>(userdata);
|
||||
ThreadSafeCurlEasyRequest* lockobj = self->get_lockobj();
|
||||
ThreadSafeBufferedCurlEasyRequest* lockobj = self->get_lockobj();
|
||||
AICurlEasyRequest_wat lock_self(*lockobj);
|
||||
return self->mWriteCallback(ptr, size, nmemb, self->mWriteCallbackUserData);
|
||||
}
|
||||
@@ -867,7 +769,7 @@ void CurlEasyRequest::setWriteCallback(curl_write_callback callback, void* userd
|
||||
size_t CurlEasyRequest::readCallback(char* ptr, size_t size, size_t nmemb, void* userdata)
|
||||
{
|
||||
CurlEasyRequest* self = static_cast<CurlEasyRequest*>(userdata);
|
||||
ThreadSafeCurlEasyRequest* lockobj = self->get_lockobj();
|
||||
ThreadSafeBufferedCurlEasyRequest* lockobj = self->get_lockobj();
|
||||
AICurlEasyRequest_wat lock_self(*lockobj);
|
||||
return self->mReadCallback(ptr, size, nmemb, self->mReadCallbackUserData);
|
||||
}
|
||||
@@ -884,7 +786,7 @@ void CurlEasyRequest::setReadCallback(curl_read_callback callback, void* userdat
|
||||
CURLcode CurlEasyRequest::SSLCtxCallback(CURL* curl, void* sslctx, void* userdata)
|
||||
{
|
||||
CurlEasyRequest* self = static_cast<CurlEasyRequest*>(userdata);
|
||||
ThreadSafeCurlEasyRequest* lockobj = self->get_lockobj();
|
||||
ThreadSafeBufferedCurlEasyRequest* lockobj = self->get_lockobj();
|
||||
AICurlEasyRequest_wat lock_self(*lockobj);
|
||||
return self->mSSLCtxCallback(curl, sslctx, self->mSSLCtxCallbackUserData);
|
||||
}
|
||||
@@ -952,7 +854,7 @@ CurlEasyRequest::~CurlEasyRequest()
|
||||
// If the CurlEasyRequest object is destructed then we need to revoke all callbacks, because
|
||||
// we can't set the lock anymore, and neither will mHeaderCallback, mWriteCallback etc,
|
||||
// be available anymore.
|
||||
send_events_to(NULL);
|
||||
send_handle_events_to(NULL);
|
||||
revokeCallbacks();
|
||||
// This wasn't freed yet if the request never finished.
|
||||
curl_slist_free_all(mHeaders);
|
||||
@@ -965,124 +867,24 @@ void CurlEasyRequest::resetState(void)
|
||||
reset();
|
||||
curl_slist_free_all(mHeaders);
|
||||
mHeaders = NULL;
|
||||
mRequestFinalized = false;
|
||||
mEventsTarget = NULL;
|
||||
mTimeoutPolicy = NULL;
|
||||
mTimeout = NULL;
|
||||
mHandleEventsTarget = NULL;
|
||||
mResult = CURLE_FAILED_INIT;
|
||||
applyDefaultOptions();
|
||||
}
|
||||
|
||||
void CurlEasyRequest::addHeader(char const* header)
|
||||
{
|
||||
llassert(!mRequestFinalized);
|
||||
llassert(!mTimeoutPolicy); // Cannot add a header after calling finalizeRequest.
|
||||
mHeaders = curl_slist_append(mHeaders, header);
|
||||
}
|
||||
|
||||
#if defined(CWDEBUG) || defined(DEBUG_CURLIO)
|
||||
|
||||
static int curl_debug_cb(CURL*, curl_infotype infotype, char* buf, size_t size, void* user_ptr)
|
||||
void CurlEasyRequest::addHeaders(AIHTTPHeaders const& headers)
|
||||
{
|
||||
#ifdef CWDEBUG
|
||||
using namespace ::libcwd;
|
||||
|
||||
CurlEasyRequest* request = (CurlEasyRequest*)user_ptr;
|
||||
std::ostringstream marker;
|
||||
marker << (void*)request->get_lockobj();
|
||||
libcw_do.push_marker();
|
||||
libcw_do.marker().assign(marker.str().data(), marker.str().size());
|
||||
if (!debug::channels::dc::curlio.is_on())
|
||||
debug::channels::dc::curlio.on();
|
||||
LibcwDoutScopeBegin(LIBCWD_DEBUGCHANNELS, libcw_do, dc::curlio|cond_nonewline_cf(infotype == CURLINFO_TEXT))
|
||||
#else
|
||||
if (infotype == CURLINFO_TEXT)
|
||||
{
|
||||
while (size > 0 && (buf[size - 1] == '\r' || buf[size - 1] == '\n'))
|
||||
--size;
|
||||
}
|
||||
LibcwDoutScopeBegin(LIBCWD_DEBUGCHANNELS, libcw_do, dc::curlio)
|
||||
#endif
|
||||
switch (infotype)
|
||||
{
|
||||
case CURLINFO_TEXT:
|
||||
LibcwDoutStream << "* ";
|
||||
break;
|
||||
case CURLINFO_HEADER_IN:
|
||||
LibcwDoutStream << "H> ";
|
||||
break;
|
||||
case CURLINFO_HEADER_OUT:
|
||||
LibcwDoutStream << "H< ";
|
||||
break;
|
||||
case CURLINFO_DATA_IN:
|
||||
LibcwDoutStream << "D> ";
|
||||
break;
|
||||
case CURLINFO_DATA_OUT:
|
||||
LibcwDoutStream << "D< ";
|
||||
break;
|
||||
case CURLINFO_SSL_DATA_IN:
|
||||
LibcwDoutStream << "S> ";
|
||||
break;
|
||||
case CURLINFO_SSL_DATA_OUT:
|
||||
LibcwDoutStream << "S< ";
|
||||
break;
|
||||
default:
|
||||
LibcwDoutStream << "?? ";
|
||||
}
|
||||
if (infotype == CURLINFO_TEXT)
|
||||
LibcwDoutStream.write(buf, size);
|
||||
else if (infotype == CURLINFO_HEADER_IN || infotype == CURLINFO_HEADER_OUT)
|
||||
LibcwDoutStream << libcwd::buf2str(buf, size);
|
||||
else if (infotype == CURLINFO_DATA_IN)
|
||||
{
|
||||
LibcwDoutStream << size << " bytes";
|
||||
bool finished = false;
|
||||
size_t i = 0;
|
||||
while (i < size)
|
||||
{
|
||||
char c = buf[i];
|
||||
if (!('0' <= c && c <= '9') && !('a' <= c && c <= 'f'))
|
||||
{
|
||||
if (0 < i && i + 1 < size && buf[i] == '\r' && buf[i + 1] == '\n')
|
||||
{
|
||||
// Binary output: "[0-9a-f]*\r\n ...binary data..."
|
||||
LibcwDoutStream << ": \"" << libcwd::buf2str(buf, i + 2) << "\"...";
|
||||
finished = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
if (!finished && size > 9 && buf[0] == '<')
|
||||
{
|
||||
// Human readable output: html, xml or llsd.
|
||||
if (!strncmp(buf, "<!DOCTYPE", 9) || !strncmp(buf, "<?xml", 5) || !strncmp(buf, "<llsd>", 6))
|
||||
{
|
||||
LibcwDoutStream << ": \"" << libcwd::buf2str(buf, size) << '"';
|
||||
finished = true;
|
||||
}
|
||||
}
|
||||
if (!finished)
|
||||
{
|
||||
// Unknown format. Only print the first and last 20 characters.
|
||||
if (size > 40UL)
|
||||
{
|
||||
LibcwDoutStream << ": \"" << libcwd::buf2str(buf, 20) << "\"...\"" << libcwd::buf2str(&buf[size - 20], 20) << '"';
|
||||
}
|
||||
else
|
||||
{
|
||||
LibcwDoutStream << ": \"" << libcwd::buf2str(buf, size) << '"';
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (infotype == CURLINFO_DATA_OUT)
|
||||
LibcwDoutStream << size << " bytes: \"" << libcwd::buf2str(buf, size) << '"';
|
||||
else
|
||||
LibcwDoutStream << size << " bytes";
|
||||
LibcwDoutScopeEnd;
|
||||
#ifdef CWDEBUG
|
||||
libcw_do.pop_marker();
|
||||
#endif
|
||||
return 0;
|
||||
llassert(!mTimeoutPolicy); // Cannot add headers after calling finalizeRequest.
|
||||
headers.append_to(mHeaders);
|
||||
}
|
||||
#endif
|
||||
|
||||
void CurlEasyRequest::applyProxySettings(void)
|
||||
{
|
||||
@@ -1120,6 +922,7 @@ void CurlEasyRequest::applyProxySettings(void)
|
||||
//static
|
||||
CURLcode CurlEasyRequest::curlCtxCallback(CURL* curl, void* sslctx, void* parm)
|
||||
{
|
||||
DoutEntering(dc::curl, "CurlEasyRequest::curlCtxCallback((CURL*)" << (void*)curl << ", " << sslctx << ", " << parm << ")");
|
||||
SSL_CTX* ctx = (SSL_CTX*)sslctx;
|
||||
// Turn off TLS v1.1 (which is not supported anyway by Linden Lab) because otherwise we fail to connect.
|
||||
// Also turn off SSL v2, which is highly broken and strongly discouraged[1].
|
||||
@@ -1152,8 +955,20 @@ void CurlEasyRequest::applyDefaultOptions(void)
|
||||
setoptString(CURLOPT_CAINFO, CertificateAuthority_r->file);
|
||||
setSSLCtxCallback(&curlCtxCallback, NULL);
|
||||
setopt(CURLOPT_NOSIGNAL, 1);
|
||||
// The old code did this for the 'buffered' version, but I think it's nonsense.
|
||||
//setopt(CURLOPT_DNS_CACHE_TIMEOUT, 0);
|
||||
// Cache DNS look ups an hour. If we set it smaller we risk frequent connect timeouts in cases where DNS look ups are slow.
|
||||
setopt(CURLOPT_DNS_CACHE_TIMEOUT, 3600);
|
||||
// Only resolve to IPV4.
|
||||
// Rationale: if a host resolves to both, ipv4 and ipv6, then this stops libcurl from
|
||||
// using the ipv6 address. If we don't do that then libcurl first attempts to connect
|
||||
// to the ipv4 IP number (using only HALF the connect timeout we passed to it!) and if
|
||||
// that fails try the ipv6 IP number, which then most likely fails with "network unreachable".
|
||||
// Then libcurl immediately returns with just the ipv6 error as result masking the real problem.
|
||||
// Since the viewer doesn't support IPv6 at least for UDP services, and there are no
|
||||
// transition plans to IPv6 anywhere at this moment, the easiest way to get rid of this
|
||||
// problem is by simply not falling back to ipv6.
|
||||
setopt(CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
|
||||
// Disable SSL/TLS session caching; some servers (aka id.secondlife.com) refuse connections when session ids are enabled.
|
||||
setopt(CURLOPT_SSL_SESSIONID_CACHE, 0);
|
||||
// Set the CURL options for either SOCKS or HTTP proxy.
|
||||
applyProxySettings();
|
||||
// Cause libcurl to print all it's I/O traffic on the debug channel.
|
||||
@@ -1161,17 +976,67 @@ void CurlEasyRequest::applyDefaultOptions(void)
|
||||
if (dc::curlio.is_on())
|
||||
{
|
||||
setopt(CURLOPT_VERBOSE, 1);
|
||||
setopt(CURLOPT_DEBUGFUNCTION, &curl_debug_cb);
|
||||
setopt(CURLOPT_DEBUGFUNCTION, &debug_callback);
|
||||
setopt(CURLOPT_DEBUGDATA, this);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void CurlEasyRequest::finalizeRequest(std::string const& url)
|
||||
// url must be of the form
|
||||
// (see http://www.ietf.org/rfc/rfc3986.txt Appendix A for definitions not given here):
|
||||
//
|
||||
// url = sheme ":" hier-part [ "?" query ] [ "#" fragment ]
|
||||
// hier-part = "//" authority path-abempty
|
||||
// authority = [ userinfo "@" ] host [ ":" port ]
|
||||
// path-abempty = *( "/" segment )
|
||||
//
|
||||
// That is, a hier-part of the form '/ path-absolute', '/ path-rootless' or
|
||||
// '/ path-empty' is NOT allowed here. This should be safe because we only
|
||||
// call this function for curl access, any file access would use APR.
|
||||
//
|
||||
// However, as a special exception, this function allows:
|
||||
//
|
||||
// url = authority path-abempty
|
||||
//
|
||||
// without the 'sheme ":" "//"' parts.
|
||||
//
|
||||
// As follows from the ABNF (see RFC, Appendix A):
|
||||
// - authority is either terminated by a '/' or by the end of the string because
|
||||
// neither userinfo, host nor port may contain a '/'.
|
||||
// - userinfo does not contain a '@', and if it exists, is always terminated by a '@'.
|
||||
// - port does not contain a ':', and if it exists is always prepended by a ':'.
|
||||
//
|
||||
// Only called by CurlEasyRequest::finalizeRequest.
|
||||
static std::string extract_canonical_hostname(std::string const& url)
|
||||
{
|
||||
llassert(!mRequestFinalized);
|
||||
mResult = CURLE_FAILED_INIT; // General error code, the final code is written here in MultiHandle::check_run_count when msg is CURLMSG_DONE.
|
||||
lldebugs << url << llendl;
|
||||
std::string::size_type pos;
|
||||
std::string::size_type authority = 0; // Default if there is no sheme.
|
||||
if ((pos = url.find("://")) != url.npos && pos < url.find('/')) authority = pos + 3; // Skip the "sheme://" if any, the second find is to avoid finding a "://" as part of path-abempty.
|
||||
std::string::size_type host = authority; // Default if there is no userinfo.
|
||||
if ((pos = url.find('@', authority)) != url.npos) host = pos + 1; // Skip the "userinfo@" if any.
|
||||
authority = url.length() - 1; // Default last character of host if there is no path-abempty.
|
||||
if ((pos = url.find('/', host)) != url.npos) authority = pos - 1; // Point to last character of host.
|
||||
std::string::size_type len = url.find_last_not_of(":0123456789", authority) - host + 1; // Skip trailing ":port", if any.
|
||||
std::string hostname(url, host, len);
|
||||
#if APR_CHARSET_EBCDIC
|
||||
#error Not implemented
|
||||
#else
|
||||
// Convert hostname to lowercase in a way that we compare two hostnames equal iff libcurl does.
|
||||
for (std::string::iterator iter = hostname.begin(); iter != hostname.end(); ++iter)
|
||||
{
|
||||
int c = *iter;
|
||||
if (c >= 'A' && c <= 'Z')
|
||||
*iter = c + ('a' - 'A');
|
||||
}
|
||||
#endif
|
||||
return hostname;
|
||||
}
|
||||
|
||||
void CurlEasyRequest::finalizeRequest(std::string const& url, AIHTTPTimeoutPolicy const& policy, AICurlEasyRequestStateMachine* state_machine)
|
||||
{
|
||||
DoutCurlEntering("CurlEasyRequest::finalizeRequest(\"" << url << "\", " << policy.name() << ", " << (void*)state_machine << ")");
|
||||
llassert(!mTimeoutPolicy); // May only call finalizeRequest once!
|
||||
mResult = CURLE_FAILED_INIT; // General error code; the final result code is stored here by MultiHandle::check_run_count when msg is CURLMSG_DONE.
|
||||
#ifdef SHOW_ASSERT
|
||||
// Do a sanity check on the headers.
|
||||
int content_type_count = 0;
|
||||
@@ -1184,12 +1049,14 @@ void CurlEasyRequest::finalizeRequest(std::string const& url)
|
||||
}
|
||||
if (content_type_count > 1)
|
||||
{
|
||||
llwarns << content_type_count << " Content-Type: headers!" << llendl;
|
||||
llwarns << "Requesting: \"" << url << "\": " << content_type_count << " Content-Type: headers!" << llendl;
|
||||
}
|
||||
#endif
|
||||
mRequestFinalized = true;
|
||||
setopt(CURLOPT_HTTPHEADER, mHeaders);
|
||||
setoptString(CURLOPT_URL, url);
|
||||
mLowercaseHostname = extract_canonical_hostname(url);
|
||||
mTimeoutPolicy = &policy;
|
||||
state_machine->setTotalDelayTimeout(policy.getTotalDelay());
|
||||
// The following line is a bit tricky: we store a pointer to the object without increasing its reference count.
|
||||
// Of course we could increment the reference count, but that doesn't help much: if then this pointer would
|
||||
// get "lost" we'd have a memory leak. Either way we must make sure that it is impossible that this pointer
|
||||
@@ -1207,7 +1074,51 @@ void CurlEasyRequest::finalizeRequest(std::string const& url)
|
||||
setopt(CURLOPT_PRIVATE, get_lockobj());
|
||||
}
|
||||
|
||||
void CurlEasyRequest::getTransferInfo(AICurlInterface::TransferInfo* info)
|
||||
// AIFIXME: Doing this only when it is actually being added assures that the first curl easy handle that is
|
||||
// // being added for a particular host will be the one getting extra 'DNS lookup' connect time.
|
||||
// // However, if another curl easy handle for the same host is added immediately after, it will
|
||||
// // get less connect time, while it still (also) has to wait for this DNS lookup.
|
||||
void CurlEasyRequest::set_timeout_opts(void)
|
||||
{
|
||||
setopt(CURLOPT_CONNECTTIMEOUT, mTimeoutPolicy->getConnectTimeout(mLowercaseHostname));
|
||||
setopt(CURLOPT_TIMEOUT, mTimeoutPolicy->getCurlTransaction());
|
||||
}
|
||||
|
||||
void CurlEasyRequest::create_timeout_object(ThreadSafeBufferedCurlEasyRequest* lockobj)
|
||||
{
|
||||
mTimeout = new curlthread::HTTPTimeout(mTimeoutPolicy, lockobj);
|
||||
}
|
||||
|
||||
LLPointer<curlthread::HTTPTimeout>& CurlEasyRequest::get_timeout_object(ThreadSafeBufferedCurlEasyRequest* lockobj)
|
||||
{
|
||||
if (mTimeoutIsOrphan)
|
||||
{
|
||||
mTimeoutIsOrphan = false;
|
||||
llassert_always(mTimeout);
|
||||
}
|
||||
else
|
||||
{
|
||||
create_timeout_object(lockobj);
|
||||
}
|
||||
return mTimeout;
|
||||
}
|
||||
|
||||
void CurlEasyRequest::print_curl_timings(void) const
|
||||
{
|
||||
double t;
|
||||
getinfo(CURLINFO_NAMELOOKUP_TIME, &t);
|
||||
DoutCurl("CURLINFO_NAMELOOKUP_TIME = " << t);
|
||||
getinfo(CURLINFO_CONNECT_TIME, &t);
|
||||
DoutCurl("CURLINFO_CONNECT_TIME = " << t);
|
||||
getinfo(CURLINFO_APPCONNECT_TIME, &t);
|
||||
DoutCurl("CURLINFO_APPCONNECT_TIME = " << t);
|
||||
getinfo(CURLINFO_PRETRANSFER_TIME, &t);
|
||||
DoutCurl("CURLINFO_PRETRANSFER_TIME = " << t);
|
||||
getinfo(CURLINFO_STARTTRANSFER_TIME, &t);
|
||||
DoutCurl("CURLINFO_STARTTRANSFER_TIME = " << t);
|
||||
}
|
||||
|
||||
void CurlEasyRequest::getTransferInfo(AITransferInfo* info)
|
||||
{
|
||||
// Curl explicitly demands a double for these info's.
|
||||
double size, total_time, speed;
|
||||
@@ -1220,7 +1131,7 @@ void CurlEasyRequest::getTransferInfo(AICurlInterface::TransferInfo* info)
|
||||
info->mSpeedDownload = speed;
|
||||
}
|
||||
|
||||
void CurlEasyRequest::getResult(CURLcode* result, AICurlInterface::TransferInfo* info)
|
||||
void CurlEasyRequest::getResult(CURLcode* result, AITransferInfo* info)
|
||||
{
|
||||
*result = mResult;
|
||||
if (info && mResult != CURLE_FAILED_INIT)
|
||||
@@ -1231,60 +1142,63 @@ void CurlEasyRequest::getResult(CURLcode* result, AICurlInterface::TransferInfo*
|
||||
|
||||
void CurlEasyRequest::added_to_multi_handle(AICurlEasyRequest_wat& curl_easy_request_w)
|
||||
{
|
||||
if (mEventsTarget)
|
||||
mEventsTarget->added_to_multi_handle(curl_easy_request_w);
|
||||
if (mHandleEventsTarget)
|
||||
mHandleEventsTarget->added_to_multi_handle(curl_easy_request_w);
|
||||
}
|
||||
|
||||
void CurlEasyRequest::finished(AICurlEasyRequest_wat& curl_easy_request_w)
|
||||
{
|
||||
if (mEventsTarget)
|
||||
mEventsTarget->finished(curl_easy_request_w);
|
||||
if (mHandleEventsTarget)
|
||||
mHandleEventsTarget->finished(curl_easy_request_w);
|
||||
}
|
||||
|
||||
void CurlEasyRequest::removed_from_multi_handle(AICurlEasyRequest_wat& curl_easy_request_w)
|
||||
{
|
||||
if (mEventsTarget)
|
||||
mEventsTarget->removed_from_multi_handle(curl_easy_request_w);
|
||||
if (mHandleEventsTarget)
|
||||
mHandleEventsTarget->removed_from_multi_handle(curl_easy_request_w);
|
||||
}
|
||||
|
||||
void CurlEasyRequest::print_diagnostics(CURLcode code)
|
||||
{
|
||||
if (code == CURLE_OPERATION_TIMEDOUT)
|
||||
{
|
||||
// mTimeout SHOULD always be set, but I see no reason not to test it, as
|
||||
// this is far from the code that guaranteeds that it is set.
|
||||
if (mTimeout)
|
||||
{
|
||||
mTimeout->print_diagnostics(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// CurlResponderBuffer
|
||||
// BufferedCurlEasyRequest
|
||||
|
||||
static unsigned int const MAX_REDIRECTS = 5;
|
||||
static S32 const CURL_REQUEST_TIMEOUT = 30; // Seconds per operation.
|
||||
static int const HTTP_REDIRECTS_DEFAULT = 10;
|
||||
|
||||
LLChannelDescriptors const CurlResponderBuffer::sChannels;
|
||||
LLChannelDescriptors const BufferedCurlEasyRequest::sChannels;
|
||||
|
||||
CurlResponderBuffer::CurlResponderBuffer()
|
||||
BufferedCurlEasyRequest::BufferedCurlEasyRequest() : mRequestTransferedBytes(0), mResponseTransferedBytes(0), mBufferEventsTarget(NULL)
|
||||
{
|
||||
ThreadSafeBufferedCurlEasyRequest* lockobj = get_lockobj();
|
||||
AICurlEasyRequest_wat curl_easy_request_w(*lockobj);
|
||||
curl_easy_request_w->send_events_to(this);
|
||||
}
|
||||
|
||||
#define llmaybeerrs lllog(LLApp::isRunning() ? LLError::LEVEL_ERROR : LLError::LEVEL_WARN, NULL, NULL, false, true)
|
||||
|
||||
// The callbacks need to be revoked when the CurlResponderBuffer is destructed (because that is what the callbacks use).
|
||||
// The AIThreadSafeSimple<CurlResponderBuffer> is destructed first (right to left), so when we get here then the
|
||||
// ThreadSafeCurlEasyRequest base class of ThreadSafeBufferedCurlEasyRequest is still intact and we can create
|
||||
// and use curl_easy_request_w.
|
||||
CurlResponderBuffer::~CurlResponderBuffer()
|
||||
BufferedCurlEasyRequest::~BufferedCurlEasyRequest()
|
||||
{
|
||||
ThreadSafeBufferedCurlEasyRequest* lockobj = get_lockobj();
|
||||
AICurlEasyRequest_wat curl_easy_request_w(*lockobj); // Wait 'til possible callbacks have returned.
|
||||
curl_easy_request_w->send_events_to(NULL);
|
||||
curl_easy_request_w->revokeCallbacks();
|
||||
send_buffer_events_to(NULL);
|
||||
revokeCallbacks();
|
||||
if (mResponder)
|
||||
{
|
||||
// If the responder is still alive, then that means that CurlResponderBuffer::processOutput was
|
||||
// If the responder is still alive, then that means that BufferedCurlEasyRequest::processOutput was
|
||||
// never called, which means that the removed_from_multi_handle event never happened.
|
||||
// This is definitely an internal error as it can only happen when libcurl is too slow,
|
||||
// in which case AICurlEasyRequestStateMachine::mTimer times out, but that already
|
||||
// calls CurlResponderBuffer::timed_out().
|
||||
llmaybeerrs << "Calling ~CurlResponderBuffer() with active responder!" << llendl;
|
||||
// calls BufferedCurlEasyRequest::timed_out().
|
||||
llmaybeerrs << "Calling ~BufferedCurlEasyRequest() with active responder!" << llendl;
|
||||
if (!LLApp::isRunning())
|
||||
{
|
||||
// It might happen if some CurlResponderBuffer escaped clean up somehow :/
|
||||
// It might happen if some BufferedCurlEasyRequest escaped clean up somehow :/
|
||||
mResponder = NULL;
|
||||
}
|
||||
else
|
||||
@@ -1295,38 +1209,39 @@ CurlResponderBuffer::~CurlResponderBuffer()
|
||||
}
|
||||
}
|
||||
|
||||
void CurlResponderBuffer::timed_out(void)
|
||||
void BufferedCurlEasyRequest::timed_out(void)
|
||||
{
|
||||
mResponder->completedRaw(HTTP_INTERNAL_ERROR, "Request timeout, aborted.", sChannels, mOutput);
|
||||
mResponder = NULL;
|
||||
mResponder->finished(CURLE_OK, HTTP_INTERNAL_ERROR, "Request timeout, aborted.", sChannels, mOutput);
|
||||
if (mResponder->needsHeaders())
|
||||
{
|
||||
send_buffer_events_to(NULL); // Revoke buffer events: we sent them to the responder.
|
||||
}
|
||||
mResponder = NULL;
|
||||
}
|
||||
|
||||
void CurlResponderBuffer::resetState(AICurlEasyRequest_wat& curl_easy_request_w)
|
||||
void BufferedCurlEasyRequest::resetState(void)
|
||||
{
|
||||
llassert(!mResponder);
|
||||
|
||||
curl_easy_request_w->resetState();
|
||||
// Call base class implementation.
|
||||
CurlEasyRequest::resetState();
|
||||
|
||||
mOutput.reset();
|
||||
mInput.reset();
|
||||
|
||||
mHeaderOutput.str("");
|
||||
mHeaderOutput.clear();
|
||||
}
|
||||
|
||||
ThreadSafeBufferedCurlEasyRequest* CurlResponderBuffer::get_lockobj(void)
|
||||
ThreadSafeBufferedCurlEasyRequest* BufferedCurlEasyRequest::get_lockobj(void)
|
||||
{
|
||||
return static_cast<ThreadSafeBufferedCurlEasyRequest*>(AIThreadSafeSimple<CurlResponderBuffer>::wrapper_cast(this));
|
||||
return static_cast<ThreadSafeBufferedCurlEasyRequest*>(AIThreadSafeSimple<BufferedCurlEasyRequest>::wrapper_cast(this));
|
||||
}
|
||||
|
||||
void CurlResponderBuffer::prepRequest(AICurlEasyRequest_wat& curl_easy_request_w, std::vector<std::string> const& headers, AICurlInterface::ResponderPtr responder, S32 time_out, bool post)
|
||||
ThreadSafeBufferedCurlEasyRequest const* BufferedCurlEasyRequest::get_lockobj(void) const
|
||||
{
|
||||
if (post)
|
||||
{
|
||||
// Accept everything (send an Accept-Encoding header containing all encodings we support (zlib and gzip)).
|
||||
curl_easy_request_w->setoptString(CURLOPT_ENCODING, ""); // CURLOPT_ACCEPT_ENCODING
|
||||
}
|
||||
return static_cast<ThreadSafeBufferedCurlEasyRequest const*>(AIThreadSafeSimple<BufferedCurlEasyRequest>::wrapper_cast(this));
|
||||
}
|
||||
|
||||
void BufferedCurlEasyRequest::prepRequest(AICurlEasyRequest_wat& curl_easy_request_w, AIHTTPHeaders const& headers, LLHTTPClient::ResponderPtr responder)
|
||||
{
|
||||
mInput.reset(new LLBufferArray);
|
||||
mInput->setThreaded(true);
|
||||
mLastRead = NULL;
|
||||
@@ -1339,134 +1254,23 @@ void CurlResponderBuffer::prepRequest(AICurlEasyRequest_wat& curl_easy_request_w
|
||||
curl_easy_request_w->setReadCallback(&curlReadCallback, lockobj);
|
||||
curl_easy_request_w->setHeaderCallback(&curlHeaderCallback, lockobj);
|
||||
|
||||
// Allow up to five redirects.
|
||||
if (responder && responder->followRedir())
|
||||
// Allow up to ten redirects.
|
||||
if (responder->followRedir())
|
||||
{
|
||||
curl_easy_request_w->setopt(CURLOPT_FOLLOWLOCATION, 1);
|
||||
curl_easy_request_w->setopt(CURLOPT_MAXREDIRS, MAX_REDIRECTS);
|
||||
curl_easy_request_w->setopt(CURLOPT_MAXREDIRS, HTTP_REDIRECTS_DEFAULT);
|
||||
}
|
||||
|
||||
curl_easy_request_w->setopt(CURLOPT_SSL_VERIFYPEER, true);
|
||||
// Don't verify host name so urls with scrubbed host names will work (improves DNS performance).
|
||||
curl_easy_request_w->setopt(CURLOPT_SSL_VERIFYHOST, 0);
|
||||
|
||||
curl_easy_request_w->setopt(CURLOPT_TIMEOUT, llmax(time_out, CURL_REQUEST_TIMEOUT));
|
||||
|
||||
// Keep responder alive.
|
||||
mResponder = responder;
|
||||
|
||||
if (!post)
|
||||
// Send header events to responder if needed.
|
||||
if (mResponder->needsHeaders())
|
||||
{
|
||||
// Add extra headers.
|
||||
for (std::vector<std::string>::const_iterator iter = headers.begin(); iter != headers.end(); ++iter)
|
||||
{
|
||||
curl_easy_request_w->addHeader((*iter).c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//static
|
||||
size_t CurlResponderBuffer::curlWriteCallback(char* data, size_t size, size_t nmemb, void* user_data)
|
||||
{
|
||||
ThreadSafeBufferedCurlEasyRequest* lockobj = static_cast<ThreadSafeBufferedCurlEasyRequest*>(user_data);
|
||||
|
||||
// We need to lock the curl easy request object too, because that lock is used
|
||||
// to make sure that callbacks and destruction aren't done simultaneously.
|
||||
AICurlEasyRequest_wat buffered_easy_request_w(*lockobj);
|
||||
|
||||
AICurlResponderBuffer_wat buffer_w(*lockobj);
|
||||
S32 n = size * nmemb;
|
||||
buffer_w->getOutput()->append(sChannels.in(), (U8 const*)data, n);
|
||||
return n;
|
||||
}
|
||||
|
||||
//static
|
||||
size_t CurlResponderBuffer::curlReadCallback(char* data, size_t size, size_t nmemb, void* user_data)
|
||||
{
|
||||
ThreadSafeBufferedCurlEasyRequest* lockobj = static_cast<ThreadSafeBufferedCurlEasyRequest*>(user_data);
|
||||
|
||||
// We need to lock the curl easy request object too, because that lock is used
|
||||
// to make sure that callbacks and destruction aren't done simultaneously.
|
||||
AICurlEasyRequest_wat buffered_easy_request_w(*lockobj);
|
||||
|
||||
S32 bytes = size * nmemb; // The maximum amount to read.
|
||||
AICurlResponderBuffer_wat buffer_w(*lockobj);
|
||||
buffer_w->mLastRead = buffer_w->getInput()->readAfter(sChannels.out(), buffer_w->mLastRead, (U8*)data, bytes);
|
||||
return bytes; // Return the amount actually read.
|
||||
}
|
||||
|
||||
//static
|
||||
size_t CurlResponderBuffer::curlHeaderCallback(char* data, size_t size, size_t nmemb, void* user_data)
|
||||
{
|
||||
ThreadSafeBufferedCurlEasyRequest* lockobj = static_cast<ThreadSafeBufferedCurlEasyRequest*>(user_data);
|
||||
|
||||
// We need to lock the curl easy request object too, because that lock is used
|
||||
// to make sure that callbacks and destruction aren't done simultaneously.
|
||||
AICurlEasyRequest_wat buffered_easy_request_w(*lockobj);
|
||||
|
||||
AICurlResponderBuffer_wat buffer_w(*lockobj);
|
||||
size_t n = size * nmemb;
|
||||
buffer_w->getHeaderOutput().write(data, n);
|
||||
return n;
|
||||
}
|
||||
|
||||
void CurlResponderBuffer::added_to_multi_handle(AICurlEasyRequest_wat& curl_easy_request_w)
|
||||
{
|
||||
Dout(dc::curl, "Calling CurlResponderBuffer::added_to_multi_handle(@" << (void*)&*curl_easy_request_w << ") for this = " << (void*)this);
|
||||
}
|
||||
|
||||
void CurlResponderBuffer::finished(AICurlEasyRequest_wat& curl_easy_request_w)
|
||||
{
|
||||
Dout(dc::curl, "Calling CurlResponderBuffer::finished(@" << (void*)&*curl_easy_request_w << ") for this = " << (void*)this);
|
||||
}
|
||||
|
||||
void CurlResponderBuffer::removed_from_multi_handle(AICurlEasyRequest_wat& curl_easy_request_w)
|
||||
{
|
||||
Dout(dc::curl, "Calling CurlResponderBuffer::removed_from_multi_handle(@" << (void*)&*curl_easy_request_w << ") for this = " << (void*)this);
|
||||
|
||||
// Lock self.
|
||||
ThreadSafeBufferedCurlEasyRequest* lockobj = get_lockobj();
|
||||
llassert(dynamic_cast<ThreadSafeBufferedCurlEasyRequest*>(static_cast<ThreadSafeCurlEasyRequest*>(ThreadSafeCurlEasyRequest::wrapper_cast(&*curl_easy_request_w))) == lockobj);
|
||||
AICurlResponderBuffer_wat buffer_w(*lockobj);
|
||||
llassert(&*buffer_w == this);
|
||||
|
||||
processOutput(curl_easy_request_w);
|
||||
}
|
||||
|
||||
void CurlResponderBuffer::processOutput(AICurlEasyRequest_wat& curl_easy_request_w)
|
||||
{
|
||||
U32 responseCode = 0;
|
||||
std::string responseReason;
|
||||
|
||||
CURLcode code;
|
||||
curl_easy_request_w->getResult(&code);
|
||||
if (code == CURLE_OK)
|
||||
{
|
||||
curl_easy_request_w->getinfo(CURLINFO_RESPONSE_CODE, &responseCode);
|
||||
//*TODO: get reason from first line of mHeaderOutput
|
||||
}
|
||||
else
|
||||
{
|
||||
responseCode = 499;
|
||||
responseReason = AICurlInterface::strerror(code) + " : ";
|
||||
if (code == CURLE_FAILED_INIT)
|
||||
{
|
||||
responseReason += "Curl Easy Handle initialization failed.";
|
||||
}
|
||||
else
|
||||
{
|
||||
responseReason += curl_easy_request_w->getErrorString();
|
||||
}
|
||||
curl_easy_request_w->setopt(CURLOPT_FRESH_CONNECT, TRUE);
|
||||
send_buffer_events_to(mResponder.get());
|
||||
}
|
||||
|
||||
if (mResponder)
|
||||
{
|
||||
mResponder->completedRaw(responseCode, responseReason, sChannels, mOutput);
|
||||
mResponder = NULL;
|
||||
}
|
||||
|
||||
resetState(curl_easy_request_w);
|
||||
// Add extra headers.
|
||||
curl_easy_request_w->addHeaders(headers);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
#include <set>
|
||||
#include <stdexcept>
|
||||
#include <boost/intrusive_ptr.hpp>
|
||||
#include <boost/shared_ptr.hpp>
|
||||
#include <boost/utility.hpp>
|
||||
|
||||
#include "llpreprocessor.h"
|
||||
@@ -48,12 +49,51 @@
|
||||
#undef CURLOPT_DNS_USE_GLOBAL_CACHE
|
||||
#define CURLOPT_DNS_USE_GLOBAL_CACHE do_not_use_CURLOPT_DNS_USE_GLOBAL_CACHE
|
||||
|
||||
#include "stdtypes.h" // U32
|
||||
#include "lliopipe.h" // LLIOPipe::buffer_ptr_t
|
||||
#include "stdtypes.h" // U16, S32, U32, F64
|
||||
#include "llatomic.h" // LLAtomicU32
|
||||
#include "aithreadsafe.h"
|
||||
#include "llhttpstatuscodes.h"
|
||||
#include "llhttpclient.h"
|
||||
|
||||
// Debug Settings.
|
||||
extern bool gNoVerifySSLCert;
|
||||
|
||||
class LLSD;
|
||||
class LLBufferArray;
|
||||
class LLChannelDescriptors;
|
||||
class AIHTTPTimeoutPolicy;
|
||||
|
||||
// Some pretty printing for curl easy handle related things:
|
||||
// Print the lock object related to the current easy handle in every debug output.
|
||||
#ifdef CWDEBUG
|
||||
#include <libcwd/buf2str.h>
|
||||
#include <sstream>
|
||||
#define DoutCurl(x) do { \
|
||||
using namespace libcwd; \
|
||||
std::ostringstream marker; \
|
||||
marker << (void*)this->get_lockobj(); \
|
||||
libcw_do.push_marker(); \
|
||||
libcw_do.marker().assign(marker.str().data(), marker.str().size()); \
|
||||
libcw_do.inc_indent(2); \
|
||||
Dout(dc::curl, x); \
|
||||
libcw_do.dec_indent(2); \
|
||||
libcw_do.pop_marker(); \
|
||||
} while(0)
|
||||
#define DoutCurlEntering(x) do { \
|
||||
using namespace libcwd; \
|
||||
std::ostringstream marker; \
|
||||
marker << (void*)this->get_lockobj(); \
|
||||
libcw_do.push_marker(); \
|
||||
libcw_do.marker().assign(marker.str().data(), marker.str().size()); \
|
||||
libcw_do.inc_indent(2); \
|
||||
DoutEntering(dc::curl, x); \
|
||||
libcw_do.dec_indent(2); \
|
||||
libcw_do.pop_marker(); \
|
||||
} while(0)
|
||||
#else // !CWDEBUG
|
||||
#define DoutCurl(x) Dout(dc::curl, x << " [" << (void*)this->get_lockobj() << ']')
|
||||
#define DoutCurlEntering(x) DoutEntering(dc::curl, x << " [" << (void*)this->get_lockobj() << ']')
|
||||
#endif // CWDEBUG
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Exceptions.
|
||||
@@ -76,40 +116,35 @@ class AICurlNoMultiHandle : public AICurlError {
|
||||
AICurlNoMultiHandle(std::string const& message) : AICurlError(message) { }
|
||||
};
|
||||
|
||||
class AICurlNoBody : public AICurlError {
|
||||
public:
|
||||
AICurlNoBody(std::string const& message) : AICurlError(message) { }
|
||||
};
|
||||
|
||||
// End Exceptions.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// Things defined in this namespace are called from elsewhere in the viewer code.
|
||||
namespace AICurlInterface {
|
||||
|
||||
// Output parameter of AICurlPrivate::CurlEasyRequest::getResult.
|
||||
// Only used by LLXMLRPCTransaction::Impl.
|
||||
struct TransferInfo {
|
||||
TransferInfo() : mSizeDownload(0.0), mTotalTime(0.0), mSpeedDownload(0.0) { }
|
||||
F64 mSizeDownload;
|
||||
F64 mTotalTime;
|
||||
F64 mSpeedDownload;
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Global functions.
|
||||
|
||||
// Called to handle changes in Debug Settings.
|
||||
bool handleCurlConcurrentConnections(LLSD const& newvalue);
|
||||
bool handleNoVerifySSLCert(LLSD const& newvalue);
|
||||
|
||||
// Called once at start of application (from newview/llappviewer.cpp by main thread (before threads are created)),
|
||||
// with main purpose to initialize curl.
|
||||
void initCurl(void (*)(void) = NULL);
|
||||
|
||||
// Called once at start of application (from LLAppViewer::initThreads), starts AICurlThread.
|
||||
void startCurlThread(void);
|
||||
void startCurlThread(U32 CurlConcurrentConnections, bool NoVerifySSLCert);
|
||||
|
||||
// Called once at end of application (from newview/llappviewer.cpp by main thread),
|
||||
// with purpose to stop curl threads, free curl resources and deinitialize curl.
|
||||
void cleanupCurl(void);
|
||||
|
||||
// Called from indra/llmessage/llurlrequest.cpp to print debug output regarding
|
||||
// an error code returned by EasyRequest::getResult.
|
||||
// Just returns curl_easy_strerror(errorcode).
|
||||
std::string strerror(CURLcode errorcode);
|
||||
|
||||
// Called from indra/newview/llfloaterabout.cpp for the About floater, and
|
||||
// from newview/llappviewer.cpp in behalf of debug output.
|
||||
// Just returns curl_version().
|
||||
@@ -123,105 +158,11 @@ void setCAFile(std::string const& file);
|
||||
// Can be used to set the path to the Certificate Authority file.
|
||||
void setCAPath(std::string const& file);
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Global classes.
|
||||
|
||||
// Responder - base class for Request::get* and Request::post API.
|
||||
//
|
||||
// The life cycle of classes derived from this class is as follows:
|
||||
// They are allocated with new on the line where get(), getByteRange() or post() is called,
|
||||
// and the pointer to the allocated object is then put in a reference counting ResponderPtr.
|
||||
// This ResponderPtr is passed to CurlResponderBuffer::prepRequest which stores it in its
|
||||
// member mResponder. Hence, the life time of a Responder is never longer than its
|
||||
// associated CurlResponderBuffer, however, if everything works correctly, then normally a
|
||||
// responder is deleted in CurlResponderBuffer::removed_from_multi_handle by setting
|
||||
// mReponder to NULL.
|
||||
//
|
||||
// Note that the lifetime of CurlResponderBuffer is (a bit) shorter than the associated
|
||||
// CurlEasyRequest (because of the order of base classes of ThreadSafeBufferedCurlEasyRequest)
|
||||
// and the callbacks, as set by prepRequest, only use those two.
|
||||
// A callback locks the CurlEasyRequest before actually making the callback, and the
|
||||
// destruction of CurlResponderBuffer also first locks the CurlEasyRequest, and then revokes
|
||||
// the callbacks. This assures that a Responder is never used when the objects it uses are
|
||||
// destructed. Also, if any of those are destructed then the Responder is automatically
|
||||
// destructed too.
|
||||
//
|
||||
class Responder {
|
||||
protected:
|
||||
Responder(void);
|
||||
virtual ~Responder();
|
||||
|
||||
private:
|
||||
// Associated URL, used for debug output.
|
||||
std::string mURL;
|
||||
|
||||
public:
|
||||
// Called to set the URL of the current request for this Responder,
|
||||
// used only when printing debug output regarding activity of the Responder.
|
||||
void setURL(std::string const& url);
|
||||
|
||||
public:
|
||||
// Called from LLHTTPClientURLAdaptor::complete():
|
||||
|
||||
// Derived classes can override this to get the HTML header that was received, when the message is completed.
|
||||
// The default does nothing.
|
||||
virtual void completedHeader(U32 status, std::string const& reason, LLSD const& content);
|
||||
|
||||
// Derived classes can override this to get the raw data of the body of the HTML message that was received.
|
||||
// The default is to interpret the content as LLSD and call completed().
|
||||
virtual void completedRaw(U32 status, std::string const& reason, LLChannelDescriptors const& channels, LLIOPipe::buffer_ptr_t const& buffer);
|
||||
|
||||
// Called from LLHTTPClient request calls, if an error occurs even before we can call one of the above.
|
||||
// It calls completed() with a fake status U32_MAX, as that is what some derived clients expect (bad design).
|
||||
// This means that if a derived class overrides completedRaw() it now STILL has to override completed() to catch this error.
|
||||
void fatalError(std::string const& reason);
|
||||
|
||||
// A derived class should return true if curl should follow redirections.
|
||||
// The default is not to follow redirections.
|
||||
virtual bool followRedir(void) { return false; }
|
||||
|
||||
protected:
|
||||
// ... or, derived classes can override this to get the LLSD content when the message is completed.
|
||||
// The default is to call result() (or errorWithContent() in case of a HTML status indicating an error).
|
||||
virtual void completed(U32 status, std::string const& reason, LLSD const& content);
|
||||
|
||||
// ... or, derived classes can override this to received the content of a body upon success.
|
||||
// The default does nothing.
|
||||
virtual void result(LLSD const& content);
|
||||
|
||||
// Derived classes can override this to get informed when a bad HTML status code is received.
|
||||
// The default calls error().
|
||||
virtual void errorWithContent(U32 status, std::string const& reason, LLSD const& content);
|
||||
|
||||
// ... or, derived classes can override this to get informed when a bad HTML statis code is received.
|
||||
// The default prints the error to llinfos.
|
||||
virtual void error(U32 status, std::string const& reason);
|
||||
|
||||
public:
|
||||
// Called from LLSDMessage::ResponderAdapter::listener.
|
||||
// LLSDMessage::ResponderAdapter is a hack, showing among others by fact that these functions need to be public.
|
||||
|
||||
void pubErrorWithContent(U32 status, std::string const& reason, LLSD const& content) { errorWithContent(status, reason, content); }
|
||||
void pubResult(LLSD const& content) { result(content); }
|
||||
|
||||
private:
|
||||
// Used by ResponderPtr. Object is deleted when reference count reaches zero.
|
||||
LLAtomicU32 mReferenceCount;
|
||||
|
||||
friend void intrusive_ptr_add_ref(Responder* p); // Called by boost::intrusive_ptr when a new copy of a boost::intrusive_ptr<Responder> is made.
|
||||
friend void intrusive_ptr_release(Responder* p); // Called by boost::intrusive_ptr when a boost::intrusive_ptr<Responder> is destroyed.
|
||||
// This function must delete the Responder object when the reference count reaches zero.
|
||||
};
|
||||
|
||||
// A Responder is passed around as ResponderPtr, which causes it to automatically
|
||||
// destruct when there are no pointers left pointing to it.
|
||||
typedef boost::intrusive_ptr<Responder> ResponderPtr;
|
||||
|
||||
} // namespace AICurlInterface
|
||||
|
||||
// Forward declaration (see aicurlprivate.h).
|
||||
namespace AICurlPrivate {
|
||||
class CurlEasyRequest;
|
||||
class BufferedCurlEasyRequest;
|
||||
} // namespace AICurlPrivate
|
||||
|
||||
// Define access types (_crat = Const Read Access Type, _rat = Read Access Type, _wat = Write Access Type).
|
||||
@@ -229,13 +170,13 @@ namespace AICurlPrivate {
|
||||
// AICurlEasyRequest h1; // Create easy handle.
|
||||
// AICurlEasyRequest h2(h1); // Make lightweight copies.
|
||||
// AICurlEasyRequest_wat h2_w(*h2); // Lock and obtain write access to the easy handle.
|
||||
// Use *h2_w, which is a reference to the locked CurlEasyRequest instance.
|
||||
// Use *h2_w, which is a reference to the locked BufferedCurlEasyRequest instance.
|
||||
// Note: As it is not allowed to use curl easy handles in any way concurrently,
|
||||
// read access would at most give access to a CURL const*, which will turn out
|
||||
// to be completely useless; therefore it is sufficient and efficient to use
|
||||
// an AIThreadSafeSimple and it's unlikely that AICurlEasyRequest_rat will be used.
|
||||
typedef AIAccessConst<AICurlPrivate::CurlEasyRequest> AICurlEasyRequest_rat;
|
||||
typedef AIAccess<AICurlPrivate::CurlEasyRequest> AICurlEasyRequest_wat;
|
||||
typedef AIAccessConst<AICurlPrivate::BufferedCurlEasyRequest> AICurlEasyRequest_rat;
|
||||
typedef AIAccess<AICurlPrivate::BufferedCurlEasyRequest> AICurlEasyRequest_wat;
|
||||
|
||||
// Events generated by AICurlPrivate::CurlEasyHandle.
|
||||
struct AICurlEasyHandleEvents {
|
||||
@@ -264,32 +205,38 @@ typedef LLPointer<AIPostField> AIPostFieldPtr;
|
||||
|
||||
#include "aicurlprivate.h"
|
||||
|
||||
// AICurlPrivate::CurlEasyRequestPtr, a boost::intrusive_ptr, is no more threadsafe than a
|
||||
// AICurlPrivate::BufferedCurlEasyRequestPtr, a boost::intrusive_ptr, is no more threadsafe than a
|
||||
// builtin type, but wrapping it in AIThreadSafe is obviously not going to help here.
|
||||
// Therefore we use the following trick: we wrap CurlEasyRequestPtr too, and only allow
|
||||
// Therefore we use the following trick: we wrap BufferedCurlEasyRequestPtr too, and only allow
|
||||
// read accesses on it.
|
||||
|
||||
// AICurlEasyRequest: a thread safe, reference counting, auto-cleaning curl easy handle.
|
||||
// AICurlEasyRequest: a thread safe, reference counting, buffered, auto-cleaning curl easy handle.
|
||||
class AICurlEasyRequest {
|
||||
public:
|
||||
private:
|
||||
// Use AICurlEasyRequestStateMachine, not AICurlEasyRequest.
|
||||
friend class AICurlEasyRequestStateMachine;
|
||||
|
||||
// Initial construction is allowed (thread-safe).
|
||||
// Note: If ThreadSafeCurlEasyRequest() throws then the memory allocated is still freed.
|
||||
// 'new' never returned however and neither the constructor nor destructor of mCurlEasyRequest is called in this case.
|
||||
// Note: If ThreadSafeBufferedCurlEasyRequest() throws then the memory allocated is still freed.
|
||||
// 'new' never returned however and neither the constructor nor destructor of mBufferedCurlEasyRequest is called in this case.
|
||||
// This might throw AICurlNoEasyHandle.
|
||||
AICurlEasyRequest(bool buffered) :
|
||||
mCurlEasyRequest(buffered ? new AICurlPrivate::ThreadSafeBufferedCurlEasyRequest : new AICurlPrivate::ThreadSafeCurlEasyRequest) { }
|
||||
AICurlEasyRequest(AICurlEasyRequest const& orig) : mCurlEasyRequest(orig.mCurlEasyRequest) { }
|
||||
AICurlEasyRequest(void) :
|
||||
mBufferedCurlEasyRequest(new AICurlPrivate::ThreadSafeBufferedCurlEasyRequest) { }
|
||||
|
||||
public:
|
||||
// Used for storing this object in a standard container (see MultiHandle::add_easy_request).
|
||||
AICurlEasyRequest(AICurlEasyRequest const& orig) : mBufferedCurlEasyRequest(orig.mBufferedCurlEasyRequest) { }
|
||||
|
||||
// For the rest, only allow read operations.
|
||||
AIThreadSafeSimple<AICurlPrivate::CurlEasyRequest>& operator*(void) const { llassert(mCurlEasyRequest.get()); return *mCurlEasyRequest; }
|
||||
AIThreadSafeSimple<AICurlPrivate::CurlEasyRequest>* operator->(void) const { llassert(mCurlEasyRequest.get()); return mCurlEasyRequest.get(); }
|
||||
AIThreadSafeSimple<AICurlPrivate::CurlEasyRequest>* get(void) const { return mCurlEasyRequest.get(); }
|
||||
AIThreadSafeSimple<AICurlPrivate::BufferedCurlEasyRequest>& operator*(void) const { llassert(mBufferedCurlEasyRequest.get()); return *mBufferedCurlEasyRequest; }
|
||||
AIThreadSafeSimple<AICurlPrivate::BufferedCurlEasyRequest>* operator->(void) const { llassert(mBufferedCurlEasyRequest.get()); return mBufferedCurlEasyRequest.get(); }
|
||||
AIThreadSafeSimple<AICurlPrivate::BufferedCurlEasyRequest>* get(void) const { return mBufferedCurlEasyRequest.get(); }
|
||||
|
||||
// Returns true if this object points to the same CurlEasyRequest object.
|
||||
bool operator==(AICurlEasyRequest const& cer) const { return mCurlEasyRequest == cer.mCurlEasyRequest; }
|
||||
// Returns true if this object points to the same BufferedCurlEasyRequest object.
|
||||
bool operator==(AICurlEasyRequest const& cer) const { return mBufferedCurlEasyRequest == cer.mBufferedCurlEasyRequest; }
|
||||
|
||||
// Returns true if this object points to a different CurlEasyRequest object.
|
||||
bool operator!=(AICurlEasyRequest const& cer) const { return mCurlEasyRequest != cer.mCurlEasyRequest; }
|
||||
// Returns true if this object points to a different BufferedCurlEasyRequest object.
|
||||
bool operator!=(AICurlEasyRequest const& cer) const { return mBufferedCurlEasyRequest != cer.mBufferedCurlEasyRequest; }
|
||||
|
||||
// Queue this request for insertion in the multi session.
|
||||
void addRequest(void);
|
||||
@@ -297,12 +244,9 @@ class AICurlEasyRequest {
|
||||
// Queue a command to remove this request from the multi session (or cancel a queued command to add it).
|
||||
void removeRequest(void);
|
||||
|
||||
// Returns true when this AICurlEasyRequest wraps a AICurlPrivate::ThreadSafeBufferedCurlEasyRequest.
|
||||
bool isBuffered(void) const { return mCurlEasyRequest->isBuffered(); }
|
||||
|
||||
private:
|
||||
// The actual pointer to the ThreadSafeCurlEasyRequest instance.
|
||||
AICurlPrivate::CurlEasyRequestPtr mCurlEasyRequest;
|
||||
// The actual pointer to the ThreadSafeBufferedCurlEasyRequest instance.
|
||||
AICurlPrivate::BufferedCurlEasyRequestPtr mBufferedCurlEasyRequest;
|
||||
|
||||
private:
|
||||
// Assignment would not be thread-safe; we may create this object and read from it.
|
||||
@@ -310,32 +254,28 @@ class AICurlEasyRequest {
|
||||
// destruct it while another thread still needs it, concurrent or not.
|
||||
AICurlEasyRequest& operator=(AICurlEasyRequest const&) { return *this; }
|
||||
|
||||
public:
|
||||
// Instead of assignment, it might be helpful to use swap.
|
||||
void swap(AICurlEasyRequest& cer) { mBufferedCurlEasyRequest.swap(cer.mBufferedCurlEasyRequest); }
|
||||
|
||||
public:
|
||||
// The more exotic member functions of this class, to deal with passing this class
|
||||
// as CURLOPT_PRIVATE pointer to a curl handle and afterwards restore it.
|
||||
// For "internal use" only; don't use things from AICurlPrivate yourself.
|
||||
|
||||
// It's thread-safe to give read access the underlaying boost::intrusive_ptr.
|
||||
// It's not OK to then call get() on that and store the AICurlPrivate::ThreadSafeCurlEasyRequest* separately.
|
||||
AICurlPrivate::CurlEasyRequestPtr const& get_ptr(void) const { return mCurlEasyRequest; }
|
||||
// It's not OK to then call get() on that and store the AICurlPrivate::ThreadSafeBufferedCurlEasyRequest* separately.
|
||||
AICurlPrivate::BufferedCurlEasyRequestPtr const& get_ptr(void) const { return mBufferedCurlEasyRequest; }
|
||||
|
||||
// If we have a correct (with regard to reference counting) AICurlPrivate::CurlEasyRequestPtr,
|
||||
// If we have a correct (with regard to reference counting) AICurlPrivate::BufferedCurlEasyRequestPtr,
|
||||
// then it's OK to construct a AICurlEasyRequest from it.
|
||||
// Note that the external AICurlPrivate::CurlEasyRequestPtr needs its own locking, because
|
||||
// Note that the external AICurlPrivate::BufferedCurlEasyRequestPtr needs its own locking, because
|
||||
// it's not thread-safe in itself.
|
||||
AICurlEasyRequest(AICurlPrivate::CurlEasyRequestPtr const& ptr) : mCurlEasyRequest(ptr) { }
|
||||
AICurlEasyRequest(AICurlPrivate::BufferedCurlEasyRequestPtr const& ptr) : mBufferedCurlEasyRequest(ptr) { }
|
||||
|
||||
// This one is obviously dangerous. It's for use only in MultiHandle::check_run_count.
|
||||
// See also the long comment in CurlEasyRequest::finalizeRequest with regard to CURLOPT_PRIVATE.
|
||||
explicit AICurlEasyRequest(AICurlPrivate::ThreadSafeCurlEasyRequest* ptr) : mCurlEasyRequest(ptr) { }
|
||||
};
|
||||
|
||||
// Write Access Type for the buffer.
|
||||
struct AICurlResponderBuffer_wat : public AIAccess<AICurlPrivate::CurlResponderBuffer> {
|
||||
explicit AICurlResponderBuffer_wat(AICurlPrivate::ThreadSafeBufferedCurlEasyRequest& lockobj) :
|
||||
AIAccess<AICurlPrivate::CurlResponderBuffer>(lockobj) { }
|
||||
AICurlResponderBuffer_wat(AIThreadSafeSimple<AICurlPrivate::CurlEasyRequest>& lockobj) :
|
||||
AIAccess<AICurlPrivate::CurlResponderBuffer>(static_cast<AICurlPrivate::ThreadSafeBufferedCurlEasyRequest&>(lockobj)) { }
|
||||
// See also the long comment in BufferedCurlEasyRequest::finalizeRequest with regard to CURLOPT_PRIVATE.
|
||||
explicit AICurlEasyRequest(AICurlPrivate::ThreadSafeBufferedCurlEasyRequest* ptr) : mBufferedCurlEasyRequest(ptr) { }
|
||||
};
|
||||
|
||||
#define AICurlPrivate DONTUSE_AICurlPrivate
|
||||
|
||||
263
indra/llmessage/aicurleasyrequeststatemachine.cpp
Normal file
263
indra/llmessage/aicurleasyrequeststatemachine.cpp
Normal file
@@ -0,0 +1,263 @@
|
||||
/**
|
||||
* @file aicurleasyrequeststatemachine.cpp
|
||||
* @brief Implementation of AICurlEasyRequestStateMachine
|
||||
*
|
||||
* Copyright (c) 2012, Aleric Inglewood.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* There are special exceptions to the terms and conditions of the GPL as
|
||||
* it is applied to this Source Code. View the full text of the exception
|
||||
* in the file doc/FLOSS-exception.txt in this software distribution.
|
||||
*
|
||||
* CHANGELOG
|
||||
* and additional copyright holders.
|
||||
*
|
||||
* 06/05/2012
|
||||
* Initial version, written by Aleric Inglewood @ SL
|
||||
*/
|
||||
|
||||
#include "linden_common.h"
|
||||
#include "aicurleasyrequeststatemachine.h"
|
||||
#include "aihttptimeoutpolicy.h"
|
||||
#include "llcontrol.h"
|
||||
|
||||
enum curleasyrequeststatemachine_state_type {
|
||||
AICurlEasyRequestStateMachine_addRequest = AIStateMachine::max_state,
|
||||
AICurlEasyRequestStateMachine_waitAdded,
|
||||
AICurlEasyRequestStateMachine_added,
|
||||
AICurlEasyRequestStateMachine_timedOut, // This must be smaller than the rest, so they always overrule.
|
||||
AICurlEasyRequestStateMachine_finished,
|
||||
AICurlEasyRequestStateMachine_removed, // The removed states must be largest two, so they are never ignored.
|
||||
AICurlEasyRequestStateMachine_removed_after_finished
|
||||
};
|
||||
|
||||
char const* AICurlEasyRequestStateMachine::state_str_impl(state_type run_state) const
|
||||
{
|
||||
switch(run_state)
|
||||
{
|
||||
AI_CASE_RETURN(AICurlEasyRequestStateMachine_addRequest);
|
||||
AI_CASE_RETURN(AICurlEasyRequestStateMachine_waitAdded);
|
||||
AI_CASE_RETURN(AICurlEasyRequestStateMachine_added);
|
||||
AI_CASE_RETURN(AICurlEasyRequestStateMachine_timedOut);
|
||||
AI_CASE_RETURN(AICurlEasyRequestStateMachine_finished);
|
||||
AI_CASE_RETURN(AICurlEasyRequestStateMachine_removed);
|
||||
AI_CASE_RETURN(AICurlEasyRequestStateMachine_removed_after_finished);
|
||||
}
|
||||
return "UNKNOWN STATE";
|
||||
}
|
||||
|
||||
void AICurlEasyRequestStateMachine::initialize_impl(void)
|
||||
{
|
||||
{
|
||||
AICurlEasyRequest_wat curlEasyRequest_w(*mCurlEasyRequest);
|
||||
llassert(curlEasyRequest_w->is_finalized()); // Call finalizeRequest() before calling run().
|
||||
curlEasyRequest_w->send_handle_events_to(this);
|
||||
}
|
||||
mAdded = false;
|
||||
mTimedOut = false;
|
||||
mFinished = false;
|
||||
mHandled = false;
|
||||
set_state(AICurlEasyRequestStateMachine_addRequest);
|
||||
}
|
||||
|
||||
// CURL-THREAD
|
||||
void AICurlEasyRequestStateMachine::added_to_multi_handle(AICurlEasyRequest_wat&)
|
||||
{
|
||||
set_state(AICurlEasyRequestStateMachine_added);
|
||||
}
|
||||
|
||||
// CURL-THREAD
|
||||
void AICurlEasyRequestStateMachine::finished(AICurlEasyRequest_wat&)
|
||||
{
|
||||
mFinished = true;
|
||||
set_state(AICurlEasyRequestStateMachine_finished);
|
||||
}
|
||||
|
||||
// CURL-THREAD
|
||||
void AICurlEasyRequestStateMachine::removed_from_multi_handle(AICurlEasyRequest_wat&)
|
||||
{
|
||||
llassert(mFinished || mTimedOut); // If we neither finished nor timed out, then why is this being removed?
|
||||
// Note that allowing this would cause an assertion later on for removing
|
||||
// a BufferedCurlEasyRequest with a still active Responder.
|
||||
set_state(mFinished ? AICurlEasyRequestStateMachine_removed_after_finished : AICurlEasyRequestStateMachine_removed);
|
||||
}
|
||||
|
||||
void AICurlEasyRequestStateMachine::multiplex_impl(void)
|
||||
{
|
||||
mSetStateLock.lock();
|
||||
state_type current_state = mRunState;
|
||||
mSetStateLock.unlock();
|
||||
switch (current_state)
|
||||
{
|
||||
case AICurlEasyRequestStateMachine_addRequest:
|
||||
{
|
||||
set_state(AICurlEasyRequestStateMachine_waitAdded);
|
||||
idle(AICurlEasyRequestStateMachine_waitAdded); // Wait till AICurlEasyRequestStateMachine::added_to_multi_handle() is called.
|
||||
// Only AFTER going idle, add request to curl thread; this is needed because calls to set_state() are
|
||||
// ignored when the statemachine is not idle, and theoretically the callbacks could be called
|
||||
// immediately after this call.
|
||||
mAdded = true;
|
||||
mCurlEasyRequest.addRequest(); // This causes the state to be changed, now or later, to
|
||||
// AICurlEasyRequestStateMachine_added, then
|
||||
// AICurlEasyRequestStateMachine_finished and then
|
||||
// AICurlEasyRequestStateMachine_removed_after_finished.
|
||||
|
||||
// The first two states might be skipped thus, and the state at this point is one of
|
||||
// 1) AICurlEasyRequestStateMachine_waitAdded (idle)
|
||||
// 2) AICurlEasyRequestStateMachine_added (running)
|
||||
// 3) AICurlEasyRequestStateMachine_finished (running)
|
||||
// 4) AICurlEasyRequestStateMachine_removed_after_finished (running)
|
||||
|
||||
if (mTotalDelayTimeout > 0.f)
|
||||
{
|
||||
// Set an inactivity timer.
|
||||
// This shouldn't really be necessary, except in the case of a bug
|
||||
// in libcurl; but lets be sure and set a timer for inactivity.
|
||||
mTimer = new AIPersistentTimer; // Do not delete timer upon expiration.
|
||||
mTimer->setInterval(mTotalDelayTimeout);
|
||||
mTimer->run(this, AICurlEasyRequestStateMachine_timedOut, false, false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AICurlEasyRequestStateMachine_added:
|
||||
{
|
||||
// The request was added to the multi handle. This is a no-op, which is good cause
|
||||
// this state might be skipped anyway ;).
|
||||
idle(current_state); // Wait for the next event.
|
||||
|
||||
// The state at this point is one of
|
||||
// 1) AICurlEasyRequestStateMachine_added (idle)
|
||||
// 2) AICurlEasyRequestStateMachine_finished (running)
|
||||
// 3) AICurlEasyRequestStateMachine_removed_after_finished (running)
|
||||
break;
|
||||
}
|
||||
case AICurlEasyRequestStateMachine_timedOut:
|
||||
{
|
||||
// It is possible that exactly at this point the state changes into
|
||||
// AICurlEasyRequestStateMachine_finished, with as result that mTimedOut
|
||||
// is set while we will continue with that state. Hence that mTimedOut
|
||||
// is explicitly reset in that state.
|
||||
|
||||
// Libcurl failed to deliver within a reasonable time... Abort operation in order
|
||||
// to free this curl easy handle and to notify the application that it didn't work.
|
||||
mTimedOut = true;
|
||||
llassert(mAdded);
|
||||
mAdded = false;
|
||||
mCurlEasyRequest.removeRequest();
|
||||
idle(current_state); // Wait till AICurlEasyRequestStateMachine::removed_from_multi_handle() is called.
|
||||
break;
|
||||
}
|
||||
case AICurlEasyRequestStateMachine_finished:
|
||||
case AICurlEasyRequestStateMachine_removed_after_finished:
|
||||
{
|
||||
if (!mHandled)
|
||||
{
|
||||
// Only do this once.
|
||||
mHandled = true;
|
||||
|
||||
if (mTimer)
|
||||
{
|
||||
// Stop the timer. Note that it's the main thread that generates timer events,
|
||||
// so we're certain that there will be no time out anymore if we reach this point.
|
||||
mTimer->abort();
|
||||
}
|
||||
|
||||
// The request finished and either data or an error code is available.
|
||||
AICurlEasyRequest_wat easy_request_w(*mCurlEasyRequest);
|
||||
easy_request_w->processOutput();
|
||||
}
|
||||
|
||||
if (current_state == AICurlEasyRequestStateMachine_finished)
|
||||
{
|
||||
idle(current_state); // Wait till AICurlEasyRequestStateMachine::removed_from_multi_handle() is called.
|
||||
break;
|
||||
}
|
||||
|
||||
// See above.
|
||||
mTimedOut = false;
|
||||
/* Fall-Through */
|
||||
}
|
||||
case AICurlEasyRequestStateMachine_removed:
|
||||
{
|
||||
// The request was removed from the multi handle.
|
||||
if (mTimedOut)
|
||||
{
|
||||
AICurlEasyRequest_wat easy_request_w(*mCurlEasyRequest);
|
||||
easy_request_w->timed_out();
|
||||
}
|
||||
|
||||
// We're done. If we timed out, abort -- or else the application will
|
||||
// think that getResult() will return a valid error code from libcurl.
|
||||
if (mTimedOut)
|
||||
abort();
|
||||
else
|
||||
finish();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AICurlEasyRequestStateMachine::abort_impl(void)
|
||||
{
|
||||
DoutEntering(dc::curl, "AICurlEasyRequestStateMachine::abort_impl() [" << (void*)this << "] [" << (void*)mCurlEasyRequest.get() << "]");
|
||||
// Revert call to addRequest() if that was already called (and the request wasn't removed again already).
|
||||
if (mAdded)
|
||||
{
|
||||
// Note that it's safe to call this even if the curl thread already removed it, or will removes it
|
||||
// after we called this, before processing the remove command; only the curl thread calls
|
||||
// MultiHandle::remove_easy_request, which is a no-op when called twice for the same easy request.
|
||||
mAdded = false;
|
||||
mCurlEasyRequest.removeRequest();
|
||||
}
|
||||
}
|
||||
|
||||
void AICurlEasyRequestStateMachine::finish_impl(void)
|
||||
{
|
||||
DoutEntering(dc::curl, "AICurlEasyRequestStateMachine::finish_impl() [" << (void*)this << "] [" << (void*)mCurlEasyRequest.get() << "]");
|
||||
// Revoke callbacks.
|
||||
{
|
||||
AICurlEasyRequest_wat curl_easy_request_w(*mCurlEasyRequest);
|
||||
curl_easy_request_w->send_buffer_events_to(NULL);
|
||||
curl_easy_request_w->send_handle_events_to(NULL);
|
||||
curl_easy_request_w->revokeCallbacks();
|
||||
}
|
||||
if (mTimer)
|
||||
{
|
||||
// Note that even if the timer expired, it wasn't deleted because we used AIPersistentTimer; so mTimer is still valid.
|
||||
// Stop the timer, if it's still running.
|
||||
if (!mHandled)
|
||||
mTimer->abort();
|
||||
}
|
||||
// Auto clean up ourselves.
|
||||
kill();
|
||||
}
|
||||
|
||||
AICurlEasyRequestStateMachine::AICurlEasyRequestStateMachine(void) :
|
||||
mTimer(NULL), mTotalDelayTimeout(AIHTTPTimeoutPolicy::getDebugSettingsCurlTimeout().getTotalDelay())
|
||||
{
|
||||
Dout(dc::statemachine, "Calling AICurlEasyRequestStateMachine(void) [" << (void*)this << "] [" << (void*)mCurlEasyRequest.get() << "]");
|
||||
}
|
||||
|
||||
void AICurlEasyRequestStateMachine::setTotalDelayTimeout(F32 totalDelayTimeout)
|
||||
{
|
||||
mTotalDelayTimeout = totalDelayTimeout;
|
||||
}
|
||||
|
||||
AICurlEasyRequestStateMachine::~AICurlEasyRequestStateMachine()
|
||||
{
|
||||
Dout(dc::statemachine, "Calling ~AICurlEasyRequestStateMachine() [" << (void*)this << "] [" << (void*)mCurlEasyRequest.get() << "]");
|
||||
}
|
||||
|
||||
106
indra/llmessage/aicurleasyrequeststatemachine.h
Normal file
106
indra/llmessage/aicurleasyrequeststatemachine.h
Normal file
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* @file aicurleasyrequest.h
|
||||
* @brief Perform a curl easy request.
|
||||
*
|
||||
* Copyright (c) 2012, Aleric Inglewood.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* There are special exceptions to the terms and conditions of the GPL as
|
||||
* it is applied to this Source Code. View the full text of the exception
|
||||
* in the file doc/FLOSS-exception.txt in this software distribution.
|
||||
*
|
||||
* CHANGELOG
|
||||
* and additional copyright holders.
|
||||
*
|
||||
* 06/05/2012
|
||||
* Initial version, written by Aleric Inglewood @ SL
|
||||
*/
|
||||
|
||||
#ifndef AICURLEASYREQUEST_H
|
||||
#define AICURLEASYREQUEST_H
|
||||
|
||||
#include "aistatemachine.h"
|
||||
#include "aitimer.h"
|
||||
#include "aicurl.h"
|
||||
|
||||
// A curl easy request state machine.
|
||||
//
|
||||
// Before calling cersm.run() initialize the object (cersm) as follows:
|
||||
//
|
||||
// AICurlEasyRequest_wat cersm_w(cersm);
|
||||
// cersm_w->setopt(...); // etc, see the interface of AICurlPrivate::CurlEasyRequest.
|
||||
//
|
||||
// When the state machine finishes, call aborted() to check
|
||||
// whether or not the statemachine succeeded in fetching
|
||||
// the URL or not.
|
||||
//
|
||||
// Objects of this type can be reused multiple times, see
|
||||
// also the documentation of AIStateMachine.
|
||||
//
|
||||
// Construction of a AICurlEasyRequestStateMachine might throw AICurlNoEasyHandle.
|
||||
class AICurlEasyRequestStateMachine : public AIStateMachine, public AICurlEasyHandleEvents {
|
||||
public:
|
||||
AICurlEasyRequestStateMachine(void);
|
||||
|
||||
// Transparent access.
|
||||
AICurlEasyRequest mCurlEasyRequest;
|
||||
|
||||
private:
|
||||
bool mAdded; // Set when the last command to the curl thread was to add the request.
|
||||
bool mTimedOut; // Set if the expiration timer timed out.
|
||||
bool mFinished; // Set by the curl thread to signal it finished.
|
||||
bool mHandled; // Set when we processed the received data.
|
||||
AITimer* mTimer; // Expiration timer.
|
||||
F32 mTotalDelayTimeout; // The time out value for mTimer.
|
||||
|
||||
public:
|
||||
// Called to set a specific time out, instead of the default one.
|
||||
void setTotalDelayTimeout(F32 totalDelayTimeout);
|
||||
|
||||
protected:
|
||||
// AICurlEasyRequest Events.
|
||||
|
||||
// Called when this curl easy handle was added to a multi handle.
|
||||
/*virtual*/ void added_to_multi_handle(AICurlEasyRequest_wat&);
|
||||
|
||||
// Called when this curl easy handle finished processing (right before it is removed from the multi handle).
|
||||
/*virtual*/ void finished(AICurlEasyRequest_wat&);
|
||||
|
||||
// Called after this curl easy handle was removed from a multi handle.
|
||||
/*virtual*/ void removed_from_multi_handle(AICurlEasyRequest_wat&);
|
||||
|
||||
protected:
|
||||
// AIStateMachine implementations.
|
||||
|
||||
// Call finish() (or abort()), not delete.
|
||||
/*virtual*/ ~AICurlEasyRequestStateMachine();
|
||||
|
||||
// Handle initializing the object.
|
||||
/*virtual*/ void initialize_impl(void);
|
||||
|
||||
// Handle mRunState.
|
||||
/*virtual*/ void multiplex_impl(void);
|
||||
|
||||
// Handle aborting from current bs_run state.
|
||||
/*virtual*/ void abort_impl(void);
|
||||
|
||||
// Handle cleaning up from initialization (or post abort) state.
|
||||
/*virtual*/ void finish_impl(void);
|
||||
|
||||
// Implemenation of state_str for run states.
|
||||
/*virtual*/ char const* state_str_impl(state_type run_state) const;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -33,9 +33,83 @@
|
||||
|
||||
#include <sstream>
|
||||
#include "llatomic.h"
|
||||
#include "llrefcount.h"
|
||||
|
||||
class AIHTTPHeaders;
|
||||
class AIHTTPTimeoutPolicy;
|
||||
class AICurlEasyRequestStateMachine;
|
||||
|
||||
namespace AICurlPrivate {
|
||||
namespace curlthread { class MultiHandle; }
|
||||
|
||||
class CurlEasyRequest;
|
||||
class ThreadSafeBufferedCurlEasyRequest;
|
||||
|
||||
namespace curlthread {
|
||||
|
||||
class MultiHandle;
|
||||
|
||||
// A class that keeps track of timeout administration per connection.
|
||||
class HTTPTimeout : public LLRefCount {
|
||||
private:
|
||||
AIHTTPTimeoutPolicy const* mPolicy; // A pointer to the used timeout policy.
|
||||
std::vector<U32> mBuckets; // An array with the number of bytes transfered in each second.
|
||||
U16 mBucket; // The bucket corresponding to mLastSecond.
|
||||
bool mNothingReceivedYet; // Set when created, reset when the HTML reply header from the server is received.
|
||||
bool mLowSpeedOn; // Set while uploading or downloading data.
|
||||
bool mUploadFinished; // Used to keep track of whether upload_finished was called yet.
|
||||
S32 mLastSecond; // The time at which lowspeed() was last called, in seconds since mLowSpeedClock.
|
||||
U32 mTotalBytes; // The sum of all bytes in mBuckets.
|
||||
U64 mLowSpeedClock; // Clock count at which low speed detection (re)started.
|
||||
U64 mStalled; // The clock count at which this transaction is considered to be stalling if nothing is transfered anymore.
|
||||
public:
|
||||
static F64 const sClockWidth; // Time between two clock ticks in seconds.
|
||||
static U64 sClockCount; // Clock count used as 'now' during one loop of the main loop.
|
||||
#if defined(CWDEBUG) || defined(DEBUG_CURLIO)
|
||||
ThreadSafeBufferedCurlEasyRequest* mLockObj;
|
||||
#endif
|
||||
|
||||
public:
|
||||
HTTPTimeout(AIHTTPTimeoutPolicy const* policy, ThreadSafeBufferedCurlEasyRequest* lock_obj) :
|
||||
mPolicy(policy), mNothingReceivedYet(true), mLowSpeedOn(false), mUploadFinished(false), mStalled((U64)-1)
|
||||
#if defined(CWDEBUG) || defined(DEBUG_CURLIO)
|
||||
, mLockObj(lock_obj)
|
||||
#endif
|
||||
{ }
|
||||
|
||||
// Called after sending all headers, when body data is written the first time.
|
||||
void connected(void);
|
||||
|
||||
// Called when everything we had to send to the server has been sent.
|
||||
void upload_finished(void);
|
||||
|
||||
// Called when data is sent. Returns true if transfer timed out.
|
||||
bool data_sent(size_t n);
|
||||
|
||||
// Called when data is received. Returns true if transfer timed out.
|
||||
bool data_received(size_t n);
|
||||
|
||||
// Called immediately before done() after curl finished, with code.
|
||||
void done(AICurlEasyRequest_wat const& curlEasyRequest_w, CURLcode code);
|
||||
|
||||
// Accessor.
|
||||
bool has_stalled(void) const { return mStalled < sClockCount; }
|
||||
|
||||
// Called from CurlResponderBuffer::processOutput if a timeout occurred.
|
||||
void print_diagnostics(CurlEasyRequest const* curl_easy_request);
|
||||
|
||||
#if defined(CWDEBUG) || defined(DEBUG_CURLIO)
|
||||
void* get_lockobj(void) const { return mLockObj; }
|
||||
#endif
|
||||
|
||||
private:
|
||||
// (Re)start low speed transer rate detection.
|
||||
void reset_lowspeed(void);
|
||||
|
||||
// Common low speed detection, Called from data_sent or data_received.
|
||||
bool lowspeed(size_t bytes);
|
||||
};
|
||||
|
||||
} // namespace curlthread
|
||||
|
||||
struct Stats {
|
||||
static LLAtomicU32 easy_calls;
|
||||
@@ -56,11 +130,8 @@ bool curlThreadIsRunning(void);
|
||||
void wakeUpCurlThread(void);
|
||||
void stopCurlThread(void);
|
||||
|
||||
class ThreadSafeCurlEasyRequest;
|
||||
class ThreadSafeBufferedCurlEasyRequest;
|
||||
|
||||
#define DECLARE_SETOPT(param_type) \
|
||||
CURLcode setopt(CURLoption option, param_type parameter)
|
||||
CURLcode setopt(CURLoption option, param_type parameter)
|
||||
|
||||
// This class wraps CURL*'s.
|
||||
// It guarantees that a pointer is cleaned up when no longer needed, as required by libcurl.
|
||||
@@ -112,20 +183,20 @@ class CurlEasyHandle : public boost::noncopyable, protected AICurlEasyHandleEven
|
||||
|
||||
// Extract information from a curl handle.
|
||||
private:
|
||||
CURLcode getinfo_priv(CURLINFO info, void* data);
|
||||
CURLcode getinfo_priv(CURLINFO info, void* data) const;
|
||||
public:
|
||||
// The rest are inlines to provide some type-safety.
|
||||
CURLcode getinfo(CURLINFO info, char** data) { return getinfo_priv(info, data); }
|
||||
CURLcode getinfo(CURLINFO info, curl_slist** data) { return getinfo_priv(info, data); }
|
||||
CURLcode getinfo(CURLINFO info, double* data) { return getinfo_priv(info, data); }
|
||||
CURLcode getinfo(CURLINFO info, long* data) { return getinfo_priv(info, data); }
|
||||
CURLcode getinfo(CURLINFO info, char** data) const { return getinfo_priv(info, data); }
|
||||
CURLcode getinfo(CURLINFO info, curl_slist** data) const { return getinfo_priv(info, data); }
|
||||
CURLcode getinfo(CURLINFO info, double* data) const { return getinfo_priv(info, data); }
|
||||
CURLcode getinfo(CURLINFO info, long* data) const { return getinfo_priv(info, data); }
|
||||
#ifdef __LP64__ // sizeof(long) > sizeof(int) ?
|
||||
// Overload for integer types that are too small (libcurl demands a long).
|
||||
CURLcode getinfo(CURLINFO info, S32* data) { long ldata; CURLcode res = getinfo_priv(info, &ldata); *data = static_cast<S32>(ldata); return res; }
|
||||
CURLcode getinfo(CURLINFO info, U32* data) { long ldata; CURLcode res = getinfo_priv(info, &ldata); *data = static_cast<U32>(ldata); return res; }
|
||||
CURLcode getinfo(CURLINFO info, S32* data) const { long ldata; CURLcode res = getinfo_priv(info, &ldata); *data = static_cast<S32>(ldata); return res; }
|
||||
CURLcode getinfo(CURLINFO info, U32* data) const { long ldata; CURLcode res = getinfo_priv(info, &ldata); *data = static_cast<U32>(ldata); return res; }
|
||||
#else // sizeof(long) == sizeof(int)
|
||||
CURLcode getinfo(CURLINFO info, S32* data) { return getinfo_priv(info, reinterpret_cast<long*>(data)); }
|
||||
CURLcode getinfo(CURLINFO info, U32* data) { return getinfo_priv(info, reinterpret_cast<long*>(data)); }
|
||||
CURLcode getinfo(CURLINFO info, S32* data) const { return getinfo_priv(info, reinterpret_cast<long*>(data)); }
|
||||
CURLcode getinfo(CURLINFO info, U32* data) const { return getinfo_priv(info, reinterpret_cast<long*>(data)); }
|
||||
#endif
|
||||
|
||||
// Perform a file transfer (blocking).
|
||||
@@ -142,7 +213,7 @@ class CurlEasyHandle : public boost::noncopyable, protected AICurlEasyHandleEven
|
||||
private:
|
||||
CURL* mEasyHandle;
|
||||
CURLM* mActiveMultiHandle;
|
||||
char* mErrorBuffer;
|
||||
mutable char* mErrorBuffer;
|
||||
AIPostFieldPtr mPostField; // This keeps the POSTFIELD data alive for as long as the easy handle exists.
|
||||
bool mQueuedForRemoval; // Set if the easy handle is (probably) added to the multi handle, but is queued for removal.
|
||||
#ifdef SHOW_ASSERT
|
||||
@@ -160,10 +231,6 @@ class CurlEasyHandle : public boost::noncopyable, protected AICurlEasyHandleEven
|
||||
// Returns true if this easy handle was added to a curl multi handle.
|
||||
bool active(void) const { return mActiveMultiHandle; }
|
||||
|
||||
// If there was an error code as result, then this returns a human readable error string.
|
||||
// Only valid when setErrorBuffer was called and the curl_easy function returned an error.
|
||||
std::string getErrorString(void) const { return mErrorBuffer ? mErrorBuffer : "(null)"; }
|
||||
|
||||
// Returns true when it is expected that the parent will revoke callbacks before the curl
|
||||
// easy handle is removed from the multi handle; that usually happens when an external
|
||||
// error demands termination of the request (ie, an expiration).
|
||||
@@ -174,7 +241,7 @@ class CurlEasyHandle : public boost::noncopyable, protected AICurlEasyHandleEven
|
||||
|
||||
private:
|
||||
// Call this prior to every curl_easy function whose return value is passed to check_easy_code.
|
||||
void setErrorBuffer(void);
|
||||
void setErrorBuffer(void) const;
|
||||
|
||||
static void handle_easy_error(CURLcode code);
|
||||
|
||||
@@ -203,23 +270,23 @@ class CurlEasyHandle : public boost::noncopyable, protected AICurlEasyHandleEven
|
||||
// to set the options on a curl easy handle.
|
||||
//
|
||||
// Calling sendRequest() will then connect to the given URL and perform
|
||||
// the data exchange. If an error occurs related to this handle, it can
|
||||
// be read by calling getErrorString().
|
||||
// the data exchange. Use getResult() to determine if an error occurred.
|
||||
//
|
||||
// Note that the life cycle of a CurlEasyRequest is controlled by AICurlEasyRequest:
|
||||
// a CurlEasyRequest is only ever created as base class of a ThreadSafeCurlEasyRequest,
|
||||
// a CurlEasyRequest is only ever created as base class of a ThreadSafeBufferedCurlEasyRequest,
|
||||
// which is only created by creating a AICurlEasyRequest. When the last copy of such
|
||||
// AICurlEasyRequest is deleted, then also the ThreadSafeCurlEasyRequest is deleted
|
||||
// AICurlEasyRequest is deleted, then also the ThreadSafeBufferedCurlEasyRequest is deleted
|
||||
// and the CurlEasyRequest destructed.
|
||||
class CurlEasyRequest : public CurlEasyHandle {
|
||||
private:
|
||||
void setPost_raw(S32 size, char const* data);
|
||||
void setPost_raw(U32 size, char const* data);
|
||||
public:
|
||||
void setPost(S32 size) { setPost_raw(size, NULL); }
|
||||
void setPost(AIPostFieldPtr const& postdata, S32 size);
|
||||
void setPost(char const* data, S32 size) { setPost(new AIPostField(data), size); }
|
||||
void setPost(U32 size) { setPost_raw(size, NULL); }
|
||||
void setPost(AIPostFieldPtr const& postdata, U32 size);
|
||||
void setPost(char const* data, U32 size) { setPost(new AIPostField(data), size); }
|
||||
void setoptString(CURLoption option, std::string const& value);
|
||||
void addHeader(char const* str);
|
||||
void addHeaders(AIHTTPHeaders const& headers);
|
||||
|
||||
private:
|
||||
// Callback stubs.
|
||||
@@ -246,61 +313,106 @@ class CurlEasyRequest : public CurlEasyHandle {
|
||||
// Call this if the set callbacks are about to be invalidated.
|
||||
void revokeCallbacks(void);
|
||||
|
||||
protected:
|
||||
// Reset everything to the state it was in when this object was just created.
|
||||
// Called by BufferedCurlEasyRequest::resetState.
|
||||
void resetState(void);
|
||||
|
||||
private:
|
||||
// Called from applyDefaultOptions.
|
||||
void applyProxySettings(void);
|
||||
|
||||
// Used in applyProxySettings.
|
||||
// Used in applyDefaultOptions.
|
||||
static CURLcode curlCtxCallback(CURL* curl, void* sslctx, void* parm);
|
||||
|
||||
// Called from get_timeout_object and httptimeout.
|
||||
void create_timeout_object(ThreadSafeBufferedCurlEasyRequest* lockobj);
|
||||
|
||||
public:
|
||||
// Set default options that we want applied to all curl easy handles.
|
||||
void applyDefaultOptions(void);
|
||||
|
||||
// Prepare the request for adding it to a multi session, or calling perform.
|
||||
// This actually adds the headers that were collected with addHeader.
|
||||
void finalizeRequest(std::string const& url);
|
||||
void finalizeRequest(std::string const& url, AIHTTPTimeoutPolicy const& policy, AICurlEasyRequestStateMachine* state_machine);
|
||||
|
||||
// Store result code that is returned by getResult.
|
||||
void store_result(CURLcode result) { mResult = result; }
|
||||
// Last second initialization. Called from MultiHandle::add_easy_request.
|
||||
void set_timeout_opts(void);
|
||||
|
||||
// Called when the curl easy handle is done.
|
||||
void done(AICurlEasyRequest_wat& curl_easy_request_w) { finished(curl_easy_request_w); }
|
||||
public:
|
||||
// Called by MultiHandle::finish_easy_request() to store result code that is returned by getResult.
|
||||
void storeResult(CURLcode result) { mResult = result; }
|
||||
|
||||
// Fill info with the transfer info.
|
||||
void getTransferInfo(AICurlInterface::TransferInfo* info);
|
||||
// Called by MultiHandle::finish_easy_request() when the curl easy handle is done.
|
||||
void done(AICurlEasyRequest_wat& curl_easy_request_w, CURLcode result)
|
||||
{
|
||||
if (mTimeout)
|
||||
{
|
||||
// Update timeout administration.
|
||||
mTimeout->done(curl_easy_request_w, result);
|
||||
}
|
||||
finished(curl_easy_request_w);
|
||||
}
|
||||
|
||||
// Called by in case of an error.
|
||||
void print_diagnostics(CURLcode code);
|
||||
|
||||
// Called by MultiHandle::check_run_count() to fill info with the transfer info.
|
||||
void getTransferInfo(AITransferInfo* info);
|
||||
|
||||
// If result != CURLE_FAILED_INIT then also info was filled.
|
||||
void getResult(CURLcode* result, AICurlInterface::TransferInfo* info = NULL);
|
||||
void getResult(CURLcode* result, AITransferInfo* info = NULL);
|
||||
|
||||
// For debugging purposes.
|
||||
void print_curl_timings(void) const;
|
||||
|
||||
private:
|
||||
curl_slist* mHeaders;
|
||||
bool mRequestFinalized;
|
||||
AICurlEasyHandleEvents* mEventsTarget;
|
||||
CURLcode mResult;
|
||||
AICurlEasyHandleEvents* mHandleEventsTarget;
|
||||
CURLcode mResult; //AIFIXME: this does not belong in the request object, but belongs in the response object.
|
||||
|
||||
private:
|
||||
// This class may only be created by constructing a ThreadSafeCurlEasyRequest.
|
||||
friend class ThreadSafeCurlEasyRequest;
|
||||
AIHTTPTimeoutPolicy const* mTimeoutPolicy;
|
||||
std::string mLowercaseHostname; // Lowercase hostname (canonicalized) extracted from the url.
|
||||
LLPointer<curlthread::HTTPTimeout> mTimeout;// Timeout administration object associated with last created CurlSocketInfo.
|
||||
bool mTimeoutIsOrphan; // Set to true when mTimeout is not (yet) associated with a CurlSocketInfo.
|
||||
#if defined(CWDEBUG) || defined(DEBUG_CURLIO)
|
||||
public:
|
||||
bool mDebugIsHeadOrGetMethod;
|
||||
#endif
|
||||
|
||||
public:
|
||||
// These two are only valid after finalizeRequest.
|
||||
AIHTTPTimeoutPolicy const* getTimeoutPolicy(void) const { return mTimeoutPolicy; }
|
||||
std::string const& getLowercaseHostname(void) const { return mLowercaseHostname; }
|
||||
// Called by CurlSocketInfo to allow access to the last (after a redirect) HTTPTimeout object related to this request.
|
||||
// This creates mTimeout (unless mTimeoutIsOrphan is set in which case it adopts the orphan).
|
||||
LLPointer<curlthread::HTTPTimeout>& get_timeout_object(ThreadSafeBufferedCurlEasyRequest* lockobj);
|
||||
// Accessor for mTimeout with optional creation of orphaned object (if lockobj != NULL).
|
||||
LLPointer<curlthread::HTTPTimeout>& httptimeout(ThreadSafeBufferedCurlEasyRequest* lockobj = NULL) { if (lockobj && !mTimeout) create_timeout_object(lockobj); return mTimeout; }
|
||||
// Return true if no data has been received on the latest socket (if any) for too long.
|
||||
bool has_stalled(void) const { return mTimeout && mTimeout->has_stalled(); }
|
||||
|
||||
protected:
|
||||
// This class may only be created as base class of BufferedCurlEasyRequest.
|
||||
// Throws AICurlNoEasyHandle.
|
||||
CurlEasyRequest(void) :
|
||||
mHeaders(NULL), mRequestFinalized(false), mEventsTarget(NULL), mResult(CURLE_FAILED_INIT)
|
||||
{ applyDefaultOptions(); }
|
||||
CurlEasyRequest(void) : mHeaders(NULL), mHandleEventsTarget(NULL), mResult(CURLE_FAILED_INIT), mTimeoutPolicy(NULL), mTimeoutIsOrphan(false)
|
||||
#if defined(CWDEBUG) || defined(DEBUG_CURLIO)
|
||||
, mDebugIsHeadOrGetMethod(false)
|
||||
#endif
|
||||
{ applyDefaultOptions(); }
|
||||
public:
|
||||
~CurlEasyRequest();
|
||||
|
||||
public:
|
||||
// Post-initialization, set the parent to pass the events to.
|
||||
void send_events_to(AICurlEasyHandleEvents* target) { mEventsTarget = target; }
|
||||
void send_handle_events_to(AICurlEasyHandleEvents* target) { mHandleEventsTarget = target; }
|
||||
|
||||
// For debugging purposes
|
||||
bool is_finalized(void) const { return mRequestFinalized; }
|
||||
bool is_finalized(void) const { return mTimeoutPolicy; }
|
||||
|
||||
// Return pointer to the ThreadSafe (wrapped) version of this object.
|
||||
ThreadSafeCurlEasyRequest* get_lockobj(void);
|
||||
inline ThreadSafeBufferedCurlEasyRequest* get_lockobj(void);
|
||||
inline ThreadSafeBufferedCurlEasyRequest const* get_lockobj(void) const;
|
||||
|
||||
protected:
|
||||
// Pass events to parent.
|
||||
@@ -309,47 +421,48 @@ class CurlEasyRequest : public CurlEasyHandle {
|
||||
/*virtual*/ void removed_from_multi_handle(AICurlEasyRequest_wat& curl_easy_request_w);
|
||||
};
|
||||
|
||||
// Buffers used by the AICurlInterface::Request API.
|
||||
// Curl callbacks write into and read from these buffers.
|
||||
// The interface with the rest of the code is through AICurlInterface::Responder.
|
||||
//
|
||||
// The lifetime of a CurlResponderBuffer is slightly shorter than its
|
||||
// associated CurlEasyRequest; this class can only be created as base class
|
||||
// of ThreadSafeBufferedCurlEasyRequest, and is therefore constructed after
|
||||
// the construction of the associated CurlEasyRequest and destructed before it.
|
||||
// Hence, it's safe to use get_lockobj() and through that access the CurlEasyRequest
|
||||
// object at all times.
|
||||
//
|
||||
// A CurlResponderBuffer is thus created when a ThreadSafeBufferedCurlEasyRequest
|
||||
// is created which only happens by creating a AICurlEasyRequest(true) instance,
|
||||
// and when the last AICurlEasyRequest is deleted, then the ThreadSafeBufferedCurlEasyRequest
|
||||
// is deleted and the CurlResponderBuffer destructed.
|
||||
class CurlResponderBuffer : protected AICurlEasyHandleEvents {
|
||||
// This class adds input/output buffers to the request and hooks up the libcurl callbacks to use those buffers.
|
||||
// Received data is partially decoded and made available through various member functions.
|
||||
class BufferedCurlEasyRequest : public CurlEasyRequest {
|
||||
public:
|
||||
void resetState(AICurlEasyRequest_wat& curl_easy_request_w);
|
||||
void prepRequest(AICurlEasyRequest_wat& buffered_curl_easy_request_w, std::vector<std::string> const& headers, AICurlInterface::ResponderPtr responder, S32 time_out = 0, bool post = false);
|
||||
// The type of the used buffers.
|
||||
typedef boost::shared_ptr<LLBufferArray> buffer_ptr_t;
|
||||
|
||||
LLIOPipe::buffer_ptr_t& getInput(void) { return mInput; }
|
||||
std::stringstream& getHeaderOutput(void) { return mHeaderOutput; }
|
||||
LLIOPipe::buffer_ptr_t& getOutput(void) { return mOutput; }
|
||||
void resetState(void);
|
||||
void prepRequest(AICurlEasyRequest_wat& buffered_curl_easy_request_w, AIHTTPHeaders const& headers, LLHTTPClient::ResponderPtr responder);
|
||||
|
||||
// Called if libcurl doesn't deliver within CurlRequestTimeOut seconds.
|
||||
buffer_ptr_t& getInput(void) { return mInput; }
|
||||
buffer_ptr_t& getOutput(void) { return mOutput; }
|
||||
|
||||
// Called if libcurl doesn't deliver within AIHTTPTimeoutPolicy::mMaximumTotalDelay seconds.
|
||||
void timed_out(void);
|
||||
|
||||
// Called after removed_from_multi_handle was called.
|
||||
void processOutput(AICurlEasyRequest_wat& curl_easy_request_w);
|
||||
void processOutput(void);
|
||||
|
||||
// Do not write more than this amount.
|
||||
//void setBodyLimit(U32 size) { mBodyLimit = size; }
|
||||
|
||||
// Post-initialization, set the parent to pass the events to.
|
||||
void send_buffer_events_to(AICurlResponderBufferEvents* target) { mBufferEventsTarget = target; }
|
||||
|
||||
protected:
|
||||
/*virtual*/ void added_to_multi_handle(AICurlEasyRequest_wat& curl_easy_request_w);
|
||||
/*virtual*/ void finished(AICurlEasyRequest_wat& curl_easy_request_w);
|
||||
/*virtual*/ void removed_from_multi_handle(AICurlEasyRequest_wat& curl_easy_request_w);
|
||||
// Events from this class.
|
||||
/*virtual*/ void received_HTTP_header(void);
|
||||
/*virtual*/ void received_header(std::string const& key, std::string const& value);
|
||||
/*virtual*/ void completed_headers(U32 status, std::string const& reason, AITransferInfo* info);
|
||||
|
||||
private:
|
||||
LLIOPipe::buffer_ptr_t mInput;
|
||||
buffer_ptr_t mInput;
|
||||
U8* mLastRead; // Pointer into mInput where we last stopped reading (or NULL to start at the beginning).
|
||||
std::stringstream mHeaderOutput;
|
||||
LLIOPipe::buffer_ptr_t mOutput;
|
||||
AICurlInterface::ResponderPtr mResponder;
|
||||
buffer_ptr_t mOutput;
|
||||
LLHTTPClient::ResponderPtr mResponder;
|
||||
//U32 mBodyLimit; // From the old LLURLRequestDetail::mBodyLimit, but never used.
|
||||
U32 mStatus; // HTTP status, decoded from the first header line.
|
||||
std::string mReason; // The "reason" from the same header line.
|
||||
S32 mRequestTransferedBytes;
|
||||
S32 mResponseTransferedBytes;
|
||||
AICurlResponderBufferEvents* mBufferEventsTarget;
|
||||
|
||||
public:
|
||||
static LLChannelDescriptors const sChannels; // Channel object for mInput (channel out()) and mOutput (channel in()).
|
||||
@@ -357,64 +470,60 @@ class CurlResponderBuffer : protected AICurlEasyHandleEvents {
|
||||
private:
|
||||
// This class may only be created by constructing a ThreadSafeBufferedCurlEasyRequest.
|
||||
friend class ThreadSafeBufferedCurlEasyRequest;
|
||||
CurlResponderBuffer(void);
|
||||
BufferedCurlEasyRequest(void);
|
||||
public:
|
||||
~CurlResponderBuffer();
|
||||
~BufferedCurlEasyRequest();
|
||||
|
||||
private:
|
||||
static size_t curlWriteCallback(char* data, size_t size, size_t nmemb, void* user_data);
|
||||
static size_t curlReadCallback(char* data, size_t size, size_t nmemb, void* user_data);
|
||||
static size_t curlHeaderCallback(char* data, size_t size, size_t nmemb, void* user_data);
|
||||
|
||||
// Called from curlHeaderCallback.
|
||||
void setStatusAndReason(U32 status, std::string const& reason);
|
||||
|
||||
public:
|
||||
// Return pointer to the ThreadSafe (wrapped) version of this object.
|
||||
ThreadSafeBufferedCurlEasyRequest* get_lockobj(void);
|
||||
ThreadSafeBufferedCurlEasyRequest const* get_lockobj(void) const;
|
||||
|
||||
// Return true when prepRequest was already called and the object has not been
|
||||
// invalidated as a result of calling timed_out().
|
||||
bool isValid(void) const { return mResponder; }
|
||||
};
|
||||
|
||||
// This class wraps CurlEasyRequest for thread-safety and adds a reference counter so we can
|
||||
inline ThreadSafeBufferedCurlEasyRequest* CurlEasyRequest::get_lockobj(void)
|
||||
{
|
||||
return static_cast<BufferedCurlEasyRequest*>(this)->get_lockobj();
|
||||
}
|
||||
|
||||
inline ThreadSafeBufferedCurlEasyRequest const* CurlEasyRequest::get_lockobj(void) const
|
||||
{
|
||||
return static_cast<BufferedCurlEasyRequest const*>(this)->get_lockobj();
|
||||
}
|
||||
|
||||
// This class wraps BufferedCurlEasyRequest for thread-safety and adds a reference counter so we can
|
||||
// copy it around cheaply and it gets destructed automatically when the last instance is deleted.
|
||||
// It guarantees that the CURL* handle is never used concurrently, which is not allowed by libcurl.
|
||||
// As AIThreadSafeSimpleDC contains a mutex, it cannot be copied. Therefore we need a reference counter for this object.
|
||||
class ThreadSafeCurlEasyRequest : public AIThreadSafeSimple<CurlEasyRequest> {
|
||||
class ThreadSafeBufferedCurlEasyRequest : public AIThreadSafeSimple<BufferedCurlEasyRequest> {
|
||||
public:
|
||||
// Throws AICurlNoEasyHandle.
|
||||
ThreadSafeCurlEasyRequest(void) : mReferenceCount(0)
|
||||
{ new (ptr()) CurlEasyRequest;
|
||||
Dout(dc::curl, "Creating ThreadSafeCurlEasyRequest with this = " << (void*)this); }
|
||||
virtual ~ThreadSafeCurlEasyRequest()
|
||||
{ Dout(dc::curl, "Destructing ThreadSafeCurlEasyRequest with this = " << (void*)this); }
|
||||
|
||||
// Returns true if this is a base class of ThreadSafeBufferedCurlEasyRequest.
|
||||
virtual bool isBuffered(void) const { return false; }
|
||||
ThreadSafeBufferedCurlEasyRequest(void) : mReferenceCount(0)
|
||||
{ new (ptr()) BufferedCurlEasyRequest;
|
||||
Dout(dc::curl, "Creating ThreadSafeBufferedCurlEasyRequest with this = " << (void*)this); }
|
||||
virtual ~ThreadSafeBufferedCurlEasyRequest()
|
||||
{ Dout(dc::curl, "Destructing ThreadSafeBufferedCurlEasyRequest with this = " << (void*)this); }
|
||||
|
||||
private:
|
||||
LLAtomicU32 mReferenceCount;
|
||||
|
||||
friend void intrusive_ptr_add_ref(ThreadSafeCurlEasyRequest* p); // Called by boost::intrusive_ptr when a new copy of a boost::intrusive_ptr<ThreadSafeCurlEasyRequest> is made.
|
||||
friend void intrusive_ptr_release(ThreadSafeCurlEasyRequest* p); // Called by boost::intrusive_ptr when a boost::intrusive_ptr<ThreadSafeCurlEasyRequest> is destroyed.
|
||||
};
|
||||
|
||||
// Same as the above but adds a CurlResponderBuffer. The latter has its own locking in order to
|
||||
// allow casting the underlying CurlEasyRequest to ThreadSafeCurlEasyRequest, independent of
|
||||
// what class it is part of: ThreadSafeCurlEasyRequest or ThreadSafeBufferedCurlEasyRequest.
|
||||
// The virtual destructor of ThreadSafeCurlEasyRequest allows to treat each easy handle transparently
|
||||
// as a ThreadSafeCurlEasyRequest object, or optionally dynamic_cast it to a ThreadSafeBufferedCurlEasyRequest.
|
||||
// Note: the order of these base classes is important: AIThreadSafeSimple<CurlResponderBuffer> is now
|
||||
// destructed before ThreadSafeCurlEasyRequest is.
|
||||
class ThreadSafeBufferedCurlEasyRequest : public ThreadSafeCurlEasyRequest, public AIThreadSafeSimple<CurlResponderBuffer> {
|
||||
public:
|
||||
// Throws AICurlNoEasyHandle.
|
||||
ThreadSafeBufferedCurlEasyRequest(void) { new (AIThreadSafeSimple<CurlResponderBuffer>::ptr()) CurlResponderBuffer; }
|
||||
|
||||
/*virtual*/ bool isBuffered(void) const { return true; }
|
||||
friend void intrusive_ptr_add_ref(ThreadSafeBufferedCurlEasyRequest* p); // Called by boost::intrusive_ptr when a new copy of a boost::intrusive_ptr<ThreadSafeBufferedCurlEasyRequest> is made.
|
||||
friend void intrusive_ptr_release(ThreadSafeBufferedCurlEasyRequest* p); // Called by boost::intrusive_ptr when a boost::intrusive_ptr<ThreadSafeBufferedCurlEasyRequest> is destroyed.
|
||||
};
|
||||
|
||||
// The curl easy request type wrapped in a reference counting pointer.
|
||||
typedef boost::intrusive_ptr<AICurlPrivate::ThreadSafeCurlEasyRequest> CurlEasyRequestPtr;
|
||||
typedef boost::intrusive_ptr<AICurlPrivate::ThreadSafeBufferedCurlEasyRequest> BufferedCurlEasyRequestPtr;
|
||||
|
||||
// This class wraps CURLM*'s.
|
||||
// It guarantees that a pointer is cleaned up when no longer needed, as required by libcurl.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -33,6 +33,7 @@
|
||||
|
||||
#include "aicurl.h"
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
|
||||
#undef AICurlPrivate
|
||||
|
||||
@@ -59,7 +60,7 @@ class MultiHandle : public CurlMultiHandle
|
||||
~MultiHandle();
|
||||
|
||||
// Add/remove an easy handle to/from a multi session.
|
||||
CURLMcode add_easy_request(AICurlEasyRequest const& easy_request);
|
||||
void add_easy_request(AICurlEasyRequest const& easy_request);
|
||||
CURLMcode remove_easy_request(AICurlEasyRequest const& easy_request, bool as_per_command = false);
|
||||
|
||||
// Reads/writes available data from a particular socket (non-blocking).
|
||||
@@ -73,14 +74,17 @@ class MultiHandle : public CurlMultiHandle
|
||||
|
||||
private:
|
||||
typedef std::set<AICurlEasyRequest, AICurlEasyRequestCompare> addedEasyRequests_type;
|
||||
addedEasyRequests_type mAddedEasyRequests;
|
||||
|
||||
bool mHandleAddedOrRemoved; // Set when an easy handle was added or removed, reset in check_run_count().
|
||||
int mPrevRunningHandles; // The last value of mRunningHandles that check_run_count() was called with.
|
||||
int mRunningHandles; // The last value returned by curl_multi_socket_action.
|
||||
long mTimeOut; // The last time out in ms as set by the callback CURLMOPT_TIMERFUNCTION.
|
||||
addedEasyRequests_type mAddedEasyRequests; // All easy requests currently added to the multi handle.
|
||||
int mRunningHandles; // The last value returned by curl_multi_socket_action.
|
||||
long mTimeout; // The last timeout in ms as set by the callback CURLMOPT_TIMERFUNCTION.
|
||||
|
||||
private:
|
||||
// Store result and trigger events for easy request.
|
||||
void finish_easy_request(AICurlEasyRequest const& easy_request, CURLcode result);
|
||||
// Remove easy request at iter (must exist).
|
||||
// Note that it's possible that a new request from mQueuedRequests is inserted before iter.
|
||||
CURLMcode remove_easy_request(addedEasyRequests_type::iterator const& iter, bool as_per_command);
|
||||
|
||||
static int socket_callback(CURL* easy, curl_socket_t s, int action, void* userp, void* socketp);
|
||||
static int timer_callback(CURLM* multi, long timeout_ms, void* userp);
|
||||
|
||||
@@ -89,17 +93,24 @@ class MultiHandle : public CurlMultiHandle
|
||||
int getRunningHandles(void) const { return mRunningHandles; }
|
||||
|
||||
// Returns how long to wait for socket action before calling socket_action(CURL_SOCKET_TIMEOUT, 0), in ms.
|
||||
int getTimeOut(void) const { return mTimeOut; }
|
||||
int getTimeout(void) const { return mTimeout; }
|
||||
|
||||
// This is called before sleeping, after calling (one or more times) socket_action.
|
||||
void check_run_count(void);
|
||||
|
||||
// Called from the main loop every time select() timed out.
|
||||
void handle_stalls(void);
|
||||
|
||||
public:
|
||||
//-----------------------------------------------------------------------------
|
||||
// Curl socket administration:
|
||||
|
||||
PollSet* mReadPollSet;
|
||||
PollSet* mWritePollSet;
|
||||
|
||||
private:
|
||||
// Temporary throttling hack.
|
||||
std::deque<AICurlEasyRequest> mQueuedRequests; // Waiting (throttled) requests.
|
||||
};
|
||||
|
||||
} // namespace curlthread
|
||||
|
||||
153
indra/llmessage/aihttpheaders.cpp
Normal file
153
indra/llmessage/aihttpheaders.cpp
Normal file
@@ -0,0 +1,153 @@
|
||||
/**
|
||||
* @file aihttpheaders.cpp
|
||||
* @brief Implementation of AIHTTPHeaders
|
||||
*
|
||||
* Copyright (c) 2012, Aleric Inglewood.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* There are special exceptions to the terms and conditions of the GPL as
|
||||
* it is applied to this Source Code. View the full text of the exception
|
||||
* in the file doc/FLOSS-exception.txt in this software distribution.
|
||||
*
|
||||
* CHANGELOG
|
||||
* and additional copyright holders.
|
||||
*
|
||||
* 15/08/2012
|
||||
* Initial version, written by Aleric Inglewood @ SL
|
||||
*/
|
||||
|
||||
#include "sys.h"
|
||||
#include "aihttpheaders.h"
|
||||
#include <curl/curl.h>
|
||||
#ifdef DEBUG_CURLIO
|
||||
#include "debug_libcurl.h"
|
||||
#endif
|
||||
|
||||
AIHTTPHeaders::AIHTTPHeaders(std::string const& key, std::string const& value) : mContainer(new Container)
|
||||
{
|
||||
addHeader(key, value);
|
||||
}
|
||||
|
||||
bool AIHTTPHeaders::addHeader(std::string const& key, std::string const& value, op_type op)
|
||||
{
|
||||
if (!mContainer)
|
||||
{
|
||||
mContainer = new Container;
|
||||
}
|
||||
insert_t res = mContainer->mKeyValuePairs.insert(container_t::value_type(key, value));
|
||||
bool key_already_exists = !res.second;
|
||||
if (key_already_exists)
|
||||
{
|
||||
llassert_always(op != new_header);
|
||||
if (op == replace_if_exists)
|
||||
res.first->second = value;
|
||||
}
|
||||
return key_already_exists;
|
||||
}
|
||||
|
||||
void AIHTTPHeaders::append_to(curl_slist*& slist) const
|
||||
{
|
||||
if (!mContainer)
|
||||
return;
|
||||
container_t::const_iterator const end = mContainer->mKeyValuePairs.end();
|
||||
for (container_t::const_iterator iter = mContainer->mKeyValuePairs.begin(); iter != end; ++iter)
|
||||
{
|
||||
slist = curl_slist_append(slist, llformat("%s: %s", iter->first.c_str(), iter->second.c_str()).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
bool AIHTTPHeaders::hasHeader(std::string const& key) const
|
||||
{
|
||||
return !mContainer ? false : (mContainer->mKeyValuePairs.find(key) != mContainer->mKeyValuePairs.end());
|
||||
}
|
||||
|
||||
bool AIHTTPHeaders::getValue(std::string const& key, std::string& value_out) const
|
||||
{
|
||||
AIHTTPHeaders::container_t::const_iterator iter;
|
||||
if (!mContainer || (iter = mContainer->mKeyValuePairs.find(key)) == mContainer->mKeyValuePairs.end())
|
||||
return false;
|
||||
value_out = iter->second;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, AIHTTPHeaders const& headers)
|
||||
{
|
||||
os << '{';
|
||||
if (headers.mContainer)
|
||||
{
|
||||
bool first = true;
|
||||
AIHTTPHeaders::container_t::const_iterator const end = headers.mContainer->mKeyValuePairs.end();
|
||||
for (AIHTTPHeaders::container_t::const_iterator iter = headers.mContainer->mKeyValuePairs.begin(); iter != end; ++iter)
|
||||
{
|
||||
if (!first)
|
||||
os << ", ";
|
||||
os << '"' << iter->first << ": " << iter->second << '"';
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
os << '}';
|
||||
return os;
|
||||
}
|
||||
|
||||
void AIHTTPReceivedHeaders::addHeader(std::string const& key, std::string const& value)
|
||||
{
|
||||
if (!mContainer)
|
||||
{
|
||||
mContainer = new Container;
|
||||
}
|
||||
mContainer->mKeyValuePairs.insert(container_t::value_type(key, value));
|
||||
}
|
||||
|
||||
bool AIHTTPReceivedHeaders::hasHeader(std::string const& key) const
|
||||
{
|
||||
return !mContainer ? false : (mContainer->mKeyValuePairs.find(key) != mContainer->mKeyValuePairs.end());
|
||||
}
|
||||
|
||||
bool AIHTTPReceivedHeaders::getFirstValue(std::string const& key, std::string& value_out) const
|
||||
{
|
||||
AIHTTPReceivedHeaders::container_t::const_iterator iter;
|
||||
if (!mContainer || (iter = mContainer->mKeyValuePairs.find(key)) == mContainer->mKeyValuePairs.end())
|
||||
return false;
|
||||
value_out = iter->second;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AIHTTPReceivedHeaders::getValues(std::string const& key, range_type& value_out) const
|
||||
{
|
||||
if (!mContainer)
|
||||
return false;
|
||||
value_out = mContainer->mKeyValuePairs.equal_range(key);
|
||||
return value_out.first != value_out.second;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, AIHTTPReceivedHeaders const& headers)
|
||||
{
|
||||
os << '{';
|
||||
if (headers.mContainer)
|
||||
{
|
||||
bool first = true;
|
||||
AIHTTPReceivedHeaders::container_t::const_iterator const end = headers.mContainer->mKeyValuePairs.end();
|
||||
for (AIHTTPReceivedHeaders::container_t::const_iterator iter = headers.mContainer->mKeyValuePairs.begin(); iter != end; ++iter)
|
||||
{
|
||||
if (!first)
|
||||
os << ", ";
|
||||
os << '"' << iter->first << ": " << iter->second << '"';
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
os << '}';
|
||||
return os;
|
||||
}
|
||||
|
||||
158
indra/llmessage/aihttpheaders.h
Normal file
158
indra/llmessage/aihttpheaders.h
Normal file
@@ -0,0 +1,158 @@
|
||||
/**
|
||||
* @file aihttpheaders.h
|
||||
* @brief Keep a list of HTTP headers.
|
||||
*
|
||||
* Copyright (c) 2012, Aleric Inglewood.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* There are special exceptions to the terms and conditions of the GPL as
|
||||
* it is applied to this Source Code. View the full text of the exception
|
||||
* in the file doc/FLOSS-exception.txt in this software distribution.
|
||||
*
|
||||
* CHANGELOG
|
||||
* and additional copyright holders.
|
||||
*
|
||||
* 15/08/2012
|
||||
* Initial version, written by Aleric Inglewood @ SL
|
||||
* 21/10/2012
|
||||
* Added AIHTTPReceivedHeaders
|
||||
*/
|
||||
|
||||
#ifndef AIHTTPHEADERS_H
|
||||
#define AIHTTPHEADERS_H
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <iosfwd>
|
||||
#include <algorithm>
|
||||
#include "llpointer.h"
|
||||
#include "llthread.h" // LLThreadSafeRefCount
|
||||
|
||||
extern "C" struct curl_slist;
|
||||
|
||||
class AIHTTPHeaders {
|
||||
public:
|
||||
enum op_type
|
||||
{
|
||||
new_header, // The inserted header must be the first one.
|
||||
replace_if_exists, // If a header of this type already exists, replace it. Otherwise add the header.
|
||||
keep_existing_header // If a header of this type already exists, do nothing.
|
||||
};
|
||||
|
||||
// Construct an empty container.
|
||||
AIHTTPHeaders(void) { }
|
||||
|
||||
// Construct a container with a single header.
|
||||
AIHTTPHeaders(std::string const& key, std::string const& value);
|
||||
|
||||
// Clear all headers.
|
||||
void clear(void) { if (mContainer) mContainer->mKeyValuePairs.clear(); }
|
||||
|
||||
// Add a header. Returns true if the header already existed.
|
||||
bool addHeader(std::string const& key, std::string const& value, op_type op = new_header);
|
||||
|
||||
// Return true if there are no headers associated with this object.
|
||||
bool empty(void) const { return !mContainer || mContainer->mKeyValuePairs.empty(); }
|
||||
|
||||
// Return true if the header already exists.
|
||||
bool hasHeader(std::string const& key) const;
|
||||
|
||||
// Return true if key exists and fill value_out with the value. Return false otherwise.
|
||||
bool getValue(std::string const& key, std::string& value_out) const;
|
||||
|
||||
// Append the headers to slist.
|
||||
void append_to(curl_slist*& slist) const;
|
||||
|
||||
// For debug purposes.
|
||||
friend std::ostream& operator<<(std::ostream& os, AIHTTPHeaders const& headers);
|
||||
|
||||
private:
|
||||
typedef std::map<std::string, std::string> container_t;
|
||||
typedef std::pair<container_t::iterator, bool> insert_t;
|
||||
|
||||
struct Container : public LLThreadSafeRefCount {
|
||||
container_t mKeyValuePairs;
|
||||
};
|
||||
|
||||
LLPointer<Container> mContainer;
|
||||
};
|
||||
|
||||
// Functor that returns true if c1 is less than c2, ignoring bit 5.
|
||||
// The effect is that characters in the range a-z equivalent ordering with A-Z.
|
||||
// This function assumes UTF-8 or ASCII encoding!
|
||||
//
|
||||
// Note that other characters aren't important in the case of HTTP header keys;
|
||||
// however if one considers all printable ASCII characters, then this functor
|
||||
// also compares "@[\]^" equal to "`{|}~" (any other is either not printable or
|
||||
// would be equal to a not printable character).
|
||||
struct AIHTTPReceivedHeadersCharCompare {
|
||||
bool operator()(std::string::value_type c1, std::string::value_type c2) const
|
||||
{
|
||||
static std::string::value_type const bit5 = 0x20;
|
||||
return (c1 | bit5) < (c2 | bit5);
|
||||
}
|
||||
};
|
||||
|
||||
// Functor to lexiographically compare two HTTP header keys using the above predicate.
|
||||
// This means that for example "Content-Type" and "content-type" will have equivalent ordering.
|
||||
struct AIHTTPReceivedHeadersCompare {
|
||||
bool operator()(std::string const& h1, std::string const& h2) const
|
||||
{
|
||||
static AIHTTPReceivedHeadersCharCompare const predicate;
|
||||
return std::lexicographical_compare(h1.begin(), h1.end(), h2.begin(), h2.end(), predicate);
|
||||
}
|
||||
};
|
||||
|
||||
class AIHTTPReceivedHeaders {
|
||||
private:
|
||||
typedef std::multimap<std::string, std::string, AIHTTPReceivedHeadersCompare> container_t;
|
||||
|
||||
public:
|
||||
typedef container_t::const_iterator iterator_type;
|
||||
typedef std::pair<iterator_type, iterator_type> range_type;
|
||||
|
||||
// Construct an empty container.
|
||||
AIHTTPReceivedHeaders(void) { }
|
||||
|
||||
// Clear all headers.
|
||||
void clear(void) { if (mContainer) mContainer->mKeyValuePairs.clear(); }
|
||||
|
||||
// Add a header.
|
||||
void addHeader(std::string const& key, std::string const& value);
|
||||
|
||||
// Return true if there are no headers associated with this object.
|
||||
bool empty(void) const { return !mContainer || mContainer->mKeyValuePairs.empty(); }
|
||||
|
||||
// Return true if the header exists.
|
||||
bool hasHeader(std::string const& key) const;
|
||||
|
||||
// Return true if key exists and fill value_out with the value. Return false otherwise.
|
||||
bool getFirstValue(std::string const& key, std::string& value_out) const;
|
||||
|
||||
// Return true if key exists and fill value_out with all values. Return false otherwise.
|
||||
bool getValues(std::string const& key, range_type& value_out) const;
|
||||
|
||||
// For debug purposes.
|
||||
friend std::ostream& operator<<(std::ostream& os, AIHTTPReceivedHeaders const& headers);
|
||||
|
||||
private:
|
||||
struct Container : public LLThreadSafeRefCount {
|
||||
container_t mKeyValuePairs;
|
||||
};
|
||||
|
||||
LLPointer<Container> mContainer;
|
||||
};
|
||||
|
||||
#endif // AIHTTPHEADERS_H
|
||||
885
indra/llmessage/aihttptimeoutpolicy.cpp
Normal file
885
indra/llmessage/aihttptimeoutpolicy.cpp
Normal file
@@ -0,0 +1,885 @@
|
||||
/**
|
||||
* @file aihttptimeoutpolicy.cpp
|
||||
* @brief Implementation of AIHTTPTimeoutPolicy
|
||||
*
|
||||
* Copyright (c) 2012, Aleric Inglewood.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* There are special exceptions to the terms and conditions of the GPL as
|
||||
* it is applied to this Source Code. View the full text of the exception
|
||||
* in the file doc/FLOSS-exception.txt in this software distribution.
|
||||
*
|
||||
* CHANGELOG
|
||||
* and additional copyright holders.
|
||||
*
|
||||
* 24/08/2012
|
||||
* Initial version, written by Aleric Inglewood @ SL
|
||||
*/
|
||||
|
||||
#include "sys.h"
|
||||
#include "aihttptimeoutpolicy.h"
|
||||
#define NOMINMAX
|
||||
#include "llerror.h"
|
||||
#include "lldefs.h"
|
||||
#include "v3math.h"
|
||||
#include <vector>
|
||||
|
||||
//!
|
||||
// Timing of a HTML connection.
|
||||
//
|
||||
// Request call
|
||||
// |
|
||||
// v ... <--low speed time--> ... ... <--low speed time--> ...
|
||||
// <--request queued--><--DNS lookup--><--connect margin--><--data transfer to server--><--reply delay--><--data transfer from server-->
|
||||
// <------------------------------------------curl transaction----------------------------------------------------->
|
||||
// <--------------------------------------------------------------total delay---------------------------------------------------------->
|
||||
// |
|
||||
// v
|
||||
// finished
|
||||
// For now, low speed limit is the same for up and download: usually download is (much) higher, but we have to take into account that
|
||||
// there might also be multiple downloads at the same time, more than simultaneous uploads.
|
||||
|
||||
// Absolute maxima (min/max range):
|
||||
// These values are intuitively determined and rather arbitrary.
|
||||
|
||||
namespace {
|
||||
|
||||
U16 const ABS_min_DNS_lookup = 0; // Rationale: If a FAST roundtrip is demanded, then setting the DNS lookup grace time
|
||||
// at 0 seconds will not make a connection fail when the lookup takes 1 second, it
|
||||
// just means that no EXTRA time is added to the connect time.
|
||||
U16 const ABS_max_DNS_lookup = 300; // Waiting longer than 5 minutes never makes sense.
|
||||
|
||||
U16 const ABS_min_connect_time = 1; // Can't demand 0 seconds, and we deal with integer numbers here.
|
||||
U16 const ABS_max_connect_time = 30; // Making a TCP/IP connection should REALLY succeed within 30 seconds or we rather try again.
|
||||
|
||||
U16 const ABS_min_reply_delay = 1; // Can't demand 0 seconds, and we deal with integer numbers here.
|
||||
U16 const ABS_max_reply_delay = 120; // If the server needs more than 2 minutes to find the reply then something just HAS to be wrong :/.
|
||||
|
||||
U16 const ABS_min_low_speed_time = 4; // Intuitively, I think it makes no sense to average a download speed over less than 4 seconds.
|
||||
U16 const ABS_max_low_speed_time = 120; // Averaging it over a time considerably larger than the normal timeout periods makes no sense either.
|
||||
|
||||
U32 const ABS_min_low_speed_limit = 1; // In case you don't want to timeout when there is any data received at all.
|
||||
U32 const ABS_max_low_speed_limit = 1000000; // This limit is almost certainly higher than the maximum speed you get from the server.
|
||||
|
||||
U16 const ABS_min_transaction = 60; // This is an absurd low value for experimentation. In reality, you should control
|
||||
// termination of really slow connections through the low_speed settings.
|
||||
U16 const ABS_max_transaction = 1200; // Insane long time. Downloads a texture of 4 MB at 3.5 kB/s. Textures are compressed though ;).
|
||||
|
||||
U16 const ABS_min_total_delay = 60; // This is an absurd low value for experimentation. In reality, you should control
|
||||
// termination of really slow connections through the low_speed settings.
|
||||
U16 const ABS_max_total_delay = 3000; // Insane long time, for when someone wants to be ABSOLUTELY sure this isn't the bottleneck.
|
||||
|
||||
using namespace AIHTTPTimeoutPolicyOperators;
|
||||
|
||||
// Default policy values.
|
||||
U16 const AITP_default_DNS_lookup_grace = 60; // Allow for 60 seconds long DNS look ups.
|
||||
U16 const AITP_default_maximum_connect_time = 10; // Allow the SSL/TLS connection through a proxy, including handshakes, to take up to 10 seconds.
|
||||
U16 const AITP_default_maximum_reply_delay = 60; // Allow the server 60 seconds to do whatever it has to do before starting to send data.
|
||||
U16 const AITP_default_low_speed_time = 30; // If a transfer speed drops below AITP_default_low_speed_limit bytes/s for 30 seconds, terminate the transfer.
|
||||
U32 const AITP_default_low_speed_limit = 56000; // In bytes per second (use for CURLOPT_LOW_SPEED_LIMIT).
|
||||
U16 const AITP_default_maximum_curl_transaction = 300; // Allow large files to be transfered over slow connections.
|
||||
U16 const AITP_default_maximum_total_delay = 600; // Avoid "leaking" by terminating anything that wasn't completed after 10 minutes.
|
||||
|
||||
} // namespace
|
||||
|
||||
AIHTTPTimeoutPolicy& AIHTTPTimeoutPolicy::operator=(AIHTTPTimeoutPolicy const& rhs)
|
||||
{
|
||||
// You're not allowed to assign to a policy that is based on another policy.
|
||||
llassert(!mBase);
|
||||
mDNSLookupGrace = rhs.mDNSLookupGrace;
|
||||
mMaximumConnectTime = rhs.mMaximumConnectTime;
|
||||
mMaximumReplyDelay = rhs.mMaximumReplyDelay;
|
||||
mLowSpeedTime = rhs.mLowSpeedTime;
|
||||
mLowSpeedLimit = rhs.mLowSpeedLimit;
|
||||
mMaximumCurlTransaction = rhs.mMaximumCurlTransaction;
|
||||
mMaximumTotalDelay = rhs.mMaximumTotalDelay;
|
||||
return *this;
|
||||
}
|
||||
|
||||
AIHTTPTimeoutPolicy::AIHTTPTimeoutPolicy(char const* name,
|
||||
U16 dns_lookup_grace, U16 subsequent_connects, U16 reply_delay,
|
||||
U16 low_speed_time, U32 low_speed_limit,
|
||||
U16 curl_transaction, U16 total_delay) :
|
||||
mName(name),
|
||||
mBase(NULL),
|
||||
mDNSLookupGrace(dns_lookup_grace),
|
||||
mMaximumConnectTime(subsequent_connects),
|
||||
mMaximumReplyDelay(reply_delay),
|
||||
mLowSpeedTime(low_speed_time),
|
||||
mLowSpeedLimit(low_speed_limit),
|
||||
mMaximumCurlTransaction(curl_transaction),
|
||||
mMaximumTotalDelay(total_delay)
|
||||
{
|
||||
sanity_checks();
|
||||
}
|
||||
|
||||
struct PolicyOp {
|
||||
PolicyOp* mNext;
|
||||
PolicyOp(void) : mNext(NULL) { }
|
||||
PolicyOp(PolicyOp& op) : mNext(&op) { }
|
||||
virtual void perform(AIHTTPTimeoutPolicy* policy) const { }
|
||||
void nextOp(AIHTTPTimeoutPolicy* policy) const { if (mNext) mNext->perform(policy); }
|
||||
};
|
||||
|
||||
class AIHTTPTimeoutPolicyBase : public AIHTTPTimeoutPolicy {
|
||||
private:
|
||||
std::vector<AIHTTPTimeoutPolicy*> mDerived; // Policies derived from this one.
|
||||
|
||||
public:
|
||||
AIHTTPTimeoutPolicyBase(U16 dns_lookup_grace, U16 subsequent_connects, U16 reply_delay,
|
||||
U16 low_speed_time, U32 low_speed_limit,
|
||||
U16 curl_transaction, U16 total_delay) :
|
||||
AIHTTPTimeoutPolicy(NULL, dns_lookup_grace, subsequent_connects, reply_delay, low_speed_time, low_speed_limit, curl_transaction, total_delay) { }
|
||||
|
||||
// Derive base from base.
|
||||
AIHTTPTimeoutPolicyBase(AIHTTPTimeoutPolicyBase& rhs, PolicyOp const& op = PolicyOp()) : AIHTTPTimeoutPolicy(rhs) { op.perform(this); }
|
||||
|
||||
// Called for every derived policy.
|
||||
void derived(AIHTTPTimeoutPolicy* derived) { mDerived.push_back(derived); }
|
||||
|
||||
// Provide public acces to sDebugSettingsCurlTimeout for this compilation unit.
|
||||
static AIHTTPTimeoutPolicyBase& getDebugSettingsCurlTimeout(void) { return sDebugSettingsCurlTimeout; }
|
||||
|
||||
protected:
|
||||
friend void AIHTTPTimeoutPolicy::setDefaultCurlTimeout(AIHTTPTimeoutPolicy const& timeout);
|
||||
AIHTTPTimeoutPolicyBase& operator=(AIHTTPTimeoutPolicy const& rhs);
|
||||
};
|
||||
|
||||
AIHTTPTimeoutPolicy::AIHTTPTimeoutPolicy(AIHTTPTimeoutPolicy& base) :
|
||||
mName(NULL),
|
||||
mBase(static_cast<AIHTTPTimeoutPolicyBase*>(&base)),
|
||||
mDNSLookupGrace(base.mDNSLookupGrace),
|
||||
mMaximumConnectTime(base.mMaximumConnectTime),
|
||||
mMaximumReplyDelay(base.mMaximumReplyDelay),
|
||||
mLowSpeedTime(base.mLowSpeedTime),
|
||||
mLowSpeedLimit(base.mLowSpeedLimit),
|
||||
mMaximumCurlTransaction(base.mMaximumCurlTransaction),
|
||||
mMaximumTotalDelay(base.mMaximumTotalDelay)
|
||||
{
|
||||
}
|
||||
|
||||
AIHTTPTimeoutPolicyBase& AIHTTPTimeoutPolicyBase::operator=(AIHTTPTimeoutPolicy const& rhs)
|
||||
{
|
||||
AIHTTPTimeoutPolicy::operator=(rhs);
|
||||
return *this;
|
||||
}
|
||||
|
||||
AIHTTPTimeoutPolicy::AIHTTPTimeoutPolicy(char const* name, AIHTTPTimeoutPolicyBase& base) :
|
||||
mName(name),
|
||||
mBase(&base),
|
||||
mDNSLookupGrace(mBase->mDNSLookupGrace),
|
||||
mMaximumConnectTime(mBase->mMaximumConnectTime),
|
||||
mMaximumReplyDelay(mBase->mMaximumReplyDelay),
|
||||
mLowSpeedTime(mBase->mLowSpeedTime),
|
||||
mLowSpeedLimit(mBase->mLowSpeedLimit),
|
||||
mMaximumCurlTransaction(mBase->mMaximumCurlTransaction),
|
||||
mMaximumTotalDelay(mBase->mMaximumTotalDelay)
|
||||
{
|
||||
sNameMap.insert(namemap_t::value_type(name, this));
|
||||
// Register for changes to the base policy.
|
||||
mBase->derived(this);
|
||||
}
|
||||
|
||||
//static
|
||||
void AIHTTPTimeoutPolicy::setDefaultCurlTimeout(AIHTTPTimeoutPolicy const& timeout)
|
||||
{
|
||||
sDebugSettingsCurlTimeout = timeout;
|
||||
llinfos << "CurlTimeout Debug Settings now"
|
||||
": DNSLookup: " << sDebugSettingsCurlTimeout.mDNSLookupGrace <<
|
||||
"; Connect: " << sDebugSettingsCurlTimeout.mMaximumConnectTime <<
|
||||
"; ReplyDelay: " << sDebugSettingsCurlTimeout.mMaximumReplyDelay <<
|
||||
"; LowSpeedTime: " << sDebugSettingsCurlTimeout.mLowSpeedTime <<
|
||||
"; LowSpeedLimit: " << sDebugSettingsCurlTimeout.mLowSpeedLimit <<
|
||||
"; MaxTransaction: " << sDebugSettingsCurlTimeout.mMaximumCurlTransaction <<
|
||||
"; MaxTotalDelay: " << sDebugSettingsCurlTimeout.mMaximumTotalDelay << llendl;
|
||||
if (sDebugSettingsCurlTimeout.mDNSLookupGrace < AITP_default_DNS_lookup_grace)
|
||||
{
|
||||
llwarns << "CurlTimeoutDNSLookup (" << sDebugSettingsCurlTimeout.mDNSLookupGrace << ") is lower than the built-in default value (" << AITP_default_DNS_lookup_grace << ")." << llendl;
|
||||
}
|
||||
if (sDebugSettingsCurlTimeout.mMaximumConnectTime < AITP_default_maximum_connect_time)
|
||||
{
|
||||
llwarns << "CurlTimeoutConnect (" << sDebugSettingsCurlTimeout.mMaximumConnectTime << ") is lower than the built-in default value (" << AITP_default_maximum_connect_time << ")." << llendl;
|
||||
}
|
||||
if (sDebugSettingsCurlTimeout.mMaximumReplyDelay < AITP_default_maximum_reply_delay)
|
||||
{
|
||||
llwarns << "CurlTimeoutReplyDelay (" << sDebugSettingsCurlTimeout.mMaximumReplyDelay << ") is lower than the built-in default value (" << AITP_default_maximum_reply_delay << ")." << llendl;
|
||||
}
|
||||
if (sDebugSettingsCurlTimeout.mLowSpeedTime < AITP_default_low_speed_time)
|
||||
{
|
||||
llwarns << "CurlTimeoutLowSpeedTime (" << sDebugSettingsCurlTimeout.mLowSpeedTime << ") is lower than the built-in default value (" << AITP_default_low_speed_time << ")." << llendl;
|
||||
}
|
||||
if (sDebugSettingsCurlTimeout.mLowSpeedLimit > AITP_default_low_speed_limit)
|
||||
{
|
||||
llwarns << "CurlTimeoutLowSpeedLimit (" << sDebugSettingsCurlTimeout.mLowSpeedLimit << ") is higher than the built-in default value (" << AITP_default_low_speed_limit << ")." << llendl;
|
||||
}
|
||||
if (sDebugSettingsCurlTimeout.mMaximumCurlTransaction < AITP_default_maximum_curl_transaction)
|
||||
{
|
||||
llwarns << "CurlTimeoutMaxTransaction (" << sDebugSettingsCurlTimeout.mMaximumCurlTransaction << ") is lower than the built-in default value (" << AITP_default_maximum_curl_transaction<< ")." << llendl;
|
||||
}
|
||||
if (sDebugSettingsCurlTimeout.mMaximumTotalDelay < AITP_default_maximum_total_delay)
|
||||
{
|
||||
llwarns << "CurlTimeoutMaxTotalDelay (" << sDebugSettingsCurlTimeout.mMaximumTotalDelay << ") is lower than the built-in default value (" << AITP_default_maximum_total_delay << ")." << llendl;
|
||||
}
|
||||
}
|
||||
|
||||
//static
|
||||
AIHTTPTimeoutPolicy const& AIHTTPTimeoutPolicy::getDebugSettingsCurlTimeout(void)
|
||||
{
|
||||
return sDebugSettingsCurlTimeout;
|
||||
}
|
||||
|
||||
#ifdef SHOW_ASSERT
|
||||
#include "aithreadid.h"
|
||||
static AIThreadID curlthread(AIThreadID::none); // Initialized by getConnectTimeout.
|
||||
#endif
|
||||
|
||||
static std::set<std::string> gSeenHostnames;
|
||||
|
||||
U16 AIHTTPTimeoutPolicy::getConnectTimeout(std::string const& hostname) const
|
||||
{
|
||||
#ifdef SHOW_ASSERT
|
||||
// Only the CURL-THREAD may access gSeenHostnames.
|
||||
if (curlthread.is_no_thread())
|
||||
curlthread.reset();
|
||||
llassert(curlthread.equals_current_thread());
|
||||
#endif
|
||||
|
||||
U16 connect_timeout = mMaximumConnectTime;
|
||||
// Add the hostname to the list of seen hostnames, if not already there.
|
||||
if (gSeenHostnames.insert(hostname).second)
|
||||
connect_timeout += mDNSLookupGrace; // If the host is not in the list, increase the connect timeout with mDNSLookupGrace.
|
||||
return connect_timeout;
|
||||
}
|
||||
|
||||
//static
|
||||
bool AIHTTPTimeoutPolicy::connect_timed_out(std::string const& hostname)
|
||||
{
|
||||
llassert(curlthread.equals_current_thread());
|
||||
|
||||
// This is called when a connect to hostname timed out on connect.
|
||||
// If the hostname is currently in the list, remove it and return true
|
||||
// so that subsequent connects will get more time to connect.
|
||||
// Otherwise return false.
|
||||
return gSeenHostnames.erase(hostname) > 0;
|
||||
}
|
||||
|
||||
//=======================================================================================================
|
||||
// Start of policy operation definitions.
|
||||
|
||||
namespace AIHTTPTimeoutPolicyOperators {
|
||||
|
||||
// Note: Policies are applied in the order First(x, Second(y, Third(z))) etc,
|
||||
// where the last (Third) has the highest priority.
|
||||
// For example: Transaction(5, Connect(40)) would first enforce a transaction time of 5 seconds,
|
||||
// and then a connect time of 40 seconds, even if that would mean increasing the transaction
|
||||
// time again.
|
||||
|
||||
struct DNS : PolicyOp {
|
||||
int mSeconds;
|
||||
DNS(int seconds) : mSeconds(seconds) { }
|
||||
DNS(int seconds, PolicyOp& op) : PolicyOp(op), mSeconds(seconds) { }
|
||||
static void fix(AIHTTPTimeoutPolicy* policy);
|
||||
static U16 min(void) { return ABS_min_DNS_lookup; }
|
||||
static U16 max(void) { return ABS_max_DNS_lookup; }
|
||||
virtual void perform(AIHTTPTimeoutPolicy* policy) const;
|
||||
};
|
||||
|
||||
struct Connect : PolicyOp {
|
||||
int mSeconds;
|
||||
Connect(int seconds) : mSeconds(seconds) { }
|
||||
Connect(int seconds, PolicyOp& op) : PolicyOp(op), mSeconds(seconds) { }
|
||||
static void fix(AIHTTPTimeoutPolicy* policy);
|
||||
static U16 min(void) { return ABS_min_connect_time; }
|
||||
static U16 max(void) { return ABS_max_connect_time; }
|
||||
virtual void perform(AIHTTPTimeoutPolicy* policy) const;
|
||||
};
|
||||
|
||||
struct Reply : PolicyOp {
|
||||
int mSeconds;
|
||||
Reply(int seconds) : mSeconds(seconds) { }
|
||||
Reply(int seconds, PolicyOp& op) : PolicyOp(op), mSeconds(seconds) { }
|
||||
static void fix(AIHTTPTimeoutPolicy* policy);
|
||||
static U16 min(void) { return ABS_min_reply_delay; }
|
||||
static U16 max(void) { return ABS_max_reply_delay; }
|
||||
virtual void perform(AIHTTPTimeoutPolicy* policy) const;
|
||||
};
|
||||
|
||||
struct Speed : PolicyOp {
|
||||
int mSeconds;
|
||||
int mRate;
|
||||
Speed(int seconds, int rate) : mSeconds(seconds), mRate(rate) { }
|
||||
Speed(int seconds, int rate, PolicyOp& op) : PolicyOp(op), mSeconds(seconds), mRate(rate) { }
|
||||
static void fix(AIHTTPTimeoutPolicy* policy);
|
||||
static U16 min(void) { return ABS_min_low_speed_time; }
|
||||
static U16 max(AIHTTPTimeoutPolicy const* policy) { return llmin(ABS_max_low_speed_time, (U16)(policy->mMaximumCurlTransaction / 2)); }
|
||||
static U32 lmin(void) { return ABS_min_low_speed_limit; }
|
||||
static U32 lmax(void) { return ABS_max_low_speed_limit; }
|
||||
virtual void perform(AIHTTPTimeoutPolicy* policy) const;
|
||||
};
|
||||
|
||||
struct Transaction : PolicyOp {
|
||||
int mSeconds;
|
||||
Transaction(int seconds) : mSeconds(seconds) { }
|
||||
Transaction(int seconds, PolicyOp& op) : PolicyOp(op), mSeconds(seconds) { }
|
||||
static void fix(AIHTTPTimeoutPolicy* policy);
|
||||
static U16 min(AIHTTPTimeoutPolicy const* policy) { return llmax(ABS_min_transaction, (U16)(policy->mMaximumConnectTime + policy->mMaximumReplyDelay + 4 * policy->mLowSpeedTime)); }
|
||||
static U16 max(void) { return ABS_max_transaction; }
|
||||
virtual void perform(AIHTTPTimeoutPolicy* policy) const;
|
||||
};
|
||||
|
||||
struct Total : PolicyOp {
|
||||
int mSeconds;
|
||||
Total(int seconds) : mSeconds(seconds) { }
|
||||
Total(int seconds, PolicyOp& op) : PolicyOp(op), mSeconds(seconds) { }
|
||||
static void fix(AIHTTPTimeoutPolicy* policy);
|
||||
static U16 min(AIHTTPTimeoutPolicy const* policy) { return llmax(ABS_min_total_delay, (U16)(policy->mMaximumCurlTransaction + 1)); }
|
||||
static U16 max(void) { return ABS_max_total_delay; }
|
||||
virtual void perform(AIHTTPTimeoutPolicy* policy) const;
|
||||
};
|
||||
|
||||
void DNS::perform(AIHTTPTimeoutPolicy* policy) const
|
||||
{
|
||||
policy->mDNSLookupGrace = mSeconds;
|
||||
fix(policy);
|
||||
nextOp(policy);
|
||||
}
|
||||
|
||||
void Connect::perform(AIHTTPTimeoutPolicy* policy) const
|
||||
{
|
||||
policy->mMaximumConnectTime = mSeconds;
|
||||
fix(policy);
|
||||
nextOp(policy);
|
||||
}
|
||||
|
||||
void Reply::perform(AIHTTPTimeoutPolicy* policy) const
|
||||
{
|
||||
policy->mMaximumReplyDelay = mSeconds;
|
||||
fix(policy);
|
||||
nextOp(policy);
|
||||
}
|
||||
|
||||
void Speed::perform(AIHTTPTimeoutPolicy* policy) const
|
||||
{
|
||||
policy->mLowSpeedTime = mSeconds;
|
||||
policy->mLowSpeedLimit = mRate;
|
||||
fix(policy);
|
||||
nextOp(policy);
|
||||
}
|
||||
|
||||
void Transaction::perform(AIHTTPTimeoutPolicy* policy) const
|
||||
{
|
||||
policy->mMaximumCurlTransaction = mSeconds;
|
||||
fix(policy);
|
||||
nextOp(policy);
|
||||
}
|
||||
|
||||
void Total::perform(AIHTTPTimeoutPolicy* policy) const
|
||||
{
|
||||
policy->mMaximumTotalDelay = mSeconds;
|
||||
fix(policy);
|
||||
nextOp(policy);
|
||||
}
|
||||
|
||||
void DNS::fix(AIHTTPTimeoutPolicy* policy)
|
||||
{
|
||||
if (policy->mDNSLookupGrace > max())
|
||||
{
|
||||
policy->mDNSLookupGrace = max();
|
||||
}
|
||||
else if (policy->mDNSLookupGrace < min())
|
||||
{
|
||||
policy->mDNSLookupGrace = min();
|
||||
}
|
||||
}
|
||||
|
||||
void Connect::fix(AIHTTPTimeoutPolicy* policy)
|
||||
{
|
||||
bool changed = false;
|
||||
if (policy->mMaximumConnectTime > max())
|
||||
{
|
||||
policy->mMaximumConnectTime = max();
|
||||
changed = true;
|
||||
}
|
||||
else if (policy->mMaximumConnectTime < min())
|
||||
{
|
||||
policy->mMaximumConnectTime = min();
|
||||
changed = true;
|
||||
}
|
||||
if (changed)
|
||||
{
|
||||
// Transaction limits depend on Connect.
|
||||
Transaction::fix(policy);
|
||||
}
|
||||
}
|
||||
|
||||
void Reply::fix(AIHTTPTimeoutPolicy* policy)
|
||||
{
|
||||
bool changed = false;
|
||||
if (policy->mMaximumReplyDelay > max())
|
||||
{
|
||||
policy->mMaximumReplyDelay = max();
|
||||
changed = true;
|
||||
}
|
||||
else if (policy->mMaximumReplyDelay < min())
|
||||
{
|
||||
policy->mMaximumReplyDelay = min();
|
||||
changed = true;
|
||||
}
|
||||
if (changed)
|
||||
{
|
||||
// Transaction limits depend on Reply.
|
||||
Transaction::fix(policy);
|
||||
}
|
||||
}
|
||||
|
||||
void Speed::fix(AIHTTPTimeoutPolicy* policy)
|
||||
{
|
||||
bool changed = false;
|
||||
if (policy->mLowSpeedTime > ABS_max_low_speed_time)
|
||||
{
|
||||
policy->mLowSpeedTime = ABS_max_low_speed_time;
|
||||
changed = true;
|
||||
}
|
||||
else if (policy->mLowSpeedTime != 0 && policy->mLowSpeedTime < min())
|
||||
{
|
||||
policy->mLowSpeedTime = min();
|
||||
changed = true;
|
||||
}
|
||||
if (changed)
|
||||
{
|
||||
// Transaction limits depend on Speed time.
|
||||
Transaction::fix(policy);
|
||||
}
|
||||
if (policy->mLowSpeedTime > max(policy))
|
||||
{
|
||||
policy->mLowSpeedTime = max(policy);
|
||||
}
|
||||
if (policy->mLowSpeedLimit > lmax())
|
||||
{
|
||||
policy->mLowSpeedLimit = lmax();
|
||||
}
|
||||
else if (policy->mLowSpeedLimit != 0 && policy->mLowSpeedLimit < lmin())
|
||||
{
|
||||
policy->mLowSpeedLimit = lmin();
|
||||
}
|
||||
}
|
||||
|
||||
void Transaction::fix(AIHTTPTimeoutPolicy* policy)
|
||||
{
|
||||
bool changed = false;
|
||||
if (policy->mMaximumCurlTransaction > max())
|
||||
{
|
||||
policy->mMaximumCurlTransaction = max();
|
||||
changed = true;
|
||||
}
|
||||
else if (policy->mMaximumCurlTransaction < ABS_min_transaction)
|
||||
{
|
||||
policy->mMaximumCurlTransaction = ABS_min_transaction;
|
||||
changed = true;
|
||||
}
|
||||
if (changed)
|
||||
{
|
||||
// Totals minimum limit depends on Transaction.
|
||||
Total::fix(policy);
|
||||
// Transaction limits depend on Connect, Reply and Speed time.
|
||||
if (policy->mMaximumCurlTransaction < min(policy))
|
||||
{
|
||||
// We need to achieve the following (from Transaction::min()):
|
||||
// policy->mMaximumCurlTransaction >= policy->mMaximumConnectTime + policy->mMaximumReplyDelay + 4 * policy->mLowSpeedTime
|
||||
|
||||
// There isn't a single way to fix this, so we just do something randomly intuitive.
|
||||
// We consider the vector space <connect_time, reply_delay, low_speed_time>;
|
||||
// In other words, we need to compare with the dot product of <1, 1, 4>.
|
||||
LLVector3 const ref(1, 1, 4);
|
||||
|
||||
// The shortest allowed vector is:
|
||||
LLVector3 const vec_min(ABS_min_connect_time, ABS_min_reply_delay, ABS_min_low_speed_time);
|
||||
|
||||
// Initialize the result vector to (0, 0, 0) (in the vector space with shifted origin).
|
||||
LLVector3 vec_res;
|
||||
|
||||
// Check if there is a solution at all:
|
||||
if (policy->mMaximumCurlTransaction > ref * vec_min) // Is vec_min small enough?
|
||||
{
|
||||
// The current point is:
|
||||
LLVector3 vec_cur(policy->mMaximumConnectTime, policy->mMaximumReplyDelay, policy->mLowSpeedTime);
|
||||
|
||||
// The default point is:
|
||||
LLVector3 vec_def(AITP_default_maximum_connect_time, AITP_default_maximum_reply_delay, AITP_default_low_speed_time);
|
||||
|
||||
// Move the origin.
|
||||
vec_cur -= vec_min;
|
||||
vec_def -= vec_min;
|
||||
|
||||
// Normalize the default vector (we only need it's direction).
|
||||
vec_def.normalize();
|
||||
|
||||
// Project the current vector onto the default vector (dp = default projection):
|
||||
LLVector3 vec_dp = vec_def * (vec_cur * vec_def);
|
||||
|
||||
// Check if the projection is a solution and choose the vectors between which the result lays.
|
||||
LLVector3 a; // vec_min is too small (a = (0, 0, 0) which corresponds to vec_min).
|
||||
LLVector3 b = vec_cur; // vec_cur is too large.
|
||||
if (policy->mMaximumCurlTransaction > ref * (vec_dp + vec_min)) // Is vec_dp small enough too?
|
||||
{
|
||||
a = vec_dp; // New lower bound.
|
||||
}
|
||||
else
|
||||
{
|
||||
b = vec_dp; // New upper bound.
|
||||
}
|
||||
// Find vec_res = a + lambda * (b - a), where 0 <= lambda <= 1, such that
|
||||
// policy->mMaximumCurlTransaction == ref * (vec_res + vec_min).
|
||||
//
|
||||
// Note that ref * (b - a) must be non-zero because if it wasn't then changing lambda wouldn't have
|
||||
// any effect on right-hand side of the equation (ref * (vec_res + vec_min)) which in contradiction
|
||||
// with the fact that a is a solution and b is not.
|
||||
F32 lambda = (policy->mMaximumCurlTransaction - ref * (a + vec_min)) / (ref * (b - a));
|
||||
vec_res = a + lambda * (b - a);
|
||||
}
|
||||
|
||||
// Shift origin back and fill in the result.
|
||||
vec_res += vec_min;
|
||||
policy->mMaximumConnectTime = vec_res[VX];
|
||||
policy->mMaximumReplyDelay = vec_res[VY];
|
||||
policy->mLowSpeedTime = vec_res[VZ];
|
||||
}
|
||||
}
|
||||
if (policy->mMaximumCurlTransaction < min(policy))
|
||||
{
|
||||
policy->mMaximumCurlTransaction = min(policy);
|
||||
}
|
||||
}
|
||||
|
||||
void Total::fix(AIHTTPTimeoutPolicy* policy)
|
||||
{
|
||||
bool changed = false;
|
||||
if (policy->mMaximumTotalDelay > max())
|
||||
{
|
||||
policy->mMaximumTotalDelay = max();
|
||||
changed = true;
|
||||
}
|
||||
else if (policy->mMaximumTotalDelay < ABS_min_total_delay)
|
||||
{
|
||||
policy->mMaximumTotalDelay = ABS_min_total_delay;
|
||||
changed = true;
|
||||
}
|
||||
if (changed)
|
||||
{
|
||||
// Totals minimum limit depends on Transaction.
|
||||
// We have to correct mMaximumCurlTransaction such that (from Total::min)
|
||||
// mMaximumTotalDelay >= llmax((int)ABS_min_total_delay, policy->mMaximumCurlTransaction + 1)
|
||||
if (policy->mMaximumTotalDelay < policy->mMaximumCurlTransaction + 1)
|
||||
{
|
||||
policy->mMaximumCurlTransaction = policy->mMaximumTotalDelay - 1;
|
||||
}
|
||||
}
|
||||
if (policy->mMaximumTotalDelay < min(policy))
|
||||
{
|
||||
policy->mMaximumTotalDelay = min(policy);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace AIHTTPTimeoutPolicyOperators
|
||||
|
||||
void AIHTTPTimeoutPolicy::sanity_checks(void) const
|
||||
{
|
||||
// Sanity checks.
|
||||
llassert( DNS::min() <= mDNSLookupGrace && mDNSLookupGrace <= DNS::max());
|
||||
llassert( Connect::min() <= mMaximumConnectTime && mMaximumConnectTime <= Connect::max());
|
||||
llassert( Reply::min() <= mMaximumReplyDelay && mMaximumReplyDelay <= Reply::max());
|
||||
llassert(mLowSpeedTime == 0 ||
|
||||
(Speed::min() <= mLowSpeedTime && mLowSpeedTime <= Speed::max(this)));
|
||||
llassert(mLowSpeedLimit == 0 ||
|
||||
(Speed::lmin() <= mLowSpeedLimit && mLowSpeedLimit <= Speed::lmax()));
|
||||
llassert(Transaction::min(this) <= mMaximumCurlTransaction && mMaximumCurlTransaction <= Transaction::max());
|
||||
llassert( Total::min(this) <= mMaximumTotalDelay && mMaximumTotalDelay <= Total::max());
|
||||
}
|
||||
|
||||
//=======================================================================================================
|
||||
// Start of policy definitions.
|
||||
|
||||
// Policy with hardcoded default values.
|
||||
AIHTTPTimeoutPolicyBase HTTPTimeoutPolicy_default(
|
||||
AITP_default_DNS_lookup_grace,
|
||||
AITP_default_maximum_connect_time,
|
||||
AITP_default_maximum_reply_delay,
|
||||
AITP_default_low_speed_time,
|
||||
AITP_default_low_speed_limit,
|
||||
AITP_default_maximum_curl_transaction,
|
||||
AITP_default_maximum_total_delay);
|
||||
|
||||
//static. Initialized here, but shortly overwritten by Debug Settings.
|
||||
AIHTTPTimeoutPolicyBase AIHTTPTimeoutPolicy::sDebugSettingsCurlTimeout(
|
||||
AITP_default_DNS_lookup_grace,
|
||||
AITP_default_maximum_connect_time,
|
||||
AITP_default_maximum_reply_delay,
|
||||
AITP_default_low_speed_time,
|
||||
AITP_default_low_speed_limit,
|
||||
AITP_default_maximum_curl_transaction,
|
||||
AITP_default_maximum_total_delay);
|
||||
|
||||
// This used to be '5 seconds'.
|
||||
AIHTTPTimeoutPolicyBase transfer_5s(AIHTTPTimeoutPolicyBase::getDebugSettingsCurlTimeout(),
|
||||
Transaction(5)
|
||||
);
|
||||
|
||||
// This used to be '18 seconds'.
|
||||
AIHTTPTimeoutPolicyBase transfer_18s(AIHTTPTimeoutPolicyBase::getDebugSettingsCurlTimeout(),
|
||||
Transaction(18)
|
||||
);
|
||||
|
||||
// This used to be '300 seconds'. We derive this from the hardcoded result so users can't mess with it.
|
||||
AIHTTPTimeoutPolicyBase transfer_300s(HTTPTimeoutPolicy_default,
|
||||
Transaction(300)
|
||||
);
|
||||
|
||||
// This used to be a call to setopt(CURLOPT_CONNECTTIMEOUT, 40L) with the remark 'Be a little impatient about establishing connections.'
|
||||
AIHTTPTimeoutPolicyBase connect_40s(AIHTTPTimeoutPolicyBase::getDebugSettingsCurlTimeout(),
|
||||
Connect(40));
|
||||
|
||||
// End of policy definitions.
|
||||
//=======================================================================================================
|
||||
|
||||
bool validateCurlTimeoutDNSLookup(LLSD const& newvalue)
|
||||
{
|
||||
U32 new_value = newvalue.asInteger();
|
||||
AIHTTPTimeoutPolicyBase timeout = AIHTTPTimeoutPolicyBase::getDebugSettingsCurlTimeout();
|
||||
AIHTTPTimeoutPolicyOperators::DNS op(new_value);
|
||||
op.perform(&timeout);
|
||||
return timeout.getDNSLookup() == new_value;
|
||||
}
|
||||
|
||||
bool handleCurlTimeoutDNSLookup(LLSD const& newvalue)
|
||||
{
|
||||
AIHTTPTimeoutPolicyBase timeout = AIHTTPTimeoutPolicyBase::getDebugSettingsCurlTimeout();
|
||||
AIHTTPTimeoutPolicyOperators::DNS op(newvalue.asInteger());
|
||||
op.perform(&timeout);
|
||||
AIHTTPTimeoutPolicy::setDefaultCurlTimeout(timeout);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool validateCurlTimeoutConnect(LLSD const& newvalue)
|
||||
{
|
||||
U32 new_value = newvalue.asInteger();
|
||||
AIHTTPTimeoutPolicyBase timeout = AIHTTPTimeoutPolicyBase::getDebugSettingsCurlTimeout();
|
||||
AIHTTPTimeoutPolicyOperators::Connect op(new_value);
|
||||
op.perform(&timeout);
|
||||
return timeout.getConnect() == new_value;
|
||||
}
|
||||
|
||||
bool handleCurlTimeoutConnect(LLSD const& newvalue)
|
||||
{
|
||||
AIHTTPTimeoutPolicyBase timeout = AIHTTPTimeoutPolicyBase::getDebugSettingsCurlTimeout();
|
||||
AIHTTPTimeoutPolicyOperators::Connect op(newvalue.asInteger());
|
||||
op.perform(&timeout);
|
||||
AIHTTPTimeoutPolicy::setDefaultCurlTimeout(timeout);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool validateCurlTimeoutReplyDelay(LLSD const& newvalue)
|
||||
{
|
||||
U32 new_value = newvalue.asInteger();
|
||||
AIHTTPTimeoutPolicyBase timeout = AIHTTPTimeoutPolicyBase::getDebugSettingsCurlTimeout();
|
||||
AIHTTPTimeoutPolicyOperators::Reply op(new_value);
|
||||
op.perform(&timeout);
|
||||
return timeout.getReplyDelay() == new_value;
|
||||
}
|
||||
|
||||
bool handleCurlTimeoutReplyDelay(LLSD const& newvalue)
|
||||
{
|
||||
AIHTTPTimeoutPolicyBase timeout = AIHTTPTimeoutPolicyBase::getDebugSettingsCurlTimeout();
|
||||
AIHTTPTimeoutPolicyOperators::Reply op(newvalue.asInteger());
|
||||
op.perform(&timeout);
|
||||
AIHTTPTimeoutPolicy::setDefaultCurlTimeout(timeout);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool validateCurlTimeoutLowSpeedLimit(LLSD const& newvalue)
|
||||
{
|
||||
U32 new_value = newvalue.asInteger();
|
||||
AIHTTPTimeoutPolicyBase timeout = AIHTTPTimeoutPolicyBase::getDebugSettingsCurlTimeout();
|
||||
AIHTTPTimeoutPolicyOperators::Speed op(timeout.getLowSpeedTime(), new_value);
|
||||
op.perform(&timeout);
|
||||
return timeout.getLowSpeedLimit() == new_value;
|
||||
}
|
||||
|
||||
bool handleCurlTimeoutLowSpeedLimit(LLSD const& newvalue)
|
||||
{
|
||||
AIHTTPTimeoutPolicyBase timeout = AIHTTPTimeoutPolicyBase::getDebugSettingsCurlTimeout();
|
||||
AIHTTPTimeoutPolicyOperators::Speed op(timeout.getLowSpeedTime(), newvalue.asInteger());
|
||||
op.perform(&timeout);
|
||||
AIHTTPTimeoutPolicy::setDefaultCurlTimeout(timeout);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool validateCurlTimeoutLowSpeedTime(LLSD const& newvalue)
|
||||
{
|
||||
U32 new_value = newvalue.asInteger();
|
||||
AIHTTPTimeoutPolicyBase timeout = AIHTTPTimeoutPolicyBase::getDebugSettingsCurlTimeout();
|
||||
AIHTTPTimeoutPolicyOperators::Speed op(new_value, timeout.getLowSpeedLimit());
|
||||
op.perform(&timeout);
|
||||
return timeout.getLowSpeedTime() == new_value;
|
||||
}
|
||||
|
||||
bool handleCurlTimeoutLowSpeedTime(LLSD const& newvalue)
|
||||
{
|
||||
AIHTTPTimeoutPolicyBase timeout = AIHTTPTimeoutPolicyBase::getDebugSettingsCurlTimeout();
|
||||
AIHTTPTimeoutPolicyOperators::Speed op(newvalue.asInteger(), timeout.getLowSpeedLimit());
|
||||
op.perform(&timeout);
|
||||
AIHTTPTimeoutPolicy::setDefaultCurlTimeout(timeout);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool validateCurlTimeoutMaxTransaction(LLSD const& newvalue)
|
||||
{
|
||||
U32 new_value = newvalue.asInteger();
|
||||
AIHTTPTimeoutPolicyBase timeout = AIHTTPTimeoutPolicyBase::getDebugSettingsCurlTimeout();
|
||||
AIHTTPTimeoutPolicyOperators::Transaction op(new_value);
|
||||
op.perform(&timeout);
|
||||
return timeout.getCurlTransaction() == new_value;
|
||||
}
|
||||
|
||||
bool handleCurlTimeoutMaxTransaction(LLSD const& newvalue)
|
||||
{
|
||||
AIHTTPTimeoutPolicyBase timeout = AIHTTPTimeoutPolicyBase::getDebugSettingsCurlTimeout();
|
||||
AIHTTPTimeoutPolicyOperators::Transaction op(newvalue.asInteger());
|
||||
op.perform(&timeout);
|
||||
AIHTTPTimeoutPolicy::setDefaultCurlTimeout(timeout);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool validateCurlTimeoutMaxTotalDelay(LLSD const& newvalue)
|
||||
{
|
||||
U32 new_value = newvalue.asInteger();
|
||||
AIHTTPTimeoutPolicyBase timeout = AIHTTPTimeoutPolicyBase::getDebugSettingsCurlTimeout();
|
||||
AIHTTPTimeoutPolicyOperators::Total op(new_value);
|
||||
op.perform(&timeout);
|
||||
return timeout.getTotalDelay() == new_value;
|
||||
}
|
||||
|
||||
bool handleCurlTimeoutMaxTotalDelay(LLSD const& newvalue)
|
||||
{
|
||||
AIHTTPTimeoutPolicyBase timeout = AIHTTPTimeoutPolicyBase::getDebugSettingsCurlTimeout();
|
||||
AIHTTPTimeoutPolicyOperators::Total op(newvalue.asInteger());
|
||||
op.perform(&timeout);
|
||||
AIHTTPTimeoutPolicy::setDefaultCurlTimeout(timeout);
|
||||
return true;
|
||||
}
|
||||
|
||||
//static
|
||||
AIHTTPTimeoutPolicy::namemap_t AIHTTPTimeoutPolicy::sNameMap;
|
||||
|
||||
//static
|
||||
AIHTTPTimeoutPolicy const* AIHTTPTimeoutPolicy::getTimeoutPolicyByName(std::string const& name)
|
||||
{
|
||||
namemap_t::iterator iter = sNameMap.find(name);
|
||||
if (iter == sNameMap.end())
|
||||
{
|
||||
if (!name.empty())
|
||||
{
|
||||
llwarns << "Cannot find AIHTTPTimeoutPolicy with name \"" << name << "\"." << llendl;
|
||||
}
|
||||
return &sDebugSettingsCurlTimeout;
|
||||
}
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
//=======================================================================================================
|
||||
// Start of Responder timeout policy list.
|
||||
|
||||
// Note: to find the actual responder class back, search for the name listed here but with upper case first character.
|
||||
// For example, if the actual responder class is LLAccountingCostResponder then the name used here is accountingCostResponder.
|
||||
|
||||
#undef P
|
||||
#define P(n) AIHTTPTimeoutPolicy n##_timeout(#n)
|
||||
#define P2(n, b) AIHTTPTimeoutPolicy n##_timeout(#n, b)
|
||||
|
||||
// Policy name Policy
|
||||
P(accountingCostResponder);
|
||||
P(agentStateResponder);
|
||||
P(assetUploadResponder);
|
||||
P(asyncConsoleResponder);
|
||||
P(authHandler);
|
||||
P(avatarNameResponder);
|
||||
P2(baseCapabilitiesComplete, transfer_18s);
|
||||
P(blockingLLSDPost);
|
||||
P(blockingLLSDGet);
|
||||
P(blockingRawGet);
|
||||
P(charactersResponder);
|
||||
P(classifiedStatsResponder);
|
||||
P(consoleResponder);
|
||||
P2(crashLoggerResponder, transfer_5s);
|
||||
P(createInventoryCategoryResponder);
|
||||
P(emeraldDicDownloader);
|
||||
P(environmentApplyResponder);
|
||||
P(environmentRequestResponder);
|
||||
P(estateChangeInfoResponder);
|
||||
P(eventPollResponder);
|
||||
P(fetchInventoryResponder);
|
||||
P(fnPtrResponder);
|
||||
P2(groupProposalBallotResponder, transfer_300s);
|
||||
P(homeLocationResponder);
|
||||
P(HTTPGetResponder);
|
||||
P(iamHereLogin);
|
||||
P(iamHere);
|
||||
P(iamHereVoice);
|
||||
P2(inventoryModelFetchDescendentsResponder, transfer_300s);
|
||||
P(inventoryModelFetchItemResponder);
|
||||
P(lcl_responder);
|
||||
P(mapLayerResponder);
|
||||
P(mediaTypeResponder);
|
||||
P(meshDecompositionResponder);
|
||||
P(meshHeaderResponder);
|
||||
P(meshLODResponder);
|
||||
P(meshPhysicsShapeResponder);
|
||||
P(meshSkinInfoResponder);
|
||||
P(mimeDiscoveryResponder);
|
||||
P(moderationModeResponder);
|
||||
P(muteTextResponder);
|
||||
P(muteVoiceResponder);
|
||||
P(navMeshRebakeResponder);
|
||||
P(navMeshResponder);
|
||||
P(navMeshStatusResponder);
|
||||
P(newAgentInventoryVariablePriceResponder);
|
||||
P(objectCostResponder);
|
||||
P(objectLinksetsResponder);
|
||||
P(physicsFlagsResponder);
|
||||
P(placeAvatarTeleportResponder);
|
||||
P(productInfoRequestResponder);
|
||||
P(regionResponder);
|
||||
P(remoteParcelRequestResponder);
|
||||
P(responderIgnore);
|
||||
P(sessionInviteResponder);
|
||||
P(setDisplayNameResponder);
|
||||
P2(simulatorFeaturesReceived, transfer_18s);
|
||||
P(startConferenceChatResponder);
|
||||
P2(startGroupVoteResponder, transfer_300s);
|
||||
P(terrainLinksetsResponder);
|
||||
P(translationReceiver);
|
||||
P(uploadModelPremissionsResponder);
|
||||
P(userReportResponder);
|
||||
P(verifiedDestinationResponder);
|
||||
P(viewerChatterBoxInvitationAcceptResponder);
|
||||
P(viewerMediaOpenIDResponder);
|
||||
P(viewerMediaWebProfileResponder);
|
||||
P(viewerStatsResponder);
|
||||
P(viewerVoiceAccountProvisionResponder);
|
||||
P(voiceCallCapResponder);
|
||||
P(voiceClientCapResponder);
|
||||
P(wholeModelFeeResponder);
|
||||
P(wholeModelUploadResponder);
|
||||
P2(XMLRPCResponder, connect_40s);
|
||||
|
||||
138
indra/llmessage/aihttptimeoutpolicy.h
Normal file
138
indra/llmessage/aihttptimeoutpolicy.h
Normal file
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* @file aihttptimeoutpolicy.h
|
||||
* @brief Store the policy on timing out a HTTP curl transaction.
|
||||
*
|
||||
* Copyright (c) 2012, Aleric Inglewood.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* There are special exceptions to the terms and conditions of the GPL as
|
||||
* it is applied to this Source Code. View the full text of the exception
|
||||
* in the file doc/FLOSS-exception.txt in this software distribution.
|
||||
*
|
||||
* CHANGELOG
|
||||
* and additional copyright holders.
|
||||
*
|
||||
* 24/09/2012
|
||||
* Initial version, written by Aleric Inglewood @ SL
|
||||
*/
|
||||
|
||||
#ifndef AIHTTPTIMEOUTPOLICY_H
|
||||
#define AIHTTPTIMEOUTPOLICY_H
|
||||
|
||||
#include "stdtypes.h"
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
class AIHTTPTimeoutPolicyBase;
|
||||
|
||||
namespace AIHTTPTimeoutPolicyOperators {
|
||||
|
||||
struct DNS;
|
||||
struct Connect;
|
||||
struct Reply;
|
||||
struct Speed;
|
||||
struct Transaction;
|
||||
struct Total;
|
||||
|
||||
} // namespace AIHTTPTimeoutPolicyOperators
|
||||
|
||||
class AIHTTPTimeoutPolicy {
|
||||
protected:
|
||||
char const* const mName; // The name of this policy, for debugging purposes.
|
||||
AIHTTPTimeoutPolicyBase* const mBase; // Policy this policy was based on.
|
||||
static AIHTTPTimeoutPolicyBase sDebugSettingsCurlTimeout; // CurlTimeout* debug settings.
|
||||
typedef std::map<std::string, AIHTTPTimeoutPolicy*> namemap_t; // Type of sNameMap.
|
||||
static namemap_t sNameMap; // Map of name of timeout policies (as returned by name()) to AIHTTPTimeoutPolicy* (this).
|
||||
|
||||
private:
|
||||
U16 mDNSLookupGrace; // Extra connect timeout the first time we connect to a host (this is to allow for DNS lookups).
|
||||
U16 mMaximumConnectTime; // Connect timeouts any subsequent connects to the same host, assuming the DNS will be cached now.
|
||||
U16 mMaximumReplyDelay; // Timeout for the period between sending data to the server and the HTTP header of the reply.
|
||||
U16 mLowSpeedTime; // The time in seconds that a transfer should be below mLowSpeedLimit before to consider it too slow and abort.
|
||||
U32 mLowSpeedLimit; // Transfer speed in bytes per second that a transfer should be below during mLowSpeedTime seconds to consider it too slow and abort.
|
||||
U16 mMaximumCurlTransaction; // Timeout for the whole curl transaction (including connect and DNS lookup).
|
||||
U16 mMaximumTotalDelay; // Timeout from moment of request (including the time a request is/was queued).
|
||||
|
||||
friend struct AIHTTPTimeoutPolicyOperators::DNS;
|
||||
friend struct AIHTTPTimeoutPolicyOperators::Connect;
|
||||
friend struct AIHTTPTimeoutPolicyOperators::Reply;
|
||||
friend struct AIHTTPTimeoutPolicyOperators::Speed;
|
||||
friend struct AIHTTPTimeoutPolicyOperators::Transaction;
|
||||
friend struct AIHTTPTimeoutPolicyOperators::Total;
|
||||
|
||||
public:
|
||||
// Construct a HTTP timeout policy object that mimics base, or Debug Settings if none given.
|
||||
AIHTTPTimeoutPolicy(
|
||||
char const* name,
|
||||
AIHTTPTimeoutPolicyBase& base = sDebugSettingsCurlTimeout);
|
||||
|
||||
// Construct a HTTP timeout policy with exact specifications.
|
||||
AIHTTPTimeoutPolicy(
|
||||
char const* name,
|
||||
U16 dns_lookup_grace,
|
||||
U16 subsequent_connects,
|
||||
U16 reply_delay,
|
||||
U16 low_speed_time,
|
||||
U32 low_speed_limit,
|
||||
U16 curl_transaction,
|
||||
U16 total_delay);
|
||||
|
||||
void sanity_checks(void) const;
|
||||
|
||||
// Accessors.
|
||||
char const* name(void) const { return mName; }
|
||||
U16 getConnectTimeout(std::string const& hostname) const;
|
||||
U16 getDNSLookup(void) const { return mDNSLookupGrace; }
|
||||
U16 getConnect(void) const { return mMaximumConnectTime; }
|
||||
U16 getReplyDelay(void) const { return mMaximumReplyDelay; }
|
||||
U16 getLowSpeedTime(void) const { return mLowSpeedTime; }
|
||||
U32 getLowSpeedLimit(void) const { return mLowSpeedLimit; }
|
||||
U16 getCurlTransaction(void) const { return mMaximumCurlTransaction; }
|
||||
U16 getTotalDelay(void) const { return mMaximumTotalDelay; }
|
||||
static AIHTTPTimeoutPolicy const& getDebugSettingsCurlTimeout(void);
|
||||
static AIHTTPTimeoutPolicy const* getTimeoutPolicyByName(std::string const& name);
|
||||
|
||||
// Called once at start up of viewer to set a different default timeout policy than HTTPTimeoutPolicy_default.
|
||||
static void setDefaultCurlTimeout(AIHTTPTimeoutPolicy const& defaultCurlTimeout);
|
||||
|
||||
// Called when a connect to a hostname timed out.
|
||||
static bool connect_timed_out(std::string const& hostname);
|
||||
|
||||
protected:
|
||||
// Used by AIHTTPTimeoutPolicyBase::AIHTTPTimeoutPolicyBase(AIHTTPTimeoutPolicyBase&).
|
||||
AIHTTPTimeoutPolicy(AIHTTPTimeoutPolicy&);
|
||||
// Abused assigned operator (called by AIHTTPTimeoutPolicyBase::operator=).
|
||||
AIHTTPTimeoutPolicy& operator=(AIHTTPTimeoutPolicy const&);
|
||||
};
|
||||
|
||||
class LLSD;
|
||||
|
||||
// Handlers for Debug Setting changes.
|
||||
bool validateCurlTimeoutDNSLookup(LLSD const& newvalue);
|
||||
bool handleCurlTimeoutDNSLookup(LLSD const& newvalue);
|
||||
bool validateCurlTimeoutConnect(LLSD const& newvalue);
|
||||
bool handleCurlTimeoutConnect(LLSD const& newvalue);
|
||||
bool validateCurlTimeoutReplyDelay(LLSD const& newvalue);
|
||||
bool handleCurlTimeoutReplyDelay(LLSD const& newvalue);
|
||||
bool validateCurlTimeoutLowSpeedLimit(LLSD const& newvalue);
|
||||
bool handleCurlTimeoutLowSpeedLimit(LLSD const& newvalue);
|
||||
bool validateCurlTimeoutLowSpeedTime(LLSD const& newvalue);
|
||||
bool handleCurlTimeoutLowSpeedTime(LLSD const& newvalue);
|
||||
bool validateCurlTimeoutMaxTransaction(LLSD const& newvalue);
|
||||
bool handleCurlTimeoutMaxTransaction(LLSD const& newvalue);
|
||||
bool validateCurlTimeoutMaxTotalDelay(LLSD const& newvalue);
|
||||
bool handleCurlTimeoutMaxTotalDelay(LLSD const& newvalue);
|
||||
|
||||
#endif // AIHTTPTIMEOUTPOLICY_H
|
||||
@@ -249,7 +249,7 @@ std::ostream& operator<<(std::ostream& os, CURLoption option)
|
||||
CASEPRINT(CURLOPT_LOW_SPEED_TIME);
|
||||
CASEPRINT(CURLOPT_RESUME_FROM);
|
||||
CASEPRINT(CURLOPT_COOKIE);
|
||||
CASEPRINT(CURLOPT_RTSPHEADER);
|
||||
CASEPRINT(CURLOPT_HTTPHEADER);
|
||||
CASEPRINT(CURLOPT_HTTPPOST);
|
||||
CASEPRINT(CURLOPT_SSLCERT);
|
||||
CASEPRINT(CURLOPT_KEYPASSWD);
|
||||
@@ -548,7 +548,7 @@ char* debug_curl_easy_escape(CURL* curl, char* url, int length)
|
||||
{
|
||||
char* ret;
|
||||
ret = curl_easy_escape(curl, url, length);
|
||||
Dout(dc::curl, "curl_easy_escape(" << curl << ", \"" << url << "\", " << length << ") = \"" << ret << '"');
|
||||
Dout(dc::curl, "curl_easy_escape(" << (AICURL*)curl << ", \"" << url << "\", " << length << ") = \"" << ret << '"');
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -569,23 +569,23 @@ CURLcode debug_curl_easy_getinfo(CURL* curl, CURLINFO info, ...)
|
||||
ret = curl_easy_getinfo(curl, info, param.some_ptr);
|
||||
if (info == CURLINFO_PRIVATE)
|
||||
{
|
||||
Dout(dc::curl, "curl_easy_getinfo(" << curl << ", " << info << ", 0x" << std::hex << (size_t)param.some_ptr << std::dec << ") = " << ret);
|
||||
Dout(dc::curl, "curl_easy_getinfo(" << (AICURL*)curl << ", " << info << ", 0x" << std::hex << (size_t)param.some_ptr << std::dec << ") = " << ret);
|
||||
}
|
||||
else
|
||||
{
|
||||
switch((info & CURLINFO_TYPEMASK))
|
||||
{
|
||||
case CURLINFO_STRING:
|
||||
Dout(dc::curl, "curl_easy_getinfo(" << curl << ", " << info << ", (char**){ \"" << (ret == CURLE_OK ? *param.char_ptr : " <unchanged> ") << "\" }) = " << ret);
|
||||
Dout(dc::curl, "curl_easy_getinfo(" << (AICURL*)curl << ", " << info << ", (char**){ \"" << (ret == CURLE_OK ? *param.char_ptr : " <unchanged> ") << "\" }) = " << ret);
|
||||
break;
|
||||
case CURLINFO_LONG:
|
||||
Dout(dc::curl, "curl_easy_getinfo(" << curl << ", " << info << ", (long*){ " << (ret == CURLE_OK ? *param.long_ptr : 0L) << "L }) = " << ret);
|
||||
Dout(dc::curl, "curl_easy_getinfo(" << (AICURL*)curl << ", " << info << ", (long*){ " << (ret == CURLE_OK ? *param.long_ptr : 0L) << "L }) = " << ret);
|
||||
break;
|
||||
case CURLINFO_DOUBLE:
|
||||
Dout(dc::curl, "curl_easy_getinfo(" << curl << ", " << info << ", (double*){" << (ret == CURLE_OK ? *param.double_ptr : 0.) << "}) = " << ret);
|
||||
Dout(dc::curl, "curl_easy_getinfo(" << (AICURL*)curl << ", " << info << ", (double*){" << (ret == CURLE_OK ? *param.double_ptr : 0.) << "}) = " << ret);
|
||||
break;
|
||||
case CURLINFO_SLIST:
|
||||
Dout(dc::curl, "curl_easy_getinfo(" << curl << ", " << info << ", (curl_slist**){ " << (ret == CURLE_OK ? **param.curl_slist_ptr : unchanged_slist) << " }) = " << ret);
|
||||
Dout(dc::curl, "curl_easy_getinfo(" << (AICURL*)curl << ", " << info << ", (curl_slist**){ " << (ret == CURLE_OK ? **param.curl_slist_ptr : unchanged_slist) << " }) = " << ret);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -702,7 +702,11 @@ CURLcode debug_curl_easy_setopt(CURL* handle, CURLoption option, ...)
|
||||
{
|
||||
LibcwDoutStream << "NULL";
|
||||
}
|
||||
LibcwDoutStream << "](" << (is_postfield ? postfieldsize : size) << " bytes))";
|
||||
LibcwDoutStream << "]";
|
||||
if (str)
|
||||
{
|
||||
LibcwDoutStream << "(" << (is_postfield ? postfieldsize : size) << " bytes))";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -755,7 +759,7 @@ char* debug_curl_easy_unescape(CURL* curl, char* url, int inlength, int* outleng
|
||||
{
|
||||
char* ret;
|
||||
ret = curl_easy_unescape(curl, url, inlength, outlength);
|
||||
Dout(dc::curl, "curl_easy_unescape(" << curl << ", \"" << url << "\", " << inlength << ", " << ((ret && outlength) ? *outlength : 1) << ") = \"" << ret << '"');
|
||||
Dout(dc::curl, "curl_easy_unescape(" << (AICURL*)curl << ", \"" << url << "\", " << inlength << ", " << ((ret && outlength) ? *outlength : 1) << ") = \"" << ret << '"');
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
@@ -129,7 +129,7 @@ namespace LLAvatarNameCache
|
||||
// Erase expired names from cache
|
||||
void eraseUnrefreshed();
|
||||
|
||||
bool expirationFromCacheControl(LLSD headers, F64 *expires);
|
||||
bool expirationFromCacheControl(AIHTTPReceivedHeaders const& headers, F64* expires);
|
||||
}
|
||||
|
||||
/* Sample response:
|
||||
@@ -171,7 +171,10 @@ namespace LLAvatarNameCache
|
||||
</llsd>
|
||||
*/
|
||||
|
||||
class LLAvatarNameResponder : public LLHTTPClient::Responder
|
||||
class AIHTTPTimeoutPolicy;
|
||||
extern AIHTTPTimeoutPolicy avatarNameResponder_timeout;
|
||||
|
||||
class LLAvatarNameResponder : public LLHTTPClient::ResponderWithResult
|
||||
{
|
||||
private:
|
||||
// need to store agent ids that are part of this request in case of
|
||||
@@ -179,16 +182,16 @@ private:
|
||||
std::vector<LLUUID> mAgentIDs;
|
||||
|
||||
// Need the headers to look up Expires: and Retry-After:
|
||||
LLSD mHeaders;
|
||||
AIHTTPReceivedHeaders mHeaders;
|
||||
|
||||
public:
|
||||
virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return avatarNameResponder_timeout; }
|
||||
|
||||
LLAvatarNameResponder(const std::vector<LLUUID>& agent_ids)
|
||||
: mAgentIDs(agent_ids),
|
||||
mHeaders()
|
||||
: mAgentIDs(agent_ids)
|
||||
{ }
|
||||
|
||||
/*virtual*/ void completedHeader(U32 status, const std::string& reason,
|
||||
const LLSD& headers)
|
||||
/*virtual*/ void completedHeaders(U32 status, std::string const& reason, AIHTTPReceivedHeaders const& headers)
|
||||
{
|
||||
mHeaders = headers;
|
||||
}
|
||||
@@ -788,43 +791,33 @@ void LLAvatarNameCache::insert(const LLUUID& agent_id, const LLAvatarName& av_na
|
||||
sCache[agent_id] = av_name;
|
||||
}
|
||||
|
||||
F64 LLAvatarNameCache::nameExpirationFromHeaders(LLSD headers)
|
||||
F64 LLAvatarNameCache::nameExpirationFromHeaders(AIHTTPReceivedHeaders const& headers)
|
||||
{
|
||||
F64 expires = 0.0;
|
||||
if (expirationFromCacheControl(headers, &expires))
|
||||
{
|
||||
return expires;
|
||||
}
|
||||
else
|
||||
{
|
||||
// With no expiration info, default to an hour
|
||||
const F64 DEFAULT_EXPIRES = 60.0 * 60.0;
|
||||
F64 now = LLFrameTimer::getTotalSeconds();
|
||||
return now + DEFAULT_EXPIRES;
|
||||
}
|
||||
F64 expires;
|
||||
expirationFromCacheControl(headers, &expires);
|
||||
return expires;
|
||||
}
|
||||
|
||||
bool LLAvatarNameCache::expirationFromCacheControl(LLSD headers, F64 *expires)
|
||||
bool LLAvatarNameCache::expirationFromCacheControl(AIHTTPReceivedHeaders const& headers, F64* expires)
|
||||
{
|
||||
bool fromCacheControl = false;
|
||||
S32 max_age = 3600; // With no expiration info, default to an hour.
|
||||
F64 now = LLFrameTimer::getTotalSeconds();
|
||||
// Allow the header to override the default
|
||||
LLSD cache_control_header = headers["cache-control"];
|
||||
if (cache_control_header.isDefined())
|
||||
std::string cache_control;
|
||||
if (headers.getFirstValue("cache-control", cache_control))
|
||||
{
|
||||
S32 max_age = 0;
|
||||
std::string cache_control = cache_control_header.asString();
|
||||
if (max_age_from_cache_control(cache_control, &max_age))
|
||||
{
|
||||
*expires = now + (F64)max_age;
|
||||
fromCacheControl = true;
|
||||
}
|
||||
}
|
||||
*expires = now + (F64)max_age;
|
||||
LL_DEBUGS("AvNameCache")
|
||||
<< ( fromCacheControl ? "expires based on cache control " : "default expiration " )
|
||||
<< "in " << *expires - now << " seconds"
|
||||
<< LL_ENDL;
|
||||
|
||||
|
||||
return fromCacheControl;
|
||||
}
|
||||
|
||||
|
||||
@@ -32,8 +32,8 @@
|
||||
|
||||
#include <boost/signals2.hpp>
|
||||
|
||||
class LLSD;
|
||||
class LLUUID;
|
||||
class AIHTTPReceivedHeaders;
|
||||
|
||||
namespace LLAvatarNameCache
|
||||
{
|
||||
@@ -98,7 +98,7 @@ namespace LLAvatarNameCache
|
||||
|
||||
// Compute name expiration time from HTTP Cache-Control header,
|
||||
// or return default value, in seconds from epoch.
|
||||
F64 nameExpirationFromHeaders(LLSD headers);
|
||||
F64 nameExpirationFromHeaders(AIHTTPReceivedHeaders const& headers);
|
||||
|
||||
void addUseDisplayNamesCallback(const use_display_name_signal_t::slot_type& cb);
|
||||
}
|
||||
|
||||
@@ -377,7 +377,7 @@ LLBufferArray::segment_iterator_t LLBufferArray::splitAfter(U8* address)
|
||||
// We have the location and the segment.
|
||||
U8* base = (*it).data();
|
||||
S32 size = (*it).size();
|
||||
if(address == (base + size))
|
||||
if(address == (base + size - 1))
|
||||
{
|
||||
// No need to split, since this is the last byte of the
|
||||
// segment. We do not want to have zero length segments, since
|
||||
@@ -393,7 +393,14 @@ LLBufferArray::segment_iterator_t LLBufferArray::splitAfter(U8* address)
|
||||
mSegments.insert(it, segment2);
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
//mMutexp should be locked before calling this.
|
||||
LLBufferArray::const_segment_iterator_t LLBufferArray::beginSegment() const
|
||||
{
|
||||
ASSERT_LLBUFFERARRAY_MUTEX_LOCKED
|
||||
return mSegments.begin();
|
||||
}
|
||||
|
||||
//mMutexp should be locked before calling this.
|
||||
LLBufferArray::segment_iterator_t LLBufferArray::beginSegment()
|
||||
{
|
||||
@@ -401,6 +408,13 @@ LLBufferArray::segment_iterator_t LLBufferArray::beginSegment()
|
||||
return mSegments.begin();
|
||||
}
|
||||
|
||||
//mMutexp should be locked before calling this.
|
||||
LLBufferArray::const_segment_iterator_t LLBufferArray::endSegment() const
|
||||
{
|
||||
ASSERT_LLBUFFERARRAY_MUTEX_LOCKED
|
||||
return mSegments.end();
|
||||
}
|
||||
|
||||
//mMutexp should be locked before calling this.
|
||||
LLBufferArray::segment_iterator_t LLBufferArray::endSegment()
|
||||
{
|
||||
@@ -636,6 +650,20 @@ U8* LLBufferArray::readAfter(
|
||||
return rv;
|
||||
}
|
||||
|
||||
void LLBufferArray::writeChannelTo(std::ostream& ostr, S32 channel) const
|
||||
{
|
||||
LLMemType m1(LLMemType::MTYPE_IO_BUFFER);
|
||||
LLMutexLock lock(mMutexp) ;
|
||||
const_segment_iterator_t const end = mSegments.end();
|
||||
for (const_segment_iterator_t it = mSegments.begin(); it != end; ++it)
|
||||
{
|
||||
if (it->isOnChannel(channel))
|
||||
{
|
||||
ostr.write((char*)it->data(), it->size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
U8* LLBufferArray::seek(
|
||||
S32 channel,
|
||||
U8* start,
|
||||
|
||||
@@ -306,7 +306,7 @@ public:
|
||||
typedef std::list<LLSegment> segment_list_t;
|
||||
typedef segment_list_t::const_iterator const_segment_iterator_t;
|
||||
typedef segment_list_t::iterator segment_iterator_t;
|
||||
enum { npos = 0xffffffff };
|
||||
static size_t const npos = (size_t)-1; // (U8*)npos is used as a magic address.
|
||||
|
||||
LLBufferArray();
|
||||
~LLBufferArray();
|
||||
@@ -495,6 +495,7 @@ public:
|
||||
* @return Returns the segment if there is one.
|
||||
*/
|
||||
segment_iterator_t beginSegment();
|
||||
const_segment_iterator_t beginSegment() const;
|
||||
|
||||
/**
|
||||
* @brief Get the one-past-the-end segment in the buffer array
|
||||
@@ -502,6 +503,7 @@ public:
|
||||
* @return Returns the iterator for an invalid segment location.
|
||||
*/
|
||||
segment_iterator_t endSegment();
|
||||
const_segment_iterator_t endSegment() const;
|
||||
|
||||
/**
|
||||
* @brief Get the segment which holds the given address.
|
||||
@@ -590,6 +592,12 @@ public:
|
||||
void setThreaded(bool threaded);
|
||||
//@}
|
||||
|
||||
/**
|
||||
* @brief Read channel channel of LLBufferArray and write it to ostr.
|
||||
* This is a Singularity extension.
|
||||
*/
|
||||
void writeChannelTo(std::ostream& ostr, S32 channel) const;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Optimally put data in buffers, and reutrn segments.
|
||||
|
||||
@@ -118,6 +118,17 @@ protected:
|
||||
//virtual streamsize xsputn(char* src, streamsize length);
|
||||
//@}
|
||||
|
||||
public:
|
||||
/*
|
||||
* @brief Return number of bytes in input channel.
|
||||
*/
|
||||
S32 count_in(void) const { return mBuffer->count(mChannels.in()); }
|
||||
|
||||
/*
|
||||
* @brief Return number of bytes in output channel.
|
||||
*/
|
||||
S32 count_out(void) const { return mBuffer->count(mChannels.out()); }
|
||||
|
||||
protected:
|
||||
// This channels we are working on.
|
||||
LLChannelDescriptors mChannels;
|
||||
@@ -144,6 +155,9 @@ public:
|
||||
LLBufferArray* buffer);
|
||||
~LLBufferStream();
|
||||
|
||||
S32 count_in(void) const { return mStreamBuf.count_in(); }
|
||||
S32 count_out(void) const { return mStreamBuf.count_out(); }
|
||||
|
||||
protected:
|
||||
LLBufferStreamBuf mStreamBuf;
|
||||
};
|
||||
|
||||
@@ -1,154 +0,0 @@
|
||||
/**
|
||||
* @file llcurl.cpp
|
||||
* @author Zero / Donovan
|
||||
* @date 2006-10-15
|
||||
* @brief Implementation of wrapper around libcurl.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2006&license=viewerlgpl$
|
||||
* Second Life Viewer Source Code
|
||||
* Copyright (C) 2010, 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$
|
||||
*/
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
/*
|
||||
The trick to getting curl to do keep-alives is to reuse the
|
||||
same easy handle for the requests. It appears that curl
|
||||
keeps a pool of connections alive for each easy handle, but
|
||||
doesn't share them between easy handles. Therefore it is
|
||||
important to keep a pool of easy handles and reuse them,
|
||||
rather than create and destroy them with each request. This
|
||||
code does this.
|
||||
|
||||
Furthermore, it would behoove us to keep track of which
|
||||
hosts an easy handle was used for and pick an easy handle
|
||||
that matches the next request. This code does not current
|
||||
do this.
|
||||
*/
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static const U32 EASY_HANDLE_POOL_SIZE = 5;
|
||||
static const S32 MULTI_PERFORM_CALL_REPEAT = 5;
|
||||
static const S32 CURL_REQUEST_TIMEOUT = 30; // seconds per operation
|
||||
static const S32 MAX_ACTIVE_REQUEST_COUNT = 100;
|
||||
|
||||
//static
|
||||
F32 LLCurl::sCurlRequestTimeOut = 120.f; //seconds
|
||||
S32 LLCurl::sMaxHandles = 256; //max number of handles, (multi handles and easy handles combined).
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
AIThreadSafeSimpleDC<LLCurl::Easy::Handles> LLCurl::Easy::sHandles;
|
||||
|
||||
//static
|
||||
CURL* LLCurl::Easy::allocEasyHandle()
|
||||
{
|
||||
llassert(*AIAccess<LLCurlThread*>(LLCurl::getCurlThread())) ;
|
||||
|
||||
CURL* ret = NULL;
|
||||
|
||||
//*** Multi-threaded.
|
||||
AIAccess<Handles> handles_w(sHandles);
|
||||
|
||||
if (handles_w->free.empty())
|
||||
{
|
||||
ret = LLCurl::newEasyHandle();
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = *(handles_w->free.begin());
|
||||
handles_w->free.erase(ret);
|
||||
}
|
||||
|
||||
if (ret)
|
||||
{
|
||||
handles_w->active.insert(ret);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
//static
|
||||
void LLCurl::Easy::releaseEasyHandle(CURL* handle)
|
||||
{
|
||||
DoutEntering(dc::curl, "LLCurl::Easy::releaseEasyHandle(" << (void*)handle << ")");
|
||||
BACKTRACE;
|
||||
|
||||
static const S32 MAX_NUM_FREE_HANDLES = 32 ;
|
||||
|
||||
if (!handle)
|
||||
{
|
||||
return ; //handle allocation failed.
|
||||
//llerrs << "handle cannot be NULL!" << llendl;
|
||||
}
|
||||
|
||||
//*** Multi-Threaded (logout only?)
|
||||
AIAccess<Handles> handles_w(sHandles);
|
||||
|
||||
if (handles_w->active.find(handle) != handles_w->active.end())
|
||||
{
|
||||
handles_w->active.erase(handle);
|
||||
|
||||
if (handles_w->free.size() < MAX_NUM_FREE_HANDLES)
|
||||
{
|
||||
curl_easy_reset(handle);
|
||||
handles_w->free.insert(handle);
|
||||
}
|
||||
else
|
||||
{
|
||||
LLCurl::deleteEasyHandle(handle) ;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
llerrs << "Invalid handle." << llendl;
|
||||
}
|
||||
}
|
||||
|
||||
LLCurl::Easy::~Easy()
|
||||
{
|
||||
AISTAccess<LLCurl::ResponderPtr> responder_w(mResponder);
|
||||
if (*responder_w && LLCurl::getNotQuitting()) //aborted
|
||||
{
|
||||
std::string reason("Request timeout, aborted.") ;
|
||||
(*responder_w)->completedRaw(408, //HTTP_REQUEST_TIME_OUT, timeout, abort
|
||||
reason, mChannels, mOutput);
|
||||
}
|
||||
*responder_w = NULL;
|
||||
}
|
||||
|
||||
LLCurl::Easy* LLCurlRequest::allocEasy()
|
||||
{
|
||||
if (!mActiveMulti ||
|
||||
mActiveRequestCount >= MAX_ACTIVE_REQUEST_COUNT ||
|
||||
mActiveMulti->mErrorCount > 0)
|
||||
{
|
||||
addMulti();
|
||||
}
|
||||
if(!mActiveMulti)
|
||||
{
|
||||
return NULL ;
|
||||
}
|
||||
|
||||
//llassert_always(mActiveMulti);
|
||||
++mActiveRequestCount;
|
||||
LLCurl::Easy* easy = mActiveMulti->allocEasy();
|
||||
return easy;
|
||||
}
|
||||
@@ -332,7 +332,7 @@ LLIOPipe::EStatus LLFilterSD2XMLRPCResponse::process_impl(
|
||||
// we have everyting in the buffer, so turn the structure data rpc
|
||||
// response into an xml rpc response.
|
||||
LLBufferStream stream(channels, buffer.get());
|
||||
stream << XML_HEADER << XMLRPC_METHOD_RESPONSE_HEADER;
|
||||
stream << XML_HEADER << XMLRPC_METHOD_RESPONSE_HEADER << std::flush; // Flush, or buffer->count() returns too much!
|
||||
LLSD sd;
|
||||
LLSDSerialize::fromNotation(sd, stream, buffer->count(channels.in()));
|
||||
|
||||
@@ -484,7 +484,7 @@ LLIOPipe::EStatus LLFilterSD2XMLRPCRequest::process_impl(
|
||||
break;
|
||||
}
|
||||
|
||||
stream << XMLRPC_REQUEST_FOOTER;
|
||||
stream << XMLRPC_REQUEST_FOOTER << std::flush;
|
||||
return STATUS_DONE;
|
||||
}
|
||||
|
||||
@@ -647,7 +647,7 @@ LLIOPipe::EStatus LLFilterXMLRPCResponse2LLSD::process_impl(
|
||||
fault_string.assign(fault_str);
|
||||
}
|
||||
stream << "'" << LLSDNotationFormatter::escapeString(fault_string)
|
||||
<< "'" <<LLSDRPC_FAULT_FOOTER;
|
||||
<< "'" <<LLSDRPC_FAULT_FOOTER << std::flush;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -658,7 +658,7 @@ LLIOPipe::EStatus LLFilterXMLRPCResponse2LLSD::process_impl(
|
||||
{
|
||||
stream_out(stream, param);
|
||||
}
|
||||
stream << LLSDRPC_RESPONSE_FOOTER;
|
||||
stream << LLSDRPC_RESPONSE_FOOTER << std::flush;
|
||||
}
|
||||
PUMP_DEBUG;
|
||||
XMLRPC_RequestFree(response, 1);
|
||||
@@ -768,7 +768,7 @@ LLIOPipe::EStatus LLFilterXMLRPCRequest2LLSD::process_impl(
|
||||
stream << "]";
|
||||
}
|
||||
}
|
||||
stream << LLSDRPC_REQUEST_FOOTER;
|
||||
stream << LLSDRPC_REQUEST_FOOTER << std::flush;
|
||||
XMLRPC_RequestFree(request, 1);
|
||||
delete[] buf;
|
||||
PUMP_DEBUG;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -32,85 +32,350 @@
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <curl/curl.h> // CURLcode
|
||||
#include <boost/intrusive_ptr.hpp>
|
||||
#include "llassettype.h"
|
||||
#include "llcurl.h"
|
||||
#include "lliopipe.h"
|
||||
#include "llurlrequest.h"
|
||||
|
||||
extern const F32 HTTP_REQUEST_EXPIRY_SECS;
|
||||
#include "llassettype.h"
|
||||
#include "llhttpstatuscodes.h"
|
||||
#include "aihttpheaders.h"
|
||||
|
||||
class LLUUID;
|
||||
class LLPumpIO;
|
||||
class LLSD;
|
||||
class AIHTTPTimeoutPolicy;
|
||||
class LLBufferArray;
|
||||
class LLChannelDescriptors;
|
||||
|
||||
extern AIHTTPTimeoutPolicy responderIgnore_timeout;
|
||||
typedef struct _xmlrpc_request* XMLRPC_REQUEST;
|
||||
typedef struct _xmlrpc_value* XMLRPC_VALUE;
|
||||
|
||||
class LLHTTPClient
|
||||
{
|
||||
// Output parameter of AICurlPrivate::CurlEasyRequest::getResult.
|
||||
// Used in XMLRPCResponder.
|
||||
struct AITransferInfo {
|
||||
AITransferInfo() : mSizeDownload(0.0), mTotalTime(0.0), mSpeedDownload(0.0) { }
|
||||
F64 mSizeDownload;
|
||||
F64 mTotalTime;
|
||||
F64 mSpeedDownload;
|
||||
};
|
||||
|
||||
// Events generated by AICurlPrivate::CurlResponderBuffer.
|
||||
struct AICurlResponderBufferEvents {
|
||||
virtual void received_HTTP_header(void) = 0; // For example "HTTP/1.0 200 OK", the first header of a reply.
|
||||
virtual void received_header(std::string const& key, std::string const& value) = 0; // Subsequent headers.
|
||||
virtual void completed_headers(U32 status, std::string const& reason, AITransferInfo* info) = 0; // Transaction completed.
|
||||
};
|
||||
|
||||
class LLHTTPClient {
|
||||
public:
|
||||
// class Responder moved to LLCurl
|
||||
|
||||
// For convenience
|
||||
typedef LLCurl::Responder Responder;
|
||||
typedef LLCurl::ResponderPtr ResponderPtr;
|
||||
/** @name Responder base classes */
|
||||
//@{
|
||||
|
||||
// The default actually already ignores responses.
|
||||
class ResponderIgnore : public Responder { };
|
||||
/**
|
||||
* @class ResponderBase
|
||||
* @brief Base class for all Responders.
|
||||
*
|
||||
* The life cycle of classes derived from this class is as follows:
|
||||
* They are allocated with new on the line where get(), getByteRange() or post() is called,
|
||||
* and the pointer to the allocated object is then put in a reference counting ResponderPtr.
|
||||
* This ResponderPtr is passed to CurlResponderBuffer::prepRequest which stores it in its
|
||||
* member mResponder. Hence, the life time of a Responder is never longer than its
|
||||
* associated CurlResponderBuffer, however, if everything works correctly, then normally a
|
||||
* responder is deleted in CurlResponderBuffer::removed_from_multi_handle by setting
|
||||
* mReponder to NULL.
|
||||
*/
|
||||
class ResponderBase : public AICurlResponderBufferEvents {
|
||||
public:
|
||||
typedef boost::shared_ptr<LLBufferArray> buffer_ptr_t;
|
||||
|
||||
protected:
|
||||
ResponderBase(void);
|
||||
virtual ~ResponderBase();
|
||||
|
||||
// Read body from buffer and put it into content. If status indicates success, interpret it as LLSD, otherwise copy it as-is.
|
||||
void decode_llsd_body(U32 status, std::string const& reason, LLChannelDescriptors const& channels, buffer_ptr_t const& buffer, LLSD& content);
|
||||
|
||||
// Read body from buffer and put it into content. Always copy it as-is.
|
||||
void decode_raw_body(U32 status, std::string const& reason, LLChannelDescriptors const& channels, buffer_ptr_t const& buffer, std::string& content);
|
||||
|
||||
protected:
|
||||
// Associated URL, used for debug output.
|
||||
std::string mURL;
|
||||
|
||||
// Headers received from the server.
|
||||
AIHTTPReceivedHeaders mReceivedHeaders;
|
||||
|
||||
// The curl result code.
|
||||
CURLcode mCode;
|
||||
|
||||
// Set when the transaction finished (with or without errors).
|
||||
bool mFinished;
|
||||
|
||||
public:
|
||||
// Called to set the URL of the current request for this Responder,
|
||||
// used only when printing debug output regarding activity of the Responder.
|
||||
void setURL(std::string const& url);
|
||||
|
||||
// Accessors.
|
||||
std::string const& getURL(void) const { return mURL; }
|
||||
CURLcode result_code(void) const { return mCode; }
|
||||
|
||||
// Called by CurlResponderBuffer::timed_out or CurlResponderBuffer::processOutput.
|
||||
virtual void finished(CURLcode code, U32 http_status, std::string const& reason, LLChannelDescriptors const& channels, buffer_ptr_t const& buffer) = 0;
|
||||
|
||||
// Return true if the curl thread is done with this transaction.
|
||||
// If this returns true then it is guaranteed that none of the
|
||||
// virtual functions will be called anymore: the curl thread
|
||||
// will not access this object anymore.
|
||||
// Note that normally you don't need to call this function.
|
||||
bool is_finished(void) const { return mFinished; }
|
||||
|
||||
protected:
|
||||
// AICurlResponderBufferEvents
|
||||
|
||||
// Called when the "HTTP/1.x <status> <reason>" header is received.
|
||||
/*virtual*/ void received_HTTP_header(void)
|
||||
{
|
||||
// It's possible that this page was moved (302), so we already saw headers
|
||||
// from the 302 page and are starting over on the new page now.
|
||||
mReceivedHeaders.clear();
|
||||
}
|
||||
|
||||
// Called for all remaining headers.
|
||||
/*virtual*/ void received_header(std::string const& key, std::string const& value)
|
||||
{
|
||||
mReceivedHeaders.addHeader(key, value);
|
||||
}
|
||||
|
||||
// Called when the whole transaction is completed (also the body was received), but before the body is processed.
|
||||
/*virtual*/ void completed_headers(U32 status, std::string const& reason, AITransferInfo* info)
|
||||
{
|
||||
completedHeaders(status, reason, mReceivedHeaders);
|
||||
}
|
||||
|
||||
public:
|
||||
// Derived classes that implement completed_headers()/completedHeaders() should return true here.
|
||||
virtual bool needsHeaders(void) const { return false; }
|
||||
|
||||
// A derived class should return true if curl should follow redirections.
|
||||
// The default is not to follow redirections.
|
||||
virtual bool followRedir(void) { return false; }
|
||||
|
||||
// Timeout policy to use.
|
||||
virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const = 0;
|
||||
|
||||
protected:
|
||||
// Derived classes can override this to get the HTML headers that were received, when the message is completed.
|
||||
virtual void completedHeaders(U32 status, std::string const& reason, AIHTTPReceivedHeaders const& headers)
|
||||
{
|
||||
// The default does nothing.
|
||||
}
|
||||
|
||||
private:
|
||||
// Used by ResponderPtr. Object is deleted when reference count reaches zero.
|
||||
LLAtomicU32 mReferenceCount;
|
||||
|
||||
friend void intrusive_ptr_add_ref(ResponderBase* p); // Called by boost::intrusive_ptr when a new copy of a boost::intrusive_ptr<ResponderBase> is made.
|
||||
friend void intrusive_ptr_release(ResponderBase* p); // Called by boost::intrusive_ptr when a boost::intrusive_ptr<ResponderBase> is destroyed.
|
||||
// This function must delete the ResponderBase object when the reference count reaches zero.
|
||||
};
|
||||
|
||||
/**
|
||||
* @class ResponderWithCompleted
|
||||
* @brief Base class for Responders that implement completed, or completedRaw if the response is not LLSD.
|
||||
*/
|
||||
class ResponderWithCompleted : public ResponderBase {
|
||||
protected:
|
||||
// ResponderBase event
|
||||
|
||||
// The responder finished. Do not override this function in derived classes; override completedRaw instead.
|
||||
/*virtual*/ void finished(CURLcode code, U32 http_status, std::string const& reason, LLChannelDescriptors const& channels, buffer_ptr_t const& buffer)
|
||||
{
|
||||
mCode = code;
|
||||
// Allow classes derived from ResponderBase to override completedRaw
|
||||
// (if not they should override completed or be derived from Responder instead).
|
||||
completedRaw(http_status, reason, channels, buffer);
|
||||
mFinished = true;
|
||||
}
|
||||
|
||||
protected:
|
||||
// Events generated by this class.
|
||||
|
||||
// Derived classes can override this to get the raw data of the body of the HTML message that was received.
|
||||
// The default is to interpret the content as LLSD and call completed().
|
||||
virtual void completedRaw(U32 status, std::string const& reason, LLChannelDescriptors const& channels, buffer_ptr_t const& buffer);
|
||||
|
||||
// ... or, derived classes can override this to get LLSD content when the message is completed.
|
||||
// The default aborts, as it should never be called (can't make it pure virtual though, so
|
||||
// classes that override completedRaw don't need to implement this function, too).
|
||||
virtual void completed(U32 status, std::string const& reason, LLSD const& content);
|
||||
|
||||
#ifdef SHOW_ASSERT
|
||||
// Responders derived from this class must override either completedRaw or completed.
|
||||
// They may not attempt to override any of the virual functions defined by ResponderBase.
|
||||
// Define those functions here with different parameters in order to cause a compile
|
||||
// warning when a class accidently tries to override them.
|
||||
enum YOU_ARE_DERIVING_FROM_THE_WRONG_CLASS { };
|
||||
virtual void result(YOU_ARE_DERIVING_FROM_THE_WRONG_CLASS) { }
|
||||
virtual void errorWithContent(YOU_ARE_DERIVING_FROM_THE_WRONG_CLASS) { }
|
||||
virtual void error(YOU_ARE_DERIVING_FROM_THE_WRONG_CLASS) { }
|
||||
#endif
|
||||
};
|
||||
|
||||
/**
|
||||
* @class ResponderWithResult
|
||||
* @brief Base class for reponders that expect LLSD in the body of the reply.
|
||||
*
|
||||
* Classes derived from ResponderWithResult must implement result, and either errorWithContent or error.
|
||||
*/
|
||||
class ResponderWithResult : public ResponderBase {
|
||||
protected:
|
||||
// The responder finished. Do not override this function in derived classes; use ResponderWithCompleted instead.
|
||||
/*virtual*/ void finished(CURLcode code, U32 http_status, std::string const& reason, LLChannelDescriptors const& channels, buffer_ptr_t const& buffer);
|
||||
|
||||
protected:
|
||||
// Events generated by this class.
|
||||
|
||||
// Derived classes must override this to receive the content of a body upon success.
|
||||
virtual void result(LLSD const& content) = 0;
|
||||
|
||||
// Derived classes can override this to get informed when a bad HTML status code is received.
|
||||
// The default calls error().
|
||||
virtual void errorWithContent(U32 status, std::string const& reason, LLSD const& content);
|
||||
|
||||
// ... or, derived classes can override this to get informed when a bad HTML status code is received.
|
||||
// The default prints the error to llinfos.
|
||||
virtual void error(U32 status, std::string const& reason);
|
||||
|
||||
public:
|
||||
// Called from LLSDMessage::ResponderAdapter::listener.
|
||||
// LLSDMessage::ResponderAdapter is a hack, showing among others by fact that it needs these functions.
|
||||
|
||||
void pubErrorWithContent(CURLcode code, U32 status, std::string const& reason, LLSD const& content) { mCode = code; errorWithContent(status, reason, content); mFinished = true; }
|
||||
void pubResult(LLSD const& content) { mCode = CURLE_OK; result(content); mFinished = true; }
|
||||
|
||||
#ifdef SHOW_ASSERT
|
||||
// Responders derived from this class must override result, and either errorWithContent or error.
|
||||
// They may not attempt to override any of the virual functions defined by ResponderWithCompleted.
|
||||
// Define those functions here with different parameter in order to cause a compile
|
||||
// warning when a class accidently tries to override them.
|
||||
enum YOU_ARE_DERIVING_FROM_THE_WRONG_CLASS { };
|
||||
virtual void completedRaw(YOU_ARE_DERIVING_FROM_THE_WRONG_CLASS) { }
|
||||
virtual void completed(YOU_ARE_DERIVING_FROM_THE_WRONG_CLASS) { }
|
||||
#endif
|
||||
};
|
||||
|
||||
/**
|
||||
* @class LegacyPolledResponder
|
||||
* @brief As ResponderWithCompleted but caches the result for polling.
|
||||
*
|
||||
* This class allows old polling code to poll if the transaction finished
|
||||
* by calling is_finished() (from the main the thread) and then access the
|
||||
* results-- as opposed to immediately digesting the results when any of
|
||||
* the virtual functions are called.
|
||||
*/
|
||||
class LegacyPolledResponder : public ResponderWithCompleted {
|
||||
protected:
|
||||
U32 mStatus;
|
||||
std::string mReason;
|
||||
|
||||
protected:
|
||||
// The responder finished. Do not override this function in derived classes.
|
||||
/*virtual*/ void finished(CURLcode code, U32 http_status, std::string const& reason, LLChannelDescriptors const& channels, buffer_ptr_t const& buffer)
|
||||
{
|
||||
mStatus = http_status;
|
||||
mReason = reason;
|
||||
// Call base class implementation.
|
||||
ResponderWithCompleted::finished(code, http_status, reason, channels, buffer);
|
||||
}
|
||||
|
||||
public:
|
||||
LegacyPolledResponder(void) : mStatus(HTTP_INTERNAL_ERROR) { }
|
||||
|
||||
// Accessors.
|
||||
U32 http_status(void) const { return mStatus; }
|
||||
std::string const& reason(void) const { return mReason; }
|
||||
};
|
||||
|
||||
/**
|
||||
* @class ResponderIgnoreBody
|
||||
* @brief Base class for responders that ignore the result body.
|
||||
*/
|
||||
class ResponderIgnoreBody : public ResponderWithResult {
|
||||
void result(LLSD const&) { }
|
||||
};
|
||||
|
||||
/**
|
||||
* @class ResponderIgnore
|
||||
* @brief Responder that ignores the reply, if any, from the server.
|
||||
*/
|
||||
class ResponderIgnore : public ResponderIgnoreBody {
|
||||
/*virtual*/ AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return responderIgnore_timeout;}
|
||||
};
|
||||
|
||||
// A Responder is passed around as ResponderPtr, which causes it to automatically
|
||||
// destruct when there are no pointers left pointing to it.
|
||||
typedef boost::intrusive_ptr<ResponderBase> ResponderPtr;
|
||||
|
||||
//@}
|
||||
|
||||
/** @name non-blocking API */
|
||||
//@{
|
||||
static void head(
|
||||
const std::string& url,
|
||||
ResponderPtr,
|
||||
const LLSD& headers = LLSD(),
|
||||
const F32 timeout=HTTP_REQUEST_EXPIRY_SECS);
|
||||
static void getByteRange(const std::string& url, S32 offset, S32 bytes, ResponderPtr, const LLSD& headers=LLSD(), const F32 timeout=HTTP_REQUEST_EXPIRY_SECS);
|
||||
static void get(const std::string& url, ResponderPtr, const LLSD& headers = LLSD(), const F32 timeout=HTTP_REQUEST_EXPIRY_SECS);
|
||||
static void get(const std::string& url, const LLSD& query, ResponderPtr, const LLSD& headers = LLSD(), const F32 timeout=HTTP_REQUEST_EXPIRY_SECS);
|
||||
static void head(std::string const& url, ResponderPtr responder, AIHTTPHeaders& headers);
|
||||
static void head(std::string const& url, ResponderPtr responder)
|
||||
{ AIHTTPHeaders headers; head(url, responder, headers); }
|
||||
|
||||
static void put(
|
||||
const std::string& url,
|
||||
const LLSD& body,
|
||||
ResponderPtr,
|
||||
const LLSD& headers = LLSD(),
|
||||
const F32 timeout=HTTP_REQUEST_EXPIRY_SECS);
|
||||
static void getHeaderOnly(const std::string& url, ResponderPtr, const F32 timeout=HTTP_REQUEST_EXPIRY_SECS);
|
||||
static void getHeaderOnly(const std::string& url, ResponderPtr, const LLSD& headers, const F32 timeout=HTTP_REQUEST_EXPIRY_SECS);
|
||||
static void getByteRange(std::string const& url, S32 offset, S32 bytes, ResponderPtr responder, AIHTTPHeaders& headers);
|
||||
static void getByteRange(std::string const& url, S32 offset, S32 bytes, ResponderPtr responder)
|
||||
{ AIHTTPHeaders headers; getByteRange(url, offset, bytes, responder, headers); }
|
||||
|
||||
static void get(std::string const& url, ResponderPtr responder, AIHTTPHeaders& headers);
|
||||
static void get(std::string const& url, ResponderPtr responder)
|
||||
{ AIHTTPHeaders headers; get(url, responder, headers); }
|
||||
|
||||
static void get(std::string const& url, LLSD const& query, ResponderPtr responder, AIHTTPHeaders& headers);
|
||||
static void get(std::string const& url, LLSD const& query, ResponderPtr responder)
|
||||
{ AIHTTPHeaders headers; get(url, query, responder, headers); }
|
||||
|
||||
static void put(std::string const& url, LLSD const& body, ResponderPtr responder, AIHTTPHeaders& headers);
|
||||
static void put(std::string const& url, LLSD const& body, ResponderPtr responder)
|
||||
{ AIHTTPHeaders headers; put(url, body, responder, headers); }
|
||||
|
||||
static void getHeaderOnly(std::string const& url, ResponderPtr responder, AIHTTPHeaders& headers);
|
||||
static void getHeaderOnly(std::string const& url, ResponderPtr responder)
|
||||
{ AIHTTPHeaders headers; getHeaderOnly(url, responder, headers); }
|
||||
|
||||
static void post(std::string const& url, LLSD const& body, ResponderPtr responder, AIHTTPHeaders& headers);
|
||||
static void post(std::string const& url, LLSD const& body, ResponderPtr responder)
|
||||
{ AIHTTPHeaders headers; post(url, body, responder, headers); }
|
||||
|
||||
/** Takes ownership of request and deletes it when sent */
|
||||
static void postXMLRPC(std::string const& url, XMLRPC_REQUEST request, ResponderPtr responder, AIHTTPHeaders& headers);
|
||||
static void postXMLRPC(std::string const& url, XMLRPC_REQUEST request, ResponderPtr responder)
|
||||
{ AIHTTPHeaders headers; postXMLRPC(url, request, responder, headers); }
|
||||
|
||||
static void postXMLRPC(std::string const& url, char const* method, XMLRPC_VALUE value, ResponderPtr responder, AIHTTPHeaders& headers);
|
||||
static void postXMLRPC(std::string const& url, char const* method, XMLRPC_VALUE value, ResponderPtr responder)
|
||||
{ AIHTTPHeaders headers; postXMLRPC(url, method, value, responder, headers); }
|
||||
|
||||
static void post(
|
||||
const std::string& url,
|
||||
const LLSD& body,
|
||||
ResponderPtr,
|
||||
const LLSD& headers = LLSD(),
|
||||
const F32 timeout=HTTP_REQUEST_EXPIRY_SECS);
|
||||
/** Takes ownership of data and deletes it when sent */
|
||||
static void postRaw(
|
||||
const std::string& url,
|
||||
const U8* data,
|
||||
S32 size,
|
||||
ResponderPtr responder,
|
||||
const LLSD& headers = LLSD(),
|
||||
const F32 timeout=HTTP_REQUEST_EXPIRY_SECS);
|
||||
static void postFile(
|
||||
const std::string& url,
|
||||
const std::string& filename,
|
||||
ResponderPtr,
|
||||
const LLSD& headers = LLSD(),
|
||||
const F32 timeout=HTTP_REQUEST_EXPIRY_SECS);
|
||||
static void postFile(
|
||||
const std::string& url,
|
||||
const LLUUID& uuid,
|
||||
LLAssetType::EType asset_type,
|
||||
ResponderPtr responder,
|
||||
const LLSD& headers = LLSD(),
|
||||
const F32 timeout=HTTP_REQUEST_EXPIRY_SECS);
|
||||
static void postRaw(std::string const& url, const char* data, S32 size, ResponderPtr responder, AIHTTPHeaders& headers);
|
||||
static void postRaw(std::string const& url, const char* data, S32 size, ResponderPtr responder)
|
||||
{ AIHTTPHeaders headers; postRaw(url, data, size, responder, headers); }
|
||||
|
||||
static void postFile(std::string const& url, std::string const& filename, ResponderPtr responder, AIHTTPHeaders& headers);
|
||||
static void postFile(std::string const& url, std::string const& filename, ResponderPtr responder)
|
||||
{ AIHTTPHeaders headers; postFile(url, filename, responder, headers); }
|
||||
|
||||
static void postFile(std::string const& url, const LLUUID& uuid, LLAssetType::EType asset_type, ResponderPtr responder, AIHTTPHeaders& headers);
|
||||
static void postFile(std::string const& url, const LLUUID& uuid, LLAssetType::EType asset_type, ResponderPtr responder)
|
||||
{ AIHTTPHeaders headers; postFile(url, uuid, asset_type, responder, headers); }
|
||||
|
||||
static void del(std::string const& url, ResponderPtr responder, AIHTTPHeaders& headers);
|
||||
static void del(std::string const& url, ResponderPtr responder)
|
||||
{ AIHTTPHeaders headers; del(url, responder, headers); }
|
||||
|
||||
static void del(
|
||||
const std::string& url,
|
||||
ResponderPtr responder,
|
||||
const LLSD& headers = LLSD(),
|
||||
const F32 timeout=HTTP_REQUEST_EXPIRY_SECS);
|
||||
///< sends a DELETE method, but we can't call it delete in c++
|
||||
|
||||
/**
|
||||
@@ -122,22 +387,28 @@ public:
|
||||
* @param headers A map of key:value headers to pass to the request
|
||||
* @param timeout The number of seconds to give the server to respond.
|
||||
*/
|
||||
static void move(
|
||||
const std::string& url,
|
||||
const std::string& destination,
|
||||
ResponderPtr responder,
|
||||
const LLSD& headers = LLSD(),
|
||||
const F32 timeout=HTTP_REQUEST_EXPIRY_SECS);
|
||||
static void move(std::string const& url, std::string const& destination, ResponderPtr responder, AIHTTPHeaders& headers);
|
||||
static void move(std::string const& url, std::string const& destination, ResponderPtr responder)
|
||||
{ AIHTTPHeaders headers; move(url, destination, responder, headers); }
|
||||
|
||||
//@}
|
||||
|
||||
/**
|
||||
* @brief Blocking HTTP get that returns an LLSD map of status and body.
|
||||
* @brief Blocking HTTP GET that returns an LLSD map of status and body.
|
||||
*
|
||||
* @param url the complete serialized (and escaped) url to get
|
||||
* @return An LLSD of { 'status':status, 'body':payload }
|
||||
*/
|
||||
static LLSD blockingGet(const std::string& url);
|
||||
static LLSD blockingGet(std::string const& url);
|
||||
|
||||
/**
|
||||
* @brief Blocking HTTP GET that returns the raw body.
|
||||
*
|
||||
* @param url the complete serialized (and escaped) url to get
|
||||
* @param result the target string to write the body to
|
||||
* @return HTTP status
|
||||
*/
|
||||
static U32 blockingGetRaw(const std::string& url, std::string& result);
|
||||
|
||||
/**
|
||||
* @brief Blocking HTTP POST that returns an LLSD map of status and body.
|
||||
@@ -146,15 +417,7 @@ public:
|
||||
* @param body the LLSD post body
|
||||
* @return An LLSD of { 'status':status (an int), 'body':payload (an LLSD) }
|
||||
*/
|
||||
static LLSD blockingPost(const std::string& url, const LLSD& body);
|
||||
|
||||
|
||||
static void setPump(LLPumpIO& pump);
|
||||
///< must be called before any of the above calls are made
|
||||
static bool hasPump();
|
||||
///< for testing
|
||||
static LLPumpIO &getPump();
|
||||
///< Hippo special
|
||||
static LLSD blockingPost(std::string const& url, LLSD const& body);
|
||||
};
|
||||
|
||||
#endif // LL_LLHTTPCLIENT_H
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
/**
|
||||
* @file llhttpclientadapter.cpp
|
||||
* @brief
|
||||
*
|
||||
* $LicenseInfo:firstyear=2009&license=viewerlgpl$
|
||||
* Second Life Viewer Source Code
|
||||
* Copyright (C) 2010, 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$
|
||||
*/
|
||||
|
||||
#include "llhttpclientadapter.h"
|
||||
#include "llhttpclient.h"
|
||||
|
||||
LLHTTPClientAdapter::~LLHTTPClientAdapter()
|
||||
{
|
||||
}
|
||||
|
||||
void LLHTTPClientAdapter::get(const std::string& url, LLCurl::ResponderPtr responder)
|
||||
{
|
||||
LLSD empty_pragma_header;
|
||||
// Pragma is required to stop curl adding "no-cache"
|
||||
// Space is required to stop llurlrequest from turnning off proxying
|
||||
empty_pragma_header["Pragma"] = " ";
|
||||
LLHTTPClient::get(url, responder, empty_pragma_header);
|
||||
}
|
||||
|
||||
void LLHTTPClientAdapter::get(const std::string& url, LLCurl::ResponderPtr responder, const LLSD& headers)
|
||||
{
|
||||
LLSD empty_pragma_header = headers;
|
||||
// as above
|
||||
empty_pragma_header["Pragma"] = " ";
|
||||
LLHTTPClient::get(url, responder, empty_pragma_header);
|
||||
}
|
||||
|
||||
void LLHTTPClientAdapter::put(const std::string& url, const LLSD& body, LLCurl::ResponderPtr responder)
|
||||
{
|
||||
LLHTTPClient::put(url, body, responder);
|
||||
}
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
/**
|
||||
* @file llhttpclientadepter.h
|
||||
* @brief
|
||||
*
|
||||
* $LicenseInfo:firstyear=2008&license=viewerlgpl$
|
||||
* Second Life Viewer Source Code
|
||||
* Copyright (C) 2010, 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 LL_HTTPCLIENTADAPTER_H
|
||||
#define LL_HTTPCLIENTADAPTER_H
|
||||
|
||||
#include "llhttpclientinterface.h"
|
||||
#include "llsingleton.h" // LLSingleton<>
|
||||
|
||||
class LLHTTPClientAdapter : public LLHTTPClientInterface, public LLSingleton<LLHTTPClientAdapter>
|
||||
{
|
||||
public:
|
||||
virtual ~LLHTTPClientAdapter();
|
||||
virtual void get(const std::string& url, LLCurl::ResponderPtr responder);
|
||||
virtual void get(const std::string& url, LLCurl::ResponderPtr responder, const LLSD& headers);
|
||||
virtual void put(const std::string& url, const LLSD& body, LLCurl::ResponderPtr responder);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
/**
|
||||
* @file llhttpclientinterface.h
|
||||
* @brief
|
||||
*
|
||||
* $LicenseInfo:firstyear=2008&license=viewerlgpl$
|
||||
* Second Life Viewer Source Code
|
||||
* Copyright (C) 2010, 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 LL_LLHTTPCLIENTINTERFACE_H
|
||||
#define LL_LLHTTPCLIENTINTERFACE_H
|
||||
|
||||
#include "linden_common.h"
|
||||
#include "llcurl.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
class LLHTTPClientInterface
|
||||
{
|
||||
public:
|
||||
virtual ~LLHTTPClientInterface() {}
|
||||
virtual void get(const std::string& url, LLCurl::ResponderPtr responder) = 0;
|
||||
virtual void get(const std::string& url, LLCurl::ResponderPtr responder, const LLSD& headers) = 0;
|
||||
virtual void put(const std::string& url, const LLSD& body, LLCurl::ResponderPtr responder) = 0;
|
||||
};
|
||||
|
||||
#endif // LL_LLHTTPCLIENTINTERFACE_H
|
||||
|
||||
@@ -272,6 +272,7 @@ LLIOPipe::EStatus LLHTTPPipe::process_impl(
|
||||
context[CONTEXT_RESPONSE][CONTEXT_HEADERS] = headers;
|
||||
LLBufferStream ostr(channels, buffer.get());
|
||||
LLSDSerialize::toXML(mGoodResult, ostr);
|
||||
ostr << std::flush;
|
||||
|
||||
return STATUS_DONE;
|
||||
}
|
||||
@@ -284,7 +285,7 @@ LLIOPipe::EStatus LLHTTPPipe::process_impl(
|
||||
context[CONTEXT_RESPONSE]["statusCode"] = mStatusCode;
|
||||
context[CONTEXT_RESPONSE]["statusMessage"] = mStatusMessage;
|
||||
LLBufferStream ostr(channels, buffer.get());
|
||||
ostr << mStatusMessage;
|
||||
ostr << mStatusMessage << std::flush;
|
||||
|
||||
return STATUS_DONE;
|
||||
}
|
||||
@@ -293,7 +294,7 @@ LLIOPipe::EStatus LLHTTPPipe::process_impl(
|
||||
context[CONTEXT_RESPONSE][CONTEXT_HEADERS] = mHeaders;
|
||||
context[CONTEXT_RESPONSE]["statusCode"] = mStatusCode;
|
||||
LLBufferStream ostr(channels, buffer.get());
|
||||
ostr << mStatusMessage;
|
||||
ostr << mStatusMessage << std::flush;
|
||||
|
||||
return STATUS_DONE;
|
||||
}
|
||||
@@ -633,7 +634,7 @@ void LLHTTPResponder::markBad(
|
||||
LLBufferStream out(channels, buffer.get());
|
||||
out << HTTP_VERSION_STR << " 400 Bad Request\r\n\r\n<html>\n"
|
||||
<< "<title>Bad Request</title>\n<body>\nBad Request.\n"
|
||||
<< "</body>\n</html>\n";
|
||||
<< "</body>\n</html>\n" << std::flush;
|
||||
}
|
||||
|
||||
static LLFastTimer::DeclareTimer FTM_PROCESS_HTTP_RESPONDER("HTTP Responder");
|
||||
@@ -926,7 +927,7 @@ LLIOPipe::EStatus LLHTTPResponder::process_impl(
|
||||
mState = STATE_SHORT_CIRCUIT;
|
||||
str << HTTP_VERSION_STR << " 404 Not Found\r\n\r\n<html>\n"
|
||||
<< "<title>Not Found</title>\n<body>\nNode '" << mAbsPathAndQuery
|
||||
<< "' not found.\n</body>\n</html>\n";
|
||||
<< "' not found.\n</body>\n</html>\n" << std::flush;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -238,14 +238,14 @@ BOOL LLPartSysData::unpack(LLDataPacker &dp)
|
||||
|
||||
std::ostream& operator<<(std::ostream& s, const LLPartSysData &data)
|
||||
{
|
||||
s << "Flags: " << std::hex << data.mFlags;
|
||||
s << " Pattern: " << std::hex << (U32) data.mPattern << "\n";
|
||||
s << "Flags: " << std::hex << data.mFlags << std::dec;
|
||||
s << " Pattern: " << std::hex << (U32) data.mPattern << std::dec << "\n";
|
||||
s << "Age: [" << data.mStartAge << ", " << data.mMaxAge << "]\n";
|
||||
s << "Angle: [" << data.mInnerAngle << ", " << data.mOuterAngle << "]\n";
|
||||
s << "Burst Rate: " << data.mBurstRate << "\n";
|
||||
s << "Burst Radius: " << data.mBurstRadius << "\n";
|
||||
s << "Burst Speed: [" << data.mBurstSpeedMin << ", " << data.mBurstSpeedMax << "]\n";
|
||||
s << "Burst Part Count: " << std::hex << (U32) data.mBurstPartCount << "\n";
|
||||
s << "Burst Part Count: " << std::hex << (U32) data.mBurstPartCount << std::dec << "\n";
|
||||
s << "Angular Velocity: " << data.mAngularVelocity << "\n";
|
||||
s << "Accel: " << data.mPartAccel;
|
||||
return s;
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
#include "llhost.h"
|
||||
#include "message.h"
|
||||
#include "llsdutil.h"
|
||||
#include "aihttptimeoutpolicy.h"
|
||||
|
||||
// Declare a static LLSDMessage instance to ensure that we have a listener as
|
||||
// soon as someone tries to post on our canonical LLEventPump name.
|
||||
@@ -62,13 +63,15 @@ LLSDMessage::LLSDMessage():
|
||||
|
||||
bool LLSDMessage::httpListener(const LLSD& request)
|
||||
{
|
||||
llassert(false); // This function is never called. --Aleric
|
||||
|
||||
// Extract what we want from the request object. We do it all up front
|
||||
// partly to document what we expect.
|
||||
LLSD::String url(request["url"]);
|
||||
LLSD payload(request["payload"]);
|
||||
LLSD::String reply(request["reply"]);
|
||||
LLSD::String error(request["error"]);
|
||||
LLSD::Real timeout(request["timeout"]);
|
||||
LLSD::String timeoutpolicy(request["timeoutpolicy"]);
|
||||
// If the LLSD doesn't even have a "url" key, we doubt it was intended for
|
||||
// this listener.
|
||||
if (url.empty())
|
||||
@@ -77,21 +80,25 @@ bool LLSDMessage::httpListener(const LLSD& request)
|
||||
out << "request event without 'url' key to '" << mEventPump.getName() << "'";
|
||||
throw ArgError(out.str());
|
||||
}
|
||||
// Establish default timeout. This test relies on LLSD::asReal() returning
|
||||
// exactly 0.0 for an undef value.
|
||||
if (! timeout)
|
||||
{
|
||||
timeout = HTTP_REQUEST_EXPIRY_SECS;
|
||||
}
|
||||
LLHTTPClient::post(url, payload,
|
||||
new LLSDMessage::EventResponder(LLEventPumps::instance(),
|
||||
request,
|
||||
url, "POST", reply, error),
|
||||
LLSD(), // headers
|
||||
(F32)timeout);
|
||||
LLSDMessage::EventResponder* responder =
|
||||
new LLSDMessage::EventResponder(LLEventPumps::instance(), request, url, "POST", reply, error);
|
||||
responder->setTimeoutPolicy(timeoutpolicy);
|
||||
LLHTTPClient::post(url, payload, responder);
|
||||
return false;
|
||||
}
|
||||
|
||||
LLSDMessage::EventResponder::EventResponder(LLEventPumps& pumps, LLSD const& request, std::string const& target,
|
||||
std::string const& message, std::string const& replyPump, std::string const& errorPump) :
|
||||
mPumps(pumps), mReqID(request), mTarget(target), mMessage(message), mReplyPump(replyPump), mErrorPump(errorPump),
|
||||
mHTTPTimeoutPolicy(AIHTTPTimeoutPolicy::getTimeoutPolicyByName(std::string()))
|
||||
{
|
||||
}
|
||||
|
||||
void LLSDMessage::EventResponder::setTimeoutPolicy(std::string const& name)
|
||||
{
|
||||
mHTTPTimeoutPolicy = AIHTTPTimeoutPolicy::getTimeoutPolicyByName(name);
|
||||
}
|
||||
|
||||
void LLSDMessage::EventResponder::result(const LLSD& data)
|
||||
{
|
||||
// If our caller passed an empty replyPump name, they're not
|
||||
@@ -121,6 +128,7 @@ void LLSDMessage::EventResponder::errorWithContent(U32 status, const std::string
|
||||
LLSD info(mReqID.makeResponse());
|
||||
info["target"] = mTarget;
|
||||
info["message"] = mMessage;
|
||||
info["code"] = mCode;
|
||||
info["status"] = LLSD::Integer(status);
|
||||
info["reason"] = reason;
|
||||
info["content"] = content;
|
||||
@@ -137,7 +145,7 @@ void LLSDMessage::EventResponder::errorWithContent(U32 status, const std::string
|
||||
}
|
||||
}
|
||||
|
||||
LLSDMessage::ResponderAdapter::ResponderAdapter(LLHTTPClient::ResponderPtr responder,
|
||||
LLSDMessage::ResponderAdapter::ResponderAdapter(LLHTTPClient::ResponderWithResult* responder,
|
||||
const std::string& name):
|
||||
mResponder(responder),
|
||||
mReplyPump(name + ".reply", true), // tweak name for uniqueness
|
||||
@@ -147,15 +155,25 @@ LLSDMessage::ResponderAdapter::ResponderAdapter(LLHTTPClient::ResponderPtr respo
|
||||
mErrorPump.listen("self", boost::bind(&ResponderAdapter::listener, this, _1, false));
|
||||
}
|
||||
|
||||
std::string LLSDMessage::ResponderAdapter::getTimeoutPolicyName(void) const
|
||||
{
|
||||
return mResponder->getHTTPTimeoutPolicy().name();
|
||||
}
|
||||
|
||||
bool LLSDMessage::ResponderAdapter::listener(const LLSD& payload, bool success)
|
||||
{
|
||||
LLHTTPClient::ResponderWithResult* responder = dynamic_cast<LLHTTPClient::ResponderWithResult*>(mResponder.get());
|
||||
// If this assertion fails then ResponderAdapter has been used for a ResponderWithCompleted derived class,
|
||||
// which is not allowed because ResponderAdapter can only work for classes derived from Responder that
|
||||
// implement result() and errorWithContent (or just error).
|
||||
llassert_always(responder);
|
||||
if (success)
|
||||
{
|
||||
mResponder->pubResult(payload);
|
||||
responder->pubResult(payload);
|
||||
}
|
||||
else
|
||||
{
|
||||
mResponder->pubErrorWithContent(payload["status"].asInteger(), payload["reason"], payload["content"]);
|
||||
responder->pubErrorWithContent((CURLcode)payload["code"].asInteger(), payload["status"].asInteger(), payload["reason"], payload["content"]);
|
||||
}
|
||||
|
||||
/*---------------- MUST BE LAST STATEMENT BEFORE RETURN ----------------*/
|
||||
|
||||
@@ -37,6 +37,8 @@
|
||||
#include <stdexcept>
|
||||
|
||||
class LLSD;
|
||||
class AIHTTPTimeoutPolicy;
|
||||
extern AIHTTPTimeoutPolicy eventResponder_timeout;
|
||||
|
||||
/**
|
||||
* Class managing the messaging API described in
|
||||
@@ -64,37 +66,47 @@ public:
|
||||
* must be visible to the reply/error methods can conveniently be stored
|
||||
* on that class itself, if it's not already.
|
||||
*
|
||||
* The LLHTTPClient::Responder idiom requires a separate instance of a
|
||||
* The LLHTTPClient::ResponderBase idiom requires a separate instance of a
|
||||
* separate class so that it can dispatch to the code of interest by
|
||||
* calling canonical virtual methods. Interesting state must be copied
|
||||
* into that new object.
|
||||
*
|
||||
* With some trepidation, because existing response code is packaged in
|
||||
* LLHTTPClient::Responder subclasses, we provide this adapter class
|
||||
* LLHTTPClient::ResponderWithResult subclasses, we provide this adapter class
|
||||
* <i>for transitional purposes only.</i> Instantiate a new heap
|
||||
* ResponderAdapter with your new LLHTTPClient::ResponderPtr. Pass
|
||||
* ResponderAdapter::getReplyName() and/or getErrorName() in your
|
||||
* LLSDMessage (or LLViewerRegion::getCapAPI()) request event. The
|
||||
* ResponderAdapter will call the appropriate Responder method, then
|
||||
* @c delete itself.
|
||||
*
|
||||
* Singularity note: I think this class/API is a bad idea that makes things
|
||||
* more complex, a lot slower and less OO. The idea to get methods called
|
||||
* on the same class that does the request is a nice idea, but should
|
||||
* be implemented through boost::bind and NOT use LLSD. Avoid.
|
||||
* Also note that this only works for ResponderWithResult derived classes,
|
||||
* not for responders derived from ResponderWithCompleted.
|
||||
* --Aleric
|
||||
*/
|
||||
class ResponderAdapter
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Bind the new LLHTTPClient::Responder subclass instance.
|
||||
* Bind the new LLHTTPClient::ResponderWithResult subclass instance.
|
||||
*
|
||||
* Passing the constructor a name other than the default is only
|
||||
* interesting if you suspect some usage will lead to an exception or
|
||||
* log message.
|
||||
*/
|
||||
ResponderAdapter(LLHTTPClient::ResponderPtr responder,
|
||||
ResponderAdapter(LLHTTPClient::ResponderWithResult* responder,
|
||||
const std::string& name="ResponderAdapter");
|
||||
|
||||
/// EventPump name on which LLSDMessage should post reply event
|
||||
std::string getReplyName() const { return mReplyPump.getName(); }
|
||||
/// EventPump name on which LLSDMessage should post error event
|
||||
std::string getErrorName() const { return mErrorPump.getName(); }
|
||||
/// Name of timeout policy to use.
|
||||
std::string getTimeoutPolicyName() const;
|
||||
|
||||
private:
|
||||
// We have two different LLEventStreams, though we route them both to
|
||||
@@ -121,11 +133,13 @@ private:
|
||||
friend class LLCapabilityListener;
|
||||
/// Responder used for internal purposes by LLSDMessage and
|
||||
/// LLCapabilityListener. Others should use higher-level APIs.
|
||||
class EventResponder: public LLHTTPClient::Responder
|
||||
class EventResponder: public LLHTTPClient::ResponderWithResult
|
||||
{
|
||||
public:
|
||||
virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return *mHTTPTimeoutPolicy; }
|
||||
|
||||
/**
|
||||
* LLHTTPClient::Responder that dispatches via named LLEventPump instances.
|
||||
* LLHTTPClient::ResponderWithResult that dispatches via named LLEventPump instances.
|
||||
* We bind LLEventPumps, even though it's an LLSingleton, for testability.
|
||||
* We bind the string names of the desired LLEventPump instances rather
|
||||
* than actually obtain()ing them so we only obtain() the one we're going
|
||||
@@ -140,14 +154,9 @@ private:
|
||||
EventResponder(LLEventPumps& pumps,
|
||||
const LLSD& request,
|
||||
const std::string& target, const std::string& message,
|
||||
const std::string& replyPump, const std::string& errorPump):
|
||||
mPumps(pumps),
|
||||
mReqID(request),
|
||||
mTarget(target),
|
||||
mMessage(message),
|
||||
mReplyPump(replyPump),
|
||||
mErrorPump(errorPump)
|
||||
{}
|
||||
const std::string& replyPump, const std::string& errorPump);
|
||||
|
||||
void setTimeoutPolicy(std::string const& name);
|
||||
|
||||
virtual void result(const LLSD& data);
|
||||
virtual void errorWithContent(U32 status, const std::string& reason, const LLSD& content);
|
||||
@@ -156,6 +165,7 @@ private:
|
||||
LLEventPumps& mPumps;
|
||||
LLReqID mReqID;
|
||||
const std::string mTarget, mMessage, mReplyPump, mErrorPump;
|
||||
AIHTTPTimeoutPolicy const* mHTTPTimeoutPolicy;
|
||||
};
|
||||
|
||||
private:
|
||||
|
||||
@@ -36,8 +36,7 @@
|
||||
#include <algorithm>
|
||||
#include <openssl/x509_vfy.h>
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
#include "llcurl.h"
|
||||
#include "aicurleasyrequeststatemachine.h"
|
||||
#include "llioutil.h"
|
||||
#include "llmemtype.h"
|
||||
#include "llpumpio.h"
|
||||
@@ -47,6 +46,7 @@
|
||||
#include "llapr.h"
|
||||
#include "llscopedvolatileaprpool.h"
|
||||
#include "llfasttimer.h"
|
||||
#include "message.h"
|
||||
static const U32 HTTP_STATUS_PIPE_ERROR = 499;
|
||||
|
||||
/**
|
||||
@@ -54,41 +54,6 @@ static const U32 HTTP_STATUS_PIPE_ERROR = 499;
|
||||
*/
|
||||
const std::string CONTEXT_TRANSFERED_BYTES("transfered_bytes");
|
||||
|
||||
|
||||
static size_t headerCallback(char* data, size_t size, size_t nmemb, void* user);
|
||||
|
||||
/**
|
||||
* class LLURLRequestDetail
|
||||
*/
|
||||
class LLURLRequestDetail
|
||||
{
|
||||
public:
|
||||
LLURLRequestDetail();
|
||||
~LLURLRequestDetail();
|
||||
std::string mURL;
|
||||
AICurlEasyRequest mCurlEasyRequest;
|
||||
LLIOPipe::buffer_ptr_t mResponseBuffer;
|
||||
LLChannelDescriptors mChannels;
|
||||
U8* mLastRead;
|
||||
U32 mBodyLimit;
|
||||
S32 mByteAccumulator;
|
||||
bool mIsBodyLimitSet;
|
||||
};
|
||||
|
||||
LLURLRequestDetail::LLURLRequestDetail() :
|
||||
mCurlEasyRequest(false),
|
||||
mLastRead(NULL),
|
||||
mBodyLimit(0),
|
||||
mByteAccumulator(0),
|
||||
mIsBodyLimitSet(false)
|
||||
{
|
||||
}
|
||||
|
||||
LLURLRequestDetail::~LLURLRequestDetail()
|
||||
{
|
||||
mLastRead = NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* class LLURLRequest
|
||||
*/
|
||||
@@ -96,7 +61,8 @@ LLURLRequestDetail::~LLURLRequestDetail()
|
||||
// static
|
||||
std::string LLURLRequest::actionAsVerb(LLURLRequest::ERequestAction action)
|
||||
{
|
||||
static const std::string VERBS[] =
|
||||
static int const array_size = HTTP_MOVE + 1; // INVALID == 0
|
||||
static char const* const VERBS[array_size] =
|
||||
{
|
||||
"(invalid)",
|
||||
"HEAD",
|
||||
@@ -106,81 +72,88 @@ std::string LLURLRequest::actionAsVerb(LLURLRequest::ERequestAction action)
|
||||
"DELETE",
|
||||
"MOVE"
|
||||
};
|
||||
if(((S32)action <=0) || ((S32)action >= REQUEST_ACTION_COUNT))
|
||||
return VERBS[action >= array_size ? INVALID : action];
|
||||
}
|
||||
|
||||
// This might throw AICurlNoEasyHandle.
|
||||
LLURLRequest::LLURLRequest(LLURLRequest::ERequestAction action, std::string const& url, Injector* body,
|
||||
LLHTTPClient::ResponderPtr responder, AIHTTPHeaders& headers, bool is_auth, bool no_compression) :
|
||||
mAction(action), mURL(url), mIsAuth(is_auth), mNoCompression(no_compression),
|
||||
mBody(body), mResponder(responder), mHeaders(headers)
|
||||
{
|
||||
}
|
||||
|
||||
void LLURLRequest::initialize_impl(void)
|
||||
{
|
||||
if (mHeaders.hasHeader("Cookie"))
|
||||
{
|
||||
return VERBS[0];
|
||||
allowCookies();
|
||||
}
|
||||
return VERBS[action];
|
||||
}
|
||||
|
||||
LLURLRequest::LLURLRequest(LLURLRequest::ERequestAction action) :
|
||||
mAction(action)
|
||||
{
|
||||
LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
|
||||
// This might throw AICurlNoEasyHandle.
|
||||
initialize();
|
||||
}
|
||||
|
||||
LLURLRequest::LLURLRequest(
|
||||
LLURLRequest::ERequestAction action,
|
||||
const std::string& url) :
|
||||
mAction(action)
|
||||
{
|
||||
LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
|
||||
// This might throw AICurlNoEasyHandle.
|
||||
initialize();
|
||||
setURL(url);
|
||||
}
|
||||
|
||||
LLURLRequest::~LLURLRequest()
|
||||
{
|
||||
LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
|
||||
// If the header is "Pragma" with no value, the caller intends to
|
||||
// force libcurl to drop the Pragma header it so gratuitously inserts.
|
||||
// Before inserting the header, force libcurl to not use the proxy.
|
||||
std::string pragma_value;
|
||||
if (mHeaders.getValue("Pragma", pragma_value) && pragma_value.empty())
|
||||
{
|
||||
AICurlEasyRequest_wat curl_easy_request_w(*mDetail->mCurlEasyRequest);
|
||||
curl_easy_request_w->revokeCallbacks();
|
||||
curl_easy_request_w->send_events_to(NULL);
|
||||
useProxy(false);
|
||||
}
|
||||
delete mDetail;
|
||||
}
|
||||
|
||||
void LLURLRequest::setURL(const std::string& url)
|
||||
{
|
||||
mDetail->mURL = url;
|
||||
}
|
||||
if (mAction == HTTP_PUT || mAction == HTTP_POST)
|
||||
{
|
||||
// If the Content-Type header was passed in we defer to the caller's wisdom,
|
||||
// but if they did not specify a Content-Type, then ask the injector.
|
||||
mHeaders.addHeader("Content-Type", mBody->contentType(), AIHTTPHeaders::keep_existing_header);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Check to see if we have already set Accept or not. If no one
|
||||
// set it, set it to application/llsd+xml since that's what we
|
||||
// almost always want.
|
||||
mHeaders.addHeader("Accept", "application/llsd+xml", AIHTTPHeaders::keep_existing_header);
|
||||
}
|
||||
|
||||
std::string LLURLRequest::getURL() const
|
||||
{
|
||||
return mDetail->mURL;
|
||||
if (mAction == HTTP_POST && gMessageSystem)
|
||||
{
|
||||
mHeaders.addHeader("X-SecondLife-UDP-Listen-Port", llformat("%d", gMessageSystem->mPort));
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
try
|
||||
{
|
||||
AICurlEasyRequest_wat easy_request_w(*mCurlEasyRequest);
|
||||
easy_request_w->prepRequest(easy_request_w, mHeaders, mResponder);
|
||||
|
||||
if (mBody)
|
||||
{
|
||||
// This might throw AICurlNoBody.
|
||||
mBodySize = mBody->get_body(easy_request_w->sChannels, easy_request_w->getInput());
|
||||
}
|
||||
|
||||
success = configure(easy_request_w);
|
||||
}
|
||||
catch (AICurlNoBody const& error)
|
||||
{
|
||||
llwarns << "Injector::get_body() failed: " << error.what() << llendl;
|
||||
}
|
||||
|
||||
if (success)
|
||||
{
|
||||
// Continue to initialize base class.
|
||||
AICurlEasyRequestStateMachine::initialize_impl();
|
||||
}
|
||||
else
|
||||
{
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
void LLURLRequest::addHeader(const char* header)
|
||||
{
|
||||
LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
|
||||
AICurlEasyRequest_wat curlEasyRequest_w(*mDetail->mCurlEasyRequest);
|
||||
AICurlEasyRequest_wat curlEasyRequest_w(*mCurlEasyRequest);
|
||||
curlEasyRequest_w->addHeader(header);
|
||||
}
|
||||
|
||||
void LLURLRequest::checkRootCertificate(bool check)
|
||||
{
|
||||
AICurlEasyRequest_wat curlEasyRequest_w(*mDetail->mCurlEasyRequest);
|
||||
curlEasyRequest_w->setopt(CURLOPT_SSL_VERIFYPEER, check ? 1L : 0L);
|
||||
curlEasyRequest_w->setoptString(CURLOPT_ENCODING, "");
|
||||
}
|
||||
|
||||
void LLURLRequest::setBodyLimit(U32 size)
|
||||
{
|
||||
mDetail->mBodyLimit = size;
|
||||
mDetail->mIsBodyLimitSet = true;
|
||||
}
|
||||
|
||||
void LLURLRequest::setCallback(LLURLRequestComplete* callback)
|
||||
{
|
||||
LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
|
||||
mCompletionCallback = callback;
|
||||
AICurlEasyRequest_wat curlEasyRequest_w(*mDetail->mCurlEasyRequest);
|
||||
curlEasyRequest_w->setHeaderCallback(&headerCallback, (void*)callback);
|
||||
}
|
||||
|
||||
// Added to mitigate the effect of libcurl looking
|
||||
// for the ALL_PROXY and http_proxy env variables
|
||||
// and deciding to insert a Pragma: no-cache
|
||||
@@ -214,284 +187,28 @@ void LLURLRequest::useProxy(bool use_proxy)
|
||||
|
||||
LL_DEBUGS("Proxy") << "use_proxy = " << (use_proxy?'Y':'N') << ", env_proxy = " << (!env_proxy.empty() ? env_proxy : "(null)") << LL_ENDL;
|
||||
|
||||
AICurlEasyRequest_wat curlEasyRequest_w(*mDetail->mCurlEasyRequest);
|
||||
AICurlEasyRequest_wat curlEasyRequest_w(*mCurlEasyRequest);
|
||||
curlEasyRequest_w->setoptString(CURLOPT_PROXY, (use_proxy && !env_proxy.empty()) ? env_proxy : std::string(""));
|
||||
}
|
||||
|
||||
#ifdef AI_UNUSED
|
||||
void LLURLRequest::useProxy(const std::string &proxy)
|
||||
{
|
||||
AICurlEasyRequest_wat curlEasyRequest_w(*mDetail->mCurlEasyRequest);
|
||||
AICurlEasyRequest_wat curlEasyRequest_w(*mCurlEasyRequest);
|
||||
curlEasyRequest_w->setoptString(CURLOPT_PROXY, proxy);
|
||||
}
|
||||
#endif
|
||||
|
||||
void LLURLRequest::allowCookies()
|
||||
{
|
||||
AICurlEasyRequest_wat curlEasyRequest_w(*mDetail->mCurlEasyRequest);
|
||||
AICurlEasyRequest_wat curlEasyRequest_w(*mCurlEasyRequest);
|
||||
curlEasyRequest_w->setoptString(CURLOPT_COOKIEFILE, "");
|
||||
}
|
||||
|
||||
//virtual
|
||||
bool LLURLRequest::hasExpiration(void) const
|
||||
bool LLURLRequest::configure(AICurlEasyRequest_wat const& curlEasyRequest_w)
|
||||
{
|
||||
// Currently, this ALWAYS returns false -- because only AICurlEasyRequestStateMachine uses buffered
|
||||
// AICurlEasyRequest objects, and LLURLRequest uses (unbuffered) AICurlEasyRequest directly, which
|
||||
// have no expiration facility.
|
||||
return mDetail->mCurlEasyRequest.isBuffered();
|
||||
}
|
||||
|
||||
//virtual
|
||||
bool LLURLRequest::hasNotExpired(void) const
|
||||
{
|
||||
if (!mDetail->mCurlEasyRequest.isBuffered())
|
||||
return true;
|
||||
AICurlEasyRequest_wat buffered_easy_request_w(*mDetail->mCurlEasyRequest);
|
||||
AICurlResponderBuffer_wat buffer_w(*mDetail->mCurlEasyRequest);
|
||||
return buffer_w->isValid();
|
||||
}
|
||||
|
||||
// virtual
|
||||
LLIOPipe::EStatus LLURLRequest::handleError(
|
||||
LLIOPipe::EStatus status,
|
||||
LLPumpIO* pump)
|
||||
{
|
||||
DoutEntering(dc::curl, "LLURLRequest::handleError(" << LLIOPipe::lookupStatusString(status) << ", " << (void*)pump << ")");
|
||||
LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
|
||||
|
||||
if (LL_LIKELY(!mDetail->mCurlEasyRequest.isBuffered())) // Currently always true.
|
||||
{
|
||||
// The last reference will be deleted when the pump that this chain belongs to
|
||||
// is removed from the running chains vector, upon returning from this function.
|
||||
// This keeps the CurlEasyRequest object alive until the curl thread cleanly removed it.
|
||||
Dout(dc::curl, "Calling mDetail->mCurlEasyRequest.removeRequest()");
|
||||
mDetail->mCurlEasyRequest.removeRequest();
|
||||
}
|
||||
else if (!hasNotExpired())
|
||||
{
|
||||
// The buffered version has it's own time out handling, and that already expired,
|
||||
// so we can ignore the expiration of this timer (currently never happens).
|
||||
// I left it here because it's what LL did (in the form if (!isValid() ...),
|
||||
// and it would be relevant if this characteristic of mDetail->mCurlEasyRequest
|
||||
// would change. --Aleric
|
||||
return STATUS_EXPIRED ;
|
||||
}
|
||||
|
||||
if(mCompletionCallback && pump)
|
||||
{
|
||||
LLURLRequestComplete* complete = NULL;
|
||||
complete = (LLURLRequestComplete*)mCompletionCallback.get();
|
||||
complete->httpStatus(
|
||||
HTTP_STATUS_PIPE_ERROR,
|
||||
LLIOPipe::lookupStatusString(status));
|
||||
complete->responseStatus(status);
|
||||
pump->respond(complete);
|
||||
mCompletionCallback = NULL;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
void LLURLRequest::added_to_multi_handle(AICurlEasyRequest_wat&)
|
||||
{
|
||||
}
|
||||
|
||||
void LLURLRequest::finished(AICurlEasyRequest_wat&)
|
||||
{
|
||||
}
|
||||
|
||||
void LLURLRequest::removed_from_multi_handle(AICurlEasyRequest_wat&)
|
||||
{
|
||||
mRemoved = true;
|
||||
}
|
||||
|
||||
static LLFastTimer::DeclareTimer FTM_PROCESS_URL_REQUEST("URL Request");
|
||||
|
||||
// virtual
|
||||
LLIOPipe::EStatus LLURLRequest::process_impl(
|
||||
const LLChannelDescriptors& channels,
|
||||
buffer_ptr_t& buffer,
|
||||
bool& eos,
|
||||
LLSD& context,
|
||||
LLPumpIO* pump)
|
||||
{
|
||||
LLFastTimer t(FTM_PROCESS_URL_REQUEST);
|
||||
PUMP_DEBUG;
|
||||
LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
|
||||
//llinfos << "LLURLRequest::process_impl()" << llendl;
|
||||
if (!buffer) return STATUS_ERROR;
|
||||
|
||||
if (!mDetail) return STATUS_ERROR; //Seems to happen on occasion. Need to hunt down why.
|
||||
|
||||
// we're still waiting or processing, check how many
|
||||
// bytes we have accumulated.
|
||||
const S32 MIN_ACCUMULATION = 100000;
|
||||
if(pump && (mDetail->mByteAccumulator > MIN_ACCUMULATION))
|
||||
{
|
||||
static LLFastTimer::DeclareTimer FTM_URL_ADJUST_TIMEOUT("Adjust Timeout");
|
||||
LLFastTimer t(FTM_URL_ADJUST_TIMEOUT);
|
||||
// This is a pretty sloppy calculation, but this
|
||||
// tries to make the gross assumption that if data
|
||||
// is coming in at 56kb/s, then this transfer will
|
||||
// probably succeed. So, if we're accumlated
|
||||
// 100,000 bytes (MIN_ACCUMULATION) then let's
|
||||
// give this client another 2s to complete.
|
||||
const F32 TIMEOUT_ADJUSTMENT = 2.0f;
|
||||
mDetail->mByteAccumulator = 0;
|
||||
pump->adjustTimeoutSeconds(TIMEOUT_ADJUSTMENT);
|
||||
lldebugs << "LLURLRequest adjustTimeoutSeconds for request: " << mDetail->mURL << llendl;
|
||||
if (mState == STATE_INITIALIZED)
|
||||
{
|
||||
llinfos << "LLURLRequest adjustTimeoutSeconds called during upload" << llendl;
|
||||
}
|
||||
}
|
||||
|
||||
switch(mState)
|
||||
{
|
||||
case STATE_INITIALIZED:
|
||||
{
|
||||
PUMP_DEBUG;
|
||||
// We only need to wait for input if we are uploading
|
||||
// something.
|
||||
if(((HTTP_PUT == mAction) || (HTTP_POST == mAction)) && !eos)
|
||||
{
|
||||
// we're waiting to get all of the information
|
||||
return STATUS_BREAK;
|
||||
}
|
||||
|
||||
// *FIX: bit of a hack, but it should work. The configure and
|
||||
// callback method expect this information to be ready.
|
||||
mDetail->mResponseBuffer = buffer;
|
||||
mDetail->mChannels = channels;
|
||||
if(!configure())
|
||||
{
|
||||
return STATUS_ERROR;
|
||||
}
|
||||
mRemoved = false;
|
||||
mState = STATE_WAITING_FOR_RESPONSE;
|
||||
mDetail->mCurlEasyRequest.addRequest(); // Add easy handle to multi handle.
|
||||
|
||||
return STATUS_BREAK;
|
||||
}
|
||||
case STATE_WAITING_FOR_RESPONSE:
|
||||
case STATE_PROCESSING_RESPONSE:
|
||||
{
|
||||
if (!mRemoved) // Not removed from multi handle yet?
|
||||
{
|
||||
// Easy handle is still being processed.
|
||||
return STATUS_BREAK;
|
||||
}
|
||||
// Curl thread finished with this easy handle.
|
||||
mState = STATE_CURL_FINISHED;
|
||||
}
|
||||
case STATE_CURL_FINISHED:
|
||||
{
|
||||
PUMP_DEBUG;
|
||||
LLIOPipe::EStatus status = STATUS_NO_CONNECTION; // Catch-all failure code.
|
||||
|
||||
// Left braces in order not to change indentation.
|
||||
{
|
||||
CURLcode result;
|
||||
|
||||
static LLFastTimer::DeclareTimer FTM_PROCESS_URL_REQUEST_GET_RESULT("Get Result");
|
||||
|
||||
AICurlEasyRequest_wat(*mDetail->mCurlEasyRequest)->getResult(&result);
|
||||
|
||||
mState = STATE_HAVE_RESPONSE;
|
||||
context[CONTEXT_REQUEST][CONTEXT_TRANSFERED_BYTES] = mRequestTransferedBytes;
|
||||
context[CONTEXT_RESPONSE][CONTEXT_TRANSFERED_BYTES] = mResponseTransferedBytes;
|
||||
lldebugs << this << "Setting context to " << context << llendl;
|
||||
switch(result)
|
||||
{
|
||||
case CURLE_OK:
|
||||
case CURLE_WRITE_ERROR:
|
||||
// NB: The error indication means that we stopped the
|
||||
// writing due the body limit being reached
|
||||
if(mCompletionCallback && pump)
|
||||
{
|
||||
LLURLRequestComplete* complete = NULL;
|
||||
complete = (LLURLRequestComplete*)
|
||||
mCompletionCallback.get();
|
||||
complete->responseStatus(
|
||||
result == CURLE_OK
|
||||
? STATUS_OK : STATUS_STOP);
|
||||
LLPumpIO::links_t chain;
|
||||
LLPumpIO::LLLinkInfo link;
|
||||
link.mPipe = mCompletionCallback;
|
||||
link.mChannels = LLBufferArray::makeChannelConsumer(
|
||||
channels);
|
||||
chain.push_back(link);
|
||||
static LLFastTimer::DeclareTimer FTM_PROCESS_URL_PUMP_RESPOND("Pump Respond");
|
||||
{
|
||||
LLFastTimer t(FTM_PROCESS_URL_PUMP_RESPOND);
|
||||
pump->respond(chain, buffer, context);
|
||||
}
|
||||
mCompletionCallback = NULL;
|
||||
}
|
||||
status = STATUS_BREAK; // This is what the old code returned. Does it make sense?
|
||||
break;
|
||||
case CURLE_FAILED_INIT:
|
||||
case CURLE_COULDNT_CONNECT:
|
||||
status = STATUS_NO_CONNECTION;
|
||||
break;
|
||||
default:
|
||||
llwarns << "URLRequest Error: " << result
|
||||
<< ", "
|
||||
<< LLCurl::strerror(result)
|
||||
<< ", "
|
||||
<< (mDetail->mURL.empty() ? "<EMPTY URL>" : mDetail->mURL)
|
||||
<< llendl;
|
||||
status = STATUS_ERROR;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
case STATE_HAVE_RESPONSE:
|
||||
PUMP_DEBUG;
|
||||
// we already stuffed everything into channel in in the curl
|
||||
// callback, so we are done.
|
||||
eos = true;
|
||||
context[CONTEXT_REQUEST][CONTEXT_TRANSFERED_BYTES] = mRequestTransferedBytes;
|
||||
context[CONTEXT_RESPONSE][CONTEXT_TRANSFERED_BYTES] = mResponseTransferedBytes;
|
||||
lldebugs << this << "Setting context to " << context << llendl;
|
||||
return STATUS_DONE;
|
||||
|
||||
default:
|
||||
PUMP_DEBUG;
|
||||
context[CONTEXT_REQUEST][CONTEXT_TRANSFERED_BYTES] = mRequestTransferedBytes;
|
||||
context[CONTEXT_RESPONSE][CONTEXT_TRANSFERED_BYTES] = mResponseTransferedBytes;
|
||||
lldebugs << this << "Setting context to " << context << llendl;
|
||||
return STATUS_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
void LLURLRequest::initialize()
|
||||
{
|
||||
LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
|
||||
mState = STATE_INITIALIZED;
|
||||
// This might throw AICurlNoEasyHandle.
|
||||
mDetail = new LLURLRequestDetail;
|
||||
|
||||
{
|
||||
AICurlEasyRequest_wat curlEasyRequest_w(*mDetail->mCurlEasyRequest);
|
||||
curlEasyRequest_w->setWriteCallback(&downCallback, (void*)this);
|
||||
curlEasyRequest_w->setReadCallback(&upCallback, (void*)this);
|
||||
}
|
||||
|
||||
mRequestTransferedBytes = 0;
|
||||
mResponseTransferedBytes = 0;
|
||||
}
|
||||
|
||||
static LLFastTimer::DeclareTimer FTM_URL_REQUEST_CONFIGURE("URL Configure");
|
||||
bool LLURLRequest::configure()
|
||||
{
|
||||
LLFastTimer t(FTM_URL_REQUEST_CONFIGURE);
|
||||
|
||||
LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
|
||||
bool rv = false;
|
||||
S32 bytes = mDetail->mResponseBuffer->countAfter(
|
||||
mDetail->mChannels.in(),
|
||||
NULL);
|
||||
{
|
||||
AICurlEasyRequest_wat curlEasyRequest_w(*mDetail->mCurlEasyRequest);
|
||||
switch(mAction)
|
||||
{
|
||||
case HTTP_HEAD:
|
||||
@@ -506,28 +223,30 @@ bool LLURLRequest::configure()
|
||||
curlEasyRequest_w->setopt(CURLOPT_FOLLOWLOCATION, 1);
|
||||
|
||||
// Set Accept-Encoding to allow response compression
|
||||
curlEasyRequest_w->setoptString(CURLOPT_ENCODING, "");
|
||||
curlEasyRequest_w->setoptString(CURLOPT_ENCODING, mNoCompression ? "identity" : "");
|
||||
rv = true;
|
||||
break;
|
||||
|
||||
case HTTP_PUT:
|
||||
{
|
||||
// Disable the expect http 1.1 extension. POST and PUT default
|
||||
// to turning this on, and I am not too sure what it means.
|
||||
// to using this, causing the broken server to get confused.
|
||||
curlEasyRequest_w->addHeader("Expect:");
|
||||
curlEasyRequest_w->setopt(CURLOPT_UPLOAD, 1);
|
||||
curlEasyRequest_w->setopt(CURLOPT_INFILESIZE, bytes);
|
||||
curlEasyRequest_w->setopt(CURLOPT_INFILESIZE, mBodySize);
|
||||
rv = true;
|
||||
break;
|
||||
|
||||
}
|
||||
case HTTP_POST:
|
||||
{
|
||||
// Set the handle for an http post
|
||||
curlEasyRequest_w->setPost(bytes);
|
||||
curlEasyRequest_w->setPost(mBodySize);
|
||||
|
||||
// Set Accept-Encoding to allow response compression
|
||||
curlEasyRequest_w->setoptString(CURLOPT_ENCODING, "");
|
||||
curlEasyRequest_w->setoptString(CURLOPT_ENCODING, mNoCompression ? "identity" : "");
|
||||
rv = true;
|
||||
break;
|
||||
|
||||
}
|
||||
case HTTP_DELETE:
|
||||
// Set the handle for an http post
|
||||
curlEasyRequest_w->setoptString(CURLOPT_CUSTOMREQUEST, "DELETE");
|
||||
@@ -537,7 +256,6 @@ bool LLURLRequest::configure()
|
||||
case HTTP_MOVE:
|
||||
// Set the handle for an http post
|
||||
curlEasyRequest_w->setoptString(CURLOPT_CUSTOMREQUEST, "MOVE");
|
||||
// *NOTE: should we check for the Destination header?
|
||||
rv = true;
|
||||
break;
|
||||
|
||||
@@ -547,205 +265,12 @@ bool LLURLRequest::configure()
|
||||
}
|
||||
if(rv)
|
||||
{
|
||||
curlEasyRequest_w->finalizeRequest(mDetail->mURL);
|
||||
curlEasyRequest_w->send_events_to(this);
|
||||
curlEasyRequest_w->setopt(CURLOPT_SSL_VERIFYPEER, gNoVerifySSLCert ? 0L : 1L);
|
||||
// Don't verify host name if this is not an authentication request,
|
||||
// so urls with scrubbed host names will work (improves DNS performance).
|
||||
curlEasyRequest_w->setopt(CURLOPT_SSL_VERIFYHOST, (gNoVerifySSLCert || !mIsAuth) ? 0L : 2L);
|
||||
curlEasyRequest_w->finalizeRequest(mURL, mResponder->getHTTPTimeoutPolicy(), this);
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
// static
|
||||
size_t LLURLRequest::downCallback(
|
||||
char* data,
|
||||
size_t size,
|
||||
size_t nmemb,
|
||||
void* user)
|
||||
{
|
||||
LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
|
||||
LLURLRequest* req = (LLURLRequest*)user;
|
||||
if(STATE_WAITING_FOR_RESPONSE == req->mState)
|
||||
{
|
||||
req->mState = STATE_PROCESSING_RESPONSE;
|
||||
}
|
||||
U32 bytes = size * nmemb;
|
||||
if (req->mDetail->mIsBodyLimitSet)
|
||||
{
|
||||
if (bytes > req->mDetail->mBodyLimit)
|
||||
{
|
||||
bytes = req->mDetail->mBodyLimit;
|
||||
req->mDetail->mBodyLimit = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
req->mDetail->mBodyLimit -= bytes;
|
||||
}
|
||||
}
|
||||
|
||||
req->mDetail->mResponseBuffer->append(
|
||||
req->mDetail->mChannels.out(),
|
||||
(U8*)data,
|
||||
bytes);
|
||||
req->mResponseTransferedBytes += bytes;
|
||||
req->mDetail->mByteAccumulator += bytes;
|
||||
return bytes;
|
||||
}
|
||||
|
||||
// static
|
||||
size_t LLURLRequest::upCallback(
|
||||
char* data,
|
||||
size_t size,
|
||||
size_t nmemb,
|
||||
void* user)
|
||||
{
|
||||
LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
|
||||
LLURLRequest* req = (LLURLRequest*)user;
|
||||
S32 bytes = llmin(
|
||||
(S32)(size * nmemb),
|
||||
req->mDetail->mResponseBuffer->countAfter(
|
||||
req->mDetail->mChannels.in(),
|
||||
req->mDetail->mLastRead));
|
||||
req->mDetail->mLastRead = req->mDetail->mResponseBuffer->readAfter(
|
||||
req->mDetail->mChannels.in(),
|
||||
req->mDetail->mLastRead,
|
||||
(U8*)data,
|
||||
bytes);
|
||||
req->mRequestTransferedBytes += bytes;
|
||||
return bytes;
|
||||
}
|
||||
|
||||
static size_t headerCallback(char* header_line, size_t size, size_t nmemb, void* user)
|
||||
{
|
||||
size_t header_len = size * nmemb;
|
||||
LLURLRequestComplete* complete = (LLURLRequestComplete*)user;
|
||||
|
||||
if (!complete || !header_line)
|
||||
{
|
||||
return header_len;
|
||||
}
|
||||
|
||||
// *TODO: This should be a utility in llstring.h: isascii()
|
||||
for (size_t i = 0; i < header_len; ++i)
|
||||
{
|
||||
if (header_line[i] < 0)
|
||||
{
|
||||
return header_len;
|
||||
}
|
||||
}
|
||||
|
||||
std::string header(header_line, header_len);
|
||||
|
||||
// Per HTTP spec the first header line must be the status line.
|
||||
if (header.substr(0,5) == "HTTP/")
|
||||
{
|
||||
std::string::iterator end = header.end();
|
||||
std::string::iterator pos1 = std::find(header.begin(), end, ' ');
|
||||
if (pos1 != end) ++pos1;
|
||||
std::string::iterator pos2 = std::find(pos1, end, ' ');
|
||||
if (pos2 != end) ++pos2;
|
||||
std::string::iterator pos3 = std::find(pos2, end, '\r');
|
||||
|
||||
std::string version(header.begin(), pos1);
|
||||
std::string status(pos1, pos2);
|
||||
std::string reason(pos2, pos3);
|
||||
|
||||
S32 status_code = atoi(status.c_str());
|
||||
if (status_code > 0)
|
||||
{
|
||||
complete->httpStatus((U32)status_code, reason);
|
||||
return header_len;
|
||||
}
|
||||
}
|
||||
|
||||
std::string::iterator sep = std::find(header.begin(),header.end(),':');
|
||||
|
||||
if (sep != header.end())
|
||||
{
|
||||
std::string key(header.begin(), sep);
|
||||
std::string value(sep + 1, header.end());
|
||||
|
||||
key = utf8str_tolower(utf8str_trim(key));
|
||||
value = utf8str_trim(value);
|
||||
|
||||
complete->header(key, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
LLStringUtil::trim(header);
|
||||
if (!header.empty())
|
||||
{
|
||||
llwarns << "Unable to parse header: " << header << llendl;
|
||||
}
|
||||
}
|
||||
|
||||
return header_len;
|
||||
}
|
||||
|
||||
/**
|
||||
* LLURLRequestComplete
|
||||
*/
|
||||
LLURLRequestComplete::LLURLRequestComplete() :
|
||||
mRequestStatus(LLIOPipe::STATUS_ERROR)
|
||||
{
|
||||
LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
|
||||
}
|
||||
|
||||
// virtual
|
||||
LLURLRequestComplete::~LLURLRequestComplete()
|
||||
{
|
||||
LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
|
||||
}
|
||||
|
||||
//virtual
|
||||
void LLURLRequestComplete::header(const std::string& header, const std::string& value)
|
||||
{
|
||||
}
|
||||
|
||||
//virtual
|
||||
void LLURLRequestComplete::complete(const LLChannelDescriptors& channels,
|
||||
const buffer_ptr_t& buffer)
|
||||
{
|
||||
if(STATUS_OK == mRequestStatus)
|
||||
{
|
||||
response(channels, buffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
noResponse();
|
||||
}
|
||||
}
|
||||
|
||||
//virtual
|
||||
void LLURLRequestComplete::response(const LLChannelDescriptors& channels,
|
||||
const buffer_ptr_t& buffer)
|
||||
{
|
||||
llwarns << "LLURLRequestComplete::response default implementation called"
|
||||
<< llendl;
|
||||
}
|
||||
|
||||
//virtual
|
||||
void LLURLRequestComplete::noResponse()
|
||||
{
|
||||
llwarns << "LLURLRequestComplete::noResponse default implementation called"
|
||||
<< llendl;
|
||||
}
|
||||
|
||||
void LLURLRequestComplete::responseStatus(LLIOPipe::EStatus status)
|
||||
{
|
||||
LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
|
||||
mRequestStatus = status;
|
||||
}
|
||||
|
||||
static LLFastTimer::DeclareTimer FTM_PROCESS_URL_COMPLETE("URL Complete");
|
||||
// virtual
|
||||
LLIOPipe::EStatus LLURLRequestComplete::process_impl(
|
||||
const LLChannelDescriptors& channels,
|
||||
buffer_ptr_t& buffer,
|
||||
bool& eos,
|
||||
LLSD& context,
|
||||
LLPumpIO* pump)
|
||||
{
|
||||
LLFastTimer t(FTM_PROCESS_URL_COMPLETE);
|
||||
PUMP_DEBUG;
|
||||
complete(channels, buffer);
|
||||
return STATUS_OK;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
* $LicenseInfo:firstyear=2005&license=viewerlgpl$
|
||||
* Second Life Viewer Source Code
|
||||
* Copyright (C) 2010, Linden Research, Inc.
|
||||
* Copyright (C) 2012, Aleric Inglewood.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
@@ -35,41 +36,21 @@
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
#include "lliopipe.h"
|
||||
#include "llchainio.h"
|
||||
#include "llerror.h"
|
||||
#include "llcurl.h"
|
||||
#include "aicurleasyrequeststatemachine.h"
|
||||
#include "aihttpheaders.h"
|
||||
|
||||
extern const std::string CONTEXT_REQUEST;
|
||||
extern const std::string CONTEXT_RESPONSE;
|
||||
extern const std::string CONTEXT_TRANSFERED_BYTES;
|
||||
|
||||
class LLURLRequestDetail;
|
||||
|
||||
class LLURLRequestComplete;
|
||||
|
||||
struct x509_store_ctx_st;
|
||||
typedef struct x509_store_ctx_st X509_STORE_CTX;
|
||||
|
||||
/**
|
||||
* @class LLURLRequest
|
||||
* @brief Class to handle url based requests.
|
||||
* @see LLIOPipe
|
||||
*
|
||||
* Currently, this class is implemented on top of curl. From the
|
||||
* vantage of a programmer using this class, you do not care so much,
|
||||
* but it's useful to know since in order to accomplish 'non-blocking'
|
||||
* behavior, we have to use a more expensive curl interface which can
|
||||
* still block if the server enters a half-accepted state. It would be
|
||||
* worth the time and effort to eventually port this to a raw client
|
||||
* socket.
|
||||
*/
|
||||
class LLURLRequest : public LLIOPipe, protected AICurlEasyHandleEvents
|
||||
class Injector
|
||||
{
|
||||
LOG_CLASS(LLURLRequest);
|
||||
public:
|
||||
public:
|
||||
typedef LLHTTPClient::ResponderBase::buffer_ptr_t buffer_ptr_t;
|
||||
virtual char const* contentType(void) const = 0;
|
||||
virtual U32 get_body(LLChannelDescriptors const& channels, buffer_ptr_t& buffer) = 0;
|
||||
// To avoid compiler warning.
|
||||
virtual ~Injector() { }
|
||||
};
|
||||
|
||||
typedef int (* SSLCertVerifyCallback)(X509_STORE_CTX *ctx, void *param);
|
||||
class LLURLRequest : public AICurlEasyRequestStateMachine {
|
||||
public:
|
||||
/**
|
||||
* @brief This enumeration is for specifying the type of request.
|
||||
*/
|
||||
@@ -86,44 +67,33 @@ public:
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Turn the requst action into an http verb.
|
||||
* @brief Turn the request action into an http verb.
|
||||
*/
|
||||
static std::string actionAsVerb(ERequestAction action);
|
||||
|
||||
/**
|
||||
* @brief Constructor.
|
||||
*
|
||||
* @param action One of the ERequestAction enumerations.
|
||||
*/
|
||||
LLURLRequest(ERequestAction action);
|
||||
|
||||
/**
|
||||
* @brief Constructor.
|
||||
*
|
||||
* @param action One of the ERequestAction enumerations.
|
||||
* @param url The url of the request. It should already be encoded.
|
||||
*/
|
||||
LLURLRequest(ERequestAction action, const std::string& url);
|
||||
LLURLRequest(ERequestAction action, std::string const& url, Injector* body, LLHTTPClient::ResponderPtr responder, AIHTTPHeaders& headers, bool is_auth, bool no_compression);
|
||||
|
||||
/**
|
||||
* @brief Destructor.
|
||||
*/
|
||||
virtual ~LLURLRequest();
|
||||
protected:
|
||||
// Call abort(), not delete.
|
||||
/*virtual*/ ~LLURLRequest() { }
|
||||
|
||||
/* @name Instance methods
|
||||
public:
|
||||
/**
|
||||
* @brief Turn on cookie handling for this request with CURLOPT_COOKIEFILE.
|
||||
*/
|
||||
//@{
|
||||
/**
|
||||
* @brief Set the url for the request
|
||||
*
|
||||
* This method assumes the url is encoded appropriately for the
|
||||
* request.
|
||||
* The url must be set somehow before the first call to process(),
|
||||
* or the url will not be set correctly.
|
||||
*
|
||||
*/
|
||||
void setURL(const std::string& url);
|
||||
std::string getURL() const;
|
||||
void allowCookies(void);
|
||||
|
||||
/**
|
||||
* @ brief Turn off (or on) the CURLOPT_PROXY header.
|
||||
*/
|
||||
void useProxy(bool use_proxy);
|
||||
|
||||
/**
|
||||
* @brief Add a header to the http post.
|
||||
*
|
||||
@@ -133,213 +103,29 @@ public:
|
||||
* required headers will be automatically constructed, so this is
|
||||
* usually useful for encoding parameters.
|
||||
*/
|
||||
void addHeader(const char* header);
|
||||
|
||||
/**
|
||||
* @brief Check remote server certificate signed by a known root CA.
|
||||
*
|
||||
* Set whether request will check that remote server
|
||||
* certificates are signed by a known root CA when using HTTPS.
|
||||
*/
|
||||
void checkRootCertificate(bool check);
|
||||
|
||||
/**
|
||||
* @brief Return at most size bytes of body.
|
||||
*
|
||||
* If the body had more bytes than this limit, they will not be
|
||||
* returned and the connection closed. In this case, STATUS_STOP
|
||||
* will be passed to responseStatus();
|
||||
*/
|
||||
void setBodyLimit(U32 size);
|
||||
|
||||
/**
|
||||
* @brief Set a completion callback for this URLRequest.
|
||||
*
|
||||
* The callback is added to this URLRequet's pump when either the
|
||||
* entire buffer is known or an error like timeout or connection
|
||||
* refused has happened. In the case of a complete transfer, this
|
||||
* object builds a response chain such that the callback and the
|
||||
* next process consumer get to read the output.
|
||||
*
|
||||
* This setup is a little fragile since the url request consumer
|
||||
* might not just read the data - it may do a channel change,
|
||||
* which invalidates the input to the callback, but it works well
|
||||
* in practice.
|
||||
*/
|
||||
void setCallback(LLURLRequestComplete* callback);
|
||||
//@}
|
||||
|
||||
/* @name LLIOPipe virtual implementations
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ brief Turn off (or on) the CURLOPT_PROXY header.
|
||||
*/
|
||||
void useProxy(bool use_proxy);
|
||||
|
||||
/**
|
||||
* @ brief Set the CURLOPT_PROXY header to the given value.
|
||||
*/
|
||||
void useProxy(const std::string& proxy);
|
||||
|
||||
/**
|
||||
* @brief Turn on cookie handling for this request with CURLOPT_COOKIEFILE.
|
||||
*/
|
||||
void allowCookies();
|
||||
|
||||
/*virtual*/ bool hasExpiration(void) const;
|
||||
/*virtual*/ bool hasNotExpired(void) const;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Give this pipe a chance to handle a generated error
|
||||
*/
|
||||
virtual EStatus handleError(EStatus status, LLPumpIO* pump);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Process the data in buffer
|
||||
*/
|
||||
virtual EStatus process_impl(
|
||||
const LLChannelDescriptors& channels,
|
||||
buffer_ptr_t& buffer,
|
||||
bool& eos,
|
||||
LLSD& context,
|
||||
LLPumpIO* pump);
|
||||
//@}
|
||||
|
||||
protected:
|
||||
enum EState
|
||||
{
|
||||
STATE_INITIALIZED,
|
||||
STATE_WAITING_FOR_RESPONSE,
|
||||
STATE_PROCESSING_RESPONSE,
|
||||
STATE_CURL_FINISHED,
|
||||
STATE_HAVE_RESPONSE,
|
||||
};
|
||||
EState mState;
|
||||
ERequestAction mAction;
|
||||
LLURLRequestDetail* mDetail;
|
||||
LLIOPipe::ptr_t mCompletionCallback;
|
||||
S32 mRequestTransferedBytes;
|
||||
S32 mResponseTransferedBytes;
|
||||
|
||||
// mRemoved is used instead of changing mState directly, because I'm not convinced the latter is atomic.
|
||||
// Set to false before adding curl request and then only tested.
|
||||
// Reset in removed_from_multi_handle (by another thread), this is thread-safe.
|
||||
bool mRemoved;
|
||||
/*virtual*/ void added_to_multi_handle(AICurlEasyRequest_wat&);
|
||||
/*virtual*/ void finished(AICurlEasyRequest_wat&);
|
||||
/*virtual*/ void removed_from_multi_handle(AICurlEasyRequest_wat&);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Initialize the object. Called during construction.
|
||||
*/
|
||||
void initialize();
|
||||
void addHeader(char const* header);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Handle action specific url request configuration.
|
||||
*
|
||||
* @return Returns true if this is configured.
|
||||
*/
|
||||
bool configure();
|
||||
bool configure(AICurlEasyRequest_wat const& curlEasyRequest_w);
|
||||
|
||||
/**
|
||||
* @brief Download callback method.
|
||||
*/
|
||||
static size_t downCallback(
|
||||
char* data,
|
||||
size_t size,
|
||||
size_t nmemb,
|
||||
void* user);
|
||||
private:
|
||||
ERequestAction mAction;
|
||||
std::string mURL;
|
||||
bool mIsAuth; // Set for authentication messages (login, buy land, buy currency).
|
||||
bool mNoCompression; // Set to disable using gzip.
|
||||
Injector* mBody; // Non-zero iff the action is HTTP_POST and HTTP_PUT.
|
||||
U32 mBodySize;
|
||||
LLHTTPClient::ResponderPtr mResponder;
|
||||
AIHTTPHeaders mHeaders;
|
||||
|
||||
/**
|
||||
* @brief Upload callback method.
|
||||
*/
|
||||
static size_t upCallback(
|
||||
char* data,
|
||||
size_t size,
|
||||
size_t nmemb,
|
||||
void* user);
|
||||
|
||||
/**
|
||||
* @brief Declaration of unimplemented method to prevent copy
|
||||
* construction.
|
||||
*/
|
||||
LLURLRequest(const LLURLRequest&);
|
||||
};
|
||||
|
||||
/**
|
||||
* @class LLURLRequestComplete
|
||||
* @brief Class which can optionally be used with an LLURLRequest to
|
||||
* get notification when the url request is complete.
|
||||
*/
|
||||
class LLURLRequestComplete : public LLIOPipe
|
||||
{
|
||||
public:
|
||||
|
||||
// Called once for each header received, except status lines
|
||||
virtual void header(const std::string& header, const std::string& value);
|
||||
|
||||
// May be called more than once, particularly for redirects and proxy madness.
|
||||
// Ex. a 200 for a connection to https through a proxy, followed by the "real" status
|
||||
// a 3xx for a redirect followed by a "real" status, or more redirects.
|
||||
virtual void httpStatus(U32 status, const std::string& reason) { }
|
||||
|
||||
virtual void complete(
|
||||
const LLChannelDescriptors& channels,
|
||||
const buffer_ptr_t& buffer);
|
||||
|
||||
/**
|
||||
* @brief This method is called when we got a valid response.
|
||||
*
|
||||
* It is up to class implementers to do something useful here.
|
||||
*/
|
||||
virtual void response(
|
||||
const LLChannelDescriptors& channels,
|
||||
const buffer_ptr_t& buffer);
|
||||
|
||||
/**
|
||||
* @brief This method is called if there was no response.
|
||||
*
|
||||
* It is up to class implementers to do something useful here.
|
||||
*/
|
||||
virtual void noResponse();
|
||||
|
||||
/**
|
||||
* @brief This method will be called by the LLURLRequest object.
|
||||
*
|
||||
* If this is set to STATUS_OK or STATUS_STOP, then the transfer
|
||||
* is asssumed to have worked. This will lead to calling response()
|
||||
* on the next call to process(). Otherwise, this object will call
|
||||
* noResponse() on the next call to process.
|
||||
* @param status The status of the URLRequest.
|
||||
*/
|
||||
void responseStatus(EStatus status);
|
||||
|
||||
// constructor & destructor.
|
||||
LLURLRequestComplete();
|
||||
virtual ~LLURLRequestComplete();
|
||||
|
||||
protected:
|
||||
/* @name LLIOPipe virtual implementations
|
||||
*/
|
||||
//@{
|
||||
/**
|
||||
* @brief Process the data in buffer
|
||||
*/
|
||||
virtual EStatus process_impl(
|
||||
const LLChannelDescriptors& channels,
|
||||
buffer_ptr_t& buffer,
|
||||
bool& eos,
|
||||
LLSD& context,
|
||||
LLPumpIO* pump);
|
||||
//@}
|
||||
|
||||
// value to note if we actually got the response. This value
|
||||
// depends on correct useage from the LLURLRequest instance.
|
||||
EStatus mRequestStatus;
|
||||
protected:
|
||||
// Handle initializing the object.
|
||||
/*virtual*/ void initialize_impl(void);
|
||||
};
|
||||
|
||||
#endif // LL_LLURLREQUEST_H
|
||||
|
||||
@@ -83,6 +83,9 @@
|
||||
#include "llmemtype.h"
|
||||
#include "llpacketring.h"
|
||||
|
||||
class AIHTTPTimeoutPolicy;
|
||||
extern AIHTTPTimeoutPolicy fnPtrResponder_timeout;
|
||||
|
||||
// Constants
|
||||
//const char* MESSAGE_LOG_FILENAME = "message.log";
|
||||
static const F32 CIRCUIT_DUMP_TIMEOUT = 30.f;
|
||||
@@ -104,7 +107,7 @@ public:
|
||||
|
||||
namespace
|
||||
{
|
||||
class LLFnPtrResponder : public LLHTTPClient::Responder
|
||||
class LLFnPtrResponder : public LLHTTPClient::ResponderWithResult
|
||||
{
|
||||
LOG_CLASS(LLFnPtrResponder);
|
||||
public:
|
||||
@@ -133,6 +136,8 @@ namespace
|
||||
if(NULL != mCallback) mCallback(mCallbackData, LL_ERR_NOERR);
|
||||
}
|
||||
|
||||
virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return fnPtrResponder_timeout; }
|
||||
|
||||
private:
|
||||
|
||||
void (*mCallback)(void **,S32);
|
||||
|
||||
Reference in New Issue
Block a user