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的Django框架中的数据过滤功能
Jul 17 Python
基于Python __dict__与dir()的区别详解
Oct 30 Python
python数据抓取分析的示例代码(python + mongodb)
Dec 25 Python
Python中将dataframe转换为字典的实例
Apr 13 Python
Python实现查找最小的k个数示例【两种解法】
Jan 08 Python
对python多线程中Lock()与RLock()锁详解
Jan 11 Python
用uWSGI和Nginx部署Flask项目的方法示例
May 05 Python
python向字符串中添加元素的实例方法
Jun 28 Python
python 日期排序的实例代码
Jul 11 Python
python GUI库图形界面开发之PyQt5单选按钮控件QRadioButton详细使用方法与实例
Feb 28 Python
Python3中的f-Strings增强版字符串格式化方法
Mar 04 Python
python 进阶学习之python装饰器小结
Sep 04 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
2019年漫画销量排行榜:鬼灭登顶 海贼单卷制霸 尾田盛赞鬼灭
2020/03/08 日漫
Linux环境下搭建php开发环境的操作步骤
2013/06/17 PHP
ThinkPHP入口文件设置及相关注意事项分析
2014/12/05 PHP
php购物车实现方法
2015/01/03 PHP
laravel 实现根据字段不同值做不同查询
2019/10/23 PHP
用js计算页面执行时间的函数
2006/12/07 Javascript
利用404错误页面实现UrlRewrite的实现代码
2008/08/20 Javascript
jQuery $.each的用法说明
2010/03/22 Javascript
js如何获取file控件的完整路径具体实现代码
2013/05/15 Javascript
js将json格式内容转换成对象的方法
2013/11/01 Javascript
document.forms[].submit()使用介绍
2014/02/19 Javascript
javascript生成随机颜色示例代码
2014/05/05 Javascript
AngularJS学习笔记之基本指令(init、repeat)
2015/06/16 Javascript
JS+CSS实现的日本门户网站经典选项卡导航效果
2015/09/27 Javascript
BootStrap Progressbar 实现大文件上传的进度条的实例代码
2016/06/27 Javascript
配置nodejs环境的方法
2017/05/13 NodeJs
详细分析jsonp的原理和实现方式
2017/11/20 Javascript
在 Vue-CLI 中引入 simple-mock实现简易的 API Mock 接口数据模拟
2018/11/28 Javascript
微信小程序 函数防抖 解决重复点击消耗性能问题实现代码
2019/09/12 Javascript
vue 实现v-for循环回来的数据动态绑定id
2019/11/07 Javascript
JQuery事件委托(适用于给动态生成的脚本元素添加事件)
2020/02/01 jQuery
vuex实现购物车功能
2020/06/28 Javascript
Vue执行方法,方法获取data值,设置data值,方法传值操作
2020/08/05 Javascript
如何在面试中手写出javascript节流和防抖函数
2020/10/22 Javascript
python使用socket远程连接错误处理方法
2015/04/29 Python
Python发送http请求解析返回json的实例
2018/03/26 Python
Django文件存储 自己定制存储系统解析
2019/08/02 Python
HTML实现代码雨源码及效果示例
2020/02/25 HTML / CSS
美国机场停车位预订:About Airport Parking
2018/03/26 全球购物
介绍一下你对SOA的认识
2016/04/24 面试题
2015年营业员工作总结
2015/04/23 职场文书
redis内存空间效率问题的深入探究
2021/05/17 Redis
分布式锁为什么要选择Zookeeper而不是Redis?看完这篇你就明白了
2021/05/21 Redis
服务器SVN搭建图文安装过程
2022/06/21 Servers
react中useState使用:如何实现在当前表格直接更改数据
2022/08/05 Javascript
css弧边选项卡的项目实践
2023/05/07 HTML / CSS