手撸简易的web框架

HTTP 协议

规定了浏览器与服务端之间的数据交互格式

四大特性

1.基于TCP/IP作用于应用层之上的协议
2.基于请求响应
3.无状态
  保存状态的的技术有cookiesessiontoken
4.无(短)连接
  长连接:websocket

数据格式

1.请求数据格式:
  请求首行(包含有请求方法...)
  请求头(K:V键值对信息)
  \r\n
  请求体(主要用来携带敏感数据)

2.响应数据格式:
  响应首行(响应状态码)
  响应头(K:V键值对)
  \r\n
  响应体(展示给用户的数据)

响应状态码

用一串数字表示信息
1xx:服务端已经接收到数据并正在处理可以继续提交
2xx:200请求成功
3xx:重定向
4xx:403 404
5xx:服务器内部错误

响应状态码可以自定义

请求方法

1.get请求
	从指定资源请求数据

2.post请求
  将数据发送到服务器

'''
get请求: url?username=xxx&password=123456
post请求:数据是放在请求体内
'''

手写 web 框架

import socket

server = socket.socket()
server.bind(('127.0.0.1', 8000))
server.listen(5)

while True:
    conn, add = server.accept()
    data = conn.recv(1024)
    # print(data)
    res = data.decode('utf8')
    path = res.split(' ')[1]
    # 发送响应头
    conn.send(b'HTTP/1.1 200 OK\r\n\r\n')
    if path == '/index':
        # conn.send(b'index')
        # 发送html页面
        with open(r'demo1.html', 'rb') as f:
            data = f.read()
            conn.send(data)
    elif path == '/login':
        conn.send(b'login')
    else:
        conn.send(b'404 error')
    # conn.send(b'hello world')
    conn.close()

基于wsgiref模块

from wsgiref.simple_server import make_server


def run(request, response):
    response('200 OK', [])
    current_path = request.get("PATH_INFO")
    if current_path == '/index':
        return [b'index']
    elif current_path == '/login':
        return [b'login']
    else:
        return [b'404 error']


if __name__ == '__main__':
    server = make_server('127.0.0.1', 8000, run)
    server.serve_forever()

封装处理

1.定义一个网址与函数的对应关系 urls.py
from views import *

urls = [
    ('/index', index_func),
    ('/login', login_func),
    ('/reg', reg_func),
]

2.定义函数功能 views.py
def index_func(request):
    return 'index'
def login_func(request):
    return 'login'
def reg_func(request):
    return 'reg'
def errors(request):
    return '404 error'

3.服务端文件 server.py
from wsgiref.simple_server import make_server
import urls
from views import *

def run(request, response):
    response('200 OK', [])
    current_path = request.get("PATH_INFO")
    func = None
    for url_tuple in urls:
        if current_path == url_tuple[0]:
            func = url_tuple[1]
            break
    if func:
        res = func(request)
    else:
        res = errors(request)
    return [bytes(res, encoding='utf8')]
if __name__ == '__main__':
    server = make_server('127.0.0.1', 8000, run)
    server.serve_forever()

动静态网页

# 静态网页
	数据全是写死的
# 动态网页
  数据来源于后端(代码数据库)
  • 访问展示当前时间(时间是后端模块生成)

    # urls.py 添加
      ('/get_time', get_time_func)
    
    # views.py中对应的功能函数
      def get_time_func(request):
          import time
          current_time = time.strftime('%Y-%m-%d %X')
          with open(r'demo1.html', 'r', encoding='utf8') as f:
              data = f.read()
          data = data.replace('asdasdasd', current_time)
          return data
    
    # demo1.html
      <h1>asdasdasd</h1>
    
  • 后端字典展示

    pip3 install jinja2
    
    # urls.py 添加
      ('/get_dict', get_dict_func)
    
    # views.py中对应的功能函数
      def get_dict_func(request):
          user_dict = {'name': 'jason', 'age': 18, 'password': 123456}
    
          with open(r'Template/dem02.html', 'r', encoding='utf8') as f:
              data = f.read()
          temp = Template(data)
          # 将user_dict 传递给demo2.html页面 在该页面上使用变量名user_data
          res = temp.render(user_data=user_dict)
          return res
    
    # Template/dem02.html
      {{ user_data }} <br>
      {{ user_data.get('name') }} <br>
      {{ user_data['age'] }} <br>
      {{ user_data.password }}
    
  • 获取 MySQL 数据库数据

    # urls.py 添加
      ('/get_sql', get_sql_func)
    
    # views.py 中对应的功能函数
      def get_sql_func(request):
          import pymysql
          conn = pymysql.connect(
              host='10.0.0.60',
              port=3306,
              user='root',
              password='123456',
              db='db1',
              charset='utf8',
              autocommit=True
          )
          cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
          sql = 'select * from student'
          affect_rows = cursor.execute(sql)
          res1 = cursor.fetchall()  # [{},{},{}]
          with open(r'Template/db.html', 'r', encoding='utf8') as f:
              data = f.read()  # 字符串
          temp = Template(data)
          # 将user_dict传递给get_dict.html页面 在该页面上使用变量名user_data调用
          res = temp.render(data_list=res1)
          return res
    
    # Template/db.html
    <div class="container">
        <div class="row">
            <h1 class="text-center">学生数据</h1>
            <div class="col-md-8 col-md-offset-2">
                <table class="table table-hover table-striped table-bordered">
                    <thead>
                    <tr>
                        <td>学号</td>
                        <td>姓名</td>
                        <td>年龄</td>
                        <td>性别</td>
                    </tr>
                    </thead>
                    <tbody>
                    {%for user_dict in data_list%}
                        <tr>
                            <td>{{user_dict.id}}</td>
                            <td>{{user_dict.name}}</td>
                            <td>{{user_dict.age}}</td>
                            <td>{{user_dict.gender}}</td>
                        </tr>
                    {%endfor%}
                    </tbody>
                </table>
            </div>
        </div>
    </div>
    

总结

wsgiref模块
  1. 封装了socket代码
  2. 处理了http数据格式

根据功能的不同拆分成不同的文件
urls.py    路由与视图函数对应关系
views.py   视图函数
templates  模板文件夹
# 1.第一步添加路由与视图函数的对应关系
# 2.去views中书写功能代码
# 3.如果需要使用到html则去模板文件夹中操作

jinja2模板语法
{{}}
{%%}

img

主流的 web 框架

Django框架
  大而全自带的功能组件非常多但是缺点就是太重了

Flask框架
  小而精自带组件少第三方模块非常多但是第三方模块比较难管理

Tornado框架
  异步非阻塞 速度非常快

A:socket部分
B:路由与视图匹配
C:模板语法

django
   A:用的是wsgiref模块
   B:自己写的
   C:自己写的
flask
   A:用的是wsgiref模块封装之后werkzeug
   B:自己写的
   C:jinja2模块
tornado
	ABC都是自己写的