本篇文章我們主要介紹WSGI協議,該協議用來描述Server與Framework之間的通信接口,我們日常使用的Python WEB框架Django、Flask、web.py等都遵循了該協議。下面我們就來詳細了解一下該協議的實現吧!
01 簡介
WSGI協議全稱Web Server Gateway Interface(Web服務器網關接口)。這是Python中定義的一個網關協議,規定了Web Server如何跟應用程序交互。該協議的主要目的就是在Python中所有Web Server程序或者網關程序,能夠通過統一的協議跟Web框架或者Web應用進行交互。如果沒有這個協議,那每個程序都要各自實現各自的交互接口,而不能夠互相兼容,重復造輪子。使用統一的協議,Web應用框架只要實現WSGI協議規范就可以與外部進行交互,不用針對某個Web Server獨立開發交互邏輯。
02 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的具體實現。
03 WSGI Application實現
在了解了基本的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然后進行調用。
04 總結
至此我們WSGI協議就講完了,如有什么問題歡迎在文章后面進行留言,最后如果喜歡本篇文章不要忘了點贊、關注與轉發哦!