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访问纯真IP数据库脚本分享
Jun 29 Python
Python如何读取MySQL数据库表数据
Mar 11 Python
利用Tkinter(python3.6)实现一个简单计算器
Dec 21 Python
Python 判断文件或目录是否存在的实例代码
Jul 19 Python
pycharm打开命令行或Terminal的方法
Jan 16 Python
Python将列表数据写入文件(txt, csv,excel)
Apr 03 Python
python 自动轨迹绘制的实例代码
Jul 05 Python
在pandas中遍历DataFrame行的实现方法
Oct 23 Python
python matplotlib中的subplot函数使用详解
Jan 19 Python
Django admin管理工具TabularInline类用法详解
May 14 Python
解决pytorch 交叉熵损失输出为负数的问题
Jul 07 Python
如何用Django处理gzip数据流
Jan 29 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
解析func_num_args与func_get_args函数的使用
2013/06/24 PHP
Yii控制器中操作视图js的方法
2016/07/04 PHP
基于Swoole实现PHP与websocket聊天室
2016/08/03 PHP
用jquery实现等比例缩放图片效果插件
2010/07/24 Javascript
用jquery实现的模拟QQ邮箱里的收件人选取及其他效果(一)
2011/01/06 Javascript
Android中资源文件(非代码部分)的使用概览
2012/12/18 Javascript
setTimeout函数兼容各主流浏览器运行执行效果实例
2013/06/13 Javascript
JavaScript中getUTCSeconds()方法的使用详解
2015/06/11 Javascript
全面解析Bootstrap图片轮播效果
2015/12/03 Javascript
BootStrap学习系列之Bootstrap Typeahead 组件实现百度下拉效果(续)
2016/07/07 Javascript
javascript垃圾收集机制的原理分析
2016/12/08 Javascript
Jquery根据浏览器窗口改变调整大小的方法
2017/02/07 Javascript
JS仿JQuery选择器功能
2017/03/08 Javascript
vue 使用ref 让父组件调用子组件的方法
2018/02/08 Javascript
创建echart多个联动的示例代码
2018/11/23 Javascript
vue实现百度搜索功能
2020/12/28 Javascript
使用p5.js临摹动态图片
2019/11/04 Javascript
Nodejs封装类似express框架的路由实例详解
2020/01/05 NodeJs
antd 表格列宽自适应方法以及错误处理操作
2020/10/27 Javascript
python实现百万答题自动百度搜索答案
2018/01/16 Python
使用python实现抓取腾讯视频所有电影的爬虫
2019/04/15 Python
pandas计数 value_counts()的使用
2019/06/24 Python
win8.1安装Python 2.7版环境图文详解
2019/07/01 Python
在python中用print()输出多个格式化参数的方法
2019/07/16 Python
HTML5 canvas 瀑布流文字效果的示例代码
2018/01/31 HTML / CSS
英国著名的药妆网站:Escentual
2016/07/29 全球购物
加拿大时尚潮流大码女装购物网站:Addition Elle
2018/04/02 全球购物
电气工程及自动化专业自荐书范文
2013/12/18 职场文书
2014高考励志标语
2014/06/05 职场文书
开业庆典活动策划方案
2014/09/21 职场文书
2014年机关工会工作总结
2014/12/19 职场文书
早安问候语大全
2015/11/10 职场文书
怎样写工作总结啊!
2019/06/18 职场文书
Python 把两层列表展开平铺成一层(5种实现方式)
2021/04/07 Python
深入解析NumPy中的Broadcasting广播机制
2021/05/30 Python
Java工作中实用的代码优化技巧分享
2022/04/21 Java/Android