Python模块WSGI使用详解


Posted in Python onFebruary 02, 2018

WSGI(Web Server Gateway Interface):Web服务网关接口,是Python中定义的服务器程序和应用程序之间的接口。

Web程序开发中,一般分为服务器程序和应用程序。服务器程序负责对socket服务的数据进行封装和整理,而应用程序则负责对Web请求进行逻辑处理。

Web应用本质上也是一个socket服务器,用户的浏览器就是一个socket客户端。

我们先用socket编程实现一个简单的Web服务器:

import socket 
 
def handle_request(client): 
  buf = client.recv(1024) 
  print(buf) 
  msg = "HTTP/1.1 200 OK\r\n\r\n" #HTTP头信息 
  client.send(('%s' % msg).encode()) 
  msg = "Hello, World!" 
  client.send(('%s' % msg).encode()) 
 
def main(): 
  ip_port = ("localhost", 8000) 
  sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
  sock.bind(ip_port) 
  sock.listen(5) 
 
  while True: 
    conn, addr = sock.accept() 
    handle_request(conn) 
    conn.close() 
 
if __name__ == "__main__": 
  main()

上述代码中,main()函数就是服务器函数,handle_request()就是应用程序。
下面我们再用python的wsgiref模块来实现跟上述代码一样的Web服务器:

from wsgiref.simple_server import make_server 
 
def handle_request(env, res): 
  res("200 OK",[("Content-Type","text/html")]) 
  body = "<h1>Hello World!</h1>" 
  return [body.encode("utf-8")] 
 
if __name__ == "__main__": 
  httpd = make_server("",8000,handle_request) 
  print("Serving http on port 80000") 
  httpd.serve_forever()

上面两份代码实现的效果是一样的,调用wsgiref模块则明显节省了代码量,是整个程序更加简洁。
wsgiref模块封装了socket服务端的代码,只留下一个调用的接口,省去了程序员的麻烦,程序员可以将精力放在Web请求的逻辑处理中。

以上述的代码为例,详细看一下wsgiref模块的源码中一些关键的地方:

if __name__ == "__main__": 
  httpd = make_server("",8000,handle_request) 
  print("Serving http on port 80000") 
  httpd.serve_forever()

1、整个程序的入口为make_server()函数:

def make_server(host, port, app, server_class=WSGIServer, handler_class=WSGIRequestHandler): 
  """Create a new WSGI server listening on `host` and `port` for `app`""" 
  server = server_class((host, port), handler_class) #默认创建一个WSGIServer类 
  server.set_app(app) #将应用程序,即逻辑处理函数传给类 
  return server

2、make_server()函数默认生成一个WSGIServer类:

class WSGIServer(HTTPServer):
class HTTPServer(socketserver.TCPServer):
class TCPServer(BaseServer):

WSGIServer,HTTPServer两个类没有初始化函数,调用父类的初始化函数,TCPServer类的__init__()函数拓展了BaseServer

类的__init__()函数:

#BaseServer类的__init__()函数: 
def __init__(self, server_address, RequestHandlerClass): 
  """Constructor. May be extended, do not override.""" 
  self.server_address = server_address 
  self.RequestHandlerClass = RequestHandlerClass 
  self.__is_shut_down = threading.Event() 
  self.__shutdown_request = False
#TCPServer类的__init__()函数: 
def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True): 
  """Constructor. May be extended, do not override.""" 
  BaseServer.__init__(self, server_address, RequestHandlerClass) 
  self.socket = socket.socket(self.address_family,self.socket_type) 
    if bind_and_activate: 
      try: 
        self.server_bind() 
        self.server_activate() 
      except: 
        self.server_close() 
        raise

TCPServer类的初始化函数还调用了server_bind(self),server_bind(self)两个函数:

def server_bind(self): 
  """Called by constructor to bind the socket.May be overridden.""" 
  if self.allow_reuse_address: 
    self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 
  self.socket.bind(self.server_address) 
  self.server_address = self.socket.getsockname()  
def self.server_activate(self): 
  """Called by constructor to activate the server.May be overridden.""" 
  self.socket.listen(self.request_queue_size)

可以看到server.bind()函数调用了socket.bind()函数,而server_activate()调用了socket.listen()函数:

3、server.set_app(app),此处传入Web请求的处理逻辑:

def set_app(self,application): 
  self.application = application

4、httpd.serve_forever()函数调用BaseServer类的_handle_request_noblock()函数处理多路请求:

def _handle_request_noblock(self): 
  try: 
    request, client_address = self.get_request() #get_request()调用了socket.accept()函数 
  except OSError: 
    return 
  if self.verify_request(request, client_address): 
    try: 
      self.process_request(request, client_address) 
    except: 
      self.handle_error(request, client_address) 
      self.shutdown_request(request) 
  else: 
    self.shutdown_request(request)
def process_request(self, request, client_address): 
  self.finish_request(request, client_address)   
  self.shutdown_request(request)#shutdown_request()调用socket.close()关闭socket 
     
def finish_request(self, request, client_address): 
  """Finish one request by instantiating RequestHandlerClass.""" 
  self.RequestHandlerClass(request, client_address, self)

5、process_request()函数调用了finish_request()函数,简介调用了make_server函数的默认参数WSGIRequestHandler类:

class WSGIRequestHandler(BaseHTTPRequestHandler):
class BaseHTTPRequestHandler(socketserver.StreamRequestHandler):
class StreamRequestHandler(BaseRequestHandler):

#调用BaseRequestHandler类的初始化函数: 
def __init__(self, request, client_address, server): 
  self.request = request 
  self.client_address = client_address 
  self.server = server 
  self.setup() 
  try: 
    self.handle() 
  finally: 
    self.finish()

6、初始化函数调用之后调用WSGIRequestHandler类的handle()函数获取server的逻辑处理函数:

def handle(self): 
  """Handle a single HTTP request""" 
  try: 
    handler = ServerHandler(self.rfile, stdout, self.get_stderr(), self.get_environ()) 
    handler.request_handler = self   # backpointer for logging 
    handler.run(self.server.get_app()) #此处调用server的逻辑处理函数 
  finally: 
    stdout.detach()

7、BaseHandler类的handler.run()函数执行逻辑处理:

def run(self, application): 
   try: 
    self.setup_environ() 
    self.result = application(self.environ, self.start_response) 
    self.finish_response() 
  except: 
    try: 
      self.handle_error() 
    except: 
      self.close() 
      raise  # ...and let the actual server figure it out.

self.environ:一个包含所有HTTP请求信息的dict对象
self.start_response:一个发送HTTP响应的函数。

在application函数中,调用:

res("200 OK",[("Content-Type","text/html")])

这样就发送了HTTP响应的头信息

8、BaseHandler类的setup_environ()函数获取HTTP请求的头信息:

def setup_environ(self): 
  """Set up the environment for one request""" 
  env = self.environ = self.os_environ.copy() 
   
os_environ= read_environ() 
 
read_environ()函数: 
 
def read_environ(): 
  """Read environment, fixing HTTP variables""" 
  enc = sys.getfilesystemencoding() 
  esc = 'surrogateescape' 
  try: 
    ''.encode('utf-8', esc) 
  except LookupError: 
    esc = 'replace' 
  environ = {} 
 
  # Take the basic environment from native-unicode os.environ. Attempt to 
  # fix up the variables that come from the HTTP request to compensate for 
  # the bytes->unicode decoding step that will already have taken place. 
  for k, v in os.environ.items(): 
    if _needs_transcode(k): 
 
      # On win32, the os.environ is natively Unicode. Different servers 
      # decode the request bytes using different encodings. 
      if sys.platform == 'win32': 
        software = os.environ.get('SERVER_SOFTWARE', '').lower() 
 
        # On IIS, the HTTP request will be decoded as UTF-8 as long 
        # as the input is a valid UTF-8 sequence. Otherwise it is 
        # decoded using the system code page (mbcs), with no way to 
        # detect this has happened. Because UTF-8 is the more likely 
        # encoding, and mbcs is inherently unreliable (an mbcs string 
        # that happens to be valid UTF-8 will not be decoded as mbcs) 
        # always recreate the original bytes as UTF-8. 
        if software.startswith('microsoft-iis/'): 
          v = v.encode('utf-8').decode('iso-8859-1') 
 
        # Apache mod_cgi writes bytes-as-unicode (as if ISO-8859-1) direct 
        # to the Unicode environ. No modification needed. 
        elif software.startswith('apache/'): 
          pass 
 
        # Python 3's http.server.CGIHTTPRequestHandler decodes 
        # using the urllib.unquote default of UTF-8, amongst other 
        # issues. 
        elif ( 
          software.startswith('simplehttp/') 
          and 'python/3' in software 
        ): 
          v = v.encode('utf-8').decode('iso-8859-1') 
 
        # For other servers, guess that they have written bytes to 
        # the environ using stdio byte-oriented interfaces, ending up 
        # with the system code page. 
        else: 
          v = v.encode(enc, 'replace').decode('iso-8859-1') 
 
      # Recover bytes from unicode environ, using surrogate escapes 
      # where available (Python 3.1+). 
      else: 
        v = v.encode(enc, esc).decode('iso-8859-1') 
 
    environ[k] = v 
  return environ

9、BaseHandler类的start_response()函数:

def start_response(self, status, headers,exc_info=None): 
  """'start_response()' callable as specified by PEP 3333""" 
  if exc_info: 
    try: 
      if self.headers_sent: 
        # Re-raise original exception if headers sent 
        raise exc_info[0](exc_info[1]).with_traceback(exc_info[2]) 
    finally: 
      exc_info = None    # avoid dangling circular ref 
  elif self.headers is not None: 
    raise AssertionError("Headers already set!") 
 
  self.status = status 
  self.headers = self.headers_class(headers) 
  status = self._convert_string_type(status, "Status") 
  assert len(status)>=4,"Status must be at least 4 characters" 
  assert status[:3].isdigit(), "Status message must begin w/3-digit code" 
  assert status[3]==" ", "Status message must have a space after code" 
 
  if __debug__: 
    for name, val in headers: 
      name = self._convert_string_type(name, "Header name") 
      val = self._convert_string_type(val, "Header value")   
  return self.write

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
python常见的格式化输出小结
Dec 15 Python
python3利用venv配置虚拟环境及过程中的小问题小结
Aug 01 Python
python微信聊天机器人改进版(定时或触发抓取天气预报、励志语录等,向好友推送)
Apr 25 Python
浅谈PyQt5 的帮助文档查找方法,可以查看每个类的方法
Jun 25 Python
Django 重写用户模型的实现
Jul 29 Python
对Pytorch神经网络初始化kaiming分布详解
Aug 18 Python
python绘制雪景图
Dec 16 Python
PyCharm中配置PySide2的图文教程
Jun 18 Python
Python使用pycharm导入pymysql教程
Sep 16 Python
Django中日期时间型字段进行年月日时分秒分组统计
Nov 27 Python
深入理解python协程
Jun 15 Python
python中pd.cut()与pd.qcut()的对比及示例
Jun 16 Python
Python常见字符串操作函数小结【split()、join()、strip()】
Feb 02 #Python
tensorflow中next_batch的具体使用
Feb 02 #Python
Python输出各行命令详解
Feb 01 #Python
Python输出由1,2,3,4组成的互不相同且无重复的三位数
Feb 01 #Python
Python实现的视频播放器功能完整示例
Feb 01 #Python
Python线性回归实战分析
Feb 01 #Python
Python使用matplotlib简单绘图示例
Feb 01 #Python
You might like
如何在PHP中使用Oracle数据库(3)
2006/10/09 PHP
第四节--构造函数和析构函数
2006/11/16 PHP
php实现refresh刷新页面批量导入数据的方法
2014/12/23 PHP
一些Javascript的IE和Firefox(火狐)兼容性的问题总结及常用例子
2009/05/21 Javascript
分享一个自己写的table表格排序js插件(高效简洁)
2011/10/29 Javascript
Jquery 自定义动画概述及示例
2013/03/29 Javascript
用Jquery实现滚动新闻
2014/02/12 Javascript
jquery仿百度百科底部浮动导航特效
2015/08/08 Javascript
jQuery实现的网页竖向菜单效果代码
2015/08/26 Javascript
jQuery实现仿新浪微博浮动的消息提示框(可智能定位)
2015/10/10 Javascript
JS动态添加选项案例分析
2016/10/17 Javascript
纯JS实现轮播图
2017/02/22 Javascript
深入理解Vuex 模块化(module)
2017/09/26 Javascript
js正则取值的结果数组调试方法
2018/10/10 Javascript
深度解读vue-resize的具体用法
2020/07/08 Javascript
typescript配置alias的详细步骤
2020/08/12 Javascript
[04:04]显微镜下的DOTA2第六期——电影级别的华丽团战
2014/06/20 DOTA
[38:31]完美世界DOTA2联赛PWL S3 Magma vs GXR 第一场 12.13
2020/12/17 DOTA
Python实现的使用telnet登陆聊天室实例
2015/06/17 Python
使用Python搭建虚拟环境的配置方法
2018/02/28 Python
pandas 条件搜索返回列表的方法
2018/10/30 Python
Python字典的核心底层原理讲解
2019/01/24 Python
Python Pexpect库的简单使用方法
2019/01/29 Python
浅谈Python爬虫原理与数据抓取
2020/07/21 Python
python爬虫请求头设置代码
2020/07/28 Python
解决pip安装的第三方包在PyCharm无法导入的问题
2020/10/15 Python
python调用win32接口进行截图的示例
2020/11/11 Python
阿联酋电子产品购物网站:Menakart
2017/09/15 全球购物
澳大利亚最大的网上油画销售画廊:Direct Art Australia
2018/04/15 全球购物
英国女性时尚精品店:THE DRESSING ROOM
2018/05/23 全球购物
计算机网络毕业生自荐信
2013/10/01 职场文书
宝宝周岁宴答谢词
2014/01/26 职场文书
村主任“四风”问题个人整改措施
2014/10/04 职场文书
2014个人年终工作总结范文
2014/12/15 职场文书
结婚十年感言
2015/07/31 职场文书
CSS实现多个元素在盒子内两端对齐效果
2021/03/30 HTML / CSS