Давайте поговорим о поверхностном значении WSGI, аббревиатуры от Web Server Gateway Interface, то есть интерфейса Web Server Gateway.
Для тех, кто раньше не знал значения WSGI, после прочтения приведенного выше объяснения, думаю, я до сих пор не знаю, поэтому давайте объединим фактическое описание сцены и дадим всем сначала общее понимание. Наконец, мы реализуем его самостоятельно, чтобы углубить наше понимание WSGI.
Теперь мы используем Python для написания веб-приложений, мы можем использовать более популярные фреймворки Flask и Django, или мы можем написать их напрямую в соответствии с нашими собственными идеями. Существует также много дополнительного серверного программного обеспечения, такого как Apache, Nginx, IIS и т. д., кроме того, существует также много нишевого программного обеспечения. Но теперь вопрос в том, как мне его развернуть? До спецификации WSGI один сервер планировал приложения Python таким образом, а другой сервер использовал этот способ. В этом случае написанное приложение можно было развернуть, только выбрав ограниченный сервер или серверы. Не общий эффект.
Примечание. Приведенный ниже код основан на Python 3.6.
Если есть такой сервер
wsgi/server.py
# coding=utf-8
import socket
listener = socket.socket()
listener.setsockopt(socket.SOL_SOCKET,
socket.SO_REUSEADDR, 1)
listener.bind(('0.0.0.0', 8080))
listener.listen(1)
print('Serving HTTP on 0.0.0.0 port 8080 ...')
while True:
client_connection, client_address = \
listener.accept()
print(f'Server received connection'
f' from {client_address}')
request = client_connection.recv(1024)
print(f'request we received: {request}')
response = b"""
HTTP/1.1 200 OK
Hello, World!
"""
client_connection.sendall(response)
client_connection.close()
Реализация относительно проста: прослушивать порт 8080, если есть запрос на печать на терминале, и возвращать ответ Hello, World!
Запустите сервер в терминале
➜ wsgi python server.py
Serving HTTP on 0.0.0.0 port 8080 ...
Откройте другой терминал и запросите
➜ ~ curl 127.0.0.1:8080
HTTP/1.1 200 OK
Hello, World!
Указывает, что сервер работает нормально.
Также есть веб-приложение
wsgi/app.py
# coding=utf-8
def simple_app():
return b'Hello, World!\r\n'
Теперь, чтобы развернуть (то есть позволить всему запуститься), простой и грубый способ — напрямую вызвать соответствующий метод в приложении на сервере. так
wsgi/server2.py
# coding=utf-8
import socket
listener = socket.socket()
listener.setsockopt(socket.SOL_SOCKET,
socket.SO_REUSEADDR, 1)
listener.bind(('0.0.0.0', 8080))
listener.listen(1)
print('Serving HTTP on 0.0.0.0 port 8080 ...')
while True:
client_connection, client_address = \
listener.accept()
print(f'Server received connection'
f' from {client_address}')
request = client_connection.recv(1024)
print(f'request we received: {request}')
from app import simple_app
response = 'HTTP/1.1 200 OK\r\n\r\n'
response = response.encode('utf-8')
response += simple_app()
client_connection.sendall(response)
client_connection.close()
запустить скрипт
Примечание. Поскольку порт тот же, закройте последний скрипт перед его выполнением, иначе будет сообщено об ошибке из-за конфликта портов.
➜ wsgi python server2.py
Serving HTTP on 0.0.0.0 port 8080 ...
Затем попросите увидеть эффект
➜ ~ curl 127.0.0.1:8080
Hello, World!
Ну, это нормально. Однако указанный выше сервер и приложение работают как единое целое, поэтому мне нужно изменить сервер или приложение. Потому что нет абсолютно никакой спецификации того, как сервер и приложение взаимодействуют, например, как сервер должен передавать информацию о запросе в приложение и как сообщить серверу, чтобы он начал возвращать ответ после обработки приложения, если все разные , сервер должен настроить приложение, а также приложение. Чтобы настроить сервер, было бы слишком много проблем для запуска приложения.
Таким образом, появление WSGI призвано решить вышеуказанные проблемы.Он определяет, как сервер сообщает информацию о запросе приложению, и как приложение отправляет статус выполнения обратно на сервер.При этом сервер и приложение работают согласно к стандарту, пока это достигается Стандарт, сервер и приложение могут быть произвольно согласованы, и гибкость значительно повышается.
Что стандартизирует WSGI, следующий рисунок может быть очень интуитивным.
Во-первых, приложение должно быть вызываемым объектом, либо функцией, либо реализацией.__call__()
объект метода.
Каждый раз при получении запроса сервер будет вызывать приложение через application_callable(environ, start_response).
Когда приложение готово вернуть данные после обработки, оно сначала вызывает функцию start_response(status, headers, exec_info), переданную ему службой, и, наконец, возвращает итерируемый объект в качестве данных. (Партнеры, которые не разбираются в итерируемых объектах, могут прочитать мою предыдущую статью «Понимание итераторов, итерируемых объектов и генераторов Python»).
Среди них environ должен быть словарем, включая соответствующую информацию о запросе, такую как метод запроса, путь запроса и т. д., start_response — это функция, которую необходимо вызвать после обработки приложения, чтобы указать службе установить информация заголовка ответа или обработка ошибок и т. д. Подождите.
статус должен быть999 Message here
Такие строки200 OK
,404 Not Found
И т. д., заголовки — это список кортежей, таких как (header_name, header_value), а последний exec_info — необязательный параметр, который обычно используется при сбое приложения.
Зная общую концепцию WSGI, давайте реализуем ее.
Во-первых, это приложение
wsgi/wsgi_app.py
# coding=utf-8
def simple_app(environ, start_response):
status = '200 OK'
response_headers = [('Content-type', 'text/plain')]
start_response(status, response_headers)
return [f'Request {environ["REQUEST_METHOD"]}'
f' {environ["PATH_INFO"]} has been'
f' processed\r\n'.encode('utf-8')]
Это определяет функцию (вызываемый объект), которая может использовать среду, связанную с запросом, переданным ей сервером, здесь с использованием информации REQUEST_METHOD и PATH_INFO. Start_response вызывается перед возвратом, чтобы сервер мог установить некоторую информацию заголовка.
затем сервер
wsgi/wsgi_server.py
# coding=utf-8
import socket
listener = socket.socket()
listener.setsockopt(socket.SOL_SOCKET,
socket.SO_REUSEADDR, 1)
listener.bind(('0.0.0.0', 8080))
listener.listen(1)
print('Serving HTTP on 0.0.0.0 port 8080 ...')
while True:
client_connection, client_address = \
listener.accept()
print(f'Server received connection'
f' from {client_address}')
request = client_connection.recv(1024)
print(f'request we received: {request}')
headers_set = None
def start_response(status, headers):
global headers_set
headers_set = [status, headers]
method, path, _ = request.split(b' ', 2)
environ = {'REQUEST_METHOD': method.decode('utf-8'),
'PATH_INFO': path.decode('utf-8')}
from wsgi_app import simple_app
app_result = simple_app(environ, start_response)
response_status, response_headers = headers_set
response = f'HTTP/1.1 {response_status}\r\n'
for header in response_headers:
response += f'{header[0]}: {header[1]}\r\n'
response += '\r\n'
response = response.encode('utf-8')
for data in app_result:
response += data
client_connection.sendall(response)
client_connection.close()
Код, связанный с мониторингом сервера, не сильно изменился, в основном потому, что обработка запроса несколько отличается.
Сначала определяется функция start_response(status, headers), и она не будет вызываться сама по себе.
Затем вызовите приложение, передайте ему текущую информацию о запросе environ и указанную выше функцию start_response, позвольте ему решить, какую информацию о запросе использовать, и вызовите start_response, чтобы установить информацию заголовка до завершения обработки и готовности вернуть данные.
Ну а после запуска сервера (то есть выполнения серверного кода, аналогичного предыдущему, так что не буду здесь вдаваться в подробности), а затем запросом посмотреть результат
➜ ~ curl 127.0.0.1:8080/user/1
Request GET /user/1 has been processed
Ну процедура нормальная.
Чтобы проиллюстрировать вышеизложенное, связь кода относительно велика.Если серверу необходимо изменить приложение, код сервера должен быть изменен, что, очевидно, проблематично. Теперь принцип почти ясен, давайте оптимизируем код
wsgi/wsgi_server_oop.py
# coding=utf-8
import socket
import sys
class WSGIServer:
def __init__(self):
self.listener = socket.socket()
self.listener.setsockopt(socket.SOL_SOCKET,
socket.SO_REUSEADDR, 1)
self.listener.bind(('0.0.0.0', 8080))
self.listener.listen(1)
print('Serving HTTP on 0.0.0.0'
' port 8080 ...')
self.app = None
self.headers_set = None
def set_app(self, application):
self.app = application
def start_response(self, status, headers):
self.headers_set = [status, headers]
def serve_forever(self):
while True:
listener = self.listener
client_connection, client_address = \
listener.accept()
print(f'Server received connection'
f' from {client_address}')
request = client_connection.recv(1024)
print(f'request we received: {request}')
method, path, _ = request.split(b' ', 2)
# 为简洁的说明问题,这里填充的内容有些随意
# 如果有需要,可以自行完善
environ = {
'wsgi.version': (1, 0),
'wsgi.url_scheme': 'http',
'wsgi.input': request,
'wsgi.errors': sys.stderr,
'wsgi.multithread': False,
'wsgi.multiprocess': False,
'wsgi.run_once': False,
'REQUEST_METHOD': method.decode('utf-8'),
'PATH_INFO': path.decode('utf-8'),
'SERVER_NAME': '127.0.0.1',
'SERVER_PORT': '8080',
}
app_result = self.app(environ, self.start_response)
response_status, response_headers = self.headers_set
response = f'HTTP/1.1 {response_status}\r\n'
for header in response_headers:
response += f'{header[0]}: {header[1]}\r\n'
response += '\r\n'
response = response.encode('utf-8')
for data in app_result:
response += data
client_connection.sendall(response)
client_connection.close()
if __name__ == '__main__':
if len(sys.argv) < 2:
sys.exit('Argv Error')
app_path = sys.argv[1]
module, app = app_path.split(':')
module = __import__(module)
app = getattr(module, app)
server = WSGIServer()
server.set_app(app)
server.serve_forever()
Основной принцип не изменился, но исходный код был изменен объектно-ориентированным образом, а среда добавила некоторую необходимую информацию об окружении.
Можно использовать предыдущие приложения
➜ wsgi python wsgi_server_oop.py wsgi_app:simple_app
Serving HTTP on 0.0.0.0 port 8080 ...
просить
➜ ~ curl 127.0.0.1:8080/user/1
Request GET /user/1 has been processed
Получил тот же результат, что и раньше.
Будет ли работать приложение Flask? Давайте попробуем, сначала создайте новый
wsgi/flask_app.py
# coding=utf-8
from flask import Flask
from flask import Response
flask_app = Flask(__name__)
@flask_app.route('/user/<int:user_id>',
methods=['GET'])
def hello_world(user_id):
return Response(
f'Get /user/{user_id} has been'
f' processed in flask app\r\n',
mimetype='text/plain'
)
перезагрузить сервер
➜ wsgi python wsgi_server_oop.py flask_app:flask_app
Serving HTTP on 0.0.0.0 port 8080 ...
просить
➜ ~ curl 127.0.0.1:8080/user/1
Get /user/1 has been processed in flask app
Поскольку Flask также придерживается спецификации WSGI, проблем с выполнением нет.
На данный момент реализована грубая спецификация WSGI, код хоть и не элегантен, но некоторые ключевые вещи все же отражены. Но ведь он игнорирует многие вещи, такие как обработка ошибок и т. д. Недостаточно использовать его в производственной среде, партнеры, которые хотят знать более всесторонне, могут перейти на PEP 3333.
Популярные в настоящее время фреймворки веб-приложений, такие как Django, Bottle и т. д., серверы Apahce, Nginx, Gunicorn и т. д. также поддерживают эту спецификацию. Таким образом, в принципе не проблема смешивать и сочетать фреймворки и приложения по желанию.
Ссылаться на
- Woohoo.Python.org/Dev/PEPs/PE…
- Войдите в Shilan Foods IV AK.com/Teacher положил все части…
- Woohoo.topthey.com/Python/friends после прослушивания...
- gist.GitHub.com/Thomas построил здание…
Подпишитесь на официальный аккаунт «Little Backend», чтобы читать больше интересных статей.