WSGI和ASGI
WSGI和ASGI,都是基于Python设计的网关接口(Gateway Interface,GI)
WSGI是基于http协议模式开发的,不支持websocket,而ASGI的诞生解决了python中的WSGI不仅支持当前的web开发中的一些新的协议标准,同时ASGI支持原有模式和Websocket的扩展,即ASGI是WSGI的扩展
网关接口
网关接口就是一种为了实现加载动态脚本而运行在WEB服务器和WEB应用程序中的通信接口,也可以理解为一种协议规范。只有web应用程序都实现了网关接口规范以后,双方的通信才能顺利完成。常见的网关接口协议
CGI,FASTAPI,WSGI,ASGI.
CGI
公共网关接口(Common Gateway Interface,CGI)是最早的Web服务器主机提供的信息服务的标准接口规范。只要实现了CGI协议,web服务器就能获取并了解客户端提交的信息,转交给服务器端的web应用程序进行处理,最后返回结果给客户端
CGI程序是一种实现CGI协议的程序
快速通用网关接口(Fast Common Gateway Interface/ FastCGI)是一种让web应用程序与web服务器通信的协议。FastCGI是公共网关接口(CGI)的增强版
Web服务器
Web服务器(Web Server)是一种运行于网站后台(物理服务器)的软件。Web服务器主要是用于提供网页浏览或文件下载服务,他可以向浏览器等Web客户端提供HTML网页文档,也可以提供其他类型的展示文档,让客户端用户浏览;还可以提供数据文件下载等。目前世界上最主流的Web服务器有Nginx,Apache ,IIS ,tomcat。
Web应用程序
web应用程序(Web)是一种能完成web业务逻辑,能让用户基于web浏览器访问的应用程序,他可以是一个实现HTTP请求和响应功能的函数或者类,也可以是Django,Flash,sanic等这样的web框架,当然也可以是其他语言的web程序或web框架
问:web服务器和web应用程序的区别?
答:web应用程序主要是完成web应用的业务逻辑的处理,web服务器则朱亚市应对外部请求的接收,响应和转发
需要使用web服务器启动运行,web应用程序才能被用户访问到。
而django框架中,我们之所以只有一个web应用程序就跑起来了,是因为我们在终端执行了,python manage.py runserver
这个命令启动了django框架中内置提供测试的web服务器
WSGI
web服务器网关接口,是Python为了解决web服务器与客户端之间的通信基于CGI标准而设计的。实现WSGI协议的服务器有uWSGI、uvicorn、gunicorn。想django框架一般在开发中就不会使用runserver来运行,而是采用上面实现的WSGI协议的web服务器来运行
django中运行润server命令事,其实内部就启动了wsgiref模块作为web服务器运行的。wsgiref是python内置的一个简单的遵循WSGI接口规范的web服务器程序。
from wsgiref.simple_server import make_server # application 由wsgi服务器调用、函数对http请求与响应的封装、使得Python专注与HTML # environ http 请求 (dist) # start_response 响应 (function) def application(environ, start_response): # 请求 if environ['REQUEST_METHOD'] == 'GET' and environ['PATH_INFO'] == '/': # 响应 start_response('200 OK', [('Content-Type', 'text/html')]) return [b'<h1>hi, python!</h1>'] if __name__ == '__main__': # 启动服务器 | 这个服务器负责与 wsgi 接口的 application 函数对接数据 httpd = make_server('127.0.0.1', 8000, application) # 监听请求 httpd.serve_forever() # 1. 监听8000端口, # 2. 把http请求根据WSGI协议将其转换到applcation中的environ参数, 然后调用application函数. # 3. wsgiref会把application函数提供的响应头设置转换为http协议的响应头, # 4. 把application的返回(return)作为响应体, 根据http协议,生成响应, 返回给浏览器.
开发中我们一般使用uWSGI或者Gunicorn作为web服务器运行django
uWSGI是一个快速的,自我驱动的,对开发者和系统管理员非常友好的容器服务器,完全由C语言编写,实现WSGI协议,uWSGI,http等协议。注意uwsgi协议是一个uWSGi服务器自有的协议,用于定义传输信息的类型,常用语uWSGI服务器与其他网络服务器的数据通信中
可参考django官方文档:https://docs.djangoproject.com/zh-hans/3.2/howto/deployment/wsgi/uwsgi/
conda虚拟环境下安装uWSGI
conda config --add channels conda-forge
conda install uWSGI
项目根目录下创建uwsgi配置文件,uwsgi.ini,代码样本:
1 [uwsgi] 2 #使用nginx连接时使用,Django程序所在服务器地址 3 socket=0.0.0.0:8000 4 #直接做web服务器使用,Django程序所在服务器地址 5 # http=0.0.0.0:8000 6 #项目目录 7 chdir=/home/项目根目录 8 #项目中wsgi.py文件的目录,相对于项目目录 9 wsgi-file=主应用目录/wsgi.py 10 # 进程数 CPU * 2 -1 11 processes=4 12 # 线程数 13 threads=2 14 # uwsgi服务器的角色 15 master=True 16 # 存放进程编号的文件 17 pidfile=uwsgi.pid 18 # 日志文件,因为uwsgi可以脱离终端在后台运行,日志看不见。我们以前的runserver是依赖终端的 19 daemonize=uwsgi.log 20 # 指定依赖的虚拟环境 21 virtualenv=/root/.virtualenvs/环境名称
具体配置实例过程
[uwsgi] #使用nginx连接时使用,Django程序所在服务器地址 # socket=0.0.0.0:8088 #直接做web服务器使用,Django程序所在服务器地址 http=0.0.0.0:8088 #项目根目录[绝对路径] chdir=/home/moluo/Desktop/demo #项目中wsgi.py文件的目录,相对于项目目录 wsgi-file=demo/wsgi.py # 进程数 CPU * 2 -1 processes=4 # 线程数 threads=2 # uwsgi服务器的角色 master=True # 存放进程编号的文件 pidfile=uwsgi.pid # 日志文件,因为uwsgi可以脱离终端在后台运行,日志看不见。我们以前的runserver是依赖终端的 daemonize=uwsgi.log # 指定依赖的虚拟环境 virtualenv=/home/moluo/anaconda3/envs/djdemo
项目根目录下,启动uwsgi服务器
# 启动项目 uwsgi --ini uwsgi.ini # 停止运行 uwsgi --stop uwsgi.pid # 查看当前系统中的指定名称的进程 ps aux | grep uwsgi # 输出效果如下,则表示成功运行,接下来就可以根据配置中设置的地址访问项目了。 # (base) moluo@ubuntu:~$ ps aux | grep uwsgi # moluo 12759 2.3 0.9 96944 37736 ? S 11:52 0:00 uwsgi --ini uwsgi.ini # moluo 13167 0.0 0.7 170676 29468 ? Sl 11:52 0:00 uwsgi --ini uwsgi.ini # moluo 13169 0.0 0.7 170676 29468 ? Sl 11:52 0:00 uwsgi --ini uwsgi.ini # moluo 13171 0.0 0.7 170676 29468 ? Sl 11:52 0:00 uwsgi --ini uwsgi.ini # moluo 13173 0.0 0.7 170676 29468 ? Sl 11:52 0:00 uwsgi --ini uwsgi.ini # moluo 13176 0.0 0.8 105140 35416 ? S 11:52 0:00 uwsgi --ini uwsgi.ini # moluo 18073 0.0 0.0 17688 740 pts/2 R+ 11:52 0:00 grep --color=auto uwsgi
uwsgi具体访问过程如下图:
ASGI
ASGI,是构建于WSGI接口规范之上的异步服务器网关接口,是WSGI的延伸和扩展。
A指的是Async,异步的意思。
下面表格列举了不同协议对在不同框架及版本中的应用
协议,规范 | 支持的请求协议(常见,未列全) | 同步/异步 | 支持的框架 |
---|---|---|---|
CGI | HTTP | CGI程序 | |
WSGI | HTTP | 同步 | django3.0以前,Flask1.0 |
ASGI | HTTP,HTTP2,WebSocket等 | 同步/异步 | FastAPI,Quart,Sanic,Tornado,django3.0以后,flask2.0 |
在 Python3.5 之后增加 async/await 特性之后简化了协程操作以后,异步编程变得异常火爆,越来越多开发者投入异步的怀抱。
3.0版本以前,django所提供的所有内部功能都是基于同步编程的。所以,在以往django开发中,针对网络请求,数据库读取等IO操作形成的阻塞,往往会导致项目运行性能的下降。虽然等待I/O操作数微秒时,但是随着流量的增加和操作的频率上升,这一点点的阻塞就会导致整个项目运作的缓慢。而如果换成异步就不会有任何阻塞,还可以同时处理其他任务,从而以较低的延迟处理更多的请求。所以在目前python开发中,越来越多的框架开始支持了异步编程。所以,3.0版本以后,django开始支持异步编程,可以让开发者在django中使用python第三方异步模块,推出了asgi异步web服务器。3.1版本推出了异步视图,当然,目前django的异步编程还不够完善,django中只有极少的功能是支持了异步操作。
uvicorn的安装
pip install uvicorn
项目根目录下,运行django项目
# uvicorn 主应用目录名.asgi:application --reload uvicorn homework.asgi:application --reload
开发中一般使用gunicorn来管理uvicorn。所以可以一并安装
pip install gunicorn
运行
# gunicorn -w 4 主应用目录名.asgi:application -k uvicorn.workers.UvicornWorker --reload gunicorn -w 4 homework.asgi:application -k uvicorn.workers.UvicornWorker --reload
异步视图
文档:
在Django3.1后的版本中,我们可以通过async def
语法,将任何函数视图定义为异步视图。
"""同步视图""" import time def home1(request): time.sleep(5) return HttpResponse('Hello, sync view!') """异步视图""" import asyncio async def home2(request): # asyncio.sleep(5) await asyncio.sleep(5) return HttpResponse('Hello, async view!')
class Home3View(View): async def __call__(self, *args, **kwargs): return super().__call__(*args, **kwargs) def get(self, request): return HttpResponse("ok, get")
django目前最新版本3.2中的ORM对数据库的访问这块还没有实现异步处理,还是同步的。当我们需要在项目中调用模型等同步操作时,django有提供了2个适配器函数,可从asgiref.sync
包中获取:async_to_sync()
和 sync_to_async()
。它们用于同步和异步之间调用风格的转换,同时保持兼容性。适配器函数既可以当包装器函数使用,也可以作为装饰器使用。
async_to_sync():异步转同步,参数就是同步函数
sync_to_async():同步转异步,参数就是异步函数
from asgiref.sync import async_to_sync # 用法1 sync_function = async_to_sync(async_function) # 用法2 @async_to_sync async def async_function(...): pass
实例代码:
from asgiref.sync import sync_to_async,async_to_sync, SyncToAsync from django.http.response import HttpResponse from .models import Student from django.views import View # 直接在异步视图中进行数据库操作的同步转异步 async def home4(request): ret = await sync_to_async(Student.objects.get, thread_sensitive=True)(pk=10) print(ret.name) return HttpResponse('Hello, async world!') # 先声明数据库同步转异步 @sync_to_async(thread_sensitive=True) def get_student_by_id(id): return Student.objects.get(pk=id) # 异步视图,直接调用异步操作 async def home5(request): ret = await get_student_by_id(id=10) print(ret.name) return HttpResponse('Hello, async world!') class Home(View): async def __call__(self, *args, **kwargs): return super().__call__(*args, **kwargs) def get(self,request): print("hello") # 添加一条数据 ret = sync_to_async(Student.objects.create, thread_sensitive=False)(name="刘德华",age=19,sex=True,class_number="305") print(ret) # 获取一条数据 # ret = sync_to_async(Student.objects.get, thread_sensitive=True)(pk=10) # print(ret) return HttpResponse('Hello, async base view!')
redis异步库:aioredis
mysql异步库:aiomysql
mongoDB异步库:motor
http网络请求异步库:httpx
import asyncio from time import sleep import httpx from django.http import HttpResponse # 异步任务 import httpx async def async_task(): for num in range(1, 6): await asyncio.sleep(1) print(num) async with httpx.AsyncClient() as client: # 打开异步http请求客户端 r = await client.get("https://httpbin.org/") print(r) # 异步视图 - 调用异步任务 # aio, bio, nio async def async_view(request): loop = asyncio.get_event_loop() loop.create_task(async_task()) return HttpResponse("Async Non-Blocking HTTP request")
import asyncio from django.utils.decorators import sync_and_async_middleware @sync_and_async_middleware def simple_middleware(get_response): # One-time configuration and initialization goes here. if asyncio.iscoroutinefunction(get_response): # 判断是否是协程异步 async def middleware(request): # Do something here! response = await get_response(request) return response else: def middleware(request): # Do something here! response = get_response(request) return response return middleware
ps:与戴王冠,必受其重。