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. mac 完全卸载vscode

    原文分隔线====================== while writing go this morning, I found that the wrong code are not under ...

  2. KiCAD原理图更换库

    KiCAD原理图库更换 将AD的工程文件转换位KiCAD后,打开原理图,系统会自动压缩生成当前工程库,但是这样将原理图复制粘贴到其他地方时,就找不到库了,原理图就会无法显示器件符号,如何将库替换为我们 ...

  3. MySQL练习题--sqlzoo刷题2

    SELECT from Nobel Tutorial 1.Change the query shown so that it displays Nobel prizes for 1950. SELEC ...

  4. 利用Process类创建多个子进程对象执行任务,主进程负责调度

    import time from multiprocessing import Process def run1(): for i in range(5): print("sunck is ...

  5. 【JZOJ6431】【luoguP5658】【CSP-S2019】括号树

    description analysis 用栈维护一下树上路径未匹配的左括号,然后在树上找右括号匹配,设\(f[i]\)为\(i\)节点的贡献,\(g[i]\)是答案 为左括号可以直接继承父节点的信息 ...

  6. C# 16进制转字符串,字符串转16进制

    { //========================================================== //16进制转字符串 public static byte[] HexTo ...

  7. Delphi max函数和min函数

    uses单元 math: min函数  min(A,B); 比较A.B的大小,取最小值 max函数  min(A,B); 比较A.B的大小,取最大值 原型示例:function Min(const A ...

  8. Java/sql找出oracle数据库有空格的列

    1.java方式 String table_sql = "select table_name from user_tables";//所有用户表 List<String> ...

  9. 【Linux】windows下编写的脚本文件,放到Linux中无法识别格式

    注意:我启动的时候遇到脚本错误 » sh startup.sh -m standalone tanghuang@bogon : command not found : command not foun ...

  10. 【LeetCode 17】电话号码的字母组合

    题目链接 [题解] 用回溯法搞一下. 一搞就有~ 注意输入空串的时候别返回那个空串.. [代码] class Solution { public: string dic[10]; vector< ...