WSGI: Python için Sunucu-Uygulama Arayüzü

Yayınlanan: 2022-03-11

1993'te web, yaklaşık 14 milyon kullanıcı ve 100 web sitesiyle henüz emekleme aşamasındaydı. Sayfalar statikti ancak güncel haberler ve veriler gibi dinamik içerikler üretmeye zaten ihtiyaç vardı. Buna yanıt olarak, Rob McCool ve diğer katkıda bulunanlar, Ulusal Süper Bilgi İşlem Uygulamaları Merkezi (NCSA) HTTPd web sunucusunda (Apache'nin öncüsü) Ortak Ağ Geçidi Arayüzü'nü (CGI) uyguladılar. Bu, ayrı bir uygulama tarafından oluşturulan içeriği sunabilen ilk web sunucusuydu.

O zamandan beri, İnternet'teki kullanıcı sayısı patladı ve dinamik web siteleri her yerde bulunur hale geldi. Geliştiriciler, yeni bir dili ilk kez öğrenirken veya hatta kodlamayı ilk öğrenirken, çok geçmeden kodlarını web'e nasıl bağlayacaklarını bilmek isterler.

Web'de Python ve WSGI'nin Yükselişi

CGI'nın yaratılmasından bu yana çok şey değişti. CGI yaklaşımı, her istekte yeni bir süreç oluşturulmasını, bellek ve CPU'yu boşa harcamayı gerektirdiğinden pratik değildi. FastCGI](http://www.fastcgi.com/) (1996) ve mod_python (2000) gibi Python web çerçeveleri ile web sunucusu arasında farklı arayüzler sağlayan bazı diğer düşük seviyeli yaklaşımlar ortaya çıktı. Farklı yaklaşımlar çoğaldıkça, geliştiricinin çerçeve seçimi web sunucularının seçimlerini kısıtladı ve bunun tersi de oldu.

Bu sorunu çözmek için 2003'te Phillip J. Eby, Python Web Sunucusu Ağ Geçidi Arayüzü (WSGI) olan PEP-0333'ü önerdi. Fikir, Python uygulamaları ve web sunucuları arasında üst düzey, evrensel bir arabirim sağlamaktı.

2003 yılında, PEP-3333, Python 3 desteği eklemek için WSGI arayüzünü güncelledi. Günümüzde, neredeyse tüm Python çerçeveleri, web sunucularıyla iletişim kurmak için tek araç olmasa da bir araç olarak WSGI'yi kullanır. Django, Flask ve diğer birçok popüler çerçeve bunu böyle yapar.

Bu makale, okuyucuya WSGI'nin nasıl çalıştığına dair bir fikir vermeyi ve okuyucunun basit bir WSGI uygulaması veya sunucusu oluşturmasını sağlamayı amaçlamaktadır. Bununla birlikte, ayrıntılı olması amaçlanmamıştır ve üretime hazır sunucular veya uygulamalar uygulamak isteyen geliştiriciler, WSGI belirtimine daha kapsamlı bir şekilde bakmalıdır.

Python WSGI Arayüzü

WSGI, sunucunun ve uygulamanın uyması gereken basit kuralları belirtir. Bu genel modeli gözden geçirerek başlayalım.

Python WSGI sunucu-uygulama arayüzü.

Uygulama Arayüzü

Python 3.5'te uygulama arayüzleri şu şekildedir:

 def application(environ, start_response): body = b'Hello world!\n' status = '200 OK' headers = [('Content-type', 'text/plain')] start_response(status, headers) return [body]

Python 2.7'de bu arayüz çok farklı olmazdı; tek değişiklik, gövdenin bir bytes yerine bir str nesnesiyle temsil edilmesi olacaktır.

Bu durumda bir işlev kullanmış olsak da, herhangi bir çağrılabilir. Buradaki uygulama nesnesi için kurallar şunlardır:

  • environ ve start_response parametreleriyle çağrılabilir olmalıdır.
  • Gövdeyi göndermeden önce start_response geri aramasını çağırmalıdır.
  • Belge gövdesinin parçalarıyla bir yinelenebilir döndürmelidir.

Bu kuralları karşılayan ve aynı etkiyi yaratan başka bir nesne örneği:

 class Application: def __init__(self, environ, start_response): self.environ = environ self.start_response = start_response def __iter__(self): body = b'Hello world!\n' status = '200 OK' headers = [('Content-type', 'text/plain')] self.start_response(status, headers) yield body

Sunucu Arayüzü

Bir WSGI sunucusu bu uygulamayla şu şekilde arayüz oluşturabilir:

 def write(chunk): '''Write data back to client''' ... def send_status(status): '''Send HTTP status code''' ... def send_headers(headers): '''Send HTTP headers''' ... def start_response(status, headers): '''WSGI start_response callable''' send_status(status) send_headers(headers) return write # Make request to application response = application(environ, start_response) try: for chunk in response: write(chunk) finally: if hasattr(response, 'close'): response.close()

Fark etmiş olabileceğiniz gibi, start_response çağrılabilir, uygulamanın istemciye veri göndermek için kullanabileceği bir write çağrılabilirliği döndürdü, ancak bu bizim uygulama kodu örneğimiz tarafından kullanılmadı. Bu write arayüzü kullanımdan kaldırıldı ve şimdilik onu görmezden gelebiliriz. Makalede daha sonra kısaca tartışılacaktır.

Sunucunun sorumluluklarının bir başka özelliği de, varsa, yanıt yineleyicide isteğe bağlı close yöntemini çağırmaktır. Graham Dumpleton'ın buradaki makalesinde belirtildiği gibi, WSGI'nın genellikle gözden kaçan bir özelliğidir. Varsa , bu yöntemin çağrılması, uygulamanın hala elinde bulundurabileceği tüm kaynakları serbest bırakmasına izin verir.

Uygulama Callable'ın environ Argümanı

environ parametresi bir sözlük nesnesi olmalıdır. Uygulamaya istek ve sunucu bilgilerini iletmek için kullanılır, tıpkı CGI'nın yaptığı gibi. Aslında, tüm CGI ortam değişkenleri WSGI'da geçerlidir ve sunucu, uygulama için geçerli olan her şeyi iletmelidir.

Geçilebilecek birçok isteğe bağlı anahtar olsa da, birkaçı zorunludur. Aşağıdaki GET isteğini örnek alarak:

 $ curl 'http://localhost:8000/auth?user=obiwan&token=123'

Sunucunun sağlaması gereken anahtarlar ve alacağı değerler şunlardır:

Anahtar Değer Yorumlar
REQUEST_METHOD "GET"
SCRIPT_NAME "" sunucu kurulumuna bağlı
PATH_INFO "/auth"
QUERY_STRING "token=123"
CONTENT_TYPE ""
CONTENT_LENGTH ""
SERVER_NAME "127.0.0.1" sunucu kurulumuna bağlı
SERVER_PORT "8000"
SERVER_PROTOCOL "HTTP/1.1"
HTTP_(...) İstemci tarafından sağlanan HTTP üstbilgileri
wsgi.version (1, 0) WSGI sürümü ile demet
wsgi.url_scheme "http"
wsgi.input Dosya benzeri nesne
wsgi.errors Dosya benzeri nesne
wsgi.multithread False Sunucu çok iş parçacıklıysa True
wsgi.multiprocess False Sunucu birden çok işlem çalıştırıyorsa True
wsgi.run_once False Sunucu bu betiğin yalnızca bir kez çalışmasını bekliyorsa True (örneğin: bir CGI ortamında)

Bu kuralın istisnası, bu anahtarlardan birinin boş olması durumunda (yukarıdaki tablodaki CONTENT_TYPE gibi), o zaman sözlükten çıkarılmaları ve boş dizeye karşılık geldiklerinin varsayılmasıdır.

wsgi.input ve wsgi.errors

Çoğu environ anahtarı basittir, ancak bunlardan ikisi biraz daha açıklığa kavuşturulmayı hak eder: istemciden gelen istek gövdesiyle bir akış içermesi gereken wsgi.input ve uygulamanın karşılaştığı hataları bildirdiği wsgi.errors . Uygulamadan wsgi.errors gönderilen hatalar genellikle sunucu hata günlüğüne gönderilir.

Bu iki anahtar, dosya benzeri nesneler içermelidir; yani, Python'da bir dosya veya soket açtığımızda elde ettiğimiz nesne gibi, akış olarak okunacak veya yazılacak arabirimler sağlayan nesneler. Bu ilk başta zor görünebilir, ancak neyse ki Python bize bununla başa çıkmak için iyi araçlar sunuyor.

İlk olarak, ne tür akışlardan bahsediyoruz? WSGI tanımına göre, wsgi.input ve wsgi.errors , Python 3'teki bytes nesnelerini ve Python 2'deki str nesnelerini işlemelidir. Her iki durumda da, WSGI üzerinden veri geçirmek veya almak için bir bellek içi arabellek kullanmak istiyorsak. interface, io.BytesIO sınıfını kullanabiliriz.

Örnek olarak, bir WSGI sunucusu yazıyorsak, istek gövdesini uygulamaya şu şekilde sağlayabiliriz:

  • Python 2.7 için
 import io ... request_data = 'some request body' environ['wsgi.input'] = io.BytesIO(request_data)
  • Python 3.5 için
 import io ... request_data = 'some request body'.encode('utf-8') # bytes object environ['wsgi.input'] = io.BytesIO(request_data)

Uygulama tarafında, aldığımız bir akış girdisini bir dizgeye dönüştürmek istersek, şöyle bir şey yazmak isterdik:

  • Python 2.7 için
 readstr = environ['wsgi.input'].read() # returns str object
  • Python 3.5 için
 readbytes = environ['wsgi.input'].read() # returns bytes object readstr = readbytes.decode('utf-8') # returns str object

Uygulama hatalarını sunucuya bildirmek için wsgi.errors akışı kullanılmalı ve satırlar bir \n ile sonlandırılmalıdır. Web sunucusu, sisteme göre farklı bir satır bitene dönüştürmeye özen göstermelidir.

Callable uygulamasının start_response Argümanı

start_response bağımsız değişkeni, status ve headers ve bir isteğe bağlı bağımsız değişken ( exc_info ) olmak üzere iki gerekli bağımsız değişkene sahip bir çağrılabilir olmalıdır. Vücudun herhangi bir kısmı web sunucusuna geri gönderilmeden önce uygulama tarafından çağrılmalıdır.

Bu makalenin başındaki ilk uygulama örneğinde, yanıtın gövdesini bir liste olarak döndürdük ve bu nedenle, listenin ne zaman yineleneceği üzerinde hiçbir kontrolümüz yok. Bu nedenle listeyi döndürmeden önce start_response çağırmak zorunda kaldık.

İkincisinde, yanıt gövdesinin ilk (ve bu durumda yalnızca) parçasını vermeden hemen önce start_response . Her iki şekilde de WSGI belirtimi dahilinde geçerlidir.

Web sunucusu tarafından, start_response çağrısı aslında başlıkları istemciye göndermemeli, ancak yanıt gövdesinde istemciye geri gönderilecek en az bir boş olmayan bayt dizisi olana kadar geciktirmelidir. Bu mimari, uygulamanın yürütülmesinin mümkün olan en son anına kadar hataların doğru bir şekilde rapor edilmesini sağlar.

start_response status Argümanı

start_response geri çağrısına iletilen status bağımsız değişkeni, tek bir boşlukla ayrılmış bir HTTP durum kodu ve açıklamasından oluşan bir dize olmalıdır. Geçerli örnekler: '200 OK' veya '404 Not Found' .

start_response headers Argümanı

start_response geri çağrısına iletilen headers bağımsız değişkeni, her bir (header_name, header_value) olarak oluşturulacak şekilde, tuple s'nin bir Python list olmalıdır. Her başlığın hem adı hem de değeri dize olmalıdır (Python sürümünden bağımsız olarak). Bu, türün önemli olduğu nadir bir örnektir, çünkü bu gerçekten WSGI belirtimi tarafından gereklidir.

Bir header argümanının nasıl görünebileceğine dair geçerli bir örnek:

 response_body = json.dumps(data).encode('utf-8') headers = [('Content-Type', 'application/json'), ('Content-Length', str(len(response_body))]

HTTP üstbilgileri büyük/küçük harfe duyarlı değildir ve WSGI uyumlu bir web sunucusu yazıyorsak, bu üstbilgileri kontrol ederken dikkate alınması gereken bir şeydir. Ayrıca, uygulama tarafından sağlanan başlıklar listesinin ayrıntılı olması gerekmez. Yanıtı istemciye geri göndermeden önce, uygulama tarafından sağlanmayan tüm başlıkları doldurarak, gerekli tüm HTTP başlıklarının var olduğundan emin olmak sunucunun sorumluluğundadır.

start_response exc_info Argümanı

start_response geri çağrısı, hata işleme için kullanılan üçüncü bir exc_info bağımsız değişkenini desteklemelidir. Bu argümanın doğru kullanımı ve uygulanması, üretim web sunucuları ve uygulamaları için son derece önemlidir, ancak bu makalenin kapsamı dışındadır.

Bununla ilgili daha fazla bilgi, burada WSGI spesifikasyonunda elde edilebilir.

start_response Dönüş Değeri – write Geri Çağırma

Geriye dönük uyumluluk amacıyla, WSGI uygulayan web sunucuları, çağrılabilir bir write döndürmelidir. Bu geri arama, uygulamanın gövde yanıt verilerini bir yineleyici aracılığıyla sunucuya vermek yerine doğrudan istemciye geri yazmasına izin vermelidir.

Varlığına rağmen, bu kullanımdan kaldırılmış bir arayüzdür ve yeni uygulamalar onu kullanmaktan kaçınmalıdır.

Yanıt Gövdesini Oluşturma

WSGI uygulayan uygulamalar, yinelenebilir bir nesne döndürerek yanıt gövdesini oluşturmalıdır. Çoğu uygulama için yanıt gövdesi çok büyük değildir ve sunucunun belleğine kolayca sığar. Bu durumda, göndermenin en verimli yolu, tek bir öğe yinelenebilir ile hepsini bir kerede göndermektir. Tüm gövdeyi belleğe yüklemenin mümkün olmadığı özel durumlarda, uygulama bu yinelenebilir arabirim aracılığıyla parça parça geri döndürebilir.

Burada Python 2'ler ve Python 3'ün WSGI'ları arasında yalnızca küçük bir fark vardır: Python 3'te yanıt gövdesi bytes nesneleri ile temsil edilir; Python 2'de bunun için doğru tür str .

UTF-8 dizelerini bytes veya str dönüştürmek kolay bir iştir:

  • Python 3.5:
 body = 'unicode stuff'.encode('utf-8')
  • Python 2.7:
 body = u'unicode stuff'.encode('utf-8')

Python 2'nin unicode ve bytestring işlemesi hakkında daha fazla bilgi edinmek isterseniz, YouTube'da güzel bir eğitim var.

WSGI uygulayan web sunucuları, yukarıda açıklandığı gibi geriye dönük uyumluluk için geri write çağrısını da desteklemelidir.

Uygulamanızı Web Sunucusu Olmadan Test Etme

Bu basit arayüzün anlaşılmasıyla, aslında bir sunucu başlatmaya gerek kalmadan uygulamalarımızı test etmek için kolayca komut dosyaları oluşturabiliriz.

Bu küçük betiği alın, örneğin:

 from io import BytesIO def get(app, path = '/', query = ''): response_status = [] response_headers = [] def start_response(status, headers): status = status.split(' ', 1) response_status.append((int(status[0]), status[1])) response_headers.append(dict(headers)) environ = { 'HTTP_ACCEPT': '*/*', 'HTTP_HOST': '127.0.0.1:8000', 'HTTP_USER_AGENT': 'TestAgent/1.0', 'PATH_INFO': path, 'QUERY_STRING': query, 'REQUEST_METHOD': 'GET', 'SERVER_NAME': '127.0.0.1', 'SERVER_PORT': '8000', 'SERVER_PROTOCOL': 'HTTP/1.1', 'SERVER_SOFTWARE': 'TestServer/1.0', 'wsgi.errors': BytesIO(b''), 'wsgi.input': BytesIO(b''), 'wsgi.multiprocess': False, 'wsgi.multithread': False, 'wsgi.run_once': False, 'wsgi.url_scheme': 'http', 'wsgi.version': (1, 0), } response_body = app(environ, start_response) merged_body = ''.join((x.decode('utf-8') for x in response_body)) if hasattr(response_body, 'close'): response_body.close() return {'status': response_status[0], 'headers': response_headers[0], 'body': merged_body}

Bu şekilde, örneğin, uygulamamıza bazı test verilerini ve sahte modülleri başlatabilir ve buna göre yanıt verip vermediğini test etmek için GET çağrıları yapabiliriz. Bunun gerçek bir web sunucusu olmadığını, uygulamaya bir start_response geri çağrısı ve ortam değişkenlerimizle bir sözlük sağlayarak karşılaştırılabilir bir şekilde uygulamamızla arayüz oluşturduğunu görebiliriz. İsteğin sonunda, yanıt gövdesi yineleyicisini tüketir ve tüm içeriğiyle birlikte bir dize döndürür. Farklı HTTP istekleri türleri için benzer yöntemler (veya genel bir yöntem) oluşturulabilir.

Sarmak

WSGI, neredeyse tüm Python web çerçevelerinin kritik bir parçasıdır.

Bu makalede, daha "gelişmiş" bir özellik olarak kabul edilebileceğinden, bir giriş makalesi için uygun olmadığından, WSGI'nin dosya yüklemeleriyle nasıl ilgilendiğini ele almadık. Bununla ilgili daha fazla bilgi edinmek isterseniz, dosya işlemeyle ilgili PEP-3333 bölümüne bakın.

Umarım bu makale Python'un web sunucularıyla nasıl konuştuğunun daha iyi anlaşılmasına yardımcı olur ve geliştiricilerin bu arayüzü ilginç ve yaratıcı şekillerde kullanmalarına olanak tanır.

Teşekkür

Editörüm Nick McCrea'ya bu makalede bana yardım ettiği için teşekkür etmek istiyorum. Çalışması nedeniyle, orijinal metin çok daha net hale geldi ve birçok hata düzeltilmedi.