django框架介绍安装-自写框架
原文链接:https://www.cnblogs.com/maple-shaw/p/8862330.html
Web框架本质
我们可以这样理解:所有的Web应用本质上就是一个socket服务端,而用户的浏览器就是一个socket客户端。 这样我们就可以自己实现Web框架了。
import socket sk = socket.socket() sk.bind(("127.0.0.1", 8000)) sk.listen() while True: conn, addr = sk.accept() data = conn.recv(8096) conn.send(b"OK") conn.close()
sk.bind(("127.0.0.1", 8000)) 当绑定为8000的时候,浏览器报错;ERR_INVALID_HTTP_RESPONSE。无效的响应,响应格式有问题
socket服务端
- import socket
- sk = socket.socket()
- sk.bind(("127.0.0.1", 8000))
- sk.listen()
- while True:
- conn, addr = sk.accept()
- data = conn.recv(8096)
- conn.send(b"OK")
- conn.close()
如果将8000端口改为80,就能浏览器就能显示出ok,但是我们想要的是其它端口也能显示内容
可以说Web服务本质上都是在这十几行代码基础上扩展出来的。这段代码就是它们的祖宗。
用户在浏览器中输入网址,浏览器会向服务端发送数据,那浏览器会发送什么数据?怎么发?这个谁来定? 你这个网站是这个规定,他那个网站按照他那个规定,那互联网还能玩么?
所以,必须有一个统一的规则,让大家发送消息、接收消息的时候都有个格式依据,不能随便写。
这个规则就是HTTP协议,以后浏览器发送请求信息也好,服务器回复响应信息也罢,都要按照这个规则来。
HTTP协议主要规定了客户端和服务器之间的通信格式,那HTTP协议是怎么规定消息格式的呢?
让我们首先打印下我们在服务端接收到的消息是什么。
- import socket
- sk = socket.socket()
- sk.bind(("127.0.0.1", 8000))
- sk.listen()
- while True:
- conn, addr = sk.accept()
- data = conn.recv(8096)
- print(data) # 将浏览器发来的消息打印出来
- conn.send(b"OK")
- conn.close()
输出:
接收回来的是bytes类型,打印解码的为(字符串类型,换行了):
print(data.decode()) # 将浏览器发来的消息打印出来
b'GET / HTTP/1.1\r\nHost: 127.0.0.1:8080\r\nConnection: keep-alive\r\nCache-Control: max-age=0\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3355.4 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\nCookie: csrftoken=CtHePYARJOKNx5oNVwxIteOJXpNyJ29L4bW4506YoVqFaIFFaHm0EWDZqKmw6Jm8\r\n\r\n'
我们将\r\n替换成换行看得更清晰点:
- GET / HTTP/1.1
- Host: 127.0.0.1:8080
- Connection: keep-alive
- Cache-Control: max-age=0
- Upgrade-Insecure-Requests: 1
- User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3355.4 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
- Cookie: csrftoken=CtHePYARJOKNx5oNVwxIteOJXpNyJ29L4bW4506YoVqFaIFFaHm0EWDZqKmw6Jm8
然后我们再看一下我们访问博客园官网时浏览器收到的响应信息是什么。
响应相关信息可以在浏览器调试窗口的Network标签页中看到。响应数据不是都能看的view sourse
点击view source之后显示如下图:
response是请求的数据体部分,复制粘贴到自己创建的socket里,让它发送给浏览器
当发送给浏览器的为HTML时:conn.send(b"HTTP/1.1 200 OK\r\n\r\n <h1>ok</h1> ")。渲染了
我们发现收发的消息需要按照一定的格式来,这里就需要了解一下HTTP协议了。
HTTP协议对收发消息的格式要求
每个HTTP请求和响应都遵循相同的格式,一个HTTP包含Header和Body两部分,其中Body是可选的。
HTTP响应的Header中有一个 Content-Type
表明响应的内容格式。它的值如text/html; charset=utf-8。
text/html则表示是网页,charset=utf-8则表示编码为utf-8。
HTTP GET请求的格式:
HTTP响应的格式:
自定义web框架
经过上面的学习,那我们基于socket服务端的十几行代码写一个我们自己的web框架。我们先不处理浏览器发送的请求,先让浏览器能显示我们web框架返回的信息,那我们就要按照HTTP协议的格式来发送响应。
- import socket
- sock = 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 True:
- # 等待连接
- 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()
当浏览器访问对应的目录时,后台显示/home,我们要根据访问的目录而send给浏览器不同的数据。
获取路径:
data = conn.recv(8096)
print(data.decode().split()) # 将浏览器发来的消息打印出来
根据索引打印
print(data.decode().split()[1]) # 将浏览器发来的消息打印出来
favicon.ico不用管,是谷歌浏览器 的title
因为获取到的有斜线,而这里判断是没有斜线的字符串,所以么有匹配成功。正则的话有米有都能实现也可以的。
先发请求头,再发数据,分两次发。
根据不同的路径返回不同的内容--函数版
上面的代码解决了不同URL路径返回不同内容的需求。
我们返回的内容是简单的几个字符,那如果我可以将返回的结果封装成一个函数呢?
- """
- 根据URL中不同的路径返回不同的内容--函数版
- """
- import socket
- sk = socket.socket()
- sk.bind(("127.0.0.1", 8080)) # 绑定IP和端口
- sk.listen() # 监听
- # 将返回不同的内容部分封装成函数
- def func(url):
- s = "这是{}页面!".format(url)
- return bytes(s, encoding="utf8")
- while True:
- # 等待连接
- 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 = func(url)
- elif url == "/home/":
- response = func(url)
- else:
- response = b"404 not found!"
- conn.send(response)
- conn.close()
def func(url): s = "this is {} page !".format(url) return bytes(s, encoding="utf8")
根据不同的路径返回不同的内容--函数进阶版
看起来上面的代码写了一个函数,那肯定可以写多个函数,不同的路径对应执行不同的函数拿到结果,但是我们要一个个判断路径,是不是很麻烦?我们有简单的办法来解决。
- """
- 根据URL中不同的路径返回不同的内容--函数进阶版
- """
- import socket
- sk = socket.socket()
- sk.bind(("127.0.0.1", 8080)) # 绑定IP和端口
- sk.listen() # 监听
- # 将返回不同的内容部分封装成不同的函数
- def index(url):
- s = "这是{}页面XX!".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 True:
- # 等待连接
- 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 item in list1:
- if item[0] == url:
- func = item[1]
- break
- if func:
- response = func(url)
- else:
- response = b"404 not found!"
- # 返回具体的响应消息
- conn.send(response)
- conn.close()
分析:
1)定义多个函数,将取出url后面的访问目录传入到函数中,执行函数。
2)定义一个列表,列表中有很多个字符串和函数名对应的元组
3)定义一个变量为None,循环列表元素,判断元素第一个字元素是否为url中取出的的访问文件,是就让变量重新赋值为这个这个元素的第二个字元素,即这个变量等于4一个定义好的函数名。
4)如果func是真,访问url中的匹配成功,那么就执行这个函数并用返回数据变量接收函数返回值,否则让请求返回的变量是404未找到。
5)然后发送请求返回的数据给浏览器。这样就实现了一个框架。
返回具体的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 True:
- # 等待连接
- 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 item in list1:
- if item[0] == url:
- func = item[1]
- break
- if func:
- response = func(url)
- else:
- response = b"404 not found!"
- # 返回具体的响应消息
- conn.send(response)
- conn.close()
<!DOCTYPE html> <html lang="en"> <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> <div>这是index页面</div> </body> </html>
<!DOCTYPE html> <html lang="en"> <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> <div>这是home页面</div> </body> </html>
1)将之前的请求过来执行的函数中,函数的返回值由字符串改为返回对应html的文件内容。
2)实现方法是在函数中将文件内容都读取出来,然后由之前的函数中定义的字符串改为返回读取出来的文件数据。
3)后面程序与上的相同,这样就实现了根据不同url执行不同的函数,返回不同的数据给浏览器:
2)定义一个列表,列表中有很多个字符串和函数名对应的元组
3)定义一个变量为None,循环列表元素,判断元素第一个字元素是否为url中取出的的访问文件,是就让变量重新赋值为这个这个元素的第二个字元素,即这个变量等于4一个定义好的函数名。
4)如果func是真,访问url中的匹配成功,那么就执行这个函数并用返回数据变量接收函数返回值,否则让请求返回的变量是404未找到。
5)然后发送请求返回的数据给浏览器。这样就实现了一个框架。
让网页动态起来
这网页能够显示出来了,但是都是静态的啊。页面的内容都不会变化的,我想要的是动态网站。
没问题,我也有办法解决。我选择使用字符串替换来实现这个需求。(这里使用时间戳来模拟动态的数据)
- """
- 根据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")
- def timer(url):
- import time
- with open("time.html", "r", encoding="utf8") as f:
- s = f.read()
- s = s.replace('@@time@@', time.strftime("%Y-%m-%d %H:%M:%S"))
- return bytes(s, encoding="utf8")
- # 定义一个url和实际要执行的函数的对应关系
- list1 = [
- ("/index/", index),
- ("/home/", home),
- ("/time/", timer),
- ]
- while True:
- # 等待连接
- 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 item in list1:
- if item[0] == url:
- func = item[1]
- break
- if func:
- response = func(url)
- else:
- response = b"404 not found!"
- # 返回具体的响应消息
- conn.send(response)
- conn.close()
<!DOCTYPE html> <html lang="en"> <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>time</title> </head> <body> <div>当前时间是:@@time@@</div> </body> </html>
<div>当前时间是:@@time@@</div> 在代码中将文件读取出来,然后字符串替换成当前的时间,然后作为函数返回值编码发送给访问这个地址的请求。实现网页中数据不固定,用数据渲染显示页面。
服务器程序和应用程序
对于真实开发中的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模块版
- """
- from wsgiref.simple_server import make_server
- # 将返回不同的内容部分封装成函数
- 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")
- def timer(url):
- import time
- with open("time.html", "r", encoding="utf8") as f:
- s = f.read()
- s = s.replace('@@time@@', time.strftime("%Y-%m-%d %H:%M:%S"))
- return bytes(s, encoding="utf8")
- # 定义一个url和实际要执行的函数的对应关系
- list1 = [
- ("/index/", index),
- ("/home/", home),
- ("/time/", timer),
- ]
- 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()
{ 'ALLUSERSPROFILE': 'C:\\ProgramData', 'APPDATA': 'C:\\Users\\Administrator\\AppData\\Roaming', 'COMMONPROGRAMFILES': 'C:\\Program Files (x86)\\Common Files', 'COMMONPROGRAMFILES(X86)': 'C:\\Program Files (x86)\\Common Files', 'COMMONPROGRAMW6432': 'C:\\Program Files\\Common Files', 'COMPUTERNAME': 'PC-20190328RVNA', 'COMSPEC': 'C:\\Windows\\system32\\cmd.exe', 'FP_NO_HOST_CHECK': 'NO', 'HOMEDRIVE': 'C:', 'HOMEPATH': '\\Users\\Administrator', 'LOCALAPPDATA': 'C:\\Users\\Administrator\\AppData\\Local', 'LOGONSERVER': '\\\\PC-20190328RVNA', 'NUMBER_OF_PROCESSORS': '4', 'OS': 'Windows_NT', 'PATH': 'C:\\mcw\\venv\\Scripts;C:\\Windows\\system32;C:\\Windows;C:\\Windows\\System32\\Wbem;C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\;C:\\软件安装\\Git\\cmd;C:\\mysql\\mysql-5.6.44-winx64\\bin;C:\\python3\\Scripts\\;C:\\python3\\', 'PATHEXT': '.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC', 'PROCESSOR_ARCHITECTURE': 'x86', 'PROCESSOR_ARCHITEW6432': 'AMD64', 'PROCESSOR_IDENTIFIER': 'Intel64 Family 6 Model 58 Stepping 9, GenuineIntel', 'PROCESSOR_LEVEL': '6', 'PROCESSOR_REVISION': '3a09', 'PROGRAMDATA': 'C:\\ProgramData', 'PROGRAMFILES': 'C:\\Program Files (x86)', 'PROGRAMFILES(X86)': 'C:\\Program Files (x86)', 'PROGRAMW6432': 'C:\\Program Files', 'PROMPT': '(venv) $P$G', 'PSMODULEPATH': 'C:\\Windows\\system32\\WindowsPowerShell\\v1.0\\Modules\\', 'PUBLIC': 'C:\\Users\\Public', 'PYCHARM_HOSTED': '1', 'PYCHARM_MATPLOTLIB_PORT': '59585', 'PYTHONIOENCODING': 'UTF-8', 'PYTHONPATH': 'C:\\mcw;C:\\软件安装\\PyCharm 2018.3.5\\helpers\\pycharm_matplotlib_backend', 'PYTHONUNBUFFERED': '1', 'SESSIONNAME': 'Console', 'SYSTEMDRIVE': 'C:', 'SYSTEMROOT': 'C:\\Windows', 'TEMP': 'C:\\Users\\ADMINI~1\\AppData\\Local\\Temp', 'TMP': 'C:\\Users\\ADMINI~1\\AppData\\Local\\Temp', 'USERDOMAIN': 'PC-20190328RVNA', 'USERNAME': 'Administrator', 'USERPROFILE': 'C:\\Users\\Administrator', 'VIRTUAL_ENV': 'C:\\mcw\\venv', 'WINDIR': 'C:\\Windows', 'WINDOWS_TRACING_FLAGS': '3', 'WINDOWS_TRACING_LOGFILE': 'C:\\BVTBin\\Tests\\installpackage\\csilogfile.log', '_OLD_VIRTUAL_PATH': 'C:\\Windows\\system32;C:\\Windows;C:\\Windows\\System32\\Wbem;C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\;C:\\杞\ue219欢瀹夎\ue5ca\\Git\\cmd;C:\\mysql\\mysql-5.6.44-winx64\\bin;C:\\python3\\Scripts\\;C:\\python3\\', '_OLD_VIRTUAL_PROMPT': '$P$G', 'SERVER_NAME': 'PC-20190328RVNA', 'GATEWAY_INTERFACE': 'CGI/1.1', 'SERVER_PORT': '8090', 'REMOTE_HOST': '', 'CONTENT_LENGTH': '', 'SCRIPT_NAME': '', 'SERVER_PROTOCOL': 'HTTP/1.1', 'SERVER_SOFTWARE': 'WSGIServer/0.2', 'REQUEST_METHOD': 'GET', 'PATH_INFO': '/time/', 'QUERY_STRING': '', 'REMOTE_ADDR': '127.0.0.1', 'CONTENT_TYPE': 'text/plain', 'HTTP_HOST': '127.0.0.1:8090', 'HTTP_CONNECTION': 'keep-alive', 'HTTP_CACHE_CONTROL': 'max-age=0', 'HTTP_UPGRADE_INSECURE_REQUESTS': '1', 'HTTP_USER_AGENT': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36', 'HTTP_ACCEPT': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 'HTTP_ACCEPT_ENCODING': 'gzip, deflate, br', 'HTTP_ACCEPT_LANGUAGE': 'zh-CN,zh;q=0.9', 'wsgi.input': < _io.BufferedReader name = 564 > , 'wsgi.errors': < _io.TextIOWrapper name = '<stderr>' mode = 'w' encoding = 'UTF-8' > , 'wsgi.version': (1, 0), 'wsgi.run_once': False, 'wsgi.url_scheme': 'http', 'wsgi.multithread': True, 'wsgi.multiprocess': False, 'wsgi.file_wrapper': < class 'wsgiref.util.FileWrapper' > } < bound method BaseHandler.start_response of < wsgiref.simple_server.ServerHandler object at 0x02A06950 >> 127.0 .0 .1 - -[11 / Jun / 2019 19: 12: 41] "GET /time/ HTTP/1.1" 200 301 { 'ALLUSERSPROFILE': 'C:\\ProgramData', 'APPDATA': 'C:\\Users\\Administrator\\AppData\\Roaming', 'COMMONPROGRAMFILES': 'C:\\Program Files (x86)\\Common Files', 'COMMONPROGRAMFILES(X86)': 'C:\\Program Files (x86)\\Common Files', 'COMMONPROGRAMW6432': 'C:\\Program Files\\Common Files', 'COMPUTERNAME': 'PC-20190328RVNA', 'COMSPEC': 'C:\\Windows\\system32\\cmd.exe', 'FP_NO_HOST_CHECK': 'NO', 'HOMEDRIVE': 'C:', 'HOMEPATH': '\\Users\\Administrator', 'LOCALAPPDATA': 'C:\\Users\\Administrator\\AppData\\Local', 'LOGONSERVER': '\\\\PC-20190328RVNA', 'NUMBER_OF_PROCESSORS': '4', 'OS': 'Windows_NT', 'PATH': 'C:\\mcw\\venv\\Scripts;C:\\Windows\\system32;C:\\Windows;C:\\Windows\\System32\\Wbem;C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\;C:\\软件安装\\Git\\cmd;C:\\mysql\\mysql-5.6.44-winx64\\bin;C:\\python3\\Scripts\\;C:\\python3\\', 'PATHEXT': '.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC', 'PROCESSOR_ARCHITECTURE': 'x86', 'PROCESSOR_ARCHITEW6432': 'AMD64', 'PROCESSOR_IDENTIFIER': 'Intel64 Family 6 Model 58 Stepping 9, GenuineIntel', 'PROCESSOR_LEVEL': '6', 'PROCESSOR_REVISION': '3a09', 'PROGRAMDATA': 'C:\\ProgramData', 'PROGRAMFILES': 'C:\\Program Files (x86)', 'PROGRAMFILES(X86)': 'C:\\Program Files (x86)', 'PROGRAMW6432': 'C:\\Program Files', 'PROMPT': '(venv) $P$G', 'PSMODULEPATH': 'C:\\Windows\\system32\\WindowsPowerShell\\v1.0\\Modules\\', 'PUBLIC': 'C:\\Users\\Public', 'PYCHARM_HOSTED': '1', 'PYCHARM_MATPLOTLIB_PORT': '59585', 'PYTHONIOENCODING': 'UTF-8', 'PYTHONPATH': 'C:\\mcw;C:\\软件安装\\PyCharm 2018.3.5\\helpers\\pycharm_matplotlib_backend', 'PYTHONUNBUFFERED': '1', 'SESSIONNAME': 'Console', 'SYSTEMDRIVE': 'C:', 'SYSTEMROOT': 'C:\\Windows', 'TEMP': 'C:\\Users\\ADMINI~1\\AppData\\Local\\Temp', 'TMP': 'C:\\Users\\ADMINI~1\\AppData\\Local\\Temp', 'USERDOMAIN': 'PC-20190328RVNA', 'USERNAME': 'Administrator', 'USERPROFILE': 'C:\\Users\\Administrator', 'VIRTUAL_ENV': 'C:\\mcw\\venv', 'WINDIR': 'C:\\Windows', 'WINDOWS_TRACING_FLAGS': '3', 'WINDOWS_TRACING_LOGFILE': 'C:\\BVTBin\\Tests\\installpackage\\csilogfile.log', '_OLD_VIRTUAL_PATH': 'C:\\Windows\\system32;C:\\Windows;C:\\Windows\\System32\\Wbem;C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\;C:\\杞\ue219欢瀹夎\ue5ca\\Git\\cmd;C:\\mysql\\mysql-5.6.44-winx64\\bin;C:\\python3\\Scripts\\;C:\\python3\\', '_OLD_VIRTUAL_PROMPT': '$P$G', 'SERVER_NAME': 'PC-20190328RVNA', 'GATEWAY_INTERFACE': 'CGI/1.1', 'SERVER_PORT': '8090', 'REMOTE_HOST': '', 'CONTENT_LENGTH': '', 'SCRIPT_NAME': '', 'SERVER_PROTOCOL': 'HTTP/1.1', 'SERVER_SOFTWARE': 'WSGIServer/0.2', 'REQUEST_METHOD': 'GET', 'PATH_INFO': '/favicon.ico', 'QUERY_STRING': '', 'REMOTE_ADDR': '127.0.0.1', 'CONTENT_TYPE': 'text/plain', 'HTTP_HOST': '127.0.0.1:8090', 'HTTP_CONNECTION': 'keep-alive', 'HTTP_PRAGMA': 'no-cache', 'HTTP_CACHE_CONTROL': 'no-cache', 'HTTP_USER_AGENT': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36', 'HTTP_ACCEPT': 'image/webp,image/apng,image/*,*/*;q=0.8', 'HTTP_REFERER': 'http://127.0.0.1:8090/time/', 'HTTP_ACCEPT_ENCODING': 'gzip, deflate, br', 'HTTP_ACCEPT_LANGUAGE': 'zh-CN,zh;q=0.9', 'wsgi.input': < _io.BufferedReader name = 564 > , 'wsgi.errors': < _io.TextIOWrapper name = '<stderr>' mode = 'w' encoding = 'UTF-8' > , 'wsgi.version': (1, 0), 'wsgi.run_once': False, 'wsgi.url_scheme': 'http', 'wsgi.multithread': True, 'wsgi.multiprocess': False, 'wsgi.file_wrapper': < class 'wsgiref.util.FileWrapper' > }
获取环境变量一个键:environ['PATH_INFO']
键的值是后面的值,就是url中获取到的值,: 'PATH_INFO': '/time/',
'PATH': 'C:\\mcw\\venv\\Scripts;C:\\Windows\\system32;C:\\Windows;C:\\Windows\\System32\\Wbem;C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\;C:\\软件安装\\Git\\cmd;C:\\mysql\\mysql-5.6.44-winx64\\bin;C:\\python3\\Scripts\\;C:\\python3\\',
start_response是个对象:<bound method BaseHandler.start_response of <wsgiref.simple_server.ServerHandler object at 0x02A16930>>
from wsgiref.simple_server import make_server
def run_server(environ, start_response): #2)定义执行函数,传参environ, start_response
start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ]) # 4) 设置HTTP响应的状态码和头信息start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ])
url = environ['PATH_INFO'] #3)取到用户输入的url environ['PATH_INFO']
if url=='/home/':
response=("response:"+url).encode('utf-8')
else:
response = b"404 not found!" #)url满足条件返回内容定义
return [response, ] #6)执行函数返回列表,列表一个元素是返回的内容
if __name__ == '__main__':
httpd = make_server('127.0.0.1', 8090, run_server) #1)make_server 做服务传ip端口和执行函数
httpd.serve_forever() #启动这个服务
用wsgiref代替服务端socket创建简易web框架:
1)make_server 做服务传ip端口和执行函数
2)定义执行函数,传参environ, start_response
3)取到用户输入的url environ['PATH_INFO']
4) 设置HTTP响应的状态码和头信息start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ])
5)url满足条件返回内容定义
6)执行函数返回列表,列表一个元素是返回的内容
7)启动这个服务
jinja2
上面的代码实现了一个简单的动态,我完全可以从数据库中查询数据,然后去替换我html中的对应内容,然后再发送给浏览器完成渲染。 这个过程就相当于HTML模板渲染数据。 本质上就是HTML内容中利用一些特殊的符号来替换要展示的数据。 我这里用的特殊符号是我定义的,其实模板渲染有个现成的工具: jinja2
下载jinja2:
pip install jinja2
<!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(url):
- # 读取HTML文件内容
- with open("index2.html", "r", encoding="utf8") as f:
- data = f.read()
- template = Template(data) # 生成模板文件
- ret = template.render({'name': '小郭吹雪', 'hobby_list': ['敲代码', '今日头条', '睡觉']} # 把数据填充到模板中
- return bytes(ret, 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()
1)从jinja2导入模板类
2)实例化模板类,传参为带特别语法的文件。
3)对象.render(字典),把数据填充到模板中
4)支持{{name}}双括号中变量替换为字典键的值,for循环创建语句。
{'name': '小郭吹雪', 'hobby_list': ['敲代码', '今日头条', '睡觉']
双花中代表变量,字典的键:<h1>姓名:{{name}}</h1>
上下花百分。上for循环字典键中列表,下endfor。中间是对for循环的每个变量生成html。
{% for hobby in hobby_list %}
<li>{{hobby}}</li>
{% endfor %}
现在的数据是我们自己手写的,那可不可以从数据库中查询数据,来填充页面呢?
使用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
安装(安装最新LTS版):
pip3 install django==1.11.20
指定安装源(清华源):
pip3 install django==1.11.21 -i https://pypi.tuna.tsinghua.edu.cn/simple
pycharm安装Django,指定版本
shift右击 在当前目录打开cmd
创建一个django项目:
下面的命令创建了一个名为"mysite"的Django 项目:
django-admin startproject mysite
目录介绍:
mysite/ ├── manage.py # 管理文件 └── mysite # 项目目录 ├── __init__.py ├── settings.py # 配置 ├── urls.py # 路由 --> URL和函数的对应关系 └── wsgi.py # runserver命令就使用wsgiref模块做简单的web server
pycharm创建,社区版不支持创建django项目。
file->new project->Django
location项目路径,名称。
解释器
虚拟环境:能隔离开各个项目的环境
不写不自动生成这个目录
修改配置:
运行Django项目:
默认端口8000么?
python manage.py runserver 127.0.0.1:8000
1.命令行 切换到项目的根目录下 manage.py python36 manage.py runserver # http://127.0.0.1:8000/ python36 manage.py runserver 80 #指定端口启动 在80端口启动 python36 manage.py runserver 0.0.0.0:80 #0.0.0.0:80 其它主机可访问,外部主机可访问
直接点击启动关闭
python36 manage.py runserver 0.0.0.0:80#并且settings设置为‘*’,允许所有主机访问
terminate是项目终止,disconnect是断开连接,项目不终止
命令行开启的可以crtl + c终止
disconect点击之后,要停止项目。可以在资源管理器中杀掉进程
多了个文件:
如果启动时报错哪个文件的问题,看看是不是启动错了,启动的不是Django项目而是某个文件:
模板文件配置:
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")
创建一个页面
from django.contrib import admin from django.urls import path from django.shortcuts import HttpResponse,render def index(request): return HttpResponse('index') urlpatterns = [ path('admin/', admin.site.urls), path('index/', index), ]
render
除request参数外还接受一个待渲染的模板文件和一个保存具体数据的字典参数。
将数据填充进模板文件,最后把结果返回给浏览器。(类似于我们上面用到的jinja2)
例如:
def index(request): # 业务逻辑代码 return render(request, "index.html", {"name": "alex", "hobby": ["烫头", "泡吧"]})
from django.contrib import admin from django.urls import path from django.shortcuts import HttpResponse,render def index(request): # return HttpResponse('index') return render(request,'index.html') urlpatterns = [ path('admin/', admin.site.urls), path('index/', index), ]
redirect
接受一个URL参数,表示跳转到指定的URL。
例如:
def index(request): # 业务逻辑代码 return redirect("/home/")
重定向是怎么回事?
启动Django报错:
Django 启动时报错 UnicodeEncodeError ...
报这个错误通常是因为计算机名为中文,改成英文的计算机名重启下电脑就可以了。