Pengenalan API Umum dalam OpenResty
API7.ai
November 4, 2022
Pada artikel sebelumnya, Anda telah mengenal banyak API Lua penting di OpenResty. Hari ini, kita akan mempelajari beberapa API umum lainnya, terutama yang terkait dengan ekspresi reguler, waktu, proses, dll.
API Terkait Ekspresi Reguler
Mari kita mulai dengan melihat ekspresi reguler yang paling sering digunakan dan paling penting. Di OpenResty, kita harus menggunakan set API yang disediakan oleh ngx.re.* untuk menangani logika yang terkait dengan ekspresi reguler, alih-alih menggunakan pencocokan pola Lua. Ini bukan hanya untuk alasan kinerja, tetapi juga karena regularitas Lua bersifat mandiri dan bukan spesifikasi PCRE, yang akan menyulitkan bagi sebagian besar pengembang.
Pada artikel sebelumnya, Anda telah mengenal beberapa API ngx.re.*, dokumentasinya sangat detail. Jadi, saya tidak akan mendaftarnya lagi. Di sini, saya akan memperkenalkan dua API berikut secara terpisah.
ngx.re.split
Yang pertama adalah ngx.re.split. Pemotongan string adalah fungsi yang sangat umum, dan OpenResty juga menyediakan API yang sesuai, tetapi banyak pengembang tidak dapat menemukan fungsi tersebut dan memilih untuk mengimplementasikannya sendiri.
Mengapa? API ngx.re.split tidak ada di lua-nginx-module tetapi di lua-resty-core; tidak ada di dokumentasi beranda lua-resty-core tetapi di dokumentasi direktori tingkat ketiga lua-resty-core/lib/ngx/re.md. Akibatnya, banyak pengembang sama sekali tidak menyadari keberadaan API ini.
Demikian pula, API yang sulit ditemukan termasuk ngx_resp.add_header, enable_privileged_agent, dll., yang telah kita sebutkan sebelumnya. Jadi, bagaimana kita dapat dengan cepat menyelesaikan masalah ini? Selain membaca dokumentasi beranda lua-resty-core, Anda perlu membaca dokumentasi *.md di direktori lua-resty-core/lib/ngx/ juga.
lua_regex_match_limit
Kedua, saya ingin memperkenalkan lua_regex_match_limit. Kami belum membahas perintah NGINX yang disediakan oleh OpenResty sebelumnya karena, dalam kebanyakan kasus, nilai default sudah cukup, dan tidak perlu memodifikasinya saat runtime. Pengecualian untuk ini adalah perintah lua_regex_match_limit, yang terkait dengan ekspresi reguler.
Kita tahu bahwa jika kita menggunakan mesin reguler yang diimplementasikan berdasarkan backtracking NFA, maka ada risiko Catastrophic Backtracking, di mana reguler terlalu banyak melakukan backtracking saat mencocokkan, menyebabkan CPU menjadi 100% dan layanan terblokir.
Setelah backtrace katastropik terjadi, kita perlu menggunakan gdb untuk menganalisis dump atau menggunakan systemtap untuk menganalisis lingkungan online untuk melokalisasinya. Sayangnya, mendeteksinya sebelumnya tidak mudah karena hanya permintaan khusus yang akan memicunya. Ini memungkinkan penyerang memanfaatkan ini, dan ReDoS (RegEx Denial of Service) mengacu pada jenis serangan ini.
Di sini, saya terutama memperkenalkan cara menggunakan baris kode berikut di OpenResty untuk menghindari masalah di atas dengan sederhana dan efektif:
lua_regex_match_limit digunakan untuk membatasi jumlah backtracking oleh mesin reguler PCRE. Dengan cara ini, bahkan jika terjadi backtracking katastropik, konsekuensinya akan dibatasi dalam rentang yang tidak akan membuat CPU Anda penuh.
lua_regex_match_limit 100000;
API Terkait Waktu
API waktu yang paling sering digunakan adalah ngx.now, yang mencetak timestamp saat ini, seperti baris kode berikut:
resty -e 'ngx.say(ngx.now())'
Seperti yang dapat Anda lihat dari hasil cetakan, ngx.now mencakup bagian pecahan, sehingga lebih akurat. API terkait ngx.time hanya mengembalikan bagian integer dari nilai tersebut. Yang lainnya, ngx.localtime, ngx.utctime, ngx.cookie_time, dan ngx.http_time terutama digunakan untuk mengembalikan dan memproses waktu dalam format yang berbeda. Jika Anda ingin menggunakannya, Anda dapat memeriksa dokumentasinya, mereka tidak sulit dipahami, jadi saya tidak perlu membahasnya.
Namun, perlu disebutkan bahwa API ini yang mengembalikan waktu saat ini, jika tidak dipicu oleh operasi IO jaringan non-blocking, akan selalu mengembalikan nilai yang di-cache daripada waktu real-time saat ini seperti yang kita inginkan. Lihatlah contoh kode berikut:
$ resty -e 'ngx.say(ngx.now()) os.execute("sleep 1") ngx.say(ngx.now())'
Di antara dua panggilan ke ngx.now, kami menggunakan fungsi blocking Lua untuk tidur selama 1 detik, tetapi timestamp yang dikembalikan sama pada kedua kesempatan, seperti yang ditunjukkan oleh hasil cetakan.
Jadi, bagaimana jika kita menggantinya dengan fungsi tidur non-blocking? Misalnya, kode baru berikut:
$ resty -e 'ngx.say(ngx.now()) ngx.sleep(1) ngx.say(ngx.now())'
Ini akan mencetak timestamp yang berbeda. Ini membawa kita ke ngx.sleep, fungsi tidur non-blocking. Selain tidur selama waktu yang ditentukan, fungsi ini memiliki tujuan khusus lainnya.
Misalnya, jika Anda memiliki sepotong kode yang melakukan perhitungan intensif, yang memakan banyak waktu, permintaan yang sesuai dengan kode ini akan terus mengambil alih sumber daya worker dan CPU selama waktu ini, menyebabkan permintaan lain mengantri dan tidak mendapatkan respons tepat waktu. Pada titik ini, kita dapat menyisipkan ngx.sleep(0) untuk membuat kode ini melepaskan kontrol sehingga permintaan lain juga dapat diproses.
API Worker dan Proses
OpenResty menyediakan API ngx.worker.* dan ngx.process.* untuk mendapatkan informasi tentang worker dan proses. Yang pertama terkait dengan proses worker Nginx, sedangkan yang kedua mengacu pada semua proses Nginx secara umum, tidak hanya proses worker, tetapi juga proses master, proses istimewa, dan sebagainya.
Masalah Nilai true dan null
Terakhir, mari kita lihat masalah nilai true dan null. Di OpenResty, penentuan nilai true dan null telah menjadi titik yang sangat merepotkan dan membingungkan.
Mari kita lihat definisi nilai true di Lua: kecuali nil dan false, semuanya adalah nilai true.
Jadi, nilai true juga akan mencakup 0, string kosong, table kosong, dll.
Mari kita lihat nil di Lua, yang berarti undefined. Misalnya, jika Anda mendeklarasikan variabel tetapi belum menginisialisasinya, nilainya adalah nil.
$ resty -e 'local a ngx.say(type(a))'
Dan nil juga merupakan tipe data di Lua. Setelah memahami dua poin ini, mari kita lihat masalah lain yang berasal dari dua definisi ini.
ngx.null
Masalah pertama adalah ngx.null. Karena nil Lua tidak dapat digunakan sebagai nilai table, OpenResty memperkenalkan ngx.null sebagai nilai null dalam tabel.
$ resty -e 'print(ngx.null)' null
$ resty -e 'print(type(ngx.null))' userdata
Seperti yang dapat Anda lihat dari dua potong kode di atas, ngx.null dicetak sebagai null, dan tipenya adalah userdata, jadi apakah itu dapat diperlakukan sebagai nilai false? Tentu saja tidak. Nilai boolean dari ngx.null adalah true.
$ resty -e 'if ngx.null then ngx.say("true") end'
Jadi, ingatlah bahwa hanya nil dan false yang merupakan nilai false. Jika Anda melewatkan poin ini, sangat mudah untuk terjebak dalam jebakan, misalnya, ketika Anda menggunakan lua-resty-redis dan membuat penilaian berikut:
local res, err = red:get("dog") if not res then res = res + "test" end
Jika nilai kembalian res adalah nil, panggilan fungsi telah gagal; jika res adalah ngx.null, kunci dog tidak ada di redis, maka kode akan crash jika kunci dog tidak ada.
cdata:NULL
Masalah kedua adalah cdata:NULL. Ketika Anda memanggil fungsi C melalui antarmuka LuaJIT FFI, dan fungsi mengembalikan pointer NULL, maka Anda akan menemukan jenis nilai null lain, cdata:NULL.
$ resty -e 'local ffi = require "ffi" local cdata_null = ffi.new("void*", nil) if cdata_null then ngx.say("true") end'
Seperti ngx.null, cdata:NULL juga true. Tetapi yang lebih membingungkan adalah bahwa kode berikut, yang mencetak true, berarti bahwa cdata:NULL setara dengan nil.
$ resty -e 'local ffi = require "ffi" local cdata_null = ffi.new("void*", nil) ngx.say(cdata_null == nil)'
Jadi, bagaimana kita harus menangani ngx.null dan cdata:NULL? Bukan solusi yang baik untuk membiarkan lapisan aplikasi peduli dengan masalah ini. Lebih baik melakukan pembungkusan tingkat kedua dan tidak membiarkan pemanggil mengetahui detail ini.
Lebih baik melakukan pembungkusan tingkat kedua dan tidak membiarkan pemanggil mengetahui detail ini.
cjson.null
Terakhir, mari kita lihat nilai null yang muncul di cjson. Pustaka cjson mengambil NULL dalam json, mendekodenya menjadi lightuserdata Lua, dan menggunakan cjson.null untuk mewakilinya.
$ resty -e 'local cjson = require "cjson" local data = cjson.encode(nil) local decode_null = cjson.decode(data) ngx.say(decode_null == cjson.null)'
nil Lua menjadi cjson.null setelah dienkode dan didekode oleh JSON. Seperti yang dapat Anda bayangkan, ini diperkenalkan untuk alasan yang sama dengan ngx.null, karena nil tidak dapat digunakan sebagai nilai dalam table.
Sejauh ini, apakah Anda bingung dengan begitu banyak jenis nilai null di OpenResty? Jangan khawatir. Baca bagian ini beberapa kali dan susun sendiri, maka Anda tidak akan bingung. Tentu saja, kita perlu berpikir lebih banyak di masa depan tentang apakah itu berfungsi ketika menulis sesuatu seperti if not foo then.
Ringkasan
Artikel hari ini memperkenalkan Anda pada API Lua yang umum digunakan di OpenResty.
Terakhir, saya akan meninggalkan Anda dengan pertanyaan: Dalam contoh ngx.now, mengapa nilai ngx.now tidak dimodifikasi ketika tidak ada operasi yield? Silakan bagikan pendapat Anda di komentar, dan juga silakan bagikan artikel ini sehingga kita dapat berkomunikasi dan meningkatkan bersama.