diff options
Diffstat (limited to 'indra/llmessage')
-rw-r--r-- | indra/llmessage/CMakeLists.txt | 6 | ||||
-rw-r--r-- | indra/llmessage/llcachename.cpp | 16 | ||||
-rw-r--r-- | indra/llmessage/llcachename.h | 16 | ||||
-rw-r--r-- | indra/llmessage/llsdmessage.cpp | 150 | ||||
-rw-r--r-- | indra/llmessage/llsdmessage.h | 146 | ||||
-rw-r--r-- | indra/llmessage/tests/llsdmessage_test.cpp | 113 |
6 files changed, 430 insertions, 17 deletions
diff --git a/indra/llmessage/CMakeLists.txt b/indra/llmessage/CMakeLists.txt index 0f3e159802..c0f7a4d335 100644 --- a/indra/llmessage/CMakeLists.txt +++ b/indra/llmessage/CMakeLists.txt @@ -3,6 +3,7 @@ project(llmessage) include(00-Common) +include(LLAddBuildTest) include(LLCommon) include(LLMath) include(LLMessage) @@ -63,6 +64,7 @@ set(llmessage_SOURCE_FILES llregionpresenceverifier.cpp llsdappservices.cpp llsdhttpserver.cpp + llsdmessage.cpp llsdmessagebuilder.cpp llsdmessagereader.cpp llsdrpcclient.cpp @@ -156,6 +158,7 @@ set(llmessage_HEADER_FILES llregionpresenceverifier.h llsdappservices.h llsdhttpserver.h + llsdmessage.h llsdmessagebuilder.h llsdmessagereader.h llsdrpcclient.h @@ -217,5 +220,6 @@ IF (NOT LINUX AND VIEWER) #ADD_BUILD_TEST(llhttpclientadapter llmessage) ADD_BUILD_TEST(lltrustedmessageservice llmessage) ADD_BUILD_TEST(lltemplatemessagedispatcher llmessage) + # Don't make llmessage depend on llsdmessage_test because ADD_COMM_BUILD_TEST depends on llmessage! + ADD_COMM_BUILD_TEST(llsdmessage "" "${CMAKE_CURRENT_SOURCE_DIR}/tests/test_llsdmessage_peer.py") ENDIF (NOT LINUX AND VIEWER) - diff --git a/indra/llmessage/llcachename.cpp b/indra/llmessage/llcachename.cpp index 629bd3836d..43abc2953d 100644 --- a/indra/llmessage/llcachename.cpp +++ b/indra/llmessage/llcachename.cpp @@ -96,7 +96,7 @@ public: { } - boost::signals::connection setCallback(const LLCacheNameCallback& cb) + boost::signals2::connection setCallback(const LLCacheNameCallback& cb) { return mSignal.connect(cb); } @@ -215,7 +215,7 @@ public: Impl(LLMessageSystem* msg); ~Impl(); - boost::signals::connection addPending(const LLUUID& id, const LLCacheNameCallback& callback); + boost::signals2::connection addPending(const LLUUID& id, const LLCacheNameCallback& callback); void addPending(const LLUUID& id, const LLHost& host); void processPendingAsks(); @@ -276,10 +276,10 @@ LLCacheName::Impl::~Impl() for_each(mReplyQueue.begin(), mReplyQueue.end(), DeletePointer()); } -boost::signals::connection LLCacheName::Impl::addPending(const LLUUID& id, const LLCacheNameCallback& callback) +boost::signals2::connection LLCacheName::Impl::addPending(const LLUUID& id, const LLCacheNameCallback& callback) { PendingReply* reply = new PendingReply(id, LLHost()); - boost::signals::connection res = reply->setCallback(callback); + boost::signals2::connection res = reply->setCallback(callback); mReplyQueue.push_back(reply); return res; } @@ -295,7 +295,7 @@ void LLCacheName::setUpstream(const LLHost& upstream_host) impl.mUpstreamHost = upstream_host; } -boost::signals::connection LLCacheName::addObserver(const LLCacheNameCallback& callback) +boost::signals2::connection LLCacheName::addObserver(const LLCacheNameCallback& callback) { return impl.mSignal.connect(callback); } @@ -554,9 +554,9 @@ BOOL LLCacheName::getGroupName(const LLUUID& id, std::string& group) // we call it immediately. -Steve // NOTE: Even though passing first and last name is a bit of extra overhead, it eliminates the // potential need for any parsing should any code need to handle first and last name independently. -boost::signals::connection LLCacheName::get(const LLUUID& id, BOOL is_group, const LLCacheNameCallback& callback) +boost::signals2::connection LLCacheName::get(const LLUUID& id, BOOL is_group, const LLCacheNameCallback& callback) { - boost::signals::connection res; + boost::signals2::connection res; if(id.isNull()) { @@ -600,7 +600,7 @@ boost::signals::connection LLCacheName::get(const LLUUID& id, BOOL is_group, con return res; } -boost::signals::connection LLCacheName::get(const LLUUID& id, BOOL is_group, old_callback_t callback, void* user_data) +boost::signals2::connection LLCacheName::get(const LLUUID& id, BOOL is_group, old_callback_t callback, void* user_data) { return get(id, is_group, boost::bind(callback, _1, _2, _3, _4, user_data)); } diff --git a/indra/llmessage/llcachename.h b/indra/llmessage/llcachename.h index 414b6590f6..792f1aeb0a 100644 --- a/indra/llmessage/llcachename.h +++ b/indra/llmessage/llcachename.h @@ -34,17 +34,17 @@ #define LL_LLCACHENAME_H #include <boost/bind.hpp> -#include <boost/signals.hpp> +#include <boost/signals2.hpp> class LLMessageSystem; class LLHost; class LLUUID; -typedef boost::signal<void (const LLUUID& id, - const std::string& first_name, - const std::string& last_name, - BOOL is_group)> LLCacheNameSignal; +typedef boost::signals2::signal<void (const LLUUID& id, + const std::string& first_name, + const std::string& last_name, + BOOL is_group)> LLCacheNameSignal; typedef LLCacheNameSignal::slot_type LLCacheNameCallback; // Old callback with user data for compatability @@ -69,7 +69,7 @@ public: // for simulators, this is the data server void setUpstream(const LLHost& upstream_host); - boost::signals::connection addObserver(const LLCacheNameCallback& callback); + boost::signals2::connection addObserver(const LLCacheNameCallback& callback); // janky old format. Remove after a while. Phoenix. 2008-01-30 void importFile(LLFILE* fp); @@ -96,10 +96,10 @@ public: // If the data is currently available, may call the callback immediatly // otherwise, will request the data, and will call the callback when // available. There is no garuntee the callback will ever be called. - boost::signals::connection get(const LLUUID& id, BOOL is_group, const LLCacheNameCallback& callback); + boost::signals2::connection get(const LLUUID& id, BOOL is_group, const LLCacheNameCallback& callback); // LEGACY - boost::signals::connection get(const LLUUID& id, BOOL is_group, old_callback_t callback, void* user_data); + boost::signals2::connection get(const LLUUID& id, BOOL is_group, old_callback_t callback, void* user_data); // This method needs to be called from time to time to send out // requests. diff --git a/indra/llmessage/llsdmessage.cpp b/indra/llmessage/llsdmessage.cpp new file mode 100644 index 0000000000..f663268466 --- /dev/null +++ b/indra/llmessage/llsdmessage.cpp @@ -0,0 +1,150 @@ +/** + * @file llsdmessage.cpp + * @author Nat Goodspeed + * @date 2008-10-31 + * @brief Implementation for llsdmessage. + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * Copyright (c) 2008, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if LL_WINDOWS +#pragma warning (disable : 4675) // "resolved by ADL" -- just as I want! +#endif + +// Precompiled header +#include "linden_common.h" +// associated header +#include "llsdmessage.h" +// STL headers +// std headers +// external library headers +// other Linden headers +#include "llevents.h" +#include "llsdserialize.h" +#include "llhttpclient.h" +#include "llmessageconfig.h" +#include "llhost.h" +#include "message.h" +#include "llsdutil.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. +static LLSDMessage httpListener; + +LLSDMessage::LLSDMessage(): + // Instantiating our own local LLEventPump with a string name the + // constructor is NOT allowed to tweak is a way of ensuring Singleton + // semantics: attempting to instantiate a second LLSDMessage object would + // throw LLEventPump::DupPumpName. + mEventPump("LLHTTPClient") +{ + mEventPump.listen("self", boost::bind(&LLSDMessage::httpListener, this, _1)); +} + +bool LLSDMessage::httpListener(const LLSD& request) +{ + // 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"]); + // If the LLSD doesn't even have a "url" key, we doubt it was intended for + // this listener. + if (url.empty()) + { + std::ostringstream out; + 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(), + url, "POST", reply, error), + LLSD(), // headers + timeout); + return false; +} + +void LLSDMessage::EventResponder::result(const LLSD& data) +{ + // If our caller passed an empty replyPump name, they're not + // listening: this is a fire-and-forget message. Don't bother posting + // to the pump whose name is "". + if (! mReplyPump.empty()) + { + mPumps.obtain(mReplyPump).post(data); + } + else // default success handling + { + LL_INFOS("LLSDMessage::EventResponder") + << "'" << mMessage << "' to '" << mTarget << "' succeeded" + << LL_ENDL; + } +} + +void LLSDMessage::EventResponder::error(U32 status, const std::string& reason, const LLSD& content) +{ + // If our caller passed an empty errorPump name, they're not + // listening: "default error handling is acceptable." Only post to an + // explicit pump name. + if (! mErrorPump.empty()) + { + LLSD info; + info["target"] = mTarget; + info["message"] = mMessage; + info["status"] = LLSD::Integer(status); + info["reason"] = reason; + info["content"] = content; + mPumps.obtain(mErrorPump).post(info); + } + else // default error handling + { + // convention seems to be to use llinfos, but that seems a bit casual? + LL_WARNS("LLSDMessage::EventResponder") + << "'" << mMessage << "' to '" << mTarget + << "' failed with code " << status << ": " << reason << '\n' + << ll_pretty_print_sd(content) + << LL_ENDL; + } +} + +LLSDMessage::ResponderAdapter::ResponderAdapter(LLHTTPClient::ResponderPtr responder, + const std::string& name): + mResponder(responder), + mReplyPump(name + ".reply", true), // tweak name for uniqueness + mErrorPump(name + ".error", true) +{ + mReplyPump.listen("self", boost::bind(&ResponderAdapter::listener, this, _1, true)); + mErrorPump.listen("self", boost::bind(&ResponderAdapter::listener, this, _1, false)); +} + +bool LLSDMessage::ResponderAdapter::listener(const LLSD& payload, bool success) +{ + if (success) + { + mResponder->result(payload); + } + else + { + mResponder->error(payload["status"].asInteger(), payload["reason"], payload["content"]); + } + + /*---------------- MUST BE LAST STATEMENT BEFORE RETURN ----------------*/ + delete this; + // Destruction of mResponder will usually implicitly free its referent as well + /*------------------------- NOTHING AFTER THIS -------------------------*/ + return false; +} + +void LLSDMessage::link() +{ +} diff --git a/indra/llmessage/llsdmessage.h b/indra/llmessage/llsdmessage.h new file mode 100644 index 0000000000..8ae9451243 --- /dev/null +++ b/indra/llmessage/llsdmessage.h @@ -0,0 +1,146 @@ +/** + * @file llsdmessage.h + * @author Nat Goodspeed + * @date 2008-10-30 + * @brief API intended to unify sending capability, UDP and TCP messages: + * https://wiki.lindenlab.com/wiki/Viewer:Messaging/Messaging_Notes + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * Copyright (c) 2008, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLSDMESSAGE_H) +#define LL_LLSDMESSAGE_H + +#include "llerror.h" // LOG_CLASS() +#include "llevents.h" // LLEventPumps +#include "llhttpclient.h" +#include <string> +#include <stdexcept> + +class LLSD; + +/** + * Class managing the messaging API described in + * https://wiki.lindenlab.com/wiki/Viewer:Messaging/Messaging_Notes + */ +class LLSDMessage +{ + LOG_CLASS(LLSDMessage); + +public: + LLSDMessage(); + + /// Exception if you specify arguments badly + struct ArgError: public std::runtime_error + { + ArgError(const std::string& what): + std::runtime_error(std::string("ArgError: ") + what) {} + }; + + /** + * The response idiom used by LLSDMessage -- LLEventPump names on which to + * post reply or error -- is designed for the case in which your + * reply/error handlers are methods on the same class as the method + * sending the message. Any state available to the sending method that + * 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 + * 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 + * <i>for transitional purposes only.</i> Instantiate a new heap + * ResponderAdapter with your new LLHTTPClient::ResponderPtr. Pass + * ResponderAdapter::getReplyName() and/or getErrorName() in your + * LLSDMessage (or LLViewerRegion::getCapAPI()) request event. The + * ResponderAdapter will call the appropriate Responder method, then + * @c delete itself. + */ + class ResponderAdapter + { + public: + /** + * Bind the new LLHTTPClient::Responder 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, + 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(); } + + private: + // We have two different LLEventStreams, though we route them both to + // the same listener, so that we can bind an extra flag identifying + // which case (reply or error) reached that listener. + bool listener(const LLSD&, bool success); + + LLHTTPClient::ResponderPtr mResponder; + LLEventStream mReplyPump, mErrorPump; + }; + + /** + * Force our implementation file to be linked with caller. The .cpp file + * contains a static instance of this class, which must be linked into the + * executable to support the canonical listener. But since the primary + * interface to that static instance is via a named LLEventPump rather + * than by direct reference, the linker doesn't necessarily perceive the + * necessity to bring in the translation unit. Referencing this dummy + * method forces the issue. + */ + static void link(); + +private: + friend class LLCapabilityListener; + /// Responder used for internal purposes by LLSDMessage and + /// LLCapabilityListener. Others should use higher-level APIs. + class EventResponder: public LLHTTPClient::Responder + { + public: + /** + * LLHTTPClient::Responder 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 + * to use. If the caller doesn't bother to listen() on it, the other pump + * may never materialize at all. + * @a target and @a message are only to clarify error processing. + * For a capability message, @a target should be the region description, + * @a message should be the capability name. + * For a service with a visible URL, pass the URL as @a target and the HTTP verb + * (e.g. "POST") as @a message. + */ + EventResponder(LLEventPumps& pumps, + const std::string& target, const std::string& message, + const std::string& replyPump, const std::string& errorPump): + mPumps(pumps), + mTarget(target), + mMessage(message), + mReplyPump(replyPump), + mErrorPump(errorPump) + {} + + virtual void result(const LLSD& data); + virtual void error(U32 status, const std::string& reason, const LLSD& content); + + private: + LLEventPumps& mPumps; + const std::string mTarget, mMessage, mReplyPump, mErrorPump; + }; + +private: + bool httpListener(const LLSD&); + LLEventStream mEventPump; +}; + +#endif /* ! defined(LL_LLSDMESSAGE_H) */ diff --git a/indra/llmessage/tests/llsdmessage_test.cpp b/indra/llmessage/tests/llsdmessage_test.cpp new file mode 100644 index 0000000000..2957d7cc4f --- /dev/null +++ b/indra/llmessage/tests/llsdmessage_test.cpp @@ -0,0 +1,113 @@ +/** + * @file llsdmessage_tut.cpp + * @author Nat Goodspeed + * @date 2008-12-22 + * @brief Test of llsdmessage.h + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * Copyright (c) 2008, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if LL_WINDOWS +#pragma warning (disable : 4675) // "resolved by ADL" -- just as I want! +#endif + +// Precompiled header +#include "linden_common.h" +// associated header +#include "llsdmessage.h" +// STL headers +#include <iostream> +// std headers +#include <stdexcept> +// external library headers +// other Linden headers +#include "../test/lltut.h" +#include "llsdserialize.h" +#include "llevents.h" +#include "stringize.h" +#include "llhost.h" +#include "tests/networkio.h" +#include "tests/commtest.h" + +/***************************************************************************** +* TUT +*****************************************************************************/ +namespace tut +{ + struct llsdmessage_data: public commtest_data + { + LLEventPump& httpPump; + + llsdmessage_data(): + httpPump(pumps.obtain("LLHTTPClient")) + { + LLSDMessage::link(); + } + }; + typedef test_group<llsdmessage_data> llsdmessage_group; + typedef llsdmessage_group::object llsdmessage_object; + llsdmessage_group llsdmgr("llsdmessage"); + + template<> template<> + void llsdmessage_object::test<1>() + { + bool threw = false; + // This should fail... + try + { + LLSDMessage localListener; + } + catch (const LLEventPump::DupPumpName&) + { + threw = true; + } + ensure("second LLSDMessage should throw", threw); + } + + template<> template<> + void llsdmessage_object::test<2>() + { + LLSD request, body; + body["data"] = "yes"; + request["payload"] = body; + request["reply"] = replyPump.getName(); + request["error"] = errorPump.getName(); + bool threw = false; + try + { + httpPump.post(request); + } + catch (const LLSDMessage::ArgError&) + { + threw = true; + } + ensure("missing URL", threw); + } + + template<> template<> + void llsdmessage_object::test<3>() + { + LLSD request, body; + body["data"] = "yes"; + request["url"] = server + "got-message"; + request["payload"] = body; + request["reply"] = replyPump.getName(); + request["error"] = errorPump.getName(); + httpPump.post(request); + ensure("got response", netio.pump()); + ensure("success response", success); + ensure_equals(result.asString(), "success"); + + body["status"] = 499; + body["reason"] = "custom error message"; + request["url"] = server + "fail"; + request["payload"] = body; + httpPump.post(request); + ensure("got response", netio.pump()); + ensure("failure response", ! success); + ensure_equals(result["status"].asInteger(), body["status"].asInteger()); + ensure_equals(result["reason"].asString(), body["reason"].asString()); + } +} // namespace tut |