咱们能够这样理解:全部的Web应用本质上就是一个socket服务端,而用户的浏览器就是一个socket客户端。 这样咱们就能够本身实现Web框架了。html
import socket #实例化服务端对象 sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #为对象绑定ip地址和端口号 sk.bind(('127.0.0.1',80)) #监听端口 sk.listen(5) while 1: #获取客户端链接对象和客户端地址信息 conn,addr = sk.accept() #获取客户端发送的信息 data = conn.recv(9000) #响应客户端发送响应行() conn.send(b'HTTP/1.1 200 OK\r\n\r\n') #发送响应数据 conn.send(b'<h1>hello kingfan!</h1>') #关闭与客户端链接 conn.close()
能够说Web服务本质上都是在这十几行代码基础上扩展出来的。这段代码就是它们的祖宗。python
用户的浏览器一输入网址,会给服务端发送数据,那浏览器会发送什么数据?怎么发?这个谁来定? 你这个网站是这个规定,他那个网站按照他那个规定,这互联网还能玩么?mysql
因此,必须有一个统一的规则,让你们发送消息、接收消息的时候有个格式依据,不能随便写。web
这个规则就是HTTP协议,之后浏览器发送请求信息也好,服务器回复响应信息也罢,都要按照这个规则来。sql
HTTP协议主要规定了客户端和服务器之间的通讯格式,那HTTP协议是怎么规定消息格式的呢?数据库
让咱们首先打印下咱们在服务端接收到的消息是什么。浏览器
import socket #实例化服务端对象 sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #为对象绑定ip地址和端口号 sk.bind(('127.0.0.1',80)) #监听端口 sk.listen(5) while 1: #获取客户端链接对象和客户端地址信息 conn,addr = sk.accept() #获取客户端发送的信息 data = conn.recv(9000) print(data) #响应客户端发送响应行() conn.send(b'HTTP/1.1 200 OK\r\n\r\n') #发送响应数据 conn.send(b'<h1>hello kingfan!</h1>') #关闭与客户端链接 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'
通过上面的补充学习,咱们知道了要想让咱们本身写的web server端正经起来,必需要让咱们的Web server在给客户端回复消息的时候按照HTTP协议的规则加上响应状态行,这样咱们就实现了一个正经的Web框架了服务器
import socket #实例化服务端对象 sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #为对象绑定ip地址和端口号 sk.bind(('127.0.0.1',80)) #监听端口 sk.listen(5) while 1: #获取客户端链接对象和客户端地址信息 conn,addr = sk.accept() #获取客户端发送的信息 data = conn.recv(9000) print(data) #响应客户端发送响应行() conn.send(b'HTTP/1.1 200 OK\r\n\r\n') #发送响应数据 conn.send(b'<h1>hello kingfan!</h1>') #关闭与客户端链接 conn.close()
咱们经过十几行代码简单地演示了web 框架的本质。app
接下来就让咱们继续完善咱们的自定义web框架吧框架
这样就结束了吗? 如何让咱们的Web服务根据用户请求的URL不一样而返回不一样的内容呢?
小事一桩,咱们能够从请求相关数据里面拿到请求URL的路径,而后拿路径作一个判断...
import socket sk = socket.socket() sk.bind(('127.0.0.1', 8080)) sk.listen(5) def login(): return bytes('欢迎来到登陆页面', encoding='utf8') def register(): return bytes('欢迎来到注册界面', encoding='utf8') def home(): return bytes('欢迎来到主页面', encoding='utf8') # 返回一个html文件 def web(): with open('login.html', 'rb') as f1: return f1.read() url_lis = [('/login/', login), ('/register/', register), ('/home/', home), ('/web/', web) ] while 1: conn, addr = sk.accept() # 接受请求消息 data = conn.recv(9000) data = data.decode('utf-8') # 获取url是什么 url = (data.split('\r\n')[0].split(' ')[1]) # 在本地url列表中查找url地址有则执行相应的函数 for i in url_lis: if url == i[0]: fun = i[1] msg = fun() break # 若是没有则发送错误信息 else: msg = b'404 not found' print(msg) # 发送响应状态(Content-Type: text/html; charset=utf-8)响应头指定返回数据类型和编码格式 conn.send(b'HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\n\r\n') # 发送响应数据 conn.send(msg) conn.close()
这网页可以显示出来了,可是都是静态的啊。页面的内容都不会变化的,我想要的是动态网站。
没问题,我也有办法解决。我选择使用字符串替换来实现这个需求。(这里使用时间戳来模拟动态的数据)
import socket, time sk = socket.socket() sk.bind(('127.0.0.1', 8080)) sk.listen(5) def login(): return bytes('欢迎来到登陆页面', encoding='utf8') def register(): return bytes('欢迎来到注册界面', encoding='utf8') def home(): return bytes('欢迎来到主页面', encoding='utf8') def nowtime(): # 将time.html文件中的XX-XX-XX替换成如今时间 with open('time.html', 'r', encoding='utf-8') as f1: msg = f1.read() msg = msg.replace('XX-XX-XX', str(time.strftime("%c"))) return bytes(msg, encoding='utf-8') # 返回一个html文件 def web(): with open('login.html', 'rb') as f1: return f1.read() url_lis = [('/login/', login), ('/register/', register), ('/home/', home), ('/web/', web), ('/time/', nowtime) ] while 1: conn, addr = sk.accept() # 接受请求消息 data = conn.recv(9000) data = data.decode('utf-8') # 获取url是什么 url = (data.split('\r\n')[0].split(' ')[1]) # 在本地url列表中查找url地址有则执行相应的函数 for i in url_lis: if url == i[0]: fun = i[1] msg = fun() break # 若是没有则发送错误信息 else: msg = b'404 not found' print(msg) # 发送响应状态(Content-Type: text/html; charset=utf-8)响应头指定返回数据类型和编码格式 conn.send(b'HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\n\r\n') # 发送响应数据 conn.send(msg) 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模块来替换咱们本身写的web框架的socket server部分:
import time from wsgiref.simple_server import make_server def login(): return bytes('欢迎来到登陆页面', encoding='utf8') def register(): return bytes('欢迎来到注册界面', encoding='utf8') def home(): return bytes('欢迎来到主页面', encoding='utf8') def nowtime(): # 将time.html文件中的XX-XX-XX替换成如今时间 with open('time.html', 'r', encoding='utf-8') as f1: msg = f1.read() msg = msg.replace('XX-XX-XX', str(time.strftime("%c"))) return bytes(msg, encoding='utf-8') # 返回一个html文件 def web(): with open('login.html', 'rb') as f1: return f1.read() url_lis = [('/login/', login), ('/register/', register), ('/home/', home), ('/web/', web), ('/time/', nowtime) ] def run(environ, start_response): # 设置HTTP响应的状态ma码和头信息(指定返回类型和编码格式) start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ]) # 获取用户访问的url 本质返回跟前例子同样如/time/ url = environ['PATH_INFO'] for i in url_lis: if url == i[0]: fun = i[1] msg = fun() break # 若是没有则发送错误信息 else: msg = b'404 not found' # wsgire模块将msg返回给客户,底层依旧是socket return [msg, ] if __name__ == '__main__': httpd = make_server('127.0.0.1', 8080, run) print('等待链接') httpd.serve_forever()
上面的代码实现了一个简单的动态,我彻底能够从数据库中查询数据,而后去替换我html中的对应内容,而后再发送给浏览器完成渲染。 这个过程就至关于HTML模板渲染数据。 本质上就是HTML内容中利用一些特殊的符号来替换要展现的数据。 我这里用的特殊符号是我定义的,其实模板渲染有个现成的工具: jinja2
import time,pymysql from wsgiref.simple_server import make_server from jinja2 import Template def login(): return bytes('欢迎来到登陆页面', encoding='utf8') def register(): return bytes('欢迎来到注册界面', encoding='utf8') def home(): return bytes('欢迎来到主页面', encoding='utf8') def nowtime(): # 将time.html文件中的XX-XX-XX替换成如今时间 with open('time.html', 'r', encoding='utf-8') as f1: msg = f1.read() msg = msg.replace('XX-XX-XX', str(time.strftime("%c"))) return bytes(msg, encoding='utf-8') def userinfo(): conn = pymysql.connect( host = '127.0.0.1', port =3306, user = 'root', password='1234', database='db1', charset = 'utf8' ) cursor = conn.cursor(cursor=pymysql.cursors.DictCursor) cursor.execute('select * from staff_info;') user_list1 = cursor.fetchall() print(user_list1) with open('userinfo.html', 'r', encoding='utf8') as f1: data = f1.read() template = Template(data) msg = template.render({'user_list':user_list1}) return bytes(msg, encoding="utf8") # 返回一个html文件 def web(): with open('login.html', 'rb') as f1: return f1.read() url_lis = [('/login/', login), ('/register/', register), ('/home/', home), ('/web/', web), ('/time/', nowtime), ('/userinfo/',userinfo) ] def run(environ, start_response): # 设置HTTP响应的状态ma码和头信息(指定返回类型和编码格式) start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ]) # 获取用户访问的url 本质返回跟前例子同样如/time/ url = environ['PATH_INFO'] print(url) for i in url_lis: if url == i[0]: fun = i[1] msg = fun() break # 若是没有则发送错误信息 else: msg = b'404 not found' # wsgire模块将msg返回给客户,底层依旧是socket return [msg, ] if __name__ == '__main__': httpd = make_server('127.0.0.1', 8080, run) print('等待链接') httpd.serve_forever()