diff --git a/indra/CMakeLists.txt b/indra/CMakeLists.txt index 5f86c3509..c32ad187c 100644 --- a/indra/CMakeLists.txt +++ b/indra/CMakeLists.txt @@ -46,6 +46,7 @@ endif(NOT STANDALONE) add_custom_target(prepare DEPENDS ${prepare_depends}) add_subdirectory(cmake) +add_subdirectory(${LIBS_OPEN_PREFIX}aistatemachine) add_subdirectory(${LIBS_OPEN_PREFIX}llaudio) add_subdirectory(${LIBS_OPEN_PREFIX}llcharacter) add_subdirectory(${LIBS_OPEN_PREFIX}llcommon) diff --git a/indra/aistatemachine/CMakeLists.txt b/indra/aistatemachine/CMakeLists.txt new file mode 100644 index 000000000..18ba3c034 --- /dev/null +++ b/indra/aistatemachine/CMakeLists.txt @@ -0,0 +1,39 @@ +# -*- cmake -*- + +project(aistatemachine) + +include(00-Common) +include(LLCommon) +include(LLMessage) +include(LLMath) +include(LLXML) + +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) + +include_directories( + ${LLCOMMON_INCLUDE_DIRS} + ${LLMESSAGE_INCLUDE_DIRS} + ${LLMATH_INCLUDE_DIRS} + ${LLXML_INCLUDE_DIRS} + ) + +set(aistatemachine_SOURCE_FILES + aistatemachine.cpp + aitimer.cpp + ) + +set(aistatemachine_HEADER_FILES + CMakeLists.txt + + aistatemachine.h + aitimer.h + ) + +set_source_files_properties(${aistatemachine_HEADER_FILES} + PROPERTIES HEADER_FILE_ONLY TRUE) + +list(APPEND aistatemachine_SOURCE_FILES ${aistatemachine_HEADER_FILES}) + +add_library (aistatemachine ${aistatemachine_SOURCE_FILES}) +add_dependencies(aistatemachine prepare) + diff --git a/indra/newview/statemachine/aistatemachine.cpp b/indra/aistatemachine/aistatemachine.cpp similarity index 89% rename from indra/newview/statemachine/aistatemachine.cpp rename to indra/aistatemachine/aistatemachine.cpp index a6a646ec9..065724961 100644 --- a/indra/newview/statemachine/aistatemachine.cpp +++ b/indra/aistatemachine/aistatemachine.cpp @@ -32,16 +32,11 @@ #include -#include "llcallbacklist.h" #include "llcontrol.h" #include "llfasttimer.h" #include "aithreadsafe.h" #include "aistatemachine.h" -extern F64 calc_clock_frequency(void); - -extern LLControlGroup gSavedSettings; - // Local variables. namespace { struct QueueElementComp; @@ -66,28 +61,18 @@ namespace { typedef std::vector active_statemachines_type; active_statemachines_type active_statemachines; - typedef std::vector continued_statemachines_type; - struct cscm_type - { - continued_statemachines_type continued_statemachines; - bool calling_mainloop; - }; - AIThreadSafeDC continued_statemachines_and_calling_mainloop; } // static -AIThreadSafeSimpleDC AIStateMachine::sMaxCount; +U64 AIStateMachine::sMaxCount; +AIThreadSafeDC AIStateMachine::sContinuedStateMachinesAndMainloopEnabled; -void AIStateMachine::updateSettings(void) +// static +void AIStateMachine::setMaxCount(F32 StateMachineMaxTime) { - static const LLCachedControl StateMachineMaxTime("StateMachineMaxTime", 20); - static U32 last_StateMachineMaxTime = 0; - if (last_StateMachineMaxTime != StateMachineMaxTime) - { - Dout(dc::statemachine, "Initializing AIStateMachine::sMaxCount"); - *AIAccess(sMaxCount) = calc_clock_frequency() * StateMachineMaxTime / 1000; - last_StateMachineMaxTime = StateMachineMaxTime; - } + llassert(is_main_thread()); + Dout(dc::statemachine, "(Re)calculating AIStateMachine::sMaxCount"); + sMaxCount = calc_clock_frequency() * StateMachineMaxTime / 1000; } //---------------------------------------------------------------------------- @@ -221,24 +206,23 @@ void AIStateMachine::locked_cont(void) // If not_active is true then main-thread is not running this statemachine. // It might call cont() (or set_state()) but never locked_cont(), and will never // start actually running until we are done here and release the lock on - // continued_statemachines_and_calling_mainloop again. It is therefore safe + // sContinuedStateMachinesAndMainloopEnabled again. It is therefore safe // to release mSetStateLock here, with as advantage that if we're not the main- // thread and not_active is true, then the main-thread won't block when it has // a timer running that times out and calls set_state(). mSetStateLock.unlock(); if (not_active) { - AIWriteAccess cscm_w(continued_statemachines_and_calling_mainloop); + AIWriteAccess csme_w(sContinuedStateMachinesAndMainloopEnabled); // See above: it is not possible that mActive was changed since not_active // was set to true above. llassert_always(mActive == as_idle); Dout(dc::statemachine, "Adding " << (void*)this << " to continued_statemachines"); - cscm_w->continued_statemachines.push_back(this); - if (!cscm_w->calling_mainloop) + csme_w->continued_statemachines.push_back(this); + if (!csme_w->mainloop_enabled) { - Dout(dc::statemachine, "Adding AIStateMachine::mainloop to gIdleCallbacks"); - cscm_w->calling_mainloop = true; - gIdleCallbacks.addFunction(&AIStateMachine::mainloop); + Dout(dc::statemachine, "Activating AIStateMachine::mainloop."); + csme_w->mainloop_enabled = true; } mActive = as_queued; llassert_always(!mIdle); // It should never happen that the main thread calls idle(), while another thread calls cont() concurrently. @@ -499,11 +483,10 @@ void AIStateMachine::multiplex(U64 current_time) } //static -void AIStateMachine::add_continued_statemachines(void) +void AIStateMachine::add_continued_statemachines(AIReadAccess& csme_r) { - AIReadAccess cscm_r(continued_statemachines_and_calling_mainloop); bool nonempty = false; - for (continued_statemachines_type::const_iterator iter = cscm_r->continued_statemachines.begin(); iter != cscm_r->continued_statemachines.end(); ++iter) + for (continued_statemachines_type::const_iterator iter = csme_r->continued_statemachines.begin(); iter != csme_r->continued_statemachines.end(); ++iter) { nonempty = true; active_statemachines.push_back(QueueElement(*iter)); @@ -511,19 +494,15 @@ void AIStateMachine::add_continued_statemachines(void) (*iter)->mActive = as_active; } if (nonempty) - AIWriteAccess(cscm_r)->continued_statemachines.clear(); + AIWriteAccess(csme_r)->continued_statemachines.clear(); } -static LLFastTimer::DeclareTimer FTM_STATEMACHINE("State Machine"); // static -void AIStateMachine::mainloop(void*) +void AIStateMachine::dowork(void) { - LLFastTimer t(FTM_STATEMACHINE); - add_continued_statemachines(); llassert(!active_statemachines.empty()); // Run one or more state machines. U64 total_clocks = 0; - U64 max_count = *AIAccess(sMaxCount); for (active_statemachines_type::iterator iter = active_statemachines.begin(); iter != active_statemachines.end(); ++iter) { AIStateMachine& statemachine(iter->statemachine()); @@ -537,7 +516,7 @@ void AIStateMachine::mainloop(void*) U64 delta = get_clock_count() - start; iter->add(delta); total_clocks += delta; - if (total_clocks >= max_count) + if (total_clocks >= sMaxCount) { #ifndef LL_RELEASE_FOR_DOWNLOAD llwarns << "AIStateMachine::mainloop did run for " << (total_clocks * 1000 / calc_clock_frequency()) << " ms." << llendl; @@ -590,12 +569,11 @@ void AIStateMachine::mainloop(void*) if (active_statemachines.empty()) { // If this was the last state machine, remove mainloop from the IdleCallbacks. - AIReadAccess cscm_r(continued_statemachines_and_calling_mainloop); - if (cscm_r->continued_statemachines.empty() && cscm_r->calling_mainloop) + AIReadAccess csme_r(sContinuedStateMachinesAndMainloopEnabled, true); + if (csme_r->continued_statemachines.empty() && csme_r->mainloop_enabled) { - Dout(dc::statemachine, "Removing AIStateMachine::mainloop from gIdleCallbacks"); - AIWriteAccess(cscm_r)->calling_mainloop = false; - gIdleCallbacks.deleteFunction(&AIStateMachine::mainloop); + Dout(dc::statemachine, "Deactivating AIStateMachine::mainloop: no active state machines left."); + AIWriteAccess(csme_r)->mainloop_enabled = false; } } } @@ -604,7 +582,10 @@ void AIStateMachine::mainloop(void*) void AIStateMachine::flush(void) { DoutEntering(dc::curl, "AIStateMachine::flush(void)"); - add_continued_statemachines(); + { + AIReadAccess csme_r(sContinuedStateMachinesAndMainloopEnabled); + add_continued_statemachines(csme_r); + } // Abort all state machines. for (active_statemachines_type::iterator iter = active_statemachines.begin(); iter != active_statemachines.end(); ++iter) { @@ -626,15 +607,18 @@ void AIStateMachine::flush(void) for(;;) { { - AIReadAccess cscm_r(continued_statemachines_and_calling_mainloop); - if (!cscm_r->calling_mainloop) + AIReadAccess csme_r(sContinuedStateMachinesAndMainloopEnabled); + if (!csme_r->mainloop_enabled) break; } - mainloop(NULL); + mainloop(); } if (batch == 1) break; - add_continued_statemachines(); + { + AIReadAccess csme_r(sContinuedStateMachinesAndMainloopEnabled); + add_continued_statemachines(csme_r); + } // Kill all state machines. for (active_statemachines_type::iterator iter = active_statemachines.begin(); iter != active_statemachines.end(); ++iter) { diff --git a/indra/newview/statemachine/aistatemachine.h b/indra/aistatemachine/aistatemachine.h similarity index 93% rename from indra/newview/statemachine/aistatemachine.h rename to indra/aistatemachine/aistatemachine.h index f3dddef1d..7ea285c01 100644 --- a/indra/newview/statemachine/aistatemachine.h +++ b/indra/aistatemachine/aistatemachine.h @@ -192,6 +192,15 @@ class AIStateMachine { as_active // State machine is on active_statemachines list. }; + //! Type of continued_statemachines. + typedef std::vector continued_statemachines_type; + //! Type of sContinuedStateMachinesAndMainloopEnabled. + struct csme_type + { + continued_statemachines_type continued_statemachines; + bool mainloop_enabled; + }; + public: typedef U32 state_type; //!< The type of mRunState @@ -230,7 +239,8 @@ class AIStateMachine { }; callback_type* mCallback; //!< Pointer to signal/connection, or NULL when not connected. - static AIThreadSafeSimpleDC sMaxCount; //!< Number of cpu clocks below which we start a new state machine within the same frame. + static U64 sMaxCount; //!< Number of cpu clocks below which we start a new state machine within the same frame. + static AIThreadSafeDC sContinuedStateMachinesAndMainloopEnabled; //!< Read/write locked variable pair. protected: LLMutex mSetStateLock; //!< For critical areas in set_state() and locked_cont(). @@ -244,7 +254,7 @@ class AIStateMachine { #ifdef SHOW_ASSERT , mContThread(AIThreadID::none), mCalledThreadUnsafeIdle(false) #endif - { updateSettings(); } + { } protected: //! The user should call 'kill()', not delete a AIStateMachine (derived) directly. @@ -339,7 +349,7 @@ class AIStateMachine { // Other. //! Called whenever the StateMachineMaxTime setting is changed. - static void updateSettings(void); + static void setMaxCount(F32 StateMachineMaxTime); //--------------------------------------- // Accessors. @@ -365,11 +375,24 @@ class AIStateMachine { char const* state_str(state_type state); private: - static void add_continued_statemachines(void); - static void mainloop(void*); + static void add_continued_statemachines(AIReadAccess& csme_r); + static void dowork(void); void multiplex(U64 current_time); public: + //! Call this once per frame to give the statemachines CPU cycles. + static void mainloop(void) + { + { + AIReadAccess csme_r(sContinuedStateMachinesAndMainloopEnabled, true); + if (!csme_r->mainloop_enabled) + return; + if (!csme_r->continued_statemachines.empty()) + add_continued_statemachines(csme_r); + } + dowork(); + } + //! Abort all running state machines and then run mainloop until all state machines are idle (called when application is exiting). static void flush(void); diff --git a/indra/newview/statemachine/aitimer.cpp b/indra/aistatemachine/aitimer.cpp similarity index 100% rename from indra/newview/statemachine/aitimer.cpp rename to indra/aistatemachine/aitimer.cpp diff --git a/indra/newview/statemachine/aitimer.h b/indra/aistatemachine/aitimer.h similarity index 100% rename from indra/newview/statemachine/aitimer.h rename to indra/aistatemachine/aitimer.h diff --git a/indra/cmake/AIStateMachine.cmake b/indra/cmake/AIStateMachine.cmake index c1bda40c4..5a0aba0d7 100644 --- a/indra/cmake/AIStateMachine.cmake +++ b/indra/cmake/AIStateMachine.cmake @@ -1,4 +1,4 @@ # -*- cmake -*- -set(AISTATEMACHINE_INCLUDE_DIRS statemachine) -set(AISTATEMACHINE_LIBRARIES statemachine) +set(AISTATEMACHINE_INCLUDE_DIRS ${LIBS_OPEN_DIR}/aistatemachine) +set(AISTATEMACHINE_LIBRARIES aistatemachine) diff --git a/indra/cmake/LLMessage.cmake b/indra/cmake/LLMessage.cmake index 0143d04fd..da2bbc43b 100644 --- a/indra/cmake/LLMessage.cmake +++ b/indra/cmake/LLMessage.cmake @@ -4,12 +4,14 @@ include(CARes) include(CURL) include(OpenSSL) include(XmlRpcEpi) +include(AIStateMachine) set(LLMESSAGE_INCLUDE_DIRS ${LIBS_OPEN_DIR}/llmessage ${CARES_INCLUDE_DIRS} ${CURL_INCLUDE_DIRS} ${OPENSSL_INCLUDE_DIRS} + ${AISTATEMACHINE_INCLUDE_DIRS} ) -set(LLMESSAGE_LIBRARIES llmessage) +set(LLMESSAGE_LIBRARIES llmessage aistatemachine) diff --git a/indra/cmake/StateMachine.cmake b/indra/cmake/StateMachine.cmake new file mode 100644 index 000000000..8a478c647 --- /dev/null +++ b/indra/cmake/StateMachine.cmake @@ -0,0 +1,4 @@ +# -*- cmake -*- + +set(STATEMACHINE_INCLUDE_DIRS statemachine) +set(STATEMACHINE_LIBRARIES statemachine) diff --git a/indra/cwdebug/debug.cc b/indra/cwdebug/debug.cc index 06723f8ae..de14fa99f 100644 --- a/indra/cwdebug/debug.cc +++ b/indra/cwdebug/debug.cc @@ -49,6 +49,8 @@ namespace debug { #if CWDEBUG_LOCATION +ll_thread_local size_t BackTrace::S_number; + void BackTrace::dump_backtrace(void) const { for (int frame = 0; frame < frames(); ++frame) @@ -67,6 +69,67 @@ void BackTrace::dump_backtrace(void) const Dout(dc::finish, mangled_function_name); } } + +void BackTraces::store_trace(size_t trace) +{ + mBackTraces.push_back(trace); +} + +void BackTraces::remove_trace(size_t trace) +{ + trace_container_type::iterator iter = mBackTraces.begin(); + while (iter != mBackTraces.end()) + { + if (*iter == trace) + { + *iter = mBackTraces.back(); + mBackTraces.pop_back(); + return; + } + ++iter; + } + DoutFatal(dc::core, "Trace doesn't exist!"); +} + +void BackTraces::dump(void) const +{ + Dout(dc::backtrace|continued_cf, "Dump for (BackTraces*)" << (void*)this << " (" << mBackTraces.size() << " backtraces): "); + for (trace_container_type::const_iterator iter = mBackTraces.begin(); iter != mBackTraces.end(); ++iter) + { + Dout(dc::continued|nonewline_cf, *iter << ' '); + } + Dout(dc::finish, ""); +} + +BackTraceTracker::BackTraceTracker(BackTraces* back_traces) : mBackTraces(back_traces) +{ + BACKTRACE; + mTrace = BackTrace::S_number; + mBackTraces->store_trace(mTrace); +} + +BackTraceTracker::~BackTraceTracker() +{ + mBackTraces->remove_trace(mTrace); +} + +BackTraceTracker::BackTraceTracker(BackTraceTracker const& orig) : mBackTraces(orig.mBackTraces) +{ + BACKTRACE; + mTrace = BackTrace::S_number; + mBackTraces->store_trace(mTrace); +} + +BackTraceTracker& BackTraceTracker::operator=(BackTraceTracker const& orig) +{ + mBackTraces->remove_trace(mTrace); + mBackTraces = orig.mBackTraces; + BACKTRACE; + mTrace = BackTrace::S_number; + mBackTraces->store_trace(mTrace); + return *this; +} + #endif // CWDEBUG_LOCATION #if CWDEBUG_ALLOC && CWDEBUG_LOCATION diff --git a/indra/cwdebug/debug.h b/indra/cwdebug/debug.h index a52837411..1deed2efb 100644 --- a/indra/cwdebug/debug.h +++ b/indra/cwdebug/debug.h @@ -173,7 +173,9 @@ extern LL_COMMON_API fake_channel const notice; #include #if CWDEBUG_LOCATION #include // Needed for 'backtrace'. +#include "llpreprocessor.h" #endif +#include #define CWD_API __attribute__ ((visibility("default"))) @@ -273,6 +275,8 @@ class BackTrace { private: boost::shared_array M_buffer; int M_frames; + public: + static ll_thread_local size_t S_number; public: BackTrace(void** buffer, int frames) : M_buffer(new void* [frames]), M_frames(frames) { std::memcpy(M_buffer.get(), buffer, sizeof(void*) * frames); } @@ -299,19 +303,81 @@ extern pthread_mutex_t backtrace_mutex; using namespace debug; \ void* buffer[32]; \ int frames = backtrace(buffer, 32); \ - size_t size; \ { \ pthread_mutex_lock(&backtrace_mutex); \ backtraces.push_back(BackTrace(buffer, frames)); \ - size = backtraces.size(); \ + BackTrace::S_number = backtraces.size(); \ pthread_mutex_unlock(&backtrace_mutex); \ } \ - Dout(dc::backtrace, "Stored backtrace #" << size); \ + Dout(dc::backtrace, "Stored backtrace #" << BackTrace::S_number); \ } while(0) + +class LL_COMMON_API BackTraces { + private: + typedef std::vector trace_container_type; + trace_container_type mBackTraces; + + public: + void store_trace(size_t trace); + void remove_trace(size_t trace); + + void dump(void) const; +}; + +class LL_COMMON_API BackTraceTracker { + private: + BackTraces* mBackTraces; + size_t mTrace; + + public: + BackTraceTracker(BackTraces* back_traces); + ~BackTraceTracker(); + + BackTraceTracker(BackTraceTracker const&); + BackTraceTracker& operator=(BackTraceTracker const&); + + void dump(void) const { mBackTraces->dump(); } +}; + #else #define BACKTRACE do { } while(0) #endif // CWDEBUG_LOCATION +template +class LL_COMMON_API InstanceTracker { + private: + T const* mInstance; + static pthread_mutex_t sInstancesMutex; + static std::set sInstances; + static void remember(T const* instance) { pthread_mutex_lock(&sInstancesMutex); sInstances.insert(instance); pthread_mutex_unlock(&sInstancesMutex); } + static void forget(T const* instance) { pthread_mutex_lock(&sInstancesMutex); sInstances.erase(instance); pthread_mutex_unlock(&sInstancesMutex); } + public: + InstanceTracker(T const* instance) : mInstance(instance) { remember(mInstance); } + ~InstanceTracker() { forget(mInstance); } + InstanceTracker& operator=(InstanceTracker const& orig) { forget(mInstance); mInstance = orig.mInstance; remember(mInstance); return *this; } + static void dump(void); + private: + // Non-copyable. Instead of copying, call InstanceTracker(T const*) with the this pointer of the new instance. + InstanceTracker(InstanceTracker const& orig); +}; + +template +pthread_mutex_t InstanceTracker::sInstancesMutex = PTHREAD_MUTEX_INITIALIZER; + +template +std::set InstanceTracker::sInstances; + +template +void InstanceTracker::dump(void) +{ + pthread_mutex_lock(&sInstancesMutex); + for (typename std::set::iterator iter = sInstances.begin(); iter != sInstances.end(); ++iter) + { + std::cout << *iter << std::endl; + } + pthread_mutex_unlock(&sInstancesMutex); +} + } // namespace debug //! Debugging macro. diff --git a/indra/llcommon/aithreadsafe.h b/indra/llcommon/aithreadsafe.h index 02bfa8cd7..a9f56afff 100644 --- a/indra/llcommon/aithreadsafe.h +++ b/indra/llcommon/aithreadsafe.h @@ -329,14 +329,14 @@ struct AIReadAccessConst }; //! Construct a AIReadAccessConst from a constant AIThreadSafe. - AIReadAccessConst(AIThreadSafe const& wrapper) + AIReadAccessConst(AIThreadSafe const& wrapper, bool high_priority = false) : mWrapper(const_cast&>(wrapper)), mState(readlocked) #if AI_NEED_ACCESS_CC , mIsCopyConstructed(false) #endif { - mWrapper.mRWLock.rdlock(); + mWrapper.mRWLock.rdlock(high_priority); } //! Destruct the AI*Access object. @@ -393,7 +393,7 @@ struct AIReadAccess : public AIReadAccessConst using AIReadAccessConst::readlocked; //! Construct a AIReadAccess from a non-constant AIThreadSafe. - AIReadAccess(AIThreadSafe& wrapper) : AIReadAccessConst(wrapper, readlocked) { this->mWrapper.mRWLock.rdlock(); } + AIReadAccess(AIThreadSafe& wrapper, bool high_priority = false) : AIReadAccessConst(wrapper, readlocked) { this->mWrapper.mRWLock.rdlock(high_priority); } protected: //! Constructor used by AIWriteAccess. diff --git a/indra/llcommon/llfile.h b/indra/llcommon/llfile.h index 31d025bb0..0c16e55de 100644 --- a/indra/llcommon/llfile.h +++ b/indra/llcommon/llfile.h @@ -227,7 +227,7 @@ public: #endif /** - * @breif filesize helpers. + * @brief filesize helpers. * * The file size helpers are not considered particularly efficient, * and should only be used for config files and the like -- not in a diff --git a/indra/llcommon/llmemtype.cpp b/indra/llcommon/llmemtype.cpp index 6290a7158..9dcdaeff1 100644 --- a/indra/llcommon/llmemtype.cpp +++ b/indra/llcommon/llmemtype.cpp @@ -179,7 +179,6 @@ LLMemType::DeclareMemType LLMemType::MTYPE_IO_BUFFER("IoBuffer"); LLMemType::DeclareMemType LLMemType::MTYPE_IO_HTTP_SERVER("IoHttpServer"); LLMemType::DeclareMemType LLMemType::MTYPE_IO_SD_SERVER("IoSDServer"); LLMemType::DeclareMemType LLMemType::MTYPE_IO_SD_CLIENT("IoSDClient"); -LLMemType::DeclareMemType LLMemType::MTYPE_IO_URL_REQUEST("IOUrlRequest"); LLMemType::DeclareMemType LLMemType::MTYPE_DIRECTX_INIT("DirectXInit"); diff --git a/indra/llcommon/llmemtype.h b/indra/llcommon/llmemtype.h index 677fad303..413540d7c 100644 --- a/indra/llcommon/llmemtype.h +++ b/indra/llcommon/llmemtype.h @@ -223,7 +223,6 @@ public: static DeclareMemType MTYPE_IO_HTTP_SERVER; static DeclareMemType MTYPE_IO_SD_SERVER; static DeclareMemType MTYPE_IO_SD_CLIENT; - static DeclareMemType MTYPE_IO_URL_REQUEST; static DeclareMemType MTYPE_DIRECTX_INIT; diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h index ee763a61a..992008ae8 100644 --- a/indra/llcommon/llstring.h +++ b/indra/llcommon/llstring.h @@ -368,6 +368,7 @@ public: * should work. */ static void _makeASCII(string_type& string); + static bool _isASCII(std::basic_string const& string); // Conversion to other data types static BOOL convertToBOOL(const string_type& string, BOOL& value); @@ -1473,6 +1474,19 @@ void LLStringUtilBase::_makeASCII(string_type& string) } } +template +bool LLStringUtilBase::_isASCII(std::basic_string const& string) +{ + size_type const len = string.length(); + T bit_collector = 0; + for (size_type i = 0; i < len; ++i) + { + bit_collector |= string[i]; + } + T const ascii_bits = 0x7f; + return !(bit_collector & ~ascii_bits); +} + // static template void LLStringUtilBase::copy( T* dst, const T* src, size_type dst_size ) diff --git a/indra/llcommon/llthread.cpp b/indra/llcommon/llthread.cpp index 5c1a29f24..421ad4513 100644 --- a/indra/llcommon/llthread.cpp +++ b/indra/llcommon/llthread.cpp @@ -113,12 +113,11 @@ void *APR_THREAD_FUNC LLThread::staticRun(apr_thread_t *apr_threadp, void *datap // Only now print this info [doing that before setting mStatus // to STOPPED makes it much more likely that another thread runs - // after the LLCurl::Multi::run() function exits and we actually - // change this variable (which really SHOULD have been inside - // the critical area of the mSignal lock)]. + // after the AICurlPrivate::curlthread::AICurlThread::run() function + // exits and we actually change this variable (which really SHOULD + // have been inside the critical area of the mSignal lock)]. lldebugs << "LLThread::staticRun() Exiting: " << name << llendl; - --sRunning; // Would be better to do this after joining with the thread, but we don't join :/ return NULL; } diff --git a/indra/llcrashlogger/llcrashlogger.cpp b/indra/llcrashlogger/llcrashlogger.cpp index c42697ad9..2537a4b87 100644 --- a/indra/llcrashlogger/llcrashlogger.cpp +++ b/indra/llcrashlogger/llcrashlogger.cpp @@ -48,14 +48,20 @@ #include "llpumpio.h" #include "llhttpclient.h" #include "llsdserialize.h" +#include "llcurl.h" LLPumpIO* gServicePump; BOOL gBreak = false; BOOL gSent = false; -class LLCrashLoggerResponder : public LLHTTPClient::Responder +class AIHTTPTimeoutPolicy; +extern AIHTTPTimeoutPolicy crashLoggerResponder_timeout; + +class LLCrashLoggerResponder : public LLHTTPClient::ResponderWithResult { public: + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return crashLoggerResponder_timeout; } + LLCrashLoggerResponder() { } @@ -308,14 +314,14 @@ bool LLCrashLogger::saveCrashBehaviorSetting(S32 crash_behavior) return true; } -bool LLCrashLogger::runCrashLogPost(std::string host, LLSD data, std::string msg, int retries, int timeout) +bool LLCrashLogger::runCrashLogPost(std::string host, LLSD data, std::string msg, int retries) { gBreak = false; std::string status_message; for(int i = 0; i < retries; ++i) { status_message = llformat("%s, try %d...", msg.c_str(), i+1); - LLHTTPClient::post(host, data, new LLCrashLoggerResponder(), timeout); + LLHTTPClient::post(host, data, new LLCrashLoggerResponder); while(!gBreak) { updateApplication(status_message); @@ -350,12 +356,12 @@ bool LLCrashLogger::sendCrashLogs() // *TODO: Translate if(mCrashHost != "") { - sent = runCrashLogPost(mCrashHost, post_data, std::string("Sending to server"), 3, 5); + sent = runCrashLogPost(mCrashHost, post_data, std::string("Sending to server"), 3); } if(!sent) { - sent = runCrashLogPost(mAltCrashHost, post_data, std::string("Sending to alternate server"), 3, 5); + sent = runCrashLogPost(mAltCrashHost, post_data, std::string("Sending to alternate server"), 3); } mSentCrashLogs = sent; @@ -367,6 +373,7 @@ void LLCrashLogger::updateApplication(const std::string& message) { gServicePump->pump(); gServicePump->callback(); + //FIXME: AIStateMachine::mainloop(); needs CPU cycles. Can't call it from here though, because it uses gSavedSettings which is part of newview. } bool LLCrashLogger::init() @@ -394,7 +401,6 @@ bool LLCrashLogger::init() } gServicePump = new LLPumpIO; - LLHTTPClient::setPump(*gServicePump); //If we've opened the crash logger, assume we can delete the marker file if it exists if( gDirUtilp ) diff --git a/indra/llcrashlogger/llcrashlogger.h b/indra/llcrashlogger/llcrashlogger.h index 72c1f3fb8..08e4a5f9f 100644 --- a/indra/llcrashlogger/llcrashlogger.h +++ b/indra/llcrashlogger/llcrashlogger.h @@ -40,6 +40,8 @@ #include "llsd.h" #include "llcontrol.h" +class AIHTTPTimeoutPolicy; + class LLCrashLogger : public LLApp { public: @@ -57,7 +59,7 @@ public: virtual bool cleanup() { return true; } void setUserText(const std::string& text) { mCrashInfo["UserNotes"] = text; } S32 getCrashBehavior() { return mCrashBehavior; } - bool runCrashLogPost(std::string host, LLSD data, std::string msg, int retries, int timeout); + bool runCrashLogPost(std::string host, LLSD data, std::string msg, int retries); protected: S32 mCrashBehavior; BOOL mCrashInPreviousExec; diff --git a/indra/llimage/llimage.cpp b/indra/llimage/llimage.cpp index c3f4878ee..66217f902 100644 --- a/indra/llimage/llimage.cpp +++ b/indra/llimage/llimage.cpp @@ -1239,6 +1239,7 @@ file_extensions[] = { { "bmp", IMG_CODEC_BMP }, { "tga", IMG_CODEC_TGA }, + { "j2k", IMG_CODEC_J2C }, { "j2c", IMG_CODEC_J2C }, { "jp2", IMG_CODEC_J2C }, { "texture", IMG_CODEC_J2C }, diff --git a/indra/llmessage/CMakeLists.txt b/indra/llmessage/CMakeLists.txt index f084200f6..f1406dc7b 100644 --- a/indra/llmessage/CMakeLists.txt +++ b/indra/llmessage/CMakeLists.txt @@ -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( diff --git a/indra/llmessage/aicurl.cpp b/indra/llmessage/aicurl.cpp index 8237fcaa4..06e13e0f0 100644 --- a/indra/llmessage/aicurl.cpp +++ b/indra/llmessage/aicurl.cpp @@ -55,9 +55,15 @@ #include "lltimer.h" // ms_sleep #include "llproxy.h" #include "llhttpstatuscodes.h" -#ifdef CWDEBUG -#include -#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(AIThreadSafeSimpleDC::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(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(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(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(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, "", 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& 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 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(AIThreadSafeSimple::wrapper_cast(this)); + return static_cast(AIThreadSafeSimple::wrapper_cast(this)); } -void CurlResponderBuffer::prepRequest(AICurlEasyRequest_wat& curl_easy_request_w, std::vector 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(AIThreadSafeSimple::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::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(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(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(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(static_cast(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); } //----------------------------------------------------------------------------- diff --git a/indra/llmessage/aicurl.h b/indra/llmessage/aicurl.h index 26491da67..4424e4299 100644 --- a/indra/llmessage/aicurl.h +++ b/indra/llmessage/aicurl.h @@ -36,6 +36,7 @@ #include #include #include +#include #include #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 +#include +#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 is made. - friend void intrusive_ptr_release(Responder* p); // Called by boost::intrusive_ptr when a boost::intrusive_ptr 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 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 AICurlEasyRequest_rat; -typedef AIAccess AICurlEasyRequest_wat; +typedef AIAccessConst AICurlEasyRequest_rat; +typedef AIAccess AICurlEasyRequest_wat; // Events generated by AICurlPrivate::CurlEasyHandle. struct AICurlEasyHandleEvents { @@ -264,32 +205,38 @@ typedef LLPointer 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& operator*(void) const { llassert(mCurlEasyRequest.get()); return *mCurlEasyRequest; } - AIThreadSafeSimple* operator->(void) const { llassert(mCurlEasyRequest.get()); return mCurlEasyRequest.get(); } - AIThreadSafeSimple* get(void) const { return mCurlEasyRequest.get(); } + AIThreadSafeSimple& operator*(void) const { llassert(mBufferedCurlEasyRequest.get()); return *mBufferedCurlEasyRequest; } + AIThreadSafeSimple* operator->(void) const { llassert(mBufferedCurlEasyRequest.get()); return mBufferedCurlEasyRequest.get(); } + AIThreadSafeSimple* 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 { - explicit AICurlResponderBuffer_wat(AICurlPrivate::ThreadSafeBufferedCurlEasyRequest& lockobj) : - AIAccess(lockobj) { } - AICurlResponderBuffer_wat(AIThreadSafeSimple& lockobj) : - AIAccess(static_cast(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 diff --git a/indra/newview/statemachine/aicurleasyrequeststatemachine.cpp b/indra/llmessage/aicurleasyrequeststatemachine.cpp similarity index 81% rename from indra/newview/statemachine/aicurleasyrequeststatemachine.cpp rename to indra/llmessage/aicurleasyrequeststatemachine.cpp index 0c2f0dbc5..a5c9f9d44 100644 --- a/indra/newview/statemachine/aicurleasyrequeststatemachine.cpp +++ b/indra/llmessage/aicurleasyrequeststatemachine.cpp @@ -30,6 +30,7 @@ #include "linden_common.h" #include "aicurleasyrequeststatemachine.h" +#include "aihttptimeoutpolicy.h" #include "llcontrol.h" enum curleasyrequeststatemachine_state_type { @@ -61,8 +62,8 @@ void AICurlEasyRequestStateMachine::initialize_impl(void) { { AICurlEasyRequest_wat curlEasyRequest_w(*mCurlEasyRequest); - llassert(curlEasyRequest_w->is_finalized()); // Call finalizeRequest(url) before calling run(). - curlEasyRequest_w->send_events_to(this); + llassert(curlEasyRequest_w->is_finalized()); // Call finalizeRequest() before calling run(). + curlEasyRequest_w->send_handle_events_to(this); } mAdded = false; mTimedOut = false; @@ -87,6 +88,9 @@ void AICurlEasyRequestStateMachine::finished(AICurlEasyRequest_wat&) // 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); } @@ -116,13 +120,15 @@ void AICurlEasyRequestStateMachine::multiplex_impl(void) // 3) AICurlEasyRequestStateMachine_finished (running) // 4) AICurlEasyRequestStateMachine_removed_after_finished (running) - // 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. - static LLCachedControl CurlRequestTimeOut("CurlRequestTimeOut", 40.f); - mTimer = new AIPersistentTimer; // Do not delete timer upon expiration. - mTimer->setInterval(CurlRequestTimeOut); - mTimer->run(this, AICurlEasyRequestStateMachine_timedOut, false, false); + 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: @@ -161,17 +167,16 @@ void AICurlEasyRequestStateMachine::multiplex_impl(void) // Only do this once. mHandled = true; - // 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(); + 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. - if (mBuffered) - { - AICurlEasyRequest_wat easy_request_w(*mCurlEasyRequest); - AICurlResponderBuffer_wat buffered_easy_request_w(*mCurlEasyRequest); - buffered_easy_request_w->processOutput(easy_request_w); - } + AICurlEasyRequest_wat easy_request_w(*mCurlEasyRequest); + easy_request_w->processOutput(); } if (current_state == AICurlEasyRequestStateMachine_finished) @@ -187,11 +192,10 @@ void AICurlEasyRequestStateMachine::multiplex_impl(void) case AICurlEasyRequestStateMachine_removed: { // The request was removed from the multi handle. - if (mBuffered && mTimedOut) + if (mTimedOut) { AICurlEasyRequest_wat easy_request_w(*mCurlEasyRequest); - AICurlResponderBuffer_wat buffered_easy_request_w(*mCurlEasyRequest); - buffered_easy_request_w->timed_out(); + easy_request_w->timed_out(); } // We're done. If we timed out, abort -- or else the application will @@ -226,20 +230,30 @@ void AICurlEasyRequestStateMachine::finish_impl(void) // Revoke callbacks. { AICurlEasyRequest_wat curl_easy_request_w(*mCurlEasyRequest); - curl_easy_request_w->send_events_to(NULL); + curl_easy_request_w->send_buffer_events_to(NULL); + curl_easy_request_w->send_handle_events_to(NULL); curl_easy_request_w->revokeCallbacks(); } - // 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(); + 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(bool buffered) : mBuffered(buffered), mCurlEasyRequest(buffered) +AICurlEasyRequestStateMachine::AICurlEasyRequestStateMachine(void) : + mTimer(NULL), mTotalDelayTimeout(AIHTTPTimeoutPolicy::getDebugSettingsCurlTimeout().getTotalDelay()) { - Dout(dc::statemachine, "Calling AICurlEasyRequestStateMachine(" << (buffered ? "true" : "false") << ") [" << (void*)this << "] [" << (void*)mCurlEasyRequest.get() << "]"); + Dout(dc::statemachine, "Calling AICurlEasyRequestStateMachine(void) [" << (void*)this << "] [" << (void*)mCurlEasyRequest.get() << "]"); +} + +void AICurlEasyRequestStateMachine::setTotalDelayTimeout(F32 totalDelayTimeout) +{ + mTotalDelayTimeout = totalDelayTimeout; } AICurlEasyRequestStateMachine::~AICurlEasyRequestStateMachine() diff --git a/indra/newview/statemachine/aicurleasyrequeststatemachine.h b/indra/llmessage/aicurleasyrequeststatemachine.h similarity index 83% rename from indra/newview/statemachine/aicurleasyrequeststatemachine.h rename to indra/llmessage/aicurleasyrequeststatemachine.h index e5f875138..8ece6fc26 100644 --- a/indra/newview/statemachine/aicurleasyrequeststatemachine.h +++ b/indra/llmessage/aicurleasyrequeststatemachine.h @@ -40,7 +40,7 @@ // 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 and it's base class AICurlPrivate::CurlEasyHandle. +// 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 @@ -52,18 +52,22 @@ // Construction of a AICurlEasyRequestStateMachine might throw AICurlNoEasyHandle. class AICurlEasyRequestStateMachine : public AIStateMachine, public AICurlEasyHandleEvents { public: - AICurlEasyRequestStateMachine(bool buffered); + AICurlEasyRequestStateMachine(void); // Transparent access. AICurlEasyRequest mCurlEasyRequest; private: - bool mBuffered; // Argument used for construction of mCurlEasyRequest. - 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. + 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. diff --git a/indra/llmessage/aicurlprivate.h b/indra/llmessage/aicurlprivate.h index 1acf08e14..9fb62ca15 100644 --- a/indra/llmessage/aicurlprivate.h +++ b/indra/llmessage/aicurlprivate.h @@ -33,9 +33,83 @@ #include #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 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(ldata); return res; } - CURLcode getinfo(CURLINFO info, U32* data) { long ldata; CURLcode res = getinfo_priv(info, &ldata); *data = static_cast(ldata); return res; } + CURLcode getinfo(CURLINFO info, S32* data) const { long ldata; CURLcode res = getinfo_priv(info, &ldata); *data = static_cast(ldata); return res; } + CURLcode getinfo(CURLINFO info, U32* data) const { long ldata; CURLcode res = getinfo_priv(info, &ldata); *data = static_cast(ldata); return res; } #else // sizeof(long) == sizeof(int) - CURLcode getinfo(CURLINFO info, S32* data) { return getinfo_priv(info, reinterpret_cast(data)); } - CURLcode getinfo(CURLINFO info, U32* data) { return getinfo_priv(info, reinterpret_cast(data)); } + CURLcode getinfo(CURLINFO info, S32* data) const { return getinfo_priv(info, reinterpret_cast(data)); } + CURLcode getinfo(CURLINFO info, U32* data) const { return getinfo_priv(info, reinterpret_cast(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 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& get_timeout_object(ThreadSafeBufferedCurlEasyRequest* lockobj); + // Accessor for mTimeout with optional creation of orphaned object (if lockobj != NULL). + LLPointer& 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 const& headers, AICurlInterface::ResponderPtr responder, S32 time_out = 0, bool post = false); + // The type of the used buffers. + typedef boost::shared_ptr 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(this)->get_lockobj(); +} + +inline ThreadSafeBufferedCurlEasyRequest const* CurlEasyRequest::get_lockobj(void) const +{ + return static_cast(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 { +class ThreadSafeBufferedCurlEasyRequest : public AIThreadSafeSimple { 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 is made. - friend void intrusive_ptr_release(ThreadSafeCurlEasyRequest* p); // Called by boost::intrusive_ptr when a boost::intrusive_ptr 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 is now -// destructed before ThreadSafeCurlEasyRequest is. -class ThreadSafeBufferedCurlEasyRequest : public ThreadSafeCurlEasyRequest, public AIThreadSafeSimple { - public: - // Throws AICurlNoEasyHandle. - ThreadSafeBufferedCurlEasyRequest(void) { new (AIThreadSafeSimple::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 is made. + friend void intrusive_ptr_release(ThreadSafeBufferedCurlEasyRequest* p); // Called by boost::intrusive_ptr when a boost::intrusive_ptr is destroyed. }; // The curl easy request type wrapped in a reference counting pointer. -typedef boost::intrusive_ptr CurlEasyRequestPtr; +typedef boost::intrusive_ptr BufferedCurlEasyRequestPtr; // This class wraps CURLM*'s. // It guarantees that a pointer is cleaned up when no longer needed, as required by libcurl. diff --git a/indra/llmessage/aicurlthread.cpp b/indra/llmessage/aicurlthread.cpp index 0b1b85c0a..91744ce23 100644 --- a/indra/llmessage/aicurlthread.cpp +++ b/indra/llmessage/aicurlthread.cpp @@ -30,7 +30,10 @@ #include "linden_common.h" #include "aicurlthread.h" -#include "lltimer.h" // ms_sleep +#include "aihttptimeoutpolicy.h" +#include "lltimer.h" // ms_sleep, get_clock_count +#include "llhttpstatuscodes.h" +#include "llbuffer.h" #include #if !LL_WINDOWS #include @@ -38,6 +41,7 @@ #include #endif #include +#include // On linux, add -DDEBUG_WINDOWS_CODE_ON_LINUX to test the windows code used in this file. #if !defined(DEBUG_WINDOWS_CODE_ON_LINUX) || !defined(LL_LINUX) || defined(LL_RELEASE) @@ -211,14 +215,14 @@ class Command { Command(AICurlEasyRequest const& easy_request, command_st command) : mCurlEasyRequest(easy_request.get_ptr()), mCommand(command) { } command_st command(void) const { return mCommand; } - CurlEasyRequestPtr const& easy_request(void) const { return mCurlEasyRequest; } + BufferedCurlEasyRequestPtr const& easy_request(void) const { return mCurlEasyRequest; } bool operator==(AICurlEasyRequest const& easy_request) const { return mCurlEasyRequest == easy_request.get_ptr(); } void reset(void); private: - CurlEasyRequestPtr mCurlEasyRequest; + BufferedCurlEasyRequestPtr mCurlEasyRequest; command_st mCommand; }; @@ -233,11 +237,11 @@ void Command::reset(void) // // MAIN-THREAD (AICurlEasyRequest::addRequest) // * command_queue locked -// - A non-active (mActiveMultiHandle is NULL) ThreadSafeCurlEasyRequest (by means of an AICurlEasyRequest pointing to it) is added to command_queue with as command cmd_add. +// - A non-active (mActiveMultiHandle is NULL) ThreadSafeBufferedCurlEasyRequest (by means of an AICurlEasyRequest pointing to it) is added to command_queue with as command cmd_add. // * command_queue unlocked // // If at this point addRequest is called again, then it is detected that the last command added to the queue -// for this ThreadSafeCurlEasyRequest is cmd_add. +// for this ThreadSafeBufferedCurlEasyRequest is cmd_add. // // CURL-THREAD (AICurlThread::wakeup): // * command_queue locked @@ -247,7 +251,7 @@ void Command::reset(void) // - The command is removed from command_queue // * command_queue unlocked // -// If at this point addRequest is called again, then it is detected that command_being_processed adds the same ThreadSafeCurlEasyRequest. +// If at this point addRequest is called again, then it is detected that command_being_processed adds the same ThreadSafeBufferedCurlEasyRequest. // // * command_being_processed is read-locked // - mActiveMultiHandle is set to point to the curl multi handle @@ -256,7 +260,7 @@ void Command::reset(void) // - command_being_processed is reset // * command_being_processed is unlocked // -// If at this point addRequest is called again, then it is detected that the ThreadSafeCurlEasyRequest is active. +// If at this point addRequest is called again, then it is detected that the ThreadSafeBufferedCurlEasyRequest is active. // Multi-threaded queue for passing Command objects from the main-thread to the curl-thread. AIThreadSafeSimpleDC > command_queue; @@ -695,11 +699,54 @@ bool MergeIterator::next(curl_socket_t& fd_out, int& ev_bitmask_out) //----------------------------------------------------------------------------- // CurlSocketInfo +#if defined(CWDEBUG) || defined(DEBUG_CURLIO) +#undef AI_CASE_RETURN +#define AI_CASE_RETURN(x) case x: return #x; +static char const* action_str(int action) +{ + switch(action) + { + AI_CASE_RETURN(CURL_POLL_NONE); + AI_CASE_RETURN(CURL_POLL_IN); + AI_CASE_RETURN(CURL_POLL_OUT); + AI_CASE_RETURN(CURL_POLL_INOUT); + AI_CASE_RETURN(CURL_POLL_REMOVE); + } + return ""; +} + +struct DebugFdSet { + int nfds; + fd_set* fdset; + DebugFdSet(int n, fd_set* p) : nfds(n), fdset(p) { } +}; + +std::ostream& operator<<(std::ostream& os, DebugFdSet const& s) +{ + if (!s.fdset) + return os << "NULL"; + bool first = true; + os << '{'; + for (int fd = 0; fd < s.nfds; ++fd) + { + if (FD_ISSET(fd, s.fdset)) + { + if (!first) + os << ", "; + os << fd; + first = false; + } + } + os << '}'; + return os; +} +#endif + // A class with info for each socket that is in use by curl. class CurlSocketInfo { public: - CurlSocketInfo(MultiHandle& multi_handle, CURL* easy, curl_socket_t s, int action); + CurlSocketInfo(MultiHandle& multi_handle, CURL* easy, curl_socket_t s, int action, ThreadSafeBufferedCurlEasyRequest* lockobj); ~CurlSocketInfo(); void set_action(int action); @@ -709,15 +756,25 @@ class CurlSocketInfo CURL const* mEasy; curl_socket_t mSocketFd; int mAction; + AICurlEasyRequest mEasyRequest; + LLPointer mTimeout; }; -CurlSocketInfo::CurlSocketInfo(MultiHandle& multi_handle, CURL* easy, curl_socket_t s, int action) : - mMultiHandle(multi_handle), mEasy(easy), mSocketFd(s), mAction(CURL_POLL_NONE) +CurlSocketInfo::CurlSocketInfo(MultiHandle& multi_handle, CURL* easy, curl_socket_t s, int action, ThreadSafeBufferedCurlEasyRequest* lockobj) : + mMultiHandle(multi_handle), mEasy(easy), mSocketFd(s), mAction(CURL_POLL_NONE), mEasyRequest(lockobj) { + llassert(*AICurlEasyRequest_wat(*mEasyRequest) == easy); mMultiHandle.assign(s, this); llassert(!mMultiHandle.mReadPollSet->contains(s)); llassert(!mMultiHandle.mWritePollSet->contains(s)); set_action(action); + // Create a new HTTPTimeout object and keep a pointer to it in the corresponding CurlEasyRequest object. + // The reason for this seemingly redundant storage (we could just store it directly in the CurlEasyRequest + // and not in CurlSocketInfo) is because in the case of a redirection there exist temporarily two + // CurlSocketInfo objects for a request and we need upload_finished() to be called on the HTTPTimeout + // object related to THIS CurlSocketInfo. + AICurlEasyRequest_wat easy_request_w(*lockobj); + mTimeout = easy_request_w->get_timeout_object(lockobj); } CurlSocketInfo::~CurlSocketInfo() @@ -727,6 +784,7 @@ CurlSocketInfo::~CurlSocketInfo() void CurlSocketInfo::set_action(int action) { + Dout(dc::curl, "CurlSocketInfo::set_action(" << action_str(mAction) << " --> " << action_str(action) << ") [" << (void*)mEasyRequest.get_ptr().get() << "]"); int toggle_action = mAction ^ action; mAction = action; if ((toggle_action & CURL_POLL_IN)) @@ -741,7 +799,20 @@ void CurlSocketInfo::set_action(int action) if ((action & CURL_POLL_OUT)) mMultiHandle.mWritePollSet->add(mSocketFd); else + { mMultiHandle.mWritePollSet->remove(mSocketFd); + + // The following is a bit of a hack, needed because of the lack of proper timeout callbacks in libcurl. + // The removal of CURL_POLL_OUT could be part of the SSL handshake, therefore check if we're already connected: + AICurlEasyRequest_wat curl_easy_request_w(*mEasyRequest); + double pretransfer_time; + curl_easy_request_w->getinfo(CURLINFO_PRETRANSFER_TIME, &pretransfer_time); + if (pretransfer_time > 0) + { + // If CURL_POLL_OUT is removed and CURLINFO_PRETRANSFER_TIME is already set, then we have nothing more to send apparently. + mTimeout->upload_finished(); // Update timeout administration. + } + } } } @@ -779,7 +850,7 @@ class AICurlThread : public LLThread curl_socket_t mWakeUpFd_in; curl_socket_t mWakeUpFd; - int mZeroTimeOut; + int mZeroTimeout; volatile bool mRunning; }; @@ -791,7 +862,7 @@ AICurlThread* AICurlThread::sInstance = NULL; AICurlThread::AICurlThread(void) : LLThread("AICurlThread"), mWakeUpFd_in(CURL_SOCKET_BAD), mWakeUpFd(CURL_SOCKET_BAD), - mZeroTimeOut(0), mRunning(true), mWakeUpFlag(false) + mZeroTimeout(0), mRunning(true), mWakeUpFlag(false) { create_wakeup_fds(); sInstance = this; @@ -805,10 +876,9 @@ AICurlThread::~AICurlThread() } #if LL_WINDOWS -static std::string formatWSAError() +static std::string formatWSAError(int e = WSAGetLastError()) { std::ostringstream r; - int e = WSAGetLastError(); LPTSTR error_str = 0; r << e; if(FormatMessage( @@ -825,9 +895,9 @@ static std::string formatWSAError() return r.str(); } #elif WINDOWS_CODE -static std::string formatWSAError() +static std::string formatWSAError(int e = errno) { - return strerror(errno); + return strerror(e); } #endif @@ -1234,31 +1304,34 @@ void AICurlThread::run(void) // We're now entering select(), during which the main thread will write to the pipe/socket // to wake us up, because it can't get the lock. struct timeval timeout; - long timeout_ms = multi_handle_w->getTimeOut(); + long timeout_ms = multi_handle_w->getTimeout(); // If no timeout is set, sleep 1 second. if (LL_UNLIKELY(timeout_ms < 0)) timeout_ms = 1000; if (LL_UNLIKELY(timeout_ms == 0)) { - if (mZeroTimeOut >= 10000) + if (mZeroTimeout >= 10000) { - if (mZeroTimeOut == 10000) + if (mZeroTimeout == 10000) llwarns << "Detected more than 10000 zero-timeout calls of select() by curl thread (more than 101 seconds)!" << llendl; } - else if (mZeroTimeOut >= 1000) + else if (mZeroTimeout >= 1000) timeout_ms = 10; - else if (mZeroTimeOut >= 100) + else if (mZeroTimeout >= 100) timeout_ms = 1; } else { - if (LL_UNLIKELY(mZeroTimeOut >= 10000)) + if (LL_UNLIKELY(mZeroTimeout >= 10000)) llinfos << "Timeout of select() call by curl thread reset (to " << timeout_ms << " ms)." << llendl; - mZeroTimeOut = 0; + mZeroTimeout = 0; } timeout.tv_sec = timeout_ms / 1000; timeout.tv_usec = (timeout_ms % 1000) * 1000; #ifdef CWDEBUG +#ifdef DEBUG_CURLIO + Dout(dc::curl|flush_cf|continued_cf, "select(" << nfds << ", " << DebugFdSet(nfds, read_fd_set) << ", " << DebugFdSet(nfds, write_fd_set) << ", NULL, timeout = " << timeout_ms << " ms) = "); +#else static int last_nfds = -1; static long last_timeout_ms = -1; static int same_count = 0; @@ -1274,10 +1347,14 @@ void AICurlThread::run(void) { ++same_count; } +#endif #endif ready = select(nfds, read_fd_set, write_fd_set, NULL, &timeout); mWakeUpMutex.unlock(); #ifdef CWDEBUG +#ifdef DEBUG_CURLIO + Dout(dc::finish|cond_error_cf(ready == -1), ready); +#else static int last_ready = -2; static int last_errno = 0; if (!same) @@ -1294,6 +1371,7 @@ void AICurlThread::run(void) last_ready = ready; if (ready == -1) last_errno = errno; +#endif #endif // Select returns the total number of bits set in each of the fd_set's (upon return), // or -1 when an error occurred. A value of 0 means that a timeout occurred. @@ -1302,9 +1380,13 @@ void AICurlThread::run(void) llwarns << "select() failed: " << errno << ", " << strerror(errno) << llendl; continue; } - else if (ready == 0) + // Clock count used for timeouts. + HTTPTimeout::sClockCount = get_clock_count(); + Dout(dc::curl, "HTTPTimeout::sClockCount = " << HTTPTimeout::sClockCount); + if (ready == 0) { multi_handle_w->socket_action(CURL_SOCKET_TIMEOUT, 0); + multi_handle_w->handle_stalls(); } else { @@ -1338,7 +1420,7 @@ void AICurlThread::run(void) //----------------------------------------------------------------------------- // MultiHandle -MultiHandle::MultiHandle(void) : mHandleAddedOrRemoved(false), mPrevRunningHandles(0), mRunningHandles(0), mTimeOut(-1), mReadPollSet(NULL), mWritePollSet(NULL) +MultiHandle::MultiHandle(void) : mRunningHandles(0), mTimeout(-1), mReadPollSet(NULL), mWritePollSet(NULL) { mReadPollSet = new PollSet; mWritePollSet = new PollSet; @@ -1356,33 +1438,37 @@ MultiHandle::~MultiHandle() // Curl demands that all handles are removed from the multi session handle before calling curl_multi_cleanup. for(addedEasyRequests_type::iterator iter = mAddedEasyRequests.begin(); iter != mAddedEasyRequests.end(); iter = mAddedEasyRequests.begin()) { + finish_easy_request(*iter, CURLE_OK); // Error code is not used anyway. remove_easy_request(*iter); } delete mWritePollSet; delete mReadPollSet; } -#if defined(CWDEBUG) || defined(DEBUG_CURLIO) -#undef AI_CASE_RETURN -#define AI_CASE_RETURN(x) do { case x: return #x; } while(0) -char const* action_str(int action) +void MultiHandle::handle_stalls(void) { - switch(action) + for(addedEasyRequests_type::iterator iter = mAddedEasyRequests.begin(); iter != mAddedEasyRequests.end();) { - AI_CASE_RETURN(CURL_POLL_NONE); - AI_CASE_RETURN(CURL_POLL_IN); - AI_CASE_RETURN(CURL_POLL_OUT); - AI_CASE_RETURN(CURL_POLL_INOUT); - AI_CASE_RETURN(CURL_POLL_REMOVE); + if (AICurlEasyRequest_wat(**iter)->has_stalled()) + { + Dout(dc::curl, "MultiHandle::handle_stalls(): Easy request stalled! [" << (void*)iter->get_ptr().get() << "]"); + finish_easy_request(*iter, CURLE_OPERATION_TIMEDOUT); + remove_easy_request(iter++, false); + } + else + ++iter; } - return ""; } -#endif //static int MultiHandle::socket_callback(CURL* easy, curl_socket_t s, int action, void* userp, void* socketp) { - DoutEntering(dc::curl, "MultiHandle::socket_callback(" << (void*)easy << ", " << s << ", " << action_str(action) << ", " << (void*)userp << ", " << (void*)socketp << ")"); +#ifdef CWDEBUG + ThreadSafeBufferedCurlEasyRequest* lockobj = NULL; + curl_easy_getinfo(easy, CURLINFO_PRIVATE, &lockobj); + DoutEntering(dc::curl, "MultiHandle::socket_callback((CURL*)" << (void*)easy << ", " << s << + ", " << action_str(action) << ", " << (void*)userp << ", " << (void*)socketp << ") [CURLINFO_PRIVATE = " << (void*)lockobj << "]"); +#endif MultiHandle& self = *static_cast(userp); CurlSocketInfo* sock_info = static_cast(socketp); if (action == CURL_POLL_REMOVE) @@ -1393,7 +1479,10 @@ int MultiHandle::socket_callback(CURL* easy, curl_socket_t s, int action, void* { if (!sock_info) { - sock_info = new CurlSocketInfo(self, easy, s, action); + ThreadSafeBufferedCurlEasyRequest* ptr; + CURLcode rese = curl_easy_getinfo(easy, CURLINFO_PRIVATE, &ptr); + llassert_always(rese == CURLE_OK); + sock_info = new CurlSocketInfo(self, easy, s, action, ptr); } else { @@ -1408,7 +1497,7 @@ int MultiHandle::timer_callback(CURLM* multi, long timeout_ms, void* userp) { MultiHandle& self = *static_cast(userp); llassert(multi == self.mMultiHandle); - self.mTimeOut = timeout_ms; + self.mTimeout = timeout_ms; Dout(dc::curl, "MultiHandle::timer_callback(): timeout set to " << timeout_ms << " ms."); return 0; } @@ -1440,25 +1529,67 @@ CURLMsg const* MultiHandle::info_read(int* msgs_in_queue) const return ret; } -CURLMcode MultiHandle::add_easy_request(AICurlEasyRequest const& easy_request) +static U32 curl_concurrent_connections = 8; // Initialized on start up by startCurlThread(). + +void MultiHandle::add_easy_request(AICurlEasyRequest const& easy_request) { - std::pair res = mAddedEasyRequests.insert(easy_request); - llassert(res.second); // May not have been added before. - CURLMcode ret; + if (mAddedEasyRequests.size() < curl_concurrent_connections) // Not throttled? { - AICurlEasyRequest_wat curl_easy_request_w(*easy_request); - ret = curl_easy_request_w->add_handle_to_multi(curl_easy_request_w, mMultiHandle); + CURLMcode ret; + { + AICurlEasyRequest_wat curl_easy_request_w(*easy_request); + curl_easy_request_w->set_timeout_opts(); + ret = curl_easy_request_w->add_handle_to_multi(curl_easy_request_w, mMultiHandle); + } + if (ret == CURLM_OK) + { + std::pair res = mAddedEasyRequests.insert(easy_request); + llassert(res.second); // May not have been added before. + Dout(dc::curl, "MultiHandle::add_easy_request: Added AICurlEasyRequest " << (void*)easy_request.get_ptr().get() << "; now processing " << mAddedEasyRequests.size() << " easy handles."); + return; + } } - mHandleAddedOrRemoved = true; - Dout(dc::curl, "MultiHandle::add_easy_request: Added AICurlEasyRequest " << (void*)easy_request.get() << "; now processing " << mAddedEasyRequests.size() << " easy handles."); - return ret; + mQueuedRequests.push_back(easy_request); +#ifdef SHOW_ASSERT + // Not active yet, but it's no longer an error if next we try to remove the request. + AICurlEasyRequest_wat(*easy_request)->mRemovedPerCommand = false; +#endif } CURLMcode MultiHandle::remove_easy_request(AICurlEasyRequest const& easy_request, bool as_per_command) { addedEasyRequests_type::iterator iter = mAddedEasyRequests.find(easy_request); if (iter == mAddedEasyRequests.end()) - return (CURLMcode)-2; // Was already removed before. + { + // The request could be queued. + std::deque::iterator const end = mQueuedRequests.end(); + std::deque::iterator cur = std::find(mQueuedRequests.begin(), end, easy_request); + if (cur != end) + { + // We can't use erase because that uses assignment to move elements, which is private because it isn't thread-safe for AICurlEasyRequest. + // Therefore, move the element that we found to the back with swap (could just swap with the end immediately, + // but I don't want to break the order in which requests where added). Swap is also not thread-safe, but OK here + // because it only touches the AICurlEasyRequest objects in the deque, and the deque is protected by the + // lock on MultiHandle. + std::deque::iterator prev = cur; + while (++cur != end) + { + prev->swap(*cur); + prev = cur; + } +#ifdef SHOW_ASSERT + // Now a second remove command would be an error again. + AICurlEasyRequest_wat(**prev)->mRemovedPerCommand = true; +#endif + mQueuedRequests.pop_back(); + } + return (CURLMcode)-2; // Was already removed before, or never added (queued). + } + return remove_easy_request(iter, as_per_command); +} + +CURLMcode MultiHandle::remove_easy_request(addedEasyRequests_type::iterator const& iter, bool as_per_command) +{ CURLMcode res; { AICurlEasyRequest_wat curl_easy_request_w(**iter); @@ -1467,15 +1598,26 @@ CURLMcode MultiHandle::remove_easy_request(AICurlEasyRequest const& easy_request curl_easy_request_w->mRemovedPerCommand = as_per_command; #endif } +#if CWDEBUG + ThreadSafeBufferedCurlEasyRequest* lockobj = iter->get_ptr().get(); +#endif mAddedEasyRequests.erase(iter); - mHandleAddedOrRemoved = true; - Dout(dc::curl, "MultiHandle::remove_easy_request: Removed AICurlEasyRequest " << (void*)easy_request.get() << "; now processing " << mAddedEasyRequests.size() << " easy handles."); + Dout(dc::curl, "MultiHandle::remove_easy_request: Removed AICurlEasyRequest " << (void*)lockobj << "; now processing " << mAddedEasyRequests.size() << " easy handles."); + + // Attempt to add a queued request, if any. + if (!mQueuedRequests.empty()) + { + add_easy_request(mQueuedRequests.front()); + mQueuedRequests.pop_front(); + } + return res; } void MultiHandle::check_run_count(void) { - if (mHandleAddedOrRemoved || mRunningHandles < mPrevRunningHandles) + llassert(mAddedEasyRequests.size() >= (size_t)mRunningHandles); + if (mAddedEasyRequests.size() - (size_t)mRunningHandles > 0) // There is no need to do this when all easy handles are accounted for. { CURLMsg const* msg; int msgs_left; @@ -1484,23 +1626,13 @@ void MultiHandle::check_run_count(void) if (msg->msg == CURLMSG_DONE) { CURL* easy = msg->easy_handle; - ThreadSafeCurlEasyRequest* ptr; + ThreadSafeBufferedCurlEasyRequest* ptr; CURLcode rese = curl_easy_getinfo(easy, CURLINFO_PRIVATE, &ptr); llassert_always(rese == CURLE_OK); AICurlEasyRequest easy_request(ptr); llassert(*AICurlEasyRequest_wat(*easy_request) == easy); - // Store the result and transfer info in the easy handle. - { - AICurlEasyRequest_wat curl_easy_request_w(*easy_request); - curl_easy_request_w->store_result(msg->data.result); -#ifdef CWDEBUG - char* eff_url; - curl_easy_request_w->getinfo(CURLINFO_EFFECTIVE_URL, &eff_url); - Dout(dc::curl, "Finished: " << eff_url << " (" << msg->data.result << ")"); -#endif - // Signal that this easy handle finished. - curl_easy_request_w->done(curl_easy_request_w); - } + // Store result and trigger events for the easy request. + finish_easy_request(easy_request, msg->data.result); // This invalidates msg, but not easy_request. CURLMcode res = remove_easy_request(easy_request); // This should hold, I think, because the handles are obviously ok and @@ -1519,9 +1651,331 @@ void MultiHandle::check_run_count(void) // Destruction of easy_request at this point, causes the CurlEasyRequest to be deleted. } } - mHandleAddedOrRemoved = false; } - mPrevRunningHandles = mRunningHandles; +} + +void MultiHandle::finish_easy_request(AICurlEasyRequest const& easy_request, CURLcode result) +{ + AICurlEasyRequest_wat curl_easy_request_w(*easy_request); + // Store the result in the easy handle. + curl_easy_request_w->storeResult(result); +#ifdef CWDEBUG + char* eff_url; + curl_easy_request_w->getinfo(CURLINFO_EFFECTIVE_URL, &eff_url); + double namelookup_time, connect_time, appconnect_time, pretransfer_time, starttransfer_time; + curl_easy_request_w->getinfo(CURLINFO_NAMELOOKUP_TIME, &namelookup_time); + curl_easy_request_w->getinfo(CURLINFO_CONNECT_TIME, &connect_time); + curl_easy_request_w->getinfo(CURLINFO_APPCONNECT_TIME, &appconnect_time); + curl_easy_request_w->getinfo(CURLINFO_PRETRANSFER_TIME, &pretransfer_time); + curl_easy_request_w->getinfo(CURLINFO_STARTTRANSFER_TIME, &starttransfer_time); + // If appconnect_time is almost equal to connect_time, then it was just set because this is a connection re-use. + if (appconnect_time - connect_time <= 1e-6) + { + appconnect_time = 0; + } + // If connect_time is almost equal to namelookup_time, then it was just set because it was already connected. + if (connect_time - namelookup_time <= 1e-6) + { + connect_time = 0; + } + // If namelookup_time is less than 500 microseconds, then it's very likely just a DNS cache lookup. + if (namelookup_time < 500e-6) + { + namelookup_time = 0; + } + Dout(dc::curl|continued_cf, "Finished: " << eff_url << " (" << curl_easy_strerror(result)); + if (result != CURLE_OK) + { + long os_error; + curl_easy_request_w->getinfo(CURLINFO_OS_ERRNO, &os_error); + if (os_error) + { +#if WINDOWS_CODE + Dout(dc::continued, ": " << formatWSAError(os_error)); +#else + Dout(dc::continued, ": " << strerror(os_error)); +#endif + } + } + Dout(dc::continued, "); "); + if (namelookup_time) + { + Dout(dc::continued, "namelookup time: " << namelookup_time << ", "); + } + if (connect_time) + { + Dout(dc::continued, "connect_time: " << connect_time << ", "); + } + if (appconnect_time) + { + Dout(dc::continued, "appconnect_time: " << appconnect_time << ", "); + } + Dout(dc::finish, "pretransfer_time: " << pretransfer_time << ", starttransfer_time: " << starttransfer_time << + ". [CURLINFO_PRIVATE = " << (void*)easy_request.get_ptr().get() << "]"); +#endif + // Signal that this easy handle finished. + curl_easy_request_w->done(curl_easy_request_w, result); +} + +//----------------------------------------------------------------------------- +// HTTPTimeout + +//static +F64 const HTTPTimeout::sClockWidth = 1.0 / calc_clock_frequency(); // Time between two clock ticks, in seconds. +U64 HTTPTimeout::sClockCount; // Clock count, set once per select() exit. + +// CURL-THREAD +// This is called when body data was sent to the server socket. +// <-----mLowSpeedOn------> +// queued--><--DNS lookup + connect + send headers-->[<--send body (if any)-->]<--replydelay--><--receive headers + body--><--done +// ^ ^ ^ ^ ^ ^ +// | | | | | | +bool HTTPTimeout::data_sent(size_t n) +{ + // Generate events. + if (!mLowSpeedOn) + { + // If we can send data (for the first time) then that's our only way to know we connected. + reset_lowspeed(); + } + // Detect low speed. + return lowspeed(n); +} + +// CURL-THREAD +// This is called when the 'low speed' timer should be started. +// <-----mLowSpeedOn------> <-------mLowSpeedOn--------> +// queued--><--DNS lookup + connect + send headers-->[<--send body (if any)-->]<--replydelay--><--receive headers + body--><--done +// ^ ^ +// | | +void HTTPTimeout::reset_lowspeed(void) +{ + mLowSpeedClock = sClockCount; + mLowSpeedOn = true; + mLastSecond = -1; // This causes lowspeed to initialize the rest. + mStalled = (U64)-1; // Stop reply delay timer. + DoutCurl("reset_lowspeed: mLowSpeedClock = " << mLowSpeedClock << "; mStalled = -1"); +} + +// CURL-THREAD +// This is called when everything we had to send to the server has been sent. +// <-----mLowSpeedOn------> +// queued--><--DNS lookup + connect + send headers-->[<--send body (if any)-->]<--replydelay--><--receive headers + body--><--done +// ^ +// | +void HTTPTimeout::upload_finished(void) +{ + llassert(!mUploadFinished); // If we get here twice, then the 'upload finished' detection failed. + mUploadFinished = true; + // We finished uploading (if there was a body to upload at all), so not more transfer rate timeouts. + mLowSpeedOn = false; + // Timeout if the server doesn't reply quick enough. + mStalled = sClockCount + mPolicy->getReplyDelay() / sClockWidth; + DoutCurl("upload_finished: mStalled set to sClockCount (" << sClockCount << ") + " << (mStalled - sClockCount) << " (" << mPolicy->getReplyDelay() << " seconds)"); +} + +// CURL-THREAD +// This is called when data was received from the server. +// +// <--------------------------------mNothingReceivedYet------------------------------><-------mLowSpeedOn--------> +// queued--><--DNS lookup + connect + send headers-->[<--send body (if any)-->]<--replydelay--><--receive headers + body--><--done +// ^ ^ ^ ^ ^ ^ ^ ^ +// | | | | | | | | +bool HTTPTimeout::data_received(size_t n) +{ + // The HTTP header of the reply is the first thing we receive. + if (mNothingReceivedYet && n > 0) + { + if (!mUploadFinished) + { + // mUploadFinished not being set this point should only happen for GET requests (in fact, then it is normal), + // because in that case it is impossible to detect the difference between connecting and waiting for a reply without + // using CURLOPT_DEBUGFUNCTION. Note that mDebugIsHeadOrGetMethod is only valid when the debug channel 'curlio' is on, + // because it is set in the debug callback function. + Debug(llassert(AICurlEasyRequest_wat(*mLockObj)->mDebugIsHeadOrGetMethod || !dc::curlio.is_on())); + // 'Upload finished' detection failed, generate it now. + upload_finished(); + } + // Turn this flag off again now that we received data, so that if 'upload_finished()' is called again + // for a future upload on the same descriptor, then that won't trigger an assert. + // Note that because we also set mNothingReceivedYet here, we won't enter this code block anymore, + // so it's safe to do this. + mUploadFinished = false; + // Mark that something was received. + mNothingReceivedYet = false; + // We received something; switch to getLowSpeedLimit()/getLowSpeedTime(). + reset_lowspeed(); + } + return mLowSpeedOn ? lowspeed(n) : false; +} + +// CURL_THREAD +// bytes is the number of bytes we just sent or received (including headers). +// Returns true if the transfer should be aborted. +// +// queued--><--DNS lookup + connect + send headers-->[<--send body (if any)-->]<--replydelay--><--receive headers + body--><--done +// ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ +// | | | | | | | | | | | | | | +bool HTTPTimeout::lowspeed(size_t bytes) +{ + DoutCurlEntering("HTTPTimeout::lowspeed(" << bytes << ")"); + + // The algorithm to determine if we timed out if different from how libcurls CURLOPT_LOW_SPEED_TIME works. + // + // libcurl determines the transfer rate since the last call to an equivalent 'lowspeed' function, and then + // triggers a timeout if CURLOPT_LOW_SPEED_TIME long such a transfer value is less than CURLOPT_LOW_SPEED_LIMIT. + // That doesn't work right because once there IS data it can happen that this function is called a few + // times (with less than a milisecond in between) causing seemingly VERY high "transfer rate" spikes. + // The only correct way to determine the transfer rate is to actually average over CURLOPT_LOW_SPEED_TIME + // seconds. + // + // We do this as follows: we create low_speed_time (in seconds) buckets and fill them with the number + // of bytes received during that second. We also keep track of the sum of all bytes received between 'now' + // and 'now - llmax(starttime, low_speed_time)'. Then if that period reaches at least low_speed_time + // seconds, and the transfer rate (sum / low_speed_time) is less than low_speed_limit, we abort. + + // When are we? + S32 second = (sClockCount - mLowSpeedClock) * sClockWidth; + llassert(sClockWidth > 0.0); + // This REALLY should never happen, but due to another bug it did happened + // and caused something so evil and hard to find that... NEVER AGAIN! + llassert(second >= 0); + + // If this is the same second as last time, just add the number of bytes to the current bucket. + if (second == mLastSecond) + { + mTotalBytes += bytes; + mBuckets[mBucket] += bytes; + return false; + } + + // We arrived at a new second. + // The below is at most executed once per second, even though for + // every currently connected transfer, CPU is not a big issue. + + // Determine the number of buckets needed and increase the number of buckets if needed. + U16 const low_speed_time = mPolicy->getLowSpeedTime(); + if (low_speed_time > mBuckets.size()) + { + mBuckets.resize(low_speed_time, 0); + } + + S32 s = mLastSecond; + mLastSecond = second; + + // If this is the first time this function is called, we need to do some initialization. + if (s == -1) + { + mBucket = 0; // It doesn't really matter where we start. + mTotalBytes = bytes; + mBuckets[mBucket] = bytes; + return false; + } + + // Update all administration. + U16 bucket = mBucket; + while(1) // Run over all the seconds that were skipped. + { + if (++bucket == low_speed_time) + bucket = 0; + if (++s == second) + break; + mTotalBytes -= mBuckets[bucket]; + mBuckets[bucket] = 0; + } + mBucket = bucket; + mTotalBytes -= mBuckets[mBucket]; + mTotalBytes += bytes; + mBuckets[mBucket] = bytes; + + // Check if we timed out. + U32 const low_speed_limit = mPolicy->getLowSpeedLimit(); + U32 mintotalbytes = low_speed_limit * low_speed_time; + DoutCurl("Transfered " << mTotalBytes << " bytes in " << llmin(second, (S32)low_speed_time) << " seconds after " << second << " second" << ((second == 1) ? "" : "s") << "."); + if (second >= low_speed_time) + { + DoutCurl("Average transfer rate is " << (mTotalBytes / low_speed_time) << " bytes/s (low speed limit is " << low_speed_limit << " bytes/s)"); + if (mTotalBytes < mintotalbytes) + { + // The average transfer rate over the passed low_speed_time seconds is too low. Abort the transfer. + llwarns << +#ifdef CWDEBUG + (void*)get_lockobj() << ": " +#endif + "aborting slow connection (average transfer rate below " << low_speed_limit << + " for more than " << low_speed_time << " second" << ((low_speed_time == 1) ? "" : "s") << ")." << llendl; + return true; + } + } + + // Calculate how long the data transfer may stall until we should timeout. + llassert_always(mintotalbytes > 0); + S32 max_stall_time = 0; + U32 dropped_bytes = 0; + while(1) + { + if (++bucket == low_speed_time) // The next second the next bucket will be emptied. + bucket = 0; + ++max_stall_time; + dropped_bytes += mBuckets[bucket]; + // Note how, when max_stall_time == low_speed_time, dropped_bytes has + // to be equal to mTotalBytes, the sum of all vector elements. + llassert_always(max_stall_time < low_speed_time || dropped_bytes == mTotalBytes); + // And thus the following will certainly abort. + if (second + max_stall_time >= low_speed_time && mTotalBytes - dropped_bytes < mintotalbytes) + break; + } + // If this function isn't called again within max_stall_time seconds, we stalled. + mStalled = sClockCount + max_stall_time / sClockWidth; + DoutCurl("mStalled set to sClockCount (" << sClockCount << ") + " << (mStalled - sClockCount) << " (" << max_stall_time << " seconds)"); + + return false; +} + +// CURL-THREAD +// This is called immediately before done() after curl finished, with code. +// <-------mLowSpeedOn--------> +// queued--><--DNS lookup + connect + send headers-->[<--send body (if any)-->]<--replydelay--><--receive headers + body--><--done +// ^ +// | +void HTTPTimeout::done(AICurlEasyRequest_wat const& curlEasyRequest_w, CURLcode code) +{ + if (code == CURLE_OPERATION_TIMEDOUT || code == CURLE_COULDNT_RESOLVE_HOST) + { + bool dns_problem = false; + if (code == CURLE_COULDNT_RESOLVE_HOST) + { + // Note that CURLINFO_OS_ERRNO returns 0; we don't know any more than this. + llwarns << "Failed to resolve hostname " << curlEasyRequest_w->getLowercaseHostname() << llendl; + dns_problem = true; + } + else if (mNothingReceivedYet) + { + // Only consider this to possibly be related to a DNS lookup if we didn't + // resolved the host yet, which can be detected by asking for + // CURLINFO_NAMELOOKUP_TIME which is set when libcurl initiates the + // actual connect and thus knows the IP# (possibly from it's DNS cache). + double namelookup_time; + curlEasyRequest_w->getinfo(CURLINFO_NAMELOOKUP_TIME, &namelookup_time); + dns_problem = (namelookup_time == 0); + } + if (dns_problem) + { + // Inform policy object that there might be problems with resolving this host. + // This will increase the connect timeout the next time we try to connect to this host. + AIHTTPTimeoutPolicy::connect_timed_out(curlEasyRequest_w->getLowercaseHostname()); + // AIFIXME: use return value to change priority + } + } + // Make sure no timeout will happen anymore. + mLowSpeedOn = false; + mStalled = (U64)-1; + DoutCurl("done: mStalled set to -1"); +} + +void HTTPTimeout::print_diagnostics(CurlEasyRequest const* curl_easy_request) +{ + llwarns << "Request to " << curl_easy_request->getLowercaseHostname() << " timed out for " << curl_easy_request->getTimeoutPolicy()->name() << llendl; } } // namespace curlthread @@ -1584,6 +2038,312 @@ void stopCurlThread(void) } } +//----------------------------------------------------------------------------- +// BufferedCurlEasyRequest + +void BufferedCurlEasyRequest::setStatusAndReason(U32 status, std::string const& reason) +{ + mStatus = status; + mReason = reason; +} + +void BufferedCurlEasyRequest::processOutput(void) +{ + U32 responseCode = 0; + std::string responseReason; + + CURLcode code; + AITransferInfo info; + getResult(&code, &info); + if (code == CURLE_OK) + { + getinfo(CURLINFO_RESPONSE_CODE, &responseCode); + // If getResult code is CURLE_OK then we should have decoded the first header line ourselves. + llassert(responseCode == mStatus); + if (responseCode == mStatus) + responseReason = mReason; + else + responseReason = "Unknown reason."; + } + else + { + responseCode = HTTP_INTERNAL_ERROR; + responseReason = curl_easy_strerror(code); + setopt(CURLOPT_FRESH_CONNECT, TRUE); + } + + if (code != CURLE_OK) + { + print_diagnostics(code); + } + if (mBufferEventsTarget) + { + // Only the responder registers for these events. + llassert(mBufferEventsTarget == mResponder.get()); + // Allow clients to parse result codes and headers before we attempt to parse + // the body and provide completed/result/error calls. + mBufferEventsTarget->completed_headers(responseCode, responseReason, (code == CURLE_FAILED_INIT) ? NULL : &info); + } + mResponder->finished(code, responseCode, responseReason, sChannels, mOutput); + mResponder = NULL; + + resetState(); +} + +void BufferedCurlEasyRequest::received_HTTP_header(void) +{ + if (mBufferEventsTarget) + mBufferEventsTarget->received_HTTP_header(); +} + +void BufferedCurlEasyRequest::received_header(std::string const& key, std::string const& value) +{ + if (mBufferEventsTarget) + mBufferEventsTarget->received_header(key, value); +} + +void BufferedCurlEasyRequest::completed_headers(U32 status, std::string const& reason, AITransferInfo* info) +{ + if (mBufferEventsTarget) + mBufferEventsTarget->completed_headers(status, reason, info); +} + +//static +size_t BufferedCurlEasyRequest::curlWriteCallback(char* data, size_t size, size_t nmemb, void* user_data) +{ + ThreadSafeBufferedCurlEasyRequest* lockobj = static_cast(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 self_w(*lockobj); + + S32 bytes = size * nmemb; // The amount to write. + // BufferedCurlEasyRequest::setBodyLimit is never called, so buffer_w->mBodyLimit is infinite. + //S32 bytes = llmin(size * nmemb, buffer_w->mBodyLimit); buffer_w->mBodyLimit -= bytes; + self_w->getOutput()->append(sChannels.in(), (U8 const*)data, bytes); + self_w->mResponseTransferedBytes += bytes; // Accumulate data received from the server. + if (self_w->httptimeout()->data_received(bytes)) // Update timeout administration. + { + // Transfer timed out. Return 0 which will abort with error CURLE_WRITE_ERROR. + return 0; + } + return bytes; +} + +//static +size_t BufferedCurlEasyRequest::curlReadCallback(char* data, size_t size, size_t nmemb, void* user_data) +{ + ThreadSafeBufferedCurlEasyRequest* lockobj = static_cast(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 self_w(*lockobj); + + S32 bytes = size * nmemb; // The maximum amount to read. + self_w->mLastRead = self_w->getInput()->readAfter(sChannels.out(), self_w->mLastRead, (U8*)data, bytes); + self_w->mRequestTransferedBytes += bytes; // Accumulate data sent to the server. + // Timeout administration. Note that it can happen that we get here + // before the socket callback has been called, because the silly libcurl + // writes headers without informing us. In that case it's OK to create + // the Timeout object on the fly, so pass lockobj. + if (self_w->httptimeout(lockobj)->data_sent(bytes)) + { + // Transfer timed out. Return CURL_READFUNC_ABORT which will abort with error CURLE_ABORTED_BY_CALLBACK. + return CURL_READFUNC_ABORT; + } + return bytes; // Return the amount actually read (might be lowered by readAfter()). +} + +//static +size_t BufferedCurlEasyRequest::curlHeaderCallback(char* data, size_t size, size_t nmemb, void* user_data) +{ + ThreadSafeBufferedCurlEasyRequest* lockobj = static_cast(user_data); + + // We need to lock the curl easy request object, because that lock is used + // to make sure that callbacks and destruction aren't done simultaneously. + AICurlEasyRequest_wat self_w(*lockobj); + + // This used to be headerCallback() in llurlrequest.cpp. + + char const* const header_line = static_cast(data); + size_t const header_len = size * nmemb; + if (self_w->httptimeout()->data_received(header_len)) // Update timeout administration. + { + // Transfer timed out. Return 0 which will abort with error CURLE_WRITE_ERROR. + return 0; + } + if (!header_len) + { + return header_len; + } + std::string header(header_line, header_len); + if (!LLStringUtil::_isASCII(header)) + { + return header_len; + } + + // Per HTTP spec the first header line must be the status line. + if (header.substr(0, 5) == "HTTP/") + { + std::string::iterator const begin = header.begin(); + std::string::iterator const end = header.end(); + std::string::iterator pos1 = std::find(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'); + U32 status; + std::string reason; + if (pos3 != end && std::isdigit(*pos1)) + { + status = atoi(&header_line[pos1 - begin]); + reason.assign(pos2, pos3); + } + else + { + status = HTTP_INTERNAL_ERROR; + reason = "Header parse error."; + llwarns << "Received broken header line from server: \"" << header << "\"" << llendl; + } + { + self_w->received_HTTP_header(); + self_w->setStatusAndReason(status, 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); + + self_w->received_header(key, value); + } + else + { + LLStringUtil::trim(header); + if (!header.empty()) + { + llwarns << "Unable to parse header: " << header << llendl; + } + } + + return header_len; +} + +#if defined(CWDEBUG) || defined(DEBUG_CURLIO) +int debug_callback(CURL*, curl_infotype infotype, char* buf, size_t size, void* user_ptr) +{ +#ifdef CWDEBUG + using namespace ::libcwd; + + BufferedCurlEasyRequest* request = (BufferedCurlEasyRequest*)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< "; + if (size >= 5 && (strncmp(buf, "GET ", 4) == 0 || strncmp(buf, "HEAD ", 5) == 0)) + request->mDebugIsHeadOrGetMethod = true; + 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, "", 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; +} +#endif // defined(CWDEBUG) || defined(DEBUG_CURLIO) + } // namespace AICurlPrivate //----------------------------------------------------------------------------- @@ -1697,14 +2457,31 @@ void AICurlEasyRequest::removeRequest(void) namespace AICurlInterface { -void startCurlThread(void) +void startCurlThread(U32 CurlConcurrentConnections, bool NoVerifySSLCert) { using namespace AICurlPrivate::curlthread; llassert(is_main_thread()); + curl_concurrent_connections = CurlConcurrentConnections; // Debug Setting. + gNoVerifySSLCert = NoVerifySSLCert; // Debug Setting. AICurlThread::sInstance = new AICurlThread; AICurlThread::sInstance->start(); } +bool handleCurlConcurrentConnections(LLSD const& newvalue) +{ + using namespace AICurlPrivate::curlthread; + + curl_concurrent_connections = newvalue.asInteger(); + llinfos << "CurlConcurrentConnections set to " << curl_concurrent_connections << llendl; + return true; +} + +bool handleNoVerifySSLCert(LLSD const& newvalue) +{ + gNoVerifySSLCert = newvalue.asBoolean(); + return true; +} + } // namespace AICurlInterface diff --git a/indra/llmessage/aicurlthread.h b/indra/llmessage/aicurlthread.h index a2bd36352..1ca677990 100644 --- a/indra/llmessage/aicurlthread.h +++ b/indra/llmessage/aicurlthread.h @@ -33,6 +33,7 @@ #include "aicurl.h" #include +#include #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 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 mQueuedRequests; // Waiting (throttled) requests. }; } // namespace curlthread diff --git a/indra/llmessage/aihttpheaders.cpp b/indra/llmessage/aihttpheaders.cpp new file mode 100644 index 000000000..0c6009cad --- /dev/null +++ b/indra/llmessage/aihttpheaders.cpp @@ -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 . + * + * 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 +#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; +} + diff --git a/indra/llmessage/aihttpheaders.h b/indra/llmessage/aihttpheaders.h new file mode 100644 index 000000000..95766c3e9 --- /dev/null +++ b/indra/llmessage/aihttpheaders.h @@ -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 . + * + * 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 +#include +#include +#include +#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 container_t; + typedef std::pair insert_t; + + struct Container : public LLThreadSafeRefCount { + container_t mKeyValuePairs; + }; + + LLPointer 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 container_t; + + public: + typedef container_t::const_iterator iterator_type; + typedef std::pair 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 mContainer; +}; + +#endif // AIHTTPHEADERS_H diff --git a/indra/llmessage/aihttptimeoutpolicy.cpp b/indra/llmessage/aihttptimeoutpolicy.cpp new file mode 100644 index 000000000..1add51a09 --- /dev/null +++ b/indra/llmessage/aihttptimeoutpolicy.cpp @@ -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 . + * + * 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 + +//! +// 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 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(&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 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 ; + // 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); + diff --git a/indra/llmessage/aihttptimeoutpolicy.h b/indra/llmessage/aihttptimeoutpolicy.h new file mode 100644 index 000000000..9d77ffd5c --- /dev/null +++ b/indra/llmessage/aihttptimeoutpolicy.h @@ -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 . + * + * 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 +#include + +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 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 diff --git a/indra/llmessage/debug_libcurl.cpp b/indra/llmessage/debug_libcurl.cpp index 40b75d200..512bc696c 100644 --- a/indra/llmessage/debug_libcurl.cpp +++ b/indra/llmessage/debug_libcurl.cpp @@ -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 : " ") << "\" }) = " << ret); + Dout(dc::curl, "curl_easy_getinfo(" << (AICURL*)curl << ", " << info << ", (char**){ \"" << (ret == CURLE_OK ? *param.char_ptr : " ") << "\" }) = " << 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; } diff --git a/indra/llmessage/llavatarnamecache.cpp b/indra/llmessage/llavatarnamecache.cpp index 07d4854c4..a3aaebb2a 100644 --- a/indra/llmessage/llavatarnamecache.cpp +++ b/indra/llmessage/llavatarnamecache.cpp @@ -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 */ -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 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& 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; } diff --git a/indra/llmessage/llavatarnamecache.h b/indra/llmessage/llavatarnamecache.h index 3846463f1..75f613e74 100644 --- a/indra/llmessage/llavatarnamecache.h +++ b/indra/llmessage/llavatarnamecache.h @@ -32,8 +32,8 @@ #include -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); } diff --git a/indra/llmessage/llbuffer.cpp b/indra/llmessage/llbuffer.cpp index 58208dbe1..b9ca7ceb6 100644 --- a/indra/llmessage/llbuffer.cpp +++ b/indra/llmessage/llbuffer.cpp @@ -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, diff --git a/indra/llmessage/llbuffer.h b/indra/llmessage/llbuffer.h index ccdb9fa7e..c7624bde3 100644 --- a/indra/llmessage/llbuffer.h +++ b/indra/llmessage/llbuffer.h @@ -306,7 +306,7 @@ public: typedef std::list 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. diff --git a/indra/llmessage/llbufferstream.h b/indra/llmessage/llbufferstream.h index 19749612f..723269307 100644 --- a/indra/llmessage/llbufferstream.h +++ b/indra/llmessage/llbufferstream.h @@ -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; }; diff --git a/indra/llmessage/llcurl.cpp b/indra/llmessage/llcurl.cpp deleted file mode 100644 index 585e4644b..000000000 --- a/indra/llmessage/llcurl.cpp +++ /dev/null @@ -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::sHandles; - -//static -CURL* LLCurl::Easy::allocEasyHandle() -{ - llassert(*AIAccess(LLCurl::getCurlThread())) ; - - CURL* ret = NULL; - - //*** Multi-threaded. - AIAccess 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_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 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; -} diff --git a/indra/llmessage/llfiltersd2xmlrpc.cpp b/indra/llmessage/llfiltersd2xmlrpc.cpp index e0ca056a5..9828a3e30 100644 --- a/indra/llmessage/llfiltersd2xmlrpc.cpp +++ b/indra/llmessage/llfiltersd2xmlrpc.cpp @@ -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) - << "'" < +#include + +#include "llhttpclient.h" #include "llbufferstream.h" #include "llsdserialize.h" #include "llvfile.h" -#include "llvfs.h" -#include "lluri.h" +#include "llurlrequest.h" +#include "llxmltree.h" +#include "aihttptimeoutpolicy.h" -#include "message.h" +class AIHTTPTimeoutPolicy; +extern AIHTTPTimeoutPolicy blockingLLSDPost_timeout; +extern AIHTTPTimeoutPolicy blockingLLSDGet_timeout; +extern AIHTTPTimeoutPolicy blockingRawGet_timeout; -const F32 HTTP_REQUEST_EXPIRY_SECS = 60.0f; //////////////////////////////////////////////////////////////////////////// -// Responder class moved to LLCurl +class XMLRPCInjector : public Injector { +public: + XMLRPCInjector(XMLRPC_REQUEST request) : mRequest(request), mRequestText(NULL) { } + ~XMLRPCInjector() { XMLRPC_RequestFree(mRequest, 1); XMLRPC_Free(const_cast(mRequestText)); } -namespace -{ - class LLHTTPClientURLAdaptor : public LLURLRequestComplete + /*virtual*/ char const* contentType(void) const { return "text/xml"; } + /*virtual*/ U32 get_body(LLChannelDescriptors const& channels, buffer_ptr_t& buffer) { + int requestTextSize; + mRequestText = XMLRPC_REQUEST_ToXML(mRequest, &requestTextSize); + if (!mRequestText) + throw AICurlNoBody("XMLRPC_REQUEST_ToXML returned NULL."); + LLBufferStream ostream(channels, buffer.get()); + ostream.write(mRequestText, requestTextSize); + ostream << std::flush; // Always flush a LLBufferStream when done writing to it. + return requestTextSize; + } + +private: + XMLRPC_REQUEST const mRequest; + char const* mRequestText; +}; + +class XmlTreeInjector : public Injector +{ public: - LLHTTPClientURLAdaptor(LLCurl::ResponderPtr responder) - : LLURLRequestComplete(), mResponder(responder), mStatus(499), - mReason("LLURLRequest complete w/no status") - { - } - - ~LLHTTPClientURLAdaptor() - { - } + XmlTreeInjector(LLXmlTree const* tree) : mTree(tree) { } + ~XmlTreeInjector() { delete const_cast(mTree); } - virtual void httpStatus(U32 status, const std::string& reason) + /*virtual*/ char const* contentType(void) const { return "application/xml"; } + /*virtual*/ U32 get_body(LLChannelDescriptors const& channels, buffer_ptr_t& buffer) { - LLURLRequestComplete::httpStatus(status,reason); - - mStatus = status; - mReason = reason; - } - - virtual void complete(const LLChannelDescriptors& channels, - const buffer_ptr_t& buffer) - { - if (mResponder.get()) - { - // Allow clients to parse headers before we attempt to parse - // the body and provide completed/result/error calls. - mResponder->completedHeader(mStatus, mReason, mHeaderOutput); - mResponder->completedRaw(mStatus, mReason, channels, buffer); - } - } - virtual void header(const std::string& header, const std::string& value) - { - mHeaderOutput[header] = value; + std::string data; + mTree->write(data); + LLBufferStream ostream(channels, buffer.get()); + ostream.write(data.data(), data.size()); + ostream << std::flush; // Always flush a LLBufferStream when done writing to it. + return data.size(); } private: - LLCurl::ResponderPtr mResponder; - U32 mStatus; - std::string mReason; - LLSD mHeaderOutput; - }; - - class Injector : public LLIOPipe + LLXmlTree const* mTree; +}; + +class LLSDInjector : public Injector +{ + public: + LLSDInjector(LLSD const& sd) : mSD(sd) { } + + /*virtual*/ char const* contentType(void) const { return "application/llsd+xml"; } + + /*virtual*/ U32 get_body(LLChannelDescriptors const& channels, buffer_ptr_t& buffer) { - public: - virtual const char* contentType() = 0; - }; + LLBufferStream ostream(channels, buffer.get()); + LLSDSerialize::toXML(mSD, ostream); + // Need to flush the LLBufferStream or count_out() returns more than the written data. + ostream << std::flush; + return ostream.count_out(); + } - class LLSDInjector : public Injector + LLSD const mSD; +}; + +class RawInjector : public Injector +{ + public: + RawInjector(char const* data, U32 size) : mData(data), mSize(size) { } + /*virtual*/ ~RawInjector() { delete [] mData; } + + /*virtual*/ char const* contentType(void) const { return "application/octet-stream"; } + + /*virtual*/ U32 get_body(LLChannelDescriptors const& channels, buffer_ptr_t& buffer) { - public: - LLSDInjector(const LLSD& sd) : mSD(sd) {} - virtual ~LLSDInjector() {} + LLBufferStream ostream(channels, buffer.get()); + ostream.write(mData, mSize); + ostream << std::flush; // Always flush a LLBufferStream when done writing to it. + return mSize; + } - const char* contentType() { return "application/llsd+xml"; } + char const* mData; + U32 mSize; +}; - virtual EStatus process_impl(const LLChannelDescriptors& channels, - buffer_ptr_t& buffer, bool& eos, LLSD& context, LLPumpIO* pump) +class FileInjector : public Injector +{ + public: + FileInjector(std::string const& filename) : mFilename(filename) { } + + char const* contentType(void) const { return "application/octet-stream"; } + + /*virtual*/ U32 get_body(LLChannelDescriptors const& channels, buffer_ptr_t& buffer) + { + llifstream fstream(mFilename, std::iostream::binary | std::iostream::out); + if (!fstream.is_open()) + throw AICurlNoBody(llformat("Failed to open \"%s\".", mFilename.c_str())); + LLBufferStream ostream(channels, buffer.get()); + char tmpbuf[4096]; +#ifdef SHOW_ASSERT + size_t total_len = 0; + fstream.seekg(0, std::ios::end); + size_t file_size = fstream.tellg(); + fstream.seekg(0, std::ios::beg); +#endif + while (fstream) { - LLBufferStream ostream(channels, buffer.get()); - LLSDSerialize::toXML(mSD, ostream); - eos = true; - return STATUS_DONE; - } - - const LLSD mSD; - }; - - class RawInjector : public Injector - { - public: - RawInjector(const U8* data, S32 size) : mData(data), mSize(size) {} - virtual ~RawInjector() {delete [] mData;} - - const char* contentType() { return "application/octet-stream"; } - - virtual EStatus process_impl(const LLChannelDescriptors& channels, - buffer_ptr_t& buffer, bool& eos, LLSD& context, LLPumpIO* pump) - { - LLBufferStream ostream(channels, buffer.get()); - ostream.write((const char *)mData, mSize); // hopefully chars are always U8s - eos = true; - return STATUS_DONE; - } - - const U8* mData; - S32 mSize; - }; - - class FileInjector : public Injector - { - public: - FileInjector(const std::string& filename) : mFilename(filename) {} - virtual ~FileInjector() {} - - const char* contentType() { return "application/octet-stream"; } - - virtual EStatus process_impl(const LLChannelDescriptors& channels, - buffer_ptr_t& buffer, bool& eos, LLSD& context, LLPumpIO* pump) - { - LLBufferStream ostream(channels, buffer.get()); - - llifstream fstream(mFilename, std::iostream::binary | std::iostream::out); - if(fstream.is_open()) + std::streamsize len = fstream.readsome(tmpbuf, sizeof(tmpbuf)); + if (len > 0) { - fstream.seekg(0, std::ios::end); - U32 fileSize = fstream.tellg(); - fstream.seekg(0, std::ios::beg); - std::vector fileBuffer(fileSize); - fstream.read(&fileBuffer[0], fileSize); - ostream.write(&fileBuffer[0], fileSize); - fstream.close(); - eos = true; - return STATUS_DONE; + ostream.write(tmpbuf, len); +#ifdef SHOW_ASSERT + total_len += len; +#endif } - - return STATUS_ERROR; } + fstream.close(); + ostream << std::flush; + llassert(total_len == file_size && total_len == ostream.count_out()); + return ostream.count_out(); + } - const std::string mFilename; - }; - - class VFileInjector : public Injector + std::string const mFilename; +}; + +class VFileInjector : public Injector +{ +public: + VFileInjector(LLUUID const& uuid, LLAssetType::EType asset_type) : mUUID(uuid), mAssetType(asset_type) { } + + /*virtual*/ char const* contentType(void) const { return "application/octet-stream"; } + + /*virtual*/ U32 get_body(LLChannelDescriptors const& channels, buffer_ptr_t& buffer) { - public: - VFileInjector(const LLUUID& uuid, LLAssetType::EType asset_type) : mUUID(uuid), mAssetType(asset_type) {} - virtual ~VFileInjector() {} + LLBufferStream ostream(channels, buffer.get()); + + LLVFile vfile(gVFS, mUUID, mAssetType, LLVFile::READ); + S32 fileSize = vfile.getSize(); + std::vector fileBuffer(fileSize); + vfile.read(&fileBuffer[0], fileSize); + ostream.write((char*)&fileBuffer[0], fileSize); + ostream << std::flush; + + return fileSize; + } - const char* contentType() { return "application/octet-stream"; } - - virtual EStatus process_impl(const LLChannelDescriptors& channels, - buffer_ptr_t& buffer, bool& eos, LLSD& context, LLPumpIO* pump) - { - LLBufferStream ostream(channels, buffer.get()); - - LLVFile vfile(gVFS, mUUID, mAssetType, LLVFile::READ); - S32 fileSize = vfile.getSize(); - std::vector fileBuffer(fileSize); - vfile.read(&fileBuffer[0], fileSize); - ostream.write((char*)&fileBuffer[0], fileSize); - eos = true; - return STATUS_DONE; - } - - const LLUUID mUUID; - LLAssetType::EType mAssetType; - }; - - LLPumpIO* theClientPump = NULL; -} + LLUUID const mUUID; + LLAssetType::EType mAssetType; +}; static void request( const std::string& url, LLURLRequest::ERequestAction method, Injector* body_injector, - LLCurl::ResponderPtr responder, - const F32 timeout = HTTP_REQUEST_EXPIRY_SECS, - const LLSD& headers = LLSD()) + LLHTTPClient::ResponderPtr responder, + AIHTTPHeaders& headers, + bool is_auth = false, + bool no_compression = false) { if (responder) { @@ -212,17 +207,10 @@ static void request( responder->setURL(url); } - if (!LLHTTPClient::hasPump()) - { - responder->fatalError("No pump"); - return; - } - LLPumpIO::chain_t chain; - LLURLRequest* req; try { - req = new LLURLRequest(method, url); + req = new LLURLRequest(method, url, body_injector, responder, headers, is_auth, no_compression); } catch(AICurlNoEasyHandle& error) { @@ -231,170 +219,294 @@ static void request( return ; } - req->checkRootCertificate(true); - - - lldebugs << LLURLRequest::actionAsVerb(method) << " " << url << " " - << headers << llendl; - - // Insert custom headers if the caller sent any - if (headers.isMap()) - { - if (headers.has("Cookie")) - { - req->allowCookies(); - } - - LLSD::map_const_iterator iter = headers.beginMap(); - LLSD::map_const_iterator end = headers.endMap(); - - for (; iter != end; ++iter) - { - std::ostringstream header; - //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 (read: llurlrequest.cpp) - static const std::string PRAGMA("Pragma"); - if ((iter->first == PRAGMA) && (iter->second.asString().empty())) - { - req->useProxy(false); - } - header << iter->first << ": " << iter->second.asString() ; - lldebugs << "header = " << header.str() << llendl; - req->addHeader(header.str().c_str()); - } - } - - // 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. - if( method != LLURLRequest::HTTP_PUT && method != LLURLRequest::HTTP_POST ) - { - static const std::string ACCEPT("Accept"); - if(!headers.has(ACCEPT)) - { - req->addHeader("Accept: application/llsd+xml"); - } - } - - req->setCallback(new LLHTTPClientURLAdaptor(responder)); - - if (method == LLURLRequest::HTTP_POST && gMessageSystem) - { - req->addHeader(llformat("X-SecondLife-UDP-Listen-Port: %d", - gMessageSystem->mPort).c_str()); - } - - if (method == LLURLRequest::HTTP_PUT || method == LLURLRequest::HTTP_POST) - { - static const std::string CONTENT_TYPE("Content-Type"); - if(!headers.has(CONTENT_TYPE)) - { - // If the Content-Type header was passed in, it has - // already been added as a header through req->addHeader - // in the loop above. We defer to the caller's wisdom, but - // if they did not specify a Content-Type, then ask the - // injector. - req->addHeader( - llformat( - "Content-Type: %s", - body_injector->contentType()).c_str()); - } - chain.push_back(LLIOPipe::ptr_t(body_injector)); - } - - chain.push_back(LLIOPipe::ptr_t(req)); - - theClientPump->addChain(chain, timeout); + req->run(); } - -void LLHTTPClient::getByteRange( - const std::string& url, - S32 offset, - S32 bytes, - ResponderPtr responder, - const LLSD& hdrs, - const F32 timeout) +void LLHTTPClient::getByteRange(std::string const& url, S32 offset, S32 bytes, ResponderPtr responder, AIHTTPHeaders& headers) { - LLSD headers = hdrs; if(offset > 0 || bytes > 0) { - std::string range = llformat("bytes=%d-%d", offset, offset+bytes-1); - headers["Range"] = range; + headers.addHeader("Range", llformat("bytes=%d-%d", offset, offset + bytes - 1)); } - request(url, LLURLRequest::HTTP_GET, NULL, responder, timeout, headers); + request(url, LLURLRequest::HTTP_GET, NULL, responder, headers); } -void LLHTTPClient::head( - const std::string& url, - ResponderPtr responder, - const LLSD& headers, - const F32 timeout) +void LLHTTPClient::head(std::string const& url, ResponderPtr responder, AIHTTPHeaders& headers) { - request(url, LLURLRequest::HTTP_HEAD, NULL, responder, timeout, headers); + request(url, LLURLRequest::HTTP_HEAD, NULL, responder, headers); } -void LLHTTPClient::get(const std::string& url, ResponderPtr responder, const LLSD& headers, const F32 timeout) +void LLHTTPClient::get(std::string const& url, ResponderPtr responder, AIHTTPHeaders& headers) { - request(url, LLURLRequest::HTTP_GET, NULL, responder, timeout, headers); -} -void LLHTTPClient::getHeaderOnly(const std::string& url, ResponderPtr responder, const LLSD& headers, const F32 timeout) -{ - request(url, LLURLRequest::HTTP_HEAD, NULL, responder, timeout, headers); -} -void LLHTTPClient::getHeaderOnly(const std::string& url, ResponderPtr responder, const F32 timeout) -{ - getHeaderOnly(url, responder, LLSD(), timeout); + request(url, LLURLRequest::HTTP_GET, NULL, responder, headers); } -void LLHTTPClient::get(const std::string& url, const LLSD& query, ResponderPtr responder, const LLSD& headers, const F32 timeout) +void LLHTTPClient::getHeaderOnly(std::string const& url, ResponderPtr responder, AIHTTPHeaders& headers) +{ + request(url, LLURLRequest::HTTP_HEAD, NULL, responder, headers); +} + +void LLHTTPClient::get(std::string const& url, LLSD const& query, ResponderPtr responder, AIHTTPHeaders& headers) { LLURI uri; uri = LLURI::buildHTTP(url, LLSD::emptyArray(), query); - get(uri.asString(), responder, headers, timeout); + get(uri.asString(), responder, headers); } -// A simple class for managing data returned from a curl http request. -class LLHTTPBuffer +//============================================================================= +// Responders base classes. +// + +//----------------------------------------------------------------------------- +// class LLHTTPClient::ResponderBase +// + +LLHTTPClient::ResponderBase::ResponderBase(void) : mReferenceCount(0), mCode(CURLE_FAILED_INIT), mFinished(false) { -public: - LLHTTPBuffer() { } + DoutEntering(dc::curl, "AICurlInterface::Responder() with this = " << (void*)this); +} - static size_t curl_write(char* ptr, size_t size, size_t nmemb, void* user_data) +LLHTTPClient::ResponderBase::~ResponderBase() +{ + DoutEntering(dc::curl, "AICurlInterface::ResponderBase::~ResponderBase() with this = " << (void*)this << "; mReferenceCount = " << mReferenceCount); + llassert(mReferenceCount == 0); +} + +void LLHTTPClient::ResponderBase::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; +} + +AIHTTPTimeoutPolicy const& LLHTTPClient::ResponderBase::getHTTPTimeoutPolicy(void) const +{ + return AIHTTPTimeoutPolicy::getDebugSettingsCurlTimeout(); +} + +void LLHTTPClient::ResponderBase::decode_llsd_body(U32 status, std::string const& reason, LLChannelDescriptors const& channels, buffer_ptr_t const& buffer, LLSD& content) +{ + // If the status indicates success (and we get here) then we expect the body to be LLSD. + bool const should_be_llsd = (200 <= status && status < 300); + if (should_be_llsd) + { + LLBufferStream istr(channels, buffer.get()); + if (LLSDSerialize::fromXML(content, istr) == LLSDParser::PARSE_FAILURE) { - LLHTTPBuffer* self = (LLHTTPBuffer*)user_data; - - size_t bytes = (size * nmemb); - self->mBuffer.append(ptr,bytes); - return nmemb; + // Unfortunately we can't show the body of the message... I think this is a pretty serious error + // though, so if this ever happens it has to be investigated by making a copy of the buffer + // before serializing it, as is done below. + llwarns << "Failed to deserialize LLSD. " << mURL << " [" << status << "]: " << reason << llendl; } - - LLSD asLLSD() + // LLSDSerialize::fromXML destructed buffer, we can't initialize content now. + return; + } + // Put the body in content as-is. + std::stringstream ss; + buffer->writeChannelTo(ss, channels.in()); + content = ss.str(); +#ifdef SHOW_ASSERT + if (!should_be_llsd) + { + // Make sure that the server indeed never returns LLSD as body when the http status is an error. + LLSD dummy; + bool server_sent_llsd_with_http_error = LLSDSerialize::fromXML(dummy, ss) > 0; + if (server_sent_llsd_with_http_error) { - LLSD content; - - if (mBuffer.empty()) return content; - - std::istringstream istr(mBuffer); - LLSDSerialize::fromXML(content, istr); - return content; + llwarns << "The server sent us a response with http status " << status << " and LLSD(!) body: \"" << ss.str() << "\"!" << llendl; } + llassert(!server_sent_llsd_with_http_error); + } +#endif +} - std::string asString() +void LLHTTPClient::ResponderBase::decode_raw_body(U32 status, std::string const& reason, LLChannelDescriptors const& channels, buffer_ptr_t const& buffer, std::string& content) +{ + LLMutexLock lock(buffer->getMutex()); + LLBufferArray::const_segment_iterator_t const end = buffer->endSegment(); + for (LLBufferArray::const_segment_iterator_t iter = buffer->beginSegment(); iter != end; ++iter) { - return mBuffer; + if (iter->isOnChannel(channels.in())) + { + content.append((char*)iter->data(), iter->size()); + } } +} +// Called with HTML body. +// virtual +void LLHTTPClient::ResponderWithCompleted::completedRaw(U32 status, std::string const& reason, LLChannelDescriptors const& channels, buffer_ptr_t const& buffer) +{ + LLSD content; + decode_llsd_body(status, reason, channels, buffer, content); + + // Allow derived class to override at this point. + completed(status, reason, content); +} + +// virtual +void LLHTTPClient::ResponderWithCompleted::completed(U32 status, std::string const& reason, LLSD const& content) +{ + // Either completedRaw() or this method must be overridden by the derived class. Hence, we should never get here. + llassert_always(false); +} + +// virtual +void LLHTTPClient::ResponderWithResult::finished(CURLcode code, U32 http_status, std::string const& reason, LLChannelDescriptors const& channels, buffer_ptr_t const& buffer) +{ + mCode = code; + + LLSD content; + decode_llsd_body(http_status, reason, channels, buffer, content); + + // HTTP status good? + if (200 <= http_status && http_status < 300) + { + // Allow derived class to override at this point. + result(content); + } + else + { + // Allow derived class to override at this point. + errorWithContent(http_status, reason, content); + } + + mFinished = true; +} + +// virtual +void LLHTTPClient::ResponderWithResult::errorWithContent(U32 status, std::string const& reason, LLSD const&) +{ + // Allow derived class to override at this point. + error(status, reason); +} + +// virtual +void LLHTTPClient::ResponderWithResult::error(U32 status, std::string const& reason) +{ + llinfos << mURL << " [" << status << "]: " << reason << llendl; +} + +// Friend functions. + +void intrusive_ptr_add_ref(LLHTTPClient::ResponderBase* responder) +{ + responder->mReferenceCount++; +} + +void intrusive_ptr_release(LLHTTPClient::ResponderBase* responder) +{ + if (--responder->mReferenceCount == 0) + { + delete responder; + } +} + +//----------------------------------------------------------------------------- +// Blocking Responders. +// + +class BlockingResponder : public LLHTTPClient::LegacyPolledResponder { private: - std::string mBuffer; + LLCondition mSignal; // Wait condition to wait till mFinished is true. + static LLSD LLSD_dummy; + static std::string Raw_dummy; + +public: + void wait(void); // Blocks until mFinished is true. + virtual LLSD const& getLLSD(void) const { llassert(false); return LLSD_dummy; } + virtual std::string const& getRaw(void) const { llassert(false); return Raw_dummy; } + +protected: + void wakeup(void); // Call this at the end of completedRaw. }; +LLSD BlockingResponder::LLSD_dummy; +std::string BlockingResponder::Raw_dummy; + +void BlockingResponder::wait(void) +{ + if (AIThreadID::in_main_thread()) + { + // We're the main thread, so we have to give AIStateMachine CPU cycles. + while (!mFinished) + { + AIStateMachine::mainloop(); + ms_sleep(10); + } + } + else // Hopefully not the curl thread :p + { + mSignal.lock(); + while (!mFinished) + mSignal.wait(); + mSignal.unlock(); + } +} + +void BlockingResponder::wakeup(void) +{ + // Normally mFinished is set immediately after returning from this function, + // but we do it here, because we need to set it before calling mSignal.signal(). + mSignal.lock(); + mFinished = true; + mSignal.unlock(); + mSignal.signal(); +} + +class BlockingLLSDResponder : public BlockingResponder { +private: + LLSD mResponse; + +protected: + /*virtual*/ LLSD const& getLLSD(void) const { llassert(mFinished && mCode == CURLE_OK && mStatus == HTTP_OK); return mResponse; } + /*virtual*/ void completedRaw(U32 status, std::string const& reason, LLChannelDescriptors const& channels, buffer_ptr_t const& buffer) + { + decode_llsd_body(status, reason, channels, buffer, mResponse); // This puts the body asString() in mResponse in case of http error. + wakeup(); + } +}; + +class BlockingRawResponder : public BlockingResponder { +private: + std::string mResponse; + +protected: + /*virtual*/ std::string const& getRaw(void) const { llassert(mFinished && mCode == CURLE_OK && mStatus == HTTP_OK); return mResponse; } + /*virtual*/ void completedRaw(U32 status, std::string const& reason, LLChannelDescriptors const& channels, buffer_ptr_t const& buffer) + { + decode_raw_body(mCode, reason, channels, buffer, mResponse); + wakeup(); + } +}; + +class BlockingLLSDPostResponder : public BlockingLLSDResponder { +public: + /*virtual*/ AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return blockingLLSDPost_timeout; } +}; + +class BlockingLLSDGetResponder : public BlockingLLSDResponder { +public: + /*virtual*/ AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return blockingLLSDGet_timeout; } +}; + +class BlockingRawGetResponder : public BlockingRawResponder { +public: + /*virtual*/ AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return blockingRawGet_timeout; } +}; + +// End (blocking) responders. +//============================================================================= + // These calls are blocking! This is usually bad, unless you're a dataserver. Then it's awesome. +enum EBlockingRequestAction { + HTTP_LLSD_POST, + HTTP_LLSD_GET, + HTTP_RAW_GET +}; + /** @brief does a blocking request on the url, returning the data or bad status. @@ -412,191 +524,156 @@ private: @returns an LLSD map: {status: integer, body: map} */ static LLSD blocking_request( - const std::string& url, - LLURLRequest::ERequestAction method, - const LLSD& body, - const LLSD& headers = LLSD(), - const F32 timeout = 5 -) + std::string const& url, + EBlockingRequestAction method, + LLSD const& body) // Only used for HTTP_LLSD_POST { lldebugs << "blockingRequest of " << url << llendl; - S32 http_status = 499; - LLSD response = LLSD::emptyMap(); - - try + AIHTTPHeaders headers; + boost::intrusive_ptr responder; + if (method == HTTP_LLSD_POST) { - AICurlEasyRequest easy_request(false); - AICurlEasyRequest_wat curlEasyRequest_w(*easy_request); + responder = new BlockingLLSDPostResponder; + LLHTTPClient::post(url, body, responder, headers); + } + else if (method == HTTP_LLSD_GET) + { + responder = new BlockingLLSDGetResponder; + LLHTTPClient::get(url, responder, headers); + } + else // method == HTTP_RAW_GET + { + responder = new BlockingRawGetResponder; + LLHTTPClient::get(url, responder, headers); + } - LLHTTPBuffer http_buffer; - - // * Set curl handle options - curlEasyRequest_w->setopt(CURLOPT_TIMEOUT, (long)timeout); // seconds, see warning at top of function. - curlEasyRequest_w->setWriteCallback(&LLHTTPBuffer::curl_write, &http_buffer); + responder->wait(); - // * Setup headers. - if (headers.isMap()) - { - LLSD::map_const_iterator iter = headers.beginMap(); - LLSD::map_const_iterator end = headers.endMap(); - for (; iter != end; ++iter) - { - std::ostringstream header; - header << iter->first << ": " << iter->second.asString() ; - lldebugs << "header = " << header.str() << llendl; - curlEasyRequest_w->addHeader(header.str().c_str()); - } - } - - // Needs to stay alive until after the call to perform(). - std::ostringstream ostr; + S32 http_status = HTTP_INTERNAL_ERROR; + LLSD response = LLSD::emptyMap(); + CURLcode result = responder->result_code(); - // * Setup specific method / "verb" for the URI (currently only GET and POST supported + poppy) - if (method == LLURLRequest::HTTP_GET) + http_status = responder->http_status(); + bool http_success = http_status >= 200 && http_status < 300; + if (result == CURLE_OK && http_success) + { + if (method == HTTP_RAW_GET) { - curlEasyRequest_w->setopt(CURLOPT_HTTPGET, 1); - } - else if (method == LLURLRequest::HTTP_POST) - { - //copied from PHP libs, correct? - curlEasyRequest_w->addHeader("Content-Type: application/llsd+xml"); - LLSDSerialize::toXML(body, ostr); - curlEasyRequest_w->setPost(ostr.str().c_str(), ostr.str().length()); - } - - // * Do the action using curl, handle results - curlEasyRequest_w->addHeader("Accept: application/llsd+xml"); - curlEasyRequest_w->finalizeRequest(url); - - S32 curl_success = curlEasyRequest_w->perform(); - curlEasyRequest_w->getinfo(CURLINFO_RESPONSE_CODE, &http_status); - // if we get a non-404 and it's not a 200 OR maybe it is but you have error bits, - if ( http_status != 404 && (http_status != 200 || curl_success != 0) ) - { - // We expect 404s, don't spam for them. - llwarns << "CURL REQ URL: " << url << llendl; - llwarns << "CURL REQ METHOD TYPE: " << method << llendl; - llwarns << "CURL REQ HEADERS: " << headers.asString() << llendl; - llwarns << "CURL REQ BODY: " << ostr.str() << llendl; - llwarns << "CURL HTTP_STATUS: " << http_status << llendl; - llwarns << "CURL ERROR: " << curlEasyRequest_w->getErrorString() << llendl; - llwarns << "CURL ERROR BODY: " << http_buffer.asString() << llendl; - response["body"] = http_buffer.asString(); + response["body"] = responder->getRaw(); } else { - response["body"] = http_buffer.asLLSD(); - lldebugs << "CURL response: " << http_buffer.asString() << llendl; + response["body"] = responder->getLLSD(); } } - catch(AICurlNoEasyHandle const& error) + else if (result == CURLE_OK) { - response["body"] = error.what(); + // We expect 404s, don't spam for them. + if (http_status != 404) + { + llwarns << "CURL REQ URL: " << url << llendl; + llwarns << "CURL REQ METHOD TYPE: " << method << llendl; + llwarns << "CURL REQ HEADERS: " << headers << llendl; + if (method == HTTP_LLSD_POST) + { + llwarns << "CURL REQ BODY: " << body.asString() << llendl; + } + llwarns << "CURL HTTP_STATUS: " << http_status << llendl; + if (method == HTTP_RAW_GET) + { + llwarns << "CURL ERROR BODY: " << responder->getRaw() << llendl; + } + else + { + llwarns << "CURL ERROR BODY: " << responder->getLLSD().asString() << llendl; + } + } + if (method == HTTP_RAW_GET) + { + response["body"] = responder->getRaw(); + } + else + { + response["body"] = responder->getLLSD().asString(); + } + } + else + { + response["body"] = responder->reason(); } response["status"] = http_status; return response; } -LLSD LLHTTPClient::blockingGet(const std::string& url) -{ - return blocking_request(url, LLURLRequest::HTTP_GET, LLSD()); -} - LLSD LLHTTPClient::blockingPost(const std::string& url, const LLSD& body) { - return blocking_request(url, LLURLRequest::HTTP_POST, body); + return blocking_request(url, HTTP_LLSD_POST, body); } -void LLHTTPClient::put( - const std::string& url, - const LLSD& body, - ResponderPtr responder, - const LLSD& headers, - const F32 timeout) +LLSD LLHTTPClient::blockingGet(const std::string& url) { - request(url, LLURLRequest::HTTP_PUT, new LLSDInjector(body), responder, timeout, headers); + return blocking_request(url, HTTP_LLSD_GET, LLSD()); } -void LLHTTPClient::post( - const std::string& url, - const LLSD& body, - ResponderPtr responder, - const LLSD& headers, - const F32 timeout) +U32 LLHTTPClient::blockingGetRaw(const std::string& url, std::string& body) { - request(url, LLURLRequest::HTTP_POST, new LLSDInjector(body), responder, timeout, headers); + LLSD result = blocking_request(url, HTTP_RAW_GET, LLSD()); + body = result["body"].asString(); + return result["status"].asInteger(); } -void LLHTTPClient::postRaw( - const std::string& url, - const U8* data, - S32 size, - ResponderPtr responder, - const LLSD& headers, - const F32 timeout) +void LLHTTPClient::put(std::string const& url, LLSD const& body, ResponderPtr responder, AIHTTPHeaders& headers) { - request(url, LLURLRequest::HTTP_POST, new RawInjector(data, size), responder, timeout, headers); + request(url, LLURLRequest::HTTP_PUT, new LLSDInjector(body), responder, headers); } -void LLHTTPClient::postFile( - const std::string& url, - const std::string& filename, - ResponderPtr responder, - const LLSD& headers, - const F32 timeout) +void LLHTTPClient::post(std::string const& url, LLSD const& body, ResponderPtr responder, AIHTTPHeaders& headers) { - request(url, LLURLRequest::HTTP_POST, new FileInjector(filename), responder, timeout, headers); + request(url, LLURLRequest::HTTP_POST, new LLSDInjector(body), responder, headers); } -void LLHTTPClient::postFile( - const std::string& url, - const LLUUID& uuid, - LLAssetType::EType asset_type, - ResponderPtr responder, - const LLSD& headers, - const F32 timeout) +void LLHTTPClient::postXMLRPC(std::string const& url, XMLRPC_REQUEST xmlrpc_request, ResponderPtr responder, AIHTTPHeaders& headers) { - request(url, LLURLRequest::HTTP_POST, new VFileInjector(uuid, asset_type), responder, timeout, headers); + request(url, LLURLRequest::HTTP_POST, new XMLRPCInjector(xmlrpc_request), responder, headers, true, false); // Does use compression. +} + +void LLHTTPClient::postXMLRPC(std::string const& url, char const* method, XMLRPC_VALUE value, ResponderPtr responder, AIHTTPHeaders& headers) +{ + XMLRPC_REQUEST xmlrpc_request = XMLRPC_RequestNew(); + XMLRPC_RequestSetMethodName(xmlrpc_request, method); + XMLRPC_RequestSetRequestType(xmlrpc_request, xmlrpc_request_call); + XMLRPC_RequestSetData(xmlrpc_request, value); + // XMLRPCInjector takes ownership of xmlrpc_request and will free it when done. + // LLURLRequest takes ownership of the XMLRPCInjector object and will free it when done. + request(url, LLURLRequest::HTTP_POST, new XMLRPCInjector(xmlrpc_request), responder, headers, true, true); // Does not use compression. +} + +void LLHTTPClient::postRaw(std::string const& url, char const* data, S32 size, ResponderPtr responder, AIHTTPHeaders& headers) +{ + request(url, LLURLRequest::HTTP_POST, new RawInjector(data, size), responder, headers); +} + +void LLHTTPClient::postFile(std::string const& url, std::string const& filename, ResponderPtr responder, AIHTTPHeaders& headers) +{ + request(url, LLURLRequest::HTTP_POST, new FileInjector(filename), responder, headers); +} + +void LLHTTPClient::postFile(std::string const& url, LLUUID const& uuid, LLAssetType::EType asset_type, ResponderPtr responder, AIHTTPHeaders& headers) +{ + request(url, LLURLRequest::HTTP_POST, new VFileInjector(uuid, asset_type), responder, headers); } // static -void LLHTTPClient::del( - const std::string& url, - ResponderPtr responder, - const LLSD& headers, - const F32 timeout) +void LLHTTPClient::del(std::string const& url, ResponderPtr responder, AIHTTPHeaders& headers) { - request(url, LLURLRequest::HTTP_DELETE, NULL, responder, timeout, headers); + request(url, LLURLRequest::HTTP_DELETE, NULL, responder, headers); } // static -void LLHTTPClient::move( - const std::string& url, - const std::string& destination, - ResponderPtr responder, - const LLSD& hdrs, - const F32 timeout) +void LLHTTPClient::move(std::string const& url, std::string const& destination, ResponderPtr responder, AIHTTPHeaders& headers) { - LLSD headers = hdrs; - headers["Destination"] = destination; - request(url, LLURLRequest::HTTP_MOVE, NULL, responder, timeout, headers); -} - - -void LLHTTPClient::setPump(LLPumpIO& pump) -{ - theClientPump = &pump; -} - -bool LLHTTPClient::hasPump() -{ - return theClientPump != NULL; -} - -//static -LLPumpIO& LLHTTPClient::getPump() -{ - return *theClientPump; + headers.addHeader("Destination", destination); + request(url, LLURLRequest::HTTP_MOVE, NULL, responder, headers); } diff --git a/indra/llmessage/llhttpclient.h b/indra/llmessage/llhttpclient.h index ae22fd30a..1c8fe9c65 100644 --- a/indra/llmessage/llhttpclient.h +++ b/indra/llmessage/llhttpclient.h @@ -32,85 +32,350 @@ */ #include - +#include // CURLcode #include -#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 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 " 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 is made. + friend void intrusive_ptr_release(ResponderBase* p); // Called by boost::intrusive_ptr when a boost::intrusive_ptr 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 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 diff --git a/indra/llmessage/llhttpclientadapter.cpp b/indra/llmessage/llhttpclientadapter.cpp deleted file mode 100644 index f5d7a9abb..000000000 --- a/indra/llmessage/llhttpclientadapter.cpp +++ /dev/null @@ -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); -} - diff --git a/indra/llmessage/llhttpclientadapter.h b/indra/llmessage/llhttpclientadapter.h deleted file mode 100644 index aae6426a5..000000000 --- a/indra/llmessage/llhttpclientadapter.h +++ /dev/null @@ -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 -{ -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 - diff --git a/indra/llmessage/llhttpclientinterface.h b/indra/llmessage/llhttpclientinterface.h deleted file mode 100644 index 12a3857a6..000000000 --- a/indra/llmessage/llhttpclientinterface.h +++ /dev/null @@ -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 - -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 - diff --git a/indra/llmessage/lliohttpserver.cpp b/indra/llmessage/lliohttpserver.cpp index 9c1a6677e..6e2ec7f9b 100644 --- a/indra/llmessage/lliohttpserver.cpp +++ b/indra/llmessage/lliohttpserver.cpp @@ -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\n" << "Bad Request\n\nBad Request.\n" - << "\n\n"; + << "\n\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\n" << "Not Found\n\nNode '" << mAbsPathAndQuery - << "' not found.\n\n\n"; + << "' not found.\n\n\n" << std::flush; } } diff --git a/indra/llmessage/llpartdata.cpp b/indra/llmessage/llpartdata.cpp index 26cafa025..de369dc3c 100644 --- a/indra/llmessage/llpartdata.cpp +++ b/indra/llmessage/llpartdata.cpp @@ -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; diff --git a/indra/llmessage/llsdmessage.cpp b/indra/llmessage/llsdmessage.cpp index 67da908b7..dc091a543 100644 --- a/indra/llmessage/llsdmessage.cpp +++ b/indra/llmessage/llsdmessage.cpp @@ -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(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 ----------------*/ diff --git a/indra/llmessage/llsdmessage.h b/indra/llmessage/llsdmessage.h index 0d34847ff..72b7dc675 100644 --- a/indra/llmessage/llsdmessage.h +++ b/indra/llmessage/llsdmessage.h @@ -37,6 +37,8 @@ #include 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 * for transitional purposes only. 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: diff --git a/indra/llmessage/llurlrequest.cpp b/indra/llmessage/llurlrequest.cpp index 4948f10d6..e6b4ee4c3 100644 --- a/indra/llmessage/llurlrequest.cpp +++ b/indra/llmessage/llurlrequest.cpp @@ -36,8 +36,7 @@ #include #include #include - -#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() ? "" : 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; -} diff --git a/indra/llmessage/llurlrequest.h b/indra/llmessage/llurlrequest.h index fdf7d873e..8f019310f 100644 --- a/indra/llmessage/llurlrequest.h +++ b/indra/llmessage/llurlrequest.h @@ -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 -#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 diff --git a/indra/llmessage/message.cpp b/indra/llmessage/message.cpp index 7f6d9e378..f1c9a1ba3 100644 --- a/indra/llmessage/message.cpp +++ b/indra/llmessage/message.cpp @@ -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); diff --git a/indra/llplugin/CMakeLists.txt b/indra/llplugin/CMakeLists.txt index 231b10bfa..446028b0f 100644 --- a/indra/llplugin/CMakeLists.txt +++ b/indra/llplugin/CMakeLists.txt @@ -3,23 +3,16 @@ project(llplugin) include(00-Common) -include(CURL) include(LLCommon) -include(LLImage) include(LLMath) include(LLMessage) include(LLRender) -include(LLXML) -include(LLWindow) include_directories( ${LLCOMMON_INCLUDE_DIRS} - ${LLIMAGE_INCLUDE_DIRS} ${LLMATH_INCLUDE_DIRS} ${LLMESSAGE_INCLUDE_DIRS} ${LLRENDER_INCLUDE_DIRS} - ${LLXML_INCLUDE_DIRS} - ${LLWINDOW_INCLUDE_DIRS} ${LLQTWEBKIT_INCLUDE_DIR} ) diff --git a/indra/llrender/llgl.cpp b/indra/llrender/llgl.cpp index 762571adc..eba807ece 100644 --- a/indra/llrender/llgl.cpp +++ b/indra/llrender/llgl.cpp @@ -97,9 +97,9 @@ void APIENTRY gl_debug_callback(GLenum source, { llwarns << "----- GL WARNING -------" << llendl; } - llwarns << "Type: " << std::hex << type << llendl; - llwarns << "ID: " << std::hex << id << llendl; - llwarns << "Severity: " << std::hex << severity << llendl; + llwarns << "Type: " << std::hex << type << std::dec << llendl; + llwarns << "ID: " << std::hex << id << std::dec<< llendl; + llwarns << "Severity: " << std::hex << severity << std::dec << llendl; llwarns << "Message: " << message << llendl; llwarns << "-----------------------" << llendl; if (severity == GL_DEBUG_SEVERITY_HIGH_ARB) diff --git a/indra/llrender/llimagegl.cpp b/indra/llrender/llimagegl.cpp index dfac4c2af..210ea7167 100644 --- a/indra/llrender/llimagegl.cpp +++ b/indra/llrender/llimagegl.cpp @@ -1196,7 +1196,7 @@ void LLImageGL::setManualImage(U32 target, S32 miplevel, S32 intformat, S32 widt intformat = GL_COMPRESSED_ALPHA; break; default: - llwarns << "Could not compress format: " << std::hex << intformat << llendl; + llwarns << "Could not compress format: " << std::hex << intformat << std::dec << llendl; break; } } diff --git a/indra/llrender/llrendertarget.cpp b/indra/llrender/llrendertarget.cpp index 6454f92d9..d770e67c9 100644 --- a/indra/llrender/llrendertarget.cpp +++ b/indra/llrender/llrendertarget.cpp @@ -49,7 +49,7 @@ void check_framebuffer_status() case GL_FRAMEBUFFER_COMPLETE: break; default: - llwarns << "check_framebuffer_status failed -- " << std::hex << status << llendl; + llwarns << "check_framebuffer_status failed -- " << std::hex << status << std::dec << llendl; ll_fail("check_framebuffer_status failed"); break; } diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt index 22651bd5d..5049da27a 100644 --- a/indra/llui/CMakeLists.txt +++ b/indra/llui/CMakeLists.txt @@ -5,7 +5,6 @@ project(llui) include(00-Common) include(LLCommon) include(LLImage) -include(LLInventory) include(LLMath) include(LLMessage) include(LLRender) @@ -16,7 +15,6 @@ include(LLXML) include_directories( ${LLCOMMON_INCLUDE_DIRS} ${LLIMAGE_INCLUDE_DIRS} - ${LLINVENTORY_INCLUDE_DIRS} ${LLMATH_INCLUDE_DIRS} ${LLMESSAGE_INCLUDE_DIRS} ${LLRENDER_INCLUDE_DIRS} diff --git a/indra/llwindow/CMakeLists.txt b/indra/llwindow/CMakeLists.txt index e5d61d180..12c778cd8 100644 --- a/indra/llwindow/CMakeLists.txt +++ b/indra/llwindow/CMakeLists.txt @@ -18,7 +18,6 @@ include(LLMath) include(LLRender) include(LLVFS) include(LLWindow) -include(LLXML) include(UI) include_directories( @@ -28,7 +27,6 @@ include_directories( ${LLRENDER_INCLUDE_DIRS} ${LLVFS_INCLUDE_DIRS} ${LLWINDOW_INCLUDE_DIRS} - ${LLXML_INCLUDE_DIRS} ) set(llwindow_SOURCE_FILES diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 97b9429b4..6ef3067f3 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -27,7 +27,7 @@ include(LLInventory) include(LLMath) include(LLMessage) include(LLPlugin) -include(AIStateMachine) +include(StateMachine) include(LLPrimitive) include(LLRender) include(LLUI) @@ -50,6 +50,7 @@ endif (WINDOWS) include_directories( ${CMAKE_SOURCE_DIR}/newview + ${STATEMACHINE_INCLUDE_DIRS} ${DBUSGLIB_INCLUDE_DIRS} ${HUNSPELL_INCLUDE_DIR} ${ELFIO_INCLUDE_DIR} @@ -90,7 +91,6 @@ set(viewer_SOURCE_FILES hippogridmanager.cpp hippolimits.cpp hippopanelgrids.cpp - hipporestrequest.cpp importtracker.cpp jcfloaterareasearch.cpp lggdicdownload.cpp @@ -132,7 +132,6 @@ set(viewer_SOURCE_FILES llconfirmationmanager.cpp llconsole.cpp llcontainerview.cpp - llcurlrequest.cpp llcurrencyuimanager.cpp llcylinder.cpp lldaycyclemanager.cpp @@ -545,7 +544,7 @@ set(viewer_SOURCE_FILES llworldmap.cpp llworldmipmap.cpp llworldmapview.cpp - llxmlrpctransaction.cpp + llxmlrpcresponder.cpp m7wlinterface.cpp NACLantispam.cpp noise.cpp @@ -592,7 +591,6 @@ set(viewer_HEADER_FILES hippogridmanager.h hippolimits.h hippopanelgrids.h - hipporestrequest.h importtracker.h jcfloaterareasearch.h lggdicdownload.h @@ -635,7 +633,6 @@ set(viewer_HEADER_FILES llconfirmationmanager.h llconsole.h llcontainerview.h - llcurlrequest.h llcurrencyuimanager.h llcylinder.h lldaycyclemanager.h @@ -1054,7 +1051,7 @@ set(viewer_HEADER_FILES llworldmap.h llworldmipmap.h llworldmapview.h - llxmlrpctransaction.h + llxmlrpcresponder.h m7wlinterface.h macmain.h NACLantispam.h @@ -1551,7 +1548,7 @@ target_link_libraries(${VIEWER_BINARY_NAME} ${LLINVENTORY_LIBRARIES} ${LLMESSAGE_LIBRARIES} ${LLPLUGIN_LIBRARIES} - ${AISTATEMACHINE_LIBRARIES} + ${STATEMACHINE_LIBRARIES} ${LLPRIMITIVE_LIBRARIES} ${LLRENDER_LIBRARIES} ${FREETYPE_LIBRARIES} diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index c0ec36c53..35313721e 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -4205,6 +4205,17 @@ Value 0 + CurlConcurrentConnections + + Comment + Maximum number of simultaneous curl connections + Persist + 1 + Type + U32 + Value + 16 + CurlMaximumNumberOfHandles Comment @@ -4216,16 +4227,82 @@ Value 256 - CurlRequestTimeOut + CurlTimeoutDNSLookup Comment - Max idle time of a curl request before killed (requires restart) + Extra time in seconds added to CurlTimeoutConnect for the initial connect to a host Persist 1 Type - F32 + U32 Value - 120.0 + 60 + + CurlTimeoutConnect + + Comment + Maximum time allowed until a connection is established (after adding the easy handle) until the server is considered unreachable and the connection is terminated + Persist + 1 + Type + U32 + Value + 10 + + CurlTimeoutReplyDelay + + Comment + Maximum time the viewer will wait between sending data to the server and receiving (a partial) reply + Persist + 1 + Type + U32 + Value + 60 + + CurlTimeoutLowSpeedLimit + + Comment + If a transfer speed drops below this value (in bytes/s) during CurlTimeoutLowSpeedTime seconds, the transfer is considered too slow and is terminated + Persist + 1 + Type + U32 + Value + 56000 + + CurlTimeoutLowSpeedTime + + Comment + If a transfer speed drops below CurlTimeoutLowSpeedLimit (in bytes/s) during this amount of seconds, the transfer is considered too slow and is terminated + Persist + 1 + Type + U32 + Value + 30 + + CurlTimeoutMaxTransaction + + Comment + Maximum total time of a curl transaction, from when the easy handle is added till the transaction has completed. This INCLUDES DNS lookups (CurlTimeoutDNSLookup), connect time (CurlTimeoutConnect) and waiting for the first server reply (CurlTimeoutReply) as well as the actually time needed for data transfer + Persist + 1 + Type + U32 + Value + 300 + + CurlTimeoutMaxTotalDelay + + Comment + Maximum total time of a curl request, from when it is requested till the transaction has completed. This includes queuing due to connection throttling on top of the events covered by CurlTimeoutMaxTransaction + Persist + 1 + Type + U32 + Value + 600 Cursor3D @@ -8982,7 +9059,7 @@ NoVerifySSLCert Comment - Do not verify SSL peers. + Do not verify SSL peers Persist 1 Type diff --git a/indra/newview/floatervoicelicense.cpp b/indra/newview/floatervoicelicense.cpp index 753c4bee6..322c4778e 100644 --- a/indra/newview/floatervoicelicense.cpp +++ b/indra/newview/floatervoicelicense.cpp @@ -52,6 +52,8 @@ #include "llvfile.h" #include "message.h" +class AIHTTPTimeoutPolicy; +extern AIHTTPTimeoutPolicy iamHereVoice_timeout; FloaterVoiceLicense::FloaterVoiceLicense(const LLSD& key) : LLModalDialog( std::string(" "), 100, 100 ), @@ -63,7 +65,7 @@ FloaterVoiceLicense::FloaterVoiceLicense(const LLSD& key) // helper class that trys to download a URL from a web site and calls a method // on parent class indicating if the web server is working or not -class LLIamHereVoice : public LLHTTPClient::Responder +class LLIamHereVoice : public LLHTTPClient::ResponderWithResult { private: LLIamHereVoice( FloaterVoiceLicense* parent ) : @@ -73,7 +75,6 @@ class LLIamHereVoice : public LLHTTPClient::Responder FloaterVoiceLicense* mParent; public: - static boost::intrusive_ptr< LLIamHereVoice > build( FloaterVoiceLicense* parent ) { return boost::intrusive_ptr< LLIamHereVoice >( new LLIamHereVoice( parent ) ); @@ -101,6 +102,8 @@ class LLIamHereVoice : public LLHTTPClient::Responder mParent->setSiteIsAlive( alive ); } }; + + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return iamHereVoice_timeout; } }; // this is global and not a class member to keep crud out of the header file diff --git a/indra/newview/hippogridmanager.cpp b/indra/newview/hippogridmanager.cpp index 73dd8d124..c0ceef71d 100644 --- a/indra/newview/hippogridmanager.cpp +++ b/indra/newview/hippogridmanager.cpp @@ -17,9 +17,6 @@ #include "llviewercontrol.h" #include "llweb.h" -#include "hipporestrequest.h" - - // ******************************************************************** // Global Variables @@ -492,7 +489,7 @@ bool HippoGridInfo::retrieveGridInfo() uri += '/'; } std::string reply; - int result = HippoRestRequest::getBlocking(uri + "get_grid_info", &reply); + int result = LLHTTPClient::blockingGetRaw(uri + "get_grid_info", reply); if (result != 200) return false; llinfos << "Received: " << reply << llendl; @@ -860,7 +857,6 @@ void HippoGridManager::loadFromFile() setCurrentGrid(last_grid); } - void HippoGridManager::parseUrl(const std::string url, bool mergeIfNewer) { llinfos << "Loading grid info from '" << url << "'." << llendl; diff --git a/indra/newview/hipporestrequest.cpp b/indra/newview/hipporestrequest.cpp deleted file mode 100644 index 12e112bcf..000000000 --- a/indra/newview/hipporestrequest.cpp +++ /dev/null @@ -1,365 +0,0 @@ - -#include "llviewerprecompiledheaders.h" - -#include "hipporestrequest.h" - -#ifndef CURL_STATICLIB -#define CURL_STATICLIB 1 -#endif - -#include -#include "llbufferstream.h" -#include "llerror.h" -#include "llhttpclient.h" -#include "llurlrequest.h" -#include "llxmltree.h" - -#include -#ifdef DEBUG_CURLIO -#include "debug_libcurl.h" -#endif - -// ******************************************************************** - - -class HippoRestComplete : public LLURLRequestComplete -{ - public: - HippoRestComplete(HippoRestHandler *handler) : - mHandler(handler), - mStatus(499), - mReason("Request completed w/o status") - { - } - - ~HippoRestComplete() - { - delete mHandler; - } - - // Called once for each header received, prior to httpStatus - void header(const std::string& header, const std::string& value) - { - mHandler->addHeader(header, value); - } - - // Always called on request completion, prior to complete - void httpStatus(U32 status, const std::string& reason) - { - LLURLRequestComplete::httpStatus(status, reason); - mStatus = status; - mReason = reason; - } - - void complete(const LLChannelDescriptors &channels, const buffer_ptr_t &buffer) - { - mHandler->handle(mStatus, mReason, channels, buffer); - } - - private: - HippoRestHandler *mHandler; - int mStatus; - std::string mReason; -}; - - -// ******************************************************************** - - -static std::string gEmptyString; - -void HippoRestHandler::addHeader(const std::string &header, const std::string &content) -{ - mHeaders[header] = content; -} - -bool HippoRestHandler::hasHeader(const std::string &header) const -{ - return (mHeaders.find(header) != mHeaders.end()); -} - -const std::string &HippoRestHandler::getHeader(const std::string &header) const -{ - std::map::const_iterator it; - it = mHeaders.find(header); - if (it != mHeaders.end()) { - return it->second; - } else { - return gEmptyString; - } -} - - -// ******************************************************************** - - -void HippoRestHandlerRaw::handle(int status, const std::string &reason, - const LLChannelDescriptors &channels, - const boost::shared_ptr &body) -{ - if (status == 200) { - std::string data; - LLBufferArray *buffer = body.get(); - LLBufferArray::segment_iterator_t it, end = buffer->endSegment(); - for (it=buffer->beginSegment(); it!=end; ++it) - if (it->isOnChannel(channels.in())) - data.append((char*)it->data(), it->size()); - result(data); - } else { - llwarns << "Rest request error " << status << ": " << reason << llendl; - } -} - -void HippoRestHandlerXml::handle(int status, const std::string &reason, - const LLChannelDescriptors &channels, - const boost::shared_ptr &body) -{ - if (status == 200) { - LLXmlTree *tree = new LLXmlTree(); - bool success = tree->parseBufferStart(); - LLBufferArray *buffer = body.get(); - LLBufferArray::segment_iterator_t it, end = buffer->endSegment(); - for (it=buffer->beginSegment(); success && (it!=end); ++it) - if (it->isOnChannel(channels.in())) - success = success && tree->parseBuffer((char*)it->data(), it->size()); - success = success && tree->parseBufferFinalize(); - if (success) result(tree); - delete tree; - } else { - llwarns << "Rest request error " << status << ": " << reason << llendl; - } -} - - -// ******************************************************************** - - -class BodyData : public LLIOPipe -{ - public: - virtual ~BodyData() { } - virtual const char *getContentMimeType() const = 0; -}; - -class BodyDataRaw : public BodyData -{ - public: - explicit BodyDataRaw(const std::string &data) : - mData(data) - { - } - virtual ~BodyDataRaw() { } - - const char *getContentMimeType() const { return "application/octet-stream"; } - - EStatus process_impl(const LLChannelDescriptors &channels, - buffer_ptr_t &buffer, bool &eos, - LLSD &context, LLPumpIO *pump) - { - LLBufferStream ostream(channels, buffer.get()); - ostream.write(mData.data(), mData.size()); - eos = true; - return STATUS_DONE; - } - - private: - std::string mData; -}; - -class BodyDataXml : public BodyData -{ - public: - explicit BodyDataXml(const LLXmlTree *tree) : - mTree(tree) - { - } - - virtual ~BodyDataXml() - { - if (mTree) delete mTree; - } - - const char *getContentMimeType() const { return "application/xml"; } - - EStatus process_impl(const LLChannelDescriptors &channels, - buffer_ptr_t &buffer, bool &eos, - LLSD &context, LLPumpIO *pump) - { - std::string data; - mTree->write(data); - LLBufferStream ostream(channels, buffer.get()); - ostream.write(data.data(), data.size()); - eos = true; - return STATUS_DONE; - } - - private: - const LLXmlTree *mTree; -}; - - -// ******************************************************************** - - -static void request(const std::string &url, - LLURLRequest::ERequestAction method, - BodyData *body, - HippoRestHandler *handler, float timeout); - - -// static -void HippoRestRequest::get(const std::string &url, - HippoRestHandler *handler, float timeout) -{ - request(url, LLURLRequest::HTTP_GET, 0, handler, timeout); -} - -// static -void HippoRestRequest::put(const std::string &url, const std::string &body, - HippoRestHandler *handler, float timeout) -{ - request(url, LLURLRequest::HTTP_PUT, new BodyDataRaw(body), handler, timeout); -} - -// static -void HippoRestRequest::put(const std::string &url, const LLXmlTree *body, - HippoRestHandler *handler, float timeout) -{ - request(url, LLURLRequest::HTTP_PUT, new BodyDataXml(body), handler, timeout); -} - -// static -void HippoRestRequest::post(const std::string &url, const std::string &body, - HippoRestHandler *handler, float timeout) -{ - request(url, LLURLRequest::HTTP_POST, new BodyDataRaw(body), handler, timeout); -} - -// static -void HippoRestRequest::post(const std::string &url, const LLXmlTree *body, - HippoRestHandler *handler, float timeout) -{ - request(url, LLURLRequest::HTTP_POST, new BodyDataXml(body), handler, timeout); -} - - -// ******************************************************************** - - -static void request(const std::string &url, - LLURLRequest::ERequestAction method, - BodyData *body, - HippoRestHandler *handler, float timeout) -{ - if (!LLHTTPClient::hasPump()) - { - // !!! responder->completed(U32_MAX, "No pump", LLSD()); - return; - } - LLPumpIO::chain_t chain; - - LLURLRequest *req; - try - { - req = new LLURLRequest(method, url); - } - catch(AICurlNoEasyHandle const& error) - { - llwarns << "Failed to create LLURLRequest: " << error.what() << llendl; - return; - } - req->checkRootCertificate(true); - - /* - // Insert custom headers if the caller sent any - if (headers.isMap()) - { - LLSD::map_const_iterator iter = headers.beginMap(); - LLSD::map_const_iterator end = headers.endMap(); - - for (; iter != end; ++iter) - { - std::ostringstream header; - //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 (read: llurlrequest.cpp) - static const std::string PRAGMA("Pragma"); - if ((iter->first == PRAGMA) && (iter->second.asString().empty())) - { - req->useProxy(false); - } - header << iter->first << ": " << iter->second.asString() ; - lldebugs << "header = " << header.str() << llendl; - req->addHeader(header.str().c_str()); - } - } - */ - - if ((method != LLURLRequest::HTTP_PUT) && (method != LLURLRequest::HTTP_POST)) { - std::string accept = "Accept: "; - accept += handler->getAcceptMimeType(); - req->addHeader(accept.c_str()); - } - - req->setCallback(new HippoRestComplete(handler)); - - if ((method == LLURLRequest::HTTP_PUT) || (method == LLURLRequest::HTTP_POST)) { - std::string content = "Content-Type: "; - content += body->getContentMimeType(); - req->addHeader(content.c_str()); - chain.push_back(LLIOPipe::ptr_t(body)); - } - - chain.push_back(LLIOPipe::ptr_t(req)); - LLHTTPClient::getPump().addChain(chain, timeout); -} - - -// ******************************************************************** - - -static size_t curlWrite(void *ptr, size_t size, size_t nmemb, void *userData) -{ - std::string *result = (std::string*)userData; - size_t bytes = (size * nmemb); - result->append((char*)ptr, bytes); - return nmemb; -} - - -// static -int HippoRestRequest::getBlocking(const std::string &url, std::string *result) -{ - llinfos << "Requesting: " << url << llendl; - - char curlErrorBuffer[CURL_ERROR_SIZE]; - CURL* curlp = curl_easy_init(); - llassert_always(curlp); - - curl_easy_setopt(curlp, CURLOPT_NOSIGNAL, 1); // don't use SIGALRM for timeouts - curl_easy_setopt(curlp, CURLOPT_TIMEOUT, 5); // seconds - curl_easy_setopt(curlp, CURLOPT_CAINFO, gDirUtilp->getCAFile().c_str()); - - curl_easy_setopt(curlp, CURLOPT_WRITEFUNCTION, curlWrite); - curl_easy_setopt(curlp, CURLOPT_WRITEDATA, result); - curl_easy_setopt(curlp, CURLOPT_URL, url.c_str()); - curl_easy_setopt(curlp, CURLOPT_ERRORBUFFER, curlErrorBuffer); - curl_easy_setopt(curlp, CURLOPT_FAILONERROR, 1); - - *result = ""; - S32 curlSuccess = curl_easy_perform(curlp); - long httpStatus = 499L; // curl_easy_getinfo demands pointer to long. - curl_easy_getinfo(curlp, CURLINFO_RESPONSE_CODE, &httpStatus); - - if (curlSuccess != 0) { - llwarns << "CURL ERROR (HTTP Status " << httpStatus << "): " << curlErrorBuffer << llendl; - } else if (httpStatus != 200) { - llwarns << "HTTP Error " << httpStatus << ", but no Curl error." << llendl; - } - - curl_easy_cleanup(curlp); - return httpStatus; -} - diff --git a/indra/newview/hipporestrequest.h b/indra/newview/hipporestrequest.h deleted file mode 100644 index 727dbf733..000000000 --- a/indra/newview/hipporestrequest.h +++ /dev/null @@ -1,104 +0,0 @@ -#ifndef __HIPPO_REST_REQUEST_H__ -#define __HIPPO_REST_REQUEST_H__ - - -#include -#include - -#include - -class LLBufferArray; -class LLChannelDescriptors; -class LLXmlTree; - - -#define HIPPO_REST_TIMEOUT 60.f - - -// ******************************************************************** - - -class HippoRestHandler -{ - public: - virtual ~HippoRestHandler() { } - - virtual const char *getAcceptMimeType() const = 0; - - bool hasHeader(const std::string &header) const; - const std::string &getHeader(const std::string &header) const; - - private: - // These functions are called by the request engine - void addHeader(const std::string &header, const std::string &content); - virtual void handle(int status, const std::string &reason, - const LLChannelDescriptors &channels, - const boost::shared_ptr &body) = 0; - - std::map mHeaders; - - friend class HippoRestComplete; -}; - - -class HippoRestHandlerRaw : public HippoRestHandler -{ - public: - virtual ~HippoRestHandlerRaw() { } - - const char *getAcceptMimeType() const { return "application/octet-stream"; } - - private: - // This function must be implemented to receive the content - // it is executed on (status == 200) only - virtual void result(const std::string &content) = 0; - - // This function is called by the request engine - void handle(int status, const std::string &reason, - const LLChannelDescriptors &channels, - const boost::shared_ptr &body); -}; - - -class HippoRestHandlerXml : public HippoRestHandler -{ - public: - virtual ~HippoRestHandlerXml() { } - - const char *getAcceptMimeType() const { return "application/xml"; } - - private: - // This function must be implemented to receive the content - virtual void result(LLXmlTree *content) = 0; - - // This function is called by the request engine - void handle(int status, const std::string &reason, - const LLChannelDescriptors &channels, - const boost::shared_ptr &body); -}; - - -// ******************************************************************** - - -class HippoRestRequest -{ - public: - // asynchronous interface - static void get(const std::string &url, - HippoRestHandler *handler, float timeout=HIPPO_REST_TIMEOUT); - static void put(const std::string &url, const std::string &body, - HippoRestHandler *handler, float timeout=HIPPO_REST_TIMEOUT); - static void put(const std::string &url, const LLXmlTree *body, - HippoRestHandler *handler, float timeout=HIPPO_REST_TIMEOUT); - static void post(const std::string &url, const std::string &body, - HippoRestHandler *handler, float timeout=HIPPO_REST_TIMEOUT); - static void post(const std::string &url, const LLXmlTree *body, - HippoRestHandler *handler, float timeout=HIPPO_REST_TIMEOUT); - - // synchronous interface - static int getBlocking(const std::string &url, std::string *result); -}; - - -#endif diff --git a/indra/newview/lggdicdownload.cpp b/indra/newview/lggdicdownload.cpp index 03a83aff0..d4caa10ae 100644 --- a/indra/newview/lggdicdownload.cpp +++ b/indra/newview/lggdicdownload.cpp @@ -49,8 +49,10 @@ #include "llbufferstream.h" class lggDicDownloadFloater; +class AIHTTPTimeoutPolicy; +extern AIHTTPTimeoutPolicy emeraldDicDownloader_timeout; -class EmeraldDicDownloader : public LLHTTPClient::Responder +class EmeraldDicDownloader : public LLHTTPClient::ResponderWithCompleted { public: EmeraldDicDownloader(lggDicDownloadFloater* spanel, std::string sname); @@ -59,7 +61,8 @@ public: U32 status, const std::string& reason, const LLChannelDescriptors& channels, - const LLIOPipe::buffer_ptr_t& buffer); + const buffer_ptr_t& buffer); + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return emeraldDicDownloader_timeout; } private: lggDicDownloadFloater* panel; std::string name; @@ -163,7 +166,7 @@ EmeraldDicDownloader::EmeraldDicDownloader(lggDicDownloadFloater* spanel, std::s } -void EmeraldDicDownloader::completedRaw(U32 status, const std::string& reason, const LLChannelDescriptors& channels, const LLIOPipe::buffer_ptr_t& buffer) +void EmeraldDicDownloader::completedRaw(U32 status, const std::string& reason, const LLChannelDescriptors& channels, const buffer_ptr_t& buffer) { if (status < 200 || status >= 300) { diff --git a/indra/newview/llaccountingcostmanager.cpp b/indra/newview/llaccountingcostmanager.cpp index 982b9df0d..a319e38d7 100644 --- a/indra/newview/llaccountingcostmanager.cpp +++ b/indra/newview/llaccountingcostmanager.cpp @@ -30,12 +30,15 @@ #include "llcurl.h" #include "llhttpclient.h" +class AIHTTPTimeoutPolicy; +extern AIHTTPTimeoutPolicy accountingCostResponder_timeout; + //=============================================================================== LLAccountingCostManager::LLAccountingCostManager() { } //=============================================================================== -class LLAccountingCostResponder : public LLCurl::Responder +class LLAccountingCostResponder : public LLHTTPClient::ResponderWithResult { public: LLAccountingCostResponder( const LLSD& objectIDs ) @@ -85,6 +88,8 @@ public: clearPendingRequests(); } + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return accountingCostResponder_timeout; } + private: //List of posted objects LLSD mObjectIDs; diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp index a0798dcd8..30415e458 100644 --- a/indra/newview/llagent.cpp +++ b/indra/newview/llagent.cpp @@ -2171,7 +2171,7 @@ void LLAgent::setStartPosition( U32 location_id ) // This awkward idiom warrants explanation. // For starters, LLSDMessage::ResponderAdapter is ONLY for testing the new - // LLSDMessage functionality with a pre-existing LLHTTPClient::Responder. + // LLSDMessage functionality with a pre-existing LLHTTPClient::ResponderWithResult. // In new code, define your reply/error methods on the same class as the // sending method, bind them to local LLEventPump objects and pass those // LLEventPump names in the request LLSD object. @@ -2188,6 +2188,7 @@ void LLAgent::setStartPosition( U32 location_id ) request["payload"] = body; request["reply"] = adapter->getReplyName(); request["error"] = adapter->getErrorName(); + request["timeoutpolicy"] = adapter->getTimeoutPolicyName(); gAgent.getRegion()->getCapAPI().post(request); diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 2fcf37fdb..9ae51fe20 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -95,6 +95,7 @@ #include "llprimitive.h" #include "llnotifications.h" #include "llnotificationsutil.h" +#include "llcurl.h" #include #if LL_WINDOWS @@ -118,6 +119,8 @@ // #include "lldelayeduidelete.h" #include "llbuildnewviewsscheduler.h" +#include "aicurleasyrequeststatemachine.h" +#include "aihttptimeoutpolicy.h" // // The files below handle dependencies from cleanup. #include "llcalc.h" @@ -592,8 +595,10 @@ bool LLAppViewer::init() // LLFastTimer::reset(); + // // We can call this early. LLFrameTimer::global_initialization(); + // // initialize SSE options LLVector4a::initClass(); @@ -610,13 +615,13 @@ bool LLAppViewer::init() initLogging(); + // // Curl must be initialized before any thread is running. AICurlInterface::initCurl(&AIStateMachine::flush); // Logging is initialized. Now it's safe to start the error thread. startErrorThread(); - // gDeleteScheduler = new LLDeleteScheduler(); gBuildNewViewsScheduler = new LLBuildNewViewsScheduler(); // @@ -638,6 +643,20 @@ bool LLAppViewer::init() mAlloc.setProfilingEnabled(gSavedSettings.getBOOL("MemProfiling")); + AIStateMachine::setMaxCount(gSavedSettings.getU32("StateMachineMaxTime")); + + AIHTTPTimeoutPolicy::setDefaultCurlTimeout( + AIHTTPTimeoutPolicy( + "CurlTimeout* Debug Settings", + gSavedSettings.getU32("CurlTimeoutDNSLookup"), + gSavedSettings.getU32("CurlTimeoutConnect"), + gSavedSettings.getU32("CurlTimeoutReplyDelay"), + gSavedSettings.getU32("CurlTimeoutLowSpeedTime"), + gSavedSettings.getU32("CurlTimeoutLowSpeedLimit"), + gSavedSettings.getU32("CurlTimeoutMaxTransaction"), + gSavedSettings.getU32("CurlTimeoutMaxTotalDelay") + )); + initThreads(); LL_INFOS("InitInfo") << "Threads initialized." << LL_ENDL ; @@ -1026,6 +1045,7 @@ static LLFastTimer::DeclareTimer FTM_PUMP_SERVICE("Service"); static LLFastTimer::DeclareTimer FTM_SERVICE_CALLBACK("Callback"); static LLFastTimer::DeclareTimer FTM_AGENT_AUTOPILOT("Autopilot"); static LLFastTimer::DeclareTimer FTM_AGENT_UPDATE("Update"); +static LLFastTimer::DeclareTimer FTM_STATEMACHINE("State Machine"); bool LLAppViewer::mainLoop() { @@ -1039,7 +1059,6 @@ bool LLAppViewer::mainLoop() // Create IO Pump to use for HTTP Requests. gServicePump = new LLPumpIO; - LLHTTPClient::setPump(*gServicePump); LLCurl::setCAFile(gDirUtilp->getCAFile()); // Note: this is where gLocalSpeakerMgr and gActiveSpeakerMgr used to be instantiated. @@ -1817,7 +1836,7 @@ bool LLAppViewer::initThreads() LLWatchdog::getInstance()->init(watchdog_killer_callback); } - AICurlInterface::startCurlThread(); + AICurlInterface::startCurlThread(gSavedSettings.getU32("CurlConcurrentConnections"), gSavedSettings.getBOOL("NoVerifySSLCert")); LLImage::initClass(); @@ -3754,6 +3773,16 @@ void LLAppViewer::idle() } } + ////////////////////////////////////// + // + // Run state machines + // + + { + LLFastTimer t(FTM_STATEMACHINE); + AIStateMachine::mainloop(); + } + // Must wait until both have avatar object and mute list, so poll // here. request_initial_instant_messages(); diff --git a/indra/newview/llassetuploadqueue.cpp b/indra/newview/llassetuploadqueue.cpp index 083166553..bb022bafa 100644 --- a/indra/newview/llassetuploadqueue.cpp +++ b/indra/newview/llassetuploadqueue.cpp @@ -47,7 +47,7 @@ public: LLAssetUploadChainResponder(const LLSD& post_data, const std::string& file_name, const LLUUID& queue_id, - U8* data, + char* data, U32 data_size, std::string script_name, LLAssetUploadQueueSupplier *supplier) : @@ -137,7 +137,7 @@ public: } LLAssetUploadQueueSupplier *mSupplier; - U8* mData; + char* mData; U32 mDataSize; std::string mScriptName; }; @@ -189,7 +189,7 @@ void LLAssetUploadQueue::queue(const std::string& filename, BOOL is_running, BOOL is_target_mono, const LLUUID& queue_id, - U8* script_data, + char* script_data, U32 data_size, std::string script_name) { diff --git a/indra/newview/llassetuploadqueue.h b/indra/newview/llassetuploadqueue.h index 930dc72a7..eaee9f424 100644 --- a/indra/newview/llassetuploadqueue.h +++ b/indra/newview/llassetuploadqueue.h @@ -54,7 +54,7 @@ public: BOOL is_running, BOOL is_target_mono, const LLUUID& queue_id, - U8* data, + char* data, U32 data_size, std::string script_name); @@ -72,7 +72,7 @@ private: BOOL mIsRunning; BOOL mIsTargetMono; LLUUID mQueueId; - U8* mData; + char* mData; U32 mDataSize; std::string mScriptName; }; diff --git a/indra/newview/llassetuploadresponders.cpp b/indra/newview/llassetuploadresponders.cpp index a9ff6e3f7..173666a12 100644 --- a/indra/newview/llassetuploadresponders.cpp +++ b/indra/newview/llassetuploadresponders.cpp @@ -192,8 +192,7 @@ void on_new_single_inventory_upload_complete( LLAssetUploadResponder::LLAssetUploadResponder(const LLSD &post_data, const LLUUID& vfile_id, LLAssetType::EType asset_type) -: LLHTTPClient::Responder(), - mPostData(post_data), +: mPostData(post_data), mVFileID(vfile_id), mAssetType(asset_type) { @@ -210,8 +209,7 @@ LLAssetUploadResponder::LLAssetUploadResponder( const LLSD &post_data, const std::string& file_name, LLAssetType::EType asset_type) -: LLHTTPClient::Responder(), - mPostData(post_data), +: mPostData(post_data), mFileName(file_name), mAssetType(asset_type) { diff --git a/indra/newview/llassetuploadresponders.h b/indra/newview/llassetuploadresponders.h index 0236b08b5..fa4e5a8eb 100644 --- a/indra/newview/llassetuploadresponders.h +++ b/indra/newview/llassetuploadresponders.h @@ -36,6 +36,10 @@ #include "llhttpclient.h" #include "llinventory.h" +class AIHTTPTimeoutPolicy; +extern AIHTTPTimeoutPolicy assetUploadResponder_timeout; +extern AIHTTPTimeoutPolicy newAgentInventoryVariablePriceResponder_timeout; + void on_new_single_inventory_upload_complete(LLAssetType::EType asset_type, LLInventoryType::EType inventory_type, const std::string inventory_type_string, @@ -47,7 +51,7 @@ void on_new_single_inventory_upload_complete(LLAssetType::EType asset_type, // Abstract class for supporting asset upload // via capabilities -class LLAssetUploadResponder : public LLHTTPClient::Responder +class LLAssetUploadResponder : public LLHTTPClient::ResponderWithResult { public: LLAssetUploadResponder(const LLSD& post_data, @@ -59,6 +63,7 @@ public: ~LLAssetUploadResponder(); virtual void error(U32 statusNum, const std::string& reason); virtual void result(const LLSD& content); + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return assetUploadResponder_timeout; } virtual void uploadUpload(const LLSD& content); virtual void uploadComplete(const LLSD& content); virtual void uploadFailure(const LLSD& content); @@ -91,7 +96,7 @@ public: // are needed (such as different confirmation messages, etc.) // the functions onApplicationLevelError and showConfirmationDialog. class LLNewAgentInventoryVariablePriceResponder : - public LLHTTPClient::Responder + public LLHTTPClient::ResponderWithResult { public: LLNewAgentInventoryVariablePriceResponder( @@ -110,6 +115,7 @@ public: const std::string& reason, const LLSD& content); void result(const LLSD& content); + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return newAgentInventoryVariablePriceResponder_timeout; } virtual void onApplicationLevelError( const LLSD& error); diff --git a/indra/newview/llcapabilitylistener.cpp b/indra/newview/llcapabilitylistener.cpp index ef9b910ae..3e2880199 100644 --- a/indra/newview/llcapabilitylistener.cpp +++ b/indra/newview/llcapabilitylistener.cpp @@ -84,7 +84,7 @@ bool LLCapabilityListener::capListener(const LLSD& request) 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 "message" key, we doubt it was intended // for this listener. if (cap.empty()) @@ -95,24 +95,15 @@ bool LLCapabilityListener::capListener(const LLSD& request) << LL_ENDL; return false; // in case fatal-error function isn't } - // Establish default timeout. This test relies on LLSD::asReal() returning - // exactly 0.0 for an undef value. - if (! timeout) - { - timeout = HTTP_REQUEST_EXPIRY_SECS; - } // Look up the url for the requested capability name. std::string url = mProvider.getCapability(cap); if (! url.empty()) { + LLSDMessage::EventResponder* responder = + new LLSDMessage::EventResponder(LLEventPumps::instance(), request, mProvider.getDescription(), cap, reply, error); + responder->setTimeoutPolicy(timeoutpolicy); // This capability is supported by the region to which we're talking. - LLHTTPClient::post(url, payload, - new LLSDMessage::EventResponder(LLEventPumps::instance(), - request, - mProvider.getDescription(), - cap, reply, error), - LLSD(), // headers - timeout); + LLHTTPClient::post(url, payload, responder); } else { diff --git a/indra/newview/llclassifiedstatsresponder.h b/indra/newview/llclassifiedstatsresponder.h index c4591df8d..f317d5851 100644 --- a/indra/newview/llclassifiedstatsresponder.h +++ b/indra/newview/llclassifiedstatsresponder.h @@ -37,7 +37,10 @@ #include "llview.h" #include "lluuid.h" -class LLClassifiedStatsResponder : public LLHTTPClient::Responder +class AIHTTPTimeoutPolicy; +extern AIHTTPTimeoutPolicy classifiedStatsResponder_timeout; + +class LLClassifiedStatsResponder : public LLHTTPClient::ResponderWithResult { public: LLClassifiedStatsResponder(LLHandle classified_panel_handle, LLUUID classified_id); @@ -45,6 +48,7 @@ public: virtual void result(const LLSD& content); //If we get back an error (not found, etc...), handle it here virtual void error(U32 status, const std::string& reason); + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return classifiedStatsResponder_timeout; } protected: LLHandle mClassifiedPanelHandle; diff --git a/indra/newview/llcompilequeue.cpp b/indra/newview/llcompilequeue.cpp index 5cf8b7fdc..50aceb389 100644 --- a/indra/newview/llcompilequeue.cpp +++ b/indra/newview/llcompilequeue.cpp @@ -437,8 +437,8 @@ void LLFloaterCompileQueue::scriptArrived(LLVFS *vfs, const LLUUID& asset_id, { // Read script source in to buffer. U32 script_size = file.getSize(); - U8* script_data = new U8[script_size]; - file.read(script_data, script_size); + char* script_data = new char[script_size]; + file.read(reinterpret_cast(script_data), script_size); queue->mUploadQueue->queue(filename, data->mTaskId, data->mItemId, is_running, queue->mMono, queue->getID(), diff --git a/indra/newview/llcurlrequest.cpp b/indra/newview/llcurlrequest.cpp deleted file mode 100644 index 4084b729f..000000000 --- a/indra/newview/llcurlrequest.cpp +++ /dev/null @@ -1,146 +0,0 @@ -/** - * @file llcurlrequest.cpp - * @brief Implementation of Request. - * - * Copyright (c) 2012, Aleric Inglewood. - * Copyright (C) 2010, Linden Research, Inc. - * - * 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 . - * - * 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. - * - * 17/03/2012 - * Initial version, written by Aleric Inglewood @ SL - * - * 20/03/2012 - * Added copyright notice for Linden Lab for those parts that were - * copied or derived from llcurl.cpp. The code of those parts are - * already in their own llcurl.cpp, so they do not ever need to - * even look at this file; the reason I added the copyright notice - * is to make clear that I am not the author of 100% of this code - * and hence I cannot change the license of it. - */ - -#include "llviewerprecompiledheaders.h" - -#include "llsdserialize.h" -#include "llcurlrequest.h" -#include "llbuffer.h" -#include "llbufferstream.h" -#include "statemachine/aicurleasyrequeststatemachine.h" - -//----------------------------------------------------------------------------- -// class Request -// - -namespace AICurlInterface { - -bool Request::get(std::string const& url, ResponderPtr responder) -{ - return getByteRange(url, headers_t(), 0, -1, responder); -} - -bool Request::getByteRange(std::string const& url, headers_t const& headers, S32 offset, S32 length, ResponderPtr responder) -{ - DoutEntering(dc::curl, "Request::getByteRange(" << url << ", ...)"); - - // This might throw AICurlNoEasyHandle. - AICurlEasyRequestStateMachine* buffered_easy_request = new AICurlEasyRequestStateMachine(true); - - { - AICurlEasyRequest_wat buffered_easy_request_w(*buffered_easy_request->mCurlEasyRequest); - - AICurlResponderBuffer_wat(*buffered_easy_request->mCurlEasyRequest)->prepRequest(buffered_easy_request_w, headers, responder); - - buffered_easy_request_w->setopt(CURLOPT_HTTPGET, 1); - if (length > 0) - { - std::string range = llformat("Range: bytes=%d-%d", offset, offset + length - 1); - buffered_easy_request_w->addHeader(range.c_str()); - } - - buffered_easy_request_w->finalizeRequest(url); - } - - buffered_easy_request->run(); - - return true; // We throw in case of problems. -} - -bool Request::post(std::string const& url, headers_t const& headers, std::string const& data, ResponderPtr responder, S32 time_out) -{ - DoutEntering(dc::curl, "Request::post(" << url << ", ...)"); - - // This might throw AICurlNoEasyHandle. - AICurlEasyRequestStateMachine* buffered_easy_request = new AICurlEasyRequestStateMachine(true); - - { - AICurlEasyRequest_wat buffered_easy_request_w(*buffered_easy_request->mCurlEasyRequest); - AICurlResponderBuffer_wat buffer_w(*buffered_easy_request->mCurlEasyRequest); - - buffer_w->prepRequest(buffered_easy_request_w, headers, responder); - - U32 bytes = data.size(); - bool success = buffer_w->getInput()->append(buffer_w->sChannels.out(), (U8 const*)data.data(), bytes); - llassert_always(success); // AIFIXME: Maybe throw an error. - if (!success) - return false; - buffered_easy_request_w->setPost(bytes); - buffered_easy_request_w->addHeader("Content-Type: application/octet-stream"); - buffered_easy_request_w->finalizeRequest(url); - - lldebugs << "POSTING: " << bytes << " bytes." << llendl; - } - - buffered_easy_request->run(); - - return true; // We throw in case of problems. -} - -bool Request::post(std::string const& url, headers_t const& headers, LLSD const& data, ResponderPtr responder, S32 time_out) -{ - DoutEntering(dc::curl, "Request::post(" << url << ", ...)"); - - // This might throw AICurlNoEasyHandle. - AICurlEasyRequestStateMachine* buffered_easy_request = new AICurlEasyRequestStateMachine(true); - - { - AICurlEasyRequest_wat buffered_easy_request_w(*buffered_easy_request->mCurlEasyRequest); - AICurlResponderBuffer_wat buffer_w(*buffered_easy_request->mCurlEasyRequest); - - buffer_w->prepRequest(buffered_easy_request_w, headers, responder); - - LLBufferStream buffer_stream(buffer_w->sChannels, buffer_w->getInput().get()); - LLSDSerialize::toXML(data, buffer_stream); - S32 bytes = buffer_w->getInput()->countAfter(buffer_w->sChannels.out(), NULL); - buffered_easy_request_w->setPost(bytes); - buffered_easy_request_w->addHeader("Content-Type: application/llsd+xml"); - buffered_easy_request_w->finalizeRequest(url); - - lldebugs << "POSTING: " << bytes << " bytes." << llendl; - } - - buffered_easy_request->run(); - - return true; // We throw in case of problems. -} - -} // namespace AICurlInterface -//================================================================================== - diff --git a/indra/newview/llcurlrequest.h b/indra/newview/llcurlrequest.h deleted file mode 100644 index d10c20b98..000000000 --- a/indra/newview/llcurlrequest.h +++ /dev/null @@ -1,57 +0,0 @@ -/** - * @file llcurlrequest.h - * @brief Declaration of class 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 . - * - * 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. - * - * 17/03/2012 - * Initial version, written by Aleric Inglewood @ SL - */ - -#ifndef AICURLREQUEST_H -#define AICURLREQUEST_H - -#include -#include -#include - -// Things defined in this namespace are called from elsewhere in the viewer code. -namespace AICurlInterface { - -// Forward declaration. -class Responder; -typedef boost::intrusive_ptr ResponderPtr; - -class Request { - public: - typedef std::vector headers_t; - - bool get(std::string const& url, ResponderPtr responder); - bool getByteRange(std::string const& url, headers_t const& headers, S32 offset, S32 length, ResponderPtr responder); - bool post(std::string const& url, headers_t const& headers, std::string const& data, ResponderPtr responder, S32 time_out = 0); - bool post(std::string const& url, headers_t const& headers, LLSD const& data, ResponderPtr responder, S32 time_out = 0); -}; - -} // namespace AICurlInterface - -#endif diff --git a/indra/newview/llcurrencyuimanager.cpp b/indra/newview/llcurrencyuimanager.cpp index e31e9d114..80a70a152 100644 --- a/indra/newview/llcurrencyuimanager.cpp +++ b/indra/newview/llcurrencyuimanager.cpp @@ -44,7 +44,8 @@ #include "llframetimer.h" #include "lllineeditor.h" #include "llviewchildren.h" -#include "llxmlrpctransaction.h" +#include "llxmlrpcresponder.h" +#include "llhttpclient.h" #include "llviewernetwork.h" #include "hippogridmanager.h" @@ -89,8 +90,8 @@ public: }; TransactionType mTransactionType; - LLXMLRPCTransaction* mTransaction; - + boost::intrusive_ptr mResponder; + bool mCurrencyChanged; LLFrameTimer mCurrencyKeyTimer; @@ -128,14 +129,13 @@ LLCurrencyUIManager::Impl::Impl(LLPanel& dialog) mSiteCurrencyEstimated(false), mSiteCurrencyEstimatedCost(0), mBought(false), - mTransactionType(TransactionNone), mTransaction(0), + mTransactionType(TransactionNone), mCurrencyChanged(false) { } LLCurrencyUIManager::Impl::~Impl() { - delete mTransaction; } void LLCurrencyUIManager::Impl::updateCurrencyInfo() @@ -166,7 +166,7 @@ void LLCurrencyUIManager::Impl::updateCurrencyInfo() void LLCurrencyUIManager::Impl::finishCurrencyInfo() { - LLXMLRPCValue result = mTransaction->responseValue(); + LLXMLRPCValue result = mResponder->responseValue(); bool success = result["success"].asBool(); if (!success) @@ -219,7 +219,7 @@ void LLCurrencyUIManager::Impl::startCurrencyBuy(const std::string& password) void LLCurrencyUIManager::Impl::finishCurrencyBuy() { - LLXMLRPCValue result = mTransaction->responseValue(); + LLXMLRPCValue result = mResponder->responseValue(); bool success = result["success"].asBool(); if (!success) @@ -246,34 +246,28 @@ void LLCurrencyUIManager::Impl::startTransaction(TransactionType type, transactionURI = LLViewerLogin::getInstance()->getHelperURI() + "currency.php"; } - delete mTransaction; - mTransactionType = type; - mTransaction = new LLXMLRPCTransaction( - transactionURI, - method, - params, - false /* don't use gzip */ - ); + mResponder = new XMLRPCResponder; + LLHTTPClient::postXMLRPC(transactionURI, method, params.getValue(), mResponder); clearError(); } bool LLCurrencyUIManager::Impl::checkTransaction() { - if (!mTransaction) + if (!mResponder) { return false; } - if (!mTransaction->is_finished()) + if (!mResponder->is_finished()) { return false; } - if (mTransaction->status(NULL) != LLXMLRPCTransaction::StatusComplete) + if (mResponder->result_code() != CURLE_OK || mResponder->http_status() < 200 || mResponder->http_status() >= 400) { - setError(mTransaction->statusMessage(), mTransaction->statusURI()); + setError(mResponder->reason(), mResponder->getURL()); } else { switch (mTransactionType) @@ -283,11 +277,11 @@ bool LLCurrencyUIManager::Impl::checkTransaction() default: ; } } - - delete mTransaction; - mTransaction = NULL; + + // We're done with this data. + mResponder = NULL; mTransactionType = TransactionNone; - + return true; } @@ -309,7 +303,7 @@ void LLCurrencyUIManager::Impl::clearError() bool LLCurrencyUIManager::Impl::considerUpdateCurrency() { if (mCurrencyChanged - && !mTransaction + && !mResponder && mCurrencyKeyTimer.getElapsedTimeF32() >= CURRENCY_ESTIMATE_FREQUENCY) { updateCurrencyInfo(); diff --git a/indra/newview/lleventpoll.cpp b/indra/newview/lleventpoll.cpp index 3114f2914..6548d520b 100644 --- a/indra/newview/lleventpoll.cpp +++ b/indra/newview/lleventpoll.cpp @@ -46,6 +46,9 @@ #include "message.h" #include "lltrans.h" +class AIHTTPTimeoutPolicy; +extern AIHTTPTimeoutPolicy eventPollResponder_timeout; + namespace { // We will wait RETRY_SECONDS + (errorCount * RETRY_SECONDS_INC) before retrying after an error. @@ -55,7 +58,7 @@ namespace const F32 EVENT_POLL_ERROR_RETRY_SECONDS_INC = 5.f; // ~ half of a normal timeout. const S32 MAX_EVENT_POLL_HTTP_ERRORS = 10; // ~5 minutes, by the above rules. - class LLEventPollResponder : public LLHTTPClient::Responder + class LLEventPollResponder : public LLHTTPClient::ResponderWithResult { public: @@ -70,13 +73,10 @@ namespace void handleMessage(const LLSD& content); - virtual void error(U32 status, const std::string& reason); + virtual void error(U32 status, const std::string& reason); virtual void result(const LLSD& content); + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return eventPollResponder_timeout; } - virtual void completedRaw(U32 status, - const std::string& reason, - const LLChannelDescriptors& channels, - const LLIOPipe::buffer_ptr_t& buffer); private: bool mDone; @@ -156,24 +156,6 @@ namespace << mPollURL << llendl; } - // virtual - void LLEventPollResponder::completedRaw(U32 status, - const std::string& reason, - const LLChannelDescriptors& channels, - const LLIOPipe::buffer_ptr_t& buffer) - { - if (status == HTTP_BAD_GATEWAY) - { - // These errors are not parsable as LLSD, - // which LLHTTPClient::Responder::completedRaw will try to do. - completed(status, reason, LLSD()); - } - else - { - LLHTTPClient::Responder::completedRaw(status,reason,channels,buffer); - } - } - void LLEventPollResponder::makeRequest() { LLSD request; @@ -291,7 +273,7 @@ LLEventPoll::LLEventPoll(const std::string& poll_url, const LLHost& sender) LLEventPoll::~LLEventPoll() { - LLHTTPClient::Responder* responderp = mImpl.get(); + LLHTTPClient::ResponderBase* responderp = mImpl.get(); LLEventPollResponder* event_poll_responder = dynamic_cast(responderp); if (event_poll_responder) event_poll_responder->stop(); } diff --git a/indra/newview/llfasttimerview.cpp b/indra/newview/llfasttimerview.cpp index eeb53a98d..9f1cbca6c 100644 --- a/indra/newview/llfasttimerview.cpp +++ b/indra/newview/llfasttimerview.cpp @@ -1146,7 +1146,7 @@ void LLFastTimerView::exportCharts(const std::string& base, const std::string& t { //read base log into memory S32 i = 0; std::ifstream is(base.c_str()); - while (!is.eof() && LLSDSerialize::fromXML(cur, is)) + while (!is.eof() && LLSDSerialize::fromXML(cur, is) > 0) { base_data[i++] = cur; } @@ -1159,7 +1159,7 @@ void LLFastTimerView::exportCharts(const std::string& base, const std::string& t { //read current log into memory S32 i = 0; std::ifstream is(target.c_str()); - while (!is.eof() && LLSDSerialize::fromXML(cur, is)) + while (!is.eof() && LLSDSerialize::fromXML(cur, is) > 0) { cur_data[i++] = cur; @@ -1450,7 +1450,7 @@ LLSD LLFastTimerView::analyzePerformanceLogDefault(std::istream& is) stats_map_t time_stats; stats_map_t sample_stats; - while (!is.eof() && LLSDSerialize::fromXML(cur, is)) + while (!is.eof() && LLSDSerialize::fromXML(cur, is) > 0) { for (LLSD::map_iterator iter = cur.beginMap(); iter != cur.endMap(); ++iter) { diff --git a/indra/newview/llfloateractivespeakers.cpp b/indra/newview/llfloateractivespeakers.cpp index 1d1fb1ebf..9aa5a7adf 100644 --- a/indra/newview/llfloateractivespeakers.cpp +++ b/indra/newview/llfloateractivespeakers.cpp @@ -57,6 +57,11 @@ #include "llavatarname.h" +class AIHTTPTimeoutPolicy; +extern AIHTTPTimeoutPolicy muteVoiceResponder_timeout; +extern AIHTTPTimeoutPolicy muteTextResponder_timeout; +extern AIHTTPTimeoutPolicy moderationModeResponder_timeout; + using namespace LLOldEvents; const F32 SPEAKER_TIMEOUT = 10.f; // seconds of not being on voice channel before removed from list of active speakers @@ -844,7 +849,7 @@ void LLPanelActiveSpeakers::onModeratorMuteVoice(LLUICtrl* ctrl, void* user_data // ctrl value represents ability to type, so invert data["params"]["mute_info"]["voice"] = !ctrl->getValue(); - class MuteVoiceResponder : public LLHTTPClient::Responder + class MuteVoiceResponder : public LLHTTPClient::ResponderIgnoreBody { public: MuteVoiceResponder(const LLUUID& session_id) @@ -882,6 +887,8 @@ void LLPanelActiveSpeakers::onModeratorMuteVoice(LLUICtrl* ctrl, void* user_data } } + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return muteVoiceResponder_timeout; } + private: LLUUID mSessionID; }; @@ -909,7 +916,7 @@ void LLPanelActiveSpeakers::onModeratorMuteText(LLUICtrl* ctrl, void* user_data) // ctrl value represents ability to type, so invert data["params"]["mute_info"]["text"] = !ctrl->getValue(); - class MuteTextResponder : public LLHTTPClient::Responder + class MuteTextResponder : public LLHTTPClient::ResponderIgnoreBody { public: MuteTextResponder(const LLUUID& session_id) @@ -947,6 +954,8 @@ void LLPanelActiveSpeakers::onModeratorMuteText(LLUICtrl* ctrl, void* user_data) } } + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return muteTextResponder_timeout; } + private: LLUUID mSessionID; }; @@ -981,12 +990,13 @@ void LLPanelActiveSpeakers::onChangeModerationMode(LLUICtrl* ctrl, void* user_da data["params"]["update_info"]["moderated_mode"]["voice"] = true; } - struct ModerationModeResponder : public LLHTTPClient::Responder + struct ModerationModeResponder : public LLHTTPClient::ResponderIgnoreBody { virtual void error(U32 status, const std::string& reason) { llwarns << status << ": " << reason << llendl; } + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return moderationModeResponder_timeout; } }; LLHTTPClient::post(url, data, new ModerationModeResponder()); diff --git a/indra/newview/llfloaterbuyland.cpp b/indra/newview/llfloaterbuyland.cpp index 909111ee3..2c1631987 100644 --- a/indra/newview/llfloaterbuyland.cpp +++ b/indra/newview/llfloaterbuyland.cpp @@ -62,8 +62,9 @@ #include "llweb.h" #include "llwindow.h" #include "llworld.h" -#include "llxmlrpctransaction.h" +#include "llxmlrpcresponder.h" #include "llviewernetwork.h" +#include "llhttpclient.h" #include "roles_constants.h" #include "hippogridmanager.h" @@ -158,7 +159,7 @@ private: TransactionCurrency, TransactionBuy }; - LLXMLRPCTransaction* mTransaction; + boost::intrusive_ptr mResponder; TransactionType mTransactionType; LLViewerParcelMgr::ParcelBuyInfo* mParcelBuyInfo; @@ -283,7 +284,7 @@ LLFloaterBuyLandUI::LLFloaterBuyLandUI() mParcel(0), mBought(false), mParcelValid(false), mSiteValid(false), - mChildren(*this), mCurrency(*this), mTransaction(0), + mChildren(*this), mCurrency(*this), mParcelBuyInfo(0) { LLUICtrlFactory::getInstance()->buildFloater(this, "floater_buy_land.xml"); @@ -294,8 +295,6 @@ LLFloaterBuyLandUI::~LLFloaterBuyLandUI() { LLViewerParcelMgr::getInstance()->removeObserver(&mParcelSelectionObserver); LLViewerParcelMgr::getInstance()->deleteParcelBuy(&mParcelBuyInfo); - - delete mTransaction; } // virtual @@ -619,7 +618,7 @@ void LLFloaterBuyLandUI::updateWebSiteInfo() S32 askBillableArea = mIsForGroup ? 0 : mParcelBillableArea; S32 askCurrencyBuy = mCurrency.getAmount(); - if (mTransaction && mTransactionType == TransactionPreflight + if (mResponder && mTransactionType == TransactionPreflight && mPreflightAskBillableArea == askBillableArea && mPreflightAskCurrencyBuy == askCurrencyBuy) { @@ -660,7 +659,7 @@ void LLFloaterBuyLandUI::updateWebSiteInfo() void LLFloaterBuyLandUI::finishWebSiteInfo() { - LLXMLRPCValue result = mTransaction->responseValue(); + LLXMLRPCValue result = mResponder->responseValue(); mSiteValid = result["success"].asBool(); if (!mSiteValid) @@ -755,7 +754,7 @@ void LLFloaterBuyLandUI::runWebSitePrep(const std::string& password) void LLFloaterBuyLandUI::finishWebSitePrep() { - LLXMLRPCValue result = mTransaction->responseValue(); + LLXMLRPCValue result = mResponder->responseValue(); bool success = result["success"].asBool(); if (!success) @@ -825,9 +824,6 @@ void LLFloaterBuyLandUI::updateName(const LLUUID& id, void LLFloaterBuyLandUI::startTransaction(TransactionType type, const LLXMLRPCValue& params) { - delete mTransaction; - mTransaction = NULL; - mTransactionType = type; // Select a URI and method appropriate for the transaction type. @@ -851,29 +847,25 @@ void LLFloaterBuyLandUI::startTransaction(TransactionType type, const LLXMLRPCVa return; } - mTransaction = new LLXMLRPCTransaction( - transaction_uri, - method, - params, - false /* don't use gzip */ - ); + mResponder = new XMLRPCResponder; + LLHTTPClient::postXMLRPC(transaction_uri, method, params.getValue(), mResponder); } bool LLFloaterBuyLandUI::checkTransaction() { - if (!mTransaction) + if (!mResponder) { return false; } - if (!mTransaction->is_finished()) + if (!mResponder->is_finished()) { return false; } - if (mTransaction->status(NULL) != LLXMLRPCTransaction::StatusComplete) + if (mResponder->result_code() != CURLE_OK || mResponder->http_status() < 200 || mResponder->http_status() >= 400) { - tellUserError(mTransaction->statusMessage(), mTransaction->statusURI()); + tellUserError(mResponder->reason(), mResponder->getURL()); } else { switch (mTransactionType) @@ -884,8 +876,8 @@ bool LLFloaterBuyLandUI::checkTransaction() } } - delete mTransaction; - mTransaction = NULL; + // We're done with this data. + mResponder = NULL; return true; } @@ -918,7 +910,7 @@ BOOL LLFloaterBuyLandUI::postBuild() void LLFloaterBuyLandUI::setParcel(LLViewerRegion* region, LLParcelSelectionHandle parcel) { - if (mTransaction && mTransactionType == TransactionBuy) + if (mResponder && mTransactionType == TransactionBuy) { // the user is buying, don't change the selection return; @@ -968,7 +960,7 @@ void LLFloaterBuyLandUI::draw() // virtual BOOL LLFloaterBuyLandUI::canClose() { - bool can_close = (mTransaction ? FALSE : TRUE) && mCurrency.canCancel(); + bool can_close = !mResponder && mCurrency.canCancel(); if (!can_close) { // explain to user why they can't do this, see DEV-9605 @@ -1285,7 +1277,7 @@ void LLFloaterBuyLandUI::refreshUI() } childSetEnabled("buy_btn", - mCanBuy && mSiteValid && willHaveEnough && !mTransaction && agrees_to_covenant); + mCanBuy && mSiteValid && willHaveEnough && !mResponder && agrees_to_covenant); } void LLFloaterBuyLandUI::startBuyPreConfirm() diff --git a/indra/newview/llfloaterimagepreview.cpp b/indra/newview/llfloaterimagepreview.cpp index d32e08847..edb961685 100644 --- a/indra/newview/llfloaterimagepreview.cpp +++ b/indra/newview/llfloaterimagepreview.cpp @@ -38,6 +38,7 @@ #include "llimagetga.h" #include "llimagejpeg.h" #include "llimagepng.h" +#include "llimagej2c.h" #include "llagent.h" #include "llbutton.h" @@ -423,6 +424,21 @@ bool LLFloaterImagePreview::loadImage(const std::string& src_filename) } } break; + case IMG_CODEC_J2C: + { + LLPointer j2c_image = new LLImageJ2C; + + if (!j2c_image->load(src_filename)) + { + return false; + } + + if (!j2c_image->decode(raw_image, 0.0f)) + { + return false; + } + } + break; default: return false; } diff --git a/indra/newview/llfloatermodelpreview.cpp b/indra/newview/llfloatermodelpreview.cpp index 004d11646..89d34dea3 100644 --- a/indra/newview/llfloatermodelpreview.cpp +++ b/indra/newview/llfloatermodelpreview.cpp @@ -344,7 +344,7 @@ BOOL stop_gloderror() if (error != GLOD_NO_ERROR) { llwarns << "GLOD error detected, cannot generate LOD: " << std::hex - << error << llendl; + << error << std::dec << llendl; return TRUE; } diff --git a/indra/newview/llfloaterregiondebugconsole.cpp b/indra/newview/llfloaterregiondebugconsole.cpp index 9312781c4..a17eeab1e 100644 --- a/indra/newview/llfloaterregiondebugconsole.cpp +++ b/indra/newview/llfloaterregiondebugconsole.cpp @@ -37,6 +37,10 @@ #include "llviewerregion.h" #include "lluictrlfactory.h" +class AIHTTPTimeoutPolicy; +extern AIHTTPTimeoutPolicy asyncConsoleResponder_timeout; +extern AIHTTPTimeoutPolicy consoleResponder_timeout; + // Two versions of the sim console API are supported. // // SimConsole capability (deprecated): @@ -72,7 +76,8 @@ namespace // This responder handles the initial response. Unless error() is called // we assume that the simulator has received our request. Error will be // called if this request times out. - class AsyncConsoleResponder : public LLHTTPClient::Responder + // + class AsyncConsoleResponder : public LLHTTPClient::ResponderIgnoreBody { public: /* virtual */ @@ -80,9 +85,10 @@ namespace { sConsoleReplySignal(UNABLE_TO_SEND_COMMAND); } + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return asyncConsoleResponder_timeout; } }; - class ConsoleResponder : public LLHTTPClient::Responder + class ConsoleResponder : public LLHTTPClient::ResponderWithResult { public: ConsoleResponder(LLTextEditor *output) : mOutput(output) @@ -110,11 +116,14 @@ namespace } } + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return consoleResponder_timeout; } + LLTextEditor * mOutput; }; // This handles responses for console commands sent via the asynchronous // API. + class ConsoleResponseNode : public LLHTTPNode { public: diff --git a/indra/newview/llfloaterregiondebugconsole.h b/indra/newview/llfloaterregiondebugconsole.h index bc20ea9c4..fdf975c48 100644 --- a/indra/newview/llfloaterregiondebugconsole.h +++ b/indra/newview/llfloaterregiondebugconsole.h @@ -34,11 +34,13 @@ #include "llhttpclient.h" class LLTextEditor; +class AIHTTPTimeoutPolicy; +extern AIHTTPTimeoutPolicy floaterRegionDebugConsole_timeout; typedef boost::signals2::signal< void (const std::string& output)> console_reply_signal_t; -class LLFloaterRegionDebugConsole : public LLFloater, public LLHTTPClient::Responder, public LLSingleton +class LLFloaterRegionDebugConsole : public LLFloater, public LLSingleton { public: LLFloaterRegionDebugConsole(); diff --git a/indra/newview/llfloaterregioninfo.cpp b/indra/newview/llfloaterregioninfo.cpp index 8395f842c..0a6b234f0 100644 --- a/indra/newview/llfloaterregioninfo.cpp +++ b/indra/newview/llfloaterregioninfo.cpp @@ -91,6 +91,9 @@ const S32 TERRAIN_TEXTURE_COUNT = 4; const S32 CORNER_COUNT = 4; +class AIHTTPTimeoutPolicy; +extern AIHTTPTimeoutPolicy estateChangeInfoResponder_timeout; + ///---------------------------------------------------------------------------- /// Local class declaration ///---------------------------------------------------------------------------- @@ -2307,7 +2310,7 @@ void LLPanelEstateInfo::getEstateOwner() } */ -class LLEstateChangeInfoResponder : public LLHTTPClient::Responder +class LLEstateChangeInfoResponder : public LLHTTPClient::ResponderWithResult { public: LLEstateChangeInfoResponder(void* userdata) : mpPanel((LLPanelEstateInfo*)userdata) {}; @@ -2325,6 +2328,9 @@ public: llinfos << "LLEstateChangeInfoResponder::error " << status << ": " << reason << llendl; } + + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return estateChangeInfoResponder_timeout; } + private: LLPanelEstateInfo* mpPanel; }; diff --git a/indra/newview/llfloaterreporter.cpp b/indra/newview/llfloaterreporter.cpp index 5888bb80f..5de9bf503 100644 --- a/indra/newview/llfloaterreporter.cpp +++ b/indra/newview/llfloaterreporter.cpp @@ -96,6 +96,9 @@ const U32 INCLUDE_SCREENSHOT = 0x01 << 0; +class AIHTTPTimeoutPolicy; +extern AIHTTPTimeoutPolicy userReportResponder_timeout; + //----------------------------------------------------------------------------- // Globals //----------------------------------------------------------------------------- @@ -855,10 +858,10 @@ public: } }; -class LLUserReportResponder : public LLHTTPClient::Responder +class LLUserReportResponder : public LLHTTPClient::ResponderWithResult { public: - LLUserReportResponder(): LLHTTPClient::Responder() {} + LLUserReportResponder() { } void error(U32 status, const std::string& reason) { @@ -870,6 +873,7 @@ public: // we don't care about what the server returns LLUploadDialog::modalUploadFinished(); } + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return userReportResponder_timeout; } }; void LLFloaterReporter::sendReportViaCaps(std::string url, std::string sshot_url, const LLSD& report) @@ -884,7 +888,7 @@ void LLFloaterReporter::sendReportViaCaps(std::string url, std::string sshot_url else { // screenshot not wanted or we don't have screenshot cap - LLHTTPClient::post(url, report, new LLUserReportResponder()); + LLHTTPClient::post(url, report, new LLUserReportResponder); } } diff --git a/indra/newview/llfloaterteleport.cpp b/indra/newview/llfloaterteleport.cpp index a4a88bfd0..04ca6d499 100644 --- a/indra/newview/llfloaterteleport.cpp +++ b/indra/newview/llfloaterteleport.cpp @@ -55,6 +55,8 @@ #include "llworld.h" #include "pipeline.h" // for gPipeline +class AIHTTPTimeoutPolicy; +extern AIHTTPTimeoutPolicy placeAvatarTeleportResponder_timeout; // OGPX HTTP responder for PlaceAvatar cap used for Teleport // very similar to the responder in Login, but not as many fields are returned in the TP version @@ -62,7 +64,7 @@ // OGPX TODO: mResult should not get replaced in result(), instead // should replace individual LLSD fields in mResult. class LLPlaceAvatarTeleportResponder : - public LLHTTPClient::Responder + public LLHTTPClient::ResponderWithResult { public: LLPlaceAvatarTeleportResponder() @@ -218,6 +220,8 @@ public: // gViewerWindow->setShowProgress(FALSE); } + + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return placeAvatarTeleportResponder_timeout; } }; // Statics diff --git a/indra/newview/llfloatertos.cpp b/indra/newview/llfloatertos.cpp index d83dab9fd..e5237e8f2 100644 --- a/indra/newview/llfloatertos.cpp +++ b/indra/newview/llfloatertos.cpp @@ -54,6 +54,8 @@ #include "llvfile.h" #include "message.h" +class AIHTTPTimeoutPolicy; +extern AIHTTPTimeoutPolicy iamHere_timeout; // static LLFloaterTOS* LLFloaterTOS::sInstance = NULL; @@ -90,7 +92,7 @@ LLFloaterTOS::LLFloaterTOS(ETOSType type, const std::string & message) // helper class that trys to download a URL from a web site and calls a method // on parent class indicating if the web server is working or not -class LLIamHere : public LLHTTPClient::Responder +class LLIamHere : public LLHTTPClient::ResponderWithResult { private: LLIamHere( LLFloaterTOS* parent ) : @@ -128,6 +130,8 @@ class LLIamHere : public LLHTTPClient::Responder mParent->setSiteIsAlive( alive ); } }; + + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return iamHere_timeout; } }; // this is global and not a class member to keep crud out of the header file diff --git a/indra/newview/llfloaterurlentry.cpp b/indra/newview/llfloaterurlentry.cpp index c56e49206..d8d4f5e84 100644 --- a/indra/newview/llfloaterurlentry.cpp +++ b/indra/newview/llfloaterurlentry.cpp @@ -45,12 +45,15 @@ #include "llviewerwindow.h" #include "llhttpclient.h" +class AIHTTPTimeoutPolicy; +extern AIHTTPTimeoutPolicy mediaTypeResponder_timeout; + static LLFloaterURLEntry* sInstance = NULL; // Move this to its own file. // helper class that tries to download a URL from a web site and calls a method // on the Panel Land Media and to discover the MIME type -class LLMediaTypeResponder : public LLHTTPClient::Responder +class LLMediaTypeResponder : public LLHTTPClient::ResponderIgnoreBody { public: LLMediaTypeResponder( const LLHandle parent ) : @@ -59,10 +62,13 @@ public: LLHandle mParent; + virtual bool needsHeaders(void) const { return true; } - virtual void completedHeader(U32 status, const std::string& reason, const LLSD& content) + virtual void completedHeaders(U32 status, std::string const& reason, AIHTTPReceivedHeaders const& headers) { - std::string media_type = content["content-type"].asString(); + std::string media_type; + bool content_type_found = headers.getFirstValue("content-type", media_type); + llassert_always(content_type_found); std::string::size_type idx1 = media_type.find_first_of(";"); std::string mime_type = media_type.substr(0, idx1); completeAny(status, mime_type); @@ -82,6 +88,8 @@ public: if ( floater_url_entry ) floater_url_entry->headerFetchComplete( status, resolved_mime_type ); } + + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return mediaTypeResponder_timeout; } }; //----------------------------------------------------------------------------- diff --git a/indra/newview/llhomelocationresponder.h b/indra/newview/llhomelocationresponder.h index 3a1d8ebfe..c9d098740 100644 --- a/indra/newview/llhomelocationresponder.h +++ b/indra/newview/llhomelocationresponder.h @@ -38,11 +38,15 @@ /* File Inclusions */ #include "llhttpclient.h" +class AIHTTPTimeoutPolicy; +extern AIHTTPTimeoutPolicy homeLocationResponder_timeout; + /* Typedef, Enum, Class, Struct, etc. */ -class LLHomeLocationResponder : public LLHTTPClient::Responder +class LLHomeLocationResponder : public LLHTTPClient::ResponderWithResult { virtual void result( const LLSD& content ); virtual void error( U32 status, const std::string& reason ); + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return homeLocationResponder_timeout; } }; #endif diff --git a/indra/newview/llimpanel.cpp b/indra/newview/llimpanel.cpp index 4ba053a40..bd8ec64be 100644 --- a/indra/newview/llimpanel.cpp +++ b/indra/newview/llimpanel.cpp @@ -84,6 +84,11 @@ #include "rlvhandler.h" // [/RLVa:KB] +class AIHTTPTimeoutPolicy; +extern AIHTTPTimeoutPolicy startConferenceChatResponder_timeout; +extern AIHTTPTimeoutPolicy voiceCallCapResponder_timeout; +extern AIHTTPTimeoutPolicy sessionInviteResponder_timeout; + // // Constants // @@ -179,7 +184,7 @@ void start_deprecated_conference_chat( delete[] bucket; } -class LLStartConferenceChatResponder : public LLHTTPClient::Responder +class LLStartConferenceChatResponder : public LLHTTPClient::ResponderIgnoreBody { public: LLStartConferenceChatResponder( @@ -214,6 +219,8 @@ public: //the possible different language translations } + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return startConferenceChatResponder_timeout; } + private: LLUUID mTempSessionID; LLUUID mCreatorID; @@ -294,13 +301,14 @@ bool send_start_session_messages( return false; } -class LLVoiceCallCapResponder : public LLHTTPClient::Responder +class LLVoiceCallCapResponder : public LLHTTPClient::ResponderWithResult { public: LLVoiceCallCapResponder(const LLUUID& session_id) : mSessionID(session_id) {}; virtual void error(U32 status, const std::string& reason); // called with bad status codes virtual void result(const LLSD& content); + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return voiceCallCapResponder_timeout; } private: LLUUID mSessionID; @@ -1550,7 +1558,7 @@ void LLFloaterIMPanel::draw() LLFloater::draw(); } -class LLSessionInviteResponder : public LLHTTPClient::Responder +class LLSessionInviteResponder : public LLHTTPClient::ResponderIgnoreBody { public: LLSessionInviteResponder(const LLUUID& session_id) @@ -1564,6 +1572,8 @@ public: //throw something back to the viewer here? } + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return sessionInviteResponder_timeout; } + private: LLUUID mSessionID; }; diff --git a/indra/newview/llimview.cpp b/indra/newview/llimview.cpp index 9783badf7..d6dabcdae 100644 --- a/indra/newview/llimview.cpp +++ b/indra/newview/llimview.cpp @@ -77,6 +77,9 @@ #include "rlvhandler.h" // [/RLVa:KB] +class AIHTTPTimeoutPolicy; +extern AIHTTPTimeoutPolicy viewerChatterBoxInvitationAcceptResponder_timeout; + // // Globals // @@ -91,9 +94,8 @@ LLIMMgr* gIMMgr = NULL; //{ // return (LLStringUtil::compareDict( a->mName, b->mName ) < 0); //} - class LLViewerChatterBoxInvitationAcceptResponder : - public LLHTTPClient::Responder + public LLHTTPClient::ResponderWithResult { public: LLViewerChatterBoxInvitationAcceptResponder( @@ -175,6 +177,8 @@ public: } } + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return viewerChatterBoxInvitationAcceptResponder_timeout; } + private: LLUUID mSessionID; LLIMMgr::EInvitationType mInvitiationType; diff --git a/indra/newview/llinventorymodel.cpp b/indra/newview/llinventorymodel.cpp index 50234039e..0b369df09 100644 --- a/indra/newview/llinventorymodel.cpp +++ b/indra/newview/llinventorymodel.cpp @@ -60,6 +60,9 @@ #include "process.h" #endif +class AIHTTPTimeoutPolicy; +extern AIHTTPTimeoutPolicy createInventoryCategoryResponder_timeout; + // Increment this if the inventory contents change in a non-backwards-compatible way. // For viewers with link items support, former caches are incorrect. const S32 LLInventoryModel::sCurrentInvCacheVersion = 2; @@ -467,7 +470,7 @@ LLUUID LLInventoryModel::findCategoryByName(std::string name) return LLUUID::null; } -class LLCreateInventoryCategoryResponder : public LLHTTPClient::Responder +class LLCreateInventoryCategoryResponder : public LLHTTPClient::ResponderWithResult { public: LLCreateInventoryCategoryResponder(LLInventoryModel* model, @@ -511,6 +514,8 @@ public: } + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return createInventoryCategoryResponder_timeout; } + private: void (*mCallback)(const LLSD&, void*); void* mData; @@ -562,14 +567,15 @@ LLUUID LLInventoryModel::createNewCategory(const LLUUID& parent_id, { //Let's use the new capability. - LLSD request, body; +// LLSD request; + LLSD body; body["folder_id"] = id; body["parent_id"] = parent_id; body["type"] = (LLSD::Integer) preferred_type; body["name"] = name; - request["message"] = "CreateInventoryCategory"; - request["payload"] = body; +// request["message"] = "CreateInventoryCategory"; +// request["payload"] = body; // viewer_region->getCapAPI().post(request); LLHTTPClient::post( diff --git a/indra/newview/llinventorymodel.h b/indra/newview/llinventorymodel.h index a7506e19a..b22efb9eb 100644 --- a/indra/newview/llinventorymodel.h +++ b/indra/newview/llinventorymodel.h @@ -43,8 +43,10 @@ #include #include -class LLInventoryObserver; +class AIHTTPTimeoutPolicy; +extern AIHTTPTimeoutPolicy fetchInventoryResponder_timeout; +class LLInventoryObserver; class LLInventoryObject; class LLInventoryItem; class LLInventoryCategory; @@ -55,7 +57,6 @@ class LLViewerInventoryCategory; class LLMessageSystem; class LLInventoryCollectFunctor; - //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // LLInventoryModel // @@ -82,12 +83,13 @@ public: typedef LLDynamicArray > item_array_t; typedef std::set changed_items_t; - class fetchInventoryResponder : public LLHTTPClient::Responder + class fetchInventoryResponder : public LLHTTPClient::ResponderWithResult { public: fetchInventoryResponder(const LLSD& request_sd) : mRequestSD(request_sd) {}; void result(const LLSD& content); void error(U32 status, const std::string& reason); + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return fetchInventoryResponder_timeout; } protected: LLSD mRequestSD; }; diff --git a/indra/newview/llinventorymodelbackgroundfetch.cpp b/indra/newview/llinventorymodelbackgroundfetch.cpp index 5e855e144..d1ed71e8b 100644 --- a/indra/newview/llinventorymodelbackgroundfetch.cpp +++ b/indra/newview/llinventorymodelbackgroundfetch.cpp @@ -38,6 +38,10 @@ #include "llviewerregion.h" #include "llviewerwindow.h" +class AIHTTPTimeoutPolicy; +extern AIHTTPTimeoutPolicy inventoryModelFetchDescendentsResponder_timeout; +extern AIHTTPTimeoutPolicy inventoryModelFetchItemResponder_timeout; + const F32 MAX_TIME_FOR_SINGLE_FETCH = 10.f; const S32 MAX_FETCH_RETRIES = 10; @@ -374,6 +378,7 @@ public: LLInventoryModelFetchItemResponder(const LLSD& request_sd) : LLInventoryModel::fetchInventoryResponder(request_sd) {}; void result(const LLSD& content); void error(U32 status, const std::string& reason); + AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return inventoryModelFetchItemResponder_timeout; } }; void LLInventoryModelFetchItemResponder::result( const LLSD& content ) @@ -388,8 +393,7 @@ void LLInventoryModelFetchItemResponder::error( U32 status, const std::string& r LLInventoryModelBackgroundFetch::instance().incrFetchCount(-1); } - -class LLInventoryModelFetchDescendentsResponder: public LLHTTPClient::Responder +class LLInventoryModelFetchDescendentsResponder: public LLHTTPClient::ResponderWithResult { public: LLInventoryModelFetchDescendentsResponder(const LLSD& request_sd, uuid_vec_t recursive_cats) : @@ -399,6 +403,8 @@ class LLInventoryModelFetchDescendentsResponder: public LLHTTPClient::Responder //LLInventoryModelFetchDescendentsResponder() {}; void result(const LLSD& content); void error(U32 status, const std::string& reason); + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return inventoryModelFetchDescendentsResponder_timeout; } + protected: BOOL getIsRecursive(const LLUUID& cat_id) const; private: @@ -699,14 +705,14 @@ void LLInventoryModelBackgroundFetch::bulkFetch() if (folder_request_body["folders"].size()) { LLInventoryModelFetchDescendentsResponder *fetcher = new LLInventoryModelFetchDescendentsResponder(folder_request_body, recursive_cats); - LLHTTPClient::post(url, folder_request_body, fetcher, 300.0); + LLHTTPClient::post(url, folder_request_body, fetcher); } if (folder_request_body_lib["folders"].size()) { std::string url_lib = gAgent.getRegion()->getCapability("FetchLibDescendents2"); LLInventoryModelFetchDescendentsResponder *fetcher = new LLInventoryModelFetchDescendentsResponder(folder_request_body_lib, recursive_cats); - LLHTTPClient::post(url_lib, folder_request_body_lib, fetcher, 300.0); + LLHTTPClient::post(url_lib, folder_request_body_lib, fetcher); } } if (item_count) diff --git a/indra/newview/llmapresponders.h b/indra/newview/llmapresponders.h index b6fb8e551..8b287c9fb 100644 --- a/indra/newview/llmapresponders.h +++ b/indra/newview/llmapresponders.h @@ -35,9 +35,13 @@ #include "llhttpclient.h" -class LLMapLayerResponder : public LLHTTPClient::Responder +class AIHTTPTimeoutPolicy; +extern AIHTTPTimeoutPolicy mapLayerResponder_timeout; + +class LLMapLayerResponder : public LLHTTPClient::ResponderWithResult { virtual void result(const LLSD& content); + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return mapLayerResponder_timeout; } }; #endif // LL_LLMAPLAYERRESPONDER_H diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index 33439f10e..74f090532 100644 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -35,7 +35,6 @@ #include "llappviewer.h" #include "llbufferstream.h" #include "llcallbacklist.h" -#include "llcurlrequest.h" #include "lldatapacker.h" #include "llfasttimer.h" #if MESH_IMPORT @@ -66,6 +65,7 @@ #include "llinventorymodel.h" #include "llfoldertype.h" #include "llviewerparcelmgr.h" +#include "aicurl.h" #include "boost/lexical_cast.hpp" #ifndef LL_WINDOWS @@ -74,6 +74,15 @@ #include +class AIHTTPTimeoutPolicy; +extern AIHTTPTimeoutPolicy meshHeaderResponder_timeout; +extern AIHTTPTimeoutPolicy meshLODResponder_timeout; +extern AIHTTPTimeoutPolicy meshSkinInfoResponder_timeout; +extern AIHTTPTimeoutPolicy meshDecompositionResponder_timeout; +extern AIHTTPTimeoutPolicy meshPhysicsShapeResponder_timeout; +extern AIHTTPTimeoutPolicy wholeModelFeeResponder_timeout; +extern AIHTTPTimeoutPolicy wholeModelUploadResponder_timeout; + LLMeshRepository gMeshRepo; const U32 MAX_MESH_REQUESTS_PER_SECOND = 100; @@ -201,7 +210,7 @@ S32 LLMeshRepoThread::sActiveHeaderRequests = 0; S32 LLMeshRepoThread::sActiveLODRequests = 0; U32 LLMeshRepoThread::sMaxConcurrentRequests = 1; -class LLMeshHeaderResponder : public LLCurl::Responder +class LLMeshHeaderResponder : public LLHTTPClient::ResponderWithCompleted { public: LLVolumeParams mMeshParams; @@ -221,9 +230,10 @@ public: const LLChannelDescriptors& channels, const LLIOPipe::buffer_ptr_t& buffer); + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return meshHeaderResponder_timeout; } }; -class LLMeshLODResponder : public LLCurl::Responder +class LLMeshLODResponder : public LLHTTPClient::ResponderWithCompleted { public: LLVolumeParams mMeshParams; @@ -246,9 +256,10 @@ public: const LLChannelDescriptors& channels, const LLIOPipe::buffer_ptr_t& buffer); + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return meshLODResponder_timeout; } }; -class LLMeshSkinInfoResponder : public LLCurl::Responder +class LLMeshSkinInfoResponder : public LLHTTPClient::ResponderWithCompleted { public: LLUUID mMeshID; @@ -264,9 +275,10 @@ public: const LLChannelDescriptors& channels, const LLIOPipe::buffer_ptr_t& buffer); + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return meshSkinInfoResponder_timeout; } }; -class LLMeshDecompositionResponder : public LLCurl::Responder +class LLMeshDecompositionResponder : public LLHTTPClient::ResponderWithCompleted { public: LLUUID mMeshID; @@ -282,9 +294,10 @@ public: const LLChannelDescriptors& channels, const LLIOPipe::buffer_ptr_t& buffer); + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return meshDecompositionResponder_timeout; } }; -class LLMeshPhysicsShapeResponder : public LLCurl::Responder +class LLMeshPhysicsShapeResponder : public LLHTTPClient::ResponderWithCompleted { public: LLUUID mMeshID; @@ -300,6 +313,7 @@ public: const LLChannelDescriptors& channels, const LLIOPipe::buffer_ptr_t& buffer); + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return meshPhysicsShapeResponder_timeout; } }; #if MESH_IMPORT @@ -352,7 +366,7 @@ void log_upload_error(S32 status, const LLSD& content, std::string stage, std::s } } -class LLWholeModelFeeResponder: public LLCurl::Responder +class LLWholeModelFeeResponder: public LLHTTPClient::ResponderWithCompleted { LLMeshUploadThread* mThread; LLSD mModelData; @@ -403,9 +417,10 @@ public: } } + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return wholeModelFeeResponder_timeout; } }; -class LLWholeModelUploadResponder: public LLCurl::Responder +class LLWholeModelUploadResponder: public LLHTTPClient::ResponderWithCompleted { LLMeshUploadThread* mThread; LLSD mModelData; @@ -459,6 +474,8 @@ public: } } } + + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return wholeModelUploadResponder_timeout; } }; #endif //MESH_IMPORT @@ -482,7 +499,6 @@ LLMeshRepoThread::~LLMeshRepoThread() void LLMeshRepoThread::run() { - mCurlRequest = new AICurlInterface::Request; #if MESH_IMPORT LLCDResult res = LLConvexDecomposition::initThread(); if (res != LLCD_OK) @@ -637,9 +653,6 @@ void LLMeshRepoThread::run() llwarns << "convex decomposition unable to be quit" << llendl; } #endif //MESH_IMPORT - - delete mCurlRequest; - mCurlRequest = NULL; } void LLMeshRepoThread::loadMeshSkinInfo(const LLUUID& mesh_id) @@ -766,15 +779,13 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id) } //reading from VFS failed for whatever reason, fetch from sim - std::vector headers; - headers.push_back("Accept: application/octet-stream"); + AIHTTPHeaders headers("Accept", "application/octet-stream"); std::string http_url = constructUrl(mesh_id); if (!http_url.empty()) { - // This might throw AICurlNoEasyHandle. - mCurlRequest->getByteRange(http_url, headers, offset, size, - new LLMeshSkinInfoResponder(mesh_id, offset, size)); + LLHTTPClient::getByteRange(http_url, offset, size, + new LLMeshSkinInfoResponder(mesh_id, offset, size), headers); LLMeshRepository::sHTTPRequestCount++; } } @@ -841,15 +852,14 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id) } //reading from VFS failed for whatever reason, fetch from sim - std::vector headers; - headers.push_back("Accept: application/octet-stream"); + AIHTTPHeaders headers("Accept", "application/octet-stream"); std::string http_url = constructUrl(mesh_id); if (!http_url.empty()) { // This might throw AICurlNoEasyHandle. - mCurlRequest->getByteRange(http_url, headers, offset, size, - new LLMeshDecompositionResponder(mesh_id, offset, size)); + LLHTTPClient::getByteRange(http_url, offset, size, + new LLMeshDecompositionResponder(mesh_id, offset, size), headers); LLMeshRepository::sHTTPRequestCount++; } } @@ -916,15 +926,14 @@ bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id) } //reading from VFS failed for whatever reason, fetch from sim - std::vector headers; - headers.push_back("Accept: application/octet-stream"); + AIHTTPHeaders headers("Accept", "application/octet-stream"); std::string http_url = constructUrl(mesh_id); if (!http_url.empty()) { // This might throw AICurlNoEasyHandle. - mCurlRequest->getByteRange(http_url, headers, offset, size, - new LLMeshPhysicsShapeResponder(mesh_id, offset, size)); + LLHTTPClient::getByteRange(http_url, offset, size, + new LLMeshPhysicsShapeResponder(mesh_id, offset, size), headers); LLMeshRepository::sHTTPRequestCount++; } } @@ -966,8 +975,7 @@ bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params, U32& c } //either cache entry doesn't exist or is corrupt, request header from simulator - std::vector headers; - headers.push_back("Accept: application/octet-stream"); + AIHTTPHeaders headers("Accept", "application/octet-stream"); std::string http_url = constructUrl(mesh_params.getSculptID()); if (!http_url.empty()) @@ -976,7 +984,7 @@ bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params, U32& c //within the first 4KB //NOTE -- this will break of headers ever exceed 4KB // This might throw AICurlNoEasyHandle. - mCurlRequest->getByteRange(http_url, headers, 0, 4096, new LLMeshHeaderResponder(mesh_params)); + LLHTTPClient::getByteRange(http_url, 0, 4096, new LLMeshHeaderResponder(mesh_params), headers); LLMeshRepository::sHTTPRequestCount++; count++; } @@ -1031,15 +1039,14 @@ void LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, } //reading from VFS failed for whatever reason, fetch from sim - std::vector headers; - headers.push_back("Accept: application/octet-stream"); + AIHTTPHeaders headers("Accept", "application/octet-stream"); std::string http_url = constructUrl(mesh_id); if (!http_url.empty()) { // This might throw AICurlNoEasyHandle. - mCurlRequest->getByteRange(constructUrl(mesh_id), headers, offset, size, - new LLMeshLODResponder(mesh_params, lod, offset, size)); + LLHTTPClient::getByteRange(constructUrl(mesh_id), offset, size, + new LLMeshLODResponder(mesh_params, lod, offset, size), headers); LLMeshRepository::sHTTPRequestCount++; count++; } @@ -1275,9 +1282,7 @@ LLMeshUploadThread::LLMeshUploadThread(LLMeshUploadThread::instance_list& data, mUploadSkin = upload_skin; mUploadJoints = upload_joints; mMutex = new LLMutex(); - mCurlRequest = NULL; mPendingUploads = 0; - mFinished = false; mOrigin = gAgent.getPositionAgent(); mHost = gAgent.getRegionHost(); @@ -1603,8 +1608,6 @@ void LLMeshUploadThread::generateHulls() void LLMeshUploadThread::doWholeModelUpload() { - mCurlRequest = new AICurlInterface::Request(); - if (mWholeModelUploadURL.empty()) { llinfos << "unable to upload, fee request failed" << llendl; @@ -1617,33 +1620,15 @@ void LLMeshUploadThread::doWholeModelUpload() wholeModelToLLSD(full_model_data, true); LLSD body = full_model_data["asset_resources"]; dump_llsd_to_file(body,make_dump_name("whole_model_body_",dump_num)); - LLCurlRequest::headers_t headers; - // This might throw AICurlNoEasyHandle. - mCurlRequest->post(mWholeModelUploadURL, headers, body, - new LLWholeModelUploadResponder(this, full_model_data, mUploadObserverHandle), mMeshUploadTimeOut); - do - { - mCurlRequest->process(); // FIXME: This function does not exist anymore. The post() gets CPU time from AICurlEasyRequestStateMachine. - // Therefore, if we do not want to continue here unless this upload is done... no wait, that would - // be blocking and we don't want blocking... - //sleep for 10ms to prevent eating a whole core - apr_sleep(10000); - } while (mCurlRequest->getQueued() > 0); + LLHTTPClient::post(mWholeModelUploadURL, body, + new LLWholeModelUploadResponder(this, full_model_data, mUploadObserverHandle)); } - - delete mCurlRequest; - mCurlRequest = NULL; - - // Currently a no-op. - mFinished = true; } void LLMeshUploadThread::requestWholeModelFee() { dump_num++; - mCurlRequest = new AICurlInterface::Request; - generateHulls(); LLSD model_data; @@ -1651,23 +1636,9 @@ void LLMeshUploadThread::requestWholeModelFee() dump_llsd_to_file(model_data,make_dump_name("whole_model_fee_request_",dump_num)); mPendingUploads++; - LLCurlRequest::headers_t headers; // This might throw AICurlNoEasyHandle. - mCurlRequest->post(mWholeModelFeeCapability, headers, model_data, - new LLWholeModelFeeResponder(this,model_data, mFeeObserverHandle), mMeshUploadTimeOut); - - do - { - mCurlRequest->process(); - //sleep for 10ms to prevent eating a whole core - apr_sleep(10000); - } while (mCurlRequest->getQueued() > 0); - - delete mCurlRequest; - mCurlRequest = NULL; - - // Currently a no-op. - mFinished = true; + LLHTTPClient::post(mWholeModelFeeCapability, model_data, + new LLWholeModelFeeResponder(this, model_data, mFeeObserverHandle)); } #endif //MESH_IMPORT @@ -1799,7 +1770,7 @@ void LLMeshLODResponder::completedRaw(U32 status, const std::string& reason, S32 data_size = buffer->countAfter(channels.in(), NULL); - if (status < 200 || status > 400) + if (status < 200 || status >= 400) { llwarns << status << ": " << reason << llendl; } @@ -1853,7 +1824,7 @@ void LLMeshSkinInfoResponder::completedRaw(U32 status, const std::string& reason { S32 data_size = buffer->countAfter(channels.in(), NULL); - if (status < 200 || status > 400) + if (status < 200 || status >= 400) { llwarns << status << ": " << reason << llendl; } @@ -1907,7 +1878,7 @@ void LLMeshDecompositionResponder::completedRaw(U32 status, const std::string& r { S32 data_size = buffer->countAfter(channels.in(), NULL); - if (status < 200 || status > 400) + if (status < 200 || status >= 400) { llwarns << status << ": " << reason << llendl; } @@ -1961,7 +1932,7 @@ void LLMeshPhysicsShapeResponder::completedRaw(U32 status, const std::string& re { S32 data_size = buffer->countAfter(channels.in(), NULL); - if (status < 200 || status > 400) + if (status < 200 || status >= 400) { llwarns << status << ": " << reason << llendl; } @@ -2013,7 +1984,7 @@ void LLMeshHeaderResponder::completedRaw(U32 status, const std::string& reason, const LLChannelDescriptors& channels, const LLIOPipe::buffer_ptr_t& buffer) { - if (status < 200 || status > 400) + if (status < 200 || status >= 400) { //llwarns // << "Header responder failed with status: " diff --git a/indra/newview/llmeshrepository.h b/indra/newview/llmeshrepository.h index 5d225cfe4..2047bf670 100644 --- a/indra/newview/llmeshrepository.h +++ b/indra/newview/llmeshrepository.h @@ -32,7 +32,6 @@ #include "lluuid.h" #include "llviewertexture.h" #include "llvolume.h" -#include "llcurlrequest.h" #if MESH_IMPORT #define LLCONVEXDECOMPINTER_STATIC 1 @@ -246,7 +245,6 @@ public: static S32 sActiveLODRequests; static U32 sMaxConcurrentRequests; - AICurlInterface::Request* mCurlRequest; LLMutex* mMutex; LLMutex* mHeaderMutex; LLCondition* mSignal; @@ -413,10 +411,8 @@ public: instance_map mInstance; LLMutex* mMutex; - LLCurlRequest* mCurlRequest; S32 mPendingUploads; LLVector3 mOrigin; - bool mFinished; bool mUploadTextures; bool mUploadSkin; bool mUploadJoints; @@ -431,7 +427,6 @@ public: LLHandle fee_observer= (LLHandle()), LLHandle upload_observer = (LLHandle())); ~LLMeshUploadThread(); - bool finished() { return mFinished; } virtual void run(); void preStart(); void discard() ; diff --git a/indra/newview/llpanelgroupvoting.cpp b/indra/newview/llpanelgroupvoting.cpp index b4bee1522..89134c437 100644 --- a/indra/newview/llpanelgroupvoting.cpp +++ b/indra/newview/llpanelgroupvoting.cpp @@ -52,6 +52,10 @@ #include "llviewerwindow.h" #include "llviewerregion.h" +class AIHTTPTimeoutPolicy; +extern AIHTTPTimeoutPolicy startGroupVoteResponder_timeout; +extern AIHTTPTimeoutPolicy groupProposalBallotResponder_timeout; + class LLPanelGroupVoting::impl { public: @@ -680,7 +684,7 @@ void LLPanelGroupVoting::handleFailure( } } -class LLStartGroupVoteResponder : public LLHTTPClient::Responder +class LLStartGroupVoteResponder : public LLHTTPClient::ResponderWithResult { public: LLStartGroupVoteResponder(const LLUUID& group_id) @@ -705,11 +709,15 @@ public: LLPanelGroupVoting::handleFailure(mGroupID); } + + //Return our timeout policy. + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return startGroupVoteResponder_timeout; } + private: LLUUID mGroupID; }; -class LLGroupProposalBallotResponder : public LLHTTPClient::Responder +class LLGroupProposalBallotResponder : public LLHTTPClient::ResponderWithResult { public: LLGroupProposalBallotResponder(const LLUUID& group_id) @@ -735,6 +743,10 @@ public: LLPanelGroupVoting::handleFailure(mGroupID); } + + //Return out timeout policy. + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return groupProposalBallotResponder_timeout; } + private: LLUUID mGroupID; }; @@ -781,8 +793,7 @@ void LLPanelGroupVoting::impl::sendStartGroupProposal() LLHTTPClient::post( url, body, - new LLStartGroupVoteResponder(mGroupID), - 300); + new LLStartGroupVoteResponder(mGroupID)); } else { //DEPRECATED!!!!!!! This is a fallback just in case our backend cap is not there. Delete this block ASAP! @@ -828,8 +839,7 @@ void LLPanelGroupVoting::impl::sendGroupProposalBallot(const std::string& vote) LLHTTPClient::post( url, body, - new LLGroupProposalBallotResponder(mGroupID), - 300); + new LLGroupProposalBallotResponder(mGroupID)); } else { //DEPRECATED!!!!!!! This is a fallback just in case our backend cap is not there. Delete this block ASAP! diff --git a/indra/newview/llpanellogin.cpp b/indra/newview/llpanellogin.cpp index 3483f2577..cc20ab576 100644 --- a/indra/newview/llpanellogin.cpp +++ b/indra/newview/llpanellogin.cpp @@ -95,6 +95,9 @@ #define USE_VIEWER_AUTH 0 +class AIHTTPTimeoutPolicy; +extern AIHTTPTimeoutPolicy iamHereLogin_timeout; + const S32 BLACK_BORDER_HEIGHT = 160; const S32 MAX_PASSWORD = 16; @@ -111,7 +114,7 @@ static bool nameSplit(const std::string& full, std::string& first, std::string& if (fragments.size() == 1) { if (gHippoGridManager->getConnectedGrid()->isSecondLife()) - last = "resident"; + last = "Resident"; else last = ""; } @@ -120,8 +123,8 @@ static bool nameSplit(const std::string& full, std::string& first, std::string& return (fragments.size() <= 2); } -static std::string nameJoin(const std::string& first,const std::string& last) { - if (last.empty() || boost::algorithm::iequals(last, "resident")) +static std::string nameJoin(const std::string& first,const std::string& last, bool strip_resident) { + if (last.empty() || (strip_resident && boost::algorithm::iequals(last, "Resident"))) return first; else { if(std::islower(last[0])) @@ -131,15 +134,15 @@ static std::string nameJoin(const std::string& first,const std::string& last) { } } -static std::string getDisplayString(const std::string& first, const std::string& last, const std::string& grid) { +static std::string getDisplayString(const std::string& first, const std::string& last, const std::string& grid, bool is_secondlife) { if(grid == gHippoGridManager->getDefaultGridNick()) - return nameJoin(first, last); + return nameJoin(first, last, is_secondlife); else - return nameJoin(first, last) + " (" + grid + ")"; + return nameJoin(first, last, is_secondlife) + " (" + grid + ")"; } static std::string getDisplayString(const LLSavedLoginEntry& entry) { - return getDisplayString(entry.getFirstName(), entry.getLastName(), entry.getGrid()); + return getDisplayString(entry.getFirstName(), entry.getLastName(), entry.getGrid(), entry.isSecondLife()); } class LLLoginRefreshHandler : public LLCommandHandler @@ -165,7 +168,7 @@ std::string gFullName; // helper class that trys to download a URL from a web site and calls a method // on parent class indicating if the web server is working or not -class LLIamHereLogin : public LLHTTPClient::Responder +class LLIamHereLogin : public LLHTTPClient::ResponderWithCompleted { private: LLIamHereLogin( LLPanelLogin* parent ) : @@ -190,20 +193,13 @@ class LLIamHereLogin : public LLHTTPClient::Responder const LLChannelDescriptors& channels, const LLIOPipe::buffer_ptr_t& buffer) { - completed(status, reason, LLSD()); // will call result() or error() + if (mParent) + { + mParent->setSiteIsAlive(200 <= status && status < 300); + } } - - virtual void result( const LLSD& content ) - { - if ( mParent ) - mParent->setSiteIsAlive( true ); - }; - virtual void error( U32 status, const std::string& reason ) - { - if ( mParent ) - mParent->setSiteIsAlive( false ); - }; + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return iamHereLogin_timeout; } }; // this is global and not a class member to keep crud out of the header file @@ -665,8 +661,7 @@ void LLPanelLogin::show(const LLRect &rect, // static void LLPanelLogin::setFields(const std::string& firstname, const std::string& lastname, - const std::string& password, - const LLSavedLogins& login_history) + const std::string& password) { if (!sInstance) { @@ -677,7 +672,7 @@ void LLPanelLogin::setFields(const std::string& firstname, LLComboBox* login_combo = sInstance->getChild("name_combo"); llassert_always(firstname.find(' ') == std::string::npos); - login_combo->setLabel(nameJoin(firstname, lastname)); + login_combo->setLabel(nameJoin(firstname, lastname, false)); // Max "actual" password length is 16 characters. // Hex digests are always 32 characters. @@ -714,7 +709,7 @@ void LLPanelLogin::setFields(const LLSavedLoginEntry& entry, bool takeFocus) } LLCheckBoxCtrl* remember_pass_check = sInstance->getChild("remember_check"); - std::string fullname = nameJoin(entry.getFirstName(), entry.getLastName()); + std::string fullname = nameJoin(entry.getFirstName(), entry.getLastName(), entry.isSecondLife()); LLComboBox* login_combo = sInstance->getChild("name_combo"); login_combo->setTextEntry(fullname); login_combo->resetTextDirty(); diff --git a/indra/newview/llpanellogin.h b/indra/newview/llpanellogin.h index 3fd697b34..53eb9b52d 100644 --- a/indra/newview/llpanellogin.h +++ b/indra/newview/llpanellogin.h @@ -71,10 +71,10 @@ public: * @param firstname First name value. * @param lastname Last name value. * @param password Password, as plaintext or munged. - * @param login_history Login history object. An empty one can be provided if no history is available. + * @param is_secondlife True if First/Last refer to a SecondLife(tm) account. */ static void setFields(const std::string& firstname, const std::string& lastname, - const std::string& password, const LLSavedLogins& login_history = LLSavedLogins()); + const std::string& password); /** * @brief Set the values of the displayed fields from a populated history entry. diff --git a/indra/newview/llpathfindingmanager.cpp b/indra/newview/llpathfindingmanager.cpp index 48775693a..4f23117cc 100644 --- a/indra/newview/llpathfindingmanager.cpp +++ b/indra/newview/llpathfindingmanager.cpp @@ -56,6 +56,15 @@ #include "llviewerregion.h" #include "llweb.h" +class AIHTTPTimeoutPolicy; +extern AIHTTPTimeoutPolicy navMeshStatusResponder_timeout; +extern AIHTTPTimeoutPolicy navMeshResponder_timeout; +extern AIHTTPTimeoutPolicy agentStateResponder_timeout; +extern AIHTTPTimeoutPolicy navMeshRebakeResponder_timeout; +extern AIHTTPTimeoutPolicy objectLinksetsResponder_timeout; +extern AIHTTPTimeoutPolicy terrainLinksetsResponder_timeout; +extern AIHTTPTimeoutPolicy charactersResponder_timeout; + #define CAP_SERVICE_RETRIEVE_NAVMESH "RetrieveNavMeshSrc" #define CAP_SERVICE_NAVMESH_STATUS "NavMeshGenerationStatus" @@ -101,7 +110,7 @@ LLHTTPRegistration gHTTPRegistrationAgentStateChangeNode // NavMeshStatusResponder //--------------------------------------------------------------------------- -class NavMeshStatusResponder : public LLHTTPClient::Responder +class NavMeshStatusResponder : public LLHTTPClient::ResponderWithResult { public: NavMeshStatusResponder(const std::string &pCapabilityURL, LLViewerRegion *pRegion, bool pIsGetStatusOnly); @@ -109,6 +118,7 @@ public: virtual void result(const LLSD &pContent); virtual void error(U32 pStatus, const std::string& pReason); + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return navMeshStatusResponder_timeout; } protected: @@ -123,7 +133,7 @@ private: // NavMeshResponder //--------------------------------------------------------------------------- -class NavMeshResponder : public LLHTTPClient::Responder +class NavMeshResponder : public LLHTTPClient::ResponderWithResult { public: NavMeshResponder(const std::string &pCapabilityURL, U32 pNavMeshVersion, LLPathfindingNavMeshPtr pNavMeshPtr); @@ -131,6 +141,7 @@ public: virtual void result(const LLSD &pContent); virtual void error(U32 pStatus, const std::string& pReason); + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return navMeshResponder_timeout; } protected: @@ -144,7 +155,7 @@ private: // AgentStateResponder //--------------------------------------------------------------------------- -class AgentStateResponder : public LLHTTPClient::Responder +class AgentStateResponder : public LLHTTPClient::ResponderWithResult { public: AgentStateResponder(const std::string &pCapabilityURL); @@ -152,6 +163,7 @@ public: virtual void result(const LLSD &pContent); virtual void error(U32 pStatus, const std::string& pReason); + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return agentStateResponder_timeout; } protected: @@ -163,7 +175,7 @@ private: //--------------------------------------------------------------------------- // NavMeshRebakeResponder //--------------------------------------------------------------------------- -class NavMeshRebakeResponder : public LLHTTPClient::Responder +class NavMeshRebakeResponder : public LLHTTPClient::ResponderWithResult { public: NavMeshRebakeResponder(const std::string &pCapabilityURL, LLPathfindingManager::rebake_navmesh_callback_t pRebakeNavMeshCallback); @@ -171,6 +183,7 @@ public: virtual void result(const LLSD &pContent); virtual void error(U32 pStatus, const std::string& pReason); + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return navMeshRebakeResponder_timeout; } protected: @@ -222,8 +235,7 @@ typedef boost::shared_ptr LinksetsResponderPtr; //--------------------------------------------------------------------------- // ObjectLinksetsResponder //--------------------------------------------------------------------------- - -class ObjectLinksetsResponder : public LLHTTPClient::Responder +class ObjectLinksetsResponder : public LLHTTPClient::ResponderWithResult { public: ObjectLinksetsResponder(const std::string &pCapabilityURL, LinksetsResponderPtr pLinksetsResponsderPtr); @@ -231,6 +243,7 @@ public: virtual void result(const LLSD &pContent); virtual void error(U32 pStatus, const std::string &pReason); + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return objectLinksetsResponder_timeout; } protected: @@ -242,8 +255,7 @@ private: //--------------------------------------------------------------------------- // TerrainLinksetsResponder //--------------------------------------------------------------------------- - -class TerrainLinksetsResponder : public LLHTTPClient::Responder +class TerrainLinksetsResponder : public LLHTTPClient::ResponderWithResult { public: TerrainLinksetsResponder(const std::string &pCapabilityURL, LinksetsResponderPtr pLinksetsResponsderPtr); @@ -251,6 +263,7 @@ public: virtual void result(const LLSD &pContent); virtual void error(U32 pStatus, const std::string &pReason); + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return terrainLinksetsResponder_timeout; } protected: @@ -262,8 +275,7 @@ private: //--------------------------------------------------------------------------- // CharactersResponder //--------------------------------------------------------------------------- - -class CharactersResponder : public LLHTTPClient::Responder +class CharactersResponder : public LLHTTPClient::ResponderWithResult { public: CharactersResponder(const std::string &pCapabilityURL, LLPathfindingManager::request_id_t pRequestId, LLPathfindingManager::object_request_callback_t pCharactersCallback); @@ -271,6 +283,7 @@ public: virtual void result(const LLSD &pContent); virtual void error(U32 pStatus, const std::string &pReason); + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return charactersResponder_timeout; } protected: @@ -778,8 +791,7 @@ void LLAgentStateChangeNode::post(ResponsePtr pResponse, const LLSD &pContext, c // NavMeshStatusResponder //--------------------------------------------------------------------------- -NavMeshStatusResponder::NavMeshStatusResponder(const std::string &pCapabilityURL, LLViewerRegion *pRegion, bool pIsGetStatusOnly) - : LLHTTPClient::Responder(), +NavMeshStatusResponder::NavMeshStatusResponder(const std::string &pCapabilityURL, LLViewerRegion *pRegion, bool pIsGetStatusOnly) : mCapabilityURL(pCapabilityURL), mRegion(pRegion), mRegionUUID(), @@ -812,8 +824,7 @@ void NavMeshStatusResponder::error(U32 pStatus, const std::string& pReason) // NavMeshResponder //--------------------------------------------------------------------------- -NavMeshResponder::NavMeshResponder(const std::string &pCapabilityURL, U32 pNavMeshVersion, LLPathfindingNavMeshPtr pNavMeshPtr) - : LLHTTPClient::Responder(), +NavMeshResponder::NavMeshResponder(const std::string &pCapabilityURL, U32 pNavMeshVersion, LLPathfindingNavMeshPtr pNavMeshPtr) : mCapabilityURL(pCapabilityURL), mNavMeshVersion(pNavMeshVersion), mNavMeshPtr(pNavMeshPtr) @@ -838,9 +849,7 @@ void NavMeshResponder::error(U32 pStatus, const std::string& pReason) // AgentStateResponder //--------------------------------------------------------------------------- -AgentStateResponder::AgentStateResponder(const std::string &pCapabilityURL) -: LLHTTPClient::Responder() -, mCapabilityURL(pCapabilityURL) +AgentStateResponder::AgentStateResponder(const std::string &pCapabilityURL) : mCapabilityURL(pCapabilityURL) { } @@ -866,8 +875,7 @@ void AgentStateResponder::error(U32 pStatus, const std::string &pReason) //--------------------------------------------------------------------------- // navmesh rebake responder //--------------------------------------------------------------------------- -NavMeshRebakeResponder::NavMeshRebakeResponder(const std::string &pCapabilityURL, LLPathfindingManager::rebake_navmesh_callback_t pRebakeNavMeshCallback) - : LLHTTPClient::Responder(), +NavMeshRebakeResponder::NavMeshRebakeResponder(const std::string &pCapabilityURL, LLPathfindingManager::rebake_navmesh_callback_t pRebakeNavMeshCallback) : mCapabilityURL(pCapabilityURL), mRebakeNavMeshCallback(pRebakeNavMeshCallback) { @@ -973,8 +981,7 @@ void LinksetsResponder::sendCallback() // ObjectLinksetsResponder //--------------------------------------------------------------------------- -ObjectLinksetsResponder::ObjectLinksetsResponder(const std::string &pCapabilityURL, LinksetsResponderPtr pLinksetsResponsderPtr) - : LLHTTPClient::Responder(), +ObjectLinksetsResponder::ObjectLinksetsResponder(const std::string &pCapabilityURL, LinksetsResponderPtr pLinksetsResponsderPtr) : mCapabilityURL(pCapabilityURL), mLinksetsResponsderPtr(pLinksetsResponsderPtr) { @@ -998,8 +1005,7 @@ void ObjectLinksetsResponder::error(U32 pStatus, const std::string &pReason) // TerrainLinksetsResponder //--------------------------------------------------------------------------- -TerrainLinksetsResponder::TerrainLinksetsResponder(const std::string &pCapabilityURL, LinksetsResponderPtr pLinksetsResponsderPtr) - : LLHTTPClient::Responder(), +TerrainLinksetsResponder::TerrainLinksetsResponder(const std::string &pCapabilityURL, LinksetsResponderPtr pLinksetsResponsderPtr) : mCapabilityURL(pCapabilityURL), mLinksetsResponsderPtr(pLinksetsResponsderPtr) { @@ -1023,8 +1029,7 @@ void TerrainLinksetsResponder::error(U32 pStatus, const std::string &pReason) // CharactersResponder //--------------------------------------------------------------------------- -CharactersResponder::CharactersResponder(const std::string &pCapabilityURL, LLPathfindingManager::request_id_t pRequestId, LLPathfindingManager::object_request_callback_t pCharactersCallback) - : LLHTTPClient::Responder(), +CharactersResponder::CharactersResponder(const std::string &pCapabilityURL, LLPathfindingManager::request_id_t pRequestId, LLPathfindingManager::object_request_callback_t pCharactersCallback) : mCapabilityURL(pCapabilityURL), mRequestId(pRequestId), mCharactersCallback(pCharactersCallback) diff --git a/indra/newview/llproductinforequest.cpp b/indra/newview/llproductinforequest.cpp index eda8cb659..ff9014deb 100644 --- a/indra/newview/llproductinforequest.cpp +++ b/indra/newview/llproductinforequest.cpp @@ -39,7 +39,10 @@ #include "lltrans.h" #include "llviewerregion.h" -class LLProductInfoRequestResponder : public LLHTTPClient::Responder +class AIHTTPTimeoutPolicy; +extern AIHTTPTimeoutPolicy productInfoRequestResponder_timeout; + +class LLProductInfoRequestResponder : public LLHTTPClient::ResponderWithResult { public: //If we get back a normal response, handle it here @@ -54,6 +57,8 @@ public: llwarns << "LLProductInfoRequest::error(" << status << ": " << reason << ")" << llendl; } + + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return productInfoRequestResponder_timeout; } }; LLProductInfoRequestManager::LLProductInfoRequestManager() : mSkuDescriptions() diff --git a/indra/newview/llremoteparcelrequest.h b/indra/newview/llremoteparcelrequest.h index c92ee3ff3..abc62a10e 100644 --- a/indra/newview/llremoteparcelrequest.h +++ b/indra/newview/llremoteparcelrequest.h @@ -38,7 +38,10 @@ #include "llhttpclient.h" #include "llpanel.h" -class LLRemoteParcelRequestResponder : public LLHTTPClient::Responder +class AIHTTPTimeoutPolicy; +extern AIHTTPTimeoutPolicy remoteParcelRequestResponder_timeout; + +class LLRemoteParcelRequestResponder : public LLHTTPClient::ResponderWithResult { public: LLRemoteParcelRequestResponder(LLHandle place_panel_handle); @@ -46,6 +49,7 @@ public: virtual void result(const LLSD& content); //If we get back an error (not found, etc...), handle it here virtual void error(U32 status, const std::string& reason); + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return remoteParcelRequestResponder_timeout; } protected: LLHandle mPlacePanelHandle; diff --git a/indra/newview/llsavedlogins.cpp b/indra/newview/llsavedlogins.cpp index 819091fc5..991f1aacf 100644 --- a/indra/newview/llsavedlogins.cpp +++ b/indra/newview/llsavedlogins.cpp @@ -145,6 +145,22 @@ const LLSD LLSavedLoginEntry::encryptPassword(const std::string& password) return pwdata; } +bool LLSavedLoginEntry::isSecondLife() const +{ + if (!mEntry.has("grid")) + { + return false; + } + std::string name = mEntry.get("grid").asString(); + HippoGridInfo* grid_info = gHippoGridManager->getGrid(name); + llassert(grid_info); + if (!grid_info) + { + return name.substr(0, 11) == "Second Life"; + } + return grid_info->isSecondLife(); +} + //--------------------------------------------------------------------------- // LLSavedLogins methods //--------------------------------------------------------------------------- diff --git a/indra/newview/llsavedlogins.h b/indra/newview/llsavedlogins.h index 592fedff6..5461bae8e 100644 --- a/indra/newview/llsavedlogins.h +++ b/indra/newview/llsavedlogins.h @@ -119,7 +119,9 @@ public: void setGrid(const std::string& value){ mEntry.insert("grid", LLSD(value)); } - + + bool isSecondLife() const; + LLSD asLLSD() const; static const size_t PASSWORD_HASH_LENGTH = 32; diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index d0a5a6364..91abb8dc5 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -32,6 +32,7 @@ #include "llviewerprecompiledheaders.h" +#include #include "llstartup.h" #if LL_WINDOWS @@ -893,7 +894,7 @@ bool idle_startup() } else { - LLPanelLogin::setFields(firstname, lastname, password, login_history); + LLPanelLogin::setFields(firstname, lastname, password); // gFullName = utf8str_tolower(firstname + " " + lastname); // @@ -1591,10 +1592,11 @@ bool idle_startup() if(process_login_success_response(password)) { std::string name = firstname; - std::string last_name = lastname; - LLStringUtil::toLower(last_name); - if(last_name != "resident") + if (!gHippoGridManager->getCurrentGrid()->isSecondLife() || + !boost::algorithm::iequals(lastname, "Resident")) + { name += " " + lastname; + } gViewerWindow->getWindow()->setTitle(LLAppViewer::instance()->getWindowTitle() + "- " + name); // Pass the user information to the voice chat server interface. gVoiceClient->userAuthorized(firstname, lastname, gAgentID); diff --git a/indra/newview/lltexturefetch.cpp b/indra/newview/lltexturefetch.cpp index 2a4abcf1b..95e43e71d 100644 --- a/indra/newview/lltexturefetch.cpp +++ b/indra/newview/lltexturefetch.cpp @@ -52,6 +52,11 @@ #include "llviewerstats.h" #include "llworld.h" #include "llstartup.h" +#include "llbuffer.h" + +class AIHTTPTimeoutPolicy; +extern AIHTTPTimeoutPolicy HTTPGetResponder_timeout; +extern AIHTTPTimeoutPolicy lcl_responder_timeout; ////////////////////////////////////////////////////////////////////////////// class LLTextureFetchWorker : public LLWorkerClass @@ -148,7 +153,7 @@ public: // void relese() { --mActiveCount; } S32 callbackHttpGet(const LLChannelDescriptors& channels, - const LLIOPipe::buffer_ptr_t& buffer, + const LLHTTPClient::ResponderBase::buffer_ptr_t& buffer, bool partial, bool success); void callbackCacheRead(bool success, LLImageFormatted* image, S32 imagesize, BOOL islocal); @@ -287,8 +292,7 @@ private: }; ////////////////////////////////////////////////////////////////////////////// - -class HTTPGetResponder : public LLCurl::Responder +class HTTPGetResponder : public LLHTTPClient::ResponderWithCompleted { LOG_CLASS(HTTPGetResponder); public: @@ -300,9 +304,11 @@ public: { } + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return HTTPGetResponder_timeout; } + virtual void completedRaw(U32 status, const std::string& reason, const LLChannelDescriptors& channels, - const LLIOPipe::buffer_ptr_t& buffer) + const buffer_ptr_t& buffer) { static LLCachedControl log_to_viewer_log(gSavedSettings,"LogTextureDownloadsToViewerLog"); static LLCachedControl log_to_sim(gSavedSettings,"LogTextureDownloadsToSimulator"); @@ -1317,13 +1323,13 @@ bool LLTextureFetchWorker::doWork(S32 param) LLImageBase::TYPE_AVATAR_BAKE == mType); #endif - // Will call callbackHttpGet when curl request completes - std::vector headers; - headers.push_back("Accept: image/x-j2c"); try { - res = mFetcher->mCurlGetRequest->getByteRange(mUrl, headers, offset, mRequestedSize, - new HTTPGetResponder(mFetcher, mID, LLTimer::getTotalTime(), mRequestedSize, offset, true)); + // Will call callbackHttpGet when curl request completes + AIHTTPHeaders headers("Accept", "image/x-j2c"); + LLHTTPClient::getByteRange(mUrl, offset, mRequestedSize, + new HTTPGetResponder(mFetcher, mID, LLTimer::getTotalTime(), mRequestedSize, offset, true), headers); + res = true; } catch(AICurlNoEasyHandle const& error) { @@ -1805,7 +1811,7 @@ bool LLTextureFetchWorker::processSimulatorPackets() ////////////////////////////////////////////////////////////////////////////// S32 LLTextureFetchWorker::callbackHttpGet(const LLChannelDescriptors& channels, - const LLIOPipe::buffer_ptr_t& buffer, + const LLHTTPClient::ResponderBase::buffer_ptr_t& buffer, bool partial, bool success) { S32 data_size = 0 ; @@ -1989,8 +1995,7 @@ LLTextureFetch::LLTextureFetch(LLTextureCache* cache, LLImageDecodeThread* image mImageDecodeThread(imagedecodethread), mTextureBandwidth(0), mHTTPTextureBits(0), - mTotalHTTPRequests(0), - mCurlGetRequest(NULL) + mTotalHTTPRequests(0) #if HTTP_METRICS ,mQAMode(qa_mode) #endif @@ -2424,26 +2429,9 @@ void LLTextureFetch::shutDownImageDecodeThread() } } -// WORKER THREAD -void LLTextureFetch::startThread() -{ - // Construct mCurlGetRequest from Worker Thread - mCurlGetRequest = new AICurlInterface::Request; -} - -// WORKER THREAD -void LLTextureFetch::endThread() -{ - // Destroy mCurlGetRequest from Worker Thread - delete mCurlGetRequest; - mCurlGetRequest = NULL; -} - // WORKER THREAD void LLTextureFetch::threadedUpdate() { - llassert_always(mCurlGetRequest); - // Limit update frequency const F32 PROCESS_TIME = 0.05f; static LLFrameTimer process_timer; @@ -3028,7 +3016,7 @@ TFReqSendMetrics::doWork(LLTextureFetch * fetcher) * refactoring of the LLQueuedThread usage, these POSTs * could be put in a request object and made more reliable. */ - class lcl_responder : public LLCurl::Responder + class lcl_responder : public LLHTTPClient::ResponderWithResult { public: lcl_responder(LLTextureFetch * fetcher, @@ -3036,8 +3024,7 @@ TFReqSendMetrics::doWork(LLTextureFetch * fetcher) volatile const S32 & live_sequence, volatile bool & reporting_break, volatile bool & reporting_started) - : LLCurl::Responder(), - mFetcher(fetcher), + : mFetcher(fetcher), mExpectedSequence(expected_sequence), mLiveSequence(live_sequence), mReportingBreak(reporting_break), @@ -3071,6 +3058,7 @@ TFReqSendMetrics::doWork(LLTextureFetch * fetcher) mReportingStarted = true; } } + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return lcl_responder_timeout; } private: LLTextureFetch * mFetcher; @@ -3113,9 +3101,7 @@ TFReqSendMetrics::doWork(LLTextureFetch * fetcher) if (! mCapsURL.empty()) { - LLCurlRequest::headers_t headers; - fetcher->getCurlRequest().post(mCapsURL, - headers, + LLHTTPClient::post(mCapsURL, merged_llsd, new lcl_responder(fetcher, report_sequence, diff --git a/indra/newview/lltexturefetch.h b/indra/newview/lltexturefetch.h index 03073d906..b0caf2619 100644 --- a/indra/newview/lltexturefetch.h +++ b/indra/newview/lltexturefetch.h @@ -37,7 +37,6 @@ #include "llimage.h" #include "lluuid.h" #include "llworkerthread.h" -#include "llcurlrequest.h" #include "lltextureinfo.h" #include "llapr.h" @@ -107,8 +106,6 @@ public: LLViewerAssetStats * main_stats); void commandDataBreak(); - AICurlInterface::Request& getCurlRequest() { return *mCurlGetRequest; } - bool isQAMode() const { return mQAMode; } // Curl POST counter maintenance @@ -128,8 +125,6 @@ protected: private: void sendRequestListToSimulators(); - /*virtual*/ void startThread(void); - /*virtual*/ void endThread(void); /*virtual*/ void threadedUpdate(void); void commonUpdate(); @@ -178,7 +173,6 @@ private: LLTextureCache* mTextureCache; LLImageDecodeThread* mImageDecodeThread; - AICurlInterface::Request* mCurlGetRequest; // Map of all requests by UUID typedef std::map map_t; diff --git a/indra/newview/lltranslate.cpp b/indra/newview/lltranslate.cpp index 5a537ae95..833926afe 100644 --- a/indra/newview/lltranslate.cpp +++ b/indra/newview/lltranslate.cpp @@ -47,7 +47,7 @@ const char* LLTranslate::m_GoogleURL = "http://ajax.googleapis.com/ajax/services const char* LLTranslate::m_GoogleLangSpec = "&langpair="; float LLTranslate::m_GoogleTimeout = 10; -LLSD LLTranslate::m_Header; +AIHTTPHeaders LLTranslate::m_Header; // These constants are for the GET header. const char* LLTranslate::m_AcceptHeader = "Accept"; const char* LLTranslate::m_AcceptType = "text/plain"; @@ -68,10 +68,10 @@ void LLTranslate::translateMessage(LLHTTPClient::ResponderPtr &result, const std std::string user_agent = gCurrentVersion; // - if (!m_Header.size()) + if (m_Header.empty()) { - m_Header.insert(m_AcceptHeader, LLSD(m_AcceptType)); - m_Header.insert(m_AgentHeader, LLSD(user_agent)); + m_Header.addHeader(m_AcceptHeader, m_AcceptType); + m_Header.addHeader(m_AgentHeader, user_agent); } LLHTTPClient::get(url, result, m_Header, m_GoogleTimeout); diff --git a/indra/newview/lltranslate.h b/indra/newview/lltranslate.h index 5fd85f534..7540dd72c 100644 --- a/indra/newview/lltranslate.h +++ b/indra/newview/lltranslate.h @@ -37,10 +37,13 @@ #include "llbufferstream.h" #include "json/reader.h" +class AIHTTPTimeoutPolicy; +extern AIHTTPTimeoutPolicy translationReceiver_timeout; + class LLTranslate { public : - class TranslationReceiver: public LLHTTPClient::Responder + class TranslationReceiver: public LLHTTPClient::ResponderWithResult { protected: TranslationReceiver(const std::string &fromLang, const std::string &toLang) @@ -63,6 +66,8 @@ public : handleFailure(); } + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return translationReceiver_timeout; } + virtual void completedRaw( U32 status, const std::string& reason, @@ -109,7 +114,7 @@ private: static void stringReplaceAll(std::string& context, const std::string& from, const std::string& to); static BOOL parseGoogleTranslate(const std::string result, std::string &translation, std::string &detectedLanguage); - static LLSD m_Header; + static AIHTTPHeaders m_Header; static const char* m_GoogleURL; static const char* m_GoogleLangSpec; static const char* m_AcceptHeader; diff --git a/indra/newview/lluploadfloaterobservers.h b/indra/newview/lluploadfloaterobservers.h index 0301f2640..85cc7b86a 100644 --- a/indra/newview/lluploadfloaterobservers.h +++ b/indra/newview/lluploadfloaterobservers.h @@ -37,6 +37,9 @@ #include "llhttpclient.h" #include "llui.h" +class AIHTTPTimeoutPolicy; +extern AIHTTPTimeoutPolicy uploadModelPremissionsResponder_timeout; + class LLUploadPermissionsObserver { public: @@ -95,7 +98,7 @@ protected: }; -class LLUploadModelPremissionsResponder : public LLHTTPClient::Responder +class LLUploadModelPremissionsResponder : public LLHTTPClient::ResponderWithResult { public: @@ -105,6 +108,8 @@ public: void result(const LLSD& content); + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return uploadModelPremissionsResponder_timeout; } + private: LLHandle mObserverHandle; }; diff --git a/indra/newview/lluserauth.cpp b/indra/newview/lluserauth.cpp index a10c66fca..a692f9575 100644 --- a/indra/newview/lluserauth.cpp +++ b/indra/newview/lluserauth.cpp @@ -42,8 +42,9 @@ #include "llappviewer.h" #include "llviewerbuild.h" #include "llviewercontrol.h" -#include "llxmlrpctransaction.h" +#include "llxmlrpcresponder.h" #include "llsdutil.h" +#include "llhttpclient.h" #include "stringize.h" // NOTE: MUST include these after otherincludes since queue gets redefined!?!! @@ -73,7 +74,6 @@ static const char* PLATFORM_STRING = "Sol"; LLUserAuth::LLUserAuth() : - mTransaction(NULL), mLastTransferRateBPS(0), mResult(LLSD()) { @@ -87,13 +87,11 @@ LLUserAuth::~LLUserAuth() void LLUserAuth::reset() { - delete mTransaction; - mTransaction = NULL; + mResponder = NULL; mResponses.clear(); mResult.clear(); } - void LLUserAuth::authenticate( const std::string& auth_uri, const std::string& method, @@ -168,15 +166,13 @@ void LLUserAuth::authenticate( // put the parameters on the request XMLRPC_RequestSetData(request, params); - mTransaction = new LLXMLRPCTransaction(auth_uri, request); - - XMLRPC_RequestFree(request, 1); + mResponder = new XMLRPCResponder; + LLHTTPClient::postXMLRPC(auth_uri, request, mResponder); LL_INFOS2("AppInit", "Authentication") << "LLUserAuth::authenticate: uri=" << auth_uri << LL_ENDL; } - // Legacy version of constructor // passwd is already MD5 hashed by the time we get to it. @@ -258,25 +254,25 @@ void LLUserAuth::authenticate( // put the parameters on the request XMLRPC_RequestSetData(request, params); - mTransaction = new LLXMLRPCTransaction(auth_uri, request); + // Post the XML RPC. + mResponder = new XMLRPCResponder; + LLHTTPClient::postXMLRPC(auth_uri, request, mResponder); - XMLRPC_RequestFree(request, 1); - LL_INFOS2("AppInit", "Authentication") << "LLUserAuth::authenticate: uri=" << auth_uri << LL_ENDL; } LLUserAuth::UserAuthcode LLUserAuth::authResponse() { - if (!mTransaction) + if (!mResponder) { return mAuthResponse; } - bool done = mTransaction->is_finished(); + bool done = mResponder->is_finished(); if (!done) { - if (LLXMLRPCTransaction::StatusDownloading == mTransaction->status(0)) + if (mResponder->is_downloading()) { mAuthResponse = E_DOWNLOADING; } @@ -284,14 +280,11 @@ LLUserAuth::UserAuthcode LLUserAuth::authResponse() return mAuthResponse; } - - mLastTransferRateBPS = mTransaction->transferRate(); + mLastTransferRateBPS = mResponder->transferRate(); + mErrorMessage = mResponder->reason(); - int result; - mTransaction->status(&result); - mErrorMessage = mTransaction->statusMessage(); - // if curl was ok, parse the download area. + CURLcode result = mResponder->result_code(); switch (result) { case CURLE_OK: @@ -313,12 +306,12 @@ LLUserAuth::UserAuthcode LLUserAuth::authResponse() mAuthResponse = E_UNHANDLED_ERROR; break; } - + LL_INFOS2("AppInit", "Authentication") << "Processed response: " << result << LL_ENDL; - delete mTransaction; - mTransaction = NULL; - + // We're done with this data. + mResponder = NULL; + return mAuthResponse; } @@ -329,7 +322,7 @@ LLUserAuth::UserAuthcode LLUserAuth::parseResponse() // mOptions. For now, we will only be looking at mResponses, which // will all be string => string pairs. UserAuthcode rv = E_UNHANDLED_ERROR; - XMLRPC_REQUEST response = mTransaction->response(); + XMLRPC_REQUEST response = mResponder->response(); if(!response) return rv; // clear out any old parsing diff --git a/indra/newview/lluserauth.h b/indra/newview/lluserauth.h index e0160d874..847b1e8b5 100644 --- a/indra/newview/lluserauth.h +++ b/indra/newview/lluserauth.h @@ -36,10 +36,12 @@ #include #include #include -typedef struct _xmlrpc_value* XMLRPC_VALUE; -// forward ecl of types from xlrpc.h +#include -class LLXMLRPCTransaction; +class XMLRPCResponder; + +// forward decl of types from xlrpc.h +typedef struct _xmlrpc_value* XMLRPC_VALUE; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Class LLUserAuth @@ -142,8 +144,7 @@ public: F64 getLastTransferRateBPS() const { return mLastTransferRateBPS; } private: - LLXMLRPCTransaction* mTransaction; - + boost::intrusive_ptr mResponder; std::string mErrorMessage; diff --git a/indra/newview/llviewercontrol.cpp b/indra/newview/llviewercontrol.cpp index a58bd2c5d..7feab1d86 100644 --- a/indra/newview/llviewercontrol.cpp +++ b/indra/newview/llviewercontrol.cpp @@ -78,10 +78,12 @@ #include "llnetmap.h" #include "llrender.h" #include "llfloaterchat.h" -#include "statemachine/aistatemachine.h" +#include "aistatemachine.h" #include "aithreadsafe.h" #include "lldrawpoolbump.h" #include "emeraldboobutils.h" +#include "aicurl.h" +#include "aihttptimeoutpolicy.h" #include "NACLantispam.h" // for NaCl Antispam Registry @@ -133,7 +135,8 @@ static bool handleTerrainScaleChanged(const LLSD& inputvalue) bool handleStateMachineMaxTimeChanged(const LLSD& newvalue) { - AIStateMachine::updateSettings(); + F32 StateMachineMaxTime = newvalue.asFloat(); + AIStateMachine::setMaxCount(StateMachineMaxTime); return true; } @@ -624,6 +627,7 @@ static bool handleAllowLargeSounds(const LLSD& newvalue) gAudiop->setAllowLargeSounds(newvalue.asBoolean()); return true; } + //////////////////////////////////////////////////////////////////////////// void settings_setup_listeners() { @@ -792,6 +796,24 @@ void settings_setup_listeners() gSavedSettings.getControl("AscentAvatarYModifier")->getSignal()->connect(boost::bind(&handleAscentAvatarModifier, _2)); gSavedSettings.getControl("AscentAvatarZModifier")->getSignal()->connect(boost::bind(&handleAscentAvatarModifier, _2)); + gSavedSettings.getControl("CurlConcurrentConnections")->getSignal()->connect(boost::bind(&AICurlInterface::handleCurlConcurrentConnections, _2)); + gSavedSettings.getControl("NoVerifySSLCert")->getSignal()->connect(boost::bind(&AICurlInterface::handleNoVerifySSLCert, _2)); + + gSavedSettings.getControl("CurlTimeoutDNSLookup")->getValidateSignal()->connect(boost::bind(&validateCurlTimeoutDNSLookup, _2)); + gSavedSettings.getControl("CurlTimeoutDNSLookup")->getSignal()->connect(boost::bind(&handleCurlTimeoutDNSLookup, _2)); + gSavedSettings.getControl("CurlTimeoutConnect")->getValidateSignal()->connect(boost::bind(&validateCurlTimeoutConnect, _2)); + gSavedSettings.getControl("CurlTimeoutConnect")->getSignal()->connect(boost::bind(&handleCurlTimeoutConnect, _2)); + gSavedSettings.getControl("CurlTimeoutReplyDelay")->getValidateSignal()->connect(boost::bind(&validateCurlTimeoutReplyDelay, _2)); + gSavedSettings.getControl("CurlTimeoutReplyDelay")->getSignal()->connect(boost::bind(&handleCurlTimeoutReplyDelay, _2)); + gSavedSettings.getControl("CurlTimeoutLowSpeedLimit")->getValidateSignal()->connect(boost::bind(&validateCurlTimeoutLowSpeedLimit, _2)); + gSavedSettings.getControl("CurlTimeoutLowSpeedLimit")->getSignal()->connect(boost::bind(&handleCurlTimeoutLowSpeedLimit, _2)); + gSavedSettings.getControl("CurlTimeoutLowSpeedTime")->getValidateSignal()->connect(boost::bind(&validateCurlTimeoutLowSpeedTime, _2)); + gSavedSettings.getControl("CurlTimeoutLowSpeedTime")->getSignal()->connect(boost::bind(&handleCurlTimeoutLowSpeedTime, _2)); + gSavedSettings.getControl("CurlTimeoutMaxTransaction")->getValidateSignal()->connect(boost::bind(&validateCurlTimeoutMaxTransaction, _2)); + gSavedSettings.getControl("CurlTimeoutMaxTransaction")->getSignal()->connect(boost::bind(&handleCurlTimeoutMaxTransaction, _2)); + gSavedSettings.getControl("CurlTimeoutMaxTotalDelay")->getValidateSignal()->connect(boost::bind(&validateCurlTimeoutMaxTotalDelay, _2)); + gSavedSettings.getControl("CurlTimeoutMaxTotalDelay")->getSignal()->connect(boost::bind(&handleCurlTimeoutMaxTotalDelay, _2)); + // [Ansariel: Display name support] gSavedSettings.getControl("PhoenixNameSystem")->getSignal()->connect(boost::bind(&handlePhoenixNameSystemChanged, _2)); // [/Ansariel: Display name support] diff --git a/indra/newview/llviewerdisplayname.cpp b/indra/newview/llviewerdisplayname.cpp index f4f23c85a..0c917ef4e 100644 --- a/indra/newview/llviewerdisplayname.cpp +++ b/indra/newview/llviewerdisplayname.cpp @@ -40,6 +40,9 @@ #include "llnotificationsutil.h" #include "llui.h" // getLanguage() +class AIHTTPTimeoutPolicy; +extern AIHTTPTimeoutPolicy setDisplayNameResponder_timeout; + namespace LLViewerDisplayName { // Fired when viewer receives server response to display name change @@ -55,7 +58,7 @@ namespace LLViewerDisplayName } -class LLSetDisplayNameResponder : public LLHTTPClient::Responder +class LLSetDisplayNameResponder : public LLHTTPClient::ResponderIgnoreBody { public: // only care about errors @@ -64,6 +67,8 @@ public: LLViewerDisplayName::sSetDisplayNameSignal(false, "", LLSD()); LLViewerDisplayName::sSetDisplayNameSignal.disconnect_all_slots(); } + + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return setDisplayNameResponder_timeout; } }; void LLViewerDisplayName::set(const std::string& display_name, const set_name_slot_t& slot) @@ -82,8 +87,7 @@ void LLViewerDisplayName::set(const std::string& display_name, const set_name_sl // People API can return localized error messages. Indicate our // language preference via header. - LLSD headers; - headers["Accept-Language"] = LLUI::getLanguage(); + AIHTTPHeaders headers("Accept-Language", LLUI::getLanguage()); // People API requires both the old and new value to change a variable. // Our display name will be in cache before the viewer's UI is available @@ -179,9 +183,8 @@ class LLDisplayNameUpdate : public LLHTTPNode // default value // *TODO: get actual headers out of ResponsePtr //LLSD headers = response->mHeaders; - LLSD headers; av_name.mExpires = - LLAvatarNameCache::nameExpirationFromHeaders(headers); + LLAvatarNameCache::nameExpirationFromHeaders(AIHTTPReceivedHeaders()); LLAvatarNameCache::insert(agent_id, av_name); diff --git a/indra/newview/llviewermedia.cpp b/indra/newview/llviewermedia.cpp index 98b78e2f5..26d2c122c 100644 --- a/indra/newview/llviewermedia.cpp +++ b/indra/newview/llviewermedia.cpp @@ -59,6 +59,11 @@ #include "llfloateravatarinfo.h" // for getProfileURL() function //#include "viewerversion.h" +class AIHTTPTimeoutPolicy; +extern AIHTTPTimeoutPolicy mimeDiscoveryResponder_timeout; +extern AIHTTPTimeoutPolicy viewerMediaOpenIDResponder_timeout; +extern AIHTTPTimeoutPolicy viewerMediaWebProfileResponder_timeout; + // Merov: Temporary definitions while porting the new viewer media code to Snowglobe const int LEFT_BUTTON = 0; const int RIGHT_BUTTON = 1; @@ -66,7 +71,7 @@ const int RIGHT_BUTTON = 1; /////////////////////////////////////////////////////////////////////////////// // Helper class that tries to download a URL from a web site and calls a method // on the Panel Land Media and to discover the MIME type -class LLMimeDiscoveryResponder : public LLHTTPClient::Responder +class LLMimeDiscoveryResponder : public LLHTTPClient::ResponderIgnoreBody { LOG_CLASS(LLMimeDiscoveryResponder); public: @@ -75,11 +80,13 @@ public: mInitialized(false) {} + virtual bool needsHeaders(void) const { return true; } - - virtual void completedHeader(U32 status, const std::string& reason, const LLSD& content) + virtual void completedHeaders(U32 status, std::string const& reason, AIHTTPReceivedHeaders const& headers) { - std::string media_type = content["content-type"].asString(); + std::string media_type; + bool content_type_found = headers.getFirstValue("content-type", media_type); + llassert_always(content_type_found); std::string::size_type idx1 = media_type.find_first_of(";"); std::string mime_type = media_type.substr(0, idx1); completeAny(status, mime_type); @@ -97,12 +104,14 @@ public: } } + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return mimeDiscoveryResponder_timeout; } + public: viewer_media_t mMediaImpl; bool mInitialized; }; -class LLViewerMediaOpenIDResponder : public LLHTTPClient::Responder +class LLViewerMediaOpenIDResponder : public LLHTTPClient::ResponderWithCompleted { LOG_CLASS(LLViewerMediaOpenIDResponder); public: @@ -114,13 +123,18 @@ public: { } - /* virtual */ void completedHeader(U32 status, const std::string& reason, const LLSD& content) + /* virtual */ bool needsHeaders(void) const { return true; } + + /* virtual */ void completedHeaders(U32 status, std::string const& reason, AIHTTPReceivedHeaders const& headers) { LL_DEBUGS("MediaAuth") << "status = " << status << ", reason = " << reason << LL_ENDL; - LL_DEBUGS("MediaAuth") << content << LL_ENDL; - std::string cookie = content["set-cookie"].asString(); - - LLViewerMedia::openIDCookieResponse(cookie); + LL_DEBUGS("MediaAuth") << headers << LL_ENDL; + AIHTTPReceivedHeaders::range_type cookies; + if (headers.getValues("set-cookie", cookies)) + { + for (AIHTTPReceivedHeaders::iterator_type cookie = cookies.first; cookie != cookies.second; ++cookie) + LLViewerMedia::openIDCookieResponse(cookie->second); + } } /* virtual */ void completedRaw( @@ -133,9 +147,10 @@ public: // We don't care about the content of the response, only the set-cookie header. } + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return viewerMediaOpenIDResponder_timeout; } }; -class LLViewerMediaWebProfileResponder : public LLHTTPClient::Responder +class LLViewerMediaWebProfileResponder : public LLHTTPClient::ResponderWithCompleted { LOG_CLASS(LLViewerMediaWebProfileResponder); public: @@ -148,17 +163,22 @@ public: { } - /* virtual */ void completedHeader(U32 status, const std::string& reason, const LLSD& content) + /* virtual */ bool needsHeaders(void) const { return true; } + + /* virtual */ void completedHeaders(U32 status, std::string const& reason, AIHTTPReceivedHeaders const& headers) { - LL_WARNS("MediaAuth") << "status = " << status << ", reason = " << reason << LL_ENDL; - LL_WARNS("MediaAuth") << content << LL_ENDL; + LL_INFOS("MediaAuth") << "status = " << status << ", reason = " << reason << LL_ENDL; + LL_INFOS("MediaAuth") << headers << LL_ENDL; - std::string cookie = content["set-cookie"].asString(); - - LLViewerMedia::getCookieStore()->setCookiesFromHost(cookie, mHost); + AIHTTPReceivedHeaders::range_type cookies; + if (headers.getValues("set-cookie", cookies)) + { + for (AIHTTPReceivedHeaders::iterator_type cookie = cookies.first; cookie != cookies.second; ++cookie) + LLViewerMedia::getCookieStore()->setCookiesFromHost(cookie->second, mHost); + } } - void completedRaw( + void completedRaw( U32 status, const std::string& reason, const LLChannelDescriptors& channels, @@ -168,6 +188,8 @@ public: // We don't care about the content of the response, only the set-cookie header. } + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return viewerMediaWebProfileResponder_timeout; } + std::string mHost; }; @@ -652,10 +674,10 @@ void LLViewerMedia::setOpenIDCookie() getCookieStore()->setCookiesFromHost(sOpenIDCookie, authority.substr(host_start, host_end - host_start)); // Do a web profile get so we can store the cookie - LLSD headers = LLSD::emptyMap(); - headers["Accept"] = "*/*"; - headers["Cookie"] = sOpenIDCookie; - headers["User-Agent"] = getCurrentUserAgent(); + AIHTTPHeaders headers; + headers.addHeader("Accept", "*/*"); + headers.addHeader("Cookie", sOpenIDCookie); + headers.addHeader("User-Agent", getCurrentUserAgent()); std::string profile_url = getProfileURL(""); LLURL raw_profile_url( profile_url.c_str() ); @@ -683,18 +705,18 @@ void LLViewerMedia::openIDSetup(const std::string &openid_url, const std::string // We shouldn't ever do this twice, but just in case this code gets repurposed later, clear existing cookies. sOpenIDCookie.clear(); - LLSD headers = LLSD::emptyMap(); + AIHTTPHeaders headers; // Keep LLHTTPClient from adding an "Accept: application/llsd+xml" header - headers["Accept"] = "*/*"; + headers.addHeader("Accept", "*/*"); // and use the expected content-type for a post, instead of the LLHTTPClient::postRaw() default of "application/octet-stream" - headers["Content-Type"] = "application/x-www-form-urlencoded"; + headers.addHeader("Content-Type", "application/x-www-form-urlencoded"); // postRaw() takes ownership of the buffer and releases it later, so we need to allocate a new buffer here. size_t size = openid_token.size(); - U8 *data = new U8[size]; + char* data = new char[size]; memcpy(data, openid_token.data(), size); - LLHTTPClient::postRaw( + LLHTTPClient::postRaw( openid_url, data, size, diff --git a/indra/newview/llviewermenufile.cpp b/indra/newview/llviewermenufile.cpp index 11c15d9d1..fc795f8fb 100644 --- a/indra/newview/llviewermenufile.cpp +++ b/indra/newview/llviewermenufile.cpp @@ -651,9 +651,10 @@ void upload_new_resource(const std::string& src_filename, std::string name, LLAssetStorage::LLStoreAssetCallback callback, S32 expected_upload_cost, void *userdata) -{ +{ // Generate the temporary UUID. std::string filename = gDirUtilp->getTempFilename(); + bool created_temp_file = false; LLTransactionID tid; LLAssetID uuid; @@ -678,10 +679,25 @@ void upload_new_resource(const std::string& src_filename, std::string name, upload_error(error_message, "NofileExtension", filename, args); return; } + else if (codec == IMG_CODEC_J2C) + { + asset_type = LLAssetType::AT_TEXTURE; + if (!LLViewerTextureList::verifyUploadFile(src_filename, codec)) + { + error_message = llformat( "Problem with file %s:\n\n%s\n", + src_filename.c_str(), LLImage::getLastError().c_str()); + args["FILE"] = src_filename; + args["ERROR"] = LLImage::getLastError(); + upload_error(error_message, "ProblemWithFile", filename, args); + return; + } + filename = src_filename; + } else if (codec != IMG_CODEC_INVALID) { // It's an image file, the upload procedure is the same for all asset_type = LLAssetType::AT_TEXTURE; + created_temp_file = true; if (!LLViewerTextureList::createUploadFile(src_filename, filename, codec)) { error_message = llformat( "Problem with file %s:\n\n%s\n", @@ -700,6 +716,7 @@ void upload_new_resource(const std::string& src_filename, std::string name, llinfos << "Attempting to encode wav as an ogg file" << llendl; encode_result = encode_vorbis_file(src_filename, filename); + created_temp_file = true; if (LLVORBISENC_NOERR != encode_result) { @@ -815,6 +832,7 @@ void upload_new_resource(const std::string& src_filename, std::string name, // copy the file's data segment into another file for uploading LLFILE* out = LLFile::fopen(filename, "wb"); /* Flawfinder: ignore */ + created_temp_file = true; if (out) { while((readbytes = fread(buf, 1, 16384, in))) /* Flawfinder: ignore */ @@ -854,11 +872,6 @@ void upload_new_resource(const std::string& src_filename, std::string name, asset_type = LLAssetType::AT_ANIMATION; filename = src_filename; } - else if(exten == "j2k" || exten == "jp2" || exten == "j2c") - { - asset_type = LLAssetType::AT_TEXTURE; - filename = src_filename; - } // else { @@ -934,11 +947,10 @@ void upload_new_resource(const std::string& src_filename, std::string name, LLSD args; args["ERROR_MESSAGE"] = error_message; LLNotificationsUtil::add("ErrorMessage", args); - if(LLFile::remove(filename) == -1) - { - lldebugs << "unable to remove temp file" << llendl; - } - //AIFIXME? LLFilePicker::instance().reset(); + } + if (created_temp_file && LLFile::remove(filename) == -1) + { + lldebugs << "unable to remove temp file" << llendl; } } // diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index 1f540a01c..7e9cc32d0 100644 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -165,7 +165,6 @@ #include "hippogridmanager.h" #include "hippolimits.h" -#include "hipporestrequest.h" #include "hippofloaterxml.h" #include "sgversion.h" #include "m7wlinterface.h" @@ -188,6 +187,8 @@ static const boost::regex NEWLINES("\\n{1}"); // NaCl End +extern AIHTTPTimeoutPolicy authHandler_timeout; + // [RLVa:KB] - Checked: 2009-07-08 (RLVa-1.0.0e) #include "llfloateravatarinfo.h" extern LLMap< const LLUUID, LLFloaterAvatarInfo* > gAvatarInfoInstances; // Only defined in llfloateravatarinfo.cpp @@ -3349,12 +3350,24 @@ void check_translate_chat(const std::string &mesg, LLChat &chat, const BOOL hist // defined in llchatbar.cpp, but not declared in any header void send_chat_from_viewer(std::string utf8_out_text, EChatType type, S32 channel); -class AuthHandler : public HippoRestHandlerRaw +class AuthHandler : public LLHTTPClient::ResponderWithCompleted { - void result(const std::string &content) +protected: + /*virtual*/ void completedRaw(U32 status, std::string const& reason, LLChannelDescriptors const& channels, buffer_ptr_t const& buffer) { - send_chat_from_viewer("AUTH:" + content, CHAT_TYPE_WHISPER, 427169570); + std::string content; + decode_raw_body(status, reason, channels, buffer, content); + if (status == HTTP_OK) + { + send_chat_from_viewer("AUTH:" + content, CHAT_TYPE_WHISPER, 427169570); + } + else + { + llwarns << "Hippo AuthHandler: non-OK HTTP status " << status << " for URL " << mURL << " (" << reason << "). Error body: \"" << content << "\"." << llendl; + } } + + /*virtual*/ AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return authHandler_timeout; } }; void process_chat_from_simulator(LLMessageSystem *msg, void **user_data) @@ -3612,7 +3625,7 @@ void process_chat_from_simulator(LLMessageSystem *msg, void **user_data) std::string authUrl = mesg.substr(8); authUrl += (authUrl.find('?') != std::string::npos)? "&auth=": "?auth="; authUrl += gAuthString; - HippoRestRequest::get(authUrl, new AuthHandler()); + LLHTTPClient::get(authUrl, new AuthHandler); return; } } diff --git a/indra/newview/llviewerobjectlist.cpp b/indra/newview/llviewerobjectlist.cpp index 475950f5d..629e766d5 100644 --- a/indra/newview/llviewerobjectlist.cpp +++ b/indra/newview/llviewerobjectlist.cpp @@ -92,6 +92,10 @@ extern ImportTracker gImportTracker; void dialog_refresh_all(); +class AIHTTPTimeoutPolicy; +extern AIHTTPTimeoutPolicy objectCostResponder_timeout; +extern AIHTTPTimeoutPolicy physicsFlagsResponder_timeout; + #define CULL_VIS //#define ORPHAN_SPAM //#define IGNORE_DEAD @@ -684,7 +688,7 @@ void LLViewerObjectList::updateApparentAngles(LLAgent &agent) LLVOAvatar::cullAvatarsByPixelArea(); } -class LLObjectCostResponder : public LLCurl::Responder +class LLObjectCostResponder : public LLHTTPClient::ResponderWithResult { public: LLObjectCostResponder(const LLSD& object_ids) @@ -768,12 +772,13 @@ public: } } + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return objectCostResponder_timeout; } + private: LLSD mObjectIDs; }; - -class LLPhysicsFlagsResponder : public LLCurl::Responder +class LLPhysicsFlagsResponder : public LLHTTPClient::ResponderWithResult { public: LLPhysicsFlagsResponder(const LLSD& object_ids) @@ -864,6 +869,8 @@ public: } } + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return physicsFlagsResponder_timeout; } + private: LLSD mObjectIDs; }; diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp index 11e47bd77..7df9352f6 100644 --- a/indra/newview/llviewerregion.cpp +++ b/indra/newview/llviewerregion.cpp @@ -82,13 +82,16 @@ #pragma warning(disable:4355) #endif +class AIHTTPTimeoutPolicy; +extern AIHTTPTimeoutPolicy baseCapabilitiesComplete_timeout; +extern AIHTTPTimeoutPolicy simulatorFeaturesReceived_timeout; + const F32 WATER_TEXTURE_SCALE = 8.f; // Number of times to repeat the water texture across a region const S16 MAX_MAP_DIST = 10; // The server only keeps our pending agent info for 60 seconds. // We want to allow for seed cap retry, but its not useful after that 60 seconds. // Give it 3 chances, each at 18 seconds to give ourselves a few seconds to connect anyways if we give up. const S32 MAX_SEED_CAP_ATTEMPTS_BEFORE_LOGIN = 3; -const F32 CAP_REQUEST_TIMEOUT = 18; // Even though we gave up on login, keep trying for caps after we are logged in: const S32 MAX_CAP_REQUEST_ATTEMPTS = 30; @@ -206,7 +209,7 @@ public: }; LLRegionHandler gRegionHandler; -class BaseCapabilitiesComplete : public LLHTTPClient::Responder +class BaseCapabilitiesComplete : public LLHTTPClient::ResponderWithResult { LOG_CLASS(BaseCapabilitiesComplete); public: @@ -262,6 +265,8 @@ public: } } + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return baseCapabilitiesComplete_timeout; } + static boost::intrusive_ptr build( U64 region_handle, S32 id ) { return boost::intrusive_ptr( @@ -1662,8 +1667,7 @@ void LLViewerRegion::setSeedCapability(const std::string& url) S32 id = ++mImpl->mHttpResponderID; LLHTTPClient::post(url, capabilityNames, - BaseCapabilitiesComplete::build(getHandle(), id), - LLSD(), CAP_REQUEST_TIMEOUT); + BaseCapabilitiesComplete::build(getHandle(), id)); } S32 LLViewerRegion::getNumSeedCapRetries() @@ -1698,8 +1702,7 @@ void LLViewerRegion::failedSeedCapability() S32 id = ++mImpl->mHttpResponderID; LLHTTPClient::post(url, capabilityNames, - BaseCapabilitiesComplete::build(getHandle(), id), - LLSD(), CAP_REQUEST_TIMEOUT); + BaseCapabilitiesComplete::build(getHandle(), id)); } else { @@ -1708,7 +1711,7 @@ void LLViewerRegion::failedSeedCapability() } } -class SimulatorFeaturesReceived : public LLHTTPClient::Responder +class SimulatorFeaturesReceived : public LLHTTPClient::ResponderWithResult { LOG_CLASS(SimulatorFeaturesReceived); public: @@ -1736,6 +1739,8 @@ public: regionp->setSimulatorFeatures(content); } + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return simulatorFeaturesReceived_timeout; } + private: void retry() { @@ -1743,7 +1748,7 @@ private: { mAttempt++; LL_WARNS2("AppInit", "SimulatorFeatures") << "Re-trying '" << mRetryURL << "'. Retry #" << mAttempt << LL_ENDL; - LLHTTPClient::get(mRetryURL, new SimulatorFeaturesReceived(*this), LLSD(), CAP_REQUEST_TIMEOUT); + LLHTTPClient::get(mRetryURL, new SimulatorFeaturesReceived(*this)); } } @@ -1769,7 +1774,7 @@ void LLViewerRegion::setCapability(const std::string& name, const std::string& u else if (name == "SimulatorFeatures") { // kick off a request for simulator features - LLHTTPClient::get(url, new SimulatorFeaturesReceived(url, getHandle()), LLSD(), CAP_REQUEST_TIMEOUT); + LLHTTPClient::get(url, new SimulatorFeaturesReceived(url, getHandle())); } else { diff --git a/indra/newview/llviewerstats.cpp b/indra/newview/llviewerstats.cpp index 258d2a788..6013a47f2 100644 --- a/indra/newview/llviewerstats.cpp +++ b/indra/newview/llviewerstats.cpp @@ -63,6 +63,8 @@ #include "llmeshrepository.h" //for LLMeshRepository::sBytesReceived #include "sgmemstat.h" +class AIHTTPTimeoutPolicy; +extern AIHTTPTimeoutPolicy viewerStatsResponder_timeout; class StatAttributes { @@ -696,7 +698,7 @@ void update_statistics(U32 frame_count) } } -class ViewerStatsResponder : public LLHTTPClient::Responder +class ViewerStatsResponder : public LLHTTPClient::ResponderWithResult { public: ViewerStatsResponder() { } @@ -711,6 +713,8 @@ public: { llinfos << "ViewerStatsResponder::result" << llendl; } + + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return viewerStatsResponder_timeout; } }; /* @@ -875,6 +879,6 @@ void send_stats() body["MinimalSkin"] = false; LLViewerStats::getInstance()->addToMessage(body); - LLHTTPClient::post(url, body, new ViewerStatsResponder()); + LLHTTPClient::post(url, body, new ViewerStatsResponder); } diff --git a/indra/newview/llviewertexturelist.cpp b/indra/newview/llviewertexturelist.cpp index 07db8e30f..937f79701 100644 --- a/indra/newview/llviewertexturelist.cpp +++ b/indra/newview/llviewertexturelist.cpp @@ -1035,54 +1035,78 @@ void LLViewerTextureList::decodeAllImages(F32 max_time) BOOL LLViewerTextureList::createUploadFile(const std::string& filename, const std::string& out_filename, const U8 codec) -{ +{ // Load the image LLPointer image = LLImageFormatted::createFromType(codec); if (image.isNull()) { - image->setLastError("Couldn't open the image to be uploaded."); + LLImage::setLastError("Couldn't open the image to be uploaded."); return FALSE; } if (!image->load(filename)) { - image->setLastError("Couldn't load the image to be uploaded."); + LLImage::setLastError("Couldn't load the image to be uploaded."); return FALSE; } // Decompress or expand it in a raw image structure LLPointer raw_image = new LLImageRaw; if (!image->decode(raw_image, 0.0f)) { - image->setLastError("Couldn't decode the image to be uploaded."); + LLImage::setLastError("Couldn't decode the image to be uploaded."); return FALSE; } // Check the image constraints if ((image->getComponents() != 3) && (image->getComponents() != 4)) { - image->setLastError("Image files with less than 3 or more than 4 components are not supported."); + LLImage::setLastError("Image files with less than 3 or more than 4 components are not supported."); return FALSE; } // Convert to j2c (JPEG2000) and save the file locally LLPointer compressedImage = convertToUploadFile(raw_image); if (compressedImage.isNull()) { - image->setLastError("Couldn't convert the image to jpeg2000."); + LLImage::setLastError("Couldn't convert the image to jpeg2000."); llinfos << "Couldn't convert to j2c, file : " << filename << llendl; return FALSE; } if (!compressedImage->save(out_filename)) { - image->setLastError("Couldn't create the jpeg2000 image for upload."); + LLImage::setLastError("Couldn't create the jpeg2000 image for upload."); llinfos << "Couldn't create output file : " << out_filename << llendl; return FALSE; } + return verifyUploadFile(out_filename, codec); +} + +static bool positive_power_of_two(int dim) +{ + return dim > 0 && !(dim & (dim - 1)); +} + +BOOL LLViewerTextureList::verifyUploadFile(const std::string& out_filename, const U8 codec) +{ // Test to see if the encode and save worked LLPointer integrity_test = new LLImageJ2C; if (!integrity_test->loadAndValidate( out_filename )) { - image->setLastError("The created jpeg2000 image is corrupt."); + LLImage::setLastError(std::string("The ") + ((codec == IMG_CODEC_J2C) ? "" : "created ") + "jpeg2000 image is corrupt: " + LLImage::getLastError()); llinfos << "Image file : " << out_filename << " is corrupt" << llendl; return FALSE; } + if (codec == IMG_CODEC_J2C) + { + if (integrity_test->getComponents() < 3 || integrity_test->getComponents() > 4) + { + LLImage::setLastError("Image files with less than 3 or more than 4 components are not supported."); + return FALSE; + } + else if (!positive_power_of_two(integrity_test->getWidth()) || + !positive_power_of_two(integrity_test->getHeight())) + { + LLImage::setLastError("The width or height is not a (positive) power of two."); + return FALSE; + } + } return TRUE; } diff --git a/indra/newview/llviewertexturelist.h b/indra/newview/llviewertexturelist.h index fa414ad5f..043a48cbd 100644 --- a/indra/newview/llviewertexturelist.h +++ b/indra/newview/llviewertexturelist.h @@ -68,6 +68,7 @@ class LLViewerTextureList public: static BOOL createUploadFile(const std::string& filename, const std::string& out_filename, const U8 codec); + static BOOL verifyUploadFile(const std::string& out_filename, const U8 codec); static LLPointer convertToUploadFile(LLPointer raw_image); static void processImageNotInDatabase( LLMessageSystem *msg, void **user_data ); static S32 calcMaxTextureRAM(); diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp index 4b27f6a2a..67b1b2b10 100644 --- a/indra/newview/llvoavatar.cpp +++ b/indra/newview/llvoavatar.cpp @@ -113,6 +113,7 @@ // #include "llfloaterexploreanimations.h" #include "llimagemetadatareader.h" +#include "aihttptimeoutpolicy.h" // #include "llavatarname.h" diff --git a/indra/newview/llvoiceclient.cpp b/indra/newview/llvoiceclient.cpp index 78a000a93..55f4f656b 100644 --- a/indra/newview/llvoiceclient.cpp +++ b/indra/newview/llvoiceclient.cpp @@ -82,6 +82,10 @@ #define USE_SESSION_GROUPS 0 +class AIHTTPTimeoutPolicy; +extern AIHTTPTimeoutPolicy viewerVoiceAccountProvisionResponder_timeout; +extern AIHTTPTimeoutPolicy voiceClientCapResponder_timeout; + static bool sConnectingToAgni = false; F32 LLVoiceClient::OVERDRIVEN_POWER_LEVEL = 0.7f; @@ -140,7 +144,7 @@ static int scale_speaker_volume(float volume) } class LLViewerVoiceAccountProvisionResponder : - public LLHTTPClient::Responder + public LLHTTPClient::ResponderWithResult { public: LLViewerVoiceAccountProvisionResponder(int retries) @@ -187,6 +191,8 @@ public: } } + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return viewerVoiceAccountProvisionResponder_timeout; } + private: int mRetries; }; @@ -1008,14 +1014,14 @@ static bool sMuteListListener_listening = false; static LLVoiceClientFriendsObserver *friendslist_listener = NULL; /////////////////////////////////////////////////////////////////////////////////////////////// - -class LLVoiceClientCapResponder : public LLHTTPClient::Responder +class LLVoiceClientCapResponder : public LLHTTPClient::ResponderWithResult { public: LLVoiceClientCapResponder(void){}; virtual void error(U32 status, const std::string& reason); // called with bad status codes virtual void result(const LLSD& content); + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return voiceClientCapResponder_timeout; } private: }; diff --git a/indra/newview/llwlhandlers.h b/indra/newview/llwlhandlers.h index 213bc7c7c..af19c74b3 100644 --- a/indra/newview/llwlhandlers.h +++ b/indra/newview/llwlhandlers.h @@ -36,6 +36,10 @@ #include "llviewerprecompiledheaders.h" #include "llhttpclient.h" +class AIHTTPTimeoutPolicy; +extern AIHTTPTimeoutPolicy environmentRequestResponder_timeout; +extern AIHTTPTimeoutPolicy environmentApplyResponder_timeout; + class LLEnvironmentRequest { LOG_CLASS(LLEnvironmentRequest); @@ -48,12 +52,13 @@ private: static bool doRequest(); }; -class LLEnvironmentRequestResponder: public LLHTTPClient::Responder +class LLEnvironmentRequestResponder: public LLHTTPClient::ResponderWithResult { LOG_CLASS(LLEnvironmentRequestResponder); public: virtual void result(const LLSD& content); virtual void error(U32 status, const std::string& reason); + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return environmentRequestResponder_timeout; } private: friend class LLEnvironmentRequest; @@ -75,7 +80,7 @@ private: static clock_t UPDATE_WAIT_SECONDS; }; -class LLEnvironmentApplyResponder: public LLHTTPClient::Responder +class LLEnvironmentApplyResponder: public LLHTTPClient::ResponderWithResult { LOG_CLASS(LLEnvironmentApplyResponder); public: @@ -97,6 +102,8 @@ public: virtual void error(U32 status, const std::string& reason); // non-200 errors only + virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return environmentApplyResponder_timeout; } + private: friend class LLEnvironmentApply; diff --git a/indra/newview/llworldmap.cpp b/indra/newview/llworldmap.cpp index 7672cca2f..91e1e6cda 100644 --- a/indra/newview/llworldmap.cpp +++ b/indra/newview/llworldmap.cpp @@ -441,7 +441,7 @@ void LLWorldMap::sendMapLayerRequest() if (!url.empty()) { llinfos << "LLWorldMap::sendMapLayerRequest via capability" << llendl; - LLHTTPClient::post(url, body, new LLMapLayerResponder()); + LLHTTPClient::post(url, body, new LLMapLayerResponder); } else { diff --git a/indra/newview/llxmlrpcresponder.cpp b/indra/newview/llxmlrpcresponder.cpp new file mode 100644 index 000000000..dac69a7f1 --- /dev/null +++ b/indra/newview/llxmlrpcresponder.cpp @@ -0,0 +1,319 @@ +/** + * @file llxmlrpcresponder.cpp + * @brief LLXMLRPCResponder and related class implementations + * + * $LicenseInfo:firstyear=2006&license=viewergpl$ + * + * Copyright (c) 2006-2009, Linden Research, Inc. + * Copyright (c) 2012, Aleric Inglewood. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * 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, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llxmlrpcresponder.h" +#include "llhttpclient.h" + +#include "llcurl.h" +#include "llviewercontrol.h" +#include "llbufferstream.h" + +// Have to include these last to avoid queue redefinition! +#include + +#include "llappviewer.h" + +#include "hippogridmanager.h" +#include "aicurleasyrequeststatemachine.h" + +#ifdef CWDEBUG +#include +#endif + +LLXMLRPCValue LLXMLRPCValue::operator[](const char* id) const +{ + return LLXMLRPCValue(XMLRPC_VectorGetValueWithID(mV, id)); +} + +std::string LLXMLRPCValue::asString() const +{ + const char* s = XMLRPC_GetValueString(mV); + return s ? s : ""; +} + +int LLXMLRPCValue::asInt() const { return XMLRPC_GetValueInt(mV); } +bool LLXMLRPCValue::asBool() const { return XMLRPC_GetValueBoolean(mV) != 0; } +double LLXMLRPCValue::asDouble() const { return XMLRPC_GetValueDouble(mV); } + +LLXMLRPCValue LLXMLRPCValue::rewind() +{ + return LLXMLRPCValue(XMLRPC_VectorRewind(mV)); +} + +LLXMLRPCValue LLXMLRPCValue::next() +{ + return LLXMLRPCValue(XMLRPC_VectorNext(mV)); +} + +bool LLXMLRPCValue::isValid() const +{ + return mV != NULL; +} + +LLXMLRPCValue LLXMLRPCValue::createArray() +{ + return LLXMLRPCValue(XMLRPC_CreateVector(NULL, xmlrpc_vector_array)); +} + +LLXMLRPCValue LLXMLRPCValue::createStruct() +{ + return LLXMLRPCValue(XMLRPC_CreateVector(NULL, xmlrpc_vector_struct)); +} + + +void LLXMLRPCValue::append(LLXMLRPCValue& v) +{ + XMLRPC_AddValueToVector(mV, v.mV); +} + +void LLXMLRPCValue::appendString(const std::string& v) +{ + XMLRPC_AddValueToVector(mV, XMLRPC_CreateValueString(NULL, v.c_str(), 0)); +} + +void LLXMLRPCValue::appendInt(int v) +{ + XMLRPC_AddValueToVector(mV, XMLRPC_CreateValueInt(NULL, v)); +} + +void LLXMLRPCValue::appendBool(bool v) +{ + XMLRPC_AddValueToVector(mV, XMLRPC_CreateValueBoolean(NULL, v)); +} + +void LLXMLRPCValue::appendDouble(double v) +{ + XMLRPC_AddValueToVector(mV, XMLRPC_CreateValueDouble(NULL, v)); +} + + +void LLXMLRPCValue::append(const char* id, LLXMLRPCValue& v) +{ + XMLRPC_SetValueID(v.mV, id, 0); + XMLRPC_AddValueToVector(mV, v.mV); +} + +void LLXMLRPCValue::appendString(const char* id, const std::string& v) +{ + XMLRPC_AddValueToVector(mV, XMLRPC_CreateValueString(id, v.c_str(), 0)); +} + +void LLXMLRPCValue::appendInt(const char* id, int v) +{ + XMLRPC_AddValueToVector(mV, XMLRPC_CreateValueInt(id, v)); +} + +void LLXMLRPCValue::appendBool(const char* id, bool v) +{ + XMLRPC_AddValueToVector(mV, XMLRPC_CreateValueBoolean(id, v)); +} + +void LLXMLRPCValue::appendDouble(const char* id, double v) +{ + XMLRPC_AddValueToVector(mV, XMLRPC_CreateValueDouble(id, v)); +} + +void LLXMLRPCValue::cleanup() +{ + XMLRPC_CleanupValue(mV); + mV = NULL; +} + +XMLRPC_VALUE LLXMLRPCValue::getValue() const +{ + return mV; +} + +void XMLRPCResponder::completed_headers(U32 status, std::string const& reason, AITransferInfo* info) +{ + if (info) + { + mTransferInfo = *info; + } + // Call base class implementation. + LegacyPolledResponder::completed_headers(status, reason, info); +} + +void XMLRPCResponder::completedRaw(U32 status, std::string const& reason, LLChannelDescriptors const& channels, buffer_ptr_t const& buffer) +{ + if (mCode == CURLE_OK) + { + mBufferSize = buffer->count(channels.in()); + if (200 <= status && status < 400) + { + char* ptr = NULL; + char* buf = NULL; + LLMutexLock lock(buffer->getMutex()); + LLBufferArray::const_segment_iterator_t const end = buffer->endSegment(); + for (LLBufferArray::const_segment_iterator_t iter = buffer->beginSegment(); iter != end; ++iter) + { + LLSegment const& segment = *iter; + if (segment.isOnChannel(channels.in())) + { + S32 const segment_size = segment.size(); + if (!buf) + { + if (segment_size == mBufferSize) + { + // It's contiguous, no need for copying. + mResponse = XMLRPC_REQUEST_FromXML((char const*)segment.data(), mBufferSize, NULL); + break; + } + ptr = buf = new char [mBufferSize]; + } + llassert(ptr + segment_size <= buf + mBufferSize); + memcpy(ptr, segment.data(), segment_size); + ptr += segment_size; + } + } + if (buf) + { + mResponse = XMLRPC_REQUEST_FromXML(buf, mBufferSize, NULL); + delete [] buf; + } + } + } +} + +LLXMLRPCValue XMLRPCResponder::responseValue(void) const +{ + return LLXMLRPCValue(XMLRPC_RequestGetData(mResponse)); +} + +#ifdef AI_UNUSED +void LLXMLRPCTransaction::Impl::setStatus(Status status, + const std::string& message, const std::string& uri) +{ + mStatus = status; + mStatusMessage = message; + mStatusURI = uri; + + if (mStatusMessage.empty()) + { + switch (mStatus) + { + case StatusNotStarted: + mStatusMessage = "(not started)"; + break; + + case StatusStarted: + mStatusMessage = "(waiting for server response)"; + break; + + case StatusDownloading: + mStatusMessage = "(reading server response)"; + break; + + case StatusComplete: + mStatusMessage = "(done)"; + break; + + default: + // Usually this means that there's a problem with the login server, + // not with the client. Direct user to status page. + // NOTE: these should really be gHippoGridManager->getCurrentGrid()->getGridName() + // but apparently that's broken as of 1.3 b2 -- MC + mStatusMessage = + "Despite our best efforts, something unexpected has gone wrong. \n" + " \n" + "Please check " + gHippoGridManager->getCurrentGrid()->getGridName() + "'s status \n" + "to see if there is a known problem with the service."; + + //mStatusURI = "http://secondlife.com/status/"; + } + } +} + +void LLXMLRPCTransaction::Impl::setCurlStatus(CURLcode code) +{ + std::string message; + std::string uri = gHippoGridManager->getCurrentGrid()->getSupportUrl(); + + switch (code) + { + case CURLE_COULDNT_RESOLVE_HOST: + message = + "DNS could not resolve the host name.\n" + "Please verify that you can connect to " + gHippoGridManager->getCurrentGrid()->getGridName() + "'s\n" + "web site. If you can, but continue to receive this error,\n" + "please go to the support section and report this problem."; + break; + + case CURLE_SSL_PEER_CERTIFICATE: + message = + "The login server couldn't verify itself via SSL.\n" + "If you continue to receive this error, please go\n" + "to the Support section of " + gHippoGridManager->getCurrentGrid()->getGridName() + "'s web site\n" + "and report the problem."; + break; + + case CURLE_SSL_CACERT: + case CURLE_SSL_CONNECT_ERROR: + message = + "Often this means that your computer's clock is set incorrectly.\n" + "Please go to Control Panels and make sure the time and date\n" + "are set correctly.\n" + "\n" + "If you continue to receive this error, please go\n" + "to the Support section of " + gHippoGridManager->getCurrentGrid()->getGridName() + "'s web site\n" + "and report the problem."; + break; + + default: + break; + } + + mCurlCode = code; + setStatus(StatusCURLError, message, uri); +} +#endif // AI_UNUSED + +F64 XMLRPCResponder::transferRate(void) const +{ + if (mTransferInfo.mSpeedDownload == 0.0) // Don't print the below stats if this wasn't initialized. + { + return 0.0; + } + + F64 rate_bits_per_sec = mTransferInfo.mSpeedDownload * 8.0; + + LL_INFOS("AppInit") << "Buffer size: " << mBufferSize << " B" << LL_ENDL; + LL_DEBUGS("AppInit") << "Transfer size: " << mTransferInfo.mSizeDownload << " B" << LL_ENDL; + LL_DEBUGS("AppInit") << "Transfer time: " << mTransferInfo.mTotalTime << " s" << LL_ENDL; + LL_INFOS("AppInit") << "Transfer rate: " << rate_bits_per_sec / 1000.0 << " kb/s" << LL_ENDL; + + return rate_bits_per_sec; +} + diff --git a/indra/newview/llxmlrpctransaction.h b/indra/newview/llxmlrpcresponder.h similarity index 63% rename from indra/newview/llxmlrpctransaction.h rename to indra/newview/llxmlrpcresponder.h index 6d70f8ff1..e42066f1c 100644 --- a/indra/newview/llxmlrpctransaction.h +++ b/indra/newview/llxmlrpcresponder.h @@ -1,10 +1,11 @@ /** - * @file llxmlrpctransaction.h - * @brief LLXMLRPCTransaction and related class header file + * @file llxmlrpcresponder.h + * @brief LLXMLRPCResponder and related class header file * * $LicenseInfo:firstyear=2006&license=viewergpl$ * * Copyright (c) 2006-2009, Linden Research, Inc. + * Copyright (c) 2012, Aleric Inglewood. * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab @@ -30,10 +31,16 @@ * $/LicenseInfo$ */ -#ifndef LLXMLRPCTRANSACTION_H -#define LLXMLRPCTRANSACTION_H +#ifndef LLXMLRPCRESPONDER_H +#define LLXMLRPCRESPONDER_H #include +#include "llurlrequest.h" // Injector +#include "llcurl.h" +#include "llhttpstatuscodes.h" + +class AIHTTPTimeoutPolicy; +extern AIHTTPTimeoutPolicy XMLRPCResponder_timeout; typedef struct _xmlrpc_request* XMLRPC_REQUEST; typedef struct _xmlrpc_value* XMLRPC_VALUE; @@ -84,56 +91,24 @@ private: XMLRPC_VALUE mV; }; - -class LLXMLRPCTransaction - // an asynchronous request and respones via XML-RPC -{ -public: - LLXMLRPCTransaction(const std::string& uri, - XMLRPC_REQUEST request, bool useGzip = true); - // does not take ownership of the request object - // request can be freed as soon as the transaction is constructed - - LLXMLRPCTransaction(const std::string& uri, - const std::string& method, LLXMLRPCValue params, bool useGzip = true); - // *does* take control of the request value, you must not free it - - ~LLXMLRPCTransaction(); - - typedef enum { - StatusNotStarted, - StatusStarted, - StatusDownloading, - StatusComplete, - StatusCURLError, - StatusXMLRPCError, - StatusOtherError - } Status; - - bool is_finished(void) const; - // Returns true when done. - - Status status(int* curlCode); - // return status, and extended CURL code, if code isn't null - std::string statusMessage(); - // return a message string, suitable for showing the user - std::string statusURI(); - // return a URI for the user with more information - // can be empty - - XMLRPC_REQUEST response(); - LLXMLRPCValue responseValue(); - // only valid if StatusComplete, otherwise NULL - // retains ownership of the result object, don't free it - - F64 transferRate(); - // only valid if StatusComplete, otherwise 0.0 - +class XMLRPCResponder : public LLHTTPClient::LegacyPolledResponder { private: - class Impl; - Impl& impl; + AITransferInfo mTransferInfo; + S32 mBufferSize; + bool mReceivedHTTPHeader; + XMLRPC_REQUEST mResponse; + +public: + // Accessors. + F64 transferRate(void) const; + bool is_downloading(void) const { return mReceivedHTTPHeader; } + XMLRPC_REQUEST response(void) const { return mResponse; } + LLXMLRPCValue responseValue(void) const; + + /*virtual*/ void received_HTTP_header(void) { mReceivedHTTPHeader = true; LLHTTPClient::ResponderBase::received_HTTP_header(); } + /*virtual*/ void completed_headers(U32 status, std::string const& reason, AITransferInfo* info); + /*virtual*/ void completedRaw(U32 status, std::string const& reason, LLChannelDescriptors const& channels, buffer_ptr_t const& buffer); + /*virtual*/ AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return XMLRPCResponder_timeout; } }; - - -#endif // LLXMLRPCTRANSACTION_H +#endif // LLXMLRPCRESPONDER_H diff --git a/indra/newview/llxmlrpctransaction.cpp b/indra/newview/llxmlrpctransaction.cpp deleted file mode 100644 index 227904f90..000000000 --- a/indra/newview/llxmlrpctransaction.cpp +++ /dev/null @@ -1,570 +0,0 @@ -/** - * @file llxmlrpctransaction.cpp - * @brief LLXMLRPCTransaction and related class implementations - * - * $LicenseInfo:firstyear=2006&license=viewergpl$ - * - * Copyright (c) 2006-2009, Linden Research, Inc. - * - * Second Life Viewer Source Code - * The source code in this file ("Source Code") is provided by Linden Lab - * to you under the terms of the GNU General Public License, version 2.0 - * ("GPL"), unless you have obtained a separate licensing agreement - * ("Other License"), formally executed by you and Linden Lab. Terms of - * the GPL can be found in doc/GPL-license.txt in this distribution, or - * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 - * - * 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, or - * online at - * http://secondlifegrid.net/programs/open_source/licensing/flossexception - * - * By copying, modifying or distributing this software, you acknowledge - * that you have read and understood your obligations described above, - * and agree to abide by those obligations. - * - * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO - * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, - * COMPLETENESS OR PERFORMANCE. - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llxmlrpctransaction.h" - -#include "llcurl.h" -#include "llviewercontrol.h" - -// Have to include these last to avoid queue redefinition! -#include - -#include "llappviewer.h" - -#include "hippogridmanager.h" -#include "statemachine/aicurleasyrequeststatemachine.h" - -#ifdef CWDEBUG -#include -#endif - -LLXMLRPCValue LLXMLRPCValue::operator[](const char* id) const -{ - return LLXMLRPCValue(XMLRPC_VectorGetValueWithID(mV, id)); -} - -std::string LLXMLRPCValue::asString() const -{ - const char* s = XMLRPC_GetValueString(mV); - return s ? s : ""; -} - -int LLXMLRPCValue::asInt() const { return XMLRPC_GetValueInt(mV); } -bool LLXMLRPCValue::asBool() const { return XMLRPC_GetValueBoolean(mV) != 0; } -double LLXMLRPCValue::asDouble() const { return XMLRPC_GetValueDouble(mV); } - -LLXMLRPCValue LLXMLRPCValue::rewind() -{ - return LLXMLRPCValue(XMLRPC_VectorRewind(mV)); -} - -LLXMLRPCValue LLXMLRPCValue::next() -{ - return LLXMLRPCValue(XMLRPC_VectorNext(mV)); -} - -bool LLXMLRPCValue::isValid() const -{ - return mV != NULL; -} - -LLXMLRPCValue LLXMLRPCValue::createArray() -{ - return LLXMLRPCValue(XMLRPC_CreateVector(NULL, xmlrpc_vector_array)); -} - -LLXMLRPCValue LLXMLRPCValue::createStruct() -{ - return LLXMLRPCValue(XMLRPC_CreateVector(NULL, xmlrpc_vector_struct)); -} - - -void LLXMLRPCValue::append(LLXMLRPCValue& v) -{ - XMLRPC_AddValueToVector(mV, v.mV); -} - -void LLXMLRPCValue::appendString(const std::string& v) -{ - XMLRPC_AddValueToVector(mV, XMLRPC_CreateValueString(NULL, v.c_str(), 0)); -} - -void LLXMLRPCValue::appendInt(int v) -{ - XMLRPC_AddValueToVector(mV, XMLRPC_CreateValueInt(NULL, v)); -} - -void LLXMLRPCValue::appendBool(bool v) -{ - XMLRPC_AddValueToVector(mV, XMLRPC_CreateValueBoolean(NULL, v)); -} - -void LLXMLRPCValue::appendDouble(double v) -{ - XMLRPC_AddValueToVector(mV, XMLRPC_CreateValueDouble(NULL, v)); -} - - -void LLXMLRPCValue::append(const char* id, LLXMLRPCValue& v) -{ - XMLRPC_SetValueID(v.mV, id, 0); - XMLRPC_AddValueToVector(mV, v.mV); -} - -void LLXMLRPCValue::appendString(const char* id, const std::string& v) -{ - XMLRPC_AddValueToVector(mV, XMLRPC_CreateValueString(id, v.c_str(), 0)); -} - -void LLXMLRPCValue::appendInt(const char* id, int v) -{ - XMLRPC_AddValueToVector(mV, XMLRPC_CreateValueInt(id, v)); -} - -void LLXMLRPCValue::appendBool(const char* id, bool v) -{ - XMLRPC_AddValueToVector(mV, XMLRPC_CreateValueBoolean(id, v)); -} - -void LLXMLRPCValue::appendDouble(const char* id, double v) -{ - XMLRPC_AddValueToVector(mV, XMLRPC_CreateValueDouble(id, v)); -} - -void LLXMLRPCValue::cleanup() -{ - XMLRPC_CleanupValue(mV); - mV = NULL; -} - -XMLRPC_VALUE LLXMLRPCValue::getValue() const -{ - return mV; -} - - -class LLXMLRPCTransaction::Impl -{ -public: - typedef LLXMLRPCTransaction::Status Status; - - AICurlEasyRequestStateMachine* mCurlEasyRequestStateMachinePtr; - - Status mStatus; - CURLcode mCurlCode; - std::string mStatusMessage; - std::string mStatusURI; - LLCurl::TransferInfo mTransferInfo; - - std::string mURI; - - std::string mProxyAddress; - - std::string mResponseText; - XMLRPC_REQUEST mResponse; - - Impl(const std::string& uri, XMLRPC_REQUEST request, bool useGzip); - Impl(const std::string& uri, - const std::string& method, LLXMLRPCValue params, bool useGzip); - ~Impl(); - - bool is_finished(void) const; - void curlEasyRequestCallback(bool success); - - void setStatus(Status code, - const std::string& message = "", const std::string& uri = ""); - void setCurlStatus(CURLcode); - -private: - void init(XMLRPC_REQUEST request, bool useGzip); - - static size_t curlDownloadCallback( - char* data, size_t size, size_t nmemb, void* user_data); -}; - -LLXMLRPCTransaction::Impl::Impl(const std::string& uri, - XMLRPC_REQUEST request, bool useGzip) - : mCurlEasyRequestStateMachinePtr(NULL), - mStatus(LLXMLRPCTransaction::StatusNotStarted), - mURI(uri), - mResponse(0) -{ - init(request, useGzip); -} - - -LLXMLRPCTransaction::Impl::Impl(const std::string& uri, - const std::string& method, LLXMLRPCValue params, bool useGzip) - : mCurlEasyRequestStateMachinePtr(NULL), - mStatus(LLXMLRPCTransaction::StatusNotStarted), - mURI(uri), - mResponse(0) -{ - XMLRPC_REQUEST request = XMLRPC_RequestNew(); - XMLRPC_RequestSetMethodName(request, method.c_str()); - XMLRPC_RequestSetRequestType(request, xmlrpc_request_call); - XMLRPC_RequestSetData(request, params.getValue()); - - init(request, useGzip); -} - -// Store pointer to data allocated with XMLRPC_REQUEST_ToXML and call XMLRPC_Free to free it upon destruction. -class AIXMLRPCData : public AIPostField -{ - public: - AIXMLRPCData(char const* allocated_data) : AIPostField(allocated_data) { } - /*virtual*/ ~AIXMLRPCData() { XMLRPC_Free(const_cast(mData)); mData = NULL; } -}; - -void LLXMLRPCTransaction::Impl::init(XMLRPC_REQUEST request, bool useGzip) -{ - { - try - { - mCurlEasyRequestStateMachinePtr = new AICurlEasyRequestStateMachine(false); - } - catch(AICurlNoEasyHandle const& error) - { - llwarns << "Failed to initialize LLXMLRPCTransaction: " << error.what() << llendl; - setStatus(StatusOtherError, "No curl easy handle"); - return; - } - AICurlEasyRequest_wat curlEasyRequest_w(*mCurlEasyRequestStateMachinePtr->mCurlEasyRequest); - - curlEasyRequest_w->setWriteCallback(&curlDownloadCallback, (void*)this); - BOOL vefifySSLCert = !gSavedSettings.getBOOL("NoVerifySSLCert"); - curlEasyRequest_w->setopt(CURLOPT_SSL_VERIFYPEER, vefifySSLCert); - curlEasyRequest_w->setopt(CURLOPT_SSL_VERIFYHOST, vefifySSLCert ? 2 : 0); - // Be a little impatient about establishing connections. - curlEasyRequest_w->setopt(CURLOPT_CONNECTTIMEOUT, 40L); - - /* Setting the DNS cache timeout to -1 disables it completely. - This might help with bug #503 */ - curlEasyRequest_w->setopt(CURLOPT_DNS_CACHE_TIMEOUT, -1L); - - curlEasyRequest_w->addHeader("Content-Type: text/xml"); - - if (useGzip) - { - curlEasyRequest_w->setoptString(CURLOPT_ENCODING, ""); - } - - int requestTextSize; - char* requestText = XMLRPC_REQUEST_ToXML(request, &requestTextSize); - if (requestText) - { - curlEasyRequest_w->setPost(new AIXMLRPCData(requestText), requestTextSize); - } - else - { - setStatus(StatusOtherError); - } - - curlEasyRequest_w->finalizeRequest(mURI); - } - if (mStatus == LLXMLRPCTransaction::StatusNotStarted) // It could be LLXMLRPCTransaction::StatusOtherError. - { - mCurlEasyRequestStateMachinePtr->run(boost::bind(&LLXMLRPCTransaction::Impl::curlEasyRequestCallback, this, _1)); - setStatus(LLXMLRPCTransaction::StatusStarted); - } - else - { - // This deletes the statemachine immediately. - mCurlEasyRequestStateMachinePtr->kill(); - mCurlEasyRequestStateMachinePtr = NULL; - } -} - -LLXMLRPCTransaction::Impl::~Impl() -{ - if (mCurlEasyRequestStateMachinePtr && mCurlEasyRequestStateMachinePtr->running()) - { - llwarns << "Calling LLXMLRPCTransaction::Impl::~Impl while mCurlEasyRequestStateMachinePtr is still running" << llendl; - mCurlEasyRequestStateMachinePtr->abort(); - } - - if (mResponse) - { - XMLRPC_RequestFree(mResponse, 1); - } -} - -bool LLXMLRPCTransaction::Impl::is_finished(void) const -{ - // Nothing to process anymore. Just wait till the statemachine finished. - return mStatus != LLXMLRPCTransaction::StatusNotStarted && - mStatus != LLXMLRPCTransaction::StatusStarted && - mStatus != LLXMLRPCTransaction::StatusDownloading; -} - -void LLXMLRPCTransaction::Impl::curlEasyRequestCallback(bool success) -{ - llassert(mStatus == LLXMLRPCTransaction::StatusStarted || mStatus == LLXMLRPCTransaction::StatusDownloading); - - AICurlEasyRequestStateMachine* state_machine = mCurlEasyRequestStateMachinePtr; - // We're done with the statemachine, one way or another. - // Set mCurlEasyRequestStateMachinePtr to NULL so we won't call mCurlEasyRequestStateMachinePtr->running() in the destructor. - // Note that the state machine auto-cleaning: it will be deleted by the main-thread after this function returns. - mCurlEasyRequestStateMachinePtr = NULL; - - if (!success) - { - // AICurlEasyRequestStateMachine did abort. - // This currently only happens when libcurl didn't finish before the timer expired. - std::ostringstream msg; - F32 timeout_value = gSavedSettings.getF32("CurlRequestTimeOut"); - msg << "Connection to " << mURI << " timed out (" << timeout_value << " s)!"; - if (timeout_value < 40) - { - msg << "\nTry increasing CurlRequestTimeOut in Debug Settings."; - } - setStatus(LLXMLRPCTransaction::StatusOtherError, msg.str()); - return; - } - - AICurlEasyRequest_wat curlEasyRequest_w(*state_machine->mCurlEasyRequest); - CURLcode result; - curlEasyRequest_w->getResult(&result, &mTransferInfo); - - if (result != CURLE_OK) - { - setCurlStatus(result); - llwarns << "LLXMLRPCTransaction CURL error " - << mCurlCode << ": " << curlEasyRequest_w->getErrorString() << llendl; - llwarns << "LLXMLRPCTransaction request URI: " - << mURI << llendl; - - return; - } - - setStatus(LLXMLRPCTransaction::StatusComplete); - - mResponse = XMLRPC_REQUEST_FromXML( - mResponseText.data(), mResponseText.size(), NULL); - - bool hasError = false; - bool hasFault = false; - int faultCode = 0; - std::string faultString; - - LLXMLRPCValue error(XMLRPC_RequestGetError(mResponse)); - if (error.isValid()) - { - hasError = true; - faultCode = error["faultCode"].asInt(); - faultString = error["faultString"].asString(); - } - else if (XMLRPC_ResponseIsFault(mResponse)) - { - hasFault = true; - faultCode = XMLRPC_GetResponseFaultCode(mResponse); - faultString = XMLRPC_GetResponseFaultString(mResponse); - } - - if (hasError || hasFault) - { - setStatus(LLXMLRPCTransaction::StatusXMLRPCError); - - llwarns << "LLXMLRPCTransaction XMLRPC " - << (hasError ? "error " : "fault ") - << faultCode << ": " - << faultString << llendl; - llwarns << "LLXMLRPCTransaction request URI: " - << mURI << llendl; - } -} - -void LLXMLRPCTransaction::Impl::setStatus(Status status, - const std::string& message, const std::string& uri) -{ - mStatus = status; - mStatusMessage = message; - mStatusURI = uri; - - if (mStatusMessage.empty()) - { - switch (mStatus) - { - case StatusNotStarted: - mStatusMessage = "(not started)"; - break; - - case StatusStarted: - mStatusMessage = "(waiting for server response)"; - break; - - case StatusDownloading: - mStatusMessage = "(reading server response)"; - break; - - case StatusComplete: - mStatusMessage = "(done)"; - break; - - default: - // Usually this means that there's a problem with the login server, - // not with the client. Direct user to status page. - // NOTE: these should really be gHippoGridManager->getCurrentGrid()->getGridName() - // but apparently that's broken as of 1.3 b2 -- MC - mStatusMessage = - "Despite our best efforts, something unexpected has gone wrong. \n" - " \n" - "Please check " + gHippoGridManager->getCurrentGrid()->getGridName() + "'s status \n" - "to see if there is a known problem with the service."; - - //mStatusURI = "http://secondlife.com/status/"; - } - } -} - -void LLXMLRPCTransaction::Impl::setCurlStatus(CURLcode code) -{ - std::string message; - std::string uri = gHippoGridManager->getCurrentGrid()->getSupportUrl(); - - switch (code) - { - case CURLE_COULDNT_RESOLVE_HOST: - message = - "DNS could not resolve the host name.\n" - "Please verify that you can connect to " + gHippoGridManager->getCurrentGrid()->getGridName() + "'s\n" - "web site. If you can, but continue to receive this error,\n" - "please go to the support section and report this problem."; - break; - - case CURLE_SSL_PEER_CERTIFICATE: - message = - "The login server couldn't verify itself via SSL.\n" - "If you continue to receive this error, please go\n" - "to the Support section of " + gHippoGridManager->getCurrentGrid()->getGridName() + "'s web site\n" - "and report the problem."; - break; - - case CURLE_SSL_CACERT: - case CURLE_SSL_CONNECT_ERROR: - message = - "Often this means that your computer's clock is set incorrectly.\n" - "Please go to Control Panels and make sure the time and date\n" - "are set correctly.\n" - "\n" - "If you continue to receive this error, please go\n" - "to the Support section of " + gHippoGridManager->getCurrentGrid()->getGridName() + "'s web site\n" - "and report the problem."; - break; - - default: - break; - } - - mCurlCode = code; - setStatus(StatusCURLError, message, uri); -} - -size_t LLXMLRPCTransaction::Impl::curlDownloadCallback( - char* data, size_t size, size_t nmemb, void* user_data) -{ - Impl& impl(*(Impl*)user_data); - - size_t n = size * nmemb; - -#ifdef CWDEBUG - if (n < 80) - Dout(dc::curl, "Entering LLXMLRPCTransaction::Impl::curlDownloadCallback(\"" << buf2str(data, n) << "\", " << size << ", " << nmemb << ", " << user_data << ")"); - else - Dout(dc::curl, "Entering LLXMLRPCTransaction::Impl::curlDownloadCallback(\"" << buf2str(data, 40) << "\"...\"" << buf2str(data + n - 40, 40) << "\", " << size << ", " << nmemb << ", " << user_data << ")"); -#endif - - impl.mResponseText.append(data, n); - - if (impl.mStatus == LLXMLRPCTransaction::StatusStarted) - { - impl.setStatus(LLXMLRPCTransaction::StatusDownloading); - } - - return n; -} - - -LLXMLRPCTransaction::LLXMLRPCTransaction( - const std::string& uri, XMLRPC_REQUEST request, bool useGzip) -: impl(* new Impl(uri, request, useGzip)) -{ } - - -LLXMLRPCTransaction::LLXMLRPCTransaction( - const std::string& uri, - const std::string& method, LLXMLRPCValue params, bool useGzip) -: impl(* new Impl(uri, method, params, useGzip)) -{ } - -LLXMLRPCTransaction::~LLXMLRPCTransaction() -{ - delete &impl; -} - -bool LLXMLRPCTransaction::is_finished(void) const -{ - return impl.is_finished(); -} - -LLXMLRPCTransaction::Status LLXMLRPCTransaction::status(int* curlCode) -{ - if (curlCode) - { - *curlCode = - (impl.mStatus == StatusCURLError) - ? impl.mCurlCode - : CURLE_OK; - } - - return impl.mStatus; -} - -std::string LLXMLRPCTransaction::statusMessage() -{ - return impl.mStatusMessage; -} - -std::string LLXMLRPCTransaction::statusURI() -{ - return impl.mStatusURI; -} - -XMLRPC_REQUEST LLXMLRPCTransaction::response() -{ - return impl.mResponse; -} - -LLXMLRPCValue LLXMLRPCTransaction::responseValue() -{ - return LLXMLRPCValue(XMLRPC_RequestGetData(impl.mResponse)); -} - - -F64 LLXMLRPCTransaction::transferRate() -{ - if (impl.mStatus != StatusComplete) - { - return 0.0L; - } - - double rate_bits_per_sec = impl.mTransferInfo.mSpeedDownload * 8.0; - - LL_INFOS("AppInit") << "Buffer size: " << impl.mResponseText.size() << " B" << LL_ENDL; - LL_DEBUGS("AppInit") << "Transfer size: " << impl.mTransferInfo.mSizeDownload << " B" << LL_ENDL; - LL_DEBUGS("AppInit") << "Transfer time: " << impl.mTransferInfo.mTotalTime << " s" << LL_ENDL; - LL_INFOS("AppInit") << "Transfer rate: " << rate_bits_per_sec / 1000.0 << " Kb/s" << LL_ENDL; - - return rate_bits_per_sec; -} diff --git a/indra/newview/statemachine/CMakeLists.txt b/indra/newview/statemachine/CMakeLists.txt index 34d362064..2da2ec4cd 100644 --- a/indra/newview/statemachine/CMakeLists.txt +++ b/indra/newview/statemachine/CMakeLists.txt @@ -35,23 +35,17 @@ include_directories( ) set(statemachine_SOURCE_FILES - aistatemachine.cpp - aicurleasyrequeststatemachine.cpp aifilepicker.cpp aifetchinventoryfolder.cpp aievent.cpp - aitimer.cpp ) set(statemachine_HEADER_FILES CMakeLists.txt - aistatemachine.h - aicurleasyrequeststatemachine.h aifilepicker.h aidirpicker.h aifetchinventoryfolder.h aievent.h - aitimer.h ) set_source_files_properties(${statemachine_HEADER_FILES} diff --git a/indra/plugins/filepicker/llfilepicker.cpp b/indra/plugins/filepicker/llfilepicker.cpp index 8342882c8..ed86e0f6e 100644 --- a/indra/plugins/filepicker/llfilepicker.cpp +++ b/indra/plugins/filepicker/llfilepicker.cpp @@ -729,12 +729,15 @@ Boolean LLFilePickerBase::navOpenFilterProc(AEDesc *theItem, void *info, void *c if (fileInfo.filetype != 'JPEG' && fileInfo.filetype != 'JPG ' && fileInfo.filetype != 'BMP ' && fileInfo.filetype != 'TGA ' && fileInfo.filetype != 'BMPf' && fileInfo.filetype != 'TPIC' && - fileInfo.filetype != 'PNG ' && + fileInfo.filetype != 'PNG ' && fileInfo.filetype != 'JP2' && (fileInfo.extension && (CFStringCompare(fileInfo.extension, CFSTR("jpeg"), kCFCompareCaseInsensitive) != kCFCompareEqualTo && CFStringCompare(fileInfo.extension, CFSTR("jpg"), kCFCompareCaseInsensitive) != kCFCompareEqualTo && CFStringCompare(fileInfo.extension, CFSTR("bmp"), kCFCompareCaseInsensitive) != kCFCompareEqualTo && CFStringCompare(fileInfo.extension, CFSTR("tga"), kCFCompareCaseInsensitive) != kCFCompareEqualTo && - CFStringCompare(fileInfo.extension, CFSTR("png"), kCFCompareCaseInsensitive) != kCFCompareEqualTo)) + CFStringCompare(fileInfo.extension, CFSTR("png"), kCFCompareCaseInsensitive) != kCFCompareEqualTo && + CFStringCompare(fileInfo.extension, CFSTR("jp2"), kCFCompareCaseInsensitive) != kCFCompareEqualTo && + CFStringCompare(fileInfo.extension, CFSTR("j2k"), kCFCompareCaseInsensitive) != kCFCompareEqualTo && + CFStringCompare(fileInfo.extension, CFSTR("j2c"), kCFCompareCaseInsensitive) != kCFCompareEqualTo)) ) { result = false; @@ -1353,7 +1356,8 @@ static std::string add_imageload_filter_to_gtkchooser(GtkWindow *picker) gtk_file_filter_add_mime_type(gfilter, "image/jpeg"); gtk_file_filter_add_mime_type(gfilter, "image/png"); gtk_file_filter_add_mime_type(gfilter, "image/bmp"); - std::string filtername = LLTrans::getString("image_files") + " (*.tga; *.bmp; *.jpg; *.png)"; + gtk_file_filter_add_mime_type(gfilter, "image/jp2"); + std::string filtername = LLTrans::getString("image_files") + " (*.tga; *.bmp; *.jpg; *.png; *.jp2; *.j2k; *.j2c)"; add_common_filters_to_gtkchooser(gfilter, picker, filtername); return filtername; }