OpenResty (簡稱OR) 是一個基於 Nginx 與 Lua 的高性能 Web 平臺,其內部集成了大量精良的Lua Api,第三方模塊以及經常使用的依賴項,基於這些能力,咱們能夠利用OR快速方便的搭建可以處理超高併發的,極具動態性和擴展的Web應用、Web服務和動態網關。html
這篇文章選擇OR中具備重要意義的一些模塊、命令、Api和框架介紹以下:mysql
OR 做爲一款 Web 服務器,提供瞭如:nginx
等函數和方法控制http請求的輸入和輸出,咱們能夠經過這些 api 靈活的控制 Web Server 中包括輸入輸出,轉發到上游或者發起子請求在內的各個環節。git
同ngx.say(), 經過該 api 能夠指定返回的請求的消息體,其中每一次函數的調用,都是在nginx的輸出鏈上增長了一個結點,因此咱們能夠屢次調用該函數而不用擔憂後者會覆蓋前者,如:github
location =/hello { content_by_lua_block { ngx.print("Hello") ngx.print(" ") ngx.print("World!") } } GET /hello 將會獲得以下結果 ====== Hello World!
ngx.exit,經過該 api能夠指定請求響應體的 http status, 如:redis
location =/error { content_by_lua_block { return ngx.exit(500) } } GET /error ====== <html> <head><title>500 Internal Server Error</title></head> <body bgcolor="white"> <center><h1>500 Internal Server Error</h1></center> <hr><center>openresty</center> </body> </html>
當咱們訪問 /error 接口的時候,能夠獲得一個默認的 500 頁面,如上所示,固然咱們能夠經過ngx.status + ngx.print 的組合方式獲得自定義的錯誤頁面,如:sql
location =/error { content_by_lua_block { ngx.status = 500 ngx.print("error here!") return ngx.exit(500) } } GET /erro ====== error here!
以上方式一樣適用於其它錯誤,如 40四、50二、503 等頁面,經過這樣的方式咱們能夠更靈活的控制當這些錯誤,從而返回更友好的頁面或者其它輸出,固然你也可用使用 nginx 原生的 error_page 指令對錯誤頁面進行控制,只是靈活性相對差一些數據庫
在OR內部經過 nginx 事件機制實現的定時器,咱們能夠經過它來實現延遲運行的任務邏輯,甚至於經過一些特殊的調用方法實現定時任務的功能,好比:json
local delay = 5 local handler handler = function (premature) -- do some routine job in Lua just like a cron job if premature then return end local ok, err = ngx.timer.at(delay, handler) if not ok then ngx.log(ngx.ERR, "failed to create the timer: ", err) return end end local ok, err = ngx.timer.at(delay, handler) if not ok then ngx.log(ngx.ERR, "failed to create the timer: ", err) return end
能夠看到咱們給ngx.timer.at的第二個參數是這個函數體自己,經過這樣的寫法,咱們能夠實現每一個delay的間隔執行一次handler
固然這種「不友好」的技巧在v0.10.9這個版本以後被ngx.timer.every給「優化」了(上述技巧依舊有效)。api
固然除此以外,這個API還有其它「非凡」的意義。
OR的各個API其實都有其「生命週期」或者說做用域,好比其中很是重要的cosocket,它的做用域以下
rewrite_by_lua , access_by_lua, content_by_lua , ngx.timer., ssl_certificate_by_lua , ssl_session_fetch_by_lua
也就是說咱們只能在上述階段使用 cosocket,因爲不少第三方組件的實現都是基於它,好比
resty-redis,resty-memcached,resty-mysql
因此 cosocket 的做用域就等同於上述這些依賴它的模塊的做用域
咱們再來看看ngx.timer.at的做用域
init_worker_by_lua , set_by_lua, rewrite_by_lua , access_by_lua, content_by_lua*,
header_filter_by_lua , body_filter_by_lua, log_by_lua , ngx.timer., balancer_by_lua*,
ssl_certificate_by_lua , ssl_session_fetch_by_lua, ssl_session_store_by_lua*
這意味着,若是咱們但願在 cosocket 不支持的做用域內使用它,那麼咱們能夠經過 ngx.timer.at 的方式橋接,完成「跨域」, 好比:
init_worker_by_lua_block { local handler = function() local redis = require "resty.redis" redc = redis:new(anyaddre) redc:set('foo', 'bar') end ngx.timer.at(0, handler) }
這就是前文所說的,這個API的」非凡「意義,固然我更指望的是 cosocket 可以支持更多的做用域 ^_^!
一般來講,咱們會將一些系統的配置寫入lua代碼中,經過require的方式在不一樣worker之間共享,這種看起來像是跨worker的共享方式實際上是利用了」一個模塊只加載一次「的特性(lua_code_cache on), 可是這種方式只適合於一些只讀數據的共享,並不適合一些可寫的(嚴格來講, 能夠經過一些技術手段實現這個級別的緩存,因爲能實現table類型的緩存,它的效率並不差,這裏不作討論),或者動態變化的數據,好比從關係型數據庫、kv數據庫,上游、第三方服務器獲取的數據的緩存。
ngx.shared.DICT 方法集提供瞭解決跨 worker 且自帶互斥鎖的共享數據方案,配合 cjson 或者 resty-dkjson 這樣的 json 解析庫,極大豐富了這個API集合的應用空間,須要注意的是:
- 它須要在nginx的配置文件中被預先定義好大小且不可動態擴容, 如: lua_shared_dict cache 16m;
- 儘量的使用 cjson 而非 dkjson, 它們之間效率相差 50 倍以上;
- 它是一個內存數據集,並不具有持久化的能力;
- 寧願定義多個容量更小的存儲體,而非一個更大的;
nginx 是支持SNI的,因此咱們能夠在一臺主機上爲同一個IP、不一樣域名的「租戶」綁定不一樣的證書,你只須要在nginx的配置中設置多個server block, 併爲每一個 block 配置server_name以及指定其證書和私鑰便可;
http { server { listen 443 ssl; server_name www.foo.com; ssl_certificate cert/foo.pem; ssl_certificate_key cert/foo.key; location / { proxy_pass http://foo; } } server { listen 443 ssl; server_name www.bar.com; ssl_certificate cert/bar.pem; ssl_certificate_key cert/bar.key; location / { proxy_pass http://bar; } } }
雖然上述方式可行,可是在更新證書的時候就會顯得很是麻煩,特別是當你擁有多個主機且每一個主機上不止一個證書的時候,證書的逐個替換,nginx 服務的重啓都是很是麻煩且容易出錯的步驟,即使是經過自動化的代碼完成上述步驟,可是多個主機的更換證書的協調性問題依然不容忽視。
ssl_certificate_by_lua_xxx 確實提供了一種更優雅的方式
前文提到的 cosocket 在 ssl_certificate_by_lua_xxx 階段是被支持的,因此咱們能夠在這個階段動態的爲終端加載其對應的證書,如:
server { listen 443 ssl; server_name test.com; # 雖然咱們能夠動態的加載證書,可是爲了不nginx報錯,這裏須要配置用於佔位的證書和私鑰 ssl_certificate placeholder.crt; ssl_certificate_key placeholder.key; ssl_certificate_by_lua_block { local ssl = require "ngx.ssl" -- 清理掉當前的證書,爲後續加載證書作準備 local ok, err = ssl.clear_certs() if not ok then ngx.log(ngx.ERR, "clear current cert err:", err) return ngx.exit(500) end -- x509 格式的證書有兩種存儲格式 PEM 和 DER,這裏只描述PEM格式 -- 獲取 Server Name Indication 簡稱(SNI) local sni = ssl.server_name() -- 這裏咱們假設已經經過cosocket實現了一個從數據庫獲取證書的函數 -- 該函數以sni爲索引查詢對應的證書並返回 local cert, err = get_pem_format_cert_by_server_name(sni) -- cert_of_x509 是一個cdata類型的數據 local cert_of_x509, err = ssl.parse_pem_cert(cert) local ok, err = ssl.set_cert(cert_of_x509) if not ok then ngx.log(ngx.ERR, "set cert failed, err:", err) return ngx.exit(500) end --- 這裏還須要設置對應的私鑰,相關函數請參考以下 } }
ssl.parse_pem_cert
ssl. parse_pem_priv_key
經過上述方式,咱們能夠經過直接修改存儲介質中某個域名對應的證書,從而實現多節點的證書和私鑰替換,更進階一些,咱們甚至能夠經過一些技術手段讓「租戶或者用戶」本身保留私鑰,即實現所謂的key-less,這個咱們後面文章再講。
待續
待續