APP下载
报价宝  ›  科技  › 

Django必备知识 拿走不谢

报价宝 来源:baojiabao.com 发布时间:2019-11-04 10:57:00 10月01日更新
报价宝综合消息Django必备知识 拿走不谢

Web框架本质

我们可以这样理解:所有的Web应用本质上就是一个socket服务端,而使用者的浏览器就是一个socket客户端。 这样我们就可以自己实现Web框架了。

半成品自定义web框架

import socket

sk = socket.socket()

sk.bind(("127.0.0.1", 80))

sk.listen()

while True:

conn, addr = sk.accept()

data = conn.recv(8096)

conn.send(b"OK")

conn.close()

可以说Web服务本质上都是在这十几行程式码基础上扩展出来的。这段程式码就是它们的祖宗。

使用者的浏览器一输入网址,会给服务端传送资料,那浏览器会发送什么资料?怎么发?这个谁来定? 你这个网站是这个规定,他那个网站按照他那个规定,这互联网还能玩么?

所以,必须有一个统一的规则,让大家传送讯息、接收讯息的时候有个格式依据,不能随便写。

这个规则就是HTTP协议,以后浏览器传送请求资讯也好,服务器回复响应资讯也罢,都要按照这个规则来。

HTTP协议主要规定了客户端和服务器之间的通讯格式,那HTTP协议是怎么规定讯息格式的呢?

让我们首先打印下我们在服务端接收到的讯息是什么。

import socket

sk = socket.socket()

sk.bind(("127.0.0.1", 80))

sk.listen()

while True:

conn, addr = sk.accept()

data = conn.recv(8096)

print(data) # 将浏览器发来的讯息打印出来

conn.send(b"OK")

conn.close()

输出:

b'GET / 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/64.0.3282.186 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 DNT: 1 Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9 Cookie: csrftoken=RKBXh1d3M97iz03Rpbojx1bR6mhHudhyX5PszUxxG3bOEwh1lxFpGOgWN93ZH3zv '

然后我们再看一下我们访问部落格园官网时浏览器收到的响应资讯是什么。

响应相关资讯可以在浏览器除错视窗的network标签页中看到。

点选view source之后显示如下图:

我们发现收发的讯息需要按照一定的格式来,这里就需要了解一下HTTP协议了。

HTTP协议介绍

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', 8000))

sock.listen()

while True:

conn, addr = sock.accept()

data = conn.recv(8096)

# 给回复的讯息加上响应状态行

conn.send(b"HTTP/1.1 200 OK ")

conn.send(b"OK")

conn.close()

我们通过十几行程式码简单地演示了web 框架的本质。

接下来就让我们继续完善我们的自定义web框架吧!

根据不同的路径返回不同的内容

这样就结束了吗? 如何让我们的Web服务根据使用者请求的URL不同而返回不同的内容呢?

小事一桩,我们可以从请求相关资料里面拿到请求URL的路径,然后拿路径做一个判断...

"""

根据URL中不同的路径返回不同的内容

"""

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") # 把收到的字节型别的资料转换成字串

# 按 分割

data1 = data.split(" ")[0]

url = data1.split()[1] # url是我们从浏览器发过来的讯息中分离出的访问路径

conn.send(b'HTTP/1.1 200 OK ') # 因为要遵循HTTP协议,所以回复的讯息也要加状态行

# 根据不同的路径返回不同内容

if url == "/index/":

response = b"index"

elif url == "/home/":

response = b"home"

else:

response = b"404 not found!"

conn.send(response)

conn.close()

根据不同的路径返回不同的内容--函式版

上面的程式码解决了不同URL路径返回不同内容的需求。

但是问题又来了,如果有很多很多路径要判断怎么办?难道要挨个写if判断? 当然不用,我们有更聪明的办法。

"""

根据URL中不同的路径返回不同的内容--函式版

"""

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")

while 1:

# 等待连线

conn, add = sk.accept()

data = conn.recv(8096) # 接收客户端发来的讯息

# 从data中取到路径

data = str(data, encoding="utf8") # 把收到的字节型别的资料转换成字串

# 按 分割

data1 = data.split(" ")[0]

url = data1.split()[1] # url是我们从浏览器发过来的讯息中分离出的访问路径

conn.send(b'HTTP/1.1 200 OK ') # 因为要遵循HTTP协议,所以回复的讯息也要加状态行

# 根据不同的路径返回不同内容,response是具体的响应体

if url == "/index/":

response = index(url)

elif url == "/home/":

response = home(url)

else:

response = b"404 not found!"

conn.send(response)

conn.close()

根据不同的路径返回不同的内容--函式进阶版

看起来上面的程式码还是要挨个写if判断,怎么办?我们还是有办法!(只要思想不滑坡,方法总比问题多!)

"""

根据URL中不同的路径返回不同的内容--函式进阶版

"""

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 1:

# 等待连线

conn, add = sk.accept()

data = conn.recv(8096) # 接收客户端发来的讯息

# 从data中取到路径

data = str(data, encoding="utf8") # 把收到的字节型别的资料转换成字串

# 按 分割

data1 = data.split(" ")[0]

url = data1.split()[1] # url是我们从浏览器发过来的讯息中分离出的访问路径

conn.send(b'HTTP/1.1 200 OK ') # 因为要遵循HTTP协议,所以回复的讯息也要加状态行

# 根据不同的路径返回不同内容

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()

返回具体的HTML档案

完美解决了不同URL返回不同内容的问题。 但是我不想仅仅返回几个字串,我想给浏览器返回完整的HTML内容,这又该怎么办呢?

没问题,不管是什么内容,最后都是转换成字节资料传送出去的。 我们可以开启HTML档案,读取出它内部的二进位制资料,然后再发送给浏览器。

"""

根据URL中不同的路径返回不同的内容--函式进阶版

返回独立的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") # 把收到的字节型别的资料转换成字串

# 按 分割

data1 = data.split(" ")[0]

url = data1.split()[1] # url是我们从浏览器发过来的讯息中分离出的访问路径

conn.send(b'HTTP/1.1 200 OK ') # 因为要遵循HTTP协议,所以回复的讯息也要加状态行

# 根据不同的路径返回不同内容

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()

让网页动态起来

这网页能够显示出来了,但是都是静态的啊。页面的内容都不会变化的,我想要的是动态网站。

没问题,我也有办法解决。我选择使用字串替换来实现这个需求。(这里使用时间戳来模拟动态的资料)

"""

根据URL中不同的路径返回不同的内容--函式进阶版

返回HTML页面

让网页动态起来

"""

import socket

import time

sk = socket.socket()

sk.bind(("127.0.0.1", 8080)) # 系结IP和埠

sk.listen() # 监听

# 将返回不同的内容部分封装成函式

def index(url):

with open("index.html", "r", encoding="utf8") as f:

s = f.read()

now = str(time.time())

s = s.replace("@@[email protected]@", 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")

# 定义一个url和实际要执行的函式的对应关系

list1 = [

("/index/", index),

("/home/", home),

]

while 1:

# 等待连线

conn, add = sk.accept()

data = conn.recv(8096) # 接收客户端发来的讯息

# 从data中取到路径

data = str(data, encoding="utf8") # 把收到的字节型别的资料转换成字串

# 按 分割

data1 = data.split(" ")[0]

url = data1.split()[1] # url是我们从浏览器发过来的讯息中分离出的访问路径

conn.send(b'HTTP/1.1 200 OK ') # 因为要遵循HTTP协议,所以回复的讯息也要加状态行

# 根据不同的路径返回不同内容

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()

好了,在这停顿...

服务器程式和应用程序

对于真实开发中的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部分:

"""

根据URL中不同的路径返回不同的内容--函式进阶版

返回HTML页面

让网页动态起来

wsgiref模组版

"""

import time

from wsgiref.simple_server import make_server

# 将返回不同的内容部分封装成函式

def index(url):

with open("index.html", "r", encoding="utf8") as f:

s = f.read()

now = str(time.time())

s = s.replace("@@[email protected]@", 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")

# 定义一个url和实际要执行的函式的对应关系

list1 = [

("/index/", index),

("/home/", home),

]

def run_server(environ, start_response):

start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ]) # 设定HTTP响应的状态码和头资讯

url = environ['PATH_INFO'] # 取到使用者输入的url

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!"

return [response, ]

if __name__ == '__main__':

httpd = make_server('127.0.0.1', 8090, run_server)

print("我在8090等你哦...")

httpd.serve_forever()

jinja2

上面的程式码实现了一个简单的动态,我完全可以从数据库中查询资料,然后去替换我html中的对应内容,然后再发送给浏览器完成渲染。 这个过程就相当于HTML模板渲染资料。 本质上就是HTML内容中利用一些特殊的符号来替换要展示的资料。 我这里用的特殊符号是我定义的,其实模板渲染有个现成的工具: jinja2

下载jinja2:

pip install jinja2

Title

姓名:{{name}}

爱好:

{% for hobby in hobby_list %}

{{hobby}}

{% endfor %}

使用jinja2渲染index2.html档案:

from wsgiref.simple_server import make_server

from jinja2 import Template

def index():

with open("index2.html", "r") as f:

data = f.read()

template = Template(data) # 生成模板档案

ret = template.render({"name": "Alex", "hobby_list": ["烫头", "泡吧"]}) # 把资料填充到模板里面

return [bytes(ret, encoding="utf8"), ]

def home():

with open("home.html", "rb") as f:

data = f.read()

return [data, ]

# 定义一个url和函式的对应关系

URL_LIST = [

("/index/", index),

("/home/", home),

]

def run_server(environ, start_response):

start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ]) # 设定HTTP响应的状态码和头资讯

url = environ['PATH_INFO'] # 取到使用者输入的url

func = None # 将要执行的函式

for i in URL_LIST:

if i[0] == url:

func = i[1] # 去之前定义好的url列表里找url应该执行的函式

break

if func: # 如果能找到要执行的函式

return func() # 返回函式的执行结果

else:

return [bytes("404没有该页面", encoding="utf8"), ]

if __name__ == '__main__':

httpd = make_server('', 8000, run_server)

print("Serving HTTP on port 8000...")

httpd.serve_forever()

现在的资料是我们自己手写的,那可不可以从数据库中查询资料,来填充页面呢?

使用pymysql连线数据库:

conn = pymysql.connect(host="127.0.0.1", port=3306, user="root", passwd="xxx", db="xxx", charset="utf8")

cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)

cursor.execute("select name, age, department_id from userinfo")

user_list = cursor.fetchall()

cursor.close()

conn.close()

建立一个测试的user表:

CREATE TABLE user(

id int auto_increment PRIMARY KEY,

name CHAR(10) NOT NULL,

hobby CHAR(20) NOT NULL

)engine=innodb DEFAULT charset=UTF8;

模板的原理就是字串替换,我们只要在HTML页面中遵循jinja2的语法规则写上,其内部就会按照指定的语法进行相应的替换,从而达到动态的返回内容。

Django

Django官网下载页面

安装(安装最新LTS版):

pip3 install django==1.11.9

建立一个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:8000

模板档案配置:

TEMPLATES = [

{

'BACKEND': 'django.template.backends.django.DjangoTemplates',

'DIRS': [os.path.join(BASE_DIR, "template")], # template资料夹位置

'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_URL = '/static/' # HTML中使用的静态资料夹字首

STATICFILES_DIRS = [

os.path.join(BASE_DIR, "static"), # 静态档案存放位置

]

看不明白?有图有真相:

刚开始学习时可在配置档案中暂时禁用csrf中介软件,方便表单提交测试。

MIDDLEWARE = [

'django.middleware.security.SecurityMiddleware',

'django.contrib.sessions.middleware.SessionMiddleware',

'django.middleware.common.CommonMiddleware',

# 'django.middleware.csrf.CsrfViewMiddleware',

'django.contrib.auth.middleware.AuthenticationMiddleware',

'django.contrib.messages.middleware.MessageMiddleware',

'django.middleware.clickjacking.XFrameOptionsMiddleware',

]

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/")

重定向是怎么回事?

课后练习:

Django版登入

启动Django报错:

Django 启动时报错 “UnicodeEncodeError ...”

报这个错误通常是因为计算机名为中文,改成英文的计算机名重启下电脑就可以了。

Django 启动报错“SyntaxError: Generator expression must be parenthesized”

报这个错很大可能是因为使用了Python3.7.0,而目前(2018-06-12)Python3.7.0和Django还有点相容性问题,换回Python3.6的环境即可。

文章标签: 报价宝 降噪耳机价格 耳机价格 红米手机价格 华为手机价格 小米手机价格 电视机价格 笔记本电脑价格 笔记本价格 汽车价格 数码相机价格 笔记本价格 电视机价格 红米手机价格 耳机价格