summaryrefslogtreecommitdiff
path: root/indra/llmessage
diff options
context:
space:
mode:
authorBrad Kittenbrink <brad@lindenlab.com>2009-06-04 16:24:21 +0000
committerBrad Kittenbrink <brad@lindenlab.com>2009-06-04 16:24:21 +0000
commit087bd265534b8e3086ae1af441a9cf0eb7c684df (patch)
treea0c911515476d4ac4aac9bd984de28eb7573a478 /indra/llmessage
parentf9b9372027a41900ad572afcd7ea0d2cc5489b8f (diff)
Merge of QAR-1383 event-system-7 into trunk.
svn merge -r 121797:121853 svn+ssh://svn.lindenlab.com/svn/linden/branches/merge-event-system-7
Diffstat (limited to 'indra/llmessage')
-rw-r--r--indra/llmessage/CMakeLists.txt6
-rw-r--r--indra/llmessage/llsdmessage.cpp150
-rw-r--r--indra/llmessage/llsdmessage.h146
-rw-r--r--indra/llmessage/tests/llsdmessage_test.cpp113
4 files changed, 414 insertions, 1 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/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