Full Code of 362228416/openresty-web-dev for AI

master 380cdb8f7f86 cached
115 files
207.1 KB
63.2k tokens
1 symbols
1 requests
Download .txt
Showing preview only (252K chars total). Download the full file or copy to clipboard to get everything.
Repository: 362228416/openresty-web-dev
Branch: master
Commit: 380cdb8f7f86
Files: 115
Total size: 207.1 KB

Directory structure:
gitextract_zad3cxq9/

├── .gitignore
├── README.md
├── demo1/
│   ├── README.md
│   ├── conf/
│   │   └── nginx.conf
│   ├── logs/
│   │   └── .gitignore
│   └── lua/
│       ├── hello.lua
│       └── welcome.lua
├── demo10/
│   ├── README.md
│   ├── conf/
│   │   └── nginx.conf
│   ├── html/
│   │   └── index.html
│   ├── logs/
│   │   └── .gitignore
│   ├── lua/
│   │   ├── rewrite.lua
│   │   └── web/
│   │       ├── article.lua
│   │       └── index.lua
│   └── lualib/
│       ├── lite/
│       │   ├── mvc.lua
│       │   └── req.lua
│       └── resty/
│           └── template.lua
├── demo11/
│   ├── README.md
│   ├── conf/
│   │   └── nginx.conf
│   ├── html/
│   │   └── index.html
│   ├── logs/
│   │   └── .gitignore
│   ├── lua/
│   │   ├── rewrite.lua
│   │   ├── waf.lua
│   │   └── web/
│   │       └── index.lua
│   └── lualib/
│       ├── lite/
│       │   ├── mvc.lua
│       │   └── req.lua
│       └── resty/
│           └── template.lua
├── demo12/
│   ├── README.md
│   ├── conf/
│   │   └── nginx.conf
│   ├── logs/
│   │   └── .gitignore
│   ├── lua/
│   │   ├── tpl/
│   │   │   └── index.html
│   │   └── web/
│   │       └── index.lua
│   └── lualib/
│       ├── lite/
│       │   ├── mvc.lua
│       │   └── req.lua
│       └── resty/
│           ├── cookie.lua
│           └── template.lua
├── demo13/
│   ├── README.md
│   ├── conf/
│   │   ├── GeoTrust_Global_CA.pem
│   │   └── nginx.conf
│   ├── html/
│   │   └── index.html
│   ├── logs/
│   │   └── .gitignore
│   ├── lua/
│   │   ├── req.lua
│   │   └── test.lua
│   └── lualib/
│       └── resty/
│           ├── http.lua
│           └── http_headers.lua
├── demo14/
│   ├── README.md
│   ├── conf/
│   │   └── nginx.conf
│   └── logs/
│       └── .gitignore
├── demo15/
│   └── README.md
├── demo16/
│   ├── README.md
│   ├── conf/
│   │   └── nginx.conf
│   ├── html/
│   │   ├── index.html
│   │   └── js/
│   │       └── index.js
│   ├── logs/
│   │   └── .gitignore
│   ├── lua/
│   │   ├── global_config.lua
│   │   ├── utils.lua
│   │   └── web/
│   │       ├── mvc.lua
│   │       └── template.lua
│   └── lualib/
│       └── resty/
│           └── template.lua
├── demo2/
│   ├── README.md
│   ├── conf/
│   │   └── nginx.conf
│   ├── logs/
│   │   └── .gitignore
│   └── lua/
│       ├── hello.lua
│       └── req.lua
├── demo3/
│   ├── README.md
│   ├── conf/
│   │   └── nginx.conf
│   ├── logs/
│   │   └── .gitignore
│   └── lua/
│       ├── hello.lua
│       └── req.lua
├── demo4/
│   ├── README.md
│   ├── conf/
│   │   └── nginx.conf
│   ├── logs/
│   │   └── .gitignore
│   └── lua/
│       ├── hello.lua
│       ├── redis.lua
│       └── req.lua
├── demo5/
│   ├── README.md
│   ├── conf/
│   │   └── nginx.conf
│   ├── logs/
│   │   └── .gitignore
│   └── lua/
│       ├── hello.lua
│       ├── mysql.lua
│       └── req.lua
├── demo6/
│   ├── README.md
│   ├── conf/
│   │   └── nginx.conf
│   ├── logs/
│   │   └── .gitignore
│   └── lua/
│       ├── hello.lua
│       └── req.lua
├── demo7/
│   ├── README.md
│   ├── conf/
│   │   └── nginx.conf
│   ├── html/
│   │   └── index.html
│   ├── logs/
│   │   └── .gitignore
│   ├── lua/
│   │   ├── hello.lua
│   │   ├── local-login.lua
│   │   ├── login.lua
│   │   └── req.lua
│   └── lualib/
│       └── resty/
│           ├── http.lua
│           └── http_headers.lua
├── demo8/
│   ├── README.md
│   ├── conf/
│   │   └── nginx.conf
│   ├── html/
│   │   ├── css/
│   │   │   └── index.css
│   │   ├── index.html
│   │   └── js/
│   │       └── index.js
│   ├── logs/
│   │   └── .gitignore
│   ├── lua/
│   │   └── web/
│   │       └── user.lua
│   └── lualib/
│       └── lite/
│           ├── mvc.lua
│           └── req.lua
├── demo9/
│   ├── README.md
│   ├── conf/
│   │   └── nginx.conf
│   ├── logs/
│   │   └── .gitignore
│   ├── lua/
│   │   ├── mvc.lua
│   │   ├── tpl/
│   │   │   └── index.html
│   │   └── web/
│   │       └── index.lua
│   └── lualib/
│       ├── lite/
│       │   ├── mvc.lua
│       │   └── req.lua
│       └── resty/
│           └── template.lua
└── install/
    └── install-openresty.sh

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
.DS_Store


================================================
FILE: README.md
================================================
# openresty-web-dev

本项目是我写的一系列openresty web 前端开发文章的实例demo,方便测试运行,喜欢请点↑↑↑右上角Star↑↑↑
### 文章目录

[openresty 前端开发序](http://blog.csdn.net/qq362228416/article/details/53537103)

[openresty 前端开发入门一](https://github.com/362228416/openresty-web-dev/tree/master/demo1)

[openresty 前端开发入门二](https://github.com/362228416/openresty-web-dev/tree/master/demo2)

[openresty 前端开发入门三之JSON篇](https://github.com/362228416/openresty-web-dev/tree/master/demo3)

[openresty 前端开发入门四之Redis篇](https://github.com/362228416/openresty-web-dev/tree/master/demo4)

[openresty 前端开发入门五之Mysql篇](https://github.com/362228416/openresty-web-dev/tree/master/demo5)

[openresty 前端开发入门六之调试篇](https://github.com/362228416/openresty-web-dev/tree/master/demo6)

[openresty 前端开发轻量级MVC框架封装一(控制器篇)](https://github.com/362228416/openresty-web-dev/tree/master/demo8)

[openresty 前端开发轻量级MVC框架封装二(渲染篇)](https://github.com/362228416/openresty-web-dev/tree/master/demo9)

[openresty 前端开发进阶一之http后端](https://github.com/362228416/openresty-web-dev/tree/master/demo7)

[openresty 前端开发进阶二之https后端](https://github.com/362228416/openresty-web-dev/tree/master/demo13)

openresty 前端开发进阶三之后端整合二

openresty 前端开发进阶四之session篇

[openresty 前端开发进阶五之cookie篇](https://github.com/362228416/openresty-web-dev/tree/master/demo12)

openresty 前端开发进阶六之websocket篇

openresty 前端开发高级应用一之性能优化

openresty 前端开发高级应用一之动态追踪技术

openresty 简单应用

openresty 灰度发布

[openresty lua单一入口实现](https://github.com/362228416/openresty-web-dev/tree/master/demo16)

[openresty 用lua实现负载均衡](https://github.com/362228416/openresty-web-dev/tree/master/demo15)

[openresty 定时任务](https://github.com/362228416/openresty-web-dev/tree/master/demo14)

[openresty 应用打包并使用luajit编译lua代码实现简单加密](https://my.oschina.net/362228416/blog/846741)

[openresty 整合阿里云oss](https://github.com/362228416/lua-resty-oss)

[openresty url重写](https://github.com/362228416/openresty-web-dev/tree/master/demo10)

[openresty IP防火墙](https://github.com/362228416/openresty-web-dev/tree/master/demo11)

[openresty 之 nginx-api-for-lua](https://github.com/openresty/lua-nginx-module#nginx-api-for-lua) ngx所有API都在这里,最好的文档

... 持续更新待续

测试运行

```
$ git checkout https://github.com/362228416/openresty-web-dev.git
$ cd openresty-web-dev
$ openresty -p demo1 # 这里按实际情况来选择一个demo运行
```

如果你觉得不错,并在项目中使用到它,还可以请我喝杯咖啡☕️

![微信二维码](https://github.com/362228416/openresty-web-dev/blob/master/wxpay.png?raw=true)


================================================
FILE: demo1/README.md
================================================
OpenResty ™ 是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。

OpenResty 通过汇聚各种设计精良的 Nginx 模块(主要由 OpenResty 团队自主开发),从而将 Nginx 有效地变成一个强大的通用 Web 应用平台。这样,Web 开发人员和系统工程师可以使用 Lua 脚本语言调动 Nginx 支持的各种 C 以及 Lua 模块,快速构造出足以胜任 10K 乃至 1000K 以上单机并发连接的高性能 Web 应用系统。

OpenResty 的目标是让你的Web服务直接跑在 Nginx 服务内部,充分利用 Nginx 的非阻塞 I/O 模型,不仅仅对 HTTP 客户端请求,甚至于对远程后端诸如 MySQL、PostgreSQL、Memcached 以及 Redis 等都进行一致的高性能响应。

以上是从官网拷过来的原话,我们通过写一个hello world,来走进openresty开发之旅

**下载地址**
http://openresty.org/cn/download.html

有的人不会下windows版,所以我这里直接给出下载地址,现在是最新版本,学会了之后,可以自己下载

mac、linux 平台
https://openresty.org/download/openresty-1.11.2.2.tar.gz

windows平台
https://openresty.org/download/openresty-1.11.2.2-win32.zip

**关于安装**
mac、linux安装看这里 http://openresty.org/cn/installation.html
windows 直接之后直接启动就可以了,不用安装

安装完之后别着急启动

**开始写代码了**

打开nginx目录下的conf/nginx.conf文件

在server中新增以下代码
```
location /hello {
    default_type text/html;
    content_by_lua '
        ngx.say("<p>hello, world</p>")
    ';
}
```

类似这样
```
http {
    server {
        listen 80;
	    server_name localhost;
        location / {
            default_type text/html;
            content_by_lua '
                ngx.say("<p>hello, world</p>")
            ';
        }
    }
}
```

现在启动nginx,然后访问 http://localhost/hello,不出意外的话应该就OK了,如果你之前启动了,那么需要reload一下,nginx的基本操作这里就不介绍了

通过**ngx.say** 我们可以往客户端输出响应文本,在整个request周期内可以多次调用,接受的参数是字符串,如果输出table会报错

还有一个输出的函数是**ngx.print**,同样也是输出响应内容

这里有一个**坑**,就是调用ngx.say会在输出我们的内容之后会额外输出一个换行,但是ngx.print就不会,我之前一个同事用lua输出了一个200,然后前端用ajax调用,判断是否200,死活是false,看输出的内容就是200,差点怀疑人生,幸亏我比较机智,直接查看ajax请求源码,发现行号多了一行,就是那个换行,如果不仔细根本看不出来,这个坑被我一个同事踩了

上面的代码直接把lua代码写到nginx配置里面了,维护起来不是很方便,而且写代码的时候也没有语法高亮,提示这些,比较蛋疼,我们把它拿出来一个单独的文件,并放到一个nginx下面单独的lua目录下,方便管理

lua/hello.lua
```
ngx.say("<p>hello, world</p>")
```

nginx.conf 改成这样
```
location / {
     default_type text/html;
     content_by_lua_file lua/hello.lua;
 }
```

然后nginx reload 一下,再看效果,应该是一样的

我们修改一下hello.lua,在hello,world后面加一个!号,刷新浏览器发现没有任何变化,这是因为lua代码被缓存了,这就导致我们修改代码,就必须reload nginx 在能看到效果,如果是这样,那简直要疯了,其实要解决这个问题很简单,只要在nginx.conf里面把lua缓存给禁止掉就行了,当然在生产线上一定要把缓存打开,不然效果大打折扣

禁止lua缓存
```
server {
   listen 80;
   server_name localhost;
   lua_code_cache off; # 这个可以放在server下面,也可以凡在location下面,作用的范围也不一样,为了简单直接放这里了
   location / {
       default_type text/html;
       content_by_lua_file lua/hello.lua;
   }
}
```
改完之后reload一下nginx,这里**重点声明**一下修改nginx配置必须要reload,否则是没有效果的

现在我们再改hello.lua,然后刷新浏览器就会发现可以实时生效了

观察以上代码其实还会发现一个问题,如果我们想要处理很多个请求,那不是要在nginx里面配置N个location吗,我们肯定不会这么做,这里可以通过nginx正在匹配动态指定lua文件名,即可完成我们的需求,后台我再介绍如何打造一个属于我们的mvc轻量级框架,这里我们先这么做

location 改成这样
```
location ~ /lua/(.+) {
	 content_by_lua_file lua/$1.lua;
}
```

reload nginx

这个时候访问hello world的请求url就变成了
http://localhost/lua/hello 了
同理,我们在lua文件里面创建一个welcome.lua的话,就可以通过
http://localhost/lua/welcome 来访问了
以此类推,我们就可以通过新增多个文件来处理不同的请求了,而且修改了还能实时生效,剩下的就是完成业务代码了,比如调一下redis返回数据,或者mysql之类的,有悟性的同学在这里已经可以做很多事情了

[示例代码](https://github.com/362228416/openresty-web-dev)  参见demo1部分


================================================
FILE: demo1/conf/nginx.conf
================================================

worker_processes  1;

error_log logs/error.log;

events {
    worker_connections 1024;
}

http {
    server {
        listen 80;
        server_name localhost;
        lua_code_cache off;
        location ~ /lua/(.+) {
        	default_type text/html;	
		    content_by_lua_file lua/$1.lua;
		}
    }
}

================================================
FILE: demo1/logs/.gitignore
================================================
*.log
*.pid


================================================
FILE: demo1/lua/hello.lua
================================================
ngx.say("<p>hello, world!</p>")

================================================
FILE: demo1/lua/welcome.lua
================================================
ngx.say("welcome to openresty world!!")

================================================
FILE: demo10/README.md
================================================
#### 这一章主要介绍一下怎么用lua来进行url重写,其实通过nginx也可以完成url重写,但是重写规则比较复杂的时候,用nginx就没有那么方便了,用lua可以轻松搞定

这里用到几个最核心的api就是`ngx.redirect`、`ngx.exec`

# ngx.redirect

顾名思义,是执行重定向动作,重定向会导致url变更,返回302状态码,浏览器会重新发起一个新请求,到重定向后的url,用法很简单

```

old uri

/index/article?id=10000

ngx.redirect('/article' .. ngx.var.is_args .. ngx.var.args)

new uri

/article?id=10000

```

# ngx.exec

直接在内部完成请求,并且直接返回内容,url不会变化,用法跟上面差不多

```

old uri

/index/article?id=10000

ngx.exec('/article' .. ngx.var.is_args .. ngx.var.args)

new uri

/index/article?id=10000

```

为了使得url重写统一写在一个地方,便于维护,我们可以拓展一下之前封装的mvc框架

加上这么一段代码

lite/mvc.lua

```
-- url 重写 begin

local ret, rewrite = pcall(require, "rewrite") -- 安全引入rewrite模块,假如没有也不会报错

if ret then
    local c_ret, r_ret = pcall(rewrite.exec, uri)
    -- c_ret 表示执行成功,r_ret 表示已重定向,两者都为true,则表示重写成功,则不继续往下执行
    if c_ret and r_ret then
        return
    end
end

-- url 重写end
```

然后在lua目录新增一个rewrite.lua文件,内容如下

rewrite.lua

```

local _M = {}

function _M.exec(uri)
    local rewrite_urls = {}

    local queryString = ngx.var.args
    if queryString == nil then queryString = "" end

    rewrite_urls['/index/article'] = '/article?' .. queryString

    local match_url = rewrite_urls[uri]

    if match_url then
        -- ngx.redirect(match_url) -- url 变化
        ngx.exec(match_url)        -- url 无变化
        return true
    end
    return false
end

return _M

```

url重新不限于当期站点,可以跨域名,比如一些很常见的场景,电脑端网页在手机端访问的时候可以调整到另外一个域名,或者页面,更好的在移动端显示,例如

```
local agent = ngx.var.http_user_agent
if agent ~= nil then
local m, ret = ngx.re.match(agent, "Android|webOS|iPhone|iPod|BlackBerry")
if m ~= nil then
    -- rewrite ... 同上,只不过外层多了一层判断,判断设备
end
```

[示例代码](https://github.com/362228416/openresty-web-dev/tree/master/demo10) 参见demo10部分


================================================
FILE: demo10/conf/nginx.conf
================================================

worker_processes  1;

error_log logs/error.log notice;

events {
    worker_connections 1024;
}

http {
    lua_package_path "$prefix/lua/?.lua;$prefix/lualib/?.lua";
    server {
        listen 80;
        server_name localhost;
        lua_code_cache off;

        location / {
            root lua;
        	default_type "text/html; charset=utf-8";
        	content_by_lua_file lualib/lite/mvc.lua;
        }

        location ~ ^/js/|^/css/|\.html {
        	root html;
        }
    }
}


================================================
FILE: demo10/html/index.html
================================================
<html>
<head>
	<meta charset="UTF-8">
	<title>demo10</title>
</head>
<body>
	<a href="/index/article?id=10000">URL重写</a>
</body>
</html>


================================================
FILE: demo10/logs/.gitignore
================================================
*.log
*.pid


================================================
FILE: demo10/lua/rewrite.lua
================================================

local _M = {}

function _M.exec1(uri)       -- 正常版
    local rewrite_urls = {}
    local queryString = ngx.var.args
    if queryString == nil then queryString = "" end
    rewrite_urls['/index/article'] = '/article?' .. queryString
    local match_url = rewrite_urls[uri]
    if match_url then
        -- ngx.redirect(match_url) -- url 变化
        ngx.exec(match_url)        -- url 无变化
        return true
    end
    return false
end

function _M.exec(uri)      -- 移动端增强版
    local agent = ngx.var.http_user_agent
    if agent ~= nil then
        local m, ret = ngx.re.match(agent, "Android|webOS|iPhone|iPod|BlackBerry")
        if m ~= nil then
            local rewrite_urls = {}
            local queryString = ngx.var.args
            if queryString == nil then queryString = "" end
            rewrite_urls['/index/article'] = '/article?' .. queryString
            local match_url = rewrite_urls[uri]
            if match_url then
                -- ngx.redirect(match_url) -- url 变化
                ngx.exec(match_url)        -- url 无变化
                return true
            end
        end
    end
    return false
end

return _M


================================================
FILE: demo10/lua/web/article.lua
================================================
local template = require "resty.template"
local req = require "lite.req"

local _M = {}

function _M.index()
	local args = req.getArgs()
	local id = args['id'] or 10001
	ngx.say('new article page ' .. id)
end

return _M


================================================
FILE: demo10/lua/web/index.lua
================================================
local req = require "lite.req"

local _M = {}

function _M.article()
	local args = req.getArgs()
	local id = args['id'] or 10001
	ngx.say('old article page ' .. id)
end

return _M


================================================
FILE: demo10/lualib/lite/mvc.lua
================================================

local uri = ngx.var.uri
-- 如果是首页
if uri == "" or uri == "/" then
    local res = ngx.location.capture("/index.html", {})
    ngx.say(res.body)
    return
end

-- url 重写 begin

local ret, rewrite = pcall(require, "rewrite") -- 安全引入rewrite模块,假如没有也不会报错

if ret then
    local c_ret, r_ret = pcall(rewrite.exec, uri)
    ngx.say(r_ret)
    -- c_ret 表示执行成功,r_ret 表示已重定向,两者都为true,则表示重写成功,则不继续往下执行
    if c_ret and r_ret then
        return
    end
end

-- url 重写end

local m, err = ngx.re.match(uri, "([a-zA-Z0-9-]+)/*([a-zA-Z0-9-]+)*")

local is_debug = true       -- 调试阶段,会输出错误信息到页面上

local moduleName = m[1]     -- 模块名
local method = m[2]         -- 方法名

if not method then
    method = "index"        -- 默认访问index方法
else
    method = ngx.re.gsub(method, "-", "_")
end

-- 控制器默认在web包下面
local prefix = "web."
local path = prefix .. moduleName

-- 尝试引入模块,不存在则报错
local ret, ctrl, err = pcall(require, path)

if ret == false then
    if is_debug then
        ngx.status = 404
        ngx.say("<p style='font-size: 50px'>Error: <span style='color:red'>" .. ctrl .. "</span> module not found !</p>")
    end
    ngx.exit(404)
end

-- 尝试获取模块方法,不存在则报错
local req_method = ctrl[method]

if req_method == nil then
    if is_debug then
        ngx.status = 404
        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>")
    end
    ngx.exit(404)
end

-- 执行模块方法,报错则显示错误信息,所见即所得,可以追踪lua报错行数
ret, err = pcall(req_method)

if ret == false then
    if is_debug then
        ngx.status = 404
        ngx.say("<p style='font-size: 50px'>Error: <span style='color:red'>" .. err .. "</span></p>")
    else
        ngx.exit(500)
    end
end


================================================
FILE: demo10/lualib/lite/req.lua
================================================
local _M = {}

-- 获取http get/post 请求参数
function _M.getArgs()
    local request_method = ngx.var.request_method
    local args = ngx.req.get_uri_args()
    -- 参数获取
    if "POST" == request_method then
        ngx.req.read_body()
        local postArgs = ngx.req.get_post_args()
        if postArgs then
            for k, v in pairs(postArgs) do
                args[k] = v
            end
        end
    end
    return args
end

return _M

================================================
FILE: demo10/lualib/resty/template.lua
================================================
local setmetatable = setmetatable
local loadstring = loadstring
local loadchunk
local tostring = tostring
local setfenv = setfenv
local require = require
local capture
local concat = table.concat
local assert = assert
local prefix
local write = io.write
local pcall = pcall
local phase
local open = io.open
local load = load
local type = type
local dump = string.dump
local find = string.find
local gsub = string.gsub
local byte = string.byte
local null
local sub = string.sub
local ngx = ngx
local jit = jit
local var

local _VERSION = _VERSION
local _ENV = _ENV
local _G = _G

local HTML_ENTITIES = {
    ["&"] = "&amp;",
    ["<"] = "&lt;",
    [">"] = "&gt;",
    ['"'] = "&quot;",
    ["'"] = "&#39;",
    ["/"] = "&#47;"
}

local CODE_ENTITIES = {
    ["{"] = "&#123;",
    ["}"] = "&#125;",
    ["&"] = "&amp;",
    ["<"] = "&lt;",
    [">"] = "&gt;",
    ['"'] = "&quot;",
    ["'"] = "&#39;",
    ["/"] = "&#47;"
}

local VAR_PHASES

local ok, newtab = pcall(require, "table.new")
if not ok then newtab = function() return {} end end

local caching = true
local template = newtab(0, 12)

template._VERSION = "1.9"
template.cache    = {}

local function enabled(val)
    if val == nil then return true end
    return val == true or (val == "1" or val == "true" or val == "on")
end

local function trim(s)
    return gsub(gsub(s, "^%s+", ""), "%s+$", "")
end

local function rpos(view, s)
    while s > 0 do
        local c = sub(view, s, s)
        if c == " " or c == "\t" or c == "\0" or c == "\x0B" then
            s = s - 1
        else
            break
        end
    end
    return s
end

local function escaped(view, s)
    if s > 1 and sub(view, s - 1, s - 1) == "\\" then
        if s > 2 and sub(view, s - 2, s - 2) == "\\" then
            return false, 1
        else
            return true, 1
        end
    end
    return false, 0
end

local function readfile(path)
    local file = open(path, "rb")
    if not file then return nil end
    local content = file:read "*a"
    file:close()
    return content
end

local function loadlua(path)
    return readfile(path) or path
end

local function loadngx(path)
    local vars = VAR_PHASES[phase()]
    local file, location = path, vars and var.template_location
    if sub(file, 1)  == "/" then file = sub(file, 2) end
    if location and location ~= "" then
        if sub(location, -1) == "/" then location = sub(location, 1, -2) end
        local res = capture(concat{ location, '/', file})
        if res.status == 200 then return res.body end
    end
    local root = vars and (var.template_root or var.document_root) or prefix
    if sub(root, -1) == "/" then root = sub(root, 1, -2) end
    return readfile(concat{ root, "/", file }) or path
end

do
    if ngx then
        VAR_PHASES = {
            set           = true,
            rewrite       = true,
            access        = true,
            content       = true,
            header_filter = true,
            body_filter   = true,
            log           = true
        }
        template.print = ngx.print or write
        template.load  = loadngx
        prefix, var, capture, null, phase = ngx.config.prefix(), ngx.var, ngx.location.capture, ngx.null, ngx.get_phase
        if VAR_PHASES[phase()] then
            caching = enabled(var.template_cache)
        end
    else
        template.print = write
        template.load  = loadlua
    end
    if _VERSION == "Lua 5.1" then
        local context = { __index = function(t, k)
            return t.context[k] or t.template[k] or _G[k]
        end }
        if jit then
            loadchunk = function(view)
                return assert(load(view, nil, nil, setmetatable({ template = template }, context)))
            end
        else
            loadchunk = function(view)
                local func = assert(loadstring(view))
                setfenv(func, setmetatable({ template = template }, context))
                return func
            end
        end
    else
        local context = { __index = function(t, k)
            return t.context[k] or t.template[k] or _ENV[k]
        end }
        loadchunk = function(view)
            return assert(load(view, nil, nil, setmetatable({ template = template }, context)))
        end
    end
end

function template.caching(enable)
    if enable ~= nil then caching = enable == true end
    return caching
end

function template.output(s)
    if s == nil or s == null then return "" end
    if type(s) == "function" then return template.output(s()) end
    return tostring(s)
end

function template.escape(s, c)
    if type(s) == "string" then
        if c then return gsub(s, "[}{\">/<'&]", CODE_ENTITIES) end
        return gsub(s, "[\">/<'&]", HTML_ENTITIES)
    end
    return template.output(s)
end

function template.new(view, layout)
    assert(view, "view was not provided for template.new(view, layout).")
    local render, compile = template.render, template.compile
    if layout then
        if type(layout) == "table" then
            return setmetatable({ render = function(self, context)
                local context = context or self
                context.blocks = context.blocks or {}
                context.view = compile(view)(context)
                layout.blocks = context.blocks or {}
                layout.view = context.view or ""
                return layout:render()
            end }, { __tostring = function(self)
                local context = self
                context.blocks = context.blocks or {}
                context.view = compile(view)(context)
                layout.blocks = context.blocks or {}
                layout.view = context.view
                return tostring(layout)
            end })
        else
            return setmetatable({ render = function(self, context)
                local context = context or self
                context.blocks = context.blocks or {}
                context.view = compile(view)(context)
                return render(layout, context)
            end }, { __tostring = function(self)
                local context = self
                context.blocks = context.blocks or {}
                context.view = compile(view)(context)
                return compile(layout)(context)
            end })
        end
    end
    return setmetatable({ render = function(self, context)
        return render(view, context or self)
    end }, { __tostring = function(self)
        return compile(view)(self)
    end })
end

function template.precompile(view, path, strip)
    local chunk = dump(template.compile(view), strip ~= false)
    if path then
        local file = open(path, "wb")
        file:write(chunk)
        file:close()
    end
    return chunk
end

function template.compile(view, key, plain)
    assert(view, "view was not provided for template.compile(view, key, plain).")
    if key == "no-cache" then
        return loadchunk(template.parse(view, plain)), false
    end
    key = key or view
    local cache = template.cache
    if cache[key] then return cache[key], true end
    local func = loadchunk(template.parse(view, plain))
    if caching then cache[key] = func end
    return func, false
end

function template.parse(view, plain)
    assert(view, "view was not provided for template.parse(view, plain).")
    if not plain then
        view = template.load(view)
        if byte(sub(view, 1, 1)) == 27 then return view end
    end
    local j = 2
    local c = {[[
context=... or {}
local function include(v, c) return template.compile(v)(c or context) end
local ___,blocks,layout={},blocks or {}
]] }
    local i, s = 1, find(view, "{", 1, true)
    while s do
        local t, p = sub(view, s + 1, s + 1), s + 2
        if t == "{" then
            local e = find(view, "}}", p, true)
            if e then
                local z, w = escaped(view, s)
                if i < s - w then
                    c[j] = "___[#___+1]=[=[\n"
                    c[j+1] = sub(view, i, s - 1 - w)
                    c[j+2] = "]=]\n"
                    j=j+3
                end
                if z then
                    i = s
                else
                    c[j] = "___[#___+1]=template.escape("
                    c[j+1] = trim(sub(view, p, e - 1))
                    c[j+2] = ")\n"
                    j=j+3
                    s, i = e + 1, e + 2
                end
            end
        elseif t == "*" then
            local e = find(view, "*}", p, true)
            if e then
                local z, w = escaped(view, s)
                if i < s - w then
                    c[j] = "___[#___+1]=[=[\n"
                    c[j+1] = sub(view, i, s - 1 - w)
                    c[j+2] = "]=]\n"
                    j=j+3
                end
                if z then
                    i = s
                else
                    c[j] = "___[#___+1]=template.output("
                    c[j+1] = trim(sub(view, p, e - 1))
                    c[j+2] = ")\n"
                    j=j+3
                    s, i = e + 1, e + 2
                end
            end
        elseif t == "%" then
            local e = find(view, "%}", p, true)
            if e then
                local z, w = escaped(view, s)
                if z then
                    if i < s - w then
                        c[j] = "___[#___+1]=[=[\n"
                        c[j+1] = sub(view, i, s - 1 - w)
                        c[j+2] = "]=]\n"
                        j=j+3
                    end
                    i = s
                else
                    local n = e + 2
                    if sub(view, n, n) == "\n" then
                        n = n + 1
                    end
                    local r = rpos(view, s - 1)
                    if i <= r then
                        c[j] = "___[#___+1]=[=[\n"
                        c[j+1] = sub(view, i, r)
                        c[j+2] = "]=]\n"
                        j=j+3
                    end
                    c[j] = trim(sub(view, p, e - 1))
                    c[j+1] = "\n"
                    j=j+2
                    s, i = n - 1, n
                end
            end
        elseif t == "(" then
            local e = find(view, ")}", p, true)
            if e then
                local z, w = escaped(view, s)
                if i < s - w then
                    c[j] = "___[#___+1]=[=[\n"
                    c[j+1] = sub(view, i, s - 1 - w)
                    c[j+2] = "]=]\n"
                    j=j+3
                end
                if z then
                    i = s
                else
                    local f = sub(view, p, e - 1)
                    local x = find(f, ",", 2, true)
                    if x then
                        c[j] = "___[#___+1]=include([=["
                        c[j+1] = trim(sub(f, 1, x - 1))
                        c[j+2] = "]=],"
                        c[j+3] = trim(sub(f, x + 1))
                        c[j+4] = ")\n"
                        j=j+5
                    else
                        c[j] = "___[#___+1]=include([=["
                        c[j+1] = trim(f)
                        c[j+2] = "]=])\n"
                        j=j+3
                    end
                    s, i = e + 1, e + 2
                end
            end
        elseif t == "[" then
            local e = find(view, "]}", p, true)
            if e then
                local z, w = escaped(view, s)
                if i < s - w then
                    c[j] = "___[#___+1]=[=[\n"
                    c[j+1] = sub(view, i, s - 1 - w)
                    c[j+2] = "]=]\n"
                    j=j+3
                end
                if z then
                    i = s
                else
                    c[j] = "___[#___+1]=include("
                    c[j+1] = trim(sub(view, p, e - 1))
                    c[j+2] = ")\n"
                    j=j+3
                    s, i = e + 1, e + 2
                end
            end
        elseif t == "-" then
            local e = find(view, "-}", p, true)
            if e then
                local x, y = find(view, sub(view, s, e + 1), e + 2, true)
                if x then
                    local z, w = escaped(view, s)
                    if z then
                        if i < s - w then
                            c[j] = "___[#___+1]=[=[\n"
                            c[j+1] = sub(view, i, s - 1 - w)
                            c[j+2] = "]=]\n"
                            j=j+3
                        end
                        i = s
                    else
                        y = y + 1
                        x = x - 1
                        if sub(view, y, y) == "\n" then
                            y = y + 1
                        end
                        local b = trim(sub(view, p, e - 1))
                        if b == "verbatim" or b == "raw" then
                            if i < s - w then
                                c[j] = "___[#___+1]=[=[\n"
                                c[j+1] = sub(view, i, s - 1 - w)
                                c[j+2] = "]=]\n"
                                j=j+3
                            end
                            c[j] = "___[#___+1]=[=["
                            c[j+1] = sub(view, e + 2, x)
                            c[j+2] = "]=]\n"
                            j=j+3
                        else
                            if sub(view, x, x) == "\n" then
                                x = x - 1
                            end
                            local r = rpos(view, s - 1)
                            if i <= r then
                                c[j] = "___[#___+1]=[=[\n"
                                c[j+1] = sub(view, i, r)
                                c[j+2] = "]=]\n"
                                j=j+3
                            end
                            c[j] = 'blocks["'
                            c[j+1] = b
                            c[j+2] = '"]=include[=['
                            c[j+3] = sub(view, e + 2, x)
                            c[j+4] = "]=]\n"
                            j=j+5
                        end
                        s, i = y - 1, y
                    end
                end
            end
        elseif t == "#" then
            local e = find(view, "#}", p, true)
            if e then
                local z, w = escaped(view, s)
                if i < s - w then
                    c[j] = "___[#___+1]=[=[\n"
                    c[j+1] = sub(view, i, s - 1 - w)
                    c[j+2] = "]=]\n"
                    j=j+3
                end
                if z then
                    i = s
                else
                    e = e + 2
                    if sub(view, e, e) == "\n" then
                        e = e + 1
                    end
                    s, i = e - 1, e
                end
            end
        end
        s = find(view, "{", s + 1, true)
    end
    s = sub(view, i)
    if s and s ~= "" then
        c[j] = "___[#___+1]=[=[\n"
        c[j+1] = s
        c[j+2] = "]=]\n"
        j=j+3
    end
    c[j] = "return layout and include(layout,setmetatable({view=table.concat(___),blocks=blocks},{__index=context})) or table.concat(___)"
    return concat(c)
end

function template.render(view, context, key, plain)
    assert(view, "view was not provided for template.render(view, context, key, plain).")
    return template.print(template.compile(view, key, plain)(context))
end

return template


================================================
FILE: demo11/README.md
================================================
## 为了保护站点安全,有时候我们需要一个web防火墙,来实现拦截过滤,在openresty里面其实很好做这个功能,下面我们简单实现一个黑白名单ip的功能

# mvc 中加上waf模块

lite/mvc.lua

```
-- waf begin

local ret, waf = pcall(require, "waf") -- 安全引入rewrite模块,假如没有也不会报错

if ret then
    local c_ret, r_ret = pcall(waf.exec)
    -- c_ret 表示执行成功,r_ret 表示已重定向,两者都为true,则表示重写成功,则不继续往下执行
    if c_ret and r_ret then
        return
    end
end

-- waf end
```

这个不一定要在mvc里面做,也可以直接在access_by_lua阶段做

# 获取用户IP

```
function get_client_ip()
    local ip = ngx.req.get_headers()["x_forwarded_for"]
    if not ip then
       ip = ngx.var.remote_addr
    else
       ip = ngx.re.gsub(ip, ",.*", "")
    end
    return ip
end
```

# 白名单实现

原理就是预先设置好一个`白名单列表`,然后判断ip是否在列表中,在则不做处理,不在则直接调用`ngx.exit`中断当前请求

```
-- 实际情况可以根据需要将ip放在共享内存,或者redis当中,这里为了简单直接写死了
local white_list_str = "127.0.0.1,192.168.0.168"
local white_list = {}
local it, err = ngx.re.gmatch(white_list_str, '([0-9]+)[.]([0-9]+)[.]([0-9]+)[.]([0-9]+)')
while true do
    local m, err = it()
    if err then
        ngx.log(ngx.ERR, "error: ", err)
        return
    end
    if not m then   break   end
    white_list[m[0]] =  true
end

local ip = get_client_ip()
if not white_list[ip] then
    ngx.exit(444)
    return true
end
return false
```

# 黑名单实现

原理就是预先设置好一个`黑名单列表`,然后判断ip是否在列表中,在则直接调用`ngx.exit`中断当前请求,不在则不做处理,跟白名单刚好相反

```
-- 实际情况可以根据需要将ip放在共享内存,或者redis当中,这里为了简单直接写死了
local black_list_str = "127.0.0.1,192.168.0.168"
local black_list = {}
local it, err = ngx.re.gmatch(black_list_str, '([0-9]+)[.]([0-9]+)[.]([0-9]+)[.]([0-9]+)')
while true do
    local m, err = it()
    if err then
        ngx.log(ngx.ERR, "error: ", err)
        return
    end
    if not m then   break   end
    black_list[m[0]] =  true
end

local ip = get_client_ip()
if black_list[ip] then
    ngx.exit(444)
    return true
end
return false
```

# 黑白名单

前面的黑白名单只能单独使用,混合使用还有一点问题,主要是白名单部分,判断不在白名单就直接中断请求了,只要稍微调整一下代码就可以了

```
local ip = get_client_ip()
-- 先检查白名单,在白名单内,则直接放行
if white_list[ip] then
    return false
end
-- 如果在黑名单内直接中断请求
if black_list[ip] then
    ngx.exit(444)
    return true
end
```

OK, 访问 http://localhost

然后,把waf.lua 里面的white_list_str、black_list_str中的127.0.0.1去掉看效果

这里只是做一个简单的ip防火墙,还比较初级,但是还是有点用的,可以用来控制调用的权限,保护服务不暴露,只针对部分服务器开放,对于一些小规模的攻击可以直接在nginx里面做拦截,而不需要动`iptables`。

我之前配合共享内存做了一个简单的防攻击防火墙,用共享内存实时的记录ip的访问次数跟速率,对于部分非法请求直接中断

具体应用场景看需求,没有做不到,只有想不到...

[示例代码](https://github.com/362228416/openresty-web-dev/tree/master/demo11) 参见demo11部分


================================================
FILE: demo11/conf/nginx.conf
================================================

worker_processes  1;

error_log logs/error.log notice;

events {
    worker_connections 1024;
}

http {
    lua_package_path "$prefix/lua/?.lua;$prefix/lualib/?.lua";
    server {
        listen 80;
        server_name localhost;
        lua_code_cache off;

        location / {
            root lua;
        	default_type "text/html; charset=utf-8";
        	content_by_lua_file lualib/lite/mvc.lua;
        }

        location ~ ^/js/|^/css/|\.html {
        	root html;
        }
    }
}


================================================
FILE: demo11/html/index.html
================================================
<html>
<head>
	<meta charset="UTF-8">
	<title>demo10</title>
</head>
<body>
	ip 防火墙示例
</body>
</html>


================================================
FILE: demo11/logs/.gitignore
================================================
*.log
*.pid


================================================
FILE: demo11/lua/rewrite.lua
================================================

local _M = {}

function _M.exec1(uri)       -- 正常版
    local rewrite_urls = {}
    local queryString = ngx.var.args
    if queryString == nil then queryString = "" end
    rewrite_urls['/index/article'] = '/article?' .. queryString
    local match_url = rewrite_urls[uri]
    if match_url then
        -- ngx.redirect(match_url) -- url 变化
        ngx.exec(match_url)        -- url 无变化
        return true
    end
    return false
end

function _M.exec(uri)      -- 移动端增强版
    local agent = ngx.var.http_user_agent
    if agent ~= nil then
        local m, ret = ngx.re.match(agent, "Android|webOS|iPhone|iPod|BlackBerry")
        if m ~= nil then
            local rewrite_urls = {}
            local queryString = ngx.var.args
            if queryString == nil then queryString = "" end
            rewrite_urls['/index/article'] = '/article?' .. queryString
            local match_url = rewrite_urls[uri]
            if match_url then
                -- ngx.redirect(match_url) -- url 变化
                ngx.exec(match_url)        -- url 无变化
                return true
            end
        end
    end
    return false
end

return _M


================================================
FILE: demo11/lua/waf.lua
================================================

local _M = {}

function parse_ip(ip_str)
    local ip_list = {}
    local it, err = ngx.re.gmatch(ip_str, '([0-9]+)[.]([0-9]+)[.]([0-9]+)[.]([0-9]+)')
    while true do
        local m, err = it()
        if err then
            ngx.log(ngx.ERR, "error: ", err)
            return
        end
        if not m then   break   end
        ip_list[m[0]] =  true
    end
    return ip_list
end

-- 实际情况可以根据需要将ip放在共享内存,或者redis当中,这里为了简单直接写死了

local white_list_str = "127.0.0.1,192.168.0.168"
local white_list = parse_ip(white_list_str)

local black_list_str = "127.0.0.1,192.168.0.168"
local black_list = parse_ip(black_list_str)

function get_client_ip()
    local ip = ngx.req.get_headers()["x_forwarded_for"]
    if not ip then
       ip = ngx.var.remote_addr
    else
       ip = ngx.re.gsub(ip, ",.*", "")
    end
    return ip
end

function _M.exec()
    -- 1、白名单
    -- local ip = get_client_ip()
    -- if not white_list[ip] then
    --     ngx.exit(444)
    --     return true
    -- end
    -- return false

    -- 2、黑名单
    -- local ip = get_client_ip()
    -- if black_list[ip] then
    --     ngx.exit(444)
    --     return true
    -- end
    -- return false

    -- 3、同时支持黑白名单
    local ip = get_client_ip()
    -- 先检查白名单,在白名单内,则直接放行
    if white_list[ip] then
        -- print('白名单')
        return false
    end
    -- 如果在黑名单内直接中断请求
    if black_list[ip] then
        -- print('黑名单')
        ngx.exit(444)
        return true
    end
    -- 其他规则

end

return _M


================================================
FILE: demo11/lua/web/index.lua
================================================
local req = require "lite.req"

local _M = {}

function _M.index()
	ngx.say('home page ')
end

return _M


================================================
FILE: demo11/lualib/lite/mvc.lua
================================================

-- waf begin

local ret, waf = pcall(require, "waf") -- 安全引入rewrite模块,假如没有也不会报错

if ret then
    local c_ret, r_ret = pcall(waf.exec)
    -- c_ret 表示执行成功,r_ret 表示已重定向,两者都为true,则表示重写成功,则不继续往下执行
    if c_ret and r_ret then
        return
    end
end

-- waf end

local uri = ngx.var.uri
-- 如果是首页
if uri == "" or uri == "/" then
    local res = ngx.location.capture("/index.html", {})
    ngx.say(res.body)
    return
end

-- url 重写 begin

local ret, rewrite = pcall(require, "rewrite") -- 安全引入rewrite模块,假如没有也不会报错

if ret then
    local c_ret, r_ret = pcall(rewrite.exec, uri)
    ngx.say(r_ret)
    -- c_ret 表示执行成功,r_ret 表示已重定向,两者都为true,则表示重写成功,则不继续往下执行
    if c_ret and r_ret then
        return
    end
end

-- url 重写end

local m, err = ngx.re.match(uri, "([a-zA-Z0-9-]+)/*([a-zA-Z0-9-]+)*")

local is_debug = true       -- 调试阶段,会输出错误信息到页面上

local moduleName = m[1]     -- 模块名
local method = m[2]         -- 方法名

if not method then
    method = "index"        -- 默认访问index方法
else
    method = ngx.re.gsub(method, "-", "_")
end

-- 控制器默认在web包下面
local prefix = "web."
local path = prefix .. moduleName

-- 尝试引入模块,不存在则报错
local ret, ctrl, err = pcall(require, path)

if ret == false then
    if is_debug then
        ngx.status = 404
        ngx.say("<p style='font-size: 50px'>Error: <span style='color:red'>" .. ctrl .. "</span> module not found !</p>")
    end
    ngx.exit(404)
end

-- 尝试获取模块方法,不存在则报错
local req_method = ctrl[method]

if req_method == nil then
    if is_debug then
        ngx.status = 404
        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>")
    end
    ngx.exit(404)
end

-- 执行模块方法,报错则显示错误信息,所见即所得,可以追踪lua报错行数
ret, err = pcall(req_method)

if ret == false then
    if is_debug then
        ngx.status = 404
        ngx.say("<p style='font-size: 50px'>Error: <span style='color:red'>" .. err .. "</span></p>")
    else
        ngx.exit(500)
    end
end


================================================
FILE: demo11/lualib/lite/req.lua
================================================
local _M = {}

-- 获取http get/post 请求参数
function _M.getArgs()
    local request_method = ngx.var.request_method
    local args = ngx.req.get_uri_args()
    -- 参数获取
    if "POST" == request_method then
        ngx.req.read_body()
        local postArgs = ngx.req.get_post_args()
        if postArgs then
            for k, v in pairs(postArgs) do
                args[k] = v
            end
        end
    end
    return args
end

return _M

================================================
FILE: demo11/lualib/resty/template.lua
================================================
local setmetatable = setmetatable
local loadstring = loadstring
local loadchunk
local tostring = tostring
local setfenv = setfenv
local require = require
local capture
local concat = table.concat
local assert = assert
local prefix
local write = io.write
local pcall = pcall
local phase
local open = io.open
local load = load
local type = type
local dump = string.dump
local find = string.find
local gsub = string.gsub
local byte = string.byte
local null
local sub = string.sub
local ngx = ngx
local jit = jit
local var

local _VERSION = _VERSION
local _ENV = _ENV
local _G = _G

local HTML_ENTITIES = {
    ["&"] = "&amp;",
    ["<"] = "&lt;",
    [">"] = "&gt;",
    ['"'] = "&quot;",
    ["'"] = "&#39;",
    ["/"] = "&#47;"
}

local CODE_ENTITIES = {
    ["{"] = "&#123;",
    ["}"] = "&#125;",
    ["&"] = "&amp;",
    ["<"] = "&lt;",
    [">"] = "&gt;",
    ['"'] = "&quot;",
    ["'"] = "&#39;",
    ["/"] = "&#47;"
}

local VAR_PHASES

local ok, newtab = pcall(require, "table.new")
if not ok then newtab = function() return {} end end

local caching = true
local template = newtab(0, 12)

template._VERSION = "1.9"
template.cache    = {}

local function enabled(val)
    if val == nil then return true end
    return val == true or (val == "1" or val == "true" or val == "on")
end

local function trim(s)
    return gsub(gsub(s, "^%s+", ""), "%s+$", "")
end

local function rpos(view, s)
    while s > 0 do
        local c = sub(view, s, s)
        if c == " " or c == "\t" or c == "\0" or c == "\x0B" then
            s = s - 1
        else
            break
        end
    end
    return s
end

local function escaped(view, s)
    if s > 1 and sub(view, s - 1, s - 1) == "\\" then
        if s > 2 and sub(view, s - 2, s - 2) == "\\" then
            return false, 1
        else
            return true, 1
        end
    end
    return false, 0
end

local function readfile(path)
    local file = open(path, "rb")
    if not file then return nil end
    local content = file:read "*a"
    file:close()
    return content
end

local function loadlua(path)
    return readfile(path) or path
end

local function loadngx(path)
    local vars = VAR_PHASES[phase()]
    local file, location = path, vars and var.template_location
    if sub(file, 1)  == "/" then file = sub(file, 2) end
    if location and location ~= "" then
        if sub(location, -1) == "/" then location = sub(location, 1, -2) end
        local res = capture(concat{ location, '/', file})
        if res.status == 200 then return res.body end
    end
    local root = vars and (var.template_root or var.document_root) or prefix
    if sub(root, -1) == "/" then root = sub(root, 1, -2) end
    return readfile(concat{ root, "/", file }) or path
end

do
    if ngx then
        VAR_PHASES = {
            set           = true,
            rewrite       = true,
            access        = true,
            content       = true,
            header_filter = true,
            body_filter   = true,
            log           = true
        }
        template.print = ngx.print or write
        template.load  = loadngx
        prefix, var, capture, null, phase = ngx.config.prefix(), ngx.var, ngx.location.capture, ngx.null, ngx.get_phase
        if VAR_PHASES[phase()] then
            caching = enabled(var.template_cache)
        end
    else
        template.print = write
        template.load  = loadlua
    end
    if _VERSION == "Lua 5.1" then
        local context = { __index = function(t, k)
            return t.context[k] or t.template[k] or _G[k]
        end }
        if jit then
            loadchunk = function(view)
                return assert(load(view, nil, nil, setmetatable({ template = template }, context)))
            end
        else
            loadchunk = function(view)
                local func = assert(loadstring(view))
                setfenv(func, setmetatable({ template = template }, context))
                return func
            end
        end
    else
        local context = { __index = function(t, k)
            return t.context[k] or t.template[k] or _ENV[k]
        end }
        loadchunk = function(view)
            return assert(load(view, nil, nil, setmetatable({ template = template }, context)))
        end
    end
end

function template.caching(enable)
    if enable ~= nil then caching = enable == true end
    return caching
end

function template.output(s)
    if s == nil or s == null then return "" end
    if type(s) == "function" then return template.output(s()) end
    return tostring(s)
end

function template.escape(s, c)
    if type(s) == "string" then
        if c then return gsub(s, "[}{\">/<'&]", CODE_ENTITIES) end
        return gsub(s, "[\">/<'&]", HTML_ENTITIES)
    end
    return template.output(s)
end

function template.new(view, layout)
    assert(view, "view was not provided for template.new(view, layout).")
    local render, compile = template.render, template.compile
    if layout then
        if type(layout) == "table" then
            return setmetatable({ render = function(self, context)
                local context = context or self
                context.blocks = context.blocks or {}
                context.view = compile(view)(context)
                layout.blocks = context.blocks or {}
                layout.view = context.view or ""
                return layout:render()
            end }, { __tostring = function(self)
                local context = self
                context.blocks = context.blocks or {}
                context.view = compile(view)(context)
                layout.blocks = context.blocks or {}
                layout.view = context.view
                return tostring(layout)
            end })
        else
            return setmetatable({ render = function(self, context)
                local context = context or self
                context.blocks = context.blocks or {}
                context.view = compile(view)(context)
                return render(layout, context)
            end }, { __tostring = function(self)
                local context = self
                context.blocks = context.blocks or {}
                context.view = compile(view)(context)
                return compile(layout)(context)
            end })
        end
    end
    return setmetatable({ render = function(self, context)
        return render(view, context or self)
    end }, { __tostring = function(self)
        return compile(view)(self)
    end })
end

function template.precompile(view, path, strip)
    local chunk = dump(template.compile(view), strip ~= false)
    if path then
        local file = open(path, "wb")
        file:write(chunk)
        file:close()
    end
    return chunk
end

function template.compile(view, key, plain)
    assert(view, "view was not provided for template.compile(view, key, plain).")
    if key == "no-cache" then
        return loadchunk(template.parse(view, plain)), false
    end
    key = key or view
    local cache = template.cache
    if cache[key] then return cache[key], true end
    local func = loadchunk(template.parse(view, plain))
    if caching then cache[key] = func end
    return func, false
end

function template.parse(view, plain)
    assert(view, "view was not provided for template.parse(view, plain).")
    if not plain then
        view = template.load(view)
        if byte(sub(view, 1, 1)) == 27 then return view end
    end
    local j = 2
    local c = {[[
context=... or {}
local function include(v, c) return template.compile(v)(c or context) end
local ___,blocks,layout={},blocks or {}
]] }
    local i, s = 1, find(view, "{", 1, true)
    while s do
        local t, p = sub(view, s + 1, s + 1), s + 2
        if t == "{" then
            local e = find(view, "}}", p, true)
            if e then
                local z, w = escaped(view, s)
                if i < s - w then
                    c[j] = "___[#___+1]=[=[\n"
                    c[j+1] = sub(view, i, s - 1 - w)
                    c[j+2] = "]=]\n"
                    j=j+3
                end
                if z then
                    i = s
                else
                    c[j] = "___[#___+1]=template.escape("
                    c[j+1] = trim(sub(view, p, e - 1))
                    c[j+2] = ")\n"
                    j=j+3
                    s, i = e + 1, e + 2
                end
            end
        elseif t == "*" then
            local e = find(view, "*}", p, true)
            if e then
                local z, w = escaped(view, s)
                if i < s - w then
                    c[j] = "___[#___+1]=[=[\n"
                    c[j+1] = sub(view, i, s - 1 - w)
                    c[j+2] = "]=]\n"
                    j=j+3
                end
                if z then
                    i = s
                else
                    c[j] = "___[#___+1]=template.output("
                    c[j+1] = trim(sub(view, p, e - 1))
                    c[j+2] = ")\n"
                    j=j+3
                    s, i = e + 1, e + 2
                end
            end
        elseif t == "%" then
            local e = find(view, "%}", p, true)
            if e then
                local z, w = escaped(view, s)
                if z then
                    if i < s - w then
                        c[j] = "___[#___+1]=[=[\n"
                        c[j+1] = sub(view, i, s - 1 - w)
                        c[j+2] = "]=]\n"
                        j=j+3
                    end
                    i = s
                else
                    local n = e + 2
                    if sub(view, n, n) == "\n" then
                        n = n + 1
                    end
                    local r = rpos(view, s - 1)
                    if i <= r then
                        c[j] = "___[#___+1]=[=[\n"
                        c[j+1] = sub(view, i, r)
                        c[j+2] = "]=]\n"
                        j=j+3
                    end
                    c[j] = trim(sub(view, p, e - 1))
                    c[j+1] = "\n"
                    j=j+2
                    s, i = n - 1, n
                end
            end
        elseif t == "(" then
            local e = find(view, ")}", p, true)
            if e then
                local z, w = escaped(view, s)
                if i < s - w then
                    c[j] = "___[#___+1]=[=[\n"
                    c[j+1] = sub(view, i, s - 1 - w)
                    c[j+2] = "]=]\n"
                    j=j+3
                end
                if z then
                    i = s
                else
                    local f = sub(view, p, e - 1)
                    local x = find(f, ",", 2, true)
                    if x then
                        c[j] = "___[#___+1]=include([=["
                        c[j+1] = trim(sub(f, 1, x - 1))
                        c[j+2] = "]=],"
                        c[j+3] = trim(sub(f, x + 1))
                        c[j+4] = ")\n"
                        j=j+5
                    else
                        c[j] = "___[#___+1]=include([=["
                        c[j+1] = trim(f)
                        c[j+2] = "]=])\n"
                        j=j+3
                    end
                    s, i = e + 1, e + 2
                end
            end
        elseif t == "[" then
            local e = find(view, "]}", p, true)
            if e then
                local z, w = escaped(view, s)
                if i < s - w then
                    c[j] = "___[#___+1]=[=[\n"
                    c[j+1] = sub(view, i, s - 1 - w)
                    c[j+2] = "]=]\n"
                    j=j+3
                end
                if z then
                    i = s
                else
                    c[j] = "___[#___+1]=include("
                    c[j+1] = trim(sub(view, p, e - 1))
                    c[j+2] = ")\n"
                    j=j+3
                    s, i = e + 1, e + 2
                end
            end
        elseif t == "-" then
            local e = find(view, "-}", p, true)
            if e then
                local x, y = find(view, sub(view, s, e + 1), e + 2, true)
                if x then
                    local z, w = escaped(view, s)
                    if z then
                        if i < s - w then
                            c[j] = "___[#___+1]=[=[\n"
                            c[j+1] = sub(view, i, s - 1 - w)
                            c[j+2] = "]=]\n"
                            j=j+3
                        end
                        i = s
                    else
                        y = y + 1
                        x = x - 1
                        if sub(view, y, y) == "\n" then
                            y = y + 1
                        end
                        local b = trim(sub(view, p, e - 1))
                        if b == "verbatim" or b == "raw" then
                            if i < s - w then
                                c[j] = "___[#___+1]=[=[\n"
                                c[j+1] = sub(view, i, s - 1 - w)
                                c[j+2] = "]=]\n"
                                j=j+3
                            end
                            c[j] = "___[#___+1]=[=["
                            c[j+1] = sub(view, e + 2, x)
                            c[j+2] = "]=]\n"
                            j=j+3
                        else
                            if sub(view, x, x) == "\n" then
                                x = x - 1
                            end
                            local r = rpos(view, s - 1)
                            if i <= r then
                                c[j] = "___[#___+1]=[=[\n"
                                c[j+1] = sub(view, i, r)
                                c[j+2] = "]=]\n"
                                j=j+3
                            end
                            c[j] = 'blocks["'
                            c[j+1] = b
                            c[j+2] = '"]=include[=['
                            c[j+3] = sub(view, e + 2, x)
                            c[j+4] = "]=]\n"
                            j=j+5
                        end
                        s, i = y - 1, y
                    end
                end
            end
        elseif t == "#" then
            local e = find(view, "#}", p, true)
            if e then
                local z, w = escaped(view, s)
                if i < s - w then
                    c[j] = "___[#___+1]=[=[\n"
                    c[j+1] = sub(view, i, s - 1 - w)
                    c[j+2] = "]=]\n"
                    j=j+3
                end
                if z then
                    i = s
                else
                    e = e + 2
                    if sub(view, e, e) == "\n" then
                        e = e + 1
                    end
                    s, i = e - 1, e
                end
            end
        end
        s = find(view, "{", s + 1, true)
    end
    s = sub(view, i)
    if s and s ~= "" then
        c[j] = "___[#___+1]=[=[\n"
        c[j+1] = s
        c[j+2] = "]=]\n"
        j=j+3
    end
    c[j] = "return layout and include(layout,setmetatable({view=table.concat(___),blocks=blocks},{__index=context})) or table.concat(___)"
    return concat(c)
end

function template.render(view, context, key, plain)
    assert(view, "view was not provided for template.render(view, context, key, plain).")
    return template.print(template.compile(view, key, plain)(context))
end

return template


================================================
FILE: demo12/README.md
================================================
# 这章主要演示怎么通过lua操作cookie

操作cookie有两种方式,一种是直接设置响应头,另外一种是用[lua-resty-cookie](https://github.com/cloudflare/lua-resty-cookie)库(其实原理是一样的,只不过做了一点封装),这个库也是春哥写的,可以放心使用,下面我分别介绍一下两种方式怎么用

## 读取cookie一(原生)
```
print(ngx.var.http_cookie) -- 获取所有cookie,这里获取到的是一个字符串,如果不存在则返回nil
print(ngx.var.cookie_username) -- 获取单个cookie,_后面的cookie的name,如果不存在则返回nil
```

## 设置cookie一(原生)
```
ngx.header['Set-Cookie'] = {'a=32; path=/', 'b=4; path=/'}  -- 批量设置cookie
ngx.header['Set-Cookie'] = 'a=32; path=/'                   -- 设置单个cookie,通过多次调用来设置多个值
ngx.header['Set-Cookie'] = 'b=4; path=/'
ngx.header['Set-Cookie'] = 'c=5; path=/; Expires=' .. ngx.cookie_time(ngx.time() + 60 * 30) -- 设置Cookie过期时间为30分钟
```

熟悉http协议的应该都知道,设置cookie是通过在响应头中的Set-Cookie字段来操作的,既然知道原理那上面的代码应该就很好理解,其实只要知道怎么用lua来设置响应头即可

## 获取cookie二(lua-resty-cookie)
```
local cookie = resty_cookie:new()
local all_cookie = cookie:get_all() -- 这里获取到所有的cookie,是一个table,如果不存在则返回nil
print(cjson.encode(all_cookie))
print(cookie:get('c'))              -- 获取单个cookie的值,如果不存在则返回nil
```

## 设置cookie二(lua-resty-cookie)
```
cookie:set({
    key = "c",
    value = "123456",
    path = "/",
    domain = "localhost",
    expires = ngx.cookie_time(ngx.time() + 60 * 13)
})
```

OK, 访问

http://localhost/index  原生

http://localhost/index2  lua-resty-cookie

两种方式各有各的好处

第一种
优点:
简单,无依赖
缺点:
太简单?不够抽象,太底层?

第二种
优点:
获取设置都很简单,简单的封装了一层,提供了更有表现力的api接口
缺点:
多引入一个库,其实也不算什么缺点

看情况而定吧,假如cookie操作得比较少的话,可以用第一种,假如操作得比较多,可以考虑用第二种,编码比较统一

[示例代码](https://github.com/362228416/openresty-web-dev/tree/master/demo12) 参见demo12部分


================================================
FILE: demo12/conf/nginx.conf
================================================

worker_processes  1;

error_log logs/error.log notice;

events {
    worker_connections 1024;
}

http {
    lua_package_path "$prefix/lua/?.lua;$prefix/lualib/?.lua";
    server {
        listen 80;
        server_name localhost;
        lua_code_cache off;

        location / {
            root lua;
        	default_type "text/html; charset=utf-8";
        	content_by_lua_file lualib/lite/mvc.lua;
        }

        location ~ ^/js/|^/css/|\.html {
        	root html;
        }
    }
}


================================================
FILE: demo12/logs/.gitignore
================================================
*.log
*.pid


================================================
FILE: demo12/lua/tpl/index.html
================================================
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Cookie示例</title>
</head>
<body>
    <script>
        if (document.cookie.length > 0) {
            var cookies = document.cookie.split('; ')
            for (var i = 0; i < cookies.length; i++) {
                var cookie = cookies[i]
                var kv = cookie.split('=')
                document.write(kv[0] + ' = ' + decodeURIComponent(kv[1]) + '<br/>')
            }
        }
    </script>
</body>
</html>


================================================
FILE: demo12/lua/web/index.lua
================================================
local req = require "lite.req"
local template = require "resty.template"
local resty_cookie = require "resty.cookie"
local cjson = require "cjson"

local _M = {}

function _M.index()
	-- ngx.header['Set-Cookie'] = {'a=32; path=/', 'b=4; path=/'}
	-- ngx.header['Set-Cookie'] = 'a=32; path=/'
	-- ngx.header['Set-Cookie'] = 'b=46; path=/'
	ngx.header['Set-Cookie'] = 'c=5; path=/; Expires=' .. ngx.cookie_time(ngx.time() + 60 * 13)
	print(ngx.var.http_cookie)
	-- print(ngx.var.cookie_username)
	template.render('tpl/index.html')
end

function _M.index2()
	local cookie = resty_cookie:new()

	cookie:set({
	    key = "c",
	    value = "123456",
	    path = "/",
	    domain = "localhost",
	    expires = ngx.cookie_time(ngx.time() + 60 * 13)
	})

	local all_cookie = cookie:get_all()
	print(cjson.encode(all_cookie))
	print(cookie:get('c'))
	template.render('tpl/index.html')
end

return _M


================================================
FILE: demo12/lualib/lite/mvc.lua
================================================
local uri = ngx.var.uri
-- 如果是首页
if uri == "" or uri == "/" then
    local res = ngx.location.capture("/index.html", {})
    ngx.say(res.body)
    return
end

local m, err = ngx.re.match(uri, "([a-zA-Z0-9-]+)/*([a-zA-Z0-9-]+)*")

local is_debug = true       -- 调试阶段,会输出错误信息到页面上

local moduleName = m[1]     -- 模块名
local method = m[2]         -- 方法名

if not method then
    method = "index"        -- 默认访问index方法
else
    method = ngx.re.gsub(method, "-", "_")
end

-- 控制器默认在web包下面
local prefix = "web."
local path = prefix .. moduleName

-- 尝试引入模块,不存在则报错
local ret, ctrl, err = pcall(require, path)

if ret == false then
    if is_debug then
        ngx.status = 404
        ngx.say("<p style='font-size: 50px'>Error: <span style='color:red'>" .. ctrl .. "</span> module not found !</p>")
    end
    ngx.exit(404)
end

-- 尝试获取模块方法,不存在则报错
local req_method = ctrl[method]

if req_method == nil then
    if is_debug then
        ngx.status = 404
        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>")
    end
    ngx.exit(404)
end

-- 执行模块方法,报错则显示错误信息,所见即所得,可以追踪lua报错行数
ret, err = pcall(req_method)

if ret == false then
    if is_debug then
        ngx.status = 404
        ngx.say("<p style='font-size: 50px'>Error: <span style='color:red'>" .. err .. "</span></p>")
    else
        ngx.exit(500)
    end
end


================================================
FILE: demo12/lualib/lite/req.lua
================================================
local _M = {}

-- 获取http get/post 请求参数
function _M.getArgs()
    local request_method = ngx.var.request_method
    local args = ngx.req.get_uri_args()
    -- 参数获取
    if "POST" == request_method then
        ngx.req.read_body()
        local postArgs = ngx.req.get_post_args()
        if postArgs then
            for k, v in pairs(postArgs) do
                args[k] = v
            end
        end
    end
    return args
end

return _M

================================================
FILE: demo12/lualib/resty/cookie.lua
================================================
-- Copyright (C) 2013-2016 Jiale Zhi (calio), CloudFlare Inc.
-- See RFC6265 http://tools.ietf.org/search/rfc6265
-- require "luacov"

local type          = type
local byte          = string.byte
local sub           = string.sub
local format        = string.format
local log           = ngx.log
local ERR           = ngx.ERR
local WARN          = ngx.WARN
local ngx_header    = ngx.header

local EQUAL         = byte("=")
local SEMICOLON     = byte(";")
local SPACE         = byte(" ")
local HTAB          = byte("\t")

-- table.new(narr, nrec)
local ok, new_tab = pcall(require, "table.new")
if not ok then
    new_tab = function () return {} end
end

local ok, clear_tab = pcall(require, "table.clear")
if not ok then
    clear_tab = function(tab) for k, _ in pairs(tab) do tab[k] = nil end end
end

local _M = new_tab(0, 2)

_M._VERSION = '0.01'


local function get_cookie_table(text_cookie)
    if type(text_cookie) ~= "string" then
        log(ERR, format("expect text_cookie to be \"string\" but found %s",
            type(text_cookie)))
        return {}
    end

    local EXPECT_KEY    = 1
    local EXPECT_VALUE  = 2
    local EXPECT_SP     = 3

    local n = 0
    local len = #text_cookie

    for i=1, len do
        if byte(text_cookie, i) == SEMICOLON then
            n = n + 1
        end
    end

    local cookie_table  = new_tab(0, n + 1)

    local state = EXPECT_SP
    local i = 1
    local j = 1
    local key, value

    while j <= len do
        if state == EXPECT_KEY then
            if byte(text_cookie, j) == EQUAL then
                key = sub(text_cookie, i, j - 1)
                state = EXPECT_VALUE
                i = j + 1
            end
        elseif state == EXPECT_VALUE then
            if byte(text_cookie, j) == SEMICOLON
                    or byte(text_cookie, j) == SPACE
                    or byte(text_cookie, j) == HTAB
            then
                value = sub(text_cookie, i, j - 1)
                cookie_table[key] = value

                key, value = nil, nil
                state = EXPECT_SP
                i = j + 1
            end
        elseif state == EXPECT_SP then
            if byte(text_cookie, j) ~= SPACE
                    and byte(text_cookie, j) ~= HTAB
            then
                state = EXPECT_KEY
                i = j
                j = j - 1
            end
        end
        j = j + 1
    end

    if key ~= nil and value == nil then
        cookie_table[key] = sub(text_cookie, i)
    end

    return cookie_table
end

function _M.new(self)
    local _cookie = ngx.var.http_cookie
    --if not _cookie then
    --return nil, "no cookie found in current request"
    --end
    return setmetatable({ _cookie = _cookie, set_cookie_table = new_tab(4, 0) },
        { __index = self })
end

function _M.get(self, key)
    if not self._cookie then
        return nil, "no cookie found in the current request"
    end
    if self.cookie_table == nil then
        self.cookie_table = get_cookie_table(self._cookie)
    end

    return self.cookie_table[key]
end

function _M.get_all(self)
    if not self._cookie then
        return nil, "no cookie found in the current request"
    end

    if self.cookie_table == nil then
        self.cookie_table = get_cookie_table(self._cookie)
    end

    return self.cookie_table
end

local function bake(cookie)
    if not cookie.key or not cookie.value then
        return nil, 'missing cookie field "key" or "value"'
    end

    if cookie["max-age"] then
        cookie.max_age = cookie["max-age"]
    end

    if (cookie.samesite) then
        local samesite = cookie.samesite

        -- if we dont have a valid-looking attribute, ignore the attribute
        if (samesite ~= "Strict" and samesite ~= "Lax") then
            log(WARN, "SameSite value must be 'Strict' or 'Lax'")
            cookie.samesite = nil
        end
    end

    local str = cookie.key .. "=" .. cookie.value
            .. (cookie.expires and "; Expires=" .. cookie.expires or "")
            .. (cookie.max_age and "; Max-Age=" .. cookie.max_age or "")
            .. (cookie.domain and "; Domain=" .. cookie.domain or "")
            .. (cookie.path and "; Path=" .. cookie.path or "")
            .. (cookie.secure and "; Secure" or "")
            .. (cookie.httponly and "; HttpOnly" or "")
            .. (cookie.samesite and "; SameSite=" .. cookie.samesite or "")
            .. (cookie.extension and "; " .. cookie.extension or "")
    return str
end

function _M.set(self, cookie)
    local cookie_str, err = bake(cookie)
    if not cookie_str then
        return nil, err
    end

    local set_cookie = ngx_header['Set-Cookie']
    local set_cookie_type = type(set_cookie)
    local t = self.set_cookie_table
    clear_tab(t)

    if set_cookie_type == "string" then
        -- only one cookie has been setted
        if set_cookie ~= cookie_str then
            t[1] = set_cookie
            t[2] = cookie_str
            ngx_header['Set-Cookie'] = t
        end
    elseif set_cookie_type == "table" then
        -- more than one cookies has been setted
        local size = #set_cookie

        -- we can not set cookie like ngx.header['Set-Cookie'][3] = val
        -- so create a new table, copy all the values, and then set it back
        for i=1, size do
            t[i] = ngx_header['Set-Cookie'][i]
            if t[i] == cookie_str then
                -- new cookie is duplicated
                return true
            end
        end
        t[size + 1] = cookie_str
        ngx_header['Set-Cookie'] = t
    else
        -- no cookie has been setted
        ngx_header['Set-Cookie'] = cookie_str
    end
    return true
end

return _M

================================================
FILE: demo12/lualib/resty/template.lua
================================================
local setmetatable = setmetatable
local loadstring = loadstring
local loadchunk
local tostring = tostring
local setfenv = setfenv
local require = require
local capture
local concat = table.concat
local assert = assert
local prefix
local write = io.write
local pcall = pcall
local phase
local open = io.open
local load = load
local type = type
local dump = string.dump
local find = string.find
local gsub = string.gsub
local byte = string.byte
local null
local sub = string.sub
local ngx = ngx
local jit = jit
local var

local _VERSION = _VERSION
local _ENV = _ENV
local _G = _G

local HTML_ENTITIES = {
    ["&"] = "&amp;",
    ["<"] = "&lt;",
    [">"] = "&gt;",
    ['"'] = "&quot;",
    ["'"] = "&#39;",
    ["/"] = "&#47;"
}

local CODE_ENTITIES = {
    ["{"] = "&#123;",
    ["}"] = "&#125;",
    ["&"] = "&amp;",
    ["<"] = "&lt;",
    [">"] = "&gt;",
    ['"'] = "&quot;",
    ["'"] = "&#39;",
    ["/"] = "&#47;"
}

local VAR_PHASES

local ok, newtab = pcall(require, "table.new")
if not ok then newtab = function() return {} end end

local caching = true
local template = newtab(0, 12)

template._VERSION = "1.9"
template.cache    = {}

local function enabled(val)
    if val == nil then return true end
    return val == true or (val == "1" or val == "true" or val == "on")
end

local function trim(s)
    return gsub(gsub(s, "^%s+", ""), "%s+$", "")
end

local function rpos(view, s)
    while s > 0 do
        local c = sub(view, s, s)
        if c == " " or c == "\t" or c == "\0" or c == "\x0B" then
            s = s - 1
        else
            break
        end
    end
    return s
end

local function escaped(view, s)
    if s > 1 and sub(view, s - 1, s - 1) == "\\" then
        if s > 2 and sub(view, s - 2, s - 2) == "\\" then
            return false, 1
        else
            return true, 1
        end
    end
    return false, 0
end

local function readfile(path)
    local file = open(path, "rb")
    if not file then return nil end
    local content = file:read "*a"
    file:close()
    return content
end

local function loadlua(path)
    return readfile(path) or path
end

local function loadngx(path)
    local vars = VAR_PHASES[phase()]
    local file, location = path, vars and var.template_location
    if sub(file, 1)  == "/" then file = sub(file, 2) end
    if location and location ~= "" then
        if sub(location, -1) == "/" then location = sub(location, 1, -2) end
        local res = capture(concat{ location, '/', file})
        if res.status == 200 then return res.body end
    end
    local root = vars and (var.template_root or var.document_root) or prefix
    if sub(root, -1) == "/" then root = sub(root, 1, -2) end
    return readfile(concat{ root, "/", file }) or path
end

do
    if ngx then
        VAR_PHASES = {
            set           = true,
            rewrite       = true,
            access        = true,
            content       = true,
            header_filter = true,
            body_filter   = true,
            log           = true
        }
        template.print = ngx.print or write
        template.load  = loadngx
        prefix, var, capture, null, phase = ngx.config.prefix(), ngx.var, ngx.location.capture, ngx.null, ngx.get_phase
        if VAR_PHASES[phase()] then
            caching = enabled(var.template_cache)
        end
    else
        template.print = write
        template.load  = loadlua
    end
    if _VERSION == "Lua 5.1" then
        local context = { __index = function(t, k)
            return t.context[k] or t.template[k] or _G[k]
        end }
        if jit then
            loadchunk = function(view)
                return assert(load(view, nil, nil, setmetatable({ template = template }, context)))
            end
        else
            loadchunk = function(view)
                local func = assert(loadstring(view))
                setfenv(func, setmetatable({ template = template }, context))
                return func
            end
        end
    else
        local context = { __index = function(t, k)
            return t.context[k] or t.template[k] or _ENV[k]
        end }
        loadchunk = function(view)
            return assert(load(view, nil, nil, setmetatable({ template = template }, context)))
        end
    end
end

function template.caching(enable)
    if enable ~= nil then caching = enable == true end
    return caching
end

function template.output(s)
    if s == nil or s == null then return "" end
    if type(s) == "function" then return template.output(s()) end
    return tostring(s)
end

function template.escape(s, c)
    if type(s) == "string" then
        if c then return gsub(s, "[}{\">/<'&]", CODE_ENTITIES) end
        return gsub(s, "[\">/<'&]", HTML_ENTITIES)
    end
    return template.output(s)
end

function template.new(view, layout)
    assert(view, "view was not provided for template.new(view, layout).")
    local render, compile = template.render, template.compile
    if layout then
        if type(layout) == "table" then
            return setmetatable({ render = function(self, context)
                local context = context or self
                context.blocks = context.blocks or {}
                context.view = compile(view)(context)
                layout.blocks = context.blocks or {}
                layout.view = context.view or ""
                return layout:render()
            end }, { __tostring = function(self)
                local context = self
                context.blocks = context.blocks or {}
                context.view = compile(view)(context)
                layout.blocks = context.blocks or {}
                layout.view = context.view
                return tostring(layout)
            end })
        else
            return setmetatable({ render = function(self, context)
                local context = context or self
                context.blocks = context.blocks or {}
                context.view = compile(view)(context)
                return render(layout, context)
            end }, { __tostring = function(self)
                local context = self
                context.blocks = context.blocks or {}
                context.view = compile(view)(context)
                return compile(layout)(context)
            end })
        end
    end
    return setmetatable({ render = function(self, context)
        return render(view, context or self)
    end }, { __tostring = function(self)
        return compile(view)(self)
    end })
end

function template.precompile(view, path, strip)
    local chunk = dump(template.compile(view), strip ~= false)
    if path then
        local file = open(path, "wb")
        file:write(chunk)
        file:close()
    end
    return chunk
end

function template.compile(view, key, plain)
    assert(view, "view was not provided for template.compile(view, key, plain).")
    if key == "no-cache" then
        return loadchunk(template.parse(view, plain)), false
    end
    key = key or view
    local cache = template.cache
    if cache[key] then return cache[key], true end
    local func = loadchunk(template.parse(view, plain))
    if caching then cache[key] = func end
    return func, false
end

function template.parse(view, plain)
    assert(view, "view was not provided for template.parse(view, plain).")
    if not plain then
        view = template.load(view)
        if byte(sub(view, 1, 1)) == 27 then return view end
    end
    local j = 2
    local c = {[[
context=... or {}
local function include(v, c) return template.compile(v)(c or context) end
local ___,blocks,layout={},blocks or {}
]] }
    local i, s = 1, find(view, "{", 1, true)
    while s do
        local t, p = sub(view, s + 1, s + 1), s + 2
        if t == "{" then
            local e = find(view, "}}", p, true)
            if e then
                local z, w = escaped(view, s)
                if i < s - w then
                    c[j] = "___[#___+1]=[=[\n"
                    c[j+1] = sub(view, i, s - 1 - w)
                    c[j+2] = "]=]\n"
                    j=j+3
                end
                if z then
                    i = s
                else
                    c[j] = "___[#___+1]=template.escape("
                    c[j+1] = trim(sub(view, p, e - 1))
                    c[j+2] = ")\n"
                    j=j+3
                    s, i = e + 1, e + 2
                end
            end
        elseif t == "*" then
            local e = find(view, "*}", p, true)
            if e then
                local z, w = escaped(view, s)
                if i < s - w then
                    c[j] = "___[#___+1]=[=[\n"
                    c[j+1] = sub(view, i, s - 1 - w)
                    c[j+2] = "]=]\n"
                    j=j+3
                end
                if z then
                    i = s
                else
                    c[j] = "___[#___+1]=template.output("
                    c[j+1] = trim(sub(view, p, e - 1))
                    c[j+2] = ")\n"
                    j=j+3
                    s, i = e + 1, e + 2
                end
            end
        elseif t == "%" then
            local e = find(view, "%}", p, true)
            if e then
                local z, w = escaped(view, s)
                if z then
                    if i < s - w then
                        c[j] = "___[#___+1]=[=[\n"
                        c[j+1] = sub(view, i, s - 1 - w)
                        c[j+2] = "]=]\n"
                        j=j+3
                    end
                    i = s
                else
                    local n = e + 2
                    if sub(view, n, n) == "\n" then
                        n = n + 1
                    end
                    local r = rpos(view, s - 1)
                    if i <= r then
                        c[j] = "___[#___+1]=[=[\n"
                        c[j+1] = sub(view, i, r)
                        c[j+2] = "]=]\n"
                        j=j+3
                    end
                    c[j] = trim(sub(view, p, e - 1))
                    c[j+1] = "\n"
                    j=j+2
                    s, i = n - 1, n
                end
            end
        elseif t == "(" then
            local e = find(view, ")}", p, true)
            if e then
                local z, w = escaped(view, s)
                if i < s - w then
                    c[j] = "___[#___+1]=[=[\n"
                    c[j+1] = sub(view, i, s - 1 - w)
                    c[j+2] = "]=]\n"
                    j=j+3
                end
                if z then
                    i = s
                else
                    local f = sub(view, p, e - 1)
                    local x = find(f, ",", 2, true)
                    if x then
                        c[j] = "___[#___+1]=include([=["
                        c[j+1] = trim(sub(f, 1, x - 1))
                        c[j+2] = "]=],"
                        c[j+3] = trim(sub(f, x + 1))
                        c[j+4] = ")\n"
                        j=j+5
                    else
                        c[j] = "___[#___+1]=include([=["
                        c[j+1] = trim(f)
                        c[j+2] = "]=])\n"
                        j=j+3
                    end
                    s, i = e + 1, e + 2
                end
            end
        elseif t == "[" then
            local e = find(view, "]}", p, true)
            if e then
                local z, w = escaped(view, s)
                if i < s - w then
                    c[j] = "___[#___+1]=[=[\n"
                    c[j+1] = sub(view, i, s - 1 - w)
                    c[j+2] = "]=]\n"
                    j=j+3
                end
                if z then
                    i = s
                else
                    c[j] = "___[#___+1]=include("
                    c[j+1] = trim(sub(view, p, e - 1))
                    c[j+2] = ")\n"
                    j=j+3
                    s, i = e + 1, e + 2
                end
            end
        elseif t == "-" then
            local e = find(view, "-}", p, true)
            if e then
                local x, y = find(view, sub(view, s, e + 1), e + 2, true)
                if x then
                    local z, w = escaped(view, s)
                    if z then
                        if i < s - w then
                            c[j] = "___[#___+1]=[=[\n"
                            c[j+1] = sub(view, i, s - 1 - w)
                            c[j+2] = "]=]\n"
                            j=j+3
                        end
                        i = s
                    else
                        y = y + 1
                        x = x - 1
                        if sub(view, y, y) == "\n" then
                            y = y + 1
                        end
                        local b = trim(sub(view, p, e - 1))
                        if b == "verbatim" or b == "raw" then
                            if i < s - w then
                                c[j] = "___[#___+1]=[=[\n"
                                c[j+1] = sub(view, i, s - 1 - w)
                                c[j+2] = "]=]\n"
                                j=j+3
                            end
                            c[j] = "___[#___+1]=[=["
                            c[j+1] = sub(view, e + 2, x)
                            c[j+2] = "]=]\n"
                            j=j+3
                        else
                            if sub(view, x, x) == "\n" then
                                x = x - 1
                            end
                            local r = rpos(view, s - 1)
                            if i <= r then
                                c[j] = "___[#___+1]=[=[\n"
                                c[j+1] = sub(view, i, r)
                                c[j+2] = "]=]\n"
                                j=j+3
                            end
                            c[j] = 'blocks["'
                            c[j+1] = b
                            c[j+2] = '"]=include[=['
                            c[j+3] = sub(view, e + 2, x)
                            c[j+4] = "]=]\n"
                            j=j+5
                        end
                        s, i = y - 1, y
                    end
                end
            end
        elseif t == "#" then
            local e = find(view, "#}", p, true)
            if e then
                local z, w = escaped(view, s)
                if i < s - w then
                    c[j] = "___[#___+1]=[=[\n"
                    c[j+1] = sub(view, i, s - 1 - w)
                    c[j+2] = "]=]\n"
                    j=j+3
                end
                if z then
                    i = s
                else
                    e = e + 2
                    if sub(view, e, e) == "\n" then
                        e = e + 1
                    end
                    s, i = e - 1, e
                end
            end
        end
        s = find(view, "{", s + 1, true)
    end
    s = sub(view, i)
    if s and s ~= "" then
        c[j] = "___[#___+1]=[=[\n"
        c[j+1] = s
        c[j+2] = "]=]\n"
        j=j+3
    end
    c[j] = "return layout and include(layout,setmetatable({view=table.concat(___),blocks=blocks},{__index=context})) or table.concat(___)"
    return concat(c)
end

function template.render(view, context, key, plain)
    assert(view, "view was not provided for template.render(view, context, key, plain).")
    return template.print(template.compile(view, key, plain)(context))
end

return template


================================================
FILE: demo13/README.md
================================================
#### 在对接一些第三方系统的时候,经常会遇到https的问题,好比如做微信公众号的开发,接口基本都是https的,这个时候,很多人试着用http的那种方式来访问https,结果报错了,误以为lua不支持https,其实不是的,只需要配置一个证书即可,证书可以通过浏览器访问接口的url,然后通过浏览器导出这个网站所对应的pem证书,然后配置到nginx里面就行了,其他的调用方法跟http的类型,所用到的http库,跟我写的这篇[文章](https://github.com/362228416/openresty-web-dev/tree/master/demo7)一致,就不过多介绍了

nginx.conf
```

worker_processes  1;

error_log logs/error.log notice;

events {
    worker_connections 1024;
}

http {
    lua_ssl_verify_depth 2;
    lua_ssl_trusted_certificate GeoTrust_Global_CA.pem;
    lua_package_path "$prefix/lua/?.lua;$prefix/lualib/?.lua";
    server {
        listen 8888;
        server_name localhost;
        lua_code_cache off;

        location / {
            root html;
            index index.html;
        }

        location ~ /lua/(.+) {
            default_type text/html;
            resolver 223.5.5.5 223.6.6.6;  # 这里位设置阿里的DNS,不设置DNS无法解析http请求的域名
            content_by_lua_file lua/$1.lua;
        }
    }
}
```

为了简单起见,以下只是调一下获取access_key的接口,只要这个可以,同理,微信下单那些也是一样的,这点可以保证,我就用openresty做过微信公众号开发,包含微信登录,微信支付,以及数据库mysql部分全都是lua开发的

lua/test.lua   
```
local req = require "req"
local cjson = require "cjson"
local http = require "resty.http"

function get_access_token(code)
    local httpc = http.new()
    local params = {}
    params['grant_type'] = 'authorization_code'
    params['appid'] = '' -- config.appid
    params['secret'] = '' -- config.secret
    params['code'] = ''
    local res,err = httpc:request_uri("https://api.weixin.qq.com/sns/oauth2/access_token?" .. ngx.encode_args(params), {
        method = "GET",
        headers = {
            ["Accept"] = "application/json",
            ["Accept-Encoding"] = "utf-8",
        }
    })
    print(err)
    httpc:set_keepalive(60)
    return cjson.decode(res.body)
end

local args = req.getArgs()
local code = args['code']
local res = get_access_token(code)
ngx.say(cjson.encode(res))
ngx.say(res.openid)

```

index.html

```
<html>
<head>
	<meta charset="UTF-8">
	<title>Login Page</title>
</head>
<body>
	<a href="javascript:void(0)" onclick="test()">测试</a>
    <pre id="ret"></pre>
	<script src="//cdn.bootcss.com/jquery/2.2.4/jquery.min.js"></script>
	<script>
		function test() {
			$('#ret').load('/lua/test', {code: '123456'})
		}
	</script>
</body>
</html>
```

启动nginx

```
$ openresty -p `pwd`/demo13
```

打开浏览器访问:http://localhost:8888/  点击页面上的测试按钮

应该会返回类似以下这样的东西,说明调用成功了,只是参数有问题而已

```
{"errcode":41002,"errmsg":"appid missing, hints: [ req_id: eMR_KA0444ns88 ]"}
nil
```

到此,你可以用openresty更深层次的跟后端进行整合,开发出更强大的前端应用了,当前开发方式很简单,部署也只需要一个nginx

[示例代码](https://github.com/362228416/openresty-web-dev) 参见demo13部分


================================================
FILE: demo13/conf/GeoTrust_Global_CA.pem
================================================
-----BEGIN CERTIFICATE-----
MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT
MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i
YWwgQ0EwHhcNMDIwNTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQG
EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMSR2VvVHJ1c3Qg
R2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2swYYzD9
9BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjoBbdq
fnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDv
iS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU
1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+
bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5aszPeE4uwc2hGKceeoW
MPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTA
ephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVkDBF9qn1l
uMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKIn
Z57QzxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfS
tQWVYrmm3ok9Nns4d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcF
PseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Un
hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV
5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw==
-----END CERTIFICATE-----


================================================
FILE: demo13/conf/nginx.conf
================================================

worker_processes  1;

error_log logs/error.log notice;

events {
    worker_connections 1024;
}

http {
    lua_ssl_verify_depth 2;
    lua_ssl_trusted_certificate GeoTrust_Global_CA.pem;
    lua_package_path "$prefix/lua/?.lua;$prefix/lualib/?.lua";
    server {
        listen 8888;
        server_name localhost;
        lua_code_cache off;

        location / {
            root html;
            index index.html;
        }

        location ~ /lua/(.+) {
            default_type text/html;
            resolver 223.5.5.5 223.6.6.6;  # 这里位设置阿里的DNS,不设置DNS无法解析http请求的域名
            content_by_lua_file lua/$1.lua;
        }
    }
}


================================================
FILE: demo13/html/index.html
================================================
<html>
<head>
	<meta charset="UTF-8">
	<title>Login Page</title>
</head>
<body>
	<a href="javascript:void(0)" onclick="test()">测试</a>
    <pre id="ret"></pre>
	<script src="//cdn.bootcss.com/jquery/2.2.4/jquery.min.js"></script>
	<script>
		function test() {
			$('#ret').load('/lua/test', {code: '123456'})
		}
	</script>
</body>
</html>


================================================
FILE: demo13/logs/.gitignore
================================================
*.log
*.pid


================================================
FILE: demo13/lua/req.lua
================================================
local _M = {}

-- 获取http get/post 请求参数
function _M.getArgs()
    local request_method = ngx.var.request_method
    local args = ngx.req.get_uri_args()
    -- 参数获取
    if "POST" == request_method then
        ngx.req.read_body()
        local postArgs = ngx.req.get_post_args()
        if postArgs then
            for k, v in pairs(postArgs) do
                args[k] = v
            end
        end
    end
    return args
end

return _M

================================================
FILE: demo13/lua/test.lua
================================================
local req = require "req"
local cjson = require "cjson"
local http = require "resty.http"

function get_access_token(code)
    local httpc = http.new()
    local params = {}
    params['grant_type'] = 'authorization_code'
    params['appid'] = '' -- config.appid
    params['secret'] = '' -- config.secret
    params['code'] = ''
    local res,err = httpc:request_uri("https://api.weixin.qq.com/sns/oauth2/access_token?" .. ngx.encode_args(params), {
        method = "GET",
        headers = {
            ["Accept"] = "application/json",
            ["Accept-Encoding"] = "utf-8",
        }
    })
    print(err)
    httpc:set_keepalive(60)
    return cjson.decode(res.body)
end

local args = req.getArgs()
local code = args['code']
local res = get_access_token(code)
ngx.say(cjson.encode(res))
ngx.say(res.openid)


================================================
FILE: demo13/lualib/resty/http.lua
================================================
local http_headers = require "resty.http_headers"

local ngx_socket_tcp = ngx.socket.tcp
local ngx_req = ngx.req
local ngx_req_socket = ngx_req.socket
local ngx_req_get_headers = ngx_req.get_headers
local ngx_req_get_method = ngx_req.get_method
local str_gmatch = string.gmatch
local str_lower = string.lower
local str_upper = string.upper
local str_find = string.find
local str_sub = string.sub
local str_gsub = string.gsub
local tbl_concat = table.concat
local tbl_insert = table.insert
local ngx_encode_args = ngx.encode_args
local ngx_re_match = ngx.re.match
local ngx_re_gsub = ngx.re.gsub
local ngx_log = ngx.log
local ngx_DEBUG = ngx.DEBUG
local ngx_ERR = ngx.ERR
local ngx_NOTICE = ngx.NOTICE
local ngx_var = ngx.var
local co_yield = coroutine.yield
local co_create = coroutine.create
local co_status = coroutine.status
local co_resume = coroutine.resume


-- http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.1
local HOP_BY_HOP_HEADERS = {
    ["connection"]          = true,
    ["keep-alive"]          = true,
    ["proxy-authenticate"]  = true,
    ["proxy-authorization"] = true,
    ["te"]                  = true,
    ["trailers"]            = true,
    ["transfer-encoding"]   = true,
    ["upgrade"]             = true,
    ["content-length"]      = true, -- Not strictly hop-by-hop, but Nginx will deal
                                    -- with this (may send chunked for example).
}


-- Reimplemented coroutine.wrap, returning "nil, err" if the coroutine cannot
-- be resumed. This protects user code from inifite loops when doing things like
-- repeat
--   local chunk, err = res.body_reader()
--   if chunk then -- <-- This could be a string msg in the core wrap function.
--     ...
--   end
-- until not chunk
local co_wrap = function(func)
    local co = co_create(func)
    if not co then
        return nil, "could not create coroutine"
    else
        return function(...)
            if co_status(co) == "suspended" then
                return select(2, co_resume(co, ...))
            else
                return nil, "can't resume a " .. co_status(co) .. " coroutine"
            end
        end
    end
end


local _M = {
    _VERSION = '0.09',
}
_M._USER_AGENT = "lua-resty-http/" .. _M._VERSION .. " (Lua) ngx_lua/" .. ngx.config.ngx_lua_version

local mt = { __index = _M }


local HTTP = {
    [1.0] = " HTTP/1.0\r\n",
    [1.1] = " HTTP/1.1\r\n",
}

local DEFAULT_PARAMS = {
    method = "GET",
    path = "/",
    version = 1.1,
}


function _M.new(self)
    local sock, err = ngx_socket_tcp()
    if not sock then
        return nil, err
    end
    return setmetatable({ sock = sock, keepalive = true }, mt)
end


function _M.set_timeout(self, timeout)
    local sock = self.sock
    if not sock then
        return nil, "not initialized"
    end

    return sock:settimeout(timeout)
end


function _M.ssl_handshake(self, ...)
    local sock = self.sock
    if not sock then
        return nil, "not initialized"
    end

    self.ssl = true

    return sock:sslhandshake(...)
end


function _M.connect(self, ...)
    local sock = self.sock
    if not sock then
        return nil, "not initialized"
    end

    self.host = select(1, ...)
    self.port = select(2, ...)

    -- If port is not a number, this is likely a unix domain socket connection.
    if type(self.port) ~= "number" then
        self.port = nil
    end

    self.keepalive = true

    return sock:connect(...)
end


function _M.set_keepalive(self, ...)
    local sock = self.sock
    if not sock then
        return nil, "not initialized"
    end

    if self.keepalive == true then
        return sock:setkeepalive(...)
    else
        -- The server said we must close the connection, so we cannot setkeepalive.
        -- If close() succeeds we return 2 instead of 1, to differentiate between
        -- a normal setkeepalive() failure and an intentional close().
        local res, err = sock:close()
        if res then
            return 2, "connection must be closed"
        else
            return res, err
        end
    end
end


function _M.get_reused_times(self)
    local sock = self.sock
    if not sock then
        return nil, "not initialized"
    end

    return sock:getreusedtimes()
end


function _M.close(self)
    local sock = self.sock
    if not sock then
        return nil, "not initialized"
    end

    return sock:close()
end


local function _should_receive_body(method, code)
    if method == "HEAD" then return nil end
    if code == 204 or code == 304 then return nil end
    if code >= 100 and code < 200 then return nil end
    return true
end


function _M.parse_uri(self, uri)
    local m, err = ngx_re_match(uri, [[^(http[s]?)://([^:/]+)(?::(\d+))?(.*)]],
        "jo")

    if not m then
        if err then
            return nil, "failed to match the uri: " .. uri .. ", " .. err
        end

        return nil, "bad uri: " .. uri
    else
        if m[3] then
            m[3] = tonumber(m[3])
        else
            if m[1] == "https" then
                m[3] = 443
            else
                m[3] = 80
            end
        end
        if not m[4] or "" == m[4] then m[4] = "/" end
        return m, nil
    end
end


local function _format_request(params)
    local version = params.version
    local headers = params.headers or {}

    local query = params.query or ""
    if query then
        if type(query) == "table" then
            query = "?" .. ngx_encode_args(query)
        end
    end

    -- Initialize request
    local req = {
        str_upper(params.method),
        " ",
        params.path,
        query,
        HTTP[version],
        -- Pre-allocate slots for minimum headers and carriage return.
        true,
        true,
        true,
    }
    local c = 6 -- req table index it's faster to do this inline vs table.insert

    -- Append headers
    for key, values in pairs(headers) do
        if type(values) ~= "table" then
            values = {values}
        end

        key = tostring(key)
        for _, value in pairs(values) do
            req[c] = key .. ": " .. tostring(value) .. "\r\n"
            c = c + 1
        end
    end

    -- Close headers
    req[c] = "\r\n"

    return tbl_concat(req)
end


local function _receive_status(sock)
    local line, err = sock:receive("*l")
    if not line then
        return nil, nil, nil, err
    end

    return tonumber(str_sub(line, 10, 12)), tonumber(str_sub(line, 6, 8)), str_sub(line, 14)
end



local function _receive_headers(sock)
    local headers = http_headers.new()

    repeat
        local line, err = sock:receive("*l")
        if not line then
            return nil, err
        end

        for key, val in str_gmatch(line, "([^:%s]+):%s*(.+)") do
            if headers[key] then
                if type(headers[key]) ~= "table" then
                    headers[key] = { headers[key] }
                end
                tbl_insert(headers[key], tostring(val))
            else
                headers[key] = tostring(val)
            end
        end
    until str_find(line, "^%s*$")

    return headers, nil
end


local function _chunked_body_reader(sock, default_chunk_size)
    return co_wrap(function(max_chunk_size)
        local max_chunk_size = max_chunk_size or default_chunk_size
        local remaining = 0
        local length

        repeat
            -- If we still have data on this chunk
            if max_chunk_size and remaining > 0 then

                if remaining > max_chunk_size then
                    -- Consume up to max_chunk_size
                    length = max_chunk_size
                    remaining = remaining - max_chunk_size
                else
                    -- Consume all remaining
                    length = remaining
                    remaining = 0
                end
            else -- This is a fresh chunk

                -- Receive the chunk size
                local str, err = sock:receive("*l")
                if not str then
                    co_yield(nil, err)
                end

                length = tonumber(str, 16)

                if not length then
                    co_yield(nil, "unable to read chunksize")
                end

                if max_chunk_size and length > max_chunk_size then
                    -- Consume up to max_chunk_size
                    remaining = length - max_chunk_size
                    length = max_chunk_size
                end
            end

            if length > 0 then
                local str, err = sock:receive(length)
                if not str then
                    co_yield(nil, err)
                end

                max_chunk_size = co_yield(str) or default_chunk_size

                -- If we're finished with this chunk, read the carriage return.
                if remaining == 0 then
                    sock:receive(2) -- read \r\n
                end
            else
                -- Read the last (zero length) chunk's carriage return
                sock:receive(2) -- read \r\n
            end

        until length == 0
    end)
end


local function _body_reader(sock, content_length, default_chunk_size)
    return co_wrap(function(max_chunk_size)
        local max_chunk_size = max_chunk_size or default_chunk_size

        if not content_length and max_chunk_size then
            -- We have no length, but wish to stream.
            -- HTTP 1.0 with no length will close connection, so read chunks to the end.
            repeat
                local str, err, partial = sock:receive(max_chunk_size)
                if not str and err == "closed" then
                    max_chunk_size = tonumber(co_yield(partial, err) or default_chunk_size)
                end

                max_chunk_size = tonumber(co_yield(str) or default_chunk_size)
                if max_chunk_size and max_chunk_size < 0 then max_chunk_size = nil end

                if not max_chunk_size then
                    ngx_log(ngx_ERR, "Buffer size not specified, bailing")
                    break
                end
            until not str

        elseif not content_length then
            -- We have no length but don't wish to stream.
            -- HTTP 1.0 with no length will close connection, so read to the end.
            co_yield(sock:receive("*a"))

        elseif not max_chunk_size then
            -- We have a length and potentially keep-alive, but want everything.
            co_yield(sock:receive(content_length))

        else
            -- We have a length and potentially a keep-alive, and wish to stream
            -- the response.
            local received = 0
            repeat
                local length = max_chunk_size
                if received + length > content_length then
                    length = content_length - received
                end

                if length > 0 then
                    local str, err = sock:receive(length)
                    if not str then
                        max_chunk_size = tonumber(co_yield(nil, err) or default_chunk_size)
                    end
                    received = received + length

                    max_chunk_size = tonumber(co_yield(str) or default_chunk_size)
                    if max_chunk_size and max_chunk_size < 0 then max_chunk_size = nil end

                    if not max_chunk_size then
                        ngx_log(ngx_ERR, "Buffer size not specified, bailing")
                        break
                    end
                end

            until length == 0
        end
    end)
end


local function _no_body_reader()
    return nil
end


local function _read_body(res)
    local reader = res.body_reader

    if not reader then
        -- Most likely HEAD or 304 etc.
        return nil, "no body to be read"
    end

    local chunks = {}
    local c = 1

    local chunk, err
    repeat
        chunk, err = reader()

        if err then
            return nil, err, tbl_concat(chunks) -- Return any data so far.
        end
        if chunk then
            chunks[c] = chunk
            c = c + 1
        end
    until not chunk

    return tbl_concat(chunks)
end


local function _trailer_reader(sock)
    return co_wrap(function()
        co_yield(_receive_headers(sock))
    end)
end


local function _read_trailers(res)
    local reader = res.trailer_reader
    if not reader then
        return nil, "no trailers"
    end

    local trailers = reader()
    setmetatable(res.headers, { __index = trailers })
end


local function _send_body(sock, body)
    if type(body) == 'function' then
        repeat
            local chunk, err, partial = body()

            if chunk then
                local ok,err = sock:send(chunk)

                if not ok then
                    return nil, err
                end
            elseif err ~= nil then
                return nil, err, partial
            end

        until chunk == nil
    elseif body ~= nil then
        local bytes, err = sock:send(body)

        if not bytes then
            return nil, err
        end
    end
    return true, nil
end


local function _handle_continue(sock, body)
    local status, version, reason, err = _receive_status(sock)
    if not status then
        return nil, nil, err
    end

    -- Only send body if we receive a 100 Continue
    if status == 100 then
        local ok, err = sock:receive("*l") -- Read carriage return
        if not ok then
            return nil, nil, err
        end
        _send_body(sock, body)
    end
    return status, version, err
end


function _M.send_request(self, params)
    -- Apply defaults
    setmetatable(params, { __index = DEFAULT_PARAMS })

    local sock = self.sock
    local body = params.body
    local headers = http_headers.new()

    local params_headers = params.headers
    if params_headers then
        -- We assign one by one so that the metatable can handle case insensitivity
        -- for us. You can blame the spec for this inefficiency.
        for k,v in pairs(params_headers) do
            headers[k] = v
        end
    end

    -- Ensure minimal headers are set
    if type(body) == 'string' and not headers["Content-Length"] then
        headers["Content-Length"] = #body
    end
    if not headers["Host"] then
        if (str_sub(self.host, 1, 5) == "unix:") then
            return nil, "Unable to generate a useful Host header for a unix domain socket. Please provide one."
        end
        -- If we have a port (i.e. not connected to a unix domain socket), and this
        -- port is non-standard, append it to the Host heaer.
        if self.port then
            if self.ssl and self.port ~= 443 then
                headers["Host"] = self.host .. ":" .. self.port
            elseif not self.ssl and self.port ~= 80 then
                headers["Host"] = self.host .. ":" .. self.port
            else
                headers["Host"] = self.host
            end
        else
            headers["Host"] = self.host
        end
    end
    if not headers["User-Agent"] then
        headers["User-Agent"] = _M._USER_AGENT
    end
    if params.version == 1.0 and not headers["Connection"] then
        headers["Connection"] = "Keep-Alive"
    end

    params.headers = headers

    -- Format and send request
    local req = _format_request(params)
    ngx_log(ngx_DEBUG, "\n", req)
    local bytes, err = sock:send(req)

    if not bytes then
        return nil, err
    end

    -- Send the request body, unless we expect: continue, in which case
    -- we handle this as part of reading the response.
    if headers["Expect"] ~= "100-continue" then
        local ok, err, partial = _send_body(sock, body)
        if not ok then
            return nil, err, partial
        end
    end

    return true
end


function _M.read_response(self, params)
    local sock = self.sock

    local status, version, reason, err

    -- If we expect: continue, we need to handle this, sending the body if allowed.
    -- If we don't get 100 back, then status is the actual status.
    if params.headers["Expect"] == "100-continue" then
        local _status, _version, _err = _handle_continue(sock, params.body)
        if not _status then
            return nil, _err
        elseif _status ~= 100 then
            status, version, err = _status, _version, _err
        end
    end

    -- Just read the status as normal.
    if not status then
        status, version, reason, err = _receive_status(sock)
        if not status then
            return nil, err
        end
    end


    local res_headers, err = _receive_headers(sock)
    if not res_headers then
        return nil, err
    end

    -- keepalive is true by default. Determine if this is correct or not.
    local ok, connection = pcall(str_lower, res_headers["Connection"])
    if ok then
        if  (version == 1.1 and connection == "close") or
            (version == 1.0 and connection ~= "keep-alive") then
            self.keepalive = false
        end
    else
        -- no connection header
        if version == 1.0 then
            self.keepalive = false
        end
    end

    local body_reader = _no_body_reader
    local trailer_reader, err = nil, nil
    local has_body = false

    -- Receive the body_reader
    if _should_receive_body(params.method, status) then
        local ok, encoding = pcall(str_lower, res_headers["Transfer-Encoding"])
        if ok and version == 1.1 and encoding == "chunked" then
            body_reader, err = _chunked_body_reader(sock)
            has_body = true
        else

            local ok, length = pcall(tonumber, res_headers["Content-Length"])
            if ok then
                body_reader, err = _body_reader(sock, length)
                has_body = true
            end
        end
    end

    if res_headers["Trailer"] then
        trailer_reader, err = _trailer_reader(sock)
    end

    if err then
        return nil, err
    else
        return {
            status = status,
            reason = reason,
            headers = res_headers,
            has_body = has_body,
            body_reader = body_reader,
            read_body = _read_body,
            trailer_reader = trailer_reader,
            read_trailers = _read_trailers,
        }
    end
end


function _M.request(self, params)
    local res, err = self:send_request(params)
    if not res then
        return res, err
    else
        return self:read_response(params)
    end
end


function _M.request_pipeline(self, requests)
    for i, params in ipairs(requests) do
        if params.headers and params.headers["Expect"] == "100-continue" then
            return nil, "Cannot pipeline request specifying Expect: 100-continue"
        end

        local res, err = self:send_request(params)
        if not res then
            return res, err
        end
    end

    local responses = {}
    for i, params in ipairs(requests) do
        responses[i] = setmetatable({
            params = params,
            response_read = false,
        }, {
            -- Read each actual response lazily, at the point the user tries
            -- to access any of the fields.
            __index = function(t, k)
                local res, err
                if t.response_read == false then
                    res, err = _M.read_response(self, t.params)
                    t.response_read = true

                    if not res then
                        ngx_log(ngx_ERR, err)
                    else
                        for rk, rv in pairs(res) do
                            t[rk] = rv
                        end
                    end
                end
                return rawget(t, k)
            end,
        })
    end
    return responses
end


function _M.request_uri(self, uri, params)
    if not params then params = {} end

    local parsed_uri, err = self:parse_uri(uri)
    if not parsed_uri then
        return nil, err
    end

    local scheme, host, port, path = unpack(parsed_uri)
    if not params.path then params.path = path end

    local c, err = self:connect(host, port)
    if not c then
        return nil, err
    end

    if scheme == "https" then
        local verify = true
        if params.ssl_verify == false then
            verify = false
        end
        local ok, err = self:ssl_handshake(nil, host, verify)
        if not ok then
            return nil, err
        end
    end

    local res, err = self:request(params)
    if not res then
        return nil, err
    end

    local body, err = res:read_body()
    if not body then
        return nil, err
    end

    res.body = body

    local ok, err = self:set_keepalive()
    if not ok then
        ngx_log(ngx_ERR, err)
    end

    return res, nil
end


function _M.get_client_body_reader(self, chunksize, sock)
    local chunksize = chunksize or 65536
    if not sock then
        local ok, err
        ok, sock, err = pcall(ngx_req_socket)

        if not ok then
            return nil, sock -- pcall err
        end

        if not sock then
            if err == "no body" then
                return nil
            else
                return nil, err
            end
        end
    end

    local headers = ngx_req_get_headers()
    local length = headers.content_length
    local encoding = headers.transfer_encoding
    if length then
        return _body_reader(sock, tonumber(length), chunksize)
    elseif encoding and str_lower(encoding) == 'chunked' then
        -- Not yet supported by ngx_lua but should just work...
        return _chunked_body_reader(sock, chunksize)
    else
       return nil
    end
end


function _M.proxy_request(self, chunksize)
    return self:request{
        method = ngx_req_get_method(),
        path = ngx_re_gsub(ngx_var.uri, "\\s", "%20", "jo") .. ngx_var.is_args .. (ngx_var.query_string or ""),
        body = self:get_client_body_reader(chunksize),
        headers = ngx_req_get_headers(),
    }
end


function _M.proxy_response(self, response, chunksize)
    if not response then
        ngx_log(ngx_ERR, "no response provided")
        return
    end

    ngx.status = response.status

    -- Filter out hop-by-hop headeres
    for k,v in pairs(response.headers) do
        if not HOP_BY_HOP_HEADERS[str_lower(k)] then
            ngx.header[k] = v
        end
    end

    local reader = response.body_reader
    repeat
        local chunk, err = reader(chunksize)
        if err then
            ngx_log(ngx_ERR, err)
            break
        end

        if chunk then
            local res, err = ngx.print(chunk)
            if not res then
                ngx_log(ngx_ERR, err)
                break
            end
        end
    until not chunk
end


return _M


================================================
FILE: demo13/lualib/resty/http_headers.lua
================================================
local   rawget, rawset, setmetatable =
        rawget, rawset, setmetatable

local str_gsub = string.gsub
local str_lower = string.lower


local _M = {
    _VERSION = '0.01',
}


-- Returns an empty headers table with internalised case normalisation.
-- Supports the same cases as in ngx_lua:
--
-- headers.content_length
-- headers["content-length"]
-- headers["Content-Length"]
function _M.new(self)
    local mt = { 
        normalised = {},
    }


    mt.__index = function(t, k)
        local k_hyphened = str_gsub(k, "_", "-")
        local matched = rawget(t, k)
        if matched then
            return matched
        else
            local k_normalised = str_lower(k_hyphened)
            return rawget(t, mt.normalised[k_normalised])
        end
    end


    -- First check the normalised table. If there's no match (first time) add an entry for
    -- our current case in the normalised table. This is to preserve the human (prettier) case
    -- instead of outputting lowercased header names.
    --
    -- If there's a match, we're being updated, just with a different case for the key. We use
    -- the normalised table to give us the original key, and perorm a rawset().
    mt.__newindex = function(t, k, v)
        -- we support underscore syntax, so always hyphenate.
        local k_hyphened = str_gsub(k, "_", "-")

        -- lowercase hyphenated is "normalised"
        local k_normalised = str_lower(k_hyphened)

        if not mt.normalised[k_normalised] then
            mt.normalised[k_normalised] = k_hyphened
            rawset(t, k_hyphened, v)
        else
            rawset(t, mt.normalised[k_normalised], v)
        end
    end

    return setmetatable({}, mt)
end


return _M


================================================
FILE: demo14/README.md
================================================
有的时候我们希望在启动openresty的时候,在后台运行一些定时任务,可以放在`init_worker_by_lua_block`阶段运行,如果想重复执行可以通过不断`ngx.timer.at`来实现多次调用

需要注意的是`init_worker_by_lua_block`会在每个worker启动的时候都会运行,当你的worker_processes配置为1的时候,没有任何问题,但是当你worker_processes配置为大于1的时候,会在后台运行多个定时任务,如果你的任务可以重复执行,那还没关系,假如不能的话,就有点问题了。

为了解决配置多个worker会启动多个任务的问题,需要有一种机制就是即使这段代码会重复运行,但是也只能启动一个定时任务,那么就需要多个worker进行排他处理

我们可以配置一个`lua_shared_dict`共享字典,这个字典在多个worker之间共享,有了这个就好办了,只需要在第一个worker启动完成之后,在内存里面设置一个字段,标识任务已经启动,那么其他worker启动的时候发现已经启动了一个定时任务,不再启动就可以了。

大多数情况下这样就已经可以了,但是lua_shared_dict保存的数据的生命周期是即使在nginx -s reload 的时候它还是会继续存在的,并不会消失,除非stop。

而nginx -s reload 又会导致旧worker被结束,新worker被启动,但是又由于之前在共享内存里面保存了已启动标识状态,导致新的worker不能启动定时任务,知道了原因解决起来就很简单了,只需要在第一worker启动定时任务的时候延迟一小会,把共享字典里面的值重置了就行了,这样下次reload的时候就相当于第一次启动,ok 完美。。

conf/nginx.conf
```

worker_processes  3;

error_log logs/error.log notice;

events {
    worker_connections 1024;
}

http {
    lua_package_path "$prefix/lua/?.lua;$prefix/lualib/?.lua";

	lua_shared_dict task 1m;
    init_worker_by_lua_block {
        local task = ngx.shared.task
        local time = 2
        local count = task:incr("invoke", 1, 0)
        if count == 1 then
            local timer_at = ngx.timer.at
            function do_some_thing()
                print("do some thing")
                timer_at(time, do_some_thing)
            end
            timer_at(time, do_some_thing)
            timer_at(5, function()
                task:set("invoke", 0)
            end
            )
        end
    }

    server {
        listen 8888;
        server_name localhost;
        lua_code_cache off;

        location / {
            default_type text/html;
            content_by_lua_block {
                ngx.say('定时任务')
            }
        }
    }
}

```

[示例代码](https://github.com/362228416/openresty-web-dev) 参见demo14部分



================================================
FILE: demo14/conf/nginx.conf
================================================
worker_processes  3;

error_log logs/error.log notice;

events {
    worker_connections 1024;
}

http {
    lua_package_path "$prefix/lua/?.lua;$prefix/lualib/?.lua";

    lua_shared_dict task 1m;
    init_worker_by_lua_block {
        local task = ngx.shared.task
        local time = 2
        local count = task:incr("invoke", 1, 0)
        if count == 1 then
            local timer_at = ngx.timer.at
            function do_some_thing()
                print("do some thing")
                timer_at(time, do_some_thing)
            end
            timer_at(time, do_some_thing)
            timer_at(5, function()
                task:set("invoke", 0)
            end
            )
        end
    }

    server {
        listen 8888;
        server_name localhost;
        lua_code_cache off;

        location / {
            default_type text/html;
            content_by_lua_block {
                ngx.say('定时任务')
            }
        }
    }
}

================================================
FILE: demo14/logs/.gitignore
================================================
*.log
*.pid


================================================
FILE: demo15/README.md
================================================
以前用tengine自带了session_sticky,现在换成openresty,没有现成的,nginx-sticky-module 太老, 编译有点问题,于是自己写了一个,废话不多说,直接看代码

lua/balancer.lua 
```lua

local balancer = require "ngx.balancer"
local upstream = require "ngx.upstream"

local upstream_name = 'backend'

local srvs = upstream.get_servers(upstream_name)

function get_server()
    local cache = ngx.shared.cache
    local key = "req_index"
    local index = cache:get(key)
    if index == nil or index > #srvs then
        index = 1
        cache:set(key, index)
    end
    cache:incr(key, 1)
    return index
end

function is_down(server)
    local down = false
    local perrs = upstream.get_primary_peers(upstream_name)
    for i = 1, #perrs do
        local peer = perrs[i]
        if server == peer.name and peer.down == true then
            down = true
        end
    end
    return down
end

----------------------------

local route = ngx.var.cookie_route

local server

if route then
    for k, v in pairs(srvs) do
        if ngx.md5(v.name) == route then
            server = v.addr
        end
    end
    if is_down(server) then
        route = nil
    end
end

if not route then
    for i = 1, #srvs do
        if not server or is_down(server) then
            server = srvs[get_server()].addr
        end
    end
    ngx.header["Set-Cookie"] = 'route=' .. ngx.md5(server) .. '; path=/;'
end

local index = string.find(server, ':')
local host = string.sub(server, 1, index - 1)
local port = string.sub(server, index + 1)
balancer.set_current_peer(host, tonumber(port))

```

nginx.conf
```
lua_shared_dict cache 1m;
upstream backend {
        server 192.168.0.2:8080;
        server 192.168.0.3:8080; 
        balancer_by_lua_file lua/balancer.lua;
} 

server {
        listen 80;
        server_name    localhost;
        location / {
            proxy_pass http://backend;
            ...
        }
        
}

# 健康检查
lua_shared_dict healthcheck 1m;
lua_socket_log_errors off;    
init_worker_by_lua_block {
        local hc = require "resty.upstream.healthcheck"
        local ok, err = hc.spawn_checker{
            shm = "healthcheck",  -- defined by "lua_shared_dict"
            upstream = "backend", -- defined by "upstream"
            type = "http",

            http_req = "GET / HTTP/1.0\r\nHost: localhost\r\n\r\n",
                    -- raw HTTP request for checking

            interval = 2000,  -- run the check cycle every 2 sec
            timeout = 1000,   -- 1 sec is the timeout for network operations
            fall = 3,  -- # of successive failures before turning a peer down
            rise = 2,  -- # of successive successes before turning a peer up
            valid_statuses = {200, 302},  -- a list valid HTTP status code
            concurrency = 10,  -- concurrency level for test requests
        }
        if not ok then
            ngx.log(ngx.ERR, "failed to spawn health checker: ", err)
            return
        end
    }

```



主要是利用`ngx.upstream`、`ngx.balancer` 这两个模块,动态获取upstream,以及设置返回的上游名单,然后写到cookie里面,这里有判断后端是否down掉,如果down掉的话,则获取下一个,后端的状态通过`resty.upstream.healthcheck`模块健康检查来实现,以此为模型可以写更复杂的负载均衡逻辑

我这里比较简单,嫌丑了


================================================
FILE: demo16/README.md
================================================

# 这篇文章主要介绍,怎么用lua实现单一文件入口

之前的例子都需要在nginx.conf文件中,单独配置静态文件location,以及lua_package_path,其实可以把这些整合到一个入口文件

## 几个关键指令和api

set_by_lua_file

access_by_lua_file

ngx.config.prefix()

package.path

ngx.get_phase()

## 实现思路

在一个location / 里面处理所有请求,所以原型配置会是这样 

```
location / {
    default_type "text/html; charset=utf-8";
    content_by_lua_file lua/web/mvc.lua;
}
```

我想要访问静态资源的时候使用nginx默认的处理机制,也就是指定root 目录,所以配置文件成了下面的样子

```
location / {
    default_type "text/html; charset=utf-8";
    access_by_lua_file lua/web/mvc.lua;
    root html;
}
```

最后我想用lua来配置root目录,那样就不需要改nginx.conf配置文件,最后变成这样

```
location / {
    default_type "text/html; charset=utf-8";
    set_by_lua_file $root lua/web/mvc.lua;
    access_by_lua_file lua/web/mvc.lua;
    root $root;
}
```

这样的话,所有的处理都指向了mvc.lua文件,接下来看lua端怎么实现

首先我们需要知道lua package加载路径,否则引入自定义的lua模块可能会失败

代码很简单

```lua
local package = package
local pack_path = package.path
local prefix = ngx.config.prefix()
local p = prefix .. "lualib/?.lua;" .. prefix .. "lua/?.lua;;" .. pack_path
package.path = p
```

先获取package默认加载路径,再获取当前项目安装目录,最后组装成新的package覆盖原来的package.path即可实现在nginx.conf里面配置lua_package_path一致的效果


接下来处理set_by_lua_file,通过ngx.get_phase()获取到当前执行环境是set,既set_by_*阶段


```lua
local phase = ngx.get_phase()

-- 设置root环境变量
if phase == 'set' then
    local global_config = require "global_config"
    return global_config.baseDir
end
```

这样外面的root值就动态指定,但是因为配置了access_by_lua_file所以静态资源请求,还是会执行这个lua文件,这里只需要忽略掉就好了,直接return

```lua
-- 静态文件
if paths[1] == 'image' or paths[1] == 'style' or paths[1] == 'js' then
    return
end

```


最后就是lua模块渲染了,这里只是随便写一个demo,仅供参考

```lua
local template = require "web.template"

local router = {}

router['/'] = function()
    local ctx = {}
    template.render('index.html', ctx)
end


local ctl = router[uri]

if ctl ~= nil then
    pcall(ctl)
else
    ngx.exit(404)
end
```

执行命令

```bash
cd demo16
openresty -p . -c conf/nginx.conf
```

浏览器访问 http://localhost:8080/

页面是由lua渲染的,可以看得出index.html里面有模板代码,都被替换了,然后js文件也能正常加载,点击hello按钮会弹出hello

到这里基本就结束了,发挥你的想象力吧,lua真的很强




================================================
FILE: demo16/conf/nginx.conf
================================================

worker_processes  1;

error_log logs/error.log notice;

events {
    worker_connections 1024;
}

http {

    server {
        listen 8080;
        server_name localhost;
        lua_code_cache off;

        location / {
        	default_type "text/html; charset=utf-8";
        	set_by_lua_file $root lua/web/mvc.lua;
        	access_by_lua_file lua/web/mvc.lua;
        	root $root;
        }

    }

}


================================================
FILE: demo16/html/index.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{*site_name*}</title>
    <script type="text/javascript" src="{*host*}/js/index.js"></script>
</head>
<body>

<button onclick="hello()">hello</button>


</body>
</html>

================================================
FILE: demo16/html/js/index.js
================================================

function hello() {
    alert('hello')
}

================================================
FILE: demo16/logs/.gitignore
================================================
*.log
*.pid


================================================
FILE: demo16/lua/global_config.lua
================================================

-- 全站配置

local _M = {
    __version = 0.1
}

-- 文件路径

_M.baseDir = "html"


--站点配置信息

_M.siteConfig = {
    site_name = '网站名称',
    domain = 'localhost:8080',
    host = 'http://localhost:8080'
}


return _M

================================================
FILE: demo16/lua/utils.lua
================================================

local _M = {
    __version = 0.1
}

-- 分隔字符串
function split( str,reps )
    local resultStrList = {}
    string.gsub(str,'[^'..reps..']+',function ( w )
        table.insert(resultStrList,w)
    end)
    return resultStrList
end


_M.split = split

return _M

================================================
FILE: demo16/lua/web/mvc.lua
================================================
local package = package
local pack_path = package.path
local prefix = ngx.config.prefix()
local p = prefix .. "lualib/?.lua;" .. prefix .. "lua/?.lua;;" .. pack_path
package.path = p

local utils = require "utils"
local uri = ngx.var.uri

local paths = utils.split(uri, '/')

local phase = ngx.get_phase()

-- 设置root环境变量
if phase == 'set' then
    local global_config = require "global_config"
    return global_config.baseDir
end

--ngx.say(uri)

-- 静态文件
if paths[1] == 'image' or paths[1] == 'style' or paths[1] == 'js' then
    return
end


local template = require "web.template"

local router = {}

router['/'] = function()
    local ctx = {}
    template.render('index.html', ctx)
end


local ctl = router[uri]

if ctl ~= nil then
    pcall(ctl)
else
    ngx.exit(404)
end



================================================
FILE: demo16/lua/web/template.lua
================================================
local template = require "resty.template"
local global_config = require "global_config"

local _M = {
    __version = 0.1
}

function _M.render(view, context, key, plain)
    if context == nil then
        context = {}
    end
    for k, v in pairs(global_config.siteConfig) do
        context[k] = v
    end
    return template.render(view, context, key, plain)
end

return _M

================================================
FILE: demo16/lualib/resty/template.lua
================================================
local setmetatable = setmetatable
local loadstring = loadstring
local loadchunk
local tostring = tostring
local setfenv = setfenv
local require = require
local capture
local concat = table.concat
local assert = assert
local prefix
local write = io.write
local pcall = pcall
local phase
local open = io.open
local load = load
local type = type
local dump = string.dump
local find = string.find
local gsub = string.gsub
local byte = string.byte
local null
local sub = string.sub
local ngx = ngx
local jit = jit
local var

local _VERSION = _VERSION
local _ENV = _ENV
local _G = _G

local HTML_ENTITIES = {
    ["&"] = "&amp;",
    ["<"] = "&lt;",
    [">"] = "&gt;",
    ['"'] = "&quot;",
    ["'"] = "&#39;",
    ["/"] = "&#47;"
}

local CODE_ENTITIES = {
    ["{"] = "&#123;",
    ["}"] = "&#125;",
    ["&"] = "&amp;",
    ["<"] = "&lt;",
    [">"] = "&gt;",
    ['"'] = "&quot;",
    ["'"] = "&#39;",
    ["/"] = "&#47;"
}

local VAR_PHASES

local ok, newtab = pcall(require, "table.new")
if not ok then newtab = function() return {} end end

local caching = true
local template = newtab(0, 12)

template._VERSION = "1.9"
template.cache    = {}

local function enabled(val)
    if val == nil then return true end
    return val == true or (val == "1" or val == "true" or val == "on")
end

local function trim(s)
    return gsub(gsub(s, "^%s+", ""), "%s+$", "")
end

local function rpos(view, s)
    while s > 0 do
        local c = sub(view, s, s)
        if c == " " or c == "\t" or c == "\0" or c == "\x0B" then
            s = s - 1
        else
            break
        end
    end
    return s
end

local function escaped(view, s)
    if s > 1 and sub(view, s - 1, s - 1) == "\\" then
        if s > 2 and sub(view, s - 2, s - 2) == "\\" then
            return false, 1
        else
            return true, 1
        end
    end
    return false, 0
end

local function readfile(path)
    local file = open(path, "rb")
    if not file then return nil end
    local content = file:read "*a"
    file:close()
    return content
end

local function loadlua(path)
    return readfile(path) or path
end

local function loadngx(path)
    local vars = VAR_PHASES[phase()]
    local file, location = path, vars and var.template_location
    if sub(file, 1)  == "/" then file = sub(file, 2) end
    if location and location ~= "" then
        if sub(location, -1) == "/" then location = sub(location, 1, -2) end
        local res = capture(concat{ location, '/', file})
        if res.status == 200 then return res.body end
    end
    local root = vars and (var.template_root or var.document_root) or prefix
    if sub(root, -1) == "/" then root = sub(root, 1, -2) end
    return readfile(concat{ root, "/", file }) or path
end

do
    if ngx then
        VAR_PHASES = {
            set           = true,
            rewrite       = true,
            access        = true,
            content       = true,
            header_filter = true,
            body_filter   = true,
            log           = true
        }
        template.print = ngx.print or write
        template.load  = loadngx
        prefix, var, capture, null, phase = ngx.config.prefix(), ngx.var, ngx.location.capture, ngx.null, ngx.get_phase
        if VAR_PHASES[phase()] then
            caching = enabled(var.template_cache)
        end
    else
        template.print = write
        template.load  = loadlua
    end
    if _VERSION == "Lua 5.1" then
        local context = { __index = function(t, k)
            return t.context[k] or t.template[k] or _G[k]
        end }
        if jit then
            loadchunk = function(view)
                return assert(load(view, nil, nil, setmetatable({ template = template }, context)))
            end
        else
            loadchunk = function(view)
                local func = assert(loadstring(view))
                setfenv(func, setmetatable({ template = template }, context))
                return func
            end
        end
    else
        local context = { __index = function(t, k)
            return t.context[k] or t.template[k] or _ENV[k]
        end }
        loadchunk = function(view)
            return assert(load(view, nil, nil, setmetatable({ template = template }, context)))
        end
    end
end

function template.caching(enable)
    if enable ~= nil then caching = enable == true end
    return caching
end

function template.output(s)
    if s == nil or s == null then return "" end
    if type(s) == "function" then return template.output(s()) end
    return tostring(s)
end

function template.escape(s, c)
    if type(s) == "string" then
        if c then return gsub(s, "[}{\">/<'&]", CODE_ENTITIES) end
        return gsub(s, "[\">/<'&]", HTML_ENTITIES)
    end
    return template.output(s)
end

function template.new(view, layout)
    assert(view, "view was not provided for template.new(view, layout).")
    local render, compile = template.render, template.compile
    if layout then
        if type(layout) == "table" then
            return setmetatable({ render = function(self, context)
                local context = context or self
                context.blocks = context.blocks or {}
                context.view = compile(view)(context)
                layout.blocks = context.blocks or {}
                layout.view = context.view or ""
                return layout:render()
            end }, { __tostring = function(self)
                local context = self
                context.blocks = context.blocks or {}
                context.view = compile(view)(context)
                layout.blocks = context.blocks or {}
                layout.view = context.view
                return tostring(layout)
            end })
        else
            return setmetatable({ render = function(self, context)
                local context = context or self
                context.blocks = context.blocks or {}
                context.view = compile(view)(context)
                return render(layout, context)
            end }, { __tostring = function(self)
                local context = self
                context.blocks = context.blocks or {}
                context.view = compile(view)(context)
                return compile(layout)(context)
            end })
        end
    end
    return setmetatable({ render = function(self, context)
        return render(view, context or self)
    end }, { __tostring = function(self)
        return compile(view)(self)
    end })
end

function template.precompile(view, path, strip)
    local chunk = dump(template.compile(view), strip ~= false)
    if path then
        local file = open(path, "wb")
        file:write(chunk)
        file:close()
    end
    return chunk
end

function template.compile(view, key, plain)
    assert(view, "view was not provided for template.compile(view, key, plain).")
    if key == "no-cache" then
        return loadchunk(template.parse(view, plain)), false
    end
    key = key or view
    local cache = template.cache
    if cache[key] then return cache[key], true end
    local func = loadchunk(template.parse(view, plain))
    if caching then cache[key] = func end
    return func, false
end

function template.parse(view, plain)
    assert(view, "view was not provided for template.parse(view, plain).")
    if not plain then
        view = template.load(view)
        if byte(sub(view, 1, 1)) == 27 then return view end
    end
    local j = 2
    local c = {[[
context=... or {}
local function include(v, c) return template.compile(v)(c or context) end
local ___,blocks,layout={},blocks or {}
]] }
    local i, s = 1, find(view, "{", 1, true)
    while s do
        local t, p = sub(view, s + 1, s + 1), s + 2
        if t == "{" then
            local e = find(view, "}}", p, true)
            if e then
                local z, w = escaped(view, s)
                if i < s - w then
                    c[j] = "___[#___+1]=[=[\n"
                    c[j+1] = sub(view, i, s - 1 - w)
                    c[j+2] = "]=]\n"
                    j=j+3
                end
                if z then
                    i = s
                else
                    c[j] = "___[#___+1]=template.escape("
                    c[j+1] = trim(sub(view, p, e - 1))
                    c[j+2] = ")\n"
                    j=j+3
                    s, i = e + 1, e + 2
                end
            end
        elseif t == "*" then
            local e = find(view, "*}", p, true)
            if e then
                local z, w = escaped(view, s)
                if i < s - w then
                    c[j] = "___[#___+1]=[=[\n"
                    c[j+1] = sub(view, i, s - 1 - w)
                    c[j+2] = "]=]\n"
                    j=j+3
                end
                if z then
                    i = s
                else
                    c[j] = "___[#___+1]=template.output("
                    c[j+1] = trim(sub(view, p, e - 1))
                    c[j+2] = ")\n"
                    j=j+3
                    s, i = e + 1, e + 2
                end
            end
        elseif t == "%" then
            local e = find(view, "%}", p, true)
            if e then
                local z, w = escaped(view, s)
                if z then
                    if i < s - w then
                        c[j] = "___[#___+1]=[=[\n"
                        c[j+1] = sub(view, i, s - 1 - w)
                        c[j+2] = "]=]\n"
                        j=j+3
                    end
                    i = s
                else
                    local n = e + 2
                    if sub(view, n, n) == "\n" then
                        n = n + 1
                    end
                    local r = rpos(view, s - 1)
                    if i <= r then
                        c[j] = "___[#___+1]=[=[\n"
                        c[j+1] = sub(view, i, r)
                        c[j+2] = "]=]\n"
                        j=j+3
                    end
                    c[j] = trim(sub(view, p, e - 1))
                    c[j+1] = "\n"
                    j=j+2
                    s, i = n - 1, n
                end
            end
        elseif t == "(" then
            local e = find(view, ")}", p, true)
            if e then
                local z, w = escaped(view, s)
                if i < s - w then
                    c[j] = "___[#___+1]=[=[\n"
                    c[j+1] = sub(view, i, s - 1 - w)
                    c[j+2] = "]=]\n"
                    j=j+3
                end
                if z then
                    i = s
                else
                    local f = sub(view, p, e - 1)
                    local x = find(f, ",", 2, true)
                    if x then
                        c[j] = "___[#___+1]=include([=["
                        c[j+1] = trim(sub(f, 1, x - 1))
                        c[j+2] = "]=],"
                        c[j+3] = trim(sub(f, x + 1))
                        c[j+4] = ")\n"
                        j=j+5
                    else
                        c[j] = "___[#___+1]=include([=["
                        c[j+1] = trim(f)
                        c[j+2] = "]=])\n"
                        j=j+3
                    end
                    s, i = e + 1, e + 2
                end
            end
        elseif t == "[" then
            local e = find(view, "]}", p, true)
            if e then
                local z, w = escaped(view, s)
                if i < s - w then
                    c[j] = "___[#___+1]=[=[\n"
                    c[j+1] = sub(view, i, s - 1 - w)
                    c[j+2] = "]=]\n"
                    j=j+3
                end
                if z then
                    i = s
                else
                    c[j] = "___[#___+1]=include("
                    c[j+1] = trim(sub(view, p, e - 1))
                    c[j+2] = ")\n"
                    j=j+3
                    s, i = e + 1, e + 2
                end
            end
        elseif t == "-" then
            local e = find(view, "-}", p, true)
            if e then
                local x, y = find(view, sub(view, s, e + 1), e + 2, true)
                if x then
                    local z, w = escaped(view, s)
                    if z then
                        if i < s - w then
                            c[j] = "___[#___+1]=[=[\n"
                            c[j+1] = sub(view, i, s - 1 - w)
                            c[j+2] = "]=]\n"
                            j=j+3
                        end
                        i = s
                    else
                        y = y + 1
                        x = x - 1
                        if sub(view, y, y) == "\n" then
                            y = y + 1
                        end
                        local b = trim(sub(view, p, e - 1))
                        if b == "verbatim" or b == "raw" then
                            if i < s - w then
                                c[j] = "___[#___+1]=[=[\n"
                                c[j+1] = sub(view, i, s - 1 - w)
                                c[j+2] = "]=]\n"
                                j=j+3
                            end
                            c[j] = "___[#___+1]=[=["
                            c[j+1] = sub(view, e + 2, x)
                            c[j+2] = "]=]\n"
                            j=j+3
                        else
                            if sub(view, x, x) == "\n" then
                                x = x - 1
                            end
                            local r = rpos(view, s - 1)
                            if i <= r then
                                c[j] = "___[#___+1]=[=[\n"
                                c[j+1] = sub(view, i, r)
                                c[j+2] = "]=]\n"
                                j=j+3
                            end
                            c[j] = 'blocks["'
                            c[j+1] = b
                            c[j+2] = '"]=include[=['
                            c[j+3] = sub(view, e + 2, x)
                            c[j+4] = "]=]\n"
                            j=j+5
                        end
                        s, i = y - 1, y
                    end
                end
            end
        elseif t == "#" then
            local e = find(view, "#}", p, true)
            if e then
                local z, w = escaped(view, s)
                if i < s - w then
                    c[j] = "___[#___+1]=[=[\n"
                    c[j+1] = sub(view, i, s - 1 - w)
                    c[j+2] = "]=]\n"
                    j=j+3
                end
                if z then
                    i = s
                else
                    e = e + 2
                    if sub(view, e, e) == "\n" then
                        e = e + 1
                    end
                    s, i = e - 1, e
                end
            end
        end
        s = find(view, "{", s + 1, true)
    end
    s = sub(view, i)
    if s and s ~= "" then
        c[j] = "___[#___+1]=[=[\n"
        c[j+1] = s
        c[j+2] = "]=]\n"
        j=j+3
    end
    c[j] = "return layout and include(layout,setmetatable({view=table.concat(___),blocks=blocks},{__index=context})) or table.concat(___)"
    return concat(c)
end

function template.render(view, context, key, plain)
    assert(view, "view was not provided for template.render(view, context, key, plain).")
    return template.print(template.compile(view, key, plain)(context))
end

return template


================================================
FILE: demo2/README.md
================================================
#### 这一章主要介绍介绍怎么获取请求参数,并且处理之后返回数据

我们知道http请求通常分为两种,分别是GET,POST,在http协议中,GET参数通常会紧跟在uri后面,而POST请求参数则包含在请求体中,nginx默认情况下是不会读取POST请求参数的,最好也不要试图使改变这种行为,因为大多数情况下,POST请求都是转到后端去处理,nginx只需要读取请求uri部分,以及请求头

由于这样的设计,所以获取请求参数的方式也有两种

GET 
```
local args = ngx.req.get_uri_args() -- 这里是一个table,包含所有get请求参数
local id = ngx.var.arg_id -- 这里获取单个请求参数,但是如果没有传递这个参数,则会报错,推荐上面那张获取方式
```

POST
```
ngx.req.read_body() -- 先读取请求体
local args = ngx.req.get_post_args() -- 这里也是一个table,包含所有post请求参数
```

可以通过下面这个方法获取http请求方法
```
local request_method = ngx.var.request_method -- GET or POST
```

为了统一获取请求参数的方式,隐藏具体细节,提供一个更友好的api接口,我们可以简单的封装一下

lua/req.lua
```
local _M = {}

-- 获取http get/post 请求参数
function _M.getArgs()
    local request_method = ngx.var.request_method
    local args = ngx.req.get_uri_args()
    -- 参数获取
    if "POST" == request_method then
        ngx.req.read_body()
        local postArgs = ngx.req.get_post_args()
        if postArgs then
            for k, v in pairs(postArgs) do
                args[k] = v
            end
        end
    end
    return args
end

return _M
```

这个模块就实现了参数的获取,而且支持GET,POST两种传参方式,以及参数放在uri,body的post请求,会合并两种方式提交的参数

接下来我们可以写一个简单的lua,来引入这个模块,然后测试一下效果

conf/nginx.conf
```
worker_processes  1;

error_log logs/error.log;

events {
    worker_connections 1024;
}

http {
    lua_package_path /Users/Lin/opensource/openresty-web-dev/demo2/lua/?.lua;  # 这里一定要指定package_path,否则会找不到引入的模块,然后会500
    server {
        listen 80;
        server_name localhost;
        lua_code_cache off;
        location ~ /lua/(.+) {
        	default_type text/html;	
		    content_by_lua_file lua/$1.lua;
		}
    }
}
```

lua/hello.lua
```
local req = require "req"

local args = req.getArgs()

local name = args['name']

if name == nil or name == "" then
	name = "guest"
end

ngx.say("<p>hello " .. name .. "!</p>")

```

测试

http://localhost/lua/hello?name=Lin
输出 hello Lin!
http://localhost/lua/hello

输出 hello guest!

ok 到这里,我们已经能够根据请求的参数,并且在做一下处理后返回数据了

[示例代码](https://github.com/362228416/openresty-web-dev)  参见demo2部分

================================================
FILE: demo2/conf/nginx.conf
================================================

worker_processes  1;

error_log logs/error.log;

events {
    worker_connections 1024;
}

http {
    lua_package_path "$prefix/lua/?.lua";
    server {
        listen 80;
        server_name localhost;
        lua_code_cache off;
        location ~ /lua/(.+) {
        	default_type text/html;
		    content_by_lua_file lua/$1.lua;
		}
    }
}


================================================
FILE: demo2/logs/.gitignore
================================================
*.log
*.pid


================================================
FILE: demo2/lua/hello.lua
================================================

local req = require "req"

local args = req.getArgs()

local name = args['name']

if name == nil or name == "" then
	name = "guest"
end

ngx.say("<p>hello, " .. name .. "!</p>")

================================================
FILE: demo2/lua/req.lua
================================================
local _M = {}

-- 获取http get/post 请求参数
function _M.getArgs()
    local request_method = ngx.var.request_method
    local args = ngx.req.get_uri_args()
    -- 参数获取
    if "POST" == request_method then
        ngx.req.read_body()
        local postArgs = ngx.req.get_post_args()
        if postArgs then
            for k, v in pairs(postArgs) do
                args[k] = v
            end
        end
    end
    return args
end

return _M

================================================
FILE: demo3/README.md
================================================
#### 这章主要介绍一下,lua怎么返回一个json字符串,怎么把一个table转成json字符串,又怎么把一个json字符串转成json

其实很简答,直接使用cjson库的encode、decode方法即可

lua/hello.lua
```
local cjson = require "cjson"

-- 先定义一个json字符串
local json_str = '{"name": "Bruce.Lin", "age": 25}'
-- 这里把它转成对象,然后输出属性
local json = cjson.decode(json_str)
ngx.say("Name = " .. json['name'] .. ", Age = " .. tostring(json['age'])) -- 这里需要把25转成字符串,才能进行字符串拼接

-- 输出 Name = Bruce.Lin, Age = 25

ngx.say('<br/>') -- 换行

-- 接下来我们再把json对象转成json字符串
local json_str2 = cjson.encode(json)
ngx.say(json_str2)

-- 输出{"name":"Bruce.Lin","age":25}

ngx.say('<br/>') -- 换行

local obj = {
	ret = 200,
	msg = "login success"
}

ngx.say(cjson.encode(obj))

ngx.say('<br/>') -- 换行

local obj2 = {}

obj2['ret'] = 200
obj2['msg'] = "login fails"

ngx.say(cjson.encode(obj2))

```

ok,这里我们就学会的json字符串

[示例代码](https://github.com/362228416/openresty-web-dev) 参见demo3部分

================================================
FILE: demo3/conf/nginx.conf
================================================

worker_processes  1;

error_log logs/error.log;

events {
    worker_connections 1024;
}

http {
    lua_package_path "$prefix/lua/?.lua";
    server {
        listen 80;
        server_name localhost;
        lua_code_cache off;
        location ~ /lua/(.+) {
        	default_type text/html;
		    content_by_lua_file lua/$1.lua;
		}
    }
}


================================================
FILE: demo3/logs/.gitignore
================================================
*.log
*.pid


================================================
FILE: demo3/lua/hello.lua
================================================
local cjson = require "cjson"

-- 先定义一个json字符串
local json_str = '{"name": "Bruce.Lin", "age": 25}'
-- 这里把它转成对象,然后输出属性
local json = cjson.decode(json_str)
ngx.say("Name = " .. json['name'] .. ", Age = " .. tostring(json['age'])) -- 这里需要把25转成字符串,才能进行字符串拼接

-- 输出 Name = Bruce.Lin, Age = 25

ngx.say('<br/>') -- 换行

-- 接下来我们再把json对象转成json字符串
local json_str2 = cjson.encode(json)
ngx.say(json_str2)

-- 输出{"name":"Bruce.Lin","age":25}

ngx.say('<br/>') -- 换行

local obj = {
	ret = 200,
	msg = "login success"
}

ngx.say(cjson.encode(obj))

ngx.say('<br/>') -- 换行

local obj2 = {}

obj2['ret'] = 200
obj2['msg'] = "login fails"

ngx.say(cjson.encode(obj2))

================================================
FILE: demo3/lua/req.lua
================================================
local _M = {}

-- 获取http get/post 请求参数
function _M.getArgs()
    local request_method = ngx.var.request_method
    local args = ngx.req.get_uri_args()
    -- 参数获取
    if "POST" == request_method then
        ngx.req.read_body()
        local postArgs = ngx.req.get_post_args()
        if postArgs then
            for k, v in pairs(postArgs) do
                args[k] = v
            end
        end
    end
    return args
end

return _M

================================================
FILE: demo4/README.md
================================================
#### 这章主要演示怎么通过lua连接redis,并根据用户输入的key从redis获取value,并返回给用户

操作redis主要用到了lua-resty-redis库,代码可以在[github](https://github.com/openresty/lua-resty-redis)上找得到

而且上面也有实例代码

由于官网给出的例子比较基本,代码也比较多,所以我这里主要介绍一些怎么封装一下,简化我们调用的代码

lua/redis.lua
```
local redis = require "resty.redis"

local config = {
	host = "127.0.0.1",
    port = 6379,
    -- pass = "1234"  -- redis 密码,没有密码的话,把这行注释掉
}

local _M = {}


function _M.new(self)
    local red = redis:new()
    red:set_timeout(1000) -- 1 second
    local res = red:connect(config['host'], config['port'])
    if not res then
        return nil
    end
    if config['pass'] ~= nil then
		res = red:auth(config['pass'])
	    if not res then
	        return nil
	    end
    end
    red.close = close
    return red
end

function close(self)
	local sock = self.sock
    if not sock then
        return nil, "not initialized"
    end
    if self.subscribed then
        return nil, "subscribed state"
    end
    return sock:setkeepalive(10000, 50)
end

return _M
```

其实就是简单把连接,跟关闭做一个简单的封装,隐藏繁琐的初始化已经连接池细节,只需要调用new,就自动就链接了redis,close自动使用连接池

lua/hello.lua
```
local cjson = require "cjson"
local redis = require "redis"
local req = require "req"

local args = req.getArgs()
local key = args['key']

if key == nil or key == "" then
	key = "foo"
end

-- 下面的代码跟官方给的基本类似,只是简化了初始化代码,已经关闭的细节,我记得网上看到过一个  是修改官网的代码实现,我不太喜欢修改库的源码,除非万不得已,所以尽量简单的实现
local red = redis:new()
local value = red:get(key)
red:close()

local data = {
	ret = 200,
	data = value
}
ngx.say(cjson.encode(data))

```

访问
http://localhost/lua/hello?key=hello

即可获取redis中的key为hello的值,如果没有key参数,则默认获取foo的值

ok,到这里我们已经可以获取用户输入的值,并且从redis中获取数据,然后返回json数据了,已经可以开发一些简单的接口了

[示例代码](https://github.com/362228416/openresty-web-dev) 参见demo4部分

================================================
FILE: demo4/conf/nginx.conf
================================================

worker_processes  1;

error_log logs/error.log;

events {
    worker_connections 1024;
}

http {
    lua_package_path "$prefix/demo4/lua/?.lua;$prefix/lualib/?.lua";
    server {
        listen 80;
        server_name localhost;
        lua_code_cache off;
        location ~ /lua/(.+) {
        	default_type text/html;
		    content_by_lua_file lua/$1.lua;
		}
    }
}


================================================
FILE: demo4/logs/.gitignore
================================================
*.log
*.pid


================================================
FILE: demo4/lua/hello.lua
================================================
local cjson = require "cjson"
local redis = require "redis"
local req = require "req"

local args = req.getArgs()

local key = args['key']

if key == nil or key == "" then
	key = "foo"
end

local red = redis:new()
local value = red:get(key)
red:close()

local data = {
	ret = 200,
	data = value
}
ngx.say(cjson.encode(data))


================================================
FILE: demo4/lua/redis.lua
================================================
local redis = require "resty.redis"

local config = {
	host = "127.0.0.1",
    port = 6379,
    -- pass = "1234"  -- redis 密码,没有密码的话,把这行注释掉
}

local _M = {}


function _M.new(self)
    local red = redis:new()
    red:set_timeout(1000) -- 1 second
    local res = red:connect(config['host'], config['port'])
    if not res then
        return nil
    end
    if config['pass'] ~= nil then
		res = red:auth(config['pass'])
	    if not res then
	        return nil
	    end
    end
    red.close = close
    return red
end

function close(self)
	local sock = self.sock
    if not sock then
        return nil, "not initialized"
    end
    if self.subscribed then
        return nil, "subscribed state"
    end
    return sock:setkeepalive(10000, 50)
end

return _M

================================================
FILE: demo4/lua/req.lua
================================================
local _M = {}

-- 获取http get/post 请求参数
function _M.getArgs()
    local request_method = ngx.var.request_method
    local args = ngx.req.get_uri_args()
    -- 参数获取
    if "POST" == request_method then
        ngx.req.read_body()
        local postArgs = ngx.req.get_post_args()
        if postArgs then
            for k, v in pairs(postArgs) do
                args[k] = v
            end
        end
    end
    return args
end

return _M

================================================
FILE: demo5/README.md
================================================
openresty 前端开发入门五之Mysql篇

#### 这章主要演示怎么通过lua连接mysql,并根据用户输入的name从mysql获取数据,并返回给用户

操作mysql主要用到了lua-resty-mysql库,代码可以在[github](https://github.com/openresty/lua-resty-mysql)上找得到

而且上面也有实例代码

由于官网给出的例子比较基本,代码也比较多,所以我这里主要介绍一些怎么封装一下,简化我们调用的代码

lua/mysql.lua
```
local mysql = require "resty.mysql"

local config = {
    host = "localhost",
    port = 3306,
    database = "mysql",
    user = "root",
    password = "admin"
}

local _M = {}


function _M.new(self)
    local db, err = mysql:new()
    if not db then
        return nil
    end
    db:set_timeout(1000) -- 1 sec

    local ok, err, errno, sqlstate = db:connect(config)

    if not ok then
        return nil
    end
    db.close = close
    return db
end

function close(self)
	local sock = self.sock
    if not sock then
        return nil, "not initialized"
    end
    if self.subscribed then
        return nil, "subscribed state"
    end
    return sock:setkeepalive(10000, 50)
end

return _M
```

其实就是简单把连接,跟关闭做一个简单的封装,隐藏繁琐的初始化已经连接池细节,只需要调用new,就自动就链接了redis,close自动使用连接池

lua/hello.lua
```
local cjson = require "cjson"
local mysql = require "mysql"
local req = require "req"

local args = req.getArgs()

local name = args['name']

if name == nil or name == "" then
	name = "root"	
end

name = ngx.quote_sql_str(name) -- SQL 转义,将 ' 转成 \', 防SQL注入,并且转义后的变量包含了引号,所以可以直接当成条件值使用

local db = mysql:new()

local sql = "select * from user where User = " .. name

ngx.say(sql)
ngx.say("<br/>")

local res, err, errno, sqlstate = db:query(sql)
db:close()
if not res then
	ngx.say(err)
    return {}
end

ngx.say(cjson.encode(res))

```

访问
http://localhost/lua/hello?name=root

即可获取mysql中的name为root的的所有用户,如果没有name参数,则默认获取root的值

从输出的数据中,可以看出res其实是一个数组,而且不管返回的数据是多少条,它都是一个数组,当我们查询的结果只有一条的时候,可以通过 res[1] 来获取一条记录,每一行数据又是一个table,可以通过列名来得到value

ok,到这里我们已经可以获取用户输入的值,并且从mysql中获取数据,然后返回json数据了,已经可以开发一些简单的接口了

[示例代码](https://github.com/362228416/openresty-web-dev) 参见demo5部分

================================================
FILE: demo5/conf/nginx.conf
================================================

worker_processes  1;

error_log logs/error.log;

events {
    worker_connections 1024;
}

http {
    lua_package_path "$prefix/lua/?.lua;$prefix/lualib/?.lua";
    server {
        listen 80;
        server_name localhost;
        lua_code_cache off;
        location ~ /lua/(.+) {
        	default_type text/html;
		    content_by_lua_file lua/$1.lua;
		}
    }
}


================================================
FILE: demo5/logs/.gitignore
================================================
*.log
*.pid


================================================
FILE: demo5/lua/hello.lua
================================================
local cjson = require "cjson"
local mysql = require "mysql"
local req = require "req"

local args = req.getArgs()

local name = args['name']

if name == nil or name == "" then
	name = "root"	
end

name = ngx.quote_sql_str(name) -- SQL 转义,将 ' 转成 \', 防SQL注入,并且转义后的变量包含了引号,所以可以直接当成条件值使用

local db = mysql:new()

local sql = "select * from user where User = " .. name

ngx.say(sql)
ngx.say("<br/>")

local res, err, errno, sqlstate = db:query(sql)
db:close()
if not res then
	ngx.say(err)
    return {}
end

ngx.say(cjson.encode(res))


================================================
FILE: demo5/lua/mysql.lua
================================================
local mysql = require "resty.mysql"

local config = {
    host = "localhost",
    port = 3306,
    database = "mysql",
    user = "root",
    password = "admin"
}

local _M = {}


function _M.new(self)
    local db, err = mysql:new()
    if not db then
        return nil
    end
    db:set_timeout(1000) -- 1 sec

    local ok, err, errno, sqlstate = db:connect(config)

    if not ok then
        return nil
    end
    db.close = close
    return db
end

function close(self)
	local sock = self.sock
    if not sock then
        return nil, "not initialized"
    end
    if self.subscribed then
        return nil, "subscribed state"
    end
    return sock:setkeepalive(10000, 50)
end

return _M

================================================
FILE: demo5/lua/req.lua
================================================
local _M = {}

-- 获取http get/post 请求参数
function _M.getArgs()
    local request_method = ngx.var.request_method
    local args = ngx.req.get_uri_args()
    -- 参数获取
    if "POST" == request_method then
        ngx.req.read_body()
        local postArgs = ngx.req.get_post_args()
        if postArgs then
            for k, v in pairs(postArgs) do
                args[k] = v
            end
        end
    end
    return args
end

return _M

================================================
FILE: demo6/README.md
================================================
#### 大多数情况下,调试信息,都可以通过ngx.say打印出来,但是有的时候,我们希望打印调试日志,不影响到返回数据,所以系统打印到其它地方,比如日志文件,或者控制台

这里主要用到一个方法就是ngx.log,这个方法可以将日志输出到error.log里面,支持多种级别消息,如下:

```
ngx.STDERR
ngx.EMERG
ngx.ALERT
ngx.CRIT
ngx.ERR
ngx.WARN
ngx.NOTICE
ngx.INFO
ngx.DEBUG
```

可以通过以下方式输出调试信息,大多数情况下我们只要使用一个来输出我们的调试信息就好了,比如ngx.ALERT,我就比较喜欢这个,并且设置为我的idea live template了,只需要sout + TAB 就可以输出,关于idea有很多玩法,有时间可以分享给大家,就里就不说了

lua/hello.lua
```
ngx.say('print to browser')

ngx.log(ngx.ALERT, 'print to error.log')
ngx.log(ngx.STDERR, 'print to error.log')
ngx.log(ngx.EMERG, 'print to error.log')
ngx.log(ngx.ALERT, 'print to error.log')
ngx.log(ngx.CRIT, 'print to error.log')
ngx.log(ngx.ERR, 'print to error.log')
ngx.log(ngx.WARN, 'print to error.log')
ngx.log(ngx.NOTICE, 'print to error.log')
ngx.log(ngx.INFO, 'print to error.log')
ngx.log(ngx.DEBUG, 'print to error.log')
```

然后用浏览器访问 http://localhost/lua/hello  查看浏览器输出,还有 logs/error.log 文件输出,就能明白大概的意思了,也不用过多解释

#### 还有一种就是直接调用lua的print方法,进行输出,这个方法默认也会输出到error.log,默认输出级别是NOTICE,但是需要编译openresty的时候加上debug参数,如果是下载的windows预编译版本的话,默认没有debug,所以部分信息可能看不到,有需要可以自己编译一个

当然nginx里面还可以配置error日志级别,如下
```
error_log  logs/error.log  notice;
```

这句默认会在nginx.conf文件里面,只是注释掉了而已,只要打开注释就可以了,这样我们就可以直接通过print来输出日志了,完全是lua自带的函数,很多代码拿过来就可以直接使用

### 还有就是lua运行时报错信息捕获,原因可能是语法问题,或者空指针等等,导致500错误的,这些错误日志都会输出在error.log里面,可以用tail命令实时查看,也可以通过封装然后采用pcall的调用形式,进行函数调用,这样就不会出现500,可以根据执行成败,将结果输出到客户端,做到像php那样所见即所得,可以大大提高我们的开发效率,这个后面我会通过封装一个轻量级框架来介绍,这里就先不讲了,有兴趣的同学可以了解一下

[示例代码](https://github.com/362228416/openresty-web-dev) 参见demo6部分

================================================
FILE: demo6/conf/nginx.conf
================================================

worker_processes  1;

error_log logs/error.log;

events {
    worker_connections 1024;
}

http {
    lua_package_path "$prefix/lua/?.lua;$prefix/lualib/?.lua";
    server {
        listen 80;
        server_name localhost;
        lua_code_cache off;
        location ~ /lua/(.+) {
        	default_type text/html;
		    content_by_lua_file lua/$1.lua;
		}
    }
}


================================================
FILE: demo6/logs/.gitignore
================================================
*.log
*.pid


================================================
FILE: demo6/lua/hello.lua
================================================

ngx.say('print to browser')

ngx.log(ngx.ALERT, 'print to error.log')
ngx.log(ngx.STDERR, 'print to error.log')
ngx.log(ngx.EMERG, 'print to error.log')
ngx.log(ngx.ALERT, 'print to error.log')
ngx.log(ngx.CRIT, 'print to error.log')
ngx.log(ngx.ERR, 'print to error.log')
ngx.log(ngx.WARN, 'print to error.log')
ngx.log(ngx.NOTICE, 'print to error.log')
ngx.log(ngx.INFO, 'print to error.log')
ngx.log(ngx.DEBUG, 'print to error.log')


================================================
FILE: demo6/lua/req.lua
================================================
local _M = {}

-- 获取http get/post 请求参数
function _M.getArgs()
    local request_method = ngx.var.request_method
    local args = ngx.req.get_uri_args()
    -- 参数获取
    if "POST" == request_method then
        ngx.req.read_body()
        local postArgs = ngx.req.get_post_args()
        if postArgs then
            for k, v in pairs(postArgs) do
                args[k] = v
            end
        end
    end
    return args
end

return _M

================================================
FILE: demo7/README.md
================================================
#### 做前端开发,大多数情况下,都需要跟后端打交道,而最常见的方式则是通过http请求,进行通信。
在openresty中,通过http跟后端整合通信的方式又很多种,各有各的好处,可以根据情况交叉使用

## 1、直接proxy

这种方式最简单,也是我们最熟悉的,直接配置一个反向代理,跟nginx的用法一致

比如我们有一个后端服务,提供用户相关接口,是java写的,端口8080,为了简单起见,我直接在openresty里面配置一个server,模拟java端,通过一个简单的案例的来说明情况

nginx.conf 
```

worker_processes  1;

error_log logs/error.log;

events {
    worker_connections 1024;
}

http {
    lua_package_path "/Users/john/opensource/openresty-web-dev/demo7/lua/?.lua;/usr/local/openresty/lualib/?.lua";
    server {
        listen 80;
        server_name localhost;
        lua_code_cache off;

        location / {
            root html;
            index index.html;
        }

        location ~ ^/user {
            proxy_pass http://127.0.0.1:8080;
        }

    }

	# 这个只是模拟后端
    server {
        listen 8080;
        server_name localhost;
        lua_code_cache off;
        location ~ /user/(.+) {
            default_type text/html; 
            content_by_lua_file lua/$1.lua;
        }
    }

}
```

上面配置了两个location,将所有以/user开头的请求都转到后端的8080服务器,其他的则是静态页面,直接从html目录读取,然后返回,从这里开始就是前端开发了


为了简单起见,假设后端提供了一个登陆接口,我们这里直接用lua来实现一下就好了,检查用户名跟密码是admin,就返回成功,否则返回失败

lua/login.lua   
```
local req = require "req"
local cjson = require "cjson"

local args = req.getArgs()

local username = args['username']
local password = args['password']

local res = {}

if username == "admin" and password == "admin" then
	res['ret'] = true
	res['token'] = ngx.md5('admin/' .. tostring(ngx.time()))
else
	res['ret'] = false
end

ngx.say(cjson.encode(res))
```

index.html

```
<html>
<head>
	<meta charset="UTF-8">
	<title>Login Page</title>
</head>
<body>
	UserName: <input type="text" id="username" value="admin">
	Password: <input type="password" id="password" value="admin">
	<a href="javascript:void(0)" onclick="login()">Login</a>
	<script src="//cdn.bootcss.com/jquery/2.2.4/jquery.min.js"></script>
	<script>
		function login() {
			var username = $('#username').val();
			var password = $('#password').val();
			$.post('/user/login', {username: username, password: password}, function(res){
				console.log(res)
				var msg = res.ret ? "登录成功" : "登录失败"
				alert(msg)
			}, 'json')
		}
	</script>
</body>
</html>
```

2、使用ngx.location.captrue

这个方法主要用于发送内部请求,即请求当前server内的其他location,默认会将当前请求的参数带过去,也可以手动指定参数,GET参数通过args传递,post参数通过body传递

如:

local req = require "req"
local args = req.getArgs()

GET 调用

local res = ngx.location.capture('/user/login', {
    method = ngx.HTTP_GET,
    args = args,
});

POST 调用

local res = ngx.location.capture('/user/login', {
    method = ngx.HTTP_POST,
    body = ngx.encode_args(args),
});

现在我们自己写一个lua来调用后台接口实现登陆,然后对请求做一点处理,实现一些额外的逻辑,比如在原来的参数上面加上一个from字段

lua/local-login.lua

```
local req = require "req"
local cjson = require "cjson"

local args = req.getArgs()

-- GET
local res = ngx.location.capture('/user/login', {method = ngx.HTTP_GET, args = args})
-- POST
-- local res = ngx.location.capture('/user/login', {method = ngx.HTTP_POST, body = ngx.encode_args(args)})

-- print(res.status) -- 状态码

if res.status == 200 then
	local ret = cjson.decode(res.body)
	ret['from'] = 'local'
	ngx.say(cjson.encode(ret))
else
	print(res.body)
	ngx.say('{"ret": false, "from": "local"}')
end

```

index.html 也需要改一下,多加一个按钮,调用本地登陆接口
```
<html>
<head>
	<meta charset="UTF-8">
	<title>Login Page</title>
</head>
<body>
	UserName: <input type="text" id="username" value="admin">
	Password: <input type="password" id="password" value="admin">
	<a href="javascript:void(0)" onclick="login()">Login</a>
	<a href="javascript:void(0)" onclick="local_login()">Local Login</a>
	<script src="//cdn.bootcss.com/jquery/2.2.4/jquery.min.js"></script>
	<script>
		function login() {
			var username = $('#username').val();
			var password = $('#password').val();
			$.post('/user/login', {username: username, password: password}, function(res){
				console.log(res)
				var msg = res.ret ? "登录成功" : "登录失败"
				alert(msg)
			}, 'json')
		}

		function local_login() {
			var username = $('#username').val();
			var password = $('#password').val();
			$.post('/lua/local-login', {username: username, password: password}, function(res){
				console.log(res)
				var msg = res.ret ? "本地登录成功" : "本地登录失败"
				alert(msg)
			}, 'json')
		}

	</script>
</body>
</html>
```

3、第三方模块[lua-resty-http](https://github.com/pintsized/lua-resty-http)

这种方式跟上面那种不同的地方是调用的时候,不会带上本地请求的请求头、cookie、以及请求参数,不过这也使得请求更纯粹,不会带上那些没必要的东西,减少数据传输

最后local-login.lua 变成如下
```
local req = require "req"
local cjson = require "cjson"
local http = require "resty.http"

local args = req.getArgs()

-- GET
-- local res = ngx.location.capture('/user/login', {method = ngx.HTTP_GET, args = args})

-- POST
-- local res = ngx.location.capture('/user/login', {method = ngx.HTTP_POST, body = ngx.encode_args(args)})

-- http
local httpc = http.new()
local res = httpc:request_uri("http://127.0.0.1:8080/user/login", {
    method = "POST",
    body = ngx.encode_args(args),
    headers = {
        ["Accept"] = "application/json",
        ["Accept-Encoding"] = "utf-8",
        ["Cookie"] = ngx.req.get_headers()['Cookie'],
        ["Content-Type"] = "application/x-www-form-urlencoded",
    }
})
httpc:set_keepalive(60)

print(res.status) -- 状态码

if res.status == 200 then
	local ret = cjson.decode(res.body)
	ret['from'] = 'local'
	ngx.say(cjson.encode(ret))
else
	print(res.body)
	ngx.say('{"ret": false, "from": "local"}')
end
```

到此,基本上已经能通过openresty,做一些前后端的交互了,下次介绍怎么使用openresty模板渲染,以及搭配react开发前端。

[示例代码](https://github.com/362228416/openresty-web-dev) 参见demo7部分






================================================
FILE: demo7/conf/nginx.conf
================================================

worker_processes  1;

error_log logs/error.log notice;

events {
    worker_connections 1024;
}

http {
    lua_package_path "$prefix/lua/?.lua;$prefix/lualib/?.lua";
    server {
        listen 80;
        server_name localhost;
        lua_code_cache off;

        location / {
            root html;
            index index.html;
        }

        location ~ /lua/(.+) {
        	default_type text/html;
		    content_by_lua_file lua/$1.lua;
		}

        location ~ ^/user {
            proxy_pass http://127.0.0.1:8080;
        }

    }

    # 这个只是模拟后端
    server {
        listen 8080;
        server_name localhost;
        lua_code_cache off;
        location ~ /user/(.+) {
            default_type text/html;
            content_by_lua_file lua/$1.lua;
        }
    }

}


================================================
FILE: demo7/html/index.html
================================================
<html>
<head>
	<meta charset="UTF-8">
	<title>Login Page</title>
</head>
<body>
	UserName: <input type="text" id="username" value="admin">
	Password: <input type="password" id="password" value="admin">
	<a href="javascript:void(0)" onclick="login()">Login</a>
	<a href="javascript:void(0)" onclick="local_login()">Local Login</a>
	<script src="//cdn.bootcss.com/jquery/2.2.4/jquery.min.js"></script>
	<script>
		function login() {
			var username = $('#username').val();
			var password = $('#password').val();
			$.post('/user/login', {username: username, password: password}, function(res){
				console.log(res)
				var msg = res.ret ? "登录成功" : "登录失败"
				alert(msg)
			}, 'json')
		}

		function local_login() {
			var username = $('#username').val();
			var password = $('#password').val();
			$.post('/lua/local-login', {username: username, password: password}, function(res){
				console.log(res)
				var msg = res.ret ? "本地登录成功" : "本地登录失败"
				alert(msg)
			}, 'json')
		}

	</script>
</body>
</html>

================================================
FILE: demo7/logs/.gitignore
================================================
*.log
*.pid


================================================
FILE: demo7/lua/hello.lua
================================================

ngx.say('print to browser')

ngx.log(ngx.ALERT, 'print to error.log')
ngx.log(ngx.STDERR, 'print to error.log')
ngx.log(ngx.EMERG, 'print to error.log')
ngx.log(ngx.ALERT, 'print to error.log')
ngx.log(ngx.CRIT, 'print to error.log')
ngx.log(ngx.ERR, 'print to error.log')
ngx.log(ngx.WARN, 'print to error.log')
ngx.log(ngx.NOTICE, 'print to error.log')
ngx.log(ngx.INFO, 'print to error.log')
ngx.log(ngx.DEBUG, 'print to error.log')


================================================
FILE: demo7/lua/local-login.lua
================================================
local req = require "req"
local cjson = require "cjson"
local http = require "resty.http"

local args = req.getArgs()

-- GET
-- local res = ngx.location.capture('/user/login', {method = ngx.HTTP_GET, args = args})

-- POST
-- local res = ngx.location.capture('/user/login', {method = ngx.HTTP_POST, body = ngx.encode_args(args)})

-- http
local httpc = http.new()
local res = httpc:request_uri("http://127.0.0.1:8080/user/login", {
    method = "POST",
    body = ngx.encode_args(args),
    headers = {
        ["Accept"] = "application/json",
        ["Accept-Encoding"] = "utf-8",
        ["Cookie"] = ngx.req.get_headers()['Cookie'],
        ["Content-Type"] = "application/x-www-form-urlencoded",
    }
})
httpc:set_keepalive(60)

print(res.status) -- 状态码

if res.status == 200 then
	local ret = cjson.decode(res.body)
	ret['from'] = 'local'
	ngx.say(cjson.encode(ret))
else
	print(res.body)
	ngx.say('{"ret": false, "from": "local"}')
end



================================================
FILE: demo7/lua/login.lua
================================================
local req = require "req"
local cjson = require "cjson"

local args = req.getArgs()

local username = args['username']
local password = args['password']

print(username)
print(password)

local res = {method = ngx.var.request_method}

if username == "admin" and password == "admin" then
	res['ret'] = true
	res['token'] = ngx.md5(username .. '/' .. tostring(ngx.time()))
else
	res['ret'] = false
end

ngx.say(cjson.encode(res))

================================================
FILE: demo7/lua/req.lua
================================================
local _M = {}

-- 获取http get/post 请求参数
function _M.getArgs()
    local request_method = ngx.var.request_method
    local args = ngx.req.get_uri_args()
    -- 参数获取
    if "POST" == request_method then
        ngx.req.read_body()
        local postArgs = ngx.req.get_post_args()
        if postArgs then
            for k, v in pairs(postArgs) do
                args[k] = v
            end
        end
    end
    return args
end

return _M

================================================
FILE: demo7/lualib/resty/http.lua
================================================
local http_headers = require "resty.http_headers"

local ngx_socket_tcp = ngx.socket.tcp
local ngx_req = ngx.req
local ngx_req_socket = ngx_req.socket
local ngx_req_get_headers = ngx_req.get_headers
local ngx_req_get_method = ngx_req.get_method
local str_gmatch = string.gmatch
local str_lower = string.lower
local str_upper = string.upper
local str_find = string.find
local str_sub = string.sub
local str_gsub = string.gsub
local tbl_concat = table.concat
local tbl_insert = table.insert
local ngx_encode_args = ngx.encode_args
local ngx_re_match = ngx.re.match
local ngx_re_gsub = ngx.re.gsub
local ngx_log = ngx.log
local ngx_DEBUG = ngx.DEBUG
local ngx_ERR = ngx.ERR
local ngx_NOTICE = ngx.NOTICE
local ngx_var = ngx.var
local co_yield = coroutine.yield
local co_create = coroutine.create
local co_status = coroutine.status
local co_resume = coroutine.resume


-- http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.1
local HOP_BY_HOP_HEADERS = {
    ["connection"]          = true,
    ["keep-alive"]          = true,
    ["proxy-authenticate"]  = true,
    ["proxy-authorization"] = true,
    ["te"]                  = true,
    ["trailers"]            = true,
    ["transfer-encoding"]   = true,
    ["upgrade"]             = true,
    ["content-length"]      = true, -- Not strictly hop-by-hop, but Nginx will deal
                                    -- with this (may send chunked for example).
}


-- Reimplemented coroutine.wrap, returning "nil, err" if the coroutine cannot
-- be resumed. This protects user code from inifite loops when doing things like
-- repeat
--   local chunk, err = res.body_reader()
--   if chunk then -- <-- This could be a string msg in the core wrap function.
--     ...
--   end
-- until not chunk
local co_wrap = function(func)
    local co = co_create(func)
    if not co then
        return nil, "could not create coroutine"
    else
        return function(...)
            if co_status(co) == "suspended" then
                return select(2, co_resume(co, ...))
            else
                return nil, "can't resume a " .. co_status(co) .. " coroutine"
            end
        end
    end
end


local _M = {
    _VERSION = '0.09',
}
_M._USER_AGENT = "lua-resty-http/" .. _M._VERSION .. " (Lua) ngx_lua/" .. ngx.config.ngx_lua_version

local mt = { __index = _M }


local HTTP = {
    [1.0] = " HTTP/1.0\r\n",
    [1.1] = " HTTP/1.1\r\n",
}

local DEFAULT_PARAMS = {
    method = "GET",
    path = "/",
    version = 1.1,
}


function _M.new(self)
    local sock, err = ngx_socket_tcp()
    if not sock then
        return nil, err
    end
    return setmetatable({ sock = sock, keepalive = true }, mt)
end


function _M.set_timeout(self, timeout)
    local sock = self.sock
    if not sock then
        return nil, "not initialized"
    end

    return sock:settimeout(timeout)
end


function _M.ssl_handshake(self, ...)
    local sock = self.sock
    if not sock then
        return nil, "not initialized"
    end

    self.ssl = true

    return sock:sslhandshake(...)
end


function _M.connect(self, ...)
    local sock = self.sock
    if not sock then
        return nil, "not initialized"
    end

    self.host = select(1, ...)
    self.port = select(2, ...)

    -- If port is not a number, this is likely a unix domain socket connection.
    if type(self.port) ~= "number" then
        self.port = nil
    end

    self.keepalive = true

    return sock:connect(...)
end


function _M.set_keepalive(self, ...)
    local sock = self.sock
    if not sock then
        return nil, "not initialized"
    end

    if self.keepalive == true then
        return sock:setkeepalive(...)
    else
        -- The server said we must close the connection, so we cannot setkeepalive.
        -- If close() succeeds we return 2 instead of 1, to differentiate between
        -- a normal setkeepalive() failure and an intentional close().
        local res, err = sock:close()
        if res then
            return 2, "connection must be closed"
        else
            return res, err
        end
    end
end


function _M.get_reused_times(self)
    local sock = self.sock
    if not sock then
        return nil, "not initialized"
    end

    return sock:getreusedtimes()
end


function _M.close(self)
    local sock = self.sock
    if not sock then
        return nil, "not initialized"
    end

    return sock:close()
end


local function _should_receive_body(method, code)
    if method == "HEAD" then return nil end
    if code == 204 or code == 304 then return nil end
    if code >= 100 and code < 200 then return nil end
    return true
end


function _M.parse_uri(self, uri)
    local m, err = ngx_re_match(uri, [[^(http[s]?)://([^:/]+)(?::(\d+))?(.*)]],
        "jo")

    if not m then
        if err then
            return nil, "failed to match the uri: " .. uri .. ", " .. err
        end

        return nil, "bad uri: " .. uri
    else
        if m[3] then
            m[3] = tonumber(m[3])
        else
            if m[1] == "https" then
                m[3] = 443
            else
                m[3] = 80
            end
        end
        if not m[4] or "" == m[4] then m[4] = "/" end
        return m, nil
    end
end


local function _format_request(params)
    local version = params.version
    local headers = params.headers or {}

    local query = params.query or ""
    if query then
        if type(query) == "table" then
            query = "?" .. ngx_encode_args(query)
        end
    end

    -- Initialize request
    local req = {
        str_upper(params.method),
        " ",
        params.path,
        query,
        HTTP[version],
        -- Pre-allocate slots for minimum headers and carriage return.
        true,
        true,
        true,
    }
    local c = 6 -- req table index it's faster to do this inline vs table.insert

    -- Append headers
    for key, values in pairs(headers) do
        if type(values) ~= "table" then
            values = {values}
        end

        key = tostring(key)
        for _, value in pairs(values) do
            req[c] = key .. ": " .. tostring(value) .. "\r\n"
            c = c + 1
        end
    end

    -- Close headers
    req[c] = "\r\n"

    return tbl_concat(req)
end


local function _receive_status(sock)
    local line, err = sock:receive("*l")
    if not line then
        return nil, nil, nil, err
    end

    return tonumber(str_sub(line, 10, 12)), tonumber(str_sub(line, 6, 8)), str_sub(line, 14)
end



local function _receive_headers(sock)
    local headers = http_headers.new()

    repeat
        local line, err = sock:receive("*l")
        if not line then
            return nil, err
        end

        for key, val in str_gmatch(line, "([^:%s]+):%s*(.+)") do
            if headers[key] then
                if type(headers[key]) ~= "table" then
                    headers[key] = { headers[key] }
                end
                tbl_insert(headers[key], tostring(val))
            else
                headers[key] = tostring(val)
            end
        end
    until str_find(line, "^%s*$")

    return headers, nil
end


local function _chunked_body_reader(sock, default_chunk_size)
    return co_wrap(function(max_chunk_size)
        local max_chunk_size = max_chunk_size or default_chunk_size
        local remaining = 0
        local length

        repeat
            -- If we still have data on this chunk
            if max_chunk_size and remaining > 0 then

                if remaining > max_chunk_size then
                    -- Consume up to max_chunk_size
                    length = max_chunk_size
                    remaining = remaining - max_chunk_size
                else
                    -- Consume all remaining
                    length = remaining
                    remaining = 0
                end
            else -- This is a fresh chunk

                -- Receive the chunk size
                local str, err = sock:receive("*l")
                if not str then
                    co_yield(nil, err)
                end

                length = tonumber(str, 16)

                if not length then
                    co_yield(nil, "unable to read chunksize")
                end

                if max_chunk_size and length > max_chunk_size then
                    -- Consume up to max_chunk_size
                    remaining = length - max_chunk_size
                    length = max_chunk_size
                end
            end

            if length > 0 then
                local str, err = sock:receive(length)
                if not str then
                    co_yield(nil, err)
                end

                max_chunk_size = co_yield(str) or default_chunk_size

                -- If we're finished with this chunk, read the carriage return.
                if remaining == 0 then
                    sock:receive(2) -- read \r\n
                end
            else
                -- Read the last (zero length) chunk's carriage return
                sock:receive(2) -- read \r\n
            end

        until length == 0
    end)
end


local function _body_reader(sock, content_length, default_chunk_size)
    return co_wrap(function(max_chunk_size)
        local max_chunk_size = max_chunk_size or default_chunk_size

        if not content_length and max_chunk_size then
            -- We have no length, but wish to stream.
            -- HTTP 1.0 with no length will close connection, so read chunks to the end.
            repeat
                local str, err, partial = sock:receive(max_chunk_size)
                if not str and err == "closed" then
                    max_chunk_size = tonumber(co_yield(partial, err) or default_chunk_size)
                end

                max_chunk_size = tonumber(co_yield(str) or default_chunk_size)
                if max_chunk_size and max_chunk_size < 0 then max_chunk_size = nil end

                if not max_chunk_size then
                    ngx_log(ngx_ERR, "Buffer size not specified, bailing")
                    break
                end
            until not str

        elseif not content_length then
            -- We have no length but don't wish to stream.
            -- HTTP 1.0 with no length will close connection, so read to the end.
            co_yield(sock:receive("*a"))

        elseif not max_chunk_size then
            -- We have a length and potentially keep-alive, but want everything.
            co_yield(sock:receive(content_length))

        else
            -- We have a length and potentially a keep-alive, and wish to stream
            -- the response.
            local received = 0
            repeat
                local length = max_chunk_size
                if received + length > content_length then
                    length = content_length - received
                end

                if length > 0 then
                    local str, err = sock:receive(length)
                    if not str then
                        max_chunk_size = tonumber(co_yield(nil, err) or default_chunk_size)
                    end
                    received = received + length

                    max_chunk_size = tonumber(co_yield(str) or default_chunk_size)
                    if max_chunk_size and max_chunk_size < 0 then max_chunk_size = nil end

                    if not max_chunk_size then
                        ngx_log(ngx_ERR, "Buffer size not specified, bailing")
                        break
                    end
                end

            until length == 0
        end
    end)
end


local function _no_body_reader()
    return nil
end


local function _read_body(res)
    local reader = res.body_reader

    if not reader then
        -- Most likely HEAD or 304 etc.
        return nil, "no body to be read"
    end

    local chunks = {}
    local c = 1

    local chunk, err
    repeat
        chunk, err = reader()

        if err then
            return nil, err, tbl_concat(chunks) -- Return any data so far.
        end
        if chunk then
            chunks[c] = chunk
            c = c + 1
        end
    until not chunk

    return tbl_concat(chunks)
end


local function _trailer_reader(sock)
    return co_wrap(function()
        co_yield(_receive_headers(sock))
    end)
end


local function _read_trailers(res)
    local reader = res.trailer_reader
    if not reader then
        return nil, "no trailers"
    end

    local trailers = reader()
    setmetatable(res.headers, { __index = trailers })
end


local function _send_body(sock, body)
    if type(body) == 'function' then
        repeat
            local chunk, err, partial = body()

            if chunk then
                local ok,err = sock:send(chunk)

                if not ok then
                    return nil, err
                end
            elseif err ~= nil then
                return nil, err, partial
            end

        until chunk == nil
    elseif body ~= nil then
        local bytes, err = sock:send(body)

        if not bytes then
            return nil, err
        end
    end
    return true, nil
end


local function _handle_continue(sock, body)
    local status, version, reason, err = _receive_status(sock)
    if not status then
        return nil, nil, err
    end

    -- Only send body if we receive a 100 Continue
    if status == 100 then
        local ok, err = sock:receive("*l") -- Read carriage return
        if not ok then
            return nil, nil, err
        end
        _send_body(sock, body)
    end
    return status, version, err
end


function _M.send_request(self, params)
    -- Apply defaults
    setmetatable(params, { __index = DEFAULT_PARAMS })

    local sock = self.sock
    local body = params.body
    local headers = http_headers.new()

    local params_headers = params.headers
    if params_headers then
        -- We assign one by one so that the metatable can handle case insensitivity
        -- for us. You can blame the spec for this inefficiency.
        for k,v in pairs(params_headers) do
            headers[k] = v
        end
    end

    -- Ensure minimal headers are set
    if type(body) == 'string' and not headers["Content-Length"] then
        headers["Content-Length"] = #body
    end
    if not headers["Host"] then
        if (str_sub(self.host, 1, 5) == "unix:") then
            return nil, "Unable to generate a useful Host header for a unix domain socket. Please provide one."
        end
        -- If we have a port (i.e. not connected to a unix domain socket), and this
        -- port is non-standard, append it to the Host heaer.
        if self.port then
            if self.ssl and self.port ~= 443 then
                headers["Host"] = self.host .. ":" .. self.port
            elseif not self.ssl and self.port ~= 80 then
                headers["Host"] = self.host .. ":" .. self.port
            else
                headers["Host"] = self.host
            end
        else
            headers["Host"] = self.host
        end
    end
    if not headers["User-Agent"] then
        headers["User-Agent"] = _M._USER_AGENT
    end
    if params.version == 1.0 and not headers["Connection"] then
        headers["Connection"] = "Keep-Alive"
    end

    params.headers = headers

    -- Format and send request
    local req = _format_request(params)
    ngx_log(ngx_DEBUG, "\n", req)
    local bytes, err = sock:send(req)

    if not bytes then
        return nil, err
    end

    -- Send the request body, unless we expect: continue, in which case
    -- we handle this as part of reading the response.
    if headers["Expect"] ~= "100-continue" then
        local ok, err, partial = _send_body(sock, body)
        if not ok then
            return nil, err, partial
        end
    end

    return true
end


function _M.read_response(self, params)
    local sock = self.sock

    local status, version, reason, err

    -- If we expect: continue, we need to handle this, sending the body if allowed.
    -- If we don't get 100 back, then status is the actual status.
    if params.headers["Expect"] == "100-continue" then
        local _status, _version, _err = _handle_continue(sock, params.body)
        if not _status then
            return nil, _err
        elseif _status ~= 100 then
            status, version, err = _status, _version, _err
        end
    end

    -- Just read the status as normal.
    if not status then
        status, version, reason, err = _receive_status(sock)
        if not status then
            return nil, err
        end
    end


    local res_headers, err = _receive_headers(sock)
    if not res_headers then
        return nil, err
    end

    -- keepalive is true by default. Determine if this is correct or not.
    local ok, connection = pcall(str_lower, res_headers["Connection"])
    if ok then
        if  (version == 1.1 and connection == "close") or
            (version == 1.0 and connection ~= "keep-alive") then
            self.keepalive = false
        end
    else
        -- no connection header
        if version == 1.0 then
            self.keepalive = false
        end
    end

    local body_reader = _no_body_reader
    local trailer_reader, err = nil, nil
    local has_body = false

    -- Receive the body_reader
    if _should_receive_body(params.method, status) then
        local ok, encoding = pcall(str_lower, res_headers["Transfer-Encoding"])
        if ok and version == 1.1 and encoding == "chunked" then
            body_reader, err = _chunked_body_reader(sock)
            has_body = true
        else

            local ok, length = pcall(tonumber, res_headers["Content-Length"])
            if ok then
                body_reader, err = _body_reader(sock, length)
                has_body = true
            end
        end
    end

    if res_headers["Trailer"] then
        trailer_reader, err = _trailer_reader(sock)
    end

    if err then
        return nil, err
    else
        return {
            status = status,
            reason = reason,
            headers = res_headers,
            has_body = has_body,
            body_reader = body_reader,
            read_body = _read_body,
            trailer_reader = trailer_reader,
            read_trailers = _read_trailers,
        }
    end
end


function _M.request(self, params)
    local res, err = self:send_request(params)
    if not res then
        return res, err
    else
        return self:read_response(params)
    end
end


function _M.request_pipeline(self, requests)
    for i, params in ipairs(requests) do
        if params.headers and params.headers["Expect"] == "100-continue" then
            return nil, "Cannot pipeline request specifying Expect: 100-continue"
        end

        local res, err = self:send_request(params)
        if not res then
            return res, err
        end
    end

    local responses = {}
    for i, params in ipairs(requests) do
        responses[i] = setmetatable({
            params = params,
            response_read = false,
        }, {
            -- Read each actual response lazily, at the point the user tries
            -- to access any of the fields.
            __index = function(t, k)
                local res, err
                if t.response_read == false then
                    res, err = _M.read_response(self, t.params)
                    t.response_read = true

                    if not res then
                        ngx_log(ngx_ERR, err)
                    else
                        for rk, rv in pairs(res) do
                            t[rk] = rv
                        end
                    end
                end
                return rawget(t, k)
            end,
        })
    end
    return responses
end


function _M.request_uri(self, uri, params)
    if not params then params = {} end

    local parsed_uri, err = self:parse_uri(uri)
    if not parsed_uri then
        return nil, err
    end

    local scheme, host, port, path = unpack(parsed_uri)
    if not params.path then params.path = path end

    local c, err = self:connect(host, port)
    if not c then
        return nil, err
    end

    if scheme == "https" then
        local verify = true
        if params.ssl_verify == false then
            verify = false
        end
        local ok, err = self:ssl_handshake(nil, host, verify)
        if not ok then
            return nil, err
        end
    end

    local res, err = self:request(params)
    if not res then
        return nil, err
    end

    local body, err = res:read_body()
    if not body then
        return nil, err
    end

    res.body = body

    local ok, err = self:set_keepalive()
    if not ok then
        ngx_log(ngx_ERR, err)
    end

    return res, nil
end


function _M.get_client_body_reader(self, chunksize, sock)
    local chunksize = chunksize or 65536
    if not sock then
        local ok, err
        ok, sock, err = pcall(ngx_req_socket)

        if not ok then
            return nil, sock -- pcall err
        end

        if not sock then
            if err == "no body" then
                return nil
            else
                return nil, err
            end
        end
    end

    local headers = ngx_req_get_headers()
    local length = headers.content_length
    local encoding = headers.transfer_encoding
    if length then
        return _body_reader(sock, tonumber(length), chunksize)
    elseif encoding and str_lower(encoding) == 'chunked' then
        -- Not yet supported by ngx_lua but should just work...
        return _chunked_body_reader(sock, chunksize)
    else
       return nil
    end
end


function _M.proxy_request(self, chunksize)
    return self:request{
        method = ngx_req_get_method(),
        path = ngx_re_gsub(ngx_var.uri, "\\s", "%20", "jo") .. ngx_var.is_args .. (ngx_var.query_string or ""),
        body = self:get_client_body_reader(chunksize),
        headers = ngx_req_get_headers(),
    }
end


function _M.proxy_response(self, response, chunksize)
    if not response then
        ngx_log(ngx_ERR, "no response provided")
        return
    end

    ngx.status = response.status

    -- Filter out hop-by-hop headeres
    for k,v in pairs(response.headers) do
        if not HOP_BY_HOP_HEADERS[str_lower(k)] then
            ngx.header[k] = v
        end
    end

    local reader = response.body_reader
    repeat
        local chunk, err = reader(chunksize)
        if err then
            ngx_log(ngx_ERR, err)
            break
        end

        if chunk then
            local res, err = ngx.print(chunk)
            if not res then
                ngx_log(ngx_ERR, err)
                break
            end
        end
    until not chunk
end


return _M


================================================
FILE: demo7/lualib/resty/http_headers.lua
================================================
local   rawget, rawset, setmetatable =
        rawget, rawset, setmetatable

local str_gsub = string.gsub
local str_lower = string.lower


local _M = {
    _VERSION = '0.01',
}


-- Returns an empty headers table with internalised case normalisation.
-- Supports the same cases as in ngx_lua:
--
-- headers.content_length
-- headers["content-length"]
-- headers["Content-Length"]
function _M.new(self)
    local mt = { 
        normalised = {},
    }


    mt.__index = function(t, k)
        local k_hyphened = str_gsub(k, "_", "-")
        local matched = rawget(t, k)
        if matched then
            return matched
        else
            local k_normalised = str_lower(k_hyphened)
            return rawget(t, mt.normalised[k_normalised])
        end
    end


    -- First check the normalised table. If there's no match (first time) add an entry for
    -- our current case in the normalised table. This is to preserve the human (prettier) case
    -- instead of outputting lowercased header names.
    --
    -- If there's a match, we're being updated, just with a different case for the key. We use
    -- the normalised table to give us the original key, and perorm a rawset().
    mt.__newindex = function(t, k, v)
        -- we support underscore syntax, so always hyphenate.
        local k_hyphened = str_gsub(k, "_", "-")

        -- lowercase hyphenated is "normalised"
        local k_normalised = str_lower(k_hyphened)

        if not mt.normalised[k_normalised] then
            mt.normalised[k_normalised] = k_hyphened
            rawset(t, k_hyphened, v)
        else
            rawset(t, mt.normalised[k_normalised], v)
        end
    end

    return setmetatable({}, mt)
end


return _M


================================================
FILE: demo8/README.md
================================================
#### 通过前面几章,我们已经掌握了一些基本的开发知识,但是代码结构比较简单,缺乏统一的标准,模块化,也缺乏统一的异常处理,这一章我们主要来学习如何封装一个轻量级的MVC框架,规范以及简化开发,并且提供类似php所见即所得的能力

## 统一入口

通常来说一个mvc框架会有一个统一的入口点,类似于spring mvc的DispatcherServlet,会拦截所有的请求,也就是/,于是我们可以得出我们的入口点

conf/nginx.conf
```
worker_processes  1;

error_log logs/error.log notice;

events {
    worker_connections 1024;
}

http {
    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";
    server {
        listen 80;
        server_name localhost;
        lua_code_cache off;

        location / {
        	content_by_lua_file lua/mvc.lua;
        }

        location ~ ^/js/|^/css/|\.html {
        	root html;
        }
    }
}
```

除了静态文件js/css/html文件,其他的请求都会被我们的mvc.lua处理。

## 默认页面

当请求uri为空时,默认返回index.html页面,当然也可以自己定义,实现这个效果很简单

```
local uri = ngx.var.uri
-- 默认首页
if uri == "" or uri == "/" then
    local res = ngx.location.capture("/index.html", {})
    ngx.say(res.body)
    return
end
```

## url解析

这里简单的把url解析成模块名模块方法,根据/分割,如果只有模块名,没有方法名,则默认为index方法

```
local m, err = ngx.re.match(uri, "([a-zA-Z0-9-]+)/*([a-zA-Z0-9-]+)*")

local moduleName = m[1]     -- 模块名
local method = m[2]         -- 方法名

if not method then
    method = "index"        -- 默认访问index方法
else
    method = ngx.re.gsub(method, "-", "_")    
end
```

## 动态Controller模块

得到模块名之后,需要动态引入模块,通过pcall,然后再调用模块的方法

```
-- 控制器默认在web包下面
local prefix = "web."       
local path = prefix .. moduleName

-- 尝试引入模块,不存在则报错
local ret, ctrl, err = pcall(require, path)

local is_debug = true       -- 调试阶段,会输出错误信息到页面上

if ret == false then
    if is_debug then
        ngx.status = 404
        ngx.say("<p style='font-size: 50px'>Error: <span style='color:red'>" .. ctrl .. "</span> module not found !</p>")
    end
    ngx.exit(404)
end

-- 尝试获取模块方法,不存在则报错
local req_method = ctrl[method]

if req_method == nil then
    if is_debug then
        ngx.status = 404
        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>")
    end
    ngx.exit(404)
end

-- 执行模块方法,报错则显示错误信息,所见即所得,可以追踪lua报错行数
ret, err = pcall(req_method)

if ret == false then
    if is_debug then
        ngx.status = 404
        ngx.say("<p style='font-size: 50px'>Error: <span style='color:red'>" .. err .. "</span></p>")
    else
        ngx.exit(500)
    end
end
```

## 异常处理

可以看到,从引入模块,到获取模块方法,已经执行方法,都有可能报错,这里通过pcall来进行调用,这种方式可以安全的调用lua代码,不会导致异常中断,然后通过定义一个变量,来区分是否为开发调试阶段,如果是则把错误信息输出到浏览器端,否则直接报404或者500,避免把错误信息输出到客户端,导致代码泄漏。

至此,一个简单的mvc框架已经可以使用了,但是现在还只能做前端渲染,下一章,我讲介绍如果进行服务端渲染。

[示例代码](https://github.com/362228416/openresty-web-dev) 参见demo8部分


================================================
FILE: demo8/conf/nginx.conf
================================================

worker_processes  1;

error_log logs/error.log notice;

events {
    worker_connections 1024;
}

http {
    lua_package_path "$prefix/lua/?.lua;$prefix/lualib/?.lua";
    server {
        listen 80;
        server_name localhost;
        lua_code_cache off;

        location / {
        	default_type "text/html; charset=utf-8";
        	content_by_lua_file lualib/lite/mvc.lua;
        }

        location ~ ^/js/|^/css/|\.html {
        	root html;
        }
    }
}


================================================
FILE: demo8/html/css/index.css
================================================
.container {
	width: 1000px;
	margin: 0 auto;
	text-align: center;
}

================================================
FILE: demo8/html/index.html
================================================
<html>
<head>
	<meta charset="UTF-8">
	<title>Home Page</title>
	<link rel="stylesheet" href="/css/index.css">
</head>
<body>
	<div class="container">
		<h1>用户列表</h1>
		<p id="users"></ul>
		<p>排名第二的是<span id="user"></span></p>
	</div>
	<script src="//cdn.bootcss.com/jquery/2.2.4/jquery.min.js"></script>
	<script src="/js/index.js"></script>
</body>
</html>

=========
Download .txt
gitextract_zad3cxq9/

├── .gitignore
├── README.md
├── demo1/
│   ├── README.md
│   ├── conf/
│   │   └── nginx.conf
│   ├── logs/
│   │   └── .gitignore
│   └── lua/
│       ├── hello.lua
│       └── welcome.lua
├── demo10/
│   ├── README.md
│   ├── conf/
│   │   └── nginx.conf
│   ├── html/
│   │   └── index.html
│   ├── logs/
│   │   └── .gitignore
│   ├── lua/
│   │   ├── rewrite.lua
│   │   └── web/
│   │       ├── article.lua
│   │       └── index.lua
│   └── lualib/
│       ├── lite/
│       │   ├── mvc.lua
│       │   └── req.lua
│       └── resty/
│           └── template.lua
├── demo11/
│   ├── README.md
│   ├── conf/
│   │   └── nginx.conf
│   ├── html/
│   │   └── index.html
│   ├── logs/
│   │   └── .gitignore
│   ├── lua/
│   │   ├── rewrite.lua
│   │   ├── waf.lua
│   │   └── web/
│   │       └── index.lua
│   └── lualib/
│       ├── lite/
│       │   ├── mvc.lua
│       │   └── req.lua
│       └── resty/
│           └── template.lua
├── demo12/
│   ├── README.md
│   ├── conf/
│   │   └── nginx.conf
│   ├── logs/
│   │   └── .gitignore
│   ├── lua/
│   │   ├── tpl/
│   │   │   └── index.html
│   │   └── web/
│   │       └── index.lua
│   └── lualib/
│       ├── lite/
│       │   ├── mvc.lua
│       │   └── req.lua
│       └── resty/
│           ├── cookie.lua
│           └── template.lua
├── demo13/
│   ├── README.md
│   ├── conf/
│   │   ├── GeoTrust_Global_CA.pem
│   │   └── nginx.conf
│   ├── html/
│   │   └── index.html
│   ├── logs/
│   │   └── .gitignore
│   ├── lua/
│   │   ├── req.lua
│   │   └── test.lua
│   └── lualib/
│       └── resty/
│           ├── http.lua
│           └── http_headers.lua
├── demo14/
│   ├── README.md
│   ├── conf/
│   │   └── nginx.conf
│   └── logs/
│       └── .gitignore
├── demo15/
│   └── README.md
├── demo16/
│   ├── README.md
│   ├── conf/
│   │   └── nginx.conf
│   ├── html/
│   │   ├── index.html
│   │   └── js/
│   │       └── index.js
│   ├── logs/
│   │   └── .gitignore
│   ├── lua/
│   │   ├── global_config.lua
│   │   ├── utils.lua
│   │   └── web/
│   │       ├── mvc.lua
│   │       └── template.lua
│   └── lualib/
│       └── resty/
│           └── template.lua
├── demo2/
│   ├── README.md
│   ├── conf/
│   │   └── nginx.conf
│   ├── logs/
│   │   └── .gitignore
│   └── lua/
│       ├── hello.lua
│       └── req.lua
├── demo3/
│   ├── README.md
│   ├── conf/
│   │   └── nginx.conf
│   ├── logs/
│   │   └── .gitignore
│   └── lua/
│       ├── hello.lua
│       └── req.lua
├── demo4/
│   ├── README.md
│   ├── conf/
│   │   └── nginx.conf
│   ├── logs/
│   │   └── .gitignore
│   └── lua/
│       ├── hello.lua
│       ├── redis.lua
│       └── req.lua
├── demo5/
│   ├── README.md
│   ├── conf/
│   │   └── nginx.conf
│   ├── logs/
│   │   └── .gitignore
│   └── lua/
│       ├── hello.lua
│       ├── mysql.lua
│       └── req.lua
├── demo6/
│   ├── README.md
│   ├── conf/
│   │   └── nginx.conf
│   ├── logs/
│   │   └── .gitignore
│   └── lua/
│       ├── hello.lua
│       └── req.lua
├── demo7/
│   ├── README.md
│   ├── conf/
│   │   └── nginx.conf
│   ├── html/
│   │   └── index.html
│   ├── logs/
│   │   └── .gitignore
│   ├── lua/
│   │   ├── hello.lua
│   │   ├── local-login.lua
│   │   ├── login.lua
│   │   └── req.lua
│   └── lualib/
│       └── resty/
│           ├── http.lua
│           └── http_headers.lua
├── demo8/
│   ├── README.md
│   ├── conf/
│   │   └── nginx.conf
│   ├── html/
│   │   ├── css/
│   │   │   └── index.css
│   │   ├── index.html
│   │   └── js/
│   │       └── index.js
│   ├── logs/
│   │   └── .gitignore
│   ├── lua/
│   │   └── web/
│   │       └── user.lua
│   └── lualib/
│       └── lite/
│           ├── mvc.lua
│           └── req.lua
├── demo9/
│   ├── README.md
│   ├── conf/
│   │   └── nginx.conf
│   ├── logs/
│   │   └── .gitignore
│   ├── lua/
│   │   ├── mvc.lua
│   │   ├── tpl/
│   │   │   └── index.html
│   │   └── web/
│   │       └── index.lua
│   └── lualib/
│       ├── lite/
│       │   ├── mvc.lua
│       │   └── req.lua
│       └── resty/
│           └── template.lua
└── install/
    └── install-openresty.sh
Download .txt
SYMBOL INDEX (1 symbols across 1 files)

FILE: demo16/html/js/index.js
  function hello (line 2) | function hello() {
Condensed preview — 115 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (248K chars).
[
  {
    "path": ".gitignore",
    "chars": 10,
    "preview": ".DS_Store\n"
  },
  {
    "path": "README.md",
    "chars": 2358,
    "preview": "# openresty-web-dev\n\n本项目是我写的一系列openresty web 前端开发文章的实例demo,方便测试运行,喜欢请点↑↑↑右上角Star↑↑↑\n### 文章目录\n\n[openresty 前端开发序](http://b"
  },
  {
    "path": "demo1/README.md",
    "chars": 3003,
    "preview": "OpenResty ™ 是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。\n"
  },
  {
    "path": "demo1/conf/nginx.conf",
    "chars": 303,
    "preview": "\nworker_processes  1;\n\nerror_log logs/error.log;\n\nevents {\n    worker_connections 1024;\n}\n\nhttp {\n    server {\n        l"
  },
  {
    "path": "demo1/logs/.gitignore",
    "chars": 12,
    "preview": "*.log\n*.pid\n"
  },
  {
    "path": "demo1/lua/hello.lua",
    "chars": 31,
    "preview": "ngx.say(\"<p>hello, world!</p>\")"
  },
  {
    "path": "demo1/lua/welcome.lua",
    "chars": 39,
    "preview": "ngx.say(\"welcome to openresty world!!\")"
  },
  {
    "path": "demo10/README.md",
    "chars": 1743,
    "preview": "#### 这一章主要介绍一下怎么用lua来进行url重写,其实通过nginx也可以完成url重写,但是重写规则比较复杂的时候,用nginx就没有那么方便了,用lua可以轻松搞定\n\n这里用到几个最核心的api就是`ngx.redirect`、"
  },
  {
    "path": "demo10/conf/nginx.conf",
    "chars": 493,
    "preview": "\nworker_processes  1;\n\nerror_log logs/error.log notice;\n\nevents {\n    worker_connections 1024;\n}\n\nhttp {\n    lua_package"
  },
  {
    "path": "demo10/html/index.html",
    "chars": 137,
    "preview": "<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>"
  },
  {
    "path": "demo10/logs/.gitignore",
    "chars": 12,
    "preview": "*.log\n*.pid\n"
  },
  {
    "path": "demo10/lua/rewrite.lua",
    "chars": 1142,
    "preview": "\nlocal _M = {}\n\nfunction _M.exec1(uri)       -- 正常版\n    local rewrite_urls = {}\n    local queryString = ngx.var.args\n   "
  },
  {
    "path": "demo10/lua/web/article.lua",
    "chars": 220,
    "preview": "local template = require \"resty.template\"\nlocal req = require \"lite.req\"\n\nlocal _M = {}\n\nfunction _M.index()\n\tlocal args"
  },
  {
    "path": "demo10/lua/web/index.lua",
    "chars": 180,
    "preview": "local req = require \"lite.req\"\n\nlocal _M = {}\n\nfunction _M.article()\n\tlocal args = req.getArgs()\n\tlocal id = args['id'] "
  },
  {
    "path": "demo10/lualib/lite/mvc.lua",
    "chars": 1750,
    "preview": "\nlocal uri = ngx.var.uri\n-- 如果是首页\nif uri == \"\" or uri == \"/\" then\n    local res = ngx.location.capture(\"/index.html\", {}"
  },
  {
    "path": "demo10/lualib/lite/req.lua",
    "chars": 439,
    "preview": "local _M = {}\n\n-- 获取http get/post 请求参数\nfunction _M.getArgs()\n    local request_method = ngx.var.request_method\n    local"
  },
  {
    "path": "demo10/lualib/resty/template.lua",
    "chars": 15478,
    "preview": "local setmetatable = setmetatable\nlocal loadstring = loadstring\nlocal loadchunk\nlocal tostring = tostring\nlocal setfenv "
  },
  {
    "path": "demo11/README.md",
    "chars": 2418,
    "preview": "## 为了保护站点安全,有时候我们需要一个web防火墙,来实现拦截过滤,在openresty里面其实很好做这个功能,下面我们简单实现一个黑白名单ip的功能\n\n# mvc 中加上waf模块\n\nlite/mvc.lua\n\n```\n-- waf "
  },
  {
    "path": "demo11/conf/nginx.conf",
    "chars": 493,
    "preview": "\nworker_processes  1;\n\nerror_log logs/error.log notice;\n\nevents {\n    worker_connections 1024;\n}\n\nhttp {\n    lua_package"
  },
  {
    "path": "demo11/html/index.html",
    "chars": 102,
    "preview": "<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",
    "chars": 12,
    "preview": "*.log\n*.pid\n"
  },
  {
    "path": "demo11/lua/rewrite.lua",
    "chars": 1142,
    "preview": "\nlocal _M = {}\n\nfunction _M.exec1(uri)       -- 正常版\n    local rewrite_urls = {}\n    local queryString = ngx.var.args\n   "
  },
  {
    "path": "demo11/lua/waf.lua",
    "chars": 1475,
    "preview": "\nlocal _M = {}\n\nfunction parse_ip(ip_str)\n    local ip_list = {}\n    local it, err = ngx.re.gmatch(ip_str, '([0-9]+)[.]("
  },
  {
    "path": "demo11/lua/web/index.lua",
    "chars": 105,
    "preview": "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",
    "chars": 2011,
    "preview": "\n-- waf begin\n\nlocal ret, waf = pcall(require, \"waf\") -- 安全引入rewrite模块,假如没有也不会报错\n\nif ret then\n    local c_ret, r_ret = p"
  },
  {
    "path": "demo11/lualib/lite/req.lua",
    "chars": 439,
    "preview": "local _M = {}\n\n-- 获取http get/post 请求参数\nfunction _M.getArgs()\n    local request_method = ngx.var.request_method\n    local"
  },
  {
    "path": "demo11/lualib/resty/template.lua",
    "chars": 15478,
    "preview": "local setmetatable = setmetatable\nlocal loadstring = loadstring\nlocal loadchunk\nlocal tostring = tostring\nlocal setfenv "
  },
  {
    "path": "demo12/README.md",
    "chars": 1538,
    "preview": "# 这章主要演示怎么通过lua操作cookie\n\n操作cookie有两种方式,一种是直接设置响应头,另外一种是用[lua-resty-cookie](https://github.com/cloudflare/lua-resty-cooki"
  },
  {
    "path": "demo12/conf/nginx.conf",
    "chars": 493,
    "preview": "\nworker_processes  1;\n\nerror_log logs/error.log notice;\n\nevents {\n    worker_connections 1024;\n}\n\nhttp {\n    lua_package"
  },
  {
    "path": "demo12/logs/.gitignore",
    "chars": 12,
    "preview": "*.log\n*.pid\n"
  },
  {
    "path": "demo12/lua/tpl/index.html",
    "chars": 602,
    "preview": "<html>\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    "
  },
  {
    "path": "demo12/lua/web/index.lua",
    "chars": 890,
    "preview": "local req = require \"lite.req\"\nlocal template = require \"resty.template\"\nlocal resty_cookie = require \"resty.cookie\"\nloc"
  },
  {
    "path": "demo12/lualib/lite/mvc.lua",
    "chars": 1447,
    "preview": "local uri = ngx.var.uri\n-- 如果是首页\nif uri == \"\" or uri == \"/\" then\n    local res = ngx.location.capture(\"/index.html\", {})"
  },
  {
    "path": "demo12/lualib/lite/req.lua",
    "chars": 439,
    "preview": "local _M = {}\n\n-- 获取http get/post 请求参数\nfunction _M.getArgs()\n    local request_method = ngx.var.request_method\n    local"
  },
  {
    "path": "demo12/lualib/resty/cookie.lua",
    "chars": 5677,
    "preview": "-- Copyright (C) 2013-2016 Jiale Zhi (calio), CloudFlare Inc.\n-- See RFC6265 http://tools.ietf.org/search/rfc6265\n-- req"
  },
  {
    "path": "demo12/lualib/resty/template.lua",
    "chars": 15478,
    "preview": "local setmetatable = setmetatable\nlocal loadstring = loadstring\nlocal loadchunk\nlocal tostring = tostring\nlocal setfenv "
  },
  {
    "path": "demo13/README.md",
    "chars": 2622,
    "preview": "#### 在对接一些第三方系统的时候,经常会遇到https的问题,好比如做微信公众号的开发,接口基本都是https的,这个时候,很多人试着用http的那种方式来访问https,结果报错了,误以为lua不支持https,其实不是的,只需要配置"
  },
  {
    "path": "demo13/conf/GeoTrust_Global_CA.pem",
    "chars": 1216,
    "preview": "-----BEGIN CERTIFICATE-----\nMIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT\nMRYwFAYDVQQKEw1HZW9UcnVzdCB"
  },
  {
    "path": "demo13/conf/nginx.conf",
    "chars": 637,
    "preview": "\nworker_processes  1;\n\nerror_log logs/error.log notice;\n\nevents {\n    worker_connections 1024;\n}\n\nhttp {\n    lua_ssl_ver"
  },
  {
    "path": "demo13/html/index.html",
    "chars": 339,
    "preview": "<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=\"t"
  },
  {
    "path": "demo13/logs/.gitignore",
    "chars": 12,
    "preview": "*.log\n*.pid\n"
  },
  {
    "path": "demo13/lua/req.lua",
    "chars": 439,
    "preview": "local _M = {}\n\n-- 获取http get/post 请求参数\nfunction _M.getArgs()\n    local request_method = ngx.var.request_method\n    local"
  },
  {
    "path": "demo13/lua/test.lua",
    "chars": 817,
    "preview": "local req = require \"req\"\nlocal cjson = require \"cjson\"\nlocal http = require \"resty.http\"\n\nfunction get_access_token(cod"
  },
  {
    "path": "demo13/lualib/resty/http.lua",
    "chars": 22692,
    "preview": "local http_headers = require \"resty.http_headers\"\n\nlocal ngx_socket_tcp = ngx.socket.tcp\nlocal ngx_req = ngx.req\nlocal n"
  },
  {
    "path": "demo13/lualib/resty/http_headers.lua",
    "chars": 1716,
    "preview": "local   rawget, rawset, setmetatable =\n        rawget, rawset, setmetatable\n\nlocal str_gsub = string.gsub\nlocal str_lowe"
  },
  {
    "path": "demo14/README.md",
    "chars": 1805,
    "preview": "有的时候我们希望在启动openresty的时候,在后台运行一些定时任务,可以放在`init_worker_by_lua_block`阶段运行,如果想重复执行可以通过不断`ngx.timer.at`来实现多次调用\n\n需要注意的是`init_w"
  },
  {
    "path": "demo14/conf/nginx.conf",
    "chars": 956,
    "preview": "worker_processes  3;\n\nerror_log logs/error.log notice;\n\nevents {\n    worker_connections 1024;\n}\n\nhttp {\n    lua_package_"
  },
  {
    "path": "demo14/logs/.gitignore",
    "chars": 12,
    "preview": "*.log\n*.pid\n"
  },
  {
    "path": "demo15/README.md",
    "chars": 3113,
    "preview": "以前用tengine自带了session_sticky,现在换成openresty,没有现成的,nginx-sticky-module 太老, 编译有点问题,于是自己写了一个,废话不多说,直接看代码\n\nlua/balancer.lua \n`"
  },
  {
    "path": "demo16/README.md",
    "chars": 2046,
    "preview": "\n# 这篇文章主要介绍,怎么用lua实现单一文件入口\n\n之前的例子都需要在nginx.conf文件中,单独配置静态文件location,以及lua_package_path,其实可以把这些整合到一个入口文件\n\n## 几个关键指令和api\n\n"
  },
  {
    "path": "demo16/conf/nginx.conf",
    "chars": 405,
    "preview": "\nworker_processes  1;\n\nerror_log logs/error.log notice;\n\nevents {\n    worker_connections 1024;\n}\n\nhttp {\n\n    server {\n "
  },
  {
    "path": "demo16/html/index.html",
    "chars": 246,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>{*site_name*}</title>\n    <script type=\"te"
  },
  {
    "path": "demo16/html/js/index.js",
    "chars": 40,
    "preview": "\nfunction hello() {\n    alert('hello')\n}"
  },
  {
    "path": "demo16/logs/.gitignore",
    "chars": 12,
    "preview": "*.log\n*.pid\n"
  },
  {
    "path": "demo16/lua/global_config.lua",
    "chars": 208,
    "preview": "\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 ="
  },
  {
    "path": "demo16/lua/utils.lua",
    "chars": 259,
    "preview": "\nlocal _M = {\n    __version = 0.1\n}\n\n-- 分隔字符串\nfunction split( str,reps )\n    local resultStrList = {}\n    string.gsub(st"
  },
  {
    "path": "demo16/lua/web/mvc.lua",
    "chars": 780,
    "preview": "local package = package\nlocal pack_path = package.path\nlocal prefix = ngx.config.prefix()\nlocal p = prefix .. \"lualib/?."
  },
  {
    "path": "demo16/lua/web/template.lua",
    "chars": 377,
    "preview": "local template = require \"resty.template\"\nlocal global_config = require \"global_config\"\n\nlocal _M = {\n    __version = 0."
  },
  {
    "path": "demo16/lualib/resty/template.lua",
    "chars": 15478,
    "preview": "local setmetatable = setmetatable\nlocal loadstring = loadstring\nlocal loadchunk\nlocal tostring = tostring\nlocal setfenv "
  },
  {
    "path": "demo2/README.md",
    "chars": 2031,
    "preview": "#### 这一章主要介绍介绍怎么获取请求参数,并且处理之后返回数据\n\n我们知道http请求通常分为两种,分别是GET,POST,在http协议中,GET参数通常会紧跟在uri后面,而POST请求参数则包含在请求体中,nginx默认情况下是不"
  },
  {
    "path": "demo2/conf/nginx.conf",
    "chars": 345,
    "preview": "\nworker_processes  1;\n\nerror_log logs/error.log;\n\nevents {\n    worker_connections 1024;\n}\n\nhttp {\n    lua_package_path \""
  },
  {
    "path": "demo2/logs/.gitignore",
    "chars": 12,
    "preview": "*.log\n*.pid\n"
  },
  {
    "path": "demo2/lua/hello.lua",
    "chars": 178,
    "preview": "\nlocal req = require \"req\"\n\nlocal args = req.getArgs()\n\nlocal name = args['name']\n\nif name == nil or name == \"\" then\n\tna"
  },
  {
    "path": "demo2/lua/req.lua",
    "chars": 439,
    "preview": "local _M = {}\n\n-- 获取http get/post 请求参数\nfunction _M.getArgs()\n    local request_method = ngx.var.request_method\n    local"
  },
  {
    "path": "demo3/README.md",
    "chars": 868,
    "preview": "#### 这章主要介绍一下,lua怎么返回一个json字符串,怎么把一个table转成json字符串,又怎么把一个json字符串转成json\n\n其实很简答,直接使用cjson库的encode、decode方法即可\n\nlua/hello.lu"
  },
  {
    "path": "demo3/conf/nginx.conf",
    "chars": 345,
    "preview": "\nworker_processes  1;\n\nerror_log logs/error.log;\n\nevents {\n    worker_connections 1024;\n}\n\nhttp {\n    lua_package_path \""
  },
  {
    "path": "demo3/logs/.gitignore",
    "chars": 12,
    "preview": "*.log\n*.pid\n"
  },
  {
    "path": "demo3/lua/hello.lua",
    "chars": 651,
    "preview": "local cjson = require \"cjson\"\n\n-- 先定义一个json字符串\nlocal json_str = '{\"name\": \"Bruce.Lin\", \"age\": 25}'\n-- 这里把它转成对象,然后输出属性\nlo"
  },
  {
    "path": "demo3/lua/req.lua",
    "chars": 439,
    "preview": "local _M = {}\n\n-- 获取http get/post 请求参数\nfunction _M.getArgs()\n    local request_method = ngx.var.request_method\n    local"
  },
  {
    "path": "demo4/README.md",
    "chars": 1725,
    "preview": "#### 这章主要演示怎么通过lua连接redis,并根据用户输入的key从redis获取value,并返回给用户\n\n操作redis主要用到了lua-resty-redis库,代码可以在[github](https://github.com"
  },
  {
    "path": "demo4/conf/nginx.conf",
    "chars": 372,
    "preview": "\nworker_processes  1;\n\nerror_log logs/error.log;\n\nevents {\n    worker_connections 1024;\n}\n\nhttp {\n    lua_package_path \""
  },
  {
    "path": "demo4/logs/.gitignore",
    "chars": 12,
    "preview": "*.log\n*.pid\n"
  },
  {
    "path": "demo4/lua/hello.lua",
    "chars": 325,
    "preview": "local cjson = require \"cjson\"\nlocal redis = require \"redis\"\nlocal req = require \"req\"\n\nlocal args = req.getArgs()\n\nlocal"
  },
  {
    "path": "demo4/lua/redis.lua",
    "chars": 762,
    "preview": "local redis = require \"resty.redis\"\n\nlocal config = {\n\thost = \"127.0.0.1\",\n    port = 6379,\n    -- pass = \"1234\"  -- red"
  },
  {
    "path": "demo4/lua/req.lua",
    "chars": 439,
    "preview": "local _M = {}\n\n-- 获取http get/post 请求参数\nfunction _M.getArgs()\n    local request_method = ngx.var.request_method\n    local"
  },
  {
    "path": "demo5/README.md",
    "chars": 1922,
    "preview": "openresty 前端开发入门五之Mysql篇\n\n#### 这章主要演示怎么通过lua连接mysql,并根据用户输入的name从mysql获取数据,并返回给用户\n\n操作mysql主要用到了lua-resty-mysql库,代码可以在[gi"
  },
  {
    "path": "demo5/conf/nginx.conf",
    "chars": 366,
    "preview": "\nworker_processes  1;\n\nerror_log logs/error.log;\n\nevents {\n    worker_connections 1024;\n}\n\nhttp {\n    lua_package_path \""
  },
  {
    "path": "demo5/logs/.gitignore",
    "chars": 12,
    "preview": "*.log\n*.pid\n"
  },
  {
    "path": "demo5/lua/hello.lua",
    "chars": 531,
    "preview": "local cjson = require \"cjson\"\nlocal mysql = require \"mysql\"\nlocal req = require \"req\"\n\nlocal args = req.getArgs()\n\nlocal"
  },
  {
    "path": "demo5/lua/mysql.lua",
    "chars": 699,
    "preview": "local mysql = require \"resty.mysql\"\n\nlocal config = {\n    host = \"localhost\",\n    port = 3306,\n    database = \"mysql\",\n "
  },
  {
    "path": "demo5/lua/req.lua",
    "chars": 439,
    "preview": "local _M = {}\n\n-- 获取http get/post 请求参数\nfunction _M.getArgs()\n    local request_method = ngx.var.request_method\n    local"
  },
  {
    "path": "demo6/README.md",
    "chars": 1523,
    "preview": "#### 大多数情况下,调试信息,都可以通过ngx.say打印出来,但是有的时候,我们希望打印调试日志,不影响到返回数据,所以系统打印到其它地方,比如日志文件,或者控制台\n\n这里主要用到一个方法就是ngx.log,这个方法可以将日志输出到e"
  },
  {
    "path": "demo6/conf/nginx.conf",
    "chars": 366,
    "preview": "\nworker_processes  1;\n\nerror_log logs/error.log;\n\nevents {\n    worker_connections 1024;\n}\n\nhttp {\n    lua_package_path \""
  },
  {
    "path": "demo6/logs/.gitignore",
    "chars": 12,
    "preview": "*.log\n*.pid\n"
  },
  {
    "path": "demo6/lua/hello.lua",
    "chars": 437,
    "preview": "\nngx.say('print to browser')\n\nngx.log(ngx.ALERT, 'print to error.log')\nngx.log(ngx.STDERR, 'print to error.log')\nngx.log"
  },
  {
    "path": "demo6/lua/req.lua",
    "chars": 439,
    "preview": "local _M = {}\n\n-- 获取http get/post 请求参数\nfunction _M.getArgs()\n    local request_method = ngx.var.request_method\n    local"
  },
  {
    "path": "demo7/README.md",
    "chars": 5528,
    "preview": "#### 做前端开发,大多数情况下,都需要跟后端打交道,而最常见的方式则是通过http请求,进行通信。\n在openresty中,通过http跟后端整合通信的方式又很多种,各有各的好处,可以根据情况交叉使用\n\n## 1、直接proxy\n\n这种"
  },
  {
    "path": "demo7/conf/nginx.conf",
    "chars": 783,
    "preview": "\nworker_processes  1;\n\nerror_log logs/error.log notice;\n\nevents {\n    worker_connections 1024;\n}\n\nhttp {\n    lua_package"
  },
  {
    "path": "demo7/html/index.html",
    "chars": 1007,
    "preview": "<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=\"userna"
  },
  {
    "path": "demo7/logs/.gitignore",
    "chars": 12,
    "preview": "*.log\n*.pid\n"
  },
  {
    "path": "demo7/lua/hello.lua",
    "chars": 437,
    "preview": "\nngx.say('print to browser')\n\nngx.log(ngx.ALERT, 'print to error.log')\nngx.log(ngx.STDERR, 'print to error.log')\nngx.log"
  },
  {
    "path": "demo7/lua/local-login.lua",
    "chars": 946,
    "preview": "local req = require \"req\"\nlocal cjson = require \"cjson\"\nlocal http = require \"resty.http\"\n\nlocal args = req.getArgs()\n\n-"
  },
  {
    "path": "demo7/lua/login.lua",
    "chars": 426,
    "preview": "local req = require \"req\"\nlocal cjson = require \"cjson\"\n\nlocal args = req.getArgs()\n\nlocal username = args['username']\nl"
  },
  {
    "path": "demo7/lua/req.lua",
    "chars": 439,
    "preview": "local _M = {}\n\n-- 获取http get/post 请求参数\nfunction _M.getArgs()\n    local request_method = ngx.var.request_method\n    local"
  },
  {
    "path": "demo7/lualib/resty/http.lua",
    "chars": 22692,
    "preview": "local http_headers = require \"resty.http_headers\"\n\nlocal ngx_socket_tcp = ngx.socket.tcp\nlocal ngx_req = ngx.req\nlocal n"
  },
  {
    "path": "demo7/lualib/resty/http_headers.lua",
    "chars": 1716,
    "preview": "local   rawget, rawset, setmetatable =\n        rawget, rawset, setmetatable\n\nlocal str_gsub = string.gsub\nlocal str_lowe"
  },
  {
    "path": "demo8/README.md",
    "chars": 2740,
    "preview": "#### 通过前面几章,我们已经掌握了一些基本的开发知识,但是代码结构比较简单,缺乏统一的标准,模块化,也缺乏统一的异常处理,这一章我们主要来学习如何封装一个轻量级的MVC框架,规范以及简化开发,并且提供类似php所见即所得的能力\n\n## "
  },
  {
    "path": "demo8/conf/nginx.conf",
    "chars": 471,
    "preview": "\nworker_processes  1;\n\nerror_log logs/error.log notice;\n\nevents {\n    worker_connections 1024;\n}\n\nhttp {\n    lua_package"
  },
  {
    "path": "demo8/html/css/index.css",
    "chars": 68,
    "preview": ".container {\n\twidth: 1000px;\n\tmargin: 0 auto;\n\ttext-align: center;\n}"
  },
  {
    "path": "demo8/html/index.html",
    "chars": 359,
    "preview": "<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<"
  },
  {
    "path": "demo8/html/js/index.js",
    "chars": 209,
    "preview": "(function(){\n\t$.getJSON('/user/', function(data){\n\t\t$('#users').html(data.join(','))\n\t\tconsole.log(data)\n\t})\n\t$.get('/us"
  },
  {
    "path": "demo8/logs/.gitignore",
    "chars": 12,
    "preview": "*.log\n*.pid\n"
  },
  {
    "path": "demo8/lua/web/user.lua",
    "chars": 324,
    "preview": "local cjson = require \"cjson\"\nlocal req = require \"lite.req\"\n\nlocal _M = {}\n\nlocal users = {\"张三\", \"李四\", \"王五\"}\n\nfunction "
  },
  {
    "path": "demo8/lualib/lite/mvc.lua",
    "chars": 1461,
    "preview": "\nlocal uri = ngx.var.uri\n-- 如果是首页\nif uri == \"\" or uri == \"/\" then\n    local res = ngx.location.capture(\"/index.html\", {}"
  },
  {
    "path": "demo8/lualib/lite/req.lua",
    "chars": 439,
    "preview": "local _M = {}\n\n-- 获取http get/post 请求参数\nfunction _M.getArgs()\n    local request_method = ngx.var.request_method\n    local"
  },
  {
    "path": "demo9/README.md",
    "chars": 2145,
    "preview": "#### 这一章主要介绍怎么使用模板,进行后端渲染,主要用到了[lua-resty-template](https://github.com/bungle/lua-resty-template)这个库,直接下载下来,放到lualib里面就行"
  },
  {
    "path": "demo9/conf/nginx.conf",
    "chars": 493,
    "preview": "\nworker_processes  1;\n\nerror_log logs/error.log notice;\n\nevents {\n    worker_connections 1024;\n}\n\nhttp {\n    lua_package"
  },
  {
    "path": "demo9/logs/.gitignore",
    "chars": 12,
    "preview": "*.log\n*.pid\n"
  },
  {
    "path": "demo9/lua/mvc.lua",
    "chars": 1461,
    "preview": "\nlocal uri = ngx.var.uri\n-- 如果是首页\nif uri == \"\" or uri == \"/\" then\n    local res = ngx.location.capture(\"/index.html\", {}"
  },
  {
    "path": "demo9/lua/tpl/index.html",
    "chars": 111,
    "preview": "<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",
    "chars": 382,
    "preview": "local template = require \"resty.template\"\n\nlocal _M = {}\n\nfunction _M.index()\n\tlocal model = {title = \"hello template\", "
  },
  {
    "path": "demo9/lualib/lite/mvc.lua",
    "chars": 1461,
    "preview": "\nlocal uri = ngx.var.uri\n-- 如果是首页\nif uri == \"\" or uri == \"/\" then\n    local res = ngx.location.capture(\"/index.html\", {}"
  },
  {
    "path": "demo9/lualib/lite/req.lua",
    "chars": 439,
    "preview": "local _M = {}\n\n-- 获取http get/post 请求参数\nfunction _M.getArgs()\n    local request_method = ngx.var.request_method\n    local"
  },
  {
    "path": "demo9/lualib/resty/template.lua",
    "chars": 15478,
    "preview": "local setmetatable = setmetatable\nlocal loadstring = loadstring\nlocal loadchunk\nlocal tostring = tostring\nlocal setfenv "
  },
  {
    "path": "install/install-openresty.sh",
    "chars": 497,
    "preview": "#!/bin/bash\nsudo apt-get update\nsudo apt-get install -y libpcre3 libpcre3-dev\nsudo apt-get install -y openssl libssl-dev"
  }
]

About this extraction

This page contains the full source code of the 362228416/openresty-web-dev GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 115 files (207.1 KB), approximately 63.2k tokens, and a symbol index with 1 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!