Authored by root

新增lua

module("resty.http", package.seeall)
_VERSION = '0.2'
-- constants
-- connection timeout in seconds
local TIMEOUT = 60
-- default ports for document retrieval
local PORT = 80
local SSL_PORT = 443
-- user agent field sent in request
local USERAGENT = 'resty.http/' .. _VERSION
-- default url parts
local default = {
host = "",
path ="/",
scheme = "http"
}
-- global variables
local url = require("resty.url")
local mt = { __index = resty.http }
local tcp = ngx.socket.tcp
local base64 = ngx.encode_base64
local function adjusturi(reqt)
local u = reqt
-- if there is a proxy, we need the full url. otherwise, just a part.
if not reqt.proxy and not PROXY then
u = {
path = reqt.path,
params = reqt.params,
query = reqt.query,
fragment = reqt.fragment
}
end
return url.build(u)
end
local function adjustheaders(reqt)
-- default headers
local lower = {
["user-agent"] = USERAGENT,
["host"] = reqt.host,
["connection"] = "close, TE",
["te"] = "trailers"
}
-- if we have authentication information, pass it along
if reqt.user and reqt.password then
lower["authorization"] =
"Basic " .. (base64(reqt.user .. ":" .. reqt.password))
end
-- override with user headers
for i,v in pairs(reqt.headers or lower) do
lower[string.lower(i)] = v
end
return lower
end
local function adjustproxy(reqt)
local proxy = reqt.proxy or PROXY
if proxy then
proxy = url.parse(proxy)
return proxy.host, proxy.port or 3128
else
return reqt.host, reqt.port
end
end
local function adjustrequest(reqt)
-- parse url if provided
local nreqt = reqt.url and url.parse(reqt.url, default) or {}
-- explicit components override url
for i,v in pairs(reqt) do nreqt[i] = v end
if nreqt.port == nil or nreqt.port == "" then
if nreqt.scheme == "https" then
nreqt.port = SSL_PORT
else
nreqt.port = PORT
end
end
-- compute uri if user hasn't overriden
nreqt.uri = reqt.uri or adjusturi(nreqt)
-- ajust host and port if there is a proxy
nreqt.host, nreqt.port = adjustproxy(nreqt)
-- adjust headers in request
nreqt.headers = adjustheaders(nreqt)
nreqt.timeout = reqt.timeout or TIMEOUT * 1000;
nreqt.fetch_size = reqt.fetch_size or 16*1024 -- 16k
nreqt.max_body_size = reqt.max_body_size or 1024*1024*1024 -- 1024mb
if reqt.keepalive then
nreqt.headers['connection'] = 'keep-alive'
end
return nreqt
end
local function receivestatusline(sock)
local status_reader = sock:receiveuntil("\r\n")
local data, err, partial = status_reader()
if not data then
return nil, "read status line failed " .. err
end
local t1, t2, code = string.find(data, "HTTP/%d*%.%d* (%d%d%d)")
return tonumber(code), data
end
local function receiveheaders(sock, headers)
local line, name, value, err, tmp1, tmp2
headers = headers or {}
-- get first line
line, err = sock:receive()
if err then return nil, err end
-- headers go until a blank line is found
while line ~= "" do
-- get field-name and value
tmp1, tmp2, name, value = string.find(line, "^(.-):%s*(.*)")
if not (name and value) then return nil, "malformed reponse headers" end
name = string.lower(name)
-- get next line (value might be folded)
line, err = sock:receive()
if err then return nil, err end
-- unfold any folded values
while string.find(line, "^%s") do
value = value .. line
line = sock:receive()
if err then return nil, err end
end
-- save pair in table
if headers[name] then
if name == "set-cookie" then
headers[name] = headers[name] .. "," .. value
else
headers[name] = headers[name] .. ", " .. value
end
else headers[name] = value end
end
return headers
end
local function read_body_data(sock, size, fetch_size, callback)
local p_size = fetch_size
while size and size > 0 do
if size < p_size then
p_size = size
end
local data, err, partial = sock:receive(p_size)
if not err then
if data then
callback(data)
end
elseif err == "closed" then
if partial then
callback(partial)
end
return 1 -- 'closed'
else
return nil, err
end
size = size - p_size
end
return 1
end
local function receivebody(sock, headers, nreqt)
local t = headers["transfer-encoding"] -- shortcut
local body = {} -- data chunks of response body
local callback = nreqt.body_callback
if not callback then
local function bc(data, chunked_header, ...)
if chunked_header then return end
body[#body+1] = data
end
callback = bc
end
if t and t ~= "identity" then
-- chunked
while true do
local chunk_header = sock:receiveuntil("\r\n")
local data, err, partial = chunk_header()
if not data then
return nil,err
else
if data == "0" then
return table.concat(body) -- end of chunk
else
local length = tonumber(data, 16)
-- TODO check nreqt.max_body_size !!
local ok, err = read_body_data(sock,length, nreqt.fetch_size, callback)
if err then
return nil,err
end
end
end
end
elseif headers["content-length"] ~= nil and tonumber(headers["content-length"]) >= 0 then
-- content length
local length = tonumber(headers["content-length"])
if length > nreqt.max_body_size then
ngx.log(ngx.INFO, 'content-length > nreqt.max_body_size !! Tail it !')
length = nreqt.max_body_size
end
local ok, err = read_body_data(sock,length, nreqt.fetch_size, callback)
if not ok then
return nil,err
end
else
-- connection close
local ok, err = read_body_data(sock,nreqt.max_body_size, nreqt.fetch_size, callback)
if not ok then
return nil,err
end
end
return table.concat(body)
end
local function shouldredirect(reqt, code, headers)
return headers.location and
string.gsub(headers.location, "%s", "") ~= "" and
(reqt.redirect ~= false) and
(code == 301 or code == 302) and
(not reqt.method or reqt.method == "GET" or reqt.method == "HEAD")
and (not reqt.nredirects or reqt.nredirects < 5)
end
local function shouldreceivebody(reqt, code)
if reqt.method == "HEAD" then return nil end
if code == 204 or code == 304 then return nil end
if code >= 100 and code < 200 then return nil end
return 1
end
function new(self)
return setmetatable({}, mt)
end
function request(self, reqt)
local code, headers, status, body, bytes, ok, err
local nreqt = adjustrequest(reqt)
local sock = tcp()
if not sock then
return nil, "create sock failed"
end
sock:settimeout(nreqt.timeout)
-- connect
ok, err = sock:connect(nreqt.host, nreqt.port)
if err then
return nil, "sock connected failed " .. err
end
-- check type of req_body, maybe string, file, function
local req_body = nreqt.body
local req_body_type = nil
if req_body then
req_body_type = type(req_body)
if req_body_type == 'string' then -- fixed Content-Length
nreqt.headers['content-length'] = #req_body
end
end
-- send request line and headers
local reqline = string.format("%s %s HTTP/1.1\r\n", nreqt.method or "GET", nreqt.uri)
local h = ""
for i, v in pairs(nreqt.headers) do
-- fix cookie is a table value
if type(v) == "table" then
if i == "cookie" then
v = table.concat(v, "; ")
else
v = table.concat(v, ", ")
end
end
h = i .. ": " .. v .. "\r\n" .. h
end
h = h .. '\r\n' -- close headers
-- @modify: add ssl support
if nreqt.scheme == 'https' then
local sess, err = sock:sslhandshake();
if err then
return nil, err;
end
end
bytes, err = sock:send(reqline .. h)
if err then
sock:close()
return nil, err
end
-- send req_body, if exists
if req_body_type == 'string' then
bytes, err = sock:send(req_body)
if err then
sock:close()
return nil, err
end
elseif req_body_type == 'file' then
local buf = nil
while true do -- TODO chunked maybe better
buf = req_body:read(8192)
if not buf then break end
bytes, err = sock:send(buf)
if err then
sock:close()
return nil, err
end
end
elseif req_body_type == 'function' then
err = req_body(sock) -- as callback(sock)
if err then
return err
end
end
-- receive status line
code, status = receivestatusline(sock)
if not code then
sock:close()
if not status then
return nil, "read status line failed "
else
return nil, "read status line failed " .. status
end
end
-- ignore any 100-continue messages
while code == 100 do
headers, err = receiveheaders(sock, {})
code, status = receivestatusline(sock)
end
-- notify code_callback
if nreqt.code_callback then
nreqt.code_callback(code)
end
-- receive headers
headers, err = receiveheaders(sock, {})
if err then
sock:close()
return nil, "read headers failed " .. err
end
-- notify header_callback
if nreqt.header_callback then
nreqt.header_callback(headers)
end
-- TODO rediret check
-- receive body
if shouldreceivebody(nreqt, code) then
body, err = receivebody(sock, headers, nreqt)
if err then
sock:close()
if code == 200 then
return 1, code, headers, status, nil
end
return nil, "read body failed " .. err
end
end
-- read CR/LF or LF otherwise thorw 'unread data in buffer' error
sock:receive("*l")
if nreqt.keepalive then
local ok, err = sock:setkeepalive(nreqt.keepalive)
if not ok then
ngx.log(ngx.WARN, "failed to set keepalive: " .. err)
end
else
sock:close()
end
return 1, code, headers, status, body
end
function proxy_pass(self, reqt)
local nreqt = {}
for i,v in pairs(reqt) do nreqt[i] = v end
if not nreqt.code_callback then
nreqt.code_callback = function(code, ...)
ngx.status = code
end
end
if not nreqt.header_callback then
nreqt.header_callback = function (headers, ...)
for i, v in pairs(headers) do
ngx.header[i] = v
end
end
end
if not nreqt.body_callback then
nreqt.body_callback = function (data, ...)
ngx.print(data) -- Will auto package as chunked format!!
end
end
return request(self, nreqt)
end
-- to prevent use of casual module global variables
getmetatable(resty.http).__newindex = function (table, key, val)
error('attempt to write to undeclared variable "' .. key .. '": '
.. debug.traceback())
end
... ...
-----------------------------------------------------------------------------
-- URI parsing, composition and relative URL resolution
-- LuaSocket toolkit.
-- Author: Diego Nehab
-- RCS ID: $Id: url.lua,v 1.38 2006/04/03 04:45:42 diego Exp $
-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
-- Declare module
-----------------------------------------------------------------------------
local string = require("string")
local base = _G
local table = require("table")
module("resty.url", package.seeall)
-----------------------------------------------------------------------------
-- Module version
-----------------------------------------------------------------------------
_VERSION = "URL 1.0.1"
-----------------------------------------------------------------------------
-- Encodes a string into its escaped hexadecimal representation
-- Input
-- s: binary string to be encoded
-- Returns
-- escaped representation of string binary
-----------------------------------------------------------------------------
function escape(s)
return string.gsub(s, "([^A-Za-z0-9_])", function(c)
return string.format("%%%02x", string.byte(c))
end)
end
-----------------------------------------------------------------------------
-- Protects a path segment, to prevent it from interfering with the
-- url parsing.
-- Input
-- s: binary string to be encoded
-- Returns
-- escaped representation of string binary
-----------------------------------------------------------------------------
local function make_set(t)
local s = {}
for i,v in base.ipairs(t) do
s[t[i]] = 1
end
return s
end
-- these are allowed withing a path segment, along with alphanum
-- other characters must be escaped
local segment_set = make_set {
"-", "_", ".", "!", "~", "*", "'", "(",
")", ":", "@", "&", "=", "+", "$", ",",
}
local function protect_segment(s)
return string.gsub(s, "([^A-Za-z0-9_])", function (c)
if segment_set[c] then return c
else return string.format("%%%02x", string.byte(c)) end
end)
end
-----------------------------------------------------------------------------
-- Encodes a string into its escaped hexadecimal representation
-- Input
-- s: binary string to be encoded
-- Returns
-- escaped representation of string binary
-----------------------------------------------------------------------------
function unescape(s)
return string.gsub(s, "%%(%x%x)", function(hex)
return string.char(base.tonumber(hex, 16))
end)
end
-----------------------------------------------------------------------------
-- Builds a path from a base path and a relative path
-- Input
-- base_path
-- relative_path
-- Returns
-- corresponding absolute path
-----------------------------------------------------------------------------
local function absolute_path(base_path, relative_path)
if string.sub(relative_path, 1, 1) == "/" then return relative_path end
local path = string.gsub(base_path, "[^/]*$", "")
path = path .. relative_path
path = string.gsub(path, "([^/]*%./)", function (s)
if s ~= "./" then return s else return "" end
end)
path = string.gsub(path, "/%.$", "/")
local reduced
while reduced ~= path do
reduced = path
path = string.gsub(reduced, "([^/]*/%.%./)", function (s)
if s ~= "../../" then return "" else return s end
end)
end
path = string.gsub(reduced, "([^/]*/%.%.)$", function (s)
if s ~= "../.." then return "" else return s end
end)
return path
end
-----------------------------------------------------------------------------
-- Parses a url and returns a table with all its parts according to RFC 2396
-- The following grammar describes the names given to the URL parts
-- <url> ::= <scheme>://<authority>/<path>;<params>?<query>#<fragment>
-- <authority> ::= <userinfo>@<host>:<port>
-- <userinfo> ::= <user>[:<password>]
-- <path> :: = {<segment>/}<segment>
-- Input
-- url: uniform resource locator of request
-- default: table with default values for each field
-- Returns
-- table with the following fields, where RFC naming conventions have
-- been preserved:
-- scheme, authority, userinfo, user, password, host, port,
-- path, params, query, fragment
-- Obs:
-- the leading '/' in {/<path>} is considered part of <path>
-----------------------------------------------------------------------------
function parse(url, default)
-- initialize default parameters
local parsed = {}
for i,v in base.pairs(default or parsed) do parsed[i] = v end
-- empty url is parsed to nil
if not url or url == "" then return nil, "invalid url" end
-- remove whitespace
-- url = string.gsub(url, "%s", "")
-- get fragment
url = string.gsub(url, "#(.*)$", function(f)
parsed.fragment = f
return ""
end)
-- get scheme
url = string.gsub(url, "^([%w][%w%+%-%.]*)%:",
function(s) parsed.scheme = s; return "" end)
-- get authority
url = string.gsub(url, "^//([^/]*)", function(n)
parsed.authority = n
return ""
end)
-- get query stringing
url = string.gsub(url, "%?(.*)", function(q)
parsed.query = q
return ""
end)
-- get params
url = string.gsub(url, "%;(.*)", function(p)
parsed.params = p
return ""
end)
-- path is whatever was left
if url ~= "" then parsed.path = url end
local authority = parsed.authority
if not authority then return parsed end
authority = string.gsub(authority,"^([^@]*)@",
function(u) parsed.userinfo = u; return "" end)
authority = string.gsub(authority, ":([^:]*)$",
function(p) parsed.port = p; return "" end)
if authority ~= "" then parsed.host = authority end
local userinfo = parsed.userinfo
if not userinfo then return parsed end
userinfo = string.gsub(userinfo, ":([^:]*)$",
function(p) parsed.password = p; return "" end)
parsed.user = userinfo
return parsed
end
-----------------------------------------------------------------------------
-- Rebuilds a parsed URL from its components.
-- Components are protected if any reserved or unallowed characters are found
-- Input
-- parsed: parsed URL, as returned by parse
-- Returns
-- a stringing with the corresponding URL
-----------------------------------------------------------------------------
function build(parsed)
local ppath = parse_path(parsed.path or "")
local url = build_path(ppath)
if parsed.params then url = url .. ";" .. parsed.params end
if parsed.query then url = url .. "?" .. parsed.query end
local authority = parsed.authority
if parsed.host then
authority = parsed.host
if parsed.port then authority = authority .. ":" .. parsed.port end
local userinfo = parsed.userinfo
if parsed.user then
userinfo = parsed.user
if parsed.password then
userinfo = userinfo .. ":" .. parsed.password
end
end
if userinfo then authority = userinfo .. "@" .. authority end
end
if authority then url = "//" .. authority .. url end
if parsed.scheme then url = parsed.scheme .. ":" .. url end
if parsed.fragment then url = url .. "#" .. parsed.fragment end
-- url = string.gsub(url, "%s", "")
return url
end
-----------------------------------------------------------------------------
-- Builds a absolute URL from a base and a relative URL according to RFC 2396
-- Input
-- base_url
-- relative_url
-- Returns
-- corresponding absolute url
-----------------------------------------------------------------------------
function absolute(base_url, relative_url)
if base.type(base_url) == "table" then
base_parsed = base_url
base_url = build(base_parsed)
else
base_parsed = parse(base_url)
end
local relative_parsed = parse(relative_url)
if not base_parsed then return relative_url
elseif not relative_parsed then return base_url
elseif relative_parsed.scheme then return relative_url
else
relative_parsed.scheme = base_parsed.scheme
if not relative_parsed.authority then
relative_parsed.authority = base_parsed.authority
if not relative_parsed.path then
relative_parsed.path = base_parsed.path
if not relative_parsed.params then
relative_parsed.params = base_parsed.params
if not relative_parsed.query then
relative_parsed.query = base_parsed.query
end
end
else
relative_parsed.path = absolute_path(base_parsed.path or "",
relative_parsed.path)
end
end
return build(relative_parsed)
end
end
-----------------------------------------------------------------------------
-- Breaks a path into its segments, unescaping the segments
-- Input
-- path
-- Returns
-- segment: a table with one entry per segment
-----------------------------------------------------------------------------
function parse_path(path)
local parsed = {}
path = path or ""
--path = string.gsub(path, "%s", "")
string.gsub(path, "([^/]+)", function (s) table.insert(parsed, s) end)
for i = 1, #parsed do
parsed[i] = unescape(parsed[i])
end
if string.sub(path, 1, 1) == "/" then parsed.is_absolute = 1 end
if string.sub(path, -1, -1) == "/" then parsed.is_directory = 1 end
return parsed
end
-----------------------------------------------------------------------------
-- Builds a path component from its segments, escaping protected characters.
-- Input
-- parsed: path segments
-- unsafe: if true, segments are not protected before path is built
-- Returns
-- path: corresponding path stringing
-----------------------------------------------------------------------------
function build_path(parsed, unsafe)
local path = ""
local n = #parsed
if unsafe then
for i = 1, n-1 do
path = path .. parsed[i]
path = path .. "/"
end
if n > 0 then
path = path .. parsed[n]
if parsed.is_directory then path = path .. "/" end
end
else
for i = 1, n-1 do
path = path .. protect_segment(parsed[i])
path = path .. "/"
end
if n > 0 then
path = path .. protect_segment(parsed[n])
if parsed.is_directory then path = path .. "/" end
end
end
if parsed.is_absolute then path = "/" .. path end
return path
end
... ...
local http = require "resty.http"
local cjson = require "cjson"
local rate_limit_conf_url="http://config.server.yohoops.org/nginx_limit_api/default"
local limit_ip_access_url="http://config.server.yohoops.org/nginx_limit_ip/default"
local nginx_common_conf_url="http://config.server.yohoops.org/nginx_common_conf/default"
local limit_ip_redis=lua_context["redis_limit_ip"]
local upstream_cache=ngx.shared.upstream
local upstream = require "ngx.upstream"
function split_str_list(str,spliter,limit)
local ips={}
if not str then
return ips
end
string.gsub(str,'[^' .. spliter ..']+',function(w) table.insert(ips, w) end )
if limit and limit>0 and limit<#ips then
local str=table.concat(ips,spliter,limit,#ips)
table.insert(ips,limit,str)
for i=1,#ips-limit do
table.remove(ips)
end
end
return ips
end
function toboolean(str,default)
if str == nil then
return default
end
if str == "true" then
return true
else
return false
end
end
local query_common_conf=function()
local httpc = http:new()
local ok, code, headers, status, body = httpc:request{
url=nginx_common_conf_url,
method="GET",
timeout=120000
}
if ok then
local rate_limit_conf=cjson.decode(body)
local property=rate_limit_conf["propertySources"]
if property then
local property=property[1]
if property then
local source=property["source"]
if source then
local common_conf={}
common_conf.lua_golbal_switch=source["lua_golbal_switch"]
lua_context.lua_conf_cache:set("common_conf",cjson.encode(common_conf))
end
end
end
else
ngx.log(ngx.ERR, "request error:" .. tostring(code))
end
end
-->>begin: query rate limit config function definition
local query_rate_limit_conf=function()
local httpc = http:new()
local ok, code, headers, status, body = httpc:request{
url=rate_limit_conf_url,
method="GET",
timeout=120000
}
if ok then
local rate_limit_conf=cjson.decode(body)
local property=rate_limit_conf["propertySources"]
if property then
local property=property[1]
if property then
local api_rate_limit_conf={api_rate_limit={}}
local source=property["source"]
api_rate_limit_conf["is_open"]=source["open_limit_flow"]
api_rate_limit_conf["default_rate_limit"]=tonumber(source["default_rate_limit"])
for k,v in pairs(source) do
if string.find(k,"^api_rate_limit*") then
local t=split_str_list(k,".")
table.remove(t,1)
local key=table.concat(t,".")
local vals=split_str_list(v,",",3)
api_rate_limit_conf.api_rate_limit[key]={tonumber(vals[1]),tonumber(vals[2]),vals[3]}
end
end
lua_context.lua_conf_cache:set("api_rate_limit_conf",cjson.encode(api_rate_limit_conf))
end
end
else
ngx.log(ngx.ERR, "request error:" .. tostring(code))
end
end
--<<end: query rate limit config function definition
-->>begin: query limit ip access config function definition
local query_limit_ip_access_conf=function()
local httpc = http:new()
local ok, code, headers, status, body = httpc:request{
url=limit_ip_access_url,
method="GET",
timeout=120000
}
if ok then
--ngx.log(ngx.ERR, ">>>>>>>>>>>>>>" .. tostring(body))
local rate_limit_conf=cjson.decode(body)
local property=rate_limit_conf["propertySources"]
if property then
local property=property[1]
if property then
local source=property["source"]
if source then
local limit_ip_access={}
local is_open=source["is_open"]
limit_ip_access["is_open"]=is_open
local ip_qps_limit=source["ip_qps_limit"]
ip_qps_limit=split_str_list(ip_qps_limit,",")
local ip_qps_limit_table={}
table.insert(ip_qps_limit_table,tonumber(ip_qps_limit[1]))
table.insert(ip_qps_limit_table,tonumber(ip_qps_limit[2]))
limit_ip_access["ip_qps_limit"]=ip_qps_limit_table
local interface_ip_qps_limit={}
local white_ips={}
for k,v in pairs(source) do
if string.find(k,"^interface_ip_qps_limit%[*") then
local t=split_str_list(k,".")
table.remove(t,1)
local key=table.concat(t,".")
local vals=split_str_list(v,",")
interface_ip_qps_limit[key]={tonumber(vals[1]),tonumber(vals[2])}
end
if string.find(k,"^white_ips%[*") then
table.insert(white_ips,v)
end
end
limit_ip_access["white_ips"]=white_ips
limit_ip_access["interface_ip_qps_limit"]=interface_ip_qps_limit
lua_context.lua_conf_cache:set("limit_ip_access",cjson.encode(limit_ip_access))
end
end
end
else
ngx.log(ngx.ERR, "request error:" .. tostring(code))
end
end
--<<end: query limit ip access config function definition
-->>begin: subscribe ip blacklist event
local cache=lua_context.mal_ip_cache
local subscribe_mal_ips=function()
if ngx.worker.id() ~= 0 then
return false
end
local connect=limit_ip_redis:getConnect()
if not connect then
ngx.log(ngx.ERR,"subscribe blacklist ip get connection err" )
return false
end
local res, err=connect:subscribe("mal_ips")
if not res then
connect:close()
ngx.log(ngx.ERR,"subscribe blacklist ip connection subscribe err:" .. tostring(err))
return false
end
connect:set_timeout(86400000)
while true do
local res, err = connect:read_reply()
if res then
if res[3] then
local t=cjson.decode(res[3])
local ips=t.ips
local expire=tonumber((not t.expire) and 43200 or t.expire)
if t.type == "add" then
for ip in string.gmatch(ips,"[^',']+") do
cache:set("yh:mip:" .. ip,"1",expire)
ngx.log(ngx.INFO,"nginx subscribe add mal ip:" .. tostring(ip) .. ":" .. tostring(expire))
end
elseif t.type == "del" then
for ip in string.gmatch(ips,"[^',']+") do
cache:delete("yh:mip:" .. ip)
ngx.log(ngx.INFO,"nginx subscribe del mal ip:" .. tostring(ip) .. ":" .. tostring(expire))
end
elseif t.type == "flush" then
cache:flush_all()
ngx.log(ngx.INFO,"nginx subscribe flush all mal ip")
end
end
elseif err ~= "timeout" then
connect:close()
ngx.log(ngx.ERR,"subscribe blacklist ip socket timeout")
return false
end
if ngx.worker.exiting() then
connect:close()
ngx.log(ngx.ERR,"subscribe blacklist ip ngx worker exit")
return false
end
end
return false
end
--<< end: subscribe ip blacklist event
function subscribe_mal_ips_loop()
if ngx.worker.id() ~= 0 then
return
end
local b = ture
while true do
local res,err=pcall(subscribe_mal_ips)
if not res then
ngx.log(ngx.ERR,"subscribe blacklist ip ngx err:" .. tostring(err))
end
-- subscribe error sleep 10 seconds and then retry
ngx.sleep(10)
if ngx.worker.exiting() then
return
end
end
end
-->>begin: timer at fix rate call function.
local timer_handler
timer_handler=function(premature,t,f,id)
if id then
if ngx.worker.id() == id then
local b,errinfo=pcall(f)
if not b then
ngx.log(ngx.ERR, "task request error:" .. tostring(errinfo))
end
end
else
local b,errinfo=pcall(f)
if not b then
ngx.log(ngx.ERR, "task request error:" .. tostring(errinfo))
end
end
ngx.timer.at(t,timer_handler,t,f)
end
--<<end: timer at fix rate call function.
-- subscribe mal ips task
ngx.timer.at(2,subscribe_mal_ips_loop)
timer_handler(true,20,query_rate_limit_conf,0)
timer_handler(true,25,query_limit_ip_access_conf,0)
timer_handler(true,30,query_common_conf,0)
-- every worker timing schedule configs from share cache
function rate_limit_conf_to_worker()
local t=lua_context.lua_conf_cache:get("api_rate_limit_conf")
if t then
local r=cjson.decode(t)
if r then
lua_context.configs["api_rate_limit_conf"]=r
--ngx.log(ngx.INFO,"++++++++++++++" .. cjson.encode(r.api_rate_limit["web.passport.getUserVerifyInfo"]))
end
end
end
function limit_ip_access_conf_to_worker()
local t=lua_context.lua_conf_cache:get("limit_ip_access")
if t then
local r=cjson.decode(t)
if r then
r["white_method"]={"app.graphic.img","app.graphic.verify"}
lua_context.configs["limit_ip_access"]=r
--ngx.log(ngx.INFO,"++++++++++++++" .. cjson.encode(lua_context.configs["limit_ip_access"]))
end
end
end
function query_common_conf_to_worker()
local t=lua_context.lua_conf_cache:get("common_conf")
if t then
local r=cjson.decode(t)
if r then
lua_context.configs["common_conf"]=r
end
end
end
timer_handler(true,2,rate_limit_conf_to_worker)
timer_handler(true,2,limit_ip_access_conf_to_worker)
timer_handler(true,2,query_common_conf_to_worker)
local stream_ctx={}
function updownstream()
local keys=upstream_cache:get_keys()
for _,k in ipairs(keys) do
if string.sub(k,1,1)=="d" then
local vKey="v" .. string.sub(k,2)
local version=upstream_cache:get(vKey)
local value=upstream_cache:get(k)
local v=cjson.decode(value)
if ( not stream_ctx[vKey] ) or stream_ctx[vKey] < version then
local ok,err=upstream.set_peer_down(v["upstream"],v["backup"],v["id"],v["value"])
if not ok then
ngx.log(ngx.ERR,"up or down stream err:",ngx.worker.id(),value,err)
else
stream_ctx[vKey]=version
end
end
end
end
end
timer_handler(true,2,updownstream)
\ No newline at end of file
... ...
local lrucache = require "resty.lrucache"
-- init redis twemproxy config
local ip_limit_redis_config={host="redis.nginx.yohoops.org",port="6379",auth="redis9646",timeout=2000,max_idle_timeout=60000,pool_size=100}
local redis_util=require("redisutil")
local redis_limit_ip=redis_util:new(ip_limit_redis_config)
-- global variable
lua_context={}
lua_context["redis_limit_ip"]=redis_limit_ip
lua_context.mal_ip_cache=ngx.shared.malips
lua_context.lua_conf_cache=ngx.shared.ngxconf
lua_context.configs={}
ngx.shared.upstream:flush_all()
ngx.shared.upstream:flush_expired()
... ...
function getIpNumber(ip)
local ipArray={};
for w in string.gmatch(ip,"%d+") do
ipArray[#ipArray+1]=w;
end
local ipNumber=0;
for k,v in ipairs(ipArray) do
if k==1 then
ipNumber=ipNumber+tonumber(v)*167777216;
elseif k==2 then
ipNumber=ipNumber+tonumber(v)*65536;
elseif k==3 then
ipNumber=ipNumber+tonumber(v)*256;
else
ipNumber=ipNumber+tonumber(v);
end
end
return ipNumber;
end
function allow()
local beginIp=ngx.var.begin_ip;
local endIp=ngx.var.end_ip;
local beginNum=getIpNumber(beginIp);
local endNum=getIpNumber(endIp);
local realIp=ngx.var.real_ip;
local realIpNum=getIpNumber(realIp);
if realIpNum < beginNum or realIpNum > endNum then
ngx.exit(ngx.HTTP_FORBIDDEN);
end
end
allow()
... ...
local modname= ...
local M={}
_G[modname]=M
package.loaded[modname]=M
local h2b = {
["0"] = "0000",
["1"] = "0001",
["2"] = "0010",
["3"] = "0011",
["4"] = "0100",
["5"] = "0101",
["6"] = "0110",
["7"] = "0111",
["8"] = "1000",
["9"] = "1001",
["A"] = "1010",
["B"] = "1011",
["C"] = "1100",
["D"] = "1101",
["E"] = "1110",
["F"] = "1111"
}
function M:split_ip(ip)
if not ip or type(ip) ~="string" then
return nil
end
local t={}
for w in string.gmatch(ip,"([^'.']+)") do
table.insert(t,tonumber(w))
end
return t
end
function M:ip_to_binary_str(ip)
if not ip or type(ip)~="string" then
return nil
end
local ip_table=self:split_ip(ip)
if not ip_table then
return nil
end
local ip_table_length=#ip_table
if ip_table_length~=4 then
return nil
end
local ip_str=""
for i=1,ip_table_length do
local x=string.upper(string.format("%x",ip_table[i]))
local len=string.len(x)
if len==1 then
x="0" .. x
elseif len>2 then
return nil
end
local f=string.sub(x,1,1)
local s=string.sub(x,2,2)
ip_str=ip_str .. h2b[f] .. h2b[s]
end
return ip_str
end
function M:check_ip_in_ipblock(ip,ipblock)
if (not ip) or (not ipblock) then
return false
end
local f,t=string.find(ipblock,"/")
if f then
local ipblock_head=string.sub(ipblock,0,f-1)
local ipblock_tail=string.sub(ipblock,f+1)
local ipblock_head_b=self:ip_to_binary_str(ipblock_head)
local ip_b=self:ip_to_binary_str(ip)
local mask_len=tonumber(ipblock_tail)
if (not mask_len)or mask_len> 32 or mask_len<0 or (not ip_b) or (not ipblock_head_b) then
return false
end
if string.sub(ipblock_head_b,0,mask_len) == string.sub(ip_b,0,mask_len) then
return true
end
else
if ipblock==ip then
return true
end
end
return false
end
function M:pcall_check_ip_in_ipblock(ip,ipblock,default)
local flag,res=pcall(self.check_ip_in_ipblock,self,ip,ipblock)
if flag then
return res
end
return default
end
... ...
local rateLimit = require "limit_common_flow"
local cjson = require "cjson"
local default_err_code=9999991
local default_err_msg="系统正忙,请稍后重试!"
function get_req_param(req_param)
local method = ngx.var.request_method
if "GET" == method then
return ngx.req.get_uri_args()[req_param]
else
ngx.req.read_body()
return ngx.req.get_post_args()[req_param]
end
end
function get_req_uri()
local limit_config=lua_context.configs["api_rate_limit_conf"]
local req_service_url = ngx.var.uri
if (not limit_config.is_open) or (not req_service_url) then
return nil
end
local beginIndex, endIndex = string.find(req_service_url, "apigateway")
if endIndex == nil then
return nil
end
local real_url=string.sub(req_service_url,endIndex+1,string.len(req_service_url))
return real_url
end
function extract_limit_method()
local uri_method=get_req_uri()
if uri_method and uri_method ~= "/" and uri_method ~= "" then
return uri_method
end
local method= get_req_param("method")
return method
end
function rate_limit()
local common_config=lua_context.configs["common_conf"]
local limit_config=lua_context.configs["api_rate_limit_conf"]
if (not common_config) or (not limit_config) then
return
end
if not common_config.lua_golbal_switch then
return
end
local api_rate_limit=limit_config.api_rate_limit
local req_uri_method = extract_limit_method()
--ngx.log(ngx.INFO,"=================>>" .. cjson.encode(api_rate_limit[req_uri_method]))
if (not limit_config.is_open) or (not req_uri_method) then
return
end
local max_per_sencond=limit_config.default_rate_limit
if api_rate_limit[req_uri_method] and api_rate_limit[req_uri_method][1] then
max_per_sencond=api_rate_limit[req_uri_method][1]
end
if not max_per_sencond then
max_per_sencond=60
end
local err_code=default_err_code
if api_rate_limit[req_uri_method] and api_rate_limit[req_uri_method][2] then
err_code=api_rate_limit[req_uri_method][2]
end
local err_msg=default_err_msg
if api_rate_limit[req_uri_method] and api_rate_limit[req_uri_method][3] then
err_msg=api_rate_limit[req_uri_method][3]
end
local flag=rateLimit.limit_flow("yh:nginx:limitflow:" .. req_uri_method,max_per_sencond)
if not flag then
ngx.log(ngx.ERR,"The request is limited :" .. req_uri_method)
ngx.header["Content-Type"]="application/json;charset=utf-8"
local msg='{"code":' .. err_code .. ',"message":"'.. err_msg .. '"}'
ngx.say(msg)
ngx.exit(ngx.HTTP_OK)
end
end
if ngx.var.request_method=="POST" and ngx.var.content_type and string.match(ngx.var.content_type,"application/x%-www%-form%-urlencoded.*") then
ngx.req.read_body()
end
rate_limit()
... ...
require "resty.core"
local rateLimit ={}
function rateLimit.limit_flow(limit_key,max_limit,seconds)
if not limit_key or not max_limit then
return true
end
if not seconds then
seconds=2
end
lua_context.lua_conf_cache:safe_add(limit_key,0,seconds)
local limit,err=lua_context.lua_conf_cache:incr(limit_key,1)
if limit then
if tonumber(limit)>max_limit then
return false
else
return true
end
end
return true
end
return rateLimit
... ...
local modname= ...
local M={}
_G[modname]=M
package.loaded[modname]=M
local cjson=require "cjson"
local rate_limit = require "limit_common_flow"
local iptool=require "iptool"
local lrucache = require "resty.lrucache"
local cache=lua_context.mal_ip_cache
local redis_limit_ip=lua_context["redis_limit_ip"]
function M.in_array(value,arr)
if not value then
return false
end
if not arr then
return false
end
for k, v in ipairs(arr) do
if value==v then
return true
end
end
return false
end
function M:get_req_param(req_param)
local method = ngx.var.request_method
if "GET" == method then
return ngx.req.get_uri_args()[req_param]
else
ngx.req.read_body()
return ngx.req.get_post_args()[req_param]
end
end
--1:api 2:service
function M:limit_ip_access()
local limit_ip_config=lua_context.configs["limit_ip_access"]
local common_config=lua_context.configs["common_conf"]
if (not common_config) or (not limit_ip_config) then
return
end
-- global switch control
if not common_config.lua_golbal_switch then
return
end
-- is open ip limit access control
if not limit_ip_config.is_open then
return
end
local ip=ngx.var.real_ip
if not ip then
return
end
--check is in white ip list
local white_ips_length=#limit_ip_config.white_ips
if white_ips_length >0 then
for i=1,white_ips_length do
local is_in_white_ips=iptool:pcall_check_ip_in_ipblock(ip,limit_ip_config.white_ips[i],false)
if is_in_white_ips then
return
end
end
end
-- check ip is in limit ip list
local is_limit=self.in_array(ip,limit_ip_config.limit_ip_config)
local limit_type="0"
-- check ip access is arrive max access
local ip_qps_limit=limit_ip_config.ip_qps_limit
if (not is_limit) and ip_qps_limit and ip_qps_limit[1] and ip_qps_limit[2] then
if not rate_limit.limit_flow("yh:limit:ip:" .. ip,limit_ip_config.ip_qps_limit[1],limit_ip_config.ip_qps_limit[2]) then
is_limit=true
limit_type="1"
end
end
-- check method or uri limit
local req_uri_method
local method_limit_conf
if(not is_limit) and limit_ip_config.interface_ip_qps_limit then
req_uri_method = self:get_req_param("method")
if not req_uri_method then
req_uri_method = ngx.var.uri
end
method_limit_conf=limit_ip_config.interface_ip_qps_limit[req_uri_method]
end
if (not is_limit) and req_uri_method and method_limit_conf then
if not rate_limit.limit_flow("yh:limit:ip:" .. ip .. ":" .. req_uri_method,method_limit_conf[1],method_limit_conf[2]) then
is_limit=true
limit_type="2"
end
end
local is_white_method=self.in_array(req_uri_method,limit_ip_config.white_method)
if is_white_method then
return
end
-- check redis config
if not is_limit then
local res=cache:get("yh:mip:" .. ip)
if res then
is_limit=true
limit_type="3"
end
end
if is_limit then
ngx.log(ngx.ERR, "[LimitIPAccess:ip]:" .. ip .. ",type:" .. limit_type)
ngx.header["Content-type"]="application/json;charset=utf-8"
if limit_type=="3" then
ngx.header["x-yoho-malicode"]="10011"
local rsp ='{"code": 10011, "message": ""}'
ngx.say(rsp)
end
ngx.exit(ngx.HTTP_OK)
end
end
-- malicious ip
function M:mal_ip()
local method=self:get_req_param("method")
local ips=self:get_req_param("ips")
local expire=self:get_req_param("expire")
ngx.header["Content-type"]="application/json;charset=utf-8"
if not method then
ngx.say('{"code": 400, "msg": "params error!"}')
ngx.exit(ngx.HTTP_OK)
end
local exists={}
if method == 'pubAdd' then
local t={}
t.ips=ips
t.expire=expire
t.type="add"
redis_limit_ip:cmd("publish","mal_ips",cjson.encode(t))
elseif method == 'pubDel' then
local t={}
t.ips=ips
t.type="del"
redis_limit_ip:cmd("publish","mal_ips",cjson.encode(t))
elseif method == 'flushAll' then
local t={}
t.type="flush"
redis_limit_ip:cmd("publish","mal_ips",cjson.encode(t))
elseif method == 'queryAll' then
local all_ips=cache:get_keys(0)
for i,v in pairs(all_ips) do
exists[#exists+1]=string.sub(v,8,string.len(v))
end
else
for ip in string.gmatch(ips,"[^',']+") do
if method == 'add' then
local expire= (not expire) and 43200 or expire
cache:set("yh:mip:" .. ip,"1",expire)
elseif method == 'del' then
cache:delete("yh:mip:" .. ip)
elseif method == 'exists' then
local res=cache:get("yh:mip:" .. ip)
res= res and true or false
exists[#exists+1]=tostring(res)
end
end
end
local body=table.concat(exists,",")
ngx.say('{"code": 200, "msg": "'.. body ..'"}')
ngx.exit(ngx.HTTP_OK)
end
... ...
#! /usr/bin/env lua
--[[
redis操作库
--]]
local resty_redis = require("resty.redis")
Redis ={}
function Redis:new(datasource)
local o={}
o.datasource = datasource or {
host = "127.0.0.1",
port = "6379",
auth = nil,
timeout = 3000,
max_idle_timeout = 60000,
pool_size = 1000
}
setmetatable(o,self)
self.__index=self
ngx.log(ngx.DEBUG, "[Redis:init] datasource ")
return o
end
function Redis:getConnect()
local connect, err = resty_redis:new()
if not connect then
ngx.log(ngx.ERR, "[Redis:getConnect] failed to create redis : " .. self.datasource.host, err)
return nil
end
connect:set_timeout(self.datasource.timeout)
local ok, err = connect:connect(self.datasource.host, self.datasource.port)
if not ok then
ngx.log(ngx.ERR, "[Redis:getConnect] failed to connect redis : " .. self.datasource.host, err)
return nil
end
if self.datasource.auth then
local res, err = connect:auth(self.datasource.auth)
end
return connect
end
function Redis:cmd(method, ...)
local connect = self:getConnect()
if not connect then
return nil
end
ngx.log(ngx.DEBUG, "[Redis:cmd] connected to redis ok.")
-- exec cmd
local res, err = connect[method](connect, ...)
-- close
self:close(connect)
return res, err
end
function Redis:close(connect)
if not connect then
return
end
if self.datasource.pool_size <= 0 then
connect:close()
return
end
-- put it into the connection pool of size 100,
-- with 10 seconds max idle timeout
local ok, err = connect:set_keepalive(self.datasource.max_idle_timeout, self.datasource.pool_size)
if not ok then
ngx.log(ngx.ERR, "[Redis:close] set keepalive failed : ", err)
else
ngx.log(ngx.DEBUG, "[Redis:close] set keepalive ok.")
end
end
return Redis
... ...
function build_request_uri(uri_str)
local uri
if uri_str and uri_str ~="" then
local i,j=string.find(uri_str,"?")
if i then
uri=string.sub(uri_str,1,i-1)
end
end
return uri
end
local uri=build_request_uri(ngx.var.request_uri)
ngx.var.request_api_method=uri
local http_method=ngx.var.request_method
local post_params={}
if http_method=="POST" and ngx.var.content_type and string.match(ngx.var.content_type,"application/x%-www%-form%-urlencoded.*") then
post_params=ngx.req.get_post_args()
if not post_params then
post_params={}
end
end
local get_params={}
if ngx.req.get_uri_args() then
get_params=ngx.req.get_uri_args()
end
if not uri or uri == "" or uri == "/" then
if get_params["method"] then
ngx.var.request_api_method=get_params["method"]
elseif post_params["method"] then
ngx.var.request_api_method=post_params["method"]
end
end
if get_params["udid"] then
ngx.var.request_udid=get_params["udid"]
elseif post_params["udid"] then
ngx.var.request_udid=post_params["udid"]
end
if get_params["uid"] then
ngx.var.request_uid=get_params["uid"]
elseif post_params["uid"] then
ngx.var.request_uid=post_params["uid"]
end
... ...
local upstream = require "ngx.upstream"
local json=require "cjson"
local get_servers = upstream.get_servers
local get_upstreams = upstream.get_upstreams
local cache=ngx.shared.upstream
-- get all peers for upstram: u
function list(u)
local d={}
d["name"]=u
d["value"]={}
-- get primary peers
local peers,err = upstream.get_primary_peers(u)
if err then
ngx.say("failed to get primary servers in upstream ", u)
return
end
for _,p in ipairs(peers) do
local s={}
s["id"]=p.id
s["down"]=p.down and p.down or false
s["name"]=p.name
s["backup"]=false
table.insert(d["value"],s)
end
-- get backup peers
peers,err = upstream.get_backup_peers(u)
if err then
ngx.say("failed to get backup servers in upstream ", u)
return
end
for _,p in ipairs(peers) do
local s={}
s["id"]=p.id
s["down"]=p.down and p.down or false
s["name"]=p.name
s["backup"]=true
table.insert(d["value"],s)
end
ngx.header["Content-type"]="application/json;charset=utf-8"
ngx.say(json.encode(d))
end
function upordown(upstream_name,is_backup,peer_id,down_value)
local t={}
t["upstream"]=upstream_name
t["backup"]=is_backup
t["id"]=peer_id
t["value"]=down_value
local rKey=upstream_name .. ":" .. tostring(id) .. ":" .. tostring(is_backup)
local key="d:" .. rKey
local vKey="v:" .. rKey
cache:add(vKey,0)
local v,err=cache:incr(vKey,1)
if not v then
return false
end
local suc=cache:set(key,json.encode(t))
return suc
end
local args=ngx.req.get_uri_args()
local method=args["method"]
if method == "list" then
local u_name = args["upstream"]
list(u_name)
elseif(method=="down" or method=="up") then
local upstream=args["upstream"]
local backup=args["backup"]=="true" and true or false
local id=tonumber(args["id"])
local down= method=="down" and true or false
local suc=upordown(upstream,backup,id,down)
local t={}
t["suc"]=suc
ngx.header["Content-type"]="application/json;charset=utf-8"
ngx.say(json.encode(t))
end
... ...
local limitIpAccess= require 'limit_ip_access'
local redirectPath = "/apigateway" ;
--- modify content-type, add utf-8 charset if absent
if ngx.req.get_headers()["content-type"] ~= nil and ngx.req.get_headers()["content-type"] == 'application/x-www-form-urlencoded' then
ngx.req.set_header("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
end
--- if url match the service url, just redirect
if ngx.var.service_matched == "1" then
limitIpAccess:limit_ip_access()
local uri = ngx.var.uri
if nil == ngx.req.get_uri_args() then
return ngx.exec(redirectPath ..uri);
else
return ngx.exec(redirectPath ..uri, ngx.req.get_uri_args());
end
end
-- method ==9 is not supported , ngx will return directly with reponse message: "please update your app "
local VERSION_ONLINE_NEW=0;
local METHOD_NEW={
["app.passport.signin"]=9,
["app.drawline.getQueueList"]=4,
["app.drawline.addQueue"]=4,
["app.drawline.getUserActivityInfo"]=4,
["app.drawline.getActivityInfo"]=4,
["app.drawline.getLuckyUserList"]=4,
["app.drawline.changeTaskValue"]=4,
["wap.activity.getActivityInfo"]=4,
["app.activity.getInfoOfOrderShare"]=4,
["wap.order.drawOrderShareCoupon"]=4,
["wap.order.registerAndSendCoupon"]=4--,
--["wechat.user.follow"]=5,
--["wechat.user.unfollow"]=5,
--["wechat.token.getmini"]=5,
--["wechat.token.get"]=5
};
--decode request method
local request_method = ngx.var.request_method;
--if header "content_type" is multipart/form-data, send to IDC
--multipart/form-data maybe used to upload image, as user header image
local request_headers = ngx.req.get_headers();
if request_headers["content_type"] ~= nil and string.sub(request_headers["content_type"],1,19) == "multipart/form-data" then
ngx.log(ngx.INFO,"redirect don't support multipart/form-data, method is " .. request_method .. ", send to api.open.yohobuy.com");
if "GET" == request_method then
ngx.exec(redirectPath,ngx.req.get_uri_args());
else
ngx.exec(redirectPath);
end
else
--if header "content_type" is x-www-form-urlencoded, decode arguments
local args = nil ;
if "GET" == request_method then
args = ngx.req.get_uri_args()
elseif "POST"== request_method then
ngx.req.read_body()
args = ngx.req.get_post_args()
end
--decode arguments "method" and "v" in request data
-- if args is nil?
if args == nil then
ngx.log(ngx.INFO, "can not get any args!")
ngx.exit(ngx.HTTP_OK )
end
local method = args["method"];
local version = tonumber(args["v"]);
if version == nil then
version=1
end
if method == nil then
method="not_support_method"
end
--ngx.log(0,method);
--ngx.log(0,version);
--ngx.log(0,METHOD_NEW[method]);
---- make sure method must be present -------
if method == nil then
ngx.log(ngx.INFO, "method not allowed")
ngx.exit(ngx.HTTP_NOT_ALLOWED )
end
if METHOD_NEW[method] == 9 then
ngx.header["Content-type"]="application/json;charset=utf-8"
local rsp = '{"alg": "SALT_MD5","code": 112232323,"data": {},"md5": "f4a7a490bb6666b005008d795ed14e5d", "message": "请升级客户端版本!"}'
ngx.say(rsp)
ngx.exit(ngx.HTTP_OK)
end
if METHOD_NEW[method] == 4 then
redirectPath = "/activityApi";
ngx.log(ngx.INFO,"request method is " .. request_method .. ", version is " .. version .. ",method is " .. method .. ", send to apigateway" .. redirectPath);
if "GET" == request_method then
ngx.exec(redirectPath,ngx.req.get_uri_args());
elseif "POST"== request_method then
ngx.exec(redirectPath);
end
elseif METHOD_NEW[method] == 5 then
ngx.log(ngx.INFO,"request method is " .. request_method .. ", version is " .. version .. ",method is " .. method .. ", send to activityApi");
if "GET" == request_method then
ngx.exec("/wechatApi",ngx.req.get_uri_args());
elseif "POST"== request_method then
ngx.exec("/wechatApi");
end
else
limitIpAccess:limit_ip_access()
if "GET" == request_method then
ngx.exec(redirectPath,ngx.req.get_uri_args());
elseif "POST"== request_method then
ngx.exec(redirectPath);
end
end
end
... ...
----- 内部域名如果http头中带有 X-YOHO-IP,则是PC/h5过来的请求,直接用这个作为real ip
----- 经过动态CDN,如果x-forward-for 只有1个IP地址,则取第一个;>=2个IP地址,例如 ip1,ip2,ip3,ip4, 则取倒数第二个,即ip3
----- 请求不经过动态CDN,取 x-forward-for 中最后一个IP地址,例如 ip1,ip2,ip3,ip4,取ip4
local realIp = ngx.req.get_headers()["X-YOHO-IP"]
if realIp and (string.find(ngx.var.http_host,"yohoops%.org") or string.find(ngx.var.http_host,"yohops%.com")) then
return realIp
end
local xForwardFor = ngx.var.http_x_forwarded_for;
if xForwardFor then
local ips={}
string.gsub(xForwardFor,'[^,]+',function(w) table.insert(ips, w) end )
if string.find(ngx.var.http_host,"api%-dc",1) == 1 then
if #ips >= 2 then
realIp=ips[#ips-1]
elseif #ips<=1 then
realIp=ips[1]
end
else
realIp=ips[#ips]
end
end
-- if not exist, then setup remote_addr
if realIp == nil then
realIp = ngx.var.remote_addr;
end
return realIp;
... ...
# Interface FOR YOHO ERP SYSTEM
server {
listen 80;
server_name erp.yoho.yohoops.org;
proxy_http_version 1.1;
proxy_set_header Connection "";
access_log /Data/logs/nginx/erp.yoho.yohoops.org_access.log fenxi;
error_log /Data/logs/nginx/erp.yoho.yohoops.org_error.log;
if ( $request_method = HEAD ) {
return 200;
}
# only allow local ip
allow 10.66.0.0/16;
allow 10.67.0.0/16;
allow 192.168.0.0/16;
allow 172.31.0.0/16;
allow 127.0.0.1;
deny all;
location = /upstreams {
content_by_lua_file "conf/lua/upstream.lua";
}
}
... ...
... ... @@ -18,4 +18,4 @@ server {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Accept-Encoding "gzip";
}
}
\ No newline at end of file
}
... ...
# java nginx 配置文件
## 说明
> 这些文件需要`copy`到`openresty` 的 `home` 目录
>
## IP block endpoints
- `Remove blocked ip`: http://erp.yoho.yohoops.org/malIp?method=pubDel&ips=1.2.3.4,1.2.3.5
- `Add to block ip`: http://erp.yoho.yohoops.org/malIp?method=pubAdd&ips=1.2.3.4,1.2.3.5&expire=110000
- `Remove all block ip`: http://erp.yoho.yohoops.org/malIp?method=flushAll
- `Retrive all block ip`: http://erp.yoho.yohoops.org/malIp?method=queryAll
## 动态`upstream`的支持
基于 [lua-upstream-nginx-module] (https://github.com/openresty/lua-upstream-nginx-module) 动态设置upstream的 `up`, `down`
> 1. 查询所有的`upstreams`信息:
>
>`curl -i -H "Host: erp.yoho.yohoops.org" "http://127.0.0.1/upstreams?method=list&upstream=apigateway"`
>
> 2. 设置 `upstream` 名称为 `apigateway`的`upstream` 第0个 `backend server` 状态为`down`:
>
> `curl -i -H "Host: erp.yoho.yohoops.org" "http://127.0.0.1/upstreams?method=down&upstream=apigateway&id=0"`
>
> 3. 设置`upstream` 名称为 `apigateway`的`upstream` 第0个 `backend server` 状态为`up`:
>
> `curl -i -H "Host: erp.yoho.yohoops.org" "http://127.0.0.1/upstreams?method=up&upstream=apigateway&id=0"`
### list 接口响应:
```json
{
"name": "apigateway",
"value": [
{
"backup": false,
"name": "172.31.70.77:8080",
"down": false,
"id": 0
},
{
"backup": false,
"name": "172.31.70.113:8080",
"down": false,
"id": 1
},
{
"backup": false,
"name": "172.31.70.8:8080",
"down": false,
"id": 2
},
{
"backup": false,
"name": "172.31.70.104:8080",
"down": false,
"id": 3
}
]
}
```
\ No newline at end of file
... ...
... ... @@ -45,17 +45,17 @@
- java-ufo-platform
- name: copy ufo api conf files to openresty
template:
src: vhosts/api.ufo.conf
dest: "{{ path }}/nginx/conf/vhosts/api.ufo.conf"
- name: copy lualib files to openresty
copy:
src: java-nginx-config/lualib/resty/
dest: "{{ path }}/lualib/resty/"
notify:
- reload nginx
- name: copy ufo admin conf files to openresty
template:
src: vhosts/platform.ufo.conf.j2
dest: "{{ path }}/nginx/conf/vhosts/platform.ufo.conf"
- name: copy yoho conf files to openresty
copy:
src: java-nginx-config/nginx/conf/
dest: "{{ path }}/nginx/conf/"
notify:
- reload nginx
... ...
#worker_processes 1;
user www www;
# setup worker proccess and worker cpu affinity
... ... @@ -65,6 +61,14 @@ http {
proxy_buffers 32 32k;
proxy_busy_buffers_size 128k;
lua_package_path "/Data/local/openresty-1.9.15.1/nginx/conf/lua/?.lua;;";
init_by_lua_file "conf/lua/init_lua.lua";
lua_shared_dict luacachedb 80m;
lua_shared_dict upstream 20m;
lua_shared_dict malips 10m;
lua_shared_dict ngxconf 20m;
init_worker_by_lua_file "conf/lua/init_config_worker.lua";
server {
listen 80;
server_name localhost;
... ...