本篇文章我們主要介紹WSGI協(xié)議,該協(xié)議用來(lái)描述Server與Framework之間的通信接口,我們?nèi)粘J褂玫腜ython WEB框架Django、Flask、web.py等都遵循了該協(xié)議。下面我們就來(lái)詳細(xì)了解一下該協(xié)議的實(shí)現(xiàn)吧!
01 簡(jiǎn)介
WSGI協(xié)議全稱(chēng)Web Server Gateway Interface(Web服務(wù)器網(wǎng)關(guān)接口)。這是Python中定義的一個(gè)網(wǎng)關(guān)協(xié)議,規(guī)定了Web Server如何跟應(yīng)用程序交互。該協(xié)議的主要目的就是在Python中所有Web Server程序或者網(wǎng)關(guān)程序,能夠通過(guò)統(tǒng)一的協(xié)議跟Web框架或者Web應(yīng)用進(jìn)行交互。如果沒(méi)有這個(gè)協(xié)議,那每個(gè)程序都要各自實(shí)現(xiàn)各自的交互接口,而不能夠互相兼容,重復(fù)造輪子。使用統(tǒng)一的協(xié)議,Web應(yīng)用框架只要實(shí)現(xiàn)WSGI協(xié)議規(guī)范就可以與外部進(jìn)行交互,不用針對(duì)某個(gè)Web Server獨(dú)立開(kāi)發(fā)交互邏輯。
02 Web Server實(shí)現(xiàn)
在了解WSGI協(xié)議之前,我們先通過(guò)socket實(shí)現(xiàn)一個(gè)Web服務(wù)器。通過(guò)監(jiān)聽(tīng)本地端口來(lái)接客戶端的web請(qǐng)求,然后進(jìn)行響應(yīng),具體如下:
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. # 設(shè)置web server響應(yīng)內(nèi)容
10. html_content = '<html><h1>My Frist Web Page<h1></html>'
11.
12. # 設(shè)置響應(yīng)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. 請(qǐng)求操作
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("請(qǐng)求內(nèi)容: ", request)
32. conn.send(_resp.encode())
33. conn.close()
34.
35.
36. def web_server():
37. # socket.AF_INET用于服務(wù)器與服務(wù)器之間同行
38. # socket.SOCK_STREAM用于基于TCP流的通信
39. server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
40.
41. # 監(jiān)聽(tīng)本地8888端口
42. server.bind(('127.0.0.1', 8888))
43. server.listen()
44. print("web server已經(jīng)啟動(dò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()
下面我們啟動(dòng)server服務(wù)器,查看頁(yè)面能不能正常訪問(wèn),同時(shí)看看
上面代碼就是最基本的web服務(wù)模型了,通過(guò)socket與HTTP協(xié)議提供Web服務(wù),但上面的web服務(wù)是單線程的,只有前一個(gè)請(qǐng)求處理結(jié)束才處理第二個(gè)請(qǐng)求,我們?cè)撛煲幌律厦娴拇a,通過(guò)python threading模塊實(shí)現(xiàn)多線程的web服務(wù)器,具體操作如下:
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. # 設(shè)置web server響應(yīng)內(nèi)容
13. html_content = '<html><h1>這是線程({})的頁(yè)面 <h1></html>'
14.
15. # 設(shè)置響應(yīng)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. 請(qǐng)求操作
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("請(qǐng)求內(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用于服務(wù)器與服務(wù)器之間同行
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)聽(tīng)本地8888端口
50. server.bind(('127.0.0.1', 8888))
51. server.listen()
52. print("web server已經(jīng)啟動(dò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. # 通過(guò)threading實(shí)現(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()
我們?cè)僭L問(wèn)該服務(wù),其返回如下:
通過(guò)上述改造我們就實(shí)現(xiàn)了多線程的web服務(wù)器,了解了web服務(wù)的基本實(shí)現(xiàn),下面我們就來(lái)看看WSGI的具體實(shí)現(xiàn)。
03 WSGI Application實(shí)現(xiàn)
在了解了基本的web服務(wù)的實(shí)現(xiàn),我們看WSGI協(xié)議,WSGI協(xié)議分為兩部分,一部分是web server或者網(wǎng)關(guān)就是上面web server代碼一樣,它監(jiān)聽(tīng)在某個(gè)端口上接受外部的請(qǐng)求,另外一部分就是web應(yīng)用,web server將接受到的請(qǐng)求數(shù)據(jù)通過(guò)WSGI協(xié)議規(guī)定的方式把數(shù)據(jù)傳遞給web應(yīng)用,web應(yīng)用處理完數(shù)據(jù)后設(shè)置對(duì)應(yīng)的狀態(tài)碼與header然后返回,web server拿到返回?cái)?shù)據(jù)之后再進(jìn)行HTTP協(xié)議封裝然后返回給客戶端,下面我們看看WSGI協(xié)議通過(guò)代碼的具體實(shí)現(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ā)送響應(yīng)頭
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. # 沒(méi)有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)在我們運(yùn)行編寫(xiě)的WSGI應(yīng)用,具體如下:
通過(guò)執(zhí)行該應(yīng)用直接返回了狀態(tài)信息、Header及body內(nèi)容。上述代碼就是Application在WSGI協(xié)議的實(shí)現(xiàn)。我們要實(shí)現(xiàn)Application只需要能夠接收一個(gè)環(huán)境變量以及一個(gè)回調(diào)函數(shù)即可,如上面代碼的“result = application(environ, response)”,在處理完請(qǐng)求通過(guò)回調(diào)函數(shù)respose來(lái)設(shè)置響應(yīng)的狀態(tài)和header,最后再返回body。在完成Application之后可以通過(guò)一些Web Server來(lái)調(diào)用,如Gunicorn Web server,限于篇幅限制就不詳細(xì)講解了,剛興趣的朋友可以安裝Gunicorn然后進(jìn)行調(diào)用。
04 總結(jié)
至此我們WSGI協(xié)議就講完了,如有什么問(wèn)題歡迎在文章后面進(jìn)行留言,最后如果喜歡本篇文章不要忘了點(diǎn)贊、關(guān)注與轉(zhuǎn)發(fā)哦!