Source code for aj.api.http

import gevent
import logging
import re
import types
from jadi import interface


[docs]def url(pattern): """ Exposes the decorated method of your :class:`HttpPlugin` via HTTP. Will be deprecated in favor of new decorators ( @get, @post, ... ) :param pattern: URL regex (``^`` and ``$`` are implicit) :type pattern: str :rtype: function Named capture groups will be fed to function as ``**kwargs`` """ def decorator(f): f.url_pattern = re.compile(f'^{pattern}$') return f return decorator
[docs]def requests_decorator_generator(method): """ Factorization to generate request decorators like @get or @post. :param method: Request method decorator to generate, like get or post :type method: basestring :return: :rtype: """ def request_decorator(pattern): """ Exposes the decorated method of your :class:`HttpPlugin` via HTTP :param pattern: URL regex (``^`` and ``$`` are implicit) :type pattern: str :rtype: function Named capture groups will be fed to function as ``**kwargs`` """ def decorator(f): # Request method involved, like get or post f.method = method f.url_pattern = re.compile(f'^{pattern}$') return f return decorator return request_decorator
# Decorators like @get and @post are defined here http_methods = ['get', 'post', 'delete', 'head', 'put', 'patch'] webdav_methods = ['propfind', 'mkcol', 'options', 'proppatch', 'copy', 'move', 'lock', 'unlock'] for method in http_methods + webdav_methods: globals()[method] = requests_decorator_generator(method)
[docs]class BaseHttpHandler(): """ Base class for everything that can process HTTP requests """
[docs] def handle(self, http_context): """ Should create a HTTP response in the given ``http_context`` and return the plain output :param http_context: HTTP context :type http_context: :class:`aj.http.HttpContext` """
[docs]@interface class HttpMiddleware(BaseHttpHandler): def __init__(self, context): self.context = context
[docs] def handle(self, http_context): pass
[docs]@interface class HttpMasterMiddleware(BaseHttpHandler): def __init__(self, context): self.context = context
[docs] def handle(self, http_context): pass
[docs]@interface class HttpPlugin(): """ A base interface for HTTP request handling:: @component class HelloHttp(HttpPlugin): @get('/hello/(?P<name>.+)') def get_page(self, http_context, name=None): context.add_header('Content-Type', 'text/plain') context.respond_ok() return 'Hello, f"{name}"!' """ def __init__(self, context): self.context = context
[docs] def handle(self, http_context): """ Finds and executes the handler for given request context (handlers were methods decorated with :func:`url` and will be decorated with e.g. @get and @post in the future) :param http_context: HTTP context :type http_context: :class:`aj.http.HttpContext` :returns: reponse data """ def check_method(handle_function, http_context): """ Check if the requested method is supported by the function, e.g. avoid accept a get request in a post method :param handle_function: :type handle_function: :param http_context: :type http_context: :return: :rtype: """ # Right http method called if handle_function.method == http_context.method.lower(): return True # Allow head request on get targets if http_context.method.lower() == 'head' and handle_function.method == 'get': return True return False for name, handle_function in self.__class__.__dict__.items(): if hasattr(handle_function, 'url_pattern'): handle_function = getattr(self, name) match = handle_function.url_pattern.match(http_context.path) if match: # New decorators @get, @post, @delete ... used if hasattr(handle_function, 'method'): if check_method(handle_function, http_context): http_context.route_data = match.groupdict() data = handle_function(http_context, **http_context.route_data) if isinstance(data, str): data = data.encode('utf-8') if isinstance(data, types.GeneratorType): return data return [data] else: # Ensure compatibility with old @url decorator logging.warning(f'Backward @url compatibility for {handle_function.__name__}') http_context.route_data = match.groupdict() data = handle_function(http_context, **http_context.route_data) if isinstance(data, str): data = data.encode('utf-8') if isinstance(data, types.GeneratorType): return data return [data]
[docs]@interface class SocketEndpoint(): """ Base interface for Socket.IO endpoints. """ plugin = None """arbitrary plugin ID for socket message routing""" def __init__(self, context): self.context = context self.greenlets = []
[docs] def on_connect(self, message): """ Called on a successful client connection """
[docs] def on_disconnect(self, message): """ Called on a client disconnect """
[docs] def destroy(self): """ Destroys endpoint, killing the running greenlets """ for gl in self.greenlets: gl.kill(block=False)
[docs] def on_message(self, message, *args): """ Called when a socket message arrives to this endpoint """
[docs] def spawn(self, target, *args, **kwargs): """ Spawns a greenlet in this endpoint, which will be auto-killed when the client disconnects :param target: target function """ logging.debug( f'Spawning sub-Socket Greenlet (in a namespace): {target.__name__}' ) greenlet = gevent.spawn(target, *args, **kwargs) self.greenlets.append(greenlet) return greenlet
[docs] def send(self, data, plugin=None): """ Sends a message to the client.the :param data: message object :param plugin: routing ID (this endpoint's ID if not specified) :type plugin: str """ self.context.worker.send_to_upstream({ 'type': 'socket', 'message': { 'plugin': plugin or self.plugin, 'data': data, }, })

Comments

comments powered by Disqus