From 75242eab8f8a892c792681fca080d86cfbb3e061 Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Mon, 11 Jun 2012 19:06:52 -0400 Subject: Bring in the testrunner/http server scaffold for better integration testing. This brings in a copy of llmessage's llsdmessage testing server. We run a mocked HTTP service to handle requests and the integration tests run against it by picking up the LL_TEST_PORT environment variable when running. Add some checks and output to produce useful info when run in the wrong environment and when bad status is received. Later will add a dead port as well so we can test that rather than use 'localhost:2'. --- indra/llcorehttp/tests/test_llcorehttp_peer.py | 146 +++++++++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 indra/llcorehttp/tests/test_llcorehttp_peer.py (limited to 'indra/llcorehttp/tests/test_llcorehttp_peer.py') diff --git a/indra/llcorehttp/tests/test_llcorehttp_peer.py b/indra/llcorehttp/tests/test_llcorehttp_peer.py new file mode 100644 index 0000000000..3e200a5c19 --- /dev/null +++ b/indra/llcorehttp/tests/test_llcorehttp_peer.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python +"""\ +@file test_llsdmessage_peer.py +@author Nat Goodspeed +@date 2008-10-09 +@brief This script asynchronously runs the executable (with args) specified on + the command line, returning its result code. While that executable is + running, we provide dummy local services for use by C++ tests. + +$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$ +""" + +import os +import sys +from threading import Thread +from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler + +mydir = os.path.dirname(__file__) # expected to be .../indra/llmessage/tests/ +sys.path.insert(0, os.path.join(mydir, os.pardir, os.pardir, "lib", "python")) +from indra.util.fastest_elementtree import parse as xml_parse +from indra.base import llsd +from testrunner import freeport, run, debug, VERBOSE + +class TestHTTPRequestHandler(BaseHTTPRequestHandler): + """This subclass of BaseHTTPRequestHandler is to receive and echo + LLSD-flavored messages sent by the C++ LLHTTPClient. + """ + def read(self): + # The following logic is adapted from the library module + # SimpleXMLRPCServer.py. + # Get arguments by reading body of request. + # We read this in chunks to avoid straining + # socket.read(); around the 10 or 15Mb mark, some platforms + # begin to have problems (bug #792570). + try: + size_remaining = int(self.headers["content-length"]) + except (KeyError, ValueError): + return "" + max_chunk_size = 10*1024*1024 + L = [] + while size_remaining: + chunk_size = min(size_remaining, max_chunk_size) + chunk = self.rfile.read(chunk_size) + L.append(chunk) + size_remaining -= len(chunk) + return ''.join(L) + # end of swiped read() logic + + def read_xml(self): + # This approach reads the entire POST data into memory first + return llsd.parse(self.read()) +## # This approach attempts to stream in the LLSD XML from self.rfile, +## # assuming that the underlying XML parser reads its input file +## # incrementally. Unfortunately I haven't been able to make it work. +## tree = xml_parse(self.rfile) +## debug("Finished raw parse") +## debug("parsed XML tree %s", tree) +## debug("parsed root node %s", tree.getroot()) +## debug("root node tag %s", tree.getroot().tag) +## return llsd.to_python(tree.getroot()) + + def do_GET(self): + # Of course, don't attempt to read data. + self.answer(dict(reply="success", status=200, + reason="Your GET operation worked")) + + def do_POST(self): + # Read the provided POST data. + self.answer(self.read()) + + def answer(self, data): + 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"))) + 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) + else: # fail requested + status = data.get("status", 500) + # self.responses maps an int status to a (short, long) pair of + # strings. We want the longer string. That's why we pass a string + # pair to get(): the [1] will select the second string, whether it + # came from self.responses or from our default pair. + reason = data.get("reason", + self.responses.get(status, + ("fail requested", + "Your request specified failure status %s " + "without providing a reason" % status))[1]) + debug("fail requested: %s: %r", status, reason) + self.send_error(status, reason) + + if not VERBOSE: + # When VERBOSE is set, skip both these overrides because they exist to + # suppress output. + + def log_request(self, code, size=None): + # For present purposes, we don't want the request splattered onto + # stderr, as it would upset devs watching the test run + pass + + def log_error(self, format, *args): + # Suppress error output as well + pass + +class Server(HTTPServer): + # This pernicious flag is on by default in HTTPServer. But proper + # operation of freeport() absolutely depends on it being off. + allow_reuse_address = False + +if __name__ == "__main__": + # Instantiate a Server(TestHTTPRequestHandler) on the first free port + # in the specified port range. Doing this inline is better than in a + # daemon thread: if it blows up here, we'll get a traceback. If it blew up + # in some other thread, the traceback would get eaten and we'd run the + # subject test program anyway. + httpd, port = freeport(xrange(8000, 8020), + lambda port: Server(('127.0.0.1', port), TestHTTPRequestHandler)) + # Pass the selected port number to the subject test program via the + # environment. We don't want to impose requirements on the test program's + # command-line parsing -- and anyway, for C++ integration tests, that's + # performed in TUT code rather than our own. + os.environ["LL_TEST_PORT"] = str(port) + debug("$LL_TEST_PORT = %s", port) + sys.exit(run(server=Thread(name="httpd", target=httpd.serve_forever), *sys.argv[1:])) -- cgit v1.3 From 7adeb3923728ca84a309a6af141c148ce38066fc Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Tue, 12 Jun 2012 17:42:33 -0400 Subject: HTTP Proxy, PUT & POST, unit tests and refactoring. Implemented/modified PUT & POST to not used chunked encoding for the request. Made the unit test much happier and probably a better thing for the pipeline. Have a cheesy static & dynamic proxy capability using both local options and a way to wire into LLProxy in llmessages. Not a clean thing but it will get the proxy path working with both socks5 & http proxies. Refactoring to get rid of unneeded library handler and unified an HttpStatus return for all requests. Big batch of code removed as a result of that and more is possible as well as some syscall avoidance with a bit more work. Boosted the unit tests for simple PUT & POST test which revealed the test harness does *not* like chunked encoding so we'll avoid it for now (and don't really need it in any of our schemes). --- indra/llcorehttp/CMakeLists.txt | 5 + indra/llcorehttp/_httpopcancel.cpp | 11 -- indra/llcorehttp/_httpopcancel.h | 3 - indra/llcorehttp/_httpoperation.cpp | 18 +- indra/llcorehttp/_httpoperation.h | 13 +- indra/llcorehttp/_httpoprequest.cpp | 63 +++++-- indra/llcorehttp/_httpoprequest.h | 7 + indra/llcorehttp/_httpopsetget.cpp | 105 +++++++++++ indra/llcorehttp/_httpopsetget.h | 78 ++++++++ indra/llcorehttp/_httpopsetpriority.cpp | 14 -- indra/llcorehttp/_httpopsetpriority.h | 4 +- indra/llcorehttp/_httppolicy.cpp | 6 + indra/llcorehttp/_httppolicy.h | 4 +- indra/llcorehttp/_httppolicyglobal.cpp | 38 ++-- indra/llcorehttp/_httppolicyglobal.h | 7 +- indra/llcorehttp/_httpservice.cpp | 11 +- indra/llcorehttp/_httpservice.h | 10 +- indra/llcorehttp/httprequest.cpp | 151 ++++++++-------- indra/llcorehttp/httprequest.h | 44 ++++- indra/llcorehttp/tests/llcorehttp_test.cpp | 6 + indra/llcorehttp/tests/test_httpoperation.hpp | 5 +- indra/llcorehttp/tests/test_httprequest.hpp | 239 +++++++++++++++++++++++++ indra/llcorehttp/tests/test_llcorehttp_peer.py | 10 +- indra/newview/llappviewer.cpp | 13 ++ 24 files changed, 702 insertions(+), 163 deletions(-) create mode 100644 indra/llcorehttp/_httpopsetget.cpp create mode 100644 indra/llcorehttp/_httpopsetget.h (limited to 'indra/llcorehttp/tests/test_llcorehttp_peer.py') diff --git a/indra/llcorehttp/CMakeLists.txt b/indra/llcorehttp/CMakeLists.txt index a0827286e3..4273b32fe3 100644 --- a/indra/llcorehttp/CMakeLists.txt +++ b/indra/llcorehttp/CMakeLists.txt @@ -10,12 +10,14 @@ include(OpenSSL) include(ZLIB) include(LLCoreHttp) include(LLAddBuildTest) +include(LLMessage) include(LLCommon) include(Tut) include_directories (${CMAKE_CURRENT_SOURCE_DIR}) include_directories( + ${LLMESSAGE_INCLUDE_DIRS} ${LLCOMMON_INCLUDE_DIRS} ${LLCOREHTTP_INCLUDE_DIRS} ) @@ -31,6 +33,7 @@ set(llcorehttp_SOURCE_FILES _httpopcancel.cpp _httpoperation.cpp _httpoprequest.cpp + _httpopsetget.cpp _httpopsetpriority.cpp _httppolicy.cpp _httppolicyglobal.cpp @@ -54,6 +57,7 @@ set(llcorehttp_HEADER_FILES _httpopcancel.h _httpoperation.h _httpoprequest.h + _httpopsetget.h _httpopsetpriority.h _httppolicy.h _httppolicyglobal.h @@ -113,6 +117,7 @@ if (LL_TESTS) set(test_libs ${LLCOREHTTP_LIBRARIES} ${WINDOWS_LIBRARIES} + ${LLMESSAGE_LIBRARIES} ${LLCOMMON_LIBRARIES} ${GOOGLEMOCK_LIBRARIES} ${CURL_LIBRARIES} diff --git a/indra/llcorehttp/_httpopcancel.cpp b/indra/llcorehttp/_httpopcancel.cpp index 69dbff4bb4..ad624d2e57 100644 --- a/indra/llcorehttp/_httpopcancel.cpp +++ b/indra/llcorehttp/_httpopcancel.cpp @@ -66,17 +66,6 @@ void HttpOpCancel::stageFromRequest(HttpService * service) } -void HttpOpCancel::visitNotifier(HttpRequest * request) -{ - if (mLibraryHandler) - { - HttpResponse * response = new HttpResponse(); - mLibraryHandler->onCompleted(static_cast(this), response); - response->release(); - } -} - - } // end namespace LLCore diff --git a/indra/llcorehttp/_httpopcancel.h b/indra/llcorehttp/_httpopcancel.h index fab6f1f362..6d1e0f8774 100644 --- a/indra/llcorehttp/_httpopcancel.h +++ b/indra/llcorehttp/_httpopcancel.h @@ -59,13 +59,10 @@ private: public: virtual void stageFromRequest(HttpService *); - - virtual void visitNotifier(HttpRequest * request); public: // Request data HttpHandle mHandle; - }; // end class HttpOpCancel diff --git a/indra/llcorehttp/_httpoperation.cpp b/indra/llcorehttp/_httpoperation.cpp index d966efd12b..b5c58013d4 100644 --- a/indra/llcorehttp/_httpoperation.cpp +++ b/indra/llcorehttp/_httpoperation.cpp @@ -47,7 +47,6 @@ namespace LLCore HttpOperation::HttpOperation() : LLCoreInt::RefCounted(true), mReplyQueue(NULL), - mLibraryHandler(NULL), mUserHandler(NULL), mReqPolicy(HttpRequest::DEFAULT_POLICY_ID), mReqPriority(0U) @@ -57,13 +56,12 @@ HttpOperation::HttpOperation() HttpOperation::~HttpOperation() { - setHandlers(NULL, NULL, NULL); + setReplyPath(NULL, NULL); } -void HttpOperation::setHandlers(HttpReplyQueue * reply_queue, - HttpHandler * lib_handler, - HttpHandler * user_handler) +void HttpOperation::setReplyPath(HttpReplyQueue * reply_queue, + HttpHandler * user_handler) { if (reply_queue != mReplyQueue) { @@ -80,9 +78,6 @@ void HttpOperation::setHandlers(HttpReplyQueue * reply_queue, mReplyQueue = reply_queue; } - // Not refcounted - mLibraryHandler = lib_handler; - // Not refcounted mUserHandler = user_handler; } @@ -121,11 +116,12 @@ void HttpOperation::stageFromActive(HttpService *) void HttpOperation::visitNotifier(HttpRequest *) { - if (mLibraryHandler) + if (mUserHandler) { HttpResponse * response = new HttpResponse(); - mLibraryHandler->onCompleted(static_cast(this), response); + response->setStatus(mStatus); + mUserHandler->onCompleted(static_cast(this), response); response->release(); } @@ -142,7 +138,7 @@ HttpStatus HttpOperation::cancel() void HttpOperation::addAsReply() { - if (mReplyQueue && mLibraryHandler) + if (mReplyQueue) { addRef(); mReplyQueue->addOp(this); diff --git a/indra/llcorehttp/_httpoperation.h b/indra/llcorehttp/_httpoperation.h index 01e26029d2..c93aa2def9 100644 --- a/indra/llcorehttp/_httpoperation.h +++ b/indra/llcorehttp/_httpoperation.h @@ -80,9 +80,8 @@ private: void operator=(const HttpOperation &); // Not defined public: - void setHandlers(HttpReplyQueue * reply_queue, - HttpHandler * lib_handler, - HttpHandler * user_handler); + void setReplyPath(HttpReplyQueue * reply_queue, + HttpHandler * handler); HttpHandler * getUserHandler() const { @@ -102,13 +101,15 @@ protected: protected: HttpReplyQueue * mReplyQueue; // Have refcount - HttpHandler * mLibraryHandler; // Have refcount - HttpHandler * mUserHandler; // Have refcount + HttpHandler * mUserHandler; public: + // Request Data HttpRequest::policy_t mReqPolicy; HttpRequest::priority_t mReqPriority; - + + // Reply Data + HttpStatus mStatus; }; // end class HttpOperation diff --git a/indra/llcorehttp/_httpoprequest.cpp b/indra/llcorehttp/_httpoprequest.cpp index ea0b99303e..e2550d057e 100644 --- a/indra/llcorehttp/_httpoprequest.cpp +++ b/indra/llcorehttp/_httpoprequest.cpp @@ -44,6 +44,7 @@ #include "_httplibcurl.h" #include "llhttpstatuscodes.h" +#include "llproxy.h" namespace { @@ -207,7 +208,7 @@ void HttpOpRequest::visitNotifier(HttpRequest * request) { static const HttpStatus partial_content(HTTP_PARTIAL_CONTENT, HE_SUCCESS); - if (mLibraryHandler) + if (mUserHandler) { HttpResponse * response = new HttpResponse(); response->setStatus(mStatus); @@ -219,7 +220,7 @@ void HttpOpRequest::visitNotifier(HttpRequest * request) response->setRange(mReplyOffset, mReplyLength); } - mLibraryHandler->onCompleted(static_cast(this), response); + mUserHandler->onCompleted(static_cast(this), response); response->release(); } @@ -304,6 +305,39 @@ HttpStatus HttpOpRequest::setupPost(HttpRequest::policy_t policy_id, } +HttpStatus HttpOpRequest::setupPut(HttpRequest::policy_t policy_id, + HttpRequest::priority_t priority, + const std::string & url, + BufferArray * body, + HttpOptions * options, + HttpHeaders * headers) +{ + HttpStatus status; + + mProcFlags = 0; + mReqPolicy = policy_id; + mReqPriority = priority; + mReqMethod = HOR_PUT; + mReqURL = url; + if (body) + { + body->addRef(); + mReqBody = body; + } + if (headers && ! mReqHeaders) + { + headers->addRef(); + mReqHeaders = headers; + } + if (options && ! mReqOptions) + { + mReqOptions = new HttpOptions(*options); + } + + return status; +} + + HttpStatus HttpOpRequest::prepareRequest(HttpService * service) { // Scrub transport and result data for retried op case @@ -346,8 +380,6 @@ HttpStatus HttpOpRequest::prepareRequest(HttpService * service) curl_easy_setopt(mCurlHandle, CURLOPT_URL, mReqURL.c_str()); curl_easy_setopt(mCurlHandle, CURLOPT_PRIVATE, this); curl_easy_setopt(mCurlHandle, CURLOPT_ENCODING, ""); - // *FIXME: Need to deal with proxy setup... - // curl_easy_setopt(handle, CURLOPT_PROXY, ""); // *FIXME: Revisit this old DNS timeout setting - may no longer be valid curl_easy_setopt(mCurlHandle, CURLOPT_DNS_CACHE_TIMEOUT, 0); @@ -361,18 +393,31 @@ HttpStatus HttpOpRequest::prepareRequest(HttpService * service) curl_easy_setopt(mCurlHandle, CURLOPT_SSL_VERIFYPEER, 1); curl_easy_setopt(mCurlHandle, CURLOPT_SSL_VERIFYHOST, 0); - std::string opt_value; + const std::string * opt_value(NULL); if (policy.get(HttpRequest::GP_CA_PATH, opt_value)) { - curl_easy_setopt(mCurlHandle, CURLOPT_CAPATH, opt_value.c_str()); + curl_easy_setopt(mCurlHandle, CURLOPT_CAPATH, opt_value->c_str()); } if (policy.get(HttpRequest::GP_CA_FILE, opt_value)) { - curl_easy_setopt(mCurlHandle, CURLOPT_CAINFO, opt_value.c_str()); + curl_easy_setopt(mCurlHandle, CURLOPT_CAINFO, opt_value->c_str()); } if (policy.get(HttpRequest::GP_HTTP_PROXY, opt_value)) { - curl_easy_setopt(mCurlHandle, CURLOPT_PROXY, opt_value.c_str()); + if (*opt_value == "LLProxy") + { + // Use the viewer-based thread-safe API which has a + // fast/safe check for proxy enable. Would like to + // encapsulate this someway... + LLProxy::getInstance()->applyProxySettings(mCurlHandle); + } + else + { + // *TODO: This is fine for now but get fuller socks/ + // authentication thing going later.... + curl_easy_setopt(mCurlHandle, CURLOPT_PROXY, opt_value->c_str()); + curl_easy_setopt(mCurlHandle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP); + } } switch (mReqMethod) @@ -394,7 +439,6 @@ HttpStatus HttpOpRequest::prepareRequest(HttpService * service) } curl_easy_setopt(mCurlHandle, CURLOPT_POSTFIELDS, static_cast(NULL)); curl_easy_setopt(mCurlHandle, CURLOPT_POSTFIELDSIZE, data_size); - mCurlHeaders = curl_slist_append(mCurlHeaders, "Transfer-Encoding: chunked"); mCurlHeaders = curl_slist_append(mCurlHeaders, "Expect:"); } break; @@ -409,7 +453,6 @@ HttpStatus HttpOpRequest::prepareRequest(HttpService * service) } curl_easy_setopt(mCurlHandle, CURLOPT_INFILESIZE, data_size); curl_easy_setopt(mCurlHandle, CURLOPT_POSTFIELDS, (void *) NULL); - mCurlHeaders = curl_slist_append(mCurlHeaders, "Transfer-Encoding: chunked"); mCurlHeaders = curl_slist_append(mCurlHeaders, "Expect:"); mCurlHeaders = curl_slist_append(mCurlHeaders, "Connection: keep-alive"); mCurlHeaders = curl_slist_append(mCurlHeaders, "Keep-alive: 300"); diff --git a/indra/llcorehttp/_httpoprequest.h b/indra/llcorehttp/_httpoprequest.h index 6dcf30ca0c..80893beb40 100644 --- a/indra/llcorehttp/_httpoprequest.h +++ b/indra/llcorehttp/_httpoprequest.h @@ -91,6 +91,13 @@ public: HttpOptions * options, HttpHeaders * headers); + HttpStatus setupPut(HttpRequest::policy_t policy_id, + HttpRequest::priority_t priority, + const std::string & url, + BufferArray * body, + HttpOptions * options, + HttpHeaders * headers); + HttpStatus prepareRequest(HttpService * service); virtual HttpStatus cancel(); diff --git a/indra/llcorehttp/_httpopsetget.cpp b/indra/llcorehttp/_httpopsetget.cpp new file mode 100644 index 0000000000..21e058b2be --- /dev/null +++ b/indra/llcorehttp/_httpopsetget.cpp @@ -0,0 +1,105 @@ +/** + * @file _httpopsetget.cpp + * @brief Definitions for internal class HttpOpSetGet + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, 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 "_httpopsetget.h" + +#include +#include + +#include "httpcommon.h" +#include "httphandler.h" +#include "httpresponse.h" + +#include "_httprequestqueue.h" +#include "_httpreplyqueue.h" +#include "_httpservice.h" +#include "_httppolicy.h" +#include "_httplibcurl.h" + + +namespace LLCore +{ + + +// ================================== +// HttpOpSetget +// ================================== + + +HttpOpSetGet::HttpOpSetGet() + : HttpOperation(), + mIsGlobal(false), + mDoSet(false), + mSetting(-1), // Nothing requested + mLongValue(0L) +{} + + +HttpOpSetGet::~HttpOpSetGet() +{} + + +void HttpOpSetGet::setupGet(HttpRequest::EGlobalPolicy setting) +{ + mIsGlobal = true; + mSetting = setting; +} + + +void HttpOpSetGet::setupSet(HttpRequest::EGlobalPolicy setting, const std::string & value) +{ + mIsGlobal = true; + mDoSet = true; + mSetting = setting; + mStrValue = value; +} + + +void HttpOpSetGet::stageFromRequest(HttpService * service) +{ + HttpPolicyGlobal & pol_opt(service->getPolicy().getGlobalOptions()); + HttpRequest::EGlobalPolicy setting(static_cast(mSetting)); + + if (mDoSet) + { + mStatus = pol_opt.set(setting, mStrValue); + } + if (mStatus) + { + const std::string * value; + if ((mStatus = pol_opt.get(setting, value))) + { + mStrValue = *value; + } + } + + addAsReply(); +} + + +} // end namespace LLCore + + diff --git a/indra/llcorehttp/_httpopsetget.h b/indra/llcorehttp/_httpopsetget.h new file mode 100644 index 0000000000..e065eb4c30 --- /dev/null +++ b/indra/llcorehttp/_httpopsetget.h @@ -0,0 +1,78 @@ +/** + * @file _httpopsetget.h + * @brief Internal declarations for the HttpOpSetGet subclass + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, 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 _LLCORE_HTTP_OPSETGET_H_ +#define _LLCORE_HTTP_OPSETGET_H_ + + +#include "linden_common.h" // Modifies curl/curl.h interfaces + +#include "httpcommon.h" + +#include + +#include "_httpoperation.h" +#include "_refcounted.h" + + +namespace LLCore +{ + + +/// HttpOpSetGet requests dynamic changes to policy and +/// configuration settings. + +class HttpOpSetGet : public HttpOperation +{ +public: + HttpOpSetGet(); + virtual ~HttpOpSetGet(); + +private: + HttpOpSetGet(const HttpOpSetGet &); // Not defined + void operator=(const HttpOpSetGet &); // Not defined + +public: + void setupGet(HttpRequest::EGlobalPolicy setting); + void setupSet(HttpRequest::EGlobalPolicy setting, const std::string & value); + + virtual void stageFromRequest(HttpService *); + +public: + // Request data + bool mIsGlobal; + bool mDoSet; + int mSetting; + long mLongValue; + std::string mStrValue; + +}; // end class HttpOpSetGet + + +} // end namespace LLCore + +#endif // _LLCORE_HTTP_OPSETGET_H_ + diff --git a/indra/llcorehttp/_httpopsetpriority.cpp b/indra/llcorehttp/_httpopsetpriority.cpp index b0ee577087..d48c7a0b7d 100644 --- a/indra/llcorehttp/_httpopsetpriority.cpp +++ b/indra/llcorehttp/_httpopsetpriority.cpp @@ -60,18 +60,4 @@ void HttpOpSetPriority::stageFromRequest(HttpService * service) } -void HttpOpSetPriority::visitNotifier(HttpRequest * request) -{ - if (mLibraryHandler) - { - HttpResponse * response = new HttpResponse(); - - response->setStatus(mStatus); - mLibraryHandler->onCompleted(static_cast(this), response); - - response->release(); - } -} - - } // end namespace LLCore diff --git a/indra/llcorehttp/_httpopsetpriority.h b/indra/llcorehttp/_httpopsetpriority.h index b972f50fff..f1e94b6e43 100644 --- a/indra/llcorehttp/_httpopsetpriority.h +++ b/indra/llcorehttp/_httpopsetpriority.h @@ -56,10 +56,8 @@ private: public: virtual void stageFromRequest(HttpService *); - virtual void visitNotifier(HttpRequest * request); - protected: - HttpStatus mStatus; + // Request Data HttpHandle mHandle; HttpRequest::priority_t mPriority; }; // end class HttpOpSetPriority diff --git a/indra/llcorehttp/_httppolicy.cpp b/indra/llcorehttp/_httppolicy.cpp index 72bb6f14e4..8ee3f88658 100644 --- a/indra/llcorehttp/_httppolicy.cpp +++ b/indra/llcorehttp/_httppolicy.cpp @@ -71,6 +71,12 @@ HttpPolicy::~HttpPolicy() } +void HttpPolicy::setPolicies(const HttpPolicyGlobal & global) +{ + mGlobalOptions = global; +} + + void HttpPolicy::addOp(HttpOpRequest * op) { const int policy_class(op->mReqPolicy); diff --git a/indra/llcorehttp/_httppolicy.h b/indra/llcorehttp/_httppolicy.h index 14f6a9a676..73c22bab78 100644 --- a/indra/llcorehttp/_httppolicy.h +++ b/indra/llcorehttp/_httppolicy.h @@ -95,7 +95,9 @@ public: { return mGlobalOptions; } - + + void setPolicies(const HttpPolicyGlobal & global); + protected: struct State { diff --git a/indra/llcorehttp/_httppolicyglobal.cpp b/indra/llcorehttp/_httppolicyglobal.cpp index 877b85896f..d95d73cfba 100644 --- a/indra/llcorehttp/_httppolicyglobal.cpp +++ b/indra/llcorehttp/_httppolicyglobal.cpp @@ -32,7 +32,7 @@ namespace LLCore HttpPolicyGlobal::HttpPolicyGlobal() - : mValidMask(0UL), + : mSetMask(0UL), mConnectionLimit(32L) {} @@ -41,6 +41,20 @@ HttpPolicyGlobal::~HttpPolicyGlobal() {} +HttpPolicyGlobal & HttpPolicyGlobal::operator=(const HttpPolicyGlobal & other) +{ + if (this != &other) + { + mSetMask = other.mSetMask; + mConnectionLimit = other.mConnectionLimit; + mCAPath = other.mCAPath; + mCAFile = other.mCAFile; + mHttpProxy = other.mHttpProxy; + } + return *this; +} + + HttpStatus HttpPolicyGlobal::set(HttpRequest::EGlobalPolicy opt, long value) { switch (opt) @@ -53,7 +67,7 @@ HttpStatus HttpPolicyGlobal::set(HttpRequest::EGlobalPolicy opt, long value) return HttpStatus(HttpStatus::LLCORE, HE_INVALID_ARG); } - mValidMask |= 1UL << int(opt); + mSetMask |= 1UL << int(opt); return HttpStatus(); } @@ -78,7 +92,7 @@ HttpStatus HttpPolicyGlobal::set(HttpRequest::EGlobalPolicy opt, const std::stri return HttpStatus(HttpStatus::LLCORE, HE_INVALID_ARG); } - mValidMask |= 1UL << int(opt); + mSetMask |= 1UL << int(opt); return HttpStatus(); } @@ -90,7 +104,7 @@ HttpStatus HttpPolicyGlobal::get(HttpRequest::EGlobalPolicy opt, long & value) switch (opt) { case HttpRequest::GP_CONNECTION_LIMIT: - if (! (mValidMask & (1UL << int(opt)))) + if (! (mSetMask & (1UL << int(opt)))) return not_set; value = mConnectionLimit; break; @@ -103,28 +117,28 @@ HttpStatus HttpPolicyGlobal::get(HttpRequest::EGlobalPolicy opt, long & value) } -HttpStatus HttpPolicyGlobal::get(HttpRequest::EGlobalPolicy opt, std::string & value) +HttpStatus HttpPolicyGlobal::get(HttpRequest::EGlobalPolicy opt, const std::string *& value) { static const HttpStatus not_set(HttpStatus::LLCORE, HE_OPT_NOT_SET); - + switch (opt) { case HttpRequest::GP_CA_PATH: - if (! (mValidMask & (1UL << int(opt)))) + if (! (mSetMask & (1UL << int(opt)))) return not_set; - value = mCAPath; + value = &mCAPath; break; case HttpRequest::GP_CA_FILE: - if (! (mValidMask & (1UL << int(opt)))) + if (! (mSetMask & (1UL << int(opt)))) return not_set; - value = mCAFile; + value = &mCAFile; break; case HttpRequest::GP_HTTP_PROXY: - if (! (mValidMask & (1UL << int(opt)))) + if (! (mSetMask & (1UL << int(opt)))) return not_set; - value = mHttpProxy; + value = &mHttpProxy; break; default: diff --git a/indra/llcorehttp/_httppolicyglobal.h b/indra/llcorehttp/_httppolicyglobal.h index 39ffbcb9bb..f4bb4d4b25 100644 --- a/indra/llcorehttp/_httppolicyglobal.h +++ b/indra/llcorehttp/_httppolicyglobal.h @@ -40,18 +40,19 @@ public: HttpPolicyGlobal(); ~HttpPolicyGlobal(); + HttpPolicyGlobal & operator=(const HttpPolicyGlobal &); + private: HttpPolicyGlobal(const HttpPolicyGlobal &); // Not defined - void operator=(const HttpPolicyGlobal &); // Not defined public: HttpStatus set(HttpRequest::EGlobalPolicy opt, long value); HttpStatus set(HttpRequest::EGlobalPolicy opt, const std::string & value); HttpStatus get(HttpRequest::EGlobalPolicy opt, long & value); - HttpStatus get(HttpRequest::EGlobalPolicy opt, std::string & value); + HttpStatus get(HttpRequest::EGlobalPolicy opt, const std::string *& value); public: - unsigned long mValidMask; + unsigned long mSetMask; long mConnectionLimit; std::string mCAPath; std::string mCAFile; diff --git a/indra/llcorehttp/_httpservice.cpp b/indra/llcorehttp/_httpservice.cpp index b038bdb720..920a3f3b6d 100644 --- a/indra/llcorehttp/_httpservice.cpp +++ b/indra/llcorehttp/_httpservice.cpp @@ -79,11 +79,8 @@ HttpService::~HttpService() mTransport = NULL; } - if (mPolicy) - { - delete mPolicy; - mPolicy = NULL; - } + delete mPolicy; + mPolicy = NULL; if (mThread) { @@ -145,6 +142,10 @@ void HttpService::startThread() { mThread->release(); } + + // Push current policy definitions + mPolicy->setPolicies(mPolicyGlobal); + mThread = new LLCoreInt::HttpThread(boost::bind(&HttpService::threadRun, this, _1)); mThread->addRef(); // Need an explicit reference, implicit one is used internally sState = RUNNING; diff --git a/indra/llcorehttp/_httpservice.h b/indra/llcorehttp/_httpservice.h index 748354a8e4..3f953ec1a7 100644 --- a/indra/llcorehttp/_httpservice.h +++ b/indra/llcorehttp/_httpservice.h @@ -30,6 +30,7 @@ #include "httpcommon.h" #include "httprequest.h" +#include "_httppolicyglobal.h" namespace LLCoreInt @@ -157,6 +158,11 @@ public: { return *mTransport; } + + HttpPolicyGlobal & getGlobalOptions() + { + return mPolicyGlobal; + } protected: void threadRun(LLCoreInt::HttpThread * thread); @@ -173,11 +179,11 @@ protected: // === calling-thread-only data === LLCoreInt::HttpThread * mThread; - + HttpPolicyGlobal mPolicyGlobal; + // === working-thread-only data === HttpPolicy * mPolicy; // Simple pointer, has ownership HttpLibcurl * mTransport; // Simple pointer, has ownership - }; // end class HttpService } // end namespace LLCore diff --git a/indra/llcorehttp/httprequest.cpp b/indra/llcorehttp/httprequest.cpp index 2f36168f8b..089eee76f3 100644 --- a/indra/llcorehttp/httprequest.cpp +++ b/indra/llcorehttp/httprequest.cpp @@ -34,6 +34,7 @@ #include "_httpoprequest.h" #include "_httpopsetpriority.h" #include "_httpopcancel.h" +#include "_httpopsetget.h" #include "lltimer.h" @@ -48,39 +49,6 @@ bool has_inited(false); namespace LLCore { -// ==================================== -// InternalHandler Implementation -// ==================================== - - -class HttpRequest::InternalHandler : public HttpHandler -{ -public: - InternalHandler(HttpRequest & request) - : mRequest(request) - {} - -protected: - InternalHandler(const InternalHandler &); // Not defined - void operator=(const InternalHandler &); // Not defined - -public: - void onCompleted(HttpHandle handle, HttpResponse * response) - { - HttpOperation * op(static_cast(handle)); - HttpHandler * user_handler(op->getUserHandler()); - if (user_handler) - { - user_handler->onCompleted(handle, response); - } - } - -protected: - HttpRequest & mRequest; - -}; // end class HttpRequest::InternalHandler - - // ==================================== // HttpRequest Implementation // ==================================== @@ -92,15 +60,12 @@ HttpRequest::policy_t HttpRequest::sNextPolicyID(1); HttpRequest::HttpRequest() : //HttpHandler(), mReplyQueue(NULL), - mRequestQueue(NULL), - mSelfHandler(NULL) + mRequestQueue(NULL) { mRequestQueue = HttpRequestQueue::instanceOf(); mRequestQueue->addRef(); mReplyQueue = new HttpReplyQueue(); - - mSelfHandler = new InternalHandler(*this); } @@ -117,9 +82,6 @@ HttpRequest::~HttpRequest() mReplyQueue->release(); mReplyQueue = NULL; } - - delete mSelfHandler; - mSelfHandler = NULL; } @@ -132,7 +94,7 @@ HttpStatus HttpRequest::setPolicyGlobalOption(EGlobalPolicy opt, long value) { // *FIXME: Fail if thread is running. - return HttpService::instanceOf()->getPolicy().getGlobalOptions().set(opt, value); + return HttpService::instanceOf()->getGlobalOptions().set(opt, value); } @@ -140,7 +102,7 @@ HttpStatus HttpRequest::setPolicyGlobalOption(EGlobalPolicy opt, const std::stri { // *FIXME: Fail if thread is running. - return HttpService::instanceOf()->getPolicy().getGlobalOptions().set(opt, value); + return HttpService::instanceOf()->getGlobalOptions().set(opt, value); } @@ -192,7 +154,7 @@ HttpHandle HttpRequest::requestGetByteRange(policy_t policy_id, mLastReqStatus = status; return handle; } - op->setHandlers(mReplyQueue, mSelfHandler, user_handler); + op->setReplyPath(mReplyQueue, user_handler); mRequestQueue->addOp(op); // transfers refcount mLastReqStatus = status; @@ -220,7 +182,7 @@ HttpHandle HttpRequest::requestPost(policy_t policy_id, mLastReqStatus = status; return handle; } - op->setHandlers(mReplyQueue, mSelfHandler, user_handler); + op->setReplyPath(mReplyQueue, user_handler); mRequestQueue->addOp(op); // transfers refcount mLastReqStatus = status; @@ -230,19 +192,31 @@ HttpHandle HttpRequest::requestPost(policy_t policy_id, } -HttpHandle HttpRequest::requestCancel(HttpHandle handle, HttpHandler * user_handler) +HttpHandle HttpRequest::requestPut(policy_t policy_id, + priority_t priority, + const std::string & url, + BufferArray * body, + HttpOptions * options, + HttpHeaders * headers, + HttpHandler * user_handler) { HttpStatus status; - HttpHandle ret_handle(LLCORE_HTTP_HANDLE_INVALID); - - HttpOpCancel * op = new HttpOpCancel(handle); - op->setHandlers(mReplyQueue, mSelfHandler, user_handler); - mRequestQueue->addOp(op); // transfer refcount as well + HttpHandle handle(LLCORE_HTTP_HANDLE_INVALID); + HttpOpRequest * op = new HttpOpRequest(); + if (! (status = op->setupPut(policy_id, priority, url, body, options, headers))) + { + op->release(); + mLastReqStatus = status; + return handle; + } + op->setReplyPath(mReplyQueue, user_handler); + mRequestQueue->addOp(op); // transfers refcount + mLastReqStatus = status; - ret_handle = static_cast(op); + handle = static_cast(op); - return ret_handle; + return handle; } @@ -252,7 +226,7 @@ HttpHandle HttpRequest::requestNoOp(HttpHandler * user_handler) HttpHandle handle(LLCORE_HTTP_HANDLE_INVALID); HttpOpNull * op = new HttpOpNull(); - op->setHandlers(mReplyQueue, mSelfHandler, user_handler); + op->setReplyPath(mReplyQueue, user_handler); mRequestQueue->addOp(op); // transfer refcount as well mLastReqStatus = status; @@ -262,23 +236,6 @@ HttpHandle HttpRequest::requestNoOp(HttpHandler * user_handler) } -HttpHandle HttpRequest::requestSetPriority(HttpHandle request, priority_t priority, - HttpHandler * handler) -{ - HttpStatus status; - HttpHandle ret_handle(LLCORE_HTTP_HANDLE_INVALID); - - HttpOpSetPriority * op = new HttpOpSetPriority(request, priority); - op->setHandlers(mReplyQueue, mSelfHandler, handler); - mRequestQueue->addOp(op); // transfer refcount as well - - mLastReqStatus = status; - ret_handle = static_cast(op); - - return ret_handle; -} - - HttpStatus HttpRequest::update(long millis) { const HttpTime limit(totalTime() + (1000 * HttpTime(millis))); @@ -302,6 +259,38 @@ HttpStatus HttpRequest::update(long millis) // Request Management Methods // ==================================== +HttpHandle HttpRequest::requestCancel(HttpHandle handle, HttpHandler * user_handler) +{ + HttpStatus status; + HttpHandle ret_handle(LLCORE_HTTP_HANDLE_INVALID); + + HttpOpCancel * op = new HttpOpCancel(handle); + op->setReplyPath(mReplyQueue, user_handler); + mRequestQueue->addOp(op); // transfer refcount as well + + mLastReqStatus = status; + ret_handle = static_cast(op); + + return ret_handle; +} + + +HttpHandle HttpRequest::requestSetPriority(HttpHandle request, priority_t priority, + HttpHandler * handler) +{ + HttpStatus status; + HttpHandle ret_handle(LLCORE_HTTP_HANDLE_INVALID); + + HttpOpSetPriority * op = new HttpOpSetPriority(request, priority); + op->setReplyPath(mReplyQueue, handler); + mRequestQueue->addOp(op); // transfer refcount as well + + mLastReqStatus = status; + ret_handle = static_cast(op); + + return ret_handle; +} + // ==================================== // Utility Methods @@ -350,7 +339,27 @@ HttpHandle HttpRequest::requestStopThread(HttpHandler * user_handler) HttpHandle handle(LLCORE_HTTP_HANDLE_INVALID); HttpOpStop * op = new HttpOpStop(); - op->setHandlers(mReplyQueue, mSelfHandler, user_handler); + op->setReplyPath(mReplyQueue, user_handler); + mRequestQueue->addOp(op); // transfer refcount as well + + mLastReqStatus = status; + handle = static_cast(op); + + return handle; +} + +// ==================================== +// Dynamic Policy Methods +// ==================================== + +HttpHandle HttpRequest::requestSetHttpProxy(const std::string & proxy, HttpHandler * handler) +{ + HttpStatus status; + HttpHandle handle(LLCORE_HTTP_HANDLE_INVALID); + + HttpOpSetGet * op = new HttpOpSetGet(); + op->setupSet(GP_HTTP_PROXY, proxy); + op->setReplyPath(mReplyQueue, handler); mRequestQueue->addOp(op); // transfer refcount as well mLastReqStatus = status; diff --git a/indra/llcorehttp/httprequest.h b/indra/llcorehttp/httprequest.h index 01dbfba6dd..a953aa28d0 100644 --- a/indra/llcorehttp/httprequest.h +++ b/indra/llcorehttp/httprequest.h @@ -124,8 +124,8 @@ public: /// @param opt Enum of option to be set. /// @param value Desired value of option. /// @return Standard status code. - HttpStatus setPolicyGlobalOption(EGlobalPolicy opt, long value); - HttpStatus setPolicyGlobalOption(EGlobalPolicy opt, const std::string & value); + static HttpStatus setPolicyGlobalOption(EGlobalPolicy opt, long value); + static HttpStatus setPolicyGlobalOption(EGlobalPolicy opt, const std::string & value); /// Create a new policy class into which requests can be made. /// @@ -236,6 +236,32 @@ public: HttpHandler * handler); + /// + /// @param policy_id Default or user-defined policy class under + /// which this request is to be serviced. + /// @param priority Standard priority scheme inherited from + /// Indra code base. + /// @param url + /// @param body Byte stream to be sent as the body. No + /// further encoding or escaping will be done + /// to the content. + /// @param options (optional) + /// @param headers (optional) + /// @param handler (optional) + /// @return The handle of the request if successfully + /// queued or LLCORE_HTTP_HANDLE_INVALID if the + /// request could not be queued. In the latter + /// case, @see getStatus() will return more info. + /// + HttpHandle requestPut(policy_t policy_id, + priority_t priority, + const std::string & url, + BufferArray * body, + HttpOptions * options, + HttpHeaders * headers, + HttpHandler * handler); + + /// Queue a NoOp request. /// The request is queued and serviced by the working thread which /// immediately processes it and returns the request to the reply @@ -325,13 +351,20 @@ public: HttpHandle requestStopThread(HttpHandler * handler); /// @} + + /// @name DynamicPolicyMethods + /// + /// @{ + + /// Request that a running transport pick up a new proxy setting. + /// An empty string will indicate no proxy is to be used. + HttpHandle requestSetHttpProxy(const std::string & proxy, HttpHandler * handler); + + /// @} protected: void generateNotification(HttpOperation * op); - class InternalHandler; - friend class InternalHandler; - private: /// @name InstanceData /// @@ -339,7 +372,6 @@ private: HttpStatus mLastReqStatus; HttpReplyQueue * mReplyQueue; HttpRequestQueue * mRequestQueue; - InternalHandler * mSelfHandler; /// @} diff --git a/indra/llcorehttp/tests/llcorehttp_test.cpp b/indra/llcorehttp/tests/llcorehttp_test.cpp index f59361ab53..2b36d3a982 100644 --- a/indra/llcorehttp/tests/llcorehttp_test.cpp +++ b/indra/llcorehttp/tests/llcorehttp_test.cpp @@ -44,6 +44,8 @@ #include "test_bufferarray.hpp" #include "test_httprequestqueue.hpp" +#include "llproxy.h" + unsigned long ssl_thread_id_callback(void); void ssl_locking_callback(int mode, int type, const char * file, int line); @@ -91,11 +93,15 @@ void init_curl() CRYPTO_set_locking_callback(ssl_locking_callback); CRYPTO_set_id_callback(ssl_thread_id_callback); } + + LLProxy::getInstance(); } void term_curl() { + LLProxy::cleanupClass(); + CRYPTO_set_locking_callback(NULL); for (int i(0); i < ssl_mutex_count; ++i) { diff --git a/indra/llcorehttp/tests/test_httpoperation.hpp b/indra/llcorehttp/tests/test_httpoperation.hpp index 6c3df1e9e3..17b1a96878 100644 --- a/indra/llcorehttp/tests/test_httpoperation.hpp +++ b/indra/llcorehttp/tests/test_httpoperation.hpp @@ -97,13 +97,12 @@ namespace tut // Get some handlers TestHandler * h1 = new TestHandler(); - TestHandler * h2 = new TestHandler(); // create a new ref counted object with an implicit reference HttpOpNull * op = new HttpOpNull(); // Add the handlers - op->setHandlers(NULL, h1, h2); + op->setReplyPath(NULL, h1); // Check ref count ensure(op->getRefCount() == 1); @@ -117,8 +116,6 @@ namespace tut // release the handlers delete h1; h1 = NULL; - delete h2; - h2 = NULL; ensure(mMemTotal == GetMemTotal()); } diff --git a/indra/llcorehttp/tests/test_httprequest.hpp b/indra/llcorehttp/tests/test_httprequest.hpp index 81f8fe4a85..61698f34d8 100644 --- a/indra/llcorehttp/tests/test_httprequest.hpp +++ b/indra/llcorehttp/tests/test_httprequest.hpp @@ -27,6 +27,7 @@ #define TEST_LLCORE_HTTP_REQUEST_H_ #include "httprequest.h" +#include "bufferarray.h" #include "httphandler.h" #include "httpresponse.h" #include "_httpservice.h" @@ -604,6 +605,244 @@ void HttpRequestTestObjectType::test<6>() } } +template <> template <> +void HttpRequestTestObjectType::test<7>() +{ + ScopedCurlInit ready; + + std::string url_base(get_base_url()); + std::cerr << "Base: " << url_base << std::endl; + + set_test_name("HttpRequest PUT to real service"); + + // Handler can be stack-allocated *if* there are no dangling + // references to it after completion of this method. + // Create before memory record as the string copy will bump numbers. + TestHandler2 handler(this, "handler"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + mHandlerCalls = 0; + + HttpRequest * req = NULL; + BufferArray * body = new BufferArray; + + try + { + // Get singletons created + HttpRequest::createService(); + + // Start threading early so that thread memory is invariant + // over the test. + HttpRequest::startThread(); + + // create a new ref counted object with an implicit reference + req = new HttpRequest(); + ensure("Memory allocated on construction", mMemTotal < GetMemTotal()); + + // Issue a GET that *can* connect + static const char * body_text("Now is the time for all good men..."); + body->append(body_text, strlen(body_text)); + mStatus = HttpStatus(200); + HttpHandle handle = req->requestPut(HttpRequest::DEFAULT_POLICY_ID, + 0U, + url_base, + body, + NULL, + NULL, + &handler); + ensure("Valid handle returned for ranged request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump. + int count(0); + int limit(10); + while (count++ < limit && mHandlerCalls < 1) + { + req->update(1000); + usleep(100000); + } + ensure("Request executed in reasonable time", count < limit); + ensure("One handler invocation for request", mHandlerCalls == 1); + + // Okay, request a shutdown of the servicing thread + mStatus = HttpStatus(); + handle = req->requestStopThread(&handler); + ensure("Valid handle returned for second request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump again + count = 0; + limit = 10; + while (count++ < limit && mHandlerCalls < 2) + { + req->update(1000); + usleep(100000); + } + ensure("Second request executed in reasonable time", count < limit); + ensure("Second handler invocation", mHandlerCalls == 2); + + // See that we actually shutdown the thread + count = 0; + limit = 10; + while (count++ < limit && ! HttpService::isStopped()) + { + usleep(100000); + } + ensure("Thread actually stopped running", HttpService::isStopped()); + + // Lose the request body + body->release(); + body = NULL; + + // release the request object + delete req; + req = NULL; + + // Shut down service + HttpRequest::destroyService(); + + ensure("Two handler calls on the way out", 2 == mHandlerCalls); + +#if defined(WIN32) + // Can only do this memory test on Windows. On other platforms, + // the LL logging system holds on to memory and produces what looks + // like memory leaks... + + // printf("Old mem: %d, New mem: %d\n", mMemTotal, GetMemTotal()); + ensure("Memory usage back to that at entry", mMemTotal == GetMemTotal()); +#endif + } + catch (...) + { + if (body) + { + body->release(); + } + stop_thread(req); + delete req; + HttpRequest::destroyService(); + throw; + } +} + +template <> template <> +void HttpRequestTestObjectType::test<8>() +{ + ScopedCurlInit ready; + + std::string url_base(get_base_url()); + std::cerr << "Base: " << url_base << std::endl; + + set_test_name("HttpRequest POST to real service"); + + // Handler can be stack-allocated *if* there are no dangling + // references to it after completion of this method. + // Create before memory record as the string copy will bump numbers. + TestHandler2 handler(this, "handler"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + mHandlerCalls = 0; + + HttpRequest * req = NULL; + BufferArray * body = new BufferArray; + + try + { + // Get singletons created + HttpRequest::createService(); + + // Start threading early so that thread memory is invariant + // over the test. + HttpRequest::startThread(); + + // create a new ref counted object with an implicit reference + req = new HttpRequest(); + ensure("Memory allocated on construction", mMemTotal < GetMemTotal()); + + // Issue a GET that *can* connect + static const char * body_text("Now is the time for all good men..."); + body->append(body_text, strlen(body_text)); + mStatus = HttpStatus(200); + HttpHandle handle = req->requestPost(HttpRequest::DEFAULT_POLICY_ID, + 0U, + url_base, + body, + NULL, + NULL, + &handler); + ensure("Valid handle returned for ranged request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump. + int count(0); + int limit(10); + while (count++ < limit && mHandlerCalls < 1) + { + req->update(1000); + usleep(100000); + } + ensure("Request executed in reasonable time", count < limit); + ensure("One handler invocation for request", mHandlerCalls == 1); + + // Okay, request a shutdown of the servicing thread + mStatus = HttpStatus(); + handle = req->requestStopThread(&handler); + ensure("Valid handle returned for second request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump again + count = 0; + limit = 10; + while (count++ < limit && mHandlerCalls < 2) + { + req->update(1000); + usleep(100000); + } + ensure("Second request executed in reasonable time", count < limit); + ensure("Second handler invocation", mHandlerCalls == 2); + + // See that we actually shutdown the thread + count = 0; + limit = 10; + while (count++ < limit && ! HttpService::isStopped()) + { + usleep(100000); + } + ensure("Thread actually stopped running", HttpService::isStopped()); + + // Lose the request body + body->release(); + body = NULL; + + // release the request object + delete req; + req = NULL; + + // Shut down service + HttpRequest::destroyService(); + + ensure("Two handler calls on the way out", 2 == mHandlerCalls); + +#if defined(WIN32) + // Can only do this memory test on Windows. On other platforms, + // the LL logging system holds on to memory and produces what looks + // like memory leaks... + + // printf("Old mem: %d, New mem: %d\n", mMemTotal, GetMemTotal()); + ensure("Memory usage back to that at entry", mMemTotal == GetMemTotal()); +#endif + } + catch (...) + { + if (body) + { + body->release(); + } + stop_thread(req); + delete req; + HttpRequest::destroyService(); + throw; + } +} + } // end namespace tut namespace diff --git a/indra/llcorehttp/tests/test_llcorehttp_peer.py b/indra/llcorehttp/tests/test_llcorehttp_peer.py index 3e200a5c19..8c3ad805b3 100644 --- a/indra/llcorehttp/tests/test_llcorehttp_peer.py +++ b/indra/llcorehttp/tests/test_llcorehttp_peer.py @@ -85,7 +85,15 @@ class TestHTTPRequestHandler(BaseHTTPRequestHandler): def do_POST(self): # Read the provided POST data. - self.answer(self.read()) + # self.answer(self.read()) + self.answer(dict(reply="success", status=200, + reason=self.read())) + + def do_PUT(self): + # Read the provided PUT data. + # self.answer(self.read()) + self.answer(dict(reply="success", status=200, + reason=self.read())) def answer(self, data): debug("%s.answer(%s): self.path = %r", self.__class__.__name__, data, self.path) diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 7a44415fba..e2c13e77e3 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -5358,6 +5358,7 @@ void CoreHttp::init() mRequest = new LLCore::HttpRequest; + // Point to our certs or SSH/https: will fail on connect status = mRequest->setPolicyGlobalOption(LLCore::HttpRequest::GP_CA_FILE, gDirUtilp->getCAFile()); if (! status) @@ -5366,6 +5367,18 @@ void CoreHttp::init() << status.toString() << LL_ENDL; } + + // Establish HTTP Proxy. "LLProxy" is a special string which directs + // the code to use LLProxy::applyProxySettings() to establish any + // HTTP or SOCKS proxy for http operations. + status = mRequest->setPolicyGlobalOption(LLCore::HttpRequest::GP_HTTP_PROXY, + std::string("LLProxy")); + if (! status) + { + LL_ERRS("Init") << "Failed to set HTTP proxy for HTTP services. Reason: " + << status.toString() + << LL_ENDL; + } status = LLCore::HttpRequest::startThread(); if (! status) -- cgit v1.3 From f0353abe7605778048d69ce3acb8f5ddd5693083 Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Tue, 19 Jun 2012 15:43:29 -0400 Subject: Implement timeout and retry count options for requests. Pretty straightforward. Still don't like how I'm managing the options block. Struct? Accessors? Can't decide. But the options now speed up the unit test runs even as I add tests. --- indra/llcorehttp/_httpoprequest.cpp | 21 +++- indra/llcorehttp/_httpoprequest.h | 2 +- indra/llcorehttp/httpoptions.cpp | 23 ++-- indra/llcorehttp/httpoptions.h | 29 +++-- indra/llcorehttp/tests/test_httprequest.hpp | 155 +++++++++++++++++++++++-- indra/llcorehttp/tests/test_llcorehttp_peer.py | 4 + 6 files changed, 204 insertions(+), 30 deletions(-) (limited to 'indra/llcorehttp/tests/test_llcorehttp_peer.py') diff --git a/indra/llcorehttp/_httpoprequest.cpp b/indra/llcorehttp/_httpoprequest.cpp index f78971d8f2..ce41ebcce0 100644 --- a/indra/llcorehttp/_httpoprequest.cpp +++ b/indra/llcorehttp/_httpoprequest.cpp @@ -108,7 +108,7 @@ HttpOpRequest::HttpOpRequest() mReplyHeaders(NULL), mPolicyRetries(0), mPolicyRetryAt(HttpTime(0)), - mPolicyRetryLimit(5) // *FIXME: Get from policy definitions + mPolicyRetryLimit(5) { // *NOTE: As members are added, retry initialization/cleanup // may need to be extended in @prepareRequest(). @@ -333,6 +333,8 @@ void HttpOpRequest::setupCommon(HttpRequest::policy_t policy_id, { mProcFlags |= PF_SAVE_HEADERS; } + mPolicyRetryLimit = options->getRetries(); + mPolicyRetryLimit = llclamp(mPolicyRetryLimit, 0, 100); mTracing = (std::max)(mTracing, llclamp(options->getTrace(), 0, 3)); } } @@ -371,10 +373,7 @@ HttpStatus HttpOpRequest::prepareRequest(HttpService * service) HttpPolicyGlobal & policy(service->getPolicy().getGlobalOptions()); mCurlHandle = curl_easy_init(); - // curl_easy_setopt(mCurlHandle, CURLOPT_VERBOSE, 1); curl_easy_setopt(mCurlHandle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); - curl_easy_setopt(mCurlHandle, CURLOPT_TIMEOUT, 30); - curl_easy_setopt(mCurlHandle, CURLOPT_CONNECTTIMEOUT, 30); curl_easy_setopt(mCurlHandle, CURLOPT_NOSIGNAL, 1); curl_easy_setopt(mCurlHandle, CURLOPT_NOPROGRESS, 1); curl_easy_setopt(mCurlHandle, CURLOPT_URL, mReqURL.c_str()); @@ -493,13 +492,25 @@ HttpStatus HttpOpRequest::prepareRequest(HttpService * service) } mCurlHeaders = curl_slist_append(mCurlHeaders, "Pragma:"); + + // Request options + long timeout(30); + if (mReqOptions) + { + timeout = mReqOptions->getTimeout(); + timeout = llclamp(timeout, 0L, 3600L); + } + curl_easy_setopt(mCurlHandle, CURLOPT_TIMEOUT, timeout); + curl_easy_setopt(mCurlHandle, CURLOPT_CONNECTTIMEOUT, timeout); + + // Request headers if (mReqHeaders) { // Caller's headers last to override mCurlHeaders = append_headers_to_slist(mReqHeaders, mCurlHeaders); } curl_easy_setopt(mCurlHandle, CURLOPT_HTTPHEADER, mCurlHeaders); - + if (mProcFlags & (PF_SCAN_RANGE_HEADER | PF_SAVE_HEADERS)) { curl_easy_setopt(mCurlHandle, CURLOPT_HEADERFUNCTION, headerCallback); diff --git a/indra/llcorehttp/_httpoprequest.h b/indra/llcorehttp/_httpoprequest.h index 4643cc3b75..9278445763 100644 --- a/indra/llcorehttp/_httpoprequest.h +++ b/indra/llcorehttp/_httpoprequest.h @@ -155,7 +155,7 @@ public: // Policy data int mPolicyRetries; HttpTime mPolicyRetryAt; - const int mPolicyRetryLimit; + int mPolicyRetryLimit; }; // end class HttpOpRequest diff --git a/indra/llcorehttp/httpoptions.cpp b/indra/llcorehttp/httpoptions.cpp index 155fbda7f1..c11d89e619 100644 --- a/indra/llcorehttp/httpoptions.cpp +++ b/indra/llcorehttp/httpoptions.cpp @@ -34,14 +34,9 @@ namespace LLCore HttpOptions::HttpOptions() : RefCounted(true), mWantHeaders(false), - mTracing(0) -{} - - -HttpOptions::HttpOptions(const HttpOptions & rhs) - : RefCounted(true), - mWantHeaders(rhs.mWantHeaders), - mTracing(rhs.mTracing) + mTracing(0), + mTimeout(30), + mRetries(5) {} @@ -61,4 +56,16 @@ void HttpOptions::setTrace(long level) } +void HttpOptions::setTimeout(unsigned int timeout) +{ + mTimeout = timeout; +} + + +void HttpOptions::setRetries(unsigned int retries) +{ + mRetries = retries; +} + + } // end namespace LLCore diff --git a/indra/llcorehttp/httpoptions.h b/indra/llcorehttp/httpoptions.h index 78d0aadb2e..a0b2253c11 100644 --- a/indra/llcorehttp/httpoptions.h +++ b/indra/llcorehttp/httpoptions.h @@ -60,29 +60,44 @@ class HttpOptions : public LLCoreInt::RefCounted { public: HttpOptions(); - HttpOptions(const HttpOptions &); protected: virtual ~HttpOptions(); // Use release() + HttpOptions(const HttpOptions &); // Not defined void operator=(const HttpOptions &); // Not defined public: - void setWantHeaders(); - bool getWantHeaders() const + void setWantHeaders(); + bool getWantHeaders() const { return mWantHeaders; } - void setTrace(long level); - int getTrace() const + void setTrace(int long); + int getTrace() const { return mTracing; } + + void setTimeout(unsigned int timeout); + unsigned int getTimeout() const + { + return mTimeout; + } + + void setRetries(unsigned int retries); + unsigned int getRetries() const + { + return mRetries; + } protected: - bool mWantHeaders; - long int mTracing; + bool mWantHeaders; + int mTracing; + unsigned int mTimeout; + unsigned int mRetries; + }; // end class HttpOptions diff --git a/indra/llcorehttp/tests/test_httprequest.hpp b/indra/llcorehttp/tests/test_httprequest.hpp index 5b04796c8a..42e4857037 100644 --- a/indra/llcorehttp/tests/test_httprequest.hpp +++ b/indra/llcorehttp/tests/test_httprequest.hpp @@ -30,6 +30,7 @@ #include "bufferarray.h" #include "httphandler.h" #include "httpresponse.h" +#include "httpoptions.h" #include "_httpservice.h" #include "_httprequestqueue.h" @@ -407,7 +408,8 @@ void HttpRequestTestObjectType::test<5>() mHandlerCalls = 0; HttpRequest * req = NULL; - + HttpOptions * opts = NULL; + try { // Get singletons created @@ -421,6 +423,9 @@ void HttpRequestTestObjectType::test<5>() req = new HttpRequest(); ensure("Memory allocated on construction", mMemTotal < GetMemTotal()); + opts = new HttpOptions(); + opts->setRetries(1); // Don't try for too long - default retries take about 18S + // Issue a GET that can't connect mStatus = HttpStatus(HttpStatus::EXT_CURL_EASY, CURLE_COULDNT_CONNECT); HttpHandle handle = req->requestGetByteRange(HttpRequest::DEFAULT_POLICY_ID, @@ -428,14 +433,14 @@ void HttpRequestTestObjectType::test<5>() "http://127.0.0.1:2/nothing/here", 0, 0, - NULL, + opts, NULL, &handler); ensure("Valid handle returned for ranged request", handle != LLCORE_HTTP_HANDLE_INVALID); // Run the notification pump. int count(0); - int limit(180); // With retries, can take more than 10 seconds to give up + int limit(50); // With one retry, should fail quickish while (count++ < limit && mHandlerCalls < 1) { req->update(1000); @@ -468,7 +473,11 @@ void HttpRequestTestObjectType::test<5>() usleep(100000); } ensure("Thread actually stopped running", HttpService::isStopped()); - + + // release options + opts->release(); + opts = NULL; + // release the request object delete req; req = NULL; @@ -490,6 +499,11 @@ void HttpRequestTestObjectType::test<5>() catch (...) { stop_thread(req); + if (opts) + { + opts->release(); + opts = NULL; + } delete req; HttpRequest::destroyService(); throw; @@ -503,7 +517,7 @@ void HttpRequestTestObjectType::test<6>() ScopedCurlInit ready; std::string url_base(get_base_url()); - std::cerr << "Base: " << url_base << std::endl; + // std::cerr << "Base: " << url_base << std::endl; set_test_name("HttpRequest GET to real service"); @@ -611,7 +625,7 @@ void HttpRequestTestObjectType::test<7>() ScopedCurlInit ready; std::string url_base(get_base_url()); - std::cerr << "Base: " << url_base << std::endl; + // std::cerr << "Base: " << url_base << std::endl; set_test_name("HttpRequest GET with Range: header to real service"); @@ -721,7 +735,7 @@ void HttpRequestTestObjectType::test<8>() ScopedCurlInit ready; std::string url_base(get_base_url()); - std::cerr << "Base: " << url_base << std::endl; + // std::cerr << "Base: " << url_base << std::endl; set_test_name("HttpRequest PUT to real service"); @@ -840,7 +854,7 @@ void HttpRequestTestObjectType::test<9>() ScopedCurlInit ready; std::string url_base(get_base_url()); - std::cerr << "Base: " << url_base << std::endl; + // std::cerr << "Base: " << url_base << std::endl; set_test_name("HttpRequest POST to real service"); @@ -959,7 +973,7 @@ void HttpRequestTestObjectType::test<10>() ScopedCurlInit ready; std::string url_base(get_base_url()); - std::cerr << "Base: " << url_base << std::endl; + // std::cerr << "Base: " << url_base << std::endl; set_test_name("HttpRequest GET with some tracing"); @@ -1065,6 +1079,129 @@ void HttpRequestTestObjectType::test<10>() } } + +template <> template <> +void HttpRequestTestObjectType::test<11>() +{ + ScopedCurlInit ready; + + set_test_name("HttpRequest GET timeout"); + + // Handler can be stack-allocated *if* there are no dangling + // references to it after completion of this method. + // Create before memory record as the string copy will bump numbers. + TestHandler2 handler(this, "handler"); + std::string url_base(get_base_url() + "/sleep/"); // path to a 30-second sleep + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + mHandlerCalls = 0; + + HttpRequest * req = NULL; + HttpOptions * opts = NULL; + + try + { + // Get singletons created + HttpRequest::createService(); + + // Start threading early so that thread memory is invariant + // over the test. + HttpRequest::startThread(); + + // create a new ref counted object with an implicit reference + req = new HttpRequest(); + ensure("Memory allocated on construction", mMemTotal < GetMemTotal()); + + opts = new HttpOptions(); + opts->setRetries(0); // Don't retry + opts->setTimeout(2); + + // Issue a GET that can't connect + mStatus = HttpStatus(HttpStatus::EXT_CURL_EASY, CURLE_OPERATION_TIMEDOUT); + HttpHandle handle = req->requestGetByteRange(HttpRequest::DEFAULT_POLICY_ID, + 0U, + url_base, + 0, + 0, + opts, + NULL, + &handler); + ensure("Valid handle returned for ranged request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump. + int count(0); + int limit(50); // With one retry, should fail quickish + while (count++ < limit && mHandlerCalls < 1) + { + req->update(1000); + usleep(100000); + } + ensure("Request executed in reasonable time", count < limit); + ensure("One handler invocation for request", mHandlerCalls == 1); + + // Okay, request a shutdown of the servicing thread + mStatus = HttpStatus(); + handle = req->requestStopThread(&handler); + ensure("Valid handle returned for second request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump again + count = 0; + limit = 100; + while (count++ < limit && mHandlerCalls < 2) + { + req->update(1000); + usleep(100000); + } + ensure("Second request executed in reasonable time", count < limit); + ensure("Second handler invocation", mHandlerCalls == 2); + + // See that we actually shutdown the thread + count = 0; + limit = 10; + while (count++ < limit && ! HttpService::isStopped()) + { + usleep(100000); + } + ensure("Thread actually stopped running", HttpService::isStopped()); + + // release options + opts->release(); + opts = NULL; + + // release the request object + delete req; + req = NULL; + + // Shut down service + HttpRequest::destroyService(); + + ensure("Two handler calls on the way out", 2 == mHandlerCalls); + +#if defined(WIN32) + // Can only do this memory test on Windows. On other platforms, + // the LL logging system holds on to memory and produces what looks + // like memory leaks... + + // printf("Old mem: %d, New mem: %d\n", mMemTotal, GetMemTotal()); + ensure("Memory usage back to that at entry", mMemTotal == GetMemTotal()); +#endif + } + catch (...) + { + stop_thread(req); + if (opts) + { + opts->release(); + opts = NULL; + } + delete req; + HttpRequest::destroyService(); + throw; + } +} + + } // end namespace tut namespace diff --git a/indra/llcorehttp/tests/test_llcorehttp_peer.py b/indra/llcorehttp/tests/test_llcorehttp_peer.py index 8c3ad805b3..5f0116d384 100644 --- a/indra/llcorehttp/tests/test_llcorehttp_peer.py +++ b/indra/llcorehttp/tests/test_llcorehttp_peer.py @@ -31,6 +31,7 @@ $/LicenseInfo$ import os import sys +import time from threading import Thread from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler @@ -97,6 +98,9 @@ class TestHTTPRequestHandler(BaseHTTPRequestHandler): def answer(self, data): debug("%s.answer(%s): self.path = %r", self.__class__.__name__, data, self.path) + if self.path.find("/sleep/") != -1: + time.sleep(30) + if "fail" not in self.path: response = llsd.format_xml(data.get("reply", llsd.LLSD("success"))) debug("success: %s", response) -- cgit v1.3 From a066bc1994fccae7967921980332505aac97953f Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Wed, 20 Jun 2012 18:43:28 -0400 Subject: SH-3181 More interface options for API. Also includes returned headers. Only thing interesting in this changeset is the discovery that a sleep in the fake HTTP server ties up tests. Need to thread that or fail on client disconnect or something to speed that up and make it usable for bigger test scenarios. But good enough for now... --- indra/llcorehttp/_httpoprequest.cpp | 20 +++- indra/llcorehttp/tests/test_httprequest.hpp | 156 ++++++++++++++++++++++++- indra/llcorehttp/tests/test_llcorehttp_peer.py | 1 + indra/newview/lltexturefetch.cpp | 5 +- 4 files changed, 175 insertions(+), 7 deletions(-) (limited to 'indra/llcorehttp/tests/test_llcorehttp_peer.py') diff --git a/indra/llcorehttp/_httpoprequest.cpp b/indra/llcorehttp/_httpoprequest.cpp index 516daadf9b..86ecee5b26 100644 --- a/indra/llcorehttp/_httpoprequest.cpp +++ b/indra/llcorehttp/_httpoprequest.cpp @@ -595,6 +595,10 @@ size_t HttpOpRequest::headerCallback(void * data, size_t size, size_t nmemb, voi op->mReplyLength = 0; op->mReplyFullLength = 0; op->mStatus = HttpStatus(); + if (op->mReplyHeaders) + { + op->mReplyHeaders->mHeaders.clear(); + } } else if (op->mProcFlags & PF_SCAN_RANGE_HEADER) { @@ -636,9 +640,19 @@ size_t HttpOpRequest::headerCallback(void * data, size_t size, size_t nmemb, voi if (op->mProcFlags & PF_SAVE_HEADERS) { // Save headers in response - // *FIXME: Implement this... - ; - + if (! op->mReplyHeaders) + { + op->mReplyHeaders = new HttpHeaders; + } + size_t wanted_size(hdr_size); + if (wanted_size && '\n' == hdr_data[wanted_size - 1]) + { + if (--wanted_size && '\r' == hdr_data[wanted_size - 1]) + { + --wanted_size; + } + } + op->mReplyHeaders->mHeaders.push_back(std::string(hdr_data, wanted_size)); } return hdr_size; diff --git a/indra/llcorehttp/tests/test_httprequest.hpp b/indra/llcorehttp/tests/test_httprequest.hpp index 42e4857037..cac927cfca 100644 --- a/indra/llcorehttp/tests/test_httprequest.hpp +++ b/indra/llcorehttp/tests/test_httprequest.hpp @@ -29,6 +29,7 @@ #include "httprequest.h" #include "bufferarray.h" #include "httphandler.h" +#include "httpheaders.h" #include "httpresponse.h" #include "httpoptions.h" #include "_httpservice.h" @@ -73,7 +74,8 @@ public: const std::string & name) : mState(state), mName(name), - mExpectHandle(LLCORE_HTTP_HANDLE_INVALID) + mExpectHandle(LLCORE_HTTP_HANDLE_INVALID), + mCheckHeader(false) {} virtual void onCompleted(HttpHandle handle, HttpResponse * response) @@ -95,12 +97,33 @@ public: { mState->mHandlerCalls++; } + if (mCheckHeader) + { + ensure("Response required with header check", response != NULL); + HttpHeaders * header(response->getHeaders()); // Will not hold onto this + ensure("Some quantity of headers returned", header != NULL); + bool found_special(false); + + for (HttpHeaders::container_t::const_iterator iter(header->mHeaders.begin()); + header->mHeaders.end() != iter; + ++iter) + { + if (std::string::npos != (*iter).find("X-LL-Special")) + { + found_special = true; + break; + } + } + ensure("Special header X-LL-Special in response", found_special); + } + // std::cout << "TestHandler2::onCompleted() invoked" << std::endl; } HttpRequestTestData * mState; std::string mName; HttpHandle mExpectHandle; + bool mCheckHeader; }; typedef test_group HttpRequestTestGroupType; @@ -1085,6 +1108,135 @@ void HttpRequestTestObjectType::test<11>() { ScopedCurlInit ready; + std::string url_base(get_base_url()); + // std::cerr << "Base: " << url_base << std::endl; + + set_test_name("HttpRequest GET with returned headers"); + + // Handler can be stack-allocated *if* there are no dangling + // references to it after completion of this method. + // Create before memory record as the string copy will bump numbers. + TestHandler2 handler(this, "handler"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + mHandlerCalls = 0; + + HttpRequest * req = NULL; + HttpOptions * opts = NULL; + + try + { + // Get singletons created + HttpRequest::createService(); + + // Enable tracing + HttpRequest::setPolicyGlobalOption(LLCore::HttpRequest::GP_TRACE, 2); + + // Start threading early so that thread memory is invariant + // over the test. + HttpRequest::startThread(); + + // create a new ref counted object with an implicit reference + req = new HttpRequest(); + ensure("Memory allocated on construction", mMemTotal < GetMemTotal()); + + opts = new HttpOptions(); + opts->setWantHeaders(); + + // Issue a GET that succeeds + mStatus = HttpStatus(200); + handler.mCheckHeader = true; + HttpHandle handle = req->requestGetByteRange(HttpRequest::DEFAULT_POLICY_ID, + 0U, + url_base, + 0, + 0, + opts, + NULL, + &handler); + ensure("Valid handle returned for ranged request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // release options + opts->release(); + opts = NULL; + + // Run the notification pump. + int count(0); + int limit(10); + while (count++ < limit && mHandlerCalls < 1) + { + req->update(1000); + usleep(100000); + } + ensure("Request executed in reasonable time", count < limit); + ensure("One handler invocation for request", mHandlerCalls == 1); + + // Okay, request a shutdown of the servicing thread + mStatus = HttpStatus(); + handler.mCheckHeader = false; + handle = req->requestStopThread(&handler); + ensure("Valid handle returned for second request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump again + count = 0; + limit = 10; + while (count++ < limit && mHandlerCalls < 2) + { + req->update(1000); + usleep(100000); + } + ensure("Second request executed in reasonable time", count < limit); + ensure("Second handler invocation", mHandlerCalls == 2); + + // See that we actually shutdown the thread + count = 0; + limit = 10; + while (count++ < limit && ! HttpService::isStopped()) + { + usleep(100000); + } + ensure("Thread actually stopped running", HttpService::isStopped()); + + // release the request object + delete req; + req = NULL; + + // Shut down service + HttpRequest::destroyService(); + + ensure("Two handler calls on the way out", 2 == mHandlerCalls); + +#if defined(WIN32) + // Can only do this memory test on Windows. On other platforms, + // the LL logging system holds on to memory and produces what looks + // like memory leaks... + + // printf("Old mem: %d, New mem: %d\n", mMemTotal, GetMemTotal()); + ensure("Memory usage back to that at entry", mMemTotal == GetMemTotal()); +#endif + } + catch (...) + { + stop_thread(req); + if (opts) + { + opts->release(); + opts = NULL; + } + delete req; + HttpRequest::destroyService(); + throw; + } +} + +// *NB: This test must be last. The sleeping webserver +// won't respond for a long time. +template <> template <> +void HttpRequestTestObjectType::test<12>() +{ + ScopedCurlInit ready; + set_test_name("HttpRequest GET timeout"); // Handler can be stack-allocated *if* there are no dangling @@ -1117,7 +1269,7 @@ void HttpRequestTestObjectType::test<11>() opts->setRetries(0); // Don't retry opts->setTimeout(2); - // Issue a GET that can't connect + // Issue a GET that sleeps mStatus = HttpStatus(HttpStatus::EXT_CURL_EASY, CURLE_OPERATION_TIMEDOUT); HttpHandle handle = req->requestGetByteRange(HttpRequest::DEFAULT_POLICY_ID, 0U, diff --git a/indra/llcorehttp/tests/test_llcorehttp_peer.py b/indra/llcorehttp/tests/test_llcorehttp_peer.py index 5f0116d384..aedd8acd83 100644 --- a/indra/llcorehttp/tests/test_llcorehttp_peer.py +++ b/indra/llcorehttp/tests/test_llcorehttp_peer.py @@ -107,6 +107,7 @@ class TestHTTPRequestHandler(BaseHTTPRequestHandler): self.send_response(200) self.send_header("Content-type", "application/llsd+xml") self.send_header("Content-Length", str(len(response))) + self.send_header("X-LL-Special", "Mememememe"); self.end_headers() self.wfile.write(response) else: # fail requested diff --git a/indra/newview/lltexturefetch.cpp b/indra/newview/lltexturefetch.cpp index 2eda6a9b80..4995d9f5ea 100644 --- a/indra/newview/lltexturefetch.cpp +++ b/indra/newview/lltexturefetch.cpp @@ -1043,8 +1043,9 @@ void LLTextureFetchWorker::startWork(S32 param) // Threads: Ttf bool LLTextureFetchWorker::doWork(S32 param) { - static const LLCore::HttpStatus http_not_found(HTTP_NOT_FOUND); - static const LLCore::HttpStatus http_service_unavail(HTTP_SERVICE_UNAVAILABLE); + static const LLCore::HttpStatus http_not_found(HTTP_NOT_FOUND); // 404 + static const LLCore::HttpStatus http_service_unavail(HTTP_SERVICE_UNAVAILABLE); // 503 + static const LLCore::HttpStatus http_not_sat(HTTP_REQUESTED_RANGE_NOT_SATISFIABLE); // 416; // Release waiters while we aren't holding the Mw lock. mFetcher->releaseHttpWaiters(); -- cgit v1.3 From 398d78a77348fb4c5ee4e96b7ed6f981b1042627 Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Mon, 9 Jul 2012 13:28:50 -0400 Subject: Rework the 'sleep' logic in the test HTTP server so that the 30-second hang doesn't break subsequent tests. Did this by introducing threads into the HTTP server as I can't find the magic to detect that my client has gone away. --- indra/llcorehttp/tests/test_httprequest.hpp | 5 ----- indra/llcorehttp/tests/test_llcorehttp_peer.py | 6 ++++-- 2 files changed, 4 insertions(+), 7 deletions(-) (limited to 'indra/llcorehttp/tests/test_llcorehttp_peer.py') diff --git a/indra/llcorehttp/tests/test_httprequest.hpp b/indra/llcorehttp/tests/test_httprequest.hpp index ed4e239fe7..914f35ec3d 100644 --- a/indra/llcorehttp/tests/test_httprequest.hpp +++ b/indra/llcorehttp/tests/test_httprequest.hpp @@ -1381,8 +1381,6 @@ void HttpRequestTestObjectType::test<13>() } -// *NB: This test must be last. The sleeping webserver -// won't respond for a long time. template <> template <> void HttpRequestTestObjectType::test<14>() { @@ -1503,9 +1501,6 @@ void HttpRequestTestObjectType::test<14>() throw; } } -// *NOTE: This test ^^^^^^^^ must be the last one in the set. It uses a -// sleeping service that interferes with other HTTP tests. Keep it -// last until that little HTTP server can get some attention... } // end namespace tut diff --git a/indra/llcorehttp/tests/test_llcorehttp_peer.py b/indra/llcorehttp/tests/test_llcorehttp_peer.py index 0e38e5a87f..9e1847c66e 100644 --- a/indra/llcorehttp/tests/test_llcorehttp_peer.py +++ b/indra/llcorehttp/tests/test_llcorehttp_peer.py @@ -32,10 +32,12 @@ $/LicenseInfo$ import os import sys import time +import select from threading import Thread from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler +from SocketServer import ThreadingMixIn -mydir = os.path.dirname(__file__) # expected to be .../indra/llmessage/tests/ +mydir = os.path.dirname(__file__) # expected to be .../indra/llcorehttp/tests/ sys.path.insert(0, os.path.join(mydir, os.pardir, os.pardir, "lib", "python")) from indra.util.fastest_elementtree import parse as xml_parse from indra.base import llsd @@ -144,7 +146,7 @@ class TestHTTPRequestHandler(BaseHTTPRequestHandler): # Suppress error output as well pass -class Server(HTTPServer): +class Server(ThreadingMixIn, HTTPServer): # This pernicious flag is on by default in HTTPServer. But proper # operation of freeport() absolutely depends on it being off. allow_reuse_address = False -- cgit v1.3 From 7010459f04177aef1875a110b3d33e10c8ec5cad Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Wed, 11 Jul 2012 15:53:57 -0400 Subject: SH-3240 Capture Content-Type and Content-Encoding headers. HttpResponse object now has two strings for these content headers. Either or both may be empty. Tidied up the cross-platform string code and got more defensive about the length of a header line. Integration test for the new response object. --- indra/llcorehttp/_httpoprequest.cpp | 158 ++++++++++++++++++------- indra/llcorehttp/_httpoprequest.h | 4 + indra/llcorehttp/httpresponse.h | 19 ++- indra/llcorehttp/tests/test_httprequest.hpp | 126 ++++++++++++++++++++ indra/llcorehttp/tests/test_llcorehttp_peer.py | 2 +- 5 files changed, 263 insertions(+), 46 deletions(-) (limited to 'indra/llcorehttp/tests/test_llcorehttp_peer.py') diff --git a/indra/llcorehttp/_httpoprequest.cpp b/indra/llcorehttp/_httpoprequest.cpp index bec82d8449..1854d7ada4 100644 --- a/indra/llcorehttp/_httpoprequest.cpp +++ b/indra/llcorehttp/_httpoprequest.cpp @@ -74,16 +74,16 @@ void escape_libcurl_debug_data(char * buffer, size_t len, bool scrub, std::string & safe_line); -#if LL_WINDOWS +// OS-neutral string comparisons of various types +int os_strncasecmp(const char *s1, const char *s2, size_t n); +int os_strcasecmp(const char *s1, const char *s2); +char * os_strtok_r(char *str, const char *delim, char **saveptr); -// Not available on windows where the legacy strtok interface -// is thread-safe. -char *strtok_r(char *str, const char *delim, char **saveptr); -#endif // LL_WINDOWS +static const char * const hdr_whitespace(" \t"); +static const char * const hdr_separator(": \t"); - -} +} // end anonymous namespace namespace LLCore @@ -228,7 +228,8 @@ void HttpOpRequest::visitNotifier(HttpRequest * request) // Got an explicit offset/length in response response->setRange(mReplyOffset, mReplyLength, mReplyFullLength); } - + response->setContent(mReplyConType, mReplyConEncode); + mUserHandler->onCompleted(static_cast(this), response); response->release(); @@ -315,7 +316,7 @@ void HttpOpRequest::setupCommon(HttpRequest::policy_t policy_id, HttpOptions * options, HttpHeaders * headers) { - mProcFlags = 0; + mProcFlags = PF_SCAN_CONTENT_HEADERS; // Always scan for content headers mReqPolicy = policy_id; mReqPriority = priority; mReqURL = url; @@ -377,6 +378,8 @@ HttpStatus HttpOpRequest::prepareRequest(HttpService * service) mReplyHeaders->release(); mReplyHeaders = NULL; } + mReplyConType.clear(); + mReplyConEncode.clear(); // *FIXME: better error handling later HttpStatus status; @@ -539,7 +542,7 @@ HttpStatus HttpOpRequest::prepareRequest(HttpService * service) } curl_easy_setopt(mCurlHandle, CURLOPT_HTTPHEADER, mCurlHeaders); - if (mProcFlags & (PF_SCAN_RANGE_HEADER | PF_SAVE_HEADERS)) + if (mProcFlags & (PF_SCAN_RANGE_HEADER | PF_SAVE_HEADERS | PF_SCAN_CONTENT_HEADERS)) { curl_easy_setopt(mCurlHandle, CURLOPT_HEADERFUNCTION, headerCallback); curl_easy_setopt(mCurlHandle, CURLOPT_HEADERDATA, this); @@ -598,12 +601,18 @@ size_t HttpOpRequest::headerCallback(void * data, size_t size, size_t nmemb, voi static const char con_ran_line[] = "content-range:"; static const size_t con_ran_line_len = sizeof(con_ran_line) - 1; + + static const char con_type_line[] = "content-type:"; + static const size_t con_type_line_len = sizeof(con_type_line) - 1; + + static const char con_enc_line[] = "content-encoding:"; + static const size_t con_enc_line_len = sizeof(con_enc_line) - 1; HttpOpRequest * op(static_cast(userdata)); const size_t hdr_size(size * nmemb); const char * hdr_data(static_cast(data)); // Not null terminated - + if (hdr_size >= status_line_len && ! strncmp(status_line, hdr_data, status_line_len)) { // One of possibly several status lines. Reset what we know and start over @@ -611,24 +620,47 @@ size_t HttpOpRequest::headerCallback(void * data, size_t size, size_t nmemb, voi op->mReplyOffset = 0; op->mReplyLength = 0; op->mReplyFullLength = 0; + op->mReplyConType.clear(); + op->mReplyConEncode.clear(); op->mStatus = HttpStatus(); if (op->mReplyHeaders) { op->mReplyHeaders->mHeaders.clear(); } } - else if (op->mProcFlags & PF_SCAN_RANGE_HEADER) + + // Nothing in here wants a final CR/LF combination. Remove + // it as much as possible. + size_t wanted_hdr_size(hdr_size); + if (wanted_hdr_size && '\n' == hdr_data[wanted_hdr_size - 1]) + { + if (--wanted_hdr_size && '\r' == hdr_data[wanted_hdr_size - 1]) + { + --wanted_hdr_size; + } + } + + // Save header if caller wants them in the response + if (op->mProcFlags & PF_SAVE_HEADERS) + { + // Save headers in response + if (! op->mReplyHeaders) + { + op->mReplyHeaders = new HttpHeaders; + } + op->mReplyHeaders->mHeaders.push_back(std::string(hdr_data, wanted_hdr_size)); + } + + // Detect and parse 'Content-Range' headers + if (op->mProcFlags & PF_SCAN_RANGE_HEADER) { char hdr_buffer[128]; // Enough for a reasonable header - size_t frag_size((std::min)(hdr_size, sizeof(hdr_buffer) - 1)); + size_t frag_size((std::min)(wanted_hdr_size, sizeof(hdr_buffer) - 1)); memcpy(hdr_buffer, hdr_data, frag_size); hdr_buffer[frag_size] = '\0'; -#if LL_WINDOWS - if (! _strnicmp(hdr_buffer, con_ran_line, (std::min)(frag_size, con_ran_line_len))) -#else - if (! strncasecmp(hdr_buffer, con_ran_line, (std::min)(frag_size, con_ran_line_len))) -#endif // LL_WINDOWS + if (frag_size > con_ran_line_len && + ! os_strncasecmp(hdr_buffer, con_ran_line, con_ran_line_len)) { unsigned int first(0), last(0), length(0); int status; @@ -656,22 +688,43 @@ size_t HttpOpRequest::headerCallback(void * data, size_t size, size_t nmemb, voi } } - if (op->mProcFlags & PF_SAVE_HEADERS) + // Detect and parse 'Content-Type' and 'Content-Encoding' headers + if (op->mProcFlags & PF_SCAN_CONTENT_HEADERS) { - // Save headers in response - if (! op->mReplyHeaders) + if (wanted_hdr_size > con_type_line_len && + ! os_strncasecmp(hdr_data, con_type_line, con_type_line_len)) { - op->mReplyHeaders = new HttpHeaders; + // Found 'Content-Type:', extract single-token value + std::string rhs(hdr_data + con_type_line_len, wanted_hdr_size - con_type_line_len); + std::string::size_type begin(0), end(rhs.size()), pos; + + if ((pos = rhs.find_first_not_of(hdr_whitespace)) != std::string::npos) + { + begin = pos; + } + if ((pos = rhs.find_first_of(hdr_whitespace, begin)) != std::string::npos) + { + end = pos; + } + op->mReplyConType.assign(rhs, begin, end - begin); } - size_t wanted_size(hdr_size); - if (wanted_size && '\n' == hdr_data[wanted_size - 1]) + else if (wanted_hdr_size > con_enc_line_len && + ! os_strncasecmp(hdr_data, con_enc_line, con_enc_line_len)) { - if (--wanted_size && '\r' == hdr_data[wanted_size - 1]) + // Found 'Content-Encoding:', extract single-token value + std::string rhs(hdr_data + con_enc_line_len, wanted_hdr_size - con_enc_line_len); + std::string::size_type begin(0), end(rhs.size()), pos; + + if ((pos = rhs.find_first_not_of(hdr_whitespace)) != std::string::npos) { - --wanted_size; + begin = pos; } + if ((pos = rhs.find_first_of(hdr_whitespace, begin)) != std::string::npos) + { + end = pos; + } + op->mReplyConEncode.assign(rhs, begin, end - begin); } - op->mReplyHeaders->mHeaders.push_back(std::string(hdr_data, wanted_size)); } return hdr_size; @@ -788,15 +841,11 @@ int parse_content_range_header(char * buffer, char * tok_state(NULL), * tok(NULL); bool match(true); - if (! strtok_r(buffer, ": \t", &tok_state)) + if (! os_strtok_r(buffer, hdr_separator, &tok_state)) match = false; - if (match && (tok = strtok_r(NULL, " \t", &tok_state))) -#if LL_WINDOWS - match = 0 == _stricmp("bytes", tok); -#else - match = 0 == strcasecmp("bytes", tok); -#endif // LL_WINDOWS - if (match && ! (tok = strtok_r(NULL, " \t", &tok_state))) + if (match && (tok = os_strtok_r(NULL, hdr_whitespace, &tok_state))) + match = 0 == os_strcasecmp("bytes", tok); + if (match && ! (tok = os_strtok_r(NULL, " \t", &tok_state))) match = false; if (match) { @@ -834,15 +883,6 @@ int parse_content_range_header(char * buffer, return 1; } -#if LL_WINDOWS - -char *strtok_r(char *str, const char *delim, char ** savestate) -{ - return strtok_s(str, delim, savestate); -} - -#endif // LL_WINDOWS - void escape_libcurl_debug_data(char * buffer, size_t len, bool scrub, std::string & safe_line) { @@ -880,6 +920,36 @@ void escape_libcurl_debug_data(char * buffer, size_t len, bool scrub, std::strin } +int os_strncasecmp(const char *s1, const char *s2, size_t n) +{ +#if LL_WINDOWS + return _strnicmp(s1, s2, n); +#else + return strncasecmp(s1, s2, n); +#endif // LL_WINDOWS +} + + +int os_strcasecmp(const char *s1, const char *s2) +{ +#if LL_WINDOWS + return _stricmp(s1, s2); +#else + return strcasecmp(s1, s2); +#endif // LL_WINDOWS +} + + +char * os_strtok_r(char *str, const char *delim, char ** savestate) +{ +#if LL_WINDOWS + return strtok_s(str, delim, savestate); +#else + return strtok_r(str, delim, savestate); +#endif +} + + } // end anonymous namespace diff --git a/indra/llcorehttp/_httpoprequest.h b/indra/llcorehttp/_httpoprequest.h index a4c5fbb3c2..200b925c4e 100644 --- a/indra/llcorehttp/_httpoprequest.h +++ b/indra/llcorehttp/_httpoprequest.h @@ -30,6 +30,7 @@ #include "linden_common.h" // Modifies curl/curl.h interfaces +#include #include #include "httpcommon.h" @@ -137,6 +138,7 @@ protected: unsigned int mProcFlags; static const unsigned int PF_SCAN_RANGE_HEADER = 0x00000001U; static const unsigned int PF_SAVE_HEADERS = 0x00000002U; + static const unsigned int PF_SCAN_CONTENT_HEADERS = 0x00000004U; public: // Request data @@ -162,6 +164,8 @@ public: size_t mReplyLength; size_t mReplyFullLength; HttpHeaders * mReplyHeaders; + std::string mReplyConType; + std::string mReplyConEncode; // Policy data int mPolicyRetries; diff --git a/indra/llcorehttp/httpresponse.h b/indra/llcorehttp/httpresponse.h index 65e403cec8..3d35050c17 100644 --- a/indra/llcorehttp/httpresponse.h +++ b/indra/llcorehttp/httpresponse.h @@ -28,6 +28,8 @@ #define _LLCORE_HTTP_RESPONSE_H_ +#include + #include "httpcommon.h" #include "_refcounted.h" @@ -130,7 +132,20 @@ public: mReplyLength = length; mReplyFullLength = full_length; } - + + /// + void getContent(std::string & con_type, std::string & con_encode) const + { + con_type = mContentType; + con_encode = mContentEncoding; + } + + void setContent(const std::string & con_type, const std::string & con_encode) + { + mContentType = con_type; + mContentEncoding = con_encode; + } + protected: // Response data here HttpStatus mStatus; @@ -139,6 +154,8 @@ protected: unsigned int mReplyFullLength; BufferArray * mBufferArray; HttpHeaders * mHeaders; + std::string mContentType; + std::string mContentEncoding; }; diff --git a/indra/llcorehttp/tests/test_httprequest.hpp b/indra/llcorehttp/tests/test_httprequest.hpp index ba7c757af4..9edf3d19ec 100644 --- a/indra/llcorehttp/tests/test_httprequest.hpp +++ b/indra/llcorehttp/tests/test_httprequest.hpp @@ -117,6 +117,15 @@ public: ensure("Special header X-LL-Special in response", found_special); } + if (! mCheckContentType.empty()) + { + ensure("Response required with content type check", response != NULL); + std::string con_type, con_enc; + response->getContent(con_type, con_enc); + ensure("Content-Type as expected (" + mCheckContentType + ")", + mCheckContentType == con_type); + } + // std::cout << "TestHandler2::onCompleted() invoked" << std::endl; } @@ -124,6 +133,7 @@ public: std::string mName; HttpHandle mExpectHandle; bool mCheckHeader; + std::string mCheckContentType; }; typedef test_group HttpRequestTestGroupType; @@ -1502,6 +1512,122 @@ void HttpRequestTestObjectType::test<14>() } } +// Test retrieval of Content-Type/Content-Encoding headers +template <> template <> +void HttpRequestTestObjectType::test<15>() +{ + ScopedCurlInit ready; + + std::string url_base(get_base_url()); + // std::cerr << "Base: " << url_base << std::endl; + + set_test_name("HttpRequest GET with Content-Type"); + + // Handler can be stack-allocated *if* there are no dangling + // references to it after completion of this method. + // Create before memory record as the string copy will bump numbers. + TestHandler2 handler(this, "handler"); + + // Load and clear the string setting to preload std::string object + // for memory return tests. + handler.mCheckContentType = "application/llsd+xml"; + handler.mCheckContentType.clear(); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + mHandlerCalls = 0; + + HttpRequest * req = NULL; + + try + { + // Get singletons created + HttpRequest::createService(); + + // Start threading early so that thread memory is invariant + // over the test. + HttpRequest::startThread(); + + // create a new ref counted object with an implicit reference + req = new HttpRequest(); + ensure("Memory allocated on construction", mMemTotal < GetMemTotal()); + + // Issue a GET that *can* connect + mStatus = HttpStatus(200); + handler.mCheckContentType = "application/llsd+xml"; + HttpHandle handle = req->requestGet(HttpRequest::DEFAULT_POLICY_ID, + 0U, + url_base, + NULL, + NULL, + &handler); + ensure("Valid handle returned for ranged request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump. + int count(0); + int limit(10); + while (count++ < limit && mHandlerCalls < 1) + { + req->update(1000000); + usleep(100000); + } + ensure("Request executed in reasonable time", count < limit); + ensure("One handler invocation for request", mHandlerCalls == 1); + + // Okay, request a shutdown of the servicing thread + mStatus = HttpStatus(); + handler.mCheckContentType.clear(); + handle = req->requestStopThread(&handler); + ensure("Valid handle returned for second request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump again + count = 0; + limit = 10; + while (count++ < limit && mHandlerCalls < 2) + { + req->update(1000000); + usleep(100000); + } + ensure("Second request executed in reasonable time", count < limit); + ensure("Second handler invocation", mHandlerCalls == 2); + + // See that we actually shutdown the thread + count = 0; + limit = 10; + while (count++ < limit && ! HttpService::isStopped()) + { + usleep(100000); + } + ensure("Thread actually stopped running", HttpService::isStopped()); + + // release the request object + delete req; + req = NULL; + + // Shut down service + HttpRequest::destroyService(); + + ensure("Two handler calls on the way out", 2 == mHandlerCalls); + +#if defined(WIN32) + // Can only do this memory test on Windows. On other platforms, + // the LL logging system holds on to memory and produces what looks + // like memory leaks... + + // printf("Old mem: %d, New mem: %d\n", mMemTotal, GetMemTotal()); + ensure("Memory usage back to that at entry", mMemTotal == GetMemTotal()); +#endif + } + catch (...) + { + stop_thread(req); + delete req; + HttpRequest::destroyService(); + throw; + } +} + + } // end namespace tut namespace diff --git a/indra/llcorehttp/tests/test_llcorehttp_peer.py b/indra/llcorehttp/tests/test_llcorehttp_peer.py index 9e1847c66e..58b5bedd09 100644 --- a/indra/llcorehttp/tests/test_llcorehttp_peer.py +++ b/indra/llcorehttp/tests/test_llcorehttp_peer.py @@ -9,7 +9,7 @@ $LicenseInfo:firstyear=2008&license=viewerlgpl$ Second Life Viewer Source Code -Copyright (C) 2010, Linden Research, Inc. +Copyright (C) 2012, 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 -- cgit v1.3 From d45b2e7caece787dce4be501b103432c0f06c0f2 Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Thu, 12 Jul 2012 17:46:53 +0000 Subject: SH-3183 Use valgrind on the library. Using http_texture_load as the test subject, library looks clean. Did some better shutdown in the program itself and it looks better. Libcurl itself is making a lot of noise. Adapted testrunner to run valgrind as well but the memory allocation tester in the tools themselves grossly interferes with Valgrind operations. --- indra/llcorehttp/examples/http_texture_load.cpp | 3 +++ indra/llcorehttp/tests/test_llcorehttp_peer.py | 14 +++++++++++++- indra/llcorehttp/tests/testrunner.py | 5 ++++- 3 files changed, 20 insertions(+), 2 deletions(-) (limited to 'indra/llcorehttp/tests/test_llcorehttp_peer.py') diff --git a/indra/llcorehttp/examples/http_texture_load.cpp b/indra/llcorehttp/examples/http_texture_load.cpp index bcb322bd5c..998dc9240b 100644 --- a/indra/llcorehttp/examples/http_texture_load.cpp +++ b/indra/llcorehttp/examples/http_texture_load.cpp @@ -269,7 +269,10 @@ int main(int argc, char** argv) << std::endl; // Clean up + hr->requestStopThread(NULL); + ms_sleep(1000); delete hr; + LLCore::HttpRequest::destroyService(); term_curl(); return 0; diff --git a/indra/llcorehttp/tests/test_llcorehttp_peer.py b/indra/llcorehttp/tests/test_llcorehttp_peer.py index 58b5bedd09..489e8b2979 100644 --- a/indra/llcorehttp/tests/test_llcorehttp_peer.py +++ b/indra/llcorehttp/tests/test_llcorehttp_peer.py @@ -33,6 +33,7 @@ import os import sys import time import select +import getopt from threading import Thread from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler from SocketServer import ThreadingMixIn @@ -152,6 +153,13 @@ class Server(ThreadingMixIn, HTTPServer): allow_reuse_address = False if __name__ == "__main__": + do_valgrind = False + path_search = False + options, args = getopt.getopt(sys.argv[1:], "V", ["valgrind"]) + for option, value in options: + if option == "-V" or option == "--valgrind": + do_valgrind = True + # Instantiate a Server(TestHTTPRequestHandler) on the first free port # in the specified port range. Doing this inline is better than in a # daemon thread: if it blows up here, we'll get a traceback. If it blew up @@ -159,10 +167,14 @@ if __name__ == "__main__": # subject test program anyway. httpd, port = freeport(xrange(8000, 8020), lambda port: Server(('127.0.0.1', port), TestHTTPRequestHandler)) + # Pass the selected port number to the subject test program via the # environment. We don't want to impose requirements on the test program's # command-line parsing -- and anyway, for C++ integration tests, that's # performed in TUT code rather than our own. os.environ["LL_TEST_PORT"] = str(port) debug("$LL_TEST_PORT = %s", port) - sys.exit(run(server=Thread(name="httpd", target=httpd.serve_forever), *sys.argv[1:])) + if do_valgrind: + args = ["valgrind", "--log-file=./valgrind.log"] + args + path_search = True + sys.exit(run(server=Thread(name="httpd", target=httpd.serve_forever), use_path=path_search, *args)) diff --git a/indra/llcorehttp/tests/testrunner.py b/indra/llcorehttp/tests/testrunner.py index 5b9beb359b..9a2de71142 100644 --- a/indra/llcorehttp/tests/testrunner.py +++ b/indra/llcorehttp/tests/testrunner.py @@ -168,7 +168,10 @@ def run(*args, **kwds): # executable passed as our first arg, # - [no e] child should inherit this process's environment. debug("Running %s...", " ".join(args)) - rc = os.spawnv(os.P_WAIT, args[0], args) + if kwds.get("use_path", False): + rc = os.spawnvp(os.P_WAIT, args[0], args) + else: + rc = os.spawnv(os.P_WAIT, args[0], args) debug("%s returned %s", args[0], rc) return rc -- cgit v1.3 From 5eb5dc6b27c57f1c3e77fc04b471614968620068 Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Fri, 13 Jul 2012 18:24:49 -0400 Subject: SH-3241 validate that request headers are correct First round of integration tests. Added a request header 'reflector' to the web server to sent the client's headers back with a 'X-Reflect-' prefix. Use boost::regex to check various headers. Run a test on a simple GET and a byte-ranged GET a la texture fetch. --- indra/llcorehttp/httpoptions.cpp | 4 +- indra/llcorehttp/httpoptions.h | 2 +- indra/llcorehttp/tests/test_httprequest.hpp | 235 +++++++++++++++++++++++-- indra/llcorehttp/tests/test_llcorehttp_peer.py | 11 +- 4 files changed, 232 insertions(+), 20 deletions(-) (limited to 'indra/llcorehttp/tests/test_llcorehttp_peer.py') diff --git a/indra/llcorehttp/httpoptions.cpp b/indra/llcorehttp/httpoptions.cpp index f2771c1f29..68f7277ed3 100644 --- a/indra/llcorehttp/httpoptions.cpp +++ b/indra/llcorehttp/httpoptions.cpp @@ -46,9 +46,9 @@ HttpOptions::~HttpOptions() {} -void HttpOptions::setWantHeaders() +void HttpOptions::setWantHeaders(bool wanted) { - mWantHeaders = true; + mWantHeaders = wanted; } diff --git a/indra/llcorehttp/httpoptions.h b/indra/llcorehttp/httpoptions.h index a0b2253c11..97e46a8cd3 100644 --- a/indra/llcorehttp/httpoptions.h +++ b/indra/llcorehttp/httpoptions.h @@ -68,7 +68,7 @@ protected: void operator=(const HttpOptions &); // Not defined public: - void setWantHeaders(); + void setWantHeaders(bool wanted); bool getWantHeaders() const { return mWantHeaders; diff --git a/indra/llcorehttp/tests/test_httprequest.hpp b/indra/llcorehttp/tests/test_httprequest.hpp index 9edf3d19ec..0f9eb93996 100644 --- a/indra/llcorehttp/tests/test_httprequest.hpp +++ b/indra/llcorehttp/tests/test_httprequest.hpp @@ -36,6 +36,8 @@ #include "_httprequestqueue.h" #include +#include +#include #include "test_allocator.h" #include "llcorehttp_test.h" @@ -74,8 +76,7 @@ public: const std::string & name) : mState(state), mName(name), - mExpectHandle(LLCORE_HTTP_HANDLE_INVALID), - mCheckHeader(false) + mExpectHandle(LLCORE_HTTP_HANDLE_INVALID) {} virtual void onCompleted(HttpHandle handle, HttpResponse * response) @@ -97,24 +98,50 @@ public: { mState->mHandlerCalls++; } - if (mCheckHeader) + if (! mHeadersRequired.empty() || ! mHeadersDisallowed.empty()) { ensure("Response required with header check", response != NULL); HttpHeaders * header(response->getHeaders()); // Will not hold onto this ensure("Some quantity of headers returned", header != NULL); - bool found_special(false); - - for (HttpHeaders::container_t::const_iterator iter(header->mHeaders.begin()); - header->mHeaders.end() != iter; - ++iter) + + if (! mHeadersRequired.empty()) + { + for (int i(0); i < mHeadersRequired.size(); ++i) + { + bool found = false; + for (HttpHeaders::container_t::const_iterator iter(header->mHeaders.begin()); + header->mHeaders.end() != iter; + ++iter) + { + if (boost::regex_match(*iter, mHeadersRequired[i])) + { + found = true; + break; + } + } + std::ostringstream str; + str << "Required header # " << i << " found in response"; + ensure(str.str(), found); + } + } + + if (! mHeadersDisallowed.empty()) { - if (std::string::npos != (*iter).find("X-LL-Special")) + for (int i(0); i < mHeadersDisallowed.size(); ++i) { - found_special = true; - break; + for (HttpHeaders::container_t::const_iterator iter(header->mHeaders.begin()); + header->mHeaders.end() != iter; + ++iter) + { + if (boost::regex_match(*iter, mHeadersDisallowed[i])) + { + std::ostringstream str; + str << "Disallowed header # " << i << " not found in response"; + ensure(str.str(), false); + } + } } } - ensure("Special header X-LL-Special in response", found_special); } if (! mCheckContentType.empty()) @@ -132,8 +159,9 @@ public: HttpRequestTestData * mState; std::string mName; HttpHandle mExpectHandle; - bool mCheckHeader; std::string mCheckContentType; + std::vector mHeadersRequired; + std::vector mHeadersDisallowed; }; typedef test_group HttpRequestTestGroupType; @@ -1268,6 +1296,10 @@ void HttpRequestTestObjectType::test<13>() { ScopedCurlInit ready; + // Warmup boost::regex to pre-alloc memory for memory size tests + boost::regex warmup("askldjflasdj;f", boost::regex::icase); + boost::regex_match("akl;sjflajfk;ajsk", warmup); + std::string url_base(get_base_url()); // std::cerr << "Base: " << url_base << std::endl; @@ -1277,6 +1309,7 @@ void HttpRequestTestObjectType::test<13>() // references to it after completion of this method. // Create before memory record as the string copy will bump numbers. TestHandler2 handler(this, "handler"); + handler.mHeadersRequired.reserve(20); // Avoid memory leak test failure // record the total amount of dynamically allocated memory mMemTotal = GetMemTotal(); @@ -1302,11 +1335,11 @@ void HttpRequestTestObjectType::test<13>() ensure("Memory allocated on construction", mMemTotal < GetMemTotal()); opts = new HttpOptions(); - opts->setWantHeaders(); + opts->setWantHeaders(true); // Issue a GET that succeeds mStatus = HttpStatus(200); - handler.mCheckHeader = true; + handler.mHeadersRequired.push_back(boost::regex("\\W*X-LL-Special:.*", boost::regex::icase)); HttpHandle handle = req->requestGetByteRange(HttpRequest::DEFAULT_POLICY_ID, 0U, url_base, @@ -1334,7 +1367,7 @@ void HttpRequestTestObjectType::test<13>() // Okay, request a shutdown of the servicing thread mStatus = HttpStatus(); - handler.mCheckHeader = false; + handler.mHeadersRequired.clear(); handle = req->requestStopThread(&handler); ensure("Valid handle returned for second request", handle != LLCORE_HTTP_HANDLE_INVALID); @@ -1628,6 +1661,176 @@ void HttpRequestTestObjectType::test<15>() } +// Test header generation on GET requests +template <> template <> +void HttpRequestTestObjectType::test<16>() +{ + ScopedCurlInit ready; + + // Warmup boost::regex to pre-alloc memory for memory size tests + boost::regex warmup("askldjflasdj;f", boost::regex::icase); + boost::regex_match("akl;sjflajfk;ajsk", warmup); + + std::string url_base(get_base_url()); + + set_test_name("Header generation for HttpRequest GET"); + + // Handler can be stack-allocated *if* there are no dangling + // references to it after completion of this method. + // Create before memory record as the string copy will bump numbers. + TestHandler2 handler(this, "handler"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + mHandlerCalls = 0; + + HttpRequest * req = NULL; + HttpOptions * options = NULL; + HttpHeaders * headers = NULL; + + try + { + // Get singletons created + HttpRequest::createService(); + + // Start threading early so that thread memory is invariant + // over the test. + HttpRequest::startThread(); + + // create a new ref counted object with an implicit reference + req = new HttpRequest(); + + // options set + options = new HttpOptions(); + options->setWantHeaders(true); + + // Issue a GET that *can* connect + mStatus = HttpStatus(200); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-connection:\\W*keep-alive", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept:\\W*\\*/\\*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\W*X-Reflect-cache-control:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\W*X-Reflect-pragma:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\W*X-Reflect-range:.*", boost::regex::icase)); + HttpHandle handle = req->requestGet(HttpRequest::DEFAULT_POLICY_ID, + 0U, + url_base + "reflect/", + options, + NULL, + &handler); + ensure("Valid handle returned for get request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump. + int count(0); + int limit(10); + while (count++ < limit && mHandlerCalls < 1) + { + req->update(1000000); + usleep(100000); + } + ensure("Request executed in reasonable time", count < limit); + ensure("One handler invocation for request", mHandlerCalls == 1); + + // Do a texture-style fetch + headers = new HttpHeaders; + headers->mHeaders.push_back("Accept: image/x-j2c"); + + mStatus = HttpStatus(200); + handler.mHeadersRequired.clear(); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-connection:\\W*keep-alive", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept:\\W*\\image/x-j2c", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\W*X-Reflect-range:.*", boost::regex::icase)); + handler.mHeadersDisallowed.clear(); + handler.mHeadersDisallowed.push_back(boost::regex("\\W*X-Reflect-cache-control:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\W*X-Reflect-pragma:.*", boost::regex::icase)); + handle = req->requestGetByteRange(HttpRequest::DEFAULT_POLICY_ID, + 0U, + url_base + "reflect/", + 0, + 47, + options, + headers, + &handler); + ensure("Valid handle returned for ranged request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump. + count = 0; + limit = 10; + while (count++ < limit && mHandlerCalls < 2) + { + req->update(1000000); + usleep(100000); + } + ensure("Request executed in reasonable time", count < limit); + ensure("One handler invocation for request", mHandlerCalls == 2); + + + // Okay, request a shutdown of the servicing thread + mStatus = HttpStatus(); + handler.mHeadersRequired.clear(); + handler.mHeadersDisallowed.clear(); + handle = req->requestStopThread(&handler); + ensure("Valid handle returned for second request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump again + count = 0; + limit = 10; + while (count++ < limit && mHandlerCalls < 3) + { + req->update(1000000); + usleep(100000); + } + ensure("Second request executed in reasonable time", count < limit); + ensure("Second handler invocation", mHandlerCalls == 3); + + // See that we actually shutdown the thread + count = 0; + limit = 10; + while (count++ < limit && ! HttpService::isStopped()) + { + usleep(100000); + } + ensure("Thread actually stopped running", HttpService::isStopped()); + + // release options & headers + if (options) + { + options->release(); + } + options = NULL; + + if (headers) + { + headers->release(); + } + headers = NULL; + + // release the request object + delete req; + req = NULL; + + // Shut down service + HttpRequest::destroyService(); + } + catch (...) + { + stop_thread(req); + if (options) + { + options->release(); + options = NULL; + } + if (headers) + { + headers->release(); + headers = NULL; + } + delete req; + HttpRequest::destroyService(); + throw; + } +} + + } // end namespace tut namespace diff --git a/indra/llcorehttp/tests/test_llcorehttp_peer.py b/indra/llcorehttp/tests/test_llcorehttp_peer.py index 489e8b2979..c527ce6ce0 100644 --- a/indra/llcorehttp/tests/test_llcorehttp_peer.py +++ b/indra/llcorehttp/tests/test_llcorehttp_peer.py @@ -104,7 +104,7 @@ class TestHTTPRequestHandler(BaseHTTPRequestHandler): def answer(self, data, withdata=True): debug("%s.answer(%s): self.path = %r", self.__class__.__name__, data, self.path) - if self.path.find("/sleep/") != -1: + if "/sleep/" in self.path: time.sleep(30) if "fail" not in self.path: @@ -114,6 +114,8 @@ class TestHTTPRequestHandler(BaseHTTPRequestHandler): response = llsd.format_xml(data) debug("success: %s", response) self.send_response(200) + if "/reflect/" in self.path: + self.reflect_headers() self.send_header("Content-type", "application/llsd+xml") self.send_header("Content-Length", str(len(response))) self.send_header("X-LL-Special", "Mememememe"); @@ -133,6 +135,13 @@ class TestHTTPRequestHandler(BaseHTTPRequestHandler): "without providing a reason" % status))[1]) debug("fail requested: %s: %r", status, reason) self.send_error(status, reason) + if "/reflect/" in self.path: + self.reflect_headers() + self.end_headers() + + def reflect_headers(self): + for name in self.headers.keys(): + self.send_header("X-Reflect-" + name, self.headers[name]) if not VERBOSE: # When VERBOSE is set, skip both these overrides because they exist to -- cgit v1.3 From 8d3e5f3959b071226c4a5c2c68d9fe8664ae1ffd Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Mon, 16 Jul 2012 17:35:44 -0400 Subject: SH-3241 Validate header correctness, SH-3243 more unit tests Define expectations for headers for GET, POST, PUT requests. Document those in the interface, test those with integration tests. Verify that header overrides work as expected. --- indra/llcorehttp/_httpoprequest.cpp | 4 + indra/llcorehttp/httprequest.h | 175 +++-- indra/llcorehttp/tests/test_httprequest.hpp | 844 ++++++++++++++++++++++++- indra/llcorehttp/tests/test_llcorehttp_peer.py | 1 + 4 files changed, 960 insertions(+), 64 deletions(-) (limited to 'indra/llcorehttp/tests/test_llcorehttp_peer.py') diff --git a/indra/llcorehttp/_httpoprequest.cpp b/indra/llcorehttp/_httpoprequest.cpp index 1a770f67be..a18a164f0d 100644 --- a/indra/llcorehttp/_httpoprequest.cpp +++ b/indra/llcorehttp/_httpoprequest.cpp @@ -468,6 +468,8 @@ HttpStatus HttpOpRequest::prepareRequest(HttpService * service) curl_easy_setopt(mCurlHandle, CURLOPT_POSTFIELDS, static_cast(NULL)); curl_easy_setopt(mCurlHandle, CURLOPT_POSTFIELDSIZE, data_size); mCurlHeaders = curl_slist_append(mCurlHeaders, "Expect:"); + mCurlHeaders = curl_slist_append(mCurlHeaders, "Connection: keep-alive"); + mCurlHeaders = curl_slist_append(mCurlHeaders, "Keep-alive: 300"); } break; @@ -482,6 +484,8 @@ HttpStatus HttpOpRequest::prepareRequest(HttpService * service) curl_easy_setopt(mCurlHandle, CURLOPT_INFILESIZE, data_size); curl_easy_setopt(mCurlHandle, CURLOPT_POSTFIELDS, (void *) NULL); mCurlHeaders = curl_slist_append(mCurlHeaders, "Expect:"); + mCurlHeaders = curl_slist_append(mCurlHeaders, "Connection: keep-alive"); + mCurlHeaders = curl_slist_append(mCurlHeaders, "Keep-alive: 300"); } break; diff --git a/indra/llcorehttp/httprequest.h b/indra/llcorehttp/httprequest.h index 9dd53f4483..ab2f302d34 100644 --- a/indra/llcorehttp/httprequest.h +++ b/indra/llcorehttp/httprequest.h @@ -214,14 +214,46 @@ public: /// request in other requests (like cancellation) and will be an /// argument when any HttpHandler object is invoked. /// + /// Headers supplied by default: + /// - Connection: keep-alive + /// - Accept: */* + /// - Accept-Encoding: deflate, gzip + /// - Keep-alive: 300 + /// - Host: + /// + /// Some headers excluded by default: + /// - Pragma: + /// - Cache-control: + /// - Range: + /// - Transfer-Encoding: + /// - Referer: + /// /// @param policy_id Default or user-defined policy class under /// which this request is to be serviced. /// @param priority Standard priority scheme inherited from /// Indra code base (U32-type scheme). - /// @param url - /// @param options (optional) - /// @param headers (optional) - /// @param handler (optional) + /// @param url URL with any encoded query parameters to + /// be accessed. + /// @param options Optional instance of an HttpOptions object + /// to provide additional controls over the request + /// function for this request only. Any such + /// object then becomes shared-read across threads + /// and no code should modify the HttpOptions + /// instance. + /// @param headers Optional instance of an HttpHeaders object + /// to provide additional and/or overridden + /// headers for the request. As with options, + /// the instance becomes shared-read across threads + /// and no code should modify the HttpHeaders + /// instance. + /// @param handler Optional pointer to an HttpHandler instance + /// whose onCompleted() method will be invoked + /// during calls to update(). This is a non- + /// reference-counted object which would be a + /// problem for shutdown and other edge cases but + /// the pointer is only dereferenced during + /// calls to update(). + /// /// @return The handle of the request if successfully /// queued or LLCORE_HTTP_HANDLE_INVALID if the /// request could not be queued. In the latter @@ -244,20 +276,29 @@ public: /// request in other requests (like cancellation) and will be an /// argument when any HttpHandler object is invoked. /// - /// @param policy_id Default or user-defined policy class under - /// which this request is to be serviced. - /// @param priority Standard priority scheme inherited from - /// Indra code base (U32-type scheme). - /// @param url - /// @param offset - /// @param len - /// @param options (optional) - /// @param headers (optional) - /// @param handler (optional) - /// @return The handle of the request if successfully - /// queued or LLCORE_HTTP_HANDLE_INVALID if the - /// request could not be queued. In the latter - /// case, @see getStatus() will return more info. + /// Headers supplied by default: + /// - Connection: keep-alive + /// - Accept: */* + /// - Accept-Encoding: deflate, gzip + /// - Keep-alive: 300 + /// - Host: + /// - Range: (will be omitted if offset == 0 and len == 0) + /// + /// Some headers excluded by default: + /// - Pragma: + /// - Cache-control: + /// - Transfer-Encoding: + /// - Referer: + /// + /// @param policy_id @see requestGet() + /// @param priority " + /// @param url " + /// @param offset Offset of first byte into resource to be returned. + /// @param len Count of bytes to be returned + /// @param options @see requestGet() + /// @param headers " + /// @param handler " + /// @return " /// HttpHandle requestGetByteRange(policy_t policy_id, priority_t priority, @@ -269,22 +310,37 @@ public: HttpHandler * handler); - /// - /// @param policy_id Default or user-defined policy class under - /// which this request is to be serviced. - /// @param priority Standard priority scheme inherited from - /// Indra code base. - /// @param url + /// Queue a full HTTP POST. Query arguments and body may + /// be provided. Caller is responsible for escaping and + /// encoding and communicating the content types. + /// + /// Headers supplied by default: + /// - Connection: keep-alive + /// - Accept: */* + /// - Accept-Encoding: deflate, gzip + /// - Keep-Alive: 300 + /// - Host: + /// - Content-Length: + /// - Content-Type: application/x-www-form-urlencoded + /// + /// Some headers excluded by default: + /// - Pragma: + /// - Cache-Control: + /// - Transfer-Encoding: ... chunked ... + /// - Referer: + /// - Content-Encoding: + /// - Expect: + /// + /// @param policy_id @see requestGet() + /// @param priority " + /// @param url " /// @param body Byte stream to be sent as the body. No /// further encoding or escaping will be done /// to the content. - /// @param options (optional) - /// @param headers (optional) - /// @param handler (optional) - /// @return The handle of the request if successfully - /// queued or LLCORE_HTTP_HANDLE_INVALID if the - /// request could not be queued. In the latter - /// case, @see getStatus() will return more info. + /// @param options @see requestGet()K(optional) + /// @param headers " + /// @param handler " + /// @return " /// HttpHandle requestPost(policy_t policy_id, priority_t priority, @@ -295,22 +351,37 @@ public: HttpHandler * handler); - /// - /// @param policy_id Default or user-defined policy class under - /// which this request is to be serviced. - /// @param priority Standard priority scheme inherited from - /// Indra code base. - /// @param url + /// Queue a full HTTP PUT. Query arguments and body may + /// be provided. Caller is responsible for escaping and + /// encoding and communicating the content types. + /// + /// Headers supplied by default: + /// - Connection: keep-alive + /// - Accept: */* + /// - Accept-Encoding: deflate, gzip + /// - Keep-Alive: 300 + /// - Host: + /// - Content-Length: + /// + /// Some headers excluded by default: + /// - Pragma: + /// - Cache-Control: + /// - Transfer-Encoding: ... chunked ... + /// - Referer: + /// - Content-Encoding: + /// - Expect: + /// - Content-Type: + /// + /// @param policy_id @see requestGet() + /// @param priority " + /// @param url " /// @param body Byte stream to be sent as the body. No /// further encoding or escaping will be done /// to the content. - /// @param options (optional) - /// @param headers (optional) - /// @param handler (optional) - /// @return The handle of the request if successfully - /// queued or LLCORE_HTTP_HANDLE_INVALID if the - /// request could not be queued. In the latter - /// case, @see getStatus() will return more info. + /// @param options @see requestGet()K(optional) + /// @param headers " + /// @param handler " + /// @return " /// HttpHandle requestPut(policy_t policy_id, priority_t priority, @@ -326,11 +397,8 @@ public: /// immediately processes it and returns the request to the reply /// queue. /// - /// @param handler (optional) - /// @return The handle of the request if successfully - /// queued or LLCORE_HTTP_HANDLE_INVALID if the - /// request could not be queued. In the latter - /// case, @see getStatus() will return more info. + /// @param handler @see requestGet() + /// @return " /// HttpHandle requestNoOp(HttpHandler * handler); @@ -345,7 +413,8 @@ public: /// spend in the call. As hinted at above, this /// is partly a function of application code so it's /// a soft limit. A '0' value will run without - /// time limit. + /// time limit until everything queued has been + /// delivered. /// /// @return Standard status code. HttpStatus update(long usecs); @@ -365,10 +434,8 @@ public: /// @param request Handle of previously-issued request to /// be changed. /// @param priority New priority value. - /// @param handler (optional) - /// @return The handle of the request if successfully - /// queued or LLCORE_HTTP_HANDLE_INVALID if the - /// request could not be queued. + /// @param handler @see requestGet() + /// @return " /// HttpHandle requestSetPriority(HttpHandle request, priority_t priority, HttpHandler * handler); diff --git a/indra/llcorehttp/tests/test_httprequest.hpp b/indra/llcorehttp/tests/test_httprequest.hpp index 1acf4f9d4b..ec144693c3 100644 --- a/indra/llcorehttp/tests/test_httprequest.hpp +++ b/indra/llcorehttp/tests/test_httprequest.hpp @@ -1705,11 +1705,18 @@ void HttpRequestTestObjectType::test<16>() // Issue a GET that *can* connect mStatus = HttpStatus(200); - handler.mHeadersRequired.push_back(boost::regex("X-Reflect-connection:\\W*keep-alive", boost::regex::icase)); - handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept:\\W*\\*/\\*", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("\\W*X-Reflect-cache-control:.*", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("\\W*X-Reflect-pragma:.*", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("\\W*X-Reflect-range:.*", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-connection:\\s*keep-alive", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept:\\s*\\*/\\*", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept-encoding:\\s*((gzip|deflate),\\s*)+(gzip|deflate)", boost::regex::icase)); // close enough + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-keep-alive:\\s*\\d+", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-host:\\s*.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-cache-control:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-pragma:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-range:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-transfer-encoding:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-referer:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-content-type:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-content-encoding:.*", boost::regex::icase)); HttpHandle handle = req->requestGet(HttpRequest::DEFAULT_POLICY_ID, 0U, url_base + "reflect/", @@ -1735,12 +1742,19 @@ void HttpRequestTestObjectType::test<16>() mStatus = HttpStatus(200); handler.mHeadersRequired.clear(); - handler.mHeadersRequired.push_back(boost::regex("X-Reflect-connection:\\W*keep-alive", boost::regex::icase)); - handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept:\\W*\\image/x-j2c", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("\\W*X-Reflect-range:.*", boost::regex::icase)); handler.mHeadersDisallowed.clear(); - handler.mHeadersDisallowed.push_back(boost::regex("\\W*X-Reflect-cache-control:.*", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("\\W*X-Reflect-pragma:.*", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-connection:\\s*keep-alive", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept:\\s*image/x-j2c", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept-encoding:\\s*((gzip|deflate),\\s*)+(gzip|deflate)", boost::regex::icase)); // close enough + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-keep-alive:\\s*\\d+", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-host:\\s*.*", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("\\W*X-Reflect-range:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-cache-control:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-pragma:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-transfer-encoding:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-referer:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-content-type:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-content-encoding:.*", boost::regex::icase)); handle = req->requestGetByteRange(HttpRequest::DEFAULT_POLICY_ID, 0U, url_base + "reflect/", @@ -1830,6 +1844,816 @@ void HttpRequestTestObjectType::test<16>() } +// Test header generation on POST requests +template <> template <> +void HttpRequestTestObjectType::test<17>() +{ + ScopedCurlInit ready; + + // Warmup boost::regex to pre-alloc memory for memory size tests + boost::regex warmup("askldjflasdj;f", boost::regex::icase); + boost::regex_match("akl;sjflajfk;ajsk", warmup); + + std::string url_base(get_base_url()); + + set_test_name("Header generation for HttpRequest POST"); + + // Handler can be stack-allocated *if* there are no dangling + // references to it after completion of this method. + // Create before memory record as the string copy will bump numbers. + TestHandler2 handler(this, "handler"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + mHandlerCalls = 0; + + HttpRequest * req = NULL; + HttpOptions * options = NULL; + HttpHeaders * headers = NULL; + BufferArray * ba = NULL; + + try + { + // Get singletons created + HttpRequest::createService(); + + // Start threading early so that thread memory is invariant + // over the test. + HttpRequest::startThread(); + + // create a new ref counted object with an implicit reference + req = new HttpRequest(); + + // options set + options = new HttpOptions(); + options->setWantHeaders(true); + + // And a buffer array + const char * msg("It was the best of times, it was the worst of times."); + ba = new BufferArray; + ba->append(msg, strlen(msg)); + + // Issue a default POST + mStatus = HttpStatus(200); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-connection:\\s*keep-alive", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept:\\s*\\*/\\*", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept-encoding:\\s*((gzip|deflate),\\s*)+(gzip|deflate)", boost::regex::icase)); // close enough + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-keep-alive:\\s*\\d+", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-host:\\s*.*", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-content-length:\\s*\\d+", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-content-type:\\s*application/x-www-form-urlencoded", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-cache-control:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-pragma:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-range:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-referer:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-content-encoding:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-expect:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-transfer-encoding:\\s*.*chunked.*", boost::regex::icase)); + HttpHandle handle = req->requestPost(HttpRequest::DEFAULT_POLICY_ID, + 0U, + url_base + "reflect/", + ba, + options, + NULL, + &handler); + ensure("Valid handle returned for get request", handle != LLCORE_HTTP_HANDLE_INVALID); + ba->release(); + ba = NULL; + + // Run the notification pump. + int count(0); + int limit(10); + while (count++ < limit && mHandlerCalls < 1) + { + req->update(1000000); + usleep(100000); + } + ensure("Request executed in reasonable time", count < limit); + ensure("One handler invocation for request", mHandlerCalls == 1); + + + // Okay, request a shutdown of the servicing thread + mStatus = HttpStatus(); + handler.mHeadersRequired.clear(); + handler.mHeadersDisallowed.clear(); + handle = req->requestStopThread(&handler); + ensure("Valid handle returned for second request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump again + count = 0; + limit = 10; + while (count++ < limit && mHandlerCalls < 2) + { + req->update(1000000); + usleep(100000); + } + ensure("Second request executed in reasonable time", count < limit); + ensure("Second handler invocation", mHandlerCalls == 2); + + // See that we actually shutdown the thread + count = 0; + limit = 10; + while (count++ < limit && ! HttpService::isStopped()) + { + usleep(100000); + } + ensure("Thread actually stopped running", HttpService::isStopped()); + + // release options & headers + if (options) + { + options->release(); + } + options = NULL; + + if (headers) + { + headers->release(); + } + headers = NULL; + + // release the request object + delete req; + req = NULL; + + // Shut down service + HttpRequest::destroyService(); + } + catch (...) + { + stop_thread(req); + if (ba) + { + ba->release(); + ba = NULL; + } + if (options) + { + options->release(); + options = NULL; + } + if (headers) + { + headers->release(); + headers = NULL; + } + delete req; + HttpRequest::destroyService(); + throw; + } +} + + +// Test header generation on PUT requests +template <> template <> +void HttpRequestTestObjectType::test<18>() +{ + ScopedCurlInit ready; + + // Warmup boost::regex to pre-alloc memory for memory size tests + boost::regex warmup("askldjflasdj;f", boost::regex::icase); + boost::regex_match("akl;sjflajfk;ajsk", warmup); + + std::string url_base(get_base_url()); + + set_test_name("Header generation for HttpRequest PUT"); + + // Handler can be stack-allocated *if* there are no dangling + // references to it after completion of this method. + // Create before memory record as the string copy will bump numbers. + TestHandler2 handler(this, "handler"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + mHandlerCalls = 0; + + HttpRequest * req = NULL; + HttpOptions * options = NULL; + HttpHeaders * headers = NULL; + BufferArray * ba = NULL; + + try + { + // Get singletons created + HttpRequest::createService(); + + // Start threading early so that thread memory is invariant + // over the test. + HttpRequest::startThread(); + + // create a new ref counted object with an implicit reference + req = new HttpRequest(); + + // options set + options = new HttpOptions(); + options->setWantHeaders(true); + + // And a buffer array + const char * msg("It was the best of times, it was the worst of times."); + ba = new BufferArray; + ba->append(msg, strlen(msg)); + + // Issue a default PUT + mStatus = HttpStatus(200); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-connection:\\s*keep-alive", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept:\\s*\\*/\\*", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept-encoding:\\s*((gzip|deflate),\\s*)+(gzip|deflate)", boost::regex::icase)); // close enough + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-keep-alive:\\s*\\d+", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-host:\\s*.*", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-content-length:\\s*\\d+", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-cache-control:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-pragma:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-range:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-referer:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-content-encoding:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-expect:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-transfer-encoding:\\s*.*chunked.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("X-Reflect-content-type:.*", boost::regex::icase)); + HttpHandle handle = req->requestPut(HttpRequest::DEFAULT_POLICY_ID, + 0U, + url_base + "reflect/", + ba, + options, + NULL, + &handler); + ensure("Valid handle returned for get request", handle != LLCORE_HTTP_HANDLE_INVALID); + ba->release(); + ba = NULL; + + // Run the notification pump. + int count(0); + int limit(10); + while (count++ < limit && mHandlerCalls < 1) + { + req->update(1000000); + usleep(100000); + } + ensure("Request executed in reasonable time", count < limit); + ensure("One handler invocation for request", mHandlerCalls == 1); + + + // Okay, request a shutdown of the servicing thread + mStatus = HttpStatus(); + handler.mHeadersRequired.clear(); + handler.mHeadersDisallowed.clear(); + handle = req->requestStopThread(&handler); + ensure("Valid handle returned for second request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump again + count = 0; + limit = 10; + while (count++ < limit && mHandlerCalls < 2) + { + req->update(1000000); + usleep(100000); + } + ensure("Second request executed in reasonable time", count < limit); + ensure("Second handler invocation", mHandlerCalls == 2); + + // See that we actually shutdown the thread + count = 0; + limit = 10; + while (count++ < limit && ! HttpService::isStopped()) + { + usleep(100000); + } + ensure("Thread actually stopped running", HttpService::isStopped()); + + // release options & headers + if (options) + { + options->release(); + } + options = NULL; + + if (headers) + { + headers->release(); + } + headers = NULL; + + // release the request object + delete req; + req = NULL; + + // Shut down service + HttpRequest::destroyService(); + } + catch (...) + { + stop_thread(req); + if (ba) + { + ba->release(); + ba = NULL; + } + if (options) + { + options->release(); + options = NULL; + } + if (headers) + { + headers->release(); + headers = NULL; + } + delete req; + HttpRequest::destroyService(); + throw; + } +} + + +// Test header generation on GET requests with overrides +template <> template <> +void HttpRequestTestObjectType::test<19>() +{ + ScopedCurlInit ready; + + // Warmup boost::regex to pre-alloc memory for memory size tests + boost::regex warmup("askldjflasdj;f", boost::regex::icase); + boost::regex_match("akl;sjflajfk;ajsk", warmup); + + std::string url_base(get_base_url()); + + set_test_name("Header generation for HttpRequest GET with header overrides"); + + // Handler can be stack-allocated *if* there are no dangling + // references to it after completion of this method. + // Create before memory record as the string copy will bump numbers. + TestHandler2 handler(this, "handler"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + mHandlerCalls = 0; + + HttpRequest * req = NULL; + HttpOptions * options = NULL; + HttpHeaders * headers = NULL; + + try + { + // Get singletons created + HttpRequest::createService(); + + // Start threading early so that thread memory is invariant + // over the test. + HttpRequest::startThread(); + + // create a new ref counted object with an implicit reference + req = new HttpRequest(); + + // options set + options = new HttpOptions(); + options->setWantHeaders(true); + + // headers + headers = new HttpHeaders; + headers->mHeaders.push_back("Keep-Alive: 120"); + headers->mHeaders.push_back("Accept-encoding: deflate"); + headers->mHeaders.push_back("Accept: text/plain"); + + // Issue a GET with modified headers + mStatus = HttpStatus(200); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-connection:\\s*keep-alive", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept:\\s*text/plain", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept-encoding:\\s*deflate", boost::regex::icase)); // close enough + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-keep-alive:\\s*120", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-host:\\s*.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("X-Reflect-accept-encoding:\\s*((gzip|deflate),\\s*)+(gzip|deflate)", boost::regex::icase)); // close enough + handler.mHeadersDisallowed.push_back(boost::regex("X-Reflect-keep-alive:\\s*300", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("X-Reflect-accept:\\s*\\*/\\*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-cache-control:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-pragma:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-range:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-transfer-encoding:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-referer:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-content-type:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-content-encoding:.*", boost::regex::icase)); + HttpHandle handle = req->requestGet(HttpRequest::DEFAULT_POLICY_ID, + 0U, + url_base + "reflect/", + options, + headers, + &handler); + ensure("Valid handle returned for get request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump. + int count(0); + int limit(10); + while (count++ < limit && mHandlerCalls < 1) + { + req->update(1000000); + usleep(100000); + } + ensure("Request executed in reasonable time", count < limit); + ensure("One handler invocation for request", mHandlerCalls == 1); + + // Okay, request a shutdown of the servicing thread + mStatus = HttpStatus(); + handler.mHeadersRequired.clear(); + handler.mHeadersDisallowed.clear(); + handle = req->requestStopThread(&handler); + ensure("Valid handle returned for second request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump again + count = 0; + limit = 10; + while (count++ < limit && mHandlerCalls < 2) + { + req->update(1000000); + usleep(100000); + } + ensure("Second request executed in reasonable time", count < limit); + ensure("Second handler invocation", mHandlerCalls == 2); + + // See that we actually shutdown the thread + count = 0; + limit = 10; + while (count++ < limit && ! HttpService::isStopped()) + { + usleep(100000); + } + ensure("Thread actually stopped running", HttpService::isStopped()); + + // release options & headers + if (options) + { + options->release(); + } + options = NULL; + + if (headers) + { + headers->release(); + } + headers = NULL; + + // release the request object + delete req; + req = NULL; + + // Shut down service + HttpRequest::destroyService(); + } + catch (...) + { + stop_thread(req); + if (options) + { + options->release(); + options = NULL; + } + if (headers) + { + headers->release(); + headers = NULL; + } + delete req; + HttpRequest::destroyService(); + throw; + } +} + + +// Test header generation on POST requests with overrides +template <> template <> +void HttpRequestTestObjectType::test<20>() +{ + ScopedCurlInit ready; + + // Warmup boost::regex to pre-alloc memory for memory size tests + boost::regex warmup("askldjflasdj;f", boost::regex::icase); + boost::regex_match("akl;sjflajfk;ajsk", warmup); + + std::string url_base(get_base_url()); + + set_test_name("Header generation for HttpRequest POST with header overrides"); + + // Handler can be stack-allocated *if* there are no dangling + // references to it after completion of this method. + // Create before memory record as the string copy will bump numbers. + TestHandler2 handler(this, "handler"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + mHandlerCalls = 0; + + HttpRequest * req = NULL; + HttpOptions * options = NULL; + HttpHeaders * headers = NULL; + BufferArray * ba = NULL; + + try + { + // Get singletons created + HttpRequest::createService(); + + // Start threading early so that thread memory is invariant + // over the test. + HttpRequest::startThread(); + + // create a new ref counted object with an implicit reference + req = new HttpRequest(); + + // options set + options = new HttpOptions(); + options->setWantHeaders(true); + + // headers + headers = new HttpHeaders(); + headers->mHeaders.push_back("keep-Alive: 120"); + headers->mHeaders.push_back("Accept: text/html"); + headers->mHeaders.push_back("content-type: application/llsd+xml"); + headers->mHeaders.push_back("cache-control: no-store"); + + // And a buffer array + const char * msg("It was the best of times, it was the worst of times."); + ba = new BufferArray; + ba->append(msg, strlen(msg)); + + // Issue a default POST + mStatus = HttpStatus(200); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-connection:\\s*keep-alive", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept:\\s*text/html", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept-encoding:\\s*((gzip|deflate),\\s*)+(gzip|deflate)", boost::regex::icase)); // close enough + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-keep-alive:\\s*120", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-host:\\s*.*", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-content-length:\\s*\\d+", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-content-type:\\s*application/llsd\\+xml", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("\\s*X-Reflect-cache-control:\\s*no-store", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("X-Reflect-content-type:\\s*application/x-www-form-urlencoded", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("X-Reflect-accept:\\s*\\*/\\*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("X-Reflect-keep-alive:\\s*300", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-pragma:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-range:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-referer:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-content-encoding:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-expect:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-transfer-encoding:\\s*.*chunked.*", boost::regex::icase)); + HttpHandle handle = req->requestPost(HttpRequest::DEFAULT_POLICY_ID, + 0U, + url_base + "reflect/", + ba, + options, + headers, + &handler); + ensure("Valid handle returned for get request", handle != LLCORE_HTTP_HANDLE_INVALID); + ba->release(); + ba = NULL; + + // Run the notification pump. + int count(0); + int limit(10); + while (count++ < limit && mHandlerCalls < 1) + { + req->update(1000000); + usleep(100000); + } + ensure("Request executed in reasonable time", count < limit); + ensure("One handler invocation for request", mHandlerCalls == 1); + + + // Okay, request a shutdown of the servicing thread + mStatus = HttpStatus(); + handler.mHeadersRequired.clear(); + handler.mHeadersDisallowed.clear(); + handle = req->requestStopThread(&handler); + ensure("Valid handle returned for second request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump again + count = 0; + limit = 10; + while (count++ < limit && mHandlerCalls < 2) + { + req->update(1000000); + usleep(100000); + } + ensure("Second request executed in reasonable time", count < limit); + ensure("Second handler invocation", mHandlerCalls == 2); + + // See that we actually shutdown the thread + count = 0; + limit = 10; + while (count++ < limit && ! HttpService::isStopped()) + { + usleep(100000); + } + ensure("Thread actually stopped running", HttpService::isStopped()); + + // release options & headers + if (options) + { + options->release(); + } + options = NULL; + + if (headers) + { + headers->release(); + } + headers = NULL; + + // release the request object + delete req; + req = NULL; + + // Shut down service + HttpRequest::destroyService(); + } + catch (...) + { + stop_thread(req); + if (ba) + { + ba->release(); + ba = NULL; + } + if (options) + { + options->release(); + options = NULL; + } + if (headers) + { + headers->release(); + headers = NULL; + } + delete req; + HttpRequest::destroyService(); + throw; + } +} + + +// Test header generation on PUT requests with overrides +template <> template <> +void HttpRequestTestObjectType::test<21>() +{ + ScopedCurlInit ready; + + // Warmup boost::regex to pre-alloc memory for memory size tests + boost::regex warmup("askldjflasdj;f", boost::regex::icase); + boost::regex_match("akl;sjflajfk;ajsk", warmup); + + std::string url_base(get_base_url()); + + set_test_name("Header generation for HttpRequest PUT with header overrides"); + + // Handler can be stack-allocated *if* there are no dangling + // references to it after completion of this method. + // Create before memory record as the string copy will bump numbers. + TestHandler2 handler(this, "handler"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + mHandlerCalls = 0; + + HttpRequest * req = NULL; + HttpOptions * options = NULL; + HttpHeaders * headers = NULL; + BufferArray * ba = NULL; + + try + { + // Get singletons created + HttpRequest::createService(); + + // Start threading early so that thread memory is invariant + // over the test. + HttpRequest::startThread(); + + // create a new ref counted object with an implicit reference + req = new HttpRequest(); + + // options set + options = new HttpOptions(); + options->setWantHeaders(true); + + // headers + headers = new HttpHeaders; + headers->mHeaders.push_back("content-type: text/plain"); + headers->mHeaders.push_back("content-type: text/html"); + headers->mHeaders.push_back("content-type: application/llsd+xml"); + + // And a buffer array + const char * msg("It was the best of times, it was the worst of times."); + ba = new BufferArray; + ba->append(msg, strlen(msg)); + + // Issue a default PUT + mStatus = HttpStatus(200); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-connection:\\s*keep-alive", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept:\\s*\\*/\\*", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept-encoding:\\s*((gzip|deflate),\\s*)+(gzip|deflate)", boost::regex::icase)); // close enough + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-keep-alive:\\s*\\d+", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-host:\\s*.*", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-content-length:\\s*\\d+", boost::regex::icase)); + handler.mHeadersRequired.push_back(boost::regex("X-Reflect-content-type:\\s*application/llsd\\+xml", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-cache-control:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-pragma:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-range:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-referer:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-content-encoding:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-expect:.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-transfer-encoding:\\s*.*chunked.*", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("X-Reflect-content-type:\\s*text/plain", boost::regex::icase)); + handler.mHeadersDisallowed.push_back(boost::regex("X-Reflect-content-type:\\s*text/html", boost::regex::icase)); + HttpHandle handle = req->requestPut(HttpRequest::DEFAULT_POLICY_ID, + 0U, + url_base + "reflect/", + ba, + options, + headers, + &handler); + ensure("Valid handle returned for get request", handle != LLCORE_HTTP_HANDLE_INVALID); + ba->release(); + ba = NULL; + + // Run the notification pump. + int count(0); + int limit(10); + while (count++ < limit && mHandlerCalls < 1) + { + req->update(1000000); + usleep(100000); + } + ensure("Request executed in reasonable time", count < limit); + ensure("One handler invocation for request", mHandlerCalls == 1); + + + // Okay, request a shutdown of the servicing thread + mStatus = HttpStatus(); + handler.mHeadersRequired.clear(); + handler.mHeadersDisallowed.clear(); + handle = req->requestStopThread(&handler); + ensure("Valid handle returned for second request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump again + count = 0; + limit = 10; + while (count++ < limit && mHandlerCalls < 2) + { + req->update(1000000); + usleep(100000); + } + ensure("Second request executed in reasonable time", count < limit); + ensure("Second handler invocation", mHandlerCalls == 2); + + // See that we actually shutdown the thread + count = 0; + limit = 10; + while (count++ < limit && ! HttpService::isStopped()) + { + usleep(100000); + } + ensure("Thread actually stopped running", HttpService::isStopped()); + + // release options & headers + if (options) + { + options->release(); + } + options = NULL; + + if (headers) + { + headers->release(); + } + headers = NULL; + + // release the request object + delete req; + req = NULL; + + // Shut down service + HttpRequest::destroyService(); + } + catch (...) + { + stop_thread(req); + if (ba) + { + ba->release(); + ba = NULL; + } + if (options) + { + options->release(); + options = NULL; + } + if (headers) + { + headers->release(); + headers = NULL; + } + delete req; + HttpRequest::destroyService(); + throw; + } +} + + } // end namespace tut namespace diff --git a/indra/llcorehttp/tests/test_llcorehttp_peer.py b/indra/llcorehttp/tests/test_llcorehttp_peer.py index c527ce6ce0..75a3c39ef2 100644 --- a/indra/llcorehttp/tests/test_llcorehttp_peer.py +++ b/indra/llcorehttp/tests/test_llcorehttp_peer.py @@ -141,6 +141,7 @@ class TestHTTPRequestHandler(BaseHTTPRequestHandler): def reflect_headers(self): for name in self.headers.keys(): + # print "Header: %s: %s" % (name, self.headers[name]) self.send_header("X-Reflect-" + name, self.headers[name]) if not VERBOSE: -- cgit v1.3