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 相关文章推荐
centos下更新Python版本的步骤
Feb 12 Python
用Python编写一个简单的Lisp解释器的教程
Apr 03 Python
编写Python脚本来获取mp3文件tag信息的教程
May 04 Python
python中for语句简单遍历数据的方法
May 07 Python
OpenCV实现人脸识别
Apr 07 Python
在python win系统下 打开TXT文件的实例
Apr 29 Python
浅谈python 中类属性共享的问题
Jul 02 Python
django框架使用方法详解
Jul 18 Python
flask 使用 flask_apscheduler 做定时循环任务的实现
Dec 10 Python
Python预测2020高考分数和录取情况
Jul 08 Python
详解Pycharm安装及Django安装配置指南
Sep 15 Python
Python-OpenCV实现图像缺陷检测的实例
Jun 11 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生成excel时单元格内换行问题的解决方法
2010/08/26 PHP
PHP学习笔记之二
2011/01/17 PHP
使用php-timeit估计php函数的执行时间
2015/09/06 PHP
php微信浏览器分享设置以及回调详解
2016/08/01 PHP
Yii2框架实现登陆添加验证码功能示例
2018/07/12 PHP
dojo 之基础篇
2007/03/24 Javascript
jQuery新的事件绑定机制on()示例应用
2014/07/18 Javascript
jQuery中first()方法用法实例
2015/01/06 Javascript
AngularJS模仿Form表单提交的实现代码
2016/12/08 Javascript
简单实现nodejs上传功能
2017/01/14 NodeJs
Cpage.js给组件绑定事件的实现代码
2017/08/31 Javascript
jQuery NProgress.js加载进度插件的简单使用方法
2018/01/31 jQuery
vue中动态绑定表单元素的属性方法
2018/02/23 Javascript
jquery实现下载图片功能
2019/07/18 jQuery
解决layui下拉框监听问题(监听不到值的变化)
2019/09/28 Javascript
vue中动态select的使用方法示例
2019/10/28 Javascript
vue-cli3访问public文件夹静态资源报错的解决方式
2020/09/02 Javascript
vue实现单一筛选、删除筛选条件
2020/10/26 Javascript
[06:50]DSPL次级职业联赛十强晋级之路
2014/11/18 DOTA
pymongo实现控制mongodb中数字字段做加法的方法
2015/03/26 Python
python八皇后问题的解决方法
2018/09/27 Python
python爬取淘宝商品销量信息
2018/11/16 Python
利用Python正则表达式过滤敏感词的方法
2019/01/21 Python
Django-Model数据库操作(增删改查、连表结构)详解
2019/07/17 Python
Django发送邮件功能实例详解
2019/09/02 Python
利用django创建一个简易的博客网站的示例
2020/09/29 Python
新西兰便宜隐形眼镜购买网站:QUICKLENS New Zealand
2019/03/02 全球购物
乌克兰移动电子产品和相关配件的在线商店:iTMag
2020/03/16 全球购物
Orlebar Brown官网:设计师泳裤和泳装
2020/12/08 全球购物
送给程序员的20个Java集合面试问题
2014/08/06 面试题
会计主管岗位职责
2014/01/03 职场文书
《将心比心》教学反思
2014/04/08 职场文书
入党积极分子批评与自我批评思想汇报
2014/09/14 职场文书
Element-ui Layout布局(Row和Col组件)的实现
2021/12/06 Vue.js
Golang解析JSON对象
2022/04/30 Golang
nginx设置资源请求目录的方式详解
2022/05/30 Servers