zoukankan      html  css  js  c++  java
  • 由OpenResty粘合的企业Web架构

    前言:
        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。

        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()函数,如下:

    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下配置如下:

    access_by_lua_file lua/ds_hotlink/accesskey.lua;

    在accesskey.lua中依然是调用在init.lua中初始化的hotlink_accesskey_module()函数,如下:

    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和解析得到的时间来判断散列值是否相匹配,并且在其中提供诸如超时时间和日志的附属功能,相关代码比较简单,贴在这里:

    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..""
    "
       fd:write(line)
       fd:flush()
    end

    其中的config模块如下:

    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中数组和字典)中,然后写下如下的代码:

    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.html

    感谢:
         在探索OpenResty的过程中,得到了春哥以及开源社区许多朋友的帮助,才得以能够有此文章,在此表示感谢。
    ruoshan https://github.com/ruoshan
    wendal https://github.com/wendal
    agentzh https://github.com/agentzh
    作者:Hevienz
    出处:http://www.cnblogs.com/hymenz/
    知识共享许可协议
    本博客原创作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
  • 相关阅读:
    分治法的经典问题——大整数相乘
    分治法的经典问题——大整数相乘
    L2-013. 红色警报(dfs)
    L2-013. 红色警报(dfs)
    L2-012. 关于堆的判断
    L2-012. 关于堆的判断
    二进制和位运算中的异或
    2015年天勤考研机试模拟赛 A 推断三角形
    怎样利用kettle官方社区查找关于carte服务的设置
    openCV中 libopencv-nonfree-dev的安装: undefined reference to `cv::initModule_nonfree()&#39;
  • 原文地址:https://www.cnblogs.com/hymenz/p/3791185.html
Copyright © 2011-2022 走看看