📜  在Python中创建代理Web服务器 2

📅  最后修改于: 2020-05-13 05:24:53             🧑  作者: Mango

先决条件:在Python中创建代理Web服务器–1
在本教程中,添加了一些有趣的功能以使其更有用。

  • 添加域黑名单。对于前 google.com,facebook.com。在我们的配置字典中创建BLACKLIST_DOMAINS的列表。现在,只需忽略/删除收到的列入黑名单的域的请求即可。(理想情况下,我们必须以禁止的response来回应。)
    # 检查host:port是否被列入黑名单
    for i in range(0, len(config['BLACKLIST_DOMAINS'])):
        if config['BLACKLIST_DOMAINS'][i] in url:
            conn.close()
    return
  • 要添加主机阻止:您可能需要允许来自特定子网的连接或特定人的连接。要添加此内容,请创建所有允许的主机的列表。由于主机也可以是子网,因此添加正则表达式以匹配IP地址,尤其是IPV4地址。“ IPv4地址以十进制表示法规范地表示,它由四个十进制数字组成,每个数字范围为0到255,由点分隔,例如172.16.254.1.。每个部分代表一组8位(八位字节)的地址。”

  • 使用正则表达式匹配正确的IP地址:
    • 在Server类中创建一个新方法_ishostAllowed,并使用fnmatch模块来匹配正则表达式。遍历所有正则表达式,如果匹配任何正则表达式则允许请求。如果没有发现客户地址是任何正则表达式的一部分,则发送一个FORBIDDEN响应。

注意:在接下来的教程中,我们将创建完整的自定义Web服务器,在那里将创建createResponse函数来处理通用响应的创建。

def _ishostAllowed(self, host):
    """ 检查是否允许主机访问内容 """
    for wildcard in config['HOST_ALLOWED']:
        if fnmatch.fnmatch(host, wildcard):
            return True
    return False

默认主机匹配正则表达式为“ *”,以匹配所有主机。但是,也可以使用形式为“ 192.168.*”的正则表达式。服务器当前正在处理请求,但不显示任何消息,因此我们不知道服务器的状态。为此,请使用日志记录模块,因为它是线程安全的。(如果您还记得服务器是多线程的)

导入模块并设置其初始配置。

logging.basicConfig(level = logging.DEBUG,
format = '[%(CurrentTime)-10s] (%(ThreadName)-10s) %(message)s',)
  • 创建一个单独的方法来记录每条消息:将其作为参数传递,并带有其他数据(例如线程名和当前时间)来跟踪日志。还创建一个使日志着色的函数,以便在STDOUT上看起来漂亮。
    为此,在配置中添加一个布尔值COLORED_LOGGING并创建一个新函数,该函数根据LOG_LEVEL为传递给它的每个消息着色。
    def log(self, log_level, client, msg):
        """ 将消息记录到适当的位置 """
        LoggerDict = {
           'CurrentTime' : strftime("%a, %d %b %Y %X", localtime()),
           'ThreadName' : threading.currentThread().getName()
        }
        if client == -1: # Main Thread
            formatedMSG = msg
        else: # 子线程或请求线程
            formatedMSG = '{0}:{1} {2}'.format(client[0], client[1], msg)
        logging.debug('%s', utils.colorizeLog(config['COLORED_LOGGING'],
        log_level, formatedMSG), extra=LoggerDict)
  • 创建一个新模块ColorizePython.py: 它包含一个pycolors类,该类维护一个颜色代码列表。将其分成另一个模块,以使代码模块化并遵循PEP8标准。
    # ColorizePython.py
    class pycolors:
    HEADER = '\033[95m'
    OKBLUE = '\033[94m'
    OKGREEN = '\033[92m'
    WARNING = '\033[93m'
    FAIL = '\033[91m'
    ENDC = '\033[0m' # End color
    BOLD = '\033[1m'
    UNDERLINE = '\033[4m'

    模组:

    import ColorizePython

    方法:

    def colorizeLog(shouldColorize, log_level, msg):
        # log()参数中的log_level越高,其优先级越低。
        colorize_log = {
        "NORMAL": ColorizePython.pycolors.ENDC,
        "WARNING": ColorizePython.pycolors.WARNING,
        "SUCCESS": ColorizePython.pycolors.OKGREEN,
        "FAIL": ColorizePython.pycolors.FAIL,
        "RESET": ColorizePython.pycolors.ENDC
        }
        if shouldColorize.lower() == "true":
            if log_level in colorize_log:
                return colorize_log[str(log_level)] + msg + colorize_log['RESET']
            return colorize_log["NORMAL"] + msg + colorize_log["RESET"]
        return msg
    • 由于colorizeLog不是服务器类的函数,因此将其创建为名为utils.py的单独模块。在需要的地方添加适当的日志消息,尤其是在服务器状态更改时。
    • 在退出应用程序之前,修改服务器中的关闭方法以退出所有正在运行的线程。threading.enumerate()遍历所有正在运行的线程,因此我们不需要维护它们的列表。当我们尝试结束main_thread时,线程模块的行为是意外的。官方文档还指出:

                  如果试图加入当前线程,join()会引发RuntimeError,因为这将导致死锁。在线程启动之前,使用join()线程也是错误的,尝试这样做会引发相同的异常。”
    因此,适当跳过它,这是相同的代码。

    def shutdown(self, signum, frame):
        """ 处理现有服务器,清除所有痕迹 """
        self.log("WARNING", -1, 'Shutting down gracefully...')
        main_thread = threading.currentThread() # Wait for all clients to exit
        for t in threading.enumerate():
            if t is main_thread:
                continue
                self.log("FAIL", -1, 'joining ' + t.getName())
            t.join()
            self.serverSocket.close()
        sys.exit(0)