mvc框架打造笔记之wsgi协议的优缺点以及接口实现


Posted in Python onAugust 01, 2018

前言:

又是WSGI ,这是我曾经比较熟悉的协议,以前针对实现了wsgi server的unicorn和uwsgi都写过源码解析的文章。  其实他们的实现也很简单,就是给flask、django这样的application传递environ,start_response 。

mvc框架打造笔记之wsgi协议的优缺点以及接口实现

什么是WSGI协议,什么是WSGI Server,他们的区别是什么?

上线的架构图很容易误导别人,乍一看有nginx这样的web服务器,又有gunicorn这样的wsgi server。  我们先说明wsgi 跟 wsgi server的关系,wsgi是个协议,是web底层跟application解耦的协议。wsgi server是自己做web服务器借用wsgi协议来调用application。 我们需要明确一点,nginx是无法直接跟flask application做通信,需要借用wsgi server。flask本身也有个web服务器是werkzeug,so 才能启动服务并监听端口。记得以前uwsgi没名气的时候,我们都在使用apache + mode_wsgi模式,apache也无法直接跟tornado通信,是借用mod_wsgi把torando做成了unix socket服务,说白了也是启动了一个服务,靠apache来转发。

nginx、apache在这里只是启动了proxy的作用,那为什么不直接把uwsgi和gunicorn给暴露出去,因为nginx的静态文件处理能力极强。

mvc框架打造笔记之wsgi协议的优缺点以及接口实现

WSGI怎么工作的

wsgi主要是两层,服务器方 和 应用程序 :

1  服务器方:从底层解析http解析,然后调用应用程序,给应用程序提供(环境信息)和(回调函数), 这个回调函数是用来将应用程序设置的http header和status等信息传递给服务器方.

2  应用程序:用来生成返回的header,body和status,以便返回给服务器方。

WSGI把来自socket的数据包解析为http格式,然后进而变化为environ变量,这environ变量里面有wsgi本身的信息(比如 host, post,进程模式等),还有client的header及body信息。start_respnse是一个函调函数,必须要附带两个参数,一个是status(http状态),response_headers(响应的header头信息)。

像flask、django、tornado都会暴露WSGI协议入口,我们只需要自己实现WSGI协议,wsgi server然后给flask传递environ,及start_response, 等到application返回值之后,我再socket send返回客户端。

WSGI的优点、缺点是什么?

优点:

多样的部署选择和组件之间的高度解耦

由于上面提到的高度解耦特性,理论上,任何一个符合WSGI规范的App都可以部署在任何一个实现了WSGI规范的Server上,这给Python Web应用的部署带来了极大的灵活性。

Flask自带了一个基于Werkzeug的调试用服务器。根据Flask的文档,在生产环境不应该使用内建的调试服务器,而应该采取以下方式之一进行部署:

GUNICORN

UWSGI

缺点:

没有

我们在wsgi层可以做什么时尚的操作:

  1. 黑白名单规则防御.
  2. 可以通过重写环境变量,根据目标URL,将请求消息路由到不同的应用对象。这意思就是说,实现一套类似nginx location proxy的规则,可以把阻塞高性能转给tornado的app. 当然这是理想化的操作.
  3. 允许在一个进程中同时运行多个应用程序或应用框架.
  4. 负载均衡和远程处理,通过在网络上转发请求和响应消息.

我们用python具体实现这个wsgi server及协议.

#xiaorui.cc
 
import socket
import StringIO
import sys
 
class WSGIServer(object):
 
 address_family = socket.AF_INET
 socket_type = socket.SOCK_STREAM
 request_queue_size = 1
 
 def __init__(self, server_address):
  # 创建一个可用的socket
  self.listen_socket = listen_socket = socket.socket(
   self.address_family,
   self.socket_type
  )
  #socket的工作模式
  listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  # Bind
  listen_socket.bind(server_address) #绑定地址
  # Activate
  listen_socket.listen(self.request_queue_size) #监听文件描述符
  # Get server host name and port
  host, port = self.listen_socket.getsockname()[:2]
  self.server_name = socket.getfqdn(host)
  self.server_port = port
  self.headers_set = []
 
 def set_app(self, application):
  self.application = application
 
 def serve_forever(self): #启动WSGI server服务,不停的监听并获取socket数据。
  listen_socket = self.listen_socket
  while True:
    self.client_connection, client_address = listen_socket.accept()
   self.handle_one_request() #处理新连接
 
 def handle_one_request(self): #主要处理函数
  self.request_data = request_data = self.client_connection.recv(1024)
  print(''.join(
   '< {line}\n'.format(line=line)
   for line in request_data.splitlines()
  ))
 
  self.parse_request(request_data)
  env = self.get_environ()
 
  #给flask\tornado传递两个参数,environ,start_response
  result = self.application(env, self.start_response)
 
  # Construct a response and send it back to the client
  self.finish_response(result)
 
 def parse_request(self, text): #处理socket的http协议
  request_line = text.splitlines()[0]
  request_line = request_line.rstrip('\r\n')
  # Break down the request line into components
  (self.request_method, # GET
   self.path,   # /hello
   self.request_version # HTTP/1.1
   ) = request_line.split()
 
 def get_environ(self): #获取environ数据
  env = {}
  env['wsgi.version']  = (1, 0)
  env['wsgi.url_scheme'] = 'http'
  env['wsgi.input']  = StringIO.StringIO(self.request_data)
  env['wsgi.errors']  = sys.stderr
  env['wsgi.multithread'] = False
  env['wsgi.multiprocess'] = False
  env['wsgi.run_once']  = False
  env['REQUEST_METHOD'] = self.request_method # GET
  env['PATH_INFO']   = self.path    # /hello
  env['SERVER_NAME']  = self.server_name  # localhost
  env['SERVER_PORT']  = str(self.server_port) # 8888
  return env
 
 def start_response(self, status, response_headers, exc_info=None): #创建回调函数.
  server_headers = [
   ('Date', 'Tue, 31 Mar 2015 12:54:48 GMT'),
   ('Server', 'WSGIServer 0.3'),
  ]
  self.headers_set = [status, response_headers + server_headers]
 
 def finish_response(self, result): #把application返回给WSGI的数据返回给客户端。
  try:
   status, response_headers = self.headers_set
   response = 'HTTP/1.1 {status}\r\n'.format(status=status)
   for header in response_headers:
    response += '{0}: {1}\r\n'.format(*header)
   response += '\r\n'
   for data in result:
    response += data
   # Print formatted response data a la 'curl -v'
   print(''.join(
    '> {line}\n'.format(line=line)
    for line in response.splitlines()
   ))
   self.client_connection.sendall(response)
  finally:
   self.client_connection.close()
 
SERVER_ADDRESS = (HOST, PORT) = '', 8888
 
def make_server(server_address, application):
 server = WSGIServer(server_address)
 server.set_app(application)
 return server
 
if __name__ == '__main__':
 if len(sys.argv) < 2:
  sys.exit('Provide a WSGI application object as module:callable')
 app_path = sys.argv[1]
 module, application = app_path.split(':')
 module = __import__(module) #动态加载模块
 application = getattr(module, application) #使用自省的模式加载application的WSGI协议入口。
 httpd = make_server(SERVER_ADDRESS, application)
 print('WSGIServer: Serving HTTP on port {port} ...\n'.format(port=PORT))
 httpd.serve_forever()

下面是flask application的实例, 我们会发现python的常见web框架都兼容了wsgi接口,没有例外。

#xiaorui.cc
from flask import Flask
from flask import Response
flask_app = Flask('flaskapp')
 
@flask_app.route('/search')
def hello_world():
 return Response(
  'xiaorui.cc Golang vs python !\n',
  mimetype='text/plain'
 )
 
app = flask_app.wsgi_app

运行方式很简单:

python webserver2.py flaskapp:app

这样一个wsgi就构成了,下次我们会借用这wsgi框架扩展成prefork wsgi server,类似gunicorn那样。   以前在wsgi做过一些application的分流,但涉及到高并发的场景下的分流,还是建议直接在nginx层面做。 现在nginx lua的编程能力越来越强,大家都在使用nginx lua做网关及入口的开发。

参考文章:

https://ruslanspivak.com/lsbaws-part2/

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

Python 相关文章推荐
Python Web开发模板引擎优缺点总结
May 06 Python
在Python的setuptools框架下生成egg的教程
Apr 13 Python
整理Python 常用string函数(收藏)
May 30 Python
利用 Monkey 命令操作屏幕快速滑动
Dec 07 Python
利用Python生成文件md5校验值函数的方法
Jan 10 Python
python使用两种发邮件的方式smtp和outlook示例
Jun 02 Python
linux下python使用sendmail发送邮件
May 22 Python
python矩阵转换为一维数组的实例
Jun 05 Python
使用pandas把某一列的字符值转换为数字的实例
Jan 29 Python
python实现拉普拉斯特征图降维示例
Nov 25 Python
Python实时监控网站浏览记录实现过程详解
Jul 14 Python
Python内置的数据类型及使用方法
Apr 13 Python
python爬虫自动创建文件夹的功能
Aug 01 #Python
浅谈关于Python3中venv虚拟环境
Aug 01 #Python
python Web开发你要理解的WSGI &amp; uwsgi详解
Aug 01 #Python
Django教程笔记之中间件middleware详解
Aug 01 #Python
flask框架中勾子函数的使用详解
Aug 01 #Python
flask中过滤器的使用详解
Aug 01 #Python
Python拼接微信好友头像大图的实现方法
Aug 01 #Python
You might like
php获取用户IPv4或IPv6地址的代码
2012/11/15 PHP
php在windows环境下获得cpu内存实时使用率(推荐)
2018/02/08 PHP
XHTML-Strict 内允许出现的标签
2006/12/11 Javascript
关于js获取radio和select的属性并控制的代码
2011/05/12 Javascript
getElementByIdx_x js自定义getElementById函数
2012/01/24 Javascript
document.createElement()用法及注意事项(ff下不兼容)
2013/03/13 Javascript
javascript中解析四则运算表达式的算法和示例
2014/08/11 Javascript
jquery实现的淡入淡出下拉菜单效果
2015/08/25 Javascript
使用微信内置浏览器点击下拉框出现页面乱跳转现象(iphone),该怎么办
2016/01/04 Javascript
EditPlus中的正则表达式 实战(2)
2016/12/15 Javascript
Angular的$http与$location
2016/12/26 Javascript
前端主流框架vue学习笔记第二篇
2017/07/26 Javascript
基于js 字符串indexof与search方法的区别(详解)
2017/12/04 Javascript
详解webpack require.ensure与require AMD的区别
2017/12/13 Javascript
高性能的javascript之加载顺序与执行原理篇
2018/01/14 Javascript
three.js实现炫酷的全景3D重力感应
2018/12/30 Javascript
js 计算图片内点个数的示例代码
2019/04/04 Javascript
利用JS代码自动删除稿件的普通弹幕功能
2019/09/20 Javascript
jQuery+ajax实现用户登录验证
2020/09/13 jQuery
Vue实现指令式动态追加小球动画组件的步骤
2020/12/18 Vue.js
[59:44]2018DOTA2亚洲邀请赛 3.31 小组赛 B组 paiN vs iG
2018/03/31 DOTA
[54:06]OG vs TNC 2018国际邀请赛小组赛BO2 第二场 8.19
2018/08/21 DOTA
Python采用raw_input读取输入值的方法
2014/08/18 Python
BP神经网络原理及Python实现代码
2018/12/18 Python
TensorFlow保存TensorBoard图像操作
2020/06/23 Python
Python脚本打包成可执行文件过程解析
2020/10/20 Python
python爬虫用scrapy获取影片的实例分析
2020/11/23 Python
整理的15个非常有用的 HTML5 开发教程和速查手册
2011/10/18 HTML / CSS
护理学毕业生自荐信
2013/10/02 职场文书
企划专员岗位职责
2013/12/09 职场文书
工程专业应届生求职信
2014/02/19 职场文书
高中教师评语大全
2014/04/25 职场文书
努力工作保证书
2015/02/28 职场文书
幼儿园重阳节活动总结
2015/05/05 职场文书
python 命令行传参方法总结
2021/05/25 Python
Python 一键获取电脑浏览器的账号密码
2022/05/11 Python