[关闭]
@ranger-01 2018-03-18T16:58:33.000000Z 字数 9132 阅读 1125

uwsgi, uWSGI, WSGI

wsgi web_server


0. 术语定义

有很多文章中都有提到web server,根据上下文,它一般有三种含义:

由于我们要介绍的是http server <-> application serverapplication server <-> application 两个层次的协议,所以在这里要做一个严格的区分,以防混淆。

1. uwsgi, uWSGI, WSGI 的关系

三个词虽然只是大小写不一样,但是他们表示的东西在三个不同的层次:uwsgi是两个server间通信的协议,WSGI是函数调用接口的定义,uWSGI是实现了uwsgi协议,与WSGI调用规范的server。

更形象的用一张图说明:
image.png-59.4kB
注: 图片中web server指application server

2. 为什么会有uwsgi

浏览器请求一个页面的流程:

  1. 浏览器发送请求给服务器,包含请求头和请求体
  2. 服务器解析请求头和请求体
  3. 服务器根据请求信息来处理请求,生成返回内容
  4. 服务器生成响应头和响应体
  5. 服务器返回响应给浏览器,浏览器显示给用户

一个网站,一般有很多个不同的请求,在这些请求中,基本1,2,4,5步都是固定的,变的只有第三步,所以把这四步抽象出来,让开发者只关注第三步,这样就可以极大提升开发效率。这样就有了application server专注于处理client的不同请求,以及http server与application server之间的通信协议。

http server 与 application server之间协议出现的顺序是:
CGI --> FastCGI --> uwsgi(先有了WSGI,后有的uwsgi)

2.1 协议比较

2.2 CGI协议测试

  1. 环境准备
    • windows环境下安装XAMPP,python
    • 修改Apache配置文件httpd.conf
  2. 创建html & python script

    1. ################ cgitest.html ################
    2. <!DOCTYPE html>
    3. <html>
    4. <head>
    5. <meta charset="utf-8">
    6. <title>菜鸟教程(runoob.com)</title>
    7. </head>
    8. <body>
    9. <form action="/cgi-bin/test.py" method="post">
    10. <textarea name="textcontent" cols="40" rows="4">在这里输入内容...</textarea>
    11. <input type="checkbox" name="runoob" value="on" /> checkbox--1
    12. <input type="checkbox" name="google" value="on" /> Google
    13. <input type="radio" name="site" value="runoob" /> radio--1
    14. <input type="radio" name="site" value="google" /> Google
    15. <select name="dropdown">
    16. <option value="runoob" selected>dropdown--1</option>
    17. <option value="google">Google</option>
    18. </select>
    19. <input type="submit" value="提交" />
    20. </form>
    21. </body>
    22. </html>
    23. ################# cgi script ################
    24. #!D:/program Files/Python35/python.exe -u
    25. # -*- coding: UTF-8 -*-
    26. import os
    27. import sys
    28. print("Content-type:text/html")
    29. print('Set-Cookie: name="rainbow";expires=Wed, 28 Aug 2018 18:30:00 GMT')
    30. print('') # 空行,告诉服务器结束头部
    31. print ("<meta charset=\"utf-8\">")
    32. raw_data = sys.stdin.read()
    33. print('+++++++++++raw data from stdin ++++++++++++</br>')
    34. print(raw_data)
    35. print("</br>")
    36. print('+++++++++++++++prit pid +++++++++++++++++++</br>')
    37. print(os.getpid())
    38. print("</br>")
    39. print ("<b>environment</b><br>");
    40. print ("<ul>")
    41. for key in os.environ.keys():
    42. print ("<li><span style='color:green'>%30s </span> : %s </li>" % (key,os.environ[key]))
    43. print ("</ul>")

    注:windows环境下python的路径

  3. 从http server传输给script信息如下:

    • POST请求
      image_1bvm2qqtp1a64dgsn74k6r1ed7m.png-207.1kB
    • GET请求
      image_1bvm326av1j82a3r11dflikuqc13.png-203.5kB

    从上图可以验证:

    • POST请求通过stdin传输用户提交的信息,GET请求通过环境变量QUERY_STRING
    • POST请求中,CONTENT_LENGTH表示stdin中传输的字符串长度
    • 说明CGI是一个文本协议,虽然它对人很友好(可读性强),但是对计算机来说就不太友好了,解析起来非常耗时。

      文本协议:一目了然,无须编程接口,无须依赖关系,通常用于比较开放的领域。
      二进制协议: C/S要共享同样的数据结构,有编译依赖关系,或者代码copy, 可用于内部通信协议,或者出于保密原因的通信鞋议。

3. 为什么会有WSGI

3.1 为什么会有web框架

把web server细分成了http server处理网络数据传输,和application serve处理用户请求,会提升整个web服务的IO性能。当application server处理用户请求过程中,也有很多通用的事情需要处理,这些通用的处理可以交给web 框架。譬如:

框架只需要对开发者开放接口,具体实现交给框架。例如:开发者只需要写好URL路由规则,具体路由的实现,交给框架。

3.2 WSGI

web框架有很多,例如:Django,Flask,webpy,bottlepy。为了保证application server能和众多的web框架work together,而不用修改任何代码。就有了WSGI。
image_1bvv3h8n8151f1gtk1fk91k9218rl9.png-37.2kB

  1. # Construct environment dictionary using request data
  2. env = self.get_environ()
  3. # It's time to call our application callable and get
  4. # back a result that will become HTTP response body
  5. result = self.application(env, self.start_response)
  6. # Construct a response and send it back to the client
  7. self.finish_response(result)
  1. def simple_app(environ, start_response):
  2. """Simplest possible application object"""
  3. status = '200 OK'
  4. response_headers = [('Content-type', 'text/plain')]
  5. start_response(status, response_headers)
  6. return ['Hello World']

4. WSGI 接口实现

4.1 server端

  1. # Tested with Python 3.6, Linux & Mac OS X
  2. import socket
  3. from io import StringIO
  4. import sys
  5. class WSGIServer(object):
  6. address_family = socket.AF_INET
  7. socket_type = socket.SOCK_STREAM
  8. request_queue_size = 1
  9. def __init__(self, server_address):
  10. # Create a listening socket
  11. self.listen_socket = listen_socket = socket.socket(
  12. self.address_family,
  13. self.socket_type
  14. )
  15. # Allow to reuse the same address
  16. listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  17. # Bind
  18. listen_socket.bind(server_address)
  19. # Activate
  20. listen_socket.listen(self.request_queue_size)
  21. # Get server host name and port
  22. host, port = self.listen_socket.getsockname()[:2]
  23. self.server_name = socket.getfqdn(host)
  24. self.server_port = port
  25. # Return headers set by Web framework/Web application
  26. self.headers_set = []
  27. def set_app(self, application):
  28. self.application = application
  29. def serve_forever(self):
  30. listen_socket = self.listen_socket
  31. while True:
  32. # New client connection
  33. self.client_connection, client_address = listen_socket.accept()
  34. # Handle one request and close the client connection. Then
  35. # loop over to wait for another client connection
  36. self.handle_one_request()
  37. def handle_one_request(self):
  38. self.request_data = request_data = self.client_connection.recv(1024).decode('utf-8')
  39. # Print formatted request data a la 'curl -v'
  40. print(''.join(
  41. '< {line}\n'.format(line=line)
  42. for line in request_data.splitlines()
  43. ))
  44. self.parse_request(request_data)
  45. # Construct environment dictionary using request data
  46. env = self.get_environ()
  47. # It's time to call our application callable and get
  48. # back a result that will become HTTP response body
  49. result = self.application(env, self.start_response)
  50. # Construct a response and send it back to the client
  51. self.finish_response(result)
  52. def parse_request(self, text):
  53. request_line = text.splitlines()[0]
  54. request_line = request_line.rstrip('\r\n')
  55. # Break down the request line into components
  56. (self.request_method, # GET
  57. self.path, # /hello
  58. self.request_version # HTTP/1.1
  59. ) = request_line.split()
  60. def get_environ(self):
  61. env = {}
  62. # The following code snippet does not follow PEP8 conventions
  63. # but it's formatted the way it is for demonstration purposes
  64. # to emphasize the required variables and their values
  65. #
  66. # Required WSGI variables
  67. env['wsgi.version'] = (1, 0)
  68. env['wsgi.url_scheme'] = 'http'
  69. env['wsgi.input'] = StringIO(self.request_data)
  70. env['wsgi.errors'] = sys.stderr
  71. env['wsgi.multithread'] = False
  72. env['wsgi.multiprocess'] = False
  73. env['wsgi.run_once'] = False
  74. # Required CGI variables
  75. env['REQUEST_METHOD'] = self.request_method # GET
  76. env['PATH_INFO'] = self.path # /hello
  77. env['SERVER_NAME'] = self.server_name # localhost
  78. env['SERVER_PORT'] = str(self.server_port) # 8888
  79. return env
  80. def start_response(self, status, response_headers, exc_info=None):
  81. # Add necessary server headers
  82. server_headers = [
  83. ('Date', 'Tue, 31 Mar 2015 12:54:48 GMT'),
  84. ('Server', 'WSGIServer 0.2'),
  85. ]
  86. self.headers_set = [status, response_headers + server_headers]
  87. # To adhere to WSGI specification the start_response must return
  88. # a 'write' callable. We simplicity's sake we'll ignore that detail
  89. # for now.
  90. # return self.finish_response
  91. def finish_response(self, result):
  92. try:
  93. status, response_headers = self.headers_set
  94. response = 'HTTP/1.1 {status}\r\n'.format(status=status)
  95. for header in response_headers:
  96. response += '{0}: {1}\r\n'.format(*header)
  97. response += '\r\n'
  98. for data in result:
  99. response += data.decode('utf-8')
  100. # Print formatted response data a la 'curl -v'
  101. print(''.join(
  102. '> {line}\n'.format(line=line)
  103. for line in response.splitlines()
  104. ))
  105. self.client_connection.sendall(response.encode('utf-8'))
  106. finally:
  107. self.client_connection.close()
  108. SERVER_ADDRESS = (HOST, PORT) = '', 8888
  109. def make_server(server_address, application):
  110. server = WSGIServer(server_address)
  111. server.set_app(application)
  112. return server
  113. if __name__ == '__main__':
  114. if len(sys.argv) < 2:
  115. sys.exit('Provide a WSGI application object as module:callable')
  116. app_path = sys.argv[1]
  117. module, application = app_path.split(':')
  118. module = __import__(module)
  119. application = getattr(module, application)
  120. httpd = make_server(SERVER_ADDRESS, application)
  121. print('WSGIServer: Serving HTTP on port {port} ...\n'.format(port=PORT))
  122. httpd.serve_forever()

4.2 application端

  1. import sys
  2. sys.path.insert(0, './hello')
  3. from hello import wsgi
  4. app = wsgi.application

注:需要自己django-admin startproject hello 创建一个django project

5. Reference

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注