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.
master
Monty Brandenberg 2013-07-12 15:00:24 -04:00
parent fb734d621e
commit eff651cffc
21 changed files with 907 additions and 398 deletions

View File

@ -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

View File

@ -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 @@ public:
///
/// @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 @@ public:
/// 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 @@ public:
///
/// @return True if handle was found and operation canceled.
///
/// Threading: called by worker thread.
bool cancel(HttpHandle handle);
protected:
@ -121,7 +132,7 @@ protected:
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

View File

@ -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 @@ public:
/// 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 @@ public:
/// call to HttpRequest::update(). This method does the necessary
/// dispatching.
///
/// Threading: called by application thread.
/// Threading: called by consumer thread.
///
virtual void visitNotifier(HttpRequest *);

View File

@ -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)

View File

@ -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;
}
HttpStatus HttpOpSetGet::setupSet(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass, const std::string & 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;
mReqStrValue = value;
return status;
}
void HttpOpSetGet::stageFromRequest(HttpService * service)
{
HttpPolicyGlobal & pol_opt(service->getPolicy().getGlobalOptions());
HttpRequest::EGlobalPolicy setting(static_cast<HttpRequest::EGlobalPolicy>(mSetting));
if (mDoSet)
if (mReqDoSet)
{
mStatus = pol_opt.set(setting, mStrValue);
}
if (mStatus)
{
const std::string * value(NULL);
if ((mStatus = pol_opt.get(setting, &value)))
if (HttpService::sOptionDesc[mReqOption].mIsLong)
{
mStrValue = *value;
mStatus = service->setPolicyOption(mReqOption, mReqClass,
mReqLongValue, &mReplyLongValue);
}
else
{
mStatus = service->setPolicyOption(mReqOption, mReqClass,
mReqStrValue, &mReplyStrValue);
}
}
else
{
if (HttpService::sOptionDesc[mReqOption].mIsLong)
{
mStatus = service->getPolicyOption(mReqOption, mReqClass, &mReplyLongValue);
}
else
{
mStatus = service->getPolicyOption(mReqOption, mReqClass, &mReplyStrValue);
}
}

View File

@ -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 @@ private:
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

View File

@ -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;
}

View File

@ -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 @@ private:
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 @@ public:
/// 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 @@ public:
/// 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 @@ public:
/// 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

View File

@ -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();
}

View File

@ -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 @@ public:
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;

View File

@ -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();
}

View File

@ -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 @@ private:
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;

View File

@ -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

View File

@ -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 @@ public:
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 @@ public:
/// 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 @@ public:
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 @@ protected:
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

View File

@ -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

View File

@ -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;

View File

@ -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,26 +86,6 @@ HttpRequest::~HttpRequest()
// ====================================
HttpStatus HttpRequest::setPolicyGlobalOption(EGlobalPolicy opt, long value)
{
if (HttpService::RUNNING == HttpService::instanceOf()->getState())
{
return HttpStatus(HttpStatus::LLCORE, HE_OPT_NOT_DYNAMIC);
}
return HttpService::instanceOf()->getGlobalOptions().set(opt, value);
}
HttpStatus HttpRequest::setPolicyGlobalOption(EGlobalPolicy opt, const std::string & value)
{
if (HttpService::RUNNING == HttpService::instanceOf()->getState())
{
return HttpStatus(HttpStatus::LLCORE, HE_OPT_NOT_DYNAMIC);
}
return HttpService::instanceOf()->getGlobalOptions().set(opt, value);
}
HttpRequest::policy_t HttpRequest::createPolicyClass()
{
if (HttpService::RUNNING == HttpService::instanceOf()->getState())
@ -120,15 +96,81 @@ HttpRequest::policy_t HttpRequest::createPolicyClass()
}
HttpStatus HttpRequest::setPolicyClassOption(policy_t policy_id,
EClassPolicy opt,
long 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()->getClassOptions(policy_id).set(opt, value);
return HttpService::instanceOf()->setPolicyOption(opt, pclass, value, ret_value);
}
HttpStatus HttpRequest::setStaticPolicyOption(EPolicyOption opt, policy_t pclass,
const std::string & value, std::string * ret_value)
{
if (HttpService::RUNNING == HttpService::instanceOf()->getState())
{
return HttpStatus(HttpStatus::LLCORE, HE_OPT_NOT_DYNAMIC);
}
return HttpService::instanceOf()->setPolicyOption(opt, pclass, value, ret_value);
}
HttpHandle HttpRequest::setPolicyOption(EPolicyOption opt, policy_t pclass,
long 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;
}
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;
}
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

View File

@ -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,61 +103,9 @@ public:
/// Represents a default, catch-all policy class that guarantees
/// eventual service for any HTTP request.
static const int DEFAULT_POLICY_ID = 0;
enum EGlobalPolicy
{
/// Maximum number of connections the library will use to
/// perform operations. This is somewhat soft as the underlying
/// transport will cache some connections (up to 5).
/// A long value setting the maximum number of connections
/// allowed over all policy classes. Note that this will be
/// a somewhat soft value. There may be an additional five
/// connections per policy class depending upon runtime
/// behavior.
GP_CONNECTION_LIMIT,
/// String containing a system-appropriate directory name
/// where SSL certs are stored.
GP_CA_PATH,
/// String giving a full path to a file containing SSL certs.
GP_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,
/// 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,
/// Long value setting the logging trace level for the
/// library. Possible values are:
/// 0 - No tracing (default)
/// 1 - Basic tracing of request start, stop and major events.
/// 2 - Connection, header and payload size information from
/// HTTP transactions.
/// 3 - Partial logging of payload itself.
///
/// 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);
static const policy_t DEFAULT_POLICY_ID = 0;
static const policy_t INVALID_POLICY_ID = 0xFFFFFFFFU;
static const policy_t GLOBAL_POLICY_ID = 0xFFFFFFFEU;
/// Create a new policy class into which requests can be made.
///
@ -171,29 +122,93 @@ public:
///
static policy_t createPolicyClass();
enum EClassPolicy
enum EPolicyOption
{
/// Limits the number of connections used for the class.
CP_CONNECTION_LIMIT,
/// Maximum number of connections the library will use to
/// perform operations. This is somewhat soft as the underlying
/// transport will cache some connections (up to 5).
/// A long value setting the maximum number of connections
/// allowed over all policy classes. Note that this will be
/// a somewhat soft value. There may be an additional five
/// connections per policy class depending upon runtime
/// behavior.
///
/// Both global and per-class
PO_CONNECTION_LIMIT,
/// Limits the number of connections used for a single
/// literal address/port pair within the class.
CP_PER_HOST_CONNECTION_LIMIT,
PO_PER_HOST_CONNECTION_LIMIT,
/// String containing a system-appropriate directory name
/// where SSL certs are stored.
PO_CA_PATH,
/// String giving a full path to a file containing SSL certs.
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.
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.
PO_LLPROXY,
/// Long value setting the logging trace level for the
/// library. Possible values are:
/// 0 - No tracing (default)
/// 1 - Basic tracing of request start, stop and major events.
/// 2 - Connection, header and payload size information from
/// HTTP transactions.
/// 3 - Partial logging of payload itself.
///
/// 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.
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 @@ public:
/// @}
/// @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 @@ private:
/// Must be established before any threading is allowed to
/// start.
///
static policy_t sNextPolicyID;
/// @}
// End Global State

View File

@ -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

View File

@ -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 @@ 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.
[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 @@ class TestHTTPRequestHandler(BaseHTTPRequestHandler):
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"))

View File

@ -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;