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 输出一个两行字符的变量
Feb 05 Python
python装饰器使用方法实例
Nov 21 Python
Python os.rename() 重命名目录和文件的示例
Oct 25 Python
python使用xlsxwriter实现有向无环图到Excel的转换
Dec 12 Python
Python面向对象程序设计类的封装与继承用法示例
Apr 12 Python
python之pyqt5通过按钮改变Label的背景颜色方法
Jun 13 Python
Python通用函数实现数组计算的方法
Jun 13 Python
基于Python批量生成指定尺寸缩略图代码实例
Nov 20 Python
python实现超市商品销售管理系统
Nov 22 Python
Python2与Python3的区别点整理
Dec 12 Python
python如何写个俄罗斯方块
Nov 06 Python
python实现简单文件读写函数
Feb 25 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设计模式 DAO(数据访问对象模式)
2011/06/26 PHP
javascript StringBuilder类实现
2008/12/22 Javascript
定时器(setTimeout/setInterval)调用带参函数失效解决方法
2013/03/26 Javascript
Node.js中child_process实现多进程
2015/02/03 Javascript
javascript实现点击商品列表checkbox实时统计金额的方法
2015/05/15 Javascript
js前端面试题及答案整理(一)
2016/08/26 Javascript
JavaScript省市区三级联动菜单效果
2016/09/21 Javascript
js手机号批量滚动抽奖实现代码
2020/04/17 Javascript
原生js实现倒计时--2018
2017/02/21 Javascript
JavaScript组件开发之输入框加候选框
2017/03/10 Javascript
浅谈vue+webpack项目调试方法步骤
2017/09/11 Javascript
手把手教你用Node.js爬虫爬取网站数据的方法
2018/07/05 Javascript
webpack 3.X学习之多页面打包的方法
2018/09/04 Javascript
JavaScript学习教程之cookie与webstorage
2019/06/23 Javascript
[01:35]辉夜杯战队访谈宣传片—iG.V
2015/12/25 DOTA
Python入门篇之字典
2014/10/17 Python
python线程、进程和协程详解
2016/07/19 Python
教你用python3根据关键词爬取百度百科的内容
2016/08/18 Python
python实现定时提取实时日志程序
2018/06/22 Python
python实现将一个数组逆序输出的方法
2018/06/25 Python
使用Template格式化Python字符串的方法
2019/01/22 Python
python celery分布式任务队列的使用详解
2019/07/08 Python
Django app配置多个数据库代码实例
2019/12/17 Python
python 正则表达式参数替换实例详解
2020/01/17 Python
python GUI库图形界面开发之PyQt5表单布局控件QFormLayout详细使用方法与实例
2020/03/06 Python
python实现简单俄罗斯方块
2020/03/13 Python
TensorFlow2.0使用keras训练模型的实现
2021/02/20 Python
安全生产计划书
2014/05/04 职场文书
银行爱岗敬业演讲稿
2014/05/05 职场文书
电子信息工程专业自荐书
2014/06/24 职场文书
公共机构节能宣传周活动总结
2014/07/09 职场文书
反腐倡廉剖析材料
2014/09/30 职场文书
党风廉政建设调研报告
2015/01/01 职场文书
2015年社区精神文明工作总结
2015/05/26 职场文书
2016年寒假学习心得体会
2015/10/09 职场文书
使用GO语言实现Mysql数据库CURD的简单示例
2021/08/07 Golang