1. 简介

WSGI

WSGI:web服务器网关接口,这是python中定义的一个网关协议,规定了Web Server如何跟应用程序交互。可以理解为一个web应用的容器,通过它可以启动应用,进而提供HTTP服务。

​ 它最主要的目的是保证在Python中所有的Web Server程序或者说Gateway程序,能够通过统一的协议跟Web框架或者说Web应用进行交互。

uWSGI

uWGSI:是一个web服务器,或者wsgi server服务器,他的任务就是接受用户请求,由于用户请求是通过网络发过来的,其中用户到服务器端之间用的是http协议,所以我们uWSGI要想接受并且正确解出相关信息,我们就需要uWSGI实现http协议,没错,uWSGI里面就实现了http协议。所以现在我们uWSGI能准确接受到用户请求,并且读出信息。现在我们的uWSGI服务器需要把信息发给Django,我们就需要用到WSGI协议,刚好uWSGI实现了WSGI协议,所以。uWSGI把接收到的信息作一次简单封装传递给Django,Django接收到信息后,再经过一层层的中间件,于是,对信息作进一步处理,最后匹配url,传递给相应的视图函数,视图函数做逻辑处理......后面的就不叙述了,然后将处理后的数据通过中间件一层层返回,到达Djagno最外层,然后,通过WSGI协议将返回数据返回给uWSGI服务器,uWSGI服务器通过http协议将数据传递给用户。这就是整个流程。

​ 这个过程中我们似乎没有用到uwsgi协议,但是他也是uWSGI实现的一种协议,鲁迅说过,存在即合理,所以说,他肯定在某个地方用到了。我们过一会再来讨论

​ 我们可以用这条命令:python manage.py runserver,启动Django自带的服务器。DJango自带的服务器(runserver 起来的 HTTPServer 就是 Python 自带的 simple_server)。是默认是单进程单多线程的,对于同一个http请求,总是先执行一个,其他等待,一个一个串行执行。无法并行。而且django自带的web服务器性能也不好,只能在开发过程中使用。于是我们就用uWSGI代替了。

为什么有了WSGI为什么还需要nginx?

​ 因为nginx具备优秀的静态内容处理能力,然后将动态内容转发给uWSGI服务器,这样可以达到很好的客户端响应。支持的并发量更高,方便管理多进程,发挥多核的优势,提升性能。这时候nginx和uWSGI之间的沟通就要用到uwsgi协议。

2. 简单的Web Server

在了解WSGI协议之前,首先看一个通过socker编程实现的Web服务的代码。

import socket
EOL1 = b'\n\n'
EOL2 = b'\n\r\n'
body = """hello world <h1> from test</h1>"""
response_params = [
'HTTP/1.0 200 OK',
'DATE: Sun, 27 may 2019 01:01:01 GMT',
'Content-Type:text/plain; charset=utf-8',
'Content-Length: {}\r\n'.format(len(body.encode())),
body,
]
response = '\r\n'.join(response_params) def handle_connection(conn, addr):
request = b""
print('new conn', conn, addr)
import time
time.sleep(100)
while EOL1 not in request and EOL2 not in request:
request += conn.recv(1024)
print(request)
conn.send(response.encode())
conn.close() def main():
serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
serversocket.bind(('127.0.0.1', 8000))
serversocket.listen(5)
print('http://127.0.0.1:8000')
try:
while True:
conn, address = serversocket.accept()
handle_connection(conn, address)
finally:
serversocket.close() if __name__ == '__main__':
main()

多线程

import errno
import socket
import threading
import time
EOL1 = b'\n\n'
EOL2 = b'\n\r\n'
body = """hello world <h1> from test</h1>"""
response_params = [
'HTTP/1.0 200 OK',
'DATE: Sun, 27 may 2019 01:01:01 GMT',
'Content-Type:text/plain; charset=utf-8',
'Content-Length: {}\r\n'.format(len(body.encode())),
body,
]
response = '\r\n'.join(response_params) def handle_connection(conn, addr):
print(conn, addr)
time.sleep(60)
request = b""
while EOL1 not in request and EOL2 not in request:
request += conn.recv(1024)
print(request)
current_thread = threading.currentThread()
content_length = len(body.format(thread_name=current_thread.name).encode())
print(current_thread.name)
conn.send(response.format(thread_name=current_thread.name, length=content_length).encode())
conn.close() def main():
serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
serversocket.bind(('127.0.0.1', 8000))
serversocket.listen(10)
print('http://127.0.0.1:8000')
serversocket.setblocking(True) # 设置socket为阻塞模式
try:
i = 0
while True:
try:
conn, address = serversocket.accept()
except socket.error as e:
if e.args[0] != errno.EAGAIN:
raise
continue
i += 1
print(i)
t = threading.Thread(target=handle_connection, args=(conn, address), name='thread-%s'%i)
t.start()
finally:
serversocket.close() if __name__ == '__main__': main()

3. 简单的WSGI Application

该协议分为两个部分:

  • Web Server 或者Gateway

    监听在某个端口上接收外部的请求

  • Web Application

​ Web Server接收请求之后,会通过WSGI协议规定的方式把数据传递给Web Application,在Web Application中处理完之后,设置对应的状态和header,之后返回body部分。Web Server拿到返回的数据之后,再进行HTTP协议的封装,最终返回完整的HTTPResponse数据。

下面我们来实现一个简单的应用:

app.py

def simple_app(environ, start_response):
status = '200 OK'
response_headers = [('Content-type', 'text/plain')]
start_response(status, response_headers)
return [b'Hello world! -by test \n']

我们需要一个脚本运行上面这个应用:

import os
import sys from app import simple_app def wsgi_to_bytes(s):
return s.encode() def run_with_cgi(application):
environ = dict(os.environ.items())
environ['wsgi.input'] = sys.stdin.buffer
environ['wsgi.errors'] = sys.stderr
environ['wsgi.version'] = (1, 0)
environ['wsgi.multithread'] = False
environ['wsgi.multiprocess'] = True
environ['wsgi.run_once'] = True
if environ.get('HTTPS', 'off') in ('on', '1'):
environ['wsgi.url_scheme'] = 'https'
else:
environ['wsgi.url_scheme'] = 'http' headers_set = []
headers_sent = [] def write(data):
out = sys.stdout.buffer
if not headers_set:
raise AssertionError('Write() before start_response()')
elif not headers_sent:
# 在输出第一行数据之前,先发送响应头
status, response_headers = headers_sent[:] = headers_set
out.write(wsgi_to_bytes('Status: %s\r\n' % status))
for header in response_headers:
out.write(wsgi_to_bytes('%s: %s\r\n' % header))
out.write(wsgi_to_bytes('\r\n'))
out.write(data)
out.flush() def start_response(status, response_headers, exc_info=None):
if exc_info:
try:
if headers_sent:
# 如果已经发送了header,则重新抛出原始异常信息
raise (exc_info[0], exc_info[1], exc_info[2])
finally:
exc_info = None
elif headers_set:
raise AssertionError('*Headers already set!')
headers_set[:] = [status, response_headers]
return write result = application(environ, start_response)
try:
for data in result:
if data:
write(data)
if not headers_sent:
write('')
finally:
if hasattr(result, 'close'):
result.close() if __name__ == '__main__':
run_with_cgi(simple_app)

运行结果:

Status: 200 OK
Content-type: text/plain Hello world! -by test

如果不是windows系统,还可以采用另一种方式运行:

  • pip install gunicorn
  • gunicorn app:simle_app

4. 理解

​ 对于上述代码我们只需要关注一点,result = application(environ, start_response),我们要实现的Application,只需要能够接收一个环境变量以及一个回调函数即可。但处理完请求之后,通过回调函数(start_response)来设置response的状态和header,最终返回结果,也就是body。

WSGI协议规定,application必须是一个可调用对象,这意味这个对象既可以是Python中的一个函数,也可以是一个实现了__call__方法的类的实例,比如:

样例一

class AppClass(object):
status = '200 OK'
response_headers = [('Content-type', 'text/plain')] def __call__(self, environ, start_response):
print(environ, start_response)
start_response(self.status, self.response_headers)
return [b'Hello AppClass.__call__\n'] application = AppClass()

gunicorn app: application运行上述文件

样例二

​ 除此之外,我们还可以通过另一种方式实现WSGI协议,从上面的simple_app和这里的AppClass.__call__的返回值来看,WSGI Server只需要返回一个可迭代的对象就行

class AppClassIter(object):
status = '200 OK'
response_headers = [('Content-type', 'text/plain')] def __init__(self, environ, start_response):
self.environ = environ
self.start_response = start_response def __iter__(self):
self.start_response(self.status, self.response_headers)
yield b'Hello AppClassIter\n'

gunicorn app: AppClassIter运行上述文件

​ 这里的启动命令并不是一个类的实例,而是类本身。通过上面两个代码,我们可以看到能够被调用的方法会传environ和start_response过来,而现在这个实现没有可调用的方式,所以就需要在实例化的时候通过参数传递进来,这样在返回body之前,可以先调用start_response方法。

​ 因此,可以推测出WSGI Server是如何调用WSGI Application的,大概代码如下:

def start_response(status, headers):
# 伪代码
set_status(status)
for k, v in headers:
set_header(k, v)
def handle_conn(conn):
# 调用我们定义的application(也就是上面的simple_app, 或者是AppClass的实例,或者是AppClassIter本身)
app = application(environ, start_response)
# 遍历返回的结果,生成response
for data in app:
response += data conn.sendall(response)

5. WSGI中间件和Werkzeug

​ WSGI中间件可以理解为Python中的一个装饰器,可以在不改变原方法的情况下对方法的输入和输出部分进行处理。

类似这样:

def simple_app(enbiron, start_response):
response = Response('Hello World', start_response=start_response)
response.set_header('Content-Type', 'text/plain') # 这个函数里面调用start_response
return response

这样就看起来更加自然一点。

​ 因此,就存在Werkzeug这样的WSGI工具集,让你能够跟WSGI协议更加友好的交互。从理论上来看,我们可以直接通过WSGI协议的简单实现写一个Web服务。但是有了Werkzeug之后,我们可以写的更加容易。

6. 杂谈

django 的并发能力真的是令人担忧,这里就使用 nginx + uwsgi 提供高并发

nginx 的并发能力超高,单台并发能力过万(这个也不是绝对),在纯静态的 web 服务中更是突出其优越的地方,由于其底层使用 epoll 异步IO模型进行处理,使其深受欢迎

做过运维的应该都知道,

Python需要使用nginx + uWSGI 提供静态页面访问,和高并发

php 需要使用 nginx + fastcgi 提供高并发,

java 需要使用 nginx + tomcat 提供 web 服务

django 原生为单线程序,当第一个请求没有完成时,第二个请求辉阻塞,知道第一个请求完成,第二个请求才会执行。
Django就没有用异步,通过线程来实现并发,这也是WSGI普遍的做法,跟tornado不是一个概念 官方文档解释django自带的server默认是多线程
django开两个接口,第一个接口sleep(20),另一个接口不做延时处理(大概耗时几毫秒)
先请求第一个接口,紧接着请求第二个接口,第二个接口返回数据,第一个接口20秒之后返回数据
证明django的server是默认多线程 启动uWSGI服务器
# 在django项目目录下 Demo工程名
uwsgi --http 0.0.0.0:8000 --file Demo/wsgi.py
经过上述的步骤测试,发现在这种情况下启动django项目,uWSGI也是单线程,访问接口需要"排队"
不给uWSGI加进程,uWSGI默认是单进程单线程 uwsgi --http 0.0.0.0:8000 --file Demo/wsgi.py --processes 4 --threads 2
# processes: 进程数 # processes 和 workers 一样的效果 # threads : 每个进程开的线程数
经过测试,接口可以"同时"访问,uWSGI提供多线程
  • Python因为GIL的存在,在一个进程中,只允许一个线程工作,导致单进程多线程无法利用多核
  • 多进程的线程之间不存在抢GIL的情况,每个进程有一个自己的线程锁,多进程多GIL

WSGI——python-Web框架基础的更多相关文章

  1. 零基础小白必看篇:从0到1构建Python Web框架

    造轮子是最好的一种学习方式,本文尝试从0开始造个Python Web框架的轮子,我称它为ToyWebF. 本文操作环境为:MacOS,文中涉及的命令,请根据自己的系统进行替换. ToyWebF的简单特 ...

  2. Python web框架开发 - WSGI协议

    浏览器进行http请求的时候,不单单会请求静态资源,还可能需要请求动态页面. 那么什么是静态资源,什么是动态页面呢? 静态资源 : 例如html文件.图片文件.css.js文件等,都可以算是静态资源 ...

  3. python web框架介绍对比

    Django Python框架虽然说是百花齐放,但仍然有那么一家是最大的,它就是Django.要说Django是Python框架里最好的,有人同意也有人 坚决反对,但说Django的文档最完善.市场占 ...

  4. python三大web框架Django,Flask,Flask,Python几种主流框架,13个Python web框架比较,2018年Python web五大主流框架

    Python几种主流框架 从GitHub中整理出的15个最受欢迎的Python开源框架.这些框架包括事件I/O,OLAP,Web开发,高性能网络通信,测试,爬虫等. Django: Python We ...

  5. Django,Flask,Tornado三大框架对比,Python几种主流框架,13个Python web框架比较,2018年Python web五大主流框架

    Django 与 Tornado 各自的优缺点Django优点: 大和全(重量级框架)自带orm,template,view 需要的功能也可以去找第三方的app注重高效开发全自动化的管理后台(只需要使 ...

  6. 2018年要学习的10大Python Web框架

    通过为开发人员提供应用程序开发结构,框架使开发人员的生活更轻松.他们自动执行通用解决方案,缩短开发时间,并允许开发人员更多地关注应用程序逻辑而不是常规元素. 在本文中,我们分享了我们自己的前十大Pyt ...

  7. python web框架——扩展Django&tornado

    一 Django自定义分页 目的:自定义分页功能,并把它写成模块(注意其中涉及到的python基础知识) models.py文件 # Create your models here. class Us ...

  8. Python Web框架

    本节对Python Web框架学习 一.MTVModel: 存放所有数据库相关文件Template:模板文件,存放html文件View: 业务处理,即函数文件 二.MVCmodel: 存放数据库相关文 ...

  9. 一步一步理解 python web 框架,才不会从入门到放弃

    要想清楚地理解 python web 框架,首先要清楚浏览器访问服务器的过程. 用户通过浏览器浏览网站的过程: 用户浏览器(socket客户端) 3. 客户端往服务端发消息 6. 客户端接收消息 7. ...

  10. “脚踢各大Python Web框架”,Sanic真有这能耐么?

    在Github上,Sanic第一句介绍语就是: "Sanic is a Flask-like Python 3.5+ web server that's written to go fast ...

随机推荐

  1. LeetCode Array Easy 217. Contains Duplicate

    Description Given an array of integers, find if the array contains any duplicates. Your function sho ...

  2. shell常见的返回状态码

  3. shell位置参数和专用参数举例

  4. Shell 脚本的建立与执行

  5. Java基本数据类型的类型转换规则

    基本类型转换分为自动转换和强制转换. 自动转换规则:容量小的数据类型可以自动转换成容量大的数据类型,也可 以说低级自动向高级转换.这儿的容量指的不是字节数,而是指类型表述的范围. 强制转换规则:高级变 ...

  6. Linux curl 命令模拟 POST/GET 请求

    Linux curl 命令模拟 POST/GET 请求   本文链接:https://blog.csdn.net/sunboy_2050/article/details/82156402 curl 命 ...

  7. Shiro学习(9)JSP标签

    Shiro提供了JSTL标签用于在JSP/GSP页面进行权限控制,如根据登录用户显示相应的页面按钮. 导入标签库 Java代码   <%@taglib prefix="shiro&qu ...

  8. 「AHOI / HNOI2018」转盘 解题报告

    「AHOI / HNOI2018」转盘 可能是我语文水平不太行... 首先可以猜到一些事实,这个策略一定可以被一个式子表示出来,不然带修修改个锤子. 然后我们发现,可以枚举起点,然后直接往前走,如果要 ...

  9. hive自定义函数UDF UDTF UDAF

    Hive 自定义函数 UDF UDTF UDAF 1.UDF:用户定义(普通)函数,只对单行数值产生作用: UDF只能实现一进一出的操作. 定义udf 计算两个数最小值 public class Mi ...

  10. 搭建本地 8.8 W 乌云漏洞库

    这个是关于两个图片马的帖子. https://zhuanlan.zhihu.com/p/27486144 这个是开源地址: https://github.com/m0l1ce/wooyunallbug ...