Web框架本质

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

半成品自定义web框架

import socketsk = socket.socket()
sk.bind(("127.0.0.1", 80))
sk.listen()while True:conn, addr = sk.accept()data = conn.recv(8096)conn.send(b"OK")conn.close()

可以说Web服务本质上都是在这十几行代码基础上扩展出来的。这段代码就是它们的祖宗。

用户的浏览器一输入网址,会给服务端发送数据,那浏览器会发送什么数据?怎么发?这个谁来定? 你这个网站是这个规定,他那个网站按照他那个规定,这互联网还能玩么?

所以,必须有一个统一的规则,让大家发送消息、接收消息的时候有个格式依据,不能随便写。

这个规则就是HTTP协议,以后浏览器发送请求信息也好,服务器回复响应信息也罢,都要按照这个规则来。

HTTP协议主要规定了客户端和服务器之间的通信格式,那HTTP协议是怎么规定消息格式的呢?

让我们首先打印下我们在服务端接收到的消息是什么。

import socketsk = 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\r\nHost: 127.0.0.1:8080\r\nConnection: keep-alive\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\r\nDNT: 1\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: zh-CN,zh;q=0.9\r\nCookie: csrftoken=RKBXh1d3M97iz03Rpbojx1bR6mhHudhyX5PszUxxG3bOEwh1lxFpGOgWN93ZH3zv\r\n\r\n'

然后我们再看一下我们访问博客园官网时浏览器收到的响应信息是什么。

响应相关信息可以在浏览器调试窗口的network标签页中看到。

点击view source之后显示如下图:

我们发现收发的消息需要按照一定的格式来,这里就需要了解一下HTTP协议了。

HTTP协议介绍

HTTP协议对收发消息的格式要求

每个HTTP请求和响应都遵循相同的格式,一个HTTP包含Header和Body两部分,其中Body是可选的。 HTTP响应的Header中有一个 Content-Type表明响应的内容格式。如 text/html表示HTML网页。

HTTP GET请求的格式:

HTTP响应的格式:

处女版自定义web框架

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

import socketsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('127.0.0.1', 8000))
sock.listen()while True:conn, addr = sock.accept()data = conn.recv(8096)# 给回复的消息加上响应状态行conn.send(b"HTTP/1.1 200 OK\r\n\r\n")conn.send(b"OK")conn.close()

我们通过十几行代码简单地演示了web 框架的本质。

接下来就让我们继续完善我们的自定义web框架吧!

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

这样就结束了吗? 如何让我们的Web服务根据用户请求的URL不同而返回不同的内容呢?

小事一桩,我们可以从请求相关数据里面拿到请求URL的路径,然后拿路径做一个判断...

"""
根据URL中不同的路径返回不同的内容
"""import socket
sk = socket.socket()
sk.bind(("127.0.0.1", 8080))  # 绑定IP和端口
sk.listen()  # 监听while 1:# 等待连接conn, add = sk.accept()data = conn.recv(8096)  # 接收客户端发来的消息# 从data中取到路径data = str(data, encoding="utf8")  # 把收到的字节类型的数据转换成字符串# 按\r\n分割data1 = data.split("\r\n")[0]url = data1.split()[1]  # url是我们从浏览器发过来的消息中分离出的访问路径conn.send(b'HTTP/1.1 200 OK\r\n\r\n')  # 因为要遵循HTTP协议,所以回复的消息也要加状态行# 根据不同的路径返回不同内容if url == "/index/":response = b"index"elif url == "/home/":response = b"home"else:response = b"404 not found!"conn.send(response)conn.close()

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

上面的代码解决了不同URL路径返回不同内容的需求。

但是问题又来了,如果有很多很多路径要判断怎么办?难道要挨个写if判断? 当然不用,我们有更聪明的办法。

"""
根据URL中不同的路径返回不同的内容--函数版
"""import socket
sk = socket.socket()
sk.bind(("127.0.0.1", 8080))  # 绑定IP和端口
sk.listen()  # 监听# 将返回不同的内容部分封装成函数
def index(url):s = "这是{}页面!".format(url)return bytes(s, encoding="utf8")def home(url):s = "这是{}页面!".format(url)return bytes(s, encoding="utf8")while 1:# 等待连接conn, add = sk.accept()data = conn.recv(8096)  # 接收客户端发来的消息# 从data中取到路径data = str(data, encoding="utf8")  # 把收到的字节类型的数据转换成字符串# 按\r\n分割data1 = data.split("\r\n")[0]url = data1.split()[1]  # url是我们从浏览器发过来的消息中分离出的访问路径conn.send(b'HTTP/1.1 200 OK\r\n\r\n')  # 因为要遵循HTTP协议,所以回复的消息也要加状态行# 根据不同的路径返回不同内容,response是具体的响应体if url == "/index/":response = index(url)elif url == "/home/":response = home(url)else:response = b"404 not found!"conn.send(response)conn.close()

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

看起来上面的代码还是要挨个写if判断,怎么办?我们还是有办法!(只要思想不滑坡,方法总比问题多!)

"""
根据URL中不同的路径返回不同的内容--函数进阶版
"""import socket
sk = socket.socket()
sk.bind(("127.0.0.1", 8080))  # 绑定IP和端口
sk.listen()  # 监听# 将返回不同的内容部分封装成函数
def index(url):s = "这是{}页面!".format(url)return bytes(s, encoding="utf8")def home(url):s = "这是{}页面!".format(url)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")  # 把收到的字节类型的数据转换成字符串# 按\r\n分割data1 = data.split("\r\n")[0]url = data1.split()[1]  # url是我们从浏览器发过来的消息中分离出的访问路径conn.send(b'HTTP/1.1 200 OK\r\n\r\n')  # 因为要遵循HTTP协议,所以回复的消息也要加状态行# 根据不同的路径返回不同内容func = None  # 定义一个保存将要执行的函数名的变量for i in list1:if i[0] == url:func = i[1]breakif func:response = func(url)else:response = b"404 not found!"# 返回具体的响应消息conn.send(response)conn.close()

返回具体的HTML文件

完美解决了不同URL返回不同内容的问题。 但是我不想仅仅返回几个字符串,我想给浏览器返回完整的HTML内容,这又该怎么办呢?

没问题,不管是什么内容,最后都是转换成字节数据发送出去的。 我们可以打开HTML文件,读取出它内部的二进制数据,然后再发送给浏览器。

"""
根据URL中不同的路径返回不同的内容--函数进阶版
返回独立的HTML页面
"""import socket
sk = socket.socket()
sk.bind(("127.0.0.1", 8080))  # 绑定IP和端口
sk.listen()  # 监听# 将返回不同的内容部分封装成函数
def index(url):# 读取index.html页面的内容with open("index.html", "r", encoding="utf8") as f:s = f.read()# 返回字节数据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")  # 把收到的字节类型的数据转换成字符串# 按\r\n分割data1 = data.split("\r\n")[0]url = data1.split()[1]  # url是我们从浏览器发过来的消息中分离出的访问路径conn.send(b'HTTP/1.1 200 OK\r\n\r\n')  # 因为要遵循HTTP协议,所以回复的消息也要加状态行# 根据不同的路径返回不同内容func = None  # 定义一个保存将要执行的函数名的变量for i in list1:if i[0] == url:func = i[1]breakif func:response = func(url)else:response = b"404 not found!"# 返回具体的响应消息conn.send(response)conn.close()

让网页动态起来

这网页能够显示出来了,但是都是静态的啊。页面的内容都不会变化的,我想要的是动态网站。

没问题,我也有办法解决。我选择使用字符串替换来实现这个需求。(这里使用时间戳来模拟动态的数据)

"""
根据URL中不同的路径返回不同的内容--函数进阶版
返回HTML页面
让网页动态起来
"""import socket
import timesk = 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")  # 把收到的字节类型的数据转换成字符串# 按\r\n分割data1 = data.split("\r\n")[0]url = data1.split()[1]  # url是我们从浏览器发过来的消息中分离出的访问路径conn.send(b'HTTP/1.1 200 OK\r\n\r\n')  # 因为要遵循HTTP协议,所以回复的消息也要加状态行# 根据不同的路径返回不同内容func = None  # 定义一个保存将要执行的函数名的变量for i in list1:if i[0] == url:func = i[1]breakif func:response = func(url)else:response = b"404 not found!"# 返回具体的响应消息conn.send(response)conn.close()

好了,在这停顿...

服务器程序和应用程序

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

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

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

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

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

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

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

从这继续...

wsgiref

我们利用wsgiref模块来替换我们自己写的web框架的socket server部分:

"""
根据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']  # 取到用户输入的urlfunc = Nonefor i in list1:if i[0] == url:func = i[1]breakif 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

下载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 Templatedef 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']  # 取到用户输入的urlfunc = None  # 将要执行的函数for i in URL_LIST:if i[0] == url:func = i[1]  # 去之前定义好的url列表里找url应该执行的函数breakif 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()

现在的数据是我们自己手写的,那可不可以从数据库中查询数据,来填充页面呢?

使用pymysql连接数据库:

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

Django官网下载页面

安装(安装最新LTS版):

pip3 install django==1.11.9

创建一个django项目:

下面的命令创建了一个名为"mysite"的Django 项目:

django-admin startproject mysite

目录介绍:

mysite/
├── manage.py  # 管理文件
└── mysite  # 项目目录├── __init__.py├── settings.py  # 配置├── urls.py  # 路由 --> URL和函数的对应关系└── wsgi.py  # runserver命令就使用wsgiref模块做简单的web server

运行Django项目:

python manage.py runserver 127.0.0.1:8000

模板文件配置:

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"),  # 静态文件存放位置
]

看不明白?有图有真相:

刚开始学习时可在配置文件中暂时禁用csrf中间件,方便表单提交测试。

MIDDLEWARE = ['django.middleware.security.SecurityMiddleware','django.contrib.sessions.middleware.SessionMiddleware','django.middleware.common.CommonMiddleware',# 'django.middleware.csrf.CsrfViewMiddleware','django.contrib.auth.middleware.AuthenticationMiddleware','django.contrib.messages.middleware.MessageMiddleware','django.middleware.clickjacking.XFrameOptionsMiddleware',
]

Django基础必备三件套:

from django.shortcuts import HttpResponse, render, redirect

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/")

重定向是怎么回事?

Django框架介绍:

http://www.cnblogs.com/liwenzhou/p/8296964.html

课后练习:

Django版登录

启动Django报错:

Django 启动时报错 UnicodeEncodeError ...

报这个错误通常是因为计算机名为中文,改成英文的计算机名重启下电脑就可以了。

转载于:https://www.cnblogs.com/zheng-xi/p/8808146.html

Python学习-基础篇14 Web框架本质及第一个Django实例相关推荐

  1. python学习随笔--web框架

    Web框架本质 众所周知,对于所有的Web应用,本质上其实就是一个socket服务端,用户的浏览器其实就是一个socket客户端. #!/usr/bin/env python #coding:utf- ...

  2. Python学习-基础篇5 常用模块+类与对象(1)

     #random模块 1 import random2 3 print(random.random())#(0,1)----float 大于0且小于1之间的小数4 5 print(random.ran ...

  3. python学习基础篇Day04(P36~~49)

    b站达内python课学习笔记 P36 Python基础-4.1 课程内容回顾 一.day03复习 """day03 复习语句选择语句if bool类型的条件:满足条件执 ...

  4. Python学习-基础篇4 模块与包与常用模块

    一 模块介绍 1.什么是模块?#常见的场景:一个模块就是一个包含了一组功能的python文件,比如spam.py,模块名为spam,可以通过import spam使用.#在python中,模块的使用方 ...

  5. Python学习-基础篇7 网络编程

    #pytho 基础之socket编程 一 客户端/服务器架构 1.硬件C/S架构(打印机) 2.软件C/S架构 互联网中处处是C/S架构 如黄色网站是服务端,你的浏览器是客户端(B/S架构也是C/S架 ...

  6. python学习基础篇Day02(P11~~23)

    b站达内python课学习笔记 P11 Python基础-2.1 课程回顾 一.复习 python 免费,开源,跨平台,动态,面向对象的编程语言 执行方式 交互式 文件式 执行过程 源代码-编译-&g ...

  7. python学习基础篇Day08(P85~~95)

    b站达内python课学习笔记 P85 Python基础-8.1 课程内容回顾 一.day07复习 """day07 复习能力提升for for# 结论:外层循环执行一次 ...

  8. Python学习-基础篇9 mysql相关

    mysql一:初始数据库 一 数据库管理软件的由来 基于我们之前所学,数据要想永久保存,都是保存于文件中,毫无疑问,一个文件仅仅只能存在于某一台机器上. 如果我们暂且忽略直接基于文件来存取数据的效率问 ...

  9. python学习基础篇——循环

    每次想找自己做过的笔记,总是需要打开电脑,打开vscode,就觉得非常麻烦.为了能够让自己更高效找到笔记,打算将这里作为我的笔记本了.本人还只在python初级学习阶段,非科班出身.虽然建过模(为了毕 ...

最新文章

  1. Java FAQ(slhynju 0.3版)
  2. httpservlet.java:131_Servlet学习1:Http状态码、多个Servlet之间调用规则、多个Servlet之间数据共享实现方案...
  3. JavaScript 中对象的属性类型
  4. 应用基础计算机一级的题目,计算机应用基础一级模拟题
  5. java中类与类之间的关系
  6. sqL编程篇(三) 游标与存储过程
  7. 数据结构相关代码-简介
  8. zookeeper中展示所有节点_zookeeper工作原理与节点使用
  9. UVA10010 Where's Waldorf?【水题】
  10. contents()与children()的用法和区别
  11. Web功能设计:登录与退出
  12. 通过天眼查查询相关企业信息
  13. 百度文心一言推出内测专用独立 App;暴雪回应被网易起诉:未收到相关诉状;iOS 17或支持第三方应用商店 | 极客头条
  14. 3D视觉感知市场规模现状及行业发展前景分析
  15. 从瀑布到敏捷——漫画解读软件开发模式变迁史
  16. R语言 quantmod 如何下载汇率
  17. 大家好!我是刘伦斯,一个喜欢书法、摄影的计算机童鞋
  18. 使用CrawlSpider爬取糗事百科段子
  19. 删除临时文件夹中的特定名称模式目录
  20. Proxy SwitchyOmega,自动代理设置

热门文章

  1. java comparator_一个Java程序员的成长历程(012)天
  2. java arraylist 构造_深入理解java集合框架之---------Arraylist集合 -----构造函数
  3. Linus 发文宣布Linux Kernel 5.0 正式发布
  4. Burpsuite工具使用
  5. linux内核的nfs实现框架
  6. HNUSTOJ-1675 Morse Code(DFS+字典序搜索)
  7. 【大话设计模式】——简单工厂模式
  8. ios运行某些工程时屏幕上下出现黑边的解决办法
  9. 第一次正面交火!版主说博文里有广告!
  10. ASM相关概念及实例创建