Web 框架的本质及自定义 Web 框架 我们可以这样理解:所有的 Web 应用本质上就是一个 socket 服务端,而用户的浏览器就是一个 socket 客户端,基于请求做出响应。客户都先请求,服务端做出对应的响应,按照 HTTP 协议的请求协议发送请求,服务端按照 HTTP 协议的响应协议来响应请求。依据这样的原理进行网络通信,我们就可以实现自己的 Web 框架了。
通过对 socket 的学习,我们已经知道如何网络通信,我们完全可以自己写了。因为 socket 就是做网络通信用的。下面我们就基于 socket 来自己实现一个 Web 框架。写一个 Web 服务端,让浏览器来请求,并通过自己的服务端把页面返回给浏览器,浏览器渲染出我们想要的效果。在后面的学习中,大家提前准备一些文件:
HTML 文件内容如下,名称为 test.html
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Title</title > <link rel ="stylesheet" href ="test.css" > <link rel ="icon" href ="wechat.ico" > </head > <body > <h1 > 姑娘,你好,我是Jaden,请问约吗?嘻嘻~~</h1 > <img src ="meinv.png" alt ="" width ="100" height ="100" > <script src ="test.js" > </script > </body > </html >
css文件内容如下,名称为test.css
:
1 2 3 4 h1 { background-color : green; color : white; }
js文件内容如下,名称为test.js
:
再准备一个图片,名称为 meinv.jpg
,再准备一个 ico 文件,名称为 wechat.ico
,其实就是个图片文件,微信官网打开之后,在浏览器最上面能够看到 ,把它保存下来
上面的文件都准备好之后,你用 PyCharm 新建一个项目,把文件都放到一个文件夹里面去,留着备用,像下面这个样子:
然后开始写我们的 Web 框架,我们分这么几步来写:
一、简单的 Web 框架 创建一个 Python 文件,内容如下,名称为 test.py
:
1 2 3 4 5 6 7 8 9 10 11 12 13 import socketsk = socket.socket() sk.bind(('127.0.0.1' ,8001 )) sk.listen() conn,addr = sk.accept() from_b_msg = conn.recv(1024 ) str_msg = from_b_msg.decode('utf-8' ) conn.send(b'HTTP/1.1 200 ok \r\n\r\n' ) conn.send(b'hello' )
我们来浏览器上看一下浏览器发送的请求:
目前我们还没有写如何返回一个 HTML 文件给浏览器,所以这里暂时不用管它。那么我们点开这个 127.0.0.1
看看:
我们在 Python 文件中打印一下浏览器发送过来的请求信息是啥:
重启我们的代码,然后在网址中输入这个:
再重启我们的代码,然后在网址中输入这个:
浏览器发过来一堆的消息,我们给浏览器回复(响应)信息的时候,也要按照一个消息格式来写,这些都是 HTTP 协议规定的。要了解 HTTP 协议的更多内容,可以参考 HTTP 协议 。
二、返回 HTML 文件的 Web 框架 我们刚刚写过一个名称为 test.html
的 HTML 文件,先把 CSS、JS 和图片相关的注释取消掉,让它们可以直接显示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Title</title > <link rel ="stylesheet" href ="test.css" > <style > h1 { background-color : green; color : white; } </style > </head > <body > <h1 > 姑娘,你好,我是Jaden,请问约吗?嘻嘻~~</h1 > <img src ="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1550395461724&di=c2b971db12eef5d85aba410d1e2e8568&imgtype=0&src=http%3A%2F%2Fy0.ifengimg.com%2Fifengimcp%2Fpic%2F20140822%2Fd69e0188b714ee789e97_size87_w800_h1227.jpg" alt ="" > <script > alert('这是我们第一个网页' ) </script > </body > </html >
准备我们的 Python 代码,服务端程序,文件内容如下,文件名称为 test.py
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import socketsk = socket.socket() sk.bind(('127.0.0.1' ,8001 )) sk.listen() conn,addr = sk.accept() from_b_msg = conn.recv(1024 ) str_msg = from_b_msg.decode('utf-8' ) print ('浏览器请求信息:' ,str_msg)conn.send(b'HTTP/1.1 200 ok \r\n\r\n' ) with open ('test1.html' ,'rb' ) as f: f_data = f.read() conn.send(f_data)
页面上输入网址看效果,css 和 js 代码的效果也有,very good:
但是我们知道,我们的 css 和 js 基本都是写在本地的文件里面的啊,而且我们的图片基本也是我们自己本地的啊。怎么办?我们将上面我们提前准备好的 js 和 css 还有那个 .ico
结尾的图片文件都准备好,来我们在来一个升级版的 Web 框架。其实 css、js、图片等文件都叫做网站的静态文件。
首先我们先看一个效果,如果我们直接将我们写好的 css 和 js 还有 .ico
和图片文件插入到我们的 HTML 页面里面,就是下面这个 HTML 文件。
名称为 test.html
,内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Title</title > <link rel ="stylesheet" href ="test.css" > <link rel ="icon" href ="wechat.ico" > </head > <body > <h1 > 姑娘,你好,我是Jaden,请问约吗?嘻嘻~~</h1 > <img src ="meinv.png" alt ="" width ="100" height ="100" > <script src ="test.js" > </script > </body > </html >
同样使用我们之前的 Python 程序,来看效果:
发现 js 和 css 的效果都没有出来,并且我们看一下浏览器调试窗口的那个 network:
在下来我们在 network 里面点击那个 test.css
文件,看看请求是什么:
还有就是当我们直接在浏览器上保存某个页面的时候,随便一个页面,我们到页面上点击右键另存为,然后存到本地的一个目录下,你会发现这个页面的 html、css、js、图片等文件都跟着保存下来了,我保存了一下博客园首页的页面,看,是一个文件夹和一个 HTML 文件:
我们点开博客园那个文件夹看看里面都有什么:
发现 js、css 还有图片什么的都被保存了下来。这说明什么?说明这些文件本身就存在浏览器上了。哦,原来就是将 HTML 页面需要的 css、js、图片等文件也发送给浏览器就可以了。并且这些静态文件都是浏览器单独过来请求的,其实和标签的属性有有关系。css 文件是 link 标签的 href 属性:<link rel="stylesheet" href="test.css">
,js 文件是 script 标签的 src 属性:<script src="test.js"></script>
,图片文件是 img 标签的 src 属性:<img src="meinv.png" alt="" width="100" height="100">
,那个 .ico 文件是 link 标签的属性:<link rel="icon" href="wechat.ico">
。其实这些属性都会在页面加载的时候,单独到自己对应的属性值里面取请求对应的文件数据。而且我们如果在值里面写的都是自己本地的路径,那么都会来自己的本地路径来找。如果我们写的是相对路径,就会到我们自己的网址 + 文件名称,这个路径来找它需要的文件。所以我们只需要将这些请求做一些响应,将对应的文件数据相应给浏览器就可以了!
并且我们通过前面的查看,能够发现,浏览器 url 的请求路径我们知道是什么,静态文件不是也这样请求的吗,好,我们针对不同的路径给它返回不同的文件, 非常好!我们来尝试一下!
三、返回静态文件的高级 Web框架 还是用第二个 Web 框架里面的那个 HTML 文件,我们只需要写一些我们的服务端程序就可以了。同样是 test.py
文件,内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 import socketsk = socket.socket() sk.bind(('127.0.0.1' ,8001 )) sk.listen() while 1 : conn,addr = sk.accept() from_b_msg = conn.recv(1024 ) str_msg = from_b_msg.decode('utf-8' ) path = str_msg.split('\r\n' )[0 ].split(' ' )[1 ] print ('path>>>' ,path) conn.send(b'HTTP/1.1 200 ok \r\n\r\n' ) if path == '/' : print (from_b_msg) with open ('test.html' ,'rb' ) as f: data = f.read() conn.send(data) conn.close() elif path == '/meinv.png' : with open ('meinv.png' ,'rb' ) as f: pic_data = f.read() conn.send(pic_data) conn.close() elif path == '/test.css' : with open ('test.css' ,'rb' ) as f: css_data = f.read() conn.send(css_data) conn.close() elif path == '/wechat.ico' : with open ('wechat.ico' ,'rb' ) as f: ico_data = f.read() conn.send(ico_data) conn.close() elif path == '/test.js' : with open ('test.js' ,'rb' ) as f: js_data = f.read() conn.send(js_data) conn.close()
运行起来我们的 py 文件,然后在浏览器访问一下我们的服务端,看效果:
666666,完全搞定了。自己通过 socket 已经完全搞定了 web 项目,激动不。哈哈,我们再来完善一下。
四、函数版高级 Web 框架 HTML 文件和其他的静态文件还是我们上面使用的,我们这次只是用函数把方法封装一下。
Python 代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 import socketsk = socket.socket() sk.bind(('127.0.0.1' ,8001 )) sk.listen() def func1 (conn ): with open ('test.html' , 'rb' ) as f: data = f.read() conn.send(data) conn.close() def func2 (conn ): with open ('meinv.png' , 'rb' ) as f: pic_data = f.read() conn.send(pic_data) conn.close() def func3 (conn ): with open ('test.css' , 'rb' ) as f: css_data = f.read() conn.send(css_data) conn.close() def func4 (conn ): with open ('wechat.ico' , 'rb' ) as f: ico_data = f.read() conn.send(ico_data) conn.close() def func5 (conn ): with open ('test.js' , 'rb' ) as f: js_data = f.read() conn.send(js_data) conn.close() while 1 : conn,addr = sk.accept() from_b_msg = conn.recv(1024 ) str_msg = from_b_msg.decode('utf-8' ) path = str_msg.split('\r\n' )[0 ].split(' ' )[1 ] print ('path>>>' ,path) conn.send(b'HTTP/1.1 200 ok \r\n\r\n' ) print (from_b_msg) if path == '/' : func1(conn) elif path == '/meinv.png' : func2(conn) elif path == '/test.css' : func3(conn) elif path == '/wechat.ico' : func4(conn) elif path == '/test.js' : func5(conn)
五、更高级版(多线程版)Web 框架 应用上我们并发编程的内容,反正 HTML 文件和静态文件都直接给浏览器,那大家就一块并发处理。HTML 文件和静态文件还是上面的。
Python 代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 import socketfrom threading import Threadsk = socket.socket() sk.bind(('127.0.0.1' ,8001 )) sk.listen() def func1 (conn ): with open ('test.html' , 'rb' ) as f: data = f.read() conn.send(data) conn.close() def func2 (conn ): with open ('meinv.png' , 'rb' ) as f: pic_data = f.read() conn.send(pic_data) conn.close() def func3 (conn ): with open ('test.css' , 'rb' ) as f: css_data = f.read() conn.send(css_data) conn.close() def func4 (conn ): with open ('wechat.ico' , 'rb' ) as f: ico_data = f.read() conn.send(ico_data) conn.close() def func5 (conn ): with open ('test.js' , 'rb' ) as f: js_data = f.read() conn.send(js_data) conn.close() while 1 : conn,addr = sk.accept() from_b_msg = conn.recv(1024 ) str_msg = from_b_msg.decode('utf-8' ) path = str_msg.split('\r\n' )[0 ].split(' ' )[1 ] print ('path>>>' ,path) conn.send(b'HTTP/1.1 200 ok \r\n\r\n' ) print (from_b_msg) if path == '/' : t = Thread(target=func1,args=(conn,)) t.start() elif path == '/meinv.png' : t = Thread(target=func2, args=(conn,)) t.start() elif path == '/test.css' : t = Thread(target=func3, args=(conn,)) t.start() elif path == '/wechat.ico' : t = Thread(target=func4, args=(conn,)) t.start() elif path == '/test.js' : t = Thread(target=func5, args=(conn,)) t.start()
六、更更高级版 Web 框架 if 判断太多了,开线程的方式也比较恶心,有多少个if判断,就写多少次创建线程,简化一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 import socketfrom threading import Threadsk = socket.socket() sk.bind(('127.0.0.1' ,8001 )) sk.listen() def func1 (conn ): conn.send(b'HTTP/1.1 200 ok\r\ncontent-type:text/html\r\ncharset:utf-8\r\n\r\n' ) with open ('test.html' , 'rb' ) as f: data = f.read() conn.send(data) conn.close() def func2 (conn ): conn.send(b'HTTP/1.1 200 ok\r\n\r\n' ) with open ('meinv.png' , 'rb' ) as f: pic_data = f.read() conn.send(pic_data) conn.close() def func3 (conn ): conn.send(b'HTTP/1.1 200 ok\r\n\r\n' ) with open ('test.css' , 'rb' ) as f: css_data = f.read() conn.send(css_data) conn.close() def func4 (conn ): conn.send(b'HTTP/1.1 200 ok\r\n\r\n' ) with open ('wechat.ico' , 'rb' ) as f: ico_data = f.read() conn.send(ico_data) conn.close() def func5 (conn ): conn.send(b'HTTP/1.1 200 ok\r\n\r\n' ) with open ('test.js' , 'rb' ) as f: js_data = f.read() conn.send(js_data) conn.close() l1 = [ ('/' ,func1), ('/meinv.png' ,func2), ('/test.css' ,func3), ('/wechat.ico' ,func4), ('/test.js' ,func5), ] def fun (path,conn ): for i in l1: if i[0 ] == path: t = Thread(target=i[1 ],args=(conn,)) t.start() while 1 : conn,addr = sk.accept() from_b_msg = conn.recv(1024 ) str_msg = from_b_msg.decode('utf-8' ) path = str_msg.split('\r\n' )[0 ].split(' ' )[1 ] print ('path>>>' ,path) print (from_b_msg) fun(path,conn)
七、根据不同路径返回不同页面的 Web 框架 既然知道了我们可以根据不同的请求路径来返回不同的内容,那么我们可不可以根据用户访问的不同路径,返回不同的页面啊,嗯,应该是可以的。
自己创建两个 HTML 文件,写几个标签在里面,名为 index.html
和 home.html
。然后根据不同的路径返回不同的页面,我就给大家写上 Python 代码吧:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 """ 根据URL中不同的路径返回不同的内容 返回独立的HTML页面 """ import socketsk = socket.socket() sk.bind(("127.0.0.1" , 8080 )) sk.listen() def index (url ): 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" ) list1 = [ ("/index/" , index), ("/home/" , home), ] while 1 : conn, add = sk.accept() data = conn.recv(8096 ) data = str (data, encoding="utf8" ) data1 = data.split("\r\n" )[0 ] url = data1.split()[1 ] conn.send(b'HTTP/1.1 200 OK\r\n\r\n' ) 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()
八、返回动态页面的 Web 框架 这网页能够显示出来了,但是都是静态的啊。页面的内容都不会变化的,我想要的是动态网站,动态网站的意思是里面有动态变化的数据,而不是页面里面有动态效果,这个大家要注意啊。
没问题,我也有办法解决。我选择使用字符串替换来实现这个需求。(这里使用时间戳来模拟动态的数据,还是只给大家 Python 代码吧)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 """ 根据URL中不同的路径返回不同的内容 返回HTML页面 让网页动态起来 """ import socketimport timesk = socket.socket() sk.bind(("127.0.0.1" , 8080 )) 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" ) list1 = [ ("/index/" , index), ("/home/" , home), ] while 1 : conn, add = sk.accept() data = conn.recv(8096 ) data = str (data, encoding="utf8" ) data1 = data.split("\r\n" )[0 ] url = data1.split()[1 ] conn.send(b'HTTP/1.1 200 OK\r\n\r\n' ) 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()
这八个框架让大家满意了吧,这下子明白整个 Web 框架的原理了吧,哈哈。但是我们写的框架还是太 low 了,不够强壮。那别人已经开发好了很多nb的框架了,如:Django、Flask、Tornado 等等,我们学学怎么用就可以啦。但是注意一个问题,我们在里面获取路径的时候,是按照 \r\n
来分割然后再通过空格来分割获取到的路径。但是如果不是 HTTP 协议的话,你自己要注意消息格式了。
接下来我们看一个别人写好的模块来搞的 Web 框架,这个模块叫做 wsgiref。
九、wsgiref 模块版 Web 框架 wsgiref 模块其实就是将整个请求信息给封装了起来,不需要你自己处理了。假如它将所有请求信息封装成了一个叫做 request 的对象,那么你直接 request.path
就能获取到用户这次请求的路径,request.method
就能获取到本次用户请求的请求方式(get 还是 post)等。那这个模块用起来,我们再写 Web 框架是不是就简单了好多啊。
对于真实开发中的 Python Web 程序来说,一般会分为两部分:服务器程序和应用程序。
服务器程序负责对 socket 服务器进行封装,并在请求到来时,对请求的各种数据进行整理。
应用程序则负责具体的逻辑处理。为了方便应用程序的开发,就出现了众多的 Web 框架,例如:Django、Flask、web.py
等。不同的框架有不同的开发方式,但是无论如何,开发出的应用程序都要和服务器程序配合,才能为用户提供服务。
这样,服务器程序就需要为不同的框架提供不同的支持。这样混乱的局面无论对于服务器还是框架,都是不好的。对服务器来说,需要支持各种不同框架,对框架来说,只有支持它的服务器才能被开发出的应用使用。最简单的 Web 应用就是先把 HTML 用文件保存好,用一个现成的 HTTP 服务器软件,接收用户请求,从文件中读取HTML,返回。如果要动态生成 HTML,就需要把上述步骤自己来实现。不过,接受 HTTP 请求、解析 HTTP 请求、发送 HTTP 响应都是苦力活,如果我们自己来写这些底层代码,还没开始写动态 HTML 呢,就得花个把月去读 HTTP 规范。
正确的做法是底层代码由专门的服务器软件实现,我们用 Python 专注于生成 HTML 文档。因为我们不希望接触到 TCP 连接、HTTP 原始请求和响应格式。所以,需要一个统一的接口协议来实现这样的服务器软件,让我们专心用 Python 编写 Web 业务。
这时候,标准化就变得尤为重要。我们可以设立一个标准,只要服务器程序支持这个标准,框架也支持这个标准,那么他们就可以配合使用。一旦标准确定,双方各自实现。这样,服务器可以支持更多支持标准的框架,框架也可以使用更多支持标准的服务器。
WSGI(Web Server Gateway Interface)就是一种规范,它定义了使用 Python 编写的 Web 应用程序与 Web 服务器程序之间的接口格式,实现 Web 应用程序与 Web 服务器程序间的解耦。
常用的 WSGI 服务器有 uwsgi、Gunicorn。而 Python 标准库提供的独立 WSGI 服务器叫 wsgiref。Django 开发环境用的就是这个模块来做服务器。
好,接下来我们就看一下(能理解就行,了解就可以了):先看看 wsfiref 怎么使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 from wsgiref.simple_server import make_serverdef application (environ, start_response ): ''' :param environ: 是全部加工好的请求信息,加工成了一个字典,通过字典取值的方式就能拿到很多你想要拿到的信息 :param start_response: 帮你封装响应信息的(响应行和响应头),注意下面的参数 :return: ''' start_response('200 OK' , [('k1' ,'v1' ),]) print (environ) print (environ['PATH_INFO' ]) return [b'<h1>Hello, web!</h1>' ] httpd = make_server('127.0.0.1' , 8080 , application) print ('Serving HTTP on port 8080...' )httpd.serve_forever()
来一个完整的 Web 项目,用户登录认证的项目。我们需要连接数据库了,所以先到 MySQL 数据库里面准备一些表和数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 mysql> create database db1; Query OK, 1 row affected (0.00 sec) mysql> use db1; Database changed mysql> create table userinfo(id int primary key auto_increment,username char(20) not null unique,password char(20) not null); Query OK, 0 rows affected (0.23 sec) mysql> insert into userinfo(username,password) values('chao','666'),('sb1','222'); Query OK, 2 rows affected (0.03 sec) Records: 2 Duplicates: 0 Warnings: 0 mysql> select * from userinfo; +----+----------+----------+ | id | username | password | +----+----------+----------+ | 1 | chao | 666 | | 2 | sb1 | 222 | +----+----------+----------+ 2 rows in set (0.00 sec)
然后再创建这么几个文件:
Python 文件名称 webmodel.py
,内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 def createtable (): import pymysql conn = pymysql.connect( host='127.0.0.1' , port=3306 , user='root' , password='666' , database='db1' , charset='utf8' ) cursor = conn.cursor(pymysql.cursors.DictCursor) sql = ''' -- 创建表 create table userinfo(id int primary key auto_increment,username char(20) not null unique,password char(20) not null); -- 插入数据 insert into userinfo(username,password) values('chao','666'),('sb1','222'); ''' cursor.execute(sql) conn.commit() cursor.close() conn.close()
Python 的名为 webauth.py
文件,内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 def auth (username,password ): import pymysql conn = pymysql.connect( host='127.0.0.1' , port=3306 , user='root' , password='123' , database='db1' , charset='utf8' ) print ('userinfo' ,username,password) cursor = conn.cursor(pymysql.cursors.DictCursor) sql = 'select * from userinfo where username=%s and password=%s;' res = cursor.execute(sql, [username, password]) if res: return True else : return False
用户输入用户名和密码的文件,名为 web.html
,内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Title</title > </head > <body > <form action ="http://127.0.0.1:8080/auth/" method ="get" > 用户名<input type ="text" name ="username" > 密码 <input type ="password" name ="password" > <input type ="submit" > </form > <script > </script > </body > </html >
用户验证成功后跳转的页面,显示成功,名为 websuccess.html
,内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Title</title > <style > h1 { color :red; } </style > </head > <body > <h1 > 宝贝儿,恭喜你登陆成功啦</h1 > </body > </html >
Python 服务端代码(主逻辑代码),名为 web_python.py
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 from urllib.parse import parse_qsfrom wsgiref.simple_server import make_serverimport webauthdef application (environ, start_response ): start_response('200 OK' , [('Content-Type' , 'text/html' )]) print (environ) print (environ['PATH_INFO' ]) path = environ['PATH_INFO' ] if path == '/login' : with open ('web.html' ,'rb' ) as f: data = f.read() elif path == '/auth/' : if environ.get("REQUEST_METHOD" ) == "POST" : try : request_body_size = int (environ.get('CONTENT_LENGTH' , 0 )) except (ValueError): request_body_size = 0 request_data = environ['wsgi.input' ].read(request_body_size) print ('>>>>>' ,request_data) print ('?????' ,environ['QUERY_STRING' ]) re_data = parse_qs(request_data) print ('拆解后的数据' ,re_data) pass if environ.get("REQUEST_METHOD" ) == "GET" : print ('?????' ,environ['QUERY_STRING' ]) request_data = environ['QUERY_STRING' ] re_data = parse_qs(request_data) print ('拆解后的数据' , re_data) username = re_data['username' ][0 ] password = re_data['password' ][0 ] print (username,password) status = webauth.auth(username,password) if status: with open ('websuccess.html' ,'rb' ) as f: data = f.read() else : data = b'auth error' else : data = b'sorry 404!,not found the page' return [data] httpd = make_server('127.0.0.1' , 8080 , application) print ('Serving HTTP on port 8080...' )httpd.serve_forever()
把代码拷走,创建文件,放到同一个目录下,运行一下 we_python.py
文件的代码就能看到效果。注意先输入的网址是 127.0.0.1:8080/login
,还要注意你的 MySQL 数据库没有问题。
十、起飞版 Web 框架 我们上一个 Web 框架把所有的代码都写在了一个 py 文件中,我们拆到其他文件里面去,并且针对不用的路径来进行分发请求的时候都用的if判断,很多值得优化的地方,好,结合我们前面几个版本的优势我们来优化一下,分几个文件和文件夹。
文件的目录结构为:
1 2 3 4 5 6 7 8 9 10 web_framework/ ├── templates/ # 用于存放 HTML 文档 | ├── index.html/ | ├── web.html/ | ├── websuccess.html/ ├── manage.py/ # 主应用 ├── model.py/ # 数据库相关操作 ├── urls.py/ # ├── views.py/ └── webauth.py/
首先是三个 HTML 文件。
index.html
1 2 3 4 5 6 7 8 9 10 11 12 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Title</title > </head > <body > <a href ="http://127.0.0.1:8080/login" > 请登录</a > </body > </html >
web.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Title</title > </head > <body > <form action ="http://127.0.0.1:8080/auth/" method ="get" > 用户名<input type ="text" name ="username" > 密码 <input type ="password" name ="password" > <input type ="submit" > </form > <script > </script > </body > </html >
websuccess.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Title</title > <style > h1 { color :red; } </style > </head > <body > <h1 > 宝贝儿,恭喜你登陆成功啦</h1 > </body > </html >
然后就是整个框架的入口,manage.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 from urls import urlpatternsfrom wsgiref.simple_server import make_serverdef application (environ, start_response ): start_response('200 OK' , [('Content-Type' , 'text/html' )]) path = environ['PATH_INFO' ] for url_tuple in urlpatterns: if url_tuple[0 ] == path: data = url_tuple[1 ](environ) break else : data = b'sorry 404!,not found the page' return [data] httpd = make_server('127.0.0.1' , 8080 , application) print ('Serving HTTP on port 8080...' )httpd.serve_forever()
然后是数据库相关的 model.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 def createtable (): import pymysql conn = pymysql.connect( host='127.0.0.1' , port=3306 , user='root' , password='666' , database='db1' , charset='utf8' ) cursor = conn.cursor(pymysql.cursors.DictCursor) sql = ''' -- 创建表 create table userinfo(id int primary key auto_increment,username char(20) not null unique,password char(20) not null); -- 插入数据 insert into userinfo(username,password) values('chao','666'),('sb1','222'); ''' cursor.execute(sql) conn.commit() cursor.close() conn.close()
url 相关的 urls.py
1 2 3 4 5 6 7 8 9 10 11 from views import login,auth,favicon,index,timerurlpatterns=[ ('/login' ,login), ('/auth/' ,auth), ('/favicon.ico' ,favicon), ('/' ,index), ('/timer' ,timer), ]
每一个请求方法的 views.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 import datetimeimport webauthfrom urllib.parse import parse_qsdef login (environ ): with open ('templates/web.html' , 'rb' ) as f: data = f.read() return data def auth (environ ): if environ.get("REQUEST_METHOD" ) == "POST" : try : request_body_size = int (environ.get('CONTENT_LENGTH' , 0 )) except (ValueError): request_body_size = 0 request_data = environ['wsgi.input' ].read(request_body_size) print ('>>>>>' , request_data) print ('?????' , environ['QUERY_STRING' ]) re_data = parse_qs(request_data) print ('拆解后的数据' , re_data) pass if environ.get("REQUEST_METHOD" ) == "GET" : print ('?????' , environ['QUERY_STRING' ]) request_data = environ['QUERY_STRING' ] re_data = parse_qs(request_data) print ('拆解后的数据' , re_data) username = re_data['username' ][0 ] password = re_data['password' ][0 ] print (username, password) status = webauth.auth(username, password) if status: with open ('templates/websuccess.html' , 'rb' ) as f: data = f.read() else : data = b'auth error' return data def favicon (environ ): with open ('wechat.ico' ,'rb' ) as f: data = f.read() return data def index (environ ): with open ('templates/index.html' ,'rb' ) as f: data = f.read() return data def timer (environ ): data = str (datetime.datetime.now()).encode('utf-8' ) return data
最后还有权限相关的 webauth.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 def auth (username,password ): import pymysql conn = pymysql.connect( host='127.0.0.1' , port=3306 , user='root' , password='123' , database='db1' , charset='utf8' ) print ('userinfo' ,username,password) cursor = conn.cursor(pymysql.cursors.DictCursor) sql = 'select * from userinfo where username=%s and password=%s;' res = cursor.execute(sql, [username, password]) if res: return True else : return False