From c6515af8bf195d8c5422dcadccee7cc76d716557 Mon Sep 17 00:00:00 2001 From: Andres Riancho Date: Fri, 27 Dec 2013 15:45:53 -0300 Subject: [PATCH] Now we have a stand-alone server which can provide services for more than one backend at the same time --- moto/core/models.py | 7 +++++ moto/core/responses.py | 10 +++--- moto/server.py | 71 ++++++++++++++++++++++++++++++++---------- 3 files changed, 67 insertions(+), 21 deletions(-) diff --git a/moto/core/models.py b/moto/core/models.py index e3777741f..c5c23d155 100644 --- a/moto/core/models.py +++ b/moto/core/models.py @@ -110,6 +110,13 @@ class BaseBackend(object): return paths + @property + def url_bases(self): + """ + A list containing the url_bases extracted from urls.py + """ + return self._url_module.url_bases + @property def flask_paths(self): """ diff --git a/moto/core/responses.py b/moto/core/responses.py index a47cf531c..a39795577 100644 --- a/moto/core/responses.py +++ b/moto/core/responses.py @@ -9,7 +9,7 @@ from moto.core.utils import camelcase_to_underscores, method_names_from_class class BaseResponse(object): def dispatch(self, request, full_url, headers): - querystring = None + querystring = {} if hasattr(request, 'body'): # Boto @@ -26,12 +26,12 @@ class BaseResponse(object): for key, value in request.form.iteritems(): querystring[key] = [value, ] + if querystring is None: + querystring.update(parse_qs(urlparse(full_url).query)) if not querystring: - querystring = parse_qs(urlparse(full_url).query) + querystring.update(parse_qs(self.body)) if not querystring: - querystring = parse_qs(self.body) - if not querystring: - querystring = headers + querystring.update(headers) self.uri = full_url self.path = urlparse(full_url).path diff --git a/moto/server.py b/moto/server.py index 9ef135359..d35cbae98 100644 --- a/moto/server.py +++ b/moto/server.py @@ -1,16 +1,54 @@ +import re import sys import argparse +from threading import Lock + from flask import Flask from werkzeug.routing import BaseConverter +from werkzeug.serving import run_simple from moto.backends import BACKENDS from moto.core.utils import convert_flask_to_httpretty_response -app = Flask(__name__) HTTP_METHODS = ["GET", "POST", "PUT", "DELETE", "HEAD"] +class DomainDispatcherApplication(object): + """ + Dispatch requests to different applications based on the "Host:" header + value. We'll match the host header value with the url_bases of each backend. + """ + + def __init__(self, create_app): + self.create_app = create_app + self.lock = Lock() + self.app_instances = {} + + def get_backend_for_host(self, host): + for backend in BACKENDS.itervalues(): + for url_base in backend.url_bases: + if re.match(url_base, 'http://%s' % host): + print url_base, 'http://%s' % host + return backend + + raise RuntimeError('Invalid host: "%s"' % host) + + def get_application(self, host): + host = host.split(':')[0] + with self.lock: + backend = self.get_backend_for_host(host) + app = self.app_instances.get(backend, None) + if app is None: + app = self.create_app(backend) + self.app_instances[backend] = app + return app + + def __call__(self, environ, start_response): + backend_app = self.get_application(environ['HTTP_HOST']) + return backend_app(environ, start_response) + + class RegexConverter(BaseConverter): # http://werkzeug.pocoo.org/docs/routing/#custom-converters def __init__(self, url_map, *items): @@ -18,25 +56,25 @@ class RegexConverter(BaseConverter): self.regex = items[0] -def configure_urls(service): - backend = BACKENDS[service] +def create_backend_app(backend): from werkzeug.routing import Map + + # Create the backend_app + backend_app = Flask(__name__) + backend_app.debug = True + # Reset view functions to reset the app - app.view_functions = {} - app.url_map = Map() - app.url_map.converters['regex'] = RegexConverter + backend_app.view_functions = {} + backend_app.url_map = Map() + backend_app.url_map.converters['regex'] = RegexConverter for url_path, handler in backend.flask_paths.iteritems(): - app.route(url_path, methods=HTTP_METHODS)(convert_flask_to_httpretty_response(handler)) + backend_app.route(url_path, methods=HTTP_METHODS)(convert_flask_to_httpretty_response(handler)) + + return backend_app def main(argv=sys.argv[1:]): - available_services = BACKENDS.keys() - parser = argparse.ArgumentParser() - parser.add_argument( - 'service', type=str, - choices=available_services, - help='Choose which mechanism you want to run') parser.add_argument( '-H', '--host', type=str, help='Which host to bind', @@ -48,10 +86,11 @@ def main(argv=sys.argv[1:]): args = parser.parse_args(argv) - configure_urls(args.service) + # Wrap the main application + main_app = DomainDispatcherApplication(create_backend_app) + main_app.debug = True - app.testing = True - app.run(host=args.host, port=args.port) + run_simple(args.host, args.port, main_app) if __name__ == '__main__': main()