美文网首页
利用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