Esempio n. 1
0
class Cup(object):
    def __init__(self, with_static=True):
        if with_static:
            self.wsgi_app = SharedDataMiddleware(self.wsgi_app, {
                '/static':  os.path.join(os.path.dirname(__file__), 'static')
            })
        self.url_map = Map()
        self.views = {}

    def add_url_rule(self, url, endpt, func):
        self.url_map.add(Rule(url, endpoint=endpt))
        self.views[endpt] = func

    def getView(self, endpoint):
        return self.views[endpoint]
    
    def route(self, url):
        def decorator(func):
            self.add_url_rule(url, func.__name__, func)
            def decorated(*args, **kwargs):
                func(*args, **kwargs)
            return decorated
        return decorator

    def dispatch_request(self, request):
        adapter = self.url_map.bind_to_environ(request.environ)
        try:
            endpoint, values = adapter.match()
            data = self.getView(endpoint)(request, **values)
            return Response(data, mimetype="text/html")
        except HTTPException, e:
            print "e"
            return e
Esempio n. 2
0
 def make_url_map(self):
     url_map = Map()
     for provider in self._providers:
         rule = provider.get_url_rule()
         rule.endpoint = provider
         url_map.add(rule)
     return url_map
Esempio n. 3
0
class RestRouter(object):
  def __init__(self, json_encoder):
    self.handlers = {}
    self.map = Map()
    self.encoder = json_encoder

  def add_rule(self, rule, fn):
    if rule.endpoint in self.handlers:
      raise ValueError("Endpoint {} already present".format(rule.endpoint))
    self.handlers[rule.endpoint] = fn
    self.map.add(rule)

  def add_resource(self, service, path):
    rules = RestRules(path)
    for method in RestRules.METHOD_NAMES:
      if callable(getattr(service, method, None)):
        self.add_rule(getattr(rules, method), getattr(service, method))

  def __call__(self, request, response):
    matcher = self.map.bind_to_environ(request.environ)
    endpoint, args = matcher.match()
    handler = self.handlers[endpoint]
    result = handler(args, request)
    if isinstance(result, Response):
      return result
    elif result is not None:
      response.set_data(self.encoder.encode(result))
      response.content_type = 'application/json'
      return response
    else:
      return response
Esempio n. 4
0
class Mapper(object):
    def __init__(self, import_name):
        self.import_name = import_name
        self.url_map = Map()
        self.url_views = {}

    @property
    def url_rules(self):
        return self.url_map.iter_rules()

    def build_endpoint(self, endpoint):
        return '{0}.{1}'.format(self.import_name, endpoint)

    def add_route(self, rule, endpoint, view_func=None, **options):
        options['endpoint'] = endpoint
        self.url_map.add(Rule(rule, **options))
        if view_func:
            self.url_views[endpoint] = view_func

    def route(self, rule, **options):
        def decorator(func):
            endpoint = options.pop('endpoint', func.__name__)
            endpoint = self.build_endpoint(endpoint)
            self.add_route(rule, endpoint, func, **options)
            return func
        return decorator

    def add_map(self, map, prefix='', rule_factory=Submount):
        self.url_views.update(map.url_views)
        self.url_map.add(
            rule_factory('/%s' % prefix.rstrip('/'), map.url_rules)
        )
Esempio n. 5
0
class App:
    def __init__(self):
        self._url_map = Map(strict_slashes=False)

    def route(self, rule, **kwargs):
        def decorator(func):
            kwargs['endpoint'] = func
            self._url_map.add(Rule(rule, **kwargs))
            return func
        return decorator

    def _dispatch(self, request):
        adapter = self._url_map.bind_to_environ(request.environ)
        try:
            endpoint, values = adapter.match()
            return endpoint(request, **values)
        except HTTPException as e:
            return e

    def __call__(self, env, sr):
        request = AppRequest(env)
        response = self._dispatch(request)
        after_handlers = getattr(request, '_after_request_handlers', None)
        if after_handlers:
            for h in after_handlers:
                response = h(response) or response
        return response(env, sr)
Esempio n. 6
0
class Dispatcher(object):
    """Dispatch requests based on a WSGI environment.
        
    The routes are loaded from the <package>/config/routes file, and each line should be blank,
    a comment, or one of the following:
        <route> <page class>
        <route> redirect:<url>
        <route> template:<filename>
    """
    
    def __init__(self, app):
        """Load the URL routing map and instantiate the responders."""
 
        self.app = app
        
        # Set up URL routing
        self.map = Map()
        routes_file = file(os.path.join(app.directory, 'config', 'routes'), 'r')
        for line in routes_file:
            # Split the line from one of the documented formats
            parts = line.split()
            if len(parts) == 0 or parts[0][0] == '#':
                # Ignore comments and blank lines
                continue
            if len(parts) != 2:
                raise ConfigurationError("Error in routes file: %s" % line)
            path, destination = parts
            if ':' in destination:
                # Responder explicitly specified
                responder_name, extra = destination.split(':', 1)
                responder_type = responder_types.get(responder_name, None)
                if responder_type is None:
                    raise ConfigurationError("Invalid destination '%s' in routes file" % destination)
                responder = responder_type(extra)
            else:
                # Default to PageResponder if there's no ':' in the destination
                responder = PageResponder(destination)
            for p, r in responder.get_routes(path): # FIXME: Better names for p and r
                rule = Rule(p, endpoint=r, methods=r.methods)
                self.map.add(rule)
        self.map.update()

    def dispatch(self, environ):
        try:
            request = Request(environ)
            urls = self.map.bind_to_environ(environ)
            responder, args = urls.match()
            with Context(self.app, environ, request, args) as context:
                for hook in self.app.get_hook_functions('pre-request'):
                    hook(context)
                context.response = responder(context)
                for hook in self.app.get_hook_functions('post-request'):
                    context.response = hook(context) or context.response
            return context.response
        
        # HTTPExceptions are returned as the response, while any other 
        # exceptions are re-raised to be either caught by the in-browser debugger
        # or generate a 500 response.
        except HTTPException, e:
            return e
Esempio n. 7
0
def build_urls():
    views = {
        'famfamfam/get' : famfamfam.get,
        'admin/configuration'     : configure.nut,
        'admin/eb'             : configure.eb,
        'admin/eb_rec'              : configure.eb_rec,
        'admin/eb_fix'               : configure.fix_nodes,
        'admin/index'          : configure.list
    }

    # secure the admin area
    from util.decorators import require_admin
    for key in views:
        if key.startswith('admin'):
            views[key] = require_admin(views[key])

    admin_tabs = []
    url_map = Map(rules())
    for nut in NutSettings().nuts:
        mod = __import__('hazel.nuts.%s.urls' % nut, fromlist=['hazel.nuts.%s' % nut])
        pub, pub_views, admin, admin_views, tabs = mod.build_rules()
        url_map.add(EndpointPrefix('nut:%s/' % nut, pub))
        url_map.add(EndpointPrefix('nut:%s/' % nut,
                                   [Submount('/admin/%s' % nut, admin)]))
        admin_tabs.extend([(rule.endpoint, name) for name, rule in tabs])
        views.update([(rule.endpoint, fn) for rule, fn in pub_views])
        # secure admin
        views.update([(rule.endpoint, require_admin(fn)) for rule, fn in admin_views])


    # tell the layout engine about the enabled modules
    from util.decorators import jinja_const
    jinja_const('admin_tabs', admin_tabs)

    return url_map, views
Esempio n. 8
0
class InputReqApp(object):
    def __init__(self):
        self.url_map = Map()
        self.url_map.add(Rule('/test/<path:url>', endpoint=self.direct_input_request))
        self.url_map.add(Rule('/test-postreq', endpoint=self.post_fullrequest))

    def direct_input_request(self, environ, url=''):
        inputreq = DirectWSGIInputRequest(environ)
        return inputreq.reconstruct_request(url)

    def post_fullrequest(self, environ):
        params = dict(parse_qsl(environ.get('QUERY_STRING', '')))
        inputreq = POSTInputRequest(environ)
        return inputreq.reconstruct_request(params['url'])

    def __call__(self, environ, start_response):
        urls = self.url_map.bind_to_environ(environ)
        try:
            endpoint, args = urls.match()
        except HTTPException as e:
            return e(environ, start_response)

        result = endpoint(environ, **args)
        start_response('200 OK', [('Content-Type', 'text/plain; charset=utf-8')])
        return [result]
Esempio n. 9
0
class BrownAnt(object):
    """The app which could manage whole crawler system."""

    def __init__(self):
        self.url_map = Map(strict_slashes=False, host_matching=True)

    def add_url_rule(self, host, rule_string, endpoint, **options):
        """Add a url rule to the app instance.

        The url rule is the same with Flask apps and other Werkzeug apps.

        :param host: the matched hostname. e.g. "www.python.org"
        :param rule_string: the matched path pattern. e.g. "/news/<int:id>"
        :param endpoint: the endpoint name as a dispatching key such as the
                         qualified name of the object.
        """
        rule = Rule(rule_string, host=host, endpoint=endpoint, **options)
        self.url_map.add(rule)

    def parse_url(self, url_string):
        """Parse the URL string with the url map of this app instance.

        :param url_string: the origin URL string.
        :returns: the tuple as `(url, url_adapter, query_args)`, the url is
                  parsed by the standard library `urlparse`, the url_adapter is
                  from the werkzeug bound URL map, the query_args is a
                  multidict from the werkzeug.
        """
        url = urlparse.urlparse(url_string)
        url_adapter = self.url_map.bind(server_name=url.hostname,
                                        url_scheme=url.scheme,
                                        path_info=url.path)
        query_args = url_decode(url.query)
        return url, url_adapter, query_args

    def dispatch_url(self, url_string):
        """Dispatch the URL string to the target endpoint function.

        :param url_string: the origin URL string.
        :returns: the return value of calling dispatched function.
        """
        url, url_adapter, query_args = self.parse_url(url_string)

        try:
            endpoint, kwargs = url_adapter.match()
        except NotFound:
            raise NotSupported(url_string)

        handler = import_string(endpoint)
        request = Request(args=query_args)
        return handler(request, **kwargs)

    def mount_site(self, site):
        """Mount a supported site to this app instance.

        :param site: the site instance be mounted.
        """
        site.play_actions(target=self)
Esempio n. 10
0
def server(path='/'):
    try:
        account = g.account
        application = g.account.application
        # prepare the jinja environment. This is used for regular routes 
        # and special handlers such as 404
        template_lookup = {t.key: t.jinja2 for t in application.templates}
        loader = DictLoader(template_lookup)
        jinja_env = SandboxedEnvironment(
            extensions=['application.app.pyjade.ext.jinja.PyJadeExtension'],
            loader=loader)
        
        # default helper utils
        path = PathUtil(request.environ)
        get = GetUtil(request)
        static = StaticUtil()

        # load template data. 404 can also use these
        template_data = {}
        template_data['path'] = path
        template_data['get'] = get
        template_data['cms'] = cms
        template_data['static'] = static
        template_data['deployment'] = config.TEMPLATE_GLOBAL_DEPLOYMENT
        template_data['markdown'] = service.markdown

        template_content = {}
        for content in application.static_contents:
            template_content.update(content.data)
        template_data['content'] = template_content

        # find the route with werkzeug
        url_map = Map()
        for route in application.routes:
            # skip non string rules like 404. These should be handled by exceptions
            if not route.rule.isnumeric():
                url_map.add(Rule(route.rule, endpoint=route.template_name))
        urls = url_map.bind_to_environ(request.environ)
        endpoint, args = urls.match()
        template_data['path'].add_placeholders(args)

        app_template = jinja_env.get_template(endpoint)
        page_content = app_template.render(**template_data)
        app.record_transfer(page_content)
        return page_content

    except NotFound as e:
        # find the template for a 404 handler if specified
        for route in application.routes:
            if route.rule == '404':
                app_template = jinja_env.get_template(route.template_name)
                not_found_page = app_template.render(**template_data)
                app.record_transfer(not_found_page)
                return not_found_page, 404
        return '404', 404

    except Exception as e:
        return '500 internal error', 500
Esempio n. 11
0
class BaseDispatcher(object):
    request_class = None
    response_class = None

    auth_class = None

    def __init__(self):
        self.url_map = Map()

    def add_resource(self, resource, parent_resource=None):
        if parent_resource:
            try:
                for rule in self.url_map.iter_rules(parent_resource.on_detail):
                    detail_rule = rule

                rulefactory = Submount(detail_rule.rule, (resource,))
            except AttributeError:
                raise AttributeError(
                    'The parent resource must define the "on_detail" method')
        else:
            rulefactory = resource

        self.url_map.add(rulefactory)

    def dispatch_request(self, request):
        adapter = self.url_map.bind_to_environ(request.environ)
        endpoint, params = adapter.match()

        params.update(data=request.data)
        params.update(request.args)

        if endpoint.requires_auth:
            endpoint = self.auth_class(endpoint, request)

        return endpoint(params)

    def __call__(self, environ, start_response):
        request = self.request_class(environ)
        try:
            response = self.response_class(self.dispatch_request(request))
        except HTTPException as e:
            response = self.response_class({'error': e.description}, e.code, headers=e.get_headers())
        return response(environ, start_response)

    def run(self, host='127.0.0.1', port=5000,
            use_debugger=True, use_reloader=True):
        from werkzeug.serving import run_simple
        run_simple(
            host,
            port,
            self,
            use_debugger=use_debugger,
            use_reloader=use_reloader
        )
Esempio n. 12
0
class Sockets(object):

    def __init__(self, app=None):
        #: Compatibility with 'Flask' application.
        #: The :class:`~werkzeug.routing.Map` for this instance. You can use
        #: this to change the routing converters after the class was created
        #: but before any routes are connected.
        self.url_map = Map()

        #: Compatibility with 'Flask' application.
        #: All the attached blueprints in a dictionary by name. Blueprints
        #: can be attached multiple times so this dictionary does not tell
        #: you how often they got attached.
        self.blueprints = {}
        self._blueprint_order = []

        if app:
            self.init_app(app)

    def init_app(self, app):
        app.wsgi_app = SocketMiddleware(app.wsgi_app, app, self)

    def route(self, rule, **options):

        def decorator(f):
            endpoint = options.pop('endpoint', None)
            self.add_url_rule(rule, endpoint, f, **options)
            return f
        return decorator

    def add_url_rule(self, rule, _, f, **options):
        self.url_map.add(Rule(rule, endpoint=f))

    def register_blueprint(self, blueprint, **options):
        """
        Registers a blueprint for web sockets like for 'Flask' application.

        Decorator :meth:`~flask.app.setupmethod` is not applied, because it
        requires ``debug`` and ``_got_first_request`` attributes to be defined.
        """
        first_registration = False

        if blueprint.name in self.blueprints:
            assert self.blueprints[blueprint.name] is blueprint, (
                'A blueprint\'s name collision occurred between %r and '
                '%r.  Both share the same name "%s".  Blueprints that '
                'are created on the fly need unique names.'
                % (blueprint, self.blueprints[blueprint.name], blueprint.name))
        else:
            self.blueprints[blueprint.name] = blueprint
            self._blueprint_order.append(blueprint)
            first_registration = True

        blueprint.register(self, options, first_registration)
Esempio n. 13
0
    def load_websites(self):
        """Load the websites and build a map of the website names to the ID
        in database for quick connection to the website
        """
        website_obj = self.pool.get("nereid.website")
        url_map_obj = self.pool.get('nereid.url_map')

        # Load all url maps first because many websites might reuse the same
        # URL map and it might be faster to load them just once
        url_map_ids = url_map_obj.search([])
        url_maps = dict.fromkeys(url_map_ids)

        for url_map_id in url_map_ids:
            url_map = Map() # Define a new map
            # Add the static url
            url_map.add(
                self.url_rule_class(
                    self.static_url_path + '/<path:filename>',
                    endpoint = 'static'
                )
            )
            url_rules = url_map_obj.get_rules_arguments(url_map_id)
            for url in url_rules:
                rule = self.url_rule_class(url.pop('rule'), **url)
                rule.provide_automatic_options = True
                url_map.add(rule)   # Add rule to map

                if (not url['build_only']) and not(url['redirect_to']):
                    # Add the method to the view_functions list if the
                    # endpoint was not a build_only url
                    self.view_functions[url['endpoint']] = self.get_method(
                            url['endpoint']
                    )
            url_maps[url_map_id] = url_map

        website_ids = website_obj.search([])
        for website in website_obj.browse(website_ids):
            self.websites[website.name] = {
                'id': website.id,
                'url_map': url_maps[website.url_map.id],
                'application_user': website.application_user.id,
                'guest_user': website.guest_user.id,
                'company': website.company.id,
            }

        # Finally add the view_function for static
        self.view_functions['static'] = self.send_static_file
Esempio n. 14
0
class Sockets(object):
    def __init__(self, app=None):
        self.url_map = Map()
        if app:
            self.init_app(app)

    def init_app(self, app):
        app.wsgi_app = SocketMiddleware(app.wsgi_app, app, self)

    def route(self, rule, **options):
        def decorator(f):
            endpoint = options.pop("endpoint", None)
            self.add_url_rule(rule, endpoint, f, **options)
            return f

        return decorator

    def add_url_rule(self, rule, _, f, **options):
        self.url_map.add(Rule(rule, endpoint=f))
Esempio n. 15
0
File: verktyg.py Progetto: keis/lera
class App(object):
    def __init__(self):
        self.urls = Map()

    def route(self, pat):
        def decorator(fun):
            self.urls.add(Rule(pat, endpoint=fun))

        return decorator

    def dispatch_request(self, req, res):
        adapter = self.urls.bind_to_environ(req.environ)
        try:
            endpoint, values = adapter.match()
            return endpoint(req, res, **values)
        except HTTPException as e:
            return e

    def __call__(self, environ, start_response):
        request = Request(environ)
        response = Response(environ, start_response)
        Task(self.dispatch_request(request, response))
        return response
Esempio n. 16
0
class TrytondWSGI(object):

    def __init__(self):
        self.url_map = Map([])
        self.protocols = [JSONProtocol, XMLProtocol]
        self.error_handlers = []

    def route(self, string, methods=None):
        def decorator(func):
            self.url_map.add(Rule(string, endpoint=func, methods=methods))
            return func
        return decorator

    @wrapt.decorator
    def auth_required(self, wrapped, instance, args, kwargs):
        request = args[0]
        if request.user_id:
            return wrapped(*args, **kwargs)
        else:
            abort(303)

    def dispatch_request(self, request):
        adapter = self.url_map.bind_to_environ(request.environ)
        try:
            endpoint, request.view_args = adapter.match()
            return endpoint(request, **request.view_args)
        except Exception, e:
            tb_s = ''.join(traceback.format_exception(*sys.exc_info()))
            for path in sys.path:
                tb_s = tb_s.replace(path, '')
            e.__format_traceback__ = tb_s
            response = e
            for error_handler in self.error_handlers:
                rv = error_handler(e)
                if isinstance(rv, Response):
                    response = rv
            return response
Esempio n. 17
0
    def __new__(cls, name, bases, attrs):
        # Add a url_map to the class
        url_map = UrlMap(strict_slashes=False)
        # Add a collection of (unbound) view functions
        view_functions = {}
        for base in bases:
            # Extend from url_map of base class
            if hasattr(base, 'url_map') and isinstance(base.url_map, UrlMap):
                for rule in base.url_map.iter_rules():
                    url_map.add(rule.empty())
            # Extend from view_functions of base class
            if hasattr(base, 'view_functions') and isinstance(base.view_functions, dict):
                view_functions.update(base.view_functions)
        for routeattr, route in attrs.items():
            if isinstance(route, _NodeRoute):
                # For wrapped routes, add a rule for each layer of wrapping
                endpoints = []
                while isinstance(route, _NodeRoute):
                    # Save the endpoint name
                    endpoints.append(route.endpoint)
                    # Construct the url rule
                    url_rule = UrlRule(route.rule, endpoint=route.endpoint, methods=route.methods, defaults=route.defaults)
                    url_rule.provide_automatic_options = True
                    url_map.add(url_rule)
                    route = route.f
                # Make a list of endpoints
                for e in endpoints:
                    view_functions[e] = route
                # Restore the original function
                attrs[routeattr] = route
        # Finally, update the URL map and insert it into the class
        url_map.update()
        attrs['url_map'] = url_map
        attrs['view_functions'] = view_functions

        return type.__new__(cls, name, bases, attrs)
Esempio n. 18
0
    def get_url_adapter(self, app):
        """
        Returns the URL adapter for the website
        """
        cache_rv = self._url_adapter_cache.get(self.id)

        if cache_rv is not None:
            return cache_rv

        url_rules = app.get_urls()[:]

        # Add the static url
        url_rules.append(
            app.url_rule_class(
                app.static_url_path + '/<path:filename>',
                endpoint='static',
            )
        )

        for url_kwargs in self.url_map.get_rules_arguments():
            rule = app.url_rule_class(
                url_kwargs.pop('rule'),
                **url_kwargs
            )
            rule.provide_automatic_options = True
            url_rules.append(rule)   # Add rule to map

        url_map = Map()
        if self.locales:
            # Create the URL map with locale prefix
            url_map.add(
                app.url_rule_class(
                    '/', redirect_to='/%s' % self.default_locale.code,
                ),
            )
            url_map.add(Submount('/<locale>', url_rules))
        else:
            # Create a new map with the given URLs
            map(url_map.add, url_rules)

        # Add the rules from the application's url map filled through the
        # route decorator or otherwise
        for rule in app.url_map._rules:
            url_map.add(rule.empty())

        self._url_adapter_cache.set(self.id, url_map)

        return url_map
Esempio n. 19
0
class WsgiApplication(HttpBase):
    '''A `PEP-3333 <http://www.python.org/dev/peps/pep-3333>`_
    compliant callable class.

    If you want to have a hard-coded URL in the wsdl document, this is how to do
    it: ::

        wsgi_app = WsgiApplication(...)
        wsgi_app.doc.wsdl11.build_interface_document("http://example.com")

    This is not strictly necessary -- if you don't do this, Spyne will get the
    URL from the first request, build the wsdl on-the-fly and cache it as a
    string in memory for later requests. However, if you want to make sure
    you only have this url on the WSDL, this is how to do it. Note that if
    your client takes the information in the Wsdl document seriously (not all
    do), all requests will go to the designated url above even when you get the
    Wsdl from another location, which can make testing a bit difficult. Use in
    moderation.

    Supported events:
        * ``wsdl``
            Called right before the wsdl data is returned to the client.

        * ``wsdl_exception``
            Called right after an exception is thrown during wsdl generation.
            The exception object is stored in ctx.transport.wsdl_error
            attribute.

        * ``wsgi_call``
            Called first when the incoming http request is identified as a rpc
            request.

        * ``wsgi_return``
            Called right before the output stream is returned to the WSGI
            handler.

        * ``wsgi_error``
            Called right before returning the exception to the client.

        * ``wsgi_close``
            Called after the whole data has been returned to the client. It's
            called both from success and error cases.
    '''

    def __init__(self, app, chunked=True,
                max_content_length=2 * 1024 * 1024,
                block_length=8 * 1024):
        HttpBase.__init__(self, app, chunked, max_content_length, block_length)

        self._mtx_build_interface_document = threading.Lock()
        self._wsdl = self.doc.wsdl11.get_interface_document()

        # Initialize HTTP Patterns
        self._http_patterns = None
        self._map_adapter = None
        self._mtx_build_map_adapter = threading.Lock()

        for k,v in self.app.interface.service_method_map.items():
            # p_ stands for primary
            p_service_class, p_method_descriptor = v[0]
            for patt in p_method_descriptor.patterns:
                if isinstance(patt, HttpPattern):
                    # We are doing this here because we want to import Werkzeug
                    # as late as possible.
                    if self._http_patterns is None:
                        from werkzeug.routing import Map
                        self._http_patterns = Map(host_matching=True)

                    for r in patt.as_werkzeug_rules():
                        self._http_patterns.add(r)

    @property
    def has_patterns(self):
        return self._http_patterns is not None

    def __call__(self, req_env, start_response, wsgi_url=None):
        '''This method conforms to the WSGI spec for callable wsgi applications
        (PEP 333). It looks in environ['wsgi.input'] for a fully formed rpc
        message envelope, will deserialize the request parameters and call the
        method on the object returned by the get_handler() method.
        '''

        url = wsgi_url
        if url is None:
            url = reconstruct_url(req_env).split('.wsdl')[0]

        if self.__is_wsdl_request(req_env):
            return self.__handle_wsdl_request(req_env, start_response, url)

        else:
            return self.handle_rpc(req_env, start_response)

    def __is_wsdl_request(self, req_env):
        # Get the wsdl for the service. Assume path_info matches pattern:
        # /stuff/stuff/stuff/serviceName.wsdl or
        # /stuff/stuff/stuff/serviceName/?wsdl

        return (
            req_env['REQUEST_METHOD'].upper() == 'GET'
            and (
                   req_env['QUERY_STRING'].lower() == 'wsdl'
                or req_env['PATH_INFO'].endswith('.wsdl')
            )
        )

    def __handle_wsdl_request(self, req_env, start_response, url):
        ctx = WsgiMethodContext(self, req_env, 'text/xml; charset=utf-8')

        if self.doc.wsdl11 is None:
            start_response(HTTP_404,
                                  _gen_http_headers(ctx.transport.resp_headers))
            return [HTTP_404]

        if self._wsdl is None:
            self._wsdl = self.doc.wsdl11.get_interface_document()

        ctx.transport.wsdl = self._wsdl

        if ctx.transport.wsdl is None:
            try:
                self._mtx_build_interface_document.acquire()

                ctx.transport.wsdl = self._wsdl

                if ctx.transport.wsdl is None:
                    self.doc.wsdl11.build_interface_document(url)
                    ctx.transport.wsdl = self._wsdl = \
                                        self.doc.wsdl11.get_interface_document()

            except Exception, e:
                logger.exception(e)
                ctx.transport.wsdl_error = e

                self.event_manager.fire_event('wsdl_exception', ctx)

                start_response(HTTP_500,
                                  _gen_http_headers(ctx.transport.resp_headers))

                return [HTTP_500]

            finally:
Esempio n. 20
0
    def make_url_map():
        map = Map()
        # general pages
        map.add(Rule('/', endpoint='general.index'))

        map.add(Rule('/login/', endpoint='general.login'))
        map.add(Rule('/logout/', endpoint='general.logout'))
        map.add(Rule('/register/', endpoint='general.register'))

        map.add(Rule('/admin/', endpoint='general.admin'))

        map.add(Rule('/cases/', endpoint='case.view_all'))
        map.add(Rule('/cases/<case_id>/', endpoint='case.view'))
        map.add(Rule('/cases/add/', endpoint='case.add'))
        map.add(Rule('/cases/edit/<case_id>/', endpoint='case.edit'))
        map.add(Rule('/cases/close/<case_id>/', endpoint='case.close'))
        map.add(Rule('/cases/change_status/<case_id>/', endpoint='case.change_status'))

        map.add(Rule('/tasks/', endpoint='task.view_all'))
        map.add(Rule('/tasks/qa/', endpoint='task.view_qas'))
        map.add(Rule('/cases/<case_id>/<task_id>/assign_me/', endpoint='task.assign_work'))
        map.add(Rule('/cases/<case_id>/<task_id>/assign/', endpoint='task.assign_work_manager'))
        map.add(Rule('/cases/<case_id>/<task_id>/', endpoint='task.view'))
        map.add(Rule('/cases/<case_id>/tasks/add/', endpoint='task.add'))
        map.add(Rule('/cases/<case_id>/<task_id>/edit/', endpoint='task.edit'))
        map.add(Rule('/cases/<case_id>/<task_id>/close/', endpoint='task.close'))
        map.add(Rule('/cases/<case_id>/<task_id>/change_status/', endpoint='task.change_status'))
        map.add(Rule('/cases/<case_id>/change_statuses/', endpoint='task.change_statuses'))

        map.add(Rule('/evidence/', endpoint='evidence.view_all'))
        map.add(Rule('/evidence/<evidence_id>/associate/', endpoint='evidence.associate'))
        map.add(Rule('/cases/<case_id>/evidence/<evidence_id>/', endpoint='evidence.view'))
        map.add(Rule('/evidence/<evidence_id>/', endpoint='evidence.view_caseless'))
        map.add(Rule('/evidence/<evidence_id>/remove/', endpoint='evidence.remove'))
        map.add(Rule('/cases/<case_id>/evidence/add/', endpoint='evidence.add'))
        map.add(Rule('/evidence/add/', endpoint='evidence.add_no_case'))
        map.add(Rule('/evidence/<evidence_id>/edit/', endpoint='evidence.edit'))
        map.add(Rule('/cases/<case_id>/evidence/<evidence_id>/remove/', endpoint='evidence.disassociate'))
        map.add(Rule('/evidence/<evidence_id>/custody/check-out/', endpoint='evidence.custody_out'))
        map.add(Rule('/evidence/<evidence_id>/custody/check-in/', endpoint='evidence.custody_in'))

        map.add(Rule('/cases/<case_id>/<task_id>/notes/', endpoint='forensics.work'))
        map.add(Rule('/cases/<case_id>/<task_id>/qa/', endpoint='forensics.qa'))

        map.add(Rule('/users/', endpoint='user.view_all'))
        map.add(Rule('/users/<user_id>/', endpoint='user.view'))
        map.add(Rule('/users/add/', endpoint='user.add'))
        map.add(Rule('/users/edit/<user_id>/', endpoint='user.edit'))
        map.add(Rule('/users/edit_password/<user_id>/', endpoint='user.edit_password'))

        map.add(Rule('/users/<user_id>/case_history/', endpoint='user.case_history'))

        map.add(Rule('/reporting/', endpoint='report.report'))
        map.add(Rule('/json/jason_tasks_assigned_to_inv/', endpoint='report.jason_tasks_assigned_to_inv'))
        map.add(Rule('/json/jason_tasks_qaed/', endpoint='report.jason_tasks_qaed'))

        map.add(Rule('/export/<case_id>/<task_id>.pdf', endpoint='export.pdf'))
        map.add(Rule('/export/<case_id>/<task_id>.rtf', endpoint='export.rtf'))
        map.add(Rule('/export/<case_id>/<task_id>.csv', endpoint='export.csv'))

        map.add(Rule('/cases/<case_id>/<task_id>/uploads/<upload_id>', endpoint='task.view_upload'))
        map.add(Rule('/cases/<case_id>/<task_id>/uploads/<upload_id>/delete/', endpoint='task.delete_upload'))

        # Static rules -- these never match, they're only used for building.
        for k in staticLocations:
            map.add(Rule('%s/<file>' % k, endpoint=k.strip('/'), build_only=True))

        return map
Esempio n. 21
0
class PredmetyApp(object):
    def __init__(self, settings):
        self.settings = settings

        loader = jinja2.PackageLoader(__package__)
        def guess_autoescape(template_name):
            return template_name and any(template_name.endswith(ext)
                for ext in ('.html', '.htm', '.xml'))
        self.jinja = jinja2.Environment(loader=loader,
                                        autoescape=guess_autoescape)

        self.session_store = FilesystemSessionStore(
            renew_missing=True, path=settings.session_path)

        self.views = {}
        for module in site_modules:
            self.views.update(module.views)
        self.url_map = Map()
        self.url_map.converters['regex'] = RegexConverter
        for module in site_modules:
            for rulefactory in module.get_routes():
                self.url_map.add(rulefactory)

        self.db_engine = settings.db_connect()
        self.DbSession = sessionmaker(bind=self.db_engine)

    def create_tables(self):
        models.create_tables(self.db_engine)

    def render(self, template_name, **context):
        template = self.jinja.get_template(template_name)
        return jinja2.Markup(template.render(context))

    def dispatch_request(self, request):
        try:
            endpoint, values = request.url_adapter.match()
            return endpoint(request, **values)
        except NotFound as e:
            return self.views['not_found'](request)
        except HttpException as e:
            return e

    @Request.application
    def wsgi_app(self, request):
        request.app = self

        request.max_content_length = 16 * 1024 * 1024
        request.max_form_memory_size = 2 * 1024 * 1024

        cookie_name = self.settings.cookie_name
        sid = request.cookies.get(cookie_name, '')
        request.session = self.session_store.get(sid)

        request.db_session = self.DbSession()

        request.url_adapter = self.url_map.bind_to_environ(request.environ)

        def build_url(view_name, *args, **kwargs):
            endpoint = self.views[view_name]
            return request.url_adapter.build(endpoint, *args, **kwargs)
        request.build_url = build_url

        response = self.dispatch_request(request)

        if request.session.should_save:
            self.session_store.save(request.session)
            response.set_cookie(cookie_name, request.session.sid)
        elif sid and request.session.new and hasattr(response, 'delete_cookie'):
            response.delete_cookie(cookie_name)

        request.db_session.close()

        return response

    def __call__(self, *args):
        return self.wsgi_app(*args)
Esempio n. 22
0
from werkzeug.routing import Map, Rule

url_map = Map()  # 关键依赖: werkzeug.routing.Map
static_path = '/static'
# todo: 待深入 关键依赖: werkzeug.routing.Rule
url_map.add(
    Rule(static_path + '/<filename>', build_only=True, endpoint='static'))

print(url_map)
Esempio n. 23
0
class Router(Generic[E]):
    """
    A Router is a wrapper around werkzeug's routing Map, that adds convenience methods and additional dispatching
    logic via the ``Dispatcher`` Protocol.
    """

    url_map: Map
    dispatcher: Dispatcher[E]

    def __init__(self,
                 dispatcher: Dispatcher[E] = None,
                 converters: Mapping[str, Type[BaseConverter]] = None):
        self.url_map = Map(host_matching=True,
                           strict_slashes=False,
                           converters=converters,
                           redirect_defaults=False)
        self.dispatcher = dispatcher or call_endpoint
        self._mutex = threading.RLock()

    def add(
        self,
        path: str,
        endpoint: E,
        host: Optional[str] = None,
        methods: Optional[Iterable[str]] = None,
        **kwargs,
    ) -> Rule:
        """
        Adds a new Rule to the URL Map.

        :param path: the path pattern to match
        :param endpoint: the endpoint to invoke
        :param host: an optional host matching pattern. if not pattern is given, the rule matches any host
        :param methods: the allowed HTTP verbs for this rule
        :param kwargs: any other argument that can be passed to ``werkzeug.routing.Rule``
        :return:
        """
        if host is None and self.url_map.host_matching:
            # this creates a "match any" rule, and will put the value of the host
            # into the variable "__host__"
            host = "<__host__>"

        # the typing for endpoint is a str, but the doc states it can be any value,
        # however then the redirection URL building will not work
        rule = Rule(path,
                    endpoint=endpoint,
                    methods=methods,
                    host=host,
                    **kwargs)
        self.add_rule(rule)
        return rule

    def add_route_endpoint(self, fn: _RouteEndpoint) -> Rule:
        """
        Adds a RouteEndpoint (typically a function decorated with ``@route``) as a rule to the router.
        :param fn: the RouteEndpoint function
        :return: the rule that was added
        """
        attr: _RuleAttributes = fn.rule_attributes

        return self.add(path=attr.path,
                        endpoint=fn,
                        host=attr.host,
                        **attr.kwargs)

    def add_route_endpoints(self, obj: object) -> List[Rule]:
        """
        Scans the given object for members that can be used as a `RouteEndpoint` and adds them to the router.
        :param obj: the object to scan
        :return: the rules that were added
        """
        rules = []

        members = inspect.getmembers(obj)
        for _, member in members:
            if hasattr(member, "rule_attributes"):
                rules.append(self.add_route_endpoint(member))

        return rules

    def add_rule(self, rule: RuleFactory):
        with self._mutex:
            self.url_map.add(rule)

    def remove_rule(self, rule: Rule):
        """
        Removes a Rule from the Router.

        **Caveat**: This is an expensive operation. Removing rules from a URL Map is intentionally not supported by
        werkzeug due to issues with thread safety, see https://github.com/pallets/werkzeug/issues/796, and because
        using a lock in ``match`` would be too expensive. However, some services that use Routers for routing
        internal resources need to be able to remove rules when those resources are removed. So to remove rules we
        create a new Map without that rule. This will not prevent the rules from dispatching until the Map has been
        completely constructed.

        :param rule: the Rule to remove that was previously returned by ``add``.
        """
        with self._mutex:
            old = self.url_map
            if rule not in old._rules:
                raise KeyError("no such rule")

            new = _clone_map_without_rules(old)

            for r in old.iter_rules():
                if r == rule:
                    # this works even with copied rules because of the __eq__ implementation of Rule
                    continue
                new.add(r.empty())
            self.url_map = new

    def dispatch(self, request: Request) -> Response:
        """
        Does the entire dispatching roundtrip, from matching the request to endpoints, and then invoking the endpoint
        using the configured dispatcher of the router. For more information on the matching behavior,
        see ``werkzeug.routing.MapAdapter.match()``.

        :param request: the HTTP request
        :return: the HTTP response
        """
        matcher = self.url_map.bind(server_name=request.host)
        handler, args = matcher.match(request.path,
                                      method=request.method,
                                      query_args=to_str(request.query_string))
        args.pop("__host__", None)
        return self.dispatcher(request, handler, args)

    def route(
        self,
        path: str,
        host: Optional[str] = None,
        methods: Optional[Iterable[str]] = None,
        **kwargs,
    ) -> Callable[[E], _RouteEndpoint]:
        """
        Returns a ``route`` decorator and immediately adds it to the router instance. This effectively mimics flask's
        ``@app.route``.

        :param path: the path pattern to match
        :param host: an optional host matching pattern. if not pattern is given, the rule matches any host
        :param methods: the allowed HTTP verbs for this rule
        :param kwargs: any other argument that can be passed to ``werkzeug.routing.Rule``
        :return: the function endpoint wrapped as a ``_RouteEndpoint``
        """
        def wrapper(fn):
            r = route(path, host, methods, **kwargs)
            fn = r(fn)
            self.add_route_endpoint(fn)
            return fn

        return wrapper
Esempio n. 24
0
class Dispatcher(object):
    """Dispatch requests based on a WSGI environment.
        
    The routes are loaded from the <package>/config/routes file, and each line should be blank,
    a comment, or one of the following:
        <route> <page class>
        <route> redirect:<url>
        <route> template:<filename>
    """
    def __init__(self, app):
        """Load the URL routing map and instantiate the responders."""

        self.app = app

        # Set up URL routing
        self.map = Map()
        routes_file = file(os.path.join(app.directory, 'config', 'routes'),
                           'r')
        for line in routes_file:
            # Split the line from one of the documented formats
            parts = line.split()
            if len(parts) == 0 or parts[0][0] == '#':
                # Ignore comments and blank lines
                continue
            if len(parts) != 2:
                raise ConfigurationError("Error in routes file: %s" % line)
            path, destination = parts
            if ':' in destination:
                # Responder explicitly specified
                responder_name, extra = destination.split(':', 1)
                responder_type = responder_types.get(responder_name, None)
                if responder_type is None:
                    raise ConfigurationError(
                        "Invalid destination '%s' in routes file" %
                        destination)
                responder = responder_type(extra)
            else:
                # Default to PageResponder if there's no ':' in the destination
                responder = PageResponder(destination)
            for p, r in responder.get_routes(
                    path):  # FIXME: Better names for p and r
                rule = Rule(p, endpoint=r, methods=r.methods)
                self.map.add(rule)
        self.map.update()

    def dispatch(self, environ):
        try:
            request = Request(environ)
            urls = self.map.bind_to_environ(environ)
            responder, args = urls.match()
            with Context(self.app, environ, request, args) as context:
                for hook in self.app.get_hook_functions('pre-request'):
                    hook(context)
                context.response = responder(context)
                for hook in self.app.get_hook_functions('post-request'):
                    context.response = hook(context) or context.response
            return context.response

        # HTTPExceptions are returned as the response, while any other
        # exceptions are re-raised to be either caught by the in-browser debugger
        # or generate a 500 response.
        except HTTPException, e:
            return e
Esempio n. 25
0
class Builder(object):
    default_ignores = (
        '.*',
        '_*',
        'config.yml',
        'Makefile',
        'README',
        '*.conf',
    )
    default_programs = {'*.rst': 'rst'}
    default_template_path = '_templates'
    default_static_folder = 'static'

    def __init__(self, project_folder, config):
        self.project_folder = os.path.abspath(project_folder)
        self.config = config
        self.programs = builtin_programs.copy()
        self.modules = []
        self.storage = {}
        self.url_map = Map()
        parsed = urlparse(self.config.root_get('canonical_url'))
        self.prefix_path = parsed.path
        self.url_adapter = self.url_map.bind('dummy.invalid',
                                             script_name=self.prefix_path)
        self.register_url('page', '/<path:slug>')

        template_path = os.path.join(
            self.project_folder,
            self.config.root_get('template_path')
            or self.default_template_path)
        self.locale = Locale(self.config.root_get('locale') or 'en')
        self.jinja_env = Environment(
            loader=FileSystemLoader([template_path, builtin_templates]),
            autoescape=self.config.root_get('template_autoescape', True),
            extensions=['jinja2.ext.autoescape', 'jinja2.ext.with_'],
        )
        self.jinja_env.globals.update(link_to=self.link_to,
                                      format_datetime=self.format_datetime,
                                      format_date=self.format_date,
                                      format_time=self.format_time)

        self.static_folder = self.config.root_get('static_folder') or \
                             self.default_static_folder

        for module in self.config.root_get('active_modules') or []:
            mod = find_module(module)
            mod.setup(self)
            self.modules.append(mod)

    @property
    def default_output_folder(self):
        return os.path.join(
            self.project_folder,
            self.config.root_get('output_folder') or OUTPUT_FOLDER)

    def link_to(self, _key, **values):
        return self.url_adapter.build(_key, values)

    def get_link_filename(self, _key, **values):
        link = url_unquote(self.link_to(_key,
                                        **values).lstrip('/')).encode('utf-8')
        if not link or link.endswith('/'):
            link += 'index.html'
        return os.path.join(self.default_output_folder, link)

    def open_link_file(self, _key, mode='w', **values):
        filename = self.get_link_filename(_key, **values)
        folder = os.path.dirname(filename)
        if not os.path.isdir(folder):
            os.makedirs(folder)
        return open(filename, mode)

    def register_url(self,
                     key,
                     rule=None,
                     config_key=None,
                     config_default=None,
                     **extra):
        if config_key is not None:
            rule = self.config.root_get(config_key, config_default)
        self.url_map.add(Rule(rule, endpoint=key, **extra))

    def get_full_static_filename(self, filename):
        return os.path.join(self.default_output_folder, self.static_folder,
                            filename)

    def get_static_url(self, filename):
        return '/' + posixpath.join(self.static_folder, filename)

    def open_static_file(self, filename, mode='w'):
        full_filename = self.get_full_static_filename(filename)
        folder = os.path.dirname(full_filename)
        if not os.path.isdir(folder):
            os.makedirs(folder)
        return open(full_filename, mode)

    def get_storage(self, module):
        return self.storage.setdefault(module, {})

    def filter_files(self, files, config):
        patterns = config.merged_get('ignore_files')
        if patterns is None:
            patterns = self.default_ignores

        result = []
        for filename in files:
            for pattern in patterns:
                if fnmatch(filename, pattern):
                    break
            else:
                result.append(filename)
        return result

    def guess_program(self, config, filename):
        mapping = config.list_entries('programs') or self.default_programs
        for pattern, program_name in mapping.iteritems():
            if fnmatch(filename, pattern):
                return program_name
        return 'copy'

    def render_template(self, template_name, context=None):
        if context is None:
            context = {}
        context['builder'] = self
        context.setdefault('config', self.config)
        tmpl = self.jinja_env.get_template(template_name)
        before_template_rendered.send(tmpl, context=context)
        return tmpl.render(context)

    def format_datetime(self, datetime=None, format='medium'):
        return dates.format_datetime(datetime, format, locale=self.locale)

    def format_time(self, time=None, format='medium'):
        return dates.format_time(time, format, locale=self.locale)

    def format_date(self, date=None, format='medium'):
        return dates.format_date(date, format, locale=self.locale)

    def iter_contexts(self, prepare=True):
        last_config = self.config
        cutoff = len(self.project_folder) + 1
        for dirpath, dirnames, filenames in os.walk(self.project_folder):
            local_config = last_config
            local_config_filename = os.path.join(dirpath, 'config.yml')
            if os.path.isfile(local_config_filename):
                with open(local_config_filename) as f:
                    local_config = last_config.add_from_file(f)

            dirnames[:] = self.filter_files(dirnames, local_config)
            filenames = self.filter_files(filenames, local_config)

            for filename in filenames:
                yield Context(self, local_config,
                              os.path.join(dirpath[cutoff:], filename),
                              prepare)

    def anything_needs_build(self):
        for context in self.iter_contexts(prepare=False):
            if context.needs_build:
                return True
        return False

    def run(self):
        self.storage.clear()
        contexts = list(self.iter_contexts())

        for context in contexts:
            if context.needs_build:
                key = context.is_new and 'A' or 'U'
                context.run()
                print key, context.source_filename

        before_build_finished.send(self)

    def debug_serve(self, host='127.0.0.1', port=5000):
        from rstblog.server import Server
        print 'Serving on http://%s:%d/' % (host, port)
        try:
            Server(host, port, self).serve_forever()
        except KeyboardInterrupt:
            pass
Esempio n. 26
0
class Spoon:
    # 请求类,默认为Request,可更改
    request_class = Request

    # 响应类, 默认为Response, 可更改
    response_class = Response

    jinja_options = dict(
        autoescape=True,
        extensions=['jinja2.ext.autoescape', 'jinja2.ext.with_'])

    static_path = '/static'

    secret_key = None

    session_cookie_name = 'session'

    def __init__(self, package_name):
        self.package_name = package_name
        self.root_path = _get_package_path(self.package_name)
        self.url_map = Map()  # 路由Map
        self.view_funcs = {}
        self.before_request_funcs = []
        self.after_request_funcs = []
        self.error_handlers = {}
        self.debug = False
        self.template_context_processors = [_default_template_ctx_processor]
        self.jinja_env = Environment(loader=self.create_jinja_loader(),
                                     **self.jinja_options)
        self.jinja_env.globals.update(
            url_for=url_for,
            get_flashed_messages=get_flashed_messages,
        )

        if self.static_path is not None:
            """
                用werkzeug中的SharedDataMiddleware托管静态文件
            """
            self.url_map.add(
                Rule(self.static_path + '/<filename>',
                     build_only=True,
                     endpoint='static'))
            target = os.path.join(self.root_path, 'static')

            self.wsgi_app = SharedDataMiddleware(self.wsgi_app,
                                                 {self.static_path: target})

    def before_request(self, func):
        """
            所有请求转发之前都要经过的函数的装饰器
        :param func:
        :return:
        """
        self.before_request_funcs.append(func)
        return func

    def after_request(self, func):
        """
            所有请求经视图函数处理完成后都要经过的函数的装饰器
        :param func:
        :return:
        """
        self.after_request_funcs.append(func)
        return func

    def preprocess_request(self):
        for func in self.before_request_funcs:
            rv = func()
            if rv is not None:
                return rv

    def process_response(self, response):
        session = _request_ctx_stack.top.session
        if session is not None:
            self.save_session(session, response)
        for handler in self.after_request_funcs:
            response = handler(response)
        return response

    def open_session(self, request):
        key = self.secret_key
        if key is not None:
            return SecureCookie.load_cookie(request,
                                            self.session_cookie_name,
                                            secret_key=key)

    def save_session(self, session, response):
        if session is not None:
            session.save_cookie(response, self.session_cookie_name)

    def create_jinja_loader(self):
        return PackageLoader(self.package_name)

    def context_processor(self, func):
        """
            添加模板变量的函数
        :param func:
        :return:
        """
        self.template_context_processors.append(func)
        return func

    def request_context(self, environ):
        return _RequestContext(self, environ)

    def test_request_context(self, *args, **kwargs):
        return self.request_context(create_environ(*args, **kwargs))

    def update_template_context(self, context):
        """
            更新jinja2上下文
        :param context:
        :return:
        """
        for func in self.template_context_processors:
            context.update(func())

    def errorhandler(self, code):
        """
            错误处理函数装饰器
        :param code: 错误状态码, 如404,500等。
        """
        def decorator(func):
            self.error_handlers[code] = func
            return func

        return decorator

    def make_response(self, rv):
        """
            根据视图函数的返回结果构造Response对象,
            最终发给客户端的响应则是调用Response对象生成的
        :param rv: 视图函数的返回结果
        :return: Response object
        """
        if isinstance(rv, self.response_class):
            return rv
        if isinstance(rv, basestring):
            return self.response_class(rv)
        if isinstance(rv, tuple):
            return self.response_class(*rv)
        return self.response_class.force_type(
            rv, _request_ctx_stack.top.request.environ)

    def dispatch_request(self):
        """
            路由转发
        :return: 路由处理函数的返回结果 或 抛出异常
        """
        try:
            endpoint, values = _request_ctx_stack.top.url_adapter.match()
            return self.view_funcs[endpoint](**values)
        except HTTPException, e:
            handler = self.error_handlers.get(e.code)
            if handler is None:
                return e
            return handler(e)
        except Exception, e:
            handler = self.error_handlers.get(500)
            if self.debug or handler is None:
                raise
            return handler(e)
Esempio n. 27
0
class Klein(object):
    """
    L{Klein} is an object which is responsible for maintaining the routing
    configuration of our application.

    @ivar _url_map: A C{werkzeug.routing.Map} object which will be used for
        routing resolution.
    @ivar _endpoints: A C{dict} mapping endpoint names to handler functions.
    """

    def __init__(self):
        self._url_map = Map()
        self._endpoints = {}


    @property
    def url_map(self):
        """
        Read only property exposing L{Klein._url_map}.
        """
        return self._url_map


    @property
    def endpoints(self):
        """
        Read only property exposing L{Klein._endpoints}.
        """
        return self._endpoints


    def resource(self):
        """
        Return an L{IResource} which suitably wraps this app.

        @returns: An L{IResource}
        """

        return KleinResource(self)


    def route(self, url, *args, **kwargs):
        """
        Add a new handler for C{url} passing C{args} and C{kwargs} directly to
        C{werkzeug.routing.Rule}.  The handler function will be passed at least
        one argument an L{twisted.web.server.Request} and any keyword arguments
        taken from the C{url} pattern.

        ::
            @app.route("/")
            def index(request):
                return "Hello"

        @param url: A werkzeug URL pattern given to C{werkzeug.routing.Rule}.
        @type url: str

        @returns: decorated handler function.
        """
        def deco(f):
            kwargs.setdefault('endpoint', f.__name__)
            if url.endswith('/'):
                branchKwargs = kwargs.copy()
                branchKwargs['endpoint'] = branchKwargs['endpoint'] + '_branch'

                @wraps(f)
                def branch_f(request, *a, **kw):
                    IKleinRequest(request).branch_segments = kw.pop('__rest__', '').split('/')
                    return f(request, *a, **kw)

                self._endpoints[branchKwargs['endpoint']] = branch_f
                self._url_map.add(Rule(url + '<path:__rest__>', *args, **branchKwargs))

            self._endpoints[kwargs['endpoint']] = f
            self._url_map.add(Rule(url, *args, **kwargs))
            return f

        return deco


    def run(self, host, port, logFile=None):
        """
        Run a minimal twisted.web server on the specified C{port}, bound to the
        interface specified by C{host} and logging to C{logFile}.

        This function will run the default reactor for your platform and so
        will block the main thread of your application.  It should be the last
        thing your klein application does.

        @param host: The hostname or IP address to bind the listening socket
            to.  "0.0.0.0" will allow you to listen on all interfaces, and
            "127.0.0.1" will allow you to listen on just the loopback interface.
        @type host: str

        @param port: The TCP port to accept HTTP requests on.
        @type port: int

        @param logFile: The file object to log to, by default C{sys.stdout}
        @type logFile: file object
        """
        if logFile is None:
            logFile = sys.stdout

        log.startLogging(logFile)
        reactor.listenTCP(port, Site(self.resource()), interface=host)
        reactor.run()
Esempio n. 28
0
class Klein(object):
    """
    L{Klein} is an object which is responsible for maintaining the routing
    configuration of our application.

    @ivar _url_map: A C{werkzeug.routing.Map} object which will be used for
        routing resolution.
    @ivar _endpoints: A C{dict} mapping endpoint names to handler functions.
    """

    _bound_klein_instances = weakref.WeakKeyDictionary()

    def __init__(self):
        self._url_map = Map()
        self._endpoints = {}
        self._error_handlers = []
        self._instance = None

    def __eq__(self, other):
        if isinstance(other, Klein):
            return vars(self) == vars(other)
        return NotImplemented

    def __ne__(self, other):
        result = self.__eq__(other)
        if result is NotImplemented:
            return result
        return not result

    @property
    def url_map(self):
        """
        Read only property exposing L{Klein._url_map}.
        """
        return self._url_map

    @property
    def endpoints(self):
        """
        Read only property exposing L{Klein._endpoints}.
        """
        return self._endpoints

    def execute_endpoint(self, endpoint, *args, **kwargs):
        """
        Execute the named endpoint with all arguments and possibly a bound
        instance.
        """
        endpoint_f = self._endpoints[endpoint]
        return endpoint_f(self._instance, *args, **kwargs)

    def execute_error_handler(self, handler, request, failure):
        """
        Execute the passed error handler, possibly with a bound instance.
        """
        return handler(self._instance, request, failure)

    def resource(self):
        """
        Return an L{IResource} which suitably wraps this app.

        @returns: An L{IResource}
        """

        return KleinResource(self)

    def __get__(self, instance, owner):
        """
        Get an instance of L{Klein} bound to C{instance}.
        """
        if instance is None:
            return self

        k = self._bound_klein_instances.get(instance)

        if k is None:
            k = self.__class__()
            k._url_map = self._url_map
            k._endpoints = self._endpoints
            k._error_handlers = self._error_handlers
            k._instance = instance
            self._bound_klein_instances[instance] = k

        return k

    def route(self, url, *args, **kwargs):
        """
        Add a new handler for C{url} passing C{args} and C{kwargs} directly to
        C{werkzeug.routing.Rule}.  The handler function will be passed at least
        one argument an L{twisted.web.server.Request} and any keyword arguments
        taken from the C{url} pattern.

        ::
            @app.route("/")
            def index(request):
                return "Hello"

        @param url: A werkzeug URL pattern given to C{werkzeug.routing.Rule}.
        @type url: str

        @param branch: A bool indiciated if a branch endpoint should
            be added that allows all child path segments that don't
            match some other route to be consumed.  Default C{False}.
        @type branch: bool


        @returns: decorated handler function.
        """
        segment_count = url.count('/')
        if url.endswith('/'):
            segment_count -= 1

        def deco(f):
            kwargs.setdefault('endpoint', f.__name__)
            if kwargs.pop('branch', False):
                branchKwargs = kwargs.copy()
                branchKwargs['endpoint'] = branchKwargs['endpoint'] + '_branch'

                @wraps(f)
                def branch_f(instance, request, *a, **kw):
                    IKleinRequest(request).branch_segments = kw.pop(
                        '__rest__', '').split('/')
                    return _call(instance, f, request, *a, **kw)

                branch_f.segment_count = segment_count

                self._endpoints[branchKwargs['endpoint']] = branch_f
                self._url_map.add(
                    Rule(
                        url.rstrip('/') + '/' + '<path:__rest__>', *args,
                        **branchKwargs))

            @wraps(f)
            def _f(instance, request, *a, **kw):
                return _call(instance, f, request, *a, **kw)

            _f.segment_count = segment_count

            self._endpoints[kwargs['endpoint']] = _f
            self._url_map.add(Rule(url, *args, **kwargs))
            return f

        return deco

    def handle_errors(self, f_or_exception, *additional_exceptions):
        """
        Register an error handler. This decorator supports two syntaxes. The
        simpler of these can be used to register a handler for all C{Exception}
        types::

            @app.handle_errors
            def error_handler(request, failure):
                request.setResponseCode(500)
                return 'Uh oh'

        Alternately, a handler can be registered for one or more specific
        C{Exception} tyes::

            @app.handle_errors(EncodingError, ValidationError):
            def error_handler(request, failure)
                request.setResponseCode(400)
                return failure.getTraceback()

        The handler will be passed a L{twisted.web.server.Request} as well as a
        L{twisted.python.failure.Failure} instance. Error handlers may return a
        deferred, a failure or a response body.

        If more than one error handler is registered, the handlers will be
        executed in the order in which they are defined, until a handler is
        encountered which completes successfully. If no handler completes
        successfully, L{twisted.web.server.Request}'s processingFailed() method
        will be called.

        In addition to handling errors that occur within a route handler, error
        handlers also handle any C{werkzeug.exceptions.HTTPException} which is
        raised during routing. In particular, C{werkzeug.exceptions.NotFound}
        will be raised if no matching route is found, so to return a custom 404
        users can do the following::

            @app.handle_errors(NotFound)
            def error_handler(request, failure):
                request.setResponseCode(404)
                return 'Not found'

        @param f_or_exception: An error handler function, or an C{Exception}
            subclass to scope the decorated handler to.
        @type f_or_exception: C{function} or C{Exception}

        @param additional_exceptions Additional C{Exception} subclasses to
            scope the decorated function to.
        @type additional_exceptions C{list} of C{Exception}s

        @returns: decorated error handler function.
        """
        # Try to detect calls using the "simple" @app.handle_error syntax by
        # introspecting the first argument - if it isn't a type which
        # subclasses Exception we assume the simple syntax was used.
        if not isinstance(f_or_exception, type) or not issubclass(
                f_or_exception, Exception):
            return self.handle_errors(Exception)(f_or_exception)

        def deco(f):
            @wraps(f)
            def _f(instance, request, failure):
                return _call(instance, f, request, failure)

            self._error_handlers.append(
                ([f_or_exception] + list(additional_exceptions), _f))
            return _f

        return deco

    def run(self, host, port, logFile=None):
        """
        Run a minimal twisted.web server on the specified C{port}, bound to the
        interface specified by C{host} and logging to C{logFile}.

        This function will run the default reactor for your platform and so
        will block the main thread of your application.  It should be the last
        thing your klein application does.

        @param host: The hostname or IP address to bind the listening socket
            to.  "0.0.0.0" will allow you to listen on all interfaces, and
            "127.0.0.1" will allow you to listen on just the loopback interface.
        @type host: str

        @param port: The TCP port to accept HTTP requests on.
        @type port: int

        @param logFile: The file object to log to, by default C{sys.stdout}
        @type logFile: file object
        """
        if logFile is None:
            logFile = sys.stdout

        log.startLogging(logFile)
        reactor.listenTCP(port, Site(self.resource()), interface=host)
        reactor.run()
Esempio n. 29
0
class Web:
    url_rule_class = Rule
    response_class = Response

    def __init__(self, config, session_path=".session\\"):
        self.session_path = session_path
        self.view_functions = {}
        self.secret_key = config.get('secret_key', os.urandom(24))
        # 配置redis
        self.redis = redis.Redis(config.get("redis_host", "localhost"),
                                 config.get("redis_port", 6379))
        template_path = os.path.join(os.path.dirname(__file__), "templates")
        self.jinja_env = Environment(loader=FileSystemLoader(template_path),
                                     autoescape=True)
        self.jinja_env.filters["hostname"] = get_hostname
        # 路由空间
        self.url_map = Map([])

    def render_template(self, template_name, **context):
        # 渲染模板
        t = self.jinja_env.get_template(template_name)
        return Response(t.render(context), mimetype="text/html")

    def dispatch_request(self, request):
        adapter = self.url_map.bind_to_environ(request.environ)
        try:
            endpoint, values = adapter.match()
            response = self.view_functions.get(f"{endpoint}")(request,
                                                              **values)
            if isinstance(response, str):
                response = Response(response)
            return response
        except NotFound:
            return self.error_404()
        except HTTPException as e:
            print(e)
            return self.error_500()

    def wsgi_app(self, environ, start_response):
        request = Request(environ)
        response = self.dispatch_request(request)
        import hashlib
        m = hashlib.md5(request.remote_addr.encode())  # 先转成二进制,再加密
        value = m.hexdigest()
        response.set_cookie(key='session_id', value=value)
        return response(environ, start_response)

    def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
        if endpoint is None:
            # 如果没有提供endpoint参数,则默认用view_func的名字
            endpoint = _endpoint_from_view_func(view_func)
        # 把endpoint参数添加到options里面
        options['endpoint'] = endpoint
        # 从options中pop出methods参数,并把值赋给methods变量,如果没有则置为None
        methods = options.pop('methods', None)
        # moehods的值为None的情况下
        if methods is None:
            # 如果view_func函数中有这个methods参数,则使用view_func中的。如果没有则赋一个列表('GET',)给methods
            methods = getattr(view_func, 'methods', None) or ('GET', )
        # 如果methods是字符串类型
        if isinstance(methods, string_types):
            # 抛出一个异常:methods需要是一个可以迭代的字符串
            raise TypeError('methods需要是一个可以迭代的字符串, '
                            '例: @app.route(..., methods=["POST"])')
        # 把methods里面的item都改成大写
        methods = set(item.upper() for item in methods)

        # 在view_func里面定义了一个属性required_methods = ()
        # 作用:用来定义一些必须的方法,配合provide_automatic_options使用
        required_methods = set(getattr(view_func, 'required_methods', ()))

        provide_automatic_options = getattr(view_func,
                                            'provide_automatic_options', None)

        # 判断provide_automati_options是否为None
        if provide_automatic_options is None:
            # 如果OPTIONS字符串没有在methods里面
            if 'OPTIONS' not in methods:
                # 则把provude_automatic_options改为True,并把OPTIONS添加到required_methods里面
                provide_automatic_options = True
                required_methods.add('OPTIONS')
            # 如果OPTIONS在methods里面,则把provide_automatic_options设置为False
            else:
                provide_automatic_options = False

        # 合并required_methods和methods这两个集合到methods里面
        methods |= required_methods

        # 创建路由规则
        # 调用url_rule_class方法,由于在Flask类的全局变量中定义了:url_rule_class = Rule, Rule是werkzeug/routing.py里面的一个类
        # 也就是相当于实例化了Rule得到了rule对象,具体实例化后的结果请看Rule源码分析
        rule = self.url_rule_class(rule, methods=methods, **options)
        # 把provide_automatic_options属性添加到rule对象里面
        rule.provide_automatic_options = provide_automatic_options

        # 在Flask类的__init__里面定义了self.url_map = Map(),Map是werkzeug/routing.py里面的一个类
        # self.url_map相当与实例化了Map,.add则是调用了Map类里面的add方法
        # 具体运行结果,请参考Map源码分析,以及Map源码中的add方法分析
        self.url_map.add(rule)
        # 如果提供了view_func
        if view_func is not None:
            # 在flask类的__init__里面定义了self.view_functions = {},
            # 从字典里面取endpoint值并赋值为old_func,(endpoint是传递的参数,默认为视图函数名)
            old_func = self.view_functions.get(endpoint)
            # 如果old_func有值,并且不等于view_func
            if old_func is not None and old_func != view_func:
                # 则抛出异常:视图函数映射被一个已经存在的函数名重写了
                # 也就是说已经存在了一个endpoint:old_func的映射,但是old_fun却不是view_func,也就是说endpoint重复了
                raise AssertionError('视图函数映射被一个已经存在的函数名重写了:' ' %s' % endpoint)
            # 添加视图函数与endpoint映射到view_functions字典里面
            self.view_functions[endpoint] = view_func

    def route(self, rule, **options):
        def decorator(f):
            endpoint = options.pop("endpoint", None)
            self.add_url_rule(rule, endpoint, f, **options)
            return f

        return decorator

    def error_500(self):
        response = self.render_template("500.html")
        response.status_code = 500
        return response

    def error_404(self):
        response = self.render_template("404.html")
        response.status_code = 404
        return response

    def __call__(self, environ, start_response):
        return self.wsgi_app(environ, start_response)
Esempio n. 30
0
class Flask(_PackageBoundObject):
    """The flask object implements a WSGI application and acts as the central
    object.  It is passed the name of the module or package of the
    application.  Once it is created it will act as a central registry for
    the view functions, the URL rules, template configuration and much more.

    The name of the package is used to resolve resources from inside the
    package or the folder the module is contained in depending on if the
    package parameter resolves to an actual python package (a folder with
    an `__init__.py` file inside) or a standard module (just a `.py` file).

    For more information about resource loading, see :func:`open_resource`.

    Usually you create a :class:`Flask` instance in your main module or
    in the `__init__.py` file of your package like this::

        from flask import Flask
        app = Flask(__name__)

    .. admonition:: About the First Parameter

        The idea of the first parameter is to give Flask an idea what
        belongs to your application.  This name is used to find resources
        on the file system, can be used by extensions to improve debugging
        information and a lot more.

        So it's important what you provide there.  If you are using a single
        module, `__name__` is always the correct value.  If you however are
        using a package, it's usually recommended to hardcode the name of
        your package there.

        For example if your application is defined in `yourapplication/app.py`
        you should create it with one of the two versions below::

            app = Flask('yourapplication')
            app = Flask(__name__.split('.')[0])

        Why is that?  The application will work even with `__name__`, thanks
        to how resources are looked up.  However it will make debugging more
        painful.  Certain extensions can make assumptions based on the
        import name of your application.  For example the Flask-SQLAlchemy
        extension will look for the code in your application that triggered
        an SQL query in debug mode.  If the import name is not properly set
        up, that debugging information is lost.  (For example it would only
        pick up SQL queries in `yourapplicaiton.app` and not
        `yourapplication.views.frontend`)

    .. versionadded:: 0.5
       The `static_path` parameter was added.

    :param import_name: the name of the application package
    :param static_path: can be used to specify a different path for the
                        static files on the web.  Defaults to ``/static``.
                        This does not affect the folder the files are served
                        *from*.
    """

    #: The class that is used for request objects.  See :class:`~flask.Request`
    #: for more information.
    request_class = Request

    #: The class that is used for response objects.  See
    #: :class:`~flask.Response` for more information.
    response_class = Response

    #: Path for the static files.  If you don't want to use static files
    #: you can set this value to `None` in which case no URL rule is added
    #: and the development server will no longer serve any static files.
    #:
    #: This is the default used for application and modules unless a
    #: different value is passed to the constructor.
    static_path = '/static'

    #: The debug flag.  Set this to `True` to enable debugging of the
    #: application.  In debug mode the debugger will kick in when an unhandled
    #: exception ocurrs and the integrated server will automatically reload
    #: the application if changes in the code are detected.
    #:
    #: This attribute can also be configured from the config with the `DEBUG`
    #: configuration key.  Defaults to `False`.
    debug = ConfigAttribute('DEBUG')

    #: The testing flask.  Set this to `True` to enable the test mode of
    #: Flask extensions (and in the future probably also Flask itself).
    #: For example this might activate unittest helpers that have an
    #: additional runtime cost which should not be enabled by default.
    #:
    #: This attribute can also be configured from the config with the
    #: `TESTING` configuration key.  Defaults to `False`.
    testing = ConfigAttribute('TESTING')

    #: If a secret key is set, cryptographic components can use this to
    #: sign cookies and other things.  Set this to a complex random value
    #: when you want to use the secure cookie for instance.
    #:
    #: This attribute can also be configured from the config with the
    #: `SECRET_KEY` configuration key.  Defaults to `None`.
    secret_key = ConfigAttribute('SECRET_KEY')

    #: The secure cookie uses this for the name of the session cookie.
    #:
    #: This attribute can also be configured from the config with the
    #: `SESSION_COOKIE_NAME` configuration key.  Defaults to ``'session'``
    session_cookie_name = ConfigAttribute('SESSION_COOKIE_NAME')

    #: A :class:`~datetime.timedelta` which is used to set the expiration
    #: date of a permanent session.  The default is 31 days which makes a
    #: permanent session survive for roughly one month.
    #:
    #: This attribute can also be configured from the config with the
    #: `PERMANENT_SESSION_LIFETIME` configuration key.  Defaults to
    #: ``timedelta(days=31)``
    permanent_session_lifetime = ConfigAttribute('PERMANENT_SESSION_LIFETIME')

    #: Enable this if you want to use the X-Sendfile feature.  Keep in
    #: mind that the server has to support this.  This only affects files
    #: sent with the :func:`send_file` method.
    #:
    #: .. versionadded:: 0.2
    #:
    #: This attribute can also be configured from the config with the
    #: `USE_X_SENDFILE` configuration key.  Defaults to `False`.
    use_x_sendfile = ConfigAttribute('USE_X_SENDFILE')

    #: The name of the logger to use.  By default the logger name is the
    #: package name passed to the constructor.
    #:
    #: .. versionadded:: 0.4
    logger_name = ConfigAttribute('LOGGER_NAME')

    #: The logging format used for the debug logger.  This is only used when
    #: the application is in debug mode, otherwise the attached logging
    #: handler does the formatting.
    #:
    #: .. versionadded:: 0.3
    debug_log_format = (
        '-' * 80 + '\n' +
        '%(levelname)s in %(module)s [%(pathname)s:%(lineno)d]:\n' +
        '%(message)s\n' + '-' * 80)

    #: Options that are passed directly to the Jinja2 environment.
    jinja_options = ImmutableDict(
        extensions=['jinja2.ext.autoescape', 'jinja2.ext.with_'])

    #: Default configuration parameters.
    default_config = ImmutableDict({
        'DEBUG':
        False,
        'TESTING':
        False,
        'SECRET_KEY':
        None,
        'SESSION_COOKIE_NAME':
        'session',
        'PERMANENT_SESSION_LIFETIME':
        timedelta(days=31),
        'USE_X_SENDFILE':
        False,
        'LOGGER_NAME':
        None,
        'SERVER_NAME':
        None
    })

    def __init__(self, import_name, static_path=None):
        _PackageBoundObject.__init__(self, import_name)
        if static_path is not None:
            self.static_path = static_path

        #: The configuration dictionary as :class:`Config`.  This behaves
        #: exactly like a regular dictionary but supports additional methods
        #: to load a config from files.
        self.config = Config(self.root_path, self.default_config)

        #: Prepare the deferred setup of the logger.
        self._logger = None
        self.logger_name = self.import_name

        #: A dictionary of all view functions registered.  The keys will
        #: be function names which are also used to generate URLs and
        #: the values are the function objects themselves.
        #: to register a view function, use the :meth:`route` decorator.
        self.view_functions = {}

        #: A dictionary of all registered error handlers.  The key is
        #: be the error code as integer, the value the function that
        #: should handle that error.
        #: To register a error handler, use the :meth:`errorhandler`
        #: decorator.
        self.error_handlers = {}

        #: A dictionary with lists of functions that should be called at the
        #: beginning of the request.  The key of the dictionary is the name of
        #: the module this function is active for, `None` for all requests.
        #: This can for example be used to open database connections or
        #: getting hold of the currently logged in user.  To register a
        #: function here, use the :meth:`before_request` decorator.
        self.before_request_funcs = {}

        #: A dictionary with lists of functions that should be called after
        #: each request.  The key of the dictionary is the name of the module
        #: this function is active for, `None` for all requests.  This can for
        #: example be used to open database connections or getting hold of the
        #: currently logged in user.  To register a function here, use the
        #: :meth:`before_request` decorator.
        self.after_request_funcs = {}

        #: A dictionary with list of functions that are called without argument
        #: to populate the template context.  They key of the dictionary is the
        #: name of the module this function is active for, `None` for all
        #: requests.  Each returns a dictionary that the template context is
        #: updated with.  To register a function here, use the
        #: :meth:`context_processor` decorator.
        self.template_context_processors = {
            None: [_default_template_ctx_processor]
        }

        #: all the loaded modules in a dictionary by name.
        #:
        #: .. versionadded:: 0.5
        self.modules = {}

        #: The :class:`~werkzeug.routing.Map` for this instance.  You can use
        #: this to change the routing converters after the class was created
        #: but before any routes are connected.  Example::
        #:
        #:    from werkzeug import BaseConverter
        #:
        #:    class ListConverter(BaseConverter):
        #:        def to_python(self, value):
        #:            return value.split(',')
        #:        def to_url(self, values):
        #:            return ','.join(BaseConverter.to_url(value)
        #:                            for value in values)
        #:
        #:    app = Flask(__name__)
        #:    app.url_map.converters['list'] = ListConverter
        self.url_map = Map()

        # if there is a static folder, register it for the application.
        if self.has_static_folder:
            self.add_url_rule(self.static_path + '/<path:filename>',
                              endpoint='static',
                              view_func=self.send_static_file)

        #: The Jinja2 environment.  It is created from the
        #: :attr:`jinja_options`.
        self.jinja_env = self.create_jinja_environment()
        self.init_jinja_globals()

    @property
    def logger(self):
        """A :class:`logging.Logger` object for this application.  The
        default configuration is to log to stderr if the application is
        in debug mode.  This logger can be used to (surprise) log messages.
        Here some examples::

            app.logger.debug('A value for debugging')
            app.logger.warning('A warning ocurred (%d apples)', 42)
            app.logger.error('An error occoured')

        .. versionadded:: 0.3
        """
        if self._logger and self._logger.name == self.logger_name:
            return self._logger
        with _logger_lock:
            if self._logger and self._logger.name == self.logger_name:
                return self._logger
            from flask.logging import create_logger
            self._logger = rv = create_logger(self)
            return rv

    def create_jinja_environment(self):
        """Creates the Jinja2 environment based on :attr:`jinja_options`
        and :meth:`create_jinja_loader`.

        .. versionadded:: 0.5
        """
        options = dict(self.jinja_options)
        if 'autoescape' not in options:
            options['autoescape'] = self.select_jinja_autoescape
        return Environment(loader=_DispatchingJinjaLoader(self), **options)

    def init_jinja_globals(self):
        """Called directly after the environment was created to inject
        some defaults (like `url_for`, `get_flashed_messages` and the
        `tojson` filter.

        .. versionadded:: 0.5
        """
        self.jinja_env.globals.update(
            url_for=url_for, get_flashed_messages=get_flashed_messages)
        self.jinja_env.filters['tojson'] = _tojson_filter

    def select_jinja_autoescape(self, filename):
        """Returns `True` if autoescaping should be active for the given
        template name.

        .. versionadded:: 0.5
        """
        if filename is None:
            return False
        return filename.endswith(('.html', '.htm', '.xml', '.xhtml'))

    def update_template_context(self, context):
        """Update the template context with some commonly used variables.
        This injects request, session and g into the template context.

        :param context: the context as a dictionary that is updated in place
                        to add extra variables.
        """
        funcs = self.template_context_processors[None]
        mod = _request_ctx_stack.top.request.module
        if mod is not None and mod in self.template_context_processors:
            funcs = chain(funcs, self.template_context_processors[mod])
        for func in funcs:
            context.update(func())

    def run(self, host='127.0.0.1', port=5000, **options):
        """Runs the application on a local development server.  If the
        :attr:`debug` flag is set the server will automatically reload
        for code changes and show a debugger in case an exception happened.

        .. admonition:: Keep in Mind

           Flask will suppress any server error with a generic error page
           unless it is in debug mode.  As such to enable just the
           interactive debugger without the code reloading, you have to
           invoke :meth:`run` with ``debug=True`` and ``use_reloader=False``.
           Setting ``use_debugger`` to `True` without being in debug mode
           won't catch any exceptions because there won't be any to
           catch.

        :param host: the hostname to listen on.  set this to ``'0.0.0.0'``
                     to have the server available externally as well.
        :param port: the port of the webserver
        :param options: the options to be forwarded to the underlying
                        Werkzeug server.  See :func:`werkzeug.run_simple`
                        for more information.
        """
        from werkzeug import run_simple
        if 'debug' in options:
            self.debug = options.pop('debug')
        options.setdefault('use_reloader', self.debug)
        options.setdefault('use_debugger', self.debug)
        return run_simple(host, port, self, **options)

    def test_client(self):
        """Creates a test client for this application.  For information
        about unit testing head over to :ref:`testing`.

        The test client can be used in a `with` block to defer the closing down
        of the context until the end of the `with` block.  This is useful if
        you want to access the context locals for testing::

            with app.test_client() as c:
                rv = c.get('/?vodka=42')
                assert request.args['vodka'] == '42'

        .. versionchanged:: 0.4
           added support for `with` block usage for the client.
        """
        from flask.testing import FlaskClient
        return FlaskClient(self, self.response_class, use_cookies=True)

    def open_session(self, request):
        """Creates or opens a new session.  Default implementation stores all
        session data in a signed cookie.  This requires that the
        :attr:`secret_key` is set.

        :param request: an instance of :attr:`request_class`.
        """
        key = self.secret_key
        if key is not None:
            return Session.load_cookie(request,
                                       self.session_cookie_name,
                                       secret_key=key)

    def save_session(self, session, response):
        """Saves the session if it needs updates.  For the default
        implementation, check :meth:`open_session`.

        :param session: the session to be saved (a
                        :class:`~werkzeug.contrib.securecookie.SecureCookie`
                        object)
        :param response: an instance of :attr:`response_class`
        """
        expires = domain = None
        if session.permanent:
            expires = datetime.utcnow() + self.permanent_session_lifetime
        if self.config['SERVER_NAME'] is not None:
            domain = '.' + self.config['SERVER_NAME']
        session.save_cookie(response,
                            self.session_cookie_name,
                            expires=expires,
                            httponly=True,
                            domain=domain)

    def register_module(self, module, **options):
        """Registers a module with this application.  The keyword argument
        of this function are the same as the ones for the constructor of the
        :class:`Module` class and will override the values of the module if
        provided.
        """
        options.setdefault('url_prefix', module.url_prefix)
        state = _ModuleSetupState(self, **options)
        for func in module._register_events:
            func(state)

    def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
        """Connects a URL rule.  Works exactly like the :meth:`route`
        decorator.  If a view_func is provided it will be registered with the
        endpoint.

        Basically this example::

            @app.route('/')
            def index():
                pass

        Is equivalent to the following::

            def index():
                pass
            app.add_url_rule('/', 'index', index)

        If the view_func is not provided you will need to connect the endpoint
        to a view function like so::

            app.view_functions['index'] = index

        .. versionchanged:: 0.2
           `view_func` parameter added.

        :param rule: the URL rule as string
        :param endpoint: the endpoint for the registered URL rule.  Flask
                         itself assumes the name of the view function as
                         endpoint
        :param view_func: the function to call when serving a request to the
                          provided endpoint
        :param options: the options to be forwarded to the underlying
                        :class:`~werkzeug.routing.Rule` object
        """
        if endpoint is None:
            assert view_func is not None, 'expected view func if endpoint ' \
                                          'is not provided.'
            endpoint = view_func.__name__
        options['endpoint'] = endpoint
        options.setdefault('methods', ('GET', ))
        self.url_map.add(Rule(rule, **options))
        if view_func is not None:
            self.view_functions[endpoint] = view_func

    def route(self, rule, **options):
        """A decorator that is used to register a view function for a
        given URL rule.  Example::

            @app.route('/')
            def index():
                return 'Hello World'

        Variables parts in the route can be specified with angular
        brackets (``/user/<username>``).  By default a variable part
        in the URL accepts any string without a slash however a different
        converter can be specified as well by using ``<converter:name>``.

        Variable parts are passed to the view function as keyword
        arguments.

        The following converters are possible:

        =========== ===========================================
        `int`       accepts integers
        `float`     like `int` but for floating point values
        `path`      like the default but also accepts slashes
        =========== ===========================================

        Here some examples::

            @app.route('/')
            def index():
                pass

            @app.route('/<username>')
            def show_user(username):
                pass

            @app.route('/post/<int:post_id>')
            def show_post(post_id):
                pass

        An important detail to keep in mind is how Flask deals with trailing
        slashes.  The idea is to keep each URL unique so the following rules
        apply:

        1. If a rule ends with a slash and is requested without a slash
           by the user, the user is automatically redirected to the same
           page with a trailing slash attached.
        2. If a rule does not end with a trailing slash and the user request
           the page with a trailing slash, a 404 not found is raised.

        This is consistent with how web servers deal with static files.  This
        also makes it possible to use relative link targets safely.

        The :meth:`route` decorator accepts a couple of other arguments
        as well:

        :param rule: the URL rule as string
        :param methods: a list of methods this rule should be limited
                        to (``GET``, ``POST`` etc.).  By default a rule
                        just listens for ``GET`` (and implicitly ``HEAD``).
        :param subdomain: specifies the rule for the subdomain in case
                          subdomain matching is in use.
        :param strict_slashes: can be used to disable the strict slashes
                               setting for this rule.  See above.
        :param options: other options to be forwarded to the underlying
                        :class:`~werkzeug.routing.Rule` object.
        """
        def decorator(f):
            self.add_url_rule(rule, None, f, **options)
            return f

        return decorator

    def errorhandler(self, code):
        """A decorator that is used to register a function give a given
        error code.  Example::

            @app.errorhandler(404)
            def page_not_found(error):
                return 'This page does not exist', 404

        You can also register a function as error handler without using
        the :meth:`errorhandler` decorator.  The following example is
        equivalent to the one above::

            def page_not_found(error):
                return 'This page does not exist', 404
            app.error_handlers[404] = page_not_found

        :param code: the code as integer for the handler
        """
        def decorator(f):
            self.error_handlers[code] = f
            return f

        return decorator

    def template_filter(self, name=None):
        """A decorator that is used to register custom template filter.
        You can specify a name for the filter, otherwise the function
        name will be used. Example::

          @app.template_filter()
          def reverse(s):
              return s[::-1]

        :param name: the optional name of the filter, otherwise the
                     function name will be used.
        """
        def decorator(f):
            self.jinja_env.filters[name or f.__name__] = f
            return f

        return decorator

    def before_request(self, f):
        """Registers a function to run before each request."""
        self.before_request_funcs.setdefault(None, []).append(f)
        return f

    def after_request(self, f):
        """Register a function to be run after each request."""
        self.after_request_funcs.setdefault(None, []).append(f)
        return f

    def context_processor(self, f):
        """Registers a template context processor function."""
        self.template_context_processors[None].append(f)
        return f

    def handle_http_exception(self, e):
        """Handles an HTTP exception.  By default this will invoke the
        registered error handlers and fall back to returning the
        exception as response.

        .. versionadded: 0.3
        """
        handler = self.error_handlers.get(e.code)
        if handler is None:
            return e
        return handler(e)

    def handle_exception(self, e):
        """Default exception handling that kicks in when an exception
        occours that is not catched.  In debug mode the exception will
        be re-raised immediately, otherwise it is logged and the handler
        for a 500 internal server error is used.  If no such handler
        exists, a default 500 internal server error message is displayed.

        .. versionadded: 0.3
        """
        handler = self.error_handlers.get(500)
        if self.debug:
            raise
        self.logger.exception('Exception on %s [%s]' %
                              (request.path, request.method))
        if handler is None:
            return InternalServerError()
        return handler(e)

    def dispatch_request(self):
        """Does the request dispatching.  Matches the URL and returns the
        return value of the view or error handler.  This does not have to
        be a response object.  In order to convert the return value to a
        proper response object, call :func:`make_response`.
        """
        req = _request_ctx_stack.top.request
        try:
            if req.routing_exception is not None:
                raise req.routing_exception
            return self.view_functions[req.endpoint](**req.view_args)
        except HTTPException, e:
            return self.handle_http_exception(e)
Esempio n. 31
0
class Flask(_PackageBoundObject):
    """The flask object implements a WSGI application and acts as the central
    object.  It is passed the name of the module or package of the
    application.  Once it is created it will act as a central registry for
    the view functions, the URL rules, template configuration and much more.

    The name of the package is used to resolve resources from inside the
    package or the folder the module is contained in depending on if the
    package parameter resolves to an actual python package (a folder with
    an `__init__.py` file inside) or a standard module (just a `.py` file).

    For more information about resource loading, see :func:`open_resource`.

    Usually you create a :class:`Flask` instance in your main module or
    in the `__init__.py` file of your package like this::

        from flask import Flask
        app = Flask(__name__)
    """

    #: the class that is used for request objects.  See :class:`~flask.Request`
    #: for more information.
    request_class = Request

    #: the class that is used for response objects.  See
    #: :class:`~flask.Response` for more information.
    response_class = Response

    #: path for the static files.  If you don't want to use static files
    #: you can set this value to `None` in which case no URL rule is added
    #: and the development server will no longer serve any static files.
    static_path = "/static"

    #: if a secret key is set, cryptographic components can use this to
    #: sign cookies and other things.  Set this to a complex random value
    #: when you want to use the secure cookie for instance.
    secret_key = None

    #: The secure cookie uses this for the name of the session cookie
    session_cookie_name = "session"

    #: A :class:`~datetime.timedelta` which is used to set the expiration
    #: date of a permanent session.  The default is 31 days which makes a
    #: permanent session survive for roughly one month.
    permanent_session_lifetime = timedelta(days=31)

    #: Enable this if you want to use the X-Sendfile feature.  Keep in
    #: mind that the server has to support this.  This only affects files
    #: sent with the :func:`send_file` method.
    #:
    #: .. versionadded:: 0.2
    use_x_sendfile = False

    #: the logging format used for the debug logger.  This is only used when
    #: the application is in debug mode, otherwise the attached logging
    #: handler does the formatting.
    #:
    #: .. versionadded:: 0.5
    debug_log_format = (
        "-" * 80 + "\n" + "%(levelname)s in %(module)s, %(pathname)s:%(lineno)d]:\n" + "%(message)s\n" + "-" * 80
    )

    #: options that are passed directly to the Jinja2 environment
    jinja_options = ImmutableDict(autoescape=True, extensions=["jinja2.ext.autoescape", "jinja2.ext.with_"])

    def __init__(self, import_name):
        _PackageBoundObject.__init__(self, import_name)

        #: the debug flag.  Set this to `True` to enable debugging of
        #: the application.  In debug mode the debugger will kick in
        #: when an unhandled exception ocurrs and the integrated server
        #: will automatically reload the application if changes in the
        #: code are detected.
        self.debug = False

        #: a dictionary of all view functions registered.  The keys will
        #: be function names which are also used to generate URLs and
        #: the values are the function objects themselves.
        #: to register a view function, use the :meth:`route` decorator.
        self.view_functions = {}

        #: a dictionary of all registered error handlers.  The key is
        #: be the error code as integer, the value the function that
        #: should handle that error.
        #: To register a error handler, use the :meth:`errorhandler`
        #: decorator.
        self.error_handlers = {}

        #: a dictionary with lists of functions that should be called at the
        #: beginning of the request.  The key of the dictionary is the name of
        #: the module this function is active for, `None` for all requests.
        #: This can for example be used to open database connections or
        #: getting hold of the currently logged in user.  To register a
        #: function here, use the :meth:`before_request` decorator.
        self.before_request_funcs = {}

        #: a dictionary with lists of functions that should be called after
        #: each request.  The key of the dictionary is the name of the module
        #: this function is active for, `None` for all requests.  This can for
        #: example be used to open database connections or getting hold of the
        #: currently logged in user.  To register a function here, use the
        #: :meth:`before_request` decorator.
        self.after_request_funcs = {}

        #: a dictionary with list of functions that are called without argument
        #: to populate the template context.  They key of the dictionary is the
        #: name of the module this function is active for, `None` for all
        #: requests.  Each returns a dictionary that the template context is
        #: updated with.  To register a function here, use the
        #: :meth:`context_processor` decorator.
        self.template_context_processors = {None: [_default_template_ctx_processor]}

        #: the :class:`~werkzeug.routing.Map` for this instance.  You can use
        #: this to change the routing converters after the class was created
        #: but before any routes are connected.  Example::
        #:
        #:    from werkzeug import BaseConverter
        #:
        #:    class ListConverter(BaseConverter):
        #:        def to_python(self, value):
        #:            return value.split(',')
        #:        def to_url(self, values):
        #:            return ','.join(BaseConverter.to_url(value)
        #:                            for value in values)
        #:
        #:    app = Flask(__name__)
        #:    app.url_map.converters['list'] = ListConverter
        self.url_map = Map()

        if self.static_path is not None:
            self.add_url_rule(self.static_path + "/<filename>", build_only=True, endpoint="static")
            if pkg_resources is not None:
                target = (self.import_name, "static")
            else:
                target = os.path.join(self.root_path, "static")
            self.wsgi_app = SharedDataMiddleware(self.wsgi_app, {self.static_path: target})

        #: the Jinja2 environment.  It is created from the
        #: :attr:`jinja_options` and the loader that is returned
        #: by the :meth:`create_jinja_loader` function.
        self.jinja_env = Environment(loader=self.create_jinja_loader(), **self.jinja_options)
        self.jinja_env.globals.update(url_for=url_for, get_flashed_messages=get_flashed_messages)
        self.jinja_env.filters["tojson"] = _tojson_filter

    @cached_property
    def logger(self):
        """A :class:`logging.Logger` object for this application.  The
        default configuration is to log to stderr if the application is
        in debug mode.  This logger can be used to (surprise) log messages.
        Here some examples::

            app.logger.debug('A value for debugging')
            app.logger.warning('A warning ocurred (%d apples)', 42)
            app.logger.error('An error occoured')

        .. versionadded:: 0.5
        """
        from logging import getLogger, StreamHandler, Formatter, DEBUG

        class DebugHandler(StreamHandler):
            def emit(x, record):
                if self.debug:
                    StreamHandler.emit(x, record)

        handler = DebugHandler()
        handler.setLevel(DEBUG)
        handler.setFormatter(Formatter(self.debug_log_format))
        logger = getLogger(self.import_name)
        logger.addHandler(handler)
        return logger

    def create_jinja_loader(self):
        """Creates the Jinja loader.  By default just a package loader for
        the configured package is returned that looks up templates in the
        `templates` folder.  To add other loaders it's possible to
        override this method.
        """
        if pkg_resources is None:
            return FileSystemLoader(os.path.join(self.root_path, "templates"))
        return PackageLoader(self.import_name)

    def update_template_context(self, context):
        """Update the template context with some commonly used variables.
        This injects request, session and g into the template context.

        :param context: the context as a dictionary that is updated in place
                        to add extra variables.
        """
        funcs = self.template_context_processors[None]
        mod = _request_ctx_stack.top.request.module
        if mod is not None and mod in self.template_context_processors:
            funcs = chain(funcs, self.template_context_processors[mod])
        for func in funcs:
            context.update(func())

    def run(self, host="127.0.0.1", port=5000, **options):
        """Runs the application on a local development server.  If the
        :attr:`debug` flag is set the server will automatically reload
        for code changes and show a debugger in case an exception happened.

        :param host: the hostname to listen on.  set this to ``'0.0.0.0'``
                     to have the server available externally as well.
        :param port: the port of the webserver
        :param options: the options to be forwarded to the underlying
                        Werkzeug server.  See :func:`werkzeug.run_simple`
                        for more information.
        """
        from werkzeug import run_simple

        if "debug" in options:
            self.debug = options.pop("debug")
        options.setdefault("use_reloader", self.debug)
        options.setdefault("use_debugger", self.debug)
        return run_simple(host, port, self, **options)

    def test_client(self):
        """Creates a test client for this application.  For information
        about unit testing head over to :ref:`testing`.
        """
        from werkzeug import Client

        return Client(self, self.response_class, use_cookies=True)

    def open_session(self, request):
        """Creates or opens a new session.  Default implementation stores all
        session data in a signed cookie.  This requires that the
        :attr:`secret_key` is set.

        :param request: an instance of :attr:`request_class`.
        """
        key = self.secret_key
        if key is not None:
            return Session.load_cookie(request, self.session_cookie_name, secret_key=key)

    def save_session(self, session, response):
        """Saves the session if it needs updates.  For the default
        implementation, check :meth:`open_session`.

        :param session: the session to be saved (a
                        :class:`~werkzeug.contrib.securecookie.SecureCookie`
                        object)
        :param response: an instance of :attr:`response_class`
        """
        expires = None
        if session.permanent:
            expires = datetime.utcnow() + self.permanent_session_lifetime
        session.save_cookie(response, self.session_cookie_name, expires=expires, httponly=True)

    def register_module(self, module, **options):
        """Registers a module with this application.  The keyword argument
        of this function are the same as the ones for the constructor of the
        :class:`Module` class and will override the values of the module if
        provided.
        """
        options.setdefault("url_prefix", module.url_prefix)
        state = _ModuleSetupState(self, **options)
        for func in module._register_events:
            func(state)

    def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
        """Connects a URL rule.  Works exactly like the :meth:`route`
        decorator.  If a view_func is provided it will be registered with the
        endpoint.

        Basically this example::

            @app.route('/')
            def index():
                pass

        Is equivalent to the following::

            def index():
                pass
            app.add_url_rule('/', 'index', index)

        If the view_func is not provided you will need to connect the endpoint
        to a view function like so::

            app.view_functions['index'] = index

        .. versionchanged:: 0.2
           `view_func` parameter added.

        :param rule: the URL rule as string
        :param endpoint: the endpoint for the registered URL rule.  Flask
                         itself assumes the name of the view function as
                         endpoint
        :param view_func: the function to call when serving a request to the
                          provided endpoint
        :param options: the options to be forwarded to the underlying
                        :class:`~werkzeug.routing.Rule` object
        """
        if endpoint is None:
            assert view_func is not None, "expected view func if endpoint " "is not provided."
            endpoint = view_func.__name__
        options["endpoint"] = endpoint
        options.setdefault("methods", ("GET",))
        self.url_map.add(Rule(rule, **options))
        if view_func is not None:
            self.view_functions[endpoint] = view_func

    def route(self, rule, **options):
        """A decorator that is used to register a view function for a
        given URL rule.  Example::

            @app.route('/')
            def index():
                return 'Hello World'

        Variables parts in the route can be specified with angular
        brackets (``/user/<username>``).  By default a variable part
        in the URL accepts any string without a slash however a different
        converter can be specified as well by using ``<converter:name>``.

        Variable parts are passed to the view function as keyword
        arguments.

        The following converters are possible:

        =========== ===========================================
        `int`       accepts integers
        `float`     like `int` but for floating point values
        `path`      like the default but also accepts slashes
        =========== ===========================================

        Here some examples::

            @app.route('/')
            def index():
                pass

            @app.route('/<username>')
            def show_user(username):
                pass

            @app.route('/post/<int:post_id>')
            def show_post(post_id):
                pass

        An important detail to keep in mind is how Flask deals with trailing
        slashes.  The idea is to keep each URL unique so the following rules
        apply:

        1. If a rule ends with a slash and is requested without a slash
           by the user, the user is automatically redirected to the same
           page with a trailing slash attached.
        2. If a rule does not end with a trailing slash and the user request
           the page with a trailing slash, a 404 not found is raised.

        This is consistent with how web servers deal with static files.  This
        also makes it possible to use relative link targets safely.

        The :meth:`route` decorator accepts a couple of other arguments
        as well:

        :param rule: the URL rule as string
        :param methods: a list of methods this rule should be limited
                        to (``GET``, ``POST`` etc.).  By default a rule
                        just listens for ``GET`` (and implicitly ``HEAD``).
        :param subdomain: specifies the rule for the subdoain in case
                          subdomain matching is in use.
        :param strict_slashes: can be used to disable the strict slashes
                               setting for this rule.  See above.
        :param options: other options to be forwarded to the underlying
                        :class:`~werkzeug.routing.Rule` object.
        """

        def decorator(f):
            self.add_url_rule(rule, None, f, **options)
            return f

        return decorator

    def errorhandler(self, code):
        """A decorator that is used to register a function give a given
        error code.  Example::

            @app.errorhandler(404)
            def page_not_found(error):
                return 'This page does not exist', 404

        You can also register a function as error handler without using
        the :meth:`errorhandler` decorator.  The following example is
        equivalent to the one above::

            def page_not_found(error):
                return 'This page does not exist', 404
            app.error_handlers[404] = page_not_found

        :param code: the code as integer for the handler
        """

        def decorator(f):
            self.error_handlers[code] = f
            return f

        return decorator

    def template_filter(self, name=None):
        """A decorator that is used to register custom template filter.
        You can specify a name for the filter, otherwise the function
        name will be used. Example::

          @app.template_filter()
          def reverse(s):
              return s[::-1]

        :param name: the optional name of the filter, otherwise the
                     function name will be used.
        """

        def decorator(f):
            self.jinja_env.filters[name or f.__name__] = f
            return f

        return decorator

    def before_request(self, f):
        """Registers a function to run before each request."""
        self.before_request_funcs.setdefault(None, []).append(f)
        return f

    def after_request(self, f):
        """Register a function to be run after each request."""
        self.after_request_funcs.setdefault(None, []).append(f)
        return f

    def context_processor(self, f):
        """Registers a template context processor function."""
        self.template_context_processors[None].append(f)
        return f

    def handle_http_exception(self, e):
        """Handles an HTTP exception.  By default this will invoke the
        registered error handlers and fall back to returning the
        exception as response.

        .. versionadded: 0.5
        """
        handler = self.error_handlers.get(e.code)
        if handler is None:
            return e
        return handler(e)

    def handle_exception(self, e):
        """Default exception handling that kicks in when an exception
        occours that is not catched.  In debug mode the exception will
        be re-raised immediately, otherwise it is logged an the handler
        for an 500 internal server error is used.  If no such handler
        exists, a default 500 internal server error message is displayed.

        .. versionadded: 0.5
        """
        handler = self.error_handlers.get(500)
        if self.debug:
            raise
        self.logger.exception("Exception on %s [%s]" % (request.path, request.method))
        if handler is None:
            return InternalServerError()
        return handler(e)

    def dispatch_request(self):
        """Does the request dispatching.  Matches the URL and returns the
        return value of the view or error handler.  This does not have to
        be a response object.  In order to convert the return value to a
        proper response object, call :func:`make_response`.
        """
        req = _request_ctx_stack.top.request
        try:
            if req.routing_exception is not None:
                raise req.routing_exception
            return self.view_functions[req.endpoint](**req.view_args)
        except HTTPException, e:
            return self.handle_http_exception(e)
Esempio n. 32
0
class Klein(object):
    """
    L{Klein} is an object which is responsible for maintaining the routing
    configuration of our application.

    @ivar _url_map: A C{werkzeug.routing.Map} object which will be used for
        routing resolution.
    @ivar _endpoints: A C{dict} mapping endpoint names to handler functions.
    """

    _subroute_segments = 0

    def __init__(self):
        self._url_map = Map()
        self._endpoints = {}
        self._error_handlers = []
        self._instance = None
        self._boundAs = None

    def __eq__(self, other):
        if isinstance(other, Klein):
            return vars(self) == vars(other)
        return NotImplemented

    def __ne__(self, other):
        result = self.__eq__(other)
        if result is NotImplemented:
            return result
        return not result

    @property
    def url_map(self):
        """
        Read only property exposing L{Klein._url_map}.
        """
        return self._url_map

    @property
    def endpoints(self):
        """
        Read only property exposing L{Klein._endpoints}.
        """
        return self._endpoints

    def execute_endpoint(self, endpoint, *args, **kwargs):
        """
        Execute the named endpoint with all arguments and possibly a bound
        instance.
        """
        endpoint_f = self._endpoints[endpoint]
        return endpoint_f(self._instance, *args, **kwargs)

    def execute_error_handler(self, handler, request, failure):
        """
        Execute the passed error handler, possibly with a bound instance.
        """
        return handler(self._instance, request, failure)

    def resource(self):
        """
        Return an L{IResource} which suitably wraps this app.

        @returns: An L{IResource}
        """

        return KleinResource(self)

    def __get__(self, instance, owner):
        """
        Get an instance of L{Klein} bound to C{instance}.
        """
        if instance is None:
            return self

        if self._boundAs is None:
            for name in dir(owner):
                obj = getattr(owner, name)
                if obj is self:
                    self._boundAs = name
                    break
            else:
                self._boundAs = 'unknown_' + str(id(self))

        boundName = "__klein_bound_{}__".format(self._boundAs)
        k = getattr(instance, boundName, lambda: None)()

        if k is None:
            k = self.__class__()
            k._url_map = self._url_map
            k._endpoints = self._endpoints
            k._error_handlers = self._error_handlers
            k._instance = instance
            kref = ref(k)
            try:
                setattr(instance, boundName, kref)
            except AttributeError:
                pass

        return k

    @staticmethod
    def _segments_in_url(url):
        segment_count = url.count('/')
        if url.endswith('/'):
            segment_count -= 1
        return segment_count

    def route(self, url, *args, **kwargs):
        """
        Add a new handler for C{url} passing C{args} and C{kwargs} directly to
        C{werkzeug.routing.Rule}.  The handler function will be passed at least
        one argument an L{twisted.web.server.Request} and any keyword arguments
        taken from the C{url} pattern.

        ::
            @app.route("/")
            def index(request):
                return "Hello"

        @param url: A werkzeug URL pattern given to C{werkzeug.routing.Rule}.
        @type url: str

        @param branch: A bool indiciated if a branch endpoint should
            be added that allows all child path segments that don't
            match some other route to be consumed.  Default C{False}.
        @type branch: bool


        @returns: decorated handler function.
        """
        segment_count = self._segments_in_url(url) + self._subroute_segments

        @named("router for '" + url + "'")
        def deco(f):
            kwargs.setdefault('endpoint', f.__name__)
            if kwargs.pop('branch', False):
                branchKwargs = kwargs.copy()
                branchKwargs['endpoint'] = branchKwargs['endpoint'] + '_branch'

                @modified("branch route '{url}' executor".format(url=url), f)
                def branch_f(instance, request, *a, **kw):
                    IKleinRequest(request).branch_segments = (kw.pop(
                        '__rest__', '').split('/'))
                    return _call(instance, f, request, *a, **kw)

                branch_f.segment_count = segment_count

                self._endpoints[branchKwargs['endpoint']] = branch_f
                self._url_map.add(
                    Rule(
                        url.rstrip('/') + '/' + '<path:__rest__>', *args,
                        **branchKwargs))

            @modified("route '{url}' executor".format(url=url), f)
            def _f(instance, request, *a, **kw):
                return _call(instance, f, request, *a, **kw)

            _f.segment_count = segment_count

            self._endpoints[kwargs['endpoint']] = _f
            self._url_map.add(Rule(url, *args, **kwargs))
            return f

        return deco

    @contextmanager
    def subroute(self, prefix):
        """
        Within this block, C{@route} adds rules to a
        C{werkzeug.routing.Submount}.

        This is implemented by tinkering with the instance's C{_url_map}
        variable. A context manager allows us to gracefully use the pattern of
        "change a variable, do some things with the new value, then put it back
        to how it was before.

        Named "subroute" to try and give callers a better idea of its
        relationship to C{@route}.

        Usage:
        ::
            with app.subroute("/prefix") as app:
                @app.route("/foo")
                def foo_handler(request):
                    return 'I respond to /prefix/foo'

        @type prefix: string
        @param prefix: The string that will be prepended to the paths of all
                       routes established during the with-block.
        @return: Returns None.
        """

        _map_before_submount = self._url_map

        segments = self._segments_in_url(prefix)

        submount_map = namedtuple('submount', ['rules', 'add'])(
            [], lambda r: submount_map.rules.append(r))

        try:
            self._url_map = submount_map
            self._subroute_segments += segments
            yield self
            _map_before_submount.add(Submount(prefix, submount_map.rules))
        finally:
            self._url_map = _map_before_submount
            self._subroute_segments -= segments

    def handle_errors(self, f_or_exception, *additional_exceptions):
        """
        Register an error handler. This decorator supports two syntaxes. The
        simpler of these can be used to register a handler for all C{Exception}
        types::

            @app.handle_errors
            def error_handler(request, failure):
                request.setResponseCode(500)
                return 'Uh oh'

        Alternately, a handler can be registered for one or more specific
        C{Exception} tyes::

            @app.handle_errors(EncodingError, ValidationError):
            def error_handler(request, failure)
                request.setResponseCode(400)
                return failure.getTraceback()

        The handler will be passed a L{twisted.web.server.Request} as well as a
        L{twisted.python.failure.Failure} instance. Error handlers may return a
        deferred, a failure or a response body.

        If more than one error handler is registered, the handlers will be
        executed in the order in which they are defined, until a handler is
        encountered which completes successfully. If no handler completes
        successfully, L{twisted.web.server.Request}'s processingFailed() method
        will be called.

        In addition to handling errors that occur within a route handler, error
        handlers also handle any C{werkzeug.exceptions.HTTPException} which is
        raised during routing. In particular, C{werkzeug.exceptions.NotFound}
        will be raised if no matching route is found, so to return a custom 404
        users can do the following::

            @app.handle_errors(NotFound)
            def error_handler(request, failure):
                request.setResponseCode(404)
                return 'Not found'

        @param f_or_exception: An error handler function, or an C{Exception}
            subclass to scope the decorated handler to.
        @type f_or_exception: C{function} or C{Exception}

        @param additional_exceptions Additional C{Exception} subclasses to
            scope the decorated function to.
        @type additional_exceptions C{list} of C{Exception}s

        @returns: decorated error handler function.
        """
        # Try to detect calls using the "simple" @app.handle_error syntax by
        # introspecting the first argument - if it isn't a type which
        # subclasses Exception we assume the simple syntax was used.
        if (not isinstance(f_or_exception, type)
                or not issubclass(f_or_exception, Exception)):
            return self.handle_errors(Exception)(f_or_exception)

        def deco(f):
            @modified("error handling wrapper", f)
            def _f(instance, request, failure):
                return _call(instance, f, request, failure)

            self._error_handlers.append(
                ([f_or_exception] + list(additional_exceptions), _f))
            return _f

        return deco

    def urlFor(self,
               request,
               endpoint,
               values=None,
               method=None,
               force_external=False,
               append_unknown=True):
        host = request.getHeader(b'host')
        if host is None:
            if force_external:
                raise ValueError("Cannot build external URL if request"
                                 " doesn't contain Host header")
            host = b''
        return self.url_map.bind(host).build(endpoint, values, method,
                                             force_external, append_unknown)

    url_for = urlFor

    def run(self,
            host=None,
            port=None,
            logFile=None,
            endpoint_description=None,
            displayTracebacks=True):
        """
        Run a minimal twisted.web server on the specified C{port}, bound to the
        interface specified by C{host} and logging to C{logFile}.

        This function will run the default reactor for your platform and so
        will block the main thread of your application.  It should be the last
        thing your klein application does.

        @param host: The hostname or IP address to bind the listening socket
            to.  "0.0.0.0" will allow you to listen on all interfaces, and
            "127.0.0.1" will allow you to listen on just the loopback
            interface.
        @type host: str

        @param port: The TCP port to accept HTTP requests on.
        @type port: int

        @param logFile: The file object to log to, by default C{sys.stdout}
        @type logFile: file object

        @param endpoint_description: specification of endpoint. Must contain
            protocol, port and interface. May contain other optional arguments,
             e.g. to use SSL: "ssl:443:privateKey=key.pem:certKey=crt.pem"
        @type endpoint_description: str

        @param displayTracebacks: Weather a processing error will result in
            a page displaying the traceback with debugging information or not.
        @type displayTracebacks: bool
        """
        if logFile is None:
            logFile = sys.stdout

        log.startLogging(logFile)

        if not endpoint_description:
            endpoint_description = "tcp:port={0}:interface={1}".format(
                port, host)

        endpoint = endpoints.serverFromString(reactor, endpoint_description)

        site = Site(self.resource())
        site.displayTracebacks = displayTracebacks

        endpoint.listen(site)
        reactor.run()
Esempio n. 33
0
class FrontEndApp(object):
    """Orchestrates pywb's core Wayback Machine functionality and is comprised of 2 core sub-apps and 3 optional apps.

    Sub-apps:
      - WarcServer: Serves the archive content (WARC/ARC and index) as well as from the live web in record/proxy mode
      - RewriterApp: Rewrites the content served by pywb (if it is to be rewritten)
      - WSGIProxMiddleware (Optional): If proxy mode is enabled, performs pywb's HTTP(s) proxy functionality
      - AutoIndexer (Optional): If auto-indexing is enabled for the collections it is started here
      - RecorderApp (Optional): Recording functionality, available when recording mode is enabled

    The RewriterApp is configurable and can be set via the class var `REWRITER_APP_CLS`, defaults to RewriterApp
    """

    REPLAY_API = 'http://localhost:%s/{coll}/resource/postreq'
    CDX_API = 'http://localhost:%s/{coll}/index'
    RECORD_SERVER = 'http://localhost:%s'
    RECORD_API = 'http://localhost:%s/%s/resource/postreq?param.recorder.coll={coll}'

    RECORD_ROUTE = '/record'

    PROXY_CA_NAME = 'pywb HTTPS Proxy CA'

    PROXY_CA_PATH = os.path.join('proxy-certs', 'pywb-ca.pem')

    REWRITER_APP_CLS = RewriterApp

    ALL_DIGITS = re.compile(r'^\d+$')

    def __init__(self, config_file=None, custom_config=None):
        """
        :param str|None config_file: Path to the config file
        :param dict|None custom_config: Dictionary containing additional configuration information
        """
        config_file = config_file or './config.yaml'
        self.handler = self.handle_request
        self.warcserver = WarcServer(config_file=config_file,
                                     custom_config=custom_config)
        self.recorder = None
        self.recorder_path = None
        self.put_custom_record_path = None
        self.proxy_default_timestamp = None

        config = self.warcserver.config

        self.debug = config.get('debug', False)

        self.warcserver_server = GeventServer(self.warcserver, port=0)

        self.proxy_prefix = None  # the URL prefix to be used for the collection with proxy mode (e.g. /coll/id_/)
        self.proxy_coll = None  # the name of the collection that has proxy mode enabled
        self.proxy_record = False  # indicate if proxy recording
        self.init_proxy(config)

        self.init_recorder(config.get('recorder'))

        self.init_autoindex(config.get('autoindex'))

        static_path = config.get('static_url_path',
                                 'pywb/static/').replace('/', os.path.sep)
        self.static_handler = StaticHandler(static_path)

        self.cdx_api_endpoint = config.get('cdx_api_endpoint', '/cdx')
        self.query_limit = config.get('query_limit')

        upstream_paths = self.get_upstream_paths(self.warcserver_server.port)

        framed_replay = config.get('framed_replay', True)
        self.rewriterapp = self.REWRITER_APP_CLS(framed_replay,
                                                 config=config,
                                                 paths=upstream_paths)

        self.templates_dir = config.get('templates_dir', 'templates')
        self.static_dir = config.get('static_dir', 'static')

        metadata_templ = os.path.join(self.warcserver.root_dir, '{coll}',
                                      'metadata.yaml')
        self.metadata_cache = MetadataCache(metadata_templ)

        self._init_routes()

    def _init_routes(self):
        """Initialize the routes and based on the configuration file makes available
        specific routes (proxy mode, record)
        """
        self.url_map = Map()
        self.url_map.add(
            Rule('/static/_/<coll>/<path:filepath>',
                 endpoint=self.serve_static))
        self.url_map.add(
            Rule('/static/<path:filepath>', endpoint=self.serve_static))
        self.url_map.add(Rule('/collinfo.json', endpoint=self.serve_listing))

        if self.is_valid_coll('$root'):
            coll_prefix = ''
        else:
            coll_prefix = '/<coll>'
            self.url_map.add(Rule('/', endpoint=self.serve_home))

        self._init_coll_routes(coll_prefix)

        if self.proxy_prefix is not None:
            # Add the proxy-fetch endpoint to enable PreservationWorker to make CORS fetches worry free in proxy mode
            self.url_map.add(
                Rule('/proxy-fetch/<path:url>',
                     endpoint=self.proxy_fetch,
                     methods=['GET', 'HEAD', 'OPTIONS']))

    def _init_coll_routes(self, coll_prefix):
        """Initialize and register the routes for specified collection path

        :param str coll_prefix: The collection path
        :rtype: None
        """
        routes = self._make_coll_routes(coll_prefix)

        # init loc routes, if any
        loc_keys = list(self.rewriterapp.loc_map.keys())
        if loc_keys:
            routes.append(Rule('/', endpoint=self.serve_home))

            submount_route = ', '.join(loc_keys)
            submount_route = '/<any({0}):lang>'.format(submount_route)

            self.url_map.add(Submount(submount_route, routes))

        for route in routes:
            self.url_map.add(route)

    def _make_coll_routes(self, coll_prefix):
        """Creates a list of standard collection routes for the
        specified collection path

        :param str coll_prefix: The collection path
        :return: A list of route rules for the supplied collection
        :rtype: list[Rule]
        """
        routes = [
            Rule(coll_prefix + self.cdx_api_endpoint, endpoint=self.serve_cdx),
            Rule(coll_prefix + '/', endpoint=self.serve_coll_page),
            Rule(coll_prefix + '/timemap/<timemap_output>/<path:url>',
                 endpoint=self.serve_content),
            Rule(coll_prefix + '/<path:url>', endpoint=self.serve_content)
        ]

        if self.recorder_path:
            routes.append(
                Rule(coll_prefix + self.RECORD_ROUTE + '/<path:url>',
                     endpoint=self.serve_record))

            # enable PUT of custom data as 'resource' records
            if self.put_custom_record_path:
                routes.append(
                    Rule(coll_prefix + self.RECORD_ROUTE,
                         endpoint=self.put_custom_record,
                         methods=["PUT"]))

        return routes

    def get_upstream_paths(self, port):
        """Retrieve a dictionary containing the full URLs of the upstream apps

        :param int port: The port used by the replay and cdx servers
        :return: A dictionary containing the upstream paths (replay, cdx-server, record [if enabled])
        :rtype: dict[str, str]
        """
        base_paths = {
            'replay': self.REPLAY_API % port,
            'cdx-server': self.CDX_API % port,
        }

        if self.recorder_path:
            base_paths['record'] = self.recorder_path

        return base_paths

    def init_recorder(self, recorder_config):
        """Initialize the recording functionality of pywb. If recording_config is None this function is a no op

        :param str|dict|None recorder_config: The configuration for the recorder app
        :rtype: None
        """
        if not recorder_config:
            self.recorder = None
            self.recorder_path = None
            return

        if isinstance(recorder_config, str):
            recorder_coll = recorder_config
            recorder_config = {}
        else:
            recorder_coll = recorder_config['source_coll']

        # cache mode
        self.rec_cache_mode = recorder_config.get('cache', 'default')

        dedup_policy = recorder_config.get('dedup_policy')
        dedup_by_url = False

        if dedup_policy == 'none':
            dedup_policy = ''

        if dedup_policy == 'keep':
            dedup_policy = WriteDupePolicy()
        elif dedup_policy == 'revisit':
            dedup_policy = WriteRevisitDupePolicy()
        elif dedup_policy == 'skip':
            dedup_policy = SkipDupePolicy()
            dedup_by_url = True
        elif dedup_policy:
            msg = 'Invalid option for dedup_policy: {0}'
            raise Exception(msg.format(dedup_policy))

        if dedup_policy:
            dedup_index = WritableRedisIndexer(
                redis_url=self.warcserver.dedup_index_url,
                dupe_policy=dedup_policy,
                rel_path_template=self.warcserver.root_dir + '/{coll}/archive')
        else:
            dedup_index = None

        warc_writer = MultiFileWARCWriter(
            self.warcserver.archive_paths,
            max_size=int(recorder_config.get('rollover_size', 1000000000)),
            max_idle_secs=int(recorder_config.get('rollover_idle_secs', 600)),
            filename_template=recorder_config.get('filename_template'),
            dedup_index=dedup_index,
            dedup_by_url=dedup_by_url)

        if dedup_policy:
            pending_counter = self.warcserver.dedup_index_url.replace(
                ':cdxj', ':pending')
            pending_timeout = recorder_config.get('pending_timeout', 30)
            create_buff_func = lambda params, name: RedisPendingCounterTempBuffer(
                512 * 1024, pending_counter, params, name, pending_timeout)
        else:
            create_buff_func = None

        self.recorder = RecorderApp(
            self.RECORD_SERVER % str(self.warcserver_server.port),
            warc_writer,
            accept_colls=recorder_config.get('source_filter'),
            create_buff_func=create_buff_func)

        recorder_server = GeventServer(self.recorder, port=0)

        self.recorder_path = self.RECORD_API % (recorder_server.port,
                                                recorder_coll)

        # enable PUT of custom data as 'resource' records
        if recorder_config.get('enable_put_custom_record'):
            self.put_custom_record_path = self.recorder_path + '&put_record={rec_type}&url={url}'

    def init_autoindex(self, auto_interval):
        """Initialize and start the auto-indexing of the collections. If auto_interval is None this is a no op.

        :param str|int auto_interval: The auto-indexing interval from the configuration file or CLI argument
        """
        if not auto_interval:
            return

        from pywb.manager.autoindex import AutoIndexer

        colls_dir = self.warcserver.root_dir if self.warcserver.root_dir else None

        indexer = AutoIndexer(colls_dir=colls_dir, interval=int(auto_interval))

        if not os.path.isdir(indexer.root_path):
            msg = 'No managed directory "{0}" for auto-indexing'
            logging.error(msg.format(indexer.root_path))
            import sys
            sys.exit(2)

        msg = 'Auto-Indexing Enabled on "{0}", checking every {1} secs'
        logging.info(msg.format(indexer.root_path, auto_interval))
        indexer.start()

    def is_proxy_enabled(self, environ):
        """Returns T/F indicating if proxy mode is enabled

        :param dict environ: The WSGI environment dictionary for the request
        :return: T/F indicating if proxy mode is enabled
        :rtype: bool
        """
        return self.proxy_prefix is not None and 'wsgiprox.proxy_host' in environ

    def serve_home(self, environ):
        """Serves the home (/) view of pywb (not a collections)

        :param dict environ: The WSGI environment dictionary for the request
        :return: The WbResponse for serving the home (/) path
        :rtype: WbResponse
        """
        home_view = BaseInsertView(self.rewriterapp.jinja_env, 'index.html')
        fixed_routes = self.warcserver.list_fixed_routes()
        dynamic_routes = self.warcserver.list_dynamic_routes()

        routes = fixed_routes + dynamic_routes

        all_metadata = self.metadata_cache.get_all(dynamic_routes)

        content = home_view.render_to_string(environ,
                                             routes=routes,
                                             all_metadata=all_metadata)

        return WbResponse.text_response(
            content, content_type='text/html; charset="utf-8"')

    def serve_static(self, environ, coll='', filepath=''):
        """Serve a static file associated with a specific collection or one of pywb's own static assets

        :param dict environ: The WSGI environment dictionary for the request
        :param str coll: The collection the static file is associated with
        :param str filepath: The file path (relative to the collection) for the static assest
        :return: The WbResponse for the static asset
        :rtype: WbResponse
        """
        proxy_enabled = self.is_proxy_enabled(environ)
        if proxy_enabled and environ.get('REQUEST_METHOD') == 'OPTIONS':
            return WbResponse.options_response(environ)
        if coll:
            path = os.path.join(self.warcserver.root_dir, coll,
                                self.static_dir)
        else:
            path = self.static_dir

        environ['pywb.static_dir'] = path
        try:
            response = self.static_handler(environ, filepath)
            if proxy_enabled:
                response.add_access_control_headers(env=environ)
            return response
        except Exception:
            self.raise_not_found(environ, 'static_file_not_found', filepath)

    def get_coll_config(self, coll):
        """Retrieve the collection config, including metadata, associated with a collection

        :param str coll: The name of the collection to receive config info for
        :return: The collections config
        :rtype: dict
        """
        coll_config = {'coll': coll, 'type': 'replay'}

        if coll in self.warcserver.list_fixed_routes():
            coll_config.update(self.warcserver.get_coll_config(coll))
        else:
            coll_config['metadata'] = self.metadata_cache.load(coll) or {}

        return coll_config

    def serve_coll_page(self, environ, coll='$root'):
        """Render and serve a collections search page (search.html).

        :param dict environ: The WSGI environment dictionary for the request
        :param str coll: The name of the collection to serve the collections search page for
        :return: The WbResponse containing the collections search page
        :rtype: WbResponse
        """
        if not self.is_valid_coll(coll):
            self.raise_not_found(environ, 'coll_not_found', coll)

        self.setup_paths(environ, coll)

        coll_config = self.get_coll_config(coll)
        metadata = coll_config.get('metadata')

        view = BaseInsertView(self.rewriterapp.jinja_env, 'search.html')

        wb_prefix = environ.get('SCRIPT_NAME', '')
        if wb_prefix:
            wb_prefix += '/'

        content = view.render_to_string(environ,
                                        wb_prefix=wb_prefix,
                                        coll=coll,
                                        coll_config=coll_config,
                                        metadata=metadata)

        return WbResponse.text_response(
            content, content_type='text/html; charset="utf-8"')

    def serve_cdx(self, environ, coll='$root'):
        """Make the upstream CDX query for a collection and response with the results of the query

        :param dict environ: The WSGI environment dictionary for the request
        :param str coll: The name of the collection this CDX query is for
        :return: The WbResponse containing the results of the CDX query
        :rtype: WbResponse
        """
        base_url = self.rewriterapp.paths['cdx-server']

        # if coll == self.all_coll:
        #    coll = '*'

        cdx_url = base_url.format(coll=coll)

        if environ.get('QUERY_STRING'):
            cdx_url += '&' if '?' in cdx_url else '?'
            cdx_url += environ.get('QUERY_STRING')

        if self.query_limit:
            cdx_url += '&' if '?' in cdx_url else '?'
            cdx_url += 'limit=' + str(self.query_limit)

        try:
            res = requests.get(cdx_url, stream=True)

            status_line = '{} {}'.format(res.status_code, res.reason)
            content_type = res.headers.get('Content-Type')

            return WbResponse.bin_stream(StreamIter(res.raw),
                                         content_type=content_type,
                                         status=status_line)

        except Exception as e:
            return WbResponse.text_response('Error: ' + str(e),
                                            status='400 Bad Request')

    def serve_record(self, environ, coll='$root', url=''):
        """Serve a URL's content from a WARC/ARC record in replay mode or from the live web in
        live, proxy, and record mode.

        :param dict environ: The WSGI environment dictionary for the request
        :param str coll: The name of the collection the record is to be served from
        :param str url: The URL for the corresponding record to be served if it exists
        :return: WbResponse containing the contents of the record/URL
        :rtype: WbResponse
        """
        if coll in self.warcserver.list_fixed_routes():
            return WbResponse.text_response(
                'Error: Can Not Record Into Custom Collection "{0}"'.format(
                    coll))

        return self.serve_content(environ, coll, url, record=True)

    def serve_content(self,
                      environ,
                      coll='$root',
                      url='',
                      timemap_output='',
                      record=False):
        """Serve the contents of a URL/Record rewriting the contents of the response when applicable.

        :param dict environ: The WSGI environment dictionary for the request
        :param str coll: The name of the collection the record is to be served from
        :param str url: The URL for the corresponding record to be served if it exists
        :param str timemap_output: The contents of the timemap included in the link header of the response
        :param bool record: Should the content being served by recorded (save to a warc). Only valid in record mode
        :return: WbResponse containing the contents of the record/URL
        :rtype: WbResponse
        """
        if not self.is_valid_coll(coll):
            self.raise_not_found(environ, 'coll_not_found', coll)

        self.setup_paths(environ, coll, record)

        request_uri = environ.get('REQUEST_URI')
        script_name = environ.get('SCRIPT_NAME', '') + '/'
        if request_uri and request_uri.startswith(script_name):
            wb_url_str = request_uri[len(script_name):]

        else:
            wb_url_str = to_native_str(url)

            if environ.get('QUERY_STRING'):
                wb_url_str += '?' + environ.get('QUERY_STRING')

        coll_config = self.get_coll_config(coll)
        if record:
            coll_config['type'] = 'record'
            coll_config['cache'] = self.rec_cache_mode

        if timemap_output:
            coll_config['output'] = timemap_output
            # ensure that the timemap path information is not included
            wb_url_str = wb_url_str.replace(
                'timemap/{0}/'.format(timemap_output), '')

        return self.rewriterapp.render_content(wb_url_str, coll_config,
                                               environ)

    def put_custom_record(self, environ, coll="$root"):
        """ When recording, PUT a custom WARC record to the specified collection
        (Available only when recording)

        :param dict environ: The WSGI environment dictionary for the request
        :param str coll: The name of the collection the record is to be served from
        """
        chunks = []
        while True:
            buff = environ["wsgi.input"].read()
            if not buff:
                break

            chunks.append(buff)

        data = b"".join(chunks)

        params = dict(parse_qsl(environ.get("QUERY_STRING")))

        rec_type = "resource"

        headers = {"Content-Type": environ.get("CONTENT_TYPE", "text/plain")}

        target_uri = params.get("url")

        if not target_uri:
            return WbResponse.json_response({"error": "no url"},
                                            status="400 Bad Request")

        timestamp = params.get("timestamp")
        if timestamp:
            headers["WARC-Date"] = timestamp_to_iso_date(timestamp)

        put_url = self.put_custom_record_path.format(url=target_uri,
                                                     coll=coll,
                                                     rec_type=rec_type)
        res = requests.put(put_url, headers=headers, data=data)

        res = res.json()

        return WbResponse.json_response(res)

    def setup_paths(self, environ, coll, record=False):
        """Populates the WSGI environment dictionary with the path information necessary to perform a response for
        content or record.

        :param dict environ: The WSGI environment dictionary for the request
        :param str coll: The name of the collection the record is to be served from
        :param bool record: Should the content being served by recorded (save to a warc). Only valid in record mode
        """
        if not coll or not self.warcserver.root_dir:
            return

        if coll != '$root':
            pop_path_info(environ)
            if record:
                pop_path_info(environ)

        paths = [self.warcserver.root_dir]

        if coll != '$root':
            paths.append(coll)

        paths.append(self.templates_dir)

        # jinja2 template paths always use '/' as separator
        environ['pywb.templates_dir'] = '/'.join(paths)

    def serve_listing(self, environ):
        """Serves the response for WARCServer fixed and dynamic listing (paths)

        :param dict environ: The WSGI environment dictionary for the request
        :return: WbResponse containing the frontend apps WARCServer URL paths
        :rtype: WbResponse
        """
        result = {
            'fixed': self.warcserver.list_fixed_routes(),
            'dynamic': self.warcserver.list_dynamic_routes()
        }

        return WbResponse.json_response(result)

    def is_valid_coll(self, coll):
        """Determines if the collection name for a request is valid (exists)

        :param str coll: The name of the collection to check
        :return: True if the collection is valid, false otherwise
        :rtype: bool
        """
        # if coll == self.all_coll:
        #    return True

        return (coll in self.warcserver.list_fixed_routes()
                or coll in self.warcserver.list_dynamic_routes())

    def raise_not_found(self, environ, err_type, url):
        """Utility function for raising a werkzeug.exceptions.NotFound execption with the supplied WSGI environment
        and message.

        :param dict environ: The WSGI environment dictionary for the request
        :param str err_type: The identifier for type of error that occured
        :param str url: The url of the archived page that was requested
        """
        raise AppPageNotFound(err_type, url)

    def _check_refer_redirect(self, environ):
        """Returns a WbResponse for a HTTP 307 redirection if the HTTP referer header is the same as the HTTP host header

        :param dict environ: The WSGI environment dictionary for the request
        :return: WbResponse HTTP 307 redirection
        :rtype: WbResponse
        """
        referer = environ.get('HTTP_REFERER')
        if not referer:
            return

        host = environ.get('HTTP_HOST')
        if host not in referer:
            return

        inx = referer[1:].find('http')
        if not inx:
            inx = referer[1:].find('///')

        if inx < 0:
            return

        url = referer[inx + 1:]
        host = referer[:inx + 1]

        orig_url = environ['PATH_INFO']
        if environ.get('QUERY_STRING'):
            orig_url += '?' + environ['QUERY_STRING']

        full_url = host + urljoin(url, orig_url)
        return WbResponse.redir_response(full_url, '307 Redirect')

    def __call__(self, environ, start_response):
        """Handles a request

        :param dict environ: The WSGI environment dictionary for the request
        :param start_response:
        :return: The WbResponse for the request
        :rtype: WbResponse
        """
        return self.handler(environ, start_response)

    def handle_request(self, environ, start_response):
        """Retrieves the route handler and calls the handler returning its the response

        :param dict environ: The WSGI environment dictionary for the request
        :param start_response:
        :return: The WbResponse for the request
        :rtype: WbResponse
        """
        urls = self.url_map.bind_to_environ(environ)
        try:
            endpoint, args = urls.match()

            self.rewriterapp.prepare_env(environ)

            # store original script_name (original prefix) before modifications are made
            environ['ORIG_SCRIPT_NAME'] = environ.get('SCRIPT_NAME')

            lang = args.pop('lang', '')
            if lang:
                pop_path_info(environ)
                environ['pywb_lang'] = lang

            response = endpoint(environ, **args)

        except RequestRedirect as rr:
            # if werkzeug throws this, likely a missing slash redirect
            # also check referrer here to avoid another redirect later
            redir = self._check_refer_redirect(environ)
            if redir:
                return redir(environ, start_response)

            response = WbResponse.redir_response(rr.new_url, '307 Redirect')

        except WbException as wbe:
            if wbe.status_code == 404:
                redir = self._check_refer_redirect(environ)
                if redir:
                    return redir(environ, start_response)

            response = self.rewriterapp.handle_error(environ, wbe)

        except Exception as e:
            if self.debug:
                traceback.print_exc()

            response = self.rewriterapp._error_response(
                environ, WbException('Internal Error: ' + str(e)))

        return response(environ, start_response)

    @classmethod
    def create_app(cls, port):
        """Create a new instance of FrontEndApp that listens on port with a hostname of 0.0.0.0

        :param int port: The port FrontEndApp is to listen on
        :return: A new instance of FrontEndApp wrapped in GeventServer
        :rtype: GeventServer
        """
        app = FrontEndApp()
        app_server = GeventServer(app, port=port, hostname='0.0.0.0')
        return app_server

    def init_proxy(self, config):
        """Initialize and start proxy mode. If proxy configuration entry is not contained in the config
        this is a no op. Causes handler to become an instance of WSGIProxMiddleware.

        :param dict config: The configuration object used to configure this instance of FrontEndApp
        """
        proxy_config = config.get('proxy')
        if not proxy_config:
            return

        if isinstance(proxy_config, str):
            proxy_coll = proxy_config
            proxy_config = {}
        else:
            proxy_coll = proxy_config['coll']

        if '/' in proxy_coll:
            raise Exception('Proxy collection can not contain "/"')

        proxy_config['ca_name'] = proxy_config.get('ca_name',
                                                   self.PROXY_CA_NAME)
        proxy_config['ca_file_cache'] = proxy_config.get(
            'ca_file_cache', self.PROXY_CA_PATH)

        if proxy_config.get('recording'):
            logging.info(
                'Proxy recording into collection "{0}"'.format(proxy_coll))
            if proxy_coll in self.warcserver.list_fixed_routes():
                raise Exception('Can not record into fixed collection')

            proxy_route = proxy_coll + self.RECORD_ROUTE
            if not config.get('recorder'):
                config['recorder'] = 'live'

            self.proxy_record = True

        else:
            logging.info(
                'Proxy enabled for collection "{0}"'.format(proxy_coll))
            self.proxy_record = False
            proxy_route = proxy_coll

        if proxy_config.get('enable_content_rewrite', True):
            self.proxy_prefix = '/{0}/bn_/'.format(proxy_route)
        else:
            self.proxy_prefix = '/{0}/id_/'.format(proxy_route)

        self.proxy_default_timestamp = proxy_config.get('default_timestamp')
        if self.proxy_default_timestamp:
            if not self.ALL_DIGITS.match(self.proxy_default_timestamp):
                try:
                    self.proxy_default_timestamp = iso_date_to_timestamp(
                        self.proxy_default_timestamp)
                except Exception:
                    raise Exception(
                        'Invalid Proxy Timestamp: Must Be All-Digit Timestamp or ISO Date Format'
                    )

        self.proxy_coll = proxy_coll

        self.handler = WSGIProxMiddleware(self.handle_request,
                                          self.proxy_route_request,
                                          proxy_host=proxy_config.get(
                                              'host', 'pywb.proxy'),
                                          proxy_options=proxy_config)

    def proxy_route_request(self, url, environ):
        """ Return the full url that this proxy request will be routed to
        The 'environ' PATH_INFO and REQUEST_URI will be modified based on the returned url

        Default is to use the 'proxy_prefix' to point to the proxy collection
        """
        if self.proxy_default_timestamp:
            environ[
                'pywb_proxy_default_timestamp'] = self.proxy_default_timestamp

        return self.proxy_prefix + url

    def proxy_fetch(self, env, url):
        """Proxy mode only endpoint that handles OPTIONS requests and COR fetches for Preservation Worker.

        Due to normal cross-origin browser restrictions in proxy mode, auto fetch worker cannot access the CSS rules
        of cross-origin style sheets and must re-fetch them in a manner that is CORS safe. This endpoint facilitates
        that by fetching the stylesheets for the auto fetch worker and then responds with its contents

        :param dict env: The WSGI environment dictionary
        :param str url:  The URL of the resource to be fetched
        :return: WbResponse that is either response to an Options request or the results of fetching url
        :rtype: WbResponse
        """
        if not self.is_proxy_enabled(env):
            # we are not in proxy mode so just respond with forbidden
            return WbResponse.text_response(
                'proxy mode must be enabled to use this endpoint',
                status='403 Forbidden')

        if env.get('REQUEST_METHOD') == 'OPTIONS':
            return WbResponse.options_response(env)

        # ensure full URL
        url = env['REQUEST_URI'].split('/proxy-fetch/', 1)[-1]

        env['REQUEST_URI'] = self.proxy_prefix + url
        env['PATH_INFO'] = self.proxy_prefix + env['PATH_INFO'].split(
            '/proxy-fetch/', 1)[-1]

        # make request using normal serve_content
        response = self.serve_content(env,
                                      self.proxy_coll,
                                      url,
                                      record=self.proxy_record)

        # for WR
        if isinstance(response, WbResponse):
            response.add_access_control_headers(env=env)
        return response
Esempio n. 34
0
class Flask(object):
    """The flask object implements a WSGI application and acts as the central
    object.  It is passed the name of the module or package of the
    application.  Once it is created it will act as a central registry for
    the view functions, the URL rules, template configuration and much more.

    The name of the package is used to resolve resources from inside the
    package or the folder the module is contained in depending on if the
    package parameter resolves to an actual python package (a folder with
    an `__init__.py` file inside) or a standard module (just a `.py` file).

    For more information about resource loading, see :func:`open_resource`.

    Usually you create a :class:`Flask` instance in your main module or
    in the `__init__.py` file of your package like this::

        from flask import Flask
        app = Flask(__name__)
    """

    #: the class that is used for request objects.  See :class:`~flask.request`
    #: for more information.
    request_class = Request

    #: the class that is used for response objects.  See
    #: :class:`~flask.Response` for more information.
    response_class = Response

    #: path for the static files.  If you don't want to use static files
    #: you can set this value to `None` in which case no URL rule is added
    #: and the development server will no longer serve any static files.
    static_path = '/static'

    #: if a secret key is set, cryptographic components can use this to
    #: sign cookies and other things.  Set this to a complex random value
    #: when you want to use the secure cookie for instance.
    secret_key = None

    #: The secure cookie uses this for the name of the session cookie
    session_cookie_name = 'session'

    #: options that are passed directly to the Jinja2 environment
    jinja_options = dict(
        autoescape=True,
        extensions=['jinja2.ext.autoescape', 'jinja2.ext.with_'])

    def __init__(self, package_name):
        #: the debug flag.  Set this to `True` to enable debugging of
        #: the application.  In debug mode the debugger will kick in
        #: when an unhandled exception ocurrs and the integrated server
        #: will automatically reload the application if changes in the
        #: code are detected.
        self.debug = False

        #: the name of the package or module.  Do not change this once
        #: it was set by the constructor.
        self.package_name = package_name

        #: where is the app root located?
        self.root_path = _get_package_path(self.package_name)

        #: a dictionary of all view functions registered.  The keys will
        #: be function names which are also used to generate URLs and
        #: the values are the function objects themselves.
        #: to register a view function, use the :meth:`route` decorator.
        self.view_functions = {}

        #: a dictionary of all registered error handlers.  The key is
        #: be the error code as integer, the value the function that
        #: should handle that error.
        #: To register a error handler, use the :meth:`errorhandler`
        #: decorator.
        self.error_handlers = {}

        #: a list of functions that should be called at the beginning
        #: of the request before request dispatching kicks in.  This
        #: can for example be used to open database connections or
        #: getting hold of the currently logged in user.
        #: To register a function here, use the :meth:`before_request`
        #: decorator.
        self.before_request_funcs = []

        #: a list of functions that are called at the end of the
        #: request.  Tha function is passed the current response
        #: object and modify it in place or replace it.
        #: To register a function here use the :meth:`after_request`
        #: decorator.
        self.after_request_funcs = []

        #: a list of functions that are called without arguments
        #: to populate the template context.  Each returns a dictionary
        #: that the template context is updated with.
        #: To register a function here, use the :meth:`context_processor`
        #: decorator.
        self.template_context_processors = [_default_template_ctx_processor]

        self.url_map = Map()

        if self.static_path is not None:
            self.url_map.add(
                Rule(self.static_path + '/<filename>',
                     build_only=True,
                     endpoint='static'))
            if pkg_resources is not None:
                target = (self.package_name, 'static')
            else:
                target = os.path.join(self.root_path, 'static')
            self.wsgi_app = SharedDataMiddleware(self.wsgi_app,
                                                 {self.static_path: target})

        #: the Jinja2 environment.  It is created from the
        #: :attr:`jinja_options` and the loader that is returned
        #: by the :meth:`create_jinja_loader` function.
        self.jinja_env = Environment(loader=self.create_jinja_loader(),
                                     **self.jinja_options)
        self.jinja_env.globals.update(
            url_for=url_for, get_flashed_messages=get_flashed_messages)

    def create_jinja_loader(self):
        """Creates the Jinja loader.  By default just a package loader for
        the configured package is returned that looks up templates in the
        `templates` folder.  To add other loaders it's possible to
        override this method.
        """
        if pkg_resources is None:
            return FileSystemLoader(os.path.join(self.root_path, 'templates'))
        return PackageLoader(self.package_name)

    def update_template_context(self, context):
        """Update the template context with some commonly used variables.
        This injects request, session and g into the template context.

        :param context: the context as a dictionary that is updated in place
                        to add extra variables.
        """
        reqctx = _request_ctx_stack.top
        for func in self.template_context_processors:
            context.update(func())

    def run(self, host='localhost', port=5000, **options):
        """Runs the application on a local development server.  If the
        :attr:`debug` flag is set the server will automatically reload
        for code changes and show a debugger in case an exception happened.

        在本地的开发服务器运行应用.如果设置了Flask 的debug,当项目代码发生变化时,
        自动重载,并在异常发生时提供一个调试器.

        :param host: the hostname to listen on.  set this to ``'0.0.0.0'``
                     to have the server available externally as well.
                    
                     需要监听的hostname, 设置为‘0.0.0.0’ 外部服务器可访问

        :param port: the port of the webserver

                     web服务的端口

        :param options: the options to be forwarded to the underlying
                        Werkzeug server.  See :func:`werkzeug.run_simple`
                        for more information.

                     这些可选项会转发给Werkzeug服务器处理.
        """
        from werkzeug import run_simple  # 使用werkzeug 的run_simple 来运行wsgi应用, 具体请看博客
        if 'debug' in options:
            self.debug = options.pop('debug')
        options.setdefault('use_reloader',
                           self.debug)  # 如果debug == True, 开启重载器,也就是监控代码是否变化
        options.setdefault('use_debugger',
                           self.debug)  # 如果debug == True,开启调试器, 在异常时提供
        return run_simple(host, port, self, **options)

    def test_client(self):
        """Creates a test client for this application.  For information
        about unit testing head over to :ref:`testing`.
        """
        from werkzeug import Client
        return Client(self, self.response_class, use_cookies=True)

    def open_resource(self, resource):
        """Opens a resource from the application's resource folder.  To see
        how this works, consider the following folder structure::

            /myapplication.py
            /schemal.sql
            /static
                /style.css
            /template
                /layout.html
                /index.html

        If you want to open the `schema.sql` file you would do the
        following::

            with app.open_resource('schema.sql') as f:
                contents = f.read()
                do_something_with(contents)

        :param resource: the name of the resource.  To access resources within
                         subfolders use forward slashes as separator.
        """
        if pkg_resources is None:
            return open(os.path.join(self.root_path, resource), 'rb')
        return pkg_resources.resource_stream(self.package_name, resource)

    def open_session(self, request):
        """Creates or opens a new session.  Default implementation stores all
        session data in a signed cookie.  This requires that the
        :attr:`secret_key` is set.

        :param request: an instance of :attr:`request_class`.
        """
        key = self.secret_key
        if key is not None:
            return SecureCookie.load_cookie(request,
                                            self.session_cookie_name,
                                            secret_key=key)

    def save_session(self, session, response):
        """Saves the session if it needs updates.  For the default
        implementation, check :meth:`open_session`.

        :param session: the session to be saved (a
                        :class:`~werkzeug.contrib.securecookie.SecureCookie`
                        object)
        :param response: an instance of :attr:`response_class`
        """
        if session is not None:
            session.save_cookie(response, self.session_cookie_name)

    def add_url_rule(self, rule, endpoint, **options):
        """Connects a URL rule.  Works exactly like the :meth:`route`
        decorator but does not register the view function for the endpoint.

        连接一个URL规则. 类似于route装饰器做的工作,但是不会为端点(endpoint)注册视图函数

        Basically this example::

            @app.route('/')
            def index():
                pass

        Is equivalent to the following::

            def index():
                pass
            app.add_url_rule('index', '/')
            app.view_functions['index'] = index

            这里示例错了,应该是
            app.add_url_rule('/', 'index')
            端点(endpoint)是视图函数名称, rule(规则)是'/'.


        :param rule: the URL rule as string    # 字符串类型的URL规则
        :param endpoint: the endpoint for the registered URL rule.  Flask
                         itself assumes the name of the view function as
                         endpoint

                         已注册URL规则的端点. Flask 本身将视图函数的名称作为一个端点.
        :param options: the options to be forwarded to the underlying
                        :class:`~werkzeug.routing.Rule` object

                        可选项,转发给底层的werkzeug.routing.Rule对象的选项
        """
        options['endpoint'] = endpoint
        options.setdefault('methods', ('GET', ))  # 默认设置methods为GET
        self.url_map.add(Rule(rule,
                              **options))  # 使用Werkzeug的Rule来实现规则的相关工作,具体参考博客.

    def route(self, rule, **options):
        """A decorator that is used to register a view function for a
        given URL rule.  
        
        用于为给定的URL 规则注册一个视图函数的装饰器.
        
        Example::

            @app.route('/')
            def index():
                return 'Hello World'

        Variables parts in the route can be specified with angular
        brackets (``/user/<username>``).  By default a variable part
        in the URL accepts any string without a slash however a different
        converter can be specified as well by using ``<converter:name>``.

        可以使用<>尖括号指定route路由的变量部分,默认情况下URL中的变量
        接收任何的没有斜杠的字符串,但是可以使用<converter:name>来指定不同的converter.

        Variable parts are passed to the view function as keyword
        arguments.

        变量部分作为视图函数的关键字参数传入.

        转换器(convertor) 类型可以如下几种:
        The following converters are possible:

        =========== ===========================================
        `int`       accepts integers
        `float`     like `int` but for floating point values
        `path`      like the default but also accepts slashes
        =========== ===========================================

        Here some examples::

            @app.route('/')
            def index():
                pass

            @app.route('/<username>')
            def show_user(username):
                pass

            @app.route('/post/<int:post_id>')
            def show_post(post_id):
                pass

        An important detail to keep in mind is how Flask deals with trailing
        slashes.  The idea is to keep each URL unique so the following rules
        apply:

        一个重要的细节需要注意的是,Flask在处理斜线的. 为了保持URL的唯一性,下面的规则会被应用:

        1. If a rule ends with a slash and is requested without a slash
           by the user, the user is automatically redirected to the same
           page with a trailing slash attached.

        1、如果一个rule以一个斜线结尾并且用户请求路径中没有带上斜线,那么用户
        自动重定向到相同的已经附加了斜线的页面。

        2. If a rule does not end with a trailing slash and the user request
           the page with a trailing slash, a 404 not found is raised.

        2、如果一个rule不以一个斜线结尾并且用户请求路径中带上这个斜线的话,
        一个404 not found 错误会报出来.

        This is consistent with how web servers deal with static files.  This
        also makes it possible to use relative link targets safely.

        The :meth:`route` decorator accepts a couple of other arguments
        as well:

        :param rule: the URL rule as string  # 字符串类型的URL 规则
        :param methods: a list of methods this rule should be limited
                        to (``GET``, ``POST`` etc.).  By default a rule
                        just listens for ``GET`` (and implicitly ``HEAD``).

                        这个rule(规则)应该限制一系列的methods(GET, POST).
                        默认规则就是只监听GET请求.
        :param subdomain: specifies the rule for the subdoain in case
                          subdomain matching is in use.

                         当子域名匹配使用时,为规则指定子域.
        :param strict_slashes: can be used to disable the strict slashes
                               setting for this rule.  See above.

                               是否为这个rule关闭严格的斜杠匹配.
        :param options: other options to be forwarded to the underlying
                        :class:`~werkzeug.routing.Rule` object.
        """
        def decorator(f):
            """
            这是最简单的装饰器的实践,使用add_url_rule将rule(路由规则)与视图函数绑定在一起.
            """
            self.add_url_rule(rule, f.__name__,
                              **options)  # 添加路由规则,options传入一些可选项给Rule类
            self.view_functions[
                f.
                __name__] = f  # 将端点(endpoint, 默认使用函数名f.__name__)与函数对象的映射存入前面Flask定义的view_functions列表中
            return f

        return decorator

    def errorhandler(self, code):
        """A decorator that is used to register a function give a given
        error code.  Example::

            @app.errorhandler(404)
            def page_not_found():
                return 'This page does not exist', 404

        You can also register a function as error handler without using
        the :meth:`errorhandler` decorator.  The following example is
        equivalent to the one above::

            def page_not_found():
                return 'This page does not exist', 404
            app.error_handlers[404] = page_not_found

        :param code: the code as integer for the handler
        """
        def decorator(f):
            self.error_handlers[code] = f
            return f

        return decorator

    def before_request(self, f):
        """Registers a function to run before each request."""
        self.before_request_funcs.append(f)
        return f

    def after_request(self, f):
        """Register a function to be run after each request."""
        self.after_request_funcs.append(f)
        return f

    def context_processor(self, f):
        """Registers a template context processor function."""
        self.template_context_processors.append(f)
        return f

    def match_request(self):
        """Matches the current request against the URL map and also
        stores the endpoint and view arguments on the request object
        is successful, otherwise the exception is stored.
        """
        rv = _request_ctx_stack.top.url_adapter.match()
        request.endpoint, request.view_args = rv
        return rv

    def dispatch_request(self):
        """Does the request dispatching.  Matches the URL and returns the
        return value of the view or error handler.  This does not have to
        be a response object.  In order to convert the return value to a
        proper response object, call :func:`make_response`.
        """
        try:
            endpoint, values = self.match_request()
            return self.view_functions[endpoint](**values)
        except HTTPException, e:
            handler = self.error_handlers.get(e.code)
            if handler is None:
                return e
            return handler(e)
        except Exception, e:
            handler = self.error_handlers.get(500)
            if self.debug or handler is None:
                raise
            return handler(e)
class WebSocket(object):
    '''
    Flask extension which makes it easy to integrate uWSGI-powered WebSockets
    into your applications.
    '''
    middleware = WebSocketMiddleware

    def __init__(self, app=None, timeout=5):
        if app:
            self.init_app(app)
        self.timeout = timeout
        self.routes = {}
        self.url_map = Map()
        self.view_functions = {}
        self.blueprints = {}
        if app is not None:
            self.debug = app.debug
            self._got_first_request = app._got_first_request
        else:
            self.debug = False
            self._got_first_request = False

    def run(self, app=None, debug=False, host='localhost', port=5000, uwsgi_binary=None, **kwargs):
        if not app:
            app = self.app.name + ':app'

        if self.app.debug:
            debug = True

        run_uwsgi(app, debug, host, port, uwsgi_binary, **kwargs)

    def init_app(self, app):
        self.app = app

        if os.environ.get('FLASK_UWSGI_DEBUG'):
            from werkzeug.debug import DebuggedApplication
            app.wsgi_app = DebuggedApplication(app.wsgi_app, True)
            app.debug = True

        app.wsgi_app = self.middleware(app.wsgi_app, self)
        app.run = lambda **kwargs: self.run(**kwargs)

    def route(self, rule, **options):
        def decorator(f):
            endpoint = options.pop('endpoint', None)
            self.add_url_rule(rule, endpoint, f, **options)
            return f
        return decorator

    def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
        assert view_func is not None, 'view_func is mandatory'
        if endpoint is None:
            endpoint = view_func.__name__
        options['endpoint'] = endpoint
        # supposed to be GET
        methods = set(('GET', ))
        provide_automatic_options = False

        rule = Rule(rule, methods=methods, **options)
        rule.provide_automatic_options = provide_automatic_options
        self.url_map.add(rule)
        if view_func is not None:
            old_func = self.view_functions.get(endpoint)
            if old_func is not None and old_func != view_func:
                raise AssertionError('View function mapping is overwriting an '
                                     'existing endpoint function: %s' % endpoint)
            self.view_functions[endpoint] = view_func

    # merged from flask.app
    @setupmethod
    def register_blueprint(self, blueprint, **options):
        '''
        Registers a blueprint on the WebSockets.
        '''
        first_registration = False
        if blueprint.name in self.blueprints:
            assert self.blueprints[blueprint.name] is blueprint, \
                'A blueprint\'s name collision occurred between %r and ' \
                '%r.  Both share the same name "%s".  Blueprints that ' \
                'are created on the fly need unique names.' % \
                (blueprint, self.blueprints[blueprint.name], blueprint.name)
        else:
            self.blueprints[blueprint.name] = blueprint
            first_registration = True
        blueprint.register(self, options, first_registration)
Esempio n. 36
0
File: flask.py Progetto: saga/flask
class Flask(object):
    """The flask object implements a WSGI application and acts as the central
    object.  It is passed the name of the module or package of the
    application.  Once it is created it will act as a central registry for
    the view functions, the URL rules, template configuration and much more.

    The name of the package is used to resolve resources from inside the
    package or the folder the module is contained in depending on if the
    package parameter resolves to an actual python package (a folder with
    an `__init__.py` file inside) or a standard module (just a `.py` file).

    For more information about resource loading, see :func:`open_resource`.

    Usually you create a :class:`Flask` instance in your main module or
    in the `__init__.py` file of your package like this::

        from flask import Flask
        app = Flask(__name__)
    """

    #: the class that is used for request objects.  See :class:`~flask.request`
    #: for more information.
    request_class = Request

    #: the class that is used for response objects.  See
    #: :class:`~flask.Response` for more information.
    response_class = Response

    #: path for the static files.  If you don't want to use static files
    #: you can set this value to `None` in which case no URL rule is added
    #: and the development server will no longer serve any static files.
    static_path = "/static"

    #: if a secret key is set, cryptographic components can use this to
    #: sign cookies and other things.  Set this to a complex random value
    #: when you want to use the secure cookie for instance.
    secret_key = None

    #: The secure cookie uses this for the name of the session cookie
    session_cookie_name = "session"

    #: options that are passed directly to the Jinja2 environment
    jinja_options = ImmutableDict(autoescape=True, extensions=["jinja2.ext.autoescape", "jinja2.ext.with_"])

    def __init__(self, package_name):
        #: the debug flag.  Set this to `True` to enable debugging of
        #: the application.  In debug mode the debugger will kick in
        #: when an unhandled exception ocurrs and the integrated server
        #: will automatically reload the application if changes in the
        #: code are detected.
        self.debug = False

        #: the name of the package or module.  Do not change this once
        #: it was set by the constructor.
        self.package_name = package_name

        #: where is the app root located?
        self.root_path = _get_package_path(self.package_name)

        #: a dictionary of all view functions registered.  The keys will
        #: be function names which are also used to generate URLs and
        #: the values are the function objects themselves.
        #: to register a view function, use the :meth:`route` decorator.
        self.view_functions = {}

        #: a dictionary of all registered error handlers.  The key is
        #: be the error code as integer, the value the function that
        #: should handle that error.
        #: To register a error handler, use the :meth:`errorhandler`
        #: decorator.
        self.error_handlers = {}

        #: a list of functions that should be called at the beginning
        #: of the request before request dispatching kicks in.  This
        #: can for example be used to open database connections or
        #: getting hold of the currently logged in user.
        #: To register a function here, use the :meth:`before_request`
        #: decorator.
        self.before_request_funcs = []

        #: a list of functions that are called at the end of the
        #: request.  The function is passed the current response
        #: object and modify it in place or replace it.
        #: To register a function here use the :meth:`after_request`
        #: decorator.
        self.after_request_funcs = []

        #: a list of functions that are called without arguments
        #: to populate the template context.  Each returns a dictionary
        #: that the template context is updated with.
        #: To register a function here, use the :meth:`context_processor`
        #: decorator.
        self.template_context_processors = [_default_template_ctx_processor]

        #: the :class:`~werkzeug.routing.Map` for this instance.  You can use
        #: this to change the routing converters after the class was created
        #: but before any routes are connected.  Example::
        #:
        #:    from werkzeug import BaseConverter
        #:
        #:    class ListConverter(BaseConverter):
        #:        def to_python(self, value):
        #:            return value.split(',')
        #:        def to_url(self, values):
        #:            return ','.join(BaseConverter.to_url(value)
        #:                            for value in values)
        #:
        #:    app = Flask(__name__)
        #:    app.url_map.converters['list'] = ListConverter
        self.url_map = Map()

        if self.static_path is not None:
            self.add_url_rule(self.static_path + "/<filename>", build_only=True, endpoint="static")
            if pkg_resources is not None:
                target = (self.package_name, "static")
            else:
                target = os.path.join(self.root_path, "static")
            self.wsgi_app = SharedDataMiddleware(self.wsgi_app, {self.static_path: target})

        #: the Jinja2 environment.  It is created from the
        #: :attr:`jinja_options` and the loader that is returned
        #: by the :meth:`create_jinja_loader` function.
        self.jinja_env = Environment(loader=self.create_jinja_loader(), **self.jinja_options)
        self.jinja_env.globals.update(url_for=url_for, get_flashed_messages=get_flashed_messages)
        self.jinja_env.filters["tojson"] = _tojson_filter

    def create_jinja_loader(self):
        """Creates the Jinja loader.  By default just a package loader for
        the configured package is returned that looks up templates in the
        `templates` folder.  To add other loaders it's possible to
        override this method.
        """
        if pkg_resources is None:
            return FileSystemLoader(os.path.join(self.root_path, "templates"))
        return PackageLoader(self.package_name)

    def update_template_context(self, context):
        """Update the template context with some commonly used variables.
        This injects request, session and g into the template context.

        :param context: the context as a dictionary that is updated in place
                        to add extra variables.
        """
        for func in self.template_context_processors:
            context.update(func())

    def run(self, host="127.0.0.1", port=5000, **options):
        """Runs the application on a local development server.  If the
        :attr:`debug` flag is set the server will automatically reload
        for code changes and show a debugger in case an exception happened.

        :param host: the hostname to listen on.  set this to ``'0.0.0.0'``
                     to have the server available externally as well.
        :param port: the port of the webserver
        :param options: the options to be forwarded to the underlying
                        Werkzeug server.  See :func:`werkzeug.run_simple`
                        for more information.
        """
        from werkzeug import run_simple

        if "debug" in options:
            self.debug = options.pop("debug")
        options.setdefault("use_reloader", self.debug)
        options.setdefault("use_debugger", self.debug)
        return run_simple(host, port, self, **options)

    def test_client(self):
        """Creates a test client for this application.  For information
        about unit testing head over to :ref:`testing`.
        """
        from werkzeug import Client

        return Client(self, self.response_class, use_cookies=True)

    def open_resource(self, resource):
        """Opens a resource from the application's resource folder.  To see
        how this works, consider the following folder structure::

            /myapplication.py
            /schemal.sql
            /static
                /style.css
            /template
                /layout.html
                /index.html

        If you want to open the `schema.sql` file you would do the
        following::

            with app.open_resource('schema.sql') as f:
                contents = f.read()
                do_something_with(contents)

        :param resource: the name of the resource.  To access resources within
                         subfolders use forward slashes as separator.
        """
        if pkg_resources is None:
            return open(os.path.join(self.root_path, resource), "rb")
        return pkg_resources.resource_stream(self.package_name, resource)

    def open_session(self, request):
        """Creates or opens a new session.  Default implementation stores all
        session data in a signed cookie.  This requires that the
        :attr:`secret_key` is set.

        :param request: an instance of :attr:`request_class`.
        """
        key = self.secret_key
        if key is not None:
            return SecureCookie.load_cookie(request, self.session_cookie_name, secret_key=key)

    def save_session(self, session, response):
        """Saves the session if it needs updates.  For the default
        implementation, check :meth:`open_session`.

        :param session: the session to be saved (a
                        :class:`~werkzeug.contrib.securecookie.SecureCookie`
                        object)
        :param response: an instance of :attr:`response_class`
        """
        session.save_cookie(response, self.session_cookie_name)

    def add_url_rule(self, rule, endpoint, view_func=None, **options):
        """Connects a URL rule.  Works exactly like the :meth:`route`
        decorator.  If a view_func is provided it will be registered with the
        endpoint.

        Basically this example::

            @app.route('/')
            def index():
                pass

        Is equivalent to the following::

            def index():
                pass
            app.add_url_rule('/', 'index', index)

        If the view_func is not provided you will need to connect the endpoint
        to a view function like so::

            app.view_functions['index'] = index

        .. versionchanged:: 0.2
           `view_func` parameter added

        :param rule: the URL rule as string
        :param endpoint: the endpoint for the registered URL rule.  Flask
                         itself assumes the name of the view function as
                         endpoint
        :param view_func: the function to call when serving a request to the
                          provided endpoint
        :param options: the options to be forwarded to the underlying
                        :class:`~werkzeug.routing.Rule` object
        """
        options["endpoint"] = endpoint
        options.setdefault("methods", ("GET",))
        self.url_map.add(Rule(rule, **options))
        if view_func is not None:
            self.view_functions[endpoint] = view_func

    def route(self, rule, **options):
        """A decorator that is used to register a view function for a
        given URL rule.  Example::

            @app.route('/')
            def index():
                return 'Hello World'

        Variables parts in the route can be specified with angular
        brackets (``/user/<username>``).  By default a variable part
        in the URL accepts any string without a slash however a different
        converter can be specified as well by using ``<converter:name>``.

        Variable parts are passed to the view function as keyword
        arguments.

        The following converters are possible:

        =========== ===========================================
        `int`       accepts integers
        `float`     like `int` but for floating point values
        `path`      like the default but also accepts slashes
        =========== ===========================================

        Here some examples::

            @app.route('/')
            def index():
                pass

            @app.route('/<username>')
            def show_user(username):
                pass

            @app.route('/post/<int:post_id>')
            def show_post(post_id):
                pass

        An important detail to keep in mind is how Flask deals with trailing
        slashes.  The idea is to keep each URL unique so the following rules
        apply:

        1. If a rule ends with a slash and is requested without a slash
           by the user, the user is automatically redirected to the same
           page with a trailing slash attached.
        2. If a rule does not end with a trailing slash and the user request
           the page with a trailing slash, a 404 not found is raised.

        This is consistent with how web servers deal with static files.  This
        also makes it possible to use relative link targets safely.

        The :meth:`route` decorator accepts a couple of other arguments
        as well:

        :param rule: the URL rule as string
        :param methods: a list of methods this rule should be limited
                        to (``GET``, ``POST`` etc.).  By default a rule
                        just listens for ``GET`` (and implicitly ``HEAD``).
        :param subdomain: specifies the rule for the subdoain in case
                          subdomain matching is in use.
        :param strict_slashes: can be used to disable the strict slashes
                               setting for this rule.  See above.
        :param options: other options to be forwarded to the underlying
                        :class:`~werkzeug.routing.Rule` object.
        """

        def decorator(f):
            self.add_url_rule(rule, f.__name__, f, **options)
            return f

        return decorator

    def errorhandler(self, code):
        """A decorator that is used to register a function give a given
        error code.  Example::

            @app.errorhandler(404)
            def page_not_found():
                return 'This page does not exist', 404

        You can also register a function as error handler without using
        the :meth:`errorhandler` decorator.  The following example is
        equivalent to the one above::

            def page_not_found():
                return 'This page does not exist', 404
            app.error_handlers[404] = page_not_found

        :param code: the code as integer for the handler
        """

        def decorator(f):
            self.error_handlers[code] = f
            return f

        return decorator

    def before_request(self, f):
        """Registers a function to run before each request."""
        self.before_request_funcs.append(f)
        return f

    def after_request(self, f):
        """Register a function to be run after each request."""
        self.after_request_funcs.append(f)
        return f

    def context_processor(self, f):
        """Registers a template context processor function."""
        self.template_context_processors.append(f)
        return f

    def match_request(self):
        """Matches the current request against the URL map and also
        stores the endpoint and view arguments on the request object
        is successful, otherwise the exception is stored.
        """
        rv = _request_ctx_stack.top.url_adapter.match()
        request.endpoint, request.view_args = rv
        return rv

    def dispatch_request(self):
        """Does the request dispatching.  Matches the URL and returns the
        return value of the view or error handler.  This does not have to
        be a response object.  In order to convert the return value to a
        proper response object, call :func:`make_response`.
        """
        try:
            endpoint, values = self.match_request()
            return self.view_functions[endpoint](**values)
        except HTTPException, e:
            handler = self.error_handlers.get(e.code)
            if handler is None:
                return e
            return handler(e)
        except Exception, e:
            handler = self.error_handlers.get(500)
            if self.debug or handler is None:
                raise
            return handler(e)
Esempio n. 37
0
class TrytondWSGI(object):
    def __init__(self):
        self.url_map = Map([], converters={
            'base64': Base64Converter,
        })
        self.protocols = [JSONProtocol, XMLProtocol]
        self.error_handlers = []

    def route(self, string, methods=None):
        def decorator(func):
            self.url_map.add(Rule(string, endpoint=func, methods=methods))
            return func

        return decorator

    def error_handler(self, handler):
        self.error_handlers.append(handler)
        return handler

    @wrapt.decorator
    def auth_required(self, wrapped, instance, args, kwargs):
        request = args[0]
        if request.user_id:
            return wrapped(*args, **kwargs)
        else:
            response = Response(None, http.client.UNAUTHORIZED,
                                {'WWW-Authenticate': 'Basic realm="Tryton"'})
            abort(http.client.UNAUTHORIZED, response=response)

    def check_request_size(self, request, size=None):
        if request.method not in {'POST', 'PUT', 'PATCH'}:
            return
        if size is None:
            if request.user_id:
                max_size = config.getint('request', 'max_size_authenticated')
            else:
                max_size = config.getint('request', 'max_size')
        else:
            max_size = size
        if max_size:
            content_length = request.content_length
            if content_length is None:
                abort(http.client.LENGTH_REQUIRED)
            elif content_length > max_size:
                abort(http.client.REQUEST_ENTITY_TOO_LARGE)

    def dispatch_request(self, request):
        adapter = self.url_map.bind_to_environ(request.environ)
        try:
            endpoint, request.view_args = adapter.match()
            max_request_size = getattr(endpoint, 'max_request_size', None)
            self.check_request_size(request, max_request_size)
            return endpoint(request, **request.view_args)
        except HTTPException as e:
            return e
        except Exception as e:
            tb_s = ''.join(traceback.format_exception(*sys.exc_info()))
            for path in sys.path:
                tb_s = tb_s.replace(path, '')
            e.__format_traceback__ = tb_s
            response = e
            for error_handler in self.error_handlers:
                rv = error_handler(self, request, e)
                if isinstance(rv, Response):
                    response = rv
            return response

    def make_response(self, request, data):
        for cls in self.protocols:
            for mimetype, _ in request.accept_mimetypes:
                if cls.content_type in mimetype:
                    response = cls.response(data, request)
                    break
            else:
                continue
            break
        else:
            for cls in self.protocols:
                if cls.content_type in request.environ.get('CONTENT_TYPE', ''):
                    response = cls.response(data, request)
                    break
            else:
                if isinstance(data, Exception):
                    response = InternalServerError(data)
                else:
                    response = Response(data)
        return response

    def wsgi_app(self, environ, start_response):
        for cls in self.protocols:
            if cls.content_type in environ.get('CONTENT_TYPE', ''):
                request = cls.request(environ)
                break
        else:
            request = Request(environ)

        origin = request.headers.get('Origin')
        origin_host = urllib.parse.urlparse(origin).netloc if origin else ''
        host = request.headers.get('Host')
        if origin and origin_host != host:
            cors = filter(None,
                          config.get('web', 'cors', default='').splitlines())
            if origin not in cors:
                abort(HTTPStatus.FORBIDDEN)

        data = self.dispatch_request(request)
        if not isinstance(data, (Response, HTTPException)):
            response = self.make_response(request, data)
        else:
            response = data

        if origin and isinstance(response, Response):
            response.headers['Access-Control-Allow-Origin'] = origin
            response.headers['Vary'] = 'Origin'
            method = request.headers.get('Access-Control-Request-Method')
            if method:
                response.headers['Access-Control-Allow-Methods'] = method
            headers = request.headers.get('Access-Control-Request-Headers')
            if headers:
                response.headers['Access-Control-Allow-Headers'] = headers
            response.headers['Access-Control-Max-Age'] = config.getint(
                'web', 'cache_timeout')
        return response(environ, start_response)

    def __call__(self, environ, start_response):
        return self.wsgi_app(environ, start_response)
Esempio n. 38
0
class SimpleURL(Brubeck):
    """SimpleURL is alternate to Brubeck Regex based routing.
    SimpleURL uses werkzeug routing to make routing simpler.
    """
    def __init__(self, brubeck_object, *args, **kwargs):
        super(Brubeck, self).__init__()
        # copy all brubeck object attributes to simpleurl.
        for key, val in brubeck_object.__dict__.iteritems():
            setattr(self, key, val)
        #: Werkzeug Rule class
        self.url_rule_class = Rule
        #: Map object which stores all the Rules
        self.url_map = Map()
        #: A dictionary of all view functions registered.  The keys will
        #: be function names which are also used to generate URLs and
        #: the values are the function objects themselves.
        #: To register a view function, use the :meth:`route` decorator.
        self.view_functions = {}
        # if handler_tuples add routes
        if brubeck_object.handler_tuples is not None:
            self.handler_tuples = brubeck_object.handler_tuples
        # Keep state for handler_tuples so that we needn't add routes in route_message
        # every time request is received
        self.is_handler_tuples_added = False
        #Set kwargs key, value since other extensions values may be passed
        for key, value in kwargs.items():
            setattr(self, key, value)

    def init_routes(self, handler_tuples):
        """Loops over a list of (rule, handler) tuples and adds them
        to the routing table.
        """
        for ht in handler_tuples:
            (rule, kallable) = ht
            # create a instance of callable and then map respective http method to
            # class method
            obj = kallable(self, self.brubeck_object.message)
            self.class_handlers.append({
                'rule': rule,
                'methods': pair[0],
                'kallable': kallable
            } for pair in inspect.getmembers(obj, predicate=inspect.ismethod)
                                       if pair[0].upper() in HTTP_METHODS)

    def add_route_url(self, rule, endpoint=None, view_func=None, **options):
        """Registration point for all URL rules.
        Basically this example::

            @app.add_route('/')
            def index(application, message):
                pass

        Is equivalent to the following::

            def index():
                pass
            app.add_route_rule('/', 'index', index)

        If the view_func is not provided you will need to connect the endpoint
        to a view function like so::

            app.view_functions['index'] = index

        :param rule: the URL rule as string
        :param endpoint: the endpoint for the registered URL rule.  Flask
                         itself assumes the name of the view function as
                         endpoint
        :param view_func: the function to call when serving a request to the
                          provided endpoint
        :param options: the options to be forwarded to the underlying
                        :class:`~werkzeug.routing.Rule` object.  A change
                        to Werkzeug is handling of method options.  methods
                        is a list of methods this rule should be limited
                        to (`GET`, `POST` etc.).  By default a rule
                        just listens for `GET` (and implicitly `HEAD`).
        """
        if endpoint is None:
            endpoint = _return_func_name(view_func)
        options['endpoint'] = endpoint
        method = options.pop('method', None)

        if method is None:
            method = getattr(view_func, 'method', None) or ('GET', )
        method = set(method)

        # Methods that should always be added
        required_method = set(getattr(view_func, 'required_methods', ()))

        # Add the required methods now.
        method |= required_method

        # due to a werkzeug bug we need to make sure that the defaults are
        # None if they are an empty dictionary.  This should not be necessary
        # with Werkzeug 0.7
        options['defaults'] = options.get('defaults') or None
        rule = self.url_rule_class(rule, methods=method, **options)
        #rule.provide_automatic_options = provide_automatic_options

        self.url_map.add(rule)
        if view_func is not None:
            old_func = self.view_functions.get(endpoint)
            if old_func is not None and old_func is not view_func:
                raise AssertionError('View function mapping is overwriting an '
                                     'existing endpoint function: %s' %
                                     endpoint)
            self.view_functions[endpoint] = view_func

    def add_route(self, rule, method=['GET'], **options):
        """A decorator that is used to register a view function for a
        given URL rule.  This does the same thing as :meth:`add_url_rule`
        but is intended for decorator usage::

            @app.add_route('/', method=['GET', 'POST'])
            def index(application, message):
                if message.method == 'GET':
                    # import render from brubeck.request_handling
                    return render("Foo Bar", 200, 'OK', {})
                    return render('Hello World'
                elif message.method == 'POST':
                    # process the arguments
                    # return render

            # Passing default values
            @app.add_route('/all/', defaults={'ids': 1}, method=['GET', 'POST'])
            @app.add_route('/all/<ids>', method=['GET', 'POST'])
            def process(application, message, ids):
                body = 'you passed id as %s' % (str(ids))
                return render(body, 200, 'OK', {})


        :param rule: the URL rule as string
        :param endpoint: the endpoint for the registered URL rule.  Flask
                         itself assumes the name of the view function as
                         endpoint
        :param view_func: the function to call when serving a request to the
                          provided endpoint
        :param options: the options to be forwarded to the underlying
                        :class:`~werkzeug.routing.Rule` object.  A change
                        to Werkzeug is handling of method options.  methods
                        is a list of methods this rule should be limited
                        to (`GET`, `POST` etc.).
        """
        def decorator(f):
            endpoint = options.pop('endpoint', None)
            self.add_route_url(rule, endpoint, f, **options)
            return f

        return decorator

    def check_handler_tuples(self):
        if self.handler_tuples is not None and not self.is_handler_tuples_added:
            for ht in self.handler_tuples:
                (rule, kallable) = ht
                # create a instance of callable and then map respective http method to
                # class method
                #obj = kallable(self, message, )
                if not inspect.isclass(kallable):
                    # currently tiple hadnlers don't mentin method make GET, HEAD as default
                    self.add_route_url(rule=rule,
                                       method=['GET', 'HEAD'],
                                       view_func=kallable)
                else:
                    for pair in inspect.getmembers(kallable,
                                                   predicate=inspect.ismethod):
                        method = pair[0].upper()
                        if method in HTTP_METHODS:
                            self.add_route_url(
                                rule=rule,
                                endpoint='_'.join([kallable.__name__, method]),
                                method=[method],
                                view_func=kallable)
            # set handler_tuples state to true
            self.is_handler_tuples_added = True

    def route_message(self, message):
        self.check_handler_tuples()
        self.url_rule_class.add = self.url_map
        #print self.url_map, self.url_rule_class
        # FIX ME: Figure out different values of url_scheme

        # check whether mongrel2 is serving or WSGI Server
        # https://github.com/j2labs/brubeck/blob/master/brubeck/request.py
        handler = None
        if message.is_wsgi:
            server_name = message.headers['HTTP_HOST']
            url_scheme = message.headers['wsgi.url_scheme']
            default_method = message.headers['METHOD']
        else:
            server_name = message.headers[u'host']
            url_scheme = message.headers[u'VERSION'].split('/')[0]
            default_method = message.method
        subdomain = extract(server_name).subdomain
        arguments = message.arguments or None
        path_info = message.path
        self.urls = self.url_map.bind(server_name=server_name,
                                      url_scheme=url_scheme,
                                      subdomain=subdomain,
                                      default_method=default_method,
                                      path_info=path_info,
                                      query_args=arguments)
        try:
            endpoint = self.urls.match(message.path)
            kallable = self.view_functions[endpoint[0]]
            if inspect.isclass(kallable):
                handler = kallable(self, message)
                handler._url_args = endpoint[-1]
                return handler
            else:
                handler = lambda: kallable(self, message, **endpoint[1])
                return handler
        except RequestRedirect, e:
            handler = self.base_handler(self, message)
            handler.set_status(e.name)
        except HTTPException, e:
            handler = self.base_handler(self, message)
            handler.set_status(e.name)
Esempio n. 39
0
class Flask(object):
    """The flask object implements a WSGI application and acts as the central
    object.  It is passed the name of the module or package of the application
    and optionally a configuration.  When it's created it sets up the
    template engine and provides ways to register view functions.
    """

    #: the class that is used for request objects
    request_class = FlaskRequest

    #: the class that is used for response objects
    response_class = FlaskResponse

    #: path for the static files.  If you don't want to use static files
    #: you can set this value to `None` in which case no URL rule is added
    #: and the development server will no longer serve any static files.
    static_path = '/static'

    #: if a secret key is set, cryptographic components can use this to
    #: sign cookies and other things.  Set this to a complex random value
    #: when you want to use the secure cookie for instance.
    secret_key = None

    #: The secure cookie uses this for the name of the session cookie
    session_cookie_name = 'session'

    #: options that are passed directly to the Jinja2 environment
    jinja_options = dict(
        autoescape=True,
        extensions=['jinja2.ext.autoescape', 'jinja2.ext.with_'])

    def __init__(self, package_name):
        self.debug = False
        self.package_name = package_name
        self.view_functions = {}
        self.error_handlers = {}
        self.request_init_funcs = []
        self.request_shutdown_funcs = []
        self.url_map = Map()

        if self.static_path is not None:
            self.url_map.add(
                Rule(self.static_path + '/<filename>',
                     build_only=True,
                     endpoint='static'))

        self.jinja_env = Environment(loader=self.create_jinja_loader(),
                                     **self.jinja_options)
        self.jinja_env.globals.update(
            url_for=url_for,
            request=request,
            session=session,
            g=g,
            get_flashed_messages=get_flashed_messages)

    def create_jinja_loader(self):
        """Creates the Jinja loader.  By default just a package loader for
        the configured package is returned that looks up templates in the
        `templates` folder.  To add other loaders it's possible to
        override this method.
        """
        return PackageLoader(self.package_name)

    def run(self, host='localhost', port=5000, **options):
        """Runs the application on a local development server"""
        from werkzeug import run_simple
        if 'debug' in options:
            self.debug = options.pop('debug')
        if self.static_path is not None:
            options['static_files'] = {
                self.static_path: (self.package_name, 'static')
            }
        options.setdefault('use_reloader', self.debug)
        options.setdefault('use_debugger', self.debug)
        return run_simple(host, port, self, **options)

    @cached_property
    def test(self):
        """A test client for this application"""
        from werkzeug import Client
        return Client(self, self.response_class, use_cookies=True)

    def open_resource(self, resource):
        """Opens a resource from the application's resource folder"""
        return pkg_resources.resource_stream(self.package_name, resource)

    def open_session(self, request):
        """Creates or opens a new session.  Default implementation requires
        that `securecookie.secret_key` is set.
        """
        key = self.secret_key
        if key is not None:
            return SecureCookie.load_cookie(request,
                                            self.session_cookie_name,
                                            secret_key=key)

    def save_session(self, session, response):
        """Saves the session if it needs updates."""
        if session is not None:
            session.save_cookie(response, self.session_cookie_name)

    def route(self, rule, **options):
        """A decorator that is used to register a view function for a
        given URL rule.  Example::

            @app.route('/')
            def index():
                return 'Hello World'
        """
        def decorator(f):
            if 'endpoint' not in options:
                options['endpoint'] = f.__name__
            self.url_map.add(Rule(rule, **options))
            self.view_functions[options['endpoint']] = f
            return f

        return decorator

    def errorhandler(self, code):
        """A decorator that is used to register a function give a given
        error code.  Example::

            @app.errorhandler(404)
            def page_not_found():
                return 'This page does not exist', 404
        """
        def decorator(f):
            self.error_handlers[code] = f
            return f

        return decorator

    def request_init(self, f):
        """Registers a function to run before each request."""
        self.request_init_funcs.append(f)
        return f

    def request_shutdown(self, f):
        """Register a function to be run after each request."""
        self.request_shutdown_funcs.append(f)
        return f

    def match_request(self):
        """Matches the current request against the URL map and also
        stores the endpoint and view arguments on the request object
        is successful, otherwise the exception is stored.
        """
        rv = _request_ctx_stack.top.url_adapter.match()
        request.endpoint, request.view_args = rv
        return rv

    def dispatch_request(self):
        """Does the request dispatching.  Matches the URL and returns the
        return value of the view or error handler.  This does not have to
        be a response object.  In order to convert the return value to a
        proper response object, call :func:`make_response`.
        """
        try:
            endpoint, values = self.match_request()
            return self.view_functions[endpoint](**values)
        except HTTPException, e:
            handler = self.error_handlers.get(e.code)
            if handler is None:
                return e
            return handler(e)
        except Exception, e:
            handler = self.error_handlers.get(500)
            if self.debug or handler is None:
                raise
            return handler(e)
Esempio n. 40
0
class FrontEndApp(object):
    """Orchestrates pywb's core Wayback Machine functionality and is comprised of 2 core sub-apps and 3 optional apps.

    Sub-apps:
      - WarcServer: Serves the archive content (WARC/ARC and index) as well as from the live web in record/proxy mode
      - RewriterApp: Rewrites the content served by pywb (if it is to be rewritten)
      - WSGIProxMiddleware (Optional): If proxy mode is enabled, performs pywb's HTTP(s) proxy functionality
      - AutoIndexer (Optional): If auto-indexing is enabled for the collections it is started here
      - RecorderApp (Optional): Recording functionality, available when recording mode is enabled
    """

    REPLAY_API = 'http://localhost:%s/{coll}/resource/postreq'
    CDX_API = 'http://localhost:%s/{coll}/index'
    RECORD_SERVER = 'http://localhost:%s'
    RECORD_API = 'http://localhost:%s/%s/resource/postreq?param.recorder.coll={coll}'

    RECORD_ROUTE = '/record'

    PROXY_CA_NAME = 'pywb HTTPS Proxy CA'

    PROXY_CA_PATH = os.path.join('proxy-certs', 'pywb-ca.pem')

    ALL_DIGITS = re.compile(r'^\d+$')

    def __init__(self, config_file='./config.yaml', custom_config=None):
        """
        :param str config_file: Path to the config file
        :param dict custom_config: Dictionary containing additional configuration information
        """
        self.handler = self.handle_request
        self.warcserver = WarcServer(config_file=config_file,
                                     custom_config=custom_config)

        config = self.warcserver.config

        self.debug = config.get('debug', False)

        self.warcserver_server = GeventServer(self.warcserver, port=0)

        self.proxy_prefix = None  # the URL prefix to be used for the collection with proxy mode (e.g. /coll/id_/)
        self.proxy_coll = None  # the name of the collection that has proxy mode enabled
        self.init_proxy(config)

        self.init_recorder(config.get('recorder'))

        self.init_autoindex(config.get('autoindex'))

        static_path = config.get('static_url_path',
                                 'pywb/static/').replace('/', os.path.sep)
        self.static_handler = StaticHandler(static_path)

        self.cdx_api_endpoint = config.get('cdx_api_endpoint', '/cdx')

        self._init_routes()

        upstream_paths = self.get_upstream_paths(self.warcserver_server.port)

        framed_replay = config.get('framed_replay', True)
        self.rewriterapp = RewriterApp(framed_replay,
                                       config=config,
                                       paths=upstream_paths)

        self.templates_dir = config.get('templates_dir', 'templates')
        self.static_dir = config.get('static_dir', 'static')

        metadata_templ = os.path.join(self.warcserver.root_dir, '{coll}',
                                      'metadata.yaml')
        self.metadata_cache = MetadataCache(metadata_templ)

    def _init_routes(self):
        """Initialize the routes and based on the configuration file makes available
        specific routes (proxy mode, record)"""
        self.url_map = Map()
        self.url_map.add(
            Rule('/static/_/<coll>/<path:filepath>',
                 endpoint=self.serve_static))
        self.url_map.add(
            Rule('/static/<path:filepath>', endpoint=self.serve_static))
        self.url_map.add(Rule('/collinfo.json', endpoint=self.serve_listing))

        if self.is_valid_coll('$root'):
            coll_prefix = ''
        else:
            coll_prefix = '/<coll>'
            self.url_map.add(Rule('/', endpoint=self.serve_home))

        self.url_map.add(
            Rule(coll_prefix + self.cdx_api_endpoint, endpoint=self.serve_cdx))
        self.url_map.add(Rule(coll_prefix + '/',
                              endpoint=self.serve_coll_page))
        self.url_map.add(
            Rule(coll_prefix + '/timemap/<timemap_output>/<path:url>',
                 endpoint=self.serve_content))

        if self.recorder_path:
            self.url_map.add(
                Rule(coll_prefix + self.RECORD_ROUTE + '/<path:url>',
                     endpoint=self.serve_record))

        if self.proxy_prefix is not None:
            # Add the proxy-fetch endpoint to enable PreservationWorker to make CORS fetches worry free in proxy mode
            self.url_map.add(
                Rule('/proxy-fetch/<path:url>',
                     endpoint=self.proxy_fetch,
                     methods=['GET', 'HEAD', 'OPTIONS']))
        self.url_map.add(
            Rule(coll_prefix + '/<path:url>', endpoint=self.serve_content))

    def get_upstream_paths(self, port):
        """Retrieve a dictionary containing the full URLs of the upstream apps

        :param int port: The port used by the replay and cdx servers
        :return: A dictionary containing the upstream paths (replay, cdx-server, record [if enabled])
        :rtype: dict[str, str]
        """
        base_paths = {
            'replay': self.REPLAY_API % port,
            'cdx-server': self.CDX_API % port,
        }

        if self.recorder_path:
            base_paths['record'] = self.recorder_path

        return base_paths

    def init_recorder(self, recorder_config):
        """Initialize the recording functionality of pywb. If recording_config is None this function is a no op"""
        if not recorder_config:
            self.recorder = None
            self.recorder_path = None
            return

        if isinstance(recorder_config, str):
            recorder_coll = recorder_config
            recorder_config = {}
        else:
            recorder_coll = recorder_config['source_coll']

        # TODO: support dedup
        dedup_index = None
        warc_writer = MultiFileWARCWriter(
            self.warcserver.archive_paths,
            max_size=int(recorder_config.get('rollover_size', 1000000000)),
            max_idle_secs=int(recorder_config.get('rollover_idle_secs', 600)),
            filename_template=recorder_config.get('filename_template'),
            dedup_index=dedup_index)

        self.recorder = RecorderApp(
            self.RECORD_SERVER % str(self.warcserver_server.port),
            warc_writer,
            accept_colls=recorder_config.get('source_filter'))

        recorder_server = GeventServer(self.recorder, port=0)

        self.recorder_path = self.RECORD_API % (recorder_server.port,
                                                recorder_coll)

    def init_autoindex(self, auto_interval):
        """Initialize and start the auto-indexing of the collections. If auto_interval is None this is a no op.

        :param str|int auto_interval: The auto-indexing interval from the configuration file or CLI argument
        """
        if not auto_interval:
            return

        from pywb.manager.autoindex import AutoIndexer

        colls_dir = self.warcserver.root_dir if self.warcserver.root_dir else None

        indexer = AutoIndexer(colls_dir=colls_dir, interval=int(auto_interval))

        if not os.path.isdir(indexer.root_path):
            msg = 'No managed directory "{0}" for auto-indexing'
            logging.error(msg.format(indexer.root_path))
            import sys
            sys.exit(2)

        msg = 'Auto-Indexing Enabled on "{0}", checking every {1} secs'
        logging.info(msg.format(indexer.root_path, auto_interval))
        indexer.start()

    def is_proxy_enabled(self, environ):
        return self.proxy_prefix is not None and 'wsgiprox.proxy_host' in environ

    def serve_home(self, environ):
        """Serves the home (/) view of pywb (not a collections)

        :param dict environ: The WSGI environment dictionary for the request
        :return: The WbResponse for serving the home (/) path
        :rtype: WbResponse
        """
        home_view = BaseInsertView(self.rewriterapp.jinja_env, 'index.html')
        fixed_routes = self.warcserver.list_fixed_routes()
        dynamic_routes = self.warcserver.list_dynamic_routes()

        routes = fixed_routes + dynamic_routes

        all_metadata = self.metadata_cache.get_all(dynamic_routes)

        content = home_view.render_to_string(environ,
                                             routes=routes,
                                             all_metadata=all_metadata)

        return WbResponse.text_response(
            content, content_type='text/html; charset="utf-8"')

    def serve_static(self, environ, coll='', filepath=''):
        """Serve a static file associated with a specific collection or one of pywb's own static assets

        :param dict environ: The WSGI environment dictionary for the request
        :param str coll: The collection the static file is associated with
        :param str filepath: The file path (relative to the collection) for the static assest
        :return: The WbResponse for the static asset
        :rtype: WbResponse
        """
        proxy_enabled = self.is_proxy_enabled(environ)
        if proxy_enabled and environ.get('REQUEST_METHOD') == 'OPTIONS':
            return WbResponse.options_response(environ)
        if coll:
            path = os.path.join(self.warcserver.root_dir, coll,
                                self.static_dir)
        else:
            path = self.static_dir

        environ['pywb.static_dir'] = path
        try:
            response = self.static_handler(environ, filepath)
            if proxy_enabled:
                response.add_access_control_headers(env=environ)
            return response
        except:
            self.raise_not_found(environ,
                                 'Static File Not Found: {0}'.format(filepath))

    def get_metadata(self, coll):
        """Retrieve the metadata associated with a collection

        :param str coll: The name of the collection to receive metadata for
        :return: The collections metadata if it exists
        :rtype: dict
        """
        #if coll == self.all_coll:
        #    coll = '*'

        metadata = {'coll': coll, 'type': 'replay'}

        if coll in self.warcserver.list_fixed_routes():
            metadata.update(self.warcserver.get_coll_config(coll))
        else:
            metadata.update(self.metadata_cache.load(coll))

        return metadata

    def serve_coll_page(self, environ, coll='$root'):
        """Render and serve a collections search page (search.html).

        :param dict environ: The WSGI environment dictionary for the request
        :param str coll: The name of the collection to serve the collections search page for
        :return: The WbResponse containing the collections search page
        :rtype: WbResponse
        """
        if not self.is_valid_coll(coll):
            self.raise_not_found(environ, 'No handler for "/{0}"'.format(coll))

        self.setup_paths(environ, coll)

        metadata = self.get_metadata(coll)

        view = BaseInsertView(self.rewriterapp.jinja_env, 'search.html')

        wb_prefix = environ.get('SCRIPT_NAME', '')
        if wb_prefix:
            wb_prefix += '/'

        content = view.render_to_string(environ,
                                        wb_prefix=wb_prefix,
                                        metadata=metadata,
                                        coll=coll)

        return WbResponse.text_response(
            content, content_type='text/html; charset="utf-8"')

    def serve_cdx(self, environ, coll='$root'):
        """Make the upstream CDX query for a collection and response with the results of the query

        :param dict environ: The WSGI environment dictionary for the request
        :param str coll: The name of the collection this CDX query is for
        :return: The WbResponse containing the results of the CDX query
        :rtype: WbResponse
        """
        base_url = self.rewriterapp.paths['cdx-server']

        #if coll == self.all_coll:
        #    coll = '*'

        cdx_url = base_url.format(coll=coll)

        if environ.get('QUERY_STRING'):
            cdx_url += '&' if '?' in cdx_url else '?'
            cdx_url += environ.get('QUERY_STRING')

        try:
            res = requests.get(cdx_url, stream=True)

            content_type = res.headers.get('Content-Type')

            return WbResponse.bin_stream(StreamIter(res.raw),
                                         content_type=content_type)

        except Exception as e:
            return WbResponse.text_response('Error: ' + str(e),
                                            status='400 Bad Request')

    def serve_record(self, environ, coll='$root', url=''):
        """Serve a URL's content from a WARC/ARC record in replay mode or from the live web in
        live, proxy, and record mode.

        :param dict environ: The WSGI environment dictionary for the request
        :param str coll: The name of the collection the record is to be served from
        :param str url: The URL for the corresponding record to be served if it exists
        :return: WbResponse containing the contents of the record/URL
        :rtype: WbResponse
        """
        if coll in self.warcserver.list_fixed_routes():
            return WbResponse.text_response(
                'Error: Can Not Record Into Custom Collection "{0}"'.format(
                    coll))

        return self.serve_content(environ, coll, url, record=True)

    def serve_content(self,
                      environ,
                      coll='$root',
                      url='',
                      timemap_output='',
                      record=False):
        """Serve the contents of a URL/Record rewriting the contents of the response when applicable.

        :param dict environ: The WSGI environment dictionary for the request
        :param str coll: The name of the collection the record is to be served from
        :param str url: The URL for the corresponding record to be served if it exists
        :param str timemap_output: The contents of the timemap included in the link header of the response
        :param bool record: Should the content being served by recorded (save to a warc). Only valid in record mode
        :return: WbResponse containing the contents of the record/URL
        :rtype: WbResponse
        """
        if not self.is_valid_coll(coll):
            self.raise_not_found(environ, 'No handler for "/{0}"'.format(coll))

        self.setup_paths(environ, coll, record)

        request_uri = environ.get('REQUEST_URI')
        script_name = environ.get('SCRIPT_NAME', '') + '/'
        if request_uri and request_uri.startswith(script_name):
            wb_url_str = request_uri[len(script_name):]

        else:
            wb_url_str = to_native_str(url)

            if environ.get('QUERY_STRING'):
                wb_url_str += '?' + environ.get('QUERY_STRING')

        metadata = self.get_metadata(coll)
        if record:
            metadata['type'] = 'record'

        if timemap_output:
            metadata['output'] = timemap_output
            # ensure that the timemap path information is not included
            wb_url_str = wb_url_str.replace(
                'timemap/{0}/'.format(timemap_output), '')
        try:
            response = self.rewriterapp.render_content(wb_url_str, metadata,
                                                       environ)
        except UpstreamException as ue:
            response = self.rewriterapp.handle_error(environ, ue)
            raise HTTPException(response=response)
        return response

    def setup_paths(self, environ, coll, record=False):
        """Populates the WSGI environment dictionary with the path information necessary to perform a response for
        content or record.

        :param dict environ: The WSGI environment dictionary for the request
        :param str coll: The name of the collection the record is to be served from
        :param bool record: Should the content being served by recorded (save to a warc). Only valid in record mode
        """
        if not coll or not self.warcserver.root_dir:
            return

        if coll != '$root':
            pop_path_info(environ)
            if record:
                pop_path_info(environ)

        paths = [self.warcserver.root_dir]

        if coll != '$root':
            paths.append(coll)

        paths.append(self.templates_dir)

        # jinja2 template paths always use '/' as separator
        environ['pywb.templates_dir'] = '/'.join(paths)

    def serve_listing(self, environ):
        """Serves the response for WARCServer fixed and dynamic listing (paths)

        :param dict environ: The WSGI environment dictionary for the request
        :return: WbResponse containing the frontend apps WARCServer URL paths
        :rtype: WbResponse
        """
        result = {
            'fixed': self.warcserver.list_fixed_routes(),
            'dynamic': self.warcserver.list_dynamic_routes()
        }

        return WbResponse.json_response(result)

    def is_valid_coll(self, coll):
        """Determines if the collection name for a request is valid (exists)

        :param str coll: The name of the collection to check
        :return: True if the collection is valid, false otherwise
        :rtype: bool
        """
        #if coll == self.all_coll:
        #    return True

        return (coll in self.warcserver.list_fixed_routes()
                or coll in self.warcserver.list_dynamic_routes())

    def raise_not_found(self, environ, msg):
        """Utility function for raising a werkzeug.exceptions.NotFound execption with the supplied WSGI environment
        and message.

        :param dict environ: The WSGI environment dictionary for the request
        :param str msg: The error message
        """
        raise NotFound(response=self.rewriterapp._error_response(environ, msg))

    def _check_refer_redirect(self, environ):
        """Returns a WbResponse for a HTTP 307 redirection if the HTTP referer header is the same as the HTTP host header

        :param dict environ: The WSGI environment dictionary for the request
        :return: WbResponse HTTP 307 redirection
        :rtype: WbResponse
        """
        referer = environ.get('HTTP_REFERER')
        if not referer:
            return

        host = environ.get('HTTP_HOST')
        if host not in referer:
            return

        inx = referer[1:].find('http')
        if not inx:
            inx = referer[1:].find('///')
            if inx > 0:
                inx + 1

        if inx < 0:
            return

        url = referer[inx + 1:]
        host = referer[:inx + 1]

        orig_url = environ['PATH_INFO']
        if environ.get('QUERY_STRING'):
            orig_url += '?' + environ['QUERY_STRING']

        full_url = host + urljoin(url, orig_url)
        return WbResponse.redir_response(full_url, '307 Redirect')

    def __call__(self, environ, start_response):
        return self.handler(environ, start_response)

    def handle_request(self, environ, start_response):
        """Retrieves the route handler and calls the handler returning its the response

        :param dict environ: The WSGI environment dictionary for the request
        :param start_response:
        :return: The WbResponse for the request
        :rtype: WbResponse
        """
        urls = self.url_map.bind_to_environ(environ)
        try:
            endpoint, args = urls.match()
            # store original script_name (original prefix) before modifications are made
            environ['pywb.app_prefix'] = environ.get('SCRIPT_NAME', '')

            response = endpoint(environ, **args)
            return response(environ, start_response)

        except HTTPException as e:
            redir = self._check_refer_redirect(environ)
            if redir:
                return redir(environ, start_response)

            return e(environ, start_response)

        except Exception as e:
            if self.debug:
                traceback.print_exc()

            response = self.rewriterapp._error_response(
                environ, 'Internal Error: ' + str(e), '500 Server Error')
            return response(environ, start_response)

    @classmethod
    def create_app(cls, port):
        """Create a new instance of FrontEndApp that listens on port with a hostname of 0.0.0.0

        :param int port: The port FrontEndApp is to listen on
        :return: A new instance of FrontEndApp wrapped in GeventServer
        :rtype: GeventServer
        """
        app = FrontEndApp()
        app_server = GeventServer(app, port=port, hostname='0.0.0.0')
        return app_server

    def init_proxy(self, config):
        """Initialize and start proxy mode. If proxy configuration entry is not contained in the config
        this is a no op. Causes handler to become an instance of WSGIProxMiddleware.

        :param dict config: The configuration object used to configure this instance of FrontEndApp
        """
        proxy_config = config.get('proxy')
        if not proxy_config:
            return

        if isinstance(proxy_config, str):
            proxy_coll = proxy_config
            proxy_config = {}
        else:
            proxy_coll = proxy_config['coll']

        if '/' in proxy_coll:
            raise Exception('Proxy collection can not contain "/"')

        proxy_config['ca_name'] = proxy_config.get('ca_name',
                                                   self.PROXY_CA_NAME)
        proxy_config['ca_file_cache'] = proxy_config.get(
            'ca_file_cache', self.PROXY_CA_PATH)

        if proxy_config.get('recording'):
            logging.info(
                'Proxy recording into collection "{0}"'.format(proxy_coll))
            if proxy_coll in self.warcserver.list_fixed_routes():
                raise Exception('Can not record into fixed collection')

            proxy_coll += self.RECORD_ROUTE
            if not config.get('recorder'):
                config['recorder'] = 'live'

        else:
            logging.info(
                'Proxy enabled for collection "{0}"'.format(proxy_coll))

        if proxy_config.get('enable_content_rewrite', True):
            self.proxy_prefix = '/{0}/bn_/'.format(proxy_coll)
        else:
            self.proxy_prefix = '/{0}/id_/'.format(proxy_coll)

        self.proxy_default_timestamp = proxy_config.get('default_timestamp')
        if self.proxy_default_timestamp:
            if not self.ALL_DIGITS.match(self.proxy_default_timestamp):
                try:
                    self.proxy_default_timestamp = iso_date_to_timestamp(
                        self.proxy_default_timestamp)
                except:
                    raise Exception(
                        'Invalid Proxy Timestamp: Must Be All-Digit Timestamp or ISO Date Format'
                    )

        self.proxy_coll = proxy_coll

        self.handler = WSGIProxMiddleware(self.handle_request,
                                          self.proxy_route_request,
                                          proxy_host=proxy_config.get(
                                              'host', 'pywb.proxy'),
                                          proxy_options=proxy_config)

    def proxy_route_request(self, url, environ):
        """ Return the full url that this proxy request will be routed to
        The 'environ' PATH_INFO and REQUEST_URI will be modified based on the returned url

        Default is to use the 'proxy_prefix' to point to the proxy collection
        """
        if self.proxy_default_timestamp:
            environ[
                'pywb_proxy_default_timestamp'] = self.proxy_default_timestamp

        return self.proxy_prefix + url

    def proxy_fetch(self, env, url):
        """Proxy mode only endpoint that handles OPTIONS requests and COR fetches for Preservation Worker.

        Due to normal cross-origin browser restrictions in proxy mode, auto fetch worker cannot access the CSS rules
        of cross-origin style sheets and must re-fetch them in a manner that is CORS safe. This endpoint facilitates
        that by fetching the stylesheets for the auto fetch worker and then responds with its contents

        :param dict env: The WSGI environment dictionary
        :param str url:  The URL of the resource to be fetched
        :return: WbResponse that is either response to an Options request or the results of fetching url
        :rtype: WbResponse
        """
        if not self.is_proxy_enabled(env):
            # we are not in proxy mode so just respond with forbidden
            return WbResponse.text_response(
                'proxy mode must be enabled to use this endpoint',
                status='403 Forbidden')

        if env.get('REQUEST_METHOD') == 'OPTIONS':
            return WbResponse.options_response(env)

        # ensure full URL
        request_url = env['REQUEST_URI']
        # replace with /id_ so we do not get rewritten
        url = request_url.replace('/proxy-fetch', '/id_')
        # update WSGI environment object
        env['REQUEST_URI'] = self.proxy_coll + url
        env['PATH_INFO'] = env['PATH_INFO'].replace('/proxy-fetch',
                                                    self.proxy_coll + '/id_')
        # make request using normal serve_content
        response = self.serve_content(env, self.proxy_coll, url)
        # for WR
        if isinstance(response, WbResponse):
            response.add_access_control_headers(env=env)
        return response
Esempio n. 41
0
class RabbitMqCommServer(CommServer):
    """CommServer for RabbitMQ server"""

    def __init__(self, server_resources, configuration):
        super().__init__(server_resources, configuration)

        self._url_map = Map()
        self._endpoints = dict()

        self._create_rules()

        credentials = pika.PlainCredentials(
                self._configuration.value('user'),
                self._configuration.value('password')
        )

        self._connection = pika.BlockingConnection(
                pika.ConnectionParameters(
                        host=self._configuration.value('server'),
                        virtual_host=self._configuration.value('vhost'),
                        credentials=credentials
                )
        )
        self._channel = self._connection.channel()
        self._channel.queue_declare(queue=self._configuration.value('queue'))
        self._channel.basic_qos(
                prefetch_count=self._configuration.value('prefetch')
        )
        self._channel.basic_consume(
                self.on_request,
                queue=self._configuration.value('queue')
        )

    def _dispatch_request(self, body):
        """Dispatches request and returns the response

        Args:
            body: str

        Return:
            HTTP Response
        """
        json_request = JsonRequest.plain_factory(content=body)

        router = self._url_map.bind(
                server_name='',
                path_info=json_request.resource
        )

        try:
            endpoint_name, values = router.match()

        except NotFound:
            return self.not_found()

        endpoint = self._endpoints[endpoint_name]

        try:
            http_response = getattr(
                    endpoint,
                    json_request.method
            )(json_request, **values)

        except NotImplementedError:
            return self.method_not_allowed()

        return http_response

    def _create_rules(self):
        while self._resources:
            resource = self._resources.pop(0)
            endpoint_name = resource.endpoint_class.__name__
            self._endpoints[endpoint_name] = resource.endpoint_class
            self._url_map.add(
                    Rule(resource.api_route, endpoint=endpoint_name)
            )

    def listen(self):
        self._channel.start_consuming()

    def stop(self):
        self._channel.cancel()

    def purge(self):
        self._channel.queue_purge(self._configuration.value('queue'))

    def reset(self):
        self._channel.queue_purge(self._configuration.value('queue'))
        self._channel.cancel()
        self._channel.queue_delete(self._configuration.value('queue'))

    def on_request(self, ch, method, properties, body):
        """Callback function launched when client catch a job

        Args:
            ch: object
            method: object
            properties: (not used here)
            body: str
        """
        http_response = self._dispatch_request(body)

        json_response = HttpResponseToJsonService.run(response=http_response)

        if json_response:
            ch.basic_publish(exchange=self._configuration.value('exchange'),
                             routing_key=properties.reply_to,
                             properties=pika.BasicProperties(
                                     correlation_id=properties.correlation_id
                             ),
                             body=json_response.to_json())

        ch.basic_ack(delivery_tag=method.delivery_tag)

        return
Esempio n. 42
0
class Flask(_PackageBoundObject):
    """The flask object implements a WSGI application and acts as the central
    object.  It is passed the name of the module or package of the
    application.  Once it is created it will act as a central registry for
    the view functions, the URL rules, template configuration and much more.

    The name of the package is used to resolve resources from inside the
    package or the folder the module is contained in depending on if the
    package parameter resolves to an actual python package (a folder with
    an `__init__.py` file inside) or a standard module (just a `.py` file).

    For more information about resource loading, see :func:`open_resource`.

    Usually you create a :class:`Flask` instance in your main module or
    in the `__init__.py` file of your package like this::

        from flask import Flask
        app = Flask(__name__)
    """

    #: the class that is used for request objects.  See :class:`~flask.request`
    #: for more information.
    request_class = Request

    #: the class that is used for response objects.  See
    #: :class:`~flask.Response` for more information.
    response_class = Response

    #: path for the static files.  If you don't want to use static files
    #: you can set this value to `None` in which case no URL rule is added
    #: and the development server will no longer serve any static files.
    static_path = '/static'

    #: if a secret key is set, cryptographic components can use this to
    #: sign cookies and other things.  Set this to a complex random value
    #: when you want to use the secure cookie for instance.
    secret_key = None

    #: The secure cookie uses this for the name of the session cookie
    session_cookie_name = 'session'

    #: A :class:`~datetime.timedelta` which is used to set the expiration
    #: date of a permanent session.  The default is 31 days which makes a
    #: permanent session survive for roughly one month.
    permanent_session_lifetime = timedelta(days=31)

    #: options that are passed directly to the Jinja2 environment
    jinja_options = ImmutableDict(
        autoescape=True,
        extensions=['jinja2.ext.autoescape', 'jinja2.ext.with_']
    )

    def __init__(self, import_name):
        _PackageBoundObject.__init__(self, import_name)

        #: the debug flag.  Set this to `True` to enable debugging of
        #: the application.  In debug mode the debugger will kick in
        #: when an unhandled exception ocurrs and the integrated server
        #: will automatically reload the application if changes in the
        #: code are detected.
        self.debug = False

        #: a dictionary of all view functions registered.  The keys will
        #: be function names which are also used to generate URLs and
        #: the values are the function objects themselves.
        #: to register a view function, use the :meth:`route` decorator.
        self.view_functions = {}

        #: a dictionary of all registered error handlers.  The key is
        #: be the error code as integer, the value the function that
        #: should handle that error.
        #: To register a error handler, use the :meth:`errorhandler`
        #: decorator.
        self.error_handlers = {}

        #: a dictionary with lists of functions that should be called at the
        #: beginning of the request.  The key of the dictionary is the name of
        #: the module this function is active for, `None` for all requests.
        #: This can for example be used to open database connections or
        #: getting hold of the currently logged in user.  To register a
        #: function here, use the :meth:`before_request` decorator.
        self.before_request_funcs = {}

        #: a dictionary with lists of functions that should be called after
        #: each request.  The key of the dictionary is the name of the module
        #: this function is active for, `None` for all requests.  This can for
        #: example be used to open database connections or getting hold of the
        #: currently logged in user.  To register a function here, use the
        #: :meth:`before_request` decorator.
        self.after_request_funcs = {}

        #: a dictionary with list of functions that are called without arguments
        #: to populate the template context.  They key of the dictionary is the
        #: name of the module this function is active for, `None` for all
        #: requests.  Each returns a dictionary that the template context is
        #: updated with.  To register a function here, use the
        #: :meth:`context_processor` decorator.
        self.template_context_processors = {
            None: [_default_template_ctx_processor]
        }

        #: the :class:`~werkzeug.routing.Map` for this instance.  You can use
        #: this to change the routing converters after the class was created
        #: but before any routes are connected.  Example::
        #:
        #:    from werkzeug import BaseConverter
        #:
        #:    class ListConverter(BaseConverter):
        #:        def to_python(self, value):
        #:            return value.split(',')
        #:        def to_url(self, values):
        #:            return ','.join(BaseConverter.to_url(value)
        #:                            for value in values)
        #:
        #:    app = Flask(__name__)
        #:    app.url_map.converters['list'] = ListConverter
        self.url_map = Map()

        if self.static_path is not None:
            self.add_url_rule(self.static_path + '/<filename>',
                              build_only=True, endpoint='static')
            if pkg_resources is not None:
                target = (self.import_name, 'static')
            else:
                target = os.path.join(self.root_path, 'static')
            self.wsgi_app = SharedDataMiddleware(self.wsgi_app, {
                self.static_path: target
            })

        #: the Jinja2 environment.  It is created from the
        #: :attr:`jinja_options` and the loader that is returned
        #: by the :meth:`create_jinja_loader` function.
        self.jinja_env = Environment(loader=self.create_jinja_loader(),
                                     **self.jinja_options)
        self.jinja_env.globals.update(
            url_for=url_for,
            get_flashed_messages=get_flashed_messages
        )
        self.jinja_env.filters['tojson'] = _tojson_filter

    def create_jinja_loader(self):
        """Creates the Jinja loader.  By default just a package loader for
        the configured package is returned that looks up templates in the
        `templates` folder.  To add other loaders it's possible to
        override this method.
        """
        if pkg_resources is None:
            return FileSystemLoader(os.path.join(self.root_path, 'templates'))
        return PackageLoader(self.import_name)

    def update_template_context(self, context):
        """Update the template context with some commonly used variables.
        This injects request, session and g into the template context.

        :param context: the context as a dictionary that is updated in place
                        to add extra variables.
        """
        funcs = self.template_context_processors[None]
        mod = _request_ctx_stack.top.request.module
        if mod is not None and mod in self.template_context_processors:
            funcs = chain(funcs, self.template_context_processors[mod])
        for func in funcs:
            context.update(func())

    def run(self, host='127.0.0.1', port=5000, **options):
        """Runs the application on a local development server.  If the
        :attr:`debug` flag is set the server will automatically reload
        for code changes and show a debugger in case an exception happened.

        :param host: the hostname to listen on.  set this to ``'0.0.0.0'``
                     to have the server available externally as well.
        :param port: the port of the webserver
        :param options: the options to be forwarded to the underlying
                        Werkzeug server.  See :func:`werkzeug.run_simple`
                        for more information.
        """
        from werkzeug import run_simple
        if 'debug' in options:
            self.debug = options.pop('debug')
        options.setdefault('use_reloader', self.debug)
        options.setdefault('use_debugger', self.debug)
        return run_simple(host, port, self, **options)

    def test_client(self):
        """Creates a test client for this application.  For information
        about unit testing head over to :ref:`testing`.
        """
        from werkzeug import Client
        return Client(self, self.response_class, use_cookies=True)

    def open_session(self, request):
        """Creates or opens a new session.  Default implementation stores all
        session data in a signed cookie.  This requires that the
        :attr:`secret_key` is set.

        :param request: an instance of :attr:`request_class`.
        """
        key = self.secret_key
        if key is not None:
            return Session.load_cookie(request, self.session_cookie_name,
                                       secret_key=key)

    def save_session(self, session, response):
        """Saves the session if it needs updates.  For the default
        implementation, check :meth:`open_session`.

        :param session: the session to be saved (a
                        :class:`~werkzeug.contrib.securecookie.SecureCookie`
                        object)
        :param response: an instance of :attr:`response_class`
        """
        expires = None
        if session.permanent:
            expires = datetime.utcnow() + self.permanent_session_lifetime
        session.save_cookie(response, self.session_cookie_name,
                            expires=expires, httponly=True)

    def register_module(self, module, **options):
        """Registers a module with this application.  The keyword argument
        of this function are the same as the ones for the constructor of the
        :class:`Module` class and will override the values of the module if
        provided.
        """
        options.setdefault('url_prefix', module.url_prefix)
        state = _ModuleSetupState(self, **options)
        for func in module._register_events:
            func(state)

    def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
        """Connects a URL rule.  Works exactly like the :meth:`route`
        decorator.  If a view_func is provided it will be registered with the
        endpoint.

        Basically this example::

            @app.route('/')
            def index():
                pass

        Is equivalent to the following::

            def index():
                pass
            app.add_url_rule('/', 'index', index)

        If the view_func is not provided you will need to connect the endpoint
        to a view function like so::

            app.view_functions['index'] = index

        .. versionchanged:: 0.2
           `view_func` parameter added.

        :param rule: the URL rule as string
        :param endpoint: the endpoint for the registered URL rule.  Flask
                         itself assumes the name of the view function as
                         endpoint
        :param view_func: the function to call when serving a request to the
                          provided endpoint
        :param options: the options to be forwarded to the underlying
                        :class:`~werkzeug.routing.Rule` object
        """
        if endpoint is None:
            assert view_func is not None, 'expected view func if endpoint ' \
                                          'is not provided.'
            endpoint = view_func.__name__
        options['endpoint'] = endpoint
        options.setdefault('methods', ('GET',))
        self.url_map.add(Rule(rule, **options))
        if view_func is not None:
            self.view_functions[endpoint] = view_func

    def route(self, rule, **options):
        """A decorator that is used to register a view function for a
        given URL rule.  Example::

            @app.route('/')
            def index():
                return 'Hello World'

        Variables parts in the route can be specified with angular
        brackets (``/user/<username>``).  By default a variable part
        in the URL accepts any string without a slash however a different
        converter can be specified as well by using ``<converter:name>``.

        Variable parts are passed to the view function as keyword
        arguments.

        The following converters are possible:

        =========== ===========================================
        `int`       accepts integers
        `float`     like `int` but for floating point values
        `path`      like the default but also accepts slashes
        =========== ===========================================

        Here some examples::

            @app.route('/')
            def index():
                pass

            @app.route('/<username>')
            def show_user(username):
                pass

            @app.route('/post/<int:post_id>')
            def show_post(post_id):
                pass

        An important detail to keep in mind is how Flask deals with trailing
        slashes.  The idea is to keep each URL unique so the following rules
        apply:

        1. If a rule ends with a slash and is requested without a slash
           by the user, the user is automatically redirected to the same
           page with a trailing slash attached.
        2. If a rule does not end with a trailing slash and the user request
           the page with a trailing slash, a 404 not found is raised.

        This is consistent with how web servers deal with static files.  This
        also makes it possible to use relative link targets safely.

        The :meth:`route` decorator accepts a couple of other arguments
        as well:

        :param rule: the URL rule as string
        :param methods: a list of methods this rule should be limited
                        to (``GET``, ``POST`` etc.).  By default a rule
                        just listens for ``GET`` (and implicitly ``HEAD``).
        :param subdomain: specifies the rule for the subdoain in case
                          subdomain matching is in use.
        :param strict_slashes: can be used to disable the strict slashes
                               setting for this rule.  See above.
        :param options: other options to be forwarded to the underlying
                        :class:`~werkzeug.routing.Rule` object.
        """
        def decorator(f):
            self.add_url_rule(rule, None, f, **options)
            return f
        return decorator

    def errorhandler(self, code):
        """A decorator that is used to register a function give a given
        error code.  Example::

            @app.errorhandler(404)
            def page_not_found():
                return 'This page does not exist', 404

        You can also register a function as error handler without using
        the :meth:`errorhandler` decorator.  The following example is
        equivalent to the one above::

            def page_not_found():
                return 'This page does not exist', 404
            app.error_handlers[404] = page_not_found

        :param code: the code as integer for the handler
        """
        def decorator(f):
            self.error_handlers[code] = f
            return f
        return decorator

    def template_filter(self, name=None):
        """A decorator that is used to register custom template filter.
        You can specify a name for the filter, otherwise the function
        name will be used. Example::

          @app.template_filter()
          def reverse(s):
              return s[::-1]

        :param name: the optional name of the filter, otherwise the
                     function name will be used.
        """
        def decorator(f):
            self.jinja_env.filters[name or f.__name__] = f
            return f
        return decorator

    def before_request(self, f):
        """Registers a function to run before each request."""
        self.before_request_funcs.setdefault(None, []).append(f)
        return f

    def after_request(self, f):
        """Register a function to be run after each request."""
        self.after_request_funcs.setdefault(None, []).append(f)
        return f

    def context_processor(self, f):
        """Registers a template context processor function."""
        self.template_context_processors[None].append(f)
        return f

    def dispatch_request(self):
        """Does the request dispatching.  Matches the URL and returns the
        return value of the view or error handler.  This does not have to
        be a response object.  In order to convert the return value to a
        proper response object, call :func:`make_response`.
        """
        req = _request_ctx_stack.top.request
        try:
            if req.routing_exception is not None:
                raise req.routing_exception
            return self.view_functions[req.endpoint](**req.view_args)
        except HTTPException, e:
            handler = self.error_handlers.get(e.code)
            if handler is None:
                return e
            return handler(e)
        except Exception, e:
            handler = self.error_handlers.get(500)
            if self.debug or handler is None:
                raise
            return handler(e)
Esempio n. 43
0
File: app.py Progetto: khameli/flask
class Flask(_PackageBoundObject):
    """The flask object implements a WSGI application and acts as the central
    object.  It is passed the name of the module or package of the
    application.  Once it is created it will act as a central registry for
    the view functions, the URL rules, template configuration and much more.

    The name of the package is used to resolve resources from inside the
    package or the folder the module is contained in depending on if the
    package parameter resolves to an actual python package (a folder with
    an `__init__.py` file inside) or a standard module (just a `.py` file).

    For more information about resource loading, see :func:`open_resource`.

    Usually you create a :class:`Flask` instance in your main module or
    in the `__init__.py` file of your package like this::

        from flask import Flask
        app = Flask(__name__)

    .. admonition:: About the First Parameter

        The idea of the first parameter is to give Flask an idea what
        belongs to your application.  This name is used to find resources
        on the file system, can be used by extensions to improve debugging
        information and a lot more.

        So it's important what you provide there.  If you are using a single
        module, `__name__` is always the correct value.  If you however are
        using a package, it's usually recommended to hardcode the name of
        your package there.

        For example if your application is defined in `yourapplication/app.py`
        you should create it with one of the two versions below::

            app = Flask('yourapplication')
            app = Flask(__name__.split('.')[0])

        Why is that?  The application will work even with `__name__`, thanks
        to how resources are looked up.  However it will make debugging more
        painful.  Certain extensions can make assumptions based on the
        import name of your application.  For example the Flask-SQLAlchemy
        extension will look for the code in your application that triggered
        an SQL query in debug mode.  If the import name is not properly set
        up, that debugging information is lost.  (For example it would only
        pick up SQL queries in `yourapplicaiton.app` and not
        `yourapplication.views.frontend`)

    .. versionadded:: 0.5
       The `static_path` parameter was added.

    :param import_name: the name of the application package
    :param static_path: can be used to specify a different path for the
                        static files on the web.  Defaults to ``/static``.
                        This does not affect the folder the files are served
                        *from*.
    """

    #: The class that is used for request objects.  See :class:`~flask.Request`
    #: for more information.
    request_class = Request

    #: The class that is used for response objects.  See
    #: :class:`~flask.Response` for more information.
    response_class = Response

    #: Path for the static files.  If you don't want to use static files
    #: you can set this value to `None` in which case no URL rule is added
    #: and the development server will no longer serve any static files.
    #:
    #: This is the default used for application and modules unless a
    #: different value is passed to the constructor.
    static_path = '/static'

    #: The debug flag.  Set this to `True` to enable debugging of the
    #: application.  In debug mode the debugger will kick in when an unhandled
    #: exception ocurrs and the integrated server will automatically reload
    #: the application if changes in the code are detected.
    #:
    #: This attribute can also be configured from the config with the `DEBUG`
    #: configuration key.  Defaults to `False`.
    debug = ConfigAttribute('DEBUG')

    #: The testing flask.  Set this to `True` to enable the test mode of
    #: Flask extensions (and in the future probably also Flask itself).
    #: For example this might activate unittest helpers that have an
    #: additional runtime cost which should not be enabled by default.
    #:
    #: This attribute can also be configured from the config with the
    #: `TESTING` configuration key.  Defaults to `False`.
    testing = ConfigAttribute('TESTING')

    #: If a secret key is set, cryptographic components can use this to
    #: sign cookies and other things.  Set this to a complex random value
    #: when you want to use the secure cookie for instance.
    #:
    #: This attribute can also be configured from the config with the
    #: `SECRET_KEY` configuration key.  Defaults to `None`.
    secret_key = ConfigAttribute('SECRET_KEY')

    #: The secure cookie uses this for the name of the session cookie.
    #:
    #: This attribute can also be configured from the config with the
    #: `SESSION_COOKIE_NAME` configuration key.  Defaults to ``'session'``
    session_cookie_name = ConfigAttribute('SESSION_COOKIE_NAME')

    #: A :class:`~datetime.timedelta` which is used to set the expiration
    #: date of a permanent session.  The default is 31 days which makes a
    #: permanent session survive for roughly one month.
    #:
    #: This attribute can also be configured from the config with the
    #: `PERMANENT_SESSION_LIFETIME` configuration key.  Defaults to
    #: ``timedelta(days=31)``
    permanent_session_lifetime = ConfigAttribute('PERMANENT_SESSION_LIFETIME')

    #: Enable this if you want to use the X-Sendfile feature.  Keep in
    #: mind that the server has to support this.  This only affects files
    #: sent with the :func:`send_file` method.
    #:
    #: .. versionadded:: 0.2
    #:
    #: This attribute can also be configured from the config with the
    #: `USE_X_SENDFILE` configuration key.  Defaults to `False`.
    use_x_sendfile = ConfigAttribute('USE_X_SENDFILE')

    #: The name of the logger to use.  By default the logger name is the
    #: package name passed to the constructor.
    #:
    #: .. versionadded:: 0.4
    logger_name = ConfigAttribute('LOGGER_NAME')

    #: The logging format used for the debug logger.  This is only used when
    #: the application is in debug mode, otherwise the attached logging
    #: handler does the formatting.
    #:
    #: .. versionadded:: 0.3
    debug_log_format = (
        '-' * 80 + '\n' +
        '%(levelname)s in %(module)s [%(pathname)s:%(lineno)d]:\n' +
        '%(message)s\n' +
        '-' * 80
    )

    #: Options that are passed directly to the Jinja2 environment.
    jinja_options = ImmutableDict(
        extensions=['jinja2.ext.autoescape', 'jinja2.ext.with_']
    )

    #: Default configuration parameters.
    default_config = ImmutableDict({
        'DEBUG':                                False,
        'TESTING':                              False,
        'SECRET_KEY':                           None,
        'SESSION_COOKIE_NAME':                  'session',
        'PERMANENT_SESSION_LIFETIME':           timedelta(days=31),
        'USE_X_SENDFILE':                       False,
        'LOGGER_NAME':                          None,
        'SERVER_NAME':                          None,
        'MAX_CONTENT_LENGTH':                   None
    })

    def __init__(self, import_name, static_path=None):
        _PackageBoundObject.__init__(self, import_name)
        if static_path is not None:
            self.static_path = static_path

        #: The configuration dictionary as :class:`Config`.  This behaves
        #: exactly like a regular dictionary but supports additional methods
        #: to load a config from files.
        self.config = Config(self.root_path, self.default_config)

        #: Prepare the deferred setup of the logger.
        self._logger = None
        self.logger_name = self.import_name

        #: A dictionary of all view functions registered.  The keys will
        #: be function names which are also used to generate URLs and
        #: the values are the function objects themselves.
        #: To register a view function, use the :meth:`route` decorator.
        self.view_functions = {}

        #: A dictionary of all registered error handlers.  The key is
        #: be the error code as integer, the value the function that
        #: should handle that error.
        #: To register a error handler, use the :meth:`errorhandler`
        #: decorator.
        self.error_handlers = {}

        #: A dictionary with lists of functions that should be called at the
        #: beginning of the request.  The key of the dictionary is the name of
        #: the module this function is active for, `None` for all requests.
        #: This can for example be used to open database connections or
        #: getting hold of the currently logged in user.  To register a
        #: function here, use the :meth:`before_request` decorator.
        self.before_request_funcs = {}

        #: A dictionary with lists of functions that should be called after
        #: each request.  The key of the dictionary is the name of the module
        #: this function is active for, `None` for all requests.  This can for
        #: example be used to open database connections or getting hold of the
        #: currently logged in user.  To register a function here, use the
        #: :meth:`after_request` decorator.
        self.after_request_funcs = {}

        #: A dictionary with list of functions that are called without argument
        #: to populate the template context.  The key of the dictionary is the
        #: name of the module this function is active for, `None` for all
        #: requests.  Each returns a dictionary that the template context is
        #: updated with.  To register a function here, use the
        #: :meth:`context_processor` decorator.
        self.template_context_processors = {
            None: [_default_template_ctx_processor]
        }

        #: all the loaded modules in a dictionary by name.
        #:
        #: .. versionadded:: 0.5
        self.modules = {}

        #: a place where extensions can store application specific state.  For
        #: example this is where an extension could store database engines and
        #: similar things.  For backwards compatibility extensions should register
        #: themselves like this::
        #:
        #:      if not hasattr(app, 'extensions'):
        #:          app.extensions = {}
        #:      app.extensions['extensionname'] = SomeObject()
        #:
        #: The key must match the name of the `flaskext` module.  For example in
        #: case of a "Flask-Foo" extension in `flaskext.foo`, the key would be
        #: ``'foo'``.
        #:
        #: .. versionadded:: 0.7
        self.extensions = {}

        #: The :class:`~werkzeug.routing.Map` for this instance.  You can use
        #: this to change the routing converters after the class was created
        #: but before any routes are connected.  Example::
        #:
        #:    from werkzeug import BaseConverter
        #:
        #:    class ListConverter(BaseConverter):
        #:        def to_python(self, value):
        #:            return value.split(',')
        #:        def to_url(self, values):
        #:            return ','.join(BaseConverter.to_url(value)
        #:                            for value in values)
        #:
        #:    app = Flask(__name__)
        #:    app.url_map.converters['list'] = ListConverter
        self.url_map = Map()

        # register the static folder for the application.  Do that even
        # if the folder does not exist.  First of all it might be created
        # while the server is running (usually happens during development)
        # but also because google appengine stores static files somewhere
        # else when mapped with the .yml file.
        self.add_url_rule(self.static_path + '/<path:filename>',
                          endpoint='static',
                          view_func=self.send_static_file)

        #: The Jinja2 environment.  It is created from the
        #: :attr:`jinja_options`.
        self.jinja_env = self.create_jinja_environment()
        self.init_jinja_globals()

    @property
    def logger(self):
        """A :class:`logging.Logger` object for this application.  The
        default configuration is to log to stderr if the application is
        in debug mode.  This logger can be used to (surprise) log messages.
        Here some examples::

            app.logger.debug('A value for debugging')
            app.logger.warning('A warning ocurred (%d apples)', 42)
            app.logger.error('An error occoured')

        .. versionadded:: 0.3
        """
        if self._logger and self._logger.name == self.logger_name:
            return self._logger
        with _logger_lock:
            if self._logger and self._logger.name == self.logger_name:
                return self._logger
            from flask.logging import create_logger
            self._logger = rv = create_logger(self)
            return rv

    def create_jinja_environment(self):
        """Creates the Jinja2 environment based on :attr:`jinja_options`
        and :meth:`select_jinja_autoescape`.

        .. versionadded:: 0.5
        """
        options = dict(self.jinja_options)
        if 'autoescape' not in options:
            options['autoescape'] = self.select_jinja_autoescape
        return Environment(loader=_DispatchingJinjaLoader(self), **options)

    def init_jinja_globals(self):
        """Called directly after the environment was created to inject
        some defaults (like `url_for`, `get_flashed_messages` and the
        `tojson` filter.

        .. versionadded:: 0.5
        """
        self.jinja_env.globals.update(
            url_for=url_for,
            get_flashed_messages=get_flashed_messages
        )
        self.jinja_env.filters['tojson'] = _tojson_filter

    def select_jinja_autoescape(self, filename):
        """Returns `True` if autoescaping should be active for the given
        template name.

        .. versionadded:: 0.5
        """
        if filename is None:
            return False
        return filename.endswith(('.html', '.htm', '.xml', '.xhtml'))

    def update_template_context(self, context):
        """Update the template context with some commonly used variables.
        This injects request, session, config and g into the template
        context as well as everything template context processors want
        to inject.  Note that the as of Flask 0.6, the original values
        in the context will not be overriden if a context processor
        decides to return a value with the same key.

        :param context: the context as a dictionary that is updated in place
                        to add extra variables.
        """
        funcs = self.template_context_processors[None]
        mod = _request_ctx_stack.top.request.module
        if mod is not None and mod in self.template_context_processors:
            funcs = chain(funcs, self.template_context_processors[mod])
        orig_ctx = context.copy()
        for func in funcs:
            context.update(func())
        # make sure the original values win.  This makes it possible to
        # easier add new variables in context processors without breaking
        # existing views.
        context.update(orig_ctx)

    def run(self, host='127.0.0.1', port=5000, **options):
        """Runs the application on a local development server.  If the
        :attr:`debug` flag is set the server will automatically reload
        for code changes and show a debugger in case an exception happened.

        If you want to run the application in debug mode, but disable the
        code execution on the interactive debugger, you can pass
        ``use_evalex=False`` as parameter.  This will keep the debugger's
        traceback screen active, but disable code execution.

        .. admonition:: Keep in Mind

           Flask will suppress any server error with a generic error page
           unless it is in debug mode.  As such to enable just the
           interactive debugger without the code reloading, you have to
           invoke :meth:`run` with ``debug=True`` and ``use_reloader=False``.
           Setting ``use_debugger`` to `True` without being in debug mode
           won't catch any exceptions because there won't be any to
           catch.

        :param host: the hostname to listen on.  set this to ``'0.0.0.0'``
                     to have the server available externally as well.
        :param port: the port of the webserver
        :param options: the options to be forwarded to the underlying
                        Werkzeug server.  See :func:`werkzeug.run_simple`
                        for more information.
        """
        from werkzeug import run_simple
        if 'debug' in options:
            self.debug = options.pop('debug')
        options.setdefault('use_reloader', self.debug)
        options.setdefault('use_debugger', self.debug)
        return run_simple(host, port, self, **options)

    def test_client(self):
        """Creates a test client for this application.  For information
        about unit testing head over to :ref:`testing`.

        The test client can be used in a `with` block to defer the closing down
        of the context until the end of the `with` block.  This is useful if
        you want to access the context locals for testing::

            with app.test_client() as c:
                rv = c.get('/?vodka=42')
                assert request.args['vodka'] == '42'

        .. versionchanged:: 0.4
           added support for `with` block usage for the client.
        """
        from flask.testing import FlaskClient
        return FlaskClient(self, self.response_class, use_cookies=True)

    def open_session(self, request):
        """Creates or opens a new session.  Default implementation stores all
        session data in a signed cookie.  This requires that the
        :attr:`secret_key` is set.

        :param request: an instance of :attr:`request_class`.
        """
        key = self.secret_key
        if key is not None:
            return Session.load_cookie(request, self.session_cookie_name,
                                       secret_key=key)

    def save_session(self, session, response):
        """Saves the session if it needs updates.  For the default
        implementation, check :meth:`open_session`.

        :param session: the session to be saved (a
                        :class:`~werkzeug.contrib.securecookie.SecureCookie`
                        object)
        :param response: an instance of :attr:`response_class`
        """
        expires = domain = None
        if session.permanent:
            expires = datetime.utcnow() + self.permanent_session_lifetime
        if self.config['SERVER_NAME'] is not None:
            domain = '.' + self.config['SERVER_NAME']
        session.save_cookie(response, self.session_cookie_name,
                            expires=expires, httponly=True, domain=domain)

    def register_module(self, module, **options):
        """Registers a module with this application.  The keyword argument
        of this function are the same as the ones for the constructor of the
        :class:`Module` class and will override the values of the module if
        provided.
        """
        options.setdefault('url_prefix', module.url_prefix)
        options.setdefault('subdomain', module.subdomain)
        state = _ModuleSetupState(self, **options)
        for func in module._register_events:
            func(state)

    def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
        """Connects a URL rule.  Works exactly like the :meth:`route`
        decorator.  If a view_func is provided it will be registered with the
        endpoint.

        Basically this example::

            @app.route('/')
            def index():
                pass

        Is equivalent to the following::

            def index():
                pass
            app.add_url_rule('/', 'index', index)

        If the view_func is not provided you will need to connect the endpoint
        to a view function like so::

            app.view_functions['index'] = index

        .. versionchanged:: 0.2
           `view_func` parameter added.

        .. versionchanged:: 0.6
           `OPTIONS` is added automatically as method.

        :param rule: the URL rule as string
        :param endpoint: the endpoint for the registered URL rule.  Flask
                         itself assumes the name of the view function as
                         endpoint
        :param view_func: the function to call when serving a request to the
                          provided endpoint
        :param options: the options to be forwarded to the underlying
                        :class:`~werkzeug.routing.Rule` object.  A change
                        to Werkzeug is handling of method options.  methods
                        is a list of methods this rule should be limited
                        to (`GET`, `POST` etc.).  By default a rule
                        just listens for `GET` (and implicitly `HEAD`).
                        Starting with Flask 0.6, `OPTIONS` is implicitly
                        added and handled by the standard request handling.
        """
        if endpoint is None:
            endpoint = _endpoint_from_view_func(view_func)
        options['endpoint'] = endpoint
        methods = options.pop('methods', ('GET',))
        provide_automatic_options = False
        if 'OPTIONS' not in methods:
            methods = tuple(methods) + ('OPTIONS',)
            provide_automatic_options = True
        rule = Rule(rule, methods=methods, **options)
        rule.provide_automatic_options = provide_automatic_options
        self.url_map.add(rule)
        if view_func is not None:
            self.view_functions[endpoint] = view_func

    def route(self, rule, **options):
        """A decorator that is used to register a view function for a
        given URL rule.  Example::

            @app.route('/')
            def index():
                return 'Hello World'

        Variables parts in the route can be specified with angular
        brackets (``/user/<username>``).  By default a variable part
        in the URL accepts any string without a slash however a different
        converter can be specified as well by using ``<converter:name>``.

        Variable parts are passed to the view function as keyword
        arguments.

        The following converters are possible:

        =========== ===========================================
        `int`       accepts integers
        `float`     like `int` but for floating point values
        `path`      like the default but also accepts slashes
        =========== ===========================================

        Here some examples::

            @app.route('/')
            def index():
                pass

            @app.route('/<username>')
            def show_user(username):
                pass

            @app.route('/post/<int:post_id>')
            def show_post(post_id):
                pass

        An important detail to keep in mind is how Flask deals with trailing
        slashes.  The idea is to keep each URL unique so the following rules
        apply:

        1. If a rule ends with a slash and is requested without a slash
           by the user, the user is automatically redirected to the same
           page with a trailing slash attached.
        2. If a rule does not end with a trailing slash and the user request
           the page with a trailing slash, a 404 not found is raised.

        This is consistent with how web servers deal with static files.  This
        also makes it possible to use relative link targets safely.

        The :meth:`route` decorator accepts a couple of other arguments
        as well:

        :param rule: the URL rule as string
        :param methods: a list of methods this rule should be limited
                        to (`GET`, `POST` etc.).  By default a rule
                        just listens for `GET` (and implicitly `HEAD`).
                        Starting with Flask 0.6, `OPTIONS` is implicitly
                        added and handled by the standard request handling.
        :param subdomain: specifies the rule for the subdomain in case
                          subdomain matching is in use.
        :param strict_slashes: can be used to disable the strict slashes
                               setting for this rule.  See above.
        :param options: other options to be forwarded to the underlying
                        :class:`~werkzeug.routing.Rule` object.
        """
        def decorator(f):
            self.add_url_rule(rule, None, f, **options)
            return f
        return decorator

    def errorhandler(self, code):
        """A decorator that is used to register a function give a given
        error code.  Example::

            @app.errorhandler(404)
            def page_not_found(error):
                return 'This page does not exist', 404

        You can also register a function as error handler without using
        the :meth:`errorhandler` decorator.  The following example is
        equivalent to the one above::

            def page_not_found(error):
                return 'This page does not exist', 404
            app.error_handlers[404] = page_not_found

        :param code: the code as integer for the handler
        """
        def decorator(f):
            self.error_handlers[code] = f
            return f
        return decorator

    def template_filter(self, name=None):
        """A decorator that is used to register custom template filter.
        You can specify a name for the filter, otherwise the function
        name will be used. Example::

          @app.template_filter()
          def reverse(s):
              return s[::-1]

        :param name: the optional name of the filter, otherwise the
                     function name will be used.
        """
        def decorator(f):
            self.jinja_env.filters[name or f.__name__] = f
            return f
        return decorator

    def before_request(self, f):
        """Registers a function to run before each request."""
        self.before_request_funcs.setdefault(None, []).append(f)
        return f

    def after_request(self, f):
        """Register a function to be run after each request."""
        self.after_request_funcs.setdefault(None, []).append(f)
        return f

    def context_processor(self, f):
        """Registers a template context processor function."""
        self.template_context_processors[None].append(f)
        return f

    def handle_http_exception(self, e):
        """Handles an HTTP exception.  By default this will invoke the
        registered error handlers and fall back to returning the
        exception as response.

        .. versionadded: 0.3
        """
        handler = self.error_handlers.get(e.code)
        if handler is None:
            return e
        return handler(e)

    def handle_exception(self, e):
        """Default exception handling that kicks in when an exception
        occours that is not catched.  In debug mode the exception will
        be re-raised immediately, otherwise it is logged and the handler
        for a 500 internal server error is used.  If no such handler
        exists, a default 500 internal server error message is displayed.

        .. versionadded: 0.3
        """
        got_request_exception.send(self, exception=e)
        handler = self.error_handlers.get(500)
        if self.debug:
            raise
        self.logger.exception('Exception on %s [%s]' % (
            request.path,
            request.method
        ))
        if handler is None:
            return InternalServerError()
        return handler(e)

    def dispatch_request(self):
        """Does the request dispatching.  Matches the URL and returns the
        return value of the view or error handler.  This does not have to
        be a response object.  In order to convert the return value to a
        proper response object, call :func:`make_response`.
        """
        req = _request_ctx_stack.top.request
        try:
            if req.routing_exception is not None:
                raise req.routing_exception
            rule = req.url_rule
            # if we provide automatic options for this URL and the
            # request came with the OPTIONS method, reply automatically 
            if rule.provide_automatic_options and req.method == 'OPTIONS':
                return self.make_default_options_response()
            # otherwise dispatch to the handler for that endpoint
            return self.view_functions[rule.endpoint](**req.view_args)
        except HTTPException, e:
            return self.handle_http_exception(e)
Esempio n. 44
0
class Flask(object):
    """The flask object implements a WSGI application and acts as the central
    object.  It is passed the name of the module or package of the
    application.  Once it is created it will act as a central registry for
    the view functions, the URL rules, template configuration and much more.
    """

    debug = ConfigAttribute('DEBUG')

    logger_name = ConfigAttribute('LOGGER_NAME')

    config_class = Config

    request_class = Request

    url_rule_class = Rule

    default_config = {
        'DEBUG': False,
        'LOGGER_NAME': None,
        'SERVER_NAME': None,
        'MAX_CONTENT_LENGTH': None,
    }

    def __init__(self,
                 import_name,
                 instance_relative_config=False,
                 root_path=None):
        self.__name__ = import_name
        if root_path is None:
            self.root_path = get_root_path(import_name)
        self.config = self.make_config(instance_relative_config)
        self.logger_name = import_name
        self._got_first_request = False

        #: A dictionary of all view functions registered.  The keys will
        #: be function names which are also used to generate URLs and
        #: the values are the function objects themselves.
        #: To register a view function, use the :meth:`route` decorator.
        self.view_functions = {}

        self.url_map = Map()

    def make_config(self, instance_relative_config=False):
        """Used to create the config attribute by the Flask constructor.
        """
        root_path = self.root_path
        return self.config_class(root_path, self.default_config)

    @property
    def got_first_request(self):
        return self._got_first_request

    @D.setupmethod
    def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
        """Connects a URL rule.  Works exactly like the :meth:`route`
        decorator.  If a view_func is provided it will be registered with the
        endpoint.

        If the view_func is not provided you will need to connect the endpoint
        to a view function like so::

            app.view_functions['index'] = index
        """
        if endpoint is None:
            endpoint = _endpoint_from_view_func(view_func)
        options['endpoint'] = endpoint

        methods = options.pop('methods', None)
        if methods is None:
            methods = getattr(view_func, 'methods', None) or ('Get')
        else:
            methods = set(item.upper() for item in methods)
        options['methods'] = methods

        rule = self.url_rule_class(rule, **options)
        self.url_map.add(rule)

        if view_func is not None:
            old_func = self.view_functions[endpoint]
            if old_func is not None and old_func != view_func:
                raise AssertionError(
                    'view function for an endpoint should not be overrwirtten')
            self.view_functions[endpoint] = view_func

    def route(self, rule, **options):
        """A decorator that is used to register a view function for a
        given URL rule.
        """
        def wrapper(func):
            endpoint = options.pop('endpoint', None)
            self.add_url_rule(rule, endpoint, func, **options)
            return func

        return wrapper

    @D.setupmethod
    def endpoint(self, endpoint):
        """A decorator to register a function as an endpoint.
        Example::

            @app.endpoint('example.endpoint')
            def example():
                return "example"
        """
        def wrapper(f):
            self.view_functions[endpoint] = f
            return f

        return wrapper

    def __call__(self, environ, start_response):
        return self.wsgi_app(environ, start_response)

    def wsgi_app(self, environ, start_response):
        """Actual WSGI Applicaiton
        """
        req = self.request_context(environ)
        try:
            rv = self.full_dispatch_request()
        except Exception:
            raise

    def request_context(self, environ):
        """Creates a ctx.RequestContext object
        :param environ: a WSGI environment
        """
        return RequestContext(self, environ)

    def full_dispatch_request(self):
        """Dispatches the request and on top of that performs request
        pre and postprocessing
        """

    def create_url_adapter(self, request):
        """Creates a URL adapter for the given request
        """
        if request is not None:
            return self.url_map.bind_to_environ(
                environ=request.environ,
                server_name=self.config['SERVER_NAME'],
            )
Esempio n. 45
0
class Flask(_PackageBoundObject):
    """The flask object implements a WSGI application and acts as the central
    object.  It is passed the name of the module or package of the
    application.  Once it is created it will act as a central registry for
    the view functions, the URL rules, template configuration and much more.

    The name of the package is used to resolve resources from inside the
    package or the folder the module is contained in depending on if the
    package parameter resolves to an actual python package (a folder with
    an `__init__.py` file inside) or a standard module (just a `.py` file).

    For more information about resource loading, see :func:`open_resource`.

    Usually you create a :class:`Flask` instance in your main module or
    in the `__init__.py` file of your package like this::

        from flask import Flask
        app = Flask(__name__)
    """

    #: the class that is used for request objects.  See :class:`~flask.request`
    #: for more information.
    request_class = Request

    #: 用作响应对象的类。更多信息参见flask.Response。
    response_class = Response

    #: 静态文件的路径。如果你不想使用静态文件,可以将这个值设为None,这样不会添加
    #: 相应的URL规则,而且开发服务器将不再提供(serve)任何静态文件。
    static_path = '/static'

    #: 如果设置了密钥(secret key),加密组件可以使用它来为
    #: cookies或其他东西签名。比如,当你想使用安全的cookie时,把它设为一个复杂的随机值。
    secret_key = None

    #: 安全cookie使用这个值作为session cookie的名称。
    session_cookie_name = 'session'  # 存储session对象数据的cookie名称

    #: A :class:`~datetime.timedelta` which is used to set the expiration
    #: date of a permanent session.  The default is 31 days which makes a
    #: permanent session survive for roughly one month.
    permanent_session_lifetime = timedelta(days=31)

    #: Enable this if you want to use the X-Sendfile feature.  Keep in
    #: mind that the server has to support this.  This only affects files
    #: sent with the :func:`send_file` method.
    #:
    #: .. versionadded:: 0.2
    use_x_sendfile = False

    #: options that are passed directly to the Jinja2 environment
    jinja_options = ImmutableDict(
        autoescape=True,
        extensions=['jinja2.ext.autoescape', 'jinja2.ext.with_'])

    def __init__(self, import_name):
        _PackageBoundObject.__init__(self, import_name)

        #: the debug flag.  Set this to `True` to enable debugging of
        #: the application.  In debug mode the debugger will kick in
        #: when an unhandled exception ocurrs and the integrated server
        #: will automatically reload the application if changes in the
        #: code are detected.
        self.debug = False

        #: a dictionary of all view functions registered.  The keys will
        #: be function names which are also used to generate URLs and
        #: the values are the function objects themselves.
        #: to register a view function, use the :meth:`route` decorator.
        self.view_functions = {}

        #: 一个储存所有已注册的错误处理器的字典。字段的键是整型(integer)类型的
        #: 错误码,字典的值是处理对应错误的函数。
        #: 要注册一个错误处理器,使用errorhandler装饰器。
        self.error_handlers = {}

        #: 一个应该在请求开始进入时、请求分发开始前调用的函数列表。举例来说,
        #: 这可以用来打开数据库连接或获取当前登录的用户。
        #: 要注册一个函数到这里,使用before_request装饰器。
        self.before_request_funcs = []

        #: 一个应该在请求处理结束时调用的函数列表。这些函数会被传入当前的响应
        #: 对象,你可以在函数内修改或替换它。
        #: 要注册一个函数到这里,使用after_request装饰器。
        self.after_request_funcs = []

        #: a dictionary with list of functions that are called without argument
        #: to populate the template context.  They key of the dictionary is the
        #: name of the module this function is active for, `None` for all
        #: requests.  Each returns a dictionary that the template context is
        #: updated with.  To register a function here, use the
        #: :meth:`context_processor` decorator.
        self.template_context_processors = {
            None: [_default_template_ctx_processor]
        }

        #: the :class:`~werkzeug.routing.Map` for this instance.  You can use
        #: this to change the routing converters after the class was created
        #: but before any routes are connected.  Example::
        #:
        #:    from werkzeug import BaseConverter
        #:
        #:    class ListConverter(BaseConverter):
        #:        def to_python(self, value):
        #:            return value.split(',')
        #:        def to_url(self, values):
        #:            return ','.join(BaseConverter.to_url(value)
        #:                            for value in values)
        #:
        #:    app = Flask(__name__)
        #:    app.url_map.converters['list'] = ListConverter
        self.url_map = Map()

        if self.static_path is not None:
            self.add_url_rule(self.static_path + '/<filename>',
                              build_only=True,
                              endpoint='static')
            if pkg_resources is not None:
                target = (self.import_name, 'static')
            else:
                target = os.path.join(self.root_path, 'static')
            self.wsgi_app = SharedDataMiddleware(
                self.wsgi_app,
                {  # SharedDataMiddleware中间件用来为程序添加处理静态文件的能力
                    self.static_path: target  # URL路径和实际文件目录(static文件夹)的映射
                })

        #: Jinja2环境。它通过jinja_options创建,加载器(loader)通过
        #: create_jinja_loader函数返回。
        self.jinja_env = Environment(loader=self.create_jinja_loader(),
                                     **self.jinja_options)
        self.jinja_env.globals.update(  # 将url_for和get_flashed_messages函数作为全局对象注入到模板上下文,以便在模板中调用
            url_for=url_for,
            get_flashed_messages=get_flashed_messages)
        self.jinja_env.filters['tojson'] = _tojson_filter

    def create_jinja_loader(self):
        """创建Jinja加载器。默认只是返回一个对应配置好的包的包加载器,它会从
        templates文件夹中寻找模板。要添加其他加载器,可以重载这个方法。
        """
        if pkg_resources is None:
            return FileSystemLoader(os.path.join(self.root_path, 'templates'))
        return PackageLoader(self.import_name)

    def update_template_context(self, context):
        """使用常用的变量更新模板上下文。这会注入request、session和g到模板上下文中。

        :param context: 包含额外添加的变量的字典,用来更新上下文。
        """
        funcs = self.template_context_processors[None]
        mod = _request_ctx_stack.top.request.module
        if mod is not None and mod in self.template_context_processors:
            funcs = chain(funcs, self.template_context_processors[mod])
        for func in funcs:
            context.update(func())

    def run(self, host='127.0.0.1', port=5000, **options):
        """Runs the application on a local development server.  If the
        :attr:`debug` flag is set the server will automatically reload
        for code changes and show a debugger in case an exception happened.

        :param host: the hostname to listen on.  set this to ``'0.0.0.0'``
                     to have the server available externally as well.
        :param port: the port of the webserver
        :param options: the options to be forwarded to the underlying
                        Werkzeug server.  See :func:`werkzeug.run_simple`
                        for more information.
        """
        from werkzeug.serving import run_simple
        if 'debug' in options:
            self.debug = options.pop('debug')
        options.setdefault('use_reloader',
                           self.debug)  # 如果debug为True,开启重载器(reloader)
        options.setdefault('use_debugger',
                           self.debug)  # 如果debug为True,开启调试器(debugger)
        return run_simple(host, port, self, **options)

    def test_client(self):
        """为这个程序创建一个测试客户端。"""
        from werkzeug.test import Client
        return Client(self, self.response_class, use_cookies=True)

    def open_session(self, request):
        """创建或打开一个新的session。默认的实现是存储所有的用户会话(session)
        数据到一个签名的cookie中。这需要secret_key属性被设置。

        :param request: request_class的实例。
        """
        key = self.secret_key
        if key is not None:
            return Session.load_cookie(request,
                                       self.session_cookie_name,
                                       secret_key=key)

    def save_session(self, session, response):
        """Saves the session if it needs updates.  For the default
        implementation, check :meth:`open_session`.

        :param session: the session to be saved (a
                        :class:`~werkzeug.contrib.securecookie.SecureCookie`
                        object)
        :param response: an instance of :attr:`response_class`
        """
        expires = None
        if session.permanent:
            expires = datetime.utcnow() + self.permanent_session_lifetime
        session.save_cookie(response,
                            self.session_cookie_name,
                            expires=expires,
                            httponly=True)

    def register_module(self, module, **options):
        """Registers a module with this application.  The keyword argument
        of this function are the same as the ones for the constructor of the
        :class:`Module` class and will override the values of the module if
        provided.
        """
        options.setdefault('url_prefix', module.url_prefix)
        state = _ModuleSetupState(self, **options)
        for func in module._register_events:
            func(state)

    def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
        """Connects a URL rule.  Works exactly like the :meth:`route`
        decorator.  If a view_func is provided it will be registered with the
        endpoint.

        Basically this example::

            @app.route('/')
            def index():
                pass

        Is equivalent to the following::

            def index():
                pass
            app.add_url_rule('/', 'index', index)

        If the view_func is not provided you will need to connect the endpoint
        to a view function like so::

            app.view_functions['index'] = index

        .. versionchanged:: 0.2
           `view_func` parameter added.

        :param rule: the URL rule as string
        :param endpoint: the endpoint for the registered URL rule.  Flask
                         itself assumes the name of the view function as
                         endpoint
        :param view_func: the function to call when serving a request to the
                          provided endpoint
        :param options: the options to be forwarded to the underlying
                        :class:`~werkzeug.routing.Rule` object
        """
        if endpoint is None:
            assert view_func is not None, 'expected view func if endpoint ' \
                                          'is not provided.'
            endpoint = view_func.__name__
        options['endpoint'] = endpoint
        options.setdefault('methods', ('GET', ))
        self.url_map.add(Rule(rule, **options))
        if view_func is not None:
            self.view_functions[endpoint] = view_func

    def route(self, rule, **options):
        """一个用于为给定的URL规则注册视图函数的装饰器。示例:

            @app.route('/')
            def index():
                return 'Hello World'

        路由中的变量部分可以使用尖括号来指定(/user/<username>)。默认情况下,
        URL中的变量部分接受任意不包含斜线的字符串,你也可以使用<converter:name>
        的形式来指定一个不同的转换器。

        变量部分将被作为关键字参数传入视图函数。

        可用的转换器如下所示:

        =========== ===========================================
        `int`       accepts integers
        `float`     like `int` but for floating point values
        `path`      like the default but also accepts slashes
        =========== ===========================================

        下面是一些示例:

            @app.route('/')
            def index():
                pass

            @app.route('/<username>')
            def show_user(username):
                pass

            @app.route('/post/<int:post_id>')
            def show_post(post_id):
                pass

        一个重要的细节是留意Flask是如何处理斜线的。为了让每一个URL独一无二,
        下面的规则被应用:

        1. 如果一个规则以一个斜线结尾而用户请求的地址不包含斜线,那么该用户
        会被重定向到相同的页面并附加一个结尾斜线。
        2. 如果一个规则没有以斜线结尾而用户请求的页面包含了一个结尾斜线,
        会抛出一个404错误。

        这和Web服务器处理静态文件的方式相一致。这也可以让你安全的使用相对链接目标。

        这个route装饰器也接受一系列参数:

        :param rule: 字符串形式的URL规则
        :param methods: 一个方法列表,可用的值限定为(GET、POST等)。默认一个
                        规则仅监听GET(以及隐式的HEAD)
        :param subdomain: 当子域名匹配使用时,为规则指定子域。
        :param strict_slashes: 可以用来为这个规则关闭严格的斜线设置,见上。
        :param options: 转发到底层的werkzeug.routing.Rule对象的其他选项。
        """
        def decorator(f):
            self.add_url_rule(rule, None, f, **options)
            return f

        return decorator

    def errorhandler(self, code):
        """一个用于为给定的错误码注册函数的装饰器。示例:

            @app.errorhandler(404)
            def page_not_found():
                return 'This page does not exist', 404

        你也可以不使用errorhandler注册一个函数作为错误处理器。下面的例子同上:

            def page_not_found():
                return 'This page does not exist', 404
            app.error_handlers[404] = page_not_found

        :param code: 对应处理器的整型类型的错误代码。
        """
        def decorator(f):
            self.error_handlers[code] = f
            return f

        return decorator

    def template_filter(self, name=None):
        """A decorator that is used to register custom template filter.
        You can specify a name for the filter, otherwise the function
        name will be used. Example::

          @app.template_filter()
          def reverse(s):
              return s[::-1]

        :param name: the optional name of the filter, otherwise the
                     function name will be used.
        """
        def decorator(f):
            self.jinja_env.filters[name or f.__name__] = f
            return f

        return decorator

    def before_request(self, f):
        """Registers a function to run before each request."""
        self.before_request_funcs.setdefault(None, []).append(f)
        return f

    def after_request(self, f):
        """Register a function to be run after each request."""
        self.after_request_funcs.setdefault(None, []).append(f)
        return f

    def context_processor(self, f):
        """Registers a template context processor function."""
        self.template_context_processors[None].append(f)
        return f

    #################################
    # 下面的几个方法用于处理请求和响应
    #################################

    def dispatch_request(self):
        """附注请求分发工作。匹配URL,返回视图函数或错误处理器的返回值。这个返回值
        不一定得是响应对象。为了将返回值返回值转换成合适的想要对象,调用make_response。
        """
        req = _request_ctx_stack.top.request
        try:
            if req.routing_exception is not None:
                raise req.routing_exception
            return self.view_functions[req.endpoint](**req.view_args)
        except HTTPException as e:
            handler = self.error_handlers.get(e.code)
            if handler is None:
                return e
            return handler(e)
        except Exception as e:
            handler = self.error_handlers.get(500)
            if self.debug or handler is None:
                raise
            return handler(e)

    def make_response(self, rv):
        """将视图函数的返回值转换成一个真正的响应对象,即response_class实例。

        rv允许的类型如下所示:

        ======================= ===========================================
        :attr:`response_class`  the object is returned unchanged
        :class:`str`            a response object is created with the
                                string as body
        :class:`unicode`        a response object is created with the
                                string encoded to utf-8 as body
        :class:`tuple`          the response object is created with the
                                contents of the tuple as arguments
        a WSGI function         the function is called as WSGI application
                                and buffered as response object
        ======================= ===========================================

        :param rv: 视图函数返回值
        """
        if rv is None:
            raise ValueError('View function did not return a response')
        if isinstance(rv, self.response_class):
            return rv
        if isinstance(rv, str):
            return self.response_class(rv)
        if isinstance(rv, tuple):
            return self.response_class(*rv)
        return self.response_class.force_type(rv, request.environ)

    def preprocess_request(self):
        """在实际的请求分发之前调用,而且将会调用每一个使用before_request
        装饰的函数。如果其中某一个函数返回一个值,这个值将会作为视图返回值
        处理并停止进一步的请求处理。
        """
        funcs = self.before_request_funcs.get(None, ())
        mod = request.module
        if mod and mod in self.before_request_funcs:
            funcs = chain(funcs, self.before_request_funcs[mod])
        for func in funcs:
            rv = func()
            if rv is not None:
                return rv

    def process_response(self, response):
        """为了在发送给WSGI服务器前修改响应对象,可以重写这个方法。 默认
        这会调用所有使用after_request装饰的函数。

        :param response: 一个response_class对象。
        :return: 一个新的响应对象或原对象,必须是response_class实例。
        """
        ctx = _request_ctx_stack.top
        mod = ctx.request.module
        if not isinstance(ctx.session, _NullSession):
            self.save_session(ctx.session, response)
        funcs = ()
        if mod and mod in self.after_request_funcs:
            funcs = chain(funcs, self.after_request_funcs[mod])
        if None in self.after_request_funcs:
            funcs = chain(funcs, self.after_request_funcs[None])
        for handler in funcs:
            response = handler(response)
        return response

    #########################################################################
    # WSGI规定的可调用对象,从请求进入,到生成响应并返回的整个处理流程都发生在这里
    #########################################################################

    def wsgi_app(self, environ, start_response):
        """The actual WSGI application.  This is not implemented in
        `__call__` so that middlewares can be applied without losing a
        reference to the class.  So instead of doing this::

            app = MyMiddleware(app)

        It's a better idea to do this instead::

            app.wsgi_app = MyMiddleware(app.wsgi_app)

        Then you still have the original application object around and
        can continue to call methods on it.

        :param environ: a WSGI environment
        :param start_response: a callable accepting a status code,
                               a list of headers and an optional
                               exception context to start the response
        """
        # 在with语句下执行相关操作,会触发_RequestContext中的__enter__方法,从而推送请求上下文到堆栈中
        with self.request_context(environ):
            rv = self.preprocess_request()  # 预处理请求,调用所有使用了before_request钩子的函数
            if rv is None:
                rv = self.dispatch_request  # 请求分发,获得视图函数返回值(或是错误处理器的返回值)
            response = self.make_response(rv)  # 生成响应,把上面的返回值转换成响应对象
            response = self.process_response(
                response)  # 响应处理,调用所有使用了after_request钩子的函数
            return response(environ, start_response)

    def request_context(self, environ):
        """从给定的环境创建一个请求上下文,并将其绑定到当前上下文。这必须搭配with
        语句使用,因为请求仅绑定在with块中的当前上下文里。

        用法示例:

            with app.request_context(environ):
                do_something_with(request)

        :param environ: 一个WSGI环境。
        """
        return _RequestContext(self, environ)

    def test_request_context(self, *args, **kwargs):
        """从给定的值创建一个WSGI环境(更多信息请参见werkzeug.create_environ,
        这个函数接受相同的参数)。
        """
        return self.request_context(create_environ(*args, **kwargs))

    def __call__(self, environ, start_response):
        """wsgi_app的快捷方式。"""
        return self.wsgi_app(environ, start_response)
Esempio n. 46
0
class KlangbeckenAPI:
    def __init__(self, stand_alone=False):
        self.data_dir = os.environ.get('KLANGBECKEN_DATA',
                                       '/var/lib/klangbecken')
        self.secret = os.environ['KLANGBECKEN_API_SECRET']
        self.url_map = Map()

        # register the TXXX key so that we can access it later as
        # mutagenfile['rg_track_gain']
        EasyID3.RegisterTXXXKey(key='track_gain', desc='REPLAYGAIN_TRACK_GAIN')
        EasyID3.RegisterTXXXKey(key='cue_in', desc='CUE_IN')
        EasyID3.RegisterTXXXKey(key='cue_out', desc='CUE_OUT')

        root_url = '/<any(' + ', '.join(PLAYLISTS) + '):category>/'

        mappings = [
            ('/login/', ('GET', 'POST'), 'login'),
            ('/logout/', ('POST', ), 'logout'),
            (root_url, ('GET', ), 'list'),
            (root_url + '<filename>', ('GET', ), 'get'),
            (root_url, ('POST', ), 'upload'),
            (root_url + '<filename>', ('PUT', ), 'update'),
            (root_url + '<filename>', ('DELETE', ), 'delete'),
        ]

        if stand_alone:
            # Serve html and prefix calls to api
            mappings = [('/api' + path, methods, endpoint)
                        for path, methods, endpoint in mappings]
            mappings.append(('/', ('GET', ), 'static'))
            mappings.append(('/<path:path>', ('GET', ), 'static'))
            cur_dir = os.path.dirname(os.path.realpath(__file__))
            dist_dir = open(pjoin(cur_dir, '.dist_dir')).read().strip()
            self.static_dir = pjoin(cur_dir, dist_dir)

        for path, methods, endpoint in mappings:
            self.url_map.add(Rule(path, methods=methods, endpoint=endpoint))

    def _full_path(self, path):
        return pjoin(self.data_dir, path)

    def _replaygain_analysis(self, mutagenfile):
        bs1770gain_cmd = [
            "/usr/bin/bs1770gain", "--ebu", "--xml", mutagenfile.filename
        ]
        output = subprocess.check_output(bs1770gain_cmd)
        bs1770gain = ElementTree.fromstring(output)
        # lu is in bs1770gain > album > track > integrated as an attribute
        track_gain = bs1770gain.find('./album/track/integrated').attrib['lu']
        mutagenfile['track_gain'] = track_gain + ' dB'

    def _silan_analysis(self, mutagenfile):
        silan_cmd = [
            '/usr/bin/silan', '--format', 'json', mutagenfile.filename
        ]
        output = subprocess.check_output(silan_cmd)
        cue_points = json.loads(output)['sound'][0]
        mutagenfile['cue_in'] = str(cue_points[0])
        mutagenfile['cue_out'] = str(cue_points[1])

    def __call__(self, environ, start_response):
        request = Request(environ)
        adapter = self.url_map.bind_to_environ(request.environ)

        session = request.client_session
        try:
            endpoint, values = adapter.match()
            if endpoint not in ['login', 'static'
                                ] and (session.new or 'user' not in session):
                raise Unauthorized()
            response = getattr(self, 'on_' + endpoint)(request, **values)
        except HTTPException as e:
            response = e
        return response(environ, start_response)

    def on_login(self, request):
        if request.remote_user is None:
            raise Unauthorized()

        response = Response(json.dumps({'status': 'OK'}), mimetype='text/json')
        session = request.client_session
        session['user'] = request.environ['REMOTE_USER']
        session.save_cookie(response)
        return response

    def on_logout(self, request):
        response = Response(json.dumps({'status': 'OK'}), mimetype='text/json')
        session = request.client_session
        del session['user']
        session.save_cookie(response)
        return response

    def on_list(self, request, category):
        cat_dir = self._full_path(category)
        filenames = os.listdir(cat_dir)
        tuples = [(filename, os.path.join(category, filename))
                  for filename in filenames]
        tuples = [
            (filename, path, mutagen.File(self._full_path(path), easy=True))
            for (filename, path) in tuples
            if os.path.isfile(self._full_path(path)) and path.endswith('.mp3')
        ]
        counter = Counter(
            path.strip()
            for path in open(self._full_path(category + ".m3u")).readlines())
        # FIXME: cue-points and replaygain
        dicts = [{
            'filename': filename,
            'path': path,
            'artist': mutagenfile.get('artist', [''])[0],
            'title': mutagenfile.get('title', [''])[0],
            'album': mutagenfile.get('album', [''])[0],
            'length': float(mutagenfile.info.length),
            'mtime': os.stat(self._full_path(path)).st_mtime,
            'repeate': counter[path],
        } for (filename, path, mutagenfile) in tuples]

        data = sorted(dicts, key=lambda v: v['mtime'], reverse=True)
        return Response(json.dumps(data,
                                   indent=2,
                                   sort_keys=True,
                                   ensure_ascii=True),
                        mimetype='text/json')

    def on_get(self, request, category, filename):
        path = pjoin(category, secure_filename(filename))
        full_path = self._full_path(path)
        if not os.path.exists(full_path):
            raise NotFound()
        return Response(wrap_file(request.environ, open(full_path, 'rb')),
                        mimetype='audio/mpeg')

    def on_upload(self, request, category):
        file = request.files['files']

        if not file:
            raise UnprocessableEntity()

        filename = secure_filename(file.filename)
        # filename = gen_file_name(filename) # FIXME: check duplicate filenames
        # mimetype = file.content_type

        if not file.filename.endswith('.mp3'):
            raise UnprocessableEntity('Filetype not allowed ')

        # save file to disk
        file_path = pjoin(category, filename)
        file.save(self._full_path(file_path))
        with open(self._full_path(category + '.m3u'), 'a') as f:
            print(file_path, file=f)

        # FIXME: silan and replaygain
        # gst-launch-1.0 -t filesrc location=02_Prada.mp3 ! decodebin !
        #  audioconvert ! audioresample ! rganalysis ! fakesink

        mutagenfile = mutagen.File(self._full_path(file_path), easy=True)
        self._replaygain_analysis(mutagenfile)
        self._silan_analysis(mutagenfile)
        mutagenfile.save()
        metadata = {
            'filename': filename,
            'path': file_path,
            'artist': mutagenfile.get('artist', [''])[0],
            'title': mutagenfile.get('title', [''])[0],
            'album': mutagenfile.get('album', [''])[0],
            'repeate': 1,
            'length': float(mutagenfile.info.length),
            'mtime': os.stat(self._full_path(file_path)).st_mtime,
        }
        return Response(json.dumps(metadata), mimetype='text/json')

    def on_update(self, request, category, filename):
        # FIXME: other values (artist, title)
        path = pjoin(category, secure_filename(filename))
        try:
            repeates = int(json.loads(request.data)['repeate'])
        except:  # noqa: E722
            raise UnprocessableEntity('Cannot parse PUT request')

        lines = open(self._full_path(category + '.m3u')).read().split('\n')
        with open(self._full_path(category + '.m3u'), 'w') as f:
            for line in lines:
                if line != path and line:
                    print(line, file=f)
            for i in range(repeates):
                print(path, file=f)
            del i

        return Response(json.dumps({'status': 'OK'}), mimetype='text/json')

    def on_delete(self, request, category, filename):
        path = pjoin(category, secure_filename(filename))
        if not os.path.exists(self._full_path(path)):
            raise NotFound()
        os.remove(self._full_path(path))
        lines = open(self._full_path(category + '.m3u')).read().split('\n')
        with open(self._full_path(category + '.m3u'), 'w') as f:
            for line in lines:
                if line != path and line:
                    print(line, file=f)
        return Response(json.dumps({'status': 'OK'}), mimetype='text/json')

    def on_static(self, request, path=''):
        if path in [''] + PLAYLISTS:
            path = 'index.html'
        path = os.path.join(self.static_dir, path)

        if path.endswith('.html'):
            mimetype = 'text/html'
        elif path.endswith('.css'):
            mimetype = 'text/css'
        elif path.endswith('.js'):
            mimetype = 'text/javascript'
        else:
            mimetype = 'text/plain'

        if not os.path.isfile(path):
            raise NotFound()

        return Response(wrap_file(request.environ, open(path, 'rb')),
                        mimetype=mimetype)
Esempio n. 47
0
class Flask(object):
    """The flask object implements a WSGI application and acts as the central
    object.  It is passed the name of the module or package of the
    application.  Once it is created it will act as a central registry for
    the view functions, the URL rules, template configuration and much more.

    The name of the package is used to resolve resources from inside the
    package or the folder the module is contained in depending on if the
    package parameter resolves to an actual python package (a folder with
    an `__init__.py` file inside) or a standard module (just a `.py` file).

    For more information about resource loading, see :func:`open_resource`.

    Usually you create a :class:`Flask` instance in your main module or
    in the `__init__.py` file of your package like this::

        from flask import Flask
        app = Flask(__name__)
    """

    #: the class that is used for request objects.  See :class:`~flask.request`
    #: for more information.
    request_class = Request      # 请求类

    #: the class that is used for response objects.  See
    #: :class:`~flask.Response` for more information.
    response_class = Response    # 响应类

    #: path for the static files.  If you don't want to use static files
    #: you can set this value to `None` in which case no URL rule is added
    #: and the development server will no longer serve any static files.
    static_path = '/static'      # 静态资源路径

    #: if a secret key is set, cryptographic components can use this to
    #: sign cookies and other things.  Set this to a complex random value
    #: when you want to use the secure cookie for instance.
    secret_key = None            # 密钥配置

    #: The secure cookie uses this for the name of the session cookie
    session_cookie_name = 'session'      # 安全cookie

    #: options that are passed directly to the Jinja2 environment
    # 模板参数
    jinja_options = dict(
        autoescape=True,
        extensions=['jinja2.ext.autoescape', 'jinja2.ext.with_']
    )

    def __init__(self, package_name):
        #: the debug flag.  Set this to `True` to enable debugging of
        #: the application.  In debug mode the debugger will kick in
        #: when an unhandled exception ocurrs and the integrated server
        #: will automatically reload the application if changes in the
        #: code are detected.
        self.debug = False     # 调试模式开关

        #: the name of the package or module.  Do not change this once
        #: it was set by the constructor.
        #
        # 注意:
        #   - 这个参数,不是随便乱给的
        #   - 要跟实际的 项目工程目录名对应,否则无法找到对应的工程
        #
        self.package_name = package_name

        #: where is the app root located?
        #
        # 注意:
        #   - 调用前面定义的 全局私有方法
        #   - 依赖前面的传入参数, 通过该参数, 获取 项目工程源码根目录.
        #
        self.root_path = _get_package_path(self.package_name)    # 获取项目根目录

        #: a dictionary of all view functions registered.  The keys will
        #: be function names which are also used to generate URLs and
        #: the values are the function objects themselves.
        #: to register a view function, use the :meth:`route` decorator.
        self.view_functions = {}         # 视图函数集

        #: a dictionary of all registered error handlers.  The key is
        #: be the error code as integer, the value the function that
        #: should handle that error.
        #: To register a error handler, use the :meth:`errorhandler`
        #: decorator.
        self.error_handlers = {}           # 出错处理

        #: a list of functions that should be called at the beginning
        #: of the request before request dispatching kicks in.  This
        #: can for example be used to open database connections or
        #: getting hold of the currently logged in user.
        #: To register a function here, use the :meth:`before_request`
        #: decorator.
        self.before_request_funcs = []     # 预处理

        #: a list of functions that are called at the end of the
        #: request.  Tha function is passed the current response
        #: object and modify it in place or replace it.
        #: To register a function here use the :meth:`after_request`
        #: decorator.
        self.after_request_funcs = []      # 结束清理

        #: a list of functions that are called without arguments
        #: to populate the template context.  Each returns a dictionary
        #: that the template context is updated with.
        #: To register a function here, use the :meth:`context_processor`
        #: decorator.
        self.template_context_processors = [_default_template_ctx_processor]

        # todo: 待深入
        self.url_map = Map()    # 关键依赖: werkzeug.routing.Map

        if self.static_path is not None:    # 处理静态资源
            #
            # todo: 待深入 关键依赖: werkzeug.routing.Rule
            self.url_map.add(Rule(self.static_path + '/<filename>',
                                  build_only=True, endpoint='static'))

            if pkg_resources is not None:
                target = (self.package_name, 'static')
            else:
                target = os.path.join(self.root_path, 'static')

            #
            # todo: 待深入, 关键依赖: werkzeug.SharedDataMiddleware
            self.wsgi_app = SharedDataMiddleware(self.wsgi_app, {
                self.static_path: target
            })

        #: the Jinja2 environment.  It is created from the
        #: :attr:`jinja_options` and the loader that is returned
        #: by the :meth:`create_jinja_loader` function.
        # todo: 待深入, jinja2 模板配置
        self.jinja_env = Environment(loader=self.create_jinja_loader(),
                                     **self.jinja_options)
        self.jinja_env.globals.update(
            url_for=url_for,
            get_flashed_messages=get_flashed_messages
        )

    # 加载 templates 目录文件
    def create_jinja_loader(self):
        """Creates the Jinja loader.  By default just a package loader for
        the configured package is returned that looks up templates in the
        `templates` folder.  To add other loaders it's possible to
        override this method.
        """
        if pkg_resources is None:
            # 加载 模板目录 文件
            return FileSystemLoader(os.path.join(self.root_path, 'templates'))
        return PackageLoader(self.package_name)

    def update_template_context(self, context):
        """Update the template context with some commonly used variables.
        This injects request, session and g into the template context.

        :param context: the context as a dictionary that is updated in place
                        to add extra variables.
        """
        reqctx = _request_ctx_stack.top
        for func in self.template_context_processors:
            context.update(func())

    #
    # 对外运行接口: 借用werkzeug.run_simple 实现
    #
    def run(self, host='localhost', port=5000, **options):
        """Runs the application on a local development server.  If the
        :attr:`debug` flag is set the server will automatically reload
        for code changes and show a debugger in case an exception happened.

        :param host: the hostname to listen on.  set this to ``'0.0.0.0'``
                     to have the server available externally as well.
        :param port: the port of the webserver
        :param options: the options to be forwarded to the underlying
                        Werkzeug server.  See :func:`werkzeug.run_simple`
                        for more information.
        """
        from werkzeug import run_simple    # todo: 待深入, 关键依赖: 核心运行模块
        if 'debug' in options:
            self.debug = options.pop('debug')
        options.setdefault('use_reloader', self.debug)
        options.setdefault('use_debugger', self.debug)

        return run_simple(host, port, self, **options)    # 关键依赖:

    def test_client(self):
        """Creates a test client for this application.  For information
        about unit testing head over to :ref:`testing`.
        """
        from werkzeug import Client        # todo: 待深入, 关键依赖:
        return Client(self, self.response_class, use_cookies=True)

    def open_resource(self, resource):
        """Opens a resource from the application's resource folder.  To see
        how this works, consider the following folder structure::

            /myapplication.py
            /schemal.sql
            /static
                /style.css
            /template
                /layout.html
                /index.html

        If you want to open the `schema.sql` file you would do the
        following::

            with app.open_resource('schema.sql') as f:
                contents = f.read()
                do_something_with(contents)

        :param resource: the name of the resource.  To access resources within
                         subfolders use forward slashes as separator.
        """
        if pkg_resources is None:
            return open(os.path.join(self.root_path, resource), 'rb')
        return pkg_resources.resource_stream(self.package_name, resource)

    #
    # 关键接口: 创建 or 打开一个 会话(session)
    #   - 实现方式: 使用 cookie 实现
    #   - 默认把全部session数据, 存入一个 cookie 中.
    #   - 对比 flask-0.4 版本, 部分重构
    #
    def open_session(self, request):
        """Creates or opens a new session.
        Default implementation stores all session data in a signed cookie.
        This requires that the :attr:`secret_key` is set.

        :param request: an instance of :attr:`request_class`.
        """
        key = self.secret_key
        if key is not None:
            return SecureCookie.load_cookie(request, self.session_cookie_name,
                                            secret_key=key)

    #
    # 关键接口: 更新session
    #
    def save_session(self, session, response):
        """Saves the session if it needs updates.  For the default
        implementation, check :meth:`open_session`.

        :param session: the session to be saved (a
                        :class:`~werkzeug.contrib.securecookie.SecureCookie`
                        object)
        :param response: an instance of :attr:`response_class`
        """
        if session is not None:
            session.save_cookie(response, self.session_cookie_name)

    # 添加路由规则, route() 装饰器的实现,依赖
    def add_url_rule(self, rule, endpoint, **options):
        """Connects a URL rule.  Works exactly like the :meth:`route`
        decorator but does not register the view function for the endpoint.

        Basically this example::

            @app.route('/')
            def index():
                pass

        Is equivalent to the following::

            def index():
                pass
            app.add_url_rule('index', '/')
            app.view_functions['index'] = index

        :param rule: the URL rule as string
        :param endpoint: the endpoint for the registered URL rule.  Flask
                         itself assumes the name of the view function as
                         endpoint
        :param options: the options to be forwarded to the underlying
                        :class:`~werkzeug.routing.Rule` object
        """
        options['endpoint'] = endpoint
        options.setdefault('methods', ('GET',))

        # 路由规则添加
        self.url_map.add(Rule(rule, **options))

    #
    # 路由装饰器定义:
    #
    def route(self, rule, **options):
        """A decorator that is used to register a view function for a
        given URL rule.  Example::

            @app.route('/')
            def index():
                return 'Hello World'

        Variables parts in the route can be specified with angular
        brackets (``/user/<username>``).  By default a variable part
        in the URL accepts any string without a slash however a different
        converter can be specified as well by using ``<converter:name>``.

        Variable parts are passed to the view function as keyword
        arguments.

        The following converters are possible:

        =========== ===========================================
        `int`       accepts integers
        `float`     like `int` but for floating point values
        `path`      like the default but also accepts slashes
        =========== ===========================================

        Here some examples::

            @app.route('/')
            def index():
                pass

            @app.route('/<username>')
            def show_user(username):
                pass

            @app.route('/post/<int:post_id>')
            def show_post(post_id):
                pass

        An important detail to keep in mind is how Flask deals with trailing
        slashes.  The idea is to keep each URL unique so the following rules
        apply:

        1. If a rule ends with a slash and is requested without a slash
           by the user, the user is automatically redirected to the same
           page with a trailing slash attached.
        2. If a rule does not end with a trailing slash and the user request
           the page with a trailing slash, a 404 not found is raised.

        This is consistent with how web servers deal with static files.  This
        also makes it possible to use relative link targets safely.

        The :meth:`route` decorator accepts a couple of other arguments
        as well:

        :param rule: the URL rule as string
        :param methods: a list of methods this rule should be limited
                        to (``GET``, ``POST`` etc.).  By default a rule
                        just listens for ``GET`` (and implicitly ``HEAD``).
        :param subdomain: specifies the rule for the subdoain in case
                          subdomain matching is in use.
        :param strict_slashes: can be used to disable the strict slashes
                               setting for this rule.  See above.
        :param options: other options to be forwarded to the underlying
                        :class:`~werkzeug.routing.Rule` object.
        """
        def decorator(f):
            self.add_url_rule(rule, f.__name__, **options)    # 添加路由规则
            self.view_functions[f.__name__] = f               # 更新 视图函数集合, 前面定义,{}
            return f
        return decorator

    #
    # 错误处理装饰器定义:
    #
    def errorhandler(self, code):
        """A decorator that is used to register a function give a given
        error code.  Example::

            @app.errorhandler(404)
            def page_not_found():
                return 'This page does not exist', 404

        You can also register a function as error handler without using
        the :meth:`errorhandler` decorator.  The following example is
        equivalent to the one above::

            def page_not_found():
                return 'This page does not exist', 404
            app.error_handlers[404] = page_not_found

        :param code: the code as integer for the handler
        """
        def decorator(f):
            self.error_handlers[code] = f     # 前述定义{}
            return f
        return decorator

    #
    # 请求前,预处理:
    #   - 注册预处理函数
    #
    def before_request(self, f):
        """Registers a function to run before each request."""
        self.before_request_funcs.append(f)
        return f

    #
    # 请求结束, 清理工作:
    #   - 注册清理函数
    #
    def after_request(self, f):
        """Register a function to be run after each request."""
        self.after_request_funcs.append(f)
        return f

    #
    # 模板上下文处理函数
    #
    def context_processor(self, f):
        """Registers a template context processor function."""
        self.template_context_processors.append(f)
        return f

    #
    # 请求匹配:
    #
    def match_request(self):
        """Matches the current request against the URL map and also
        stores the endpoint and view arguments on the request object
        is successful, otherwise the exception is stored.
        """
        rv = _request_ctx_stack.top.url_adapter.match()
        request.endpoint, request.view_args = rv
        return rv

    #
    # 处理请求:
    #   - 处理 路由URL 和 对应的 视图函数
    #
    def dispatch_request(self):
        """Does the request dispatching.  Matches the URL and returns the
        return value of the view or error handler.  This does not have to
        be a response object.  In order to convert the return value to a
        proper response object, call :func:`make_response`.
        """
        try:
            endpoint, values = self.match_request()    # 请求匹配
            return self.view_functions[endpoint](**values)
        except HTTPException, e:
            handler = self.error_handlers.get(e.code)
            if handler is None:
                return e
            return handler(e)
        except Exception, e:
            handler = self.error_handlers.get(500)
            if self.debug or handler is None:
                raise
            return handler(e)
Esempio n. 48
0
class Klein:
    """
    L{Klein} is an object which is responsible for maintaining the routing
    configuration of our application.

    @ivar _url_map: A C{werkzeug.routing.Map} object which will be used for
        routing resolution.
    @ivar _endpoints: A C{dict} mapping endpoint names to handler functions.
    """

    _subroute_segments = 0

    def __init__(self) -> None:
        self._url_map = Map()
        self._endpoints: Dict[str, KleinRouteHandler] = {}
        self._error_handlers: ErrorHandlers = []
        self._instance: Optional[Klein] = None
        self._boundAs: Optional[str] = None

    def __eq__(self, other: Any) -> bool:
        if isinstance(other, Klein):
            return vars(self) == vars(other)
        return NotImplemented

    def __ne__(self, other: Any) -> bool:
        result = self.__eq__(other)
        if result is NotImplemented:
            return result
        return not result

    @property
    def url_map(self) -> Map:
        """
        Read only property exposing L{Klein._url_map}.
        """
        return self._url_map

    @property
    def endpoints(self) -> Dict[str, KleinRouteHandler]:
        """
        Read only property exposing L{Klein._endpoints}.
        """
        return self._endpoints

    def execute_endpoint(
        self, endpoint: str, request: IRequest, *args: Any, **kwargs: Any
    ) -> KleinRenderable:
        """
        Execute the named endpoint with all arguments and possibly a bound
        instance.
        """
        endpoint_f = self._endpoints[endpoint]
        # type note: endpoint_f is a KleinRouteHandler, which is not defined as
        # taking *args, **kwargs (because they aren't required), but we're
        # going to pass them along here anyway.
        return endpoint_f(
            self._instance, request, *args, **kwargs
        )  # type: ignore[call-arg]

    def execute_error_handler(
        self,
        handler: KleinErrorMethod,
        request: IRequest,
        failure: Failure,
    ) -> KleinRenderable:
        """
        Execute the passed error handler, possibly with a bound instance.
        """
        return handler(self._instance, request, failure)

    def resource(self) -> KleinResource:
        """
        Return an L{IResource} which suitably wraps this app.

        @returns: An L{IResource}
        """

        return KleinResource(self)

    def __get__(self, instance: Any, owner: object) -> "Klein":
        """
        Get an instance of L{Klein} bound to C{instance}.
        """
        if instance is None:
            return self

        if self._boundAs is None:
            for name in dir(owner):
                # Properties may raise an AttributeError on access even though
                # they're visible on the instance, we can ignore those because
                # Klein instances won't raise AttributeError.
                obj = getattr(owner, name, None)
                if obj is self:
                    self._boundAs = name
                    break
            else:
                self._boundAs = "unknown_" + str(id(self))

        boundName = f"__klein_bound_{self._boundAs}__"
        k = cast(
            Optional["Klein"], getattr(instance, boundName, lambda: None)()
        )

        if k is None:
            k = self.__class__()
            k._url_map = self._url_map
            k._endpoints = self._endpoints
            k._error_handlers = self._error_handlers
            k._instance = instance
            kref = ref(k)
            try:
                setattr(instance, boundName, kref)
            except AttributeError:
                pass

        return k

    @staticmethod
    def _segments_in_url(url: str) -> int:
        segment_count = url.count("/")
        if url.endswith("/"):
            segment_count -= 1
        return segment_count

    def route(
        self, url: str, *args: Any, **kwargs: Any
    ) -> Callable[[KleinRouteHandler], KleinRouteHandler]:
        """
        Add a new handler for C{url} passing C{args} and C{kwargs} directly to
        C{werkzeug.routing.Rule}.  The handler function will be passed at least
        one argument an L{twisted.web.server.Request} and any keyword arguments
        taken from the C{url} pattern.

        ::
            @app.route("/")
            def index(request):
                return "Hello"

        @param url: A werkzeug URL pattern given to C{werkzeug.routing.Rule}.
        @type url: str

        @param branch: A bool indiciated if a branch endpoint should
            be added that allows all child path segments that don't
            match some other route to be consumed.  Default C{False}.
        @type branch: bool


        @returns: decorated handler function.
        """
        segment_count = self._segments_in_url(url) + self._subroute_segments

        @named("router for '" + url + "'")
        def deco(f: KleinRouteHandler) -> KleinRouteHandler:
            kwargs.setdefault(
                "endpoint",
                f.__name__,  # type: ignore[union-attr]
            )
            if kwargs.pop("branch", False):
                branchKwargs = kwargs.copy()
                branchKwargs["endpoint"] = branchKwargs["endpoint"] + "_branch"

                @modified(f"branch route '{url}' executor", f)
                def branch_f(
                    instance: Any,
                    request: IRequest,
                    *a: Any,
                    **kw: Any,
                ) -> KleinRenderable:
                    IKleinRequest(request).branch_segments = kw.pop(
                        "__rest__", ""
                    ).split("/")
                    return _call(instance, f, request, *a, **kw)

                branch_f = cast(KleinRouteHandler, branch_f)

                branch_f.segment_count = (  # type: ignore[union-attr]
                    segment_count
                )

                self._endpoints[branchKwargs["endpoint"]] = branch_f
                self._url_map.add(
                    Rule(
                        url.rstrip("/") + "/" + "<path:__rest__>",
                        *args,
                        **branchKwargs,
                    )
                )

            @modified(f"route '{url}' executor", f)
            def _f(
                instance: Any,
                request: IRequest,
                *a: Any,
                **kw: Any,
            ) -> KleinRenderable:
                return _call(instance, f, request, *a, **kw)

            _f = cast(KleinRouteHandler, _f)

            _f.segment_count = segment_count  # type: ignore[union-attr]

            self._endpoints[kwargs["endpoint"]] = _f
            self._url_map.add(Rule(url, *args, **kwargs))
            return f

        return deco

    @contextmanager
    def subroute(self, prefix: str) -> Iterator["Klein"]:
        """
        Within this block, C{@route} adds rules to a
        C{werkzeug.routing.Submount}.

        This is implemented by tinkering with the instance's C{_url_map}
        variable. A context manager allows us to gracefully use the pattern of
        "change a variable, do some things with the new value, then put it back
        to how it was before.

        Named "subroute" to try and give callers a better idea of its
        relationship to C{@route}.

        Usage:
        ::
            with app.subroute("/prefix") as app:
                @app.route("/foo")
                def foo_handler(request):
                    return 'I respond to /prefix/foo'

        @type prefix: string
        @param prefix: The string that will be prepended to the paths of all
                       routes established during the with-block.
        @return: Returns None.
        """

        _map_before_submount = self._url_map

        segments = self._segments_in_url(prefix)

        class SubmountMap:
            def __init__(self) -> None:
                self.rules: List[Rule] = []

            def add(self, rule: Rule) -> None:
                self.rules.append(rule)

        submount_map = SubmountMap()

        try:
            self._url_map = cast(Map, submount_map)
            self._subroute_segments += segments
            yield self
            _map_before_submount.add(Submount(prefix, submount_map.rules))
        finally:
            self._url_map = _map_before_submount
            self._subroute_segments -= segments

    @overload
    def handle_errors(
        self,
        f_or_exception: KleinErrorHandler,
        *additional_exceptions: Type[Exception],
    ) -> Callable[[KleinErrorHandler], Callable]:
        ...  # pragma: no cover

    @overload
    def handle_errors(
        self,
        f_or_exception: Type[Exception],
        *additional_exceptions: Type[Exception],
    ) -> Callable[[KleinErrorHandler], Callable]:
        ...  # pragma: no cover

    def handle_errors(
        self,
        f_or_exception: Union[KleinErrorHandler, Type[Exception]],
        *additional_exceptions: Type[Exception],
    ) -> Callable[[KleinErrorHandler], Callable]:
        """
        Register an error handler. This decorator supports two syntaxes. The
        simpler of these can be used to register a handler for all C{Exception}
        types::

            @app.handle_errors
            def error_handler(request, failure):
                request.setResponseCode(500)
                return 'Uh oh'

        Alternately, a handler can be registered for one or more specific
        C{Exception} types::

            @app.handle_errors(EncodingError, ValidationError):
            def error_handler(request, failure)
                request.setResponseCode(400)
                return failure.getTraceback()

        The handler will be passed a L{twisted.web.server.Request} as well as a
        L{twisted.python.failure.Failure} instance. Error handlers may return a
        deferred, a failure or a response body.

        If more than one error handler is registered, the handlers will be
        executed in the order in which they are defined, until a handler is
        encountered which completes successfully. If no handler completes
        successfully, L{twisted.web.server.Request}'s processingFailed() method
        will be called.

        In addition to handling errors that occur within a L{KleinRouteHandler},
        error handlers also handle any L{werkzeug.exceptions.HTTPException}
        which is raised during request routing.

        In particular, C{werkzeug.exceptions.NotFound} will be raised if no
        matching route is found, so to return a custom 404 users can do the
        following::

            @app.handle_errors(NotFound)
            def error_handler(request, failure):
                request.setResponseCode(404)
                return 'Not found'

        @param f_or_exception: An error handler function, or an C{Exception}
            subclass to scope the decorated handler to.
        @type f_or_exception: C{function} or C{Exception}

        @param additional_exceptions: Additional C{Exception} subclasses to
            scope the decorated function to.
        @type additional_exceptions: C{list} of C{Exception}s

        @returns: decorated error handler function.
        """
        # Try to detect calls using the "simple" @app.handle_error syntax by
        # introspecting the first argument - if it isn't a type which
        # subclasses Exception we assume the simple syntax was used.
        if not isinstance(f_or_exception, type) or not issubclass(
            f_or_exception, Exception
        ):
            # f_or_exception is a KleinErrorHandler
            f = cast(KleinErrorHandler, f_or_exception)
            return self.handle_errors(Exception)(f)

        # f_or_exception is an Exception class
        exceptions = [f_or_exception] + list(additional_exceptions)

        def deco(f: KleinErrorHandler) -> Callable:
            @modified("error handling wrapper", f)
            def _f(
                instance: Optional["Klein"],
                request: IRequest,
                failure: Failure,
            ) -> KleinRenderable:
                return _call(instance, f, request, failure)

            self._error_handlers.append((exceptions, _f))

            return cast(Callable, _f)

        return deco

    def urlFor(
        self,
        request: IRequest,
        endpoint: str,
        values: Optional[Mapping[str, KleinQueryValue]] = None,
        method: Optional[str] = None,
        force_external: bool = False,
        append_unknown: bool = True,
    ) -> str:
        host = request.getHeader(b"host")
        if host is None:
            if force_external:
                raise ValueError(
                    "Cannot build external URL if request"
                    " doesn't contain Host header"
                )
            host = b""
        return buildURL(
            self.url_map.bind(host),
            endpoint,
            values,
            method,
            force_external,
            append_unknown,
        )

    url_for = urlFor

    def run(
        self,
        host: Optional[str] = None,
        port: Optional[int] = None,
        logFile: Optional[IO] = None,
        endpoint_description: Optional[str] = None,
        displayTracebacks: bool = True,
    ) -> None:
        """
        Run a minimal twisted.web server on the specified C{port}, bound to the
        interface specified by C{host} and logging to C{logFile}.

        This function will run the default reactor for your platform and so
        will block the main thread of your application.  It should be the last
        thing your klein application does.

        @param host: The hostname or IP address to bind the listening socket
            to.  "0.0.0.0" will allow you to listen on all interfaces, and
            "127.0.0.1" will allow you to listen on just the loopback
            interface.

        @param port: The TCP port to accept HTTP requests on.

        @param logFile: The file object to log to, by default C{sys.stdout}

        @param endpoint_description: specification of endpoint. Must contain
             protocol, port and interface. May contain other optional arguments,
             e.g. to use SSL: "ssl:443:privateKey=key.pem:certKey=crt.pem"

        @param displayTracebacks: Weather a processing error will result in
            a page displaying the traceback with debugging information or not.
        """
        if logFile is None:
            logFile = sys.stdout

        log.startLogging(logFile)

        if not endpoint_description:
            endpoint_description = f"tcp:port={port}:interface={host}"

        endpoint = serverFromString(reactor, endpoint_description)

        site = Site(self.resource())
        site.displayTracebacks = displayTracebacks

        endpoint.listen(site)
        reactor.run()
Esempio n. 49
0
class Flask(object):
    """
        from flask import Flask
        app = Flask(__name__)
    """
    request_class = Request  # 请求类
    response_class = Response  # 响应类
    static_path = '/static'  # 静态资源路径
    secret_key = None  # 密钥配置
    session_cookie_name = 'session'  # 安全cookie
    # 模板参数
    jinja_options = dict(
        autoescape=True,
        extensions=['jinja2.ext.autoescape', 'jinja2.ext.with_'])

    def __init__(self, package_name):
        print("Flask __init__ start!")
        self.debug = False  # 调试模式开关
        # 注意:
        #   - 这个参数,不是随便乱给的
        #   - 要跟实际的 项目工程目录名对应,否则无法找到对应的工程
        self.package_name = package_name
        # 注意:
        #   - 调用前面定义的 全局私有方法
        #   - 依赖前面的传入参数, 通过该参数, 获取 项目工程源码根目录.
        self.root_path = _get_package_path(self.package_name)  # 获取项目根目录
        self.view_functions = {}  # 视图函数集
        self.error_handlers = {}  # 出错处理
        self.before_request_funcs = []  # 预处理
        self.after_request_funcs = []  # 结束清理
        self.template_context_processors = [_default_template_ctx_processor]

        # todo: 待深入
        self.url_map = Map()  # 关键依赖: werkzeug.routing.Map
        if self.static_path is not None:  # 处理静态资源
            #
            # todo: 待深入 关键依赖: werkzeug.routing.Rule
            self.url_map.add(
                Rule(self.static_path + '/<filename>',
                     build_only=True,
                     endpoint='static'))

            if pkg_resources is not None:
                target = (self.package_name, 'static')
            else:
                target = os.path.join(self.root_path, 'static')

            #
            # todo: 待深入, 关键依赖: werkzeug.SharedDataMiddleware
            self.wsgi_app = SharedDataMiddleware(self.wsgi_app,
                                                 {self.static_path: target})
        # todo: 待深入, jinja2 模板配置
        self.jinja_env = Environment(loader=self.create_jinja_loader(),
                                     **self.jinja_options)
        self.jinja_env.globals.update(
            url_for=url_for, get_flashed_messages=get_flashed_messages)
        print("Flask __init__ end!")

    # 加载 templates 目录文件
    def create_jinja_loader(self):
        if pkg_resources is None:
            # 加载 模板目录 文件
            return FileSystemLoader(os.path.join(self.root_path, 'templates'))
        return PackageLoader(self.package_name)

    def update_template_context(self, context):
        reqctx = _request_ctx_stack.top
        print("reqctx:", to_dict(reqctx))
        print("template_context_processors:", self.template_context_processors)
        for func in self.template_context_processors:
            print("func:", func())
            context.update(func())

    # 对外运行接口: 借用werkzeug.run_simple 实现
    def run(self, host='localhost', port=5000, **options):
        # from werkzeug import run_simple
        from werkzeug.serving import run_simple  # 关键依赖: 核心运行模块
        if 'debug' in options:
            self.debug = options.pop('debug')
        options.setdefault('use_reloader', self.debug)
        options.setdefault('use_debugger', self.debug)
        return run_simple(
            host, port, self, **options
        )  # 依赖 werkzeug->werkzeug.serving.make_server->werkzeug.serving.BaseWSGIServer,
        # 主要就是建立socket监听,绑定server、app,其中内容丰富还需深入

    def test_client(self):
        # from werkzeug import Client        # todo: 待深入, 关键依赖 已失效 更换如下
        from werkzeug.test import Client
        return Client(self, self.response_class, use_cookies=True)

    def open_resource(self, resource):
        """Opens a resource from the application's resource folder.  To see
        how this works, consider the following folder structure::
            /myapplication.py
            /schemal.sql
            /static
                /style.css
            /template
                /layout.html
                /index.html
        If you want to open the `schema.sql` file you would do the
        following::
            with app.open_resource('schema.sql') as f:
                contents = f.read()
                do_something_with(contents)
        """
        if pkg_resources is None:
            return open(os.path.join(self.root_path, resource), 'rb')
        return pkg_resources.resource_stream(self.package_name, resource)

    # 关键接口: 创建 or 打开一个 会话(session)
    #   - 实现方式: 使用 cookie 实现
    #   - 默认把全部session数据, 存入一个 cookie 中.
    #   - 对比 flask-0.4 版本, 部分重构
    def open_session(self, request):
        key = self.secret_key
        if key is not None:
            return SecureCookie.load_cookie(request,
                                            self.session_cookie_name,
                                            secret_key=key)

    # 关键接口: 更新session-->所以说flask的session是依赖cookie的,Flask中的session是存在浏览器中
    def save_session(self, session, response):
        if session is not None:
            session.save_cookie(response, self.session_cookie_name)

    # 添加路由规则, route() 装饰器的实现,依赖
    def add_url_rule(self, rule, endpoint, **options):
        """
        Basically this example::
            @app.route('/')
            def index():
                pass
        Is equivalent to the following::
            def index():
                pass
            app.add_url_rule('index', '/')
            app.view_functions['index'] = index
        """
        options['endpoint'] = endpoint
        options.setdefault('methods', ('GET', ))

        # 路由规则添加
        self.url_map.add(Rule(rule, **options))

    # 路由装饰器定义:
    def route(self, rule, **options):
        """A decorator that is used to register a view function for a
        given URL rule.  Example::

            @app.route('/')
            def index():
                return 'Hello World'
        The following converters are possible:

        =========== ===========================================
        `int`       accepts integers
        `float`     like `int` but for floating point values
        `path`      like the default but also accepts slashes
        =========== ===========================================

        Here some examples::

            @app.route('/')
            def index():
                pass

            @app.route('/<username>')
            def show_user(username):
                pass

            @app.route('/post/<int:post_id>')
            def show_post(post_id):
                pass
        """
        def decorator(f):
            self.add_url_rule(rule, f.__name__, **options)  # 添加路由规则
            self.view_functions[f.__name__] = f  # 更新 视图函数集合, 前面定义,{}
            return f

        return decorator

    # 错误处理装饰器定义:
    def errorhandler(self, code):
        """
            @app.errorhandler(404)
            def page_not_found():
                return 'This page does not exist', 404

            def page_not_found():
                return 'This page does not exist', 404
            app.error_handlers[404] = page_not_found
        """
        def decorator(f):
            self.error_handlers[code] = f  # 前述定义{}
            return f

        return decorator

    # 请求前,预处理:
    #   - 注册预处理函数
    def before_request(self, f):
        """Registers a function to run before each request."""
        self.before_request_funcs.append(f)
        return f

    # 请求结束, 清理工作:
    #   - 注册清理函数
    def after_request(self, f):
        """Register a function to be run after each request."""
        self.after_request_funcs.append(f)
        return f

    # 模板上下文处理函数
    def context_processor(self, f):
        """Registers a template context processor function."""
        self.template_context_processors.append(f)
        return f

    # 请求匹配:
    def match_request(self):
        rv = _request_ctx_stack.top.url_adapter.match()
        request.endpoint, request.view_args = rv
        return rv

    # 处理请求:
    #   - 处理 路由URL 和 对应的 视图函数
    def dispatch_request(self):
        try:
            endpoint, values = self.match_request()  # 请求匹配
            return self.view_functions[endpoint](
                **
                values)  # 视图处理集中的函数如下,以url为key,以对应的函数为value,返回的就是对应url下逻辑的处理结果

            # 'view_functions': {
            #     'index': < function index at 0x00000000034256D8 > ,
            #     'hello4': < function hello4 at 0x0000000003425898 > ,
            #     'hello3': < function hello3 at 0x0000000003425828 > ,
            #     '/hello2': < function hello2 at 0x0000000003425668 > ,
            #     'hello1': < function hello1 at 0x0000000003425748 >
            # },

        except HTTPException, e:
            handler = self.error_handlers.get(e.code)
            if handler is None:
                return e
            return handler(e)
        except Exception, e:
            handler = self.error_handlers.get(500)
            if self.debug or handler is None:
                raise
            return handler(e)
Esempio n. 50
0
class Flask(object):
    request_class = FlaskRequest
    response_class = FlaskResponse

    secret_key = 'dangerous'
    session_cookie_name = 'flask_session'

    def __init__(self):
        self.debug = False
        self.url_map = Map()
        self.view_functions = {}

    def dispatch_request(self, environ):
        """
        url_map:Map([<Rule '/favicon.ico' (HEAD, GET) -> favicon>,
                     <Rule '/hello/<user>' (HEAD, GET) -> hello>])
        endpoint:hello ,
        values:{'user': '******'}
        """
        url_adapter = self.url_map.bind_to_environ(environ)
        endpoint, values = url_adapter.match()
        print('endpoint:%s, values:%s' % (endpoint, values))
        return self.view_functions[endpoint](**values)

    def route(self, rule, **options):
        def decorator(f):
            endpoint = options.setdefault('endpoint', f.__name__)
            self.url_map.add(Rule(rule, **options))
            self.view_functions[endpoint] = f
            return f

        return decorator

    def open_session(self, request):
        '''
        load_cookie:
        data = request.cookies.get(key)
        if not data:
            return cls(secret_key=secret_key)
        return cls.unserialize(data, secret_key)
        '''
        return SecureCookie.load_cookie(request,
                                        key=self.session_cookie_name,
                                        secret_key=self.secret_key)

    def save_session(self, session, response):
        session.save_cookie(response, key=self.session_cookie_name)

    def make_response(self, session, response):
        self.save_session(session, response)
        return response

    def wsgi_app(self, environ, start_response):
        request = self.request_class(environ)

        session = self.open_session(request)
        print('total_request: %d' % session.setdefault('total_request', 0))
        session['total_request'] += 1

        rv = self.dispatch_request(environ)
        response = self.response_class(rv)
        response = self.make_response(session, response)
        return response(environ, start_response)

    def __call__(self, environ, start_response):
        return self.wsgi_app(environ, start_response)

    def run(self, host='localhost', port=5000, **options):
        if 'debug' in options:
            self.debug = options.pop('debug')

        options.setdefault('use_reloader', self.debug)
        options.setdefault('use_debugger', self.debug)

        from werkzeug.serving import run_simple
        run_simple(host, port, self, **options)
Esempio n. 51
0
class Sockets:
    def __init__(self, app=None):
        #: Compatibility with 'Flask' application.
        #: The :class:`~werkzeug.routing.Map` for this instance. You can use
        #: this to change the routing converters after the class was created
        #: but before any routes are connected.
        self.url_map = Map()

        #: Compatibility with 'Flask' application.
        #: All the attached blueprints in a dictionary by name. Blueprints
        #: can be attached multiple times so this dictionary does not tell
        #: you how often they got attached.
        self.blueprints = {}
        self._blueprint_order = []

        self.view_functions = {}

        if app:
            self.init_app(app)

    def __create_url_adapter(self, url_map, request):
        if request is not None:
            return url_map.bind_to_environ(
                request.environ, server_name=self.app.config["SERVER_NAME"])
        elif self.app.config["SERVER_NAME"] is not None:
            return url_map.bind(
                self.app.config["SERVER_NAME"],
                script_name=self.app.config["APPLICATION_ROOT"] or "/",
                url_scheme=self.app.config["PREFERRED_URL_SCHEME"],
            )

    def create_url_adapter(self, request):
        adapter_for_app = self.__create_url_adapter(self.app.url_map, request)
        adapter_for_sockets = self.__create_url_adapter(self.url_map, request)
        return WsUrlAdapterWrapper(adapter_for_app, adapter_for_sockets)

    def init_app(self, app):
        self.app = app
        self.app_wsgi_app = app.wsgi_app

        app.wsgi_app = self.wsgi_app
        app.create_url_adapter = self.create_url_adapter

    def route(self, rule, **options):
        def decorator(f):
            endpoint = options.pop("endpoint", None)
            self.add_url_rule(rule, endpoint=endpoint, view_func=f, **options)
            return f

        return decorator

    def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
        if endpoint is None:
            endpoint = _endpoint_from_view_func(view_func)

        self.url_map.add(Rule(rule, endpoint=endpoint, **options))
        self.view_functions[endpoint] = view_func

    def add_view(self, url, f, endpoint=None, **options):
        return self.add_url_rule(url, endpoint, f, **options)

    def register_blueprint(self, blueprint, **options):
        """
        Registers a blueprint for web sockets like for 'Flask' application.
        Decorator :meth:`~flask.app.setupmethod` is not applied, because it
        requires ``debug`` and ``_got_first_request`` attributes to be defined.
        """
        first_registration = False

        if blueprint.name in self.blueprints:
            assert self.blueprints[blueprint.name] is blueprint, (
                "A blueprint's name collision occurred between %r and "
                '%r.  Both share the same name "%s".  Blueprints that '
                "are created on the fly need unique names." %
                (blueprint, self.blueprints[blueprint.name], blueprint.name))
        else:
            self.blueprints[blueprint.name] = blueprint
            self._blueprint_order.append(blueprint)
            first_registration = True

        blueprint.register(self, options, first_registration)

    def wsgi_app(self, environ, start_response):
        adapter = self.url_map.bind_to_environ(environ)
        try:
            # Find handler view function
            endpoint, values = adapter.match()
            handler = self.view_functions[endpoint]

            # Handle environment
            if not environ.get("wsgi.websocket"):
                return self.app_wsgi_app(environ, start_response)

            ws = environ["wsgi.websocket"]

            cookie = None
            if "HTTP_COOKIE" in environ:
                cookie = parse_cookie(environ["HTTP_COOKIE"])

            with self.app.app_context():
                with self.app.request_context(environ):
                    # add cookie to the request to have correct session handling
                    request.cookie = cookie
                    # Run WebSocket handler
                    handler(ws, **values)
                    return []
        except (NotFound, KeyError):
            if environ.get("wsgi.websocket"):
                environ.get("wsgi.websocket").close()
            return self.app_wsgi_app(environ, start_response)
Esempio n. 52
0
class Flask(object):
    request_class = FlaskRequest
    response_class = FlaskResponse

    secret_key = 'dangerous'
    session_cookie_name = 'flask_session'

    def __init__(self):
        self.debug = False
        self.url_map = Map()
        self.view_functions = {}

    def dispatch_request(self, environ):
        url_adapter = self.url_map.bind_to_environ(environ)
        endpoint, values = url_adapter.match()
        return self.view_functions[endpoint](**values)

    def route(self, rule, **options):
        def decorator(f):
            endpoint = options.setdefault('endpoint', f.__name__)
            self.url_map.add(Rule(rule, **options))
            self.view_functions[endpoint] = f
            return f

        return decorator

    def open_session(self, request):
        return SecureCookie.load_cookie(request,
                                        key=self.session_cookie_name,
                                        secret_key=self.secret_key)

    def save_session(self, session, response):
        session.save_cookie(response, key=self.session_cookie_name)

    def make_response(self, session, response):
        self.save_session(session, response)
        return response

    def wsgi_app(self, environ, start_response):
        request = self.request_class(environ)

        session = self.open_session(request)
        print('total_request: %d' % session.setdefault('total_request', 0))
        session['total_request'] += 1

        rv = self.dispatch_request(environ)
        response = self.response_class(rv)
        response = self.make_response(session, response)
        return response(environ, start_response)

    def __call__(self, environ, start_response):
        return self.wsgi_app(environ, start_response)

    def run(self, host='localhost', port=5000, **options):
        if 'debug' in options:
            self.debug = options.pop('debug')

        options.setdefault('use_reloader', self.debug)
        options.setdefault('use_debugger', self.debug)

        from werkzeug.serving import run_simple
        run_simple(host, port, self, **options)
Esempio n. 53
0
class Application:

    default_config = {
        "SESSION_TYPE": "redis",
        "SESSION_COOKIE_NAME": "session",
        "SESSION_KEY_PREFIX": "session:",
        "SECRET_KEY": "secret",
        "SESSION_LIFETIME": 60*10,
        "REDIS_HOST": "127.0.0.1",
        "REDIS_PORT": 6379,
        "REDIS_PWD": None
    }

    session_interface_map = ImmutableDict({
        "redis": RedisSessionInterface
    })

    secret_key = 'secret'
    session_lifetime = 60*10

    def __init__(
        self,
        config=None,
        root_path=None,
        static_folder='static',
        template_folder='templates',
        template_loader=None,
        static_url_path='/static'
    ):
        if config is None:
            config = self.default_config
        self.config = ImmutableDict(config)
        for config in self.config:
            setattr(self, config.lower(), self.config[config])

        self.view_functions = dict()
        self.handler_map = dict()
        self.url_map = Map()
        self.before_request_funcs = dict()

        if root_path is None:
            root_path = os.path.dirname(os.path.abspath('<input>'))
        self.root_path = root_path
        self.static_folder = static_folder
        self.template_folder = template_folder
        self.static_url_path = static_url_path

        self.jinja_loader = template_loader
        self.jinja_env = self.create_jinja_env()

        self.session_interface = self.get_interface()

        self.add_url_rule(self.static_url_path+"/<path:filename>", endpoint='static', view_func=self.send_static_file)

    def wsgi_app(self, environ, start_response):

        local.request = Request(environ, self)
        local.current_app = self
        local.g = Global()
        local.session = self.session_interface.open_session(self, request)

        resp = self.full_dispatch_request()
        iterable = resp(environ, start_response)

        return iterable

    def send_static_file(self, filename):
        return send_from_directory(self.static_folder, filename)

    def before_request(self, endpoint=None):
        def decorator(f):
            self.before_request_funcs.setdefault(endpoint, []).append(f)
            return f()

        # 为了让before_request不用加( )而加的傻逼东西,迟早删了
        if type(endpoint) is not str:
            self.before_request_funcs.setdefault(None, []).append(endpoint)

        return decorator

    def add_before_request_func(self, func, endpoint=None):
        self.before_request_funcs.setdefault(endpoint, []).append(func)

    def preprocess_request(self):
        endpoint = request.url_rule.endpoint if request.url_rule is not None else None
        funcs = self.before_request_funcs.get(None, [])
        if endpoint is not None:
            for func in self.before_request_funcs.get(endpoint, ()):
                funcs.append(func)

        for func in funcs:
            rv = func()
            if rv is not None:
                return rv

    def register_error_handler(self, exc_class_or_code, handler):
        exc_class = _find_exceptions(exc_class_or_code)
        if isinstance(exc_class, Exception):
            raise ValueError("Handler can only be registered for exception classes, not instance")
        self.handler_map[exc_class] = handler

    def route(self, path, **options):

        def decorator(f):
            self.add_url_rule(path, f, **options)
            return f

        return decorator

    def error_handler(self, exc_class):
        def decorator(f):
            self.register_error_handler(exc_class, f)
            return f
        return decorator

    def add_url_rule(self, path, view_func, endpoint=None, methods=None):
        if endpoint is None:
            endpoint = view_func.__name__

        old_func = self.view_functions.get(endpoint, None)
        if old_func is not None and old_func != view_func:
            raise AssertionError(
                "View function mapping is overwriting an "
                "existing endpoint function: %s" % endpoint
             )

        if methods is None:
            methods = getattr(view_func, "methods", None) or ("GET",)

        rule = Rule(path, endpoint=endpoint, methods=methods)
        self.url_map.add(rule)
        self.view_functions[endpoint] = view_func

    # match the commented out MethodView
    # def add_method_view(self, path, method_view, endpoint=None):
    #     if endpoint is None:
    #         endpoint = method_view.__name__
    #
    #     old_view = self.view_functions.get(endpoint, None)
    #     if old_view is not None and old_view != method_view:
    #         raise AssertionError(
    #             "View function mapping is overwriting an "
    #             "existing endpoint function(MethodView): %s" % endpoint
    #         )
    #
    #     rule = Rule(path, endpoint=endpoint, methods=method_view.methods)
    #     self.url_map.add(rule)
    #     self.view_functions[endpoint] = method_view

    def dispatch_request(self):
        # return value from view_function
        if request.routing_exception is not None:
            raise request.routing_exception
        rule = request.url_rule
        return self.view_functions[rule.endpoint](**request.view_args)

    def full_dispatch_request(self):
        # return a Response object
        try:
            resp = self.preprocess_request()
            if resp is None:
                resp = self.dispatch_request()
        except Exception as e:
            resp = self.handler_exceptions(e)

        return self.finalize_request(resp)

    def finalize_request(self, resp):
        if isinstance(resp, BaseResponse) or callable(resp):
            resp = resp
        else:
            resp = Response(resp)

        self.session_interface.save_session(session, resp, self.session_lifetime)
        return resp

    def handler_exceptions(self, e):
        exc_type = type(e)

        if exc_type in self.handler_map:
            handler = self.handler_map.get(exc_type)
            return handler(e)
        elif issubclass(exc_type, HTTPException):
            return e
        else:
            raise e

    def get_interface(self):
        session_type = self.config.get("SESSION_TYPE", 'redis')
        interface_class = self.session_interface_map[session_type]
        return interface_class(self)

    def render_template(self, template_name, **kws):
        template = self.jinja_env.get_template(template_name)
        return Response(template.render(**kws), content_type='text/html')

    def create_jinja_env(self):
        if self.jinja_loader is None:
            self.jinja_loader = FileSystemLoader(os.path.join(self.root_path, self.template_folder))
        env = Environment(loader=self.jinja_loader)
        env.globals.update(
            url_for=url_for,
            config=self.config,
            request=request,
            g=g,
            session=session
        )
        return env

    def __call__(self, environ, start_response):
        return self.wsgi_app(environ, start_response)
Esempio n. 54
0
class Flask(object):
    """The flask object implements a WSGI application and acts as the central
    object.  It is passed the name of the module or package of the
    application.  Once it is created it will act as a central registry for
    the view functions, the URL rules, template configuration and much more.

    The name of the package is used to resolve resources from inside the
    package or the folder the module is contained in depending on if the
    package parameter resolves to an actual python package (a folder with
    an `__init__.py` file inside) or a standard module (just a `.py` file).

    For more information about resource loading, see :func:`open_resource`.

    Usually you create a :class:`Flask` instance in your main module or
    in the `__init__.py` file of your package like this::

        from flask import Flask
        app = Flask(__name__)
    """

    #: the class that is used for request objects.  See :class:`~flask.request`
    #: for more information.
    request_class = Request

    #: the class that is used for response objects.  See
    #: :class:`~flask.Response` for more information.
    response_class = Response

    #: path for the static files.  If you don't want to use static files
    #: you can set this value to `None` in which case no URL rule is added
    #: and the development server will no longer serve any static files.
    static_path = '/static'

    #: if a secret key is set, cryptographic components can use this to
    #: sign cookies and other things.  Set this to a complex random value
    #: when you want to use the secure cookie for instance.
    secret_key = None

    #: The secure cookie uses this for the name of the session cookie
    session_cookie_name = 'session'

    #: options that are passed directly to the Jinja2 environment
    jinja_options = dict(
        autoescape=True,
        extensions=['jinja2.ext.autoescape', 'jinja2.ext.with_']
    )

    def __init__(self, package_name):
        #: the debug flag.  Set this to `True` to enable debugging of
        #: the application.  In debug mode the debugger will kick in
        #: when an unhandled exception ocurrs and the integrated server
        #: will automatically reload the application if changes in the
        #: code are detected.
        self.debug = False

        #: the name of the package or module.  Do not change this once
        #: it was set by the constructor.
        self.package_name = package_name

        #: where is the app root located?
        self.root_path = _get_package_path(self.package_name)

        #: a dictionary of all view functions registered.  The keys will
        #: be function names which are also used to generate URLs and
        #: the values are the function objects themselves.
        #: to register a view function, use the :meth:`route` decorator.
        self.view_functions = {}

        #: a dictionary of all registered error handlers.  The key is
        #: be the error code as integer, the value the function that
        #: should handle that error.
        #: To register a error handler, use the :meth:`errorhandler`
        #: decorator.
        self.error_handlers = {}

        #: a list of functions that should be called at the beginning
        #: of the request before request dispatching kicks in.  This
        #: can for example be used to open database connections or
        #: getting hold of the currently logged in user.
        #: To register a function here, use the :meth:`before_request`
        #: decorator.
        self.before_request_funcs = []

        #: a list of functions that are called at the end of the
        #: request.  Tha function is passed the current response
        #: object and modify it in place or replace it.
        #: To register a function here use the :meth:`after_request`
        #: decorator.
        self.after_request_funcs = []

        #: a list of functions that are called without arguments
        #: to populate the template context.  Each returns a dictionary
        #: that the template context is updated with.
        #: To register a function here, use the :meth:`context_processor`
        #: decorator.
        self.template_context_processors = [_default_template_ctx_processor]

        self.url_map = Map()

        if self.static_path is not None:
            self.url_map.add(Rule(self.static_path + '/<filename>',
                                  build_only=True, endpoint='static'))
            if pkg_resources is not None:
                target = (self.package_name, 'static')
            else:
                target = os.path.join(self.root_path, 'static')
            self.wsgi_app = SharedDataMiddleware(self.wsgi_app, {
                self.static_path: target
            })

        #: the Jinja2 environment.  It is created from the
        #: :attr:`jinja_options` and the loader that is returned
        #: by the :meth:`create_jinja_loader` function.
        self.jinja_env = Environment(loader=self.create_jinja_loader(),
                                     **self.jinja_options)
        self.jinja_env.globals.update(
            url_for=url_for,
            get_flashed_messages=get_flashed_messages
        )

    def create_jinja_loader(self):
        """Creates the Jinja loader.  By default just a package loader for
        the configured package is returned that looks up templates in the
        `templates` folder.  To add other loaders it's possible to
        override this method.
        """
        if pkg_resources is None:
            return FileSystemLoader(os.path.join(self.root_path, 'templates'))
        return PackageLoader(self.package_name)

    def update_template_context(self, context):
        """Update the template context with some commonly used variables.
        This injects request, session and g into the template context.

        :param context: the context as a dictionary that is updated in place
                        to add extra variables.
        """
        reqctx = _request_ctx_stack.top
        for func in self.template_context_processors:
            context.update(func())

    def run(self, host='localhost', port=5000, **options):
        """Runs the application on a local development server.  If the
        :attr:`debug` flag is set the server will automatically reload
        for code changes and show a debugger in case an exception happened.

        :param host: the hostname to listen on.  set this to ``'0.0.0.0'``
                     to have the server available externally as well.
        :param port: the port of the webserver
        :param options: the options to be forwarded to the underlying
                        Werkzeug server.  See :func:`werkzeug.run_simple`
                        for more information.
        """
        from werkzeug import run_simple
        if 'debug' in options:
            self.debug = options.pop('debug')
        options.setdefault('use_reloader', self.debug)
        options.setdefault('use_debugger', self.debug)
        return run_simple(host, port, self, **options)

    def test_client(self):
        """Creates a test client for this application.  For information
        about unit testing head over to :ref:`testing`.
        """
        from werkzeug import Client
        return Client(self, self.response_class, use_cookies=True)

    def open_resource(self, resource):
        """Opens a resource from the application's resource folder.  To see
        how this works, consider the following folder structure::

            /myapplication.py
            /schemal.sql
            /static
                /style.css
            /template
                /layout.html
                /index.html

        If you want to open the `schema.sql` file you would do the
        following::

            with app.open_resource('schema.sql') as f:
                contents = f.read()
                do_something_with(contents)

        :param resource: the name of the resource.  To access resources within
                         subfolders use forward slashes as separator.
        """
        if pkg_resources is None:
            return open(os.path.join(self.root_path, resource), 'rb')
        return pkg_resources.resource_stream(self.package_name, resource)

    def open_session(self, request):
        """Creates or opens a new session.  Default implementation stores all
        session data in a signed cookie.  This requires that the
        :attr:`secret_key` is set.

        :param request: an instance of :attr:`request_class`.
        """
        key = self.secret_key
        if key is not None:
            return SecureCookie.load_cookie(request, self.session_cookie_name,
                                            secret_key=key)

    def save_session(self, session, response):
        """Saves the session if it needs updates.  For the default
        implementation, check :meth:`open_session`.

        :param session: the session to be saved (a
                        :class:`~werkzeug.contrib.securecookie.SecureCookie`
                        object)
        :param response: an instance of :attr:`response_class`
        """
        if session is not None:
            session.save_cookie(response, self.session_cookie_name)

    def add_url_rule(self, rule, endpoint, **options):
        """Connects a URL rule.  Works exactly like the :meth:`route`
        decorator but does not register the view function for the endpoint.

        Basically this example::

            @app.route('/')
            def index():
                pass

        Is equivalent to the following::

            def index():
                pass
            app.add_url_rule('index', '/')
            app.view_functions['index'] = index

        :param rule: the URL rule as string
        :param endpoint: the endpoint for the registered URL rule.  Flask
                         itself assumes the name of the view function as
                         endpoint
        :param options: the options to be forwarded to the underlying
                        :class:`~werkzeug.routing.Rule` object
        """
        options['endpoint'] = endpoint
        options.setdefault('methods', ('GET',))
        self.url_map.add(Rule(rule, **options))

    def route(self, rule, **options):
        """A decorator that is used to register a view function for a
        given URL rule.  Example::

            @app.route('/')
            def index():
                return 'Hello World'

        Variables parts in the route can be specified with angular
        brackets (``/user/<username>``).  By default a variable part
        in the URL accepts any string without a slash however a different
        converter can be specified as well by using ``<converter:name>``.

        Variable parts are passed to the view function as keyword
        arguments.

        The following converters are possible:

        =========== ===========================================
        `int`       accepts integers
        `float`     like `int` but for floating point values
        `path`      like the default but also accepts slashes
        =========== ===========================================

        Here some examples::

            @app.route('/')
            def index():
                pass

            @app.route('/<username>')
            def show_user(username):
                pass

            @app.route('/post/<int:post_id>')
            def show_post(post_id):
                pass

        An important detail to keep in mind is how Flask deals with trailing
        slashes.  The idea is to keep each URL unique so the following rules
        apply:

        1. If a rule ends with a slash and is requested without a slash
           by the user, the user is automatically redirected to the same
           page with a trailing slash attached.
        2. If a rule does not end with a trailing slash and the user request
           the page with a trailing slash, a 404 not found is raised.

        This is consistent with how web servers deal with static files.  This
        also makes it possible to use relative link targets safely.

        The :meth:`route` decorator accepts a couple of other arguments
        as well:

        :param rule: the URL rule as string
        :param methods: a list of methods this rule should be limited
                        to (``GET``, ``POST`` etc.).  By default a rule
                        just listens for ``GET`` (and implicitly ``HEAD``).
        :param subdomain: specifies the rule for the subdoain in case
                          subdomain matching is in use.
        :param strict_slashes: can be used to disable the strict slashes
                               setting for this rule.  See above.
        :param options: other options to be forwarded to the underlying
                        :class:`~werkzeug.routing.Rule` object.
        """
        def decorator(f):
            self.add_url_rule(rule, f.__name__, **options)
            self.view_functions[f.__name__] = f
            return f
        return decorator

    def errorhandler(self, code):
        """A decorator that is used to register a function give a given
        error code.  Example::

            @app.errorhandler(404)
            def page_not_found():
                return 'This page does not exist', 404

        You can also register a function as error handler without using
        the :meth:`errorhandler` decorator.  The following example is
        equivalent to the one above::

            def page_not_found():
                return 'This page does not exist', 404
            app.error_handlers[404] = page_not_found

        :param code: the code as integer for the handler
        """
        def decorator(f):
            self.error_handlers[code] = f
            return f
        return decorator

    def before_request(self, f):
        """Registers a function to run before each request."""
        self.before_request_funcs.append(f)
        return f

    def after_request(self, f):
        """Register a function to be run after each request."""
        self.after_request_funcs.append(f)
        return f

    def context_processor(self, f):
        """Registers a template context processor function."""
        self.template_context_processors.append(f)
        return f

    def match_request(self):
        """Matches the current request against the URL map and also
        stores the endpoint and view arguments on the request object
        is successful, otherwise the exception is stored.
        """
        rv = _request_ctx_stack.top.url_adapter.match()
        request.endpoint, request.view_args = rv
        return rv

    def dispatch_request(self):
        """Does the request dispatching.  Matches the URL and returns the
        return value of the view or error handler.  This does not have to
        be a response object.  In order to convert the return value to a
        proper response object, call :func:`make_response`.
        """
        try:
            endpoint, values = self.match_request()
            return self.view_functions[endpoint](**values)
        except HTTPException, e:
            handler = self.error_handlers.get(e.code)
            if handler is None:
                return e
            return handler(e)
        except Exception, e:
            handler = self.error_handlers.get(500)
            if self.debug or handler is None:
                raise
            return handler(e)
Esempio n. 55
0
class Router(object):
    """ API Router
    """

    def __init__(self):
        logger.info("DefaultRouter::__init__")
        self.rule_class = Rule
        self.view_functions = {}
        self.url_map = Map()
        self.is_initialized = False
        self.http_host = ""

    def initialize(self, context, request):
        """ called by the API Framework
        """
        logger.info("DefaultRouter.initialize: context=%r request=%r" % (context, request))

        self.context = context
        self.request = request

        self.environ = request.environ
        self.http_host = request["HTTP_HOST"]
        self.url = request.getURL()

        if self.is_initialized:
            return

        logger.info("DefaultRouter::initialize")
        for name, provider in component.getUtilitiesFor(IRouteProvider):
            logger.info("DefaultRouter::initialize: name=%s, provider=%r", name, provider)

            if getattr(provider, "initialize", None):
                provider.initialize(context, request)

            for route in provider.routes:
                self.add_url_rule(*route)

        self.is_initialized = True


    def add_url_rule(self, rule, endpoint=None, view_func=None, options=None):
        """ adds a rule to the url map

        :param rule:      the url rule, e.g /version
        :param endpoint:  the name of the rule, e.g version
        :param endpoint:  The endpoint for this rule. This can be anything
        :param options:   additional options to be passed to the router
        """
        logger.info("DefaultRouter.add_url_rule: %s (%s) -> %r options: %r", rule, endpoint, view_func.func_name, options)
        if endpoint is None:
            endpoint = view_func.__name__

        old_func = self.view_functions.get(endpoint)
        if old_func is not None and old_func != view_func:
            raise AssertionError('View function mapping is overwriting an '
                                 'existing endpoint function: %s' % endpoint)

        self.view_functions[endpoint] = view_func

        if options is None:
            # http://werkzeug.pocoo.org/docs/routing/#werkzeug.routing.Rule
            return self.url_map.add(self.rule_class(rule, endpoint=endpoint))

        return self.url_map.add(self.rule_class(rule, endpoint=endpoint, **options))

    def get_adapter(self, **options):
        """ return a new werkzeug map adapter

        default options:
        (script_name=None, subdomain=None, url_scheme='http', default_method='GET', path_info=None, query_args=None)
        see: http://werkzeug.pocoo.org/docs/routing/#werkzeug.routing.Map.bind
        """
        adapter = self.url_map.bind(self.http_host, **options)
        return adapter

    def match(self, context, request, path):
        """ url matcher

        default options:
        (path_info=None, method=None, return_rule=False, query_args=None)
        see: http://werkzeug.pocoo.org/docs/routing/#werkzeug.routing.MapAdapter.match
        """
        method = request.environ.get("REQUEST_METHOD", "GET")
        logger.info("router.match: method=%s" % method)
        adapter = self.get_adapter(path_info=path)
        endpoint, values = adapter.match(method=method)
        return endpoint, values

    def url_for(self, endpoint, **options):
        """ get the url for the endpoint

        default options:
        (values=None, method=None, force_external=False, append_unknown=True)
        see: http://werkzeug.pocoo.org/docs/routing/#werkzeug.routing.MapAdapter.build
        """

        # XXX: this is all a little bit hacky, especially when it comes to virtual hosting.

        spp = self.request.physicalPathFromURL(self.url)

        # find the API view root
        path = []
        for el in spp:
            path.append(el)
            if el == "API" or el == "@@API":
                break

        virt_path = self.request.physicalPathToVirtualPath(path)
        script_name = self.request.physicalPathToURL(virt_path, relative=1)

        adapter = self.get_adapter(script_name=script_name)
        return adapter.build(endpoint, **options)

    def __call__(self, context, request, path):
        """ calls the matching view function for the given path
        """
        logger.info("router.__call__: path=%s" % path)

        endpoint, values = self.match(context, request, path)
        return self.view_functions[endpoint](context, request, **values)
Esempio n. 56
0
class PPrika(object):
    """
    类似于flask的Flask,其示例用于注册路由、启动应用等
    专注于 restful,有方便灵活的错误处理,支持蓝图
    因此将不会有静态资源、模板、session、重定向等的实现
    应该会有数据库连接、请求钩子等功能...
    """
    def __init__(self):
        self.url_map = Map()
        self.view_functions = {}  # {endpoint: view_func}
        self.blueprints = {}  # {bp_name: blueprint}
        self.error_handlers = {}  # {bp_name: {status: {error: function}}}
        self.api_set = set()  # {bp_name, bp_name, ...}

    def wsgi_app(self, environ, start_response):
        """
        类似flask的同名函数 'wsgi_app'
        WSGI app,接受 __call__ / 前方server 调用,处理所有请求的入口
        匹配、处理请求并返回响应结果,捕捉、处理异常
        """
        ctx = RequestContext(self, environ)  # 请求上下文对象
        try:
            try:
                ctx.bind()  # 绑定请求上下文并匹配路由
                rv = self.dispatch_request()
                response = make_response(rv)
            except Exception as e:
                response = self.handle_exception(e)
            return response(environ, start_response)
        finally:
            ctx.unbind()

    def __call__(self, environ, start_response):
        return self.wsgi_app(environ, start_response)

    def run(self, host='localhost', port=9000, **options):
        """
        以 werkzeug 提供的服务器启动该应用实例
        以run_simple的 use_reloader、use_debugger 实现灵活的debug
        """
        options.setdefault("threaded", True)  # 线程隔离
        run_simple(host, port, self, **options)

    def add_url_rule(self, path, endpoint=None, view_func=None, **options):
        """
        类似于flask的同名函数 `add_url_rule`,但仅实现了最基本的功能
        将一个url rule注册到对应的endpoint上,并把endpoint关联到处理函数view_func上
        借助endpoint实现path与func多对一,其中path与endpoint多对一,endpoint与view_func一对一
        """
        if endpoint is None:
            assert view_func is not None, "无endpoint时view_func不可为空"
            endpoint = view_func.__name__

        methods = options.pop('methods', '') or ("GET", )
        if isinstance(methods, str):
            # 即允许了类似methods="POST"的method指定方式
            methods = (methods, )
        methods = set(item.upper() for item in methods)

        rule = Rule(path, methods=methods, endpoint=endpoint, **options)
        self.url_map.add(rule)

        # 为已有func的endpoint不带func地绑定新path - 单func多@route?
        if view_func is not None:
            old_func = self.view_functions.get(endpoint)
            if old_func is not None and old_func != view_func:
                raise AssertionError("此endpoint已有对应函数: %s" % endpoint)
            self.view_functions[endpoint] = view_func

    def route(self, path, **options):
        """
        类似于flask的同名函数 `route`
        add_url_rule的装饰器版本,作用一致
        """
        def wrapper(func):
            endpoint = options.pop("endpoint", None)
            self.add_url_rule(path, endpoint, func, **options)
            return func

        return wrapper

    def dispatch_request(self):
        """
        接受 'wsgi_app'的调用,通过请求上下文得到对应endpoint与函数参数args
        再以endpoint作为键值得到处理该url的视图函数,传入args,返回函数结果
        """
        if request.routing_exception is not None:
            return self.handle_user_exception(request.routing_exception)
        # 'url_adapter.match' 时可能产生的路由错误

        try:
            endpoint, args = request.rule.endpoint, request.view_args
            rv = self.view_functions[endpoint](**args)
        except Exception as e:
            rv = self.handle_user_exception(e)
        return rv

    def register_blueprint(self, blueprint):
        """
        接收blueprint实例,通过其register方法实现注册
        需保证注册的blueprint名都唯一
        """
        bp_name = blueprint.name
        if bp_name in self.blueprints:
            assert blueprint is self.blueprints[bp_name], f"""
                已存在名为 {bp_name} 的blueprints:{self.blueprints[bp_name]},
                请确保正在创建的 {blueprint} 名称唯一
            """
        else:
            self.blueprints[bp_name] = blueprint
        blueprint.register(self)

    @staticmethod
    def _get_exc_class_and_code(exc_class_or_code):
        """
        根据 status code 或 exception class 自动补出另一个
        若default_exceptions无code对应的类则报错
        """
        if isinstance(exc_class_or_code, int):
            try:
                exc_class = default_exceptions[exc_class_or_code]
            except KeyError:
                raise KeyError(f"""
                    {exc_class_or_code} 并非标准的HTTP错误码,
                    请用HTTPException构造自定义的HTTP错误
                """)
        else:
            exc_class = exc_class_or_code

        assert issubclass(exc_class, Exception)

        if issubclass(exc_class, HTTPException) and exc_class.code:
            return exc_class, exc_class.code
        else:
            return exc_class, None

    def _find_error_handler(self, e):
        """
        按照code优先、field次要的顺序为寻找异常处理函数:
        1.蓝图 with code,2.全局 with code
        3.蓝图 without code,4.全局 without code
        若没有匹配的处理函数则返回None
        """
        exc_class, code = self._get_exc_class_and_code(type(e))

        for field, c in (
            (request.blueprint, code),
            (None, code),
            (request.blueprint, None),
            (None, None),
        ):
            if request.blueprint in self.api_set and not field:
                continue
            # .restful.Api 仅使用自身设置的错误处理器

            handler_map = self.error_handlers.setdefault(field, {}).get(c)

            if not handler_map:
                continue

            for cls in exc_class.__mro__:  # 以该异常类的继承顺序尝试获取自身及父类的handler
                handler = handler_map.get(cls)
                if handler is not None:
                    return handler

    def register_error_handler(self, code_or_exception, func, field=None):
        """
        通过status code 或 Exception class注册一个错误处理函数func
        func被调用时接受该异常实例作为参数
        其中field为None时作用于全局(app);为str时是蓝图名,仅作用于该蓝图(blueprint)
        """
        if isinstance(code_or_exception, Exception):
            raise ValueError(f"""
                不可注册异常实例: {repr(code_or_exception)},
                只能是异常类或HTTP错误码
            """)

        exc_class, code = self._get_exc_class_and_code(code_or_exception)

        handlers = self.error_handlers.setdefault(field,
                                                  {}).setdefault(code, {})
        handlers[exc_class] = func

    def error_handler(self, code_or_exception):
        """
        register_error_handler的全局装饰器版本
        """
        def wrapper(func):
            self.register_error_handler(code_or_exception, func)
            return func

        return wrapper

    def handle_user_exception(self, e):
        """
        处理所有注册过的异常,如果未注册则再抛出
        而HTTPException及其子类实例可直接作为响应返回
        """
        handler = self._find_error_handler(e)
        if handler is not None:
            return handler(e)
        raise e

    def handle_exception(self, e):
        """
        处理无对应处理函数或处理函数中再次发生的异常
        非HTTPException将统一返回 500 ``InternalServerError`` 响应
        """
        if isinstance(e, HTTPException):
            return e

        server_error = InternalServerError()
        server_error.original_exception = e

        handler = self._find_error_handler(e) or self._find_error_handler(
            server_error)
        if handler is not None:
            server_error = handler(server_error)
        else:
            print_exception(*exc_info())

        return make_response(server_error)
Esempio n. 57
0
#######################

from os import path
from urlparse import urlparse
from werkzeug.wrappers import Response
from jinja2 import Environment, FileSystemLoader

ALLOWED_SCHEMES = frozenset(['http', 'https', 'ftp', 'ftps'])
TEMPLATE_PATH = path.join(path.dirname(__file__), 'templates')
jinja_env = Environment(loader=FileSystemLoader(TEMPLATE_PATH))
jinja_env.globals['url_for'] = url_for

def render_template(template, **context):
    return Response(jinja_env.get_template(template).render(**context),
                    mimetype='text/html')

def validate_url(url):
    return urlparse(url)[0] in ALLOWED_SCHEMES

##############################################
######utils for shared data middleware
##############################################

from os import path
from werkzeug.wsgi import SharedDataMiddleware

STATIC_PATH = path.join(path.dirname(__file__), 'static')
url_map.add(Rule('/static/<file>', endpoint='static', build_only=True))

##########################
Esempio n. 58
0
class WsgiApplication(HttpBase):
    '''A `PEP-3333 <http://www.python.org/dev/peps/pep-3333>`_
    compliant callable class.

    Supported events:
        * ``wsdl``
            Called right before the wsdl data is returned to the client.

        * ``wsdl_exception``
            Called right after an exception is thrown during wsdl generation.
            The exception object is stored in ctx.transport.wsdl_error attribute.

        * ``wsgi_call``
            Called first when the incoming http request is identified as a rpc
            request.

        * ``wsgi_return``
            Called right before the output stream is returned to the WSGI handler.

        * ``wsgi_error``
            Called right before returning the exception to the client.

        * ``wsgi_close``
            Called after the whole data has been returned to the client. It's
            called both from success and error cases.
    '''
    def __init__(self, app, chunked=True):
        HttpBase.__init__(self, app, chunked)

        self._allowed_http_verbs = app.in_protocol.allowed_http_verbs
        self._verb_handlers = {
            "GET": self.handle_rpc,
            "POST": self.handle_rpc,
        }
        self._mtx_build_interface_document = threading.Lock()
        self._wsdl = None

        # Initialize HTTP Patterns
        self._http_patterns = None
        self._map_adapter = None
        self._mtx_build_map_adapter = threading.Lock()

        for k, v in self.app.interface.service_method_map.items():
            p_service_class, p_method_descriptor = v[0]
            for p in p_method_descriptor.patterns:
                if isinstance(p, HttpPattern):
                    r = p.as_werkzeug_rule()

                    # We do this here because we don't want to import
                    # Werkzeug until the last moment.
                    if self._http_patterns is None:
                        from werkzeug.routing import Map
                        self._http_patterns = Map()

                    self._http_patterns.add(r)

    @property
    def has_patterns(self):
        return self._http_patterns is not None

    def __call__(self, req_env, start_response, wsgi_url=None):
        '''This method conforms to the WSGI spec for callable wsgi applications
        (PEP 333). It looks in environ['wsgi.input'] for a fully formed rpc
        message envelope, will deserialize the request parameters and call the
        method on the object returned by the get_handler() method.
        '''

        url = wsgi_url
        verb = req_env['REQUEST_METHOD'].upper()
        if url is None:
            url = reconstruct_url(req_env).split('.wsdl')[0]

        if self.__is_wsdl_request(req_env):
            return self.__handle_wsdl_request(req_env, start_response, url)

        elif not (self._allowed_http_verbs is None or verb
                  in self._allowed_http_verbs or verb in self._verb_handlers):
            start_response(HTTP_405, [
                ('Content-Type', ''),
                ('Allow', ', '.join(self._allowed_http_verbs)),
            ])
            return [HTTP_405]

        else:
            return self._verb_handlers[verb](req_env, start_response)

    def __is_wsdl_request(self, req_env):
        # Get the wsdl for the service. Assume path_info matches pattern:
        # /stuff/stuff/stuff/serviceName.wsdl or
        # /stuff/stuff/stuff/serviceName/?wsdl

        return (req_env['REQUEST_METHOD'].upper() == 'GET'
                and (req_env['QUERY_STRING'].lower() == 'wsdl'
                     or req_env['PATH_INFO'].endswith('.wsdl')))

    def __handle_wsdl_request(self, req_env, start_response, url):
        ctx = WsgiMethodContext(self, req_env, 'text/xml; charset=utf-8')

        if self.doc.wsdl11 is None:
            start_response(HTTP_404,
                           _gen_http_headers(ctx.transport.resp_headers))
            return [HTTP_404]

        ctx.transport.wsdl = self._wsdl

        if ctx.transport.wsdl is None:
            try:
                self._mtx_build_interface_document.acquire()

                ctx.transport.wsdl = self._wsdl

                if ctx.transport.wsdl is None:
                    self.doc.wsdl11.build_interface_document(url)
                    ctx.transport.wsdl = self._wsdl = \
                                        self.doc.wsdl11.get_interface_document()

            except Exception, e:
                logger.exception(e)
                ctx.transport.wsdl_error = e

                # implementation hook
                self.event_manager.fire_event('wsdl_exception', ctx)

                start_response(HTTP_500,
                               _gen_http_headers(ctx.transport.resp_headers))

                return [HTTP_500]

            finally:
Esempio n. 59
0
class Builder(object):
    default_ignores = ('.*', '_*', 'config.yml', 'Makefile', 'README')
    default_programs = {
        '*.rst':    'rst'
    }
    default_template_path = '_templates'
    default_static_folder = 'static'

    def __init__(self, project_folder, config):
        self.project_folder = os.path.abspath(project_folder)
        self.config = config
        self.programs = builtin_programs.copy()
        self.modules = []
        self.storage = {}
        self.url_map = Map()
        self.url_adapter = self.url_map.bind('dummy.invalid')
        self.register_url('page', '/<path:slug>')

        template_path = os.path.join(self.project_folder,
            self.config.root_get('template_path') or
                self.default_template_path)
        self.locale = Locale(self.config.root_get('locale') or 'en')
        self.jinja_env = Environment(
            loader=FileSystemLoader([template_path, builtin_templates]),
            autoescape=self.config.root_get('template_autoescape', True),
            extensions=['jinja2.ext.autoescape', 'jinja2.ext.with_'],
        )
        self.jinja_env.globals.update(
            link_to=self.link_to,
            format_datetime=self.format_datetime,
            format_date=self.format_date,
            format_time=self.format_time
        )

        self.static_folder = self.config.root_get('static_folder') or \
                             self.default_static_folder

        for module in self.config.root_get('active_modules') or []:
            mod = find_module(module)
            mod.setup(self)
            self.modules.append(mod)

    @property
    def default_output_folder(self):
        return os.path.join(self.project_folder,
                            self.config.root_get('output_folder')
                            or OUTPUT_FOLDER)

    def link_to(self, _key, **values):
        return self.url_adapter.build(_key, values)

    def get_link_filename(self, _key, **values):
        link = url_unquote(self.link_to(_key, **values).lstrip('/')).encode('utf-8')
        if not link or link.endswith('/'):
            link += 'index.html'
        return os.path.join(self.default_output_folder, link)

    def open_link_file(self, _key, mode='w', **values):
        filename = self.get_link_filename(_key, **values)
        folder = os.path.dirname(filename)
        if not os.path.isdir(folder):
            os.makedirs(folder)
        return open(filename, mode)

    def register_url(self, key, rule=None, config_key=None,
                     config_default=None, **extra):
        if config_key is not None:
            rule = self.config.root_get(config_key, config_default)
        self.url_map.add(Rule(rule, endpoint=key, **extra))

    def get_full_static_filename(self, filename):
        return os.path.join(self.default_output_folder,
                            self.static_folder, filename)

    def get_static_url(self, filename):
        return '/' + posixpath.join(self.static_folder, filename)

    def open_static_file(self, filename, mode='w'):
        full_filename = self.get_full_static_filename(filename)
        folder = os.path.dirname(full_filename)
        if not os.path.isdir(folder):
            os.makedirs(folder)
        return open(full_filename, mode)

    def get_storage(self, module):
        return self.storage.setdefault(module, {})

    def filter_files(self, files, config):
        patterns = config.merged_get('ignore_files')
        if patterns is None:
            patterns = self.default_ignores

        result = []
        for filename in files:
            for pattern in patterns:
                if fnmatch(filename, pattern):
                    break
            else:
                result.append(filename)
        return result

    def guess_program(self, config, filename):
        mapping = config.list_entries('programs') or self.default_programs
        for pattern, program_name in mapping.iteritems():
            if fnmatch(filename, pattern):
                return program_name
        return 'copy'

    def render_template(self, template_name, context=None):
        if context is None:
            context = {}
        context['builder'] = self
        context.setdefault('config', self.config)
        tmpl = self.jinja_env.get_template(template_name)
        before_template_rendered.send(tmpl, context=context)
        return tmpl.render(context)

    def format_datetime(self, datetime=None, format='medium'):
        return dates.format_datetime(datetime, format, locale=self.locale)

    def format_time(self, time=None, format='medium'):
        return dates.format_time(time, format, locale=self.locale)

    def format_date(self, date=None, format='medium'):
        return dates.format_date(date, format, locale=self.locale)

    def iter_contexts(self, prepare=True):
        last_config = self.config
        cutoff = len(self.project_folder) + 1
        for dirpath, dirnames, filenames in os.walk(self.project_folder):
            local_config = last_config
            local_config_filename = os.path.join(dirpath, 'config.yml')
            if os.path.isfile(local_config_filename):
                with open(local_config_filename) as f:
                    local_config = last_config.add_from_file(f)

            dirnames[:] = self.filter_files(dirnames, local_config)
            filenames = self.filter_files(filenames, local_config)

            for filename in filenames:
                yield Context(self, local_config, os.path.join(
                    dirpath[cutoff:], filename), prepare)

    def anything_needs_build(self):
        for context in self.iter_contexts(prepare=False):
            if context.needs_build:
                return True
        return False

    def run(self):
        self.storage.clear()
        contexts = list(self.iter_contexts())

        for context in contexts:
            if context.needs_build:
                key = context.is_new and 'A' or 'U'
                context.run()
                print key, context.source_filename

        before_build_finished.send(self)

    def debug_serve(self, host='127.0.0.1', port=5000):
        from rstblog.server import Server
        print 'Serving on http://%s:%d/' % (host, port)
        try:
            Server(host, port, self).serve_forever()
        except KeyboardInterrupt:
            pass
Esempio n. 60
0
class Muxi(object):
    """
	The :class~Muxi: obj implements a WSGI application and
	automatically config the app & register view functions through
	the name of the module or package passed.
	:ex:

		from muxi import Muxi
		app = Muxi(__name__)
		app.secret_key = "I love muxi"

	and app is ~WSGI~application
	"""

    # the class for request obj
    request_class = MuxiRequest

    # the class for response obj
    response_class = MuxiResponse

    # static path
    # path for the static file
    # if we don't want use static files
    # we can set this to None !
    static_path = '/static'

    # if secret_key is set, we can use this to
    # sign cookies or some auth info
    # follow this you can simple have a secret_key
    #	..--> import os
    #	..--> os.urandom(24)
    #	'YkB\xe4\x11\xef\xa0\xe4\x9e\x8cZ\xb2}^>T\x12a\x96\x90\xcc\xfd;b'
    # and it is better to set secret_key into environment variable
    secret_key = None

    session_cookie_name = 'session'

    # options that are passed directly to the jinja environment
    jinja_options = dict(
        autoescape=True,
        extensions=['jinja2.ext.autoescape', 'jinja2.ext.with_'])

    def __init__(self, package_name):
        # app = Muxi(__name__)
        self.debug = False
        self.package_name = package_name
        # endpoint => views func -> dict
        self.view_functions = {}
        # error => error handle func -> dict
        self.error_handlers = {}
        # the func run before request
        self.request_init_funcs = []
        self.request_shutdown_functions = []
        self.url_map = Map()
        self.secret_key = "I love muxi"

        if self.static_path is not None:
            # auto add ~endpoint:static~
            self.url_map.add(
                Rule(self.static_path + "/<filename>",
                     build_only=True,
                     endpoint='static'))

        self.jinja_env = Environment(loader=self.create_jinja_loader(),
                                     **self.jinja_options)

        self.jinja_env.globals.update(
            # jinja_env globals
            gen_url=gen_url,
            request=request,
            # so we can use session in jinja
            session=session,
            g=g,
            get_show_msg=get_show_msg)

    def create_jinja_loader(self):
        """create jinja loader,which can auto find templates floder"""
        return PackageLoader(self.package_name)

    def run(self, host="localhost", port=3044, **options):
        """run muxi application~:root URL:~http://muxihost:304"""
        from werkzeug import run_simple
        if 'debug' in options:
            self.debug = options.pop('debug')
        if self.static_path is not None:
            options['static_files'] = {
                self.static_path: (self.package_name, 'static')
            }
            options.setdefault('use_reloader', self.debug)
        options.setdefault('use_debugger', self.debug)
        return run_simple(host, port, self, **options)

    def open_resource(self, resource):
        """
		open a resource from app's resource floder
		return a readable file-like object for specified resource
		"""
        return pkg_resources.resource_stream(self.package_name, resource)

    def open_session(self, resource):
        """creates or opens a new session"""
        key = self.secret_key
        if key is not None:
            return SecureCookie.load_cookie(
                # here, we should use global request
                # which is a context-local object
                # request,  # so this request is the global request...
                Request(environ),
                # request,
                self.session_cookie_name,
                secret_key=key)

    def save_session(self, session, response):
        """Saves the session if it needs updates."""
        if session is not None:
            session.save_cookie(response, self.session_cookie_name)

    def request_init(self, f):
        """registers a function to run before each request"""
        self.request_init_funcs.append(f)
        return f

    def preprocess_request(self):
        """make sure return value is not None"""
        for func in self.request_init_funcs:
            rv = func()
            if rv is not None:
                return rv

    def match_request(self):
        """
		match the current(active) URL according to the URL Map,
		and stores the endpoint and view arguments on the request obj,
		else:
			the exception is stored
		"""
        rv = _request_ctx_stack.top.url_adapter.match()
        request.endpoint, request.view_args = rv
        return rv

    def dispatch_request(self):
        """
		When a request happend, matchs the URL and
		returns the return value of view function
		dispatch the URL to the viewfunction and
		also pass http code and info
		"""
        try:
            endpoint, values = self.match_request()
            return self.view_functions[endpoint](**values)
        except HTTPException, e:
            # still not handle error
            return e
        except Exception, e:
            return e