PEP 3333 – Python Web Server Gateway Interface v1.0.1
- Autor:
- Phillip J. Eby <pje at telecommunity.com>
- Discussions-To:
- Web-SIG Liste
- Status:
- Final
- Typ:
- Informational
- Erstellt:
- 26-Sep-2010
- Post-History:
- 26-Sep-2010, 04-Oct-2010
- Ersetzt:
- 333
Vorwort für Leser von PEP 333
Dies ist eine aktualisierte Version von PEP 333, leicht modifiziert, um die Benutzerfreundlichkeit unter Python 3 zu verbessern und mehrere langjährige De-facto-Änderungen am WSGI-Protokoll zu integrieren. (Die Codebeispiele wurden ebenfalls nach Python 3 portiert.)
Obwohl dies aus prozeduralen Gründen [6] ein eigenständiger PEP sein muss, wurden keine Änderungen vorgenommen, die zuvor konforme Server oder Anwendungen unter Python 2.x ungültig machen. Wenn Ihre 2.x-Anwendung oder Ihr 2.x-Server mit PEP 333 konform ist, ist er auch mit diesem PEP konform.
Unter Python 3 müssen Ihre Anwendung oder Ihr Server jedoch auch die Regeln befolgen, die in den unten stehenden Abschnitten mit den Titeln Eine Anmerkung zu Zeichentypen und Unicode-Probleme aufgeführt sind.
Für detaillierte Unterschiede Zeile für Zeile zwischen diesem Dokument und PEP 333 können Sie die SVN-Revisionshistorie [7] ab Revision 84854 einsehen.
Zusammenfassung
Dieses Dokument spezifiziert eine vorgeschlagene Standard-Schnittstelle zwischen Webservern und Python-Webanwendungen oder -Frameworks, um die Portabilität von Webanwendungen über eine Vielzahl von Webservern hinweg zu fördern.
Ursprüngliche Begründung und Ziele (aus PEP 333)
Python verfügt derzeit über eine große Vielfalt an Webanwendungs-Frameworks, wie z. B. Zope, Quixote, Webware, SkunkWeb, PSO und Twisted Web – um nur einige zu nennen [1]. Diese große Auswahl kann für neue Python-Benutzer ein Problem darstellen, da ihre Wahl des Web-Frameworks im Allgemeinen ihre Wahl der nutzbaren Webserver einschränken wird und umgekehrt.
Im Gegensatz dazu ist es Java trotz einer ebenso großen Anzahl von Webanwendungs-Frameworks dank der „Servlet“-API von Java möglich, Anwendungen, die mit einem beliebigen Java-Webanwendungs-Framework geschrieben wurden, auf jedem Webserver auszuführen, der die Servlet-API unterstützt.
Die Verfügbarkeit und weit verbreitete Nutzung einer solchen API in Webservern für Python – sei es, dass diese Server in Python geschrieben sind (z. B. Medusa), Python einbetten (z. B. mod_python) oder Python über ein Gateway-Protokoll aufrufen (z. B. CGI, FastCGI usw.) – würde die Wahl des Frameworks von der Wahl des Webservers trennen und Benutzern die Freiheit geben, eine Kombination zu wählen, die ihnen gefällt, während Framework- und Serverentwickler sich auf ihren bevorzugten Spezialbereich konzentrieren können.
Dieser PEP schlägt daher eine einfache und universelle Schnittstelle zwischen Webservern und Webanwendungen oder Frameworks vor: die Python Web Server Gateway Interface (WSGI).
Die bloße Existenz einer WSGI-Spezifikation trägt jedoch nichts zur Bewältigung des bestehenden Zustands von Servern und Frameworks für Python-Webanwendungen bei. Server- und Framework-Autoren und -Wartende müssen WSGI tatsächlich implementieren, damit es eine Wirkung gibt.
Da jedoch keine bestehenden Server oder Frameworks WSGI unterstützen, gibt es für einen Autor, der WSGI-Unterstützung implementiert, wenig unmittelbaren Nutzen. Daher **muss** WSGI einfach zu implementieren sein, damit die anfängliche Investition eines Autors in die Schnittstelle einigermaßen gering sein kann.
Daher ist die Einfachheit der Implementierung sowohl auf der Server- als auch auf der Framework-Seite der Schnittstelle absolut entscheidend für den Nutzen der WSGI-Schnittstelle und ist daher das Hauptkriterium für alle Designentscheidungen.
Beachten Sie jedoch, dass die Einfachheit der Implementierung für einen Framework-Autor nicht dasselbe ist wie die Benutzerfreundlichkeit für einen Webanwendungsautor. WSGI präsentiert dem Framework-Autor eine absolut „nüchterne“ Schnittstelle, da Extras und Schnickschnack wie Antwortobjekte und Cookie-Handling nur die Handhabung dieser Probleme durch bestehende Frameworks behindern würden. Auch hier ist das Ziel von WSGI die Erleichterung der einfachen Verbindung bestehender Server und Anwendungen oder Frameworks, nicht die Schaffung eines neuen Web-Frameworks.
Beachten Sie außerdem, dass dieses Ziel ausschließt, dass WSGI etwas erfordert, das nicht bereits in bereitgestellten Versionen von Python verfügbar ist. Daher werden keine neuen Standardbibliotheksmodule von dieser Spezifikation vorgeschlagen oder benötigt, und nichts in WSGI erfordert eine Python-Version größer als 2.2.2. (Es wäre jedoch eine gute Idee, zukünftige Python-Versionen mit Unterstützung für diese Schnittstelle in den von der Standardbibliothek bereitgestellten Webservern einzuschließen.)
Neben der einfachen Implementierung für bestehende und zukünftige Frameworks und Server sollte es auch einfach sein, Request-Präprozessoren, Response-Postprozessoren und andere WSGI-basierte „Middleware“-Komponenten zu erstellen, die für ihren enthaltenden Server wie eine Anwendung aussehen und gleichzeitig als Server für ihre enthaltenen Anwendungen fungieren.
Wenn Middleware sowohl einfach als auch robust sein kann und WSGI in Servern und Frameworks weit verbreitet ist, ermöglicht dies die Möglichkeit einer völlig neuen Art von Python-Webanwendungs-Framework: einem, das aus lose gekoppelten WSGI-Middleware-Komponenten besteht. Tatsächlich könnten bestehende Framework-Autoren sogar wählen, ihre Framework-Dienste auf diese Weise zu refaktorisieren und eher wie Bibliotheken zu werden, die mit WSGI verwendet werden, und weniger wie monolithische Frameworks. Dies würde es Anwendungsentwicklern ermöglichen, „Best-of-Breed“-Komponenten für spezifische Funktionalitäten auszuwählen, anstatt sich auf alle Vor- und Nachteile eines einzelnen Frameworks festlegen zu müssen.
Natürlich ist dieser Tag zum Zeitpunkt des Schreibens zweifellos noch weit entfernt. Bis dahin ist es ein ausreichendes kurzfristiges Ziel für WSGI, die Verwendung jedes Frameworks mit jedem Server zu ermöglichen.
Schließlich sei erwähnt, dass die aktuelle Version von WSGI keinen bestimmten Mechanismus für die „Bereitstellung“ einer Anwendung für die Verwendung mit einem Webserver oder einem Server-Gateway vorschreibt. Derzeit ist dies notwendigerweise implementierungsabhängig durch den Server oder das Gateway. Nachdem eine ausreichende Anzahl von Servern und Frameworks WSGI implementiert hat, um Praxiserfahrung mit unterschiedlichen Bereitstellungsanforderungen zu sammeln, kann es sinnvoll sein, einen weiteren PEP zu erstellen, der einen Bereitstellungsstandard für WSGI-Server und Anwendung-Frameworks beschreibt.
Übersicht der Spezifikation
Die WSGI-Schnittstelle hat zwei Seiten: die „Server“- oder „Gateway“-Seite und die „Anwendungs“- oder „Framework“-Seite. Die Serverseite ruft ein aufrufbares Objekt auf, das von der Anwendungsseite bereitgestellt wird. Die Einzelheiten, wie dieses Objekt bereitgestellt wird, liegen beim Server oder Gateway. Es wird davon ausgegangen, dass einige Server oder Gateways vom Bereitsteller einer Anwendung verlangen, dass dieser ein kurzes Skript schreibt, um eine Instanz des Servers oder Gateways zu erstellen und ihm das Anwendungsobjekt zu übergeben. Andere Server und Gateways können Konfigurationsdateien oder andere Mechanismen verwenden, um anzugeben, woher ein Anwendungsobjekt importiert oder anderweitig bezogen werden soll.
Zusätzlich zu „reinen“ Servern/Gateways und Anwendungen/Frameworks ist es auch möglich, „Middleware“-Komponenten zu erstellen, die beide Seiten dieser Spezifikation implementieren. Solche Komponenten fungieren als Anwendung für ihren enthaltenden Server und als Server für eine enthaltene Anwendung und können verwendet werden, um erweiterte APIs, Inhalts-Transformationen, Navigation und andere nützliche Funktionen bereitzustellen.
Im gesamten Umfang dieser Spezifikation werden wir den Begriff „ein aufrufbares Objekt“ verwenden, um „eine Funktion, Methode, Klasse oder eine Instanz mit einer __call__-Methode“ zu bedeuten. Es liegt beim Server, Gateway oder der Anwendung, die das aufrufbare Objekt implementiert, die geeignete Implementierungstechnik für ihre Bedürfnisse zu wählen. Umgekehrt darf ein Server, ein Gateway oder eine Anwendung, die ein aufrufbares Objekt aufruft, **keine** Abhängigkeit davon haben, welche Art von aufrufbarem Objekt ihr bereitgestellt wurde. Aufrufbare Objekte dürfen nur aufgerufen, nicht introspektiert werden.
Eine Anmerkung zu Zeichentypen
Im Allgemeinen befasst sich HTTP mit Bytes, was bedeutet, dass sich diese Spezifikation hauptsächlich mit der Handhabung von Bytes befasst.
Der Inhalt dieser Bytes hat jedoch oft eine Art textuelle Interpretation, und in Python sind Zeichenketten die bequemste Art, Text zu handhaben.
Aber in vielen Python-Versionen und Implementierungen sind Zeichenketten Unicode und keine Bytes. Dies erfordert ein sorgfältiges Gleichgewicht zwischen einer nutzbaren API und korrekten Übersetzungen zwischen Bytes und Text im Kontext von HTTP … insbesondere zur Unterstützung der Portierung von Code zwischen Python-Implementierungen mit unterschiedlichen str-Typen.
WSGI definiert daher zwei Arten von „Zeichenketten“
- „Native“ Zeichenketten (die immer mit dem Typ
strimplementiert werden) für Request-/Response-Header und Metadaten - „Bytestrings“ (die in Python 3 mit dem Typ
bytesund ansonsten mitstrimplementiert werden) für die Körper von Anfragen und Antworten (z. B. POST/PUT-Eingabedaten und HTML-Seiten-Ausgaben).
Lassen Sie sich jedoch nicht verwirren: Selbst wenn der str-Typ von Python tatsächlich Unicode „unter der Haube“ ist, muss der *Inhalt* von nativen Zeichenketten über die Latin-1-Kodierung in Bytes übersetzbar sein! (Siehe den Abschnitt über Unicode-Probleme später in diesem Dokument für weitere Details.)
Kurz gesagt: Wo Sie das Wort „Zeichenkette“ in diesem Dokument sehen, bezieht es sich auf eine „native“ Zeichenkette, d. h. ein Objekt vom Typ str, unabhängig davon, ob es intern als Bytes oder Unicode implementiert ist. Wo Sie auf „Bytestring“ verweisen, sollte dies als „ein Objekt vom Typ bytes unter Python 3 oder Typ str unter Python 2“ gelesen werden.
Und so gibt es, obwohl HTTP in gewissem Sinne „wirklich nur Bytes“ sind, viele API-Komfortfunktionen, die durch die Verwendung des Standard-Python-Typs str erzielt werden.
Die Anwendungs-/Framework-Seite
Das Anwendungsobjekt ist einfach ein aufrufbares Objekt, das zwei Argumente akzeptiert. Der Begriff „Objekt“ sollte nicht missverstanden werden als die Anforderung einer tatsächlichen Objektinstanz: Eine Funktion, Methode, Klasse oder Instanz mit einer __call__-Methode sind alle als Anwendungsobjekt geeignet. Anwendungsobjekte müssen mehrfach aufrufbar sein, da praktisch alle Server/Gateways (außer CGI) solche wiederholten Anfragen stellen werden.
(Hinweis: Obwohl wir uns auf ein „Anwendungs“-Objekt beziehen, sollte dies nicht so interpretiert werden, dass Anwendungsentwickler WSGI als Web-Programmier-API verwenden werden! Es wird davon ausgegangen, dass Anwendungsentwickler weiterhin bestehende, übergeordnete Framework-Dienste zur Entwicklung ihrer Anwendungen nutzen werden. WSGI ist ein Werkzeug für Framework- und Serverentwickler und nicht dazu gedacht, Anwendungsentwickler direkt zu unterstützen.)
Hier sind zwei Beispielanwendungsobjekte; eines ist eine Funktion und das andere eine Klasse
HELLO_WORLD = b"Hello world!\n"
def simple_app(environ, start_response):
"""Simplest possible application object"""
status = '200 OK'
response_headers = [('Content-type', 'text/plain')]
start_response(status, response_headers)
return [HELLO_WORLD]
class AppClass:
"""Produce the same output, but using a class
(Note: 'AppClass' is the "application" here, so calling it
returns an instance of 'AppClass', which is then the iterable
return value of the "application callable" as required by
the spec.
If we wanted to use *instances* of 'AppClass' as application
objects instead, we would have to implement a '__call__'
method, which would be invoked to execute the application,
and we would need to create an instance for use by the
server or gateway.
"""
def __init__(self, environ, start_response):
self.environ = environ
self.start = start_response
def __iter__(self):
status = '200 OK'
response_headers = [('Content-type', 'text/plain')]
self.start(status, response_headers)
yield HELLO_WORLD
Die Server-/Gateway-Seite
Der Server oder das Gateway ruft das Anwendungs-Callable einmal pro empfangener Anfrage von einem HTTP-Client auf, die an die Anwendung gerichtet ist. Zur Veranschaulichung ist hier ein einfaches CGI-Gateway, implementiert als Funktion, die ein Anwendungsobjekt entgegennimmt. Beachten Sie, dass dieses einfache Beispiel eine eingeschränkte Fehlerbehandlung aufweist, da eine nicht abgefangene Ausnahme standardmäßig nach sys.stderr ausgegeben und vom Webserver protokolliert wird.
import os, sys
enc, esc = sys.getfilesystemencoding(), 'surrogateescape'
def unicode_to_wsgi(u):
# Convert an environment variable to a WSGI "bytes-as-unicode" string
return u.encode(enc, esc).decode('iso-8859-1')
def wsgi_to_bytes(s):
return s.encode('iso-8859-1')
def run_with_cgi(application):
environ = {k: unicode_to_wsgi(v) for k,v in os.environ.items()}
environ['wsgi.input'] = sys.stdin.buffer
environ['wsgi.errors'] = sys.stderr
environ['wsgi.version'] = (1, 0)
environ['wsgi.multithread'] = False
environ['wsgi.multiprocess'] = True
environ['wsgi.run_once'] = True
if environ.get('HTTPS', 'off') in ('on', '1'):
environ['wsgi.url_scheme'] = 'https'
else:
environ['wsgi.url_scheme'] = 'http'
headers_set = []
headers_sent = []
def write(data):
out = sys.stdout.buffer
if not headers_set:
raise AssertionError("write() before start_response()")
elif not headers_sent:
# Before the first output, send the stored headers
status, response_headers = headers_sent[:] = headers_set
out.write(wsgi_to_bytes('Status: %s\r\n' % status))
for header in response_headers:
out.write(wsgi_to_bytes('%s: %s\r\n' % header))
out.write(wsgi_to_bytes('\r\n'))
out.write(data)
out.flush()
def start_response(status, response_headers, exc_info=None):
if exc_info:
try:
if headers_sent:
# Re-raise original exception if headers sent
raise exc_info[1].with_traceback(exc_info[2])
finally:
exc_info = None # avoid dangling circular ref
elif headers_set:
raise AssertionError("Headers already set!")
headers_set[:] = [status, response_headers]
# Note: error checking on the headers should happen here,
# *after* the headers are set. That way, if an error
# occurs, start_response can only be re-called with
# exc_info set.
return write
result = application(environ, start_response)
try:
for data in result:
if data: # don't send headers until body appears
write(data)
if not headers_sent:
write(b'') # send headers now if body was empty
finally:
if hasattr(result, 'close'):
result.close()
Middleware: Komponenten, die beide Seiten bespielen
Beachten Sie, dass ein einzelnes Objekt die Rolle eines Servers gegenüber einigen Anwendungen spielen kann und gleichzeitig als Anwendung gegenüber einigen Servern fungieren kann. Solche „Middleware“-Komponenten können Funktionen wie die folgenden ausführen:
- Weiterleitung einer Anfrage an verschiedene Anwendungsobjekte basierend auf der Ziel-URL, nachdem die
environentsprechend umgeschrieben wurde. - Mehrere Anwendungen oder Frameworks nebeneinander im selben Prozess ausführen lassen
- Lastverteilung und Remote-Verarbeitung durch Weiterleitung von Anfragen und Antworten über ein Netzwerk
- Inhaltsnachbearbeitung durchführen, z. B. Anwenden von XSL-Stylesheets
Die Anwesenheit von Middleware im Allgemeinen ist für die „Server/Gateway“- und die „Anwendungs/Framework“-Seiten der Schnittstelle transparent und erfordert keine spezielle Unterstützung. Ein Benutzer, der Middleware in eine Anwendung integrieren möchte, stellt die Middleware-Komponente einfach dem Server zur Verfügung, als wäre sie eine Anwendung, und konfiguriert die Middleware-Komponente so, dass sie die Anwendung aufruft, als wäre die Middleware-Komponente ein Server. Natürlich kann die „Anwendung“, die die Middleware umschließt, tatsächlich eine weitere Middleware-Komponente sein, die eine weitere Anwendung umschließt, und so weiter, wodurch ein sogenannter „Middleware-Stack“ entsteht.
Größtenteils muss Middleware den Beschränkungen und Anforderungen sowohl der Server- als auch der Anwendungsseite von WSGI entsprechen. In einigen Fällen sind die Anforderungen an Middleware jedoch strenger als für einen „reinen“ Server oder eine Anwendung, und diese Punkte werden in der Spezifikation vermerkt.
Hier ist ein (augenscheinlich ironisches) Beispiel für eine Middleware-Komponente, die text/plain-Antworten in Pig Latin umwandelt, unter Verwendung von Joe Strouts piglatin.py. (Hinweis: Eine „echte“ Middleware-Komponente würde wahrscheinlich eine robustere Methode zur Überprüfung des Inhaltstyps verwenden und auch eine Inhaltskodierung prüfen. Außerdem ignoriert dieses einfache Beispiel die Möglichkeit, dass ein Wort über eine Blockgrenze geteilt werden könnte.)
from piglatin import piglatin
class LatinIter:
"""Transform iterated output to piglatin, if it's okay to do so
Note that the "okayness" can change until the application yields
its first non-empty bytestring, so 'transform_ok' has to be a mutable
truth value.
"""
def __init__(self, result, transform_ok):
if hasattr(result, 'close'):
self.close = result.close
self._next = iter(result).__next__
self.transform_ok = transform_ok
def __iter__(self):
return self
def __next__(self):
data = self._next()
if self.transform_ok:
return piglatin(data) # call must be byte-safe on Py3
else:
return data
class Latinator:
# by default, don't transform output
transform = False
def __init__(self, application):
self.application = application
def __call__(self, environ, start_response):
transform_ok = []
def start_latin(status, response_headers, exc_info=None):
# Reset ok flag, in case this is a repeat call
del transform_ok[:]
for name, value in response_headers:
if name.lower() == 'content-type' and value == 'text/plain':
transform_ok.append(True)
# Strip content-length if present, else it'll be wrong
response_headers = [(name, value)
for name, value in response_headers
if name.lower() != 'content-length'
]
break
write = start_response(status, response_headers, exc_info)
if transform_ok:
def write_latin(data):
write(piglatin(data)) # call must be byte-safe on Py3
return write_latin
else:
return write
return LatinIter(self.application(environ, start_latin), transform_ok)
# Run foo_app under a Latinator's control, using the example CGI gateway
from foo_app import foo_app
run_with_cgi(Latinator(foo_app))
Details der Spezifikation
Das Anwendungsobjekt muss zwei Positionsargumente akzeptieren. Zu Illustrationszwecken haben wir sie environ und start_response genannt, aber sie müssen diese Namen nicht haben. Ein Server oder Gateway **muss** das Anwendungsobjekt mit Positions- (nicht Schlüsselwort-) Argumenten aufrufen. (Z. B. durch Aufruf von result = application(environ, start_response) wie oben gezeigt.)
Der Parameter environ ist ein Wörterbuchobjekt, das CGI-ähnliche Umgebungsvariablen enthält. Dieses Objekt **muss** ein eingebautes Python-Wörterbuch sein (nicht eine Unterklasse, UserDict oder eine andere Wörterbuch-Emulation), und die Anwendung darf das Wörterbuch beliebig ändern. Das Wörterbuch muss auch bestimmte WSGI-erforderliche Variablen enthalten (die in einem späteren Abschnitt beschrieben werden) und kann auch serverspezifische Erweiterungsvariablen enthalten, die gemäß einer unten beschriebenen Konvention benannt sind.
Der Parameter start_response ist ein aufrufbares Objekt, das zwei erforderliche Positionsargumente und ein optionales Argument akzeptiert. Zu Illustrationszwecken haben wir diese Argumente status, response_headers und exc_info genannt, aber sie müssen diese Namen nicht haben, und die Anwendung **muss** das start_response-Objekt mit Positionsargumenten aufrufen (z. B. start_response(status, response_headers)).
Das Argument status ist ein Status-String der Form "999 Nachricht hier", und response_headers ist eine Liste von Tupeln (header_name, header_value), die den HTTP-Response-Header beschreiben. Das optionale Argument exc_info wird unten in den Abschnitten Der start_response()-Callable und Fehlerbehandlung beschrieben. Es wird nur verwendet, wenn die Anwendung einen Fehler abgefangen hat und versucht, eine Fehlermeldung an den Browser zu senden.
Der start_response Callable muss einen write(body_data) Callable zurückgeben, der einen Positions-Parameter entgegennimmt: eine Bytestring, die als Teil des HTTP-Response-Bodys geschrieben werden soll. (Hinweis: Der write() Callable wird nur zur Unterstützung bestimmter imperativer Ausgabeschnittstellen bestehender Frameworks bereitgestellt; er sollte von neuen Anwendungen oder Frameworks vermieden werden, wenn dies möglich ist. Siehe den Abschnitt Pufferung und Streaming für weitere Details.)
Wenn das Anwendungsobjekt vom Server aufgerufen wird, muss es ein Iterable zurückgeben, das null oder mehr Bytestrings liefert. Dies kann auf verschiedene Weise erreicht werden, z. B. durch Rückgabe einer Liste von Bytestrings, oder dadurch, dass die Anwendung eine Generatorfunktion ist, die Bytestrings liefert, oder dadurch, dass die Anwendung eine Klasse ist, deren Instanzen iterierbar sind. Unabhängig davon, wie es erreicht wird, muss das Anwendungsobjekt immer ein Iterable zurückgeben, das null oder mehr Bytestrings liefert.
Der Server oder das Gateway muss die gelieferten Bytestrings ungepuffert an den Client übertragen und die Übertragung jedes Bytestrings abschließen, bevor er einen weiteren anfordert. (Mit anderen Worten, Anwendungen **sollten** ihre eigene Pufferung durchführen. Siehe den Abschnitt Pufferung und Streaming unten für weitere Informationen zur Handhabung der Anwendungsausgabe.)
Der Server oder das Gateway sollte die gelieferten Bytestrings als binäre Byte-Sequenzen behandeln: Insbesondere sollte er sicherstellen, dass Zeilenumbrüche nicht verändert werden. Die Anwendung ist dafür verantwortlich, sicherzustellen, dass die zu schreibenden Bytestrings in einem für den Client geeigneten Format vorliegen. (Der Server oder das Gateway **darf** HTTP-Übertragungskodierungen anwenden oder andere Transformationen durchführen, um HTTP-Funktionen wie Byte-Range-Übertragung zu implementieren. Siehe Andere HTTP-Funktionen unten für weitere Details.)
Wenn ein Aufruf von len(iterable) erfolgreich ist, muss der Server sich auf die Richtigkeit des Ergebnisses verlassen können. Das heißt, wenn das von der Anwendung zurückgegebene Iterable eine funktionierende __len__()-Methode bereitstellt, **muss** sie ein korrektes Ergebnis liefern. (Siehe den Abschnitt Behandlung des Content-Length Headers für Informationen, wie dies normalerweise verwendet würde.)
Wenn das von der Anwendung zurückgegebene Iterable eine close()-Methode hat, **muss** der Server oder das Gateway diese Methode nach Abschluss der aktuellen Anfrage aufrufen, unabhängig davon, ob die Anfrage normal abgeschlossen wurde oder aufgrund eines Anwendungsfehlers während der Iteration oder einer frühen Trennung des Browsers vorzeitig beendet wurde. (Die Anforderung der close()-Methode dient der Ressourcenfreigabe durch die Anwendung. Dieses Protokoll soll die Generatorunterstützung von PEP 342 und andere gängige Iterables mit close()-Methoden ergänzen.)
Anwendungen, die einen Generator oder einen anderen benutzerdefinierten Iterator zurückgeben, **sollten nicht** davon ausgehen, dass der gesamte Iterator verbraucht wird, da er möglicherweise vom Server vorzeitig geschlossen wird.
(Hinweis: Die Anwendung **muss** den start_response() Callable aufrufen, bevor das Iterable seinen ersten Body-Bytestring liefert, damit der Server die Header vor jeglichem Body-Inhalt senden kann. Dieser Aufruf **kann** jedoch bei der ersten Iteration des Iterables erfolgen, sodass Server **nicht** davon ausgehen dürfen, dass start_response() aufgerufen wurde, bevor sie mit der Iteration über das Iterable beginnen.)
Schließlich **dürfen** Server und Gateways keine anderen Attribute des von der Anwendung zurückgegebenen Iterables direkt verwenden, es sei denn, es handelt sich um eine Instanz eines spezifischen Typs für diesen Server oder Gateway, wie z. B. ein „File Wrapper“, der von wsgi.file_wrapper zurückgegeben wird (siehe Optionale plattformspezifische Dateibehandlung). Im allgemeinen Fall sind nur hier spezifizierte Attribute oder solche, die über z. B. die Iterations-APIs von PEP 234 zugänglich sind, zulässig.
environ-Variablen
Das environ-Wörterbuch muss diese CGI-Umgebungsvariablen enthalten, wie in der Common Gateway Interface-Spezifikation [2] definiert. Die folgenden Variablen **müssen** vorhanden sein, es sei denn, ihr Wert wäre eine leere Zeichenkette, in welchem Fall sie **weggelassen werden dürfen**, außer wie unten anders angegeben.
REQUEST_METHOD- Die HTTP-Anfragemethode, wie z. B.
"GET"oder"POST". Dies kann niemals eine leere Zeichenkette sein und ist daher immer erforderlich. SCRIPT_NAME- Der Anfangsteil des „path“-Segments der Anforderungs-URL, das dem Anwendungsobjekt entspricht, damit die Anwendung seine virtuelle „Position“ kennt. Dies **kann** eine leere Zeichenkette sein, wenn die Anwendung dem „Root“ des Servers entspricht.
PATH_INFO- Der Rest des „path“-Segments der Anforderungs-URL, der die virtuelle „Position“ des Ziels der Anfrage innerhalb der Anwendung bezeichnet. Dies **kann** eine leere Zeichenkette sein, wenn die Anforderungs-URL auf das Anwendungs-Root zielt und keinen nachgestellten Schrägstrich hat.
QUERY_STRING- Der Teil der Anforderungs-URL, der auf das
"?"folgt, falls vorhanden. Kann leer oder abwesend sein. CONTENT_TYPE- Der Inhalt aller
Content-Type-Felder in der HTTP-Anfrage. Kann leer oder abwesend sein. CONTENT_LENGTH- Der Inhalt aller
Content-Length-Felder in der HTTP-Anfrage. Kann leer oder abwesend sein. SERVER_NAME,SERVER_PORT- Wenn
HTTP_HOSTnicht gesetzt ist, können diese Variablen kombiniert werden, um einen Standardwert zu ermitteln. Siehe den Abschnitt URL-Rekonstruktion unten für weitere Details.SERVER_NAMEundSERVER_PORTsind erforderliche Zeichenketten und dürfen niemals leer sein. SERVER_PROTOCOL- Die Version des Protokolls, das der Client zum Senden der Anfrage verwendet hat. Typischerweise ist dies etwas wie
"HTTP/1.0"oder"HTTP/1.1"und kann von der Anwendung verwendet werden, um zu bestimmen, wie HTTP-Anfrage-Header behandelt werden sollen. (Diese Variable sollte wahrscheinlichREQUEST_PROTOCOLgenannt werden, da sie das in der Anfrage verwendete Protokoll bezeichnet und nicht notwendigerweise das Protokoll ist, das in der Antwort des Servers verwendet wird. Aus Kompatibilitätsgründen mit CGI müssen wir jedoch den bestehenden Namen beibehalten.) HTTP_-Variablen- Variablen, die den vom Client bereitgestellten HTTP-Anfrage-Headern entsprechen (d. h. Variablen, deren Namen mit
"HTTP_"beginnen). Das Vorhandensein oder Fehlen dieser Variablen sollte dem Vorhandensein oder Fehlen des entsprechenden HTTP-Headers in der Anfrage entsprechen.
Ein Server oder Gateway **sollte** versuchen, so viele andere CGI-Variablen wie anwendbar bereitzustellen. Zusätzlich, wenn SSL verwendet wird, **sollte** der Server oder Gateway auch so viele der Apache SSL-Umgebungsvariablen [5] wie anwendbar bereitstellen, wie z. B. HTTPS=on und SSL_PROTOCOL. Beachten Sie jedoch, dass eine Anwendung, die andere CGI-Variablen als die oben aufgeführten verwendet, zwangsläufig nicht portierbar zu Webservern ist, die die relevanten Erweiterungen nicht unterstützen. (Zum Beispiel können Webserver, die keine Dateien veröffentlichen, kein aussagekräftiges DOCUMENT_ROOT oder PATH_TRANSLATED bereitstellen.)
Ein WSGI-konformer Server oder Gateway **sollte** dokumentieren, welche Variablen er bereitstellt, zusammen mit ihren Definitionen, wo dies angemessen ist. Anwendungen **sollten** auf die Anwesenheit jeder von ihnen benötigten Variablen prüfen und einen Fallback-Plan haben, falls eine solche Variable fehlt.
Hinweis: Fehlende Variablen (wie z. B. REMOTE_USER, wenn keine Authentifizierung stattgefunden hat) sollten aus dem environ-Wörterbuch weggelassen werden. Beachten Sie auch, dass CGI-definierte Variablen native Zeichenketten sein müssen, wenn sie überhaupt vorhanden sind. Es verstößt gegen diese Spezifikation, wenn der Wert *irgendeiner* CGI-Variablen einen anderen Typ als str hat.
Zusätzlich zu den CGI-definierten Variablen **kann** das environ-Wörterbuch auch beliebige „Umgebungsvariablen“ des Betriebssystems enthalten, und **muss** die folgenden WSGI-definierten Variablen enthalten:
| Variable. | Wert |
|---|---|
wsgi.version |
Das Tupel (1, 0), das die WSGI-Version 1.0 repräsentiert. |
wsgi.url_scheme |
Eine Zeichenkette, die den „Scheme“-Teil der URL repräsentiert, unter der die Anwendung aufgerufen wird. Normalerweise hat dieser den Wert "http" oder "https", je nach Situation. |
wsgi.input |
Ein Eingabestrom (file-ähnliches Objekt), aus dem die Bytes des HTTP-Request-Bodys gelesen werden können. (Der Server oder das Gateway kann auf Abruf Leseoperationen durchführen, wie von der Anwendung angefordert, oder er kann den Request-Body des Clients vorab lesen und ihn im Speicher oder auf der Festplatte puffern, oder jede andere Technik zur Bereitstellung eines solchen Eingabestroms nach Belieben verwenden.) |
wsgi.errors |
Ein Ausgabestrom (file-ähnliches Objekt), in den Fehlerausgaben geschrieben werden können, um Programm- oder andere Fehler an einem standardisierten und möglicherweise zentralisierten Ort aufzuzeichnen. Dies sollte ein „Textmodus“-Strom sein; d. h. Anwendungen sollten "\n" als Zeilenumbruch verwenden und davon ausgehen, dass dieser vom Server/Gateway in den korrekten Zeilenumbruch konvertiert wird.(Auf Plattformen, auf denen der Für viele Server wird |
wsgi.multithread |
Dieser Wert sollte wahr sein, wenn das Anwendungsobjekt gleichzeitig von einem anderen Thread im selben Prozess aufgerufen werden kann, und sollte andernfalls falsch sein. |
wsgi.multiprocess |
Dieser Wert sollte wahr sein, wenn ein gleichwertiges Anwendungsobjekt gleichzeitig von einem anderen Prozess aufgerufen werden kann, und sollte andernfalls falsch sein. |
wsgi.run_once |
Dieser Wert sollte wahr sein, wenn der Server oder das Gateway erwartet (aber nicht garantiert!), dass die Anwendung während der Lebensdauer ihres enthaltenden Prozesses nur einmal aufgerufen wird. Normalerweise ist dies nur bei einem Gateway der Fall, das auf CGI (oder etwas Ähnlichem) basiert. |
Schließlich **kann** das environ-Wörterbuch auch serverspezifische Variablen enthalten. Diese Variablen sollten nur Kleinbuchstaben, Zahlen, Punkte und Unterstriche enthalten und mit einem Namen präfixiert sein, der für den definierenden Server oder das Gateway eindeutig ist. Zum Beispiel könnte mod_python Variablen mit Namen wie mod_python.some_variable definieren.
Eingabe- und Fehlerströme
Die vom Server bereitgestellten Eingabe- und Fehlerströme müssen die folgenden Methoden unterstützen
| Methode | Stream | Anmerkungen |
|---|---|---|
read(size) |
input |
1 |
readline() |
input |
1, 2 |
readlines(hint) |
input |
1, 3 |
__iter__() |
input |
|
flush() |
errors |
4 |
write(str) |
errors |
|
writelines(seq) |
errors |
Die Semantik jeder Methode ist wie in der Python Library Reference dokumentiert, außer für diese Hinweise, die in der obigen Tabelle aufgeführt sind:
- Der Server ist nicht verpflichtet, über den vom Client angegebenen
Content-Lengthhinaus zu lesen, und **sollte** eine End-of-File-Bedingung simulieren, wenn die Anwendung versucht, über diesen Punkt hinaus zu lesen. Die Anwendung **sollte nicht** versuchen, mehr Daten zu lesen, als in der VariablenCONTENT_LENGTHangegeben ist.Ein Server **sollte** erlauben,
read()ohne Argument aufzurufen und den Rest des Eingabestroms des Clients zurückzugeben.Ein Server **sollte** leere Bytestrings von jedem Versuch zurückgeben, aus einem leeren oder erschöpften Eingabestrom zu lesen.
- Server **sollten** das optionale Argument „size“ für
readline()unterstützen, aber wie in WSGI 1.0 dürfen sie die Unterstützung dafür weglassen.(In WSGI 1.0 wurde das size-Argument nicht unterstützt, mit der Begründung, dass seine Implementierung komplex sein könnte und in der Praxis nicht oft verwendet wurde … aber dann begann das
cgi-Modul es zu verwenden, und so mussten praktische Server es trotzdem unterstützen!) - Beachten Sie, dass das Argument
hintfürreadlines()sowohl für den Aufrufer als auch für den Implementierer optional ist. Die Anwendung muss es nicht angeben, und der Server oder das Gateway kann es ignorieren. - Da der
errors-Stream nicht zurückgespult werden kann, sind Server und Gateways frei, Schreiboperationen sofort weiterzuleiten, ohne zu puffern. In diesem Fall kann dieflush()-Methode ein No-Op sein. Portierbare Anwendungen können jedoch nicht davon ausgehen, dass die Ausgabe unbuffered ist oder dassflush()ein No-Op ist. Sie müssenflush()aufrufen, wenn sie sicherstellen müssen, dass die Ausgabe tatsächlich geschrieben wurde. (Zum Beispiel, um die Vermischung von Daten aus mehreren Prozessen zu minimieren, die in dasselbe Fehlerprotokoll schreiben.)
Die in der obigen Tabelle aufgeführten Methoden **müssen** von allen dieser Spezifikation entsprechenden Servern unterstützt werden. Anwendungen, die dieser Spezifikation entsprechen, **dürfen keine** anderen Methoden oder Attribute der input- oder errors-Objekte verwenden. Insbesondere **dürfen** Anwendungen nicht versuchen, diese Ströme zu schließen, selbst wenn sie close()-Methoden besitzen.
Der start_response() Callable
Der zweite Parameter, der an das Anwendungsobjekt übergeben wird, ist ein aufrufbares Objekt der Form start_response(status, response_headers, exc_info=None). (Wie bei allen WSGI-Aufrufobjekten müssen die Argumente positionsweise, nicht per Schlüsselwort übergeben werden.) Der start_response Callable wird verwendet, um die HTTP-Antwort zu beginnen, und er muss einen write(body_data) Callable zurückgeben (siehe den Abschnitt Pufferung und Streaming unten).
Das Argument status ist ein HTTP-„Status“-String wie "200 OK" oder "404 Not Found". Das heißt, es ist eine Zeichenkette, die aus einem Status-Code und einer Grund-Phrase besteht, in dieser Reihenfolge und durch ein einzelnes Leerzeichen getrennt, ohne umschließende Leerzeichen oder andere Zeichen. (Siehe RFC 2616, Abschnitt 6.1.1 für weitere Informationen.) Die Zeichenkette **darf keine** Steuerzeichen enthalten und **darf nicht** mit einem Wagenrücklauf, Zeilenvorschub oder einer Kombination davon abgeschlossen werden.
Das Argument response_headers ist eine Liste von Tupeln (header_name, header_value). Es muss eine Python-Liste sein; d. h. type(response_headers) is ListType, und der Server **darf** ihren Inhalt beliebig ändern. Jeder header_name muss ein gültiger HTTP-Headerfeldname sein (wie definiert in RFC 2616, Abschnitt 4.2), ohne einen nachgestellten Doppelpunkt oder andere Satzzeichen.
Jeder header_value **darf keine** *irgendwelchen* Steuerzeichen enthalten, einschließlich Wagenrückläufen oder Zeilenumbrüchen, weder eingebettet noch am Ende. (Diese Anforderungen dienen der Minimierung der Komplexität von Parsing-Vorgängen, die von Servern, Gateways und Zwischenantwortprozessoren durchgeführt werden müssen, die Antwortheader inspizieren oder ändern müssen.)
Im Allgemeinen ist der Server oder das Gateway dafür verantwortlich, sicherzustellen, dass die korrekten Header an den Client gesendet werden: Wenn die Anwendung einen von HTTP (oder anderen relevanten, geltenden Spezifikationen) geforderten Header weglässt, **muss** der Server oder das Gateway ihn hinzufügen. Zum Beispiel würden die HTTP-Header Date: und Server: normalerweise vom Server oder Gateway bereitgestellt werden.
(Eine Erinnerung für Server/Gateway-Autoren: HTTP-Header-Namen sind nicht case-sensitiv, also stellen Sie sicher, dass Sie das bei der Überprüfung anwendungsseitig bereitgestellter Header berücksichtigen!)
Anwendungen und Middleware dürfen keine HTTP/1.1 „Hop-by-Hop“-Funktionen oder Header, keine gleichwertigen Funktionen in HTTP/1.0 oder keine Header verwenden, die die Persistenz der Client-Verbindung zum Webserver beeinträchtigen würden. Diese Funktionen sind das ausschließliche Territorium des tatsächlichen Webservers, und ein Server oder Gateway **sollte** es als fatalen Fehler betrachten, wenn eine Anwendung versucht, diese zu senden, und einen Fehler auslösen, wenn sie an start_response() übergeben werden. (Weitere Details zu „Hop-by-Hop“-Funktionen und -Headern finden Sie im Abschnitt Andere HTTP-Funktionen unten.)
Server **sollten** zum Zeitpunkt des Aufrufs von start_response auf Fehler in den Headern prüfen, damit ein Fehler ausgelöst werden kann, während die Anwendung noch läuft.
Die start_response aufrufbare Funktion **darf** die Antwort-Header jedoch nicht tatsächlich übertragen. Stattdessen muss sie diese für den Server oder das Gateway speichern, um sie **erst** nach der ersten Iteration des Rückgabewerts der Anwendung, der eine nicht leere Bytestring liefert, oder bei der ersten Ausführung des write() aufrufbaren Objekts durch die Anwendung zu übertragen. Mit anderen Worten, die Antwort-Header dürfen nicht gesendet werden, bis tatsächliche Body-Daten verfügbar sind oder bis das von der Anwendung zurückgegebene Iterable erschöpft ist. (Die einzige mögliche Ausnahme von dieser Regel ist, wenn die Antwort-Header explizit eine Content-Length von Null enthalten.)
Diese Verzögerung bei der Übertragung der Antwort-Header soll sicherstellen, dass gepufferte und asynchrone Anwendungen ihre ursprünglich beabsichtigte Ausgabe bis zum letzten möglichen Moment durch eine Fehlerausgabe ersetzen können. Beispielsweise muss die Anwendung möglicherweise den Antwortstatus von „200 OK“ auf „500 Internal Error“ ändern, wenn während der Generierung des Bodys in einem Anwendungspuffer ein Fehler auftritt.
Das Argument exc_info muss, wenn es übergeben wird, ein Python sys.exc_info() Tupel sein. Dieses Argument sollte nur dann von der Anwendung übergeben werden, wenn start_response von einem Fehlerbehandlungsroutine aufgerufen wird. Wenn exc_info übergeben wird und noch keine HTTP-Header ausgegeben wurden, sollte start_response die aktuell gespeicherten HTTP-Antwort-Header durch die neu übergebenen ersetzen, wodurch die Anwendung ihre Meinung über die Ausgabe ändern kann, wenn ein Fehler aufgetreten ist.
Wenn jedoch exc_info übergeben wird und die HTTP-Header bereits gesendet wurden, **muss** start_response einen Fehler auslösen und **sollte** diesen unter Verwendung des exc_info Tupels erneut auslösen. Das heißt
raise exc_info[1].with_traceback(exc_info[2])
Dies wird die vom Anwendung abgefangene Ausnahme erneut auslösen und sollte grundsätzlich die Anwendung abbrechen. (Es ist für die Anwendung nicht sicher, zu versuchen, eine Fehlerausgabe an den Browser zu senden, sobald die HTTP-Header bereits gesendet wurden.) Die Anwendung **darf** keine Ausnahmen abfangen, die von start_response ausgelöst werden, wenn sie start_response mit exc_info aufgerufen hat. Stattdessen sollte sie zulassen, dass solche Ausnahmen an den Server oder das Gateway zurückpropagieren. Siehe Fehlerbehandlung unten für weitere Details.
Die Anwendung **darf** start_response mehr als einmal aufrufen, wenn und nur wenn das Argument exc_info übergeben wird. Genauer gesagt ist es ein fataler Fehler, start_response ohne das Argument exc_info aufzurufen, wenn start_response bereits innerhalb des aktuellen Aufrufs der Anwendung aufgerufen wurde. Dies schließt den Fall ein, dass der erste Aufruf von start_response einen Fehler ausgelöst hat. (Siehe das Beispiel eines CGI-Gateways oben zur Veranschaulichung der korrekten Logik.)
Hinweis: Server, Gateways oder Middleware, die start_response implementieren, **sollten** sicherstellen, dass keine Referenz auf den Parameter exc_info über die Dauer der Funktionsausführung hinaus gehalten wird, um eine zirkuläre Referenz durch den Traceback und die beteiligten Frames zu vermeiden. Der einfachste Weg, dies zu tun, ist etwa so:
def start_response(status, response_headers, exc_info=None):
if exc_info:
try:
# do stuff w/exc_info here
finally:
exc_info = None # Avoid circular ref.
Das Beispiel eines CGI-Gateways liefert eine weitere Veranschaulichung dieser Technik.
Behandlung des Content-Length Headers
Wenn die Anwendung einen Content-Length-Header angibt, **sollte** der Server nicht mehr Bytes an den Client senden, als der Header erlaubt, und **sollte** die Iteration über die Antwort stoppen, wenn genügend Daten gesendet wurden, oder einen Fehler auslösen, wenn die Anwendung versucht, über diesen Punkt hinaus zu write(). (Wenn die Anwendung natürlich nicht **genügend** Daten liefert, um ihre angegebene Content-Length zu erfüllen, **sollte** der Server die Verbindung schließen und den Fehler protokollieren oder anderweitig melden.)
Wenn die Anwendung keinen Content-Length-Header angibt, kann ein Server oder Gateway einen von mehreren Ansätzen wählen, um damit umzugehen. Der einfachste davon ist, die Client-Verbindung zu schließen, wenn die Antwort abgeschlossen ist.
Unter bestimmten Umständen kann der Server oder das Gateway jedoch entweder einen Content-Length-Header generieren oder zumindest vermeiden, die Client-Verbindung schließen zu müssen. Wenn die Anwendung den write() aufrufbaren nicht aufruft und ein Iterable zurückgibt, dessen len() 1 ist, kann der Server die Content-Length automatisch ermitteln, indem er die Länge der ersten von dem Iterable gelieferten Bytestring nimmt.
Und wenn Server und Client beide HTTP/1.1 „Chunked Encoding“ unterstützen, dann **kann** der Server Chunked Encoding verwenden, um einen Chunk für jeden write()-Aufruf oder jede von dem Iterable gelieferte Bytestring zu senden, wodurch ein Content-Length-Header für jeden Chunk generiert wird. Dies ermöglicht es dem Server, die Client-Verbindung offen zu halten, wenn er dies wünscht. Beachten Sie, dass der Server bei der Ausführung **vollständig** RFC 2616 befolgen muss, andernfalls auf eine der anderen Strategien zum Umgang mit dem Fehlen von Content-Length zurückfallen muss.
(Hinweis: Anwendungen und Middleware **dürfen** keine Art von Transfer-Encoding auf ihre Ausgabe anwenden, wie z. B. Chunking oder Gzipping; als „Hop-by-Hop“-Operationen sind diese Encodings das Territorium des tatsächlichen Webservers/Gateways. Weitere Details finden Sie im Abschnitt Andere HTTP-Funktionen unten.)
Puffern und Streaming
Im Allgemeinen erzielen Anwendungen den besten Durchsatz, indem sie ihre (moderat großen) Ausgaben puffern und sie alle auf einmal senden. Dies ist ein gängiger Ansatz in bestehenden Frameworks wie Zope: Die Ausgabe wird in einem StringIO-Objekt oder einem ähnlichen Objekt gepuffert, dann auf einmal zusammen mit den Antwort-Headern übertragen.
Der entsprechende Ansatz in WSGI besteht darin, dass die Anwendung einfach ein Iterable mit einem einzigen Element (wie z. B. einer Liste) zurückgibt, das den Antwort-Body als einzelne Bytestring enthält. Dies ist der empfohlene Ansatz für die überwiegende Mehrheit der Anwendungsfunktionen, die HTML-Seiten rendern, deren Text leicht in den Speicher passt.
Für große Dateien oder für spezialisierte Anwendungen von HTTP-Streaming (wie z. B. Multipart "Server Push") muss eine Anwendung jedoch möglicherweise die Ausgabe in kleineren Blöcken bereitstellen (z. B. um zu vermeiden, eine große Datei in den Speicher zu laden). Es kommt auch manchmal vor, dass die Erstellung eines Teils einer Antwort zeitaufwändig ist, aber es wäre nützlich, den Teil der Antwort, der davor liegt, vorab zu senden.
In diesen Fällen gibt die Anwendung normalerweise ein Iterator (oft ein Generator-Iterator) zurück, der die Ausgabe blockweise erzeugt. Diese Blöcke können unterbrochen werden, um mit Multipart-Grenzen (für „Server Push“) zusammenzufallen, oder kurz vor zeitaufwändigen Aufgaben (wie dem Lesen eines weiteren Blocks einer Datei auf der Festplatte).
WSGI-Server, Gateways und Middleware **dürfen** die Übertragung eines Blocks nicht verzögern; sie **müssen** entweder den Block vollständig an den Client übertragen oder garantieren, dass sie die Übertragung fortsetzen, auch während die Anwendung ihren nächsten Block erzeugt. Ein Server/Gateway oder eine Middleware kann diese Garantie auf eine von drei Arten bieten:
- Den gesamten Block an das Betriebssystem senden (und anfordern, dass alle Puffer des Betriebssystems geleert werden), bevor die Kontrolle an die Anwendung zurückgegeben wird, ODER
- Einen anderen Thread verwenden, um sicherzustellen, dass die Übertragung des Blocks fortgesetzt wird, während die Anwendung den nächsten Block generiert.
- (Nur Middleware) den gesamten Block an sein übergeordnetes Gateway/Server senden
Durch die Bereitstellung dieser Garantie ermöglicht WSGI Anwendungen, sicherzustellen, dass die Übertragung nicht an einer beliebigen Stelle ihrer Ausgabedaten ins Stocken gerät. Dies ist entscheidend für die ordnungsgemäße Funktion von z. B. Streaming mit „Server Push“ über Multipart, wo Daten zwischen Multipart-Grenzen vollständig an den Client übertragen werden sollten.
Middleware-Behandlung von Blockgrenzen
Um asynchrone Anwendungen und Server besser zu unterstützen, **dürfen** Middleware-Komponenten die Iteration nicht blockieren, während sie auf mehrere Werte aus einem Anwendungs-Iterable wartet. Wenn die Middleware mehr Daten von der Anwendung sammeln muss, bevor sie eine Ausgabe liefern kann, **muss** sie eine leere Bytestring liefern.
Anders ausgedrückt, eine Middleware-Komponente **muss mindestens einen Wert liefern**, jedes Mal, wenn ihre zugrunde liegende Anwendung einen Wert liefert. Wenn die Middleware keinen anderen Wert liefern kann, muss sie eine leere Bytestring liefern.
Diese Anforderung stellt sicher, dass asynchrone Anwendungen und Server zusammenarbeiten können, um die Anzahl der Threads zu reduzieren, die zur gleichzeitigen Ausführung einer bestimmten Anzahl von Anwendungsinstanzen erforderlich sind.
Beachten Sie auch, dass diese Anforderung bedeutet, dass Middleware ein Iterable zurückgeben **muss**, sobald ihre zugrunde liegende Anwendung ein Iterable zurückgibt. Es ist Middleware auch untersagt, den write() aufrufbaren zu verwenden, um Daten zu übertragen, die von einem zugrunde liegenden Anwendung geliefert werden. Middleware darf nur den write() aufrufbaren ihres übergeordneten Servers verwenden, um Daten zu übertragen, die die zugrunde liegende Anwendung mit einem von der Middleware bereitgestellten write() aufrufbaren Objekt gesendet hat.
Der write() Callable
Einige vorhandene APIs von Anwendungsframeworks unterstützen unbuffered Ausgabe anders als WSGI. Insbesondere stellen sie eine "write"-Funktion oder -Methode zur Verfügung, um einen unbuffered Datenblock zu schreiben, oder sie stellen eine buffered "write"-Funktion und einen "flush"-Mechanismus zur Verfügung, um den Puffer zu leeren.
Leider können solche APIs nicht auf der Grundlage des WSGI-Rückgabewerts der Anwendung als „Iterable“ implementiert werden, es sei denn, Threads oder andere spezielle Mechanismen werden verwendet.
Daher enthält WSGI, um diesen Frameworks die fortgesetzte Verwendung einer imperativen API zu ermöglichen, ein spezielles write() aufrufbares Objekt, das von dem start_response aufrufbaren Objekt zurückgegeben wird.
Neue WSGI-Anwendungen und Frameworks **sollten** das write() aufrufbare Objekt nicht verwenden, wenn dies vermieden werden kann. Das write() aufrufbare Objekt ist streng genommen ein Hack zur Unterstützung imperativer Streaming-APIs. Im Allgemeinen sollten Anwendungen ihre Ausgabe über ihr zurückgegebenes Iterable erzeugen, da dies es Webservern ermöglicht, andere Aufgaben im selben Python-Thread zu verschachteln, was potenziell einen besseren Durchsatz für den Server als Ganzes bietet.
Das write() aufrufbare Objekt wird von dem start_response() aufrufbaren Objekt zurückgegeben und akzeptiert einen einzigen Parameter: eine Bytestring, die als Teil des HTTP-Antwort-Bodys geschrieben wird und genau so behandelt wird, als ob sie vom Ausgabe-Iterable geliefert worden wäre. Mit anderen Worten, bevor write() zurückkehrt, muss es garantieren, dass die übergebene Bytestring entweder vollständig an den Client gesendet wurde oder dass sie zur Übertragung gepuffert wird, während die Anwendung fortfährt.
Eine Anwendung **muss** ein Iterable-Objekt zurückgeben, auch wenn sie write() verwendet, um ganz oder teilweise ihren Antwort-Body zu erzeugen. Das zurückgegebene Iterable **kann** leer sein (d. h. keine nicht leeren Bytestrings liefern), aber wenn es nicht leere Bytestrings liefert, muss diese Ausgabe vom Server oder Gateway normal behandelt werden (d. h. sie muss sofort gesendet oder in die Warteschlange gestellt werden). Anwendungen **dürfen** write() nicht innerhalb ihres Rückgabe-Iterables aufrufen, und daher werden alle vom Iterable gelieferten Bytestrings nach allen an write() übergebenen Bytestrings an den Client gesendet.
Unicode-Probleme
HTTP unterstützt Unicode nicht direkt, und diese Schnittstelle auch nicht. Alle Kodierungen/Dekodierungen müssen von der Anwendung gehandhabt werden; alle an den Server oder von ihm übergebenen Zeichenfolgen müssen vom Typ str oder bytes sein, niemals unicode. Das Ergebnis der Verwendung eines unicode-Objekts, wo ein Zeichenkettenobjekt erforderlich ist, ist undefiniert.
Beachten Sie auch, dass Zeichenketten, die als Status oder als Antwort-Header an start_response() übergeben werden, **gemäß** RFC 2616 mit Bezug auf die Kodierung sein müssen. Das heißt, sie müssen entweder ISO-8859-1-Zeichen sein oder die RFC 2047 MIME-Kodierung verwenden.
Auf Python-Plattformen, auf denen der Typ str oder StringType tatsächlich Unicode-basiert ist (z. B. Jython, IronPython, Python 3 usw.), müssen alle in dieser Spezifikation als „Zeichenketten“ bezeichneten Zeichen nur Code-Punkte enthalten, die in der ISO-8859-1-Kodierung darstellbar sind (\u0000 bis \u00FF, einschließlich). Es ist ein fataler Fehler für eine Anwendung, Zeichenketten mit anderen Unicode-Zeichen oder Code-Punkten zu übergeben. Ebenso **dürfen** Server und Gateways keine Zeichenketten an eine Anwendung übergeben, die andere Unicode-Zeichen enthalten.
Nochmal, alle in dieser Spezifikation als „Zeichenketten“ bezeichneten Objekte **müssen** vom Typ str oder StringType sein und **dürfen nicht** vom Typ unicode oder UnicodeType sein. Und selbst wenn eine gegebene Plattform mehr als 8 Bit pro Zeichen in str/StringType Objekten zulässt, dürfen nur die unteren 8 Bit für jeden Wert verwendet werden, der in dieser Spezifikation als „Zeichenkette“ bezeichnet wird.
Für Werte, die in dieser Spezifikation als „Bytestrings“ bezeichnet werden (d. h. Werte, die aus wsgi.input gelesen, an write() übergeben oder von der Anwendung geliefert werden), **muss** der Wert vom Typ bytes unter Python 3 und str in früheren Versionen von Python sein.
Fehlerbehandlung
Im Allgemeinen **sollten** Anwendungen versuchen, ihre eigenen internen Fehler abzufangen und eine hilfreiche Nachricht im Browser anzuzeigen. (Es liegt an der Anwendung zu entscheiden, was „hilfreich“ in diesem Kontext bedeutet.)
Um eine solche Nachricht anzuzeigen, darf die Anwendung jedoch noch keine Daten an den Browser gesendet haben, sonst riskiert sie, die Antwort zu beschädigen. WSGI stellt daher einen Mechanismus zur Verfügung, um der Anwendung entweder zu ermöglichen, ihre Fehlermeldung zu senden, oder automatisch abgebrochen zu werden: das Argument exc_info für start_response. Hier ist ein Beispiel für seine Verwendung:
try:
# regular application code here
status = "200 Froody"
response_headers = [("content-type", "text/plain")]
start_response(status, response_headers)
return ["normal body goes here"]
except:
# XXX should trap runtime issues like MemoryError, KeyboardInterrupt
# in a separate handler before this bare 'except:'...
status = "500 Oops"
response_headers = [("content-type", "text/plain")]
start_response(status, response_headers, sys.exc_info())
return ["error body goes here"]
Wenn beim Auftreten einer Ausnahme keine Ausgabe geschrieben wurde, gibt der Aufruf von start_response normal zurück, und die Anwendung gibt einen Fehler-Body zurück, der an den Browser gesendet wird. Wenn jedoch bereits eine Ausgabe an den Browser gesendet wurde, wird start_response die bereitgestellte Ausnahme erneut auslösen. Diese Ausnahme **sollte nicht** von der Anwendung abgefangen werden, und so wird die Anwendung abgebrochen. Der Server oder das Gateway kann dann diese (fatale) Ausnahme abfangen und die Antwort abbrechen.
Server **sollten** jede Ausnahme abfangen und protokollieren, die eine Anwendung oder die Iteration ihres Rückgabewerts abbricht. Wenn bereits eine teilweise Antwort an den Browser gesendet wurde, wenn ein Anwendungsfehler auftritt, **kann** der Server oder das Gateway versuchen, eine Fehlermeldung zur Ausgabe hinzuzufügen, wenn die bereits gesendeten Header einen text/* Inhaltstyp angeben, den der Server sauber modifizieren kann.
Einige Middleware möchten möglicherweise zusätzliche Fehlerbehandlungsdienste anbieten oder Anwendungsfehlermeldungen abfangen und ersetzen. In solchen Fällen kann die Middleware wählen, die an start_response übergebene exc_info **nicht** erneut auszulösen, sondern stattdessen eine Middleware-spezifische Ausnahme auszulösen oder einfach ohne Ausnahme zurückzukehren, nachdem die übergebenen Argumente gespeichert wurden. Dies führt dann dazu, dass die Anwendung ihr Fehler-Body-Iterable zurückgibt (oder write() aufruft), was es der Middleware ermöglicht, die Fehlerausgabe zu erfassen und zu modifizieren. Diese Techniken funktionieren, solange die Anwendungsautoren
- Immer
exc_infobereitstellen, wenn eine Fehlerantwort begonnen wird - Niemals Fehler abfangen, die von
start_responseausgelöst werden, wennexc_infobereitgestellt wird
HTTP 1.1 Expect/Continue
Server und Gateways, die HTTP 1.1 implementieren, müssen transparente Unterstützung für den "Expect/Continue"-Mechanismus von HTTP 1.1 bieten. Dies kann auf verschiedene Arten erfolgen:
- Auf Anfragen mit
Expect: 100-continuemit einer sofortigen "100 Continue"-Antwort reagieren und normal fortfahren. - Fahren Sie mit der Anfrage normal fort, stellen Sie der Anwendung jedoch einen
wsgi.inputStream zur Verfügung, der die Antwort „100 Continue“ sendet, wenn/wenn die Anwendung zum ersten Mal aus dem Eingabestrom liest. Die Leseanfrage muss dann blockiert bleiben, bis der Client antwortet. - Warten, bis der Client entscheidet, dass der Server Expect/Continue nicht unterstützt und den Anfrage-Body selbst sendet. (Dies ist suboptimal und nicht empfehlenswert.)
Beachten Sie, dass diese Verhaltensbeschränkungen nicht für HTTP 1.0-Anfragen oder für Anfragen gelten, die nicht an ein Anwendungsobjekt gerichtet sind. Weitere Informationen zu HTTP 1.1 Expect/Continue finden Sie in RFC 2616, Abschnitte 8.2.3 und 10.1.1.
Andere HTTP-Funktionen
Im Allgemeinen sollten Server und Gateways „dumm spielen“ und der Anwendung die volle Kontrolle über ihre Ausgabe überlassen. Sie sollten nur Änderungen vornehmen, die die effektive Semantik der Anwendungsantwort nicht verändern. Es ist immer möglich, dass der Anwendungsentwickler Middleware-Komponenten hinzufügt, um zusätzliche Funktionen bereitzustellen, sodass Server-/Gateway-Entwickler bei ihrer Implementierung konservativ vorgehen sollten. In gewissem Sinne sollte sich ein Server wie ein HTTP-„Gateway-Server“ betrachten, wobei die Anwendung ein HTTP-„Origin-Server“ ist. (Siehe RFC 2616, Abschnitt 1.3, für die Definition dieser Begriffe.)
Da WSGI-Server und -Anwendungen jedoch nicht über HTTP kommunizieren, gelten die in RFC 2616 als „Hop-by-Hop“-Header bezeichneten Header nicht für die interne WSGI-Kommunikation. WSGI-Anwendungen **dürfen keine** „Hop-by-Hop“-Header generieren, keine HTTP-Funktionen verwenden, die sie zur Generierung solcher Header erfordern würden, oder sich auf den Inhalt eingehender „Hop-by-Hop“-Header im environ-Wörterbuch verlassen. WSGI-Server **müssen** unterstützte eingehende „Hop-by-Hop“-Header selbst verarbeiten, z. B. durch Dekodierung jeder eingehenden Transfer-Encoding, einschließlich Chunked Encoding, falls zutreffend.
Wenn man diese Prinzipien auf eine Vielzahl von HTTP-Features anwendet, sollte klar sein, dass ein Server die Cache-Validierung über die If-None-Match und If-Modified-Since-Anfrageheader und die Last-Modified und ETag-Antwortheader handhaben kann. Dies ist jedoch nicht erforderlich, und die Anwendung sollte ihre eigene Cache-Validierung durchführen, wenn sie diese Funktion unterstützen möchte, da der Server/Gateway eine solche Validierung nicht durchführen muss.
Ähnlich kann ein Server die Antwort einer Anwendung neu kodieren oder transportkodieren, aber die Anwendung sollte selbst eine geeignete Inhaltskodierung verwenden und darf keine Transportkodierung anwenden. Ein Server kann Byte-Bereiche der Antwort der Anwendung übertragen, wenn diese vom Client angefordert werden und die Anwendung Byte-Bereiche nicht nativ unterstützt. Auch hier sollte die Anwendung diese Funktion jedoch selbst durchführen, wenn gewünscht.
Beachten Sie, dass diese Einschränkungen für Anwendungen nicht unbedingt bedeuten, dass jede Anwendung jede HTTP-Funktion neu implementieren muss; viele HTTP-Funktionen können teilweise oder vollständig von Middleware-Komponenten implementiert werden, wodurch sowohl Server- als auch Anwendungsautoren von der wiederholten Implementierung derselben Funktionen befreit werden.
Thread-Unterstützung
Die Thread-Unterstützung oder deren Fehlen ist ebenfalls serverabhängig. Server, die mehrere Anfragen parallel ausführen können, sollten auch die Option bieten, eine Anwendung im Single-Thread-Modus auszuführen, damit Anwendungen oder Frameworks, die nicht Thread-sicher sind, auch mit diesem Server verwendet werden können.
Implementierungs-/Anwendungsnotizen
Server-Erweiterungs-APIs
Einige Serverautoren möchten möglicherweise erweiterte APIs bereitstellen, die Anwendungs- oder Framework-Autoren für spezielle Zwecke verwenden können. Beispielsweise könnte ein Gateway, das auf mod_python basiert, einen Teil der Apache-API als WSGI-Erweiterung freigeben.
Im einfachsten Fall erfordert dies nichts weiter als die Definition einer environ-Variablen, z. B. mod_python.some_api. In vielen Fällen kann die mögliche Anwesenheit von Middleware dies jedoch erschweren. Beispielsweise könnte eine API, die Zugriff auf dieselben HTTP-Header bietet, wie sie in environ-Variablen zu finden sind, andere Daten zurückgeben, wenn environ von Middleware geändert wurde.
Im Allgemeinen laufen Erweiterungs-APIs, die die Funktionalität von WSGI duplizieren, ersetzen oder umgehen, Gefahr, mit Middleware-Komponenten inkompatibel zu sein. Server-/Gateway-Entwickler sollten *nicht* davon ausgehen, dass niemand Middleware verwenden wird, da einige Framework-Entwickler explizit beabsichtigen, ihre Frameworks so zu organisieren oder neu zu organisieren, dass sie fast ausschließlich als Middleware verschiedener Arten fungieren.
Um maximale Kompatibilität zu gewährleisten, **müssen** Server und Gateways, die Erweiterungs-APIs bereitstellen, die eine WSGI-Funktionalität ersetzen, diese APIs so gestalten, dass sie mit dem Teil der API aufgerufen werden, den sie ersetzen. Zum Beispiel muss eine Erweiterungs-API zum Zugriff auf HTTP-Anfrage-Header erfordern, dass die Anwendung ihre aktuelle environ übergibt, damit der Server/Gateway überprüfen kann, ob über die API zugängliche HTTP-Header nicht von Middleware geändert wurden. Wenn die Erweiterungs-API nicht garantieren kann, dass sie immer mit environ über den Inhalt von HTTP-Headern übereinstimmt, muss sie die Dienstleistung für die Anwendung verweigern, z. B. durch Auslösen eines Fehlers, Rückgabe von None anstelle einer Header-Sammlung oder was auch immer für die API angemessen ist.
Ähnlich verhält es sich, wenn eine Erweiterungs-API einen alternativen Weg zum Schreiben von Antwortdaten oder Headern bietet, sollte sie den start_response aufrufbaren übergeben, bevor die Anwendung den erweiterten Dienst erhalten kann. Wenn das übergebene Objekt nicht dasselbe ist, das der Server/Gateway ursprünglich der Anwendung zur Verfügung gestellt hat, kann er keine korrekte Funktion garantieren und muss sich weigern, den erweiterten Dienst der Anwendung zur Verfügung zu stellen.
Diese Richtlinien gelten auch für Middleware, die Informationen wie geparste Cookies, Formulardaten, Sitzungen und dergleichen zu environ hinzufügt. Insbesondere sollte solche Middleware diese Funktionen als Funktionen bereitstellen, die auf environ operieren, anstatt Werte einfach in environ zu stopfen. Dies trägt dazu bei, dass Informationen aus environ berechnet werden, nachdem jede Middleware URL-Rewrites oder andere environ-Änderungen vorgenommen hat.
Es ist sehr wichtig, dass diese "sicheren Erweiterungsregeln" sowohl von Server-/Gateway- als auch von Middleware-Entwicklern befolgt werden, um eine Zukunft zu vermeiden, in der Middleware-Entwickler gezwungen sind, alle Erweiterungs-APIs aus environ zu löschen, um sicherzustellen, dass ihre Vermittlung nicht von Anwendungen umgangen wird, die diese Erweiterungen nutzen!
Anwendungskonfiguration
Diese Spezifikation definiert nicht, wie ein Server eine Anwendung auswählt oder erhält, um sie aufzurufen. Dies und andere Konfigurationsoptionen sind sehr serverspezifische Angelegenheiten. Es wird erwartet, dass Server-/Gateway-Autoren dokumentieren, wie der Server für die Ausführung eines bestimmten Anwendungsobjekts konfiguriert wird und mit welchen Optionen (wie Threading-Optionen).
Framework-Autoren hingegen sollten dokumentieren, wie ein Anwendungsobjekt erstellt wird, das die Funktionalität ihres Frameworks umschließt. Der Benutzer, der sowohl den Server als auch das Anwendungsframework gewählt hat, muss die beiden miteinander verbinden. Da jedoch sowohl das Framework als auch der Server nun eine gemeinsame Schnittstelle haben, sollte dies lediglich eine mechanische Angelegenheit sein und kein erheblicher Entwicklungsaufwand für jedes neue Server-/Framework-Paar.
Schließlich möchten einige Anwendungen, Frameworks und Middleware möglicherweise das environ-Dictionary verwenden, um einfache String-Konfigurationsoptionen zu empfangen. Server und Gateways sollten dies unterstützen, indem sie es dem Bereitsteller einer Anwendung ermöglichen, Namens-Wert-Paare anzugeben, die in environ eingefügt werden sollen. Im einfachsten Fall kann diese Unterstützung lediglich darin bestehen, alle vom Betriebssystem bereitgestellten Umgebungsvariablen aus os.environ in das environ-Dictionary zu kopieren, da der Bereitsteller diese prinzipiell extern zum Server konfigurieren kann oder im CGI-Fall über die Konfigurationsdateien des Servers festgelegt werden können.
Anwendungen sollten versuchen, solche erforderlichen Variablen auf ein Minimum zu beschränken, da nicht alle Server eine einfache Konfiguration dafür unterstützen werden. Selbst im schlimmsten Fall können Personen, die eine Anwendung bereitstellen, natürlich ein Skript erstellen, um die erforderlichen Konfigurationswerte bereitzustellen
from the_app import application
def new_app(environ, start_response):
environ['the_app.configval1'] = 'something'
return application(environ, start_response)
Die meisten bestehenden Anwendungen und Frameworks benötigen wahrscheinlich nur einen einzigen Konfigurationswert aus environ, um den Speicherort ihrer anwendungs- oder frameworkspezifischen Konfigurationsdatei(en) anzugeben. (Natürlich sollten Anwendungen diese Konfiguration cachen, um zu vermeiden, sie bei jedem Aufruf erneut lesen zu müssen.)
URL-Rekonstruktion
Wenn eine Anwendung die vollständige URL einer Anfrage rekonstruieren möchte, kann sie dies mit dem folgenden Algorithmus tun, der von Ian Bicking beigesteuert wurde.
from urllib.parse import quote
url = environ['wsgi.url_scheme']+'://'
if environ.get('HTTP_HOST'):
url += environ['HTTP_HOST']
else:
url += environ['SERVER_NAME']
if environ['wsgi.url_scheme'] == 'https':
if environ['SERVER_PORT'] != '443':
url += ':' + environ['SERVER_PORT']
else:
if environ['SERVER_PORT'] != '80':
url += ':' + environ['SERVER_PORT']
url += quote(environ.get('SCRIPT_NAME', ''))
url += quote(environ.get('PATH_INFO', ''))
if environ.get('QUERY_STRING'):
url += '?' + environ['QUERY_STRING']
Beachten Sie, dass eine solche rekonstruierte URL möglicherweise nicht genau dieselbe URI ist, die vom Client angefordert wurde. Server-Rewrite-Regeln können beispielsweise die ursprünglich vom Client angeforderte URL geändert haben, um sie in eine kanonische Form zu bringen.
Unterstützung älterer (<2.2) Python-Versionen
Einige Server, Gateways oder Anwendungen möchten möglicherweise ältere (<2.2) Versionen von Python unterstützen. Dies ist besonders wichtig, wenn Jython eine Zielplattform ist, da zum Zeitpunkt des Schreibens noch keine produktionsreife Version von Jython 2.2 verfügbar ist.
Für Server und Gateways ist dies relativ unkompliziert: Server und Gateways, die sich an Vor-2.2-Versionen von Python richten, müssen sich einfach darauf beschränken, nur eine Standard-„for“-Schleife zur Iteration über jedes von einer Anwendung zurückgegebene Iterable zu verwenden. Dies ist der einzige Weg, Quellcode-Kompatibilität sowohl mit dem vor-2.2-Iterator-Protokoll (weiter unten diskutiert) als auch mit dem „heutigen“ Iterator-Protokoll (siehe PEP 234) zu gewährleisten.
(Beachten Sie, dass diese Technik notwendigerweise nur für Server, Gateways oder Middleware gilt, die in Python geschrieben sind. Die Diskussion darüber, wie Iterator-Protokolle aus anderen Sprachen korrekt verwendet werden, liegt außerhalb des Rahmens dieses PEP.)
Für Anwendungen ist die Unterstützung von Vor-2.2-Versionen von Python etwas komplexer.
- Sie dürfen kein Dateiobjekt zurückgeben und erwarten, dass es als Iterable funktioniert, da vor Python 2.2 Dateien nicht iterierbar waren. (Im Allgemeinen sollten Sie dies sowieso nicht tun, da es die meiste Zeit sehr schlecht abschneiden wird!) Verwenden Sie
wsgi.file_wrapperoder eine anwendungsspezifische Datei-Wrapper-Klasse. (Siehe Optionale plattformspezifische Dateiverarbeitung für mehr überwsgi.file_wrapperund eine Beispielklasse, die Sie zum Umhüllen einer Datei als Iterable verwenden können.) - Wenn Sie ein benutzerdefiniertes Iterable zurückgeben, **muss** es das vor-2.2-Iterator-Protokoll implementieren. Das heißt, es muss eine
__getitem__Methode bereitstellen, die einen ganzzahligen Schlüssel akzeptiert undIndexErrorauslöst, wenn sie erschöpft ist. (Beachten Sie, dass eingebaute Sequenztypen ebenfalls akzeptabel sind, da sie dieses Protokoll ebenfalls implementieren.)
Schließlich muss Middleware, die Vor-2.2-Versionen von Python unterstützen möchte und über Anwendungsrückgabewerte iteriert oder selbst ein Iterable zurückgibt (oder beides), die entsprechenden Empfehlungen befolgen.
(Hinweis: Es versteht sich von selbst, dass zur Unterstützung von Vor-2.2-Versionen von Python jeder Server, jedes Gateway, jede Anwendung oder Middleware nur Sprachfunktionen verwenden muss, die in der Zielversion verfügbar sind, 1 und 0 anstelle von True und False usw. verwendet.)
Optionale plattformspezifische Dateibehandlung
Einige Betriebsumgebungen bieten spezielle Hochleistungs-Dateiübertragungseinrichtungen, wie den Unix-Aufruf sendfile(). Server und Gateways **können** diese Funktionalität über einen optionalen wsgi.file_wrapper Schlüssel im environ freigeben. Eine Anwendung **kann** diesen „Datei-Wrapper“ verwenden, um ein Datei- oder dateiähnliches Objekt in ein Iterable zu konvertieren, das sie dann zurückgibt, z. B.
if 'wsgi.file_wrapper' in environ:
return environ['wsgi.file_wrapper'](filelike, block_size)
else:
return iter(lambda: filelike.read(block_size), '')
Wenn der Server oder das Gateway wsgi.file_wrapper bereitstellt, muss es ein aufrufbares Objekt sein, das einen erforderlichen Positionsargument und ein optionales Positionsargument akzeptiert. Das erste Argument ist das zu sendende dateiähnliche Objekt, und das zweite Argument ist eine optionale Blockgrößen-"Vorgabe" (die der Server/Gateway nicht verwenden muss). Das aufrufbare Objekt **muss** ein Iterable-Objekt zurückgeben und **darf keine** Datenübertragung durchführen, es sei denn und bis der Server/Gateway das Iterable tatsächlich als Rückgabewert von der Anwendung erhält. (Andernfalls würde es Middleware daran hindern, die Antwortdaten zu interpretieren oder zu überschreiben.)
Um als „dateiähnlich“ zu gelten, muss das von der Anwendung bereitgestellte Objekt über eine read() Methode verfügen, die ein optionales Größenargument akzeptiert. Es **kann** eine close() Methode haben, und wenn ja, **muss** das von wsgi.file_wrapper zurückgegebene Iterable eine close() Methode haben, die die close() Methode des ursprünglichen dateiähnlichen Objekts aufruft. Wenn das „dateiähnliche“ Objekt andere Methoden oder Attribute mit Namen hat, die denen von Python-eingebauten Dateiobjekten entsprechen (z. B. fileno()), **kann** der wsgi.file_wrapper davon ausgehen, dass diese Methoden oder Attribute die gleichen Semantiken wie die eines eingebauten Dateiobjekts haben.
Die tatsächliche Implementierung der plattformspezifischen Dateibehandlung muss erfolgen, nachdem die Anwendung zurückkehrt und der Server oder Gateway prüft, ob ein Wrapper-Objekt zurückgegeben wurde. (Auch hier gilt: Aufgrund der Anwesenheit von Middleware, Fehlerbehandlern und ähnlichem ist nicht garantiert, dass ein erstellter Wrapper tatsächlich verwendet wird.)
Abgesehen von der Behandlung von close() sollten die Semantik der Rückgabe eines Dateiverpackers aus der Anwendung dieselben sein, als ob die Anwendung iter(filelike.read, '') zurückgegeben hätte. Mit anderen Worten, die Übertragung sollte an der aktuellen Position innerhalb der „Datei“ zum Zeitpunkt des Beginns der Übertragung beginnen und bis zum Erreichen des Endes fortfahren oder bis Content-Length Bytes geschrieben wurden. (Wenn die Anwendung keine Content-Length angibt, kann der Server eine aus der Datei generieren, basierend auf seinem Wissen über die zugrunde liegende Dateimplementierung.)
Natürlich akzeptieren plattformspezifische File-Transmission-APIs normalerweise keine beliebigen „file-like“ Objekte. Daher muss ein wsgi.file_wrapper das übergebene Objekt auf Dinge wie fileno() (Unix-ähnliche Betriebssysteme) oder ein java.nio.FileChannel (unter Jython) untersuchen, um festzustellen, ob das file-like Objekt für die Verwendung mit der von ihm unterstützten plattformspezifischen API geeignet ist.
Beachten Sie, dass auch wenn das Objekt nicht für die Plattform-API geeignet ist, wsgi.file_wrapper immer noch ein Iterable zurückgeben muss, das read() und close() umschließt, damit Anwendungen, die Dateiverpacker verwenden, plattformübergreifend portabel sind. Hier ist eine einfache plattformunabhängige Dateiverpacker-Klasse, die sowohl für alte (vor 2.2) als auch für neue Pythons geeignet ist.
class FileWrapper:
def __init__(self, filelike, blksize=8192):
self.filelike = filelike
self.blksize = blksize
if hasattr(filelike, 'close'):
self.close = filelike.close
def __getitem__(self, key):
data = self.filelike.read(self.blksize)
if data:
return data
raise IndexError
und hier ist ein Ausschnitt aus einem Server/Gateway, der ihn verwendet, um Zugriff auf eine plattformspezifische API zu ermöglichen.
environ['wsgi.file_wrapper'] = FileWrapper
result = application(environ, start_response)
try:
if isinstance(result, FileWrapper):
# check if result.filelike is usable w/platform-specific
# API, and if so, use that API to transmit the result.
# If not, fall through to normal iterable handling
# loop below.
for data in result:
# etc.
finally:
if hasattr(result, 'close'):
result.close()
Fragen und Antworten
- Warum muss
environein Wörterbuch sein? Was ist falsch daran, eine Unterklasse zu verwenden?Die Begründung für die Anforderung eines Wörterbuchs ist die Maximierung der Portabilität zwischen Servern. Die Alternative wäre die Definition einer Teilmenge der Methoden eines Wörterbuchs als standardmäßige und portable Schnittstelle. In der Praxis werden die meisten Server ein Wörterbuch wahrscheinlich als ausreichend für ihre Bedürfnisse empfinden, und daher werden Framework-Autoren erwarten, dass die vollständige Menge der Wörterbuchfunktionen verfügbar ist, da sie dort öfter als nicht vorhanden sein werden. Wenn sich jedoch ein Server entscheidet, kein Wörterbuch zu verwenden, wird es trotz der „Konformität“ dieses Servers mit der Spezifikation zu Interoperabilitätsproblemen kommen. Daher vereinfacht die obligatorische Vorgabe eines Wörterbuchs die Spezifikation und garantiert die Interoperabilität.
Beachten Sie, dass dies Server- oder Framework-Entwickler nicht daran hindert, spezialisierte Dienste als benutzerdefinierte Variablen innerhalb des
environ-Wörterbuchs anzubieten. Dies ist der empfohlene Ansatz, um solche Mehrwertdienste anzubieten. - Warum kann man
write()aufrufen und Bytestrings erzeugen/ein Iterable zurückgeben? Sollten wir uns nicht für einen Weg entscheiden?Wenn wir nur den Iterationsansatz unterstützen würden, würden aktuelle Frameworks, die die Verfügbarkeit von „Push“ voraussetzen, leiden. Aber wenn wir nur das Pushen über
write()unterstützen, leidet die Serverleistung bei der Übertragung von z. B. großen Dateien (wenn ein Worker-Thread nicht mit einer neuen Anfrage beginnen kann, bis die gesamte Ausgabe gesendet wurde). Somit ermöglicht dieser Kompromiss einem Anwendungsframework, beide Ansätze nach Bedarf zu unterstützen, allerdings mit nur geringfügig mehr Aufwand für den Serverimplementierer als ein reiner Push-Ansatz erfordern würde. - Wozu dient
close()?Wenn während der Ausführung eines Anwendungsobjekts Schreibvorgänge durchgeführt werden, kann die Anwendung sicherstellen, dass Ressourcen mit einem try/finally-Block freigegeben werden. Wenn die Anwendung jedoch ein Iterable zurückgibt, werden alle verwendeten Ressourcen erst freigegeben, wenn das Iterable garbage-collected wird. Das
close()-Idiom ermöglicht es einer Anwendung, kritische Ressourcen am Ende einer Anfrage freizugeben, und ist zukunftssicher mit der Unterstützung für try/finally in Generatoren, die von PEP 325 vorgeschlagen wird. - Warum ist diese Schnittstelle so low-level? Ich möchte Funktion X! (z. B. Cookies, Sitzungen, Persistenz, ...)
Dies ist nicht Yet Another Python Web Framework. Es ist nur eine Möglichkeit für Frameworks, mit Webservern zu kommunizieren und umgekehrt. Wenn Sie diese Funktionen wünschen, müssen Sie ein Webframework auswählen, das die gewünschten Funktionen bietet. Und wenn dieses Framework es Ihnen ermöglicht, eine WSGI-Anwendung zu erstellen, sollten Sie sie auf den meisten WSGI-fähigen Servern ausführen können. Außerdem bieten einige WSGI-Server möglicherweise zusätzliche Dienste über Objekte an, die in ihrem
environ-Wörterbuch bereitgestellt werden. Einzelheiten finden Sie in der entsprechenden Serverdokumentation. (Natürlich werden Anwendungen, die solche Erweiterungen verwenden, nicht auf andere WSGI-basierte Server portierbar sein.) - Warum CGI-Variablen anstelle von guten alten HTTP-Headern verwenden? Und warum sie mit WSGI-definierten Variablen mischen?
Viele bestehende Webframeworks basieren stark auf der CGI-Spezifikation, und bestehende Webserver wissen, wie man CGI-Variablen generiert. Im Gegensatz dazu sind alternative Methoden zur Darstellung eingehender HTTP-Informationen fragmentiert und haben keinen Marktanteil. Daher scheint die Verwendung des CGI-„Standards“ ein guter Weg zu sein, bestehende Implementierungen zu nutzen. Was das Mischen mit WSGI-Variablen betrifft, so würde die Trennung lediglich zwei Dictionary-Argumente erfordern, die herumgereicht werden müssten, während keine wirklichen Vorteile entstünden.
- Was ist mit dem Status-String? Können wir nicht einfach die Zahl verwenden und
200anstelle von"200 OK"übergeben?Dies würde den Server oder das Gateway verkomplizieren, da sie eine Tabelle mit numerischen Stati und entsprechenden Meldungen benötigen würden. Im Gegensatz dazu ist es für einen Anwendungs- oder Framework-Autor einfach, den zusätzlichen Text für den spezifischen Antwortcode zu tippen, den er verwendet, und bestehende Frameworks haben oft bereits eine Tabelle mit den benötigten Meldungen. Daher scheint es insgesamt besser zu sein, die Verantwortung auf den Anwendungs-/Framework-Autor zu legen und nicht auf den Server oder das Gateway.
- Warum ist nicht garantiert, dass
wsgi.run_oncedie App nur einmal ausführt?Da es sich lediglich um einen Vorschlag an die Anwendung handelt, sich auf „seltene Ausführungen“ einzustellen. Dies ist für Anwendungsframeworks gedacht, die mehrere Betriebsmodi für Caching, Sitzungen usw. haben. Im Modus „mehrfache Ausführung“ können solche Frameworks Caches vorladen und z. B. Protokolle oder Sitzungsdaten nicht nach jeder Anfrage auf die Festplatte schreiben. Im Modus „einmalige Ausführung“ vermeiden solche Frameworks das Vorladen und leeren alle notwendigen Schreibvorgänge nach jeder Anfrage.
Um jedoch eine Anwendung oder ein Framework zu testen und den korrekten Betrieb im letzteren Modus zu überprüfen, kann es notwendig (oder zumindest praktikabel) sein, es mehr als einmal aufzurufen. Daher sollte sich eine Anwendung nicht darauf verlassen, dass sie definitiv nicht erneut ausgeführt wird, nur weil sie mit
wsgi.run_onceaufTruegesetzt aufgerufen wird. - Feature X (Dictionaries, Callables usw.) sind für die Verwendung im Anwendungscode hässlich; warum verwenden wir nicht Objekte stattdessen?
Alle diese Implementierungsentscheidungen von WSGI sind speziell darauf ausgelegt, Funktionen voneinander zu entkoppeln. Die Neukombination dieser Funktionen in gekapselten Objekten erschwert das Schreiben von Servern oder Gateways, und um eine Größenordnung schwieriger das Schreiben von Middleware, die nur kleine Teile der Gesamtfunktionalität ersetzt oder modifiziert.
Im Wesentlichen möchte Middleware ein „Chain of Responsibility“-Muster haben, bei dem sie als „Handler“ für einige Funktionen fungieren kann, während andere unverändert bleiben. Dies ist mit gewöhnlichen Python-Objekten schwierig zu erreichen, wenn die Schnittstelle erweiterbar bleiben soll. Zum Beispiel muss man
__getattr__oder__getattribute__Überschreibungen verwenden, um sicherzustellen, dass Erweiterungen (wie Attribute, die von zukünftigen WSGI-Versionen definiert werden) weitergegeben werden.Diese Art von Code ist bekanntermaßen schwierig, 100 % korrekt zu sein, und nur wenige Leute werden ihn selbst schreiben wollen. Sie werden daher die Implementierungen anderer kopieren, aber sie nicht aktualisieren, wenn die Person, von der sie kopiert haben, noch eine weitere Eckfall korrigiert.
Darüber hinaus wären diese notwendigen Boilerplate-Codes reine Zusatzkosten, eine Entwicklersteuer, die von Middleware-Entwicklern gezahlt wird, um eine etwas schönere API für Anwendungsframework-Entwickler zu unterstützen. Aber Anwendungsframework-Entwickler werden typischerweise nur ein Framework aktualisieren, um WSGI zu unterstützen, und das in einem sehr begrenzten Teil ihres Frameworks als Ganzes. Es wird wahrscheinlich ihre erste (und vielleicht ihre einzige) WSGI-Implementierung sein, und daher werden sie wahrscheinlich mit dieser Spezifikation griffbereit implementieren. Daher wäre der Aufwand, die API mit Objektattributen und Ähnlichem „schöner“ zu machen, für dieses Publikum wahrscheinlich verschwendet.
Wir ermutigen diejenigen, die eine schönere (oder anderweitig verbesserte) WSGI-Schnittstelle für die direkte Webanwendungsentwicklung (im Gegensatz zur Webframework-Entwicklung) wünschen, APIs oder Frameworks zu entwickeln, die WSGI für die bequeme Nutzung durch Anwendungsentwickler umschließen. Auf diese Weise kann WSGI für Server- und Middleware-Autoren bequem low-level bleiben, ohne für Anwendungsentwickler „hässlich“ zu sein.
Vorgeschlagen/Diskutiert
Diese Punkte werden derzeit im Web-SIG und anderswo diskutiert oder stehen auf der To-Do-Liste des PEP-Autors.
- Sollte
wsgi.inputein Iterator anstelle einer Datei sein? Dies würde für asynchrone Anwendungen und Chunked-Encoding-Input-Streams hilfreich sein. - Optionale Erweiterungen werden diskutiert, um die Iteration der Ausgabe einer Anwendung anzuhalten, bis Eingaben verfügbar sind oder bis ein Rückruf erfolgt.
- Fügen Sie einen Abschnitt über synchrone vs. asynchrone Apps und Server, die relevanten Threading-Modelle und Probleme/Designziele in diesen Bereichen hinzu.
Danksagungen
Vielen Dank an die vielen Leute in der Web-SIG-Mailingliste, deren durchdachtes Feedback diesen überarbeiteten Entwurf möglich gemacht hat. Insbesondere
- Gregory „Grisha“ Trubetskoy, Autor von
mod_python, der den ersten Entwurf als nicht vorteilhafter gegenüber „plain old CGI“ kritisierte und mich somit ermutigte, einen besseren Ansatz zu suchen. - Ian Bicking, der mir half, die Multithreading- und Multiprocess-Optionen richtig zu spezifizieren, sowie mich drängte, einen Mechanismus für Server bereitzustellen, um benutzerdefinierte Erweiterungsdaten an eine Anwendung zu übergeben.
- Tony Lownds, der das Konzept einer
start_response-Funktion entwickelte, die den Status und die Header entgegennahm und einewrite-Funktion zurückgab. Sein Input leitete auch das Design der Ausnahmsbehandlungsfunktionen, insbesondere im Bereich der Ermöglichung von Middleware, die Anwendungsfehlermeldungen überschreibt. - Alan Kennedy, dessen mutige Versuche, WSGI auf Jython zu implementieren (lange bevor die Spezifikation fertiggestellt war), dazu beigetragen haben, den Abschnitt „Unterstützung älterer Python-Versionen“ sowie die optionale
wsgi.file_wrapper-Einrichtung und einige der frühen Entscheidungen zu Bytes/Unicode zu gestalten. - Mark Nottingham, der die Spezifikation ausführlich auf Probleme mit der Konformität mit HTTP-RFCs überprüfte, insbesondere im Hinblick auf HTTP/1.1-Funktionen, von denen ich bis zu seiner Erwähnung nicht einmal wusste, dass sie existieren.
- Graham Dumpleton, der unermüdlich (auch angesichts meiner Faulheit und Dummheit) daran arbeitete, eine Art Python 3-Version von WSGI herauszubringen, der das Konzept „native Strings“ vs. „Byte-Strings“ vorschlug und eine Vielzahl von HTTP-,
wsgi.input- und anderen Änderungen durchdacht bearbeitete. Der meiste, wenn nicht der gesamte Ruhm für diesen neuen PEP gebührt ihm.
Referenzen
Urheberrecht
Dieses Dokument wurde gemeinfrei erklärt.
Quelle: https://github.com/python/peps/blob/main/peps/pep-3333.rst
Zuletzt geändert: 2025-02-01 08:55:40 GMT