diff options
author | W. Trevor King <wking@tremily.us> | 2012-08-27 13:37:58 -0400 |
---|---|---|
committer | W. Trevor King <wking@tremily.us> | 2012-08-27 13:37:58 -0400 |
commit | 0fa17f6bf6a809df14ae1930542059a9e62066b0 (patch) | |
tree | 164f7d437785d1ed7350ce64d9b5b12c54c433dc /libbe/command/serve_commands.py | |
parent | c09b4d12195c9f2b69ace7369147881103bfe6f4 (diff) | |
download | bugseverywhere-0fa17f6bf6a809df14ae1930542059a9e62066b0.tar.gz |
libbe:util:wsgi: extract WSGI utilities into a separate module.
Diffstat (limited to 'libbe/command/serve_commands.py')
-rw-r--r-- | libbe/command/serve_commands.py | 208 |
1 files changed, 26 insertions, 182 deletions
diff --git a/libbe/command/serve_commands.py b/libbe/command/serve_commands.py index 810f698..75d9c7a 100644 --- a/libbe/command/serve_commands.py +++ b/libbe/command/serve_commands.py @@ -23,53 +23,26 @@ See Also :py:meth:`be-libbe.command.base.Command._run_remote` : the associated client """ -import hashlib import logging import os.path import posixpath import re -import sys -import time -import traceback -import types import urllib import wsgiref.simple_server import yaml -try: - # Python >= 2.6 - from urlparse import parse_qs -except ImportError: - # Python <= 2.5 - from cgi import parse_qs -try: - import cherrypy - import cherrypy.wsgiserver -except ImportError: - cherrypy = None -if cherrypy != None: - try: # CherryPy >= 3.2 - import cherrypy.wsgiserver.ssl_builtin - except ImportError: # CherryPy <= 3.1.X - cherrypy.wsgiserver.ssl_builtin = None -try: - import OpenSSL -except ImportError: - OpenSSL = None - import libbe import libbe.command -import libbe.command.serve -import libbe.command.util -import libbe.util.encoding -import libbe.util.subproc +import libbe.command.base +import libbe.util.wsgi import libbe.version -if libbe.TESTING == True: +if libbe.TESTING: import copy import doctest import StringIO + import sys import unittest import wsgiref.validate try: @@ -82,7 +55,8 @@ if libbe.TESTING == True: import libbe.command.list -class ServerApp (libbe.command.serve.WSGI_AppObject): +class ServerApp (libbe.util.wsgi.WSGI_AppObject, + libbe.util.wsgi.WSGI_DataObject): """WSGI server for a BE Command invocation over HTTP. RESTful_ WSGI request handler for serving the @@ -95,39 +69,16 @@ class ServerApp (libbe.command.serve.WSGI_AppObject): """ server_version = "BE-command-server/" + libbe.version.version() - def __init__(self, storage, notify=False, **kwargs): - libbe.command.serve.WSGI_AppObject.__init__(self, **kwargs) + def __init__(self, storage=None, notify=False, **kwargs): + super(ServerApp, self).__init__( + urls=[ + (r'^run/?$', self.run), + ], + **kwargs) self.storage = storage self.ui = libbe.command.base.UserInterface() self.notify = notify self.http_user_error = 418 - self.urls = [(r'^run$', self.run)] - - def __call__(self, environ, start_response): - """The main WSGI application. - - Dispatch the current request to the functions from above and - store the regular expression captures in the WSGI environment - as `be-server.url_args` so that the functions from above can - access the url placeholders. - - URL dispatcher from Armin Ronacher's "Getting Started with WSGI" - http://lucumr.pocoo.org/2007/5/21/getting-started-with-wsgi - """ - if self.logger != None: - self.logger.log(logging.DEBUG, 'ServerApp') - path = environ.get('PATH_INFO', '').lstrip('/') - try: - for regex, callback in self.urls: - match = re.search(regex, path) - if match is not None: - environ['be-server.url_args'] = match.groups() - return callback(environ, start_response) - print('not found') - raise libbe.command.serve._HandlerError(404, 'Not Found') - except libbe.command.serve._HandlerError, e: - return self.error(environ, start_response, - e.code, e.msg, e.headers) # handlers def run(self, environ, start_response): @@ -139,7 +90,7 @@ class ServerApp (libbe.command.serve.WSGI_AppObject): try: Class = libbe.command.get_command_class(command_name=name) except libbe.command.UnknownCommand, e: - raise libbe.command.serve._HandlerError( + raise libbe.util.wsgi.HandlerError( self.http_user_error, 'UnknownCommand {}'.format(e)) command = Class(ui=self.ui) self.ui.setup_command(command) @@ -156,8 +107,9 @@ class ServerApp (libbe.command.serve.WSGI_AppObject): def check_login(self, environ): user = environ.get('be-auth.user', None) - if user != None: # we're running under AuthenticationApp + if user is not None: # we're running under AuthenticationApp if environ['REQUEST_METHOD'] == 'POST': + # TODO: better detection of commands requiring writes if user == 'guest' or self.storage.is_writeable() == False: raise _Unauthorized() # only non-guests allowed to write # allow read-only commands for all users @@ -192,7 +144,7 @@ class ServerApp (libbe.command.serve.WSGI_AppObject): libbe.util.subproc.invoke(self.notify, stdin=message, shell=True) -class Serve_Commands (libbe.command.Command): +class Serve_Commands (libbe.util.wsgi.ServerCommand): """Serve commands over HTTP. This allows you to run local `be` commands interfacing with remote @@ -204,120 +156,9 @@ class Serve_Commands (libbe.command.Command): name = 'serve-commands' - def __init__(self, *args, **kwargs): - libbe.command.Command.__init__(self, *args, **kwargs) - self.options.extend([ - libbe.command.Option(name='port', - help='Bind server to port (%default)', - arg=libbe.command.Argument( - name='port', metavar='INT', type='int', default=8000)), - libbe.command.Option(name='host', - help='Set host string (blank for localhost, %default)', - arg=libbe.command.Argument( - name='host', metavar='HOST', default='')), - libbe.command.Option(name='read-only', short_name='r', - help='Dissable operations that require writing'), - libbe.command.Option(name='notify', short_name='n', - help='Send notification emails for changes.', - arg=libbe.command.Argument( - name='notify', metavar='EMAIL-COMMAND', default=None)), - libbe.command.Option(name='ssl', short_name='s', - help='Use CherryPy to serve HTTPS (HTTP over SSL/TLS)'), - libbe.command.Option(name='auth', short_name='a', - help='Require authentication. FILE should be a file containing colon-separated UNAME:USER:sha1(PASSWORD) lines, for example: "jdoe:John Doe <jdoe@example.com>:read:d99f8e5a4b02dc25f49da2ea67c0034f61779e72"', - arg=libbe.command.Argument( - name='auth', metavar='FILE', default=None, - completion_callback=libbe.command.util.complete_path)), - ]) - - def _run(self, **params): - self._setup_logging() - storage = self._get_storage() - if params['read-only'] == True: - writeable = storage.writeable - storage.writeable = False - if params['host'] == '': - params['host'] = 'localhost' - if params['auth'] != None: - self._check_restricted_access(storage, params['auth']) - users = libbe.command.serve.Users(params['auth']) - users.load() - app = ServerApp( - storage=storage, notify=params['notify'], logger=self.logger) - if params['auth'] != None: - app = AdminApp(app, users=users, logger=self.logger) - app = AuthenticationApp(app, realm=storage.repo, - users=users, logger=self.logger) - app = libbe.command.serve.UppercaseHeaderApp(app, logger=self.logger) - server,details = self._get_server(params, app) - details['repo'] = storage.repo - try: - self._start_server(params, server, details) - except KeyboardInterrupt: - pass - self._stop_server(params, server) - if params['read-only'] == True: - storage.writeable = writeable - - def _setup_logging(self, log_level=logging.INFO): - self.logger = logging.getLogger('be-serve') - self.log_level = logging.INFO - console = logging.StreamHandler(self.stdout) - console.setFormatter(logging.Formatter('%(message)s')) - self.logger.addHandler(console) - self.logger.propagate = False - if log_level is not None: - console.setLevel(log_level) - self.logger.setLevel(log_level) - - def _get_server(self, params, app): - details = {'port':params['port']} - app = libbe.command.serve.ExceptionApp(app, logger=self.logger) - if params['ssl'] == True: - details['protocol'] = 'HTTPS' - if cherrypy == None: - raise libbe.command.UserError, \ - '--ssl requires the cherrypy module' - server = cherrypy.wsgiserver.CherryPyWSGIServer( - (params['host'], params['port']), app) - #server.throw_errors = True - #server.show_tracebacks = True - private_key,certificate = libbe.command.serve.get_cert_filenames( - 'be-server', logger=self.logger) - if cherrypy.wsgiserver.ssl_builtin == None: - server.ssl_module = 'builtin' - server.ssl_private_key = private_key - server.ssl_certificate = certificate - else: - server.ssl_adapter = \ - cherrypy.wsgiserver.ssl_builtin.BuiltinSSLAdapter( - certificate=certificate, private_key=private_key) - details['socket-name'] = params['host'] - else: - details['protocol'] = 'HTTP' - server = wsgiref.simple_server.make_server( - params['host'], params['port'], app, - handler_class=libbe.command.serve.SilentRequestHandler) - details['socket-name'] = server.socket.getsockname()[0] - return (server, details) - - def _start_server(self, params, server, details): - self.logger.log(self.log_level, - 'Serving %(protocol)s on %(socket-name)s port %(port)s ...' \ - % details) - self.logger.log(self.log_level, - 'BE repository %(repo)s' % details) - if params['ssl'] == True: - server.start() - else: - server.serve_forever() - - def _stop_server(self, params, server): - self.logger.log(self.log_level, 'Clossing server') - if params['ssl'] == True: - server.stop() - else: - server.server_close() + def _get_app(self, logger, storage, **kwargs): + return ServerApp( + logger=logger, storage=storage, notify=kwargs.get('notify', False)) def _long_help(self): return """ @@ -342,15 +183,18 @@ for example:: # alias for libbe.command.base.get_command_class() Serve_commands = Serve_Commands -if libbe.TESTING == True: - class ServerAppTestCase (libbe.command.serve.WSGITestCase): + +if libbe.TESTING: + class ServerAppTestCase (libbe.util.wsgi.WSGITestCase): def setUp(self): - libbe.command.serve.WSGITestCase.setUp(self) + libbe.util.wsgi.WSGITestCase.setUp(self) self.bd = libbe.bugdir.SimpleBugDir(memory=False) self.app = ServerApp(self.bd.storage, logger=self.logger) + def tearDown(self): self.bd.cleanup() - libbe.command.serve.WSGITestCase.tearDown(self) + libbe.util.wsgi.WSGITestCase.tearDown(self) + def test_run_list(self): list = libbe.command.list.List() params = list._parse_options_args() |