最近在做图片服务的时候,一开始用的tomcat,图片的url后面拼接上token,在tomcat里进行token验证后再把二进制流返回给前台。不过,我们项目中有一个页面,在进去的时候,会一次性加载1000张左右的图片,等图片加载完,花都谢了。
不过也不能把后台校验去了,所以就有了在nginx上做token验证的想法,同事说可以开发脚本,随后就有了接下来的故事。
OpenResty就是我需要用到的集成了nginx和lua开发环境的框架,该框架甚至集成了redis连接包等,里面的东西很多,具体的可以看这篇文章:OpenResty 最佳实践,本篇文章只说具体实现。另外高喊一句,lua牛批。
不过在搭建lua的开发环境的时候,可能是因为我是Windows系统,所以环境没能安装成功, 不过我按照Idea+lua 开发环境搭建(一)文章里说的,成功运行lua。虽然。。。我搭好lua环境后,只是用来练习语法和部分标准库了,nginx的开发没用上。。。
第一版中,我只是在里面通过Redis做了token校验,只要请求过来,就会先去Redis里面判断一下是否有这个token,如果有的话就返回图片。不过返回图片卡住了好久,按照《OpenResty 最佳实践》中说的,我返回图片流给页面,页面并不能看到图片。于是我决定利用子查询来做,将该请求转到nginx中的另一个location下。
第二版加入了token缓存,在缓存中存储接收到的token,等下一个请求来时,先去缓存中看有没有,如果没有的话就去redis中查一下。虽然这么设置了,图片还是没有达到想要的速度,只能跟前端的大哥商量,说能不能让图片懒加载,一次请求1000个真的有点搞。。。
nginx.conf: 应用了
#user nobody;
worker_processes 2;
error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
# 声明创建缓存
lua_shared_dict my_cache 64m;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
server {
listen 80;
server_name localhost;
charset UTF-8;
# access_log logs/host.access.log info;
location ^~ /images/ {
content_by_lua_file lua/auth/token.lua;
}
location ^~ /static/images/ {
alias E:/static/images/toyota/ImageGroup/;
}
}
}
lua/auth/token.lua: 负责token验证的逻辑
local arg = ngx.req.get_uri_args()
-- get请求参数中T就是token
local token = arg.T
if not token then
ngx.status = ngx.HTTP_FORBIDDEN
ngx.say("No token,dude.")
ngx.exit(200)
end
-- 先去本地缓存中查找是否存在该token
local cache_ngx = ngx.shared.my_cache
local value = cache_ngx:get(token)
if not value then
-- 本地缓存没有,去Redis中找
-- 从reids中获取token,如果不存在,返回406(Not Acceptable)
-- require设置的路径是从lua包下开始算起的
local redis = require "api.redis"
local red = redis:new()
-- 存储在Redis中的token有前缀
local token_key = "_token_"..token
ok, err = red:get(token_key)
if not ok then
-- token已过期
ngx.status = ngx.HTTP_FORBIDDEN
ngx.log(ngx.INFO, "Token passed away.")
ngx.exit(406)
else
value = ok
-- 在nginx缓存中添加该token,设置过期时间为30分钟
cache_ngx:set(token, "", 30 * 60)
end
end
if value then
-- 截取访问的图片名称
-- 获取uri
local uri = ngx.var.request_uri
local t_index = string.find(uri, "?T")
-- uri的起始部分必定有/images/, 所以从9位置开始截取
local file_path = string.sub(uri, 9, t_index - 1)
if string.len(file_path) < 5 then
ngx.exit(404)
end
file_path = "/static/images/"..file_path
res = ngx.location.capture(
file_path,
{method = ngx.HTTP_GET}
)
ngx.say(res.body)
end
lua/api/redis.lua: 负责redis的创建及设置
---
--- redis util
--- Generated by EmmyLua(https://github.com/EmmyLua)
--- Created by SSM.
--- DateTime: 2020/3/24 10:01
---
local redis_c = require "resty.redis"
local ok, new_tab = pcall(require, "table.new")
if not ok or type(new_tab) ~= "function" then
new_tab = function (narr, nrec) return {} end
end
local _M = new_tab(0, 155)
_M._VERSION = "0.01"
local commands = {
"append", "auth", "bgrewriteaof",
"bgsave", "bitcount", "bitop",
"blpop", "brpop",
"brpoplpush", "client", "config",
"dbsize",
"debug", "decr", "decrby",
"del", "discard", "dump",
"echo",
"eval", "exec", "exists",
"expire", "expireat", "flushall",
"flushdb", "get", "getbit",
"getrange", "getset", "hdel",
"hexists", "hget", "hgetall",
"hincrby", "hincrbyfloat", "hkeys",
"hlen",
"hmget", "hmset", "hscan",
"hset",
"hsetnx", "hvals", "incr",
"incrby", "incrbyfloat", "info",
"keys",
"lastsave", "lindex", "linsert",
"llen", "lpop", "lpush",
"lpushx", "lrange", "lrem",
"lset", "ltrim", "mget",
"migrate",
"monitor", "move", "mset",
"msetnx", "multi", "object",
"persist", "pexpire", "pexpireat",
"ping", "psetex", "psubscribe",
"pttl",
"publish", --[[ "punsubscribe", ]] "pubsub",
"quit",
"randomkey", "rename", "renamenx",
"restore",
"rpop", "rpoplpush", "rpush",
"rpushx", "sadd", "save",
"scan", "scard", "script",
"sdiff", "sdiffstore",
"select", "set", "setbit",
"setex", "setnx", "setrange",
"shutdown", "sinter", "sinterstore",
"sismember", "slaveof", "slowlog",
"smembers", "smove", "sort",
"spop", "srandmember", "srem",
"sscan",
"strlen", --[[ "subscribe", ]] "sunion",
"sunionstore", "sync", "time",
"ttl",
"type", --[[ "unsubscribe", ]] "unwatch",
"watch", "zadd", "zcard",
"zcount", "zincrby", "zinterstore",
"zrange", "zrangebyscore", "zrank",
"zrem", "zremrangebyrank", "zremrangebyscore",
"zrevrange", "zrevrangebyscore", "zrevrank",
"zscan",
"zscore", "zunionstore", "evalsha"
}
local mt = { __index = _M }
local function is_redis_null( res )
if type(res) == "table" then
for k,v in pairs(res) do
if v ~= ngx.null then
return false
end
end
return true
elseif res == ngx.null then
return true
elseif res == nil then
return true
end
return false
end
-- change connect address and password as you need
function _M.connect_mod( self, redis )
redis:set_timeout(self.timeout)
local ok, err = redis:connect("192.168.1.8", 6379)
-- 没有密码的话,下面这句话就可以去掉了
ok, err = redis:auth("123456")
return ok, err
end
function _M.set_keepalive_mod( redis )
-- put it into the connection pool of size 100, with 60 seconds max idle time
return redis:set_keepalive(60000, 100)
end
function _M.init_pipeline( self )
self._reqs = {}
end
function _M.commit_pipeline( self )
local reqs = self._reqs
if nil == reqs or 0 == #reqs then
return {}, "no pipeline"
else
self._reqs = nil
end
local redis, err = redis_c:new()
if not redis then
return nil, err
end
local ok, err = self:connect_mod(redis)
if not ok then
return {}, err
end
redis:init_pipeline()
for _, vals in ipairs(reqs) do
local fun = redis[vals[1]]
table.remove(vals , 1)
fun(redis, unpack(vals))
end
local results, err = redis:commit_pipeline()
if not results or err then
return {}, err
end
if is_redis_null(results) then
results = {}
ngx.log(ngx.WARN, "is null")
end
-- table.remove (results , 1)
self.set_keepalive_mod(redis)
for i,value in ipairs(results) do
if is_redis_null(value) then
results[i] = nil
end
end
return results, err
end
function _M.subscribe( self, channel )
local redis, err = redis_c:new()
if not redis then
return nil, err
end
local ok, err = self:connect_mod(redis)
if not ok or err then
return nil, err
end
local res, err = redis:subscribe(channel)
if not res then
return nil, err
end
res, err = redis:read_reply()
if not res then
return nil, err
end
redis:unsubscribe(channel)
self.set_keepalive_mod(redis)
return res, err
end
local function do_command(self, cmd, ... )
if self._reqs then
table.insert(self._reqs, {cmd, ...})
return
end
local redis, err = redis_c:new()
if not redis then
return nil, err
end
local ok, err = self:connect_mod(redis)
if not ok or err then
return nil, err
end
local fun = redis[cmd]
local result, err = fun(redis, ...)
if not result or err then
-- ngx.log(ngx.ERR, "pipeline result:", result, " err:", err)
return nil, err
end
if is_redis_null(result) then
result = nil
end
self.set_keepalive_mod(redis)
return result, err
end
--- 把声明所有的方法
for i = 1, #commands do
local cmd = commands[i]
_M[cmd] =
function (self, ...)
return do_command(self, cmd, ...)
end
end
function _M.new(self, opts)
opts = opts or {}
local timeout = (opts.timeout and opts.timeout * 1000) or 1000
local db_index= opts.db_index or 0
return setmetatable({
timeout = timeout,
db_index = db_index,
_reqs = nil }, mt)
end
return _M
OpenResty真的很强大,有时间的话真的想透彻的学学,下次一定。。。。