Web框架 — Django框架 服务器程序和应用程序 Django

Web框架本质

我们可以这理解:所有的Web应用本质上就是一个socket服务端,而用户的浏览器就是一个socket客户端。

不完善的Web服务端示例

"""
不完善的web服务端示例
"""
import socket
# 生成socket示例对象
sk = socket.socket()
# 绑定IP端口
sk.bind(("127.0.0.1",8080))
# 监听
sk.listen()
# 写一个死循环,一直等待客户端来连接
while True:
    # 获取与客户端的连接
    conn,addr = sk.accept()
    # 接收客户端发来的消息
    data = conn.recv(8096)
    print(data)
    # 给客户端回复消息
    # conn.send(b'http/1.1 200 OK
content-type:text/html;charset=utf-8

')
    # 想让浏览器在页面上显示出来的内容都是响应正文
    conn.send(b'<h1>s10</h1>')

    conn.close()
    sk.close()

HTTP协议就是对浏览器的发送消息和服务端的响应信息作出一个统一的规则

打印在我们在服务端接收到的消息

import socket

sk = socket.socket()
sk.bind(("127.0.0.1", 80))
sk.listen()


while True:
    conn, addr = sk.accept()
    data = conn.recv(8096)
    print(data)  # 将浏览器发来的消息打印出来
    conn.send(b"OK")
    conn.close()

输出

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

'

HTTP GET请求的格式:

Web框架 — Django框架
服务器程序和应用程序
Django

HTTP响应的格式:

Web框架 — Django框架
服务器程序和应用程序
Django

经过上面的补充学习,我们知道了要想让我们自己写的web server端正经起来,必须要让我们的Web server在给客户端回复消息的时候按照HTTP协议的规则加上响应状态行,这样我们就实现了一个正经的Web框架了。

正经的Web框架

"""
完善的web服务端示例
"""
import socket
# 生成socket示例对象
sk = socket.socket()
# 绑定IP端口
sk.bind(("127.0.0.1",8081))
# 监听
sk.listen()
# 写一个死循环,一直等待客户端来连接
while True:
    # 获取与客户端的连接
    conn,addr = sk.accept()
    # 接收客户端发来的消息
    data = conn.recv(8096)
    print(data)
    # 给客户端回复消息
    conn.send(b'http/1.1 200 OK
content-type:text/html;charset=utf-8

')
    # 想让浏览器在页面上显示出来的内容都是响应正文
    conn.send(b'<h1>s10</h1>')

    # conn.close()
    # sk.close()

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

"""
完善的web服务端示例
根据不同的路径返回不同的内容
"""
import socket
# 生成socket示例对象
sk = socket.socket()
# 绑定IP端口
sk.bind(("127.0.0.1",8082))
# 监听
sk.listen()
# 写一个死循环,一直等待客户端来连接
while True:
    # 获取与客户端的连接
    conn,addr = sk.accept()
    # 接收客户端发来的消息
    data = conn.recv(8096)
    data_str = str(data,encoding="utf-8")
    print(data_str)
    # 用
去切割上面的字符串
    l1 = data_str.split("
")
    print(l1[0])
    # 按照空格去切割上面的字符串
    l2 = l1[0].split()
    url = l2[1]
    print(url)

    # 给客户端回复消息
    conn.send(b'http/1.1 200 OK
content-type:text/html;charset=utf-8

')
    # 想让浏览器在页面上显示出来的内容都是响应正文
    if url == "/yimi/":
        response = b'<h1>hello yimi</h1>'
    elif url == "/xiaohei/":
        response = b'<h1>hello xiaohei</h1>'
    else:
        response = b'<h1>404!not fund!</h1>'
    conn.send(response)

    # conn.close()
    # sk.close()

根据不同的路径返回不同的内容——函数版

"""
完善的web服务端示例
函数版根据不同的路径返回不同的内容
"""
import socket
# 生成socket示例对象
sk = socket.socket()
# 绑定IP端口
sk.bind(("127.0.0.1",8083))
# 监听
sk.listen()

# 定义一个处理/yimi/的函数
def yimi(url):
    ret = 'hello {}'.format(url)
    return bytes(ret,encoding="utf-8")
# 定义一个处理/xiaohei/的函数
def xiaohei(url):
    ret = 'hello {}'.format(url)
    return bytes(ret,encoding="utf-8")

# 写一个死循环,一直等待客户端来连接
while True:
    # 获取与客户端的连接
    conn,addr = sk.accept()
    # 接收客户端发来的消息
    data = conn.recv(8096)
    data_str = str(data,encoding="utf-8")
    print(data_str)
    # 用
去切割上面的字符串
    l1 = data_str.split("
")
    print(l1[0])
    # 按照空格去切割上面的字符串
    l2 = l1[0].split()
    url = l2[1]
    print(url)

    # 给客户端回复消息
    conn.send(b'http/1.1 200 OK
content-type:text/html;charset=utf-8

')
    # 想让浏览器在页面上显示出来的内容都是响应正文

    # 根据不同的url返回不同的内容
    if url == "/yimi/":
        response = yimi(url)
    elif url == "/xiaohei/":
        response = xiaohei(url)
    else:
        response = b'<h1>404!not fund!</h1>'
    conn.send(response)

    # conn.close()
    # sk.close()

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

"""
完善的web服务端示例
函数版根据不同的路径返回不同的内容
进阶函数版 不写if判断了,用url名字去找对应的函数名
"""
import socket
# 生成socket示例对象
sk = socket.socket()
# 绑定IP端口
sk.bind(("127.0.0.1",8084))
# 监听
sk.listen()

# 定义一个处理/yimi/的函数
def yimi(url):
    ret = 'hello {}'.format(url)
    return bytes(ret,encoding="utf-8")
# 定义一个处理/xiaohei/的函数
def xiaohei(url):
    ret = 'hello {}'.format(url)
    return bytes(ret,encoding="utf-8")

# 定义一个专门来处理404的函数
def f404(url):
    ret = "你访问的这个{}找不到".format(url)
    return bytes(ret,encoding="utf-8")

url_func = [
    ("/yimi/",yimi),
    ("/xiaohei/",xiaohei),
]

# 写一个死循环,一直等待客户端来连接
while True:
    # 获取与客户端的连接
    conn,addr = sk.accept()
    # 接收客户端发来的消息
    data = conn.recv(8096)
    data_str = str(data,encoding="utf-8")
    print(data_str)
    # 用
去切割上面的字符串
    l1 = data_str.split("
")
    print(l1[0])
    # 按照空格去切割上面的字符串
    l2 = l1[0].split()
    url = l2[1]
    print(url)

    # 给客户端回复消息
    conn.send(b'http/1.1 200 OK
content-type:text/html;charset=utf-8

')
    # 想让浏览器在页面上显示出来的内容都是响应正文

    # 根据不同的url返回不同的内容
    # 去url_func里面找对应关系
    for i in url_func:
        if i[0] == url:
            func = i[1]
            break
    # 找不到对应关系就默认执行404函数
    else:
        func = f404
    # 拿到函数的执行结果
    response = func(url)

    # 将函数返回的结果发给浏览器
    conn.send(response)

    # conn.close()
    # sk.close()

返回具体点HTML文件

"""
完善的web服务端示例
函数版根据不同的路径返回不同的内容
进阶函数版 不写if判断了,用url名字去找对应的函数名
返回html页面
"""
import socket
# 生成socket示例对象
sk = socket.socket()
# 绑定IP端口
sk.bind(("127.0.0.1",8085))
# 监听
sk.listen()

# 定义一个处理/yimi/的函数
def yimi(url):
    with open("yimi.html","rb") as f:
        ret = f.read()
    return ret
# 定义一个处理/xiaohei/的函数
def xiaohei(url):
    with open("xiaohei.html","rb") as f:
        ret = f.read()
    return ret

# 定义一个专门来处理404的函数
def f404(url):
    ret = "你访问的这个{}找不到".format(url)
    return bytes(ret,encoding="utf-8")

url_func = [
    ("/yimi/",yimi),
    ("/xiaohei/",xiaohei),
]

# 写一个死循环,一直等待客户端来连接
while True:
    # 获取与客户端的连接
    conn,addr = sk.accept()
    # 接收客户端发来的消息
    data = conn.recv(8096)
    data_str = str(data,encoding="utf-8")
    print(data_str)
    # 用
去切割上面的字符串
    l1 = data_str.split("
")
    print(l1[0])
    # 按照空格去切割上面的字符串
    l2 = l1[0].split()
    url = l2[1]
    print(url)

    # 给客户端回复消息
    conn.send(b'http/1.1 200 OK
content-type:text/html;charset=utf-8

')
    # 想让浏览器在页面上显示出来的内容都是响应正文

    # 根据不同的url返回不同的内容
    # 去url_func里面找对应关系
    for i in url_func:
        if i[0] == url:
            func = i[1]
            break
    # 找不到对应关系就默认执行404函数
    else:
        func = f404
    # 拿到函数的执行结果
    response = func(url)

    # 将函数返回的结果发给浏览器
    conn.send(response)

    # conn.close()
    # sk.close()

让网页动起来

"""
根据URL中不同的路径返回不同的内容--函数进阶版
返回HTML页面
让网页动态起来
"""

import socket
import time

sk = socket.socket()
sk.bind(("127.0.0.1", 8080))  # 绑定IP和端口
sk.listen()  # 监听


# 将返回不同的内容部分封装成函数
def index(url):
    with open("index.html", "r", encoding="utf8") as f:
        s = f.read()
        now = str(time.time())
        s = s.replace("@@oo@@", now)  # 在网页中定义好特殊符号,用动态的数据去替换提前定义好的特殊符号
    return bytes(s, encoding="utf8")


def home(url):
    with open("home.html", "r", encoding="utf8") as f:
        s = f.read()
    return bytes(s, encoding="utf8")


# 定义一个url和实际要执行的函数的对应关系
list1 = [
    ("/index/", index),
    ("/home/", home),
]

while 1:
    # 等待连接
    conn, add = sk.accept()
    data = conn.recv(8096)  # 接收客户端发来的消息
    # 从data中取到路径
    data = str(data, encoding="utf8")  # 把收到的字节类型的数据转换成字符串
    # 按
分割
    data1 = data.split("
")[0]
    url = data1.split()[1]  # url是我们从浏览器发过来的消息中分离出的访问路径
    conn.send(b'HTTP/1.1 200 OK

')  # 因为要遵循HTTP协议,所以回复的消息也要加状态行
    # 根据不同的路径返回不同内容
    func = None  # 定义一个保存将要执行的函数名的变量
    for i in list1:
        if i[0] == url:
            func = i[1]
            break
    if func:
        response = func(url)
    else:
        response = b"404 not found!"

    # 返回具体的响应消息
    conn.send(response)
    conn.close()

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

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

wsgiref

"""
根据URL中不同的路径返回不同的内容--函数进阶版
返回HTML页面
让网页动态起来
wsgiref模块版
"""

import time
from wsgiref.simple_server import make_server


# 将返回不同的内容部分封装成函数
def index(url):
    with open("index.html", "r", encoding="utf8") as f:
        s = f.read()
        now = str(time.time())
        s = s.replace("@@oo@@", now)
    return bytes(s, encoding="utf8")


def home(url):
    with open("home.html", "r", encoding="utf8") as f:
        s = f.read()
    return bytes(s, encoding="utf8")


# 定义一个url和实际要执行的函数的对应关系
list1 = [
    ("/index/", index),
    ("/home/", home),
]


def run_server(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ])  # 设置HTTP响应的状态码和头信息
    url = environ['PATH_INFO']  # 取到用户输入的url
    func = None
    for i in list1:
        if i[0] == url:
            func = i[1]
            break
    if func:
        response = func(url)
    else:
        response = b"404 not found!"
    return [response, ]


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

jinja2

上面的代码实现了一个简单的动态,我完全可以从数据库中查询数据,然后去替换我html中的对应内容,然后再发送给浏览器完成渲染。 这个过程就相当于HTML模板渲染数据。 本质上就是HTML内容中利用一些特殊的符号来替换要展示的数据。 我这里用的特殊符号是我定义的,其实模板渲染有个现成的工具: 

下载jinja2

pip install jinja2

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

使用jinja2渲染index2.html文件:

from wsgiref.simple_server import make_server
from jinja2 import Template


def index():
    with open("index2.html", "r") as f:
        data = f.read()
    template = Template(data)  # 生成模板文件
    ret = template.render({"name": "Alex", "hobby_list": ["烫头", "泡吧"]})  # 把数据填充到模板里面
    return [bytes(ret, encoding="utf8"), ]


def home():
    with open("home.html", "rb") as f:
        data = f.read()
    return [data, ]


# 定义一个url和函数的对应关系
URL_LIST = [
    ("/index/", index),
    ("/home/", home),
]


def run_server(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ])  # 设置HTTP响应的状态码和头信息
    url = environ['PATH_INFO']  # 取到用户输入的url
    func = None  # 将要执行的函数
    for i in URL_LIST:
        if i[0] == url:
            func = i[1]  # 去之前定义好的url列表里找url应该执行的函数
            break
    if func:  # 如果能找到要执行的函数
        return func()  # 返回函数的执行结果
    else:
        return [bytes("404没有该页面", encoding="utf8"), ]


if __name__ == '__main__':
    httpd = make_server('', 8000, run_server)
    print("Serving HTTP on port 8000...")
    httpd.serve_forever()

使用python来连接数据库

conn = pymysql.connect(host="127.0.0.1", port=3306, user="root", passwd="xxx", db="xxx", charset="utf8")
cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
cursor.execute("select name, age, department_id from userinfo")
user_list = cursor.fetchall()
cursor.close()
conn.close()

创建一个测试用的user表

CREATE TABLE user(
  id int auto_increment PRIMARY KEY,
  name CHAR(10) NOT NULL,
  hobby CHAR(20) NOT NULL
)engine=innodb DEFAULT charset=UTF8;

模板的原理就是字符串替换,我们只要在HTML页面中遵循jinja2的语法规则写上,其内部就会按照指定的语法进行相应的替换,从而达到动态的返回内容。

Django

安装

pip3 install django==1.11.9

创建一个django项目

django-admin startproject mysite

运行Django项目

python manage.py runserver 127.0.0.1:8080

模板文件配置

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, "template")],  # template文件夹位置
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

静态文件配置

STATIC_URL = '/static/'  # HTML中使用的静态文件夹前缀
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, "static"),  # 静态文件存放位置
]

HttpResponse

内部传入一个字符串参数,返回给浏览器。

例如:

def index(request):
    # 业务逻辑代码
    return HttpResponse("OK")

render

除request参数外还接受一个待渲染的模板文件和一个保存具体数据的字典参数。

将数据填充进模板文件,最后把结果返回给浏览器。(类似于我们上面用到的jinja2)

例如:

def index(request):
    # 业务逻辑代码
    return render(request, "index.html", {"name": "alex", "hobby": ["烫头", "泡吧"]})

redirect

接受一个URL参数,表示跳转到指定的URL。

例如:

def index(request):
    # 业务逻辑代码
    return redirect("/home/")