WSGI: Antarmuka Aplikasi Server untuk Python

Diterbitkan: 2022-03-11

Pada tahun 1993, web masih dalam masa pertumbuhan, dengan sekitar 14 juta pengguna dan 100 situs web. Halamannya statis tetapi sudah ada kebutuhan untuk menghasilkan konten dinamis, seperti berita dan data terkini. Menanggapi hal ini, Rob McCool dan kontributor lainnya mengimplementasikan Common Gateway Interface (CGI) di server web HTTPd National Center for Supercomputing Applications (NCSA) (pendahulu Apache). Ini adalah server web pertama yang dapat menyajikan konten yang dihasilkan oleh aplikasi terpisah.

Sejak itu, jumlah pengguna di Internet telah meledak, dan situs web dinamis telah ada di mana-mana. Saat pertama kali mempelajari bahasa baru atau bahkan pertama kali belajar kode, pengembang, segera, ingin tahu tentang cara mengaitkan kode mereka ke web.

Python di Web dan Bangkitnya WSGI

Sejak penciptaan CGI, banyak yang telah berubah. Pendekatan CGI menjadi tidak praktis, karena memerlukan pembuatan proses baru pada setiap permintaan, membuang-buang memori dan CPU. Beberapa pendekatan tingkat rendah lainnya muncul, seperti FastCGI](http://www.fastcgi.com/) (1996) dan mod_python (2000), menyediakan antarmuka yang berbeda antara kerangka kerja web Python dan server web. Ketika pendekatan yang berbeda berkembang biak, pilihan kerangka kerja pengembang akhirnya membatasi pilihan server web dan sebaliknya.

Untuk mengatasi masalah ini, pada tahun 2003 Phillip J. Eby mengusulkan PEP-0333, Python Web Server Gateway Interface (WSGI). Idenya adalah untuk menyediakan antarmuka universal tingkat tinggi antara aplikasi Python dan server web.

Pada tahun 2003, PEP-3333 memperbarui antarmuka WSGI untuk menambahkan dukungan Python 3. Saat ini, hampir semua kerangka kerja Python menggunakan WSGI sebagai sarana, jika bukan satu-satunya cara, untuk berkomunikasi dengan server web mereka. Ini adalah bagaimana Django, Flask dan banyak kerangka kerja populer lainnya melakukannya.

Artikel ini bermaksud memberi pembaca gambaran sekilas tentang cara kerja WSGI, dan memungkinkan pembaca membangun aplikasi atau server WSGI sederhana. Namun, ini tidak dimaksudkan untuk menjadi lengkap, dan pengembang yang berniat untuk mengimplementasikan server atau aplikasi yang siap produksi harus melihat spesifikasi WSGI dengan lebih teliti.

Antarmuka Python WSGI

WSGI menetapkan aturan sederhana yang harus dipatuhi oleh server dan aplikasi. Mari kita mulai dengan meninjau pola keseluruhan ini.

Antarmuka aplikasi server WSGI Python.

Antarmuka Aplikasi

Di Python 3.5, antarmuka aplikasi berjalan seperti ini:

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

Di Python 2.7, antarmuka ini tidak akan jauh berbeda; satu-satunya perubahan adalah bahwa tubuh diwakili oleh objek str , bukan satu bytes .

Meskipun kami telah menggunakan fungsi dalam kasus ini, panggilan apa pun akan dilakukan. Aturan untuk objek aplikasi di sini adalah:

  • Harus dapat dipanggil dengan parameter environ dan start_response .
  • Harus memanggil callback start_response sebelum mengirim isi.
  • Harus mengembalikan iterable dengan potongan badan dokumen.

Contoh lain dari objek yang memenuhi aturan ini dan akan menghasilkan efek yang sama adalah:

 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

Antarmuka Server

Server WSGI mungkin berinteraksi dengan aplikasi ini seperti ini::

 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()

Seperti yang mungkin telah Anda perhatikan, callable start_response mengembalikan callable write yang mungkin digunakan aplikasi untuk mengirim data kembali ke klien, tetapi itu tidak digunakan oleh contoh kode aplikasi kita. Antarmuka write ini tidak digunakan lagi, dan kita dapat mengabaikannya untuk saat ini. Nanti akan dibahas secara singkat di artikel.

Keunikan lain dari tanggung jawab server adalah memanggil metode close opsional pada iterator respons, jika ada. Seperti yang ditunjukkan dalam artikel Graham Dumpleton di sini, ini adalah fitur WSGI yang sering diabaikan. Memanggil metode ini, jika ada , memungkinkan aplikasi melepaskan sumber daya apa pun yang mungkin masih ada.

Argumen environ Application Callable

Parameter environ harus berupa objek kamus. Ini digunakan untuk meneruskan informasi permintaan dan server ke aplikasi, seperti halnya CGI. Faktanya, semua variabel lingkungan CGI valid di WSGI dan server harus melewati semua yang berlaku untuk aplikasi.

Meskipun ada banyak kunci opsional yang dapat dilewati, beberapa di antaranya wajib. Mengambil contoh permintaan GET berikut:

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

Ini adalah kunci yang harus disediakan server, dan nilai yang akan mereka ambil:

Kunci Nilai Komentar
REQUEST_METHOD "GET"
SCRIPT_NAME "" tergantung pengaturan server
PATH_INFO "/auth"
QUERY_STRING "token=123"
CONTENT_TYPE ""
CONTENT_LENGTH ""
SERVER_NAME "127.0.0.1" tergantung pengaturan server
SERVER_PORT "8000"
SERVER_PROTOCOL "HTTP/1.1"
HTTP_(...) Header HTTP yang disediakan klien
wsgi.version (1, 0) tuple dengan versi WSGI
wsgi.url_scheme "http"
wsgi.input Objek seperti file
wsgi.errors Objek seperti file
wsgi.multithread False True jika server multithreaded
wsgi.multiprocess False True jika server menjalankan banyak proses
wsgi.run_once False True jika server mengharapkan skrip ini dijalankan hanya sekali (mis.: di lingkungan CGI)

Pengecualian untuk aturan ini adalah jika salah satu kunci ini kosong (seperti CONTENT_TYPE pada tabel di atas), maka kunci tersebut dapat dihilangkan dari kamus, dan akan dianggap sesuai dengan string kosong.

wsgi.input dan wsgi.errors

Sebagian besar kunci environ bersifat langsung, tetapi dua di antaranya layak mendapatkan klarifikasi lebih lanjut: wsgi.input , yang harus berisi aliran dengan badan permintaan dari klien, dan wsgi.errors , tempat aplikasi melaporkan kesalahan apa pun yang ditemuinya. Kesalahan yang dikirim dari aplikasi ke wsgi.errors biasanya akan dikirim ke log kesalahan server.

Kedua kunci ini harus berisi objek seperti file; yaitu, objek yang menyediakan antarmuka untuk dibaca atau ditulis sebagai aliran, sama seperti objek yang kita dapatkan saat membuka file atau soket dengan Python. Ini mungkin tampak rumit pada awalnya, tetapi untungnya, Python memberi kita alat yang bagus untuk menangani ini.

Pertama, aliran seperti apa yang sedang kita bicarakan? Sesuai definisi WSGI, wsgi.input dan wsgi.errors harus menangani objek bytes di Python 3 dan objek str di Python 2. Dalam kedua kasus, jika kita ingin menggunakan buffer dalam memori untuk melewatkan atau mendapatkan data melalui WSGI antarmuka, kita dapat menggunakan kelas io.BytesIO .

Sebagai contoh, jika kita menulis server WSGI, kita dapat memberikan badan permintaan ke aplikasi seperti ini:

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

Di sisi aplikasi, jika kami ingin mengubah input aliran yang kami terima menjadi string, kami ingin menulis sesuatu seperti ini:

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

Aliran wsgi.errors harus digunakan untuk melaporkan kesalahan aplikasi ke server, dan baris harus diakhiri dengan \n . Server web harus berhati-hati dalam mengonversi ke akhir baris yang berbeda sesuai dengan sistem.

Argumen start_response Application Callable

Argumen start_response harus dapat dipanggil dengan dua argumen yang diperlukan, yaitu status dan headers , dan satu argumen opsional, exc_info . Itu harus dipanggil oleh aplikasi sebelum bagian tubuh mana pun dikirim kembali ke server web.

Dalam contoh aplikasi pertama di awal artikel ini, kami telah mengembalikan isi respons sebagai daftar, dan dengan demikian, kami tidak memiliki kendali atas kapan daftar akan diulang. Karena itu, kami harus memanggil start_response sebelum mengembalikan daftar.

Dalam yang kedua, kami telah memanggil start_response tepat sebelum menghasilkan bagian pertama (dan, dalam hal ini, hanya) dari badan respons. Either way valid dalam spesifikasi WSGI.

Dari sisi server web, pemanggilan start_response seharusnya tidak benar-benar mengirim header ke klien, tetapi tunda sampai setidaknya ada satu bytestring yang tidak kosong di badan respons untuk dikirim kembali ke klien. Arsitektur ini memungkinkan kesalahan dilaporkan dengan benar hingga saat-saat terakhir dari eksekusi aplikasi.

status Argumen dari start_response

Argumen status yang diteruskan ke callback start_response harus berupa string yang terdiri dari kode status HTTP dan deskripsi, dipisahkan oleh satu spasi. Contoh yang valid adalah: '200 OK' , atau '404 Not Found' .

Argumen headers dari start_response

Argumen headers yang diteruskan ke callback start_response harus berupa list Python dari tuple s, dengan setiap tuple disusun sebagai (header_name, header_value) . Baik nama dan nilai setiap header harus berupa string (terlepas dari versi Python). Ini adalah contoh langka di mana tipe penting, karena ini memang diperlukan oleh spesifikasi WSGI.

Berikut adalah contoh yang valid tentang seperti apa argumen header :

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

Header HTTP tidak peka huruf besar/kecil, dan jika kita menulis server web yang sesuai dengan WSGI, itu adalah sesuatu yang perlu diperhatikan saat memeriksa header ini. Juga, daftar header yang disediakan oleh aplikasi tidak seharusnya lengkap. Merupakan tanggung jawab server untuk memastikan bahwa semua header HTTP yang diperlukan ada sebelum mengirim respons kembali ke klien, mengisi header yang tidak disediakan oleh aplikasi.

Argumen exc_info dari start_response

Callback start_response harus mendukung argumen ketiga exc_info , yang digunakan untuk penanganan kesalahan. Penggunaan dan implementasi yang benar dari argumen ini sangat penting untuk server web dan aplikasi produksi, tetapi berada di luar cakupan artikel ini.

Informasi lebih lanjut dapat diperoleh di spesifikasi WSGI, di sini.

Nilai Pengembalian start_response – Panggilan Balik write

Untuk tujuan kompatibilitas mundur, server web yang mengimplementasikan WSGI harus mengembalikan callable write . Panggilan balik ini harus memungkinkan aplikasi untuk menulis data respons tubuh langsung kembali ke klien, alih-alih menyerahkannya ke server melalui iterator.

Terlepas dari kehadirannya, ini adalah antarmuka yang tidak digunakan lagi dan aplikasi baru harus menahan diri untuk tidak menggunakannya.

Menghasilkan Badan Respon

Aplikasi yang mengimplementasikan WSGI harus menghasilkan badan respons dengan mengembalikan objek yang dapat diubah. Untuk sebagian besar aplikasi, badan respons tidak terlalu besar dan mudah masuk ke dalam memori server. Dalam hal ini, cara paling efisien untuk mengirimkannya adalah sekaligus, dengan satu elemen yang dapat diubah. Dalam kasus khusus, di mana memuat seluruh tubuh ke dalam memori tidak dapat dilakukan, aplikasi dapat mengembalikannya bagian demi bagian melalui antarmuka yang dapat diubah ini.

Hanya ada sedikit perbedaan di sini antara WSGI Python 2 dan Python 3: di Python 3, badan respons diwakili oleh objek bytes ; di Python 2, tipe yang benar untuk ini adalah str .

Mengubah string UTF-8 menjadi bytes atau str adalah tugas yang mudah:

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

Jika Anda ingin mempelajari lebih lanjut tentang unicode dan penanganan bytestring Python 2, ada tutorial yang bagus di YouTube.

Server web yang mengimplementasikan WSGI juga harus mendukung callback write untuk kompatibilitas mundur, seperti dijelaskan di atas.

Menguji Aplikasi Anda Tanpa Server Web

Dengan pemahaman tentang antarmuka yang sederhana ini, kita dapat dengan mudah membuat skrip untuk menguji aplikasi kita tanpa benar-benar perlu memulai server.

Ambil skrip kecil ini, misalnya:

 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}

Dengan cara ini, kita mungkin, misalnya, menginisialisasi beberapa data pengujian dan modul tiruan ke dalam aplikasi kita, dan membuat panggilan GET untuk menguji apakah itu merespons dengan tepat. Kita dapat melihat bahwa itu bukan server web yang sebenarnya, tetapi antarmuka dengan aplikasi kita dengan cara yang sebanding dengan menyediakan aplikasi dengan panggilan balik start_response dan kamus dengan variabel lingkungan kita. Di akhir permintaan, ia menggunakan iterator badan respons dan mengembalikan string dengan semua kontennya. Metode serupa (atau yang umum) dapat dibuat untuk berbagai jenis permintaan HTTP.

Bungkus

WSGI adalah bagian penting dari hampir semua kerangka kerja web Python.

Dalam artikel ini, kami belum membahas bagaimana WSGI menangani unggahan file, karena ini dapat dianggap sebagai fitur yang lebih "maju", tidak cocok untuk artikel pengantar. Jika Anda ingin tahu lebih banyak tentangnya, lihat bagian PEP-3333 yang mengacu pada penanganan file.

Saya harap artikel ini berguna dalam membantu menciptakan pemahaman yang lebih baik tentang bagaimana Python berbicara dengan server web, dan memungkinkan pengembang untuk menggunakan antarmuka ini dengan cara yang menarik dan kreatif.

ucapan terima kasih

Saya ingin berterima kasih kepada editor saya Nick McCrea karena telah membantu saya dengan artikel ini. Karena karyanya, teks asli menjadi lebih jelas dan beberapa kesalahan tidak diperbaiki.