def read_config(config_path): default_config = { "address": '0.0.0.0', "queue": 8, "port": 80, "datasize": 1024, "cpu_limit": psutil.cpu_count(), "document_root": "/server/" } file_existed = os.path.exists(config_path) if not file_existed: raise ValueError(f"Missed config on {config_path}") config_raw, error = open_io(config_path) if error: raise ValueError(f"Read file error: {config_path}") config_values = config_raw.decode("utf-8").split('\n') for values in config_values: if values: key, value = re.search(r'\S* \S*', values).group(0).split(' ') default_config.update({key: value}) default_config["binding"] = (default_config["address"], default_config["port"]) default_config["cpu_limit"] = int(default_config["cpu_limit"]) logger.info(f"Config: {default_config}") return default_config
def server_init(self): self.socket_initialize() for worker in range(self.cpu_number): pid = os.fork() if pid > 0: self.forking_workers.append(pid) elif pid == 0: logger.info(f'Create new worker with pid: {os.getpid()}') while True: client_socket, client_address = self.socket.accept() raw_request = client_socket.recv(self.size) if raw_request.strip() == 0: client_socket.close() continue request = http_handlers.request_handler(raw_request) if not request.validated: response = self.answers["FORBIDDEN"] else: response = self.handle_request(request) client_socket.send(response) client_socket.close() else: logger.info("Cant fork anymore") self.socket.close() for worker_pid in self.forking_workers: os.waitpid(worker_pid, 0)
def socket_initialize(self): server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server_socket.bind(self.binding) server_socket.listen(self.queue) self.socket = server_socket logger.info(f"""Binding on {self.binding} with process count {self.cpu_number} with filepath {self.root}""")
def file_handler(self, filepath): content_type = None content_length = 0 data, error = open_io(filepath) if error: logger.info(f"Error in reading file with filename:{filepath}") # Raise some exception? else: content_length = len(data) content_type = self.content_type(filepath) return data, content_length, content_type
def request_handler(input_request): method, path = None, None logger.info(f"Input raw request {input_request} \n") request_body = input_request.decode('utf-8') request_by_string = request_body.split(" ") validate = True if len(request_by_string) >= 3 else False # Minimum length ( head example ) if validate: method = request_by_string[0] path = unquote(request_by_string[1].split('?')[0]) processing_request = Request(input_request.decode('utf-8'), method, validate, path) return processing_request
def handle_request(self, request): filepath = os.path.normpath(self.root + request.path) logger.info(f"Filepath {filepath}") # Prefix check if os.path.commonprefix([self.root, filepath]) != self.root: logger.info("Status: 403") return self.answers["FORBIDDEN"] if request.method not in ("GET", "HEAD"): logger.info("Status: 405") return self.answers["NOT_ALLOWED"] elif not os.path.exists(filepath): logger.info("Status: 404") return self.answers["NOT_FOUND"] # Check dir path if os.path.isdir(filepath): filepath += '/index.html' if not os.path.exists(filepath): logger.info("Status: 403") return self.answers["FORBIDDEN"] if not os.path.isfile(filepath): logger.info("Status: 403") return self.answers["FORBIDDEN"] data, content_length, content_type = self.file_handler(filepath) if content_type is None: logger.info("Status: 404") return self.answers["NOT_FOUND"] if request.method == 'HEAD': logger.info("Status: 200") return http_handlers.response_handler(http_version=HTTP_VERSION, status_code=HTTPStatus.OK, content_len=content_length, content_type=content_type) logger.info("Status: 200") return http_handlers.response_handler(http_version=HTTP_VERSION, status_code=HTTPStatus.OK, content_len=content_length, content_type=content_type, data=data)