Django基础(1) - Web框架原理

一 web框架的本质

  简单描述就是:浏览器通过你输入的网址给你的socket服务端发送请求,服务端接受到请求给其回复一个对应的html页面,这就是web项目。所有的Web应用本质上就是一个socket服务端,而用户的浏览器就是一个socket客户端,基于请求做出响应,客户都先请求,服务端做出对应的响应,按照http协议的请求协议发送请求,服务端按照http协议的响应协议来响应请求,这样的网络通信,我们就可以自己实现Web框架了。

 

 

  什么是web框架?这就好比建设房子,房子主体钢结构等都为我们搭建好了,我们就是抹抹墙面,添加一些装饰,修饰一下即可。Django框架就是已经为我们搭建好的主题钢结构,剩下的根据不同的业务自定制即可。但是我们先不着急学习Django框架,今天我们先自己搭建一个web框架,自己搭建了web框架之后,你对Django框架就会比较好理解了。

1.1 先构建socket服务端

import socket
server = socket.socket()
server.bind(('127.0.0.1', 8002))
server.listen()

while 1:
    conn, addr = server.accept()
    while 1:
        client_data = conn.recv(1024).decode('utf-8')
        print(client_data)
        # 服务端与客户端建立联系必须要遵循一个协议,此时我们用http协议示例。
        conn.send('HTTP/1.1 200 OK \r\n\r\n'.encode('utf-8'))
        conn.send('<h1>hello</h1>'.encode('utf-8'))
    conn.close()

  我们通过浏览器请求服务端,服务端给我们返回一个hello标签。客户端请求过来之后,要想让服务端给客户端返回消息,必须基于一个协议,我们用http协议示例。那么服务端如果返回给浏览器一个页面呢?

1.2 构建一个html页面

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <style>
        div {
            background-color: #7fb8ff;
            color: red;
        }
    </style>
</head>
<body>
<div>你好,世界</div>

</body>
</html>

此时你的服务端必须将html页面返回给客户端,怎么返回?就得通过发送bytes数据。

服务端:

import socket
server = socket.socket()
server.bind(('127.0.0.1', 8002))
server.listen()

while 1:
    conn, addr = server.accept()
    client_data = conn.recv(1024).decode('utf-8')
    print(client_data)
    # 服务端与客户端建立联系必须要遵循一个协议,此时我们用http协议示例。
    conn.send('HTTP/1.1 200 OK \r\n\r\n'.encode('utf-8'))
    with open('demo.html', mode='rb') as f1:
        conn.send(f1.read())
conn.close()

这么我们发现一个问题,当你的浏览器访问服务端时,服务端会接受到这些信息:

GET / HTTP/1.1
Host: 127.0.0.1:8001
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36
Sec-Fetch-User: ?1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9

  这些是什么信息?这是因为服务端与客户端遵循http协议,这是协议做的事情,所以在我们研究web框架之前,必不可少的需要研究一下这个协议,协议到底做了什么事情。

http协议详见:《图解HTTP》- 上野·宣《HTTP抓包实战》- 肖佳

1.3 http协议具体研究

1.3.1 请求步骤

1.3.1.1  建立链接:客户端连接到Web服务器。  

一个HTTP客户端,通常是浏览器,与Web服务器的HTTP端口(默认为80)建立一个TCP套接字连接。例如,http://www.baidu.com。

1.3.1.2 发送HTTP请求

  通过TCP套接字,客户端向Web服务器发送一个文本的请求报文,一个请求报文由请求行、请求头部、空行和请求数据4部分组成。

这里分不同的请求,主要是get、post请求。

首先看get请求:

  更改一下我们的html页面:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Bootstrap 101 Template</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" rel="stylesheet">


</head>
<body>

<form action="http://127.0.0.1:8002">
用户名:<input type="text" name="username">
密码:<input type="password" name="password">
验证码:<input type="text" name="code">
<input type="submit">
</form>
</body>
</html>

  此时你发送的是get请求,服务器接受到的消息为:

GET /?username=taibai&password=123&code=2w3e HTTP/1.1
Host: 127.0.0.1:8002
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36
Sec-Fetch-User: ?1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Referer: http://127.0.0.1:8002/?username=taibai&password=123&code=3er4
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9

  你的get请求的数据都是拼接到url后面的:

 

 

再来看post请求:

更改一下我们的html页面:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Bootstrap 101 Template</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" rel="stylesheet">


</head>
<body>

<form action="http://127.0.0.1:8002" method="post">
用户名:<input type="text" name="username">
密码:<input type="password" name="password">
验证码:<input type="text" name="code">
<input type="submit">
</form>
</body>
</html>

  此时你发送的是post请求,服务器接受到的消息为:

POST / HTTP/1.1
Host: 127.0.0.1:8002
Connection: keep-alive
Content-Length: 38
Cache-Control: max-age=0
Origin: http://127.0.0.1:8002
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36
Sec-Fetch-User: ?1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Referer: http://127.0.0.1:8002/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9

username=taibai&password=123&code=2w3e

post请求,请求数据是在最后一行的。

正确的请求的格式为:

 

 

  请求头部有一些重要的键值对:

Host: 127.0.0.1:8002------> 主机(IP地址和端口)
Connection: keep-alive-----> 链接是否保留一段时间
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.87 Safari/537.36            ------> 用户的浏览器代理
Content-Length: 29    ------> 请求数据的长度
1.3.1.3 服务器接受请求并返回HTTP响应

  Web服务器解析请求,定位请求资源。服务器将资源复本写到TCP套接字,由客户端读取。一个响应由状态行、响应头部、空行和响应数据4部分组成。

  响应从哪里看呢?响应从服务器中获取:

 

 

 

 

  标准的响应格式:

 

 

1.3.1.4 释放连接TCP连接

  若connection 模式为close,则服务器主动关闭TCP连接,客户端被动关闭连接,释放TCP连接;若connection 模式为keepalive,则该连接会保持一段时间,在该时间内可以继续接收请求;

1.3.1.5 客户端浏览器解析HTML内容

  客户端浏览器首先解析状态行,查看表明请求是否成功的状态代码。然后解析每一个响应头,响应头告知以下为若干字节的HTML文档和文档的字符集。客户端浏览器读取响应数据HTML,根据HTML的语法对其进行格式化,并在浏览器窗口中显示。

1.3.2 重要知识点

URL、请求方式、http协议的特点,请求流程,请求格式,响应格式。响应状态码等。

post与get请求的区别:

GET提交的数据会放在URL之后,也就是请求行里面,以?分割URL和传输数据,参数之间以&相连,如EditBook?name=test1&id=123456.(请求头里面那个content-type做的这种参数形式,后面讲) 
POST方法是把提交的数据放在HTTP包的请求体中.

GET提交的数据大小有限制(因为浏览器对URL的长度有限制),而POST方法提交的数据没有限制.

GET与POST请求在服务端获取请求数据方式不同,就是我们自己在服务端取请求数据的时候的方式不同了,这句废话昂。
常用的get请求:浏览器输入网址,a标签,form标签默认get请求。
常用的post请求:一般就是用来提交数据,比如用户名密码登录。

二、构建web框架

2.1 简单版web框架

  之前通过pycharm打开html页面,是pychram给你提供的服务端,我们应该自己提供服务端,通过浏览器与服务端交互。

html:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <style>
        div{
            background-color: #7fb8ff;
            font-size: 16px;
        }
    </style>

</head>
<body>
<div>欢迎访问xx商城</div>

<ul>
    <li>aa</li>
    <li>bb</li>
    <li>cc</li>
</ul>

</body>
</html>

python:

import socket
server = socket.socket()
server.bind(('127.0.0.1', 8001))
server.listen()

while 1:
    conn, addr = server.accept()
    client_data = conn.recv(1024).decode('utf-8')
    print(client_data)
    # 服务端与客户端建立联系必须要遵循一个协议,此时我们用http协议示例。
    conn.send('HTTP/1.1 200 OK \r\nk1:v1\r\n\r\n'.encode('utf-8'))
    with open('02 简单版html.html', mode='rb') as f1:
        conn.send(f1.read())
    conn.close()

2.2 升级版web框架

  首先我们应该创建不同的css、js文件,通过html引入。

css

div{
            background-color: #7fb8ff;
            font-size: 16px;
        }

js

alert('未满18岁禁止入内');

 

 

  浏览器执行到link标签时,href会发出一个请求,请求你当前根目录下面的test.css文件,其实完整url也就是http://127.0.0.1:8002/test.css,你引入的js同理,此过程是异步的。html代码不会等你引入完css之后,再执行下面代码。

  你的python服务端与简单版本一致,我们启动一下服务端,看浏览器发送了什么请求。

  此时你的服务端接受到了如下几个请求:

GET / HTTP/1.1
Host: 127.0.0.1:8002
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36
Sec-Fetch-User: ?1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9


GET /test.css HTTP/1.1
Host: 127.0.0.1:8002
Connection: keep-alive
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36
Accept: text/css,*/*;q=0.1
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: no-cors
Referer: http://127.0.0.1:8002/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9



GET /test.js HTTP/1.1
Host: 127.0.0.1:8002
Connection: keep-alive
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36
Accept: */*
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: no-cors
Referer: http://127.0.0.1:8002/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9


GET /favicon.ico HTTP/1.1
Host: 127.0.0.1:8002
Connection: keep-alive
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36
Accept: image/webp,image/apng,image/*,*/*;q=0.8
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: no-cors
Referer: http://127.0.0.1:8002/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9

  你的浏览器给服务端发送了4个请求,这4个请求分别是:对根目录也就是html的请求、对css文件请求、对js文件请求、以及favincon也就是小图标的请求,

 

 

  虽然浏览器发送了四个请求,但是我们统一回复的就是一个html页面,相当于我们重复回复了4次同一个html页面,这也导致了我们的页面没有css,js的效果:

 

 

  所以我们应该怎么做?对不同的请求返回给不同的文件资源,比如他请求的css文件,我们就返回css文件资源,这样页面就有了更加丰富的样式。那么如何区分他的请求呢?就是通过每次请求的第一行的请求路径,所以我们应该把每次的请求路径分割,根据不同的请求路径,返回给其不同的文件资源。

  最终的html、以及服务端代码:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="test.css">
    <link rel="icon" href="jd.ico"> 
    

</head>
<body>
<div>欢迎访问和软商城</div>

<ul>
    <li>aa</li>
    <li>bb</li>
    <li>cc</li>
</ul>
<img src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1573643177693&di=8164a8d196f71177af75ed9fdaf7c3bd&imgtype=0&src=http%3A%2F%2Fhbimg.b0.upaiyun.com%2F04763621f7af266b3cc1d9d2fdd44e283f42188a13bda-9dpYLt_fw658" alt="">
{#<img src="mv.jpeg" alt="">#}
<script src="test.js"></script>
</body>
</html>

# 上面我添加了两个标签,就是对图片的请求。

python

import socket
server = socket.socket()
server.bind(('127.0.0.1', 8001))
server.listen()

while 1:
    conn, addr = server.accept()
    client_data = conn.recv(1024)
    request_path = client_data.decode('utf-8').split('\r\n')[0].split()[1]
    # 服务端与客户端建立联系必须要遵循一个协议,此时我们用http协议示例。
    conn.send('HTTP/1.1 200 OK \r\nk1:v1\r\n\r\n'.encode('utf-8'))
    if request_path == '/':
        with open('03 升级版html.html', mode='rb') as f1:
            conn.send(f1.read())
    elif request_path == '/test.css':
        with open('test.css', mode='rb') as f1:
            conn.send(f1.read())
    elif request_path == '/mv.jpeg':
        with open('mv.jpeg', mode='rb') as f1:
            conn.send(f1.read())
    elif request_path == '/test.js':
        with open('test.js', mode='rb') as f1:
            conn.send(f1.read())
    elif request_path == '/jd.ico':
        with open('jd.ico', mode='rb') as f1:
            conn.send(f1.read())
    conn.close()

favicon.ico 就是窗口小图标,浏览器一直会向服务器发送请求。
图片的索取与文件是一样的,也就是通过路径索取资源。

这样升级版web框架我们就完成了。

2.3 函数版web框架

  上一个版本用了多个if elif elif....... 这种方式比较low,并且代码应该整合成函数而不能使用纯面向过程方式,所以我们进行改版:

html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="test.css">
    <link rel="icon" href="jd.ico">

</head>
<body>
<div>欢迎访问和软商城</div>

<ul>
    <li>aa</li>
    <li>bb</li>
    <li>cc</li>
</ul>
<img src="mv.jpeg" alt="">
<script src="test.js"></script>
</body>
</html>

python

import socket
server = socket.socket()
server.bind(('127.0.0.1', 8001))
server.listen()

def html(conn):
    with open('04 函数进阶版html.html', mode='rb') as f1:
        conn.send(f1.read())
        conn.close()

def css(conn):
    with open('test.css', mode='rb') as f1:
        conn.send(f1.read())
        conn.close()

def jpeg(conn):
    with open('mv.jpeg', mode='rb') as f1:
        conn.send(f1.read())
        conn.close()

def js(conn):
    with open('test.js', mode='rb') as f1:
        conn.send(f1.read())
        conn.close()

def jd(conn):
    with open('jd.ico', mode='rb') as f1:
        conn.send(f1.read())
        conn.close()

request_list = [
    ('/', html),
    ('/test.css', css),
    ('/mv.jpeg', jpeg),
    ('/test.js', js),
    ('/jd.ico', jd),
]
while 1:
    conn, addr = server.accept()
    client_data = conn.recv(1024)
    request_path = client_data.decode('utf-8').split('\r\n')[0].split()[1]
    # 服务端与客户端建立联系必须要遵循一个协议,此时我们用http协议示例。
    conn.send('HTTP/1.1 200 OK \r\nk1:v1\r\n\r\n'.encode('utf-8'))
    for i in request_list:
        if request_path == i[0]:
            i[1](conn)
    conn.close()

  虽然这个这个版本感觉更加简洁明了了,但是这个版本还是不完美,现在虽然是异步处理请求,但是上一阶段我们学完并发,我们对于这些异步请求应该用并发处理更加合理。

2.4 并发版web框架

此时我们要加上多线程处理浏览器发送过来的请求。

html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="test.css">
    <link rel="icon" href="jd.ico">

</head>
<body>

<div>欢迎访问河软商城</div>
<div>{time_now}</div>
<ul>
    <li>aa</li>
    <li>bb</li>
    <li>cc</li>
</ul>
<!--<img src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1573643177693&di=8164a8d196f71177af75ed9fdaf7c3bd&imgtype=0&src=http%3A%2F%2Fhbimg.b0.upaiyun.com%2F04763621f7af266b3cc1d9d2fdd44e283f42188a13bda-9dpYLt_fw658" alt="">-->
<img src="mv.jpeg" alt="">
<script src="test.js"></script>
</body>
</html>

python

import socket
from threading import Thread
server = socket.socket()
server.bind(('127.0.0.1', 8002))
server.listen()

def html(conn):
    with open('05 高级版html.html', mode='rb') as f1:
        data = f1.read()
    conn.send(data)
    conn.close()


def css(conn):
    with open('test.css', mode='rb') as f2:
        data = f2.read()
    conn.send(data)
    conn.close()


def jpeg(conn):
    with open('mv.jpeg', mode='rb') as f3:
        data = f3.read()
    conn.send(data)
    conn.close()

def js(conn):
    with open('test.js', mode='rb') as f4:
        data = f4.read()
    conn.send(data)
    conn.close()

def jd(conn):
    with open('jd.ico', mode='rb') as f5:
        data = f5.read()
    conn.send(data)
    conn.close()

request_list = [
    ('/', html),
    ('/test.css', css),
    ('/mv.jpeg', jpeg),
    ('/test.js', js),
    ('/jd.ico', jd),
]
while 1:
    conn, addr = server.accept()
    client_data = conn.recv(1024)
    request_path = client_data.decode('utf-8').split('\r\n')[0].split()[1]
    print(request_path)
    # 服务端与客户端建立联系必须要遵循一个协议,此时我们用http协议示例。
    conn.send('HTTP/1.1 200 OK \r\n\r\n'.encode('utf-8'))
    # 只要有一个线程处理conn管道,accept就会接受新的请求创建另一个管道
    for i in request_list:
        if request_path == i[0]:
            t = Thread(target=i[1], args=(conn,))
            t.start()
    # conn.close()

  此版本并不是完结版本,上面的版本我们写的都是静态页面,没有实现动态框架以及与数据库交互等。所以我们继续更新。

2.5 动态版web框架

  一般情况下,我们的数据都是在数据库中,动态获取,实时变化,但是现在我们模拟一下数据,利用时间戳。

html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="test.css">
    <link rel="icon" href="jd.ico">

</head>
<body>

<div>欢迎访问河软商城</div>
<div>{time_now}</div>
<ul>
    <li>aa</li>
    <li>bb</li>
    <li>cc</li>
</ul>
<!--<img src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1573643177693&di=8164a8d196f71177af75ed9fdaf7c3bd&imgtype=0&src=http%3A%2F%2Fhbimg.b0.upaiyun.com%2F04763621f7af266b3cc1d9d2fdd44e283f42188a13bda-9dpYLt_fw658" alt="">-->
<img src="mv.jpeg" alt="">
<script src="test.js"></script>
</body>
</html>

python

import socket
import time
from threading import Thread
server = socket.socket()
server.bind(('127.0.0.1', 8002))
server.listen()

def html(conn):
    time_now = time.strftime('%Y/%m/%d %H:%M:%S',time.localtime())
    with open('06 高级版动态html.html', encoding='utf-8') as f1:
        data = f1.read().format(time_now=time_now)
    conn.send(data.encode('utf-8'))
    conn.close()


def css(conn):
    with open('test.css', mode='rb') as f2:
        data = f2.read()
    conn.send(data)
    conn.close()


def jpeg(conn):
    with open('mv.jpeg', mode='rb') as f3:
        data = f3.read()
    conn.send(data)
    conn.close()

def js(conn):
    with open('test.js', mode='rb') as f4:
        data = f4.read()
    conn.send(data)
    conn.close()

def jd(conn):
    with open('jd.ico', mode='rb') as f5:
        data = f5.read()
    conn.send(data)
    conn.close()

request_list = [
    ('/', html),
    ('/test.css', css),
    ('/mv.jpeg', jpeg),
    ('/test.js', js),
    ('/jd.ico', jd),
]
while 1:
    conn, addr = server.accept()
    client_data = conn.recv(1024)
    request_path = client_data.decode('utf-8').split('\r\n')[0].split()[1]
    print(request_path)
    # 服务端与客户端建立联系必须要遵循一个协议,此时我们用http协议示例。
    conn.send('HTTP/1.1 200 OK \r\n\r\n'.encode('utf-8'))
    # 只要有一个线程处理conn管道,accept就会接受新的请求创建另一个管道
    for i in request_list:
        if request_path == i[0]:
            t = Thread(target=i[1], args=(conn,))
            t.start()

2.6 wsgiref模块版+数据库web框架

  wsgiref模块其实就是将整个请求信息给封装了起来,就不需要你自己处理了,假如它将所有请求信息封装成了一个叫做request的对象,那么你直接request.path就能获取到用户这次请求的路径,request.method就能获取到本次用户请求的请求方式(get还是post)等,那这个模块用起来,我们再写web框架是不是就简单了好多啊。

  对于真实开发中的python web程序来说,一般会分为两部分:服务器程序和应用程序。

服务器程序:

 应用程序:

 

  服务器程序负责对socket服务器进行封装,并在请求到来时,对请求的各种数据进行整理。

  应用程序则负责具体的逻辑处理。为了方便应用程序的开发,就出现了众多的Web框架,例如:Django、Flask、web.py 等。不同的框架有不同的开发方式,但是无论如何,开发出的应用程序都要和服务器程序配合,才能为用户提供服务。

  这样,服务器程序就需要为不同的框架提供不同的支持。这样混乱的局面无论对于服务器还是框架,都是不好的。对服务器来说,需要支持各种不同框架,对框架来说,只有支持它的服务器才能被开发出的应用使用。最简单的Web应用就是先把HTML用文件保存好,用一个现成的HTTP服务器软件,接收用户请求,从文件中读取HTML,返回。如果要动态生成HTML,就需要把上述步骤自己来实现。不过,接受HTTP请求、解析HTTP请求、发送HTTP响应都是苦力活,如果我们自己来写这些底层代码,还没开始写动态HTML呢,就得花个把月去读HTTP规范。

  正确的做法是底层代码由专门的服务器软件实现,我们用Python专注于生成HTML文档。因为我们不希望接触到TCP连接、HTTP原始请求和响应格式,所以,需要一个统一的接口协议来实现这样的服务器软件,让我们专心用Python编写Web业务。

  这时候,标准化就变得尤为重要。我们可以设立一个标准,只要服务器程序支持这个标准,框架也支持这个标准,那么他们就可以配合使用。一旦标准确定,双方各自实现。这样,服务器可以支持更多支持标准的框架,框架也可以使用更多支持标准的服务器。

  WSGI(Web Server Gateway Interface)就是一种规范,它定义了使用Python编写的web应用程序与web服务器程序之间的接口格式,实现web应用程序与web服务器程序间的解耦。

  常用的WSGI服务器有uwsgi、Gunicorn。而Python标准库提供的独立WSGI服务器叫wsgiref,Django开发环境用的就是这个模块来做服务器。

接下来我们先看一看wsgiref的简单用法:

from wsgiref.simple_server import make_server
# wsgiref本身就是个web框架,提供了一些固定的功能(请求和响应信息的封装,不需要我们自己写原生的socket了也不需要咱们自己来完成请求信息的提取了,提取起来很方便)
#函数名字随便起
def application(environ, start_response):
    '''
    :param environ: 是全部加工好的请求信息,加工成了一个字典,通过字典取值的方式就能拿到很多你想要拿到的信息
    :param start_response: 帮你封装响应信息的(响应行和响应头),注意下面的参数
    :return:
    '''
    start_response('200 OK', [('k1','v1'),])
    print(environ)
    print(environ['PATH_INFO'])  #输入地址127.0.0.1:8000,这个打印的是'/',输入的是127.0.0.1:8000/index,打印结果是'/index'
    return [b'<h1>Hello, web!</h1>']

#和咱们学的socketserver那个模块很像啊
httpd = make_server('127.0.0.1', 8080, application)

print('Serving HTTP on port 8080...')
# 开始监听HTTP请求:
httpd.serve_forever()

  这个模块就是封装好服务器程序的处理,方便你使用。

  接下来我们引入数据库内容,首先创建一个数据库,然后在插入一些数据:

  现将mysql改成自动账号密码登录:

  找到mysql软件的min.ini 配置文件,

 

 

 

 

 

 

 

  先要将cmd设置成以管理员身份启动:c:\windws\System32 寻找cmd

 

 

 

 

 

 

 

 

 

 

配置数据库:

利用pymysql先创建一个表:

 

创建成功:

插入一些数据:

 

  这样你成功的在数据库创建并插入了一些数据,然后就是三个文件了:

html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="test.css">
    <link rel="icon" href="jd.ico">

</head>
<body>

<div>欢迎访问河软商城</div>
<div>{data}</div>
<ul>
    <li>aa</li>
    <li>bb</li>
    <li>cc</li>
</ul>
<!--<img src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1573643177693&di=8164a8d196f71177af75ed9fdaf7c3bd&imgtype=0&src=http%3A%2F%2Fhbimg.b0.upaiyun.com%2F04763621f7af266b3cc1d9d2fdd44e283f42188a13bda-9dpYLt_fw658" alt="">-->
<img src="mv.jpeg" alt="">
<script src="test.js"></script>
</body>
</html>

socket服务器

from wsgiref.simple_server import make_server
import getdata

def html():
    from_mysql_data = getdata.get_data()
    with open('07 wsgiref版+数据库html.html', encoding='utf-8') as f1:
        data = f1.read().format(data=from_mysql_data)
    return data.encode('utf-8')


def css():
    with open('test.css', mode='rb') as f2:
        data = f2.read()
    return data


def jpeg():
    with open('mv.jpeg', mode='rb') as f3:
        data = f3.read()
    return data


def js():
    with open('test.js', mode='rb') as f4:
        data = f4.read()
    return data


def jd():
    with open('jd.ico', mode='rb') as f5:
        data = f5.read()
    return data

request_list = [
    ('/', html),
    ('/test.css', css),
    ('/mv.jpeg', jpeg),
    ('/test.js', js),
    ('/jd.ico', jd),
]



def application(environ, start_response):

    start_response('200 OK', [('k1', 'v1'), ('k2', 'v2')])
    print(environ)
    request_path = environ['PATH_INFO']
    for i in request_list:
        if request_path == i[0]:
            ret = i[1]()
            return [ret]
    else:

        return [b'<h1>404.....</h1>']




httpd = make_server('127.0.0.1', 8080, application)
print('Serving HTTP on port 8080...')
# 开始监听HTTP请求:
httpd.serve_forever()

创建数据

import pymysql

conn = pymysql.connect(
    host='127.0.0.1',
    port=3306,
    user='root',
    password='123',
    database='webbase',
    charset='utf8'
)


cursor = conn.cursor(pymysql.cursors.DictCursor)
# 创建userinfo数据表
# cursor.execute('create table userinfo(id int primary key auto_increment, name varchar(16) not null unique, age int not null);')

# 给userinfo插入数据
# cursor.execute("""
#     insert into userinfo(name,age) values
#     ('小明',28),
#     ('小红',18),
#     ('太白',18);
#     """
# )

conn.commit()

cursor.close()

conn.close()

getdata

import pymysql


def get_data():

    conn = pymysql.connect(
        host='127.0.0.1',
        port=3306,
        user='root',
        password='123',
        database='webbase',
        charset='utf8'
    )

    cursor = conn.cursor(pymysql.cursors.DictCursor)

    # 获取所有数据
    cursor.execute("select * from userinfo;")
    data = cursor.fetchone()
    conn.commit()
    cursor.close()
    conn.close()
    return data

2.7 wsgiref模块版+数据库+jinja2 web框架

  上面的代码实现了一个简单的动态页面(字符串替换),我完全可以从数据库中查询数据,然后去替换我html中的对应内容(专业名词叫做模板渲染,你先渲染一下,再给浏览器进行渲染),然后再发送给浏览器完成渲染。 这个过程就相当于HTML模板渲染数据。 本质上就是HTML内容中利用一些特殊的符号来替换要展示的数据。 我这里用的特殊符号是我定义的,其实模板渲染有个现成的工具: jinja2,Django有自带的模版渲染方法,和jinja2很像,但是只能适用于Django框架,而jinja2可以适用于多种框架。

下载:

pip install jinja2

 用法直接体现在项目中:

getdata

import pymysql


def get_data():

    conn = pymysql.connect(
        host='127.0.0.1',
        port=3306,
        user='root',
        password='123',
        database='webbase',
        charset='utf8'
    )

    cursor = conn.cursor(pymysql.cursors.DictCursor)

    # 获取所有数据
    cursor.execute("select * from userinfo;")
    data = cursor.fetchone()
    conn.commit()
    cursor.close()
    conn.close()
    return data

html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="test.css">
    <link rel="icon" href="jd.ico">

</head>
<body>

<div>欢迎访问河软商城</div>
<div>{{userinfo}}</div>

<ul>
    {% for k,v in userinfo.items()%}
    <li>{{k}} --  {{v}}</li>
    {%endfor%}
</ul>
<!--<img src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1573643177693&di=8164a8d196f71177af75ed9fdaf7c3bd&imgtype=0&src=http%3A%2F%2Fhbimg.b0.upaiyun.com%2F04763621f7af266b3cc1d9d2fdd44e283f42188a13bda-9dpYLt_fw658" alt="">-->
<img src="mv.jpeg" alt="">
<script src="test.js"></script>
</body>
</html>

socket服务器

import getdata
from jinja2 import Template
from wsgiref.simple_server import make_server


def html():
    from_mysql_data = getdata.get_data()
    with open('08 wsgiref版+数据库+jinja2html.html', encoding='utf-8') as f1:
        data = f1.read()
    temp = Template(data)
    temp_data = temp.render({'userinfo': from_mysql_data})
    return temp_data.encode('utf-8')


def css():
    with open('test.css', mode='rb') as f2:
        data = f2.read()
    return data


def jpeg():
    with open('mv.jpeg', mode='rb') as f3:
        data = f3.read()
    return data


def js():
    with open('test.js', mode='rb') as f4:
        data = f4.read()
    return data


def jd():
    with open('jd.ico', mode='rb') as f5:
        data = f5.read()
    return data

request_list = [
    ('/', html),
    ('/test.css', css),
    ('/mv.jpeg', jpeg),
    ('/test.js', js),
    ('/jd.ico', jd),
]



def application(environ, start_response):

    start_response('200 OK', [('k1', 'v1'), ('k2', 'v2')])
    print(environ)
    request_path = environ['PATH_INFO']
    for i in request_list:
        if request_path == i[0]:
            ret = i[1]()
            return [ret]
    else:

        return [b'<h1>404.....</h1>']




httpd = make_server('127.0.0.1', 8848, application)
print('Serving HTTP on port 8080...')
# 开始监听HTTP请求:
httpd.serve_forever()

2.8 起飞版web框架

  上面所有的还不能称为一个框架,真正的框架是分文件开发,也就是咱们在规范化格式目录时学习过的,不同的文件拥有不同的功能,真正的框架就是要分文件各司其职。

static静态文件

  静态文件存放的就是css、js、jQuery等相关的文件,因为这些文件可以分类放置并且每种都可以有很多文件。

urls:路由分发

  我们代码中有一个列表,根据不同的路径执行不同的函数,这个列表就称为路由分发,所以要单独设立一个文件。

template:html文件

  template文件夹就是专门放置html文件的,你们以后的web项目html文件会非常多,所以必须用单独的一个文件夹存放。

 

 

 

views文件:专门放置应用程序的代码,处理业务逻辑。

 

manage文件:专门放置服务器程序的代码,处理http协议,socket等。

 

getdata文件:文件名自己定义,主要方式处理数据库相关逻辑。

 

 

至此,我们自己搭建的起飞版的web框架就算完成了,这个就是所有web框架的雏形,只要你把这个框架研究明白,那么接下来你无论研究什么框架,都易如反掌了。

 

 

posted @ 2020-07-30 01:08  dongye95  阅读(390)  评论(0编辑  收藏  举报