一、web的请求流程
web应用的请求流程:
就是浏览器和server端的一次通信过程,浏览器作为客户端发了一次请求到服务器,服务器接收到这个内容之后再返回一个响应,这就是一次web应用。而在这一次web应用中如果返回的是一个固定的html内容的话,这里就不会涉及任何的web框架。
用户向服务器送一个请求,这个请求是浏览器把url打包成一个个键值对发过去的,所以发过去的是带着url的请求,不光有url,还有很多键值对,这些键值对里面包含了访问的主机信息、请求信息等等。发出去后,server端接收到了这次请求,如果要返回的是一个固定的html页面,用户不能做任何交互,那么这个过程就变得简单了:服务器只需要把用户发过来的请求按照http协议解析开,然后服务器只需要把用户想要html页面打包发过去就可以了。在这里会有一个web服务器的概念, 以后工作中会遇到叫nginx或apache的web服务器软件。
nginx和apache是用来接收用户请求来做http的解析的web服务器软件,它们就是用来做http协议的解析与http协议的封装的。它们把客户端发过的数据打包成一个对象的形式交给服务器的web应用。 有了这些web服务器软件程序员就不必管用户发过来的请求是按什么规范创造的,然后去按什么规范去解了,节省了繁琐的过程,程序员只需要专注网页的开发。
web服务器软件(nginx或apache)要做的事是:它先把用户发过来的数据打包成一个对象的形式后交给web应用,对于web应用而言,我怎么取方便我就怎么拿(通过键值对等)。web服务器类似于一个接口,接口就是为别人提供一个功能,别人不管这个功能是怎么实现的,他只要知道调用接口名并实现了某个功能即可。
web应用才是逻辑指挥的,它相当于大脑。web服务器软件把生成的对象交给web应用后,web应用来决定返回哪个html页面。返回的html页面还是得交给web服务器软件来进行http协议的组装。最后web服务器把组装好的html页面发给浏览器。这就是一次完整的web请求。
我们要学的 django框架目的是为了节省开发时间,提高开发效率,所以它的位置应该是web应用这里起作用。一次web应用里可能重复很多步,我们把这些重复的内容作为一个框架,每次再创建一个应用,再把这些重复的内容下载下来再添加一些其它的需求。如果没有一个web服务器,没有解析过程、没有发送过程,那么这套模式就不能够被调度起来。
由于我们没有学nginx或apache这些web服务器软件,所以我们这里暂时用python的一个wsgiref模块来替代web服务器软件。
python自己设计了一套服务器的接口,只要符合接口规范,那么我就可以认为你可以完成我的类似于服务器软件的功能,所以这个接口协议叫做WSGI,python里有一个叫wsgiref模块。这个wsgiref模块内部里封装了socket套接字的创建,有了这个模块之后,它就能代替web服务器软件了。
二、wsgi协议
自己先写一个web框架
为什么自己来写个web框架呢?因为直用django的话,只能看到它的效果,它的流程是看不到的。我们用自己写的web框架再去与django框架去类比一下,然后就知道具体是怎么实现的了。
import socket def handle_request(client): buf = client.recv(1024) client.send("HTTP/1.1 200 OK\r\n\r\n".encode("utf8")) # 响应头信息 client.send("<h1 style='color:red'>Hello,XiaoBai</h1>".encode("utf8")) # \r\n\r\n后就是你要渲染到页面上的内容了 def main(): sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM) sock.bind(('localhost',8001)) sock.listen(5) while True: connection,address = sock.accept() handle_request(connection) connection.close() if __name__ == '__main__': main()
(1)我们要引用wsgiref模块中的一个叫make_server的类,这个类是通过simple_server来调用的,所以我们要做的第一件事是先把它引用过来。
from wsgiref.simple_server import make_server
# 用到wsgiref接口模块下面的一个叫make_server的类 def application(environ,start_response):
# wsgiref所有的请求信息封装成一个对象,这个对象就是environ,这里面所有的封装信息是通过解析http协议给封装好的。
# 所以我们要写需求的时候,直接从这个environ对象里想要什么拿什么就是了,http规范这一步就被忽略隐藏了。 start_response('200 OK',[('Content-Type','text/html')])
# Content-Type是告诉你的浏览器给你返回的是一个什么类型的文本。
# 这一步必须有的原因是:http协议解析里面必须包含一个响应头和一个响应体,响应头start_response这个函数就是帮我设置这些内容的。
# 第一个:状态码,第二部分内容放响应头信息。响应头是由许多键值对组成的。它会把每一个键值对放到一个元组里。后面还可以跟元组,
# 每个括号里都存放两个内容,一个代表键,一个代表值。把所有响应头的信息和设置全写进去。 return [b'Hello world'] t = make_server("",8800,application)
# 这是一个类,它只是实例化出来 一个对象。如果不调用这个对象下面的server_forever方法,它不会执行。
# make_server帮我创建好了socket对象。当调用它时t.serve_forever(),一定会调用application函数的执行,当有请求信息过来时则执行以下两行代码 # start_response('200 OK',[('Content-Type','text/html')])
# return [b'<h1>Hello,world!<\h1>']
# 你给它一个响应200 ok,然后拿到这个hello world,这个时候没有处理来自客户端的请求信息。
t.serve_forever() # 括号中的前两个参数放的是ip地址和端口号,由于是本机的ip地址,所以可以不写。实现一次web通信就必须要 有ip和端口,这样别人才能访问你。
# 第三个参数放的是函数名,当执行下面的httpd.server_forever()的时候,它执行的是application这个函数。
# 实例化出一个类对象,这个对象调serve_forever()的时候,它会在内部创建一个socket对象来进行绑定进行listen,完事后accept起来
# 当有外部客户端来连接的时候,它会执行对应的application函数下的代码,最后完成了一次最简单的请求。
httpd.serve_forever()
# 开始监听http请求:
三、自定义一个web框架(1)
environ是一个请求信息,environ包含所有的信息。它是一个字典,封装了所有的请求信息
打印一下environ,查找打印的信息中有一个叫 ‘PATH_INFO’:'/favicon.ico' 的键值对。
environ {'ALLUSERSPROFILE': 'C:\\ProgramData', 'APPDATA': 'C:\\Users\\William\\AppData\\Roaming', 'COMMONPROGRAMFILES': 'C:\\Program Files (x86)\\Common Files', 'COMMONPROGRAMFILES(X86)': 'C:\\Program Files (x86)\\Common Files', 'COMMONPROGRAMW6432': 'C:\\Program Files\\Common Files', 'COMPUTERNAME': 'LAPTOP-E61OFNCB', 'COMSPEC': 'C:\\Windows\\system32\\cmd.exe', 'FPS_BROWSER_APP_PROFILE_STRING': 'Internet Explorer', 'FPS_BROWSER_USER_PROFILE_STRING': 'Default', 'HOMEDRIVE': 'C:', 'HOMEPATH': '\\Users\\William', 'LOCALAPPDATA': 'C:\\Users\\William\\AppData\\Local', 'LOGONSERVER': '\\\\LAPTOP-E61OFNCB', 'NUMBER_OF_PROCESSORS': '4', 'ONEDRIVE': 'C:\\Users\\William\\OneDrive', 'OS': 'Windows_NT', 'PATH': 'C:\\Python\\Python36\\Scripts\\;C:\\Python\\Python36\\;C:\\Windows\\system32;C:\\Windows;C:\\Windows\\System32\\Wbem;C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\;C:\\Program Files (x86)\\NVIDIA Corporation\\PhysX\\Common;C:\\Program Files\\Intel\\WiFi\\bin\\;C:\\Program Files\\Common Files\\Intel\\WirelessCommon\\;C:\\Windows\\system32;C:\\Windows;C:\\Windows\\System32\\Wbem;C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\;C:\\Python\\Python27\\Scripts\\;C:\\Python\\Python27;D:\\MySQL\\mysql-5.7.19-winx64\\bin;C:\\Program Files (x86)\\PBB Reader\\x64\\;C:\\Users\\William\\AppData\\Local\\Microsoft\\WindowsApps;D:\\MySQL\\mysql-5.7.19-winx64\\bin', 'PATHEXT': '.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.PY;.PYW', 'PROCESSOR_ARCHITECTURE': 'x86', 'PROCESSOR_ARCHITEW6432': 'AMD64', 'PROCESSOR_IDENTIFIER': 'Intel64 Family 6 Model 78 Stepping 3, GenuineIntel', 'PROCESSOR_LEVEL': '6', 'PROCESSOR_REVISION': '4e03', 'PROGRAMDATA': 'C:\\ProgramData', 'PROGRAMFILES': 'C:\\Program Files (x86)', 'PROGRAMFILES(X86)': 'C:\\Program Files (x86)', 'PROGRAMW6432': 'C:\\Program Files', 'PSMODULEPATH': 'C:\\Program Files\\WindowsPowerShell\\Modules;C:\\Windows\\system32\\WindowsPowerShell\\v1.0\\Modules', 'PUBLIC': 'C:\\Users\\Public', 'PYCHARM_HOSTED': '1', 'PYTHONIOENCODING': 'UTF-8', 'PYTHONPATH': 'C:\\Users\\William\\PycharmProjects\\Item_Python1', 'PYTHONUNBUFFERED': '1', 'SESSIONNAME': 'Console', 'SYSTEMDRIVE': 'C:', 'SYSTEMROOT': 'C:\\Windows', 'TEMP': 'C:\\Users\\William\\AppData\\Local\\Temp', 'TMP': 'C:\\Users\\William\\AppData\\Local\\Temp', 'USERDOMAIN': 'LAPTOP-E61OFNCB', 'USERDOMAIN_ROAMINGPROFILE': 'LAPTOP-E61OFNCB', 'USERNAME': 'William', 'USERPROFILE': 'C:\\Users\\William', 'WINDIR': 'C:\\Windows', 'SERVER_NAME': 'LAPTOP-E61OFNCB', 'GATEWAY_INTERFACE': 'CGI/1.1', 'SERVER_PORT': '8800', 'REMOTE_HOST': '', 'CONTENT_LENGTH': '', 'SCRIPT_NAME': '', 'SERVER_PROTOCOL': 'HTTP/1.1', 'SERVER_SOFTWARE': 'WSGIServer/0.2', 'REQUEST_METHOD': 'GET', 'PATH_INFO': '/', 'QUERY_STRING': '', 'REMOTE_ADDR': '127.0.0.1', 'CONTENT_TYPE': 'text/plain', 'HTTP_HOST': '127.0.0.1:8800', 'HTTP_CONNECTION': 'keep-alive', 'HTTP_UPGRADE_INSECURE_REQUESTS': '1', 'HTTP_USER_AGENT': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3159.5 Safari/537.36', 'HTTP_ACCEPT': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'HTTP_DNT': '1', 'HTTP_ACCEPT_ENCODING': 'gzip, deflate, br', 'HTTP_ACCEPT_LANGUAGE': 'zh-CN,zh;q=0.8', 'wsgi.input': <_io.BufferedReader name=788>, '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'>}
找到 ‘PATH_INFO’:'/favicon.ico' 这个键值对后,我们当时在浏览器中输入127.0.0.1:8800时并没有输入路径。什么是路径?
例如:127.0.0.1:8800/xiaobai 后面这个/xiaobai就是路径。
这时我们想让浏览器中输入127.0.0.1:8800/xiaobai显示一个内容,127.0.0.1:8800/xiaoxiaobai又显示另一个内容,它们的区别就是它们的路径不一样,而所有的请求信息都在这个environ里面,这些路径我们可以从environ里面拿,所以我们要根据判断用get方法把PATH取出来。
path = environ.get("PATH_INFO")
print("path",path)
加上以上两条代码后,再点启动
然后在浏览器中输入127.0.0.1:8800/xiaobai,回车,再查看pycharm中的代码变化
发现打印出来了,
path /xiaobai就是我们想要的。
path /favicon.ico是它自己默认的一个变量
有了以上的内容后,改成别的名字也可以拿到了。例:
那么现在我们可以通过这个变量来做分支判断。在这里我们可以不用return等于一个固定的内容了。这样就可以根据不同的人来返回不同的页面了。前提必须是“xiaobai”和“xiaobaibai”必须各有一个页面。所以我们先各建一个html。
from wsgiref.simple_server import make_server
def application(environ,start_response):
path = environ.get("PATH_INFO")
print("path",path)
start_response('200 OK',[('Content-Type','text/html')])
if path == '/xiaobai': # 做分支判断
f = open("xiaobai.html","rb")
# 打开xiaobai.html,发的时候是按照rb模式来发的,所以应该按rb来读
# 它这个模块必须是对应字节才能发送,所以我们应该通过rb去拿
data = f.read()
f.close()
return [data]
elif path == "/xiaoxiaobai":
f = open("xiaoxiaobai.html","rb")
data = f.read()
f.close()
return [data]
else:
return [b"<h1>404</h1>"]
# return [b"<h1>hello world</h1>"]
t = make_server("",8800,application)
t.serve_forever()
点击启动
得到如下结果:
输入127.0.0.1:8800/xiaobai时则显示xiaobai的html页面
输入127.0.0.1:8800/xiaoxiaobai时则显示xiaobai的html页面
在127.0.0.1:8800后随便输入字符,如/haha时,则显示404的页面
以上就是路径的分派,根据封装好的environ对象。
四、自定义一个web框架(2)
一个网站存放的路由可能是成百上千个,用if...else去判断是一个非常繁琐的过程。所以可以用反射或字典的格式
写一个路径的函数:
routers
自定义一个登陆页面:
自定义一个xiaobai页面:
自定义一个xiaoxiaobai页面:
MyWeb.py的代码:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Author : William
# @Time : 2017/8/28 12:03
# @File : MyWeb.py
from wsgiref.simple_server import make_server
def foo1(request):
f = open("xiaobai.html", "rb")
data = f.read()
f.close()
return [data]
def foo2(request):
f = open("xiaoxiaobai.html", "rb")
data = f.read()
f.close()
return [data]
def login(request):
f = open("login.html","rb")
data = f.read()
f.close()
return [data]
def auth(request):
return[b"OK"]
def routers():
URLpattern = (
# 每个路径对应一个函数名来处理它
("/auth",auth),
("/login", login),
("/xiaobai", foo1),
("/xiaoxiaobai", foo2),
)
return URLpattern
def application(environ,start_response):
path = environ.get("PATH_INFO")
print("path",path)
start_response('200 OK',[('Content-Type','text/html')])
urlpattern = routers()
# 执行routers()后得到的是一个对应关系的元组。拿到这个元组后起个名urlpattern
func = None # 拿一个变量func赋给它,默认为None
for item in urlpattern:
# 对这个urlpattern进行for循环遍历,这个item就是遍历对应的关系。
# 我们的目的是当用户输入一个xiaobai时执行这个foo1函数。当输入xiaoxiaobai时执行foo2函数
# 所以我们在这里应该取出来进行判断,让谁跟这个path比,path就是用户输入的内容。
if path == item[0]:# 例如:当用户输入/xiaobai时
func = item[1]
# 例如:匹配成功时则执行item[1],就是执行foo1这个函数,把这个变量赋给func
break
# 加上break是跳出循环
# 如果都没有,则返回的是None
if func: # 如果func有值,它不是None值,相当于找到了。找到的这个func就是要执行的函数
return func(environ) # 执行找到的值的函数,如/xiaobai下的foo1函数
else:
return[b"404"]
# elif path == "/xiaoxiaobai":
# else:
# return [b"<h1>404</h1>"]
# return [b"<h1>hello world</h1>"]
t = make_server("",8800,application)
t.serve_forever()
点击运行后执行效果:
点击提交按钮后:
此时,输入的用户密码信息就在浏览器中显示了,因为之前用的是get方法,换成post就不会显示在浏览器输入栏里。
打印一下request的值:
C:\Python\Python36\python.exe C:/Users/William/PycharmProjects/Item_Python1/Study/MyWeb.py 127.0.0.1 - - [28/Aug/2017 17:49:42] "GET /auth?user=xiaobai&pwd=123 HTTP/1.1" 200 2 path /auth +++++++ {'ALLUSERSPROFILE': 'C:\\ProgramData', 'APPDATA': 'C:\\Users\\William\\AppData\\Roaming', 'COMMONPROGRAMFILES': 'C:\\Program Files (x86)\\Common Files', 'COMMONPROGRAMFILES(X86)': 'C:\\Program Files (x86)\\Common Files', 'COMMONPROGRAMW6432': 'C:\\Program Files\\Common Files', 'COMPUTERNAME': 'LAPTOP-E61OFNCB', 'COMSPEC': 'C:\\Windows\\system32\\cmd.exe', 'FPS_BROWSER_APP_PROFILE_STRING': 'Internet Explorer', 'FPS_BROWSER_USER_PROFILE_STRING': 'Default', 'HOMEDRIVE': 'C:', 'HOMEPATH': '\\Users\\William', 'LOCALAPPDATA': 'C:\\Users\\William\\AppData\\Local', 'LOGONSERVER': '\\\\LAPTOP-E61OFNCB', 'NUMBER_OF_PROCESSORS': '4', 'ONEDRIVE': 'C:\\Users\\William\\OneDrive', 'OS': 'Windows_NT', 'PATH': 'C:\\Python\\Python36\\Scripts\\;C:\\Python\\Python36\\;C:\\Windows\\system32;C:\\Windows;C:\\Windows\\System32\\Wbem;C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\;C:\\Program Files (x86)\\NVIDIA Corporation\\PhysX\\Common;C:\\Program Files\\Intel\\WiFi\\bin\\;C:\\Program Files\\Common Files\\Intel\\WirelessCommon\\;C:\\Windows\\system32;C:\\Windows;C:\\Windows\\System32\\Wbem;C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\;C:\\Python\\Python27\\Scripts\\;C:\\Python\\Python27;D:\\MySQL\\mysql-5.7.19-winx64\\bin;C:\\Program Files (x86)\\PBB Reader\\x64\\;C:\\Users\\William\\AppData\\Local\\Microsoft\\WindowsApps;D:\\MySQL\\mysql-5.7.19-winx64\\bin', 'PATHEXT': '.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.PY;.PYW', 'PROCESSOR_ARCHITECTURE': 'x86', 'PROCESSOR_ARCHITEW6432': 'AMD64', 'PROCESSOR_IDENTIFIER': 'Intel64 Family 6 Model 78 Stepping 3, GenuineIntel', 'PROCESSOR_LEVEL': '6', 'PROCESSOR_REVISION': '4e03', 'PROGRAMDATA': 'C:\\ProgramData', 'PROGRAMFILES': 'C:\\Program Files (x86)', 'PROGRAMFILES(X86)': 'C:\\Program Files (x86)', 'PROGRAMW6432': 'C:\\Program Files', 'PSMODULEPATH': 'C:\\Program Files\\WindowsPowerShell\\Modules;C:\\Windows\\system32\\WindowsPowerShell\\v1.0\\Modules', 'PUBLIC': 'C:\\Users\\Public', 'PYCHARM_HOSTED': '1', 'PYTHONIOENCODING': 'UTF-8', 'PYTHONPATH': 'C:\\Users\\William\\PycharmProjects\\Item_Python1', 'PYTHONUNBUFFERED': '1', 'SESSIONNAME': 'Console', 'SYSTEMDRIVE': 'C:', 'SYSTEMROOT': 'C:\\Windows', 'TEMP': 'C:\\Users\\William\\AppData\\Local\\Temp', 'TMP': 'C:\\Users\\William\\AppData\\Local\\Temp', 'USERDOMAIN': 'LAPTOP-E61OFNCB', 'USERDOMAIN_ROAMINGPROFILE': 'LAPTOP-E61OFNCB', 'USERNAME': 'William', 'USERPROFILE': 'C:\\Users\\William', 'WINDIR': 'C:\\Windows', 'SERVER_NAME': 'LAPTOP-E61OFNCB', 'GATEWAY_INTERFACE': 'CGI/1.1', 'SERVER_PORT': '8800', 'REMOTE_HOST': '', 'CONTENT_LENGTH': '', 'SCRIPT_NAME': '', 'SERVER_PROTOCOL': 'HTTP/1.1', 'SERVER_SOFTWARE': 'WSGIServer/0.2', 'REQUEST_METHOD': 'GET', 'PATH_INFO': '/auth', 'QUERY_STRING': 'user=xiaobai&pwd=123', 'REMOTE_ADDR': '127.0.0.1', 'CONTENT_TYPE': 'text/plain', 'HTTP_HOST': '127.0.0.1:8800', 'HTTP_CONNECTION': 'keep-alive', 'HTTP_UPGRADE_INSECURE_REQUESTS': '1', 'HTTP_USER_AGENT': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3159.5 Safari/537.36', 'HTTP_ACCEPT': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'HTTP_DNT': '1', 'HTTP_REFERER': 'http://127.0.0.1:8800/login', 'HTTP_ACCEPT_ENCODING': 'gzip, deflate, br', 'HTTP_ACCEPT_LANGUAGE': 'zh-CN,zh;q=0.8', 'wsgi.input': <_io.BufferedReader name=812>, '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'>} 127.0.0.1 - - [28/Aug/2017 17:49:42] "GET /favicon.ico HTTP/1.1" 200 3 path /favicon.ico
找到用户名和密码后先进行分割,再进行判断:
运行,看效果。
在输入xiaobai和123,账号密码正确的情况下显示:
在输入其它用户名密码的情况下显示:
五、自定义一个web框架(3)
由于以上写的代码看起来很乱,为了使代码有可读性,我们将其进一步优化,就是把处理的函数都单独放到一个文件里,然后调用。
回到MyWeb.py文件里添加一行导入views.py的代码:
from views import *
再作进一步优化,把这些对应关系拿走,传到一个叫urls.py的文件里。
回到MyWeb.py文件里添加导入urls.py的代码:
还有在MyWeb.py文件末尾加上
if __name__ == '__main__':,把以下两句代码缩进来,表示这是一个执行文件。
最后,我们把xiaobai.html、xiaoxiaobai.html、login.html这三个页面放到一个文件夹中便于统一管理。
创建一个文件夹:templates,然后把这些html文件放到里面,最后要引入时,则在views文件里把所有的html文件的路径进行修改即可
以上就是自定义了一个基本的web框架。
代码如下:
login.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>login</title> </head> <body> <h2>登陆页面</h2> <form action="http://127.0.0.1:8800/auth "> <p>用户名<input type="text" name="user"></p> <p>密 码<input type="password" name="pwd"></p> <p> <input type="submit"> </p> </form> </body> </html>
xiaobai.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>xiaobai</title> </head> <body> <h1> Welcome xiaobai</h1> </body> </html>
xiaoxiaobai.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>xiaobai</title> </head> <body> <h1> Welcome xiaobaibai</h1> </body> </html>
MyWeb.py
#!/usr/bin/env python # -*- coding: utf-8 -*- # @Author : William # @Time : 2017/8/28 12:03 # @File : MyWeb.py from wsgiref.simple_server import make_server from views import * import urls def routers(): urls.URLpattern return URLpattern def application(environ,start_response): path = environ.get("PATH_INFO") print("path",path) start_response('200 OK',[('Content-Type','text/html')]) urlpattern = routers() # 执行routers()后得到的是一个对应关系的元组。拿到这个元组后起个名urlpattern func = None # 拿一个变量func赋给它,默认为None for item in urlpattern: # 对这个urlpattern进行for循环遍历,这个item就是遍历对应的关系。 # 我们的目的是当用户输入一个xiaobai时执行这个foo1函数。当输入xiaoxiaobai时执行foo2函数 # 所以我们在这里应该取出来进行判断,让谁跟这个path比,path就是用户输入的内容。 if path == item[0]:# 例如:当用户输入/xiaobai时 func = item[1] # 例如:匹配成功时则执行item[1],就是执行foo1这个函数,把这个变量赋给func break # 加上break是跳出循环 # 如果都没有,则返回的是None if func: # 如果func有值,它不是None值,相当于找到了。找到的这个func就是要执行的函数 return func(environ) # 执行找到的值的函数,如/xiaobai下的foo1函数 else: return[b"404"] # elif path == "/xiaoxiaobai": # else: # return [b"<h1>404</h1>"] # return [b"<h1>hello world</h1>"] if __name__ == '__main__': t = make_server("",8800,application) t.serve_forever()
urls.py
from views import * URLpattern = ( # 每个路径对应一个函数名来处理它 ("/auth", auth), ("/login", login), ("/xiaobai", foo1), ("/xiaoxiaobai", foo2), )
views.py
def foo1(request): f = open("templates/xiaobai.html", "rb") data = f.read() f.close() return [data] def foo2(request): f = open("templates/xiaoxiaobai.html", "rb") data = f.read() f.close() return [data] def login(request): f = open("templates/login.html","rb") data = f.read() f.close() return [data] def auth(request): print("+++++++",request) user_union,pwd_union = request.get("QUERY_STRING").split("&") _,user = user_union.split("=") _,pwd = pwd_union.split("=") if user == "xiaobai" and pwd =="123": # 这里应该接数据库,由于是演示,直接用简单的字符串代替 return [b"login successfully"] else: return [b"user or password is wrong"]
以上代码中涉及到框架的是
urls.py中的 URLpattern = ()
MyWeb.py中的所有代码都是
以后要用直接把它下载下来,第一步写对应关系,第二步写视图函数就OK了,再把MyWeb.py改成bin.py作为启动文件即可。如果设置到页面,直接到tempaltes里创建页面就可以了。这就是一个最简单的框架。
六、MTV模型
MTV和MVC框架是web框架的中的出名的两个模型,不管是MVC还是MTV,它们的流程都是一样的。
MVC model view controller
MTV model templates view
两个model都是数据模型,就是跟数据库打交道的模块,所以我们会把数据相关的内容放到一个模块里,一个web框架或web应用最重要的一点就是如何与数据库打交道。
MVC的view和MTV的templates是一样的,都是存放html文件的。
controller是控制器的意思,view是视图的意思。MTV中的view包含view+controller
我们学的Django模型是MTV模型。
所以MTV包含以下四块:
model view templates controller
controller就是我们之前写的自定义web框架中的urls,还有在bin.py文件中的if判断语句,它来决定哪个url对应哪个函数。是用来作路由分发的。
view就是我们之前写的自定义web框架中的views,视图就是对来做逻辑处理的。
templates就是我们之前写的自定义web框架中的templates,它是用来存放html文件的。
七、Django项目的创建
Django是python的web框架。所以Django框架一定是依赖于python解释器的。所以,安装Django时,pip install django是把django放到python2解释器里,pip3 install django是把django放到python3解释器里。
检测是django是放到python2解释器里还是python3解释器里,只需要查看python2或python3的安装目录下的Scripts中,谁有django-admin.exe和django-admin.py这两个文件就知道django用的哪个解释器了。
由于我早已安装了django,所以才出来已安装的提示。
若dos下安装不成功,则在pycharm上安装django
打开pycharm——File——settings——找到你创建的项目名(我的项目名是Project:Item_Python1)——Project Interpreter——在右面的选项框里
创建一个django项目:
django-admin startproject 自定义项目名
成功创建了一个叫Mysite的项目名。
打开Mysite项目,里面有两个文件,一个是Mysite,一个是manage.py
manage.py是一个起始文件,与django进行交互的命令脚本,启动项目就是通过manage.py。你无论想要django做什么事情都要通过manage.py
Mysite文件的名字与创建的项目名字相同,它一全局的一个文件夹,打开Mysite,里面有4个文件:
__init__.py:空的,说明这个Mysite是一个包,既然是个包就可以进行包与包之间、模块与模块之间的调用。与django进行交互的命令脚本
settings.py:完成相互的配置的。
urls.py:做路由分发的,url跟视图的一个分发关系。(url中的path与视图函数的一一映射关系)重点
wsgi.py:封装了socket套接字,它是一个底层文件
项目与应用的关系:
一个项目有多个应用(常见)
一个应用可以被多个项目拥有
创建一个django应用:
想要创建一个django应用就必须告诉manage.py这个脚本。
python manage.py startapp 应用名
所有需求都写在这个应用里了,必须创建应用名。一个项目里可以只有一个应用,但是不可以没有。所以在创建django项目后就必须创建一个应用。
创建了一个叫blog的文件夹。在电脑安装了多版本python的情况下,有的是pyhon3 manage.py startapp blog来创建应用名。
打开django项目里可以看到创建的blog应用名。
打开blog文件夹,我们重点要掌握的就是models.py和views.py这两个文件。
models是与数据库打交道的文件。
views.py是存放视图函数的文件。
admin.py是与后台数据管理相关的文件。
apps.py是与应用相关的文件。
tests.py是用来测试的文件。
八、Django项目登陆设计
1、pycharm中创建Django项目
打开pycharm——File——New Project
django项目创建完成
2、写一个登录项目。
第一步:先在urls.py文件里添加logoin函数映射关系。
第二步:写一个登录的函数。
第三步:写一个返回给用户的login页面。
表格里的action="http://127.0.0.1:8080/auth"可以直接简写成"/auth",因为只写一个/auth,它默认就会拿当前网站的IP地址和端口
第四步:启动django项目:
python manage.py runserver 8800
# runserver后面什么也不加默认为本机的8000端口,若想要改,则直接在后面加端口号,本例改为8800
第五步:打开浏览器,输入本机IP地址127.0.0.1:8800/login
第六步:写一个验证用户名和密码的文件
(1)当我们输入用户名和密码后,点击提交后,希望对输入的用户名和密码进行验证,然后走http://127.0.0.1:8800/auth这个地址。然而直接访问这个地址则不能被访问,因为这是启动的是django项目。所以我们得加一个叫auth的路径了。
(2)输入账号密码点提交后应该走post的提交方式。后面不写method="post"默认是以get的提交方式提交。
(3)
注意:django里面改一下代码可以不用重启,它自动给你重启了。
(4)点击提交后的流程。
点击提交后,它应该走action那个路径
action里的auth其实就是urls.py里的auth对应的视图函数——auth
打开这个视图函数,该函数又返回了一个login.html的页面。
点击提交后的页面:
注意(只是在post提交数据的时候,get不会有这种情况)
出现以上页面这是因为django自带的一个防止攻击的安全机制,我们只需把这个机制关闭即可。
具体方法:进入setting.py文件里——找到'django.middleware.csrf.CsrfViewMiddleware'——用#号注释即可
(5)打开浏览器再次输入账号密码后提交取得用户输入的账号密码
点击提交后,页面会刷新,此时回到后台,查看pycharm下的代码,我们得到了用户输入的一个字典形式的用户名和密码了。现在只需要通过键就能取出来了。
第七步:回到views.py,之前我们输入的打印内容
第八步:将拿到用户输入的账号密码进行判断
打开浏览器,继续输入用户名和密码进行测试:
(1)登陆失败时,页面刷新
点击提交后登陆没成功,返回一个页面:
(2)登陆成功时,则会提示“欢迎登陆”的字符串
点击提交后页面跳转至auth,并显示“登陆成功”的字符串:
扩展部分:
需求:把login路径和auth路径合并成一个login视图。
答:
第1步:为了防止报错,先注释auth函数里的代码
第2步:在login页面里把action="http//127.0.0.1:8800/auth/"改成action="http//127.0.0.1:8800/login/"
第3步:接下来我们就要把用户输入传过来的数据取出来做判断了。
之前login函数下的代码和auth函数下的代码它们唯一的区别就是请求方式不同
login函数下的代码是走的是第一次请求,地址栏,默认走的是get请求
auth函数下的代码走的是第二次请求,form表单提交走的是post请求
这两次请求它们的区别就是,logoin是get方式请求,auth是post方式请求
所以我们只需要作个if判断,判断你走的是get请求还是post请求即可。
如果是get请求,我就给你一个登陆页面,如果是post请求,说明是第2次过程,提交数据了。我们再把判断那步放到就行了。
最后进行测试:
登陆失败情况下:
点击提交,刷新后仍然是在login页面上
登陆成功情况下:
点击提交,刷新后显示“登陆成功”字符串,依然是在login页面上