aboutsummaryrefslogtreecommitdiffstats
path: root/libbe/command/serve_commands.py
diff options
context:
space:
mode:
authorW. Trevor King <wking@tremily.us>2012-08-27 13:37:58 -0400
committerW. Trevor King <wking@tremily.us>2012-08-27 13:37:58 -0400
commit0fa17f6bf6a809df14ae1930542059a9e62066b0 (patch)
tree164f7d437785d1ed7350ce64d9b5b12c54c433dc /libbe/command/serve_commands.py
parentc09b4d12195c9f2b69ace7369147881103bfe6f4 (diff)
downloadbugseverywhere-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.py208
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()