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
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
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
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) )
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)
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
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
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]
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)
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
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 )
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)
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
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))
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
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
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)
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
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:
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
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)
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)
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
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
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
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)
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()
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()
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)
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)
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)
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()
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
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)
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)
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)
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)
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)
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
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
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)
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)
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'], )
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)
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)
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)
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()
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)
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)
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)
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)
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)
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)
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)
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)
####################### 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)) ##########################
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:
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
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