本篇文章我们主要介绍WSGI协议,该协议用来描述Server与Framework之间的通信接口,我们日常使用的Python WEB框架Django、Flask、web.py等都遵循了该协议。下面我们就来详细了解一下该协议的实现吧!
WSGI协议全称Web Server Gateway Interface(Web服务器网关接口)。这是Python中定义的一个网关协议,规定了Web Server如何跟应用程序交互。该协议的主要目的就是在Python中所有Web Server程序或者网关程序,能够通过统一的协议跟Web框架或者Web应用进行交互。如果没有这个协议,那每个程序都要各自实现各自的交互接口,而不能够互相兼容,重复造轮子。使用统一的协议,Web应用框架只要实现WSGI协议规范就可以与外部进行交互,不用针对某个Web Server独立开发交互逻辑。
在了解WSGI协议之前,我们先通过socket实现一个Web服务器。通过监听本地端口来接客户端的web请求,然后进行响应,具体如下:
1. #!/usr/bin/env/ python
2. # -*- coding:utf-8 -*-
3.
4. import socket
5.
6. END_TAG_F = b'nn'
7. END_TAG_S = b'nrn'
8.
9. # 设置web server响应内容
10. html_content = '<html><h1>My Frist Web Page<h1></html>'
11.
12. # 设置响应headers
13. resp_args = ['HTTP/1.0 200 OK', 'Date: Sun, 22 nov 2020 19:00:00 GMT',
14. 'Content-Type: text/html;charset=utf-8',
15. 'Content-Length: {}rn'.format(len(html_content)), html_content]
16.
17. _resp = "rn".join(resp_args)
18.
19.
20. def connet_operate(conn, addr):
21. """
22. 请求操作
23. :param conn:
24. :param addr:
25. :return:
26. """
27. request = b''
28. while END_TAG_F not in request and END_TAG_S not in request:
29. request += conn.recv(1024)
30.
31. print("请求内容: ", request)
32. conn.send(_resp.encode())
33. conn.close()
34.
35.
36. def web_server():
37. # socket.AF_INET用于服务器与服务器之间同行
38. # socket.SOCK_STREAM用于基于TCP流的通信
39. server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
40.
41. # 监听本地8888端口
42. server.bind(('127.0.0.1', 8888))
43. server.listen()
44. print("web server已经启动")
45.
46. try:
47. while True:
48. conn, address = server.accept()
49. connet_operate(conn, address)
50. except:
51. server.close()
52.
53.
54. if __name__ == "__main__":
55. web_server()
下面我们启动server服务器,查看页面能不能正常访问,同时看看
上面代码就是最基本的web服务模型了,通过socket与HTTP协议提供Web服务,但上面的web服务是单线程的,只有前一个请求处理结束才处理第二个请求,我们该造一下上面的代码,通过python threading模块实现多线程的web服务器,具体操作如下:
1. #!/usr/bin/env/ python
2. # -*- coding:utf-8 -*-
3.
4. import traceback
5. import socket
6. import errno
7. import threading
8.
9. END_TAG_F = b'nn'
10. END_TAG_S = b'nrn'
11.
12. # 设置web server响应内容
13. html_content = '<html><h1>这是线程({})的页面 <h1></html>'
14.
15. # 设置响应headers
16. resp_args = ['HTTP/1.0 200 OK', 'Date: Sun, 22 nov 2020 19:00:00 GMT',
17. 'Content-Type: text/html;charset=utf-8',
18. 'Content-Length: {}rn']
19.
20.
21. def connet_operate(conn, addr):
22. """
23. 请求操作
24. :param conn:
25. :param addr:
26. :return:
27. """
28. request = b''
29. while END_TAG_F not in request and END_TAG_S not in request:
30. request += conn.recv(1024)
31.
32. print("请求内容: ", request)
33. c = threading.current_thread()
34. _ = html_content.format(c.name)
35. resp_args.Append(_)
36. content_length = len(_.encode())
37. _resp = "rn".join(resp_args)
38.
39. _resp = _resp.format(content_length)
40. conn.send(_resp.encode())
41. conn.close()
42.
43.
44. def web_server():
45. # socket.AF_INET用于服务器与服务器之间同行
46. # socket.SOCK_STREAM用于基于TCP流的通信
47. server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
48. server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
49. # 监听本地8888端口
50. server.bind(('127.0.0.1', 8888))
51. server.listen()
52. print("web server已经启动")
53.
54. try:
55. n = 0
56. while True:
57. try:
58. conn, address = server.accept()
59. except socket.error as e:
60. if e.args[0] != errno.EAGAIN:
61. raise Exception(e)
62. continue
63.
64. n += 1
65. # 通过threading实现web server多线程
66. t = threading.Thread(target=connet_operate, args=(conn, address), name='thread{}'.format(n))
67. t.start()
68. except Exception as e:
69. print(traceback.format_exc(e))
70. server.close()
71.
72. if __name__ == "__main__":
73. web_server()
我们再访问该服务,其返回如下:
通过上述改造我们就实现了多线程的web服务器,了解了web服务的基本实现,下面我们就来看看WSGI的具体实现。
在了解了基本的web服务的实现,我们看WSGI协议,WSGI协议分为两部分,一部分是web server或者网关就是上面web server代码一样,它监听在某个端口上接受外部的请求,另外一部分就是web应用,web server将接受到的请求数据通过WSGI协议规定的方式把数据传递给web应用,web应用处理完数据后设置对应的状态码与header然后返回,web server拿到返回数据之后再进行HTTP协议封装然后返回给客户端,下面我们看看WSGI协议通过代码的具体实现
1. #!/usr/bin/env/ python
2. # -*- coding:utf-8 -*-
3.
4. import os
5. import sys
6.
7.
8. def _app(environ, response):
9. status = "200 OK"
10. resp_hearders = [('Content-Type', 'text/html')]
11. response(status, resp_hearders)
12. return [b'<h1>simple wsgi app</h1>n']
13.
14. def _to_bytes(content):
15. return content.encode()
16.
17. def run_with_cgi(application):
18. environ = dict(os.environ.items())
19. environ['wsgi.input'] = sys.stdin.buffer
20. environ['wsgi.errors'] = sys.stderr
21. environ['wsgi.version'] = (1, 0)
22. environ['wsgi.multithread'] = False
23. environ['wsgi.multiprocess'] = True
24. environ['wsgi.run_once'] = True
25.
26. if environ.get('HTTPS', 'off') in ('on', '1'):
27. environ['wsgi.url_scheme'] = 'https'
28. else:
29. environ['wsgi.url_scheme'] = 'http'
30.
31. headers_set = []
32. headers_sent = []
33.
34. def write(data):
35. out = sys.stdout.buffer
36.
37. if not headers_set:
38. raise ValueError("write before response()")
39.
40. elif not headers_sent:
41. # 输出数据前, 先发送响应头
42. status, response_headers = headers_sent[:] = headers_set
43. out.write(_to_bytes('Status: {}rn'.format(status)))
44. for header in response_headers:
45. out.write(_to_bytes('{}: {}rn'.format(header, header)))
46. out.write(_to_bytes('rn'))
47.
48. out.write(data)
49. out.flush()
50.
51. def response(status, response_headers, error_info=None):
52. if error_info:
53. try:
54. if headers_sent:
55. # 已经发送header就抛出异常
56. raise (error_info[0], error_info[1], error_info[2])
57.
58. finally:
59. error_info = None
60.
61. elif headers_set:
62. raise ValueError("Headers already set")
63.
64. headers_set[:] = [status, response_headers]
65. return write
66.
67. result = application(environ, response)
68.
69. try:
70. for data in result:
71. # 没有body数据则不发送header
72. if data:
73. write(data)
74. if not headers_sent:
75. write('')
76.
77. finally:
78. if hasattr(result, 'close'):
79. result.clost()
80.
81. if __name__ == "__main__":
82. run_with_cgi(_app)
现在我们运行编写的WSGI应用,具体如下:
通过执行该应用直接返回了状态信息、Header及body内容。上述代码就是Application在WSGI协议的实现。我们要实现Application只需要能够接收一个环境变量以及一个回调函数即可,如上面代码的“result = application(environ, response)”,在处理完请求通过回调函数respose来设置响应的状态和header,最后再返回body。在完成Application之后可以通过一些Web Server来调用,如Gunicorn Web server,限于篇幅限制就不详细讲解了,刚兴趣的朋友可以安装Gunicorn然后进行调用。
至此我们WSGI协议就讲完了,如有什么问题欢迎在文章后面进行留言,最后如果喜欢本篇文章不要忘了点赞、关注与转发哦!