def __compile_regex(self, url): try: log.debug("Compile url to regex.") self.num_slashes = [] slash = 0 params = [] current_param = "" param_reading = False self.regex_str = "^" for char in url: if char is "{": self.num_slashes.append(slash) param_reading = True elif char is "}": param_reading = False params.append(current_param) self.regex_str += "\w+" current_param = "" elif param_reading: current_param += char else: self.regex_str += char slash = slash + 1 if char is "/" else slash self.regex_str += "$" return ure.compile(self.regex_str), params except: raise CompileRegexException( "Error with compiling regex in route class!")
def __parse_data(self, data): log.info("Parsing FormData.") rows = data.split("\r\n") read_values = False current_value = "" current_is_file = False filename = None current_key = None for row in rows: if not (row[0:2] == "--"): if not read_values: if row != "": param_header = row.split(";") current_is_file = len(param_header) == 3 filename = param_header[2] if current_is_file else None current_key = param_header[1].split('"')[1] read_values = True else: current_value += row elif current_key is not None: if current_is_file: current_value = self.__create_file(filename, current_value) log.debug("Set form data attribute with key: {k}.".format( k=current_key)) setattr(self, current_key, current_value) read_values = False current_key = None current_value = "" filename = None
async def parse_header(self, data): """ Accept HTTP request header in raw and parse them and store to attribute. :param data: Incoming header row. :return: Boolean if we can continue read headers. """ try: if not self.has_path: self.method, full_path, proto = data.split() log.info("Incoming HTTP request {m} {p}.".format(m=self.method, p=full_path)) await self.__find_query_params(full_path) self.has_path = True else: header, value = data.split(": ") self.headers[header] = value.replace("\r\n", "") log.debug("Header\n {k}: {v}".format(k=header, v=self.headers[header])) if "Content-Length" == header: self.headers["Content-Type"] = self.headers["Content-Type"].split(";")[0] self.content_len = int(value) self.content_read = self.content_len > 0 return False return True except HeaderException: log.error("Error during read of request headers!")
async def __handle(self, reader, writer): req = Request() h_read = True while h_read: h_line = await reader.readline() if h_line == b"\r\n": log.debug("All headers was accepted.") break if len(h_line) > 0: h_read = await req.parse_header(h_line.decode()) if req.content_read: content = await reader.read(-1) await req.parse_content(content.decode()) res = await self.miniweb.handle_response(req, Response()) log.debug("Response arrived back to server.py") if res is not None and res.can_send: await self.__send_headers(res, writer) await self.__send_data(res, writer) await self.__close_connection(writer) else: log.warning( "End communication with client - will drop on timeout!") if not self.keep_run: await self.__stop()
def build(self): """ Send response to client and mark it as finished. :return: self """ log.debug("Response was marked as builded.") self.can_send = True return self
def is_match(self, path): """ Accept current path from HTTP request and looking for match with current route. :param path: Incoming path from HTTP request. :return: Boolean of match """ log.debug("Looking for route match.\n Path: {p}\n Regex: {r}".format( p=path, r=self.regex_str)) return ure.match(self.regex, path) is not None
def __init__(self, path, params=None): if path is None: path = "/default/" log.debug("Creating new controller with path: {p}.".format(p=path)) self.path = path self.params = params self.filters = []
def _filter(fc): if controller is None: log.debug("Adding new global middleware function.") global_filter.append(fc) else: log.debug( "Adding new controller middleware function for controller with path: {p}" .format(p=controller.path)) controller.add_filter(fc) return fc
def get_mime_by_suffix(destination_file): """ Find mime type by incoming file suffix. :param suff: File suffix. :return: mime type """ suff = destination_file.split('.')[1] log.debug("Incoming suffix to recognize Mime: {s}.".format(s=suff)) return suffix_file[suff]
def type(self, mime): """ Set MIME type to Response. :param mime: Response data type. :return: self """ log.debug("Response type was set to {m}.".format(m=mime)) self.mime = mime return self
def status(self, status): """ Set HTTP status to Response. :param status: HTTP status code. :return: self """ log.debug("Response status was set to {s}.".format(s=status)) self.stat = status return self
async def parse_content(self, data): """ Accept Content-Data and parse them to object. Object is stored to Request class. :param data: Incoming Content-Data :return: None """ log.debug("Content data: \r\n"+data) try: self.content = get_content(data, self.headers["Content-Type"]) except ContentTypeException: log.error("Error in parsing Content-Type!")
def is_match(self, path): try: destination_file = path.replace(self.file_path, self.root, 1) log.info("Looking for static file.") log.debug("File destination path should be {df}.".format( df=destination_file)) self.file = open(destination_file) self.mime = get_mime_by_suffix(destination_file) return True except: log.debug("Match with static route was not found.") return False
async def __find_query_params(self, full_path): if "?" in full_path: log.debug("Parsing query params.") self.params = {} self.path, q_par_str = full_path.split("?", 1) q_par_arr = q_par_str.split("&") for par in q_par_arr: key, value = par.split("=") self.params[key] = value log.debug("Query param\n {k}: {v}".format(k=key, v=value)) else: self.path = full_path
def validate_consumes(mime, req, res): """ Validate incoming Content-Type. :param mime: Mime type which route can accept. :param req: HTTP request wrapped inside of Request class. :param res: Response class which will define HTTP response. :return: Boolean of success/fail. """ log.info("Validate consumes.") if (mime is None) or (req.headers["Content-Type"] in mime): log.debug("Consumes middleware was suceed.") return True else: log.warning("Consumes middleware failed!") res.type("text/html").entity(consume_error( req.headers["Content-Type"])).status(400).build() return False
def validate(controller, req, res): """ Validate incoming HTTP request - run middleware function. :param controller: Group router to group and define path. (optional parameter) :param req: HTTP request wrapped inside of Request class. :param res: Response class which will define HTTP response. :return: Boolean of success/fail. """ log.info("Validate middlewares.") if not check_filters_group(global_filter, req, res): return False if controller is not None: if not check_filters_group(controller.filters, req, res): return False log.debug("Middleware function was suceed.") return True
def get_path_params(self, path): """ Accept current path from HTTP request and try found path parameters. :param path: Incoming path from HTTP request. :return: path parameters in dictionary """ params = None if len(self.param_keys) > 0: log.debug("Getting path variables from path.") params = {} split_path = path.split("/") slash_arr_size = len(self.num_slashes) for i in range(0, slash_arr_size): params[self.param_keys[i]] = split_path[self.num_slashes[i]] return params
async def __send_data(self, res, writer): log.debug("Sending response data") d_type = res.ent.__class__.__name__ if d_type == "TextIOWrapper": log.debug("Sending file/s.") b_arr = bytearray(self.config.buffer) while True: data = res.ent.readinto(b_arr) if not data: break await writer.awrite(b_arr, 0, data) elif d_type == "dict": log.debug("Dumps dictionary to JSON string and sending to client.") json_str = ujson.dumps(res.ent) await writer.awrite(json_str) else: await writer.awrite(res.ent)
async def __close_connection(self, writer): log.debug("Closing communication with client.") await writer.aclose()
async def __send_headers(self, res, writer): log.debug("Sending response headers.") await writer.awrite("HTTP/1.1 " + str(res.stat) + "\r\n") await writer.awrite("Content-Type: " + res.mime + "\r\n\r\n")
async def __stop(self): log.debug( "Miniweb server will be stopped in {t}ms.".format(t=self.delay)) await asyncio.sleep_ms(self.delay) self.e_loop.stop() log.info("Miniweb has been stopped.")