class Rederict(BaseSiteModule): def __init__(self, location=None, code=302, headers=None, methods=None, url_modifier=None): if not location and not url_modifier: raise ValueError('must be passed "location" or "url_modifier"') self._code = 302 self._headers = AutoCFG(headers or {}) self._url_modifier = url_modifier if not url_modifier: self._headers.update(location=location) for method in (methods or HTTP_METHODS): setattr(self, method.lower(), self._req_handler) async def _req_handler(self, req): headers = dict(self._headers) if self._url_modifier: headers.update(location=self._url_modifier(req)) return Response( code=self._code, headers=headers, )
def __init__(self, data=None, headers=None, code=200, cookies=None, http_version='HTTP/1.1'): if data is not None and not isinstance(data, (str, bytes)): raise TypeError('data must be str or bytes') if headers is not None and not isinstance(headers, dict): raise TypeError('headers must be dict') if not isinstance(code, int): raise TypeError('code must be int') # response data self._data = data or b'' # dict of headers: {'Content-Type': 'text/html'} self._headers = AutoCFG(headers or {}, key_modifier=lambda x: x.lower()) # HTTP status code: 200 self._code = code # dict of dicts for Set-Cookie header: # {'uid': {'value': '123', 'flags':['HttpOnly'], 'properties': {'Path': '/'}} self._cookies = AutoCFG() if cookies: for ck in cookies: self.add_cookie(**ck) self._http_version = http_version
async def parse_data(reader, **kwargs): """ take io stream and cfg and parse HTTP headers return dict of attributes/headers/url/args """ cfg = AutoCFG(PARSE_DATA_DEFAULTS).update_fields(kwargs) req = AutoCFG({ 'url': b'', 'args': {}, 'method': b'', 'http_version': b'', 'headers': AutoCFG(key_modifier=lambda x: x.lower()), 'data': b'', }) if reader.at_eof(): raise RuntimeError('socket was closed') if not (st := (await readln( reader, max_len=cfg.max_uri_length + 12, ignore_zeros=True, exception=AeonResponse('URI too long', code=414), )).strip()): raise AeonResponse('empty string', code=400, close_conn=True, silent=True)
def __init__(self, location=None, code=302, headers=None, methods=None, url_modifier=None): if not location and not url_modifier: raise ValueError('must be passed "location" or "url_modifier"') self._code = 302 self._headers = AutoCFG(headers or {}) self._url_modifier = url_modifier if not url_modifier: self._headers.update(location=location) for method in (methods or HTTP_METHODS): setattr(self, method.lower(), self._req_handler)
def __init__(self, addr, reader, writer, **kwargs): self.__start_time = time.time() self._initialized = False self.cfg = AutoCFG(self.defaults).update_fields(kwargs) self.logger = logger.new_channel(f'{addr[0]}:{addr[1]}', parent='aeon') self._addr = addr self._reader = reader self._writer = writer self._source_ip = self._addr[0] self.port = self._addr[1] self.keep_alive = True self._real_ip = self._addr self._url = None self._args = {} self._method = None self._http_version = None self._headers = {} self._data = b'' self._ssl = kwargs.get('ssl', False) self._send = False self._callback = { 'before_send': [], 'after_send': [], }
async def emulate_request( self, url: str, headers: Dict[str, str] = None, args: Dict[str, str] = None, data: Optional[bytes] = None, method: str = 'GET', http_version: str = 'HTTP/1.1', _run_ware: bool = False, ) -> Response: """ imulate incoming request usefull for testing """ request = Request( addr=('127.0.0.1', 0), reader=None, writer=None, **self._request_prop, ) request.init_from_dict( AutoCFG({ 'url': url, 'headers': headers or {}, 'args': args or {}, 'method': method, 'http_version': http_version, 'data': data or b'', })) return await self._handle_request(request, _run_ware=_run_ware)
async def _request(method, url, params=None, data=None, json=None, headers=None, *a, **b): url = urlparse(url) host, port = (url.netloc.split(':') if ':' in url.netloc else ( url.netloc, 80 if url.scheme == 'http' else 443, )) headers = AutoCFG(headers or {}).update_missing({ 'Connection': 'close', }) async with ClientSession( host=host, port=int(port), ssl=url.scheme == 'https', *a, **b, ) as session: b.pop('timeout', None) return await session._request( method=method, url=url.path or '/', params=params, data=data, json=json, headers=headers, *a, **b, )
def __init__(self, **kwargs): self.cfg = AutoCFG( {k: kwargs.get(k, v) for k, v in ABSTRACT_AEON_DEFAULTS.items()}) if self.cfg.loop is None: self.cfg.loop = asyncio.get_event_loop() self._contexts = {} if self.cfg.ssl is None and self.cfg.certs: for host in self.cfg.certs: context = ssl.create_default_context( purpose=ssl.Purpose.CLIENT_AUTH, cafile=self.cfg.ca_cert, ) context.load_cert_chain( certfile=self.cfg.certs[host]['certfile'], keyfile=self.cfg.certs[host]['keyfile'], password=self.cfg.certs[host].get('keypassword', None), ) self._contexts[host] = context self.cfg.ssl = ssl.create_default_context( purpose=ssl.Purpose.CLIENT_AUTH, cafile=self.cfg.ca_cert, ) self.cfg.ssl.set_servername_callback(self.servername_callback) self._task = None self._server = None self._logger = logger.new_channel( key='aeon', parent=logger.get_channel('base_logger'), **self.cfg.logger, ) self.running_tasks = []
def __init__(self, request, **kwargs): super().__init__() self.cfg = AutoCFG(STATIC_RESPONSE_DEFAULTS).update_fields(kwargs) self.content_mod = None self.vars = dict({ 'req': request, }, **(kwargs.get('vars') or {})) self.req = request self._data = '' self._cached = False
def add_cookie(self, name: str, value: str, *options, **kwoptions): """ name - cookie name value - value of cookie options - boolean properties like HttpOnly and Secure kwoptions - kev-value properties like Max-Age and Domain """ self._cookies[name] = { 'value': value, 'flags': set(options), 'properties': AutoCFG(kwoptions) }
class NameSpace: TYPE_LEAFE = 0 TYPE_SUBTREE = 1 def __init__(self, tree=None) -> None: self._keys = AutoCFG() if tree: self.create_tree(tree) def __contains__(self, key) -> bool: return key in self._keys def __getitem__(self, key) -> Tuple[str, str]: return self._keys[key]['value'], self._keys[key]['type'] def __setitem__(self, key: str, value) -> None: try: err = (not isinstance( value, (BaseSiteModule, WSHandler, NameSpace, Response, dict))) and ( not issubclass(value, WSHandler)) except TypeError: err = True if err: raise TypeError( 'value "{}" for key "{}" must be NameSpace, dict, BaseSiteModule, WSHandler or Response' .format( value, key, )) self._keys[key] = { 'value': value, 'type': (self.TYPE_SUBTREE if isinstance(value, (NameSpace, dict)) else self.TYPE_LEAFE) } def items(self): yield from self._keys.items() def find_best( self, name, args: Optional[Dict[str, Any]] = None ) -> Tuple[Optional[str], Optional[Dict[str, str]]]: args = args or {} if name in self._keys and self._keys[name]['type'] == self.TYPE_LEAFE: return self._keys[name]['value'], args if not (az := [(key, m) for key in self._keys if (m := re.match(key, name))]):
def __init__(self, target, **kwargs): if not (callable(target) or asyncio.iscoroutinefunction(target)): raise TypeError('target must be callable or async coroutine') self._cfg = AutoCFG(TASK_DEFAULTS).deep_update_fields(kwargs) self._target = target self._offset = (self._cfg.offset['hour'] * 60 + self._cfg.offset['min']) * 60 + self._cfg.offset['sec'] self._inter = (self._cfg.interval['hour'] * 60 + self._cfg. interval['min']) * 60 + self._cfg.interval['sec'] self.logger = logger.new_channel('planner_task_{}'.format( self._cfg.key), parent='planner') self._shedule = []
def __init__(self, req, **kwargs): self.req = req self.alive = True self.rfile, self.wfile = req.get_rw() self.args = AutoCFG(kwargs) self.handler_map = { self.OPCODE_TEXT: self.handle_incoming_msg, self.OPCODE_BINARY: self.handle_incoming_bin, self.OPCODE_PING: self.send_pong, self.OPCODE_PONG: lambda msg: msg, } self.keep_alive = True self.handshake_done = False self.valid_client = False
async def parse_response_data(reader, **kwargs): """ take io stream and cfg and parse HTTP headers return dict of attributes/headers/url/args """ cfg = AutoCFG(PARSE_RESPONSE_DEFAULTS).update_fields(kwargs) if reader.at_eof(): raise ValueError('socket was closed') if not (st := (await readln( reader, max_len=cfg.max_status_length, ignore_zeros=True, )).decode()) or not st.startswith('HTTP/'): raise ValueError('Invalid protocol')
def __init__(self, host, port, ssl=False, limit=None, loop=None, **kwargs): self._conn_args = { 'host': host, 'port': port, 'ssl': ssl, **{k: v for k, v in kwargs.items() if k not in {'timeout'}}, } if limit: self._conn_args['limit'] = limit if loop: self._conn_args['loop'] = loop self._rd = None self._wr = None self.cfg = AutoCFG(kwargs).update_missing(self.defaults) self._logger = None
def __init__(self, **kwargs) -> None: self.cfg = AutoCFG(CHANNEL_DEFAULTS).update_fields(kwargs) if self.cfg.key is not None: if any((re.match(f'--stdout=.*{self.cfg.key}.*', x) is not None for x in sys.argv)): self.cfg.stdout = True if any((re.match(f'--debug=.*{self.cfg.key}.*', x) is not None for x in sys.argv)): self.cfg.log_levels.add('debug') if any((re.match(f'--no-log=.*{self.cfg.key}.*', x) is not None for x in sys.argv)): self.cfg.autosave = False if self.cfg.autosave and self.cfg.log_file not in Locks: Locks[self.cfg.log_file] = Lock() self.parents = [] # type: List[Dict[str, Union[Channel, Iterable]]] self._logs = [] # type: List[Dict[str, Union[str, int]]] self._t = 0 self._logger = logging.Logger(self.cfg.key)
max_len=cfg.max_status_length, ignore_zeros=True, )).decode()) or not st.startswith('HTTP/'): raise ValueError('Invalid protocol') version, code, *_ = st.split() if version not in cfg.expected_http_version: raise ValueError('unsupported protocol version "{}"'.format(version)) if not code.isdigit(): raise ValueError('status code is not integer "{}"'.format(code)) if not (100 <= (code := int(code)) < 600): raise ValueError('Invalid status code') headers = AutoCFG(key_modifier=lambda x: x.lower()) for _ in range(cfg.max_header_count): if not (st := (await readln( reader, max_len=cfg.max_header_length)).decode().strip()): break key, *value = st.split(':') if not value: raise ValueError('Invalid headers format') value = unquote(':'.join(value).strip()) if (key := key.lower()) in headers: headers[key] = ', '.join([ headers[key], value, ], )
async def _request(self, method, url, params=None, data=None, json=None, headers=None, **kwargs): params = params or {} (headers := AutoCFG( headers or {}, key_modifier=lambda x: x.lower(), )).update_missing({ 'Host': self._conn_args['host'], 'User-Agent': 'AeonClient/1.0', # FIXME # Cannot read data as Content-Length not passed # 'Accept-Encoding': 'gzip', }) if not data and json: data = dumps(json) headers.update_missing({ 'Content-Type': 'application/json', }) elif not data and method in {'POST', 'PUT', 'DELETE'} and params: data = urlencode(params) params = {} if data: if isinstance(data, str): data = data.encode() headers.update_missing({ 'Content-Length': len(data), }) await self._logger.debug( 'making request: {method} {host}:{port}{url} ? {params}', method=method, host=self._conn_args['host'], port=self._conn_args['port'], url=url, params=params, ) self._wr.write(b'\r\n'.join([ '{method} {url}{params} HTTP/1.1'.format( method=method, url=url, params=(''.join([ '?', urlencode(params), ]) if params else ''), ).encode(), ] + ([ f'{k}: {quote_from_bytes(v) if isinstance(v, bytes) else quote(str(v))}' .encode() for k, v in headers.items() ] if headers else []) + ([ b'', data, ] if data else [ b'', b'', ]))) await self._wr.drain() return await asyncio.wait_for( self._read_answer(), timeout=kwargs.get('timeout') or self.cfg.timeout, )
def __init__(self, tree=None) -> None: self._keys = AutoCFG() if tree: self.create_tree(tree)
def __init__(self, **kwargs): self.cfg = AutoCFG(kwargs).update_missing(STATS_CGI_DEFAULTS)
class Response: """ basic class for response """ def __init__(self, data=None, headers=None, code=200, cookies=None, http_version='HTTP/1.1'): if data is not None and not isinstance(data, (str, bytes)): raise TypeError('data must be str or bytes') if headers is not None and not isinstance(headers, dict): raise TypeError('headers must be dict') if not isinstance(code, int): raise TypeError('code must be int') # response data self._data = data or b'' # dict of headers: {'Content-Type': 'text/html'} self._headers = AutoCFG(headers or {}, key_modifier=lambda x: x.lower()) # HTTP status code: 200 self._code = code # dict of dicts for Set-Cookie header: # {'uid': {'value': '123', 'flags':['HttpOnly'], 'properties': {'Path': '/'}} self._cookies = AutoCFG() if cookies: for ck in cookies: self.add_cookie(**ck) self._http_version = http_version async def _extra_prepare_data(self): return self.data async def _cache_n_zip(self, data): return data def __str__(self): return f'<Response: {self._code} {HTTP_CODE_MSG[self._code]}>' @property def code(self): return self._code @code.setter def code(self, code): if code not in HTTP_CODE_MSG: raise ValueError('Code must be in k2.utils.http.HTTP_CODE_MSG') self._code = code @property def http_version(self) -> str: return self._http_version @property def data(self): return self._data @property def cookies(self) -> AutoCFG: return self._cookies @property def headers(self) -> AutoCFG: return self._headers @data.setter def data(self, d): self._data = b'' if d is None else ( d.encode() if isinstance(d, str) else d) def add_headers(self, *args, **kwargs): if any(not isinstance(i, (dict, tuple)) for i in args): raise TypeError('HTTP-header must be tuple of dict') if any(not isinstance(kwargs[i], str) for i in kwargs): raise TypeError('HTTP-header value must be string') self._headers.update(*args, **kwargs) def add_cookie(self, name: str, value: str, *options, **kwoptions): """ name - cookie name value - value of cookie options - boolean properties like HttpOnly and Secure kwoptions - kev-value properties like Max-Age and Domain """ self._cookies[name] = { 'value': value, 'flags': set(options), 'properties': AutoCFG(kwoptions) } async def export(self) -> bytes: data = await self._cache_n_zip(data.encode() if isinstance( data := await self._extra_prepare_data(), str) else data) headers = self._headers.update_missing(STANDART_HEADERS) headers.update({'Content-Length': len(data)}) return b''.join([ '\r\n'.join([ f'{self.http_version} ' f'{204 if len(data) <= 0 and self.code in {200, 201} else self.code} ' f'{HTTP_CODE_MSG[self.code]}', *[ ''.join([key, ': ', str(value)]) for key, value in headers.items() if key and value ], *[ 'Set-Cookie: {}'.format('; '.join([ f'''{name}={quote(self._cookies[name]['value'])}''', *[ f'{key}={quote(str(val))}' for key, val in self._cookies[name]['properties'].items() ], *list(self._cookies[name]['flags']) ])) for name in self._cookies ], '\r\n', ]).encode(), data, ])