summaryrefslogtreecommitdiff
path: root/indra/llmessage
diff options
context:
space:
mode:
Diffstat (limited to 'indra/llmessage')
-rw-r--r--indra/llmessage/CMakeLists.txt8
-rw-r--r--indra/llmessage/llcurl.cpp56
-rw-r--r--indra/llmessage/llcurl.h5
-rw-r--r--indra/llmessage/tests/llhttpclient_test.cpp376
-rw-r--r--indra/llmessage/tests/llsdmessage_test.cpp38
-rw-r--r--indra/llmessage/tests/test_llsdmessage_peer.py19
6 files changed, 444 insertions, 58 deletions
diff --git a/indra/llmessage/CMakeLists.txt b/indra/llmessage/CMakeLists.txt
index 0f40a670fa..d98781e9e6 100644
--- a/indra/llmessage/CMakeLists.txt
+++ b/indra/llmessage/CMakeLists.txt
@@ -254,6 +254,14 @@ if (LL_TESTS)
"${CMAKE_CURRENT_SOURCE_DIR}/tests/test_llsdmessage_peer.py"
)
+ LL_ADD_INTEGRATION_TEST(
+ llhttpclient
+ "llhttpclient.cpp"
+ "${test_libs}"
+ ${PYTHON_EXECUTABLE}
+ "${CMAKE_CURRENT_SOURCE_DIR}/tests/test_llsdmessage_peer.py"
+ )
+
LL_ADD_INTEGRATION_TEST(llavatarnamecache "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llhost "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llpartdata "" "${test_libs}")
diff --git a/indra/llmessage/llcurl.cpp b/indra/llmessage/llcurl.cpp
index b93d429feb..5ea9b58300 100644
--- a/indra/llmessage/llcurl.cpp
+++ b/indra/llmessage/llcurl.cpp
@@ -548,6 +548,7 @@ LLCurl::Multi::Multi(F32 idle_time_out)
mErrorCount(0),
mState(STATE_READY),
mDead(FALSE),
+ mValid(TRUE),
mMutexp(NULL),
mDeletionMutexp(NULL),
mEasyMutexp(NULL)
@@ -583,22 +584,33 @@ LLCurl::Multi::Multi(F32 idle_time_out)
LLCurl::Multi::~Multi()
{
- cleanup() ;
+ cleanup(true) ;
+
+ delete mDeletionMutexp ;
+ mDeletionMutexp = NULL ;
}
-void LLCurl::Multi::cleanup()
+void LLCurl::Multi::cleanup(bool deleted)
{
if(!mCurlMultiHandle)
{
return ; //nothing to clean.
}
+ llassert_always(deleted || !mValid) ;
+ LLMutexLock lock(mDeletionMutexp);
+
// Clean up active
for(easy_active_list_t::iterator iter = mEasyActiveList.begin();
iter != mEasyActiveList.end(); ++iter)
{
Easy* easy = *iter;
check_curl_multi_code(curl_multi_remove_handle(mCurlMultiHandle, easy->getCurlHandle()));
+
+ if(deleted)
+ {
+ easy->mResponder = NULL ; //avoid triggering mResponder.
+ }
delete easy;
}
mEasyActiveList.clear();
@@ -610,11 +622,9 @@ void LLCurl::Multi::cleanup()
check_curl_multi_code(LLCurl::deleteMultiHandle(mCurlMultiHandle));
mCurlMultiHandle = NULL ;
-
+
delete mMutexp ;
mMutexp = NULL ;
- delete mDeletionMutexp ;
- mDeletionMutexp = NULL ;
delete mEasyMutexp ;
mEasyMutexp = NULL ;
@@ -644,10 +654,20 @@ void LLCurl::Multi::unlock()
void LLCurl::Multi::markDead()
{
- LLMutexLock lock(mDeletionMutexp) ;
+ {
+ LLMutexLock lock(mDeletionMutexp) ;
- mDead = TRUE ;
- LLCurl::getCurlThread()->setPriority(mHandle, LLQueuedThread::PRIORITY_URGENT) ;
+ if(mCurlMultiHandle != NULL)
+ {
+ mDead = TRUE ;
+ LLCurl::getCurlThread()->setPriority(mHandle, LLQueuedThread::PRIORITY_URGENT) ;
+
+ return;
+ }
+ }
+
+ //not valid, delete it.
+ delete this;
}
void LLCurl::Multi::setState(LLCurl::Multi::ePerformState state)
@@ -741,10 +761,14 @@ bool LLCurl::Multi::doPerform()
setState(STATE_COMPLETED) ;
mIdleTimer.reset() ;
}
- else if(mIdleTimer.getElapsedTimeF32() > mIdleTimeOut) //idle for too long, remove it.
+ else if(!mValid && mIdleTimer.getElapsedTimeF32() > mIdleTimeOut) //idle for too long, remove it.
{
dead = true ;
}
+ else if(mValid && mIdleTimer.getElapsedTimeF32() > mIdleTimeOut - 1.f) //idle for too long, mark it invalid.
+ {
+ mValid = FALSE ;
+ }
return dead ;
}
@@ -966,15 +990,8 @@ void LLCurlThread::killMulti(LLCurl::Multi* multi)
return ;
}
- if(multi->isValid())
- {
multi->markDead() ;
}
- else
- {
- deleteMulti(multi) ;
- }
-}
//private
bool LLCurlThread::doMultiPerform(LLCurl::Multi* multi)
@@ -992,6 +1009,10 @@ void LLCurlThread::deleteMulti(LLCurl::Multi* multi)
void LLCurlThread::cleanupMulti(LLCurl::Multi* multi)
{
multi->cleanup() ;
+ if(multi->isDead()) //check if marked dead during cleaning up.
+ {
+ deleteMulti(multi) ;
+ }
}
//------------------------------------------------------------
@@ -1506,7 +1527,8 @@ void LLCurl::cleanupClass()
delete sHandleMutexp ;
sHandleMutexp = NULL ;
- llassert(Easy::sActiveHandles.empty());
+ // removed as per https://jira.secondlife.com/browse/SH-3115
+ //llassert(Easy::sActiveHandles.empty());
}
//static
diff --git a/indra/llmessage/llcurl.h b/indra/llmessage/llcurl.h
index fd664c0fa1..d6a7714d4c 100644
--- a/indra/llmessage/llcurl.h
+++ b/indra/llmessage/llcurl.h
@@ -304,7 +304,7 @@ public:
ePerformState getState() ;
bool isCompleted() ;
- bool isValid() {return mCurlMultiHandle != NULL ;}
+ bool isValid() {return mCurlMultiHandle != NULL && mValid;}
bool isDead() {return mDead;}
bool waitToComplete() ;
@@ -318,7 +318,7 @@ public:
private:
void easyFree(LLCurl::Easy*);
- void cleanup() ;
+ void cleanup(bool deleted = false) ;
CURLM* mCurlMultiHandle;
@@ -333,6 +333,7 @@ private:
ePerformState mState;
BOOL mDead ;
+ BOOL mValid ;
LLMutex* mMutexp ;
LLMutex* mDeletionMutexp ;
LLMutex* mEasyMutexp ;
diff --git a/indra/llmessage/tests/llhttpclient_test.cpp b/indra/llmessage/tests/llhttpclient_test.cpp
new file mode 100644
index 0000000000..843c3bcc4b
--- /dev/null
+++ b/indra/llmessage/tests/llhttpclient_test.cpp
@@ -0,0 +1,376 @@
+/**
+ * @file llhttpclient_test.cpp
+ * @brief Testing the HTTP client classes.
+ *
+ * $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$
+ */
+
+/**
+ *
+ * These classes test the HTTP client framework.
+ *
+ */
+
+#include <tut/tut.hpp>
+#include "linden_common.h"
+
+#include "lltut.h"
+#include "llhttpclient.h"
+#include "llformat.h"
+#include "llpipeutil.h"
+#include "llproxy.h"
+#include "llpumpio.h"
+
+#include "llsdhttpserver.h"
+#include "lliohttpserver.h"
+#include "lliosocket.h"
+#include "stringize.h"
+
+namespace tut
+{
+ LLSD storage;
+
+ class LLSDStorageNode : public LLHTTPNode
+ {
+ public:
+ LLSD simpleGet() const { return storage; }
+ LLSD simplePut(const LLSD& value) const { storage = value; return LLSD(); }
+ };
+
+ class ErrorNode : public LLHTTPNode
+ {
+ public:
+ void get(ResponsePtr r, const LLSD& context) const
+ { r->status(599, "Intentional error"); }
+ void post(ResponsePtr r, const LLSD& context, const LLSD& input) const
+ { r->status(input["status"], input["reason"]); }
+ };
+
+ class TimeOutNode : public LLHTTPNode
+ {
+ public:
+ void get(ResponsePtr r, const LLSD& context) const
+ {
+ /* do nothing, the request will eventually time out */
+ }
+ };
+
+ LLHTTPRegistration<LLSDStorageNode> gStorageNode("/test/storage");
+ LLHTTPRegistration<ErrorNode> gErrorNode("/test/error");
+ LLHTTPRegistration<TimeOutNode> gTimeOutNode("/test/timeout");
+
+ struct HTTPClientTestData
+ {
+ public:
+ HTTPClientTestData():
+ local_server(STRINGIZE("http://127.0.0.1:" << getenv("PORT") << "/"))
+ {
+ apr_pool_create(&mPool, NULL);
+ LLCurl::initClass(false);
+ mServerPump = new LLPumpIO(mPool);
+ mClientPump = new LLPumpIO(mPool);
+
+ LLHTTPClient::setPump(*mClientPump);
+ }
+
+ ~HTTPClientTestData()
+ {
+ delete mServerPump;
+ delete mClientPump;
+ LLProxy::cleanupClass();
+ apr_pool_destroy(mPool);
+ }
+
+ void setupTheServer()
+ {
+ LLHTTPNode& root = LLIOHTTPServer::create(mPool, *mServerPump, 8888);
+
+ LLHTTPStandardServices::useServices();
+ LLHTTPRegistrar::buildAllServices(root);
+ }
+
+ void runThePump(float timeout = 100.0f)
+ {
+ LLTimer timer;
+ timer.setTimerExpirySec(timeout);
+
+ while(!mSawCompleted && !mSawCompletedHeader && !timer.hasExpired())
+ {
+ if (mServerPump)
+ {
+ mServerPump->pump();
+ mServerPump->callback();
+ }
+ if (mClientPump)
+ {
+ mClientPump->pump();
+ mClientPump->callback();
+ }
+ }
+ }
+
+ void killServer()
+ {
+ delete mServerPump;
+ mServerPump = NULL;
+ }
+
+ const std::string local_server;
+
+ private:
+ apr_pool_t* mPool;
+ LLPumpIO* mServerPump;
+ LLPumpIO* mClientPump;
+
+ protected:
+ void ensureStatusOK()
+ {
+ if (mSawError)
+ {
+ std::string msg =
+ llformat("error() called when not expected, status %d",
+ mStatus);
+ fail(msg);
+ }
+ }
+
+ void ensureStatusError()
+ {
+ if (!mSawError)
+ {
+ fail("error() wasn't called");
+ }
+ }
+
+ LLSD getResult()
+ {
+ return mResult;
+ }
+ LLSD getHeader()
+ {
+ return mHeader;
+ }
+
+ protected:
+ bool mSawError;
+ U32 mStatus;
+ std::string mReason;
+ bool mSawCompleted;
+ bool mSawCompletedHeader;
+ LLSD mResult;
+ LLSD mHeader;
+ bool mResultDeleted;
+
+ class Result : public LLHTTPClient::Responder
+ {
+ protected:
+ Result(HTTPClientTestData& client)
+ : mClient(client)
+ {
+ }
+
+ public:
+ static boost::intrusive_ptr<Result> build(HTTPClientTestData& client)
+ {
+ return boost::intrusive_ptr<Result>(new Result(client));
+ }
+
+ ~Result()
+ {
+ mClient.mResultDeleted = true;
+ }
+
+ virtual void error(U32 status, const std::string& reason)
+ {
+ mClient.mSawError = true;
+ mClient.mStatus = status;
+ mClient.mReason = reason;
+ }
+
+ virtual void result(const LLSD& content)
+ {
+ mClient.mResult = content;
+ }
+
+ virtual void completed(
+ U32 status, const std::string& reason,
+ const LLSD& content)
+ {
+ LLHTTPClient::Responder::completed(status, reason, content);
+
+ mClient.mSawCompleted = true;
+ }
+
+ virtual void completedHeader(
+ U32 status, const std::string& reason,
+ const LLSD& content)
+ {
+ mClient.mHeader = content;
+ mClient.mSawCompletedHeader = true;
+ }
+
+ private:
+ HTTPClientTestData& mClient;
+ };
+
+ friend class Result;
+
+ protected:
+ LLHTTPClient::ResponderPtr newResult()
+ {
+ mSawError = false;
+ mStatus = 0;
+ mSawCompleted = false;
+ mSawCompletedHeader = false;
+ mResult.clear();
+ mHeader.clear();
+ mResultDeleted = false;
+
+ return Result::build(*this);
+ }
+ };
+
+
+ typedef test_group<HTTPClientTestData> HTTPClientTestGroup;
+ typedef HTTPClientTestGroup::object HTTPClientTestObject;
+ HTTPClientTestGroup httpClientTestGroup("http_client");
+
+ template<> template<>
+ void HTTPClientTestObject::test<1>()
+ {
+ LLHTTPClient::get(local_server, newResult());
+ runThePump();
+ ensureStatusOK();
+ ensure("result object wasn't destroyed", mResultDeleted);
+ }
+
+ template<> template<>
+ void HTTPClientTestObject::test<2>()
+ {
+ // Please nobody listen on this particular port...
+ LLHTTPClient::get("http://127.0.0.1:7950", newResult());
+ runThePump();
+ ensureStatusError();
+ }
+
+ template<> template<>
+ void HTTPClientTestObject::test<3>()
+ {
+ LLSD sd;
+
+ sd["list"][0]["one"] = 1;
+ sd["list"][0]["two"] = 2;
+ sd["list"][1]["three"] = 3;
+ sd["list"][1]["four"] = 4;
+
+ setupTheServer();
+
+ LLHTTPClient::post("http://localhost:8888/web/echo", sd, newResult());
+ runThePump();
+ ensureStatusOK();
+ ensure_equals("echoed result matches", getResult(), sd);
+ }
+
+ template<> template<>
+ void HTTPClientTestObject::test<4>()
+ {
+ LLSD sd;
+
+ sd["message"] = "This is my test message.";
+
+ setupTheServer();
+ LLHTTPClient::put("http://localhost:8888/test/storage", sd, newResult());
+ runThePump();
+ ensureStatusOK();
+
+ LLHTTPClient::get("http://localhost:8888/test/storage", newResult());
+ runThePump();
+ ensureStatusOK();
+ ensure_equals("echoed result matches", getResult(), sd);
+
+ }
+
+ template<> template<>
+ void HTTPClientTestObject::test<5>()
+ {
+ LLSD sd;
+ sd["status"] = 543;
+ sd["reason"] = "error for testing";
+
+ setupTheServer();
+
+ LLHTTPClient::post("http://localhost:8888/test/error", sd, newResult());
+ runThePump();
+ ensureStatusError();
+ ensure_contains("reason", mReason, sd["reason"]);
+ }
+
+ template<> template<>
+ void HTTPClientTestObject::test<6>()
+ {
+ setupTheServer();
+
+ LLHTTPClient::get("http://localhost:8888/test/timeout", newResult());
+ runThePump(1.0f);
+ killServer();
+ runThePump();
+ ensureStatusError();
+ ensure_equals("reason", mReason, "STATUS_ERROR");
+ }
+
+ template<> template<>
+ void HTTPClientTestObject::test<7>()
+ {
+ // Can not use the little mini server. The blocking request
+ // won't ever let it run. Instead get from a known LLSD
+ // source and compare results with the non-blocking get which
+ // is tested against the mini server earlier.
+ LLHTTPClient::get(local_server, newResult());
+ runThePump();
+ ensureStatusOK();
+ LLSD expected = getResult();
+
+ LLSD result;
+ result = LLHTTPClient::blockingGet(local_server);
+ LLSD body = result["body"];
+ ensure_equals("echoed result matches", body.size(), expected.size());
+ }
+ template<> template<>
+ void HTTPClientTestObject::test<8>()
+ {
+ // This is testing for the presence of the Header in the returned results
+ // from an HTTP::get call.
+ LLHTTPClient::get(local_server, newResult());
+ runThePump();
+ ensureStatusOK();
+ LLSD header = getHeader();
+ ensure("got a header", ! header.emptyMap().asBoolean());
+ }
+ template<> template<>
+ void HTTPClientTestObject::test<9>()
+ {
+ LLHTTPClient::head(local_server, newResult());
+ runThePump();
+ ensureStatusOK();
+ ensure("result object wasn't destroyed", mResultDeleted);
+ }
+}
diff --git a/indra/llmessage/tests/llsdmessage_test.cpp b/indra/llmessage/tests/llsdmessage_test.cpp
index 0f2c069303..44b024a83f 100644
--- a/indra/llmessage/tests/llsdmessage_test.cpp
+++ b/indra/llmessage/tests/llsdmessage_test.cpp
@@ -42,6 +42,7 @@
// external library headers
// other Linden headers
#include "../test/lltut.h"
+#include "../test/catch_and_store_what_in.h"
#include "llsdserialize.h"
#include "llevents.h"
#include "stringize.h"
@@ -72,43 +73,14 @@ namespace tut
template<> template<>
void llsdmessage_object::test<1>()
{
- bool threw = false;
+ std::string threw;
// This should fail...
try
{
LLSDMessage localListener;
}
- catch (const LLEventPump::DupPumpName&)
- {
- threw = true;
- }
- catch (const std::runtime_error& ex)
- {
- // This clause is because on Linux, on the viewer side, for this
- // one test program (though not others!), the
- // LLEventPump::DupPumpName exception isn't caught by the clause
- // above. Warn the user...
- std::cerr << "Failed to catch " << typeid(ex).name() << std::endl;
- // But if the expected exception was thrown, allow the test to
- // succeed anyway. Not sure how else to handle this odd case.
- if (std::string(typeid(ex).name()) == typeid(LLEventPump::DupPumpName).name())
- {
- threw = true;
- }
- else
- {
- // We don't even recognize this exception. Let it propagate
- // out to TUT to fail the test.
- throw;
- }
- }
- catch (...)
- {
- std::cerr << "Utterly failed to catch expected exception!" << std::endl;
- // This case is full of fail. We HAVE to address it.
- throw;
- }
- ensure("second LLSDMessage should throw", threw);
+ CATCH_AND_STORE_WHAT_IN(threw, LLEventPump::DupPumpName)
+ ensure("second LLSDMessage should throw", ! threw.empty());
}
template<> template<>
@@ -143,7 +115,7 @@ namespace tut
httpPump.post(request);
ensure("got response", netio.pump());
ensure("success response", success);
- ensure_equals(result.asString(), "success");
+ ensure_equals(result["reply"].asString(), "success");
body["status"] = 499;
body["reason"] = "custom error message";
diff --git a/indra/llmessage/tests/test_llsdmessage_peer.py b/indra/llmessage/tests/test_llsdmessage_peer.py
index 22edd9dad8..fe4f3a8c01 100644
--- a/indra/llmessage/tests/test_llsdmessage_peer.py
+++ b/indra/llmessage/tests/test_llsdmessage_peer.py
@@ -78,25 +78,32 @@ class TestHTTPRequestHandler(BaseHTTPRequestHandler):
## debug("root node tag %s", tree.getroot().tag)
## return llsd.to_python(tree.getroot())
- def do_GET(self):
+ def do_HEAD(self):
+ self.do_GET(withdata=False)
+
+ def do_GET(self, withdata=True):
# Of course, don't attempt to read data.
- self.answer(dict(reply="success", status=500,
- reason="Your GET operation requested failure"))
+ data = dict(reply="success", body="avatar", random=17)
+ self.answer(data, withdata=withdata)
def do_POST(self):
# Read the provided POST data.
self.answer(self.read_xml())
- def answer(self, data):
+ def answer(self, data, withdata=True):
debug("%s.answer(%s): self.path = %r", self.__class__.__name__, data, self.path)
if "fail" not in self.path:
- response = llsd.format_xml(data.get("reply", llsd.LLSD("success")))
+ data = data.copy() # we're going to modify
+ # Ensure there's a "reply" key in data, even if there wasn't before
+ data["reply"] = data.get("reply", llsd.LLSD("success"))
+ response = llsd.format_xml(data)
debug("success: %s", response)
self.send_response(200)
self.send_header("Content-type", "application/llsd+xml")
self.send_header("Content-Length", str(len(response)))
self.end_headers()
- self.wfile.write(response)
+ if withdata:
+ self.wfile.write(response)
else: # fail requested
status = data.get("status", 500)
# self.responses maps an int status to a (short, long) pair of