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实现的数据结构与算法之快速排序详解
Apr 22 Python
windows下安装Python和pip终极图文教程
Mar 05 Python
Python爬取qq music中的音乐url及批量下载
Mar 23 Python
python 获取list特定元素下标的实例讲解
Apr 09 Python
Python中if elif else及缩进的使用简述
May 31 Python
python根据文本生成词云图代码实例
Nov 15 Python
matplotlib实现显示伪彩色图像及色度条
Dec 07 Python
python 实现将Numpy数组保存为图像
Jan 09 Python
Python利用逻辑回归模型解决MNIST手写数字识别问题详解
Jan 14 Python
学python最电脑配置有要求么
Jul 05 Python
Pandas的数据过滤实现
Jan 15 Python
教你用Python matplotlib库制作简单的动画
Jun 11 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
为jquery.ui.dialog 增加“自动记住关闭时的位置”的功能
2009/11/24 Javascript
JavaScript为事件句柄绑定监听函数实例详解
2015/12/15 Javascript
JavaScript实现拖拽元素对齐到网格(每次移动固定距离)
2016/11/30 Javascript
javascript事件捕获机制【深入分析IE和DOM中的事件模型】
2016/12/15 Javascript
jQuery简单实现遍历单选框的方法
2017/03/06 Javascript
值得分享和收藏的xmlplus组件学习教程
2017/05/05 Javascript
基于iScroll实现下拉刷新和上滑加载效果
2017/07/18 Javascript
Vue.js 2.x之组件的定义和注册图文详解
2018/06/19 Javascript
详解javascript replace高级用法
2019/02/17 Javascript
详解Vue之父子组件传值
2019/04/01 Javascript
layui实现鼠标移动到单元格上显示数据的方法
2019/09/11 Javascript
js单线程的本质 Event Loop解析
2019/10/29 Javascript
Vue替代marquee标签超出宽度文字横向滚动效果
2019/12/09 Javascript
javascript实现前端分页效果
2020/06/24 Javascript
Python时间模块datetime、time、calendar的使用方法
2016/01/13 Python
python 对类的成员函数开启线程的方法
2019/01/22 Python
关于python导入模块import与常见的模块详解
2019/08/28 Python
python TK库简单应用(实时显示子进程输出)
2019/10/29 Python
python如何导出微信公众号文章方法详解
2020/08/31 Python
Python自定义sorted排序实现方法详解
2020/09/18 Python
python 多进程和协程配合使用写入数据
2020/10/30 Python
精致的手工皮鞋:Shoe Embassy
2019/11/08 全球购物
The Outnet亚太地区:折扣设计师时装店
2019/12/05 全球购物
乌克兰移动电子产品和相关配件的在线商店:iTMag
2020/03/16 全球购物
实习教师自我鉴定
2013/12/09 职场文书
个人简历自我评价
2014/01/06 职场文书
工商治理实习生的自我评价分享
2014/02/20 职场文书
创先争优公开承诺书
2014/08/30 职场文书
工作收入证明模板
2014/10/10 职场文书
教师作风整改措施思想汇报
2014/10/12 职场文书
2015年人力资源工作总结
2015/04/08 职场文书
幼儿园开学家长寄语(2015秋季)
2015/05/27 职场文书
国庆节主题班会
2015/08/15 职场文书
小学五年级(说明文3篇)
2019/08/13 职场文书
MySQL中IO问题的深入分析与优化
2022/04/02 MySQL
Golang获取List列表元素的四种方式
2022/04/20 Golang