本篇文章我們主要介紹WSGI協(xié)議,該協(xié)議用來描述Server與Framework之間的通信接口,我們?nèi)粘J褂玫腜ython WEB框架Django、Flask、web.py等都遵循了該協(xié)議。下面我們就來詳細了解一下該協(xié)議的實現(xiàn)吧!
01 簡介
WSGI協(xié)議全稱Web Server Gateway Interface(Web服務器網(wǎng)關(guān)接口)。這是Python中定義的一個網(wǎng)關(guān)協(xié)議,規(guī)定了Web Server如何跟應用程序交互。該協(xié)議的主要目的就是在Python中所有Web Server程序或者網(wǎng)關(guān)程序,能夠通過統(tǒng)一的協(xié)議跟Web框架或者Web應用進行交互。如果沒有這個協(xié)議,那每個程序都要各自實現(xiàn)各自的交互接口,而不能夠互相兼容,重復造輪子。使用統(tǒng)一的協(xié)議,Web應用框架只要實現(xiàn)WSGI協(xié)議規(guī)范就可以與外部進行交互,不用針對某個Web Server獨立開發(fā)交互邏輯。
02 Web Server實現(xiàn)
在了解WSGI協(xié)議之前,我們先通過socket實現(xiàn)一個Web服務器。通過監(jiān)聽本地端口來接客戶端的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響應內(nèi)容
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("請求內(nèi)容: ", 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. # 監(jiān)聽本地8888端口
42. server.bind(('127.0.0.1', 8888))
43. server.listen()
44. print("web server已經(jīng)啟動")
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協(xié)議提供Web服務,但上面的web服務是單線程的,只有前一個請求處理結(jié)束才處理第二個請求,我們該造一下上面的代碼,通過python threading模塊實現(xiàn)多線程的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響應內(nèi)容
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("請求內(nèi)容: ", 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. # 監(jiān)聽本地8888端口
50. server.bind(('127.0.0.1', 8888))
51. server.listen()
52. print("web server已經(jīng)啟動")
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實現(xiàn)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()
我們再訪問該服務,其返回如下:

通過上述改造我們就實現(xiàn)了多線程的web服務器,了解了web服務的基本實現(xiàn),下面我們就來看看WSGI的具體實現(xiàn)。
03 WSGI Application實現(xiàn)
在了解了基本的web服務的實現(xiàn),我們看WSGI協(xié)議,WSGI協(xié)議分為兩部分,一部分是web server或者網(wǎng)關(guān)就是上面web server代碼一樣,它監(jiān)聽在某個端口上接受外部的請求,另外一部分就是web應用,web server將接受到的請求數(shù)據(jù)通過WSGI協(xié)議規(guī)定的方式把數(shù)據(jù)傳遞給web應用,web應用處理完數(shù)據(jù)后設置對應的狀態(tài)碼與header然后返回,web server拿到返回數(shù)據(jù)之后再進行HTTP協(xié)議封裝然后返回給客戶端,下面我們看看WSGI協(xié)議通過代碼的具體實現(xiàn)
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. # 輸出數(shù)據(jù)前, 先發(fā)送響應頭
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. # 已經(jīng)發(fā)送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數(shù)據(jù)則不發(fā)送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)
現(xiàn)在我們運行編寫的WSGI應用,具體如下:

通過執(zhí)行該應用直接返回了狀態(tài)信息、Header及body內(nèi)容。上述代碼就是Application在WSGI協(xié)議的實現(xiàn)。我們要實現(xiàn)Application只需要能夠接收一個環(huán)境變量以及一個回調(diào)函數(shù)即可,如上面代碼的“result = application(environ, response)”,在處理完請求通過回調(diào)函數(shù)respose來設置響應的狀態(tài)和header,最后再返回body。在完成Application之后可以通過一些Web Server來調(diào)用,如Gunicorn Web server,限于篇幅限制就不詳細講解了,剛興趣的朋友可以安裝Gunicorn然后進行調(diào)用。
04 總結(jié)
至此我們WSGI協(xié)議就講完了,如有什么問題歡迎在文章后面進行留言,最后如果喜歡本篇文章不要忘了點贊、關(guān)注與轉(zhuǎn)發(fā)哦!