[
  {
    "path": ".gitignore",
    "content": ".DS_Store\n"
  },
  {
    "path": "README.md",
    "content": "# openresty-web-dev\n\n本项目是我写的一系列openresty web 前端开发文章的实例demo，方便测试运行，喜欢请点↑↑↑右上角Star↑↑↑\n### 文章目录\n\n[openresty 前端开发序](http://blog.csdn.net/qq362228416/article/details/53537103)\n\n[openresty 前端开发入门一](https://github.com/362228416/openresty-web-dev/tree/master/demo1)\n\n[openresty 前端开发入门二](https://github.com/362228416/openresty-web-dev/tree/master/demo2)\n\n[openresty 前端开发入门三之JSON篇](https://github.com/362228416/openresty-web-dev/tree/master/demo3)\n\n[openresty 前端开发入门四之Redis篇](https://github.com/362228416/openresty-web-dev/tree/master/demo4)\n\n[openresty 前端开发入门五之Mysql篇](https://github.com/362228416/openresty-web-dev/tree/master/demo5)\n\n[openresty 前端开发入门六之调试篇](https://github.com/362228416/openresty-web-dev/tree/master/demo6)\n\n[openresty 前端开发轻量级MVC框架封装一（控制器篇）](https://github.com/362228416/openresty-web-dev/tree/master/demo8)\n\n[openresty 前端开发轻量级MVC框架封装二（渲染篇）](https://github.com/362228416/openresty-web-dev/tree/master/demo9)\n\n[openresty 前端开发进阶一之http后端](https://github.com/362228416/openresty-web-dev/tree/master/demo7)\n\n[openresty 前端开发进阶二之https后端](https://github.com/362228416/openresty-web-dev/tree/master/demo13)\n\nopenresty 前端开发进阶三之后端整合二\n\nopenresty 前端开发进阶四之session篇\n\n[openresty 前端开发进阶五之cookie篇](https://github.com/362228416/openresty-web-dev/tree/master/demo12)\n\nopenresty 前端开发进阶六之websocket篇\n\nopenresty 前端开发高级应用一之性能优化\n\nopenresty 前端开发高级应用一之动态追踪技术\n\nopenresty 简单应用\n\nopenresty 灰度发布\n\n[openresty lua单一入口实现](https://github.com/362228416/openresty-web-dev/tree/master/demo16)\n\n[openresty 用lua实现负载均衡](https://github.com/362228416/openresty-web-dev/tree/master/demo15)\n\n[openresty 定时任务](https://github.com/362228416/openresty-web-dev/tree/master/demo14)\n\n[openresty 应用打包并使用luajit编译lua代码实现简单加密](https://my.oschina.net/362228416/blog/846741)\n\n[openresty 整合阿里云oss](https://github.com/362228416/lua-resty-oss)\n\n[openresty url重写](https://github.com/362228416/openresty-web-dev/tree/master/demo10)\n\n[openresty IP防火墙](https://github.com/362228416/openresty-web-dev/tree/master/demo11)\n\n[openresty 之 nginx-api-for-lua](https://github.com/openresty/lua-nginx-module#nginx-api-for-lua) ngx所有API都在这里，最好的文档\n\n... 持续更新待续\n\n测试运行\n\n```\n$ git checkout https://github.com/362228416/openresty-web-dev.git\n$ cd openresty-web-dev\n$ openresty -p demo1 # 这里按实际情况来选择一个demo运行\n```\n\n如果你觉得不错，并在项目中使用到它，还可以请我喝杯咖啡☕️\n\n![微信二维码](https://github.com/362228416/openresty-web-dev/blob/master/wxpay.png?raw=true)\n"
  },
  {
    "path": "demo1/README.md",
    "content": "OpenResty ™ 是一个基于 Nginx 与 Lua 的高性能 Web 平台，其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。\n\nOpenResty 通过汇聚各种设计精良的 Nginx 模块（主要由 OpenResty 团队自主开发），从而将 Nginx 有效地变成一个强大的通用 Web 应用平台。这样，Web 开发人员和系统工程师可以使用 Lua 脚本语言调动 Nginx 支持的各种 C 以及 Lua 模块，快速构造出足以胜任 10K 乃至 1000K 以上单机并发连接的高性能 Web 应用系统。\n\nOpenResty 的目标是让你的Web服务直接跑在 Nginx 服务内部，充分利用 Nginx 的非阻塞 I/O 模型，不仅仅对 HTTP 客户端请求,甚至于对远程后端诸如 MySQL、PostgreSQL、Memcached 以及 Redis 等都进行一致的高性能响应。\n\n以上是从官网拷过来的原话，我们通过写一个hello world，来走进openresty开发之旅\n\n**下载地址**\nhttp://openresty.org/cn/download.html\n\n有的人不会下windows版，所以我这里直接给出下载地址，现在是最新版本，学会了之后，可以自己下载\n\nmac、linux 平台\nhttps://openresty.org/download/openresty-1.11.2.2.tar.gz\n\nwindows平台\nhttps://openresty.org/download/openresty-1.11.2.2-win32.zip\n\n**关于安装**\nmac、linux安装看这里 http://openresty.org/cn/installation.html\nwindows 直接之后直接启动就可以了，不用安装\n\n安装完之后别着急启动\n\n**开始写代码了**\n\n打开nginx目录下的conf/nginx.conf文件\n\n在server中新增以下代码\n```\nlocation /hello {\n    default_type text/html;\n    content_by_lua '\n        ngx.say(\"<p>hello, world</p>\")\n    ';\n}\n```\n\n类似这样\n```\nhttp {\n    server {\n        listen 80;\n\t    server_name localhost;\n        location / {\n            default_type text/html;\n            content_by_lua '\n                ngx.say(\"<p>hello, world</p>\")\n            ';\n        }\n    }\n}\n```\n\n现在启动nginx，然后访问 http://localhost/hello，不出意外的话应该就OK了，如果你之前启动了，那么需要reload一下，nginx的基本操作这里就不介绍了\n\n通过**ngx.say** 我们可以往客户端输出响应文本，在整个request周期内可以多次调用，接受的参数是字符串，如果输出table会报错\n\n还有一个输出的函数是**ngx.print**，同样也是输出响应内容\n\n这里有一个**坑**，就是调用ngx.say会在输出我们的内容之后会额外输出一个换行，但是ngx.print就不会，我之前一个同事用lua输出了一个200，然后前端用ajax调用，判断是否200，死活是false，看输出的内容就是200，差点怀疑人生，幸亏我比较机智，直接查看ajax请求源码，发现行号多了一行，就是那个换行，如果不仔细根本看不出来，这个坑被我一个同事踩了\n\n上面的代码直接把lua代码写到nginx配置里面了，维护起来不是很方便，而且写代码的时候也没有语法高亮，提示这些，比较蛋疼，我们把它拿出来一个单独的文件，并放到一个nginx下面单独的lua目录下，方便管理\n\nlua/hello.lua\n```\nngx.say(\"<p>hello, world</p>\")\n```\n\nnginx.conf 改成这样\n```\nlocation / {\n     default_type text/html;\n     content_by_lua_file lua/hello.lua;\n }\n```\n\n然后nginx reload 一下，再看效果，应该是一样的\n\n我们修改一下hello.lua，在hello，world后面加一个！号，刷新浏览器发现没有任何变化，这是因为lua代码被缓存了，这就导致我们修改代码，就必须reload nginx 在能看到效果，如果是这样，那简直要疯了，其实要解决这个问题很简单，只要在nginx.conf里面把lua缓存给禁止掉就行了，当然在生产线上一定要把缓存打开，不然效果大打折扣\n\n禁止lua缓存\n```\nserver {\n   listen 80;\n   server_name localhost;\n   lua_code_cache off; # 这个可以放在server下面，也可以凡在location下面，作用的范围也不一样，为了简单直接放这里了\n   location / {\n       default_type text/html;\n       content_by_lua_file lua/hello.lua;\n   }\n}\n```\n改完之后reload一下nginx，这里**重点声明**一下修改nginx配置必须要reload，否则是没有效果的\n\n现在我们再改hello.lua，然后刷新浏览器就会发现可以实时生效了\n\n观察以上代码其实还会发现一个问题，如果我们想要处理很多个请求，那不是要在nginx里面配置N个location吗，我们肯定不会这么做，这里可以通过nginx正在匹配动态指定lua文件名，即可完成我们的需求，后台我再介绍如何打造一个属于我们的mvc轻量级框架，这里我们先这么做\n\nlocation 改成这样\n```\nlocation ~ /lua/(.+) {\n\t content_by_lua_file lua/$1.lua;\n}\n```\n\nreload nginx\n\n这个时候访问hello world的请求url就变成了\nhttp://localhost/lua/hello 了\n同理，我们在lua文件里面创建一个welcome.lua的话，就可以通过\nhttp://localhost/lua/welcome 来访问了\n以此类推，我们就可以通过新增多个文件来处理不同的请求了，而且修改了还能实时生效，剩下的就是完成业务代码了，比如调一下redis返回数据，或者mysql之类的，有悟性的同学在这里已经可以做很多事情了\n\n[示例代码](https://github.com/362228416/openresty-web-dev)  参见demo1部分\n"
  },
  {
    "path": "demo1/conf/nginx.conf",
    "content": "\nworker_processes  1;\n\nerror_log logs/error.log;\n\nevents {\n    worker_connections 1024;\n}\n\nhttp {\n    server {\n        listen 80;\n        server_name localhost;\n        lua_code_cache off;\n        location ~ /lua/(.+) {\n        \tdefault_type text/html;\t\n\t\t    content_by_lua_file lua/$1.lua;\n\t\t}\n    }\n}"
  },
  {
    "path": "demo1/logs/.gitignore",
    "content": "*.log\n*.pid\n"
  },
  {
    "path": "demo1/lua/hello.lua",
    "content": "ngx.say(\"<p>hello, world!</p>\")"
  },
  {
    "path": "demo1/lua/welcome.lua",
    "content": "ngx.say(\"welcome to openresty world!!\")"
  },
  {
    "path": "demo10/README.md",
    "content": "#### 这一章主要介绍一下怎么用lua来进行url重写，其实通过nginx也可以完成url重写，但是重写规则比较复杂的时候，用nginx就没有那么方便了，用lua可以轻松搞定\n\n这里用到几个最核心的api就是`ngx.redirect`、`ngx.exec`\n\n# ngx.redirect\n\n顾名思义，是执行重定向动作，重定向会导致url变更，返回302状态码，浏览器会重新发起一个新请求，到重定向后的url，用法很简单\n\n```\n\nold uri\n\n/index/article?id=10000\n\nngx.redirect('/article' .. ngx.var.is_args .. ngx.var.args)\n\nnew uri\n\n/article?id=10000\n\n```\n\n# ngx.exec\n\n直接在内部完成请求，并且直接返回内容，url不会变化，用法跟上面差不多\n\n```\n\nold uri\n\n/index/article?id=10000\n\nngx.exec('/article' .. ngx.var.is_args .. ngx.var.args)\n\nnew uri\n\n/index/article?id=10000\n\n```\n\n为了使得url重写统一写在一个地方，便于维护，我们可以拓展一下之前封装的mvc框架\n\n加上这么一段代码\n\nlite/mvc.lua\n\n```\n-- url 重写 begin\n\nlocal ret, rewrite = pcall(require, \"rewrite\") -- 安全引入rewrite模块，假如没有也不会报错\n\nif ret then\n    local c_ret, r_ret = pcall(rewrite.exec, uri)\n    -- c_ret 表示执行成功，r_ret 表示已重定向，两者都为true，则表示重写成功，则不继续往下执行\n    if c_ret and r_ret then\n        return\n    end\nend\n\n-- url 重写end\n```\n\n然后在lua目录新增一个rewrite.lua文件，内容如下\n\nrewrite.lua\n\n```\n\nlocal _M = {}\n\nfunction _M.exec(uri)\n    local rewrite_urls = {}\n\n    local queryString = ngx.var.args\n    if queryString == nil then queryString = \"\" end\n\n    rewrite_urls['/index/article'] = '/article?' .. queryString\n\n    local match_url = rewrite_urls[uri]\n\n    if match_url then\n        -- ngx.redirect(match_url) -- url 变化\n        ngx.exec(match_url)        -- url 无变化\n        return true\n    end\n    return false\nend\n\nreturn _M\n\n```\n\nurl重新不限于当期站点，可以跨域名，比如一些很常见的场景，电脑端网页在手机端访问的时候可以调整到另外一个域名，或者页面，更好的在移动端显示，例如\n\n```\nlocal agent = ngx.var.http_user_agent\nif agent ~= nil then\nlocal m, ret = ngx.re.match(agent, \"Android|webOS|iPhone|iPod|BlackBerry\")\nif m ~= nil then\n    -- rewrite ... 同上，只不过外层多了一层判断，判断设备\nend\n```\n\n[示例代码](https://github.com/362228416/openresty-web-dev/tree/master/demo10) 参见demo10部分\n"
  },
  {
    "path": "demo10/conf/nginx.conf",
    "content": "\nworker_processes  1;\n\nerror_log logs/error.log notice;\n\nevents {\n    worker_connections 1024;\n}\n\nhttp {\n    lua_package_path \"$prefix/lua/?.lua;$prefix/lualib/?.lua\";\n    server {\n        listen 80;\n        server_name localhost;\n        lua_code_cache off;\n\n        location / {\n            root lua;\n        \tdefault_type \"text/html; charset=utf-8\";\n        \tcontent_by_lua_file lualib/lite/mvc.lua;\n        }\n\n        location ~ ^/js/|^/css/|\\.html {\n        \troot html;\n        }\n    }\n}\n"
  },
  {
    "path": "demo10/html/index.html",
    "content": "<html>\n<head>\n\t<meta charset=\"UTF-8\">\n\t<title>demo10</title>\n</head>\n<body>\n\t<a href=\"/index/article?id=10000\">URL重写</a>\n</body>\n</html>\n"
  },
  {
    "path": "demo10/logs/.gitignore",
    "content": "*.log\n*.pid\n"
  },
  {
    "path": "demo10/lua/rewrite.lua",
    "content": "\nlocal _M = {}\n\nfunction _M.exec1(uri)       -- 正常版\n    local rewrite_urls = {}\n    local queryString = ngx.var.args\n    if queryString == nil then queryString = \"\" end\n    rewrite_urls['/index/article'] = '/article?' .. queryString\n    local match_url = rewrite_urls[uri]\n    if match_url then\n        -- ngx.redirect(match_url) -- url 变化\n        ngx.exec(match_url)        -- url 无变化\n        return true\n    end\n    return false\nend\n\nfunction _M.exec(uri)      -- 移动端增强版\n    local agent = ngx.var.http_user_agent\n    if agent ~= nil then\n        local m, ret = ngx.re.match(agent, \"Android|webOS|iPhone|iPod|BlackBerry\")\n        if m ~= nil then\n            local rewrite_urls = {}\n            local queryString = ngx.var.args\n            if queryString == nil then queryString = \"\" end\n            rewrite_urls['/index/article'] = '/article?' .. queryString\n            local match_url = rewrite_urls[uri]\n            if match_url then\n                -- ngx.redirect(match_url) -- url 变化\n                ngx.exec(match_url)        -- url 无变化\n                return true\n            end\n        end\n    end\n    return false\nend\n\nreturn _M\n"
  },
  {
    "path": "demo10/lua/web/article.lua",
    "content": "local template = require \"resty.template\"\nlocal req = require \"lite.req\"\n\nlocal _M = {}\n\nfunction _M.index()\n\tlocal args = req.getArgs()\n\tlocal id = args['id'] or 10001\n\tngx.say('new article page ' .. id)\nend\n\nreturn _M\n"
  },
  {
    "path": "demo10/lua/web/index.lua",
    "content": "local req = require \"lite.req\"\n\nlocal _M = {}\n\nfunction _M.article()\n\tlocal args = req.getArgs()\n\tlocal id = args['id'] or 10001\n\tngx.say('old article page ' .. id)\nend\n\nreturn _M\n"
  },
  {
    "path": "demo10/lualib/lite/mvc.lua",
    "content": "\nlocal uri = ngx.var.uri\n-- 如果是首页\nif uri == \"\" or uri == \"/\" then\n    local res = ngx.location.capture(\"/index.html\", {})\n    ngx.say(res.body)\n    return\nend\n\n-- url 重写 begin\n\nlocal ret, rewrite = pcall(require, \"rewrite\") -- 安全引入rewrite模块，假如没有也不会报错\n\nif ret then\n    local c_ret, r_ret = pcall(rewrite.exec, uri)\n    ngx.say(r_ret)\n    -- c_ret 表示执行成功，r_ret 表示已重定向，两者都为true，则表示重写成功，则不继续往下执行\n    if c_ret and r_ret then\n        return\n    end\nend\n\n-- url 重写end\n\nlocal m, err = ngx.re.match(uri, \"([a-zA-Z0-9-]+)/*([a-zA-Z0-9-]+)*\")\n\nlocal is_debug = true       -- 调试阶段，会输出错误信息到页面上\n\nlocal moduleName = m[1]     -- 模块名\nlocal method = m[2]         -- 方法名\n\nif not method then\n    method = \"index\"        -- 默认访问index方法\nelse\n    method = ngx.re.gsub(method, \"-\", \"_\")\nend\n\n-- 控制器默认在web包下面\nlocal prefix = \"web.\"\nlocal path = prefix .. moduleName\n\n-- 尝试引入模块，不存在则报错\nlocal ret, ctrl, err = pcall(require, path)\n\nif ret == false then\n    if is_debug then\n        ngx.status = 404\n        ngx.say(\"<p style='font-size: 50px'>Error: <span style='color:red'>\" .. ctrl .. \"</span> module not found !</p>\")\n    end\n    ngx.exit(404)\nend\n\n-- 尝试获取模块方法，不存在则报错\nlocal req_method = ctrl[method]\n\nif req_method == nil then\n    if is_debug then\n        ngx.status = 404\n        ngx.say(\"<p style='font-size: 50px'>Error: <span style='color:red'>\" .. method .. \"()</span> method not found in <span style='color:red'>\" .. moduleName .. \"</span> lua module !</p>\")\n    end\n    ngx.exit(404)\nend\n\n-- 执行模块方法，报错则显示错误信息，所见即所得，可以追踪lua报错行数\nret, err = pcall(req_method)\n\nif ret == false then\n    if is_debug then\n        ngx.status = 404\n        ngx.say(\"<p style='font-size: 50px'>Error: <span style='color:red'>\" .. err .. \"</span></p>\")\n    else\n        ngx.exit(500)\n    end\nend\n"
  },
  {
    "path": "demo10/lualib/lite/req.lua",
    "content": "local _M = {}\n\n-- 获取http get/post 请求参数\nfunction _M.getArgs()\n    local request_method = ngx.var.request_method\n    local args = ngx.req.get_uri_args()\n    -- 参数获取\n    if \"POST\" == request_method then\n        ngx.req.read_body()\n        local postArgs = ngx.req.get_post_args()\n        if postArgs then\n            for k, v in pairs(postArgs) do\n                args[k] = v\n            end\n        end\n    end\n    return args\nend\n\nreturn _M"
  },
  {
    "path": "demo10/lualib/resty/template.lua",
    "content": "local setmetatable = setmetatable\nlocal loadstring = loadstring\nlocal loadchunk\nlocal tostring = tostring\nlocal setfenv = setfenv\nlocal require = require\nlocal capture\nlocal concat = table.concat\nlocal assert = assert\nlocal prefix\nlocal write = io.write\nlocal pcall = pcall\nlocal phase\nlocal open = io.open\nlocal load = load\nlocal type = type\nlocal dump = string.dump\nlocal find = string.find\nlocal gsub = string.gsub\nlocal byte = string.byte\nlocal null\nlocal sub = string.sub\nlocal ngx = ngx\nlocal jit = jit\nlocal var\n\nlocal _VERSION = _VERSION\nlocal _ENV = _ENV\nlocal _G = _G\n\nlocal HTML_ENTITIES = {\n    [\"&\"] = \"&amp;\",\n    [\"<\"] = \"&lt;\",\n    [\">\"] = \"&gt;\",\n    ['\"'] = \"&quot;\",\n    [\"'\"] = \"&#39;\",\n    [\"/\"] = \"&#47;\"\n}\n\nlocal CODE_ENTITIES = {\n    [\"{\"] = \"&#123;\",\n    [\"}\"] = \"&#125;\",\n    [\"&\"] = \"&amp;\",\n    [\"<\"] = \"&lt;\",\n    [\">\"] = \"&gt;\",\n    ['\"'] = \"&quot;\",\n    [\"'\"] = \"&#39;\",\n    [\"/\"] = \"&#47;\"\n}\n\nlocal VAR_PHASES\n\nlocal ok, newtab = pcall(require, \"table.new\")\nif not ok then newtab = function() return {} end end\n\nlocal caching = true\nlocal template = newtab(0, 12)\n\ntemplate._VERSION = \"1.9\"\ntemplate.cache    = {}\n\nlocal function enabled(val)\n    if val == nil then return true end\n    return val == true or (val == \"1\" or val == \"true\" or val == \"on\")\nend\n\nlocal function trim(s)\n    return gsub(gsub(s, \"^%s+\", \"\"), \"%s+$\", \"\")\nend\n\nlocal function rpos(view, s)\n    while s > 0 do\n        local c = sub(view, s, s)\n        if c == \" \" or c == \"\\t\" or c == \"\\0\" or c == \"\\x0B\" then\n            s = s - 1\n        else\n            break\n        end\n    end\n    return s\nend\n\nlocal function escaped(view, s)\n    if s > 1 and sub(view, s - 1, s - 1) == \"\\\\\" then\n        if s > 2 and sub(view, s - 2, s - 2) == \"\\\\\" then\n            return false, 1\n        else\n            return true, 1\n        end\n    end\n    return false, 0\nend\n\nlocal function readfile(path)\n    local file = open(path, \"rb\")\n    if not file then return nil end\n    local content = file:read \"*a\"\n    file:close()\n    return content\nend\n\nlocal function loadlua(path)\n    return readfile(path) or path\nend\n\nlocal function loadngx(path)\n    local vars = VAR_PHASES[phase()]\n    local file, location = path, vars and var.template_location\n    if sub(file, 1)  == \"/\" then file = sub(file, 2) end\n    if location and location ~= \"\" then\n        if sub(location, -1) == \"/\" then location = sub(location, 1, -2) end\n        local res = capture(concat{ location, '/', file})\n        if res.status == 200 then return res.body end\n    end\n    local root = vars and (var.template_root or var.document_root) or prefix\n    if sub(root, -1) == \"/\" then root = sub(root, 1, -2) end\n    return readfile(concat{ root, \"/\", file }) or path\nend\n\ndo\n    if ngx then\n        VAR_PHASES = {\n            set           = true,\n            rewrite       = true,\n            access        = true,\n            content       = true,\n            header_filter = true,\n            body_filter   = true,\n            log           = true\n        }\n        template.print = ngx.print or write\n        template.load  = loadngx\n        prefix, var, capture, null, phase = ngx.config.prefix(), ngx.var, ngx.location.capture, ngx.null, ngx.get_phase\n        if VAR_PHASES[phase()] then\n            caching = enabled(var.template_cache)\n        end\n    else\n        template.print = write\n        template.load  = loadlua\n    end\n    if _VERSION == \"Lua 5.1\" then\n        local context = { __index = function(t, k)\n            return t.context[k] or t.template[k] or _G[k]\n        end }\n        if jit then\n            loadchunk = function(view)\n                return assert(load(view, nil, nil, setmetatable({ template = template }, context)))\n            end\n        else\n            loadchunk = function(view)\n                local func = assert(loadstring(view))\n                setfenv(func, setmetatable({ template = template }, context))\n                return func\n            end\n        end\n    else\n        local context = { __index = function(t, k)\n            return t.context[k] or t.template[k] or _ENV[k]\n        end }\n        loadchunk = function(view)\n            return assert(load(view, nil, nil, setmetatable({ template = template }, context)))\n        end\n    end\nend\n\nfunction template.caching(enable)\n    if enable ~= nil then caching = enable == true end\n    return caching\nend\n\nfunction template.output(s)\n    if s == nil or s == null then return \"\" end\n    if type(s) == \"function\" then return template.output(s()) end\n    return tostring(s)\nend\n\nfunction template.escape(s, c)\n    if type(s) == \"string\" then\n        if c then return gsub(s, \"[}{\\\">/<'&]\", CODE_ENTITIES) end\n        return gsub(s, \"[\\\">/<'&]\", HTML_ENTITIES)\n    end\n    return template.output(s)\nend\n\nfunction template.new(view, layout)\n    assert(view, \"view was not provided for template.new(view, layout).\")\n    local render, compile = template.render, template.compile\n    if layout then\n        if type(layout) == \"table\" then\n            return setmetatable({ render = function(self, context)\n                local context = context or self\n                context.blocks = context.blocks or {}\n                context.view = compile(view)(context)\n                layout.blocks = context.blocks or {}\n                layout.view = context.view or \"\"\n                return layout:render()\n            end }, { __tostring = function(self)\n                local context = self\n                context.blocks = context.blocks or {}\n                context.view = compile(view)(context)\n                layout.blocks = context.blocks or {}\n                layout.view = context.view\n                return tostring(layout)\n            end })\n        else\n            return setmetatable({ render = function(self, context)\n                local context = context or self\n                context.blocks = context.blocks or {}\n                context.view = compile(view)(context)\n                return render(layout, context)\n            end }, { __tostring = function(self)\n                local context = self\n                context.blocks = context.blocks or {}\n                context.view = compile(view)(context)\n                return compile(layout)(context)\n            end })\n        end\n    end\n    return setmetatable({ render = function(self, context)\n        return render(view, context or self)\n    end }, { __tostring = function(self)\n        return compile(view)(self)\n    end })\nend\n\nfunction template.precompile(view, path, strip)\n    local chunk = dump(template.compile(view), strip ~= false)\n    if path then\n        local file = open(path, \"wb\")\n        file:write(chunk)\n        file:close()\n    end\n    return chunk\nend\n\nfunction template.compile(view, key, plain)\n    assert(view, \"view was not provided for template.compile(view, key, plain).\")\n    if key == \"no-cache\" then\n        return loadchunk(template.parse(view, plain)), false\n    end\n    key = key or view\n    local cache = template.cache\n    if cache[key] then return cache[key], true end\n    local func = loadchunk(template.parse(view, plain))\n    if caching then cache[key] = func end\n    return func, false\nend\n\nfunction template.parse(view, plain)\n    assert(view, \"view was not provided for template.parse(view, plain).\")\n    if not plain then\n        view = template.load(view)\n        if byte(sub(view, 1, 1)) == 27 then return view end\n    end\n    local j = 2\n    local c = {[[\ncontext=... or {}\nlocal function include(v, c) return template.compile(v)(c or context) end\nlocal ___,blocks,layout={},blocks or {}\n]] }\n    local i, s = 1, find(view, \"{\", 1, true)\n    while s do\n        local t, p = sub(view, s + 1, s + 1), s + 2\n        if t == \"{\" then\n            local e = find(view, \"}}\", p, true)\n            if e then\n                local z, w = escaped(view, s)\n                if i < s - w then\n                    c[j] = \"___[#___+1]=[=[\\n\"\n                    c[j+1] = sub(view, i, s - 1 - w)\n                    c[j+2] = \"]=]\\n\"\n                    j=j+3\n                end\n                if z then\n                    i = s\n                else\n                    c[j] = \"___[#___+1]=template.escape(\"\n                    c[j+1] = trim(sub(view, p, e - 1))\n                    c[j+2] = \")\\n\"\n                    j=j+3\n                    s, i = e + 1, e + 2\n                end\n            end\n        elseif t == \"*\" then\n            local e = find(view, \"*}\", p, true)\n            if e then\n                local z, w = escaped(view, s)\n                if i < s - w then\n                    c[j] = \"___[#___+1]=[=[\\n\"\n                    c[j+1] = sub(view, i, s - 1 - w)\n                    c[j+2] = \"]=]\\n\"\n                    j=j+3\n                end\n                if z then\n                    i = s\n                else\n                    c[j] = \"___[#___+1]=template.output(\"\n                    c[j+1] = trim(sub(view, p, e - 1))\n                    c[j+2] = \")\\n\"\n                    j=j+3\n                    s, i = e + 1, e + 2\n                end\n            end\n        elseif t == \"%\" then\n            local e = find(view, \"%}\", p, true)\n            if e then\n                local z, w = escaped(view, s)\n                if z then\n                    if i < s - w then\n                        c[j] = \"___[#___+1]=[=[\\n\"\n                        c[j+1] = sub(view, i, s - 1 - w)\n                        c[j+2] = \"]=]\\n\"\n                        j=j+3\n                    end\n                    i = s\n                else\n                    local n = e + 2\n                    if sub(view, n, n) == \"\\n\" then\n                        n = n + 1\n                    end\n                    local r = rpos(view, s - 1)\n                    if i <= r then\n                        c[j] = \"___[#___+1]=[=[\\n\"\n                        c[j+1] = sub(view, i, r)\n                        c[j+2] = \"]=]\\n\"\n                        j=j+3\n                    end\n                    c[j] = trim(sub(view, p, e - 1))\n                    c[j+1] = \"\\n\"\n                    j=j+2\n                    s, i = n - 1, n\n                end\n            end\n        elseif t == \"(\" then\n            local e = find(view, \")}\", p, true)\n            if e then\n                local z, w = escaped(view, s)\n                if i < s - w then\n                    c[j] = \"___[#___+1]=[=[\\n\"\n                    c[j+1] = sub(view, i, s - 1 - w)\n                    c[j+2] = \"]=]\\n\"\n                    j=j+3\n                end\n                if z then\n                    i = s\n                else\n                    local f = sub(view, p, e - 1)\n                    local x = find(f, \",\", 2, true)\n                    if x then\n                        c[j] = \"___[#___+1]=include([=[\"\n                        c[j+1] = trim(sub(f, 1, x - 1))\n                        c[j+2] = \"]=],\"\n                        c[j+3] = trim(sub(f, x + 1))\n                        c[j+4] = \")\\n\"\n                        j=j+5\n                    else\n                        c[j] = \"___[#___+1]=include([=[\"\n                        c[j+1] = trim(f)\n                        c[j+2] = \"]=])\\n\"\n                        j=j+3\n                    end\n                    s, i = e + 1, e + 2\n                end\n            end\n        elseif t == \"[\" then\n            local e = find(view, \"]}\", p, true)\n            if e then\n                local z, w = escaped(view, s)\n                if i < s - w then\n                    c[j] = \"___[#___+1]=[=[\\n\"\n                    c[j+1] = sub(view, i, s - 1 - w)\n                    c[j+2] = \"]=]\\n\"\n                    j=j+3\n                end\n                if z then\n                    i = s\n                else\n                    c[j] = \"___[#___+1]=include(\"\n                    c[j+1] = trim(sub(view, p, e - 1))\n                    c[j+2] = \")\\n\"\n                    j=j+3\n                    s, i = e + 1, e + 2\n                end\n            end\n        elseif t == \"-\" then\n            local e = find(view, \"-}\", p, true)\n            if e then\n                local x, y = find(view, sub(view, s, e + 1), e + 2, true)\n                if x then\n                    local z, w = escaped(view, s)\n                    if z then\n                        if i < s - w then\n                            c[j] = \"___[#___+1]=[=[\\n\"\n                            c[j+1] = sub(view, i, s - 1 - w)\n                            c[j+2] = \"]=]\\n\"\n                            j=j+3\n                        end\n                        i = s\n                    else\n                        y = y + 1\n                        x = x - 1\n                        if sub(view, y, y) == \"\\n\" then\n                            y = y + 1\n                        end\n                        local b = trim(sub(view, p, e - 1))\n                        if b == \"verbatim\" or b == \"raw\" then\n                            if i < s - w then\n                                c[j] = \"___[#___+1]=[=[\\n\"\n                                c[j+1] = sub(view, i, s - 1 - w)\n                                c[j+2] = \"]=]\\n\"\n                                j=j+3\n                            end\n                            c[j] = \"___[#___+1]=[=[\"\n                            c[j+1] = sub(view, e + 2, x)\n                            c[j+2] = \"]=]\\n\"\n                            j=j+3\n                        else\n                            if sub(view, x, x) == \"\\n\" then\n                                x = x - 1\n                            end\n                            local r = rpos(view, s - 1)\n                            if i <= r then\n                                c[j] = \"___[#___+1]=[=[\\n\"\n                                c[j+1] = sub(view, i, r)\n                                c[j+2] = \"]=]\\n\"\n                                j=j+3\n                            end\n                            c[j] = 'blocks[\"'\n                            c[j+1] = b\n                            c[j+2] = '\"]=include[=['\n                            c[j+3] = sub(view, e + 2, x)\n                            c[j+4] = \"]=]\\n\"\n                            j=j+5\n                        end\n                        s, i = y - 1, y\n                    end\n                end\n            end\n        elseif t == \"#\" then\n            local e = find(view, \"#}\", p, true)\n            if e then\n                local z, w = escaped(view, s)\n                if i < s - w then\n                    c[j] = \"___[#___+1]=[=[\\n\"\n                    c[j+1] = sub(view, i, s - 1 - w)\n                    c[j+2] = \"]=]\\n\"\n                    j=j+3\n                end\n                if z then\n                    i = s\n                else\n                    e = e + 2\n                    if sub(view, e, e) == \"\\n\" then\n                        e = e + 1\n                    end\n                    s, i = e - 1, e\n                end\n            end\n        end\n        s = find(view, \"{\", s + 1, true)\n    end\n    s = sub(view, i)\n    if s and s ~= \"\" then\n        c[j] = \"___[#___+1]=[=[\\n\"\n        c[j+1] = s\n        c[j+2] = \"]=]\\n\"\n        j=j+3\n    end\n    c[j] = \"return layout and include(layout,setmetatable({view=table.concat(___),blocks=blocks},{__index=context})) or table.concat(___)\"\n    return concat(c)\nend\n\nfunction template.render(view, context, key, plain)\n    assert(view, \"view was not provided for template.render(view, context, key, plain).\")\n    return template.print(template.compile(view, key, plain)(context))\nend\n\nreturn template\n"
  },
  {
    "path": "demo11/README.md",
    "content": "## 为了保护站点安全，有时候我们需要一个web防火墙，来实现拦截过滤，在openresty里面其实很好做这个功能，下面我们简单实现一个黑白名单ip的功能\n\n# mvc 中加上waf模块\n\nlite/mvc.lua\n\n```\n-- waf begin\n\nlocal ret, waf = pcall(require, \"waf\") -- 安全引入rewrite模块，假如没有也不会报错\n\nif ret then\n    local c_ret, r_ret = pcall(waf.exec)\n    -- c_ret 表示执行成功，r_ret 表示已重定向，两者都为true，则表示重写成功，则不继续往下执行\n    if c_ret and r_ret then\n        return\n    end\nend\n\n-- waf end\n```\n\n这个不一定要在mvc里面做，也可以直接在access_by_lua阶段做\n\n# 获取用户IP\n\n```\nfunction get_client_ip()\n    local ip = ngx.req.get_headers()[\"x_forwarded_for\"]\n    if not ip then\n       ip = ngx.var.remote_addr\n    else\n       ip = ngx.re.gsub(ip, \",.*\", \"\")\n    end\n    return ip\nend\n```\n\n# 白名单实现\n\n原理就是预先设置好一个`白名单列表`，然后判断ip是否在列表中，在则不做处理，不在则直接调用`ngx.exit`中断当前请求\n\n```\n-- 实际情况可以根据需要将ip放在共享内存，或者redis当中，这里为了简单直接写死了\nlocal white_list_str = \"127.0.0.1,192.168.0.168\"\nlocal white_list = {}\nlocal it, err = ngx.re.gmatch(white_list_str, '([0-9]+)[.]([0-9]+)[.]([0-9]+)[.]([0-9]+)')\nwhile true do\n    local m, err = it()\n    if err then\n        ngx.log(ngx.ERR, \"error: \", err)\n        return\n    end\n    if not m then   break   end\n    white_list[m[0]] =  true\nend\n\nlocal ip = get_client_ip()\nif not white_list[ip] then\n    ngx.exit(444)\n    return true\nend\nreturn false\n```\n\n# 黑名单实现\n\n原理就是预先设置好一个`黑名单列表`，然后判断ip是否在列表中，在则直接调用`ngx.exit`中断当前请求，不在则不做处理，跟白名单刚好相反\n\n```\n-- 实际情况可以根据需要将ip放在共享内存，或者redis当中，这里为了简单直接写死了\nlocal black_list_str = \"127.0.0.1,192.168.0.168\"\nlocal black_list = {}\nlocal it, err = ngx.re.gmatch(black_list_str, '([0-9]+)[.]([0-9]+)[.]([0-9]+)[.]([0-9]+)')\nwhile true do\n    local m, err = it()\n    if err then\n        ngx.log(ngx.ERR, \"error: \", err)\n        return\n    end\n    if not m then   break   end\n    black_list[m[0]] =  true\nend\n\nlocal ip = get_client_ip()\nif black_list[ip] then\n    ngx.exit(444)\n    return true\nend\nreturn false\n```\n\n# 黑白名单\n\n前面的黑白名单只能单独使用，混合使用还有一点问题，主要是白名单部分，判断不在白名单就直接中断请求了，只要稍微调整一下代码就可以了\n\n```\nlocal ip = get_client_ip()\n-- 先检查白名单，在白名单内，则直接放行\nif white_list[ip] then\n    return false\nend\n-- 如果在黑名单内直接中断请求\nif black_list[ip] then\n    ngx.exit(444)\n    return true\nend\n```\n\nOK, 访问 http://localhost\n\n然后，把waf.lua 里面的white_list_str、black_list_str中的127.0.0.1去掉看效果\n\n这里只是做一个简单的ip防火墙，还比较初级，但是还是有点用的，可以用来控制调用的权限，保护服务不暴露，只针对部分服务器开放，对于一些小规模的攻击可以直接在nginx里面做拦截，而不需要动`iptables`。\n\n我之前配合共享内存做了一个简单的防攻击防火墙，用共享内存实时的记录ip的访问次数跟速率，对于部分非法请求直接中断\n\n具体应用场景看需求，没有做不到，只有想不到...\n\n[示例代码](https://github.com/362228416/openresty-web-dev/tree/master/demo11) 参见demo11部分\n"
  },
  {
    "path": "demo11/conf/nginx.conf",
    "content": "\nworker_processes  1;\n\nerror_log logs/error.log notice;\n\nevents {\n    worker_connections 1024;\n}\n\nhttp {\n    lua_package_path \"$prefix/lua/?.lua;$prefix/lualib/?.lua\";\n    server {\n        listen 80;\n        server_name localhost;\n        lua_code_cache off;\n\n        location / {\n            root lua;\n        \tdefault_type \"text/html; charset=utf-8\";\n        \tcontent_by_lua_file lualib/lite/mvc.lua;\n        }\n\n        location ~ ^/js/|^/css/|\\.html {\n        \troot html;\n        }\n    }\n}\n"
  },
  {
    "path": "demo11/html/index.html",
    "content": "<html>\n<head>\n\t<meta charset=\"UTF-8\">\n\t<title>demo10</title>\n</head>\n<body>\n\tip 防火墙示例\n</body>\n</html>\n"
  },
  {
    "path": "demo11/logs/.gitignore",
    "content": "*.log\n*.pid\n"
  },
  {
    "path": "demo11/lua/rewrite.lua",
    "content": "\nlocal _M = {}\n\nfunction _M.exec1(uri)       -- 正常版\n    local rewrite_urls = {}\n    local queryString = ngx.var.args\n    if queryString == nil then queryString = \"\" end\n    rewrite_urls['/index/article'] = '/article?' .. queryString\n    local match_url = rewrite_urls[uri]\n    if match_url then\n        -- ngx.redirect(match_url) -- url 变化\n        ngx.exec(match_url)        -- url 无变化\n        return true\n    end\n    return false\nend\n\nfunction _M.exec(uri)      -- 移动端增强版\n    local agent = ngx.var.http_user_agent\n    if agent ~= nil then\n        local m, ret = ngx.re.match(agent, \"Android|webOS|iPhone|iPod|BlackBerry\")\n        if m ~= nil then\n            local rewrite_urls = {}\n            local queryString = ngx.var.args\n            if queryString == nil then queryString = \"\" end\n            rewrite_urls['/index/article'] = '/article?' .. queryString\n            local match_url = rewrite_urls[uri]\n            if match_url then\n                -- ngx.redirect(match_url) -- url 变化\n                ngx.exec(match_url)        -- url 无变化\n                return true\n            end\n        end\n    end\n    return false\nend\n\nreturn _M\n"
  },
  {
    "path": "demo11/lua/waf.lua",
    "content": "\nlocal _M = {}\n\nfunction parse_ip(ip_str)\n    local ip_list = {}\n    local it, err = ngx.re.gmatch(ip_str, '([0-9]+)[.]([0-9]+)[.]([0-9]+)[.]([0-9]+)')\n    while true do\n        local m, err = it()\n        if err then\n            ngx.log(ngx.ERR, \"error: \", err)\n            return\n        end\n        if not m then   break   end\n        ip_list[m[0]] =  true\n    end\n    return ip_list\nend\n\n-- 实际情况可以根据需要将ip放在共享内存，或者redis当中，这里为了简单直接写死了\n\nlocal white_list_str = \"127.0.0.1,192.168.0.168\"\nlocal white_list = parse_ip(white_list_str)\n\nlocal black_list_str = \"127.0.0.1,192.168.0.168\"\nlocal black_list = parse_ip(black_list_str)\n\nfunction get_client_ip()\n    local ip = ngx.req.get_headers()[\"x_forwarded_for\"]\n    if not ip then\n       ip = ngx.var.remote_addr\n    else\n       ip = ngx.re.gsub(ip, \",.*\", \"\")\n    end\n    return ip\nend\n\nfunction _M.exec()\n    -- 1、白名单\n    -- local ip = get_client_ip()\n    -- if not white_list[ip] then\n    --     ngx.exit(444)\n    --     return true\n    -- end\n    -- return false\n\n    -- 2、黑名单\n    -- local ip = get_client_ip()\n    -- if black_list[ip] then\n    --     ngx.exit(444)\n    --     return true\n    -- end\n    -- return false\n\n    -- 3、同时支持黑白名单\n    local ip = get_client_ip()\n    -- 先检查白名单，在白名单内，则直接放行\n    if white_list[ip] then\n        -- print('白名单')\n        return false\n    end\n    -- 如果在黑名单内直接中断请求\n    if black_list[ip] then\n        -- print('黑名单')\n        ngx.exit(444)\n        return true\n    end\n    -- 其他规则\n\nend\n\nreturn _M\n"
  },
  {
    "path": "demo11/lua/web/index.lua",
    "content": "local req = require \"lite.req\"\n\nlocal _M = {}\n\nfunction _M.index()\n\tngx.say('home page ')\nend\n\nreturn _M\n"
  },
  {
    "path": "demo11/lualib/lite/mvc.lua",
    "content": "\n-- waf begin\n\nlocal ret, waf = pcall(require, \"waf\") -- 安全引入rewrite模块，假如没有也不会报错\n\nif ret then\n    local c_ret, r_ret = pcall(waf.exec)\n    -- c_ret 表示执行成功，r_ret 表示已重定向，两者都为true，则表示重写成功，则不继续往下执行\n    if c_ret and r_ret then\n        return\n    end\nend\n\n-- waf end\n\nlocal uri = ngx.var.uri\n-- 如果是首页\nif uri == \"\" or uri == \"/\" then\n    local res = ngx.location.capture(\"/index.html\", {})\n    ngx.say(res.body)\n    return\nend\n\n-- url 重写 begin\n\nlocal ret, rewrite = pcall(require, \"rewrite\") -- 安全引入rewrite模块，假如没有也不会报错\n\nif ret then\n    local c_ret, r_ret = pcall(rewrite.exec, uri)\n    ngx.say(r_ret)\n    -- c_ret 表示执行成功，r_ret 表示已重定向，两者都为true，则表示重写成功，则不继续往下执行\n    if c_ret and r_ret then\n        return\n    end\nend\n\n-- url 重写end\n\nlocal m, err = ngx.re.match(uri, \"([a-zA-Z0-9-]+)/*([a-zA-Z0-9-]+)*\")\n\nlocal is_debug = true       -- 调试阶段，会输出错误信息到页面上\n\nlocal moduleName = m[1]     -- 模块名\nlocal method = m[2]         -- 方法名\n\nif not method then\n    method = \"index\"        -- 默认访问index方法\nelse\n    method = ngx.re.gsub(method, \"-\", \"_\")\nend\n\n-- 控制器默认在web包下面\nlocal prefix = \"web.\"\nlocal path = prefix .. moduleName\n\n-- 尝试引入模块，不存在则报错\nlocal ret, ctrl, err = pcall(require, path)\n\nif ret == false then\n    if is_debug then\n        ngx.status = 404\n        ngx.say(\"<p style='font-size: 50px'>Error: <span style='color:red'>\" .. ctrl .. \"</span> module not found !</p>\")\n    end\n    ngx.exit(404)\nend\n\n-- 尝试获取模块方法，不存在则报错\nlocal req_method = ctrl[method]\n\nif req_method == nil then\n    if is_debug then\n        ngx.status = 404\n        ngx.say(\"<p style='font-size: 50px'>Error: <span style='color:red'>\" .. method .. \"()</span> method not found in <span style='color:red'>\" .. moduleName .. \"</span> lua module !</p>\")\n    end\n    ngx.exit(404)\nend\n\n-- 执行模块方法，报错则显示错误信息，所见即所得，可以追踪lua报错行数\nret, err = pcall(req_method)\n\nif ret == false then\n    if is_debug then\n        ngx.status = 404\n        ngx.say(\"<p style='font-size: 50px'>Error: <span style='color:red'>\" .. err .. \"</span></p>\")\n    else\n        ngx.exit(500)\n    end\nend\n"
  },
  {
    "path": "demo11/lualib/lite/req.lua",
    "content": "local _M = {}\n\n-- 获取http get/post 请求参数\nfunction _M.getArgs()\n    local request_method = ngx.var.request_method\n    local args = ngx.req.get_uri_args()\n    -- 参数获取\n    if \"POST\" == request_method then\n        ngx.req.read_body()\n        local postArgs = ngx.req.get_post_args()\n        if postArgs then\n            for k, v in pairs(postArgs) do\n                args[k] = v\n            end\n        end\n    end\n    return args\nend\n\nreturn _M"
  },
  {
    "path": "demo11/lualib/resty/template.lua",
    "content": "local setmetatable = setmetatable\nlocal loadstring = loadstring\nlocal loadchunk\nlocal tostring = tostring\nlocal setfenv = setfenv\nlocal require = require\nlocal capture\nlocal concat = table.concat\nlocal assert = assert\nlocal prefix\nlocal write = io.write\nlocal pcall = pcall\nlocal phase\nlocal open = io.open\nlocal load = load\nlocal type = type\nlocal dump = string.dump\nlocal find = string.find\nlocal gsub = string.gsub\nlocal byte = string.byte\nlocal null\nlocal sub = string.sub\nlocal ngx = ngx\nlocal jit = jit\nlocal var\n\nlocal _VERSION = _VERSION\nlocal _ENV = _ENV\nlocal _G = _G\n\nlocal HTML_ENTITIES = {\n    [\"&\"] = \"&amp;\",\n    [\"<\"] = \"&lt;\",\n    [\">\"] = \"&gt;\",\n    ['\"'] = \"&quot;\",\n    [\"'\"] = \"&#39;\",\n    [\"/\"] = \"&#47;\"\n}\n\nlocal CODE_ENTITIES = {\n    [\"{\"] = \"&#123;\",\n    [\"}\"] = \"&#125;\",\n    [\"&\"] = \"&amp;\",\n    [\"<\"] = \"&lt;\",\n    [\">\"] = \"&gt;\",\n    ['\"'] = \"&quot;\",\n    [\"'\"] = \"&#39;\",\n    [\"/\"] = \"&#47;\"\n}\n\nlocal VAR_PHASES\n\nlocal ok, newtab = pcall(require, \"table.new\")\nif not ok then newtab = function() return {} end end\n\nlocal caching = true\nlocal template = newtab(0, 12)\n\ntemplate._VERSION = \"1.9\"\ntemplate.cache    = {}\n\nlocal function enabled(val)\n    if val == nil then return true end\n    return val == true or (val == \"1\" or val == \"true\" or val == \"on\")\nend\n\nlocal function trim(s)\n    return gsub(gsub(s, \"^%s+\", \"\"), \"%s+$\", \"\")\nend\n\nlocal function rpos(view, s)\n    while s > 0 do\n        local c = sub(view, s, s)\n        if c == \" \" or c == \"\\t\" or c == \"\\0\" or c == \"\\x0B\" then\n            s = s - 1\n        else\n            break\n        end\n    end\n    return s\nend\n\nlocal function escaped(view, s)\n    if s > 1 and sub(view, s - 1, s - 1) == \"\\\\\" then\n        if s > 2 and sub(view, s - 2, s - 2) == \"\\\\\" then\n            return false, 1\n        else\n            return true, 1\n        end\n    end\n    return false, 0\nend\n\nlocal function readfile(path)\n    local file = open(path, \"rb\")\n    if not file then return nil end\n    local content = file:read \"*a\"\n    file:close()\n    return content\nend\n\nlocal function loadlua(path)\n    return readfile(path) or path\nend\n\nlocal function loadngx(path)\n    local vars = VAR_PHASES[phase()]\n    local file, location = path, vars and var.template_location\n    if sub(file, 1)  == \"/\" then file = sub(file, 2) end\n    if location and location ~= \"\" then\n        if sub(location, -1) == \"/\" then location = sub(location, 1, -2) end\n        local res = capture(concat{ location, '/', file})\n        if res.status == 200 then return res.body end\n    end\n    local root = vars and (var.template_root or var.document_root) or prefix\n    if sub(root, -1) == \"/\" then root = sub(root, 1, -2) end\n    return readfile(concat{ root, \"/\", file }) or path\nend\n\ndo\n    if ngx then\n        VAR_PHASES = {\n            set           = true,\n            rewrite       = true,\n            access        = true,\n            content       = true,\n            header_filter = true,\n            body_filter   = true,\n            log           = true\n        }\n        template.print = ngx.print or write\n        template.load  = loadngx\n        prefix, var, capture, null, phase = ngx.config.prefix(), ngx.var, ngx.location.capture, ngx.null, ngx.get_phase\n        if VAR_PHASES[phase()] then\n            caching = enabled(var.template_cache)\n        end\n    else\n        template.print = write\n        template.load  = loadlua\n    end\n    if _VERSION == \"Lua 5.1\" then\n        local context = { __index = function(t, k)\n            return t.context[k] or t.template[k] or _G[k]\n        end }\n        if jit then\n            loadchunk = function(view)\n                return assert(load(view, nil, nil, setmetatable({ template = template }, context)))\n            end\n        else\n            loadchunk = function(view)\n                local func = assert(loadstring(view))\n                setfenv(func, setmetatable({ template = template }, context))\n                return func\n            end\n        end\n    else\n        local context = { __index = function(t, k)\n            return t.context[k] or t.template[k] or _ENV[k]\n        end }\n        loadchunk = function(view)\n            return assert(load(view, nil, nil, setmetatable({ template = template }, context)))\n        end\n    end\nend\n\nfunction template.caching(enable)\n    if enable ~= nil then caching = enable == true end\n    return caching\nend\n\nfunction template.output(s)\n    if s == nil or s == null then return \"\" end\n    if type(s) == \"function\" then return template.output(s()) end\n    return tostring(s)\nend\n\nfunction template.escape(s, c)\n    if type(s) == \"string\" then\n        if c then return gsub(s, \"[}{\\\">/<'&]\", CODE_ENTITIES) end\n        return gsub(s, \"[\\\">/<'&]\", HTML_ENTITIES)\n    end\n    return template.output(s)\nend\n\nfunction template.new(view, layout)\n    assert(view, \"view was not provided for template.new(view, layout).\")\n    local render, compile = template.render, template.compile\n    if layout then\n        if type(layout) == \"table\" then\n            return setmetatable({ render = function(self, context)\n                local context = context or self\n                context.blocks = context.blocks or {}\n                context.view = compile(view)(context)\n                layout.blocks = context.blocks or {}\n                layout.view = context.view or \"\"\n                return layout:render()\n            end }, { __tostring = function(self)\n                local context = self\n                context.blocks = context.blocks or {}\n                context.view = compile(view)(context)\n                layout.blocks = context.blocks or {}\n                layout.view = context.view\n                return tostring(layout)\n            end })\n        else\n            return setmetatable({ render = function(self, context)\n                local context = context or self\n                context.blocks = context.blocks or {}\n                context.view = compile(view)(context)\n                return render(layout, context)\n            end }, { __tostring = function(self)\n                local context = self\n                context.blocks = context.blocks or {}\n                context.view = compile(view)(context)\n                return compile(layout)(context)\n            end })\n        end\n    end\n    return setmetatable({ render = function(self, context)\n        return render(view, context or self)\n    end }, { __tostring = function(self)\n        return compile(view)(self)\n    end })\nend\n\nfunction template.precompile(view, path, strip)\n    local chunk = dump(template.compile(view), strip ~= false)\n    if path then\n        local file = open(path, \"wb\")\n        file:write(chunk)\n        file:close()\n    end\n    return chunk\nend\n\nfunction template.compile(view, key, plain)\n    assert(view, \"view was not provided for template.compile(view, key, plain).\")\n    if key == \"no-cache\" then\n        return loadchunk(template.parse(view, plain)), false\n    end\n    key = key or view\n    local cache = template.cache\n    if cache[key] then return cache[key], true end\n    local func = loadchunk(template.parse(view, plain))\n    if caching then cache[key] = func end\n    return func, false\nend\n\nfunction template.parse(view, plain)\n    assert(view, \"view was not provided for template.parse(view, plain).\")\n    if not plain then\n        view = template.load(view)\n        if byte(sub(view, 1, 1)) == 27 then return view end\n    end\n    local j = 2\n    local c = {[[\ncontext=... or {}\nlocal function include(v, c) return template.compile(v)(c or context) end\nlocal ___,blocks,layout={},blocks or {}\n]] }\n    local i, s = 1, find(view, \"{\", 1, true)\n    while s do\n        local t, p = sub(view, s + 1, s + 1), s + 2\n        if t == \"{\" then\n            local e = find(view, \"}}\", p, true)\n            if e then\n                local z, w = escaped(view, s)\n                if i < s - w then\n                    c[j] = \"___[#___+1]=[=[\\n\"\n                    c[j+1] = sub(view, i, s - 1 - w)\n                    c[j+2] = \"]=]\\n\"\n                    j=j+3\n                end\n                if z then\n                    i = s\n                else\n                    c[j] = \"___[#___+1]=template.escape(\"\n                    c[j+1] = trim(sub(view, p, e - 1))\n                    c[j+2] = \")\\n\"\n                    j=j+3\n                    s, i = e + 1, e + 2\n                end\n            end\n        elseif t == \"*\" then\n            local e = find(view, \"*}\", p, true)\n            if e then\n                local z, w = escaped(view, s)\n                if i < s - w then\n                    c[j] = \"___[#___+1]=[=[\\n\"\n                    c[j+1] = sub(view, i, s - 1 - w)\n                    c[j+2] = \"]=]\\n\"\n                    j=j+3\n                end\n                if z then\n                    i = s\n                else\n                    c[j] = \"___[#___+1]=template.output(\"\n                    c[j+1] = trim(sub(view, p, e - 1))\n                    c[j+2] = \")\\n\"\n                    j=j+3\n                    s, i = e + 1, e + 2\n                end\n            end\n        elseif t == \"%\" then\n            local e = find(view, \"%}\", p, true)\n            if e then\n                local z, w = escaped(view, s)\n                if z then\n                    if i < s - w then\n                        c[j] = \"___[#___+1]=[=[\\n\"\n                        c[j+1] = sub(view, i, s - 1 - w)\n                        c[j+2] = \"]=]\\n\"\n                        j=j+3\n                    end\n                    i = s\n                else\n                    local n = e + 2\n                    if sub(view, n, n) == \"\\n\" then\n                        n = n + 1\n                    end\n                    local r = rpos(view, s - 1)\n                    if i <= r then\n                        c[j] = \"___[#___+1]=[=[\\n\"\n                        c[j+1] = sub(view, i, r)\n                        c[j+2] = \"]=]\\n\"\n                        j=j+3\n                    end\n                    c[j] = trim(sub(view, p, e - 1))\n                    c[j+1] = \"\\n\"\n                    j=j+2\n                    s, i = n - 1, n\n                end\n            end\n        elseif t == \"(\" then\n            local e = find(view, \")}\", p, true)\n            if e then\n                local z, w = escaped(view, s)\n                if i < s - w then\n                    c[j] = \"___[#___+1]=[=[\\n\"\n                    c[j+1] = sub(view, i, s - 1 - w)\n                    c[j+2] = \"]=]\\n\"\n                    j=j+3\n                end\n                if z then\n                    i = s\n                else\n                    local f = sub(view, p, e - 1)\n                    local x = find(f, \",\", 2, true)\n                    if x then\n                        c[j] = \"___[#___+1]=include([=[\"\n                        c[j+1] = trim(sub(f, 1, x - 1))\n                        c[j+2] = \"]=],\"\n                        c[j+3] = trim(sub(f, x + 1))\n                        c[j+4] = \")\\n\"\n                        j=j+5\n                    else\n                        c[j] = \"___[#___+1]=include([=[\"\n                        c[j+1] = trim(f)\n                        c[j+2] = \"]=])\\n\"\n                        j=j+3\n                    end\n                    s, i = e + 1, e + 2\n                end\n            end\n        elseif t == \"[\" then\n            local e = find(view, \"]}\", p, true)\n            if e then\n                local z, w = escaped(view, s)\n                if i < s - w then\n                    c[j] = \"___[#___+1]=[=[\\n\"\n                    c[j+1] = sub(view, i, s - 1 - w)\n                    c[j+2] = \"]=]\\n\"\n                    j=j+3\n                end\n                if z then\n                    i = s\n                else\n                    c[j] = \"___[#___+1]=include(\"\n                    c[j+1] = trim(sub(view, p, e - 1))\n                    c[j+2] = \")\\n\"\n                    j=j+3\n                    s, i = e + 1, e + 2\n                end\n            end\n        elseif t == \"-\" then\n            local e = find(view, \"-}\", p, true)\n            if e then\n                local x, y = find(view, sub(view, s, e + 1), e + 2, true)\n                if x then\n                    local z, w = escaped(view, s)\n                    if z then\n                        if i < s - w then\n                            c[j] = \"___[#___+1]=[=[\\n\"\n                            c[j+1] = sub(view, i, s - 1 - w)\n                            c[j+2] = \"]=]\\n\"\n                            j=j+3\n                        end\n                        i = s\n                    else\n                        y = y + 1\n                        x = x - 1\n                        if sub(view, y, y) == \"\\n\" then\n                            y = y + 1\n                        end\n                        local b = trim(sub(view, p, e - 1))\n                        if b == \"verbatim\" or b == \"raw\" then\n                            if i < s - w then\n                                c[j] = \"___[#___+1]=[=[\\n\"\n                                c[j+1] = sub(view, i, s - 1 - w)\n                                c[j+2] = \"]=]\\n\"\n                                j=j+3\n                            end\n                            c[j] = \"___[#___+1]=[=[\"\n                            c[j+1] = sub(view, e + 2, x)\n                            c[j+2] = \"]=]\\n\"\n                            j=j+3\n                        else\n                            if sub(view, x, x) == \"\\n\" then\n                                x = x - 1\n                            end\n                            local r = rpos(view, s - 1)\n                            if i <= r then\n                                c[j] = \"___[#___+1]=[=[\\n\"\n                                c[j+1] = sub(view, i, r)\n                                c[j+2] = \"]=]\\n\"\n                                j=j+3\n                            end\n                            c[j] = 'blocks[\"'\n                            c[j+1] = b\n                            c[j+2] = '\"]=include[=['\n                            c[j+3] = sub(view, e + 2, x)\n                            c[j+4] = \"]=]\\n\"\n                            j=j+5\n                        end\n                        s, i = y - 1, y\n                    end\n                end\n            end\n        elseif t == \"#\" then\n            local e = find(view, \"#}\", p, true)\n            if e then\n                local z, w = escaped(view, s)\n                if i < s - w then\n                    c[j] = \"___[#___+1]=[=[\\n\"\n                    c[j+1] = sub(view, i, s - 1 - w)\n                    c[j+2] = \"]=]\\n\"\n                    j=j+3\n                end\n                if z then\n                    i = s\n                else\n                    e = e + 2\n                    if sub(view, e, e) == \"\\n\" then\n                        e = e + 1\n                    end\n                    s, i = e - 1, e\n                end\n            end\n        end\n        s = find(view, \"{\", s + 1, true)\n    end\n    s = sub(view, i)\n    if s and s ~= \"\" then\n        c[j] = \"___[#___+1]=[=[\\n\"\n        c[j+1] = s\n        c[j+2] = \"]=]\\n\"\n        j=j+3\n    end\n    c[j] = \"return layout and include(layout,setmetatable({view=table.concat(___),blocks=blocks},{__index=context})) or table.concat(___)\"\n    return concat(c)\nend\n\nfunction template.render(view, context, key, plain)\n    assert(view, \"view was not provided for template.render(view, context, key, plain).\")\n    return template.print(template.compile(view, key, plain)(context))\nend\n\nreturn template\n"
  },
  {
    "path": "demo12/README.md",
    "content": "# 这章主要演示怎么通过lua操作cookie\n\n操作cookie有两种方式，一种是直接设置响应头，另外一种是用[lua-resty-cookie](https://github.com/cloudflare/lua-resty-cookie)库（其实原理是一样的，只不过做了一点封装），这个库也是春哥写的，可以放心使用，下面我分别介绍一下两种方式怎么用\n\n## 读取cookie一（原生）\n```\nprint(ngx.var.http_cookie) -- 获取所有cookie，这里获取到的是一个字符串，如果不存在则返回nil\nprint(ngx.var.cookie_username) -- 获取单个cookie，_后面的cookie的name，如果不存在则返回nil\n```\n\n## 设置cookie一（原生）\n```\nngx.header['Set-Cookie'] = {'a=32; path=/', 'b=4; path=/'}  -- 批量设置cookie\nngx.header['Set-Cookie'] = 'a=32; path=/'                   -- 设置单个cookie，通过多次调用来设置多个值\nngx.header['Set-Cookie'] = 'b=4; path=/'\nngx.header['Set-Cookie'] = 'c=5; path=/; Expires=' .. ngx.cookie_time(ngx.time() + 60 * 30) -- 设置Cookie过期时间为30分钟\n```\n\n熟悉http协议的应该都知道，设置cookie是通过在响应头中的Set-Cookie字段来操作的，既然知道原理那上面的代码应该就很好理解，其实只要知道怎么用lua来设置响应头即可\n\n## 获取cookie二（lua-resty-cookie）\n```\nlocal cookie = resty_cookie:new()\nlocal all_cookie = cookie:get_all() -- 这里获取到所有的cookie，是一个table，如果不存在则返回nil\nprint(cjson.encode(all_cookie))\nprint(cookie:get('c'))              -- 获取单个cookie的值，如果不存在则返回nil\n```\n\n## 设置cookie二（lua-resty-cookie）\n```\ncookie:set({\n    key = \"c\",\n    value = \"123456\",\n    path = \"/\",\n    domain = \"localhost\",\n    expires = ngx.cookie_time(ngx.time() + 60 * 13)\n})\n```\n\nOK, 访问\n\nhttp://localhost/index  原生\n\nhttp://localhost/index2  lua-resty-cookie\n\n两种方式各有各的好处\n\n第一种\n优点：\n简单，无依赖\n缺点：\n太简单？不够抽象，太底层？\n\n第二种\n优点：\n获取设置都很简单，简单的封装了一层，提供了更有表现力的api接口\n缺点：\n多引入一个库，其实也不算什么缺点\n\n看情况而定吧，假如cookie操作得比较少的话，可以用第一种，假如操作得比较多，可以考虑用第二种，编码比较统一\n\n[示例代码](https://github.com/362228416/openresty-web-dev/tree/master/demo12) 参见demo12部分\n"
  },
  {
    "path": "demo12/conf/nginx.conf",
    "content": "\nworker_processes  1;\n\nerror_log logs/error.log notice;\n\nevents {\n    worker_connections 1024;\n}\n\nhttp {\n    lua_package_path \"$prefix/lua/?.lua;$prefix/lualib/?.lua\";\n    server {\n        listen 80;\n        server_name localhost;\n        lua_code_cache off;\n\n        location / {\n            root lua;\n        \tdefault_type \"text/html; charset=utf-8\";\n        \tcontent_by_lua_file lualib/lite/mvc.lua;\n        }\n\n        location ~ ^/js/|^/css/|\\.html {\n        \troot html;\n        }\n    }\n}\n"
  },
  {
    "path": "demo12/logs/.gitignore",
    "content": "*.log\n*.pid\n"
  },
  {
    "path": "demo12/lua/tpl/index.html",
    "content": "<html>\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n    <title>Cookie示例</title>\n</head>\n<body>\n    <script>\n        if (document.cookie.length > 0) {\n            var cookies = document.cookie.split('; ')\n            for (var i = 0; i < cookies.length; i++) {\n                var cookie = cookies[i]\n                var kv = cookie.split('=')\n                document.write(kv[0] + ' = ' + decodeURIComponent(kv[1]) + '<br/>')\n            }\n        }\n    </script>\n</body>\n</html>\n"
  },
  {
    "path": "demo12/lua/web/index.lua",
    "content": "local req = require \"lite.req\"\nlocal template = require \"resty.template\"\nlocal resty_cookie = require \"resty.cookie\"\nlocal cjson = require \"cjson\"\n\nlocal _M = {}\n\nfunction _M.index()\n\t-- ngx.header['Set-Cookie'] = {'a=32; path=/', 'b=4; path=/'}\n\t-- ngx.header['Set-Cookie'] = 'a=32; path=/'\n\t-- ngx.header['Set-Cookie'] = 'b=46; path=/'\n\tngx.header['Set-Cookie'] = 'c=5; path=/; Expires=' .. ngx.cookie_time(ngx.time() + 60 * 13)\n\tprint(ngx.var.http_cookie)\n\t-- print(ngx.var.cookie_username)\n\ttemplate.render('tpl/index.html')\nend\n\nfunction _M.index2()\n\tlocal cookie = resty_cookie:new()\n\n\tcookie:set({\n\t    key = \"c\",\n\t    value = \"123456\",\n\t    path = \"/\",\n\t    domain = \"localhost\",\n\t    expires = ngx.cookie_time(ngx.time() + 60 * 13)\n\t})\n\n\tlocal all_cookie = cookie:get_all()\n\tprint(cjson.encode(all_cookie))\n\tprint(cookie:get('c'))\n\ttemplate.render('tpl/index.html')\nend\n\nreturn _M\n"
  },
  {
    "path": "demo12/lualib/lite/mvc.lua",
    "content": "local uri = ngx.var.uri\n-- 如果是首页\nif uri == \"\" or uri == \"/\" then\n    local res = ngx.location.capture(\"/index.html\", {})\n    ngx.say(res.body)\n    return\nend\n\nlocal m, err = ngx.re.match(uri, \"([a-zA-Z0-9-]+)/*([a-zA-Z0-9-]+)*\")\n\nlocal is_debug = true       -- 调试阶段，会输出错误信息到页面上\n\nlocal moduleName = m[1]     -- 模块名\nlocal method = m[2]         -- 方法名\n\nif not method then\n    method = \"index\"        -- 默认访问index方法\nelse\n    method = ngx.re.gsub(method, \"-\", \"_\")\nend\n\n-- 控制器默认在web包下面\nlocal prefix = \"web.\"\nlocal path = prefix .. moduleName\n\n-- 尝试引入模块，不存在则报错\nlocal ret, ctrl, err = pcall(require, path)\n\nif ret == false then\n    if is_debug then\n        ngx.status = 404\n        ngx.say(\"<p style='font-size: 50px'>Error: <span style='color:red'>\" .. ctrl .. \"</span> module not found !</p>\")\n    end\n    ngx.exit(404)\nend\n\n-- 尝试获取模块方法，不存在则报错\nlocal req_method = ctrl[method]\n\nif req_method == nil then\n    if is_debug then\n        ngx.status = 404\n        ngx.say(\"<p style='font-size: 50px'>Error: <span style='color:red'>\" .. method .. \"()</span> method not found in <span style='color:red'>\" .. moduleName .. \"</span> lua module !</p>\")\n    end\n    ngx.exit(404)\nend\n\n-- 执行模块方法，报错则显示错误信息，所见即所得，可以追踪lua报错行数\nret, err = pcall(req_method)\n\nif ret == false then\n    if is_debug then\n        ngx.status = 404\n        ngx.say(\"<p style='font-size: 50px'>Error: <span style='color:red'>\" .. err .. \"</span></p>\")\n    else\n        ngx.exit(500)\n    end\nend\n"
  },
  {
    "path": "demo12/lualib/lite/req.lua",
    "content": "local _M = {}\n\n-- 获取http get/post 请求参数\nfunction _M.getArgs()\n    local request_method = ngx.var.request_method\n    local args = ngx.req.get_uri_args()\n    -- 参数获取\n    if \"POST\" == request_method then\n        ngx.req.read_body()\n        local postArgs = ngx.req.get_post_args()\n        if postArgs then\n            for k, v in pairs(postArgs) do\n                args[k] = v\n            end\n        end\n    end\n    return args\nend\n\nreturn _M"
  },
  {
    "path": "demo12/lualib/resty/cookie.lua",
    "content": "-- Copyright (C) 2013-2016 Jiale Zhi (calio), CloudFlare Inc.\n-- See RFC6265 http://tools.ietf.org/search/rfc6265\n-- require \"luacov\"\n\nlocal type          = type\nlocal byte          = string.byte\nlocal sub           = string.sub\nlocal format        = string.format\nlocal log           = ngx.log\nlocal ERR           = ngx.ERR\nlocal WARN          = ngx.WARN\nlocal ngx_header    = ngx.header\n\nlocal EQUAL         = byte(\"=\")\nlocal SEMICOLON     = byte(\";\")\nlocal SPACE         = byte(\" \")\nlocal HTAB          = byte(\"\\t\")\n\n-- table.new(narr, nrec)\nlocal ok, new_tab = pcall(require, \"table.new\")\nif not ok then\n    new_tab = function () return {} end\nend\n\nlocal ok, clear_tab = pcall(require, \"table.clear\")\nif not ok then\n    clear_tab = function(tab) for k, _ in pairs(tab) do tab[k] = nil end end\nend\n\nlocal _M = new_tab(0, 2)\n\n_M._VERSION = '0.01'\n\n\nlocal function get_cookie_table(text_cookie)\n    if type(text_cookie) ~= \"string\" then\n        log(ERR, format(\"expect text_cookie to be \\\"string\\\" but found %s\",\n            type(text_cookie)))\n        return {}\n    end\n\n    local EXPECT_KEY    = 1\n    local EXPECT_VALUE  = 2\n    local EXPECT_SP     = 3\n\n    local n = 0\n    local len = #text_cookie\n\n    for i=1, len do\n        if byte(text_cookie, i) == SEMICOLON then\n            n = n + 1\n        end\n    end\n\n    local cookie_table  = new_tab(0, n + 1)\n\n    local state = EXPECT_SP\n    local i = 1\n    local j = 1\n    local key, value\n\n    while j <= len do\n        if state == EXPECT_KEY then\n            if byte(text_cookie, j) == EQUAL then\n                key = sub(text_cookie, i, j - 1)\n                state = EXPECT_VALUE\n                i = j + 1\n            end\n        elseif state == EXPECT_VALUE then\n            if byte(text_cookie, j) == SEMICOLON\n                    or byte(text_cookie, j) == SPACE\n                    or byte(text_cookie, j) == HTAB\n            then\n                value = sub(text_cookie, i, j - 1)\n                cookie_table[key] = value\n\n                key, value = nil, nil\n                state = EXPECT_SP\n                i = j + 1\n            end\n        elseif state == EXPECT_SP then\n            if byte(text_cookie, j) ~= SPACE\n                    and byte(text_cookie, j) ~= HTAB\n            then\n                state = EXPECT_KEY\n                i = j\n                j = j - 1\n            end\n        end\n        j = j + 1\n    end\n\n    if key ~= nil and value == nil then\n        cookie_table[key] = sub(text_cookie, i)\n    end\n\n    return cookie_table\nend\n\nfunction _M.new(self)\n    local _cookie = ngx.var.http_cookie\n    --if not _cookie then\n    --return nil, \"no cookie found in current request\"\n    --end\n    return setmetatable({ _cookie = _cookie, set_cookie_table = new_tab(4, 0) },\n        { __index = self })\nend\n\nfunction _M.get(self, key)\n    if not self._cookie then\n        return nil, \"no cookie found in the current request\"\n    end\n    if self.cookie_table == nil then\n        self.cookie_table = get_cookie_table(self._cookie)\n    end\n\n    return self.cookie_table[key]\nend\n\nfunction _M.get_all(self)\n    if not self._cookie then\n        return nil, \"no cookie found in the current request\"\n    end\n\n    if self.cookie_table == nil then\n        self.cookie_table = get_cookie_table(self._cookie)\n    end\n\n    return self.cookie_table\nend\n\nlocal function bake(cookie)\n    if not cookie.key or not cookie.value then\n        return nil, 'missing cookie field \"key\" or \"value\"'\n    end\n\n    if cookie[\"max-age\"] then\n        cookie.max_age = cookie[\"max-age\"]\n    end\n\n    if (cookie.samesite) then\n        local samesite = cookie.samesite\n\n        -- if we dont have a valid-looking attribute, ignore the attribute\n        if (samesite ~= \"Strict\" and samesite ~= \"Lax\") then\n            log(WARN, \"SameSite value must be 'Strict' or 'Lax'\")\n            cookie.samesite = nil\n        end\n    end\n\n    local str = cookie.key .. \"=\" .. cookie.value\n            .. (cookie.expires and \"; Expires=\" .. cookie.expires or \"\")\n            .. (cookie.max_age and \"; Max-Age=\" .. cookie.max_age or \"\")\n            .. (cookie.domain and \"; Domain=\" .. cookie.domain or \"\")\n            .. (cookie.path and \"; Path=\" .. cookie.path or \"\")\n            .. (cookie.secure and \"; Secure\" or \"\")\n            .. (cookie.httponly and \"; HttpOnly\" or \"\")\n            .. (cookie.samesite and \"; SameSite=\" .. cookie.samesite or \"\")\n            .. (cookie.extension and \"; \" .. cookie.extension or \"\")\n    return str\nend\n\nfunction _M.set(self, cookie)\n    local cookie_str, err = bake(cookie)\n    if not cookie_str then\n        return nil, err\n    end\n\n    local set_cookie = ngx_header['Set-Cookie']\n    local set_cookie_type = type(set_cookie)\n    local t = self.set_cookie_table\n    clear_tab(t)\n\n    if set_cookie_type == \"string\" then\n        -- only one cookie has been setted\n        if set_cookie ~= cookie_str then\n            t[1] = set_cookie\n            t[2] = cookie_str\n            ngx_header['Set-Cookie'] = t\n        end\n    elseif set_cookie_type == \"table\" then\n        -- more than one cookies has been setted\n        local size = #set_cookie\n\n        -- we can not set cookie like ngx.header['Set-Cookie'][3] = val\n        -- so create a new table, copy all the values, and then set it back\n        for i=1, size do\n            t[i] = ngx_header['Set-Cookie'][i]\n            if t[i] == cookie_str then\n                -- new cookie is duplicated\n                return true\n            end\n        end\n        t[size + 1] = cookie_str\n        ngx_header['Set-Cookie'] = t\n    else\n        -- no cookie has been setted\n        ngx_header['Set-Cookie'] = cookie_str\n    end\n    return true\nend\n\nreturn _M"
  },
  {
    "path": "demo12/lualib/resty/template.lua",
    "content": "local setmetatable = setmetatable\nlocal loadstring = loadstring\nlocal loadchunk\nlocal tostring = tostring\nlocal setfenv = setfenv\nlocal require = require\nlocal capture\nlocal concat = table.concat\nlocal assert = assert\nlocal prefix\nlocal write = io.write\nlocal pcall = pcall\nlocal phase\nlocal open = io.open\nlocal load = load\nlocal type = type\nlocal dump = string.dump\nlocal find = string.find\nlocal gsub = string.gsub\nlocal byte = string.byte\nlocal null\nlocal sub = string.sub\nlocal ngx = ngx\nlocal jit = jit\nlocal var\n\nlocal _VERSION = _VERSION\nlocal _ENV = _ENV\nlocal _G = _G\n\nlocal HTML_ENTITIES = {\n    [\"&\"] = \"&amp;\",\n    [\"<\"] = \"&lt;\",\n    [\">\"] = \"&gt;\",\n    ['\"'] = \"&quot;\",\n    [\"'\"] = \"&#39;\",\n    [\"/\"] = \"&#47;\"\n}\n\nlocal CODE_ENTITIES = {\n    [\"{\"] = \"&#123;\",\n    [\"}\"] = \"&#125;\",\n    [\"&\"] = \"&amp;\",\n    [\"<\"] = \"&lt;\",\n    [\">\"] = \"&gt;\",\n    ['\"'] = \"&quot;\",\n    [\"'\"] = \"&#39;\",\n    [\"/\"] = \"&#47;\"\n}\n\nlocal VAR_PHASES\n\nlocal ok, newtab = pcall(require, \"table.new\")\nif not ok then newtab = function() return {} end end\n\nlocal caching = true\nlocal template = newtab(0, 12)\n\ntemplate._VERSION = \"1.9\"\ntemplate.cache    = {}\n\nlocal function enabled(val)\n    if val == nil then return true end\n    return val == true or (val == \"1\" or val == \"true\" or val == \"on\")\nend\n\nlocal function trim(s)\n    return gsub(gsub(s, \"^%s+\", \"\"), \"%s+$\", \"\")\nend\n\nlocal function rpos(view, s)\n    while s > 0 do\n        local c = sub(view, s, s)\n        if c == \" \" or c == \"\\t\" or c == \"\\0\" or c == \"\\x0B\" then\n            s = s - 1\n        else\n            break\n        end\n    end\n    return s\nend\n\nlocal function escaped(view, s)\n    if s > 1 and sub(view, s - 1, s - 1) == \"\\\\\" then\n        if s > 2 and sub(view, s - 2, s - 2) == \"\\\\\" then\n            return false, 1\n        else\n            return true, 1\n        end\n    end\n    return false, 0\nend\n\nlocal function readfile(path)\n    local file = open(path, \"rb\")\n    if not file then return nil end\n    local content = file:read \"*a\"\n    file:close()\n    return content\nend\n\nlocal function loadlua(path)\n    return readfile(path) or path\nend\n\nlocal function loadngx(path)\n    local vars = VAR_PHASES[phase()]\n    local file, location = path, vars and var.template_location\n    if sub(file, 1)  == \"/\" then file = sub(file, 2) end\n    if location and location ~= \"\" then\n        if sub(location, -1) == \"/\" then location = sub(location, 1, -2) end\n        local res = capture(concat{ location, '/', file})\n        if res.status == 200 then return res.body end\n    end\n    local root = vars and (var.template_root or var.document_root) or prefix\n    if sub(root, -1) == \"/\" then root = sub(root, 1, -2) end\n    return readfile(concat{ root, \"/\", file }) or path\nend\n\ndo\n    if ngx then\n        VAR_PHASES = {\n            set           = true,\n            rewrite       = true,\n            access        = true,\n            content       = true,\n            header_filter = true,\n            body_filter   = true,\n            log           = true\n        }\n        template.print = ngx.print or write\n        template.load  = loadngx\n        prefix, var, capture, null, phase = ngx.config.prefix(), ngx.var, ngx.location.capture, ngx.null, ngx.get_phase\n        if VAR_PHASES[phase()] then\n            caching = enabled(var.template_cache)\n        end\n    else\n        template.print = write\n        template.load  = loadlua\n    end\n    if _VERSION == \"Lua 5.1\" then\n        local context = { __index = function(t, k)\n            return t.context[k] or t.template[k] or _G[k]\n        end }\n        if jit then\n            loadchunk = function(view)\n                return assert(load(view, nil, nil, setmetatable({ template = template }, context)))\n            end\n        else\n            loadchunk = function(view)\n                local func = assert(loadstring(view))\n                setfenv(func, setmetatable({ template = template }, context))\n                return func\n            end\n        end\n    else\n        local context = { __index = function(t, k)\n            return t.context[k] or t.template[k] or _ENV[k]\n        end }\n        loadchunk = function(view)\n            return assert(load(view, nil, nil, setmetatable({ template = template }, context)))\n        end\n    end\nend\n\nfunction template.caching(enable)\n    if enable ~= nil then caching = enable == true end\n    return caching\nend\n\nfunction template.output(s)\n    if s == nil or s == null then return \"\" end\n    if type(s) == \"function\" then return template.output(s()) end\n    return tostring(s)\nend\n\nfunction template.escape(s, c)\n    if type(s) == \"string\" then\n        if c then return gsub(s, \"[}{\\\">/<'&]\", CODE_ENTITIES) end\n        return gsub(s, \"[\\\">/<'&]\", HTML_ENTITIES)\n    end\n    return template.output(s)\nend\n\nfunction template.new(view, layout)\n    assert(view, \"view was not provided for template.new(view, layout).\")\n    local render, compile = template.render, template.compile\n    if layout then\n        if type(layout) == \"table\" then\n            return setmetatable({ render = function(self, context)\n                local context = context or self\n                context.blocks = context.blocks or {}\n                context.view = compile(view)(context)\n                layout.blocks = context.blocks or {}\n                layout.view = context.view or \"\"\n                return layout:render()\n            end }, { __tostring = function(self)\n                local context = self\n                context.blocks = context.blocks or {}\n                context.view = compile(view)(context)\n                layout.blocks = context.blocks or {}\n                layout.view = context.view\n                return tostring(layout)\n            end })\n        else\n            return setmetatable({ render = function(self, context)\n                local context = context or self\n                context.blocks = context.blocks or {}\n                context.view = compile(view)(context)\n                return render(layout, context)\n            end }, { __tostring = function(self)\n                local context = self\n                context.blocks = context.blocks or {}\n                context.view = compile(view)(context)\n                return compile(layout)(context)\n            end })\n        end\n    end\n    return setmetatable({ render = function(self, context)\n        return render(view, context or self)\n    end }, { __tostring = function(self)\n        return compile(view)(self)\n    end })\nend\n\nfunction template.precompile(view, path, strip)\n    local chunk = dump(template.compile(view), strip ~= false)\n    if path then\n        local file = open(path, \"wb\")\n        file:write(chunk)\n        file:close()\n    end\n    return chunk\nend\n\nfunction template.compile(view, key, plain)\n    assert(view, \"view was not provided for template.compile(view, key, plain).\")\n    if key == \"no-cache\" then\n        return loadchunk(template.parse(view, plain)), false\n    end\n    key = key or view\n    local cache = template.cache\n    if cache[key] then return cache[key], true end\n    local func = loadchunk(template.parse(view, plain))\n    if caching then cache[key] = func end\n    return func, false\nend\n\nfunction template.parse(view, plain)\n    assert(view, \"view was not provided for template.parse(view, plain).\")\n    if not plain then\n        view = template.load(view)\n        if byte(sub(view, 1, 1)) == 27 then return view end\n    end\n    local j = 2\n    local c = {[[\ncontext=... or {}\nlocal function include(v, c) return template.compile(v)(c or context) end\nlocal ___,blocks,layout={},blocks or {}\n]] }\n    local i, s = 1, find(view, \"{\", 1, true)\n    while s do\n        local t, p = sub(view, s + 1, s + 1), s + 2\n        if t == \"{\" then\n            local e = find(view, \"}}\", p, true)\n            if e then\n                local z, w = escaped(view, s)\n                if i < s - w then\n                    c[j] = \"___[#___+1]=[=[\\n\"\n                    c[j+1] = sub(view, i, s - 1 - w)\n                    c[j+2] = \"]=]\\n\"\n                    j=j+3\n                end\n                if z then\n                    i = s\n                else\n                    c[j] = \"___[#___+1]=template.escape(\"\n                    c[j+1] = trim(sub(view, p, e - 1))\n                    c[j+2] = \")\\n\"\n                    j=j+3\n                    s, i = e + 1, e + 2\n                end\n            end\n        elseif t == \"*\" then\n            local e = find(view, \"*}\", p, true)\n            if e then\n                local z, w = escaped(view, s)\n                if i < s - w then\n                    c[j] = \"___[#___+1]=[=[\\n\"\n                    c[j+1] = sub(view, i, s - 1 - w)\n                    c[j+2] = \"]=]\\n\"\n                    j=j+3\n                end\n                if z then\n                    i = s\n                else\n                    c[j] = \"___[#___+1]=template.output(\"\n                    c[j+1] = trim(sub(view, p, e - 1))\n                    c[j+2] = \")\\n\"\n                    j=j+3\n                    s, i = e + 1, e + 2\n                end\n            end\n        elseif t == \"%\" then\n            local e = find(view, \"%}\", p, true)\n            if e then\n                local z, w = escaped(view, s)\n                if z then\n                    if i < s - w then\n                        c[j] = \"___[#___+1]=[=[\\n\"\n                        c[j+1] = sub(view, i, s - 1 - w)\n                        c[j+2] = \"]=]\\n\"\n                        j=j+3\n                    end\n                    i = s\n                else\n                    local n = e + 2\n                    if sub(view, n, n) == \"\\n\" then\n                        n = n + 1\n                    end\n                    local r = rpos(view, s - 1)\n                    if i <= r then\n                        c[j] = \"___[#___+1]=[=[\\n\"\n                        c[j+1] = sub(view, i, r)\n                        c[j+2] = \"]=]\\n\"\n                        j=j+3\n                    end\n                    c[j] = trim(sub(view, p, e - 1))\n                    c[j+1] = \"\\n\"\n                    j=j+2\n                    s, i = n - 1, n\n                end\n            end\n        elseif t == \"(\" then\n            local e = find(view, \")}\", p, true)\n            if e then\n                local z, w = escaped(view, s)\n                if i < s - w then\n                    c[j] = \"___[#___+1]=[=[\\n\"\n                    c[j+1] = sub(view, i, s - 1 - w)\n                    c[j+2] = \"]=]\\n\"\n                    j=j+3\n                end\n                if z then\n                    i = s\n                else\n                    local f = sub(view, p, e - 1)\n                    local x = find(f, \",\", 2, true)\n                    if x then\n                        c[j] = \"___[#___+1]=include([=[\"\n                        c[j+1] = trim(sub(f, 1, x - 1))\n                        c[j+2] = \"]=],\"\n                        c[j+3] = trim(sub(f, x + 1))\n                        c[j+4] = \")\\n\"\n                        j=j+5\n                    else\n                        c[j] = \"___[#___+1]=include([=[\"\n                        c[j+1] = trim(f)\n                        c[j+2] = \"]=])\\n\"\n                        j=j+3\n                    end\n                    s, i = e + 1, e + 2\n                end\n            end\n        elseif t == \"[\" then\n            local e = find(view, \"]}\", p, true)\n            if e then\n                local z, w = escaped(view, s)\n                if i < s - w then\n                    c[j] = \"___[#___+1]=[=[\\n\"\n                    c[j+1] = sub(view, i, s - 1 - w)\n                    c[j+2] = \"]=]\\n\"\n                    j=j+3\n                end\n                if z then\n                    i = s\n                else\n                    c[j] = \"___[#___+1]=include(\"\n                    c[j+1] = trim(sub(view, p, e - 1))\n                    c[j+2] = \")\\n\"\n                    j=j+3\n                    s, i = e + 1, e + 2\n                end\n            end\n        elseif t == \"-\" then\n            local e = find(view, \"-}\", p, true)\n            if e then\n                local x, y = find(view, sub(view, s, e + 1), e + 2, true)\n                if x then\n                    local z, w = escaped(view, s)\n                    if z then\n                        if i < s - w then\n                            c[j] = \"___[#___+1]=[=[\\n\"\n                            c[j+1] = sub(view, i, s - 1 - w)\n                            c[j+2] = \"]=]\\n\"\n                            j=j+3\n                        end\n                        i = s\n                    else\n                        y = y + 1\n                        x = x - 1\n                        if sub(view, y, y) == \"\\n\" then\n                            y = y + 1\n                        end\n                        local b = trim(sub(view, p, e - 1))\n                        if b == \"verbatim\" or b == \"raw\" then\n                            if i < s - w then\n                                c[j] = \"___[#___+1]=[=[\\n\"\n                                c[j+1] = sub(view, i, s - 1 - w)\n                                c[j+2] = \"]=]\\n\"\n                                j=j+3\n                            end\n                            c[j] = \"___[#___+1]=[=[\"\n                            c[j+1] = sub(view, e + 2, x)\n                            c[j+2] = \"]=]\\n\"\n                            j=j+3\n                        else\n                            if sub(view, x, x) == \"\\n\" then\n                                x = x - 1\n                            end\n                            local r = rpos(view, s - 1)\n                            if i <= r then\n                                c[j] = \"___[#___+1]=[=[\\n\"\n                                c[j+1] = sub(view, i, r)\n                                c[j+2] = \"]=]\\n\"\n                                j=j+3\n                            end\n                            c[j] = 'blocks[\"'\n                            c[j+1] = b\n                            c[j+2] = '\"]=include[=['\n                            c[j+3] = sub(view, e + 2, x)\n                            c[j+4] = \"]=]\\n\"\n                            j=j+5\n                        end\n                        s, i = y - 1, y\n                    end\n                end\n            end\n        elseif t == \"#\" then\n            local e = find(view, \"#}\", p, true)\n            if e then\n                local z, w = escaped(view, s)\n                if i < s - w then\n                    c[j] = \"___[#___+1]=[=[\\n\"\n                    c[j+1] = sub(view, i, s - 1 - w)\n                    c[j+2] = \"]=]\\n\"\n                    j=j+3\n                end\n                if z then\n                    i = s\n                else\n                    e = e + 2\n                    if sub(view, e, e) == \"\\n\" then\n                        e = e + 1\n                    end\n                    s, i = e - 1, e\n                end\n            end\n        end\n        s = find(view, \"{\", s + 1, true)\n    end\n    s = sub(view, i)\n    if s and s ~= \"\" then\n        c[j] = \"___[#___+1]=[=[\\n\"\n        c[j+1] = s\n        c[j+2] = \"]=]\\n\"\n        j=j+3\n    end\n    c[j] = \"return layout and include(layout,setmetatable({view=table.concat(___),blocks=blocks},{__index=context})) or table.concat(___)\"\n    return concat(c)\nend\n\nfunction template.render(view, context, key, plain)\n    assert(view, \"view was not provided for template.render(view, context, key, plain).\")\n    return template.print(template.compile(view, key, plain)(context))\nend\n\nreturn template\n"
  },
  {
    "path": "demo13/README.md",
    "content": "#### 在对接一些第三方系统的时候，经常会遇到https的问题，好比如做微信公众号的开发，接口基本都是https的，这个时候，很多人试着用http的那种方式来访问https，结果报错了，误以为lua不支持https，其实不是的，只需要配置一个证书即可，证书可以通过浏览器访问接口的url，然后通过浏览器导出这个网站所对应的pem证书，然后配置到nginx里面就行了，其他的调用方法跟http的类型，所用到的http库，跟我写的这篇[文章](https://github.com/362228416/openresty-web-dev/tree/master/demo7)一致，就不过多介绍了\n\nnginx.conf\n```\n\nworker_processes  1;\n\nerror_log logs/error.log notice;\n\nevents {\n    worker_connections 1024;\n}\n\nhttp {\n    lua_ssl_verify_depth 2;\n    lua_ssl_trusted_certificate GeoTrust_Global_CA.pem;\n    lua_package_path \"$prefix/lua/?.lua;$prefix/lualib/?.lua\";\n    server {\n        listen 8888;\n        server_name localhost;\n        lua_code_cache off;\n\n        location / {\n            root html;\n            index index.html;\n        }\n\n        location ~ /lua/(.+) {\n            default_type text/html;\n            resolver 223.5.5.5 223.6.6.6;  # 这里位设置阿里的DNS，不设置DNS无法解析http请求的域名\n            content_by_lua_file lua/$1.lua;\n        }\n    }\n}\n```\n\n为了简单起见，以下只是调一下获取access_key的接口，只要这个可以，同理，微信下单那些也是一样的，这点可以保证，我就用openresty做过微信公众号开发，包含微信登录，微信支付，以及数据库mysql部分全都是lua开发的\n\nlua/test.lua   \n```\nlocal req = require \"req\"\nlocal cjson = require \"cjson\"\nlocal http = require \"resty.http\"\n\nfunction get_access_token(code)\n    local httpc = http.new()\n    local params = {}\n    params['grant_type'] = 'authorization_code'\n    params['appid'] = '' -- config.appid\n    params['secret'] = '' -- config.secret\n    params['code'] = ''\n    local res,err = httpc:request_uri(\"https://api.weixin.qq.com/sns/oauth2/access_token?\" .. ngx.encode_args(params), {\n        method = \"GET\",\n        headers = {\n            [\"Accept\"] = \"application/json\",\n            [\"Accept-Encoding\"] = \"utf-8\",\n        }\n    })\n    print(err)\n    httpc:set_keepalive(60)\n    return cjson.decode(res.body)\nend\n\nlocal args = req.getArgs()\nlocal code = args['code']\nlocal res = get_access_token(code)\nngx.say(cjson.encode(res))\nngx.say(res.openid)\n\n```\n\nindex.html\n\n```\n<html>\n<head>\n\t<meta charset=\"UTF-8\">\n\t<title>Login Page</title>\n</head>\n<body>\n\t<a href=\"javascript:void(0)\" onclick=\"test()\">测试</a>\n    <pre id=\"ret\"></pre>\n\t<script src=\"//cdn.bootcss.com/jquery/2.2.4/jquery.min.js\"></script>\n\t<script>\n\t\tfunction test() {\n\t\t\t$('#ret').load('/lua/test', {code: '123456'})\n\t\t}\n\t</script>\n</body>\n</html>\n```\n\n启动nginx\n\n```\n$ openresty -p `pwd`/demo13\n```\n\n打开浏览器访问：http://localhost:8888/  点击页面上的测试按钮\n\n应该会返回类似以下这样的东西，说明调用成功了，只是参数有问题而已\n\n```\n{\"errcode\":41002,\"errmsg\":\"appid missing, hints: [ req_id: eMR_KA0444ns88 ]\"}\nnil\n```\n\n到此，你可以用openresty更深层次的跟后端进行整合，开发出更强大的前端应用了，当前开发方式很简单，部署也只需要一个nginx\n\n[示例代码](https://github.com/362228416/openresty-web-dev) 参见demo13部分\n"
  },
  {
    "path": "demo13/conf/GeoTrust_Global_CA.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT\nMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i\nYWwgQ0EwHhcNMDIwNTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQG\nEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMSR2VvVHJ1c3Qg\nR2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2swYYzD9\n9BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjoBbdq\nfnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDv\niS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU\n1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+\nbw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5aszPeE4uwc2hGKceeoW\nMPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTA\nephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVkDBF9qn1l\nuMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKIn\nZ57QzxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfS\ntQWVYrmm3ok9Nns4d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcF\nPseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Un\nhw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV\n5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "demo13/conf/nginx.conf",
    "content": "\nworker_processes  1;\n\nerror_log logs/error.log notice;\n\nevents {\n    worker_connections 1024;\n}\n\nhttp {\n    lua_ssl_verify_depth 2;\n    lua_ssl_trusted_certificate GeoTrust_Global_CA.pem;\n    lua_package_path \"$prefix/lua/?.lua;$prefix/lualib/?.lua\";\n    server {\n        listen 8888;\n        server_name localhost;\n        lua_code_cache off;\n\n        location / {\n            root html;\n            index index.html;\n        }\n\n        location ~ /lua/(.+) {\n            default_type text/html;\n            resolver 223.5.5.5 223.6.6.6;  # 这里位设置阿里的DNS，不设置DNS无法解析http请求的域名\n            content_by_lua_file lua/$1.lua;\n        }\n    }\n}\n"
  },
  {
    "path": "demo13/html/index.html",
    "content": "<html>\n<head>\n\t<meta charset=\"UTF-8\">\n\t<title>Login Page</title>\n</head>\n<body>\n\t<a href=\"javascript:void(0)\" onclick=\"test()\">测试</a>\n    <pre id=\"ret\"></pre>\n\t<script src=\"//cdn.bootcss.com/jquery/2.2.4/jquery.min.js\"></script>\n\t<script>\n\t\tfunction test() {\n\t\t\t$('#ret').load('/lua/test', {code: '123456'})\n\t\t}\n\t</script>\n</body>\n</html>\n"
  },
  {
    "path": "demo13/logs/.gitignore",
    "content": "*.log\n*.pid\n"
  },
  {
    "path": "demo13/lua/req.lua",
    "content": "local _M = {}\n\n-- 获取http get/post 请求参数\nfunction _M.getArgs()\n    local request_method = ngx.var.request_method\n    local args = ngx.req.get_uri_args()\n    -- 参数获取\n    if \"POST\" == request_method then\n        ngx.req.read_body()\n        local postArgs = ngx.req.get_post_args()\n        if postArgs then\n            for k, v in pairs(postArgs) do\n                args[k] = v\n            end\n        end\n    end\n    return args\nend\n\nreturn _M"
  },
  {
    "path": "demo13/lua/test.lua",
    "content": "local req = require \"req\"\nlocal cjson = require \"cjson\"\nlocal http = require \"resty.http\"\n\nfunction get_access_token(code)\n    local httpc = http.new()\n    local params = {}\n    params['grant_type'] = 'authorization_code'\n    params['appid'] = '' -- config.appid\n    params['secret'] = '' -- config.secret\n    params['code'] = ''\n    local res,err = httpc:request_uri(\"https://api.weixin.qq.com/sns/oauth2/access_token?\" .. ngx.encode_args(params), {\n        method = \"GET\",\n        headers = {\n            [\"Accept\"] = \"application/json\",\n            [\"Accept-Encoding\"] = \"utf-8\",\n        }\n    })\n    print(err)\n    httpc:set_keepalive(60)\n    return cjson.decode(res.body)\nend\n\nlocal args = req.getArgs()\nlocal code = args['code']\nlocal res = get_access_token(code)\nngx.say(cjson.encode(res))\nngx.say(res.openid)\n"
  },
  {
    "path": "demo13/lualib/resty/http.lua",
    "content": "local http_headers = require \"resty.http_headers\"\n\nlocal ngx_socket_tcp = ngx.socket.tcp\nlocal ngx_req = ngx.req\nlocal ngx_req_socket = ngx_req.socket\nlocal ngx_req_get_headers = ngx_req.get_headers\nlocal ngx_req_get_method = ngx_req.get_method\nlocal str_gmatch = string.gmatch\nlocal str_lower = string.lower\nlocal str_upper = string.upper\nlocal str_find = string.find\nlocal str_sub = string.sub\nlocal str_gsub = string.gsub\nlocal tbl_concat = table.concat\nlocal tbl_insert = table.insert\nlocal ngx_encode_args = ngx.encode_args\nlocal ngx_re_match = ngx.re.match\nlocal ngx_re_gsub = ngx.re.gsub\nlocal ngx_log = ngx.log\nlocal ngx_DEBUG = ngx.DEBUG\nlocal ngx_ERR = ngx.ERR\nlocal ngx_NOTICE = ngx.NOTICE\nlocal ngx_var = ngx.var\nlocal co_yield = coroutine.yield\nlocal co_create = coroutine.create\nlocal co_status = coroutine.status\nlocal co_resume = coroutine.resume\n\n\n-- http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.1\nlocal HOP_BY_HOP_HEADERS = {\n    [\"connection\"]          = true,\n    [\"keep-alive\"]          = true,\n    [\"proxy-authenticate\"]  = true,\n    [\"proxy-authorization\"] = true,\n    [\"te\"]                  = true,\n    [\"trailers\"]            = true,\n    [\"transfer-encoding\"]   = true,\n    [\"upgrade\"]             = true,\n    [\"content-length\"]      = true, -- Not strictly hop-by-hop, but Nginx will deal\n                                    -- with this (may send chunked for example).\n}\n\n\n-- Reimplemented coroutine.wrap, returning \"nil, err\" if the coroutine cannot\n-- be resumed. This protects user code from inifite loops when doing things like\n-- repeat\n--   local chunk, err = res.body_reader()\n--   if chunk then -- <-- This could be a string msg in the core wrap function.\n--     ...\n--   end\n-- until not chunk\nlocal co_wrap = function(func)\n    local co = co_create(func)\n    if not co then\n        return nil, \"could not create coroutine\"\n    else\n        return function(...)\n            if co_status(co) == \"suspended\" then\n                return select(2, co_resume(co, ...))\n            else\n                return nil, \"can't resume a \" .. co_status(co) .. \" coroutine\"\n            end\n        end\n    end\nend\n\n\nlocal _M = {\n    _VERSION = '0.09',\n}\n_M._USER_AGENT = \"lua-resty-http/\" .. _M._VERSION .. \" (Lua) ngx_lua/\" .. ngx.config.ngx_lua_version\n\nlocal mt = { __index = _M }\n\n\nlocal HTTP = {\n    [1.0] = \" HTTP/1.0\\r\\n\",\n    [1.1] = \" HTTP/1.1\\r\\n\",\n}\n\nlocal DEFAULT_PARAMS = {\n    method = \"GET\",\n    path = \"/\",\n    version = 1.1,\n}\n\n\nfunction _M.new(self)\n    local sock, err = ngx_socket_tcp()\n    if not sock then\n        return nil, err\n    end\n    return setmetatable({ sock = sock, keepalive = true }, mt)\nend\n\n\nfunction _M.set_timeout(self, timeout)\n    local sock = self.sock\n    if not sock then\n        return nil, \"not initialized\"\n    end\n\n    return sock:settimeout(timeout)\nend\n\n\nfunction _M.ssl_handshake(self, ...)\n    local sock = self.sock\n    if not sock then\n        return nil, \"not initialized\"\n    end\n\n    self.ssl = true\n\n    return sock:sslhandshake(...)\nend\n\n\nfunction _M.connect(self, ...)\n    local sock = self.sock\n    if not sock then\n        return nil, \"not initialized\"\n    end\n\n    self.host = select(1, ...)\n    self.port = select(2, ...)\n\n    -- If port is not a number, this is likely a unix domain socket connection.\n    if type(self.port) ~= \"number\" then\n        self.port = nil\n    end\n\n    self.keepalive = true\n\n    return sock:connect(...)\nend\n\n\nfunction _M.set_keepalive(self, ...)\n    local sock = self.sock\n    if not sock then\n        return nil, \"not initialized\"\n    end\n\n    if self.keepalive == true then\n        return sock:setkeepalive(...)\n    else\n        -- The server said we must close the connection, so we cannot setkeepalive.\n        -- If close() succeeds we return 2 instead of 1, to differentiate between\n        -- a normal setkeepalive() failure and an intentional close().\n        local res, err = sock:close()\n        if res then\n            return 2, \"connection must be closed\"\n        else\n            return res, err\n        end\n    end\nend\n\n\nfunction _M.get_reused_times(self)\n    local sock = self.sock\n    if not sock then\n        return nil, \"not initialized\"\n    end\n\n    return sock:getreusedtimes()\nend\n\n\nfunction _M.close(self)\n    local sock = self.sock\n    if not sock then\n        return nil, \"not initialized\"\n    end\n\n    return sock:close()\nend\n\n\nlocal function _should_receive_body(method, code)\n    if method == \"HEAD\" then return nil end\n    if code == 204 or code == 304 then return nil end\n    if code >= 100 and code < 200 then return nil end\n    return true\nend\n\n\nfunction _M.parse_uri(self, uri)\n    local m, err = ngx_re_match(uri, [[^(http[s]?)://([^:/]+)(?::(\\d+))?(.*)]],\n        \"jo\")\n\n    if not m then\n        if err then\n            return nil, \"failed to match the uri: \" .. uri .. \", \" .. err\n        end\n\n        return nil, \"bad uri: \" .. uri\n    else\n        if m[3] then\n            m[3] = tonumber(m[3])\n        else\n            if m[1] == \"https\" then\n                m[3] = 443\n            else\n                m[3] = 80\n            end\n        end\n        if not m[4] or \"\" == m[4] then m[4] = \"/\" end\n        return m, nil\n    end\nend\n\n\nlocal function _format_request(params)\n    local version = params.version\n    local headers = params.headers or {}\n\n    local query = params.query or \"\"\n    if query then\n        if type(query) == \"table\" then\n            query = \"?\" .. ngx_encode_args(query)\n        end\n    end\n\n    -- Initialize request\n    local req = {\n        str_upper(params.method),\n        \" \",\n        params.path,\n        query,\n        HTTP[version],\n        -- Pre-allocate slots for minimum headers and carriage return.\n        true,\n        true,\n        true,\n    }\n    local c = 6 -- req table index it's faster to do this inline vs table.insert\n\n    -- Append headers\n    for key, values in pairs(headers) do\n        if type(values) ~= \"table\" then\n            values = {values}\n        end\n\n        key = tostring(key)\n        for _, value in pairs(values) do\n            req[c] = key .. \": \" .. tostring(value) .. \"\\r\\n\"\n            c = c + 1\n        end\n    end\n\n    -- Close headers\n    req[c] = \"\\r\\n\"\n\n    return tbl_concat(req)\nend\n\n\nlocal function _receive_status(sock)\n    local line, err = sock:receive(\"*l\")\n    if not line then\n        return nil, nil, nil, err\n    end\n\n    return tonumber(str_sub(line, 10, 12)), tonumber(str_sub(line, 6, 8)), str_sub(line, 14)\nend\n\n\n\nlocal function _receive_headers(sock)\n    local headers = http_headers.new()\n\n    repeat\n        local line, err = sock:receive(\"*l\")\n        if not line then\n            return nil, err\n        end\n\n        for key, val in str_gmatch(line, \"([^:%s]+):%s*(.+)\") do\n            if headers[key] then\n                if type(headers[key]) ~= \"table\" then\n                    headers[key] = { headers[key] }\n                end\n                tbl_insert(headers[key], tostring(val))\n            else\n                headers[key] = tostring(val)\n            end\n        end\n    until str_find(line, \"^%s*$\")\n\n    return headers, nil\nend\n\n\nlocal function _chunked_body_reader(sock, default_chunk_size)\n    return co_wrap(function(max_chunk_size)\n        local max_chunk_size = max_chunk_size or default_chunk_size\n        local remaining = 0\n        local length\n\n        repeat\n            -- If we still have data on this chunk\n            if max_chunk_size and remaining > 0 then\n\n                if remaining > max_chunk_size then\n                    -- Consume up to max_chunk_size\n                    length = max_chunk_size\n                    remaining = remaining - max_chunk_size\n                else\n                    -- Consume all remaining\n                    length = remaining\n                    remaining = 0\n                end\n            else -- This is a fresh chunk\n\n                -- Receive the chunk size\n                local str, err = sock:receive(\"*l\")\n                if not str then\n                    co_yield(nil, err)\n                end\n\n                length = tonumber(str, 16)\n\n                if not length then\n                    co_yield(nil, \"unable to read chunksize\")\n                end\n\n                if max_chunk_size and length > max_chunk_size then\n                    -- Consume up to max_chunk_size\n                    remaining = length - max_chunk_size\n                    length = max_chunk_size\n                end\n            end\n\n            if length > 0 then\n                local str, err = sock:receive(length)\n                if not str then\n                    co_yield(nil, err)\n                end\n\n                max_chunk_size = co_yield(str) or default_chunk_size\n\n                -- If we're finished with this chunk, read the carriage return.\n                if remaining == 0 then\n                    sock:receive(2) -- read \\r\\n\n                end\n            else\n                -- Read the last (zero length) chunk's carriage return\n                sock:receive(2) -- read \\r\\n\n            end\n\n        until length == 0\n    end)\nend\n\n\nlocal function _body_reader(sock, content_length, default_chunk_size)\n    return co_wrap(function(max_chunk_size)\n        local max_chunk_size = max_chunk_size or default_chunk_size\n\n        if not content_length and max_chunk_size then\n            -- We have no length, but wish to stream.\n            -- HTTP 1.0 with no length will close connection, so read chunks to the end.\n            repeat\n                local str, err, partial = sock:receive(max_chunk_size)\n                if not str and err == \"closed\" then\n                    max_chunk_size = tonumber(co_yield(partial, err) or default_chunk_size)\n                end\n\n                max_chunk_size = tonumber(co_yield(str) or default_chunk_size)\n                if max_chunk_size and max_chunk_size < 0 then max_chunk_size = nil end\n\n                if not max_chunk_size then\n                    ngx_log(ngx_ERR, \"Buffer size not specified, bailing\")\n                    break\n                end\n            until not str\n\n        elseif not content_length then\n            -- We have no length but don't wish to stream.\n            -- HTTP 1.0 with no length will close connection, so read to the end.\n            co_yield(sock:receive(\"*a\"))\n\n        elseif not max_chunk_size then\n            -- We have a length and potentially keep-alive, but want everything.\n            co_yield(sock:receive(content_length))\n\n        else\n            -- We have a length and potentially a keep-alive, and wish to stream\n            -- the response.\n            local received = 0\n            repeat\n                local length = max_chunk_size\n                if received + length > content_length then\n                    length = content_length - received\n                end\n\n                if length > 0 then\n                    local str, err = sock:receive(length)\n                    if not str then\n                        max_chunk_size = tonumber(co_yield(nil, err) or default_chunk_size)\n                    end\n                    received = received + length\n\n                    max_chunk_size = tonumber(co_yield(str) or default_chunk_size)\n                    if max_chunk_size and max_chunk_size < 0 then max_chunk_size = nil end\n\n                    if not max_chunk_size then\n                        ngx_log(ngx_ERR, \"Buffer size not specified, bailing\")\n                        break\n                    end\n                end\n\n            until length == 0\n        end\n    end)\nend\n\n\nlocal function _no_body_reader()\n    return nil\nend\n\n\nlocal function _read_body(res)\n    local reader = res.body_reader\n\n    if not reader then\n        -- Most likely HEAD or 304 etc.\n        return nil, \"no body to be read\"\n    end\n\n    local chunks = {}\n    local c = 1\n\n    local chunk, err\n    repeat\n        chunk, err = reader()\n\n        if err then\n            return nil, err, tbl_concat(chunks) -- Return any data so far.\n        end\n        if chunk then\n            chunks[c] = chunk\n            c = c + 1\n        end\n    until not chunk\n\n    return tbl_concat(chunks)\nend\n\n\nlocal function _trailer_reader(sock)\n    return co_wrap(function()\n        co_yield(_receive_headers(sock))\n    end)\nend\n\n\nlocal function _read_trailers(res)\n    local reader = res.trailer_reader\n    if not reader then\n        return nil, \"no trailers\"\n    end\n\n    local trailers = reader()\n    setmetatable(res.headers, { __index = trailers })\nend\n\n\nlocal function _send_body(sock, body)\n    if type(body) == 'function' then\n        repeat\n            local chunk, err, partial = body()\n\n            if chunk then\n                local ok,err = sock:send(chunk)\n\n                if not ok then\n                    return nil, err\n                end\n            elseif err ~= nil then\n                return nil, err, partial\n            end\n\n        until chunk == nil\n    elseif body ~= nil then\n        local bytes, err = sock:send(body)\n\n        if not bytes then\n            return nil, err\n        end\n    end\n    return true, nil\nend\n\n\nlocal function _handle_continue(sock, body)\n    local status, version, reason, err = _receive_status(sock)\n    if not status then\n        return nil, nil, err\n    end\n\n    -- Only send body if we receive a 100 Continue\n    if status == 100 then\n        local ok, err = sock:receive(\"*l\") -- Read carriage return\n        if not ok then\n            return nil, nil, err\n        end\n        _send_body(sock, body)\n    end\n    return status, version, err\nend\n\n\nfunction _M.send_request(self, params)\n    -- Apply defaults\n    setmetatable(params, { __index = DEFAULT_PARAMS })\n\n    local sock = self.sock\n    local body = params.body\n    local headers = http_headers.new()\n\n    local params_headers = params.headers\n    if params_headers then\n        -- We assign one by one so that the metatable can handle case insensitivity\n        -- for us. You can blame the spec for this inefficiency.\n        for k,v in pairs(params_headers) do\n            headers[k] = v\n        end\n    end\n\n    -- Ensure minimal headers are set\n    if type(body) == 'string' and not headers[\"Content-Length\"] then\n        headers[\"Content-Length\"] = #body\n    end\n    if not headers[\"Host\"] then\n        if (str_sub(self.host, 1, 5) == \"unix:\") then\n            return nil, \"Unable to generate a useful Host header for a unix domain socket. Please provide one.\"\n        end\n        -- If we have a port (i.e. not connected to a unix domain socket), and this\n        -- port is non-standard, append it to the Host heaer.\n        if self.port then\n            if self.ssl and self.port ~= 443 then\n                headers[\"Host\"] = self.host .. \":\" .. self.port\n            elseif not self.ssl and self.port ~= 80 then\n                headers[\"Host\"] = self.host .. \":\" .. self.port\n            else\n                headers[\"Host\"] = self.host\n            end\n        else\n            headers[\"Host\"] = self.host\n        end\n    end\n    if not headers[\"User-Agent\"] then\n        headers[\"User-Agent\"] = _M._USER_AGENT\n    end\n    if params.version == 1.0 and not headers[\"Connection\"] then\n        headers[\"Connection\"] = \"Keep-Alive\"\n    end\n\n    params.headers = headers\n\n    -- Format and send request\n    local req = _format_request(params)\n    ngx_log(ngx_DEBUG, \"\\n\", req)\n    local bytes, err = sock:send(req)\n\n    if not bytes then\n        return nil, err\n    end\n\n    -- Send the request body, unless we expect: continue, in which case\n    -- we handle this as part of reading the response.\n    if headers[\"Expect\"] ~= \"100-continue\" then\n        local ok, err, partial = _send_body(sock, body)\n        if not ok then\n            return nil, err, partial\n        end\n    end\n\n    return true\nend\n\n\nfunction _M.read_response(self, params)\n    local sock = self.sock\n\n    local status, version, reason, err\n\n    -- If we expect: continue, we need to handle this, sending the body if allowed.\n    -- If we don't get 100 back, then status is the actual status.\n    if params.headers[\"Expect\"] == \"100-continue\" then\n        local _status, _version, _err = _handle_continue(sock, params.body)\n        if not _status then\n            return nil, _err\n        elseif _status ~= 100 then\n            status, version, err = _status, _version, _err\n        end\n    end\n\n    -- Just read the status as normal.\n    if not status then\n        status, version, reason, err = _receive_status(sock)\n        if not status then\n            return nil, err\n        end\n    end\n\n\n    local res_headers, err = _receive_headers(sock)\n    if not res_headers then\n        return nil, err\n    end\n\n    -- keepalive is true by default. Determine if this is correct or not.\n    local ok, connection = pcall(str_lower, res_headers[\"Connection\"])\n    if ok then\n        if  (version == 1.1 and connection == \"close\") or\n            (version == 1.0 and connection ~= \"keep-alive\") then\n            self.keepalive = false\n        end\n    else\n        -- no connection header\n        if version == 1.0 then\n            self.keepalive = false\n        end\n    end\n\n    local body_reader = _no_body_reader\n    local trailer_reader, err = nil, nil\n    local has_body = false\n\n    -- Receive the body_reader\n    if _should_receive_body(params.method, status) then\n        local ok, encoding = pcall(str_lower, res_headers[\"Transfer-Encoding\"])\n        if ok and version == 1.1 and encoding == \"chunked\" then\n            body_reader, err = _chunked_body_reader(sock)\n            has_body = true\n        else\n\n            local ok, length = pcall(tonumber, res_headers[\"Content-Length\"])\n            if ok then\n                body_reader, err = _body_reader(sock, length)\n                has_body = true\n            end\n        end\n    end\n\n    if res_headers[\"Trailer\"] then\n        trailer_reader, err = _trailer_reader(sock)\n    end\n\n    if err then\n        return nil, err\n    else\n        return {\n            status = status,\n            reason = reason,\n            headers = res_headers,\n            has_body = has_body,\n            body_reader = body_reader,\n            read_body = _read_body,\n            trailer_reader = trailer_reader,\n            read_trailers = _read_trailers,\n        }\n    end\nend\n\n\nfunction _M.request(self, params)\n    local res, err = self:send_request(params)\n    if not res then\n        return res, err\n    else\n        return self:read_response(params)\n    end\nend\n\n\nfunction _M.request_pipeline(self, requests)\n    for i, params in ipairs(requests) do\n        if params.headers and params.headers[\"Expect\"] == \"100-continue\" then\n            return nil, \"Cannot pipeline request specifying Expect: 100-continue\"\n        end\n\n        local res, err = self:send_request(params)\n        if not res then\n            return res, err\n        end\n    end\n\n    local responses = {}\n    for i, params in ipairs(requests) do\n        responses[i] = setmetatable({\n            params = params,\n            response_read = false,\n        }, {\n            -- Read each actual response lazily, at the point the user tries\n            -- to access any of the fields.\n            __index = function(t, k)\n                local res, err\n                if t.response_read == false then\n                    res, err = _M.read_response(self, t.params)\n                    t.response_read = true\n\n                    if not res then\n                        ngx_log(ngx_ERR, err)\n                    else\n                        for rk, rv in pairs(res) do\n                            t[rk] = rv\n                        end\n                    end\n                end\n                return rawget(t, k)\n            end,\n        })\n    end\n    return responses\nend\n\n\nfunction _M.request_uri(self, uri, params)\n    if not params then params = {} end\n\n    local parsed_uri, err = self:parse_uri(uri)\n    if not parsed_uri then\n        return nil, err\n    end\n\n    local scheme, host, port, path = unpack(parsed_uri)\n    if not params.path then params.path = path end\n\n    local c, err = self:connect(host, port)\n    if not c then\n        return nil, err\n    end\n\n    if scheme == \"https\" then\n        local verify = true\n        if params.ssl_verify == false then\n            verify = false\n        end\n        local ok, err = self:ssl_handshake(nil, host, verify)\n        if not ok then\n            return nil, err\n        end\n    end\n\n    local res, err = self:request(params)\n    if not res then\n        return nil, err\n    end\n\n    local body, err = res:read_body()\n    if not body then\n        return nil, err\n    end\n\n    res.body = body\n\n    local ok, err = self:set_keepalive()\n    if not ok then\n        ngx_log(ngx_ERR, err)\n    end\n\n    return res, nil\nend\n\n\nfunction _M.get_client_body_reader(self, chunksize, sock)\n    local chunksize = chunksize or 65536\n    if not sock then\n        local ok, err\n        ok, sock, err = pcall(ngx_req_socket)\n\n        if not ok then\n            return nil, sock -- pcall err\n        end\n\n        if not sock then\n            if err == \"no body\" then\n                return nil\n            else\n                return nil, err\n            end\n        end\n    end\n\n    local headers = ngx_req_get_headers()\n    local length = headers.content_length\n    local encoding = headers.transfer_encoding\n    if length then\n        return _body_reader(sock, tonumber(length), chunksize)\n    elseif encoding and str_lower(encoding) == 'chunked' then\n        -- Not yet supported by ngx_lua but should just work...\n        return _chunked_body_reader(sock, chunksize)\n    else\n       return nil\n    end\nend\n\n\nfunction _M.proxy_request(self, chunksize)\n    return self:request{\n        method = ngx_req_get_method(),\n        path = ngx_re_gsub(ngx_var.uri, \"\\\\s\", \"%20\", \"jo\") .. ngx_var.is_args .. (ngx_var.query_string or \"\"),\n        body = self:get_client_body_reader(chunksize),\n        headers = ngx_req_get_headers(),\n    }\nend\n\n\nfunction _M.proxy_response(self, response, chunksize)\n    if not response then\n        ngx_log(ngx_ERR, \"no response provided\")\n        return\n    end\n\n    ngx.status = response.status\n\n    -- Filter out hop-by-hop headeres\n    for k,v in pairs(response.headers) do\n        if not HOP_BY_HOP_HEADERS[str_lower(k)] then\n            ngx.header[k] = v\n        end\n    end\n\n    local reader = response.body_reader\n    repeat\n        local chunk, err = reader(chunksize)\n        if err then\n            ngx_log(ngx_ERR, err)\n            break\n        end\n\n        if chunk then\n            local res, err = ngx.print(chunk)\n            if not res then\n                ngx_log(ngx_ERR, err)\n                break\n            end\n        end\n    until not chunk\nend\n\n\nreturn _M\n"
  },
  {
    "path": "demo13/lualib/resty/http_headers.lua",
    "content": "local   rawget, rawset, setmetatable =\n        rawget, rawset, setmetatable\n\nlocal str_gsub = string.gsub\nlocal str_lower = string.lower\n\n\nlocal _M = {\n    _VERSION = '0.01',\n}\n\n\n-- Returns an empty headers table with internalised case normalisation.\n-- Supports the same cases as in ngx_lua:\n--\n-- headers.content_length\n-- headers[\"content-length\"]\n-- headers[\"Content-Length\"]\nfunction _M.new(self)\n    local mt = { \n        normalised = {},\n    }\n\n\n    mt.__index = function(t, k)\n        local k_hyphened = str_gsub(k, \"_\", \"-\")\n        local matched = rawget(t, k)\n        if matched then\n            return matched\n        else\n            local k_normalised = str_lower(k_hyphened)\n            return rawget(t, mt.normalised[k_normalised])\n        end\n    end\n\n\n    -- First check the normalised table. If there's no match (first time) add an entry for\n    -- our current case in the normalised table. This is to preserve the human (prettier) case\n    -- instead of outputting lowercased header names.\n    --\n    -- If there's a match, we're being updated, just with a different case for the key. We use\n    -- the normalised table to give us the original key, and perorm a rawset().\n    mt.__newindex = function(t, k, v)\n        -- we support underscore syntax, so always hyphenate.\n        local k_hyphened = str_gsub(k, \"_\", \"-\")\n\n        -- lowercase hyphenated is \"normalised\"\n        local k_normalised = str_lower(k_hyphened)\n\n        if not mt.normalised[k_normalised] then\n            mt.normalised[k_normalised] = k_hyphened\n            rawset(t, k_hyphened, v)\n        else\n            rawset(t, mt.normalised[k_normalised], v)\n        end\n    end\n\n    return setmetatable({}, mt)\nend\n\n\nreturn _M\n"
  },
  {
    "path": "demo14/README.md",
    "content": "有的时候我们希望在启动openresty的时候，在后台运行一些定时任务，可以放在`init_worker_by_lua_block`阶段运行，如果想重复执行可以通过不断`ngx.timer.at`来实现多次调用\n\n需要注意的是`init_worker_by_lua_block`会在每个worker启动的时候都会运行，当你的worker_processes配置为1的时候，没有任何问题，但是当你worker_processes配置为大于1的时候，会在后台运行多个定时任务，如果你的任务可以重复执行，那还没关系，假如不能的话，就有点问题了。\n\n为了解决配置多个worker会启动多个任务的问题，需要有一种机制就是即使这段代码会重复运行，但是也只能启动一个定时任务，那么就需要多个worker进行排他处理\n\n我们可以配置一个`lua_shared_dict`共享字典，这个字典在多个worker之间共享，有了这个就好办了，只需要在第一个worker启动完成之后，在内存里面设置一个字段，标识任务已经启动，那么其他worker启动的时候发现已经启动了一个定时任务，不再启动就可以了。\n\n大多数情况下这样就已经可以了，但是lua_shared_dict保存的数据的生命周期是即使在nginx -s reload 的时候它还是会继续存在的，并不会消失，除非stop。\n\n而nginx -s reload 又会导致旧worker被结束，新worker被启动，但是又由于之前在共享内存里面保存了已启动标识状态，导致新的worker不能启动定时任务，知道了原因解决起来就很简单了，只需要在第一worker启动定时任务的时候延迟一小会，把共享字典里面的值重置了就行了，这样下次reload的时候就相当于第一次启动，ok 完美。。\n\nconf/nginx.conf\n```\n\nworker_processes  3;\n\nerror_log logs/error.log notice;\n\nevents {\n    worker_connections 1024;\n}\n\nhttp {\n    lua_package_path \"$prefix/lua/?.lua;$prefix/lualib/?.lua\";\n\n\tlua_shared_dict task 1m;\n    init_worker_by_lua_block {\n        local task = ngx.shared.task\n        local time = 2\n        local count = task:incr(\"invoke\", 1, 0)\n        if count == 1 then\n            local timer_at = ngx.timer.at\n            function do_some_thing()\n                print(\"do some thing\")\n                timer_at(time, do_some_thing)\n            end\n            timer_at(time, do_some_thing)\n            timer_at(5, function()\n                task:set(\"invoke\", 0)\n            end\n            )\n        end\n    }\n\n    server {\n        listen 8888;\n        server_name localhost;\n        lua_code_cache off;\n\n        location / {\n            default_type text/html;\n            content_by_lua_block {\n                ngx.say('定时任务')\n            }\n        }\n    }\n}\n\n```\n\n[示例代码](https://github.com/362228416/openresty-web-dev) 参见demo14部分\n\n"
  },
  {
    "path": "demo14/conf/nginx.conf",
    "content": "worker_processes  3;\n\nerror_log logs/error.log notice;\n\nevents {\n    worker_connections 1024;\n}\n\nhttp {\n    lua_package_path \"$prefix/lua/?.lua;$prefix/lualib/?.lua\";\n\n    lua_shared_dict task 1m;\n    init_worker_by_lua_block {\n        local task = ngx.shared.task\n        local time = 2\n        local count = task:incr(\"invoke\", 1, 0)\n        if count == 1 then\n            local timer_at = ngx.timer.at\n            function do_some_thing()\n                print(\"do some thing\")\n                timer_at(time, do_some_thing)\n            end\n            timer_at(time, do_some_thing)\n            timer_at(5, function()\n                task:set(\"invoke\", 0)\n            end\n            )\n        end\n    }\n\n    server {\n        listen 8888;\n        server_name localhost;\n        lua_code_cache off;\n\n        location / {\n            default_type text/html;\n            content_by_lua_block {\n                ngx.say('定时任务')\n            }\n        }\n    }\n}"
  },
  {
    "path": "demo14/logs/.gitignore",
    "content": "*.log\n*.pid\n"
  },
  {
    "path": "demo15/README.md",
    "content": "以前用tengine自带了session_sticky，现在换成openresty，没有现成的，nginx-sticky-module 太老， 编译有点问题，于是自己写了一个，废话不多说，直接看代码\n\nlua/balancer.lua \n```lua\n\nlocal balancer = require \"ngx.balancer\"\nlocal upstream = require \"ngx.upstream\"\n\nlocal upstream_name = 'backend'\n\nlocal srvs = upstream.get_servers(upstream_name)\n\nfunction get_server()\n    local cache = ngx.shared.cache\n    local key = \"req_index\"\n    local index = cache:get(key)\n    if index == nil or index > #srvs then\n        index = 1\n        cache:set(key, index)\n    end\n    cache:incr(key, 1)\n    return index\nend\n\nfunction is_down(server)\n    local down = false\n    local perrs = upstream.get_primary_peers(upstream_name)\n    for i = 1, #perrs do\n        local peer = perrs[i]\n        if server == peer.name and peer.down == true then\n            down = true\n        end\n    end\n    return down\nend\n\n----------------------------\n\nlocal route = ngx.var.cookie_route\n\nlocal server\n\nif route then\n    for k, v in pairs(srvs) do\n        if ngx.md5(v.name) == route then\n            server = v.addr\n        end\n    end\n    if is_down(server) then\n        route = nil\n    end\nend\n\nif not route then\n    for i = 1, #srvs do\n        if not server or is_down(server) then\n            server = srvs[get_server()].addr\n        end\n    end\n    ngx.header[\"Set-Cookie\"] = 'route=' .. ngx.md5(server) .. '; path=/;'\nend\n\nlocal index = string.find(server, ':')\nlocal host = string.sub(server, 1, index - 1)\nlocal port = string.sub(server, index + 1)\nbalancer.set_current_peer(host, tonumber(port))\n\n```\n\nnginx.conf\n```\nlua_shared_dict cache 1m;\nupstream backend {\n        server 192.168.0.2:8080;\n        server 192.168.0.3:8080; \n        balancer_by_lua_file lua/balancer.lua;\n} \n\nserver {\n        listen 80;\n        server_name    localhost;\n        location / {\n            proxy_pass http://backend;\n            ...\n        }\n        \n}\n\n# 健康检查\nlua_shared_dict healthcheck 1m;\nlua_socket_log_errors off;    \ninit_worker_by_lua_block {\n        local hc = require \"resty.upstream.healthcheck\"\n        local ok, err = hc.spawn_checker{\n            shm = \"healthcheck\",  -- defined by \"lua_shared_dict\"\n            upstream = \"backend\", -- defined by \"upstream\"\n            type = \"http\",\n\n            http_req = \"GET / HTTP/1.0\\r\\nHost: localhost\\r\\n\\r\\n\",\n                    -- raw HTTP request for checking\n\n            interval = 2000,  -- run the check cycle every 2 sec\n            timeout = 1000,   -- 1 sec is the timeout for network operations\n            fall = 3,  -- # of successive failures before turning a peer down\n            rise = 2,  -- # of successive successes before turning a peer up\n            valid_statuses = {200, 302},  -- a list valid HTTP status code\n            concurrency = 10,  -- concurrency level for test requests\n        }\n        if not ok then\n            ngx.log(ngx.ERR, \"failed to spawn health checker: \", err)\n            return\n        end\n    }\n\n```\n\n\n\n主要是利用`ngx.upstream`、`ngx.balancer` 这两个模块，动态获取upstream，以及设置返回的上游名单，然后写到cookie里面，这里有判断后端是否down掉，如果down掉的话，则获取下一个，后端的状态通过`resty.upstream.healthcheck`模块健康检查来实现，以此为模型可以写更复杂的负载均衡逻辑\n\n我这里比较简单，嫌丑了\n"
  },
  {
    "path": "demo16/README.md",
    "content": "\n# 这篇文章主要介绍，怎么用lua实现单一文件入口\n\n之前的例子都需要在nginx.conf文件中，单独配置静态文件location，以及lua_package_path，其实可以把这些整合到一个入口文件\n\n## 几个关键指令和api\n\nset_by_lua_file\n\naccess_by_lua_file\n\nngx.config.prefix()\n\npackage.path\n\nngx.get_phase()\n\n## 实现思路\n\n在一个location / 里面处理所有请求，所以原型配置会是这样 \n\n```\nlocation / {\n    default_type \"text/html; charset=utf-8\";\n    content_by_lua_file lua/web/mvc.lua;\n}\n```\n\n我想要访问静态资源的时候使用nginx默认的处理机制，也就是指定root 目录，所以配置文件成了下面的样子\n\n```\nlocation / {\n    default_type \"text/html; charset=utf-8\";\n    access_by_lua_file lua/web/mvc.lua;\n    root html;\n}\n```\n\n最后我想用lua来配置root目录，那样就不需要改nginx.conf配置文件，最后变成这样\n\n```\nlocation / {\n    default_type \"text/html; charset=utf-8\";\n    set_by_lua_file $root lua/web/mvc.lua;\n    access_by_lua_file lua/web/mvc.lua;\n    root $root;\n}\n```\n\n这样的话，所有的处理都指向了mvc.lua文件，接下来看lua端怎么实现\n\n首先我们需要知道lua package加载路径，否则引入自定义的lua模块可能会失败\n\n代码很简单\n\n```lua\nlocal package = package\nlocal pack_path = package.path\nlocal prefix = ngx.config.prefix()\nlocal p = prefix .. \"lualib/?.lua;\" .. prefix .. \"lua/?.lua;;\" .. pack_path\npackage.path = p\n```\n\n先获取package默认加载路径，再获取当前项目安装目录，最后组装成新的package覆盖原来的package.path即可实现在nginx.conf里面配置lua_package_path一致的效果\n\n\n接下来处理set_by_lua_file，通过ngx.get_phase()获取到当前执行环境是set，既set_by_*阶段\n\n\n```lua\nlocal phase = ngx.get_phase()\n\n-- 设置root环境变量\nif phase == 'set' then\n    local global_config = require \"global_config\"\n    return global_config.baseDir\nend\n```\n\n这样外面的root值就动态指定，但是因为配置了access_by_lua_file所以静态资源请求，还是会执行这个lua文件，这里只需要忽略掉就好了，直接return\n\n```lua\n-- 静态文件\nif paths[1] == 'image' or paths[1] == 'style' or paths[1] == 'js' then\n    return\nend\n\n```\n\n\n最后就是lua模块渲染了，这里只是随便写一个demo，仅供参考\n\n```lua\nlocal template = require \"web.template\"\n\nlocal router = {}\n\nrouter['/'] = function()\n    local ctx = {}\n    template.render('index.html', ctx)\nend\n\n\nlocal ctl = router[uri]\n\nif ctl ~= nil then\n    pcall(ctl)\nelse\n    ngx.exit(404)\nend\n```\n\n执行命令\n\n```bash\ncd demo16\nopenresty -p . -c conf/nginx.conf\n```\n\n浏览器访问 http://localhost:8080/\n\n页面是由lua渲染的，可以看得出index.html里面有模板代码，都被替换了，然后js文件也能正常加载，点击hello按钮会弹出hello\n\n到这里基本就结束了，发挥你的想象力吧，lua真的很强\n\n\n"
  },
  {
    "path": "demo16/conf/nginx.conf",
    "content": "\nworker_processes  1;\n\nerror_log logs/error.log notice;\n\nevents {\n    worker_connections 1024;\n}\n\nhttp {\n\n    server {\n        listen 8080;\n        server_name localhost;\n        lua_code_cache off;\n\n        location / {\n        \tdefault_type \"text/html; charset=utf-8\";\n        \tset_by_lua_file $root lua/web/mvc.lua;\n        \taccess_by_lua_file lua/web/mvc.lua;\n        \troot $root;\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "demo16/html/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>{*site_name*}</title>\n    <script type=\"text/javascript\" src=\"{*host*}/js/index.js\"></script>\n</head>\n<body>\n\n<button onclick=\"hello()\">hello</button>\n\n\n</body>\n</html>"
  },
  {
    "path": "demo16/html/js/index.js",
    "content": "\nfunction hello() {\n    alert('hello')\n}"
  },
  {
    "path": "demo16/logs/.gitignore",
    "content": "*.log\n*.pid\n"
  },
  {
    "path": "demo16/lua/global_config.lua",
    "content": "\n-- 全站配置\n\nlocal _M = {\n    __version = 0.1\n}\n\n-- 文件路径\n\n_M.baseDir = \"html\"\n\n\n--站点配置信息\n\n_M.siteConfig = {\n    site_name = '网站名称',\n    domain = 'localhost:8080',\n    host = 'http://localhost:8080'\n}\n\n\nreturn _M"
  },
  {
    "path": "demo16/lua/utils.lua",
    "content": "\nlocal _M = {\n    __version = 0.1\n}\n\n-- 分隔字符串\nfunction split( str,reps )\n    local resultStrList = {}\n    string.gsub(str,'[^'..reps..']+',function ( w )\n        table.insert(resultStrList,w)\n    end)\n    return resultStrList\nend\n\n\n_M.split = split\n\nreturn _M"
  },
  {
    "path": "demo16/lua/web/mvc.lua",
    "content": "local package = package\nlocal pack_path = package.path\nlocal prefix = ngx.config.prefix()\nlocal p = prefix .. \"lualib/?.lua;\" .. prefix .. \"lua/?.lua;;\" .. pack_path\npackage.path = p\n\nlocal utils = require \"utils\"\nlocal uri = ngx.var.uri\n\nlocal paths = utils.split(uri, '/')\n\nlocal phase = ngx.get_phase()\n\n-- 设置root环境变量\nif phase == 'set' then\n    local global_config = require \"global_config\"\n    return global_config.baseDir\nend\n\n--ngx.say(uri)\n\n-- 静态文件\nif paths[1] == 'image' or paths[1] == 'style' or paths[1] == 'js' then\n    return\nend\n\n\nlocal template = require \"web.template\"\n\nlocal router = {}\n\nrouter['/'] = function()\n    local ctx = {}\n    template.render('index.html', ctx)\nend\n\n\nlocal ctl = router[uri]\n\nif ctl ~= nil then\n    pcall(ctl)\nelse\n    ngx.exit(404)\nend\n\n"
  },
  {
    "path": "demo16/lua/web/template.lua",
    "content": "local template = require \"resty.template\"\nlocal global_config = require \"global_config\"\n\nlocal _M = {\n    __version = 0.1\n}\n\nfunction _M.render(view, context, key, plain)\n    if context == nil then\n        context = {}\n    end\n    for k, v in pairs(global_config.siteConfig) do\n        context[k] = v\n    end\n    return template.render(view, context, key, plain)\nend\n\nreturn _M"
  },
  {
    "path": "demo16/lualib/resty/template.lua",
    "content": "local setmetatable = setmetatable\nlocal loadstring = loadstring\nlocal loadchunk\nlocal tostring = tostring\nlocal setfenv = setfenv\nlocal require = require\nlocal capture\nlocal concat = table.concat\nlocal assert = assert\nlocal prefix\nlocal write = io.write\nlocal pcall = pcall\nlocal phase\nlocal open = io.open\nlocal load = load\nlocal type = type\nlocal dump = string.dump\nlocal find = string.find\nlocal gsub = string.gsub\nlocal byte = string.byte\nlocal null\nlocal sub = string.sub\nlocal ngx = ngx\nlocal jit = jit\nlocal var\n\nlocal _VERSION = _VERSION\nlocal _ENV = _ENV\nlocal _G = _G\n\nlocal HTML_ENTITIES = {\n    [\"&\"] = \"&amp;\",\n    [\"<\"] = \"&lt;\",\n    [\">\"] = \"&gt;\",\n    ['\"'] = \"&quot;\",\n    [\"'\"] = \"&#39;\",\n    [\"/\"] = \"&#47;\"\n}\n\nlocal CODE_ENTITIES = {\n    [\"{\"] = \"&#123;\",\n    [\"}\"] = \"&#125;\",\n    [\"&\"] = \"&amp;\",\n    [\"<\"] = \"&lt;\",\n    [\">\"] = \"&gt;\",\n    ['\"'] = \"&quot;\",\n    [\"'\"] = \"&#39;\",\n    [\"/\"] = \"&#47;\"\n}\n\nlocal VAR_PHASES\n\nlocal ok, newtab = pcall(require, \"table.new\")\nif not ok then newtab = function() return {} end end\n\nlocal caching = true\nlocal template = newtab(0, 12)\n\ntemplate._VERSION = \"1.9\"\ntemplate.cache    = {}\n\nlocal function enabled(val)\n    if val == nil then return true end\n    return val == true or (val == \"1\" or val == \"true\" or val == \"on\")\nend\n\nlocal function trim(s)\n    return gsub(gsub(s, \"^%s+\", \"\"), \"%s+$\", \"\")\nend\n\nlocal function rpos(view, s)\n    while s > 0 do\n        local c = sub(view, s, s)\n        if c == \" \" or c == \"\\t\" or c == \"\\0\" or c == \"\\x0B\" then\n            s = s - 1\n        else\n            break\n        end\n    end\n    return s\nend\n\nlocal function escaped(view, s)\n    if s > 1 and sub(view, s - 1, s - 1) == \"\\\\\" then\n        if s > 2 and sub(view, s - 2, s - 2) == \"\\\\\" then\n            return false, 1\n        else\n            return true, 1\n        end\n    end\n    return false, 0\nend\n\nlocal function readfile(path)\n    local file = open(path, \"rb\")\n    if not file then return nil end\n    local content = file:read \"*a\"\n    file:close()\n    return content\nend\n\nlocal function loadlua(path)\n    return readfile(path) or path\nend\n\nlocal function loadngx(path)\n    local vars = VAR_PHASES[phase()]\n    local file, location = path, vars and var.template_location\n    if sub(file, 1)  == \"/\" then file = sub(file, 2) end\n    if location and location ~= \"\" then\n        if sub(location, -1) == \"/\" then location = sub(location, 1, -2) end\n        local res = capture(concat{ location, '/', file})\n        if res.status == 200 then return res.body end\n    end\n    local root = vars and (var.template_root or var.document_root) or prefix\n    if sub(root, -1) == \"/\" then root = sub(root, 1, -2) end\n    return readfile(concat{ root, \"/\", file }) or path\nend\n\ndo\n    if ngx then\n        VAR_PHASES = {\n            set           = true,\n            rewrite       = true,\n            access        = true,\n            content       = true,\n            header_filter = true,\n            body_filter   = true,\n            log           = true\n        }\n        template.print = ngx.print or write\n        template.load  = loadngx\n        prefix, var, capture, null, phase = ngx.config.prefix(), ngx.var, ngx.location.capture, ngx.null, ngx.get_phase\n        if VAR_PHASES[phase()] then\n            caching = enabled(var.template_cache)\n        end\n    else\n        template.print = write\n        template.load  = loadlua\n    end\n    if _VERSION == \"Lua 5.1\" then\n        local context = { __index = function(t, k)\n            return t.context[k] or t.template[k] or _G[k]\n        end }\n        if jit then\n            loadchunk = function(view)\n                return assert(load(view, nil, nil, setmetatable({ template = template }, context)))\n            end\n        else\n            loadchunk = function(view)\n                local func = assert(loadstring(view))\n                setfenv(func, setmetatable({ template = template }, context))\n                return func\n            end\n        end\n    else\n        local context = { __index = function(t, k)\n            return t.context[k] or t.template[k] or _ENV[k]\n        end }\n        loadchunk = function(view)\n            return assert(load(view, nil, nil, setmetatable({ template = template }, context)))\n        end\n    end\nend\n\nfunction template.caching(enable)\n    if enable ~= nil then caching = enable == true end\n    return caching\nend\n\nfunction template.output(s)\n    if s == nil or s == null then return \"\" end\n    if type(s) == \"function\" then return template.output(s()) end\n    return tostring(s)\nend\n\nfunction template.escape(s, c)\n    if type(s) == \"string\" then\n        if c then return gsub(s, \"[}{\\\">/<'&]\", CODE_ENTITIES) end\n        return gsub(s, \"[\\\">/<'&]\", HTML_ENTITIES)\n    end\n    return template.output(s)\nend\n\nfunction template.new(view, layout)\n    assert(view, \"view was not provided for template.new(view, layout).\")\n    local render, compile = template.render, template.compile\n    if layout then\n        if type(layout) == \"table\" then\n            return setmetatable({ render = function(self, context)\n                local context = context or self\n                context.blocks = context.blocks or {}\n                context.view = compile(view)(context)\n                layout.blocks = context.blocks or {}\n                layout.view = context.view or \"\"\n                return layout:render()\n            end }, { __tostring = function(self)\n                local context = self\n                context.blocks = context.blocks or {}\n                context.view = compile(view)(context)\n                layout.blocks = context.blocks or {}\n                layout.view = context.view\n                return tostring(layout)\n            end })\n        else\n            return setmetatable({ render = function(self, context)\n                local context = context or self\n                context.blocks = context.blocks or {}\n                context.view = compile(view)(context)\n                return render(layout, context)\n            end }, { __tostring = function(self)\n                local context = self\n                context.blocks = context.blocks or {}\n                context.view = compile(view)(context)\n                return compile(layout)(context)\n            end })\n        end\n    end\n    return setmetatable({ render = function(self, context)\n        return render(view, context or self)\n    end }, { __tostring = function(self)\n        return compile(view)(self)\n    end })\nend\n\nfunction template.precompile(view, path, strip)\n    local chunk = dump(template.compile(view), strip ~= false)\n    if path then\n        local file = open(path, \"wb\")\n        file:write(chunk)\n        file:close()\n    end\n    return chunk\nend\n\nfunction template.compile(view, key, plain)\n    assert(view, \"view was not provided for template.compile(view, key, plain).\")\n    if key == \"no-cache\" then\n        return loadchunk(template.parse(view, plain)), false\n    end\n    key = key or view\n    local cache = template.cache\n    if cache[key] then return cache[key], true end\n    local func = loadchunk(template.parse(view, plain))\n    if caching then cache[key] = func end\n    return func, false\nend\n\nfunction template.parse(view, plain)\n    assert(view, \"view was not provided for template.parse(view, plain).\")\n    if not plain then\n        view = template.load(view)\n        if byte(sub(view, 1, 1)) == 27 then return view end\n    end\n    local j = 2\n    local c = {[[\ncontext=... or {}\nlocal function include(v, c) return template.compile(v)(c or context) end\nlocal ___,blocks,layout={},blocks or {}\n]] }\n    local i, s = 1, find(view, \"{\", 1, true)\n    while s do\n        local t, p = sub(view, s + 1, s + 1), s + 2\n        if t == \"{\" then\n            local e = find(view, \"}}\", p, true)\n            if e then\n                local z, w = escaped(view, s)\n                if i < s - w then\n                    c[j] = \"___[#___+1]=[=[\\n\"\n                    c[j+1] = sub(view, i, s - 1 - w)\n                    c[j+2] = \"]=]\\n\"\n                    j=j+3\n                end\n                if z then\n                    i = s\n                else\n                    c[j] = \"___[#___+1]=template.escape(\"\n                    c[j+1] = trim(sub(view, p, e - 1))\n                    c[j+2] = \")\\n\"\n                    j=j+3\n                    s, i = e + 1, e + 2\n                end\n            end\n        elseif t == \"*\" then\n            local e = find(view, \"*}\", p, true)\n            if e then\n                local z, w = escaped(view, s)\n                if i < s - w then\n                    c[j] = \"___[#___+1]=[=[\\n\"\n                    c[j+1] = sub(view, i, s - 1 - w)\n                    c[j+2] = \"]=]\\n\"\n                    j=j+3\n                end\n                if z then\n                    i = s\n                else\n                    c[j] = \"___[#___+1]=template.output(\"\n                    c[j+1] = trim(sub(view, p, e - 1))\n                    c[j+2] = \")\\n\"\n                    j=j+3\n                    s, i = e + 1, e + 2\n                end\n            end\n        elseif t == \"%\" then\n            local e = find(view, \"%}\", p, true)\n            if e then\n                local z, w = escaped(view, s)\n                if z then\n                    if i < s - w then\n                        c[j] = \"___[#___+1]=[=[\\n\"\n                        c[j+1] = sub(view, i, s - 1 - w)\n                        c[j+2] = \"]=]\\n\"\n                        j=j+3\n                    end\n                    i = s\n                else\n                    local n = e + 2\n                    if sub(view, n, n) == \"\\n\" then\n                        n = n + 1\n                    end\n                    local r = rpos(view, s - 1)\n                    if i <= r then\n                        c[j] = \"___[#___+1]=[=[\\n\"\n                        c[j+1] = sub(view, i, r)\n                        c[j+2] = \"]=]\\n\"\n                        j=j+3\n                    end\n                    c[j] = trim(sub(view, p, e - 1))\n                    c[j+1] = \"\\n\"\n                    j=j+2\n                    s, i = n - 1, n\n                end\n            end\n        elseif t == \"(\" then\n            local e = find(view, \")}\", p, true)\n            if e then\n                local z, w = escaped(view, s)\n                if i < s - w then\n                    c[j] = \"___[#___+1]=[=[\\n\"\n                    c[j+1] = sub(view, i, s - 1 - w)\n                    c[j+2] = \"]=]\\n\"\n                    j=j+3\n                end\n                if z then\n                    i = s\n                else\n                    local f = sub(view, p, e - 1)\n                    local x = find(f, \",\", 2, true)\n                    if x then\n                        c[j] = \"___[#___+1]=include([=[\"\n                        c[j+1] = trim(sub(f, 1, x - 1))\n                        c[j+2] = \"]=],\"\n                        c[j+3] = trim(sub(f, x + 1))\n                        c[j+4] = \")\\n\"\n                        j=j+5\n                    else\n                        c[j] = \"___[#___+1]=include([=[\"\n                        c[j+1] = trim(f)\n                        c[j+2] = \"]=])\\n\"\n                        j=j+3\n                    end\n                    s, i = e + 1, e + 2\n                end\n            end\n        elseif t == \"[\" then\n            local e = find(view, \"]}\", p, true)\n            if e then\n                local z, w = escaped(view, s)\n                if i < s - w then\n                    c[j] = \"___[#___+1]=[=[\\n\"\n                    c[j+1] = sub(view, i, s - 1 - w)\n                    c[j+2] = \"]=]\\n\"\n                    j=j+3\n                end\n                if z then\n                    i = s\n                else\n                    c[j] = \"___[#___+1]=include(\"\n                    c[j+1] = trim(sub(view, p, e - 1))\n                    c[j+2] = \")\\n\"\n                    j=j+3\n                    s, i = e + 1, e + 2\n                end\n            end\n        elseif t == \"-\" then\n            local e = find(view, \"-}\", p, true)\n            if e then\n                local x, y = find(view, sub(view, s, e + 1), e + 2, true)\n                if x then\n                    local z, w = escaped(view, s)\n                    if z then\n                        if i < s - w then\n                            c[j] = \"___[#___+1]=[=[\\n\"\n                            c[j+1] = sub(view, i, s - 1 - w)\n                            c[j+2] = \"]=]\\n\"\n                            j=j+3\n                        end\n                        i = s\n                    else\n                        y = y + 1\n                        x = x - 1\n                        if sub(view, y, y) == \"\\n\" then\n                            y = y + 1\n                        end\n                        local b = trim(sub(view, p, e - 1))\n                        if b == \"verbatim\" or b == \"raw\" then\n                            if i < s - w then\n                                c[j] = \"___[#___+1]=[=[\\n\"\n                                c[j+1] = sub(view, i, s - 1 - w)\n                                c[j+2] = \"]=]\\n\"\n                                j=j+3\n                            end\n                            c[j] = \"___[#___+1]=[=[\"\n                            c[j+1] = sub(view, e + 2, x)\n                            c[j+2] = \"]=]\\n\"\n                            j=j+3\n                        else\n                            if sub(view, x, x) == \"\\n\" then\n                                x = x - 1\n                            end\n                            local r = rpos(view, s - 1)\n                            if i <= r then\n                                c[j] = \"___[#___+1]=[=[\\n\"\n                                c[j+1] = sub(view, i, r)\n                                c[j+2] = \"]=]\\n\"\n                                j=j+3\n                            end\n                            c[j] = 'blocks[\"'\n                            c[j+1] = b\n                            c[j+2] = '\"]=include[=['\n                            c[j+3] = sub(view, e + 2, x)\n                            c[j+4] = \"]=]\\n\"\n                            j=j+5\n                        end\n                        s, i = y - 1, y\n                    end\n                end\n            end\n        elseif t == \"#\" then\n            local e = find(view, \"#}\", p, true)\n            if e then\n                local z, w = escaped(view, s)\n                if i < s - w then\n                    c[j] = \"___[#___+1]=[=[\\n\"\n                    c[j+1] = sub(view, i, s - 1 - w)\n                    c[j+2] = \"]=]\\n\"\n                    j=j+3\n                end\n                if z then\n                    i = s\n                else\n                    e = e + 2\n                    if sub(view, e, e) == \"\\n\" then\n                        e = e + 1\n                    end\n                    s, i = e - 1, e\n                end\n            end\n        end\n        s = find(view, \"{\", s + 1, true)\n    end\n    s = sub(view, i)\n    if s and s ~= \"\" then\n        c[j] = \"___[#___+1]=[=[\\n\"\n        c[j+1] = s\n        c[j+2] = \"]=]\\n\"\n        j=j+3\n    end\n    c[j] = \"return layout and include(layout,setmetatable({view=table.concat(___),blocks=blocks},{__index=context})) or table.concat(___)\"\n    return concat(c)\nend\n\nfunction template.render(view, context, key, plain)\n    assert(view, \"view was not provided for template.render(view, context, key, plain).\")\n    return template.print(template.compile(view, key, plain)(context))\nend\n\nreturn template\n"
  },
  {
    "path": "demo2/README.md",
    "content": "#### 这一章主要介绍介绍怎么获取请求参数，并且处理之后返回数据\n\n我们知道http请求通常分为两种，分别是GET，POST，在http协议中，GET参数通常会紧跟在uri后面，而POST请求参数则包含在请求体中，nginx默认情况下是不会读取POST请求参数的，最好也不要试图使改变这种行为，因为大多数情况下，POST请求都是转到后端去处理，nginx只需要读取请求uri部分，以及请求头\n\n由于这样的设计，所以获取请求参数的方式也有两种\n\nGET \n```\nlocal args = ngx.req.get_uri_args() -- 这里是一个table，包含所有get请求参数\nlocal id = ngx.var.arg_id -- 这里获取单个请求参数，但是如果没有传递这个参数，则会报错，推荐上面那张获取方式\n```\n\nPOST\n```\nngx.req.read_body() -- 先读取请求体\nlocal args = ngx.req.get_post_args() -- 这里也是一个table，包含所有post请求参数\n```\n\n可以通过下面这个方法获取http请求方法\n```\nlocal request_method = ngx.var.request_method -- GET or POST\n```\n\n为了统一获取请求参数的方式，隐藏具体细节，提供一个更友好的api接口，我们可以简单的封装一下\n\nlua/req.lua\n```\nlocal _M = {}\n\n-- 获取http get/post 请求参数\nfunction _M.getArgs()\n    local request_method = ngx.var.request_method\n    local args = ngx.req.get_uri_args()\n    -- 参数获取\n    if \"POST\" == request_method then\n        ngx.req.read_body()\n        local postArgs = ngx.req.get_post_args()\n        if postArgs then\n            for k, v in pairs(postArgs) do\n                args[k] = v\n            end\n        end\n    end\n    return args\nend\n\nreturn _M\n```\n\n这个模块就实现了参数的获取，而且支持GET，POST两种传参方式，以及参数放在uri，body的post请求，会合并两种方式提交的参数\n\n接下来我们可以写一个简单的lua，来引入这个模块，然后测试一下效果\n\nconf/nginx.conf\n```\nworker_processes  1;\n\nerror_log logs/error.log;\n\nevents {\n    worker_connections 1024;\n}\n\nhttp {\n    lua_package_path /Users/Lin/opensource/openresty-web-dev/demo2/lua/?.lua;  # 这里一定要指定package_path，否则会找不到引入的模块，然后会500\n    server {\n        listen 80;\n        server_name localhost;\n        lua_code_cache off;\n        location ~ /lua/(.+) {\n        \tdefault_type text/html;\t\n\t\t    content_by_lua_file lua/$1.lua;\n\t\t}\n    }\n}\n```\n\nlua/hello.lua\n```\nlocal req = require \"req\"\n\nlocal args = req.getArgs()\n\nlocal name = args['name']\n\nif name == nil or name == \"\" then\n\tname = \"guest\"\nend\n\nngx.say(\"<p>hello \" .. name .. \"!</p>\")\n\n```\n\n测试\n\nhttp://localhost/lua/hello?name=Lin\n输出 hello Lin!\nhttp://localhost/lua/hello\n\n输出 hello guest!\n\nok 到这里，我们已经能够根据请求的参数，并且在做一下处理后返回数据了\n\n[示例代码](https://github.com/362228416/openresty-web-dev)  参见demo2部分"
  },
  {
    "path": "demo2/conf/nginx.conf",
    "content": "\nworker_processes  1;\n\nerror_log logs/error.log;\n\nevents {\n    worker_connections 1024;\n}\n\nhttp {\n    lua_package_path \"$prefix/lua/?.lua\";\n    server {\n        listen 80;\n        server_name localhost;\n        lua_code_cache off;\n        location ~ /lua/(.+) {\n        \tdefault_type text/html;\n\t\t    content_by_lua_file lua/$1.lua;\n\t\t}\n    }\n}\n"
  },
  {
    "path": "demo2/logs/.gitignore",
    "content": "*.log\n*.pid\n"
  },
  {
    "path": "demo2/lua/hello.lua",
    "content": "\nlocal req = require \"req\"\n\nlocal args = req.getArgs()\n\nlocal name = args['name']\n\nif name == nil or name == \"\" then\n\tname = \"guest\"\nend\n\nngx.say(\"<p>hello, \" .. name .. \"!</p>\")"
  },
  {
    "path": "demo2/lua/req.lua",
    "content": "local _M = {}\n\n-- 获取http get/post 请求参数\nfunction _M.getArgs()\n    local request_method = ngx.var.request_method\n    local args = ngx.req.get_uri_args()\n    -- 参数获取\n    if \"POST\" == request_method then\n        ngx.req.read_body()\n        local postArgs = ngx.req.get_post_args()\n        if postArgs then\n            for k, v in pairs(postArgs) do\n                args[k] = v\n            end\n        end\n    end\n    return args\nend\n\nreturn _M"
  },
  {
    "path": "demo3/README.md",
    "content": "#### 这章主要介绍一下，lua怎么返回一个json字符串，怎么把一个table转成json字符串，又怎么把一个json字符串转成json\n\n其实很简答，直接使用cjson库的encode、decode方法即可\n\nlua/hello.lua\n```\nlocal cjson = require \"cjson\"\n\n-- 先定义一个json字符串\nlocal json_str = '{\"name\": \"Bruce.Lin\", \"age\": 25}'\n-- 这里把它转成对象，然后输出属性\nlocal json = cjson.decode(json_str)\nngx.say(\"Name = \" .. json['name'] .. \", Age = \" .. tostring(json['age'])) -- 这里需要把25转成字符串，才能进行字符串拼接\n\n-- 输出 Name = Bruce.Lin, Age = 25\n\nngx.say('<br/>') -- 换行\n\n-- 接下来我们再把json对象转成json字符串\nlocal json_str2 = cjson.encode(json)\nngx.say(json_str2)\n\n-- 输出{\"name\":\"Bruce.Lin\",\"age\":25}\n\nngx.say('<br/>') -- 换行\n\nlocal obj = {\n\tret = 200,\n\tmsg = \"login success\"\n}\n\nngx.say(cjson.encode(obj))\n\nngx.say('<br/>') -- 换行\n\nlocal obj2 = {}\n\nobj2['ret'] = 200\nobj2['msg'] = \"login fails\"\n\nngx.say(cjson.encode(obj2))\n\n```\n\nok，这里我们就学会的json字符串\n\n[示例代码](https://github.com/362228416/openresty-web-dev) 参见demo3部分"
  },
  {
    "path": "demo3/conf/nginx.conf",
    "content": "\nworker_processes  1;\n\nerror_log logs/error.log;\n\nevents {\n    worker_connections 1024;\n}\n\nhttp {\n    lua_package_path \"$prefix/lua/?.lua\";\n    server {\n        listen 80;\n        server_name localhost;\n        lua_code_cache off;\n        location ~ /lua/(.+) {\n        \tdefault_type text/html;\n\t\t    content_by_lua_file lua/$1.lua;\n\t\t}\n    }\n}\n"
  },
  {
    "path": "demo3/logs/.gitignore",
    "content": "*.log\n*.pid\n"
  },
  {
    "path": "demo3/lua/hello.lua",
    "content": "local cjson = require \"cjson\"\n\n-- 先定义一个json字符串\nlocal json_str = '{\"name\": \"Bruce.Lin\", \"age\": 25}'\n-- 这里把它转成对象，然后输出属性\nlocal json = cjson.decode(json_str)\nngx.say(\"Name = \" .. json['name'] .. \", Age = \" .. tostring(json['age'])) -- 这里需要把25转成字符串，才能进行字符串拼接\n\n-- 输出 Name = Bruce.Lin, Age = 25\n\nngx.say('<br/>') -- 换行\n\n-- 接下来我们再把json对象转成json字符串\nlocal json_str2 = cjson.encode(json)\nngx.say(json_str2)\n\n-- 输出{\"name\":\"Bruce.Lin\",\"age\":25}\n\nngx.say('<br/>') -- 换行\n\nlocal obj = {\n\tret = 200,\n\tmsg = \"login success\"\n}\n\nngx.say(cjson.encode(obj))\n\nngx.say('<br/>') -- 换行\n\nlocal obj2 = {}\n\nobj2['ret'] = 200\nobj2['msg'] = \"login fails\"\n\nngx.say(cjson.encode(obj2))"
  },
  {
    "path": "demo3/lua/req.lua",
    "content": "local _M = {}\n\n-- 获取http get/post 请求参数\nfunction _M.getArgs()\n    local request_method = ngx.var.request_method\n    local args = ngx.req.get_uri_args()\n    -- 参数获取\n    if \"POST\" == request_method then\n        ngx.req.read_body()\n        local postArgs = ngx.req.get_post_args()\n        if postArgs then\n            for k, v in pairs(postArgs) do\n                args[k] = v\n            end\n        end\n    end\n    return args\nend\n\nreturn _M"
  },
  {
    "path": "demo4/README.md",
    "content": "#### 这章主要演示怎么通过lua连接redis，并根据用户输入的key从redis获取value，并返回给用户\n\n操作redis主要用到了lua-resty-redis库，代码可以在[github](https://github.com/openresty/lua-resty-redis)上找得到\n\n而且上面也有实例代码\n\n由于官网给出的例子比较基本，代码也比较多，所以我这里主要介绍一些怎么封装一下，简化我们调用的代码\n\nlua/redis.lua\n```\nlocal redis = require \"resty.redis\"\n\nlocal config = {\n\thost = \"127.0.0.1\",\n    port = 6379,\n    -- pass = \"1234\"  -- redis 密码，没有密码的话，把这行注释掉\n}\n\nlocal _M = {}\n\n\nfunction _M.new(self)\n    local red = redis:new()\n    red:set_timeout(1000) -- 1 second\n    local res = red:connect(config['host'], config['port'])\n    if not res then\n        return nil\n    end\n    if config['pass'] ~= nil then\n\t\tres = red:auth(config['pass'])\n\t    if not res then\n\t        return nil\n\t    end\n    end\n    red.close = close\n    return red\nend\n\nfunction close(self)\n\tlocal sock = self.sock\n    if not sock then\n        return nil, \"not initialized\"\n    end\n    if self.subscribed then\n        return nil, \"subscribed state\"\n    end\n    return sock:setkeepalive(10000, 50)\nend\n\nreturn _M\n```\n\n其实就是简单把连接，跟关闭做一个简单的封装，隐藏繁琐的初始化已经连接池细节，只需要调用new，就自动就链接了redis，close自动使用连接池\n\nlua/hello.lua\n```\nlocal cjson = require \"cjson\"\nlocal redis = require \"redis\"\nlocal req = require \"req\"\n\nlocal args = req.getArgs()\nlocal key = args['key']\n\nif key == nil or key == \"\" then\n\tkey = \"foo\"\nend\n\n-- 下面的代码跟官方给的基本类似，只是简化了初始化代码，已经关闭的细节，我记得网上看到过一个  是修改官网的代码实现，我不太喜欢修改库的源码，除非万不得已，所以尽量简单的实现\nlocal red = redis:new()\nlocal value = red:get(key)\nred:close()\n\nlocal data = {\n\tret = 200,\n\tdata = value\n}\nngx.say(cjson.encode(data))\n\n```\n\n访问\nhttp://localhost/lua/hello?key=hello\n\n即可获取redis中的key为hello的值，如果没有key参数，则默认获取foo的值\n\nok，到这里我们已经可以获取用户输入的值，并且从redis中获取数据，然后返回json数据了，已经可以开发一些简单的接口了\n\n[示例代码](https://github.com/362228416/openresty-web-dev) 参见demo4部分"
  },
  {
    "path": "demo4/conf/nginx.conf",
    "content": "\nworker_processes  1;\n\nerror_log logs/error.log;\n\nevents {\n    worker_connections 1024;\n}\n\nhttp {\n    lua_package_path \"$prefix/demo4/lua/?.lua;$prefix/lualib/?.lua\";\n    server {\n        listen 80;\n        server_name localhost;\n        lua_code_cache off;\n        location ~ /lua/(.+) {\n        \tdefault_type text/html;\n\t\t    content_by_lua_file lua/$1.lua;\n\t\t}\n    }\n}\n"
  },
  {
    "path": "demo4/logs/.gitignore",
    "content": "*.log\n*.pid\n"
  },
  {
    "path": "demo4/lua/hello.lua",
    "content": "local cjson = require \"cjson\"\nlocal redis = require \"redis\"\nlocal req = require \"req\"\n\nlocal args = req.getArgs()\n\nlocal key = args['key']\n\nif key == nil or key == \"\" then\n\tkey = \"foo\"\nend\n\nlocal red = redis:new()\nlocal value = red:get(key)\nred:close()\n\nlocal data = {\n\tret = 200,\n\tdata = value\n}\nngx.say(cjson.encode(data))\n"
  },
  {
    "path": "demo4/lua/redis.lua",
    "content": "local redis = require \"resty.redis\"\n\nlocal config = {\n\thost = \"127.0.0.1\",\n    port = 6379,\n    -- pass = \"1234\"  -- redis 密码，没有密码的话，把这行注释掉\n}\n\nlocal _M = {}\n\n\nfunction _M.new(self)\n    local red = redis:new()\n    red:set_timeout(1000) -- 1 second\n    local res = red:connect(config['host'], config['port'])\n    if not res then\n        return nil\n    end\n    if config['pass'] ~= nil then\n\t\tres = red:auth(config['pass'])\n\t    if not res then\n\t        return nil\n\t    end\n    end\n    red.close = close\n    return red\nend\n\nfunction close(self)\n\tlocal sock = self.sock\n    if not sock then\n        return nil, \"not initialized\"\n    end\n    if self.subscribed then\n        return nil, \"subscribed state\"\n    end\n    return sock:setkeepalive(10000, 50)\nend\n\nreturn _M"
  },
  {
    "path": "demo4/lua/req.lua",
    "content": "local _M = {}\n\n-- 获取http get/post 请求参数\nfunction _M.getArgs()\n    local request_method = ngx.var.request_method\n    local args = ngx.req.get_uri_args()\n    -- 参数获取\n    if \"POST\" == request_method then\n        ngx.req.read_body()\n        local postArgs = ngx.req.get_post_args()\n        if postArgs then\n            for k, v in pairs(postArgs) do\n                args[k] = v\n            end\n        end\n    end\n    return args\nend\n\nreturn _M"
  },
  {
    "path": "demo5/README.md",
    "content": "openresty 前端开发入门五之Mysql篇\n\n#### 这章主要演示怎么通过lua连接mysql，并根据用户输入的name从mysql获取数据，并返回给用户\n\n操作mysql主要用到了lua-resty-mysql库，代码可以在[github](https://github.com/openresty/lua-resty-mysql)上找得到\n\n而且上面也有实例代码\n\n由于官网给出的例子比较基本，代码也比较多，所以我这里主要介绍一些怎么封装一下，简化我们调用的代码\n\nlua/mysql.lua\n```\nlocal mysql = require \"resty.mysql\"\n\nlocal config = {\n    host = \"localhost\",\n    port = 3306,\n    database = \"mysql\",\n    user = \"root\",\n    password = \"admin\"\n}\n\nlocal _M = {}\n\n\nfunction _M.new(self)\n    local db, err = mysql:new()\n    if not db then\n        return nil\n    end\n    db:set_timeout(1000) -- 1 sec\n\n    local ok, err, errno, sqlstate = db:connect(config)\n\n    if not ok then\n        return nil\n    end\n    db.close = close\n    return db\nend\n\nfunction close(self)\n\tlocal sock = self.sock\n    if not sock then\n        return nil, \"not initialized\"\n    end\n    if self.subscribed then\n        return nil, \"subscribed state\"\n    end\n    return sock:setkeepalive(10000, 50)\nend\n\nreturn _M\n```\n\n其实就是简单把连接，跟关闭做一个简单的封装，隐藏繁琐的初始化已经连接池细节，只需要调用new，就自动就链接了redis，close自动使用连接池\n\nlua/hello.lua\n```\nlocal cjson = require \"cjson\"\nlocal mysql = require \"mysql\"\nlocal req = require \"req\"\n\nlocal args = req.getArgs()\n\nlocal name = args['name']\n\nif name == nil or name == \"\" then\n\tname = \"root\"\t\nend\n\nname = ngx.quote_sql_str(name) -- SQL 转义，将 ' 转成 \\', 防SQL注入，并且转义后的变量包含了引号，所以可以直接当成条件值使用\n\nlocal db = mysql:new()\n\nlocal sql = \"select * from user where User = \" .. name\n\nngx.say(sql)\nngx.say(\"<br/>\")\n\nlocal res, err, errno, sqlstate = db:query(sql)\ndb:close()\nif not res then\n\tngx.say(err)\n    return {}\nend\n\nngx.say(cjson.encode(res))\n\n```\n\n访问\nhttp://localhost/lua/hello?name=root\n\n即可获取mysql中的name为root的的所有用户，如果没有name参数，则默认获取root的值\n\n从输出的数据中，可以看出res其实是一个数组，而且不管返回的数据是多少条，它都是一个数组，当我们查询的结果只有一条的时候，可以通过 res[1] 来获取一条记录，每一行数据又是一个table，可以通过列名来得到value\n\nok，到这里我们已经可以获取用户输入的值，并且从mysql中获取数据，然后返回json数据了，已经可以开发一些简单的接口了\n\n[示例代码](https://github.com/362228416/openresty-web-dev) 参见demo5部分"
  },
  {
    "path": "demo5/conf/nginx.conf",
    "content": "\nworker_processes  1;\n\nerror_log logs/error.log;\n\nevents {\n    worker_connections 1024;\n}\n\nhttp {\n    lua_package_path \"$prefix/lua/?.lua;$prefix/lualib/?.lua\";\n    server {\n        listen 80;\n        server_name localhost;\n        lua_code_cache off;\n        location ~ /lua/(.+) {\n        \tdefault_type text/html;\n\t\t    content_by_lua_file lua/$1.lua;\n\t\t}\n    }\n}\n"
  },
  {
    "path": "demo5/logs/.gitignore",
    "content": "*.log\n*.pid\n"
  },
  {
    "path": "demo5/lua/hello.lua",
    "content": "local cjson = require \"cjson\"\nlocal mysql = require \"mysql\"\nlocal req = require \"req\"\n\nlocal args = req.getArgs()\n\nlocal name = args['name']\n\nif name == nil or name == \"\" then\n\tname = \"root\"\t\nend\n\nname = ngx.quote_sql_str(name) -- SQL 转义，将 ' 转成 \\', 防SQL注入，并且转义后的变量包含了引号，所以可以直接当成条件值使用\n\nlocal db = mysql:new()\n\nlocal sql = \"select * from user where User = \" .. name\n\nngx.say(sql)\nngx.say(\"<br/>\")\n\nlocal res, err, errno, sqlstate = db:query(sql)\ndb:close()\nif not res then\n\tngx.say(err)\n    return {}\nend\n\nngx.say(cjson.encode(res))\n"
  },
  {
    "path": "demo5/lua/mysql.lua",
    "content": "local mysql = require \"resty.mysql\"\n\nlocal config = {\n    host = \"localhost\",\n    port = 3306,\n    database = \"mysql\",\n    user = \"root\",\n    password = \"admin\"\n}\n\nlocal _M = {}\n\n\nfunction _M.new(self)\n    local db, err = mysql:new()\n    if not db then\n        return nil\n    end\n    db:set_timeout(1000) -- 1 sec\n\n    local ok, err, errno, sqlstate = db:connect(config)\n\n    if not ok then\n        return nil\n    end\n    db.close = close\n    return db\nend\n\nfunction close(self)\n\tlocal sock = self.sock\n    if not sock then\n        return nil, \"not initialized\"\n    end\n    if self.subscribed then\n        return nil, \"subscribed state\"\n    end\n    return sock:setkeepalive(10000, 50)\nend\n\nreturn _M"
  },
  {
    "path": "demo5/lua/req.lua",
    "content": "local _M = {}\n\n-- 获取http get/post 请求参数\nfunction _M.getArgs()\n    local request_method = ngx.var.request_method\n    local args = ngx.req.get_uri_args()\n    -- 参数获取\n    if \"POST\" == request_method then\n        ngx.req.read_body()\n        local postArgs = ngx.req.get_post_args()\n        if postArgs then\n            for k, v in pairs(postArgs) do\n                args[k] = v\n            end\n        end\n    end\n    return args\nend\n\nreturn _M"
  },
  {
    "path": "demo6/README.md",
    "content": "#### 大多数情况下，调试信息，都可以通过ngx.say打印出来，但是有的时候，我们希望打印调试日志，不影响到返回数据，所以系统打印到其它地方，比如日志文件，或者控制台\n\n这里主要用到一个方法就是ngx.log，这个方法可以将日志输出到error.log里面，支持多种级别消息，如下：\n\n```\nngx.STDERR\nngx.EMERG\nngx.ALERT\nngx.CRIT\nngx.ERR\nngx.WARN\nngx.NOTICE\nngx.INFO\nngx.DEBUG\n```\n\n可以通过以下方式输出调试信息，大多数情况下我们只要使用一个来输出我们的调试信息就好了，比如ngx.ALERT，我就比较喜欢这个，并且设置为我的idea live template了，只需要sout + TAB 就可以输出，关于idea有很多玩法，有时间可以分享给大家，就里就不说了\n\nlua/hello.lua\n```\nngx.say('print to browser')\n\nngx.log(ngx.ALERT, 'print to error.log')\nngx.log(ngx.STDERR, 'print to error.log')\nngx.log(ngx.EMERG, 'print to error.log')\nngx.log(ngx.ALERT, 'print to error.log')\nngx.log(ngx.CRIT, 'print to error.log')\nngx.log(ngx.ERR, 'print to error.log')\nngx.log(ngx.WARN, 'print to error.log')\nngx.log(ngx.NOTICE, 'print to error.log')\nngx.log(ngx.INFO, 'print to error.log')\nngx.log(ngx.DEBUG, 'print to error.log')\n```\n\n然后用浏览器访问 http://localhost/lua/hello  查看浏览器输出，还有 logs/error.log 文件输出，就能明白大概的意思了，也不用过多解释\n\n#### 还有一种就是直接调用lua的print方法，进行输出，这个方法默认也会输出到error.log，默认输出级别是NOTICE，但是需要编译openresty的时候加上debug参数，如果是下载的windows预编译版本的话，默认没有debug，所以部分信息可能看不到，有需要可以自己编译一个\n\n当然nginx里面还可以配置error日志级别，如下\n```\nerror_log  logs/error.log  notice;\n```\n\n这句默认会在nginx.conf文件里面，只是注释掉了而已，只要打开注释就可以了，这样我们就可以直接通过print来输出日志了，完全是lua自带的函数，很多代码拿过来就可以直接使用\n\n### 还有就是lua运行时报错信息捕获，原因可能是语法问题，或者空指针等等，导致500错误的，这些错误日志都会输出在error.log里面，可以用tail命令实时查看，也可以通过封装然后采用pcall的调用形式，进行函数调用，这样就不会出现500，可以根据执行成败，将结果输出到客户端，做到像php那样所见即所得，可以大大提高我们的开发效率，这个后面我会通过封装一个轻量级框架来介绍，这里就先不讲了，有兴趣的同学可以了解一下\n\n[示例代码](https://github.com/362228416/openresty-web-dev) 参见demo6部分"
  },
  {
    "path": "demo6/conf/nginx.conf",
    "content": "\nworker_processes  1;\n\nerror_log logs/error.log;\n\nevents {\n    worker_connections 1024;\n}\n\nhttp {\n    lua_package_path \"$prefix/lua/?.lua;$prefix/lualib/?.lua\";\n    server {\n        listen 80;\n        server_name localhost;\n        lua_code_cache off;\n        location ~ /lua/(.+) {\n        \tdefault_type text/html;\n\t\t    content_by_lua_file lua/$1.lua;\n\t\t}\n    }\n}\n"
  },
  {
    "path": "demo6/logs/.gitignore",
    "content": "*.log\n*.pid\n"
  },
  {
    "path": "demo6/lua/hello.lua",
    "content": "\nngx.say('print to browser')\n\nngx.log(ngx.ALERT, 'print to error.log')\nngx.log(ngx.STDERR, 'print to error.log')\nngx.log(ngx.EMERG, 'print to error.log')\nngx.log(ngx.ALERT, 'print to error.log')\nngx.log(ngx.CRIT, 'print to error.log')\nngx.log(ngx.ERR, 'print to error.log')\nngx.log(ngx.WARN, 'print to error.log')\nngx.log(ngx.NOTICE, 'print to error.log')\nngx.log(ngx.INFO, 'print to error.log')\nngx.log(ngx.DEBUG, 'print to error.log')\n"
  },
  {
    "path": "demo6/lua/req.lua",
    "content": "local _M = {}\n\n-- 获取http get/post 请求参数\nfunction _M.getArgs()\n    local request_method = ngx.var.request_method\n    local args = ngx.req.get_uri_args()\n    -- 参数获取\n    if \"POST\" == request_method then\n        ngx.req.read_body()\n        local postArgs = ngx.req.get_post_args()\n        if postArgs then\n            for k, v in pairs(postArgs) do\n                args[k] = v\n            end\n        end\n    end\n    return args\nend\n\nreturn _M"
  },
  {
    "path": "demo7/README.md",
    "content": "#### 做前端开发，大多数情况下，都需要跟后端打交道，而最常见的方式则是通过http请求，进行通信。\n在openresty中，通过http跟后端整合通信的方式又很多种，各有各的好处，可以根据情况交叉使用\n\n## 1、直接proxy\n\n这种方式最简单，也是我们最熟悉的，直接配置一个反向代理，跟nginx的用法一致\n\n比如我们有一个后端服务，提供用户相关接口，是java写的，端口8080，为了简单起见，我直接在openresty里面配置一个server，模拟java端，通过一个简单的案例的来说明情况\n\nnginx.conf \n```\n\nworker_processes  1;\n\nerror_log logs/error.log;\n\nevents {\n    worker_connections 1024;\n}\n\nhttp {\n    lua_package_path \"/Users/john/opensource/openresty-web-dev/demo7/lua/?.lua;/usr/local/openresty/lualib/?.lua\";\n    server {\n        listen 80;\n        server_name localhost;\n        lua_code_cache off;\n\n        location / {\n            root html;\n            index index.html;\n        }\n\n        location ~ ^/user {\n            proxy_pass http://127.0.0.1:8080;\n        }\n\n    }\n\n\t# 这个只是模拟后端\n    server {\n        listen 8080;\n        server_name localhost;\n        lua_code_cache off;\n        location ~ /user/(.+) {\n            default_type text/html; \n            content_by_lua_file lua/$1.lua;\n        }\n    }\n\n}\n```\n\n上面配置了两个location，将所有以/user开头的请求都转到后端的8080服务器，其他的则是静态页面，直接从html目录读取，然后返回，从这里开始就是前端开发了\n\n\n为了简单起见，假设后端提供了一个登陆接口，我们这里直接用lua来实现一下就好了，检查用户名跟密码是admin，就返回成功，否则返回失败\n\nlua/login.lua   \n```\nlocal req = require \"req\"\nlocal cjson = require \"cjson\"\n\nlocal args = req.getArgs()\n\nlocal username = args['username']\nlocal password = args['password']\n\nlocal res = {}\n\nif username == \"admin\" and password == \"admin\" then\n\tres['ret'] = true\n\tres['token'] = ngx.md5('admin/' .. tostring(ngx.time()))\nelse\n\tres['ret'] = false\nend\n\nngx.say(cjson.encode(res))\n```\n\nindex.html\n\n```\n<html>\n<head>\n\t<meta charset=\"UTF-8\">\n\t<title>Login Page</title>\n</head>\n<body>\n\tUserName: <input type=\"text\" id=\"username\" value=\"admin\">\n\tPassword: <input type=\"password\" id=\"password\" value=\"admin\">\n\t<a href=\"javascript:void(0)\" onclick=\"login()\">Login</a>\n\t<script src=\"//cdn.bootcss.com/jquery/2.2.4/jquery.min.js\"></script>\n\t<script>\n\t\tfunction login() {\n\t\t\tvar username = $('#username').val();\n\t\t\tvar password = $('#password').val();\n\t\t\t$.post('/user/login', {username: username, password: password}, function(res){\n\t\t\t\tconsole.log(res)\n\t\t\t\tvar msg = res.ret ? \"登录成功\" : \"登录失败\"\n\t\t\t\talert(msg)\n\t\t\t}, 'json')\n\t\t}\n\t</script>\n</body>\n</html>\n```\n\n2、使用ngx.location.captrue\n\n这个方法主要用于发送内部请求，即请求当前server内的其他location，默认会将当前请求的参数带过去，也可以手动指定参数，GET参数通过args传递，post参数通过body传递\n\n如：\n\nlocal req = require \"req\"\nlocal args = req.getArgs()\n\nGET 调用\n\nlocal res = ngx.location.capture('/user/login', {\n    method = ngx.HTTP_GET,\n    args = args,\n});\n\nPOST 调用\n\nlocal res = ngx.location.capture('/user/login', {\n    method = ngx.HTTP_POST,\n    body = ngx.encode_args(args),\n});\n\n现在我们自己写一个lua来调用后台接口实现登陆，然后对请求做一点处理，实现一些额外的逻辑，比如在原来的参数上面加上一个from字段\n\nlua/local-login.lua\n\n```\nlocal req = require \"req\"\nlocal cjson = require \"cjson\"\n\nlocal args = req.getArgs()\n\n-- GET\nlocal res = ngx.location.capture('/user/login', {method = ngx.HTTP_GET, args = args})\n-- POST\n-- local res = ngx.location.capture('/user/login', {method = ngx.HTTP_POST, body = ngx.encode_args(args)})\n\n-- print(res.status) -- 状态码\n\nif res.status == 200 then\n\tlocal ret = cjson.decode(res.body)\n\tret['from'] = 'local'\n\tngx.say(cjson.encode(ret))\nelse\n\tprint(res.body)\n\tngx.say('{\"ret\": false, \"from\": \"local\"}')\nend\n\n```\n\nindex.html 也需要改一下，多加一个按钮，调用本地登陆接口\n```\n<html>\n<head>\n\t<meta charset=\"UTF-8\">\n\t<title>Login Page</title>\n</head>\n<body>\n\tUserName: <input type=\"text\" id=\"username\" value=\"admin\">\n\tPassword: <input type=\"password\" id=\"password\" value=\"admin\">\n\t<a href=\"javascript:void(0)\" onclick=\"login()\">Login</a>\n\t<a href=\"javascript:void(0)\" onclick=\"local_login()\">Local Login</a>\n\t<script src=\"//cdn.bootcss.com/jquery/2.2.4/jquery.min.js\"></script>\n\t<script>\n\t\tfunction login() {\n\t\t\tvar username = $('#username').val();\n\t\t\tvar password = $('#password').val();\n\t\t\t$.post('/user/login', {username: username, password: password}, function(res){\n\t\t\t\tconsole.log(res)\n\t\t\t\tvar msg = res.ret ? \"登录成功\" : \"登录失败\"\n\t\t\t\talert(msg)\n\t\t\t}, 'json')\n\t\t}\n\n\t\tfunction local_login() {\n\t\t\tvar username = $('#username').val();\n\t\t\tvar password = $('#password').val();\n\t\t\t$.post('/lua/local-login', {username: username, password: password}, function(res){\n\t\t\t\tconsole.log(res)\n\t\t\t\tvar msg = res.ret ? \"本地登录成功\" : \"本地登录失败\"\n\t\t\t\talert(msg)\n\t\t\t}, 'json')\n\t\t}\n\n\t</script>\n</body>\n</html>\n```\n\n3、第三方模块[lua-resty-http](https://github.com/pintsized/lua-resty-http)\n\n这种方式跟上面那种不同的地方是调用的时候，不会带上本地请求的请求头、cookie、以及请求参数，不过这也使得请求更纯粹，不会带上那些没必要的东西，减少数据传输\n\n最后local-login.lua 变成如下\n```\nlocal req = require \"req\"\nlocal cjson = require \"cjson\"\nlocal http = require \"resty.http\"\n\nlocal args = req.getArgs()\n\n-- GET\n-- local res = ngx.location.capture('/user/login', {method = ngx.HTTP_GET, args = args})\n\n-- POST\n-- local res = ngx.location.capture('/user/login', {method = ngx.HTTP_POST, body = ngx.encode_args(args)})\n\n-- http\nlocal httpc = http.new()\nlocal res = httpc:request_uri(\"http://127.0.0.1:8080/user/login\", {\n    method = \"POST\",\n    body = ngx.encode_args(args),\n    headers = {\n        [\"Accept\"] = \"application/json\",\n        [\"Accept-Encoding\"] = \"utf-8\",\n        [\"Cookie\"] = ngx.req.get_headers()['Cookie'],\n        [\"Content-Type\"] = \"application/x-www-form-urlencoded\",\n    }\n})\nhttpc:set_keepalive(60)\n\nprint(res.status) -- 状态码\n\nif res.status == 200 then\n\tlocal ret = cjson.decode(res.body)\n\tret['from'] = 'local'\n\tngx.say(cjson.encode(ret))\nelse\n\tprint(res.body)\n\tngx.say('{\"ret\": false, \"from\": \"local\"}')\nend\n```\n\n到此，基本上已经能通过openresty，做一些前后端的交互了，下次介绍怎么使用openresty模板渲染，以及搭配react开发前端。\n\n[示例代码](https://github.com/362228416/openresty-web-dev) 参见demo7部分\n\n\n\n\n"
  },
  {
    "path": "demo7/conf/nginx.conf",
    "content": "\nworker_processes  1;\n\nerror_log logs/error.log notice;\n\nevents {\n    worker_connections 1024;\n}\n\nhttp {\n    lua_package_path \"$prefix/lua/?.lua;$prefix/lualib/?.lua\";\n    server {\n        listen 80;\n        server_name localhost;\n        lua_code_cache off;\n\n        location / {\n            root html;\n            index index.html;\n        }\n\n        location ~ /lua/(.+) {\n        \tdefault_type text/html;\n\t\t    content_by_lua_file lua/$1.lua;\n\t\t}\n\n        location ~ ^/user {\n            proxy_pass http://127.0.0.1:8080;\n        }\n\n    }\n\n    # 这个只是模拟后端\n    server {\n        listen 8080;\n        server_name localhost;\n        lua_code_cache off;\n        location ~ /user/(.+) {\n            default_type text/html;\n            content_by_lua_file lua/$1.lua;\n        }\n    }\n\n}\n"
  },
  {
    "path": "demo7/html/index.html",
    "content": "<html>\n<head>\n\t<meta charset=\"UTF-8\">\n\t<title>Login Page</title>\n</head>\n<body>\n\tUserName: <input type=\"text\" id=\"username\" value=\"admin\">\n\tPassword: <input type=\"password\" id=\"password\" value=\"admin\">\n\t<a href=\"javascript:void(0)\" onclick=\"login()\">Login</a>\n\t<a href=\"javascript:void(0)\" onclick=\"local_login()\">Local Login</a>\n\t<script src=\"//cdn.bootcss.com/jquery/2.2.4/jquery.min.js\"></script>\n\t<script>\n\t\tfunction login() {\n\t\t\tvar username = $('#username').val();\n\t\t\tvar password = $('#password').val();\n\t\t\t$.post('/user/login', {username: username, password: password}, function(res){\n\t\t\t\tconsole.log(res)\n\t\t\t\tvar msg = res.ret ? \"登录成功\" : \"登录失败\"\n\t\t\t\talert(msg)\n\t\t\t}, 'json')\n\t\t}\n\n\t\tfunction local_login() {\n\t\t\tvar username = $('#username').val();\n\t\t\tvar password = $('#password').val();\n\t\t\t$.post('/lua/local-login', {username: username, password: password}, function(res){\n\t\t\t\tconsole.log(res)\n\t\t\t\tvar msg = res.ret ? \"本地登录成功\" : \"本地登录失败\"\n\t\t\t\talert(msg)\n\t\t\t}, 'json')\n\t\t}\n\n\t</script>\n</body>\n</html>"
  },
  {
    "path": "demo7/logs/.gitignore",
    "content": "*.log\n*.pid\n"
  },
  {
    "path": "demo7/lua/hello.lua",
    "content": "\nngx.say('print to browser')\n\nngx.log(ngx.ALERT, 'print to error.log')\nngx.log(ngx.STDERR, 'print to error.log')\nngx.log(ngx.EMERG, 'print to error.log')\nngx.log(ngx.ALERT, 'print to error.log')\nngx.log(ngx.CRIT, 'print to error.log')\nngx.log(ngx.ERR, 'print to error.log')\nngx.log(ngx.WARN, 'print to error.log')\nngx.log(ngx.NOTICE, 'print to error.log')\nngx.log(ngx.INFO, 'print to error.log')\nngx.log(ngx.DEBUG, 'print to error.log')\n"
  },
  {
    "path": "demo7/lua/local-login.lua",
    "content": "local req = require \"req\"\nlocal cjson = require \"cjson\"\nlocal http = require \"resty.http\"\n\nlocal args = req.getArgs()\n\n-- GET\n-- local res = ngx.location.capture('/user/login', {method = ngx.HTTP_GET, args = args})\n\n-- POST\n-- local res = ngx.location.capture('/user/login', {method = ngx.HTTP_POST, body = ngx.encode_args(args)})\n\n-- http\nlocal httpc = http.new()\nlocal res = httpc:request_uri(\"http://127.0.0.1:8080/user/login\", {\n    method = \"POST\",\n    body = ngx.encode_args(args),\n    headers = {\n        [\"Accept\"] = \"application/json\",\n        [\"Accept-Encoding\"] = \"utf-8\",\n        [\"Cookie\"] = ngx.req.get_headers()['Cookie'],\n        [\"Content-Type\"] = \"application/x-www-form-urlencoded\",\n    }\n})\nhttpc:set_keepalive(60)\n\nprint(res.status) -- 状态码\n\nif res.status == 200 then\n\tlocal ret = cjson.decode(res.body)\n\tret['from'] = 'local'\n\tngx.say(cjson.encode(ret))\nelse\n\tprint(res.body)\n\tngx.say('{\"ret\": false, \"from\": \"local\"}')\nend\n\n"
  },
  {
    "path": "demo7/lua/login.lua",
    "content": "local req = require \"req\"\nlocal cjson = require \"cjson\"\n\nlocal args = req.getArgs()\n\nlocal username = args['username']\nlocal password = args['password']\n\nprint(username)\nprint(password)\n\nlocal res = {method = ngx.var.request_method}\n\nif username == \"admin\" and password == \"admin\" then\n\tres['ret'] = true\n\tres['token'] = ngx.md5(username .. '/' .. tostring(ngx.time()))\nelse\n\tres['ret'] = false\nend\n\nngx.say(cjson.encode(res))"
  },
  {
    "path": "demo7/lua/req.lua",
    "content": "local _M = {}\n\n-- 获取http get/post 请求参数\nfunction _M.getArgs()\n    local request_method = ngx.var.request_method\n    local args = ngx.req.get_uri_args()\n    -- 参数获取\n    if \"POST\" == request_method then\n        ngx.req.read_body()\n        local postArgs = ngx.req.get_post_args()\n        if postArgs then\n            for k, v in pairs(postArgs) do\n                args[k] = v\n            end\n        end\n    end\n    return args\nend\n\nreturn _M"
  },
  {
    "path": "demo7/lualib/resty/http.lua",
    "content": "local http_headers = require \"resty.http_headers\"\n\nlocal ngx_socket_tcp = ngx.socket.tcp\nlocal ngx_req = ngx.req\nlocal ngx_req_socket = ngx_req.socket\nlocal ngx_req_get_headers = ngx_req.get_headers\nlocal ngx_req_get_method = ngx_req.get_method\nlocal str_gmatch = string.gmatch\nlocal str_lower = string.lower\nlocal str_upper = string.upper\nlocal str_find = string.find\nlocal str_sub = string.sub\nlocal str_gsub = string.gsub\nlocal tbl_concat = table.concat\nlocal tbl_insert = table.insert\nlocal ngx_encode_args = ngx.encode_args\nlocal ngx_re_match = ngx.re.match\nlocal ngx_re_gsub = ngx.re.gsub\nlocal ngx_log = ngx.log\nlocal ngx_DEBUG = ngx.DEBUG\nlocal ngx_ERR = ngx.ERR\nlocal ngx_NOTICE = ngx.NOTICE\nlocal ngx_var = ngx.var\nlocal co_yield = coroutine.yield\nlocal co_create = coroutine.create\nlocal co_status = coroutine.status\nlocal co_resume = coroutine.resume\n\n\n-- http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.1\nlocal HOP_BY_HOP_HEADERS = {\n    [\"connection\"]          = true,\n    [\"keep-alive\"]          = true,\n    [\"proxy-authenticate\"]  = true,\n    [\"proxy-authorization\"] = true,\n    [\"te\"]                  = true,\n    [\"trailers\"]            = true,\n    [\"transfer-encoding\"]   = true,\n    [\"upgrade\"]             = true,\n    [\"content-length\"]      = true, -- Not strictly hop-by-hop, but Nginx will deal\n                                    -- with this (may send chunked for example).\n}\n\n\n-- Reimplemented coroutine.wrap, returning \"nil, err\" if the coroutine cannot\n-- be resumed. This protects user code from inifite loops when doing things like\n-- repeat\n--   local chunk, err = res.body_reader()\n--   if chunk then -- <-- This could be a string msg in the core wrap function.\n--     ...\n--   end\n-- until not chunk\nlocal co_wrap = function(func)\n    local co = co_create(func)\n    if not co then\n        return nil, \"could not create coroutine\"\n    else\n        return function(...)\n            if co_status(co) == \"suspended\" then\n                return select(2, co_resume(co, ...))\n            else\n                return nil, \"can't resume a \" .. co_status(co) .. \" coroutine\"\n            end\n        end\n    end\nend\n\n\nlocal _M = {\n    _VERSION = '0.09',\n}\n_M._USER_AGENT = \"lua-resty-http/\" .. _M._VERSION .. \" (Lua) ngx_lua/\" .. ngx.config.ngx_lua_version\n\nlocal mt = { __index = _M }\n\n\nlocal HTTP = {\n    [1.0] = \" HTTP/1.0\\r\\n\",\n    [1.1] = \" HTTP/1.1\\r\\n\",\n}\n\nlocal DEFAULT_PARAMS = {\n    method = \"GET\",\n    path = \"/\",\n    version = 1.1,\n}\n\n\nfunction _M.new(self)\n    local sock, err = ngx_socket_tcp()\n    if not sock then\n        return nil, err\n    end\n    return setmetatable({ sock = sock, keepalive = true }, mt)\nend\n\n\nfunction _M.set_timeout(self, timeout)\n    local sock = self.sock\n    if not sock then\n        return nil, \"not initialized\"\n    end\n\n    return sock:settimeout(timeout)\nend\n\n\nfunction _M.ssl_handshake(self, ...)\n    local sock = self.sock\n    if not sock then\n        return nil, \"not initialized\"\n    end\n\n    self.ssl = true\n\n    return sock:sslhandshake(...)\nend\n\n\nfunction _M.connect(self, ...)\n    local sock = self.sock\n    if not sock then\n        return nil, \"not initialized\"\n    end\n\n    self.host = select(1, ...)\n    self.port = select(2, ...)\n\n    -- If port is not a number, this is likely a unix domain socket connection.\n    if type(self.port) ~= \"number\" then\n        self.port = nil\n    end\n\n    self.keepalive = true\n\n    return sock:connect(...)\nend\n\n\nfunction _M.set_keepalive(self, ...)\n    local sock = self.sock\n    if not sock then\n        return nil, \"not initialized\"\n    end\n\n    if self.keepalive == true then\n        return sock:setkeepalive(...)\n    else\n        -- The server said we must close the connection, so we cannot setkeepalive.\n        -- If close() succeeds we return 2 instead of 1, to differentiate between\n        -- a normal setkeepalive() failure and an intentional close().\n        local res, err = sock:close()\n        if res then\n            return 2, \"connection must be closed\"\n        else\n            return res, err\n        end\n    end\nend\n\n\nfunction _M.get_reused_times(self)\n    local sock = self.sock\n    if not sock then\n        return nil, \"not initialized\"\n    end\n\n    return sock:getreusedtimes()\nend\n\n\nfunction _M.close(self)\n    local sock = self.sock\n    if not sock then\n        return nil, \"not initialized\"\n    end\n\n    return sock:close()\nend\n\n\nlocal function _should_receive_body(method, code)\n    if method == \"HEAD\" then return nil end\n    if code == 204 or code == 304 then return nil end\n    if code >= 100 and code < 200 then return nil end\n    return true\nend\n\n\nfunction _M.parse_uri(self, uri)\n    local m, err = ngx_re_match(uri, [[^(http[s]?)://([^:/]+)(?::(\\d+))?(.*)]],\n        \"jo\")\n\n    if not m then\n        if err then\n            return nil, \"failed to match the uri: \" .. uri .. \", \" .. err\n        end\n\n        return nil, \"bad uri: \" .. uri\n    else\n        if m[3] then\n            m[3] = tonumber(m[3])\n        else\n            if m[1] == \"https\" then\n                m[3] = 443\n            else\n                m[3] = 80\n            end\n        end\n        if not m[4] or \"\" == m[4] then m[4] = \"/\" end\n        return m, nil\n    end\nend\n\n\nlocal function _format_request(params)\n    local version = params.version\n    local headers = params.headers or {}\n\n    local query = params.query or \"\"\n    if query then\n        if type(query) == \"table\" then\n            query = \"?\" .. ngx_encode_args(query)\n        end\n    end\n\n    -- Initialize request\n    local req = {\n        str_upper(params.method),\n        \" \",\n        params.path,\n        query,\n        HTTP[version],\n        -- Pre-allocate slots for minimum headers and carriage return.\n        true,\n        true,\n        true,\n    }\n    local c = 6 -- req table index it's faster to do this inline vs table.insert\n\n    -- Append headers\n    for key, values in pairs(headers) do\n        if type(values) ~= \"table\" then\n            values = {values}\n        end\n\n        key = tostring(key)\n        for _, value in pairs(values) do\n            req[c] = key .. \": \" .. tostring(value) .. \"\\r\\n\"\n            c = c + 1\n        end\n    end\n\n    -- Close headers\n    req[c] = \"\\r\\n\"\n\n    return tbl_concat(req)\nend\n\n\nlocal function _receive_status(sock)\n    local line, err = sock:receive(\"*l\")\n    if not line then\n        return nil, nil, nil, err\n    end\n\n    return tonumber(str_sub(line, 10, 12)), tonumber(str_sub(line, 6, 8)), str_sub(line, 14)\nend\n\n\n\nlocal function _receive_headers(sock)\n    local headers = http_headers.new()\n\n    repeat\n        local line, err = sock:receive(\"*l\")\n        if not line then\n            return nil, err\n        end\n\n        for key, val in str_gmatch(line, \"([^:%s]+):%s*(.+)\") do\n            if headers[key] then\n                if type(headers[key]) ~= \"table\" then\n                    headers[key] = { headers[key] }\n                end\n                tbl_insert(headers[key], tostring(val))\n            else\n                headers[key] = tostring(val)\n            end\n        end\n    until str_find(line, \"^%s*$\")\n\n    return headers, nil\nend\n\n\nlocal function _chunked_body_reader(sock, default_chunk_size)\n    return co_wrap(function(max_chunk_size)\n        local max_chunk_size = max_chunk_size or default_chunk_size\n        local remaining = 0\n        local length\n\n        repeat\n            -- If we still have data on this chunk\n            if max_chunk_size and remaining > 0 then\n\n                if remaining > max_chunk_size then\n                    -- Consume up to max_chunk_size\n                    length = max_chunk_size\n                    remaining = remaining - max_chunk_size\n                else\n                    -- Consume all remaining\n                    length = remaining\n                    remaining = 0\n                end\n            else -- This is a fresh chunk\n\n                -- Receive the chunk size\n                local str, err = sock:receive(\"*l\")\n                if not str then\n                    co_yield(nil, err)\n                end\n\n                length = tonumber(str, 16)\n\n                if not length then\n                    co_yield(nil, \"unable to read chunksize\")\n                end\n\n                if max_chunk_size and length > max_chunk_size then\n                    -- Consume up to max_chunk_size\n                    remaining = length - max_chunk_size\n                    length = max_chunk_size\n                end\n            end\n\n            if length > 0 then\n                local str, err = sock:receive(length)\n                if not str then\n                    co_yield(nil, err)\n                end\n\n                max_chunk_size = co_yield(str) or default_chunk_size\n\n                -- If we're finished with this chunk, read the carriage return.\n                if remaining == 0 then\n                    sock:receive(2) -- read \\r\\n\n                end\n            else\n                -- Read the last (zero length) chunk's carriage return\n                sock:receive(2) -- read \\r\\n\n            end\n\n        until length == 0\n    end)\nend\n\n\nlocal function _body_reader(sock, content_length, default_chunk_size)\n    return co_wrap(function(max_chunk_size)\n        local max_chunk_size = max_chunk_size or default_chunk_size\n\n        if not content_length and max_chunk_size then\n            -- We have no length, but wish to stream.\n            -- HTTP 1.0 with no length will close connection, so read chunks to the end.\n            repeat\n                local str, err, partial = sock:receive(max_chunk_size)\n                if not str and err == \"closed\" then\n                    max_chunk_size = tonumber(co_yield(partial, err) or default_chunk_size)\n                end\n\n                max_chunk_size = tonumber(co_yield(str) or default_chunk_size)\n                if max_chunk_size and max_chunk_size < 0 then max_chunk_size = nil end\n\n                if not max_chunk_size then\n                    ngx_log(ngx_ERR, \"Buffer size not specified, bailing\")\n                    break\n                end\n            until not str\n\n        elseif not content_length then\n            -- We have no length but don't wish to stream.\n            -- HTTP 1.0 with no length will close connection, so read to the end.\n            co_yield(sock:receive(\"*a\"))\n\n        elseif not max_chunk_size then\n            -- We have a length and potentially keep-alive, but want everything.\n            co_yield(sock:receive(content_length))\n\n        else\n            -- We have a length and potentially a keep-alive, and wish to stream\n            -- the response.\n            local received = 0\n            repeat\n                local length = max_chunk_size\n                if received + length > content_length then\n                    length = content_length - received\n                end\n\n                if length > 0 then\n                    local str, err = sock:receive(length)\n                    if not str then\n                        max_chunk_size = tonumber(co_yield(nil, err) or default_chunk_size)\n                    end\n                    received = received + length\n\n                    max_chunk_size = tonumber(co_yield(str) or default_chunk_size)\n                    if max_chunk_size and max_chunk_size < 0 then max_chunk_size = nil end\n\n                    if not max_chunk_size then\n                        ngx_log(ngx_ERR, \"Buffer size not specified, bailing\")\n                        break\n                    end\n                end\n\n            until length == 0\n        end\n    end)\nend\n\n\nlocal function _no_body_reader()\n    return nil\nend\n\n\nlocal function _read_body(res)\n    local reader = res.body_reader\n\n    if not reader then\n        -- Most likely HEAD or 304 etc.\n        return nil, \"no body to be read\"\n    end\n\n    local chunks = {}\n    local c = 1\n\n    local chunk, err\n    repeat\n        chunk, err = reader()\n\n        if err then\n            return nil, err, tbl_concat(chunks) -- Return any data so far.\n        end\n        if chunk then\n            chunks[c] = chunk\n            c = c + 1\n        end\n    until not chunk\n\n    return tbl_concat(chunks)\nend\n\n\nlocal function _trailer_reader(sock)\n    return co_wrap(function()\n        co_yield(_receive_headers(sock))\n    end)\nend\n\n\nlocal function _read_trailers(res)\n    local reader = res.trailer_reader\n    if not reader then\n        return nil, \"no trailers\"\n    end\n\n    local trailers = reader()\n    setmetatable(res.headers, { __index = trailers })\nend\n\n\nlocal function _send_body(sock, body)\n    if type(body) == 'function' then\n        repeat\n            local chunk, err, partial = body()\n\n            if chunk then\n                local ok,err = sock:send(chunk)\n\n                if not ok then\n                    return nil, err\n                end\n            elseif err ~= nil then\n                return nil, err, partial\n            end\n\n        until chunk == nil\n    elseif body ~= nil then\n        local bytes, err = sock:send(body)\n\n        if not bytes then\n            return nil, err\n        end\n    end\n    return true, nil\nend\n\n\nlocal function _handle_continue(sock, body)\n    local status, version, reason, err = _receive_status(sock)\n    if not status then\n        return nil, nil, err\n    end\n\n    -- Only send body if we receive a 100 Continue\n    if status == 100 then\n        local ok, err = sock:receive(\"*l\") -- Read carriage return\n        if not ok then\n            return nil, nil, err\n        end\n        _send_body(sock, body)\n    end\n    return status, version, err\nend\n\n\nfunction _M.send_request(self, params)\n    -- Apply defaults\n    setmetatable(params, { __index = DEFAULT_PARAMS })\n\n    local sock = self.sock\n    local body = params.body\n    local headers = http_headers.new()\n\n    local params_headers = params.headers\n    if params_headers then\n        -- We assign one by one so that the metatable can handle case insensitivity\n        -- for us. You can blame the spec for this inefficiency.\n        for k,v in pairs(params_headers) do\n            headers[k] = v\n        end\n    end\n\n    -- Ensure minimal headers are set\n    if type(body) == 'string' and not headers[\"Content-Length\"] then\n        headers[\"Content-Length\"] = #body\n    end\n    if not headers[\"Host\"] then\n        if (str_sub(self.host, 1, 5) == \"unix:\") then\n            return nil, \"Unable to generate a useful Host header for a unix domain socket. Please provide one.\"\n        end\n        -- If we have a port (i.e. not connected to a unix domain socket), and this\n        -- port is non-standard, append it to the Host heaer.\n        if self.port then\n            if self.ssl and self.port ~= 443 then\n                headers[\"Host\"] = self.host .. \":\" .. self.port\n            elseif not self.ssl and self.port ~= 80 then\n                headers[\"Host\"] = self.host .. \":\" .. self.port\n            else\n                headers[\"Host\"] = self.host\n            end\n        else\n            headers[\"Host\"] = self.host\n        end\n    end\n    if not headers[\"User-Agent\"] then\n        headers[\"User-Agent\"] = _M._USER_AGENT\n    end\n    if params.version == 1.0 and not headers[\"Connection\"] then\n        headers[\"Connection\"] = \"Keep-Alive\"\n    end\n\n    params.headers = headers\n\n    -- Format and send request\n    local req = _format_request(params)\n    ngx_log(ngx_DEBUG, \"\\n\", req)\n    local bytes, err = sock:send(req)\n\n    if not bytes then\n        return nil, err\n    end\n\n    -- Send the request body, unless we expect: continue, in which case\n    -- we handle this as part of reading the response.\n    if headers[\"Expect\"] ~= \"100-continue\" then\n        local ok, err, partial = _send_body(sock, body)\n        if not ok then\n            return nil, err, partial\n        end\n    end\n\n    return true\nend\n\n\nfunction _M.read_response(self, params)\n    local sock = self.sock\n\n    local status, version, reason, err\n\n    -- If we expect: continue, we need to handle this, sending the body if allowed.\n    -- If we don't get 100 back, then status is the actual status.\n    if params.headers[\"Expect\"] == \"100-continue\" then\n        local _status, _version, _err = _handle_continue(sock, params.body)\n        if not _status then\n            return nil, _err\n        elseif _status ~= 100 then\n            status, version, err = _status, _version, _err\n        end\n    end\n\n    -- Just read the status as normal.\n    if not status then\n        status, version, reason, err = _receive_status(sock)\n        if not status then\n            return nil, err\n        end\n    end\n\n\n    local res_headers, err = _receive_headers(sock)\n    if not res_headers then\n        return nil, err\n    end\n\n    -- keepalive is true by default. Determine if this is correct or not.\n    local ok, connection = pcall(str_lower, res_headers[\"Connection\"])\n    if ok then\n        if  (version == 1.1 and connection == \"close\") or\n            (version == 1.0 and connection ~= \"keep-alive\") then\n            self.keepalive = false\n        end\n    else\n        -- no connection header\n        if version == 1.0 then\n            self.keepalive = false\n        end\n    end\n\n    local body_reader = _no_body_reader\n    local trailer_reader, err = nil, nil\n    local has_body = false\n\n    -- Receive the body_reader\n    if _should_receive_body(params.method, status) then\n        local ok, encoding = pcall(str_lower, res_headers[\"Transfer-Encoding\"])\n        if ok and version == 1.1 and encoding == \"chunked\" then\n            body_reader, err = _chunked_body_reader(sock)\n            has_body = true\n        else\n\n            local ok, length = pcall(tonumber, res_headers[\"Content-Length\"])\n            if ok then\n                body_reader, err = _body_reader(sock, length)\n                has_body = true\n            end\n        end\n    end\n\n    if res_headers[\"Trailer\"] then\n        trailer_reader, err = _trailer_reader(sock)\n    end\n\n    if err then\n        return nil, err\n    else\n        return {\n            status = status,\n            reason = reason,\n            headers = res_headers,\n            has_body = has_body,\n            body_reader = body_reader,\n            read_body = _read_body,\n            trailer_reader = trailer_reader,\n            read_trailers = _read_trailers,\n        }\n    end\nend\n\n\nfunction _M.request(self, params)\n    local res, err = self:send_request(params)\n    if not res then\n        return res, err\n    else\n        return self:read_response(params)\n    end\nend\n\n\nfunction _M.request_pipeline(self, requests)\n    for i, params in ipairs(requests) do\n        if params.headers and params.headers[\"Expect\"] == \"100-continue\" then\n            return nil, \"Cannot pipeline request specifying Expect: 100-continue\"\n        end\n\n        local res, err = self:send_request(params)\n        if not res then\n            return res, err\n        end\n    end\n\n    local responses = {}\n    for i, params in ipairs(requests) do\n        responses[i] = setmetatable({\n            params = params,\n            response_read = false,\n        }, {\n            -- Read each actual response lazily, at the point the user tries\n            -- to access any of the fields.\n            __index = function(t, k)\n                local res, err\n                if t.response_read == false then\n                    res, err = _M.read_response(self, t.params)\n                    t.response_read = true\n\n                    if not res then\n                        ngx_log(ngx_ERR, err)\n                    else\n                        for rk, rv in pairs(res) do\n                            t[rk] = rv\n                        end\n                    end\n                end\n                return rawget(t, k)\n            end,\n        })\n    end\n    return responses\nend\n\n\nfunction _M.request_uri(self, uri, params)\n    if not params then params = {} end\n\n    local parsed_uri, err = self:parse_uri(uri)\n    if not parsed_uri then\n        return nil, err\n    end\n\n    local scheme, host, port, path = unpack(parsed_uri)\n    if not params.path then params.path = path end\n\n    local c, err = self:connect(host, port)\n    if not c then\n        return nil, err\n    end\n\n    if scheme == \"https\" then\n        local verify = true\n        if params.ssl_verify == false then\n            verify = false\n        end\n        local ok, err = self:ssl_handshake(nil, host, verify)\n        if not ok then\n            return nil, err\n        end\n    end\n\n    local res, err = self:request(params)\n    if not res then\n        return nil, err\n    end\n\n    local body, err = res:read_body()\n    if not body then\n        return nil, err\n    end\n\n    res.body = body\n\n    local ok, err = self:set_keepalive()\n    if not ok then\n        ngx_log(ngx_ERR, err)\n    end\n\n    return res, nil\nend\n\n\nfunction _M.get_client_body_reader(self, chunksize, sock)\n    local chunksize = chunksize or 65536\n    if not sock then\n        local ok, err\n        ok, sock, err = pcall(ngx_req_socket)\n\n        if not ok then\n            return nil, sock -- pcall err\n        end\n\n        if not sock then\n            if err == \"no body\" then\n                return nil\n            else\n                return nil, err\n            end\n        end\n    end\n\n    local headers = ngx_req_get_headers()\n    local length = headers.content_length\n    local encoding = headers.transfer_encoding\n    if length then\n        return _body_reader(sock, tonumber(length), chunksize)\n    elseif encoding and str_lower(encoding) == 'chunked' then\n        -- Not yet supported by ngx_lua but should just work...\n        return _chunked_body_reader(sock, chunksize)\n    else\n       return nil\n    end\nend\n\n\nfunction _M.proxy_request(self, chunksize)\n    return self:request{\n        method = ngx_req_get_method(),\n        path = ngx_re_gsub(ngx_var.uri, \"\\\\s\", \"%20\", \"jo\") .. ngx_var.is_args .. (ngx_var.query_string or \"\"),\n        body = self:get_client_body_reader(chunksize),\n        headers = ngx_req_get_headers(),\n    }\nend\n\n\nfunction _M.proxy_response(self, response, chunksize)\n    if not response then\n        ngx_log(ngx_ERR, \"no response provided\")\n        return\n    end\n\n    ngx.status = response.status\n\n    -- Filter out hop-by-hop headeres\n    for k,v in pairs(response.headers) do\n        if not HOP_BY_HOP_HEADERS[str_lower(k)] then\n            ngx.header[k] = v\n        end\n    end\n\n    local reader = response.body_reader\n    repeat\n        local chunk, err = reader(chunksize)\n        if err then\n            ngx_log(ngx_ERR, err)\n            break\n        end\n\n        if chunk then\n            local res, err = ngx.print(chunk)\n            if not res then\n                ngx_log(ngx_ERR, err)\n                break\n            end\n        end\n    until not chunk\nend\n\n\nreturn _M\n"
  },
  {
    "path": "demo7/lualib/resty/http_headers.lua",
    "content": "local   rawget, rawset, setmetatable =\n        rawget, rawset, setmetatable\n\nlocal str_gsub = string.gsub\nlocal str_lower = string.lower\n\n\nlocal _M = {\n    _VERSION = '0.01',\n}\n\n\n-- Returns an empty headers table with internalised case normalisation.\n-- Supports the same cases as in ngx_lua:\n--\n-- headers.content_length\n-- headers[\"content-length\"]\n-- headers[\"Content-Length\"]\nfunction _M.new(self)\n    local mt = { \n        normalised = {},\n    }\n\n\n    mt.__index = function(t, k)\n        local k_hyphened = str_gsub(k, \"_\", \"-\")\n        local matched = rawget(t, k)\n        if matched then\n            return matched\n        else\n            local k_normalised = str_lower(k_hyphened)\n            return rawget(t, mt.normalised[k_normalised])\n        end\n    end\n\n\n    -- First check the normalised table. If there's no match (first time) add an entry for\n    -- our current case in the normalised table. This is to preserve the human (prettier) case\n    -- instead of outputting lowercased header names.\n    --\n    -- If there's a match, we're being updated, just with a different case for the key. We use\n    -- the normalised table to give us the original key, and perorm a rawset().\n    mt.__newindex = function(t, k, v)\n        -- we support underscore syntax, so always hyphenate.\n        local k_hyphened = str_gsub(k, \"_\", \"-\")\n\n        -- lowercase hyphenated is \"normalised\"\n        local k_normalised = str_lower(k_hyphened)\n\n        if not mt.normalised[k_normalised] then\n            mt.normalised[k_normalised] = k_hyphened\n            rawset(t, k_hyphened, v)\n        else\n            rawset(t, mt.normalised[k_normalised], v)\n        end\n    end\n\n    return setmetatable({}, mt)\nend\n\n\nreturn _M\n"
  },
  {
    "path": "demo8/README.md",
    "content": "#### 通过前面几章，我们已经掌握了一些基本的开发知识，但是代码结构比较简单，缺乏统一的标准，模块化，也缺乏统一的异常处理，这一章我们主要来学习如何封装一个轻量级的MVC框架，规范以及简化开发，并且提供类似php所见即所得的能力\n\n## 统一入口\n\n通常来说一个mvc框架会有一个统一的入口点，类似于spring mvc的DispatcherServlet，会拦截所有的请求，也就是/，于是我们可以得出我们的入口点\n\nconf/nginx.conf\n```\nworker_processes  1;\n\nerror_log logs/error.log notice;\n\nevents {\n    worker_connections 1024;\n}\n\nhttp {\n    lua_package_path \"/Users/john/opensource/openresty-web-dev/demo8/lua/?.lua;/Users/john/opensource/openresty-web-dev/demo8/lualib/?.lua;/usr/local/openresty/lualib/?.lua\";\n    server {\n        listen 80;\n        server_name localhost;\n        lua_code_cache off;\n\n        location / {\n        \tcontent_by_lua_file lua/mvc.lua;\n        }\n\n        location ~ ^/js/|^/css/|\\.html {\n        \troot html;\n        }\n    }\n}\n```\n\n除了静态文件js/css/html文件，其他的请求都会被我们的mvc.lua处理。\n\n## 默认页面\n\n当请求uri为空时，默认返回index.html页面，当然也可以自己定义，实现这个效果很简单\n\n```\nlocal uri = ngx.var.uri\n-- 默认首页\nif uri == \"\" or uri == \"/\" then\n    local res = ngx.location.capture(\"/index.html\", {})\n    ngx.say(res.body)\n    return\nend\n```\n\n## url解析\n\n这里简单的把url解析成模块名模块方法，根据/分割，如果只有模块名，没有方法名，则默认为index方法\n\n```\nlocal m, err = ngx.re.match(uri, \"([a-zA-Z0-9-]+)/*([a-zA-Z0-9-]+)*\")\n\nlocal moduleName = m[1]     -- 模块名\nlocal method = m[2]         -- 方法名\n\nif not method then\n    method = \"index\"        -- 默认访问index方法\nelse\n    method = ngx.re.gsub(method, \"-\", \"_\")    \nend\n```\n\n## 动态Controller模块\n\n得到模块名之后，需要动态引入模块，通过pcall，然后再调用模块的方法\n\n```\n-- 控制器默认在web包下面\nlocal prefix = \"web.\"       \nlocal path = prefix .. moduleName\n\n-- 尝试引入模块，不存在则报错\nlocal ret, ctrl, err = pcall(require, path)\n\nlocal is_debug = true       -- 调试阶段，会输出错误信息到页面上\n\nif ret == false then\n    if is_debug then\n        ngx.status = 404\n        ngx.say(\"<p style='font-size: 50px'>Error: <span style='color:red'>\" .. ctrl .. \"</span> module not found !</p>\")\n    end\n    ngx.exit(404)\nend\n\n-- 尝试获取模块方法，不存在则报错\nlocal req_method = ctrl[method]\n\nif req_method == nil then\n    if is_debug then\n        ngx.status = 404\n        ngx.say(\"<p style='font-size: 50px'>Error: <span style='color:red'>\" .. method .. \"()</span> method not found in <span style='color:red'>\" .. moduleName .. \"</span> lua module !</p>\")\n    end\n    ngx.exit(404)\nend\n\n-- 执行模块方法，报错则显示错误信息，所见即所得，可以追踪lua报错行数\nret, err = pcall(req_method)\n\nif ret == false then\n    if is_debug then\n        ngx.status = 404\n        ngx.say(\"<p style='font-size: 50px'>Error: <span style='color:red'>\" .. err .. \"</span></p>\")\n    else\n        ngx.exit(500)\n    end\nend\n```\n\n## 异常处理\n\n可以看到，从引入模块，到获取模块方法，已经执行方法，都有可能报错，这里通过pcall来进行调用，这种方式可以安全的调用lua代码，不会导致异常中断，然后通过定义一个变量，来区分是否为开发调试阶段，如果是则把错误信息输出到浏览器端，否则直接报404或者500，避免把错误信息输出到客户端，导致代码泄漏。\n\n至此，一个简单的mvc框架已经可以使用了，但是现在还只能做前端渲染，下一章，我讲介绍如果进行服务端渲染。\n\n[示例代码](https://github.com/362228416/openresty-web-dev) 参见demo8部分\n"
  },
  {
    "path": "demo8/conf/nginx.conf",
    "content": "\nworker_processes  1;\n\nerror_log logs/error.log notice;\n\nevents {\n    worker_connections 1024;\n}\n\nhttp {\n    lua_package_path \"$prefix/lua/?.lua;$prefix/lualib/?.lua\";\n    server {\n        listen 80;\n        server_name localhost;\n        lua_code_cache off;\n\n        location / {\n        \tdefault_type \"text/html; charset=utf-8\";\n        \tcontent_by_lua_file lualib/lite/mvc.lua;\n        }\n\n        location ~ ^/js/|^/css/|\\.html {\n        \troot html;\n        }\n    }\n}\n"
  },
  {
    "path": "demo8/html/css/index.css",
    "content": ".container {\n\twidth: 1000px;\n\tmargin: 0 auto;\n\ttext-align: center;\n}"
  },
  {
    "path": "demo8/html/index.html",
    "content": "<html>\n<head>\n\t<meta charset=\"UTF-8\">\n\t<title>Home Page</title>\n\t<link rel=\"stylesheet\" href=\"/css/index.css\">\n</head>\n<body>\n\t<div class=\"container\">\n\t\t<h1>用户列表</h1>\n\t\t<p id=\"users\"></ul>\n\t\t<p>排名第二的是<span id=\"user\"></span></p>\n\t</div>\n\t<script src=\"//cdn.bootcss.com/jquery/2.2.4/jquery.min.js\"></script>\n\t<script src=\"/js/index.js\"></script>\n</body>\n</html>"
  },
  {
    "path": "demo8/html/js/index.js",
    "content": "(function(){\n\t$.getJSON('/user/', function(data){\n\t\t$('#users').html(data.join(','))\n\t\tconsole.log(data)\n\t})\n\t$.get('/user/get', {index: 2}, function(data){\n\t\t$('#user').html(data)\n\t\tconsole.log(data)\n\t})\n})()"
  },
  {
    "path": "demo8/logs/.gitignore",
    "content": "*.log\n*.pid\n"
  },
  {
    "path": "demo8/lua/web/user.lua",
    "content": "local cjson = require \"cjson\"\nlocal req = require \"lite.req\"\n\nlocal _M = {}\n\nlocal users = {\"张三\", \"李四\", \"王五\"}\n\nfunction _M.index()\n\tngx.say(cjson.encode(users))\nend\n\nfunction _M.get()\n\tlocal args = req.getArgs()\n\tlocal index = tonumber(args['index'])\n\tif not index then\n\t\tindex = 1\n\tend\n\tngx.say(users[index])\nend\n\nreturn _M"
  },
  {
    "path": "demo8/lualib/lite/mvc.lua",
    "content": "\nlocal uri = ngx.var.uri\n-- 如果是首页\nif uri == \"\" or uri == \"/\" then\n    local res = ngx.location.capture(\"/index.html\", {})\n    ngx.say(res.body)\n    return\nend\n\nlocal m, err = ngx.re.match(uri, \"([a-zA-Z0-9-]+)/*([a-zA-Z0-9-]+)*\")\n\nlocal is_debug = true       -- 调试阶段，会输出错误信息到页面上\n\nlocal moduleName = m[1]     -- 模块名\nlocal method = m[2]         -- 方法名\n\nif not method then\n    method = \"index\"        -- 默认访问index方法\nelse\n    method = ngx.re.gsub(method, \"-\", \"_\")    \nend\n\n-- 控制器默认在web包下面\nlocal prefix = \"web.\"       \nlocal path = prefix .. moduleName\n\n-- 尝试引入模块，不存在则报错\nlocal ret, ctrl, err = pcall(require, path)\n\nif ret == false then\n    if is_debug then\n        ngx.status = 404\n        ngx.say(\"<p style='font-size: 50px'>Error: <span style='color:red'>\" .. ctrl .. \"</span> module not found !</p>\")\n    end\n    ngx.exit(404)\nend\n\n-- 尝试获取模块方法，不存在则报错\nlocal req_method = ctrl[method]\n\nif req_method == nil then\n    if is_debug then\n        ngx.status = 404\n        ngx.say(\"<p style='font-size: 50px'>Error: <span style='color:red'>\" .. method .. \"()</span> method not found in <span style='color:red'>\" .. moduleName .. \"</span> lua module !</p>\")\n    end\n    ngx.exit(404)\nend\n\n-- 执行模块方法，报错则显示错误信息，所见即所得，可以追踪lua报错行数\nret, err = pcall(req_method)\n\nif ret == false then\n    if is_debug then\n        ngx.status = 404\n        ngx.say(\"<p style='font-size: 50px'>Error: <span style='color:red'>\" .. err .. \"</span></p>\")\n    else\n        ngx.exit(500)\n    end\nend\n\n\n"
  },
  {
    "path": "demo8/lualib/lite/req.lua",
    "content": "local _M = {}\n\n-- 获取http get/post 请求参数\nfunction _M.getArgs()\n    local request_method = ngx.var.request_method\n    local args = ngx.req.get_uri_args()\n    -- 参数获取\n    if \"POST\" == request_method then\n        ngx.req.read_body()\n        local postArgs = ngx.req.get_post_args()\n        if postArgs then\n            for k, v in pairs(postArgs) do\n                args[k] = v\n            end\n        end\n    end\n    return args\nend\n\nreturn _M"
  },
  {
    "path": "demo9/README.md",
    "content": "#### 这一章主要介绍怎么使用模板，进行后端渲染，主要用到了[lua-resty-template](https://github.com/bungle/lua-resty-template)这个库，直接下载下来，放到lualib里面就行了，推荐第三方库，已经框架都放到lualib目录里面，lua目录放项目源码，比较好管理，可以知道那些是项目的，哪些是第三方库，可复用的\n\n下载解压到lualib目录之后，就算安装完成了，下面来试用一下，更详细的可以到github上面看文档\n\nconf/nginx.conf\n\n```\n\nworker_processes  1;\n\nerror_log logs/error.log notice;\n\nevents {\n    worker_connections 1024;\n}\n\nhttp {\n    lua_package_path \"/Users/john/opensource/openresty-web-dev/demo9/lua/?.lua;/Users/john/opensource/openresty-web-dev/demo9/lualib/?.lua;/usr/local/openresty/lualib/?.lua\";\n    server {\n        listen 80;\n        server_name localhost;\n        lua_code_cache off;\n\n        location / {\n            root lua; # 这个很重要，不然模板文件会找不到\n            default_type \"text/html; charset=utf-8\";\n            content_by_lua_file lualib/lite/mvc.lua;\n        }\n\n        location ~ ^/js/|^/css/|\\.html {\n            root html;\n        }\n    }\n}\n```\n\nlua/index.lua\n\n```\nlocal template = require \"resty.template\"\n\nlocal _M = {}\n\nfunction _M.index()\n    local model = {title = \"hello template\", content = \"<h1>content</h1>\"}\n    -- 1、外部模板文件\n    -- template.render('tpl/index.html', model)\n    -- 2、内嵌模板代码\n    template.render([[\n<html>\n<head>\n    <meta charset=\"UTF-8\">\n    <title>{{ title }}</title>\n</head>\n<body>\n    {* content *}\n</body>\n</html>\n        ]], model)\nend\n\nreturn _M\n```\n\nlua/tpl/index.html\n\n```\n<html>\n<head>\n    <meta charset=\"UTF-8\">\n    <title>{{title}}</title>\n</head>\n<body>\n    {* content *}\n</body>\n</html>\n```\n\n跟spring mvc 有点像，指定一个 view , model，然后就可以渲染了，模板语法有很多种，{{ 变量 }} 会进行转义，{* 不会转义 *}，{% lua 代码 %}，跟jsp有点类似，但是很轻量，只有单个文件，更多用法可以到[github](https://github.com/bungle/lua-resty-template)上面看。\n\n浏览器访问 http://localhost/index ，输出content\n\n至此，服务端渲染就搞定了，已经可以开发一些常见的web应用，使用openresty来做前端，然后通过http访问后端的java，也可以在前端，直接访问mysql、redis，只不过mysql只能做一些简单的非事务操作，因为lua-resty-mysql这个库不支持事务，我在github上面问过春哥了，当然如果你直接调用存储过程，把事务放在过程里面控制的话也可以，现在你可以直接写同步的代码风格，就能获得高并发、低消耗，非堵塞等各种好处。\n\n我们已经用openresty开发了pc版，还有微信版的web应用，已经运行几个月了，很稳定，上手也简单，开发的时候不用编译，直接启动一个nginx就搞定，部署的时候只需要10几M的内存，还可以用openresty做各种事情，高并发api、web防火墙，直接跑在nginx里面，简直爽歪歪，有机会跟大家分享。\n\n[示例代码](https://github.com/362228416/openresty-web-dev) 参见demo9部分\n"
  },
  {
    "path": "demo9/conf/nginx.conf",
    "content": "\nworker_processes  1;\n\nerror_log logs/error.log notice;\n\nevents {\n    worker_connections 1024;\n}\n\nhttp {\n    lua_package_path \"$prefix/lua/?.lua;$prefix/lualib/?.lua\";\n    server {\n        listen 80;\n        server_name localhost;\n        lua_code_cache off;\n\n        location / {\n            root lua;\n        \tdefault_type \"text/html; charset=utf-8\";\n        \tcontent_by_lua_file lualib/lite/mvc.lua;\n        }\n\n        location ~ ^/js/|^/css/|\\.html {\n        \troot html;\n        }\n    }\n}\n"
  },
  {
    "path": "demo9/logs/.gitignore",
    "content": "*.log\n*.pid\n"
  },
  {
    "path": "demo9/lua/mvc.lua",
    "content": "\nlocal uri = ngx.var.uri\n-- 如果是首页\nif uri == \"\" or uri == \"/\" then\n    local res = ngx.location.capture(\"/index.html\", {})\n    ngx.say(res.body)\n    return\nend\n\nlocal m, err = ngx.re.match(uri, \"([a-zA-Z0-9-]+)/*([a-zA-Z0-9-]+)*\")\n\nlocal moduleName = m[1]     -- 模块名\nlocal method = m[2]         -- 方法名\n\nif not method then\n    method = \"index\"        -- 默认访问index方法\nelse\n    method = ngx.re.gsub(method, \"-\", \"_\")    \nend\n\n-- 控制器默认在web包下面\nlocal prefix = \"web.\"       \nlocal path = prefix .. moduleName\n\n-- 尝试引入模块，不存在则报错\nlocal ret, ctrl, err = pcall(require, path)\n\nlocal is_debug = true       -- 调试阶段，会输出错误信息到页面上\n\nif ret == false then\n    if is_debug then\n        ngx.status = 404\n        ngx.say(\"<p style='font-size: 50px'>Error: <span style='color:red'>\" .. ctrl .. \"</span> module not found !</p>\")\n    end\n    ngx.exit(404)\nend\n\n-- 尝试获取模块方法，不存在则报错\nlocal req_method = ctrl[method]\n\nif req_method == nil then\n    if is_debug then\n        ngx.status = 404\n        ngx.say(\"<p style='font-size: 50px'>Error: <span style='color:red'>\" .. method .. \"()</span> method not found in <span style='color:red'>\" .. moduleName .. \"</span> lua module !</p>\")\n    end\n    ngx.exit(404)\nend\n\n-- 执行模块方法，报错则显示错误信息，所见即所得，可以追踪lua报错行数\nret, err = pcall(req_method)\n\nif ret == false then\n    if is_debug then\n        ngx.status = 404\n        ngx.say(\"<p style='font-size: 50px'>Error: <span style='color:red'>\" .. err .. \"</span></p>\")\n    else\n        ngx.exit(500)\n    end\nend\n\n\n"
  },
  {
    "path": "demo9/lua/tpl/index.html",
    "content": "<html>\n<head>\n\t<meta charset=\"UTF-8\">\n\t<title>{{ title }}</title>\n</head>\n<body>\n\t{* content *}\n</body>\n</html>"
  },
  {
    "path": "demo9/lua/web/index.lua",
    "content": "local template = require \"resty.template\"\n\nlocal _M = {}\n\nfunction _M.index()\n\tlocal model = {title = \"hello template\", content = \"<h1>content</h1>\"}\n\ttemplate.render('tpl/index.html', model)\n-- \ttemplate.render([[\n-- <html>\n-- <head>\n-- \t<meta charset=\"UTF-8\">\n-- \t<title>{{title}}</title>\n-- </head>\n-- <body>\n-- \t{* content *}\n-- </body>\n-- </html>\n-- \t\t]], model)\nend\n\nreturn _M"
  },
  {
    "path": "demo9/lualib/lite/mvc.lua",
    "content": "\nlocal uri = ngx.var.uri\n-- 如果是首页\nif uri == \"\" or uri == \"/\" then\n    local res = ngx.location.capture(\"/index.html\", {})\n    ngx.say(res.body)\n    return\nend\n\nlocal m, err = ngx.re.match(uri, \"([a-zA-Z0-9-]+)/*([a-zA-Z0-9-]+)*\")\n\nlocal is_debug = true       -- 调试阶段，会输出错误信息到页面上\n\nlocal moduleName = m[1]     -- 模块名\nlocal method = m[2]         -- 方法名\n\nif not method then\n    method = \"index\"        -- 默认访问index方法\nelse\n    method = ngx.re.gsub(method, \"-\", \"_\")    \nend\n\n-- 控制器默认在web包下面\nlocal prefix = \"web.\"       \nlocal path = prefix .. moduleName\n\n-- 尝试引入模块，不存在则报错\nlocal ret, ctrl, err = pcall(require, path)\n\nif ret == false then\n    if is_debug then\n        ngx.status = 404\n        ngx.say(\"<p style='font-size: 50px'>Error: <span style='color:red'>\" .. ctrl .. \"</span> module not found !</p>\")\n    end\n    ngx.exit(404)\nend\n\n-- 尝试获取模块方法，不存在则报错\nlocal req_method = ctrl[method]\n\nif req_method == nil then\n    if is_debug then\n        ngx.status = 404\n        ngx.say(\"<p style='font-size: 50px'>Error: <span style='color:red'>\" .. method .. \"()</span> method not found in <span style='color:red'>\" .. moduleName .. \"</span> lua module !</p>\")\n    end\n    ngx.exit(404)\nend\n\n-- 执行模块方法，报错则显示错误信息，所见即所得，可以追踪lua报错行数\nret, err = pcall(req_method)\n\nif ret == false then\n    if is_debug then\n        ngx.status = 404\n        ngx.say(\"<p style='font-size: 50px'>Error: <span style='color:red'>\" .. err .. \"</span></p>\")\n    else\n        ngx.exit(500)\n    end\nend\n\n\n"
  },
  {
    "path": "demo9/lualib/lite/req.lua",
    "content": "local _M = {}\n\n-- 获取http get/post 请求参数\nfunction _M.getArgs()\n    local request_method = ngx.var.request_method\n    local args = ngx.req.get_uri_args()\n    -- 参数获取\n    if \"POST\" == request_method then\n        ngx.req.read_body()\n        local postArgs = ngx.req.get_post_args()\n        if postArgs then\n            for k, v in pairs(postArgs) do\n                args[k] = v\n            end\n        end\n    end\n    return args\nend\n\nreturn _M"
  },
  {
    "path": "demo9/lualib/resty/template.lua",
    "content": "local setmetatable = setmetatable\nlocal loadstring = loadstring\nlocal loadchunk\nlocal tostring = tostring\nlocal setfenv = setfenv\nlocal require = require\nlocal capture\nlocal concat = table.concat\nlocal assert = assert\nlocal prefix\nlocal write = io.write\nlocal pcall = pcall\nlocal phase\nlocal open = io.open\nlocal load = load\nlocal type = type\nlocal dump = string.dump\nlocal find = string.find\nlocal gsub = string.gsub\nlocal byte = string.byte\nlocal null\nlocal sub = string.sub\nlocal ngx = ngx\nlocal jit = jit\nlocal var\n\nlocal _VERSION = _VERSION\nlocal _ENV = _ENV\nlocal _G = _G\n\nlocal HTML_ENTITIES = {\n    [\"&\"] = \"&amp;\",\n    [\"<\"] = \"&lt;\",\n    [\">\"] = \"&gt;\",\n    ['\"'] = \"&quot;\",\n    [\"'\"] = \"&#39;\",\n    [\"/\"] = \"&#47;\"\n}\n\nlocal CODE_ENTITIES = {\n    [\"{\"] = \"&#123;\",\n    [\"}\"] = \"&#125;\",\n    [\"&\"] = \"&amp;\",\n    [\"<\"] = \"&lt;\",\n    [\">\"] = \"&gt;\",\n    ['\"'] = \"&quot;\",\n    [\"'\"] = \"&#39;\",\n    [\"/\"] = \"&#47;\"\n}\n\nlocal VAR_PHASES\n\nlocal ok, newtab = pcall(require, \"table.new\")\nif not ok then newtab = function() return {} end end\n\nlocal caching = true\nlocal template = newtab(0, 12)\n\ntemplate._VERSION = \"1.9\"\ntemplate.cache    = {}\n\nlocal function enabled(val)\n    if val == nil then return true end\n    return val == true or (val == \"1\" or val == \"true\" or val == \"on\")\nend\n\nlocal function trim(s)\n    return gsub(gsub(s, \"^%s+\", \"\"), \"%s+$\", \"\")\nend\n\nlocal function rpos(view, s)\n    while s > 0 do\n        local c = sub(view, s, s)\n        if c == \" \" or c == \"\\t\" or c == \"\\0\" or c == \"\\x0B\" then\n            s = s - 1\n        else\n            break\n        end\n    end\n    return s\nend\n\nlocal function escaped(view, s)\n    if s > 1 and sub(view, s - 1, s - 1) == \"\\\\\" then\n        if s > 2 and sub(view, s - 2, s - 2) == \"\\\\\" then\n            return false, 1\n        else\n            return true, 1\n        end\n    end\n    return false, 0\nend\n\nlocal function readfile(path)\n    local file = open(path, \"rb\")\n    if not file then return nil end\n    local content = file:read \"*a\"\n    file:close()\n    return content\nend\n\nlocal function loadlua(path)\n    return readfile(path) or path\nend\n\nlocal function loadngx(path)\n    local vars = VAR_PHASES[phase()]\n    local file, location = path, vars and var.template_location\n    if sub(file, 1)  == \"/\" then file = sub(file, 2) end\n    if location and location ~= \"\" then\n        if sub(location, -1) == \"/\" then location = sub(location, 1, -2) end\n        local res = capture(concat{ location, '/', file})\n        if res.status == 200 then return res.body end\n    end\n    local root = vars and (var.template_root or var.document_root) or prefix\n    if sub(root, -1) == \"/\" then root = sub(root, 1, -2) end\n    return readfile(concat{ root, \"/\", file }) or path\nend\n\ndo\n    if ngx then\n        VAR_PHASES = {\n            set           = true,\n            rewrite       = true,\n            access        = true,\n            content       = true,\n            header_filter = true,\n            body_filter   = true,\n            log           = true\n        }\n        template.print = ngx.print or write\n        template.load  = loadngx\n        prefix, var, capture, null, phase = ngx.config.prefix(), ngx.var, ngx.location.capture, ngx.null, ngx.get_phase\n        if VAR_PHASES[phase()] then\n            caching = enabled(var.template_cache)\n        end\n    else\n        template.print = write\n        template.load  = loadlua\n    end\n    if _VERSION == \"Lua 5.1\" then\n        local context = { __index = function(t, k)\n            return t.context[k] or t.template[k] or _G[k]\n        end }\n        if jit then\n            loadchunk = function(view)\n                return assert(load(view, nil, nil, setmetatable({ template = template }, context)))\n            end\n        else\n            loadchunk = function(view)\n                local func = assert(loadstring(view))\n                setfenv(func, setmetatable({ template = template }, context))\n                return func\n            end\n        end\n    else\n        local context = { __index = function(t, k)\n            return t.context[k] or t.template[k] or _ENV[k]\n        end }\n        loadchunk = function(view)\n            return assert(load(view, nil, nil, setmetatable({ template = template }, context)))\n        end\n    end\nend\n\nfunction template.caching(enable)\n    if enable ~= nil then caching = enable == true end\n    return caching\nend\n\nfunction template.output(s)\n    if s == nil or s == null then return \"\" end\n    if type(s) == \"function\" then return template.output(s()) end\n    return tostring(s)\nend\n\nfunction template.escape(s, c)\n    if type(s) == \"string\" then\n        if c then return gsub(s, \"[}{\\\">/<'&]\", CODE_ENTITIES) end\n        return gsub(s, \"[\\\">/<'&]\", HTML_ENTITIES)\n    end\n    return template.output(s)\nend\n\nfunction template.new(view, layout)\n    assert(view, \"view was not provided for template.new(view, layout).\")\n    local render, compile = template.render, template.compile\n    if layout then\n        if type(layout) == \"table\" then\n            return setmetatable({ render = function(self, context)\n                local context = context or self\n                context.blocks = context.blocks or {}\n                context.view = compile(view)(context)\n                layout.blocks = context.blocks or {}\n                layout.view = context.view or \"\"\n                return layout:render()\n            end }, { __tostring = function(self)\n                local context = self\n                context.blocks = context.blocks or {}\n                context.view = compile(view)(context)\n                layout.blocks = context.blocks or {}\n                layout.view = context.view\n                return tostring(layout)\n            end })\n        else\n            return setmetatable({ render = function(self, context)\n                local context = context or self\n                context.blocks = context.blocks or {}\n                context.view = compile(view)(context)\n                return render(layout, context)\n            end }, { __tostring = function(self)\n                local context = self\n                context.blocks = context.blocks or {}\n                context.view = compile(view)(context)\n                return compile(layout)(context)\n            end })\n        end\n    end\n    return setmetatable({ render = function(self, context)\n        return render(view, context or self)\n    end }, { __tostring = function(self)\n        return compile(view)(self)\n    end })\nend\n\nfunction template.precompile(view, path, strip)\n    local chunk = dump(template.compile(view), strip ~= false)\n    if path then\n        local file = open(path, \"wb\")\n        file:write(chunk)\n        file:close()\n    end\n    return chunk\nend\n\nfunction template.compile(view, key, plain)\n    assert(view, \"view was not provided for template.compile(view, key, plain).\")\n    if key == \"no-cache\" then\n        return loadchunk(template.parse(view, plain)), false\n    end\n    key = key or view\n    local cache = template.cache\n    if cache[key] then return cache[key], true end\n    local func = loadchunk(template.parse(view, plain))\n    if caching then cache[key] = func end\n    return func, false\nend\n\nfunction template.parse(view, plain)\n    assert(view, \"view was not provided for template.parse(view, plain).\")\n    if not plain then\n        view = template.load(view)\n        if byte(sub(view, 1, 1)) == 27 then return view end\n    end\n    local j = 2\n    local c = {[[\ncontext=... or {}\nlocal function include(v, c) return template.compile(v)(c or context) end\nlocal ___,blocks,layout={},blocks or {}\n]] }\n    local i, s = 1, find(view, \"{\", 1, true)\n    while s do\n        local t, p = sub(view, s + 1, s + 1), s + 2\n        if t == \"{\" then\n            local e = find(view, \"}}\", p, true)\n            if e then\n                local z, w = escaped(view, s)\n                if i < s - w then\n                    c[j] = \"___[#___+1]=[=[\\n\"\n                    c[j+1] = sub(view, i, s - 1 - w)\n                    c[j+2] = \"]=]\\n\"\n                    j=j+3\n                end\n                if z then\n                    i = s\n                else\n                    c[j] = \"___[#___+1]=template.escape(\"\n                    c[j+1] = trim(sub(view, p, e - 1))\n                    c[j+2] = \")\\n\"\n                    j=j+3\n                    s, i = e + 1, e + 2\n                end\n            end\n        elseif t == \"*\" then\n            local e = find(view, \"*}\", p, true)\n            if e then\n                local z, w = escaped(view, s)\n                if i < s - w then\n                    c[j] = \"___[#___+1]=[=[\\n\"\n                    c[j+1] = sub(view, i, s - 1 - w)\n                    c[j+2] = \"]=]\\n\"\n                    j=j+3\n                end\n                if z then\n                    i = s\n                else\n                    c[j] = \"___[#___+1]=template.output(\"\n                    c[j+1] = trim(sub(view, p, e - 1))\n                    c[j+2] = \")\\n\"\n                    j=j+3\n                    s, i = e + 1, e + 2\n                end\n            end\n        elseif t == \"%\" then\n            local e = find(view, \"%}\", p, true)\n            if e then\n                local z, w = escaped(view, s)\n                if z then\n                    if i < s - w then\n                        c[j] = \"___[#___+1]=[=[\\n\"\n                        c[j+1] = sub(view, i, s - 1 - w)\n                        c[j+2] = \"]=]\\n\"\n                        j=j+3\n                    end\n                    i = s\n                else\n                    local n = e + 2\n                    if sub(view, n, n) == \"\\n\" then\n                        n = n + 1\n                    end\n                    local r = rpos(view, s - 1)\n                    if i <= r then\n                        c[j] = \"___[#___+1]=[=[\\n\"\n                        c[j+1] = sub(view, i, r)\n                        c[j+2] = \"]=]\\n\"\n                        j=j+3\n                    end\n                    c[j] = trim(sub(view, p, e - 1))\n                    c[j+1] = \"\\n\"\n                    j=j+2\n                    s, i = n - 1, n\n                end\n            end\n        elseif t == \"(\" then\n            local e = find(view, \")}\", p, true)\n            if e then\n                local z, w = escaped(view, s)\n                if i < s - w then\n                    c[j] = \"___[#___+1]=[=[\\n\"\n                    c[j+1] = sub(view, i, s - 1 - w)\n                    c[j+2] = \"]=]\\n\"\n                    j=j+3\n                end\n                if z then\n                    i = s\n                else\n                    local f = sub(view, p, e - 1)\n                    local x = find(f, \",\", 2, true)\n                    if x then\n                        c[j] = \"___[#___+1]=include([=[\"\n                        c[j+1] = trim(sub(f, 1, x - 1))\n                        c[j+2] = \"]=],\"\n                        c[j+3] = trim(sub(f, x + 1))\n                        c[j+4] = \")\\n\"\n                        j=j+5\n                    else\n                        c[j] = \"___[#___+1]=include([=[\"\n                        c[j+1] = trim(f)\n                        c[j+2] = \"]=])\\n\"\n                        j=j+3\n                    end\n                    s, i = e + 1, e + 2\n                end\n            end\n        elseif t == \"[\" then\n            local e = find(view, \"]}\", p, true)\n            if e then\n                local z, w = escaped(view, s)\n                if i < s - w then\n                    c[j] = \"___[#___+1]=[=[\\n\"\n                    c[j+1] = sub(view, i, s - 1 - w)\n                    c[j+2] = \"]=]\\n\"\n                    j=j+3\n                end\n                if z then\n                    i = s\n                else\n                    c[j] = \"___[#___+1]=include(\"\n                    c[j+1] = trim(sub(view, p, e - 1))\n                    c[j+2] = \")\\n\"\n                    j=j+3\n                    s, i = e + 1, e + 2\n                end\n            end\n        elseif t == \"-\" then\n            local e = find(view, \"-}\", p, true)\n            if e then\n                local x, y = find(view, sub(view, s, e + 1), e + 2, true)\n                if x then\n                    local z, w = escaped(view, s)\n                    if z then\n                        if i < s - w then\n                            c[j] = \"___[#___+1]=[=[\\n\"\n                            c[j+1] = sub(view, i, s - 1 - w)\n                            c[j+2] = \"]=]\\n\"\n                            j=j+3\n                        end\n                        i = s\n                    else\n                        y = y + 1\n                        x = x - 1\n                        if sub(view, y, y) == \"\\n\" then\n                            y = y + 1\n                        end\n                        local b = trim(sub(view, p, e - 1))\n                        if b == \"verbatim\" or b == \"raw\" then\n                            if i < s - w then\n                                c[j] = \"___[#___+1]=[=[\\n\"\n                                c[j+1] = sub(view, i, s - 1 - w)\n                                c[j+2] = \"]=]\\n\"\n                                j=j+3\n                            end\n                            c[j] = \"___[#___+1]=[=[\"\n                            c[j+1] = sub(view, e + 2, x)\n                            c[j+2] = \"]=]\\n\"\n                            j=j+3\n                        else\n                            if sub(view, x, x) == \"\\n\" then\n                                x = x - 1\n                            end\n                            local r = rpos(view, s - 1)\n                            if i <= r then\n                                c[j] = \"___[#___+1]=[=[\\n\"\n                                c[j+1] = sub(view, i, r)\n                                c[j+2] = \"]=]\\n\"\n                                j=j+3\n                            end\n                            c[j] = 'blocks[\"'\n                            c[j+1] = b\n                            c[j+2] = '\"]=include[=['\n                            c[j+3] = sub(view, e + 2, x)\n                            c[j+4] = \"]=]\\n\"\n                            j=j+5\n                        end\n                        s, i = y - 1, y\n                    end\n                end\n            end\n        elseif t == \"#\" then\n            local e = find(view, \"#}\", p, true)\n            if e then\n                local z, w = escaped(view, s)\n                if i < s - w then\n                    c[j] = \"___[#___+1]=[=[\\n\"\n                    c[j+1] = sub(view, i, s - 1 - w)\n                    c[j+2] = \"]=]\\n\"\n                    j=j+3\n                end\n                if z then\n                    i = s\n                else\n                    e = e + 2\n                    if sub(view, e, e) == \"\\n\" then\n                        e = e + 1\n                    end\n                    s, i = e - 1, e\n                end\n            end\n        end\n        s = find(view, \"{\", s + 1, true)\n    end\n    s = sub(view, i)\n    if s and s ~= \"\" then\n        c[j] = \"___[#___+1]=[=[\\n\"\n        c[j+1] = s\n        c[j+2] = \"]=]\\n\"\n        j=j+3\n    end\n    c[j] = \"return layout and include(layout,setmetatable({view=table.concat(___),blocks=blocks},{__index=context})) or table.concat(___)\"\n    return concat(c)\nend\n\nfunction template.render(view, context, key, plain)\n    assert(view, \"view was not provided for template.render(view, context, key, plain).\")\n    return template.print(template.compile(view, key, plain)(context))\nend\n\nreturn template\n"
  },
  {
    "path": "install/install-openresty.sh",
    "content": "#!/bin/bash\nsudo apt-get update\nsudo apt-get install -y libpcre3 libpcre3-dev\nsudo apt-get install -y openssl libssl-dev\nsudo apt-get install -y openssl zlib1g-dev\ncd /tmp\nversion=\"1.21.4.1\"\nname=openresty-$version\nfile=$name.tar.gz\nprefix=/usr/local/openresty\n#echo $file\nwget https://openresty.org/download/$file\ntar zxvf $file\ncd $name\n./configure --with-http_stub_status_module --with-http_realip_module\nmake -j2\nsudo make install\nsudo ln -s $prefix/nginx/sbin/nginx /usr/local/sbin/openresty\n"
  }
]