From eff651cffca60f2b69f6c596a8e9aa9e1ab44d3c Mon Sep 17 00:00:00 2001
From: Monty Brandenberg <monty@lindenlab.com>
Date: Fri, 12 Jul 2013 15:00:24 -0400
Subject: [PATCH] SH-4312  Configuration data between viewer and llcorehttp is
 clumsy. Much improved.  Unified the global and class options into a single
 option list.  Implemented static and dynamic setting paths as much as
 possible.  Dynamic path does require packet/RPC but otherwise there's near
 unification.  Dynamic modes can't get values back yet due to the
 response/notifier scheme but this doesn't bother me. Flatten global and class
 options into simpler struct-like entities. Setter/getter available on these
 when needed (external APIs) but code can otherwise fiddle directly when it
 knows what to do.  Much duplicated options/state removed from HttpPolicy. 
 Comments cleaned up.  Threads better described and consistently mentioned in
 API docs.  Integration test extended for 503 responses with Reply-After
 headers.

---
 indra/llcorehttp/_httpinternal.h              |   6 +-
 indra/llcorehttp/_httplibcurl.h               |  15 +-
 indra/llcorehttp/_httpoperation.h             |   8 +-
 indra/llcorehttp/_httpoprequest.cpp           |  19 +-
 indra/llcorehttp/_httpopsetget.cpp            |  93 +++++++---
 indra/llcorehttp/_httpopsetget.h              |  27 +--
 indra/llcorehttp/_httppolicy.cpp              | 110 ++++++------
 indra/llcorehttp/_httppolicy.h                |  39 ++--
 indra/llcorehttp/_httppolicyclass.cpp         |  37 ++--
 indra/llcorehttp/_httppolicyclass.h           |  19 +-
 indra/llcorehttp/_httppolicyglobal.cpp        |  66 +++----
 indra/llcorehttp/_httppolicyglobal.h          |  23 ++-
 indra/llcorehttp/_httpservice.cpp             | 170 ++++++++++++++++--
 indra/llcorehttp/_httpservice.h               |  52 +++---
 .../llcorehttp/examples/http_texture_load.cpp |   7 +-
 indra/llcorehttp/httpcommon.h                 | 109 +++++++++--
 indra/llcorehttp/httprequest.cpp              | 107 ++++++-----
 indra/llcorehttp/httprequest.h                | 128 ++++++-------
 indra/llcorehttp/tests/test_httprequest.hpp   | 140 ++++++++++++++-
 .../llcorehttp/tests/test_llcorehttp_peer.py  |  49 ++++-
 indra/newview/llappcorehttp.cpp               |  29 +--
 21 files changed, 881 insertions(+), 372 deletions(-)

diff --git a/indra/llcorehttp/_httpinternal.h b/indra/llcorehttp/_httpinternal.h
index f085ca3b91a..80f4f349424 100755
--- a/indra/llcorehttp/_httpinternal.h
+++ b/indra/llcorehttp/_httpinternal.h
@@ -36,7 +36,8 @@
 // General library to-do list
 //
 // - Implement policy classes.  Structure is mostly there just didn't
-//   need it for the first consumer.
+//   need it for the first consumer.  [Classes are there.  More
+//   advanced features, like borrowing, aren't there yet.]
 // - Consider Removing 'priority' from the request interface.  Its use
 //   in an always active class can lead to starvation of low-priority
 //   requests.  Requires coodination of priority values across all
@@ -46,6 +47,7 @@
 //   may not really need it.
 // - Set/get for global policy and policy classes is clumsy.  Rework
 //   it heading in a direction that allows for more dynamic behavior.
+//   [Mostly fixed]
 // - Move HttpOpRequest::prepareRequest() to HttpLibcurl for the
 //   pedantic.
 // - Update downloader and other long-duration services are going to
@@ -73,7 +75,7 @@
 //   the main source file.
 // - Expand areas of usage eventually leading to the removal of LLCurl.
 //   Rough order of expansion:
-//   .  Mesh fetch
+//   .  Mesh fetch [Underway]
 //   .  Avatar names
 //   .  Group membership lists
 //   .  Caps access in general
diff --git a/indra/llcorehttp/_httplibcurl.h b/indra/llcorehttp/_httplibcurl.h
index 611f029ef51..0ec90437bbf 100755
--- a/indra/llcorehttp/_httplibcurl.h
+++ b/indra/llcorehttp/_httplibcurl.h
@@ -4,7 +4,7 @@
  *
  * $LicenseInfo:firstyear=2012&license=viewerlgpl$
  * Second Life Viewer Source Code
- * Copyright (C) 2012, Linden Research, Inc.
+ * Copyright (C) 2012-2013, 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
@@ -71,16 +71,22 @@ class HttpLibcurl
 	///
 	/// @return			Indication of how long this method is
 	///					willing to wait for next service call.
+	///
+	/// Threading:  called by worker thread.
 	HttpService::ELoopSpeed processTransport();
 
 	/// Add request to the active list.  Caller is expected to have
 	/// provided us with a reference count on the op to hold the
 	/// request.  (No additional references will be added.)
+	///
+	/// Threading:  called by worker thread.
 	void addOp(HttpOpRequest * op);
 
 	/// One-time call to set the number of policy classes to be
 	/// serviced and to create the resources for each.  Value
 	/// must agree with HttpPolicy::setPolicies() call.
+	///
+	/// Threading:  called by init thread.
 	void start(int policy_count);
 
 	/// Synchronously stop libcurl operations.  All active requests
@@ -91,9 +97,13 @@ class HttpLibcurl
 	/// respective reply queues.
 	///
 	/// Can be restarted with a start() call.
+	///
+	/// Threading:  called by worker thread.
 	void shutdown();
 
 	/// Return global and per-class counts of active requests.
+	///
+	/// Threading:  called by worker thread.
 	int getActiveCount() const;
 	int getActiveCountInClass(int policy_class) const;
 
@@ -103,6 +113,7 @@ class HttpLibcurl
 	///
 	/// @return			True if handle was found and operation canceled.
 	///
+	/// Threading:  called by worker thread.
 	bool cancel(HttpHandle handle);
 
 protected:
@@ -121,7 +132,7 @@ class HttpLibcurl
 	HttpService *		mService;				// Simple reference, not owner
 	active_set_t		mActiveOps;
 	int					mPolicyCount;
-	CURLM **			mMultiHandles;
+	CURLM **			mMultiHandles;			// One handle per policy class
 }; // end class HttpLibcurl
 
 }  // end namespace LLCore
diff --git a/indra/llcorehttp/_httpoperation.h b/indra/llcorehttp/_httpoperation.h
index 914627fad0a..937a61187de 100755
--- a/indra/llcorehttp/_httpoperation.h
+++ b/indra/llcorehttp/_httpoperation.h
@@ -4,7 +4,7 @@
  *
  * $LicenseInfo:firstyear=2012&license=viewerlgpl$
  * Second Life Viewer Source Code
- * Copyright (C) 2012, Linden Research, Inc.
+ * Copyright (C) 2012-2013, 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
@@ -72,7 +72,7 @@ class HttpService;
 class HttpOperation : public LLCoreInt::RefCounted
 {
 public:
-	/// Threading:  called by a consumer/application thread.
+	/// Threading:  called by consumer thread.
 	HttpOperation();
 
 protected:
@@ -108,7 +108,7 @@ class HttpOperation : public LLCoreInt::RefCounted
 	///							by the worker thread.  This is passible data
 	///							until notification is performed.
 	///
-	/// Threading:  called by application thread.
+	/// Threading:  called by consumer thread.
 	///
 	void setReplyPath(HttpReplyQueue * reply_queue,
 					  HttpHandler * handler);
@@ -141,7 +141,7 @@ class HttpOperation : public LLCoreInt::RefCounted
 	/// call to HttpRequest::update().  This method does the necessary
 	/// dispatching.
 	///
-	/// Threading:  called by application thread.
+	/// Threading:  called by consumer thread.
 	///
 	virtual void visitNotifier(HttpRequest *);
 
diff --git a/indra/llcorehttp/_httpoprequest.cpp b/indra/llcorehttp/_httpoprequest.cpp
index ce0fc605ab8..d72f8f61197 100755
--- a/indra/llcorehttp/_httpoprequest.cpp
+++ b/indra/llcorehttp/_httpoprequest.cpp
@@ -402,7 +402,7 @@ HttpStatus HttpOpRequest::prepareRequest(HttpService * service)
 	// *FIXME:  better error handling later
 	HttpStatus status;
 
-	// Get policy options
+	// Get global policy options
 	HttpPolicyGlobal & policy(service->getPolicy().getGlobalOptions());
 	
 	mCurlHandle = curl_easy_init();
@@ -441,30 +441,27 @@ HttpStatus HttpOpRequest::prepareRequest(HttpService * service)
 	curl_easy_setopt(mCurlHandle, CURLOPT_SSL_VERIFYPEER, 1);
 	curl_easy_setopt(mCurlHandle, CURLOPT_SSL_VERIFYHOST, 0);
 
-	const std::string * opt_value(NULL);
-	long opt_long(0L);
-	policy.get(HttpRequest::GP_LLPROXY, &opt_long);
-	if (opt_long)
+	if (policy.mUseLLProxy)
 	{
 		// 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 if (policy.get(HttpRequest::GP_HTTP_PROXY, &opt_value))
+	else if (policy.mHttpProxy.size())
 	{
 		// *TODO:  This is fine for now but get fuller socks5/
 		// authentication thing going later....
-		curl_easy_setopt(mCurlHandle, CURLOPT_PROXY, opt_value->c_str());
+		curl_easy_setopt(mCurlHandle, CURLOPT_PROXY, policy.mHttpProxy.c_str());
 		curl_easy_setopt(mCurlHandle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
 	}
-	if (policy.get(HttpRequest::GP_CA_PATH, &opt_value))
+	if (policy.mCAPath.size())
 	{
-		curl_easy_setopt(mCurlHandle, CURLOPT_CAPATH, opt_value->c_str());
+		curl_easy_setopt(mCurlHandle, CURLOPT_CAPATH, policy.mCAPath.c_str());
 	}
-	if (policy.get(HttpRequest::GP_CA_FILE, &opt_value))
+	if (policy.mCAFile.size())
 	{
-		curl_easy_setopt(mCurlHandle, CURLOPT_CAINFO, opt_value->c_str());
+		curl_easy_setopt(mCurlHandle, CURLOPT_CAINFO, policy.mCAFile.c_str());
 	}
 	
 	switch (mReqMethod)
diff --git a/indra/llcorehttp/_httpopsetget.cpp b/indra/llcorehttp/_httpopsetget.cpp
index 8198528a9bd..a5363f9170b 100755
--- a/indra/llcorehttp/_httpopsetget.cpp
+++ b/indra/llcorehttp/_httpopsetget.cpp
@@ -4,7 +4,7 @@
  *
  * $LicenseInfo:firstyear=2012&license=viewerlgpl$
  * Second Life Viewer Source Code
- * Copyright (C) 2012, Linden Research, Inc.
+ * Copyright (C) 2012-2013, 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
@@ -27,6 +27,7 @@
 #include "_httpopsetget.h"
 
 #include "httpcommon.h"
+#include "httprequest.h"
 
 #include "_httpservice.h"
 #include "_httppolicy.h"
@@ -43,10 +44,11 @@ namespace LLCore
 
 HttpOpSetGet::HttpOpSetGet()
 	: HttpOperation(),
-	  mIsGlobal(false),
-	  mDoSet(false),
-	  mSetting(-1),				// Nothing requested
-	  mLongValue(0L)
+	  mReqOption(HttpRequest::PO_CONNECTION_LIMIT),
+	  mReqClass(HttpRequest::INVALID_POLICY_ID),
+	  mReqDoSet(false),
+	  mReqLongValue(0L),
+	  mReplyLongValue(0L)
 {}
 
 
@@ -54,37 +56,84 @@ HttpOpSetGet::~HttpOpSetGet()
 {}
 
 
-void HttpOpSetGet::setupGet(HttpRequest::EGlobalPolicy setting)
+HttpStatus HttpOpSetGet::setupGet(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass)
 {
-	mIsGlobal = true;
-	mSetting = setting;
+	HttpStatus status;
+	
+	mReqOption = opt;
+	mReqClass = pclass;
+	return status;
 }
 
 
-void HttpOpSetGet::setupSet(HttpRequest::EGlobalPolicy setting, const std::string & value)
+HttpStatus HttpOpSetGet::setupSet(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass, long value)
 {
-	mIsGlobal = true;
-	mDoSet = true;
-	mSetting = setting;
-	mStrValue = value;
+	HttpStatus status;
+
+	if (! HttpService::sOptionDesc[opt].mIsLong)
+	{
+		return HttpStatus(HttpStatus::LLCORE, HE_INVALID_ARG);
+	}
+	if (! HttpService::sOptionDesc[opt].mIsDynamic)
+	{
+		return HttpStatus(HttpStatus::LLCORE, HE_OPT_NOT_DYNAMIC);
+	}
+	
+	mReqOption = opt;
+	mReqClass = pclass;
+	mReqDoSet = true;
+	mReqLongValue = value;
+	
+	return status;
 }
 
 
-void HttpOpSetGet::stageFromRequest(HttpService * service)
+HttpStatus HttpOpSetGet::setupSet(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass, const std::string & value)
 {
-	HttpPolicyGlobal & pol_opt(service->getPolicy().getGlobalOptions());
-	HttpRequest::EGlobalPolicy setting(static_cast<HttpRequest::EGlobalPolicy>(mSetting));
+	HttpStatus status;
+
+	if (HttpService::sOptionDesc[opt].mIsLong)
+	{
+		return HttpStatus(HttpStatus::LLCORE, HE_INVALID_ARG);
+	}
+	if (! HttpService::sOptionDesc[opt].mIsDynamic)
+	{
+		return HttpStatus(HttpStatus::LLCORE, HE_OPT_NOT_DYNAMIC);
+	}
+
+	mReqOption = opt;
+	mReqClass = pclass;
+	mReqDoSet = true;
+	mReqStrValue = value;
 	
-	if (mDoSet)
+	return status;
+}
+
+
+void HttpOpSetGet::stageFromRequest(HttpService * service)
+{
+	if (mReqDoSet)
 	{
-		mStatus = pol_opt.set(setting, mStrValue);
+		if (HttpService::sOptionDesc[mReqOption].mIsLong)
+		{
+			mStatus = service->setPolicyOption(mReqOption, mReqClass,
+											   mReqLongValue, &mReplyLongValue);
+		}
+		else
+		{
+			mStatus = service->setPolicyOption(mReqOption, mReqClass,
+											   mReqStrValue, &mReplyStrValue);
+		}
 	}
-	if (mStatus)
+	else
 	{
-		const std::string * value(NULL);
-		if ((mStatus = pol_opt.get(setting, &value)))
+		if (HttpService::sOptionDesc[mReqOption].mIsLong)
+		{
+			mStatus = service->getPolicyOption(mReqOption, mReqClass, &mReplyLongValue);
+		}
+		else
 		{
-			mStrValue = *value;
+			mStatus = service->getPolicyOption(mReqOption, mReqClass, &mReplyStrValue);
 		}
 	}
 	
diff --git a/indra/llcorehttp/_httpopsetget.h b/indra/llcorehttp/_httpopsetget.h
index 6966b9d94e6..a1e76dd4299 100755
--- a/indra/llcorehttp/_httpopsetget.h
+++ b/indra/llcorehttp/_httpopsetget.h
@@ -4,7 +4,7 @@
  *
  * $LicenseInfo:firstyear=2012&license=viewerlgpl$
  * Second Life Viewer Source Code
- * Copyright (C) 2012, Linden Research, Inc.
+ * Copyright (C) 2012-2013, 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
@@ -46,7 +46,10 @@ namespace LLCore
 /// configuration settings.
 ///
 /// *NOTE:  Expect this to change.  Don't really like it yet.
-
+///
+/// *TODO:  Can't return values to caller yet.  Need to do
+/// something better with HttpResponse and visitNotifier().
+///
 class HttpOpSetGet : public HttpOperation
 {
 public:
@@ -61,19 +64,23 @@ class HttpOpSetGet : public HttpOperation
 
 public:
 	/// Threading:  called by application thread
-	void setupGet(HttpRequest::EGlobalPolicy setting);
-	void setupSet(HttpRequest::EGlobalPolicy setting, const std::string & value);
+	HttpStatus setupGet(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass);
+	HttpStatus setupSet(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass, long value);
+	HttpStatus setupSet(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass, const std::string & value);
 
 	virtual void stageFromRequest(HttpService *);
 
 public:
 	// Request data
-	bool				mIsGlobal;
-	bool				mDoSet;
-	int					mSetting;
-	long				mLongValue;
-	std::string			mStrValue;
-
+	HttpRequest::EPolicyOption	mReqOption;
+	HttpRequest::policy_t		mReqClass;
+	bool						mReqDoSet;
+	long						mReqLongValue;
+	std::string					mReqStrValue;
+
+	// Reply Data
+	long						mReplyLongValue;
+	std::string					mReplyStrValue;
 };  // end class HttpOpSetGet
 
 
diff --git a/indra/llcorehttp/_httppolicy.cpp b/indra/llcorehttp/_httppolicy.cpp
index 5f303dd0fe6..2754e8ef079 100755
--- a/indra/llcorehttp/_httppolicy.cpp
+++ b/indra/llcorehttp/_httppolicy.cpp
@@ -4,7 +4,7 @@
  *
  * $LicenseInfo:firstyear=2012&license=viewerlgpl$
  * Second Life Viewer Source Code
- * Copyright (C) 2012, Linden Research, Inc.
+ * Copyright (C) 2012-2013, 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
@@ -41,57 +41,64 @@ namespace LLCore
 
 
 // Per-policy-class data for a running system.
-// Collection of queues, parameters, history, metrics, etc.
+// Collection of queues, options and other data
 // for a single policy class.
 //
 // Threading:  accessed only by worker thread
-struct HttpPolicy::State
+struct HttpPolicy::ClassState
 {
 public:
-	State()
-		: mConnMax(HTTP_CONNECTION_LIMIT_DEFAULT),
-		  mConnAt(HTTP_CONNECTION_LIMIT_DEFAULT),
-		  mConnMin(1),
-		  mNextSample(0),
-		  mErrorCount(0),
-		  mErrorFactor(0)
+	ClassState()
 		{}
 	
 	HttpReadyQueue		mReadyQueue;
 	HttpRetryQueue		mRetryQueue;
 
 	HttpPolicyClass		mOptions;
-
-	long				mConnMax;
-	long				mConnAt;
-	long				mConnMin;
-
-	HttpTime			mNextSample;
-	unsigned long		mErrorCount;
-	unsigned long		mErrorFactor;
 };
 
 
 HttpPolicy::HttpPolicy(HttpService * service)
-	: mActiveClasses(0),
-	  mState(NULL),
-	  mService(service)
-{}
+	: mService(service)
+{
+	// Create default class
+	mClasses.push_back(new ClassState());
+}
 
 
 HttpPolicy::~HttpPolicy()
 {
 	shutdown();
+
+	for (class_list_t::iterator it(mClasses.begin()); it != mClasses.end(); ++it)
+	{
+		delete (*it);
+	}
+	mClasses.clear();
 	
 	mService = NULL;
 }
 
 
+HttpRequest::policy_t HttpPolicy::createPolicyClass()
+{
+	const HttpRequest::policy_t policy_class(mClasses.size());
+	if (policy_class >= HTTP_POLICY_CLASS_LIMIT)
+	{
+		return HttpRequest::INVALID_POLICY_ID;
+	}
+	mClasses.push_back(new ClassState());
+	return policy_class;
+}
+
+
 void HttpPolicy::shutdown()
 {
-	for (int policy_class(0); policy_class < mActiveClasses; ++policy_class)
+	for (int policy_class(0); policy_class < mClasses.size(); ++policy_class)
 	{
-		HttpRetryQueue & retryq(mState[policy_class].mRetryQueue);
+		ClassState & state(*mClasses[policy_class]);
+		
+		HttpRetryQueue & retryq(state.mRetryQueue);
 		while (! retryq.empty())
 		{
 			HttpOpRequest * op(retryq.top());
@@ -101,7 +108,7 @@ void HttpPolicy::shutdown()
 			op->release();
 		}
 
-		HttpReadyQueue & readyq(mState[policy_class].mReadyQueue);
+		HttpReadyQueue & readyq(state.mReadyQueue);
 		while (! readyq.empty())
 		{
 			HttpOpRequest * op(readyq.top());
@@ -111,28 +118,11 @@ void HttpPolicy::shutdown()
 			op->release();
 		}
 	}
-	delete [] mState;
-	mState = NULL;
-	mActiveClasses = 0;
 }
 
 
-void HttpPolicy::start(const HttpPolicyGlobal & global,
-					   const std::vector<HttpPolicyClass> & classes)
-{
-	llassert_always(! mState);
-
-	mGlobalOptions = global;
-	mActiveClasses = classes.size();
-	mState = new State [mActiveClasses];
-	for (int i(0); i < mActiveClasses; ++i)
-	{
-		mState[i].mOptions = classes[i];
-		mState[i].mConnMax = classes[i].mConnectionLimit;
-		mState[i].mConnAt = mState[i].mConnMax;
-		mState[i].mConnMin = 2;
-	}
-}
+void HttpPolicy::start()
+{}
 
 
 void HttpPolicy::addOp(HttpOpRequest * op)
@@ -141,7 +131,7 @@ void HttpPolicy::addOp(HttpOpRequest * op)
 	
 	op->mPolicyRetries = 0;
 	op->mPolicy503Retries = 0;
-	mState[policy_class].mReadyQueue.push(op);
+	mClasses[policy_class]->mReadyQueue.push(op);
 }
 
 
@@ -183,7 +173,7 @@ void HttpPolicy::retryOp(HttpOpRequest * op)
 							 << static_cast<HttpHandle>(op)
 							 << LL_ENDL;
 	}
-	mState[policy_class].mRetryQueue.push(op);
+	mClasses[policy_class]->mRetryQueue.push(op);
 }
 
 
@@ -204,11 +194,11 @@ HttpService::ELoopSpeed HttpPolicy::processReadyQueue()
 	HttpService::ELoopSpeed result(HttpService::REQUEST_SLEEP);
 	HttpLibcurl & transport(mService->getTransport());
 	
-	for (int policy_class(0); policy_class < mActiveClasses; ++policy_class)
+	for (int policy_class(0); policy_class < mClasses.size(); ++policy_class)
 	{
-		State & state(mState[policy_class]);
+		ClassState & state(*mClasses[policy_class]);
 		int active(transport.getActiveCountInClass(policy_class));
-		int needed(state.mConnAt - active);		// Expect negatives here
+		int needed(state.mOptions.mConnectionLimit - active);		// Expect negatives here
 
 		HttpRetryQueue & retryq(state.mRetryQueue);
 		HttpReadyQueue & readyq(state.mReadyQueue);
@@ -256,9 +246,9 @@ HttpService::ELoopSpeed HttpPolicy::processReadyQueue()
 
 bool HttpPolicy::changePriority(HttpHandle handle, HttpRequest::priority_t priority)
 {
-	for (int policy_class(0); policy_class < mActiveClasses; ++policy_class)
+	for (int policy_class(0); policy_class < mClasses.size(); ++policy_class)
 	{
-		State & state(mState[policy_class]);
+		ClassState & state(*mClasses[policy_class]);
 		// We don't scan retry queue because a priority change there
 		// is meaningless.  The request will be issued based on retry
 		// intervals not priority value, which is now moot.
@@ -286,9 +276,9 @@ bool HttpPolicy::changePriority(HttpHandle handle, HttpRequest::priority_t prior
 
 bool HttpPolicy::cancel(HttpHandle handle)
 {
-	for (int policy_class(0); policy_class < mActiveClasses; ++policy_class)
+	for (int policy_class(0); policy_class < mClasses.size(); ++policy_class)
 	{
-		State & state(mState[policy_class]);
+		ClassState & state(*mClasses[policy_class]);
 
 		// Scan retry queue
 		HttpRetryQueue::container_type & c1(state.mRetryQueue.get_container());
@@ -382,13 +372,21 @@ bool HttpPolicy::stageAfterCompletion(HttpOpRequest * op)
 	return false;						// not active
 }
 
+	
+HttpPolicyClass & HttpPolicy::getClassOptions(HttpRequest::policy_t pclass)
+{
+	llassert_always(pclass >= 0 && pclass < mClasses.size());
+	
+	return mClasses[pclass]->mOptions;
+}
+
 
 int HttpPolicy::getReadyCount(HttpRequest::policy_t policy_class) const
 {
-	if (policy_class < mActiveClasses)
+	if (policy_class < mClasses.size())
 	{
-		return (mState[policy_class].mReadyQueue.size()
-				+ mState[policy_class].mRetryQueue.size());
+		return (mClasses[policy_class]->mReadyQueue.size()
+				+ mClasses[policy_class]->mRetryQueue.size());
 	}
 	return 0;
 }
diff --git a/indra/llcorehttp/_httppolicy.h b/indra/llcorehttp/_httppolicy.h
index 03d92c0b8e1..bf1aa742673 100755
--- a/indra/llcorehttp/_httppolicy.h
+++ b/indra/llcorehttp/_httppolicy.h
@@ -4,7 +4,7 @@
  *
  * $LicenseInfo:firstyear=2012&license=viewerlgpl$
  * Second Life Viewer Source Code
- * Copyright (C) 2012, Linden Research, Inc.
+ * Copyright (C) 2012-2013, 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
@@ -60,6 +60,9 @@ class HttpPolicy
 	void operator=(const HttpPolicy &);			// Not defined
 
 public:
+	/// Threading:  called by init thread.
+	HttpRequest::policy_t createPolicyClass();
+	
 	/// Cancel all ready and retry requests sending them to
 	/// their notification queues.  Release state resources
 	/// making further request handling impossible.
@@ -71,9 +74,8 @@ class HttpPolicy
 	/// requests.  One-time call invoked before starting
 	/// the worker thread.
 	///
-	/// Threading:  called by application thread
-	void start(const HttpPolicyGlobal & global,
-			   const std::vector<HttpPolicyClass> & classes);
+	/// Threading:  called by init thread
+	void start();
 
 	/// Give the policy layer some cycles to scan the ready
 	/// queue promoting higher-priority requests to active
@@ -93,7 +95,7 @@ class HttpPolicy
 	/// and should not be modified by anyone until retrieved
 	/// from queue.
 	///
-	/// Threading:  called by any thread
+	/// Threading:  called by worker thread
 	void addOp(HttpOpRequest *);
 
 	/// Similar to addOp, used when a caller wants to retry a
@@ -130,30 +132,39 @@ class HttpPolicy
 	/// Threading:  called by worker thread
 	bool stageAfterCompletion(HttpOpRequest * op);
 	
-	// Get pointer to global policy options.  Caller is expected
-	// to do context checks like no setting once running.
+	/// Get a reference to global policy options.  Caller is expected
+	/// to do context checks like no setting once running.  These
+	/// are done, for example, in @see HttpService interfaces.
 	///
 	/// Threading:  called by any thread *but* the object may
 	/// only be modified by the worker thread once running.
-	///
 	HttpPolicyGlobal & getGlobalOptions()
 		{
 			return mGlobalOptions;
 		}
 
+	/// Get a reference to class policy options.  Caller is expected
+	/// to do context checks like no setting once running.  These
+	/// are done, for example, in @see HttpService interfaces.
+	///
+	/// Threading:  called by any thread *but* the object may
+	/// only be modified by the worker thread once running and
+	/// read accesses by other threads are exposed to races at
+	/// that point.
+	HttpPolicyClass & getClassOptions(HttpRequest::policy_t pclass);
+	
 	/// Get ready counts for a particular policy class
 	///
 	/// Threading:  called by worker thread
 	int getReadyCount(HttpRequest::policy_t policy_class) const;
 	
 protected:
-	struct State;
-
-	int									mActiveClasses;
-	State *								mState;
-	HttpService *						mService;				// Naked pointer, not refcounted, not owner
-	HttpPolicyGlobal					mGlobalOptions;
+	struct ClassState;
+	typedef std::vector<ClassState *>	class_list_t;
 	
+	HttpPolicyGlobal					mGlobalOptions;
+	class_list_t						mClasses;
+	HttpService *						mService;				// Naked pointer, not refcounted, not owner
 };  // end class HttpPolicy
 
 }  // end namespace LLCore
diff --git a/indra/llcorehttp/_httppolicyclass.cpp b/indra/llcorehttp/_httppolicyclass.cpp
index 1a55ab1ac6b..fe4359081a0 100755
--- a/indra/llcorehttp/_httppolicyclass.cpp
+++ b/indra/llcorehttp/_httppolicyclass.cpp
@@ -34,8 +34,7 @@ namespace LLCore
 
 
 HttpPolicyClass::HttpPolicyClass()
-	: mSetMask(0UL),
-	  mConnectionLimit(HTTP_CONNECTION_LIMIT_DEFAULT),
+	: mConnectionLimit(HTTP_CONNECTION_LIMIT_DEFAULT),
 	  mPerHostConnectionLimit(HTTP_CONNECTION_LIMIT_DEFAULT),
 	  mPipelining(HTTP_PIPELINING_DEFAULT)
 {}
@@ -49,7 +48,6 @@ HttpPolicyClass & HttpPolicyClass::operator=(const HttpPolicyClass & other)
 {
 	if (this != &other)
 	{
-		mSetMask = other.mSetMask;
 		mConnectionLimit = other.mConnectionLimit;
 		mPerHostConnectionLimit = other.mPerHostConnectionLimit;
 		mPipelining = other.mPipelining;
@@ -59,26 +57,25 @@ HttpPolicyClass & HttpPolicyClass::operator=(const HttpPolicyClass & other)
 
 
 HttpPolicyClass::HttpPolicyClass(const HttpPolicyClass & other)
-	: mSetMask(other.mSetMask),
-	  mConnectionLimit(other.mConnectionLimit),
+	: mConnectionLimit(other.mConnectionLimit),
 	  mPerHostConnectionLimit(other.mPerHostConnectionLimit),
 	  mPipelining(other.mPipelining)
 {}
 
 
-HttpStatus HttpPolicyClass::set(HttpRequest::EClassPolicy opt, long value)
+HttpStatus HttpPolicyClass::set(HttpRequest::EPolicyOption opt, long value)
 {
 	switch (opt)
 	{
-	case HttpRequest::CP_CONNECTION_LIMIT:
+	case HttpRequest::PO_CONNECTION_LIMIT:
 		mConnectionLimit = llclamp(value, long(HTTP_CONNECTION_LIMIT_MIN), long(HTTP_CONNECTION_LIMIT_MAX));
 		break;
 
-	case HttpRequest::CP_PER_HOST_CONNECTION_LIMIT:
+	case HttpRequest::PO_PER_HOST_CONNECTION_LIMIT:
 		mPerHostConnectionLimit = llclamp(value, long(HTTP_CONNECTION_LIMIT_MIN), mConnectionLimit);
 		break;
 
-	case HttpRequest::CP_ENABLE_PIPELINING:
+	case HttpRequest::PO_ENABLE_PIPELINING:
 		mPipelining = llclamp(value, 0L, 1L);
 		break;
 
@@ -86,38 +83,30 @@ HttpStatus HttpPolicyClass::set(HttpRequest::EClassPolicy opt, long value)
 		return HttpStatus(HttpStatus::LLCORE, HE_INVALID_ARG);
 	}
 
-	mSetMask |= 1UL << int(opt);
 	return HttpStatus();
 }
 
 
-HttpStatus HttpPolicyClass::get(HttpRequest::EClassPolicy opt, long * value)
+HttpStatus HttpPolicyClass::get(HttpRequest::EPolicyOption opt, long * value) const
 {
-	static const HttpStatus not_set(HttpStatus::LLCORE, HE_OPT_NOT_SET);
-	long * src(NULL);
-	
 	switch (opt)
 	{
-	case HttpRequest::CP_CONNECTION_LIMIT:
-		src = &mConnectionLimit;
+	case HttpRequest::PO_CONNECTION_LIMIT:
+		*value = mConnectionLimit;
 		break;
 
-	case HttpRequest::CP_PER_HOST_CONNECTION_LIMIT:
-		src = &mPerHostConnectionLimit;
+	case HttpRequest::PO_PER_HOST_CONNECTION_LIMIT:
+		*value = mPerHostConnectionLimit;
 		break;
 
-	case HttpRequest::CP_ENABLE_PIPELINING:
-		src = &mPipelining;
+	case HttpRequest::PO_ENABLE_PIPELINING:
+		*value = mPipelining;
 		break;
 
 	default:
 		return HttpStatus(HttpStatus::LLCORE, HE_INVALID_ARG);
 	}
 
-	if (! (mSetMask & (1UL << int(opt))))
-		return not_set;
-
-	*value = *src;
 	return HttpStatus();
 }
 
diff --git a/indra/llcorehttp/_httppolicyclass.h b/indra/llcorehttp/_httppolicyclass.h
index d175413cbd4..69fb459d227 100755
--- a/indra/llcorehttp/_httppolicyclass.h
+++ b/indra/llcorehttp/_httppolicyclass.h
@@ -4,7 +4,7 @@
  *
  * $LicenseInfo:firstyear=2012&license=viewerlgpl$
  * Second Life Viewer Source Code
- * Copyright (C) 2012, Linden Research, Inc.
+ * Copyright (C) 2012-2013, 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
@@ -34,6 +34,18 @@
 namespace LLCore
 {
 
+/// Options struct for per-class policy options.
+///
+/// Combines both raw blob data access with semantics-enforcing
+/// set/get interfaces.  For internal operations by the worker
+/// thread, just grab the setting directly from instance and test/use
+/// as needed.  When attached to external APIs (the public API
+/// options interfaces) the set/get methods are available to
+/// enforce correct ranges, data types, contexts, etc. and suitable
+/// status values are returned.
+///
+/// Threading:  Single-threaded.  In practice, init thread before
+/// worker starts, worker thread after.
 class HttpPolicyClass
 {
 public:
@@ -44,11 +56,10 @@ class HttpPolicyClass
 	HttpPolicyClass(const HttpPolicyClass &);			// Not defined
 
 public:
-	HttpStatus set(HttpRequest::EClassPolicy opt, long value);
-	HttpStatus get(HttpRequest::EClassPolicy opt, long * value);
+	HttpStatus set(HttpRequest::EPolicyOption opt, long value);
+	HttpStatus get(HttpRequest::EPolicyOption opt, long * value) const;
 	
 public:
-	unsigned long				mSetMask;
 	long						mConnectionLimit;
 	long						mPerHostConnectionLimit;
 	long						mPipelining;
diff --git a/indra/llcorehttp/_httppolicyglobal.cpp b/indra/llcorehttp/_httppolicyglobal.cpp
index 72f409d3b1d..1dc95f3dce1 100755
--- a/indra/llcorehttp/_httppolicyglobal.cpp
+++ b/indra/llcorehttp/_httppolicyglobal.cpp
@@ -4,7 +4,7 @@
  *
  * $LicenseInfo:firstyear=2012&license=viewerlgpl$
  * Second Life Viewer Source Code
- * Copyright (C) 2012, Linden Research, Inc.
+ * Copyright (C) 2012-2013, 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
@@ -34,8 +34,7 @@ namespace LLCore
 
 
 HttpPolicyGlobal::HttpPolicyGlobal()
-	: mSetMask(0UL),
-	  mConnectionLimit(HTTP_CONNECTION_LIMIT_DEFAULT),
+	: mConnectionLimit(HTTP_CONNECTION_LIMIT_DEFAULT),
 	  mTrace(HTTP_TRACE_OFF),
 	  mUseLLProxy(0)
 {}
@@ -49,7 +48,6 @@ HttpPolicyGlobal & HttpPolicyGlobal::operator=(const HttpPolicyGlobal & other)
 {
 	if (this != &other)
 	{
-		mSetMask = other.mSetMask;
 		mConnectionLimit = other.mConnectionLimit;
 		mCAPath = other.mCAPath;
 		mCAFile = other.mCAFile;
@@ -61,19 +59,19 @@ HttpPolicyGlobal & HttpPolicyGlobal::operator=(const HttpPolicyGlobal & other)
 }
 
 
-HttpStatus HttpPolicyGlobal::set(HttpRequest::EGlobalPolicy opt, long value)
+HttpStatus HttpPolicyGlobal::set(HttpRequest::EPolicyOption opt, long value)
 {
 	switch (opt)
 	{
-	case HttpRequest::GP_CONNECTION_LIMIT:
+	case HttpRequest::PO_CONNECTION_LIMIT:
 		mConnectionLimit = llclamp(value, long(HTTP_CONNECTION_LIMIT_MIN), long(HTTP_CONNECTION_LIMIT_MAX));
 		break;
 
-	case HttpRequest::GP_TRACE:
+	case HttpRequest::PO_TRACE:
 		mTrace = llclamp(value, long(HTTP_TRACE_MIN), long(HTTP_TRACE_MAX));
 		break;
 
-	case HttpRequest::GP_LLPROXY:
+	case HttpRequest::PO_LLPROXY:
 		mUseLLProxy = llclamp(value, 0L, 1L);
 		break;
 
@@ -81,24 +79,23 @@ HttpStatus HttpPolicyGlobal::set(HttpRequest::EGlobalPolicy opt, long value)
 		return HttpStatus(HttpStatus::LLCORE, HE_INVALID_ARG);
 	}
 
-	mSetMask |= 1UL << int(opt);
 	return HttpStatus();
 }
 
 
-HttpStatus HttpPolicyGlobal::set(HttpRequest::EGlobalPolicy opt, const std::string & value)
+HttpStatus HttpPolicyGlobal::set(HttpRequest::EPolicyOption opt, const std::string & value)
 {
 	switch (opt)
 	{
-	case HttpRequest::GP_CA_PATH:
+	case HttpRequest::PO_CA_PATH:
 		mCAPath = value;
 		break;
 
-	case HttpRequest::GP_CA_FILE:
+	case HttpRequest::PO_CA_FILE:
 		mCAFile = value;
 		break;
 
-	case HttpRequest::GP_HTTP_PROXY:
+	case HttpRequest::PO_HTTP_PROXY:
 		mCAFile = value;
 		break;
 
@@ -106,69 +103,54 @@ HttpStatus HttpPolicyGlobal::set(HttpRequest::EGlobalPolicy opt, const std::stri
 		return HttpStatus(HttpStatus::LLCORE, HE_INVALID_ARG);
 	}
 	
-	mSetMask |= 1UL << int(opt);
 	return HttpStatus();
 }
 
 
-HttpStatus HttpPolicyGlobal::get(HttpRequest::EGlobalPolicy opt, long * value)
+HttpStatus HttpPolicyGlobal::get(HttpRequest::EPolicyOption opt, long * value) const
 {
-	static const HttpStatus not_set(HttpStatus::LLCORE, HE_OPT_NOT_SET);
-	long * src(NULL);
-	
 	switch (opt)
 	{
-	case HttpRequest::GP_CONNECTION_LIMIT:
-		src = &mConnectionLimit;
+	case HttpRequest::PO_CONNECTION_LIMIT:
+		*value = mConnectionLimit;
 		break;
 
-	case HttpRequest::GP_TRACE:
-		src = &mTrace;
+	case HttpRequest::PO_TRACE:
+		*value = mTrace;
 		break;
 
-	case HttpRequest::GP_LLPROXY:
-		src = &mUseLLProxy;
+	case HttpRequest::PO_LLPROXY:
+		*value = mUseLLProxy;
 		break;
 
 	default:
 		return HttpStatus(HttpStatus::LLCORE, HE_INVALID_ARG);
 	}
 
-	if (! (mSetMask & (1UL << int(opt))))
-		return not_set;
-
-	*value = *src;
 	return HttpStatus();
 }
 
 
-HttpStatus HttpPolicyGlobal::get(HttpRequest::EGlobalPolicy opt, const std::string ** value)
+HttpStatus HttpPolicyGlobal::get(HttpRequest::EPolicyOption opt, std::string * value) const
 {
-	static const HttpStatus not_set(HttpStatus::LLCORE, HE_OPT_NOT_SET);
-	const std::string * src(NULL);
-
 	switch (opt)
 	{
-	case HttpRequest::GP_CA_PATH:
-		src = &mCAPath;
+	case HttpRequest::PO_CA_PATH:
+		*value = mCAPath;
 		break;
 
-	case HttpRequest::GP_CA_FILE:
-		src = &mCAFile;
+	case HttpRequest::PO_CA_FILE:
+		*value = mCAFile;
 		break;
 
-	case HttpRequest::GP_HTTP_PROXY:
-		src = &mHttpProxy;
+	case HttpRequest::PO_HTTP_PROXY:
+		*value = mHttpProxy;
 		break;
 
 	default:
 		return HttpStatus(HttpStatus::LLCORE, HE_INVALID_ARG);
 	}
 	
-	if (! (mSetMask & (1UL << int(opt))))
-		return not_set;
-
-	*value = src;
 	return HttpStatus();
 }
 
diff --git a/indra/llcorehttp/_httppolicyglobal.h b/indra/llcorehttp/_httppolicyglobal.h
index a50d0e41887..67c4ba9481d 100755
--- a/indra/llcorehttp/_httppolicyglobal.h
+++ b/indra/llcorehttp/_httppolicyglobal.h
@@ -4,7 +4,7 @@
  *
  * $LicenseInfo:firstyear=2012&license=viewerlgpl$
  * Second Life Viewer Source Code
- * Copyright (C) 2012, Linden Research, Inc.
+ * Copyright (C) 2012-2013, 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
@@ -34,6 +34,18 @@
 namespace LLCore
 {
 
+/// Options struct for global policy options.
+///
+/// Combines both raw blob data access with semantics-enforcing
+/// set/get interfaces.  For internal operations by the worker
+/// thread, just grab the setting directly from instance and test/use
+/// as needed.  When attached to external APIs (the public API
+/// options interfaces) the set/get methods are available to
+/// enforce correct ranges, data types, contexts, etc. and suitable
+/// status values are returned.
+///
+/// Threading:  Single-threaded.  In practice, init thread before
+/// worker starts, worker thread after.
 class HttpPolicyGlobal
 {
 public:
@@ -46,13 +58,12 @@ class HttpPolicyGlobal
 	HttpPolicyGlobal(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, const std::string ** value);
+	HttpStatus set(HttpRequest::EPolicyOption opt, long value);
+	HttpStatus set(HttpRequest::EPolicyOption opt, const std::string & value);
+	HttpStatus get(HttpRequest::EPolicyOption opt, long * value) const;
+	HttpStatus get(HttpRequest::EPolicyOption opt, std::string * value) const;
 	
 public:
-	unsigned long		mSetMask;
 	long				mConnectionLimit;
 	std::string			mCAPath;
 	std::string			mCAFile;
diff --git a/indra/llcorehttp/_httpservice.cpp b/indra/llcorehttp/_httpservice.cpp
index 0821401289f..e21d196a3e2 100755
--- a/indra/llcorehttp/_httpservice.cpp
+++ b/indra/llcorehttp/_httpservice.cpp
@@ -4,7 +4,7 @@
  *
  * $LicenseInfo:firstyear=2012&license=viewerlgpl$
  * Second Life Viewer Source Code
- * Copyright (C) 2012, Linden Research, Inc.
+ * Copyright (C) 2012-2013, 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
@@ -43,6 +43,17 @@
 namespace LLCore
 {
 
+const HttpService::OptionDescriptor HttpService::sOptionDesc[] =
+{ //    isLong     isDynamic  isGlobal    isClass
+	{	true,		true,		true,		true	},		// PO_CONNECTION_LIMIT
+	{	true,		true,		false,		true	},		// PO_PER_HOST_CONNECTION_LIMIT
+	{	false,		false,		true,		false	},		// PO_CA_PATH
+	{	false,		false,		true,		false	},		// PO_CA_FILE
+	{	false,		true,		true,		false	},		// PO_HTTP_PROXY
+	{	true,		true,		true,		false	},		// PO_LLPROXY
+	{	true,		true,		true,		false	},		// PO_TRACE
+	{	true,		true,		false,		true	}		// PO_ENABLE_PIPELINING
+};
 HttpService * HttpService::sInstance(NULL);
 volatile HttpService::EState HttpService::sState(NOT_INITIALIZED);
 
@@ -51,12 +62,9 @@ HttpService::HttpService()
 	  mExitRequested(0U),
 	  mThread(NULL),
 	  mPolicy(NULL),
-	  mTransport(NULL)
-{
-	// Create the default policy class
-	HttpPolicyClass pol_class;
-	mPolicyClasses.push_back(pol_class);
-}
+	  mTransport(NULL),
+	  mLastPolicy(0)
+{}
 
 
 HttpService::~HttpService()
@@ -146,13 +154,8 @@ void HttpService::term()
 
 HttpRequest::policy_t HttpService::createPolicyClass()
 {
-	const HttpRequest::policy_t policy_class(mPolicyClasses.size());
-	if (policy_class >= HTTP_POLICY_CLASS_LIMIT)
-	{
-		return 0;
-	}
-	mPolicyClasses.push_back(HttpPolicyClass());
-	return policy_class;
+	mLastPolicy = mPolicy->createPolicyClass();
+	return mLastPolicy;
 }
 
 
@@ -185,8 +188,8 @@ void HttpService::startThread()
 	}
 
 	// Push current policy definitions, enable policy & transport components
-	mPolicy->start(mPolicyGlobal, mPolicyClasses);
-	mTransport->start(mPolicyClasses.size());
+	mPolicy->start();
+	mTransport->start(mLastPolicy + 1);
 
 	mThread = new LLCoreInt::HttpThread(boost::bind(&HttpService::threadRun, this, _1));
 	sState = RUNNING;
@@ -319,7 +322,7 @@ HttpService::ELoopSpeed HttpService::processRequestQueue(ELoopSpeed loop)
 		{
 			// Setup for subsequent tracing
 			long tracing(HTTP_TRACE_OFF);
-			mPolicy->getGlobalOptions().get(HttpRequest::GP_TRACE, &tracing);
+			mPolicy->getGlobalOptions().get(HttpRequest::PO_TRACE, &tracing);
 			op->mTracing = (std::max)(op->mTracing, int(tracing));
 
 			if (op->mTracing > HTTP_TRACE_OFF)
@@ -342,4 +345,137 @@ HttpService::ELoopSpeed HttpService::processRequestQueue(ELoopSpeed loop)
 }
 
 
+HttpStatus HttpService::getPolicyOption(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass,
+										long * ret_value)
+{
+	if (opt < HttpRequest::PO_CONNECTION_LIMIT											// option must be in range
+		|| opt >= HttpRequest::PO_LAST													// ditto
+		|| (! sOptionDesc[opt].mIsLong)													// datatype is long
+		|| (pclass != HttpRequest::GLOBAL_POLICY_ID && pclass > mLastPolicy)			// pclass in valid range
+		|| (pclass == HttpRequest::GLOBAL_POLICY_ID && ! sOptionDesc[opt].mIsGlobal)	// global setting permitted
+		|| (pclass != HttpRequest::GLOBAL_POLICY_ID && ! sOptionDesc[opt].mIsClass))	// class setting permitted
+																						// can always get, no dynamic check
+	{
+		return HttpStatus(HttpStatus::LLCORE, LLCore::HE_INVALID_ARG);
+	}
+
+	HttpStatus status;
+	if (pclass == HttpRequest::GLOBAL_POLICY_ID)
+	{
+		HttpPolicyGlobal & opts(mPolicy->getGlobalOptions());
+		
+		status = opts.get(opt, ret_value);
+	}
+	else
+	{
+		HttpPolicyClass & opts(mPolicy->getClassOptions(pclass));
+
+		status = opts.get(opt, ret_value);
+	}
+
+	return status;
+}
+
+
+HttpStatus HttpService::getPolicyOption(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass,
+										std::string * ret_value)
+{
+	HttpStatus status(HttpStatus::LLCORE, LLCore::HE_INVALID_ARG);
+
+	if (opt < HttpRequest::PO_CONNECTION_LIMIT											// option must be in range
+		|| opt >= HttpRequest::PO_LAST													// ditto
+		|| (sOptionDesc[opt].mIsLong)													// datatype is string
+		|| (pclass != HttpRequest::GLOBAL_POLICY_ID && pclass > mLastPolicy)			// pclass in valid range
+		|| (pclass == HttpRequest::GLOBAL_POLICY_ID && ! sOptionDesc[opt].mIsGlobal)	// global setting permitted
+		|| (pclass != HttpRequest::GLOBAL_POLICY_ID && ! sOptionDesc[opt].mIsClass))	// class setting permitted
+																						// can always get, no dynamic check
+	{
+		return status;
+	}
+
+	// Only global has string values
+	if (pclass == HttpRequest::GLOBAL_POLICY_ID)
+	{
+		HttpPolicyGlobal & opts(mPolicy->getGlobalOptions());
+
+		status = opts.get(opt, ret_value);
+	}
+
+	return status;
+}
+
+
+HttpStatus HttpService::setPolicyOption(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass,
+										long value, long * ret_value)
+{
+	HttpStatus status(HttpStatus::LLCORE, LLCore::HE_INVALID_ARG);
+	
+	if (opt < HttpRequest::PO_CONNECTION_LIMIT											// option must be in range
+		|| opt >= HttpRequest::PO_LAST													// ditto
+		|| (! sOptionDesc[opt].mIsLong)													// datatype is long
+		|| (pclass != HttpRequest::GLOBAL_POLICY_ID && pclass > mLastPolicy)			// pclass in valid range
+		|| (pclass == HttpRequest::GLOBAL_POLICY_ID && ! sOptionDesc[opt].mIsGlobal)	// global setting permitted
+		|| (pclass != HttpRequest::GLOBAL_POLICY_ID && ! sOptionDesc[opt].mIsClass)		// class setting permitted
+		|| (RUNNING == sState && ! sOptionDesc[opt].mIsDynamic))						// dynamic setting permitted
+	{
+		return status;
+	}
+
+	if (pclass == HttpRequest::GLOBAL_POLICY_ID)
+	{
+		HttpPolicyGlobal & opts(mPolicy->getGlobalOptions());
+		
+		status = opts.set(opt, value);
+		if (status && ret_value)
+		{
+			status = opts.get(opt, ret_value);
+		}
+	}
+	else
+	{
+		HttpPolicyClass & opts(mPolicy->getClassOptions(pclass));
+
+		status = opts.set(opt, value);
+		if (status && ret_value)
+		{
+			status = opts.get(opt, ret_value);
+		}
+	}
+
+	return status;
+}
+
+
+HttpStatus HttpService::setPolicyOption(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass,
+										const std::string & value, std::string * ret_value)
+{
+	HttpStatus status(HttpStatus::LLCORE, LLCore::HE_INVALID_ARG);
+	
+	if (opt < HttpRequest::PO_CONNECTION_LIMIT											// option must be in range
+		|| opt >= HttpRequest::PO_LAST													// ditto
+		|| (sOptionDesc[opt].mIsLong)													// datatype is string
+		|| (pclass != HttpRequest::GLOBAL_POLICY_ID && pclass > mLastPolicy)			// pclass in valid range
+		|| (pclass == HttpRequest::GLOBAL_POLICY_ID && ! sOptionDesc[opt].mIsGlobal)	// global setting permitted
+		|| (pclass != HttpRequest::GLOBAL_POLICY_ID && ! sOptionDesc[opt].mIsClass)		// class setting permitted
+		|| (RUNNING == sState && ! sOptionDesc[opt].mIsDynamic))						// dynamic setting permitted
+	{
+		return status;
+	}
+
+	// Only string values are global at this time
+	if (pclass == HttpRequest::GLOBAL_POLICY_ID)
+	{
+		HttpPolicyGlobal & opts(mPolicy->getGlobalOptions());
+		
+		status = opts.set(opt, value);
+		if (status && ret_value)
+		{
+			status = opts.get(opt, ret_value);
+		}
+	}
+
+	return status;
+}
+	
+
 }  // end namespace LLCore
diff --git a/indra/llcorehttp/_httpservice.h b/indra/llcorehttp/_httpservice.h
index ffe0349d4d5..cf23f3ab61f 100755
--- a/indra/llcorehttp/_httpservice.h
+++ b/indra/llcorehttp/_httpservice.h
@@ -4,7 +4,7 @@
  *
  * $LicenseInfo:firstyear=2012&license=viewerlgpl$
  * Second Life Viewer Source Code
- * Copyright (C) 2012, Linden Research, Inc.
+ * Copyright (C) 2012-2013, 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
@@ -53,6 +53,7 @@ namespace LLCore
 class HttpRequestQueue;
 class HttpPolicy;
 class HttpLibcurl;
+class HttpOpSetGet;
 
 
 /// The HttpService class does the work behind the request queue.  It
@@ -106,7 +107,7 @@ class HttpService
 		NORMAL,					///< continuous polling of request, ready, active queues
 		REQUEST_SLEEP			///< can sleep indefinitely waiting for request queue write
 	};
-		
+
 	static void init(HttpRequestQueue *);
 	static void term();
 
@@ -136,7 +137,7 @@ class HttpService
 	/// acquires its weaknesses.
 	static bool isStopped();
 
-	/// Threading:  callable by consumer thread *once*.
+	/// Threading:  callable by init thread *once*.
 	void startThread();
 
 	/// Threading:  callable by worker thread.
@@ -180,28 +181,39 @@ class HttpService
 			return *mRequestQueue;
 		}
 
-	/// Threading:  callable by consumer thread.
-	HttpPolicyGlobal & getGlobalOptions()
-		{
-			return mPolicyGlobal;
-		}
-
 	/// Threading:  callable by consumer thread.
 	HttpRequest::policy_t createPolicyClass();
 	
-	/// Threading:  callable by consumer thread.
-	HttpPolicyClass & getClassOptions(HttpRequest::policy_t policy_class)
-		{
-			llassert(policy_class >= 0 && policy_class < mPolicyClasses.size());
-			return mPolicyClasses[policy_class];
-		}
-	
 protected:
 	void threadRun(LLCoreInt::HttpThread * thread);
 	
 	ELoopSpeed processRequestQueue(ELoopSpeed loop);
+
+protected:
+	friend class HttpOpSetGet;
+	friend class HttpRequest;
+	
+	// Used internally to describe what operations are allowed
+	// on each policy option.
+	struct OptionDescriptor
+	{
+		bool		mIsLong;
+		bool		mIsDynamic;
+		bool		mIsGlobal;
+		bool		mIsClass;
+	};
+		
+	HttpStatus getPolicyOption(HttpRequest::EPolicyOption opt, HttpRequest::policy_t,
+							   long * ret_value);
+	HttpStatus getPolicyOption(HttpRequest::EPolicyOption opt, HttpRequest::policy_t,
+							   std::string * ret_value);
+	HttpStatus setPolicyOption(HttpRequest::EPolicyOption opt, HttpRequest::policy_t,
+							   long value, long * ret_value);
+	HttpStatus setPolicyOption(HttpRequest::EPolicyOption opt, HttpRequest::policy_t,
+							   const std::string & value, std::string * ret_value);
 	
 protected:
+	static const OptionDescriptor		sOptionDesc[HttpRequest::PO_LAST];
 	static HttpService *				sInstance;
 	
 	// === shared data ===
@@ -210,13 +222,13 @@ class HttpService
 	LLAtomicU32							mExitRequested;
 	LLCoreInt::HttpThread *				mThread;
 	
-	// === consumer-thread-only data ===
-	HttpPolicyGlobal					mPolicyGlobal;
-	std::vector<HttpPolicyClass>		mPolicyClasses;
-	
 	// === working-thread-only data ===
 	HttpPolicy *						mPolicy;		// Simple pointer, has ownership
 	HttpLibcurl *						mTransport;		// Simple pointer, has ownership
+	
+	// === main-thread-only data ===
+	HttpRequest::policy_t				mLastPolicy;
+	
 };  // end class HttpService
 
 }  // end namespace LLCore
diff --git a/indra/llcorehttp/examples/http_texture_load.cpp b/indra/llcorehttp/examples/http_texture_load.cpp
index 88c8102b27c..73c49687d7f 100755
--- a/indra/llcorehttp/examples/http_texture_load.cpp
+++ b/indra/llcorehttp/examples/http_texture_load.cpp
@@ -236,9 +236,10 @@ int main(int argc, char** argv)
 	// Initialization
 	init_curl();
 	LLCore::HttpRequest::createService();
-	LLCore::HttpRequest::setPolicyClassOption(LLCore::HttpRequest::DEFAULT_POLICY_ID,
-											  LLCore::HttpRequest::CP_CONNECTION_LIMIT,
-											  concurrency_limit);
+	LLCore::HttpRequest::setStaticPolicyOption(LLCore::HttpRequest::PO_CONNECTION_LIMIT,
+											   LLCore::HttpRequest::DEFAULT_POLICY_ID,
+											   concurrency_limit,
+											   NULL);
 	LLCore::HttpRequest::startThread();
 	
 	// Get service point
diff --git a/indra/llcorehttp/httpcommon.h b/indra/llcorehttp/httpcommon.h
index c0d4ec5aada..9db884057fc 100755
--- a/indra/llcorehttp/httpcommon.h
+++ b/indra/llcorehttp/httpcommon.h
@@ -4,7 +4,7 @@
  *
  * $LicenseInfo:firstyear=2012&license=viewerlgpl$
  * Second Life Viewer Source Code
- * Copyright (C) 2012, Linden Research, Inc.
+ * Copyright (C) 2012-2013, 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
@@ -29,9 +29,9 @@
 
 /// @package LLCore::HTTP
 ///
-/// This library implements a high-level, Indra-code-free client interface to
-/// HTTP services based on actual patterns found in the viewer and simulator.
-/// Interfaces are similar to those supplied by the legacy classes
+/// This library implements a high-level, Indra-code-free (somewhat) client
+/// interface to HTTP services based on actual patterns found in the viewer
+/// and simulator.  Interfaces are similar to those supplied by the legacy classes
 /// LLCurlRequest and LLHTTPClient.  To that is added a policy scheme that
 /// allows an application to specify connection behaviors:  limits on
 /// connections, HTTP keepalive, HTTP pipelining, retry-on-error limits, etc.
@@ -52,7 +52,7 @@
 /// - "llcorehttp/httprequest.h"
 /// - "llcorehttp/httpresponse.h"
 ///
-/// The library is still under early development and particular users
+/// The library is still under development and particular users
 /// may need access to internal implementation details that are found
 /// in the _*.h header files.  But this is a crutch to be avoided if at
 /// all possible and probably indicates some interface work is neeeded.
@@ -66,6 +66,8 @@
 ///   .  CRYPTO_set_id_callback(...)
 /// - HttpRequest::createService() called to instantiate singletons
 ///   and support objects.
+/// - HttpRequest::startThread() to kick off the worker thread and
+///   begin servicing requests.
 ///
 /// An HTTP consumer in an application, and an application may have many
 /// consumers, does a few things:
@@ -91,10 +93,12 @@
 ///   objects.
 /// - Do completion processing in your onCompletion() method.
 ///
-/// Code fragments:
-/// Rather than a poorly-maintained example in comments, look in the
-/// example subdirectory which is a minimal yet functional tool to do
-/// GET request performance testing.  With four calls:
+/// Code fragments.
+///
+/// Initialization.  Rather than a poorly-maintained example in
+/// comments, look in the example subdirectory which is a minimal
+/// yet functional tool to do GET request performance testing.
+/// With four calls:
 ///
 ///   	init_curl();
 ///     LLCore::HttpRequest::createService();
@@ -103,7 +107,85 @@
 ///
 /// the program is basically ready to issue requests.
 ///
-
+/// HttpHandler.  Having started life as a non-indra library,
+/// this code broke away from the classic Responder model and
+/// introduced a handler class to represent an interface for
+/// request responses.  This is a non-reference-counted entity
+/// which can be used as a base class or a mixin.  An instance
+/// of a handler can be used for each request or can be shared
+/// among any number of requests.  Your choice but expect to
+/// code something like the following:
+///
+///     class AppHandler : public LLCore::HttpHandler
+///     {
+///     public:
+///         virtual void onCompleted(HttpHandle handle,
+///                                  HttpResponse * response)
+///         {
+///             ...
+///         }
+///         ...
+///     };
+///     ...
+///     handler = new handler(...);
+///
+///
+/// Issuing requests.  Using 'hr' above,
+///
+///     hr->requestGet(HttpRequest::DEFAULT_POLICY_ID,
+///                    0,				// Priority, not used yet
+///                    url,
+///                    NULL,			// options
+///                    NULL,            // additional headers
+///                    handler);
+///
+/// If that returns a value other than LLCORE_HTTP_HANDLE_INVALID,
+/// the request was successfully issued and there will eventally
+/// be a status delivered to the handler.  If invalid is returnedd,
+/// the actual status can be retrieved by calling hr->getStatus().
+///
+/// Completing requests and delivering notifications.  Operations
+/// are all performed by the worker thread and will be driven to
+/// completion regardless of caller actions.  Notification of
+/// completion (success or failure) is done by calls to
+/// HttpRequest::update() which will invoke handlers for completed
+/// requests:
+///
+///     hr->update(0);
+///       // Callbacks into handler->onCompleted()
+///
+///
+/// Threads.
+///
+/// Threads are supported and used by this library.  The various
+/// classes, methods and members are documented with thread
+/// constraints which programmers must follow and which are
+/// defined as follows:
+///
+/// consumer	Any thread that has instanced HttpRequest and is
+///             issuing requests.  A particular instance can only
+///             be used by one consumer thread but a consumer may
+///             have many instances available to it.
+/// init		Special consumer thread, usually the main thread,
+///             involved in setting up the library at startup.
+/// worker      Thread used internally by the library to perform
+///             HTTP operations.  Consumers will not have to deal
+///             with this thread directly but some APIs are reserved
+///             to it.
+/// any         Consumer or worker thread.
+///
+/// For the most part, API users will not have to do much in the
+/// way of ensuring thread safely.  However, there is a tremendous
+/// amount of sharing between threads of read-only data.  So when
+/// documentation declares that an option or header instance
+/// becomes shared between consumer and worker, the consumer must
+/// not modify the shared object.
+///
+/// Internally, there is almost no thread synchronization.  During
+/// normal operations (non-init, non-term), only the request queue
+/// and the multiple reply queues are shared between threads and
+/// only here are mutexes used.
+///
 
 #include "linden_common.h"		// Modifies curl/curl.h interfaces
 
@@ -239,9 +321,10 @@ struct HttpStatus
 			return *this;
 		}
 	
-	static const type_enum_t EXT_CURL_EASY = 0;
-	static const type_enum_t EXT_CURL_MULTI = 1;
-	static const type_enum_t LLCORE = 2;
+	static const type_enum_t EXT_CURL_EASY = 0;			///< mStatus is an error from a curl_easy_*() call
+	static const type_enum_t EXT_CURL_MULTI = 1;		///< mStatus is an error from a curl_multi_*() call
+	static const type_enum_t LLCORE = 2;				///< mStatus is an HE_* error code
+														///< 100-999 directly represent HTTP status codes
 	
 	type_enum_t			mType;
 	short				mStatus;
diff --git a/indra/llcorehttp/httprequest.cpp b/indra/llcorehttp/httprequest.cpp
index 9b739a8825a..7b1888e3eba 100755
--- a/indra/llcorehttp/httprequest.cpp
+++ b/indra/llcorehttp/httprequest.cpp
@@ -4,7 +4,7 @@
  *
  * $LicenseInfo:firstyear=2012&license=viewerlgpl$
  * Second Life Viewer Source Code
- * Copyright (C) 2012, Linden Research, Inc.
+ * Copyright (C) 2012-2013, 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
@@ -54,12 +54,8 @@ namespace LLCore
 // ====================================
 
 
-HttpRequest::policy_t HttpRequest::sNextPolicyID(1);
-
-
 HttpRequest::HttpRequest()
-	: //HttpHandler(),
-	  mReplyQueue(NULL),
+	: mReplyQueue(NULL),
 	  mRequestQueue(NULL)
 {
 	mRequestQueue = HttpRequestQueue::instanceOf();
@@ -90,45 +86,91 @@ HttpRequest::~HttpRequest()
 // ====================================
 
 
-HttpStatus HttpRequest::setPolicyGlobalOption(EGlobalPolicy opt, long value)
+HttpRequest::policy_t HttpRequest::createPolicyClass()
 {
 	if (HttpService::RUNNING == HttpService::instanceOf()->getState())
 	{
-		return HttpStatus(HttpStatus::LLCORE, HE_OPT_NOT_DYNAMIC);
+		return 0;
 	}
-	return HttpService::instanceOf()->getGlobalOptions().set(opt, value);
+	return HttpService::instanceOf()->createPolicyClass();
 }
 
 
-HttpStatus HttpRequest::setPolicyGlobalOption(EGlobalPolicy opt, const std::string & value)
+HttpStatus HttpRequest::setStaticPolicyOption(EPolicyOption opt, policy_t pclass,
+											  long value, long * ret_value)
 {
 	if (HttpService::RUNNING == HttpService::instanceOf()->getState())
 	{
 		return HttpStatus(HttpStatus::LLCORE, HE_OPT_NOT_DYNAMIC);
 	}
-	return HttpService::instanceOf()->getGlobalOptions().set(opt, value);
+	return HttpService::instanceOf()->setPolicyOption(opt, pclass, value, ret_value);
 }
 
 
-HttpRequest::policy_t HttpRequest::createPolicyClass()
+HttpStatus HttpRequest::setStaticPolicyOption(EPolicyOption opt, policy_t pclass,
+											  const std::string & value, std::string * ret_value)
 {
 	if (HttpService::RUNNING == HttpService::instanceOf()->getState())
 	{
-		return 0;
+		return HttpStatus(HttpStatus::LLCORE, HE_OPT_NOT_DYNAMIC);
 	}
-	return HttpService::instanceOf()->createPolicyClass();
+	return HttpService::instanceOf()->setPolicyOption(opt, pclass, value, ret_value);
 }
 
 
-HttpStatus HttpRequest::setPolicyClassOption(policy_t policy_id,
-											 EClassPolicy opt,
-											 long value)
+HttpHandle HttpRequest::setPolicyOption(EPolicyOption opt, policy_t pclass,
+										long value, HttpHandler * handler)
 {
-	if (HttpService::RUNNING == HttpService::instanceOf()->getState())
+	HttpStatus status;
+	HttpHandle handle(LLCORE_HTTP_HANDLE_INVALID);
+
+	HttpOpSetGet * op = new HttpOpSetGet();
+	if (! (status = op->setupSet(opt, pclass, value)))
 	{
-		return HttpStatus(HttpStatus::LLCORE, HE_OPT_NOT_DYNAMIC);
+		op->release();
+		mLastReqStatus = status;
+		return handle;
+	}
+	op->setReplyPath(mReplyQueue, handler);
+	if (! (status = mRequestQueue->addOp(op)))			// transfers refcount
+	{
+		op->release();
+		mLastReqStatus = status;
+		return handle;
+	}
+	
+	mLastReqStatus = status;
+	handle = static_cast<HttpHandle>(op);
+	
+	return handle;
+}
+
+
+HttpHandle HttpRequest::setPolicyOption(EPolicyOption opt, policy_t pclass,
+										const std::string & value, HttpHandler * handler)
+{
+	HttpStatus status;
+	HttpHandle handle(LLCORE_HTTP_HANDLE_INVALID);
+
+	HttpOpSetGet * op = new HttpOpSetGet();
+	if (! (status = op->setupSet(opt, pclass, value)))
+	{
+		op->release();
+		mLastReqStatus = status;
+		return handle;
+	}
+	op->setReplyPath(mReplyQueue, handler);
+	if (! (status = mRequestQueue->addOp(op)))			// transfers refcount
+	{
+		op->release();
+		mLastReqStatus = status;
+		return handle;
 	}
-	return HttpService::instanceOf()->getClassOptions(policy_id).set(opt, value);
+	
+	mLastReqStatus = status;
+	handle = static_cast<HttpHandle>(op);
+	
+	return handle;
 }
 
 
@@ -474,31 +516,6 @@ HttpHandle HttpRequest::requestSpin(int mode)
 	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);
-	if (! (status = mRequestQueue->addOp(op)))			// transfers refcount
-	{
-		op->release();
-		mLastReqStatus = status;
-		return handle;
-	}
-
-	mLastReqStatus = status;
-	handle = static_cast<HttpHandle>(op);
-
-	return handle;
-}
-
 
 }   // end namespace LLCore
 
diff --git a/indra/llcorehttp/httprequest.h b/indra/llcorehttp/httprequest.h
index 5000f47d0d2..5c54d35a210 100755
--- a/indra/llcorehttp/httprequest.h
+++ b/indra/llcorehttp/httprequest.h
@@ -56,6 +56,9 @@ class BufferArray;
 /// The class supports the current HTTP request operations:
 ///
 /// - requestGetByteRange:  GET with Range header for a single range of bytes
+/// - requestGet:
+/// - requestPost:
+/// - requestPut:
 ///
 /// Policy Classes
 ///
@@ -100,9 +103,26 @@ class HttpRequest
 
 	/// Represents a default, catch-all policy class that guarantees
 	/// eventual service for any HTTP request.
-	static const int DEFAULT_POLICY_ID = 0;
+	static const policy_t DEFAULT_POLICY_ID = 0;
+	static const policy_t INVALID_POLICY_ID = 0xFFFFFFFFU;
+	static const policy_t GLOBAL_POLICY_ID = 0xFFFFFFFEU;
 
-	enum EGlobalPolicy
+	/// Create a new policy class into which requests can be made.
+	///
+	/// All class creation must occur before threads are started and
+	/// transport begins.  Policy classes are limited to a small value.
+	/// Currently that limit is the default class + 1.
+	///
+	/// @return			If positive, the policy_id used to reference
+	///					the class in other methods.  If 0, requests
+	///					for classes have exceeded internal limits
+	///					or caller has tried to create a class after
+	///					threads have been started.  Caller must fallback
+	///					and recover.
+	///
+	static policy_t createPolicyClass();
+
+	enum EPolicyOption
 	{
 		/// Maximum number of connections the library will use to
 		/// perform operations.  This is somewhat soft as the underlying
@@ -113,24 +133,30 @@ class HttpRequest
 		/// a somewhat soft value.  There may be an additional five
 		/// connections per policy class depending upon runtime
 		/// behavior.
-		GP_CONNECTION_LIMIT,
+		///
+		/// Both global and per-class
+		PO_CONNECTION_LIMIT,
+
+		/// Limits the number of connections used for a single
+		/// literal address/port pair within the class.
+		PO_PER_HOST_CONNECTION_LIMIT,
 
 		/// String containing a system-appropriate directory name
 		/// where SSL certs are stored.
-		GP_CA_PATH,
+		PO_CA_PATH,
 
 		/// String giving a full path to a file containing SSL certs.
-		GP_CA_FILE,
+		PO_CA_FILE,
 
 		/// String of host/port to use as simple HTTP proxy.  This is
 		/// going to change in the future into something more elaborate
 		/// that may support richer schemes.
-		GP_HTTP_PROXY,
+		PO_HTTP_PROXY,
 
 		/// Long value that if non-zero enables the use of the
 		/// traditional LLProxy code for http/socks5 support.  If
-		/// enabled, has priority over GP_HTTP_PROXY.
-		GP_LLPROXY,
+		// enabled, has priority over GP_HTTP_PROXY.
+		PO_LLPROXY,
 
 		/// Long value setting the logging trace level for the
 		/// library.  Possible values are:
@@ -143,57 +169,46 @@ class HttpRequest
 		/// These values are also used in the trace modes for
 		/// individual requests in HttpOptions.  Also be aware that
 		/// tracing tends to impact performance of the viewer.
-		GP_TRACE
-	};
-
-	/// Set a parameter on a global policy option.  Calls
-	/// made after the start of the servicing thread are
-	/// not honored and return an error status.
-	///
-	/// @param opt		Enum of option to be set.
-	/// @param value	Desired value of option.
-	/// @return			Standard status code.
-	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.
-	///
-	/// All class creation must occur before threads are started and
-	/// transport begins.  Policy classes are limited to a small value.
-	/// Currently that limit is the default class + 1.
-	///
-	/// @return			If positive, the policy_id used to reference
-	///					the class in other methods.  If 0, requests
-	///					for classes have exceeded internal limits
-	///					or caller has tried to create a class after
-	///					threads have been started.  Caller must fallback
-	///					and recover.
-	///
-	static policy_t createPolicyClass();
-
-	enum EClassPolicy
-	{
-		/// Limits the number of connections used for the class.
-		CP_CONNECTION_LIMIT,
-
-		/// Limits the number of connections used for a single
-		/// literal address/port pair within the class.
-		CP_PER_HOST_CONNECTION_LIMIT,
+		PO_TRACE,
 
 		/// Suitable requests are allowed to pipeline on their
 		/// connections when they ask for it.
-		CP_ENABLE_PIPELINING
+		PO_ENABLE_PIPELINING,
+
+		PO_LAST  // Always at end
 	};
-	
+
+	/// Set a policy option for a global or class parameter at
+	/// startup time (prior to thread start).
+	///
+	/// @param opt			Enum of option to be set.
+	/// @param pclass		For class-based options, the policy class ID to
+	///					    be changed.  For globals, specify GLOBAL_POLICY_ID.
+	/// @param value		Desired value of option.
+	/// @param ret_value	Pointer to receive effective set value
+	///						if successful.  May be NULL if effective
+	///						value not wanted.
+	/// @return				Standard status code.
+	static HttpStatus setStaticPolicyOption(EPolicyOption opt, policy_t pclass,
+											long value, long * ret_value);
+	static HttpStatus setStaticPolicyOption(EPolicyOption opt, policy_t pclass,
+											const std::string & value, std::string * ret_value);
+
 	/// Set a parameter on a class-based policy option.  Calls
 	/// made after the start of the servicing thread are
 	/// not honored and return an error status.
 	///
-	/// @param policy_id		ID of class as returned by @see createPolicyClass().
-	/// @param opt				Enum of option to be set.
-	/// @param value			Desired value of option.
-	/// @return					Standard status code.
-	static HttpStatus setPolicyClassOption(policy_t policy_id, EClassPolicy opt, long value);
+	/// @param opt			Enum of option to be set.
+	/// @param pclass		For class-based options, the policy class ID to
+	///					    be changed.  Ignored for globals but recommend
+	///					    using INVALID_POLICY_ID in this case.
+	/// @param value		Desired value of option.
+	/// @return				Handle of dynamic request.  Use @see getStatus() if
+	///						the returned handle is invalid.
+	HttpHandle setPolicyOption(EPolicyOption opt, policy_t pclass, long value,
+							   HttpHandler * handler);
+	HttpHandle setPolicyOption(EPolicyOption opt, policy_t pclass, const std::string & value,
+							   HttpHandler * handler);
 
 	/// @}
 
@@ -495,16 +510,6 @@ class HttpRequest
 
 	/// @}
 	
-	/// @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);
 
@@ -526,7 +531,6 @@ class HttpRequest
 	/// Must be established before any threading is allowed to
 	/// start.
 	///
-	static policy_t		sNextPolicyID;
 	
 	/// @}
 	// End Global State
diff --git a/indra/llcorehttp/tests/test_httprequest.hpp b/indra/llcorehttp/tests/test_httprequest.hpp
index 27d65f171ec..f1b9c023933 100755
--- a/indra/llcorehttp/tests/test_httprequest.hpp
+++ b/indra/llcorehttp/tests/test_httprequest.hpp
@@ -1213,7 +1213,7 @@ void HttpRequestTestObjectType::test<12>()
 		HttpRequest::createService();
 
 		// Enable tracing
-		HttpRequest::setPolicyGlobalOption(LLCore::HttpRequest::GP_TRACE, 2);
+		HttpRequest::setStaticPolicyOption(HttpRequest::PO_TRACE, HttpRequest::DEFAULT_POLICY_ID, 2, NULL);
 
 		// Start threading early so that thread memory is invariant
 		// over the test.
@@ -1331,7 +1331,7 @@ void HttpRequestTestObjectType::test<13>()
 		HttpRequest::createService();
 
 		// Enable tracing
-		HttpRequest::setPolicyGlobalOption(LLCore::HttpRequest::GP_TRACE, 2);
+		HttpRequest::setStaticPolicyOption(HttpRequest::PO_TRACE, HttpRequest::DEFAULT_POLICY_ID, 2, NULL);
 
 		// Start threading early so that thread memory is invariant
 		// over the test.
@@ -2972,6 +2972,142 @@ void HttpRequestTestObjectType::test<21>()
 }
 
 
+template <> template <>
+void HttpRequestTestObjectType::test<22>()
+{
+	ScopedCurlInit ready;
+
+	set_test_name("HttpRequest GET 503s with 'Retry-After'");
+
+	// This tests mainly that the code doesn't fall over if
+	// various well- and mis-formed Retry-After headers are
+	// sent along with the response.  Direct inspection of
+	// the parsing result isn't supported.
+	
+	// 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() + "/503/");	// path to 503 generators
+		
+	// 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(1);			// Retry once only
+		opts->setUseRetryAfter(true);	// Try to parse the retry-after header
+		
+		// Issue a GET that 503s with valid retry-after
+		mStatus = HttpStatus(503);
+		int url_limit(6);
+		for (int i(0); i < url_limit; ++i)
+		{
+			std::ostringstream url;
+			url << url_base << i << "/";
+			HttpHandle handle = req->requestGetByteRange(HttpRequest::DEFAULT_POLICY_ID,
+														 0U,
+														 url.str(),
+														 0,
+														 0,
+														 opts,
+														 NULL,
+														 &handler);
+
+			std::ostringstream testtag;
+			testtag << "Valid handle returned for 503 request #" << i;
+			ensure(testtag.str(), handle != LLCORE_HTTP_HANDLE_INVALID);
+		}
+		
+
+		// Run the notification pump.
+		int count(0);
+		int limit(300);				// One retry but several seconds needed
+		while (count++ < limit && mHandlerCalls < url_limit)
+		{
+			req->update(0);
+			usleep(100000);
+		}
+		ensure("Request executed in reasonable time", count < limit);
+		ensure("One handler invocation for request", mHandlerCalls == url_limit);
+
+		// Okay, request a shutdown of the servicing thread
+		mStatus = HttpStatus();
+		mHandlerCalls = 0;
+		HttpHandle 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 < 1)
+		{
+			req->update(1000000);
+			usleep(100000);
+		}
+		ensure("Second request executed in reasonable time", count < limit);
+		ensure("Second handler invocation", mHandlerCalls == 1);
+
+		// 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();
+	
+#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 75a3c39ef2f..f6c4d1a820b 100755
--- 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) 2012, Linden Research, Inc.
+Copyright (C) 2012-2013, 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
@@ -47,6 +47,17 @@
 class TestHTTPRequestHandler(BaseHTTPRequestHandler):
     """This subclass of BaseHTTPRequestHandler is to receive and echo
     LLSD-flavored messages sent by the C++ LLHTTPClient.
+
+    [Merge with viewer-cat later]
+    - '/503/'           Generate 503 responses with various kinds
+                        of 'retry-after' headers
+    -- '/503/0/'            "Retry-After: 2"   
+    -- '/503/1/'            "Retry-After: Thu, 31 Dec 2043 23:59:59 GMT"
+    -- '/503/2/'            "Retry-After: Fri, 31 Dec 1999 23:59:59 GMT"
+    -- '/503/3/'            "Retry-After: "
+    -- '/503/4/'            "Retry-After: (*#*(@*(@(")"
+    -- '/503/5/'            "Retry-After: aklsjflajfaklsfaklfasfklasdfklasdgahsdhgasdiogaioshdgo"
+    -- '/503/6/'            "Retry-After: 1 2 3 4 5 6 7 8 9 10"
     """
     def read(self):
         # The following logic is adapted from the library module
@@ -107,7 +118,41 @@ def answer(self, data, withdata=True):
         if "/sleep/" in self.path:
             time.sleep(30)
 
-        if "fail" not in self.path:
+        if "/503/" in self.path:
+            # Tests for various kinds of 'Retry-After' header parsing
+            body = None
+            if "/503/0/" in self.path:
+                self.send_response(503)
+                self.send_header("retry-after", "2")
+            elif "/503/1/" in self.path:
+                self.send_response(503)
+                self.send_header("retry-after", "Thu, 31 Dec 2043 23:59:59 GMT")
+            elif "/503/2/" in self.path:
+                self.send_response(503)
+                self.send_header("retry-after", "Fri, 31 Dec 1999 23:59:59 GMT")
+            elif "/503/3/" in self.path:
+                self.send_response(503)
+                self.send_header("retry-after", "")
+            elif "/503/4/" in self.path:
+                self.send_response(503)
+                self.send_header("retry-after", "(*#*(@*(@(")
+            elif "/503/5/" in self.path:
+                self.send_response(503)
+                self.send_header("retry-after", "aklsjflajfaklsfaklfasfklasdfklasdgahsdhgasdiogaioshdgo")
+            elif "/503/6/" in self.path:
+                self.send_response(503)
+                self.send_header("retry-after", "1 2 3 4 5 6 7 8 9 10")
+            else:
+                # Unknown request
+                self.send_response(400)
+                body = "Unknown /503/ path in server"
+            if "/reflect/" in self.path:
+                self.reflect_headers()
+            self.send_header("Content-type", "text/plain")
+            self.end_headers()
+            if body:
+                self.wfile.write(body)
+        elif "fail" not in self.path:
             data = data.copy()          # we're going to modify
             # Ensure there's a "reply" key in data, even if there wasn't before
             data["reply"] = data.get("reply", llsd.LLSD("success"))
diff --git a/indra/newview/llappcorehttp.cpp b/indra/newview/llappcorehttp.cpp
index 0c242e57db3..104debe0230 100755
--- a/indra/newview/llappcorehttp.cpp
+++ b/indra/newview/llappcorehttp.cpp
@@ -105,8 +105,9 @@ void LLAppCoreHttp::init()
 	}
 
 	// Point to our certs or SSH/https: will fail on connect
-	status = LLCore::HttpRequest::setPolicyGlobalOption(LLCore::HttpRequest::GP_CA_FILE,
-														gDirUtilp->getCAFile());
+	status = LLCore::HttpRequest::setStaticPolicyOption(LLCore::HttpRequest::PO_CA_FILE,
+														LLCore::HttpRequest::GLOBAL_POLICY_ID,
+														gDirUtilp->getCAFile(), NULL);
 	if (! status)
 	{
 		LL_ERRS("Init") << "Failed to set CA File for HTTP services.  Reason:  " << status.toString()
@@ -114,7 +115,9 @@ void LLAppCoreHttp::init()
 	}
 
 	// Establish HTTP Proxy, if desired.
-	status = LLCore::HttpRequest::setPolicyGlobalOption(LLCore::HttpRequest::GP_LLPROXY, 1);
+	status = LLCore::HttpRequest::setStaticPolicyOption(LLCore::HttpRequest::PO_LLPROXY,
+														LLCore::HttpRequest::GLOBAL_POLICY_ID,
+														1, NULL);
 	if (! status)
 	{
 		LL_WARNS("Init") << "Failed to set HTTP proxy for HTTP services.  Reason:  " << status.toString()
@@ -131,7 +134,9 @@ void LLAppCoreHttp::init()
 	{
 		long trace_level(0L);
 		trace_level = long(gSavedSettings.getU32(http_trace));
-		status = LLCore::HttpRequest::setPolicyGlobalOption(LLCore::HttpRequest::GP_TRACE, trace_level);
+		status = LLCore::HttpRequest::setStaticPolicyOption(LLCore::HttpRequest::PO_TRACE,
+															LLCore::HttpRequest::GLOBAL_POLICY_ID,
+															trace_level, NULL);
 	}
 	
 	// Setup default policy and constrain if directed to
@@ -164,6 +169,9 @@ void LLAppCoreHttp::init()
 		}
 	}
 
+	// Need a request object to handle dynamic options before setting them
+	mRequest = new LLCore::HttpRequest;
+
 	// Apply initial settings
 	refreshSettings(true);
 	
@@ -175,8 +183,6 @@ void LLAppCoreHttp::init()
 						<< LL_ENDL;
 	}
 
-	mRequest = new LLCore::HttpRequest;
-
 	// Register signals for settings and state changes
 	for (int i(0); i < LL_ARRAY_SIZE(init_data); ++i)
 	{
@@ -287,12 +293,13 @@ void LLAppCoreHttp::refreshSettings(bool initial)
 		// Set it and report
 		// *TODO:  These are intended to be per-host limits when we can
 		// support that in llcorehttp/libcurl.
-		LLCore::HttpStatus status;
-		status = LLCore::HttpRequest::setPolicyClassOption(mPolicies[policy],
-														   LLCore::HttpRequest::CP_CONNECTION_LIMIT,
-														   setting);
-		if (! status)
+		LLCore::HttpHandle handle;
+		handle = mRequest->setPolicyOption(LLCore::HttpRequest::PO_CONNECTION_LIMIT,
+										   mPolicies[policy],
+										   setting, NULL);
+		if (LLCORE_HTTP_HANDLE_INVALID == handle)
 		{
+			LLCore::HttpStatus status(mRequest->getStatus());
 			LL_WARNS("Init") << "Unable to set " << init_data[i].mUsage
 							 << " concurrency.  Reason:  " << status.toString()
 							 << LL_ENDL;
-- 
GitLab