前言:
2012年2月章亦春(agentzh)在Tech-Club的一次線下聚會上以《由Lua 粘合的Nginx生態環境》爲主題作了演講,分析了企業Web架構的趨勢,即一個看起來完整的Web應用,每每在後臺被拆分紅多個Service,由多個部門分別實現,而每一個部門提供給其它部門的都是http協議resful形式的接口,隨後介紹了一些Nginx模塊,最後講到了將Lua嵌入到Nginx,對之因此採用Nginx+Lua來構建給出了緣由。
相關連接:
http://www.tech-club.org/?p=247
http://blog.zoomquiet.org/pyblosxom/oss/openresty-intro-2012-03-06-01-13.html
http://agentzh.org/misc/slides/ngx-openresty-ecosystem/#1
正文:
不久前,無心間看到春哥的這篇文章,也由於以前有了解過併發編程的一些概念,對Lua中的協程早有耳聞,因而花了幾天時間看了Lua的語法,被這個小巧的高性能的語言所吸引,因而決定一探究竟,爲方便實驗,便直接下載了OpenResty,編譯運行。下面與你們分享個人一些經驗。
這裏以我寫的開源防盜鏈系統(github地址https://github.com/Hevienz/nginx-lua-ds-hotlink)爲例,防盜鏈系統的實現有兩種方法,一種是檢查HTTP頭中的Referer字段,這種方法只能用於通常性的場合,第二種方法是爲特定的IP和文件生成accesskey,而後用戶經過特定的accesskey來訪問特定的文件。
首先,咱們在nginx配置中配置以下,以使應用開發人員能夠方便的取得accesskey。html
location = /get_key { allow 10.0.2.2; deny all; content_by_lua_file lua/ds_hotlink/get_key.lua; }
在accesskey.lua文件中調用hotlink_get_key() 函數,而此函數的實現放在init.lua之中,這樣此函數在nginx啓動時便被初始化,經測試,這種作法對性能的提高很明顯。
在init.lua中咱們實現hotlink_get_key()函數,以下:mysql
function hotlink_get_key() local path=ngx.var.arg_path if not path then ngx.exit(405) end local ip=ngx.var.arg_ip if not ip then ngx.exit(405) end local time=ngx.now() local string=time..path..ip local digest = ngx.hmac_sha1(config.secret_key, string) ngx.say(ngx.encode_base64(time..":"..digest)) end
經過ngx.var.arg_path和ngx.var.arg_ip來分別取得url中的path和ip兩個參數,而後基於時間生成散列值,附上時間信息後用base64編碼後返回給客戶端。
而後在須要防盜鏈的location下配置以下:nginx
access_by_lua_file lua/ds_hotlink/accesskey.lua;
在accesskey.lua中依然是調用在init.lua中初始化的hotlink_accesskey_module()函數,以下:git
function hotlink_accesskey_module() local key=ngx.var.arg_key if not key then log{module_name="HOTLINK_ACCESSKEY_MODULE"} ngx.exit(405) end local uri=ngx.var.request_uri local path=nil for i in string.gmatch(uri, "[^\\?]+") do path=i break end local user_ip=get_user_ip() local time_digest=ngx.decode_base64(key) if not time_digest then log{module_name="HOTLINK_ACCESSKEY_MODULE"} ngx.exit(405) end if not time_digest:match(":") then log{module_name="HOTLINK_ACCESSKEY_MODULE"} ngx.exit(405) end local tmp_dic_time_digest={} for i in string.gmatch(time_digest,"[^\\:]+") do table.insert(tmp_dic_time_digest,i) end local time=tmp_dic_time_digest[1] local digest=tmp_dic_time_digest[2] if not time or not tonumber(time) or not digest or time+config.expiration_time < ngx.now() then log{module_name="HOTLINK_ACCESSKEY_MODULE"} ngx.exit(405) end local string=time..path..user_ip local real_digest = ngx.hmac_sha1(config.secret_key, string) if digest ~=real_digest then log{module_name="HOTLINK_ACCESSKEY_MODULE"} ngx.exit(405) end return end
在url中取得參數key,用base64解碼後獲得時間和散列信息,而後用ngx.var.request_uri,用戶IP和解析獲得的時間來判斷散列值是否相匹配,而且在其中提供諸如超時時間和日誌的附屬功能,相關代碼比較簡單,貼在這裏:github
local config=require("config") fd = io.open(config.logs_pwd.."hotlink.log","ab") local function get_user_ip() local req_headers = ngx.req.get_headers() return (req_headers["X-Real-IP"] or req_headers["X_Forwarded_For"]) or ngx.var.remote_addr end local function log(args) local req_headers = ngx.req.get_headers() local time=ngx.localtime() local user_ip = get_user_ip() local method=ngx.req.get_method() local request_uri=ngx.var.request_uri local user_agent=ngx.var.http_user_agent or "-" local http_version=ngx.req.http_version() local header_refer=req_headers["Referer"] or "-" local key=ngx.var.arg_key or "-" local line = "["..args.module_name.."] "..user_ip.." ["..time.."] \""..method.." "..request_uri.." "..http_version.."\" \""..user_agent.."\" \""..header_refer.."\" \""..key.."\"\n" fd:write(line) fd:flush() end
其中的config模塊以下:redis
module("config") logs_pwd="/var/log/hotlink/" ----refer module---- white_domains={ "geekhub\\.cc", } ----accesskey module---- secret_key="naudw72h2iu34298dnawi81" expiration_time=3600
開發人員能夠修改其中的配置,如日誌路徑,refer模塊的域白名單,accesskey模塊的secret_key和超時時間。
可能有人會問這和Web開發有什麼關係,其實從這個開源軟件的開發過程就能夠擴展到resful形式的接口的開發。
Openresty提供瞭如下lib用於與上游的各類數據庫進行通訊:
lua-resty-memcached
lua-resty-mysql
lua-resty-redis
除此以外還有各類第三方的lib,如用於mongodb的bigplum/lua-resty-mongol等,這些lib都在其github頁上提供了例子和接口文檔,而將從數據庫中取得的數據格式化爲json格式,也是十分的方便,只需將相關的數據放到table(相似於Python中數組和字典)中,而後寫下以下的代碼:sql
local cjson = require "cjson" ngx.say("result: ", cjson.encode(res))
對於新手來講,golgote/lua-resty-info 是一個獲取OpenResty相關信息的好方法,它會提供重要的package.path和package.cpath的信息,還會列出一些變量,函數和模塊的信息。這是一個demo,http://www.kembox.com/lua-resty-info.htmlmongodb