美文网首页
利用socket 搭建一个简单web 框架

利用socket 搭建一个简单web 框架

作者: 两点半的杂货铺 | 来源:发表于2018-07-18 18:21 被阅读109次

    [success] 尝试搭建框架

    我们所要做的工程目录划分
    1.一个db 文件夹用来存储数据
    2.一个models 文件夹用来操作数据
    3.一个static文件夹用来保存静态文件
    4.一个templates 用来保存html
    5.一个routes.py 用来做路由映射
    6.一个server.py 用来做启动文件
    7.一个utils.py 用来配置log    
    

    [info] 编写记录log -- utils.py

    import time
    
    
    def log(*args, **kwargs):
        # time.time() 返回 unix time
        format = '%Y/%m/%d %H:%M:%S'
        value = time.localtime(int(time.time()))
        dt = time.strftime(format, value)
        print(dt, *args, **kwargs)
    

    [info] 编写服务器入口文件--server.py

    1.Request 类进行,保存不同路由映射函数信息
    2.response_for_path 处理run中获取的地址进行路由映射
    3.run 方法用来启动server服务器
    

    [danger] Request 保存路由映射对应请求信息类

    1.初始化参数 method 记录每一个映射路由的请求
    2.path 记录每一个路由的地址路径
    3.body 保存每一个路由的body 内容主要针对post
    4.query 记录每一个get请求链接上的参数 配合parsed_path 函数
    5.form 用来处理post 请求参数urllib.parse .unquote(v) 处理v 是因为post 的时候空格是+
    
    class Request:
        def __init__(self):
            self.method = "GET"
            self.path = ""
            self.body = ""
            self.query = {}
    
        def form(self):
            args = self.body.split("&")
            f = {}
            for arg in args:
                k, v = arg.split("=")
                f[k] = urllib.parse .unquote(v)
            return f
     
    # 实例化对象
    request = Reuqest()
    

    [danger] parsed_path 处理get 请求

    def parsed_path(path):
        index = path.find("?")
        if index == -1:
            return path, {}
        else:
            path, query_string = path.split("?", 1)
            args = query_string.split("&")
            query = {}
            for arg in args:
                k, v = arg.split('=')
                query[k] = v
            return path, query
    

    [danger] 编写一个异常处理的erro文件返回404

    def error(request, code=404):
        e = {
            404: b'HTTP/1.1 404 NOT FOUND\r\n\r\n<h1>NOT FOUND</h1>',
        }
        return e.get(code, b'')
    

    [danger] response_for_path 处理路由映射关系

    1.parsed_path用来处理 get 请求方法,将get 请求方法清洗,返回地址和请求参数
    2.return 返回的是将路由映射保存的request 对象传入到对应的映射方法
    
    def response_for_path(path):
        # 如果你是get
        path, query = parsed_path(path)
        request.path = path
        request.query = query
    
        r = {
            "/static":route_static,
        }
        r.update(urls)
        response = r.get(path, error)
        return response(request)
    

    [danger] run 启动服务器文件

    def run(host='', port=3000):
        
        # 在控制台打印端口,和服务器启动时间
        log('start at', '{}:{}'.format(host, port))
        # 使用 with 可以保证程序中断的时候正确关闭 socket 释放占用的端口
        with socket.socket() as s:
            s.bind((host, port))
            while True:
                s.listen(5)
                connection, address = s.accept()
                r = b""
                buffer_size = 1024
                while True:
                    r_connection = connection.recv(buffer_size)
                    r += r_connection
                    if len(r_connection) <= 1024:
                        break
                r = r.decode('utf-8')
                if len(r.split())<2:
                    continue
                    
                # 拆分原始数据 获取 请求方法和路径 GET / HTTP/1.1 
                path = r.split()[1]
                request.method = r.split()[0]
                
                # 利用"\r\n\r\n" 是将请求体分割格式,获取请求体
                request.body = r.split("\r\n\r\n", 1)[1]
                # 路由映射
                response = response_for_path(path)
                connection.sendall(response)
                # 处理完请求, 关闭连接
                connection.close()
    

    [info] 路由映射文件 -- routes.py

    1.这个文件主要处理每一个,映射函数的请求
    2.核对templates 文件的搭配
    3.以及处理一些简单的模板
    

    [danger] 处理读取HTML函数的 -- template

    1.注意一定要对open 中的encoding 进行编码,win默认是gbk模式
    
    def template(name):
        path = "template/" + name
        with open(path, "r",encoding='utf-8') as f:
            return f.read()
    

    [danger] 配置url 参数映射字典

    urls = {
        '/': route_index,
        '/login': route_login,
        '/register': route_register,
        '/messages': route_message,
    }
    

    [danger] 编写静态文件的映射函数 -- route_static

    def route_static(request):
        """
        两种情况的处理
        path, query = response_for_path('/static?file=doge.gif')
        path  '/static'
        """
        filename = request.query.get('file', 'doge.gif')
        path = 'static/' + filename
        with open(path, 'rb') as f:
            header = b'HTTP/1.1 200 OK\r\nContent-Type: image/gif\r\n'
            img = header + b'\r\n'+ f.read()
            return img
    

    [danger] 编写第一个主页映射函数 -- route_index

    def route_index(request):
        """
        主页的处理函数, 返回主页的响应
        """
        header = 'HTTP/1.1 210 VERY OK\r\nContent-Type: text/html\r\n'
        body = template('index.html')
        r = header + '\r\n' + body
        return r.encode(encoding='utf-8')
    

    [danger] 编写登录逻辑处理的---route_login

    1.传的参数中的request 保存的是每一个函数的,请求时候的处理信息
    2.如果是post 请求处理创建的Request 类中form 方法保存的body中的内容
    3.利用replace 替代我们在html 中特殊格式化的数据,这个页面最后渲染不应该属于任何请求,而是所有请求处理的展示内
     容改变
     
    
    def route_login(request):
        header = 'HTTP/1.1 210 VERY OK\r\nContent-Type: text/html\r\n'
        if request.method == 'POST':
            form = request.form()
            u = User.new(form)
            if u.validate_login():
                result = '登录成功'
            else:
                result = '用户名或者密码错误'
        else:
            result = ''
        body = template('login.html')
        body = body.replace('{{result}}', result)
        r = header + '\r\n' + body
        return r.encode(encoding='utf-8')
    

    [danger] 编写注册处理函数 --- route_register

    def route_register(request):
        header = 'HTTP/1.1 210 VERY OK\r\nContent-Type: text/html\r\n'
        if request.method == 'POST':
            # HTTP BODY 如下
            # username=gw123&password=123
            # 经过 request.form() 函数之后会变成一个字典
            form = request.form()
            u = User.new(form)
            if u.validate_register():
                u.save()
                result = '注册成功<br> <pre>{}</pre>'.format(User.all())
            else:
                result = '用户名或者密码长度必须大于2'
        else:
            result = ''
        body = template('register.html')
        body = body.replace('{{result}}', result)
        r = header + '\r\n' + body
        return r.encode(encoding='utf-8')
    

    [danger] 模拟简单的psot get请求 ---route_message

    # message_list 存储了所有的 message
    message_list = []
    
    
    def route_message(request):
        log('本次请求的 method', request.method)
        if request.method == 'POST':
            form = request.form()
            msg = Message.new(form)
            log('post', form)
            message_list.append(msg)
            # 应该在这里保存 message_list
        header = 'HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n'
        body = template('html_basic.html')
        # '#'.join(['a', 'b', 'c']) 的结果是 'a#b#c'
        msgs = '<br>'.join([str(m) for m in message_list])
        body = body.replace('{{messages}}', msgs)
        r = header + '\r\n' + body
        return r.encode(encoding='utf-8')
    

    [info] 编写数据处理文件夹 model

    1.我们处理数据库的连接模块进行拆分,成三个小py文件
    2.__init__.py用来编写model 的父类和数据存放方法
    3.user.py 用来处理用户信息判断验证
    4.message.py 用来处理信息验证
    

    [danger] 编写init文件

    1.class Model 类用来处理文件的保存和读取
       --- 注意这个类几点说明
       --- db_path 中使用了cls.__name__ 的方法,可以获取使用类的类名,因为类名和对象不同点在于,类名是唯一的,做成
       父类后所有子类就变成唯一的
       --- new 相当于创建了对应的类
       --- all 是获取了所有储存文件的内容
       --- save 将所有内容保存到文件目录中
    2.save 方法用来保存文件信息
    3.load 用来加载文件信息
    
    def save(data, path):
        """
        本函数把一个 dict 或者 list 写入文件
        data 是 dict 或者 list
        path 是保存文件的路径
        indent 是缩进
        ensure_ascii=False 用于保存中文
        """
        s = json.dumps(data, indent=2, ensure_ascii=False)
        with open(path, 'w+', encoding='utf-8') as f:
            log('save', path, s, data)
            f.write(s)
    
    
    
    def load(path):
        """
        本函数从一个文件中载入数据并转化为 dict 或者 list
        path 是保存文件的路径
        """
        with open(path, 'r', encoding='utf-8') as f:
            s = f.read()
            log('load', s)
            return json.loads(s)
    
    
    # Model 是用于存储数据的基类
    class Model(object):
        @classmethod
        def db_path(cls):
            
            classname = cls.__name__
            path = 'db/{}.txt'.format(classname)
            return path
    
        @classmethod
        def new(cls, form):
            # 下面一句相当于 User(form) 或者 Msg(form)
            m = cls(form)
            return m
    
        @classmethod
        def all(cls):
            """
            得到一个类的所有存储的实例
            """
            path = cls.db_path()
            models = load(path)
            log('log', models)
            ms = [cls.new(m) for m in models]
            return ms
    
        def save(self):
            """
            save 函数用于把一个 Model 的实例保存到文件中
            """
            models = self.all()
            log('models', models)
            models.append(self)
            # __dict__ 是包含了对象所有属性和值的字典
            l = [m.__dict__ for m in models]
            log("lmodel", l)
            path = self.db_path()
            save(l, path)
    
        def __repr__(self):
            """
            对象的显示形式,__dict__ 返回的是初始化 参数
            """
            classname = self.__class__.__name__
            properties = ['{}: ({})'.format(k, v) for k, v in self.__dict__.items()]
            s = '\n'.join(properties)
            return '< {}\n{} >\n'.format(classname, s)
    

    [danger] 用来进行用户注册登录判断的 user.py

    from models import Model
    
    
    class User(Model):
        def __init__(self, form):
            self.username = form.get('username', '')
            self.password = form.get('password', '')
    
        def validate_login(self):
            return self.username == 'gua' and self.password == '123'
    
        def validate_register(self):
            return len(self.username) > 2 and len(self.password) > 2
    

    [danger] 用来进行简单的post,get message.py

    from models import Model
    
    
    # 定义一个 class 用于保存 message
    class Message(Model):
        def __init__(self, form):
            self.author = form.get('author', '')
            self.message = form.get('message', '')
    
    

    [info] ## html 文件

    [danger] ##### login.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>注册登录页面</title>
    </head>
    <body>
        <h1>登录</h1>
        <form action="/login" method="post">
            <input type="text" name="username" placeholder="请输入用户名">
            <br>
            <input type="text" name="password" placeholder="请输入密码">
            <br>
            <button type="submit">登录</button>
        </form>
        <h3>{{result}}</h3>
    </body>
    </html>
    

    [danger] ##### register.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>注册页面</title>
    </head>
    <body>
        <h1>注册</h1>
        <form action="/register" method="post">
            <input type="text" name="username" placeholder="请输入用户名">
            <br>
            <input type="text" name="password" placeholder="请输入密码">
            <br>
            <button type="submit">注册</button>
        </form>
        <h3>{{result}}</h3>
    </body>
    </html>
    

    [danger] ##### index.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>吃瓜主页</title>
    </head>
    <body>
        <h1>吃瓜</h1>
        <a href="/login">Login</a>
        <img src="/static?file=doge.gif"/>
        <img src="/static?file=doge1.jpg"/>
        <img src="/static?file=doge2.gif"/>
    </body>
    </html>
    

    [danger] ##### html_basic.html

    <!DOCTYPE html>
    <!-- 注释是这样的, 不会被显示出来 -->
    <!--
        html 格式是浏览器使用的标准网页格式
        简而言之就是 标签套标签
    -->
    <!-- html 中是所有的内容 -->
    <html>
        <!-- head 中是放一些控制信息, 不会被显示 -->
        <head>
            <!-- meta charset 指定了页面编码, 否则中文会乱码 -->
            <meta charset="utf-8">
            <!-- title 是浏览器显示的页面标题 -->
            <title>例子 1</title>
        </head>
        <!-- body 中是浏览器要显示的内容 -->
        <body>
            <!-- html 中的空格是会被转义的, 所以显示的和写的是不一样的 -->
            <!-- 代码写了很多空格, 显示的时候就只有一个 -->
            很        好普通版
            <h1>很好 h1 版</h1>
            <h2>很好 h2 版</h2>
            <h3>很好 h3 版</h3>
            <!-- form 是用来给服务器传递数据的 tag -->
            <!-- action 属性是 path -->
            <!-- method 属性是 HTTP方法 一般是 get 或者 post -->
            <!-- get post 的区别上课会讲 -->
            <form action="/messages" method="post">
                <!-- textarea 是一个文本域 -->
                <!-- name 属性, 用处上课讲 -->
                <textarea name="message"></textarea>
                <textarea name="author"></textarea>
                <!-- button type=submit 才可以提交表单 -->
                <button type="submit">POST 提交</button>
            </form>
            <form action="/messages" method="get">
                <textarea name="message"></textarea>
                <button type="submit">GET 提交</button>
            </form>
        {{messages}}
        </body>
    </html>
    
    

    相关文章

      网友评论

          本文标题:利用socket 搭建一个简单web 框架

          本文链接:https://www.haomeiwen.com/subject/aarapftx.html