web 框架本质及第一个Django实例
web 框架本质
我们可以这样理解,所有的web应用本质就是一个socket服务端,而用户的浏览器就是一个socket客户端,这样我们就可以自己实现web框架了。
半成品自定义web框架 import socket server = socket.socket() server.bind(('127.0.0.1',8080)) server.listen() while True: conn,addr = server.acceqt() data = conn.recv(1024) conn.send(b'ok') #回复消息 conn.close()
这里我们需要回顾一下之前学习的HTTP协议
https://www.cnblogs.com/gaobei/articles/9093058.html
HTTP协议对收发消息的格式要求
每个HTTP请求和响应都应该遵循相同的格式,一个HTTP包含HEADER和Body两部分,其中Body是可选的,HTTP响应的Header中有一个Content-Type表明响应的内容格式,如text/html表示HTML网页。
HTTP GET请求的格式:
HTTP响应的格式:
简单版的web框架
经过上面的补充学习,我们知道了要想让我们自己写的web server端正常起来,必须要让我们的Web server在给客户端回复消息的时候按照HTTP协议的规则加上响应状态行,这样我们就实现了一个简单的Web框架了。 import socket sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind(('127.0.0.1', 8080)) sock.listen() while True: conn, addr = sock.accept() data = conn.recv(1024) # 给回复的消息加上响应状态行 conn.send(b"HTTP/1.1 200 OK\r\n\r\n<h1>哈哈哈哈</h1>") conn.close()
这里我们就通过几十行代码简单的演示web框架的本质,接下来就继续完善我们自定义的web框架吧。
根据不同的路径返回不同的内容
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()
不同的路径返回不同的内容,优化频繁的if判断
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 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 i in list1: if i[0] == url: func = i[1] break
else:
func = None
if func: response = func(url) #执行对应的函数 else: response = b"<h1>404</h1>" #找不到要执行的函数就返回404 # 返回具体的响应消息
conn.send(b'HTTP/1.1 200 OK\r\n\r\n') conn.send(response) conn.close()
返回具体的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] break else: func = None if func: msg= func(url) else: msg = b'<h1>404</h1>' # 找不到要执行的函数就返回404 # 按照HTTP协议的格式要求 回复消息 conn.send(b'HTTP/1.1 200 OK\r\n\r\n') # 发送状态行 conn.send(msg) # 发送响应体 conn.close()
让网页动起来
import socket import time server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind(('127.0.0.1', 8888)) server.listen() def home(url): s = "this is {} page!".format(url) return bytes(s, encoding="utf8") def index(url): return b'<h1>index page</h1>' def user(url): # 不同的用户得到的页面上显示不同的时间 c_time = str(time.time()) with open("user.html", "r") as f: data_s = f.read() data_s = data_s.replace("@@xx@@", c_time) # 替换后的字符串数据 return bytes(data_s, encoding="utf8") def login(url): with open("login.html", "rb") as f: return f.read() # url和将要执行的函数的对应关系 url2func = [ ("/index/", index), ("/home/", home), ("/user/", user), ("/login/", login), ] while 1: conn, addr = server.accept() data = conn.recv(8096) # 收消息 # print(data) # 从浏览器发送消息中,拿到用户访问的路径 data_str = str(data, encoding="utf8") # print(data_str) url = data_str.split("\r\n")[0].split(" ")[1] print(url) func = None for i in url2func: if url == i[0]: func = i[1] # 拿到将要执行的函数 break else: func = None if func: msg = func(url) # 执行对应的函数 else: msg = b'<h1>404</h1>' # 找不到要执行的函数就返回404 # 按照HTTP协议的格式要求 回复消息 conn.send(b'HTTP/1.1 200 OK\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模块
我们利用wsgiref模块来替我们自己写的web框架的socket server部分:
""" 根据浏览器访问的路径的不同,返回不同的内容 将不同页面的处理代码封装到函数中 优化频繁的if判断 返回具体的HTML文件 实现不同的用户得到不同的HTML页面 """ import time from wsgiref.simple_server import make_server def home(url): s = "this is {} page!".format(url) return bytes(s, encoding="utf8") def index(url): return b'<h1>index page</h1>' def user(url): # 不同的用户得到的页面上显示不同的时间 c_time = str(time.time()) with open("user.html", "r") as f: data_s = f.read() data_s = data_s.replace("@@xx@@", c_time) # 替换后的字符串数据 return bytes(data_s, encoding="utf8") def login(url): with open("login.html", "rb") as f: return f.read() # url和将要执行的函数的对应关系 url2func = [ ("/index/", index), ("/home/", home), ("/user/", user), ("/login/", login), ] # 按照wsgiref的要求定义一个run_server函数 def run_server(environ, start_response): """ :param environ: 跟请求相关的参数 :param start_response: :return: """ # 设置HTTP响应的状态码和头信息 start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ]) url = environ['PATH_INFO'] # 取到用户输入的url func = None for i in url2func: if url == i[0]: func = i[1] # 拿到将要执行的函数 break else: func = None if func: msg = func(url) # 执行对应的函数 else: msg = b'<h1>404</h1>' # 找不到要执行的函数就返回404 return [msg, ] if __name__ == '__main__': httpd = make_server('127.0.0.1', 8080, run_server) print("我在等你哦...") httpd.serve_forever()
jinja2
上面的代码实现了一个简单的动态,我完全可以从数据中查询数据,然后去替换我html中的对应内容,然后再发送给浏览器完成渲染。
这个过程就相当于HTML模板渲染数据。 本质上就是HTML内容中利用一些特殊的符号来替换要展示的数据。 我这里用的特殊符号是我定义的,其实模板渲染有个现成的工具: jinja2
下载jinja2:
pip install jinja2
结合数据库 先要在数据库中建一个表 CREATE TABLE user( id int auto_increment PRIMARY KEY, name CHAR(10) NOT NULL, hobby CHAR(20) NOT NULL ); import time from wsgiref.simple_server import make_server from jinja2 import Template import pymysql def home(url): s = "this is {} page!".format(url) return bytes(s, encoding="utf8") def index(url): return b'<h1>index page</h1>' def user(url): # 从数据库里面去到所有的用户信息, conn = pymysql.connect( host='127.0.0.1', port=3306, user='root', password='123', database='day61', charset='utf8' ) cursor = conn.cursor(cursor=pymysql.cursors.DictCursor) cursor.execute('select * from user') ret = cursor.fetchall() print(ret) # 在页面上显示出来 with open("user.html", "r", encoding="utf8") as f: data_s = f.read() template = Template(data_s) # 生成一个模板文件实例 msg = template.render({"user_list": ret}) # 把数据填充到模板里面 return bytes(msg, encoding="utf8") def login(url): with open("login.html", "rb") as f: return f.read() # url和将要执行的函数的对应关系 url2func = [ ("/index/", index), ("/home/", home), ("/user/", user), ("/login/", login), ] # 按照wsgiref的要求定义一个run_server函数 def run_server(environ, start_response): """ :param environ: 跟请求相关的参数 :param start_response: :return: """ # 设置HTTP响应的状态码和头信息 start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ]) url = environ['PATH_INFO'] # 取到用户输入的url func = None for i in url2func: if url == i[0]: func = i[1] # 拿到将要执行的函数 break else: func = None if func: msg = func(url) # 执行对应的函数 else: msg = b'<h1>404</h1>' # 找不到要执行的函数就返回404 return [msg, ] if __name__ == '__main__': httpd = make_server('127.0.0.1', 8090, run_server) print("我在8090等你哦...") httpd.serve_forever()
Django框架
安装Django:(在命令行中)
pip3 install django==1.11.11
创建一个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:8080
静态文件配置:
STATIC_URL = '/static/' # HTML中使用的静态文件夹前缀 STATICFILES_DIRS = [ os.path.join(BASE_DIR, "static"), # 静态文件存放位置 ]
列表内可以放多个文件存放的位置
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/")