在nginx中集成lua腳本:添加自定義Http頭,封IP等


Lua是一個可以嵌入到Nginx配置文件中的動態腳本語言,從而可以在Nginx請求處理的任何階段執行各種Lua代碼。剛開始我們只是用Lua 把請求路由到后端服務器,但是它對我們架構的作用超出了我們的預期。下面就講講我們所做的工作。

強制搜索引擎只索引mixlr.com

Google把子域名當作完全獨立的網站,我們不希望爬蟲抓取子域名的頁面,降低我們的Page rank。

location /{ header_filter_by_lua ' if ngx.var.query_string and ngx.re.match( ngx.var.query_string, "^([0-9]{10})$" ) then ngx.header["Expires"] = ngx.http_time( ngx.time() + 31536000 ); ngx.header["Cache-Control"] = "max-age=31536000"; end ';

如果對robots.txt的請求不是mixlr.com域名的話,則內部重寫到robots_diallow.txt,雖然標准的重寫指令也可以實現這個需求,但是 Lua的實現更容易理解和維護。

根據程序邏輯設置響應頭

Lua提供了比Nginx默認配置規則更加靈活的設置方式。 在下面的例子中,我們要保證正確設置響應頭,這樣瀏覽器如果發送了指定請求頭后,就可以 無限期緩存靜態文件,是的用戶只需下載一次即可。 這個重寫規則使得任何靜態文件,如果請求參數中包含時間戳值,那么就設置相應的Expires和Cache-Control響應頭。

location /{ header_filter_by_lua ' if ngx.var.query_string and ngx.re.match( ngx.var.query_string, "^([0-9]{10})$" ) then ngx.header["Expires"] = ngx.http_time( ngx.time() + 31536000 ); ngx.header["Cache-Control"] = "max-age=31536000"; end '; try_files $uri @dynamic;}

刪除jQuery JSONP請求的時間戳參數

很多外部客戶端請求JSONP接口時,都會包含一個時間戳類似的參數,從而導致Nginx proxy緩存無法命中(因為無法忽略指定的HTTP參數)。下面的 規則刪除了時間戳參數,使得Nginx可以緩存upstream server的響應內容,減輕后端服務器的負載。

location /{ rewrite_by_lua ' if ngx.var.args ~= nil then -- /some_request?_=1346491660 becomes /some_request local fixed_args, count = ngx.re.sub( ngx.var.args, "&?_=[0-9]+", "" ); if count > 0 then return ngx.exec(ngx.var.uri, fixed_args); end end ';}

把后端的慢請求日志記錄到Nginx的錯誤日志

如果后端請求響應很慢,可以把它記錄到Nginx的錯誤日志,以備后續追查。

location /{ log_by_lua ' if tonumber(ngx.var.upstream_response_time) >= 1 then ngx.log(ngx.WARN, "[SLOW] Ngx upstream response time: " .. ngx.var.upstream_response_time .. "s from " .. ngx.var.upstream_addr); end ';}

基於Redis的實時IP封禁

某些情況下,需要阻止流氓爬蟲的抓取,這可以通過專門的封禁設備去做,但是通過Lua,也可以實現簡單版本的封禁。

lua_shared_dict banned_ips 1m; location /{ access_by_lua ' local banned_ips = ngx.shared.banned_ips; local updated_at = banned_ips:get("updated_at"); -- only update banned_ips from Redis once every ten seconds: if updated_at == nil or updated_at < ( ngx.now() - 10 ) then local redis = require "resty.redis"; local red = redis:new(); red:set_timeout(200); local ok, err = red:connect("your-redis-hostname", 6379); if not ok then ngx.log(ngx.WARN, "Redis connection error retrieving banned_ips: " .. err); else local updated_banned_ips, err = red:smembers("banned_ips"); if err then ngx.log(ngx.WARN, "Redis read error retrieving banned_ips: " .. err); else -- replace the locally stored banned_ips with the updated values: banned_ips:flush_all(); for index, banned_ip in ipairs(updated_banned_ips) do banned_ips:set(banned_ip, true); end banned_ips:set("updated_at", ngx.now()); end end end if banned_ips:get(ngx.var.remote_addr) then ngx.log(ngx.WARN, "Banned IP detected and refused access: " .. ngx.var.remote_addr); return ngx.exit(ngx.HTTP_FORBIDDEN); end ';}

現在就可以阻止特定IP的訪問:

ruby> $redis.sadd("banned_ips","200.1.35.4")

Nginx進程每隔10秒從Redis獲取一次最新的禁止IP名單。需要注意的是,如果架構中使用了Haproxy這樣類似的負載均衡服務器時, 需要把$remote_addr設置為正確的遠端IP地址。

這個方法還可以用於HTTP User-Agent字段的檢查,要求滿足指定條件。

使用Nginx輸出CSRF(form_authenticity_token)

Mixlr大量使用頁面緩存,由此引入的一個問題是如何給每個頁面輸出會話級別的CSRF token。我們通過Nginx的子請求,從upstream web server 獲取token,然后利用Nginx的SSI(server-side include)功能輸出到頁面中。這樣既解決了CSRF攻擊問題,也保證了cache能被正常利用。

location /csrf_token_endpoint {internal; include /opt/nginx/conf/proxy.conf; proxy_pass "http://upstream";} location @dynamic{ ssi on;set $csrf_token ''; rewrite_by_lua ' -- Using a subrequest, we our upstream servers for the CSRF token for this session: local csrf_capture = ngx.location.capture("/csrf_token_endpoint"); if csrf_capture.status == 200 then ngx.var.csrf_token = csrf_capture.body; -- if this is a new session, ensure it sticks by passing through the new session_id -- to both the subsequent upstream request, and the response: if not ngx.var.cookie_session then local match = ngx.re.match(csrf_capture.header["Set-Cookie"], "session=([a-zA-Z0-9_+=/+]+);"); if match then ngx.req.set_header("Cookie", "session=" .. match[1]); ngx.header["Set-Cookie"] = csrf_capture.header["Set-Cookie"]; end end else ngx.log(ngx.WARN, "No CSRF token returned from upstream, ignoring."); end '; try_files /maintenance.html /rails_cache$uri @thin;}

CSRF token生成 app/metal/csrf_token_endpoint.rb:

classCsrfTokenEndpointdefself.call(env)if env["PATH_INFO"]=~/^\/csrf_token_endpoint/ session = env["rack.session"]||{} token = session[:_csrf_token]if token.nil? token =SecureRandom.base64(32) session[:_csrf_token]= token end[200,{"Content-Type"=>"text/plain"},[ token ]]else[404,{"Content-Type"=>"text/html"},["Not Found"]]end endend

我們的模版文件示例:

<metaname=”csrf-param”value=”authenticity_token”/> <meta name=”csrf-token” value=”<!–# echo var=”csrf_token” default=”” encoding=”none” –>”/>

Again you could make use of lua_shared_dict to store in memory the CSRF token for a particular session. This minimises the number of trips made to /csrf_token_endpoint.

原文鏈接:http://devblog.mixlr.com/2012/09/01/nginx-lua/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM