Result of svn merge -r119432:120464 svn+ssh://svn/svn/linden/branches/http_database/merge-03 into trunk. QAR-1462

master
Aaron Brashears 2009-05-18 23:38:35 +00:00
parent 0257214763
commit 6df2755ba6
29 changed files with 632 additions and 187 deletions

View File

@ -10,7 +10,7 @@ values = (
'&<>',
u'\u81acj',
llsd.uri('http://foo<'),
lluuid.LLUUID(),
lluuid.UUID(),
llsd.LLSD(['thing']),
1,
myint(31337),

View File

@ -72,8 +72,11 @@ BOOL_FALSE = ('0', '0.0', 'false', '')
def format_datestr(v):
""" Formats a datetime object into the string format shared by xml and notation serializations."""
return v.isoformat() + 'Z'
""" Formats a datetime or date object into the string format shared by xml and notation serializations."""
if hasattr(v, 'microsecond'):
return v.isoformat() + 'Z'
else:
return v.strftime('%Y-%m-%dT%H:%M:%SZ')
def parse_datestr(datestr):
"""Parses a datetime object from the string format shared by xml and notation serializations."""
@ -183,6 +186,7 @@ class LLSDXMLFormatter(object):
unicode : self.STRING,
uri : self.URI,
datetime.datetime : self.DATE,
datetime.date : self.DATE,
list : self.ARRAY,
tuple : self.ARRAY,
types.GeneratorType : self.ARRAY,
@ -347,6 +351,7 @@ class LLSDNotationFormatter(object):
unicode : self.STRING,
uri : self.URI,
datetime.datetime : self.DATE,
datetime.date : self.DATE,
list : self.ARRAY,
tuple : self.ARRAY,
types.GeneratorType : self.ARRAY,
@ -924,12 +929,13 @@ def _format_binary_recurse(something):
(type(something), something))
def parse_binary(something):
header = '<?llsd/binary?>\n'
if not something.startswith(header):
raise LLSDParseError('LLSD binary encoding header not found')
return LLSDBinaryParser().parse(something[len(header):])
def parse_binary(binary):
if binary.startswith('<?llsd/binary?>'):
just_binary = binary.split('\n', 1)[1]
else:
just_binary = binary
return LLSDBinaryParser().parse(just_binary)
def parse_xml(something):
try:
return to_python(fromstring(something)[0])

View File

@ -29,25 +29,93 @@ $/LicenseInfo$
"""
import sys
from indra.base import llsd
try:
import syslog
except ImportError:
# Windows
import sys
class syslog(object):
# wrap to a lame syslog for windows
_logfp = sys.stderr
def syslog(msg):
_logfp.write(msg)
if not msg.endswith('\n'):
_logfp.write('\n')
syslog = staticmethod(syslog)
_sequence_id = 0
from indra.base.llsd import format_notation
def record_metrics(table, stats, dest=None):
def record_metrics(table, stats):
"Write a standard metrics log"
_log("LLMETRICS", table, stats, dest)
_log("LLMETRICS", table, stats)
def record_event(table, data, dest=None):
def record_event(table, data):
"Write a standard logmessage log"
_log("LLLOGMESSAGE", table, data, dest)
_log("LLLOGMESSAGE", table, data)
def _log(header, table, data, dest):
def set_destination(dest):
"""Set the destination of metrics logs for this process.
If you do not call this function prior to calling a logging
method, that function will open sys.stdout as a destination.
Attempts to set dest to None will throw a RuntimeError.
@param dest a file-like object which will be the destination for logs."""
if dest is None:
# do this check here in case sys.stdout changes at some
# point. as a default parameter, it will never be
# re-evaluated.
dest = sys.stdout
raise RuntimeError("Attempt to unset metrics destination.")
global _destination
_destination = dest
def destination():
"""Get the destination of the metrics logs for this process.
Returns None if no destination is set"""
global _destination
return _destination
class SysLogger(object):
"A file-like object which writes to syslog."
def __init__(self, ident='indra', logopt = None, facility = None):
try:
if logopt is None:
logopt = syslog.LOG_CONS | syslog.LOG_PID
if facility is None:
facility = syslog.LOG_LOCAL0
syslog.openlog(ident, logopt, facility)
import atexit
atexit.register(syslog.closelog)
except AttributeError:
# No syslog module on Windows
pass
def write(str):
syslog.syslog(str)
write = staticmethod(write)
def flush():
pass
flush = staticmethod(flush)
#
# internal API
#
_sequence_id = 0
_destination = None
def _next_id():
global _sequence_id
print >>dest, header, "(" + str(_sequence_id) + ")",
print >>dest, table, llsd.format_notation(data)
next = _sequence_id
_sequence_id += 1
return next
def _dest():
global _destination
if _destination is None:
# this default behavior is documented in the metrics functions above.
_destination = sys.stdout
return _destination
def _log(header, table, data):
log_line = "%s (%d) %s %s" \
% (header, _next_id(), table, format_notation(data))
dest = _dest()
dest.write(log_line)
dest.flush()

View File

@ -39,6 +39,12 @@ except:
pass
_g_builder = None
def _builder():
global _g_builder
if _g_builder is None:
_g_builder = ServiceBuilder()
return _g_builder
def build(name, context={}, **kwargs):
""" Convenience method for using a global, singleton, service builder. Pass arguments either via a dict or via python keyword arguments, or both!
@ -56,6 +62,11 @@ def build(name, context={}, **kwargs):
_g_builder = ServiceBuilder()
return _g_builder.buildServiceURL(name, context, **kwargs)
def build_path(name, context={}, **kwargs):
context = context.copy() # shouldn't modify the caller's dictionary
context.update(kwargs)
return _builder().buildPath(name, context)
class ServiceBuilder(object):
def __init__(self, services_definition = services_config):
"""\
@ -73,12 +84,21 @@ class ServiceBuilder(object):
continue
if isinstance(service_builder, dict):
# We will be constructing several builders
for name, builder in service_builder.items():
for name, builder in service_builder.iteritems():
full_builder_name = service['name'] + '-' + name
self.builders[full_builder_name] = builder
else:
self.builders[service['name']] = service_builder
def buildPath(self, name, context):
"""\
@brief given the environment on construction, return a service path.
@param name The name of the service.
@param context A dict of name value lookups for the service.
@returns Returns the
"""
return russ.format(self.builders[name], context)
def buildServiceURL(self, name, context={}, **kwargs):
"""\
@brief given the environment on construction, return a service URL.

View File

@ -1,3 +1,32 @@
"""\
@file siesta.py
@brief A tiny llsd based RESTful web services framework
$LicenseInfo:firstyear=2008&license=mit$
Copyright (c) 2008, Linden Research, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
$/LicenseInfo$
"""
from indra.base import config
from indra.base import llsd
from webob import exc
import webob
@ -37,11 +66,11 @@ def mime_type(content_type):
return content_type.split(';', 1)[0].strip().lower()
class BodyLLSD(object):
'''Give a webob Request or Response an llsd property.
'''Give a webob Request or Response an llsd based "content" property.
Getting the llsd property parses the body, and caches the result.
Getting the content property parses the body, and caches the result.
Setting the llsd property formats a payload, and the body property
Setting the content property formats a payload, and the body property
is set.'''
def _llsd__get(self):
@ -80,7 +109,7 @@ class BodyLLSD(object):
if hasattr(self, '_llsd'):
del self._llsd
llsd = property(_llsd__get, _llsd__set, _llsd__del)
content = property(_llsd__get, _llsd__set, _llsd__del)
class Response(webob.Response, BodyLLSD):
@ -114,10 +143,10 @@ class Request(webob.Request, BodyLLSD):
Sensible content type and accept headers are used by default.
Setting the llsd property also sets the body. Getting the llsd
Setting the content property also sets the body. Getting the content
property parses the body if necessary.
If you set the body property directly, the llsd property will be
If you set the body property directly, the content property will be
deleted.'''
default_content_type = 'application/llsd+xml'
@ -149,11 +178,11 @@ class Request(webob.Request, BodyLLSD):
body = property(webob.Request._body__get, _body__set,
webob.Request._body__del, webob.Request._body__get.__doc__)
def create_response(self, llsd=None, status='200 OK',
def create_response(self, content=None, status='200 OK',
conditional_response=webob.NoDefault):
resp = self.ResponseClass(status=status, request=self,
conditional_response=conditional_response)
resp.llsd = llsd
resp.content = content
return resp
def curl(self):
@ -196,12 +225,18 @@ llsd_formatters = {
'application/xml': llsd.format_xml,
}
formatter_qualities = (
('application/llsd+xml', 1.0),
('application/llsd+notation', 0.5),
('application/llsd+binary', 0.4),
('application/xml', 0.3),
('application/json', 0.2),
)
def formatter_for_mime_type(mime_type):
'''Return a formatter that encodes to the given MIME type.
The result is a pair of function and MIME type.'''
try:
return llsd_formatters[mime_type], mime_type
except KeyError:
@ -214,21 +249,19 @@ def formatter_for_request(req):
'''Return a formatter that encodes to the preferred type of the client.
The result is a pair of function and actual MIME type.'''
for ctype in req.accept.best_matches('application/llsd+xml'):
try:
return llsd_formatters[ctype], ctype
except KeyError:
pass
else:
ctype = req.accept.best_match(formatter_qualities)
try:
return llsd_formatters[ctype], ctype
except KeyError:
raise exc.HTTPNotAcceptable().exception
def wsgi_adapter(func, environ, start_response):
'''Adapt a Siesta callable to act as a WSGI application.'''
# Process the request as appropriate.
try:
req = Request(environ)
#print req.urlvars
resp = func(req, **req.urlvars)
if not isinstance(resp, webob.Response):
try:
@ -281,7 +314,8 @@ def llsd_class(cls):
allowed = [m for m in http11_methods
if hasattr(instance, 'handle_' + m.lower())]
raise exc.HTTPMethodNotAllowed(
headers={'Allowed': ', '.join(allowed)}).exception
headers={'Allow': ', '.join(allowed)}).exception
#print "kwargs: ", kwargs
return handler(req, **kwargs)
def replacement(environ, start_response):
@ -336,7 +370,7 @@ def curl(reqs):
route_re = re.compile(r'''
\{ # exact character "{"
(\w+) # variable name (restricted to a-z, 0-9, _)
(\w*) # "config" or variable (restricted to a-z, 0-9, _)
(?:([:~])([^}]+))? # optional :type or ~regex part
\} # exact character "}"
''', re.VERBOSE)
@ -344,27 +378,37 @@ route_re = re.compile(r'''
predefined_regexps = {
'uuid': r'[a-f0-9][a-f0-9-]{31,35}',
'int': r'\d+',
'host': r'[a-z0-9][a-z0-9\-\.]*',
}
def compile_route(route):
fp = StringIO()
last_pos = 0
for match in route_re.finditer(route):
#print "matches: ", match.groups()
fp.write(re.escape(route[last_pos:match.start()]))
var_name = match.group(1)
sep = match.group(2)
expr = match.group(3)
if expr:
if sep == ':':
expr = predefined_regexps[expr]
# otherwise, treat what follows '~' as a regexp
if var_name == 'config':
expr = re.escape(str(config.get(var_name)))
else:
expr = '[^/]+'
expr = '(?P<%s>%s)' % (var_name, expr)
if expr:
if sep == ':':
expr = predefined_regexps[expr]
# otherwise, treat what follows '~' as a regexp
else:
expr = '[^/]+'
if var_name != '':
expr = '(?P<%s>%s)' % (var_name, expr)
else:
expr = '(%s)' % (expr,)
fp.write(expr)
last_pos = match.end()
fp.write(re.escape(route[last_pos:]))
return '^%s$' % fp.getvalue()
compiled_route = '^%s$' % fp.getvalue()
#print route, "->", compiled_route
return compiled_route
class Router(object):
'''WSGI routing class. Parses a URL and hands off a request to
@ -372,21 +416,43 @@ class Router(object):
responds with a 404.'''
def __init__(self):
self.routes = []
self.paths = []
self._new_routes = []
self._routes = []
self._paths = []
def add(self, route, app, methods=None):
self.paths.append(route)
self.routes.append((re.compile(compile_route(route)), app,
methods and dict.fromkeys(methods)))
self._new_routes.append((route, app, methods))
def _create_routes(self):
for route, app, methods in self._new_routes:
self._paths.append(route)
self._routes.append(
(re.compile(compile_route(route)),
app,
methods and dict.fromkeys(methods)))
self._new_routes = []
def __call__(self, environ, start_response):
# load up the config from the config file. Only needs to be
# done once per interpreter. This is the entry point of all
# siesta applications, so this is where we trap it.
_conf = config.get_config()
if _conf is None:
import os.path
fname = os.path.join(
environ.get('ll.config_dir', '/local/linden/etc'),
'indra.xml')
config.load(fname)
# proceed with handling the request
self._create_routes()
path_info = environ['PATH_INFO']
request_method = environ['REQUEST_METHOD']
allowed = []
for regex, app, methods in self.routes:
for regex, app, methods in self._routes:
m = regex.match(path_info)
if m:
#print "groupdict:",m.groupdict()
if not methods or request_method in methods:
environ['paste.urlvars'] = m.groupdict()
return app(environ, start_response)
@ -396,7 +462,7 @@ class Router(object):
allowed = dict.fromkeys(allows).keys()
allowed.sort()
resp = exc.HTTPMethodNotAllowed(
headers={'Allowed': ', '.join(allowed)})
headers={'Allow': ', '.join(allowed)})
else:
resp = exc.HTTPNotFound()
return resp(environ, start_response)

View File

@ -47,10 +47,8 @@ except NameError:
from indra.base import llsd
from indra.base import config
DEBUG = False
NQ_FILE_SUFFIX = config.get('named-query-file-suffix', '.nq')
NQ_FILE_SUFFIX_LEN = len(NQ_FILE_SUFFIX)
NQ_FILE_SUFFIX = None
NQ_FILE_SUFFIX_LEN = None
_g_named_manager = None
@ -60,6 +58,11 @@ def _init_g_named_manager(sql_dir = None):
This function is intended entirely for testing purposes,
because it's tricky to control the config from inside a test."""
global NQ_FILE_SUFFIX
NQ_FILE_SUFFIX = config.get('named-query-file-suffix', '.nq')
global NQ_FILE_SUFFIX_LEN
NQ_FILE_SUFFIX_LEN = len(NQ_FILE_SUFFIX)
if sql_dir is None:
sql_dir = config.get('named-query-base-dir')
@ -73,11 +76,11 @@ def _init_g_named_manager(sql_dir = None):
_g_named_manager = NamedQueryManager(
os.path.abspath(os.path.realpath(sql_dir)))
def get(name):
def get(name, schema = None):
"Get the named query object to be used to perform queries"
if _g_named_manager is None:
_init_g_named_manager()
return _g_named_manager.get(name)
return _g_named_manager.get(name).for_schema(schema)
def sql(connection, name, params):
# use module-global NamedQuery object to perform default substitution
@ -330,6 +333,8 @@ class NamedQuery(object):
def for_schema(self, db_name):
"Look trough the alternates and return the correct query"
if db_name is None:
return self
try:
return self._alternative[db_name]
except KeyError, e:
@ -359,10 +364,10 @@ class NamedQuery(object):
if DEBUG:
print "SQL:", self.sql(connection, params)
rows = cursor.execute(full_query, params)
# *NOTE: the expect_rows argument is a very cheesy way to get some
# validation on the result set. If you want to add more expectation
# logic, do something more object-oriented and flexible. Or use an ORM.
# logic, do something more object-oriented and flexible. Or use an ORM.
if(self._return_as_map):
expect_rows = 1
if expect_rows is not None and rows != expect_rows:

View File

@ -32,7 +32,6 @@ set(llcommon_SOURCE_FILES
llformat.cpp
llframetimer.cpp
llheartbeat.cpp
llindraconfigfile.cpp
llliveappconfig.cpp
lllivefile.cpp
lllog.cpp
@ -118,7 +117,6 @@ set(llcommon_HEADER_FILES
llheartbeat.h
llhttpstatuscodes.h
llindexedqueue.h
llindraconfigfile.h
llkeythrottle.h
lllinkedqueue.h
llliveappconfig.h

View File

@ -38,7 +38,9 @@
#include "llerrorcontrol.h"
#include "llerrorthread.h"
#include "llframetimer.h"
#include "lllivefile.h"
#include "llmemory.h"
#include "llstl.h" // for DeletePointer()
#include "lltimer.h"
//
@ -91,7 +93,6 @@ LLAppChildCallback LLApp::sDefaultChildCallback = NULL;
LLApp::LLApp() : mThreadErrorp(NULL)
{
commonCtor();
startErrorThread();
}
void LLApp::commonCtor()
@ -106,9 +107,6 @@ void LLApp::commonCtor()
sSigChildCount = new LLAtomicU32(0);
#endif
// Setup error handling
setupErrorHandling();
// initialize the options structure. We need to make this an array
// because the structured data will not auto-allocate if we
// reference an invalid location with the [] operator.
@ -141,6 +139,11 @@ LLApp::~LLApp()
delete sSigChildCount;
sSigChildCount = NULL;
#endif
// reclaim live file memory
std::for_each(mLiveFiles.begin(), mLiveFiles.end(), DeletePointer());
mLiveFiles.clear();
setStopped();
// HACK: wait for the error thread to clean itself
ms_sleep(20);
@ -214,6 +217,15 @@ bool LLApp::parseCommandOptions(int argc, char** argv)
return true;
}
void LLApp::manageLiveFile(LLLiveFile* livefile)
{
if(!livefile) return;
livefile->checkAndReload();
livefile->addToEventTimer();
mLiveFiles.push_back(livefile);
}
bool LLApp::setOptionData(OptionPriority level, LLSD data)
{
if((level < 0)
@ -275,6 +287,7 @@ void LLApp::setupErrorHandling()
#endif
startErrorThread();
}
void LLApp::startErrorThread()
@ -283,10 +296,13 @@ void LLApp::startErrorThread()
// Start the error handling thread, which is responsible for taking action
// when the app goes into the APP_STATUS_ERROR state
//
llinfos << "Starting error thread" << llendl;
mThreadErrorp = new LLErrorThread();
mThreadErrorp->setUserData((void *) this);
mThreadErrorp->start();
if(!mThreadErrorp)
{
llinfos << "Starting error thread" << llendl;
mThreadErrorp = new LLErrorThread();
mThreadErrorp->setUserData((void *) this);
mThreadErrorp->start();
}
}
void LLApp::setErrorHandler(LLAppErrorHandler handler)

View File

@ -40,8 +40,7 @@
// Forward declarations
class LLErrorThread;
class LLApp;
class LLLiveFile;
typedef void (*LLAppErrorHandler)();
typedef void (*LLAppChildCallback)(int pid, bool exited, int status);
@ -127,6 +126,19 @@ public:
*/
bool parseCommandOptions(int argc, char** argv);
/**
* @brief Keep track of live files automatically.
*
* *TODO: it currently uses the <code>addToEventTimer()</code> API
* instead of the runner. I should probalby use the runner.
*
* *NOTE: DO NOT add the livefile instance to any kind of check loop.
*
* @param livefile A valid instance of an LLLiveFile. This LLApp
* instance will delete the livefile instance.
*/
void manageLiveFile(LLLiveFile* livefile);
/**
* @brief Set the options at the specified priority.
*
@ -194,11 +206,26 @@ public:
#endif
static int getPid();
//
// Error handling methods
//
/** @name Error handling methods */
//@{
/**
* @brief Do our generic platform-specific error-handling setup --
* signals on unix, structured exceptions on windows.
*
* DO call this method if your app will either spawn children or be
* spawned by a launcher.
* Call just after app object construction.
* (Otherwise your app will crash when getting signals,
* and will not core dump.)
*
* DO NOT call this method if your application has specialized
* error handling code.
*/
void setupErrorHandling();
void setErrorHandler(LLAppErrorHandler handler);
void setSyncErrorHandler(LLAppErrorHandler handler);
//@}
#if !LL_WINDOWS
//
@ -214,8 +241,9 @@ public:
void setDefaultChildCallback(LLAppChildCallback callback);
// Fork and do the proper signal handling/error handling mojo
// WARNING: You need to make sure your signal handling callback is correct after
// you fork, because not all threads are duplicated when you fork!
// *NOTE: You need to make sure your signal handling callback is
// correct after you fork, because not all threads are duplicated
// when you fork!
pid_t fork();
#endif
@ -255,7 +283,6 @@ protected:
private:
void startErrorThread();
void setupErrorHandling(); // Do platform-specific error-handling setup (signals, structured exceptions)
static void runErrorHandler(); // run shortly after we detect an error, ran in the relatively robust context of the LLErrorThread - preferred.
static void runSyncErrorHandler(); // run IMMEDIATELY when we get an error, ran in the context of the faulting thread.
@ -278,6 +305,8 @@ private:
// The application options.
LLSD mOptions;
// The live files for this application
std::vector<LLLiveFile*> mLiveFiles;
//@}
private:

View File

@ -289,7 +289,7 @@ namespace
public:
static LogControlFile& fromDirectory(const std::string& dir);
virtual void loadFile();
virtual bool loadFile();
private:
LogControlFile(const std::string &filename)
@ -317,7 +317,7 @@ namespace
// NB: This instance is never freed
}
void LogControlFile::loadFile()
bool LogControlFile::loadFile()
{
LLSD configuration;
@ -333,12 +333,13 @@ namespace
llwarns << filename() << " missing, ill-formed,"
" or simply undefined; not changing configuration"
<< llendl;
return;
return false;
}
}
LLError::configure(configuration);
llinfos << "logging reconfigured from " << filename() << llendl;
return true;
}

View File

@ -38,9 +38,12 @@
#include "llsd.h"
#include "llsdserialize.h"
LLLiveAppConfig::LLLiveAppConfig(LLApp* app, const std::string& filename, F32 refresh_period)
: LLLiveFile(filename, refresh_period),
mApp(app)
LLLiveAppConfig::LLLiveAppConfig(
const std::string& filename,
F32 refresh_period,
LLApp::OptionPriority priority) :
LLLiveFile(filename, refresh_period),
mPriority(priority)
{ }
@ -48,7 +51,7 @@ LLLiveAppConfig::~LLLiveAppConfig()
{ }
// virtual
void LLLiveAppConfig::loadFile()
bool LLLiveAppConfig::loadFile()
{
llinfos << "LLLiveAppConfig::loadFile(): reading from "
<< filename() << llendl;
@ -59,12 +62,25 @@ void LLLiveAppConfig::loadFile()
LLSDSerialize::fromXML(config, file);
if(!config.isMap())
{
llinfos << "LLDataserverConfig::loadFile(): not an map!"
llwarns << "Live app config not an map in " << filename()
<< " Ignoring the data." << llendl;
return;
return false;
}
file.close();
}
mApp->setOptionData(
LLApp::PRIORITY_SPECIFIC_CONFIGURATION, config);
else
{
llinfos << "Live file " << filename() << " does not exit." << llendl;
}
// *NOTE: we do not handle the else case here because we would not
// have attempted to load the file unless LLLiveFile had
// determined there was a reason to load it. This only happens
// when either the file has been updated or it is either suddenly
// in existence or has passed out of existence. Therefore, we want
// to set the config to an empty config, and return that it
// changed.
LLApp* app = LLApp::instance();
if(app) app->setOptionData(mPriority, config);
return true;
}

View File

@ -33,25 +33,43 @@
#ifndef LLLIVEAPPCONFIG_H
#define LLLIVEAPPCONFIG_H
#include "llapp.h"
#include "lllivefile.h"
class LLApp;
/**
* @class LLLiveAppConfig
* @see LLLiveFile
*
* To use this, instantiate a LLLiveAppConfig object inside your main
* loop. The traditional name for it is live_config. Be sure to call
* <code>live_config.checkAndReload()</code> periodically.
*/
class LLLiveAppConfig : public LLLiveFile
{
public:
// To use this, instantiate a LLLiveAppConfig object inside your main loop.
// The traditional name for it is live_config.
// Be sure to call live_config.checkAndReload() periodically.
LLLiveAppConfig(LLApp* app, const std::string& filename, F32 refresh_period);
~LLLiveAppConfig();
/**
* @brief Constructor
*
* @param filename. The name of the file for periodically checking
* configuration.
* @param refresh_period How often the internal timer should
* bother checking the filesystem.
* @param The application priority level of that configuration file.
*/
LLLiveAppConfig(
const std::string& filename,
F32 refresh_period,
LLApp::OptionPriority priority);
~LLLiveAppConfig(); ///< Destructor
protected:
/*virtual*/ void loadFile();
/*virtual*/ bool loadFile();
private:
LLApp* mApp;
LLApp::OptionPriority mPriority;
};
#endif

View File

@ -35,14 +35,17 @@
#include "llframetimer.h"
#include "lltimer.h"
const F32 DEFAULT_CONFIG_FILE_REFRESH = 5.0f;
class LLLiveFile::Impl
{
public:
Impl(const std::string &filename, const F32 refresh_period);
Impl(const std::string& filename, const F32 refresh_period);
~Impl();
bool check();
void changed();
bool mForceCheck;
F32 mRefreshPeriod;
@ -50,16 +53,19 @@ public:
std::string mFilename;
time_t mLastModTime;
time_t mLastStatTime;
bool mLastExists;
LLEventTimer* mEventTimer;
};
LLLiveFile::Impl::Impl(const std::string &filename, const F32 refresh_period)
: mForceCheck(true),
LLLiveFile::Impl::Impl(const std::string& filename, const F32 refresh_period)
:
mForceCheck(true),
mRefreshPeriod(refresh_period),
mFilename(filename),
mLastModTime(0),
mLastStatTime(0),
mLastExists(false),
mEventTimer(NULL)
{
@ -70,7 +76,7 @@ LLLiveFile::Impl::~Impl()
delete mEventTimer;
}
LLLiveFile::LLLiveFile(const std::string &filename, const F32 refresh_period)
LLLiveFile::LLLiveFile(const std::string& filename, const F32 refresh_period)
: impl(* new Impl(filename, refresh_period))
{
}
@ -121,17 +127,30 @@ bool LLLiveFile::Impl::check()
// We want to read the file. Update status info for the file.
mLastExists = true;
mLastModTime = stat_data.st_mtime;
mLastStatTime = stat_data.st_mtime;
return true;
}
void LLLiveFile::Impl::changed()
{
// we wanted to read this file, and we were successful.
mLastModTime = mLastStatTime;
}
bool LLLiveFile::checkAndReload()
{
bool changed = impl.check();
if (changed)
{
loadFile();
if(loadFile())
{
impl.changed();
this->changed();
}
else
{
changed = false;
}
}
return changed;
}

View File

@ -33,29 +33,65 @@
#ifndef LL_LLLIVEFILE_H
#define LL_LLLIVEFILE_H
const F32 configFileRefreshRate = 5.0; // seconds
extern const F32 DEFAULT_CONFIG_FILE_REFRESH;
class LLLiveFile
{
public:
LLLiveFile(const std::string &filename, const F32 refresh_period = 5.f);
LLLiveFile(const std::string& filename, const F32 refresh_period = 5.f);
virtual ~LLLiveFile();
/**
* @brief Check to see if this live file should reload.
*
* Call this before using anything that was read & cached
* from the file.
*
* This method calls the <code>loadFile()</code> method if
* any of:
* file has a new modify time since the last check
* file used to exist and now does not
* file used to not exist but now does
* @return Returns true if the file was reloaded.
*/
bool checkAndReload();
// Returns true if the file changed in any way
// Call this before using anything that was read & cached from the file
std::string filename() const;
/**
* @brief Add this live file to an automated recheck.
*
* Normally, just calling checkAndReload() is enough. In some
* cases though, you may need to let the live file periodically
* check itself.
*/
void addToEventTimer();
// Normally, just calling checkAndReload() is enough. In some cases
// though, you may need to let the live file periodically check itself.
void setRefreshPeriod(F32 seconds);
protected:
virtual void loadFile() = 0; // Implement this to load your file if it changed
/**
* @breif Implement this to load your file if it changed.
*
* This method is called automatically by <code>checkAndReload()</code>,
* so though you must implement this in derived classes, you do
* not need to call it manually.
* @return Returns true if the file was successfully loaded.
*/
virtual bool loadFile() = 0;
/**
* @brief Implement this method if you want to get a change callback.
*
* This virtual function will be called automatically at the end
* of <code>checkAndReload()</code> if a new configuration was
* loaded. This does not track differences between the current and
* newly loaded file, so any successful load event will trigger a
* <code>changed()</code> callback. Default is to do nothing.
*/
virtual void changed() {}
private:
class Impl;

View File

@ -62,7 +62,7 @@ public:
static std::string filename();
protected:
/* virtual */ void loadFile();
/* virtual */ bool loadFile();
public:
void init(LLPerfStats* statsp);
@ -94,12 +94,12 @@ LLStatsConfigFile& LLStatsConfigFile::instance()
/* virtual */
// Load and parse the stats configuration file
void LLStatsConfigFile::loadFile()
bool LLStatsConfigFile::loadFile()
{
if (!mStatsp)
{
llwarns << "Tries to load performance configure file without initializing LPerfStats" << llendl;
return;
return false;
}
mChanged = true;
@ -113,7 +113,7 @@ void LLStatsConfigFile::loadFile()
{
llinfos << "Performance statistics configuration file ill-formed, not recording statistics" << llendl;
mStatsp->setReportPerformanceDuration( 0.f );
return;
return false;
}
}
else
@ -123,7 +123,7 @@ void LLStatsConfigFile::loadFile()
llinfos << "Performance statistics configuration file deleted, not recording statistics" << llendl;
mStatsp->setReportPerformanceDuration( 0.f );
}
return;
return true;
}
}
@ -159,6 +159,7 @@ void LLStatsConfigFile::loadFile()
{
llinfos << "Performance stats recording turned off" << llendl;
}
return true;
}

View File

@ -228,7 +228,25 @@ public:
// True if this is the head of s.
static BOOL isHead( const std::basic_string<T>& string, const T* s );
/**
* @brief Returns true if string starts with substr
*
* If etither string or substr are empty, this method returns false.
*/
static bool startsWith(
const std::basic_string<T>& string,
const std::basic_string<T>& substr);
/**
* @brief Returns true if string ends in substr
*
* If etither string or substr are empty, this method returns false.
*/
static bool endsWith(
const std::basic_string<T>& string,
const std::basic_string<T>& substr);
static void addCRLF(std::basic_string<T>& string);
static void removeCRLF(std::basic_string<T>& string);
@ -335,7 +353,7 @@ public:
* This function works on bytes rather than glyphs, so this will
* incorrectly truncate non-single byte strings.
* Use utf8str_truncate() for utf8 strings
* @return a copy of in string minus the trailing count characters.
* @return a copy of in string minus the trailing count bytes.
*/
inline std::string chop_tail_copy(
const std::string& in,
@ -1065,6 +1083,30 @@ BOOL LLStringUtilBase<T>::isHead( const std::basic_string<T>& string, const T* s
}
}
// static
template<class T>
bool LLStringUtilBase<T>::startsWith(
const std::basic_string<T>& string,
const std::basic_string<T>& substr)
{
if(string.empty() || (substr.empty())) return false;
if(0 == string.find(substr)) return true;
return false;
}
// static
template<class T>
bool LLStringUtilBase<T>::endsWith(
const std::basic_string<T>& string,
const std::basic_string<T>& substr)
{
if(string.empty() || (substr.empty())) return false;
std::string::size_type idx = string.rfind(substr);
if(std::string::npos == idx) return false;
return (idx == (string.size() - substr.size()));
}
template<class T>
BOOL LLStringUtilBase<T>::convertToBOOL(const std::basic_string<T>& string, BOOL& value)
{

View File

@ -162,11 +162,10 @@ namespace
{ return LLURI::escape(s, unreserved() + ":@!$'()*+,="); } // sub_delims - "&;" + ":@"
}
// *TODO: Consider using curl. After http textures gets merged everywhere.
// static
//static
std::string LLURI::escape(const std::string& str)
{
static std::string default_allowed(unreserved() + ":@!$'()*+,=/?&#;");
static std::string default_allowed = unreserved();
static bool initialized = false;
if(!initialized)
{

View File

@ -127,27 +127,16 @@ public:
/** @name Escaping Utilities */
//@{
/**
* @brief Escape a raw url with a reasonable set of allowed characters.
*
* The default set was chosen to match HTTP urls and general
* guidelines for naming resources. Passing in a raw url does not
* produce well defined results because you really need to know
* which segments are path parts because path parts are supposed
* to be escaped individually. The default set chosen is:
* @brief Escape the string passed except for unreserved
*
* ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
* 0123456789
* -._~
* :@!$'()*+,=/?&#;
*
* *NOTE: This API is basically broken because it does not
* allow you to specify significant path characters. For example,
* if the filename actually contained a /, then you cannot use
* this function to generate the serialized url for that
* resource.
* @see http://www.ietf.org/rfc/rfc1738.txt
*
* @param str The raw URI to escape.
* @return Returns the escaped uri or an empty string.
* @return Returns the rfc 1738 escaped uri or an empty string.
*/
static std::string escape(const std::string& str);

View File

@ -89,7 +89,8 @@ LLCrashLogger::LLCrashLogger() :
mSentCrashLogs(false),
mCrashHost("")
{
// Set up generic error handling
setupErrorHandling();
}
LLCrashLogger::~LLCrashLogger()

View File

@ -175,7 +175,7 @@ void LLParcel::init(const LLUUID &owner_id,
mSaleTimerExpires.stop();
mGraceExtension = 0;
//mExpireAction = STEA_REVERT;
mRecordTransaction = FALSE;
//mRecordTransaction = FALSE;
mAuctionID = 0;
mInEscrow = false;

View File

@ -413,12 +413,6 @@ public:
void completeSale(U32& type, U8& flags, LLUUID& to_id);
void clearSale();
// this function returns TRUE if the parcel needs conversion to a
// lease from a non-owned-status state.
BOOL getRecordTransaction() const { return mRecordTransaction; }
void setRecordTransaction(BOOL record) { mRecordTransaction = record; }
// more accessors
U32 getParcelFlags() const { return mParcelFlags; }
@ -596,8 +590,6 @@ protected:
ELandingType mLandingType;
LLTimer mSaleTimerExpires;
S32 mGraceExtension;
BOOL mRecordTransaction;
// This value is non-zero if there is an auction associated with
// the parcel.

View File

@ -224,6 +224,10 @@ static void request(
LLURLRequest* req = new LLURLRequest(method, url);
req->checkRootCertificate(true);
lldebugs << LLURLRequest::actionAsVerb(method) << " " << url << " "
<< headers << llendl;
// Insert custom headers is the caller sent any
if (headers.isMap())
{
@ -375,72 +379,140 @@ private:
std::string mBuffer;
};
// *TODO: Deprecate (only used by dataserver)
// This call is blocking! This is probably usually bad. :(
LLSD LLHTTPClient::blockingGet(const std::string& url)
// These calls are blocking! This is usually bad, unless you're a dataserver. Then it's awesome.
/**
@brief does a blocking request on the url, returning the data or bad status.
@param url URI to verb on.
@param method the verb to hit the URI with.
@param body the body of the call (if needed - for instance not used for GET and DELETE, but is for POST and PUT)
@param headers HTTP headers to use for the request.
@param timeout Curl timeout to use. Defaults to 5. Rationale:
Without this timeout, blockingGet() calls have been observed to take
up to 90 seconds to complete. Users of blockingGet() already must
check the HTTP return code for validity, so this will not introduce
new errors. A 5 second timeout will succeed > 95% of the time (and
probably > 99% of the time) based on my statistics. JC
@returns an LLSD map: {status: integer, body: map}
*/
static LLSD blocking_request(
const std::string& url,
LLURLRequest::ERequestAction method,
const LLSD& body,
const LLSD& headers = LLSD(),
const F32 timeout = 5
)
{
llinfos << "blockingGet of " << url << llendl;
// Returns an LLSD map: {status: integer, body: map}
char curl_error_buffer[CURL_ERROR_SIZE];
lldebugs << "blockingRequest of " << url << llendl;
char curl_error_buffer[CURL_ERROR_SIZE] = "\0";
CURL* curlp = curl_easy_init();
LLHTTPBuffer http_buffer;
// Without this timeout, blockingGet() calls have been observed to take
// up to 90 seconds to complete. Users of blockingGet() already must
// check the HTTP return code for validity, so this will not introduce
// new errors. A 5 second timeout will succeed > 95% of the time (and
// probably > 99% of the time) based on my statistics. JC
std::string body_str;
// other request method checks root cert first, we skip?
//req->checkRootCertificate(true);
// * Set curl handle options
curl_easy_setopt(curlp, CURLOPT_NOSIGNAL, 1); // don't use SIGALRM for timeouts
curl_easy_setopt(curlp, CURLOPT_TIMEOUT, 5); // seconds
curl_easy_setopt(curlp, CURLOPT_TIMEOUT, timeout); // seconds, see warning at top of function.
curl_easy_setopt(curlp, CURLOPT_WRITEFUNCTION, LLHTTPBuffer::curl_write);
curl_easy_setopt(curlp, CURLOPT_WRITEDATA, &http_buffer);
curl_easy_setopt(curlp, CURLOPT_URL, url.c_str());
curl_easy_setopt(curlp, CURLOPT_ERRORBUFFER, curl_error_buffer);
curl_easy_setopt(curlp, CURLOPT_FAILONERROR, 1);
// * Setup headers (don't forget to free them after the call!)
curl_slist* headers_list = NULL;
if (headers.isMap())
{
LLSD::map_const_iterator iter = headers.beginMap();
LLSD::map_const_iterator end = headers.endMap();
for (; iter != end; ++iter)
{
std::ostringstream header;
header << iter->first << ": " << iter->second.asString() ;
lldebugs << "header = " << header.str() << llendl;
headers_list = curl_slist_append(headers_list, header.str().c_str());
}
}
// * Setup specific method / "verb" for the URI (currently only GET and POST supported + poppy)
if (method == LLURLRequest::HTTP_GET)
{
curl_easy_setopt(curlp, CURLOPT_HTTPGET, 1);
}
else if (method == LLURLRequest::HTTP_POST)
{
curl_easy_setopt(curlp, CURLOPT_POST, 1);
//serialize to ostr then copy to str - need to because ostr ptr is unstable :(
std::ostringstream ostr;
LLSDSerialize::toXML(body, ostr);
body_str = ostr.str();
curl_easy_setopt(curlp, CURLOPT_POSTFIELDS, body_str.c_str());
//copied from PHP libs, correct?
headers_list = curl_slist_append(headers_list, "Content-Type: application/llsd+xml");
struct curl_slist *header_list = NULL;
header_list = curl_slist_append(header_list, "Accept: application/llsd+xml");
CURLcode curl_result = curl_easy_setopt(curlp, CURLOPT_HTTPHEADER, header_list);
// copied from llurlrequest.cpp
// it appears that apache2.2.3 or django in etch is busted. If
// we do not clear the expect header, we get a 500. May be
// limited to django/mod_wsgi.
headers_list = curl_slist_append(headers_list, "Expect:");
}
// * Do the action using curl, handle results
lldebugs << "HTTP body: " << body_str << llendl;
headers_list = curl_slist_append(headers_list, "Accept: application/llsd+xml");
CURLcode curl_result = curl_easy_setopt(curlp, CURLOPT_HTTPHEADER, headers_list);
if ( curl_result != CURLE_OK )
{
llinfos << "Curl is hosed - can't add Accept header for llsd+xml" << llendl;
llinfos << "Curl is hosed - can't add headers" << llendl;
}
LLSD response = LLSD::emptyMap();
S32 curl_success = curl_easy_perform(curlp);
S32 http_status = 499;
curl_easy_getinfo(curlp,CURLINFO_RESPONSE_CODE, &http_status);
curl_easy_getinfo(curlp, CURLINFO_RESPONSE_CODE, &http_status);
response["status"] = http_status;
if (curl_success != 0
&& http_status != 404) // We expect 404s, don't spam for them.
// if we get a non-404 and it's not a 200 OR maybe it is but you have error bits,
if ( http_status != 404 && (http_status != 200 || curl_success != 0) )
{
// We expect 404s, don't spam for them.
llwarns << "CURL REQ URL: " << url << llendl;
llwarns << "CURL REQ METHOD TYPE: " << method << llendl;
llwarns << "CURL REQ HEADERS: " << headers.asString() << llendl;
llwarns << "CURL REQ BODY: " << body_str << llendl;
llwarns << "CURL HTTP_STATUS: " << http_status << llendl;
llwarns << "CURL ERROR: " << curl_error_buffer << llendl;
llwarns << "CURL ERROR BODY: " << http_buffer.asString() << llendl;
response["body"] = http_buffer.asString();
}
else
{
response["body"] = http_buffer.asLLSD();
lldebugs << "CURL response: " << http_buffer.asString() << llendl;
}
if(header_list)
if(headers_list)
{ // free the header list
curl_slist_free_all(header_list);
header_list = NULL;
curl_slist_free_all(headers_list);
}
// * Cleanup
curl_easy_cleanup(curlp);
return response;
}
LLSD LLHTTPClient::blockingGet(const std::string& url)
{
return blocking_request(url, LLURLRequest::HTTP_GET, LLSD());
}
LLSD LLHTTPClient::blockingPost(const std::string& url, const LLSD& body)
{
return blocking_request(url, LLURLRequest::HTTP_POST, body);
}
void LLHTTPClient::put(
const std::string& url,
const LLSD& body,

View File

@ -142,6 +142,14 @@ public:
*/
static LLSD blockingGet(const std::string& url);
/**
* @brief Blocking HTTP POST that returns an LLSD map of status and body.
*
* @param url the complete serialized (and escaped) url to get
* @param body the LLSD post body
* @return An LLSD of { 'status':status (an int), 'body':payload (an LLSD) }
*/
static LLSD blockingPost(const std::string& url, const LLSD& body);
static void setPump(LLPumpIO& pump);

View File

@ -66,7 +66,7 @@ public:
static LLMessageConfigFile& instance();
// return the singleton configuration file
/* virtual */ void loadFile();
/* virtual */ bool loadFile();
void loadServerDefaults(const LLSD& data);
void loadMaxQueuedEvents(const LLSD& data);
void loadMessages(const LLSD& data);
@ -98,7 +98,7 @@ LLMessageConfigFile& LLMessageConfigFile::instance()
}
// virtual
void LLMessageConfigFile::loadFile()
bool LLMessageConfigFile::loadFile()
{
LLSD data;
{
@ -115,7 +115,7 @@ void LLMessageConfigFile::loadFile()
LL_INFOS("AppInit") << "LLMessageConfigFile::loadFile: file missing,"
" ill-formed, or simply undefined; not changing the"
" file" << LL_ENDL;
return;
return false;
}
}
loadServerDefaults(data);
@ -123,6 +123,7 @@ void LLMessageConfigFile::loadFile()
loadMessages(data);
loadCapBans(data);
loadMessageBans(data);
return true;
}
void LLMessageConfigFile::loadServerDefaults(const LLSD& data)

View File

@ -98,6 +98,26 @@ LLURLRequestDetail::~LLURLRequestDetail()
* class LLURLRequest
*/
// static
std::string LLURLRequest::actionAsVerb(LLURLRequest::ERequestAction action)
{
static const std::string VERBS[] =
{
"(invalid)",
"HEAD",
"GET",
"PUT",
"POST",
"DELETE",
"MOVE"
};
if(((S32)action <=0) || ((S32)action >= REQUEST_ACTION_COUNT))
{
return VERBS[0];
}
return VERBS[action];
}
LLURLRequest::LLURLRequest(LLURLRequest::ERequestAction action) :
mAction(action)
{

View File

@ -81,6 +81,11 @@ public:
REQUEST_ACTION_COUNT
};
/**
* @brief Turn the requst action into an http verb.
*/
static std::string actionAsVerb(ERequestAction action);
/**
* @brief Constructor.
*

View File

@ -509,6 +509,22 @@ private:
public:
// BOOL decodeData(const U8 *buffer, const LLHost &host);
/**
gets binary data from the current message.
@param blockname the name of the block in the message (from the message template)
@param varname
@param datap
@param size expected size - set to zero to get any amount of data up to max_size.
Make sure max_size is set in that case!
@param blocknum
@param max_size the max number of bytes to read
*/
void getBinaryDataFast(const char *blockname, const char *varname, void *datap, S32 size, S32 blocknum = 0, S32 max_size = S32_MAX);
void getBinaryData(const char *blockname, const char *varname, void *datap, S32 size, S32 blocknum = 0, S32 max_size = S32_MAX);
void getBOOLFast( const char *block, const char *var, BOOL &data, S32 blocknum = 0);

View File

@ -523,6 +523,7 @@ LLAppViewer::LLAppViewer() :
llerrs << "Oh no! An instance of LLAppViewer already exists! LLAppViewer is sort of like a singleton." << llendl;
}
setupErrorHandling();
sInstance = this;
}

View File

@ -2720,7 +2720,7 @@ version 2.0
// end viewer to simulator section
{
ViewerStats Low 131 NotTrusted Zerocoded
ViewerStats Low 131 NotTrusted Zerocoded UDPDeprecated
{
AgentData Single
{ AgentID LLUUID }