Web框架的原理和Django初识
一、Web框架的本质 1、本质 实际上Web应用本质上就是一个socket服务端, 而用户的浏览器就是一个socket客户端。 2、最原始的web框架 socket服务端 import socket sk = socket.socket() sk.bind(("127.0.0.1", 80)) sk.listen() while True: conn, addr = sk.accept() data = conn.recv(5120) print(data) # 打印一下接收到的数据是什么 conn.send(b"Hello World") conn.close() 上面这几行代码就是最基础的web框架的服务端, 用户使用浏览器输入网址(IP),就会给服务端发送请求消息, 比如你在浏览器输入上面服务器的ip地址 127.0.0.1:80 浏览器就会帮你给服务端发送请求消息,服务端就可以接收到你的请求消息, 并对此可以做出相应的操作。 当然了,虽然上面的代码是web框架的本质,但是还不能进行消息的响应,为什么呢? 因为没有一个统一的规则啊,你想想如果每个人的网站都按照自己的意愿随便定制规则, 那浏览器不就崩了? 所以必须有一个统一的规则,让大家发送消息、接收消息的时候有个格式依据,不能随便写。 这个规则就是HTTP协议,以后浏览器发送请求信息也好,服务器回复响应信息也罢,都要按照这个规则来进行 现在去看一眼刚才收到的data数据是什么(客户端的请求数据) b'GET /index/ 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/69.0.3497.92 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\n\r\n' 然后可以再看一眼平时我们访问百度时浏览器接收到的响应信息 HTTP/1.1 301 Moved Permanently Date: Wed, 24 Oct 2018 07:06:00 GMT Content-Type: text/html; charset=UTF-8 Content-Length: 147 Location: https://www.cnblogs.com/ X-UA-Compatible: IE=10 X-Frame-Options: SAMEORIGIN 这样看起来,好像找不出什么规则,那就先了解一下HTTP协议(上一篇博客),在回来看看是否发现什么规则
HTTP协议对收发消息的格式要求: 每个HTTP请求和响应都遵循相同的格式,一个HTTP包含Header和Body两部分,其中Body是可选的(就是可以不写)。 HTTP响应的Header中有一个 Content-Type表明响应的内容格式。如 text/html表示HTML网页。 那么上面的请求数据data,我们也可以给它分出Header和Body两部分(\r\n代表回车换行),如下: GET /index/ HTTP/1.1 Host: 127.0.0.1:8080 Connection: keep-alive Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 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 请求数据 所以我们想要开始那段代码能顺利给客户端返回消息,我们也按照HTTP协议进行发送就好了 import socket sk = socket.socket() sk.bind(('127.0.0.1',80)) sk.listen() while True: conn,addr = sk.accept() data = conn.recv(5120) print(data) conn.send(b'HTTP/1.1 200 OK\r\n\r\nHello World') # HTTP/1.1 200 OK\r\n\r\n 是响应头部为空,响应正文为Hello World的HTTP响应格式 conn.close() 这个时候你在浏览器输入127.0.0.1:80就可以收到服务端给你返回的消息了 3、服务器例子 1、根据不同的路径返回不同的内容 注意:我们服务器接收到的请求数据全是bytes类型的,回车换行是用\r\n表示的 原始请求数据:b'GET /index/ 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/69.0.3497.92 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\n\r\n' """ Web框架原理: 根据不同的URL返回不同的内容 1. 先拿到用户访问的URL是什么 2. 返回不同的内容 """ import socket sk = socket.socket() sk.bind(('127.0.0.1',8080)) sk.listen() while 1: conn, addr = sk.accept() # socket收到的消息是bytes类型的 data = conn.recv(5120) # 从请求的消息中拿到请求的URL是什么 data_str = data.decode('utf8') # 按照\r\n分割字符串 list1 = data_str.split('\r\n') # url在第一个元素里面再进行分割取到 url = list1[0].split()[1] # 对不同的url返回不同的消息 if url == '/index/': msg = 'This is index page' elif url == '/home/': msg = 'This is home page' else: msg = '404 Not Found' conn.send(b'HTTP/1.1 200 OK\r\n\r\n') # 先发送响应行 conn.send(msg.encode('utf8')) conn.close() 此时你在浏览器输入: http://127.0.0.1:8080 ---> 404 Not Found http://127.0.0.1:8080/index/ ---> This is index page http://127.0.0.1:8080/home/ ---> This is home page 2、根据不同的路径返回不同的内容函数版 """ Web框架原理: 根据不同的URL返回不同的内容函数版 1. 先拿到用户访问的URL是什么 2. 返回不同的内容 """ import socket sk = socket.socket() sk.bind(('127.0.0.1',8080)) sk.listen() # 定义处理用户请求的函数 def index(url): s = '你访问的是%s页面' %url return s.encode('utf8') def home(url): s = '你访问的是%s页面' %url return s.encode('utf8') while 1: conn, addr = sk.accept() # socket收到的消息是bytes类型的 data = conn.recv(5120) # 从请求的消息中拿到请求的URL是什么 data_str = data.decode('utf8') # 按照\r\n分割字符串 list1 = data_str.split('\r\n') # url在第一个元素里面再进行分割取到 url = list1[0].split()[1] # 对不同的url返回不同的消息 if url == '/index/': msg = index(url) elif url == '/home/': msg = home(url) else: msg = b'404 Not Found' # 因为有中文,所有要在响应头部添加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() 3、根据不同的路径返回不同的内容函数进阶版 """ Web框架原理: 根据不同的URL返回不同的内容函数进阶版 1. 先拿到用户访问的URL是什么 2.设置一个url和函数的对应关系 3. 根据对应关系返回不同的内容 """ import socket sk = socket.socket() sk.bind(('127.0.0.1',8080)) sk.listen() # 定义处理用户请求的函数 def index(url): s = '你访问的是%s页面' %url return s.encode('utf8') def home(url): s = '你访问的是%s页面' %url return s.encode('utf8') # 定义一个用户访问的url和要执行的函数的对应关系 url_func = [ ('/index/', index), ('/home/', home) ] while 1: conn, addr = sk.accept() # socket收到的消息是bytes类型的 data = conn.recv(5120) # 从请求的消息中拿到请求的URL是什么 data_str = data.decode('utf8') # 按照\r\n分割字符串 list1 = data_str.split('\r\n') # url在第一个元素里面再进行分割取到 url = list1[0].split()[1] # 循环url列表,对不同的url返回不同的消息 func = None for i in url_func: if i[0] == url: func = i[1] break if func: msg = func(url) else: msg = b'404 Not Found' # 因为有中文,所有要在响应头部添加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() 4、返回具体的html页面 import socket sk = socket.socket() sk.bind(('127.0.0.1',8080)) sk.listen() # 定义处理用户请求的函数 def index(url): s = '你访问的是%s页面' %url return s.encode('utf8') def home(url): s = '你访问的是%s页面' %url return s.encode('utf8') def table(url): # 返回TableOp.html with open('TableOp.html', mode='rb') as f: return f.read() # 定义一个用户访问的url和要执行的函数的对应关系 url_func = [ ('/index/', index), ('/home/', home), ('/table/',table) ] while 1: conn, addr = sk.accept() # socket收到的消息是bytes类型的 data = conn.recv(5120) # 从请求的消息中拿到请求的URL是什么 data_str = data.decode('utf8') # 按照\r\n分割字符串 list1 = data_str.split('\r\n') # url在第一个元素里面再进行分割取到 url = list1[0].split()[1] # 循环url列表,对不同的url返回不同的消息 func = None for i in url_func: if i[0] == url: func = i[1] break if func: msg = func(url) else: msg = b'404 Not Found' # 因为有中文,所有要在响应头部添加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() 5、返回动态的html页面 """ 因为时间是会动的,所以这里用时间戳代表动态的事件 """
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>这是index页面!</h1> <p>现在的时间是:@t@</p> </body> </html>
import time import socket sk = socket.socket() sk.bind(('127.0.0.1',8080)) sk.listen() # 定义处理用户请求的函数 def index(url): with open('index.html', 'r', encoding='utf8') as f1: html_s = f1.read() # 根据用户不同,取出不同的数据 # 用不同的数据去替换页面上的特殊符号 now = str(time.strftime("%H:%M:%S")) msg = html_s.replace('@t@', now) return msg.encode('utf8') def home(url): s = '你访问的是%s页面' %url return s.encode('utf8') def table(url): # 返回TableOp.html with open('TableOp.html', mode='rb') as f: return f.read() # 定义一个用户访问的url和要执行的函数的对应关系 url_func = [ ('/index/', index), ('/home/', home), ('/table/', table) ] while 1: conn, addr = sk.accept() # socket收到的消息是bytes类型的 data = conn.recv(5120) # 从请求的消息中拿到请求的URL是什么 data_str = data.decode('utf8') # 按照\r\n分割字符串 list1 = data_str.split('\r\n') # url在第一个元素里面再进行分割取到 url = list1[0].split()[1] # 循环url列表,对不同的url返回不同的消息 func = None for i in url_func: if i[0] == url: func = i[1] break if func: msg = func(url) else: msg = b'404 Not Found' # 因为有中文,所有要在响应头部添加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() 二、服务器程序和应用程序 1、本质介绍 对于真实开发中的python web程序来说,一般会分为两部分:服务器程序和应用程序,然后通过一个协议连接起来,如下: 收发socket消息(Web服务器程序) --> uWsgi、Gunicorn、wsgiref (Nginx和tomcat) WSGI协议 业务逻辑不同(Web应用程序) --> Django、Flask、Webpy、bottle、Tornado 服务器程序负责对socket服务器进行封装,并在请求到来时,对请求的各种数据进行整理。 应用程序则负责具体的逻辑处理。为了方便应用程序的开发,就出现了众多的Web框架,例如:Django、Flask、web.py 等。不同的框架有不同的开发方式,但是无论如何,开发出的应用程序都要和服务器程序配合,才能为用户提供服务。 这样,服务器程序就需要为不同的框架提供不同的支持。这样混乱的局面无论对于服务器还是框架,都是不好的。对服务器来说,需要支持各种不同框架,对框架来说,只有支持它的服务器才能被开发出的应用使用。 这时候,标准化就变得尤为重要。我们可以设立一个标准,只要服务器程序支持这个标准,框架也支持这个标准,那么他们就可以配合使用。一旦标准确定,双方各自实现。这样,服务器可以支持更多支持标准的框架,框架也可以使用更多支持标准的服务器。 WSGI(Web Server Gateway Interface)就是一种规范,它定义了使用Python编写的web应用程序与web服务器程序之间的接口格式,实现web应用程序与web服务器程序间的解耦。 常用的WSGI服务器有uwsgi、Gunicorn。而Python标准库提供的独立WSGI服务器叫wsgiref,Django开发环境用的就是这个模块来做服务器。 Python中Web框架的分类 a. 收发socket消息 b. 根据不同的URL执行不同的函数(业务逻辑) c. 字符串替换(动态网页) 1. 第一种分类:(按照上面的三部分功能划分) 1. 自己实现b和c,使用第三方的a --> Django 2. 自己实现b,使用第三方的a和c --> Flask 3. 自己实现a、b、c --> Tornado 2. 第二种分类: 1. Django(大而全) 2. 其他 2、例子 1、wsgiref版web开发 """ 利用wsgiref模块来替换我们自己写的web框架的socket server部分 """ import time from wsgiref.simple_server import make_server # 定义处理用户请求的函数 def index(url): with open('index.html', 'r', encoding='utf8') as f1: html_s = f1.read() # 根据用户不同,取出不同的数据 # 用不同的数据去替换页面上的特殊符号 now = str(time.strftime("%H:%M:%S")) msg = html_s.replace('@t@', now) return msg.encode('utf8') def home(url): s = '你访问的是%s页面' %url return s.encode('utf8') def table(url): # 返回TableOp.html with open('TableOp.html', mode='rb') as f: return f.read() # 定义一个用户访问的url和要执行的函数的对应关系 url_func = [ ('/index/', index), ('/home/', home), ('/table/', table) ] # wsgiref模块的格式要求 def run_server(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ]) # 设置HTTP响应的状态码和头信息 url = environ['PATH_INFO'] # 取到用户输入的url # 循环url列表,对不同的url返回不同的消息 func = None for i in url_func: if i[0] == url: func = i[1] break if func: msg = func(url) else: msg = b'404 Not Found' return [msg] if __name__ == '__main__': httpd = make_server('127.0.0.1', 8080, run_server) httpd.serve_forever() 2、wsgiref+jinja2版web开发 上面的代码实现了一个简单的动态,我完全可以从数据库中查询数据,然后去替换我html中的对应内容,然后再发送给浏览器完成渲染。 这个过程就相当于HTML模板渲染数据。 本质上就是HTML内容中利用一些特殊的符号来替换要展示的数据。 我这里用的特殊符号是我定义的,其实模板渲染有个现成的工具: jinja2 下载jinja2:在cmd命令行输入 pip install jinja2 安装第三方包
""" 利用wsgiref和jinja2模块动态渲染userinfo页面 """
<!DOCTYPE html> <html lang="zh-CN"> <head> <title>用户列表</title> </head> <body> <table border="1"> <thead> <tr> <th>id值</th> <th>姓名</th> <th>密码</th> </tr> </thead> <tbody> {% for user in user_dict %} <tr> <td>{{user.id}}</td> <td>{{user.username}}</td> <td>{{user.password}}</td> </tr> {% endfor %} </tbody> </table> </body> </html>
import time import pymysql from wsgiref.simple_server import make_server from jinja2 import Template # 定义处理用户请求的函数 def index(url): with open('index.html', 'r', encoding='utf8') as f1: html_s = f1.read() # 根据用户不同,取出不同的数据 # 用不同的数据去替换页面上的特殊符号 now = str(time.strftime("%H:%M:%S")) msg = html_s.replace('@t@', now) return msg.encode('utf8') def home(url): s = '你访问的是%s页面' %url return s.encode('utf8') def table(url): # 返回TableOp.html with open('TableOp.html', mode='rb') as f: return f.read() def userinfo(url): # 1. 连接数据库,把所有的用户数据拿到 conn = pymysql.connect( host='127.0.0.1', port=3306, user='root', password='123abc', database='userinfo', charset='utf8' ) cursor = conn.cursor(cursor=pymysql.cursors.DictCursor) cursor.execute('select * from userinfo;') # 从数据库中拿到的数据user_dict user_dict = cursor.fetchall() with open('userinfo.html','r',encoding='utf8') as f: data = f.read() # 读取网页内容,生成jinja2模板对象 template = Template(data) # 让jinja2用数据替换HTML中的特殊符号,拿到新的html内容并返回 msg = template.render({'user_dict':user_dict}) return bytes(msg, encoding="utf8") # 定义一个用户访问的url和要执行的函数的对应关系 url_func = [ ('/index/', index), ('/home/', home), ('/table/', table), ('/userinfo/', userinfo) ] # wsgiref模块的格式要求 def run_server(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ]) # 设置HTTP响应的状态码和头信息 url = environ['PATH_INFO'] # 取到用户输入的url # 循环url列表,对不同的url返回不同的消息 func = None for i in url_func: if i[0] == url: func = i[1] break if func: msg = func(url) else: msg = b'404 Not Found' return [msg] if __name__ == '__main__': httpd = make_server('127.0.0.1', 8080, run_server) httpd.serve_forever() 三、Django初识 1、安装 命令安装:pip install django==1.11.16 PyCharm安装:
2、创建一个django项目 命令行创建:django-admin startproject first_django(项目名) PyCharm创建:File -> new project -> Django --> 右边填项目名并且选择Python解释器
3、Django app
切换到项目目录下
命令行创建:python manage.py startapp appName
PyCharm创建:File --> new project --> Django --> More Settings --> Application name
4、运行Django项目 命令行 切换到项目的目录下 python manage.py runserver # 默认8000端口 python manage.py runserver 127.0.0.1:8080 # 可以自己设置ip和端口 python manage.py runserver 8090 # 也可以只设置端口,ip默认是本地ip127.0.0.1 PyCharm 点绿色的三角(注意左侧名称要与项目名相同)
5、目录介绍 first_django/ ├── manage.py # 管理文件 └── first_django # 项目目录 ├── __init__.py ├── settings.py # 配置文件 ├── urls.py # 路由 --> URL和函数的对应关系 └── wsgi.py # runserver命令就使用wsgiref模块做简单的web server
四、django基础 # urls.py from django.shortcuts import HttpResponse, render, redirect 1、HttpResponse 内部传入一个字符串参数,返回给浏览器。 例如: def index(request): # request: 表示所有和请求相关的数据都封装在这个参数里面(固定传入这个参数) # HttpResponse: # 按照HTTP协议的格式返回 return HttpResponse("OK")
2、render 返回一个html页面 除request参数外还接受一个待渲染的模板文件和一个保存具体数据的字典参数。 将数据填充进模板文件,最后把结果返回给浏览器。(类似于我们上面用到的jinja2) 例如:无参数时,表示打开这个网页并返回给客户端 def meun(request): # render: # 1. 找到那个html文件 # 2. 读取文件内容 # 3. 按照HTTP协议的格式返回 return render(request, "meun.html")
有参数时,代表打开这个网页,并用这个参数(字典)去替换网页内的特殊字符,然后返回给客户端
def meun(request):
# 找到那个html文件
# 读取文件内容
# 用参数去替换文件内的特殊字符
# 按照http协议的格式返回
rerurn render(request,'meun.html',{"name":"ming","age":18})
3、redirect 重定向 接受一个URL参数,表示跳转到指定的URL。 例如: def home(request): # 业务逻辑代码 return redirect("/index/") 五、启动Django报错 Django 启动时报错 “UnicodeEncodeError ...” 报这个错误通常是因为计算机名为中文,改成英文的计算机名重启下电脑就可以了。 Django 启动报错“SyntaxError: Generator expression must be parenthesized” 报这个错很大可能是因为使用了Python3.7.0,而目前(2018-06-12)Python3.7.0和Django还有点兼容性问题,换回Python3.6的环境即可。
六、urls.py
主要是写页面与函数的对应关系,比如
from django.conf.urls import url
from django.contrib import admin
from django.shortcuts import HttpResponse,render,redirect # 导入django的模块
def index(request):
# request: 表示所有和请求相关的数据都封装在这个参数里面(固定传入这个参数)
# HttpResponse:
# 按照HTTP协议的格式返回
return HttpResponse('OK')
def meun(request):
# render:
# 1. 找到那个html文件
# 2. 读取文件内容
# 3. 按照HTTP协议的格式返回
return render(request, "meun.html")
def dashboard(request):
return render(request,'Dashboard.html')
def home(request):
# 业务逻辑代码
return redirect("/index/")
urlpatterns = [ # 这里写页面和函数的对应关系,前面是你在浏览器输入的页面url,后面是你要定义的函数
url(r'^admin/', admin.site.urls),
url(r'^index/',index),
url(r'^meun/',meun),
url(r'^Dashboard/',dashboard),
url(r'^home/',home),
]
七、settings.py简单分析
# 项目的起始路径
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# 你自定义的app都要在这里注册
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'appName.apps.AppnameConfig',
]
# 所有和HTML文件相关的配置
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')] # 告诉Django框架去哪里找HTML文件,所以htnl页面应该放在templates目录里面
,
'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/是下面STATICFILES_DIRS里面所有路径的别名(这里/static/就可以代表mystatic的路径)
# 因此你要引用什么静态文件。比如css、js文件等都要在路径前添加/static/
STATIC_URL = '/static/'
# 固定的配置项,告诉Django框架我的静态文件保存在哪些目录下
STATICFILES_DIRS = [
os.path.join(BASE_DIR,'mystatic')
]
八、request
1、request.POST
通过POST方法提交给服务器的数据都会存在request.POST里面,request.POST是一个类似于字典的类型,取值方法跟字典一样,可以根据键去取值,也可以通过get方法,例如:
request.POST["name"] # 拿到键为name的数据
request.POST.get("name",None) # 拿到键为name的数据,若这个数据不存在,则返回None
2、request.method
request.method就是获取客户端提交数据的方式(POST、GET等)
九、总结
Django基础 创建Django项目的步骤
1. PyCharm或者命令行创建一个Django项目
django-admin startproject 项目名
2. 创建一个初始的app,并且在settings.py中告诉Django
python manage.py startapp app的名字
3. 检查settings.py 专门存放HTML文件的Templates配置项
4. 配置静态文件相关
1. STATIC_URL = '/static/'
2. STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static'),
]
3. 在项目的根目录创建一个用来存放静态文件的static目录
5. 注释csrf相关的那一行(大概在46行)
否则表单没办法提交数据,就会提示Forbidden 403的错误