OpenResty Adalah NGINX yang Ditingkatkan dengan Permintaan dan Respons Dinamis

API7.ai

October 23, 2022

OpenResty (NGINX + Lua)

Setelah pengenalan sebelumnya, Anda pasti sudah memahami konsep OpenResty dan cara mempelajarinya. Artikel ini akan memandu kita tentang bagaimana OpenResty menangani permintaan dan respons klien.

Meskipun OpenResty adalah server web berbasis NGINX, ia pada dasarnya berbeda dari NGINX: NGINX digerakkan oleh file konfigurasi statis, sementara OpenResty digerakkan oleh API Lua, menawarkan fleksibilitas dan kemampuan pemrograman yang lebih besar.

Mari saya ajak Anda melalui manfaat dari API Lua.

Kategori API

Pertama, kita perlu tahu bahwa API OpenResty dibagi menjadi beberapa kategori besar berikut.

  • Pemrosesan permintaan dan respons.
  • Terkait SSL.
  • Shared dict.
  • Cosocket.
  • Menangani lalu lintas empat lapis.
  • Proses dan worker.
  • Akses ke variabel dan konfigurasi NGINX.
  • String, waktu, codec, dan fungsi umum lainnya, dll.

Di sini, saya menyarankan Anda juga membuka dokumentasi API Lua OpenResty dan memeriksanya terhadap daftar API untuk melihat apakah Anda dapat menghubungkannya dengan kategori ini.

API OpenResty tidak hanya ada di proyek lua-nginx-module, tetapi juga di proyek lua-resty-core, seperti ngx.ssl, ngx.base64, ngx.errlog, ngx.process, ngx.re.split, ngx.resp.add_header, ngx.balancer, ngx.semaphore, ngx.ocsp, dan API lainnya.

Untuk API yang tidak ada di proyek lua-nginx-module, Anda perlu memuatnya secara terpisah untuk menggunakannya. Misalnya, jika Anda ingin menggunakan fungsi split, Anda perlu memanggilnya seperti berikut.

$ resty -e 'local ngx_re = require "ngx.re" local res, err = ngx_re.split("a,b,c,d", ",", nil, {pos = 5}) print(res) '

Tentu saja, ini mungkin membingungkan Anda: di proyek lua-nginx-module, ada beberapa API yang dimulai dengan ngx.re.sub, ngx.re.find, dll. Mengapa hanya API ngx.re.split yang perlu dimuat terlebih dahulu sebelum digunakan?

Seperti yang kami sebutkan di bab lua-resty-core sebelumnya, API OpenResty baru diimplementasikan di repositori lua-rety-core dengan cara FFI, sehingga ada perasaan terfragmentasi. Saya berharap masalah ini dapat diselesaikan di masa depan dengan menggabungkan proyek lua-nginx-module dan lua-resty-core.

Permintaan

Selanjutnya, mari kita lihat bagaimana OpenResty menangani permintaan dan respons klien. Pertama, mari kita lihat API untuk menangani permintaan, tetapi ada lebih dari 20 API yang dimulai dengan ngx.req, jadi bagaimana kita memulainya?

Kita tahu bahwa pesan permintaan HTTP terdiri dari tiga bagian: baris permintaan, header permintaan, dan badan permintaan, jadi saya akan memperkenalkan API dalam tiga bagian ini.

Baris Permintaan

Pertama adalah baris permintaan, yang berisi metode permintaan, URI, dan versi protokol HTTP. Di NGINX, Anda bisa mendapatkan nilai ini menggunakan variabel bawaan, sementara di OpenResty, ini sesuai dengan API ngx.var.*. Mari kita lihat dua contoh.

  • Variabel bawaan $scheme, yang mewakili nama protokol di NGINX, adalah http atau https; di OpenResty, Anda dapat menggunakan ngx.var.scheme untuk mengembalikan nilai yang sama.
  • $request_method mewakili metode permintaan seperti GET, POST, dll.; di OpenResty, Anda dapat mengembalikan nilai yang sama melalui ngx.var.request_method.

Anda dapat mengunjungi dokumentasi resmi NGINX untuk mendapatkan daftar lengkap variabel bawaan NGINX: http://nginx.org/en/docs/http/ngx_http_core_module.html#variables.

Jadi pertanyaannya muncul: mengapa OpenResty menyediakan API terpisah untuk baris permintaan ketika Anda bisa mendapatkan data dalam baris permintaan dengan mengembalikan nilai variabel seperti ngx.var.*?

Hasilnya mengandung banyak faktor:

  • Pertama, tidak disarankan untuk membaca ngx.var berulang kali karena kinerjanya tidak efisien.
  • Kedua, dari pertimbangan aspek yang ramah program, ngx.var mengembalikan string, bukan objek Lua. Ini sulit ditangani ketika mendapatkan args, yang mungkin mengembalikan banyak nilai.
  • Ketiga, dari aspek fleksibilitas, sebagian besar ngx.var hanya dapat dibaca, dan hanya beberapa variabel yang dapat ditulis, seperti $args dan limit_rate. Namun, kita sering perlu memodifikasi metode, URI, dan args.

Oleh karena itu, OpenResty menyediakan beberapa API khusus untuk memanipulasi baris permintaan, yang dapat menulis ulang baris permintaan untuk operasi selanjutnya seperti pengalihan.

Mari kita lihat bagaimana mendapatkan nomor versi protokol HTTP melalui API. API OpenResty ngx.req.http_version melakukan hal yang sama dengan variabel NGINX $server_protocol: mengembalikan nomor versi protokol HTTP. Namun, nilai kembalian API ini bukan string tetapi dalam format numerik, nilai yang mungkin adalah 2.0, 1.0, 1.1, dan 0.9. Nil dikembalikan jika hasilnya di luar rentang nilai ini.

Mari kita lihat metode mendapatkan permintaan dalam baris permintaan. Seperti yang disebutkan, peran dan nilai kembalian ngx.req.get_method dan variabel NGINX $request_method adalah sama: dalam format string.

Namun, format parameter dari metode permintaan HTTP saat ini ngx.req.set_method bukan string tetapi konstanta numerik bawaan. Misalnya, kode berikut menulis ulang metode permintaan menjadi POST.

ngx.req.set_method(ngx.HTTP_POST)

Untuk memverifikasi bahwa konstanta bawaan, ngx.HTTP_POST memang angka dan bukan string, Anda dapat mencetak nilainya dan melihat apakah outputnya adalah 8.

resty -e 'print(ngx.HTTP_POST)'

Dengan cara ini, nilai kembalian dari metode get adalah string, sementara nilai input dari metode set adalah angka. Ini tidak masalah ketika metode set melewati nilai yang membingungkan karena API bisa crash dan melaporkan kesalahan 500. Namun, dalam logika penilaian berikut:

if (ngx.req.get_method() == ngx.HTTP_POST) then -- do something end

Jenis kode ini bekerja dengan baik, tidak melaporkan kesalahan, dan sulit ditemukan bahkan selama tinjauan kode. Saya pernah membuat kesalahan serupa sebelumnya dan masih mengingatnya: saya sudah melalui dua putaran tinjauan kode dan kasus uji yang tidak lengkap untuk mencoba menutupinya. Pada akhirnya, anomali lingkungan online melacak saya ke masalah tersebut.

Tidak ada cara praktis untuk menyelesaikan masalah seperti ini kecuali lebih berhati-hati atau menambahkan lapisan enkapsulasi lain. Ketika Anda merancang API bisnis Anda, Anda juga dapat mempertimbangkan dan menjaga format parameter yang konsisten dari metode get dan set, bahkan jika Anda perlu mengorbankan beberapa kinerja.

Selain itu, di antara metode untuk menulis ulang baris permintaan, ada dua API, ngx.req.set_uri dan ngx.req.set_uri_args, yang dapat digunakan untuk menulis ulang URI dan args. Mari kita lihat konfigurasi NGINX ini.

rewrite ^ /foo?a=3? break;

Jadi, bagaimana kita bisa menyelesaikannya dengan API Lua yang setara? Jawabannya adalah dua baris kode berikut.

ngx.req.set_uri_args("a=3") ngx.req.set_uri("/foo")

Jika Anda telah membaca dokumentasi resmi, Anda akan menemukan bahwa ngx.req.set_uri memiliki parameter kedua: jump, yang secara default adalah "false". Jika Anda mengaturnya sebagai "true", itu sama dengan mengatur flag perintah rewrite ke last alih-alih break dalam contoh di atas.

Namun, saya tidak terlalu menyukai konfigurasi flag dari perintah rewrite karena tidak mudah dibaca dan dikenali serta jauh kurang intuitif dan mudah dipelihara daripada kode.

Header Permintaan

Seperti yang kita tahu, header permintaan HTTP dalam format key : value, misalnya:

Accept: text/css,*/*;q=0.1 Accept-Encoding: gzip, deflate, br

Di OpenResty, Anda dapat menggunakan ngx.req.get_headers untuk memparsing dan mendapatkan header permintaan, dan tipe nilai kembalian adalah tabel.

local h, err = ngx.req.get_headers() if err == "truncated" then -- one can choose to ignore or reject the current request here end for k, v in pairs(h) do ... end

Secara default, ini akan mengembalikan 100 header pertama. Jika jumlahnya melebihi 100, itu akan melaporkan kesalahan truncated, membiarkan pengembang memutuskan bagaimana menanganinya. Anda mungkin bertanya-tanya mengapa mengambil cara ini, yang akan saya sebutkan nanti di bagian tentang kerentanan keamanan.

Namun, kita harus mencatat bahwa OpenResty tidak menyediakan API khusus untuk mendapatkan header permintaan tertentu, yang berarti tidak ada bentuk seperti ngx.req.header['host']. Jika Anda memiliki kebutuhan seperti itu, Anda harus mengandalkan variabel NGINX $http_xxx untuk mencapainya. Kemudian di OpenResty, Anda bisa mendapatkannya dengan ngx.var.http_xxx.

Sekarang mari kita lihat bagaimana kita harus menulis ulang dan menghapus header permintaan. API untuk kedua operasi ini cukup intuitif:

ngx.req.set_header("Content-Type", "text/css") ngx.req.clear_header("Content-Type")

Tentu saja, dokumentasi resmi juga menyebutkan metode lain untuk menghapus header permintaan, seperti mengatur nilai judul ke nil, dll. Namun, saya masih merekomendasikan menggunakan clear_header untuk melakukannya secara seragam demi kejelasan kode.

Badan Permintaan

Terakhir, mari kita lihat badan permintaan. Untuk alasan kinerja, OpenResty tidak secara aktif membaca badan permintaan kecuali Anda memaksa mengaktifkan direktif lua_need_request_body di nginx.conf. Selain itu, untuk badan permintaan yang lebih besar, OpenResty menyimpan konten ke file sementara di disk, sehingga seluruh proses membaca badan permintaan terlihat seperti ini.

ngx.req.read_body() local data = ngx.req.get_body_data() if not data then local tmp_file = ngx.req.get_body_file() -- io.open(tmp_file) -- ... end

Kode ini memiliki operasi IO-blocking untuk membaca file disk. Anda harus menyesuaikan konfigurasi client_body_buffer_size (16 KB secara default pada sistem 64-bit) untuk meminimalkan operasi blocking; Anda juga dapat mengonfigurasi client_body_buffer_size dan client_max_body_size menjadi sama dan menanganinya sepenuhnya dalam memori, tergantung pada ukuran memori Anda dan jumlah permintaan bersamaan yang Anda tangani.

Selain itu, badan permintaan dapat ditulis ulang. Dua API ngx.req.set_body_data dan ngx.req.set_body_file menerima string dan file disk lokal sebagai parameter input untuk menyelesaikan penulisan ulang badan permintaan. Namun, jenis operasi ini tidak umum, dan Anda dapat memeriksa dokumentasi untuk detail lebih lanjut.

Respons

Setelah permintaan diproses, kita perlu mengirim respons kembali ke klien. Seperti pesan permintaan, pesan respons juga terdiri dari beberapa bagian: baris status, header respons, dan badan respons. Saya akan memperkenalkan API yang sesuai menurut tiga bagian ini.

Baris Status

Hal utama yang kita perhatikan dalam baris status adalah kode status. Secara default, kode status HTTP yang dikembalikan adalah 200, yang merupakan konstanta ngx.HTTP_OK yang dibangun dalam OpenResty. Tetapi dalam dunia kode, selalu ada kode yang menangani kasus yang paling luar biasa.

Jika Anda mendeteksi pesan permintaan dan menemukan bahwa itu adalah permintaan jahat, maka Anda perlu menghentikan permintaan:

ngx.exit(ngx.HTTP_BAD_REQUEST)

Namun, ada konstanta khusus dalam kode status HTTP OpenResty: ngx.OK. Dalam situasi ngx.exit(ngx.OK), permintaan keluar dari fase pemrosesan saat ini dan beralih ke tahap berikutnya alih-alih kembali langsung ke klien.

Tentu saja, Anda juga dapat memilih untuk tidak keluar dan hanya menulis ulang kode status menggunakan ngx.status, seperti yang ditulis dalam cara berikut.

ngx.status = ngx.HTTP_FORBIDDEN

Anda dapat mencarinya di dokumentasi jika Anda ingin tahu lebih banyak tentang konstanta kode status.

Header Respons

Mengenai header respons, ada dua cara Anda dapat mengaturnya. Yang pertama adalah yang paling sederhana.

ngx.header.content_type = 'text/plain' ngx.header["X-My-Header"] = 'blah blah' ngx.header["X-My-Header"] = nil -- hapus

Di sini ngx.header menyimpan informasi header respons, yang dapat dibaca, dimodifikasi, dan dihapus.

Cara kedua untuk mengatur header respons adalah ngx_resp.add_header, dari repositori lua-resty-core, yang menambahkan pesan header, dipanggil dengan:

local ngx_resp = require "ngx.resp" ngx_resp.add_header("Foo", "bar")

Perbedaan dengan metode pertama adalah bahwa add_header tidak menimpa bidang yang sudah ada dengan nama yang sama.

Badan Respons

Terakhir, lihat badan respons. Di OpenResty, Anda dapat menggunakan ngx.say dan ngx.print untuk mengeluarkan badan respons.

ngx.say('hello, world')

Fungsi dari dua API ini identik, satu-satunya perbedaan adalah bahwa ngx.say memiliki baris baru di akhir.

Untuk menghindari ketidakefisienan penggabungan string, ngx.say / ngx.print mendukung string dan format array sebagai parameter.

$ resty -e 'ngx.say({"hello", ", ", "world"})' hello, world

Metode ini melewati penggabungan string di tingkat Lua dan membiarkannya ditangani oleh fungsi C.

Ringkasan

Mari kita tinjau konten hari ini. Kami memperkenalkan API OpenResty yang terkait dengan pesan permintaan dan respons. Seperti yang Anda lihat, API OpenResty lebih fleksibel dan kuat daripada direktif NGINX.

Akibatnya, apakah API Lua yang disediakan oleh OpenResty cukup untuk memenuhi kebutuhan Anda ketika Anda menangani permintaan HTTP? Silakan tinggalkan komentar Anda dan bagikan artikel ini dengan kolega dan teman Anda sehingga kita dapat berkomunikasi dan meningkatkan bersama.