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 相关文章推荐
在Linux下使用Python的matplotlib绘制数据图的教程
Jun 11 Python
Python操作串口的方法
Jun 17 Python
python连接mysql实例分享
Oct 09 Python
Anaconda下安装mysql-python的包实例
Jun 11 Python
Python Learning 列表的更多操作及示例代码
Aug 22 Python
在Python中关于使用os模块遍历目录的实现方法
Jan 03 Python
对Python Pexpect 模块的使用说明详解
Feb 14 Python
Python实现K折交叉验证法的方法步骤
Jul 11 Python
QML使用Python的函数过程解析
Sep 26 Python
浅析Python 简单工厂模式和工厂方法模式的优缺点
Jul 13 Python
高考要来啦!用Python爬取历年高考数据并分析
Jun 03 Python
Python使用海龟绘图实现贪吃蛇游戏
Jun 18 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中session_id()函数详细介绍,会话id生成过程及session id长度
2015/09/23 PHP
Django中通过定时任务触发页面静态化的处理方式
2018/08/29 PHP
JS的数组的扩展实例代码
2008/07/09 Javascript
js替代copy(示例代码)
2013/11/27 Javascript
原始XMLHttpRequest方法详情回顾
2013/11/28 Javascript
Jqgrid设置全选(选择)及获取选择行的值示例代码
2013/12/28 Javascript
Jquery on方法绑定事件后执行多次的解决方法
2016/06/02 Javascript
JS+DIV实现的卷帘效果示例
2017/03/22 Javascript
VUE使用vuex解决模块间传值问题的方法
2017/06/01 Javascript
关于react-router的几种配置方式详解
2017/07/24 Javascript
JavaScript使用FileReader实现图片上传预览效果
2020/03/27 Javascript
微信小程序内拖动图片实现移动、放大、旋转的方法
2018/09/04 Javascript
jQuery使用bind动态绑定事件无效的处理方法
2018/12/11 jQuery
jQuery内容选择器与表单选择器实例分析
2019/06/28 jQuery
解决Layui 表格自适应高度的问题
2019/11/15 Javascript
vue与iframe之间的信息交互的实现
2020/04/08 Javascript
[02:05:03]完美世界DOTA2联赛循环赛 LBZS VS Matador BO2 10.28
2020/10/28 DOTA
[45:50]完美世界DOTA2联赛PWL S3 CPG vs Forest 第二场 12.16
2020/12/17 DOTA
python实现获取Ip归属地等信息
2016/08/27 Python
利用Tkinter和matplotlib两种方式画饼状图的实例
2017/11/06 Python
python利用thrift服务读取hbase数据的方法
2018/12/27 Python
PyQt5 窗口切换与自定义对话框的实例
2019/06/20 Python
一篇文章搞定Python操作文件与目录
2019/08/13 Python
Python读取YAML文件过程详解
2019/12/30 Python
Python 读取有公式cell的结果内容实例方法
2020/02/17 Python
jurlique茱莉蔻英国官网:澳洲天然护肤品
2018/08/03 全球购物
Myprotein西班牙官网:欧洲第一大运动营养品牌
2020/02/24 全球购物
《胖乎乎的小手》教学反思
2014/02/26 职场文书
出国签证在职证明
2014/09/20 职场文书
国际商务专业毕业生自我鉴定2014
2014/09/27 职场文书
2014年感恩节活动策划方案
2014/10/06 职场文书
合伙购房协议样本
2014/10/06 职场文书
探究Mysql模糊查询是否区分大小写
2021/06/11 MySQL
SQL实现LeetCode(175.联合两表)
2021/08/04 MySQL
Prometheus 监控MySQL使用grafana展示
2021/08/30 MySQL
基于Redission的分布式锁实战
2022/08/14 Redis