编程的三部分: 使用数据 存储数据 展示数据

1. 在浏览器中输入URL并回车后都发生了什么?

  • 浏览器连接网页服务器
  • 浏览器向服务端发送了一条消息
  • 网页服务器接收到消息
  • 网页服务器向浏览器发送消息(html文件)
  • 浏览器展示页面

2.使浏览器 和 网页服务器的说明

  • 网页服务器相当于 socket 服务端
  • 浏览器相当于 socket 客户端

3.本质:

  • web框架的本质
    • 本质上:通过http协议使用 socket 互发消息

  • 不同的URL返回不同的页面
    • 本质上:根据路径的不同返回不同的html文件内容

  • 动态网页(html模板渲染)
    • 本质上:字符串的替换

4. Python Web框架的分类:

  • A. socket -> 服务器程序(专门用来处理 socket 收发消息
  • B. 根据不同的URL路径执行不同的函数 -> 应用程序(处理业务逻辑,控制服务器程序要返回的消息的具体内容
  • C. 动态页面(html模板渲染)

  • Django -> 第三方的 A, 框架自带 B、C
  • Flask -> 第三方的 A、C, 框架自带 B
  • Tornado -> 框架自带 A、B、C

3. 使用 socket 模拟上网

import socket

sk = socket.socket()
sk.bind(('127.0.0.1', 8080))
sk.listen()

while 1:
    conn, addr = sk.accept()
    data = conn.recv(2048)
    print(data)
    conn.send(b'HTTP/1.1 200 OK\r\n\r\n')  # 因为要遵循HTTP协议,所以要在回复消息之前先发送状态行消息 -> 这里就相当于使用 socket 发送文件的例子中的报头(即:接收文件前要知道文件的名称/大小/类型,才可以开始接收文件,如果不懂的就看回 socket 中的报头)
    conn.send(b'<h1>Hello</h1>')
    conn.send(b'<a href="http://www.baidu.com">baidu</a>')
    conn.close()

# socket 服务端所接收到消息 -> 其实服务端所接收到的就是网页请求头的内容

b'GET / HTTP/1.1\r\nHost: 127.0.0.1:8080\r\nConnection: keep-alive\r\nPragma: no-cache\r\nCache-Control: no-cache\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: zh-CN,zh;q=0.9\r\n\r\n'

# 优化返回的信息 -> \r\n 代表着回车

b'GET / HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9

'


# 浏览器访问 127.0.0.1:8080 -> 因为浏览器相当于 socket 客户端,所以不用编写 socket 客户端


4. 根据不同的路径返回不同的内容

import socket

sk = socket.socket()
sk.bind(('127.0.0.1', 8080))
sk.listen()

while 1:
    conn, addr = sk.accept()
    data = conn.recv(2048)  # 接收浏览器发送过来的消息

 # 从浏览器发送过来的消息中获取路径
    str_data = str(data, encoding='utf-8')  # 把bytes类型转换为字符串类型
    path = str_data.split()[1]  # /index/

    conn.send(b'HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\n\r\n')  # 需要设置为 utf-8 不然浏览器会以系统默认的 GBK 编码对发送过来的内容进行编译,这样就会出现乱码

 # 根据不同的路径返回不同的内容
    if path == '/index/':
        response = bytes('<h1>index 页面</h1>', encoding='utf-8')
    elif path == '/login/':
        response = bytes('<h1>login 页面</h1>', encoding='utf-8')
    else:
        response = bytes('<h1>404 not found!</h1>', encoding='utf-8')

    conn.send(response)  # 向浏览器发送具体响应体(内容)
    conn.close()


4. 根据不同的路径返回不同的内容 -- 函数版

import socket

sk = socket.socket()
sk.bind(('127.0.0.1', 8080))
sk.listen()


# 将返回不同内容的部分封装成函数
def index(path):
    s = '<h1>这是{}页面!</h1>'.format(path)
    return bytes(s, encoding='utf-8')


def login(path):
    s = '<h1>这是{}页面!</h1>'.format(path)
    return bytes(s, encoding='utf-8')


while 1:
    conn, addr = sk.accept()
    data = conn.recv(2048)  # 接收浏览器发送过来的消息

# 从浏览器发送过来的消息中获取路径
    str_data = str(data, encoding='utf-8')  # 把bytes类型转换为字符串类型
    path = str_data.split()[1]  # /index/

    conn.send(b'HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\n\r\n')  # 需要设置为 utf-8 不然浏览器会以系统默认的 GBK 编码对发送过来的内容进行编译,这样就会出现乱码

# 根据不同的路径返回不同的内容
    if path == '/index/':
        response = index(path)
    elif path == '/login/':
        response = login(path)
    else:
        response = bytes('<h1>404 not found!</h1>', encoding='utf-8')

    conn.send(response)  # 向浏览器发送具体响应体(内容)
    conn.close()

5. 根据不同的路径返回不同的内容 -- 函数版进阶版

import socket

sk = socket.socket()
sk.bind(('127.0.0.1', 8080))
sk.listen()


# 将返回不同内容的部分封装成函数
def index(path):
    s = '<h1>这是{}页面!</h1>'.format(path)
    return bytes(s, encoding='utf-8')


def login(path):
    s = '<h1>这是{}页面!</h1>'.format(path)
    return bytes(s, encoding='utf-8')


# 使用反射解决每次都要更新判断的问题
lis = [
    ('/index/', index),
    ('/login/', login)
]

while 1:
    conn, addr = sk.accept()
    data = conn.recv(2048)  # 接收浏览器发送过来的消息

    # 从浏览器发送过来的消息中获取路径
    str_data = str(data, encoding='utf-8')  # 把bytes类型转换为字符串类型
    path = str_data.split()[1]  # /index/

    conn.send(b'HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\n\r\n')  # 需要设置为 utf-8 不然浏览器会以系统默认的 GBK 编码对发送过来的内容进行编译,这样就会出现乱码

# 根据不同的路径返回不同的内容
 # 使用反射解决每次都要更新判断的问题
    func = None
    for i in lis:
        if i[0] == path:
            func = i[1]
            break

    if func:
        response = func(path)
    else:
        response = bytes('<h1>404 not found!</h1>', encoding='utf-8')

    conn.send(response)  # 向浏览器发送具体响应体(内容)
    conn.close()

6. 根据不同的路径返回具体的html文件

import socket

sk = socket.socket()
sk.bind(('127.0.0.1', 8080))
sk.listen()


# 将返回不同内容的部分封装成函数
def index(path):
    with open('index.html', encoding='utf-8') as f:  
        s = f.read()  # 读取 index.html 的内容
    return bytes(s, encoding='utf-8')  # 返回 bytes类型的 html 内容


def login(path):
    with open('login.html', encoding='utf-8') as f:
        s = f.read()  # 读取 login.html 的内容
    return bytes(s, encoding='utf-8')  # 返回 bytes类型的 html 内容


# 使用反射解决每次都要更新判断的问题
lis = [
    ('/index/', index),
    ('/login/', login)
]

while 1:
    conn, addr = sk.accept()
    data = conn.recv(2048)  # 接收浏览器发送过来的消息

# 从浏览器发送过来的消息中获取路径
    str_data = str(data, encoding='utf-8')  # 把bytes类型转换为字符串类型
    path = str_data.split()[1]  # /index/

    conn.send(b'HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\n\r\n')  # 需要设置为 utf-8 不然浏览器会以系统默认的 GBK 编码对发送过来的内容进行编译,这样就会出现乱码

 # 根据不同的路径返回不同的内容
    # 使用反射解决每次都要更新判断的问题
    func = None
    for i in lis:
        if i[0] == path:
            func = i[1]
            break

    if func:
        response = func(path)
    else:
        response = bytes('<h1>404 not found!</h1>', encoding='utf-8')

    conn.send(response)  # 向浏览器发送具体响应体(内容)
    conn.close()

# index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <title>index</title>
    <meta name="description" content="">
    <meta name="keywords" content="">
    <link rel="stylesheet" href="">
    <script type="text/javascript" src=''></script>
</head>
<body>
<h1>这是index页面</h1>
<script type="text/javascript"></script>
</body>
</html>

# login.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <title>login</title>
    <meta name="description" content="">
    <meta name="keywords" content="">
    <link rel="stylesheet" href="">
    <script type="text/javascript" src=''></script>
</head>
<body>
<h1>这是login页面</h1>
<script type="text/javascript"></script>
</body>
</html>

7. 动态网页(html模板渲染)

import socket
import time

sk = socket.socket()
sk.bind(('127.0.0.1', 8080))
sk.listen()


# 将返回不同内容的部分封装成函数
def index(path):
    with open('index.html', encoding='utf-8') as f:  
        s = f.read()  # 读取 index.html 的内容
now = str(time.time())
        s = s.replace('@@xx@@', now)  # 在网页中定义好特殊符号,用动态的数据去替换提前定义好的特殊符号
    return bytes(s, encoding='utf-8')  # 返回 bytes类型的 html 内容


def login(path):
    with open('login.html', encoding='utf-8') as f:
        s = f.read()  # 读取 login.html 的内容
    return bytes(s, encoding='utf-8')  # 返回 bytes类型的 html 内容


# 使用反射解决每次都要更新判断的问题
lis = [
    ('/index/', index),
    ('/login/', login)
]

while 1:
    conn, addr = sk.accept()
    data = conn.recv(2048)  # 接收浏览器发送过来的消息

# 从浏览器发送过来的消息中获取路径
    str_data = str(data, encoding='utf-8')  # 把bytes类型转换为字符串类型
    path = str_data.split()[1]  # /index/

    conn.send(b'HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\n\r\n')  # 需要设置为 utf-8 不然浏览器会以系统默认的 GBK 编码对发送过来的内容进行编译,这样就会出现乱码

 # 根据不同的路径返回不同的内容
    # 使用反射解决每次都要更新判断的问题
    func = None
    for i in lis:
        if i[0] == path:
            func = i[1]
            break

    if func:
        response = func(path)
    else:
        response = bytes('<h1>404 not found!</h1>', encoding='utf-8')

    conn.send(response)  # 向浏览器发送具体响应体(内容)
    conn.close()

# index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <title>index</title>
    <meta name="description" content="">
    <meta name="keywords" content="">
    <link rel="stylesheet" href="">
    <script type="text/javascript" src=''></script>
</head>
<body>
<h1>时间戳:@@xx@@</h1>
<script type="text/javascript"></script>
</body>
</html>


8. 服务器程序 和 应用程序

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

  • 服务器程序: 主要负责对socket服务器进行封装,并在请求到来时,对请求的各种数据进行整理(通俗理解: 专门用来处理 socket 收发消息),例如:wsgiref(Python的内置模块)

  • 应用程序: 主要负责具体的逻辑处理(通俗理解: 处理业务逻辑,控制服务器程序要返回的消息的具体内容)。为了方便应用程序的开发,就出现了众多的Web框架,例如:Django、Flask、web.py 等。不同的框架有不同的开发方式,但是无论如何,开发出的应用程序都要和服务器程序配合,才能为用户提供服务

由于出现了众多的Web框架,服务器程序就需要为不同的框架提供不同的支持。这样混乱的局面无论对于服务器还是框架,都是不好的。对服务器来说,需要支持各种不同框架,对框架来说,只有支持它的服务器才能被开发出的应用使用。

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

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

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

WSGI 没有并发的功能,只能排队执行



9. wsgiref 服务器程序模块

  • wsgiref模块是一个遵循 wsgi 协议处理请求的服务器程序模块

  • 利用wsgiref模块来替换我们自己写的web框架的 socket 部分的服务区程序

import time
from wsgiref.simple_server import make_server


# 将返回不同内容的部分封装成函数
def index(path):
    with open('index.html', encoding='utf-8') as f:
        s = f.read()  # 读取 index.html 的内容
        now = str(time.time())
        s = s.replace('@@xx@@', now)  # 在网页中定义好特殊符号,用动态的数据去替换提前定义好的特殊符号
    return bytes(s, encoding='utf-8')  # 返回 bytes类型的 html 内容


def login(path):
    with open('login.html', encoding='utf-8') as f:
        s = f.read()  # 读取 login.html 的内容
    return bytes(s, encoding='utf-8')  # 返回 bytes类型的 html 内容


# 使用反射解决每次都要更新判断的问题
lis = [
    ('/index/', index),
    ('/login/', login)
]


# 使用wsgiref模块帮我们收发浏览器的消息
def run_server(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ])  # 设置HTTP响应的状态码和头信息
    path = environ['PATH_INFO']  # 取到用户输入的url

# 根据不同的路径返回不同的内容
    # 使用反射解决每次都要更新判断的问题
    func = None
    for i in lis:
        if i[0] == path:
            func = i[1]
            break

    if func:
        response = func(path)
    else:
        response = bytes('<h1>404 not found!</h1>', encoding='utf-8')

    return [response]


if __name__ == '__main__':
    httpd = make_server('127.0.0.1', 8080, run_server)
    print("我在8080等你哦...")
    httpd.serve_forever()

10. jinja2 模块

  • jinja2 模块一般用于html模板渲染,和上面所写的动态网页例子是一样的

# jinja2 的安装

pip3 install jinja2 -i https://pypi.douban.com/simple # 使用豆瓣的镜像

from wsgiref.simple_server import make_server
import jinja2


# 将返回不同内容的部分封装成函数
def index(path):
    with open('index.html', encoding='utf-8') as f:
        data = f.read()  # 读取 index.html 的内容

    template = jinja2.Template(data)  # 生成模板文件
    s = template.render({"name": "Alex", "hobby_list": ["烫头", "泡吧"]})  # 把数据填充到模板里

    return bytes(s, encoding='utf-8')  # 返回 bytes类型的 html 内容


def login(path):
    with open('login.html', encoding='utf-8') as f:
        s = f.read()  # 读取 login.html 的内容
    return bytes(s, encoding='utf-8')  # 返回 bytes类型的 html 内容


# 使用反射解决每次都要更新判断的问题
lis = [
    ('/index/', index),
    ('/login/', login)
]


def run_server(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ])  # 设置HTTP响应的状态码和头信息
    path = environ['PATH_INFO']  # 取到用户输入的url

# 根据不同的路径返回不同的内容
    # 使用反射解决每次都要更新判断的问题
    func = None
    for i in lis:
        if i[0] == path:
            func = i[1]
            break

    if func:
        response = func(path)
    else:
        response = bytes('<h1>404 not found!</h1>', encoding='utf-8')

    return [response]


if __name__ == '__main__':
    httpd = make_server('127.0.0.1', 8080, run_server)
    print("我在8080等你哦...")
    httpd.serve_forever()

# index.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>index</title>
</head>
<body>
<h1>姓名:{{name}}</h1>
<h1>爱好:</h1>
<ul>
    {% for hobby in hobby_list %}
    <li>{{hobby}}</li>
    {% endfor %}
</ul>
</body>
</html>