<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>绿色记忆 &#187; Lua</title>
	<atom:link href="https://blog.gmem.cc/category/work/lua/feed" rel="self" type="application/rss+xml" />
	<link>https://blog.gmem.cc</link>
	<description></description>
	<lastBuildDate>Tue, 21 Apr 2026 10:40:56 +0000</lastBuildDate>
	<language>en-US</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.9.14</generator>
	<item>
		<title>OpenResty学习笔记</title>
		<link>https://blog.gmem.cc/openresty-study-note</link>
		<comments>https://blog.gmem.cc/openresty-study-note#comments</comments>
		<pubDate>Sat, 24 Feb 2018 09:13:20 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[Lua]]></category>
		<category><![CDATA[Nginx]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=18811</guid>
		<description><![CDATA[<p>简介 OpenResty是一个基于Nginx+Lua的Web运行环境，它打包了标准的 Nginx 核心，很多的常用的第三方模块，以及它们的大多数依赖项。OpenResty可以用来实现高并发的动态Web应用 Open 取自“开放”之意，而Resty便是 REST 风格的意思 OpenResty使用的Lua版本是5.1，不使用更新版本的原因是5.2+版本的Lua API和C API都不兼容于5.1。 自从 OpenResty 1.5.8.1 版本之后，默认捆绑的 Lua 解释器就被替换成了 LuaJIT，而不再是标准 Lua。 安装 [crayon-69e7b06a5c1ab573608096/] 起步 <a class="read-more" href="https://blog.gmem.cc/openresty-study-note">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/openresty-study-note">OpenResty学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">简介</span></div>
<p>OpenResty是一个基于Nginx+Lua的Web运行环境，它打包了标准的 Nginx 核心，很多的常用的第三方模块，以及它们的大多数依赖项。OpenResty可以用来实现高并发的动态Web应用</p>
<p>Open 取自“开放”之意，而Resty便是 REST 风格的意思</p>
<p>OpenResty使用的Lua版本是5.1，不使用更新版本的原因是5.2+版本的Lua API和C API都不兼容于5.1。</p>
<p>自从 OpenResty 1.5.8.1 版本之后，默认捆绑的 Lua 解释器就被替换成了 LuaJIT，而不再是标准 Lua。</p>
<div class="blog_h1"><span class="graybg">安装</span></div>
<pre class="crayon-plain-tag">wget https://openresty.org/download/openresty-1.13.6.1.tar.gz
tar xzf openresty-1.13.6.1.tar.gz
cd openresty-1.13.6.1/
./configure --prefix=/home/alex/Lua/openresty/1.13.6    \
            # 启用LuaJIT，这是一个Lua的JIT编译器，默认没有启用
            --with-luajit \
            # 使用Lua 5.1标准解释器，不推荐，应该尽可能使用LuaJIT
            --with-lua51 \
            # Drizzle、Postgres、 Iconv这几个模块默认没有启用
           --with-http_drizzle_module、--with-http_postgres_module  --with-http_iconv_module 
# Nginx 路径如下：
# nginx path prefix: "/home/alex/Lua/openresty/1.13.6/nginx"
# nginx binary file: "/home/alex/Lua/openresty/1.13.6/nginx/sbin/nginx"
# nginx modules path: "/home/alex/Lua/openresty/1.13.6/nginx/modules"
# nginx configuration prefix: "/home/alex/Lua/openresty/1.13.6/nginx/conf"
# nginx configuration file: "/home/alex/Lua/openresty/1.13.6/nginx/conf/nginx.conf"
# nginx pid file: "/home/alex/Lua/openresty/1.13.6/nginx/logs/nginx.pid"
# nginx error log file: "/home/alex/Lua/openresty/1.13.6/nginx/logs/error.log"
# nginx http access log file: "/home/alex/Lua/openresty/1.13.6/nginx/logs/access.log"
# nginx http client request body temporary files: "client_body_temp"
# nginx http proxy temporary files: "proxy_temp"
# nginx http fastcgi temporary files: "fastcgi_temp"
# nginx http uwsgi temporary files: "uwsgi_temp"
# nginx http scgi temporary files: "scgi_temp"

make -j8 &amp;&amp; make install</pre>
<div class="blog_h1"><span class="graybg">起步</span></div>
<div class="blog_h2"><span class="graybg">创建工程</span></div>
<p>一个OpenRestry工程，实际上就是对应了Nginx运行环境的目录结构。例如：</p>
<pre class="crayon-plain-tag">mkdir -p ~/Lua/projects/openrestry
cd ~/Lua/projects/openrestry
mkdir conf &amp;&amp; mkdir logs</pre>
<div class="blog_h2"><span class="graybg">配置文件</span></div>
<p>使用lua-nginx-module模块提供的指令，你可以嵌入Lua脚本到Nginx配置文件中，以生成响应内容：</p>
<pre class="crayon-plain-tag">daemon off;
worker_processes  1;
error_log stderr debug;
events {
    worker_connections 1024;
}
http {
    access_log /dev/stdout;
    server {
        listen 8080;
        location / {
            default_type text/html;
            # lua-nginx-module模块，属于OpenResty项目，支持根据Lua脚本输出响应
            content_by_lua_block {
                ngx.say("&lt;p&gt;hello, world&lt;/p&gt;")
            }
        }
    }
}</pre>
<div class="blog_h2"><span class="graybg">启动服务</span></div>
<pre class="crayon-plain-tag"># 将Nginx运行时的前缀设置为上面的工程目录
~/Lua/openresty/1.13.6/nginx/sbin/nginx -p ~/Lua/projects/openrestry -c conf/nginx.conf</pre>
<div class="blog_h2"><span class="graybg">测试服务</span></div>
<pre class="crayon-plain-tag">curl http://localhost:8080/
# &lt;p&gt;hello, world&lt;/p&gt;</pre>
<div class="blog_h1"><span class="graybg">IDE</span></div>
<div class="blog_h2"><span class="graybg">Intellij</span></div>
<p>安装三个插件：</p>
<ol>
<li>nginx support：支持Nginx配置文件的语法高亮、格式化、自动完成。自动基于Lua语言对lua-nginx-module模块的相关指令进行语法高亮、自动完成</li>
<li>Lua：支持Lua语言的开发和调试</li>
<li>OpenResty Lua Support：为OpenResty提供自动完成</li>
</ol>
<div class="blog_h1"><span class="graybg">ngx_lua_module</span></div>
<div class="blog_h2"><span class="graybg">指令执行阶段</span></div>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2018/02/ngx-lua-phase.png"><img class="aligncenter  wp-image-19291" src="https://blog.gmem.cc/wp-content/uploads/2018/02/ngx-lua-phase.png" alt="ngx-lua-phase" width="843" height="798" /></a></p>
<p>不同类型的指令，职责如下：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">指令</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>set_by_lua*</td>
<td>流程分支处理判断变量初始化</td>
</tr>
<tr>
<td>rewrite_by_lua*</td>
<td>转发、重定向、缓存等功能</td>
</tr>
<tr>
<td>access_by_lua*</td>
<td>IP 准入、身份验证、接口权限、解密</td>
</tr>
<tr>
<td>content_by_lua*</td>
<td>内容生成</td>
</tr>
<tr>
<td>header_filter_by_lua*</td>
<td>响应头部过滤处理，可以添加响应头</td>
</tr>
<tr>
<td>body_filter_by_lua*</td>
<td>响应体过滤处理，例如转换响应体</td>
</tr>
<tr>
<td>log_by_lua*</td>
<td>异步完成日志记录，日志可以记录在本地，还可以同步到其他机器</td>
</tr>
</tbody>
</table>
<p>尽管仅使用单个阶段的指令content_by_lua*就可以完成以上职责，但是把逻辑划分在不同阶段，更加容易维护。</p>
<div class="blog_h1"><span class="graybg">HowTos</span></div>
<div class="blog_h2"><span class="graybg">API框架</span></div>
<p>一个简单的API框架：</p>
<pre class="crayon-plain-tag">daemon off;
worker_processes  1;
error_log stderr debug;
events {
    worker_connections 1024;
}
http {
    access_log /dev/stdout;
    # lua模块搜索路径
    # 如果使用相对路径，则必须将Nginx所在目录作为工作目录，然后启动服务
    # ${prefix}为Nginx的前缀目录，可以在启动Nginx时使用-p来指定
    lua_package_path '$prefix/scripts/?.lua;;';

    # 在开发阶段，可以设置为off，这样避免每次修改代码后都需要reload
    # 生产环境一定要设置为on
    lua_code_cache off;

    server {
        listen 80;

        location ~ ^/api/([-_a-zA-Z0-9]+) {
            # 在access阶段执行，进行合法性校验
            access_by_lua_file  scripts/auth-and-check.lua;
            # 生成内容，API名称即为Lua脚本名称
            content_by_lua_file scripts/$1.lua;
        }
    }
}</pre>
<p>在access阶段，你可以进行身份验证、访问控制、请求参数校验：</p>
<pre class="crayon-plain-tag">-- 黑名单
local black_ips = {["127.0.0.1"]=true}

-- 当前客户端IP
local ip = ngx.var.remote_addr
if true == black_ips[ip] then
    -- 返回相应的HTTP状态码
    ngx.exit(ngx.HTTP_FORBIDDEN)
end</pre>
<div class="blog_h2"><span class="graybg">使用Ng变量</span></div>
<p>要在OpenResty中引用Nginx变量，可以使用<pre class="crayon-plain-tag">ngx.var.VARIABLE</pre>，要将变量从字符串转换为数字，可以使用<pre class="crayon-plain-tag">tonumber</pre>函数。</p>
<p>经常用到的Ng变量如下表：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">变量</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>arg_name</td>
<td>请求中的name参数</td>
</tr>
<tr>
<td>args</td>
<td>请求中的参数</td>
</tr>
<tr>
<td>binary_remote_addr</td>
<td>远程地址的二进制表示</td>
</tr>
<tr>
<td>body_bytes_sent</td>
<td>已发送的消息体字节数</td>
</tr>
<tr>
<td>content_length</td>
<td>HTTP请求信息里的"Content-Length"</td>
</tr>
<tr>
<td>content_type</td>
<td>请求信息里的"Content-Type"</td>
</tr>
<tr>
<td>document_root</td>
<td>针对当前请求的根路径设置值</td>
</tr>
<tr>
<td>document_uri</td>
<td>与$uri相同; 比如 /test2/test.php</td>
</tr>
<tr>
<td>host</td>
<td>请求信息中的"Host"，如果请求中没有Host行，则等于设置的服务器名</td>
</tr>
<tr>
<td>hostname</td>
<td>机器名使用 gethostname系统调用的值</td>
</tr>
<tr>
<td>http_cookie</td>
<td>Cookie信息</td>
</tr>
<tr>
<td>http_referer</td>
<td>引用地址</td>
</tr>
<tr>
<td>http_user_agent</td>
<td>客户端代理信息</td>
</tr>
<tr>
<td>http_via</td>
<td>最后一个访问服务器的Ip地址。</td>
</tr>
<tr>
<td>http_x_forwarded_for</td>
<td>相当于网络访问路径</td>
</tr>
<tr>
<td>is_args</td>
<td>如果请求行带有参数，返回“?”，否则返回空字符串</td>
</tr>
<tr>
<td>limit_rate</td>
<td>
<p>对连接速率的限制。此变量支持写入：</p>
<pre class="crayon-plain-tag">-- 设置当前请求的响应传递速率限制
ngx.var.limit_rate = 1000 </pre>
</td>
</tr>
<tr>
<td>nginx_version</td>
<td>当前运行的nginx版本号</td>
</tr>
<tr>
<td>pid</td>
<td>Worker进程的PID</td>
</tr>
<tr>
<td>query_string</td>
<td>与$args相同</td>
</tr>
<tr>
<td>realpath_root</td>
<td>按root指令或alias指令算出的当前请求的绝对路径。其中的符号链接都会解析成真是文件路径</td>
</tr>
<tr>
<td>remote_addr</td>
<td>客户端IP地址</td>
</tr>
<tr>
<td>remote_port</td>
<td>客户端端口号</td>
</tr>
<tr>
<td>remote_user</td>
<td>客户端用户名，认证用</td>
</tr>
<tr>
<td>request</td>
<td>用户请求</td>
</tr>
<tr>
<td>request_body</td>
<td>这个变量（0.7.58+）包含请求的主要信息。在使用proxy_pass或fastcgi_pass指令的location中比较有意义</td>
</tr>
<tr>
<td>request_body_file</td>
<td>客户端请求主体信息的临时文件名</td>
</tr>
<tr>
<td>request_completion</td>
<td>如果请求成功，设为"OK"；如果请求未完成或者不是一系列请求中最后一部分则设为空</td>
</tr>
<tr>
<td>request_filename</td>
<td>当前请求的文件路径名，比如/opt/nginx/www/test.php</td>
</tr>
<tr>
<td>request_method</td>
<td>请求的方法，比如"GET"、"POST"等</td>
</tr>
<tr>
<td>request_uri</td>
<td>请求的URI，带参数</td>
</tr>
<tr>
<td>scheme</td>
<td>所用的协议，比如http或者是https</td>
</tr>
<tr>
<td>server_addr</td>
<td>服务器地址，如果没有用listen指明服务器地址，使用这个变量将发起一次系统调用以取得地址(造成资源浪费)</td>
</tr>
<tr>
<td>server_name</td>
<td>请求到达的服务器名</td>
</tr>
<tr>
<td>server_port</td>
<td>请求到达的服务器端口号</td>
</tr>
<tr>
<td>server_protocol</td>
<td>请求的协议版本，"HTTP/1.0"或"HTTP/1.1"</td>
</tr>
<tr>
<td>uri</td>
<td>请求的URI，可能和最初的值有不同，比如经过重定向之类的</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">数据共享</span></div>
<div class="blog_h3"><span class="graybg">跨工作进程</span></div>
<p>可以使用共享内存方式实现。
<div class="blog_h3"><span class="graybg">跨请求</span></div>
<p>可以使用Lua模块方式实现。</p>
<div class="blog_h3"><span class="graybg">跨阶段</span></div>
<p>在单个请求中，跨越多个Ng处理阶段（access、content）共享变量时，可以使用<pre class="crayon-plain-tag">ngx.ctx</pre>表：</p>
<pre class="crayon-plain-tag">location /test {
     rewrite_by_lua_block {
         ngx.ctx.foo = 76
     }
     access_by_lua_block {
         ngx.ctx.foo = ngx.ctx.foo + 3
     }
     content_by_lua_block {
         ngx.say(ngx.ctx.foo)
     }
 }</pre>
<p>ngx.ctx表的生命周期和请求相同，类似于Nginx变量。需要注意，<span style="background-color: #c0c0c0;">每个子请求都有自己的ngx.ctx表</span>，它们相互独立。</p>
<p>你可以为ngx.ctx表注册元表，任何数据都可以放到该表中。</p>
<p>注意：访问<span style="background-color: #c0c0c0;">ngx.ctx需要相对昂贵的元方法调用，不要为了避免传参而大量使用</span>，影响性能。</p>
<div class="blog_h2"><span class="graybg">指定Lua包路径</span></div>
<p>使用如下指令：</p>
<pre class="crayon-plain-tag">#  设置纯 Lua 扩展库的搜寻路径
# ';;' 是默认路径
lua_package_path "/path/to/lua-resty-logger-socket/lib/?.lua;;";

# 设置 C 编写的 Lua 扩展模块的搜寻路径
# ';;' 是默认路径
lua_package_cpath '/bar/baz/?.so;/blah/blah/?.so;;';</pre>
<div class="blog_h2"><span class="graybg">请求参数</span></div>
<pre class="crayon-plain-tag">location /print-params{
    # 使用Ng配置文件外部的Lua脚本
    content_by_lua_file scripts/print-params.lua;
}</pre>
<div class="blog_h3"><span class="graybg">获取URI参数</span></div>
<pre class="crayon-plain-tag">local args = ngx.req.get_uri_args()
for name, value in pairs(args) do
    ngx.say(name .. ' ' .. value)
end</pre>
<p>测试：</p>
<pre class="crayon-plain-tag">curl 'http://localhost:8080/print-params?user=alex&amp;age=32'
# user alex
# age 32</pre>
<div class="blog_h3"><span class="graybg">获取POST参数 </span></div>
<pre class="crayon-plain-tag">-- 必须先读取请求体，才能从中解析参数
ngx.req.read_body()
-- 获取POST参数
local args = ngx.req.get_post_args()

for name, value in pairs(args) do
    ngx.say(name .. ' ' .. value)
end
-- curl -d 'user=alex&amp;age=32' 'http://localhost:8080/print-params'</pre>
<div class="blog_h3"><span class="graybg">传递请求参数</span></div>
<p>当调用其它location，需要传递请求参数时，可以进行编码：</p>
<pre class="crayon-plain-tag">local res = ngx.location.capture('/print-param-internal', {
    -- 调用使用的HTTP方法
    method = ngx.HTTP_POST,
    -- URL参数
    args = ngx.encode_args({ a = 1, b = '2&amp;' }), -- 编码为 'a=1&amp;b=2%26'
    -- POST参数
    body = ngx.encode_args({ c = 3, d = '4&amp;' })

    -- 注意，capture可以直接接收Lua Table，下面的更简洁
    args = {a = 1, b = '2&amp;'},
})
ngx.say(res.body)</pre>
<div class="blog_h2"><span class="graybg">读取请求体 </span></div>
<p>当用Nginx作为负载均衡或反向代理时，基本上仅仅需要读取请求头就足够了。使用OpenResty 后，你可以把Nginx直接作为API服务器、Web服务器，这是就需要操控请求体、响应体了。</p>
<p>要通过Lua读取请求体，可以添加指令：</p>
<pre class="crayon-plain-tag"># 总是让Lua读取请求体
lua_need_request_body on;</pre>
<p>如果仅仅<span style="background-color: #c0c0c0;">希望某个接口读取请求体</span>，可以调用：<pre class="crayon-plain-tag">ngx.req.read_body()</pre></p>
<p>如果请求体已经被存入临时文件，则需要调用<pre class="crayon-plain-tag">ngx.req.get_body_file()</pre>。</p>
<p>如果需要强制把请求体存入临时文件，配置<pre class="crayon-plain-tag">client_body_in_file_only on;</pre></p>
<p>如果需要强制在内存中保留请求体，配置client_body_buffer_size和client_max_body_size 为相同值。</p>
<p>读取请求体的代码：</p>
<pre class="crayon-plain-tag">local data = ngx.req.get_body_data()
ngx.say(data)

-- curl -d Greetings 'http://localhost:8080/get-req-body'
-- Greetings</pre>
<div class="blog_h2"><span class="graybg">输出响应体 </span></div>
<p>要输出响应体，可以调用：<pre class="crayon-plain-tag">ngx.say</pre>、<pre class="crayon-plain-tag">ngx.print</pre>，输出不会立即写入套接字，你可以调用<pre class="crayon-plain-tag">ngx.flush()</pre>刷出缓冲区。</p>
<div class="blog_h3"><span class="graybg">大响应体</span></div>
<p>大静态文件的响应，让Nginx自己完成。</p>
<p>如果是应用程序动态生成的大响应体，可以使用HTTP 1.1的CHUNKED编码。对应响应头：<pre class="crayon-plain-tag">Transfer-Encoding: chunked</pre>。这样响应就可以逐块的发送到客户端，不至于占用服务器内存。</p>
<pre class="crayon-plain-tag">-- 可以进行限速，单位字节
ngx.var.limit_rate = 64
--                        获取配置目录
local file, err = io.open(ngx.config.prefix() .. "nginx.conf", "r")
if not file then
    -- 打印Nginx日志
    ngx.log(ngx.ERR, "open file error:", err)
    -- 以指定的HTTP状态码退出处理
    ngx.exit(ngx.HTTP_SERVICE_UNAVAILABLE)
end
-- 如果没有ngx.exit，则：
local data
while true do
    data = file:read(64)
    if nil == data then
        break
    end
    ngx.print(data)
    --        true表示等待IO操作完成
    ngx.flush(true)
    ngx.sleep(1)
end
file:close()

-- http://localhost:8080/put-res-body-chunked 会一行行的输出</pre>
<div class="blog_h2"><span class="graybg">配合其它location</span></div>
<div class="blog_h3"><span class="graybg">内部调用</span></div>
<p>使用内部调用（子查询），可以向某个location非阻塞的发起调用。目录location可以是静态文件目录，也可以由gx_proxy、ngx_fastcgi、ngx_memc、ngx_postgres、ngx_drizzle甚至其它ngx_lua模块提供内容生成。</p>
<p>需要注意：</p>
<ol>
<li>内部调用仅仅是模拟HTTP的接口形式，不会产生额外的HTTP/TCP流量</li>
<li>内部调用完全不同于HTTP 301/302重定向指令，和内部重定向（ngx.exec）也完全不同</li>
<li>、在发起内部调用之前，必须先读取完整的请求体。你可以设置lua_need_request_body指令或者调用ngx.req.read_body。如果<span style="background-color: #c0c0c0;">请求体太大，可以考虑使用cosockets模块</span>进行流式处理</li>
<li><span style="background-color: #c0c0c0;">子请求默认继承当前请求的所有请求头信息</span>。配置<pre class="crayon-plain-tag">proxy_pass_request_headers=off;</pre>可以忽略父请求的头</li>
<li>ngx.location.capture/capture_multi无法请求包含以下指令的location：add_before_body, add_after_body, auth_request, echo_location, echo_location_async, echo_subrequest, echo_subrequest_async</li>
</ol>
<pre class="crayon-plain-tag">location /sum {
    -- 仅允许内部跳转调用
    internal;
    content_by_lua_block {
        -- 解析请求参数
        local args = ngx.req.get_uri_args()
        -- 输出
        ngx.say(tonumber(args.a)+tonumber(args.b))
    }
}
location /sub {
    internal;
    content_by_lua_block{
        -- 休眠
        ngx.sleep(0.1)
        local args = ngx.req.get_uri_args()
        ngx.print(tonumber(args.a) - tonumber(args.b))
    }

}
location /test{
    content_by_lua_block{
        -- 发起一个子查询
        -- res.status 子请求的响应状态码
        -- res.header 子请求的响应头，如果某个头是多值的，则存放在table中
        -- res.body 子请求的响应体
        -- res.truncated 标记响应体是否被截断。截断意味着子请求处理过程中出现不可恢复的错误，例如超时、早断
        local res = ngx.location.capture( "/sum", {
            args={a=3, b=8},  -- 为子请求附加URI参数
            method = ngx.HTTP_POST, -- 指定请求方法，默认GET
            body = 'hello, world'   -- 指定请求体
        } )
        -- 并行的发起多个子查询
        local res1, res2 = ngx.location.capture_multi( {
            {"/sum", {args={a=3, b=8}}},
            {"/sub", {args={a=3, b=8}}}
        })
        ngx.say(res1.status," ",res1.body)
        ngx.say(res2.status," ",res2.body)
    }
}</pre>
<div class="blog_h3"><span class="graybg">内部跳转</span></div>
<pre class="crayon-plain-tag">location ~ ^/static/([-_a-zA-Z0-9/]+).jpg {
    -- 这里将URI中捕获的第一个分组，赋值给变量
    set $image_name $1;
    content_by_lua_block {
        -- ng.var可以读取Nginx变量
        -- ngx.exec执行跳转
        ngx.exec("/download_internal/images/" .. ngx.var.image_name .. ".jpg");
    };
}

location /download_internal {
    internal;
    -- 可以在这里进行各种声明，例如限速
    alias ../download;
}</pre>
<p>注意，ngx.exec引发的跳转完全在Ng内部完成，不会产生HTTP协议层的信号。</p>
<div class="blog_h3"><span class="graybg">外部跳转</span></div>
<p>使用ngx.redirect可以进行外部跳转，也就是重定向。</p>
<pre class="crayon-plain-tag">location = / {
    rewrite_by_lua_block {
        return ngx.redirect('/blog');
    }
}</pre>
<div class="blog_h2"><span class="graybg">日志记录</span></div>
<p>OpenResty提供的日志API为<pre class="crayon-plain-tag">ngx.log(log_level, ...)</pre> 日志输出到Nginx的errorlog中。 </p>
<p>支持的日志级别如下：</p>
<pre class="crayon-plain-tag">ngx.STDERR     -- 标准输出
ngx.EMERG      -- 紧急报错
ngx.ALERT      -- 报警
ngx.CRIT       -- 严重，系统故障，触发运维告警系统
ngx.ERR        -- 错误，业务不可恢复性错误
ngx.WARN       -- 告警，业务中可忽略错误
ngx.NOTICE     -- 提醒，业务比较重要信息
ngx.INFO       -- 信息，业务琐碎日志信息，包含不同情况判断等
ngx.DEBUG      -- 调试</pre>
<div class="blog_h3"><span class="graybg">日志归集</span></div>
<p>模块lua-resty-logger-socket用于替代ngx_http_log_module，将Nginx日志异步的推送到远程服务器上。该模块的特性包括：</p>
<ol>
<li>基于 cosocket 非阻塞 IO 实现</li>
<li>日志累计到一定量，集体提交，增加网络传输利用率</li>
<li>短时间的网络抖动，自动容错</li>
<li>日志累计到一定量，如果没有传输完毕，直接丢弃</li>
<li>日志传输过程完全不落地，没有任何磁盘 IO 消耗</li>
</ol>
<p>示例代码：</p>
<pre class="crayon-plain-tag">lua_package_path "/path/to/lua-resty-logger-socket/lib/?.lua;;";
log_by_lua_file log.lua;</pre><br />
<pre class="crayon-plain-tag">local logger = require "resty.logger.socket"
if not logger.initted() then
    local ok, err = logger.init {
        host = 'ops.gmem.cc',
        port = 8087,
        flush_limit = 1234,
        drop_limit = 5678,
    }
    if not ok then
        ngx.log(ngx.ERR, "failed to initialize the logger: ", err)
        return
    end
end

-- 通过变量msg来访问 accesslog

local bytes, err = logger.log(msg)
if err then
    ngx.log(ngx.ERR, "failed to log message: ", err)
    return
end</pre>
<div class="blog_h2"><span class="graybg">调用数据库</span></div>
<pre class="crayon-plain-tag">-- 引入操控MySQL需要的模块
local mysql = require "resty.mysql"
-- 初始化数据库对象
local db, err = mysql:new()
if not db then
    ngx.say("failed to instantiate mysql: ", err)
    return
end
-- 设置连接超时
db:set_timeout(1000)
-- 设置连接最大空闲时间，连接池容量
db:set_keepalive(10000, 100)

-- 发起数据库连接
local ok, err, errno, sqlstate = db:connect {
    host = "127.0.0.1",
    port = 3306,
    database = "test",
    user = "root",
    password = "root",
    max_packet_size = 1024 * 1024
}

if not ok then
    ngx.say("Failed to connect: ", err, ": ", errno, " ", sqlstate)
    return
end


local res, err, _, _ = db:query([[
    DROP TABLE IF EXISTS USERS;
]])
if not res then ngx.say(err); return end

res, err, errno, sqlstate = db:query([[
    CREATE TABLE USERS
    (
        ID INT ,
        NAME VARCHAR(64)
    );
]])
if not res then ngx.say(err); return end


res, err, errno, sqlstate = db:query([[
    INSERT INTO USERS (ID,NAME) VALUES ('10000','Alex');
    INSERT INTO USERS (ID,NAME) VALUES ('10001','Meng');
]])
if not res then ngx.say(err); return end

local cjson = require "cjson"
ngx.say(cjson.encode(res))</pre>
<p>要防止SQL注入，可以预处理一下用户提供的参数：</p>
<pre class="crayon-plain-tag">req_id = ndk.set_var.set_quote_sql_str(req_id)))</pre>
<div class="blog_h2"><span class="graybg">调用HTTP</span></div>
<p>你可以用ngx.location.capture发起对另外一个location的子调用，并将后者配置为上游服务器的代理。如果：</p>
<ol>
<li>内部请求数量较多</li>
<li>需要频繁修改上游服务器的地址</li>
</ol>
<p>最好使用<a href="https://github.com/pintsized/lua-resty-http">lua-resty-http</a>模块。该模块提供了基于cosocket的HTTP客户端。具有特性：</p>
<ol>
<li>支持HTTP 1.0/1.1</li>
<li>支持SSL</li>
<li>支持响应体的流式接口，内存用量可控</li>
<li>对于简单的应用场景，提供更简单的接口</li>
<li>支持Keepalive</li>
</ol>
<p>示例：</p>
<pre class="crayon-plain-tag">ngx.req.read_body()
-- 获取当前请求的参数
local args, err = ngx.req.get_uri_args()

local http = require "resty.http"
-- 创建HTTP客户端
local httpc = http.new()
-- request_uri函数在内部自动处理连接池
local res, err = httpc:request_uri("http://media-api.dev.svc.k8s.gmem.cc:8800/media/newpub/2017-01-01", {
    method = "POST",
    body = args.data,  -- 转发请求参数给上游服务器
})

if 200 ~= res.status then
    ngx.exit(res.status)
end

if args.key == res.body then
    ngx.say("valid request")
else
    ngx.say("invalid request")
end</pre>
<div class="blog_h2"><span class="graybg">编解码JSON</span></div>
<p>cjson模块提供了编解码JSON的支持。</p>
<div class="blog_h3"><span class="graybg">编码</span></div>
<pre class="crayon-plain-tag">local json = require("cjson")
json.encode(data)</pre>
<p>对于稀疏数组，例如：</p>
<pre class="crayon-plain-tag">local data = {1, 2}
data[1000] = 99     -- 稀疏</pre>
<p>会导致编码失败，提示：Cannot serialise table: excessively sparse array。其原因是数组太稀疏了，cjson为了保护资源默认抛出错误。</p>
<p>如果非要编码稀疏数组，考虑使用<pre class="crayon-plain-tag">encode_sparse_array</pre>函数。</p>
<p>由于Lua把字典、数组统一作为表格管理，因此就会牵涉到某个对象是编码为[]还是{}形式的问题：</p>
<pre class="crayon-plain-tag">cjson.encode({})            -- {}
cjson.encode({dogs = {}})   -- {"dogs":{}}

-- 可以提示cjson，让它把空表格编码为数组而非字典
cjson.encode_empty_table_as_object(false)
cjson.encode({})            -- []
cjson.encode({dogs = {}})   -- {"dogs":[]}</pre>
<div class="blog_h1"><span class="graybg">常见问题</span></div>
<div class="blog_h2"><span class="graybg">加载模块失败</span></div>
<p>报错信息：error loading module 'cjson' from file '/home/alex/Lua/sdk/5.3.4': cannot read /home/alex/Lua/sdk/5.3.4: Is a directory</p>
<p>原因：安装了多套Lua环境，错误的设置了LUA_PATH、LUA_CPATH环境变量导致</p>
<p>解决办法：可以清空这些环境变量。另外这两个环境变量中不要包含目录。</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/openresty-study-note">OpenResty学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/openresty-study-note/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Lua学习笔记</title>
		<link>https://blog.gmem.cc/lua-study-note</link>
		<comments>https://blog.gmem.cc/lua-study-note#comments</comments>
		<pubDate>Fri, 13 May 2016 15:12:36 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Lua]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=18697</guid>
		<description><![CDATA[<p>安装 参考如下命令，编译并安装Lua解释器： [crayon-69e7b06a5d005364452181/] 语言基础 变量 要声明一个变量，使用如下语法：[crayon-69e7b06a5d009100117186-i/]  任何变量，除非显式添加local限定符，否则均为全局变量，不管它在何处声明： [crayon-69e7b06a5d00b645387513/] 局部变量 Lua支持块级作用域，局部变量的作用域在所在块内部。 全局变量 一般的，应当严格避免使用自己定义的全局变量。 Lua在一个名为[crayon-69e7b06a5d00d904533686-i/]的表格中管理所有全局变量： [crayon-69e7b06a5d00f734400693/] 虚变量 类似于Go语言的空白标识符。如果多返回值中有你不需要的，可以将其赋值给[crayon-69e7b06a5d011500027257-i/] 操作符 需要注意Lua不支持++、--、+=之类的操作符。 Lua中的逻辑或与非操作符为[crayon-69e7b06a5d013293573545-i/]、[crayon-69e7b06a5d015713969348-i/]、[crayon-69e7b06a5d017394888207-i/]，不等于操作符为[crayon-69e7b06a5d019470160026-i/] Lua中有一个特殊操作符[crayon-69e7b06a5d01b692722429-i/]，它可以获取对象的长度，实际上是调用元方法__len() 类型 数字 <a class="read-more" href="https://blog.gmem.cc/lua-study-note">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/lua-study-note">Lua学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">安装</span></div>
<p>参考如下命令，编译并安装Lua解释器：</p>
<pre class="crayon-plain-tag">curl -R -O http://www.lua.org/ftp/lua-5.3.4.tar.gz
tar zxf lua-5.3.4.tar.gz
cd lua-5.3.4
sudo apt-get install libreadline-dev
make linux
make install INSTALL_TOP=/home/alex/Lua/sdk/5.3.4</pre>
<div class="blog_h1"><span class="graybg">语言基础</span></div>
<div class="blog_h2"><span class="graybg">变量</span></div>
<p>要声明一个变量，使用如下语法：<pre class="crayon-plain-tag">varname = value</pre> </p>
<p>任何变量，除非显式添加local限定符，否则均为全局变量，不管它在何处声明：</p>
<pre class="crayon-plain-tag">globalVar = true
local localVar = nil</pre>
<div class="blog_h3"><span class="graybg">局部变量</span></div>
<p>Lua支持块级作用域，局部变量的作用域在所在块内部。</p>
<div class="blog_h3"><span class="graybg">全局变量</span></div>
<p>一般的，<span style="background-color: #c0c0c0;">应当严格避免使用自己定义的全局变量</span>。</p>
<p>Lua在一个名为<pre class="crayon-plain-tag">_G</pre>的表格中管理所有全局变量：</p>
<pre class="crayon-plain-tag">_G.globalVar
_G["globalVar"]</pre>
<div class="blog_h3"><span class="graybg">虚变量</span></div>
<p>类似于Go语言的空白标识符。如果多返回值中有你不需要的，可以将其赋值给<pre class="crayon-plain-tag">_</pre></p>
<div class="blog_h2"><span class="graybg">操作符</span></div>
<p>需要注意Lua不支持++、--、+=之类的操作符。</p>
<p>Lua中的逻辑或与非操作符为<pre class="crayon-plain-tag">or</pre>、<pre class="crayon-plain-tag">and</pre>、<pre class="crayon-plain-tag">not</pre>，不等于操作符为<pre class="crayon-plain-tag">~=</pre></p>
<p>Lua中有一个特殊操作符<pre class="crayon-plain-tag">#</pre>，它可以获取对象的长度，实际上是调用元方法__len()</p>
<div class="blog_h2"><span class="graybg">类型</span></div>
<div class="blog_h3"><span class="graybg">数字</span></div>
<p>Lua中的数字均为双精度浮点型，你不需要担心运算精度或者速度问题。示例：</p>
<pre class="crayon-plain-tag">-- 语句尾部的分号是可选的
num = 4096
num = 6.12
num = 6.02E23
num = 0xff</pre>
<div class="blog_h3"><span class="graybg">字符串</span></div>
<p>可以使用单引号、双引号包围，支持多行字符串，支持C风格的转义字符。示例： </p>
<pre class="crayon-plain-tag">str = "Hello World"
str = 'Hello\n World'
str = [[Hello
World]]</pre>
<div class="blog_h3"><span class="graybg">空值</span></div>
<p>访问一个未定义变量时，其值为空，使用关键字<pre class="crayon-plain-tag">nil</pre>表示。 </p>
<div class="blog_h3"><span class="graybg">布尔</span></div>
<p>有true和false两个值。类型转换时，仅nil转换为false，其余的例如数字0、空串都转换为true。</p>
<div class="blog_h3"><span class="graybg">表格</span></div>
<p>Lua不区分数组和映射，以整数作为键的表格，可以作为数组来使用：</p>
<pre class="crayon-plain-tag">-- 如果不显示指定键，则自动从1开始为每个元素赋予键
users = { "Alex", "Meng", "Cai", "Dang" }
-- 数组的第一个元素的索引为1，而不是0
print(users[1])  -- Alex
-- 可以使用复数索引
users[-1] = 'Nobody'
print(users[-1]) -- Nobody</pre>
<p>以字符串作为键的表格，则相当于其它编程语言中的映射或字典：</p>
<pre class="crayon-plain-tag">user = {
    -- 注意语法，使用=号指定键值对
    name = "Alex Wong",
    age = 32,
    gender = 'M',
    -- 也可以使用方括号语法指定键值对
    ['nickname'] = 'Alex'
}
-- 方括号语法、点号导航都支持
user.name = "汪震"
user.name = nil
user.nickname = '震'</pre>
<p>要获取表格的元素个数，可以使用<pre class="crayon-plain-tag">#</pre>操作符：</p>
<pre class="crayon-plain-tag">users = { [1] = 'Alex', [2] = 'Meng', [3] = 'Cai', [4] = 'Dang' }
-- #users 可以获取表格users的长度
for i = 1, #users do
    print(users[i])
end</pre>
<p>要遍历表格的键值对，可以：</p>
<pre class="crayon-plain-tag">for idx, user in pairs( users ) do
    print( idx, user )
end


array = {
    ['Z'] = 'z',
    [0] = 'a',
    [1] = 'b',
    [10] = 'b'
};
-- pairs打印所有键值对
for i, v in pairs(array) do print(i .. '=' .. v) end
-- ipairs只能打印从索引从1开始的，而且不能有中断
for i, v in ipairs(array) do print(i .. '=' .. v) end -- 只打印1=b</pre>
<p>注意：<span style="background-color: #c0c0c0;">不要在表格中使用 nil 值，如果一个元素要删除，直接 remove，不要用 nil 去代替</span>：</p>
<pre class="crayon-plain-tag">users = { 1, 2, 3 }
table.remove(users, 1)
print(table.concat(users, ' ')) -- 2 3</pre>
<div class="blog_h2"><span class="graybg">注释</span></div>
<pre class="crayon-plain-tag">-- 单行注释

--[[
多行注释
--]]</pre>
<div class="blog_h2"><span class="graybg">控制结构</span></div>
<div class="blog_h3"><span class="graybg">循环</span></div>
<pre class="crayon-plain-tag">n, i, factorial = 10, 1, 1

-- while循环
while i &lt;= n do
    factorial = factorial * i
    i = i + 1
end
print(factorial) -- 3628800

-- for循环
for i = 1, n do
    factorial = factorial / i
end
print(factorial) -- 1.0

-- until循环
i = 1
repeat
    factorial = factorial * i
    i = i + 1
until i &gt; n </pre>
<div class="blog_h3"><span class="graybg">分支</span></div>
<pre class="crayon-plain-tag">if len &gt; 100 then
    print("L")
elseif len &lt;= 100 and len &gt; 50 then
    print("M")
elseif len ~= 0 then
    print("S")
else
end</pre>
<div class="blog_h2"><span class="graybg">函数</span></div>
<p>Lua中函数必须在先定义，然后再使用。也就是说，在脚本中函数定义出现在前面。</p>
<pre class="crayon-plain-tag">function getUserName(id)
    return "Alex"
end

-- 支持返回多个值
function getUser(id)
    return id, "Alex", 32
end

id, name, age = getUser(1)
-- 如果左边变量不足，多余的返回值自动丢弃
-- 如果左边变量过多，后面的全部置为nil


-- 函数调用出现在列表表达式中时，除非函数调用在列表尾部，否则仅仅返回其第一个值
local function twonums() return 1, 2 end
-- 注意下面两个列表表达式的区别
local x, y, z = twonums(), 3;
print(x, y, z) -- 1	3	nil
local x, y, z = 3, twonums();
print(x, y, z) -- 3	1	2
-- print的实参也是列表表达式
print(twonums(), 3)    -- 1	   3
print(3, twonums())    -- 3    1    2
-- 要强制仅仅返回第一个值，可以用()包围函数调用
print(3, (twonums()))  -- 3    1


-- 支持闭包
function counter()
    local count = 0
    return function()
        count = count + 1
        return count
    end
end

c = counter()
print(c(), c()) -- 1  2

-- 函数可以递归调用自身
function fib(n)
    if n &lt; 2 then return 1 end
    return fib(n - 2) + fib(n - 1)
end

-- 具有局部作用域的函数
local function f()
end

-- 函数可以赋值给变量
local f = function() return 0 end
f()



-- 变长参数
local function join(...)
    local args = { ... } -- 把变长参数填充到表格
    print(table.concat(args, "."))
end

-- LuaJIT 2尚不能JIT编译这种变长参数的用法，只能解释执行。所以对性能敏感的代码，应当避免使用此种形式
join('cc', 'gmem', 'study') -- cc.gmem.study

-- 当表格作为参数时，传递的是引用
local function printUser(user)
    print(user.name, user.age)
end
printUser({ name = 'Alex', age = 32 })


-- 调用回调
local function call(func, ...)
    local args = { ... } or {}
    -- unpack，解包表格
    func(...)
end
local function add(x, y, z) print(x + y + z) end
call(add, 1, 2, 3) -- 6</pre>
<div class="blog_h2"><span class="graybg">方法</span></div>
<p>作为表格成员的函数，即为方法：</p>
<pre class="crayon-plain-tag">Person = {
    name = "Unknown"
}
-- 方法可以用TableName.MethodName的形式定义，此时第一个形参必须为self
-- 代表当前对象
function Person.getName(self)
    return self.name
end

-- 方法也可以用TableName:MethodName的形式定义，此时隐式的自动定义self局部变量
function Person:getName()
    return self.name
end

print(Person:getName()) -- Unknown</pre>
<div class="blog_h2"><span class="graybg">MetaTable</span></div>
<p>MetaTable可以用来实现操作符重载：</p>
<pre class="crayon-plain-tag">-- 元表格
user_ops = {}
-- __add是一种元方法（MetaMethod）
function user_ops.__add(u1, u2)
    return 'child of ' .. u1.name .. ' and ' .. u2.name
end

alex = { name = 'Alex' }
meng = { name = 'Meng' }
-- 可以为任何表格设置元表格
setmetatable(alex, user_ops)
setmetatable(meng, user_ops)

print(alex + meng) -- child of Alex and Meng</pre>
<p>所有可用的元方法如下表：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">元方法</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>__add(a, b)</td>
<td>a + b</td>
</tr>
<tr>
<td>__sub(a, b)</td>
<td>a - b</td>
</tr>
<tr>
<td>__mul(a, b)</td>
<td>a * b</td>
</tr>
<tr>
<td>__div(a, b)</td>
<td>a / b</td>
</tr>
<tr>
<td>__mod(a, b)</td>
<td>a % b</td>
</tr>
<tr>
<td>__pow(a, b)</td>
<td>a ^ b</td>
</tr>
<tr>
<td>__unm(a)</td>
<td>-a</td>
</tr>
<tr>
<td>__concat(a, b)</td>
<td>a .. b</td>
</tr>
<tr>
<td>__len(a)</td>
<td>#a</td>
</tr>
<tr>
<td>__eq(a, b)</td>
<td>a == b</td>
</tr>
<tr>
<td>__lt(a, b)</td>
<td>a &lt; b</td>
</tr>
<tr>
<td>__le(a, b)</td>
<td>a &lt;= b</td>
</tr>
<tr>
<td>__index(a, b)</td>
<td>a.b</td>
</tr>
<tr>
<td>__newindex(a, b, c)</td>
<td>a.b = c</td>
</tr>
<tr>
<td>__call(a, ...)</td>
<td>a(...)</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">面向对象</span></div>
<p>利用元方法__index，可以实现基于原型的继承机制，类似于JavaScript：</p>
<pre class="crayon-plain-tag">Flower = {}
print(Flower) -- 0x1cc1400
-- 定义构造器
function Flower:new(f)
    -- self指向调用者本身 0x1cc1400 也就是Flower这个表格
    -- 下面这个表达式将Flower变为元表格
    self.__index = self
    print(self)
    -- 调用setmetatable(a, {__index = b})，即让b作为a的原型
    -- 在这里，就是让self成为f的原型
    return setmetatable(f, self)
end

-- 定义普通方法
function Flower:getColor()
    return self.color
end

-- 也可以使用点形式定义，但是self需要作为第一个形参
function Flower.getColor(self)
    return self.color
end

-- 实例化新对象
jasmine = Flower:new({ color = 'White' })
print(jasmine.color) -- White

-- 调用方法，注意使用冒号而非点号
print(jasmine:getColor()) -- White
-- 如果用点号调用，则必须把self作为第一个参数传入
print(jasmine.getColor(jasmine)) -- White

-- 继承
Jasmine = Flower:new({ color = 'White' })
function Jasmine:new(j)
    self.__index = self
    return setmetatable(j, self)
end

function Jasmine:getSmell()
    return self.smell
end

function Jasmine:toString()
    -- 可以在方法中定义方法
    return self:getColor() .. ' Jasmine Smells ' .. self.smell
end

jasmine = Jasmine:new({ smell = 'Nice' })
print(jasmine:toString()) -- White Jasmine Smells Nice</pre>
<div class="blog_h2"><span class="graybg">模块</span></div>
<p>你可以使用<span style="color: #424242;"><pre class="crayon-plain-tag">require('XX')</pre>语句来引入其它模块，也就是XX.lua文件。require时<span style="background-color: #c0c0c0;">目标.lua文件会自动被执行</span>。注意：</span></p>
<ol>
<li>如果多次require同一个文件，则仅仅第一次会执行目标文件</li>
<li>如果想多次执行某个文件，调用<pre class="crayon-plain-tag">dofile('xx')</pre></li>
<li>如果仅仅希望载入模块，但是不执行其中的代码，可以调用：<br />
<pre class="crayon-plain-tag">local xxModule = loadfile("XX")
-- ...
-- 下面的调用导致目标模块被执行
xxModule()</pre>
</li>
</ol>
<div class="blog_h3"><span class="graybg">定义模块</span></div>
<p>一个长方形模块的示例：</p>
<pre class="crayon-plain-tag">-- 此局部变量，一个表格，作为模块成员的容器
local _M = {}
-- 此属性，通常作为模块版本
_M._VERSION = '1.0'

local mt = { __index = _M }

function _M.new(self, width, height)
    return setmetatable({ width=width, height=height }, mt)
end

function _M.get_rectangle(self)
    return self.width * self.height
end

function _M.get_circumference(self)
    return (self.width + self.height) * 2
end

-- 返回模块容器
return _M</pre>
<p>使用此模块：</p>
<pre class="crayon-plain-tag">local rectangle = require "rectangle"

local s = rectangle:new(1, 2)
print(s:get_rectangle())          -- 2
print(s:get_circumference())   -- 6 </pre>
<div class="blog_h2"><span class="graybg">错误处理</span></div>
<p>要在 Lua 中处理错误，必须使用函数 pcall（protected call）来包装需要执行的代码。 pcall接受以下参数：</p>
<ol>
<li>被调用的函数</li>
<li>转发给被调用函数的参数</li>
</ol>
<p>pcall的返回值有两个：是否调用成功，被调用函数的返回值。</p>
<p>示例：</p>
<pre class="crayon-plain-tag">function json_decode( str )
    -- 安全的解码JSON
    local ok, t = pcall(_json_decode, str)
    if not ok then
      return nil
    end
    return t
end</pre>
<div class="blog_h1"><span class="graybg">常用模块</span></div>
<div class="blog_h2"><span class="graybg">string</span></div>
<pre class="crayon-plain-tag">-- string.byte(s [, i [, j ]])
-- 获取字符串s从i到j这些字符的ASCII码，返回列表
a, b, c = string.byte("abc", 1, 3)
print(a, b, c) -- 97	98	99
-- string.char (...)
-- 从列表ASCII码构建字符串
print(string.char(97, 98, 99)) -- abc

-- 大小写转换
print(string.upper("Hello Lua")) -- HELLO LUA
print(string.lower("Hello Lua")) -- hello lua

-- 重复字符串
print(string.rep("abc", 3))      -- abcabcabc

-- 返回子串 string.sub(s, i [, j])
print(string.sub(123456,3,5))    -- 345

-- 替换子串 string.gsub(s, p, r [, n])
-- 替换s中的p为r，n表示替换的最大次数
-- 返回两个值：替换后的字符串，替换发生的次数
print(string.gsub("Lua Lua Lua", "Lua", "hello")) -- hello hello hello	3

-- 获得字符串长度
str = "hello lua"
print(string.len(str))
print(#str) -- 推荐使用这种方式获得长度，获取字节数

-- string.find(s, p [, init [, plain]])
-- 子串查找，从s中勋撰p，可以指定搜索起始位置init。返回子串的索引
sidx, eidx = string.find('123456789', '45')
print(sidx, eidx) -- 4	5

-- C风格的字符串格式化  string.format(formatstring, ...)
print(string.format("%.4f", 3.1415926)) -- 3.1416
print(string.format("%d %x %o", 31, 31, 31)) -- 31 1f 37
print(string.format("%02d/%02d/%d", 1, 1, 2018)) -- 01/01/2018

-- 模式匹配，不支持JIT，考虑使用ngx.re.match代替
-- string.match(s, p [, init]) 从s中搜索模式p，返回匹配的子串
print(string.match("Linux Zircon 3.13.0-83-generic", "%d.%d+.%d%-%d+")) -- 3.13.0-83

-- 全局模式匹配，返回一个迭代器，此迭代器依次返回每个匹配的子串
for word in string.gmatch("quick fox jumps over the lazy dog", "%w+") do
    print(word)
end</pre>
<div class="blog_h2"><span class="graybg">table</span></div>
<p>此库将表格作为数组来操作。</p>
<pre class="crayon-plain-tag">-- table.getn(t) 获取表格长度
-- 注意：获取到的是最后一个数字下标
print(table.getn({ 1, 2, 3 }))                -- 3
print(table.getn({ 1, two = 2, 3 }))          -- 2 最后一个索引从1开始顺延
print(table.getn({ 1, 2, nil, 4, 5, 6 }))     -- 6 如果最后是非nil，其索引被使用
print(table.getn({ 1, 2, nil, 4, 5, nil }))   -- 2 如果最后是nil，则第一个nil之前的索引被使用

-- table.maxn (table) 返回最大的索引值
print(table.maxn({ [1000] = 0 })) -- 1000

-- table.concat (table [, sep [, i [, j ] ] ]) 连接数组元素为字符串
-- sep没认为空串，i为起始元素，j为结束元素
print(table.concat({'Alex','Meng'}))          -- AlexMeng


-- table.insert (table, [pos ,] value) 插入元素到数组，pos默认为表长度+1，即默认为附加元素
array = { 1, 2, 3 }
table.insert(array, 4)
print(array[4])  -- 4


-- table.remove (table [, pos]) 删除指定位置上的元素，默认从尾部删除
table.remove(array)
print(#array) -- 3


-- table.sort (table [, comp]) 根据传入的比较器来排序
local function compare(x, y)
    --从大到小排序
    return x &gt; y
end

table.sort(array) -- 默认从小到大
table.sort(array,compare) -- 从大到小</pre>
<p>LuaJIT 2.1 新增加 table.new 和 table.clear。前者用来预分配 Lua table 空间，后者用来高效的释放 table 空间，都可以被 JIT 编译。 </p>
<div class="blog_h2"><span class="graybg">时间</span></div>
<p>os模块的一些函数，提供了日期、时间相关的功能，但是比较重量级，不支持被LuaJIT编译。使用OpenResty时推荐使用ngx_lua模块提供的接口。</p>
<pre class="crayon-plain-tag">-- os.time ([table]) 返回当前时间，或者table所指定的时间（从UNIX纪元开始流逝的秒数
print(os.time()) -- 当前时间
a = { year = 1970, month = 1, day = 1, hour = 8, min = 0, sec = 10 }
print(os.time(a)) -- 10，因为当前是东八区

-- os.difftime (t2, t1) 返回两个时间的差值（t2-t1） 单位秒
a = { year = 1970, month = 1, day = 1, hour = 8, min = 0, sec = 10 }
b = { year = 1970, month = 1, day = 1, hour = 8, min = 0, sec = 15 }
print(os.difftime(os.time(a), os.time(b))) -- -5


-- os.date ([format [, time]])，返回一个描述时间的表格
-- format 格式说明，time时间点（秒）
local tab1 = os.date("*t")
local now = ""
for k, v in pairs(tab1) do --把tab1转换成一个字符串
    now = string.format("%s %s = %s ", now, k, tostring(v))
end
print(now) -- hour = 18  min = 3  wday = 2  day = 26  month = 2  year = 2018  sec = 52  yday = 57  isdst = false

-- 下面的占位符用于格式化日期
-- %a	一星期中天数的简写（例如：Wed）
-- %A	一星期中天数的全称（例如：Wednesday）
-- %b	月份的简写（例如：Sep）
-- %B	月份的全称（例如：September）
-- %c	日期和时间（例如：07/30/15 16:57:24）
-- %d	一个月中的第几天[01 ~ 31]
-- %H	24小时制中的小时数[00 ~ 23]
-- %I	12小时制中的小时数[01 ~ 12]
-- %j	一年中的第几天[001 ~ 366]
-- %M	分钟数[00 ~ 59]
-- %m	月份数[01 ~ 12]
-- %p	“上午（am）”或“下午（pm）”
-- %S	秒数[00 ~ 59]
-- %w	一星期中的第几天[1 ~ 7 = 星期天 ~ 星期六]
-- %x	日期（例如：07/30/15）
-- %X	时间（例如：16:57:24）
-- %y	两位数的年份[00 ~ 99]
-- %Y	完整的年份（例如：2015）
-- %%	字符'%'
print(os.date('%A'))        -- Monday
print(os.date('%Y-%m-%d'))  -- 2018-02-26</pre>
<div class="blog_h2"><span class="graybg">数学</span></div>
<pre class="crayon-plain-tag">math.max(x, ...)	           -- 返回参数中值最大的那个数
math.min(x, ...)	           -- 返回参数中值最小的那个数

math.abs(x)                    -- 返回x的绝对值
math.fmod(x, y)	               -- 返回 x对y取余数
math.pow(x, y)	               -- 返回x的y次方
math.sqrt(x)	               -- 返回x的算术平方根
math.exp(x)	                   -- 返回自然数e的x次方
math.log(x)	                   -- 返回x的自然对数
math.log10(x)	               -- 返回以10为底，x的对数
math.floor(x)	               -- 返回最大且不大于x的整数
math.ceil(x)	               -- 返回最小且不小于x的整数


math.rad(x)	                   -- 角度x转换成弧度
math.deg(x)	                   -- 弧度x转换成角度
math.sin(x)	                   -- 求弧度x的正弦值
math.cos(x)	                   -- 求弧度x的余弦值
math.tan(x)	                   -- 求弧度x的正切值
math.asin(x)	               -- 求x的反正弦值
math.acos(x)	               -- 求x的反余弦值
math.atan(x)	               -- 求x的反正切值

-- 不传入参数时，返回 一个在区间[0,1)内均匀分布的伪随机实数
-- 只使用一个整数参数m时，返回一个在区间[1, m]内均匀分布的伪随机整数
-- 使用两个整数参数时，返回一个在区间[m, n]内均匀分布的伪随机整数
math.random (m,n)
-- 为伪随机数生成器设置一个种子，设置后math.random产生的随机序列改变
math.randomseed (x)

pi=math.pi	                    -- 圆周率</pre>
<div class="blog_h2"><span class="graybg">文件</span></div>
<p>Lua I/O提供的文件操作都是阻塞性的，不应该在OpenResty中和非阻塞的网络IO混合在一起。</p>
<pre class="crayon-plain-tag">path = [[/home/alex/Lua/projects/luna/logs/error.log]]
-- 设置输入输出默认针对的文件
file = io.input(path)
-- 逐行读取默认文件
repeat
    line = io.read() -- 读取一行
    if (line) then print(line) end
until (nil == line)
io.close(file) -- 关闭文件描述符

-- 以追加模式打开文件
file = io.open(path, "a+")
-- 所有可用的文件打开模式
-- 默认     说明                                            文件不存在时的行为
-- "r"	    读模式 (默认)	                                返回nil加错误信息
-- "w"	    写模式	                                        创建文件
-- "a"	    添加模式	                                    创建文件
-- "r+"    	更新模式，保存之前的数据	                    返回nil加错误信息
-- "w+"    	更新模式，清除之前的数据	                    创建文件
-- "a+"    	添加更新模式，保存之前的数据,在文件尾进行添加	创建文件

-- 设置默认输出文件
io.output(file)
io.write("\nHello Lua")



-- 调用file:***方法，可以针对特定文件（而不是默认文件）进行读写
file = io.open(path, "r")
-- file:lines()方法返回所有行的迭代器
for line in file:lines() do
    print(line)
end

-- 其它方法：
-- file:read (...)  读取数据
-- file:write (...) 写入数据：
--     *n 读取一个数字
--     *a 从当前位置读取整个文件。若当前位置为文件尾，则返回空字符串
--     *l 读取下一行的内容。若为文件尾，则返回nil
--    100 读取100字节
print(file:read(10))

-- file:seek ([whence] [, offset]) 设置和获取当前文件位置
-- offset 向前搜索的偏移量
-- whence 偏移量的相对位置：set相对文件开始；cur相对当前位置（默认）；end相对文件结束
file:seek('cur', 100)
-- file:setvbuf (mode [, size])  设置输出文件的缓冲模式
-- mode 模式：no无缓冲；full全缓冲（缓冲区满了后才输出）；line最多缓冲一行
file:setvbuf('full', 2048)

-- file:flush ()  刷出文件缓冲区
file:flush()
-- file:close ()  关闭文件
file:close() </pre>
<div class="blog_h1"><span class="graybg">LuaJIT</span></div>
<div class="blog_h2"><span class="graybg">简介</span></div>
<p>LuaJIT是Lua的一个即时（Just-In-Time）编译器，是最快的动态语言实现之一。通过编译，Lua的执行效率大大提高。LuaJIT被大量的用在游戏、网络等编程领域。LuaJIT仅仅是一个独立的可执行程序，其命令行选项和标准的Lua没有太大区别。</p>
<p class="comments-section">LuaJIT 的运行时环境包括一个用手写汇编实现的 Lua 解释器和一个可以直接生成机器代码的 JIT 编译器。</p>
<div class="blog_h2"><span class="graybg">安装</span></div>
<pre class="crayon-plain-tag">wget http://luajit.org/download/LuaJIT-2.0.5.tar.gz
tar xzf LuaJIT-2.0.5.tar.gz &amp;&amp; rm LuaJIT-2.0.5.tar.gz
cd LuaJIT-2.0.5/
make install PREFIX=/home/alex/Lua/LuaJIT/2.0.5</pre>
<div class="blog_h1"><span class="graybg">FFI</span></div>
<p>C函数要能够作为Lua扩展库，其接口必须遵循<pre class="crayon-plain-tag">typedef int (*lua_CFunction)(lua_State* L)</pre>的形式。此接口的入参是lua_State类型的指针，从Lua传递来的实参，必须通过该指针间接的读取。返回值int表示返回值的数量，返回值本身无法直接传递回去，必须使用虚拟栈来传递。开发Lua扩展库非常麻烦。</p>
<p>FFI是LuaJIT中最重要的一个扩展库。支持<span style="background-color: #c0c0c0;">从纯Lua代码调用外部C函数</span>，使用C数据结构。有了FFI，你就不再需要编写Lua扩展库，另外FFI的性能比扩展库更高。</p>
<div class="blog_h2"><span class="graybg">术语</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 15%; text-align: center;">名词</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>cdecl</td>
<td>一个抽象的C类型的定义，表现为Lua字符串</td>
</tr>
<tr>
<td>ctype</td>
<td>C类型对象，属于一种特殊的cdata，由 <pre class="crayon-plain-tag">ffi.typeof()</pre>获得，你可以调用它，获得cdata对象</td>
</tr>
<tr>
<td>cdata</td>
<td>C数据对象，和ctype类型匹配的值</td>
</tr>
<tr>
<td>ct</td>
<td>C类型格式，可以指cdecl, cdata, ctype</td>
</tr>
<tr>
<td>cb</td>
<td>回调对象，持有一个特殊的函数指针，从C代码调用此函数指针，会执行对应的Lua函数</td>
</tr>
<tr>
<td>VLA</td>
<td>变长数组，例如<pre class="crayon-plain-tag">int[?]</pre>。数组元素的个数，必须在创建VLA时给出</td>
</tr>
<tr>
<td>VLS</td>
<td>变长结构，即最后一个成员是VLA的C结构体</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">ffi模块</span></div>
<div class="blog_h3"><span class="graybg">ffi.cdef</span></div>
<p>用于声明（而不是定义）C函数或者C数据结构。所有使用到的函数都需要声明。</p>
<p>函数可以是 C 标准函数，第三方库函数，或自定义的函数。</p>
<p>据结构可以是结构体、枚举或者是联合体。</p>
<p>示例：</p>
<pre class="crayon-plain-tag">ffi.cdef [[
    typedef struct foo { int a, b; } foo_t;
    int printf(const char *fmt, ...);
]]</pre>
<div class="blog_h3"><span class="graybg">ffi.load</span></div>
<p>通过给定的名称来加载动态库，调用格式：<pre class="crayon-plain-tag">ffi.load(name [,global])</pre>。</p>
<p>动态库可以指定全限定路径，如果仅仅指定basename则必须位于LD_LIBRARY_PATH，动态库扩展名可以不加：</p>
<pre class="crayon-plain-tag">ffi.load("z")   -- 寻找libz.so，或者z.dll</pre>
<p>如果global设置为true，则在POSIX系统中目标动态库中的符号被加载到命名空间<pre class="crayon-plain-tag">ffi.C</pre>。例如：</p>
<pre class="crayon-plain-tag">ffi.load('z',true)

ffi.cdef[[
    int z(int x, int y); 
]]

local res = ffi.C.z(1, 2)</pre>
<div class="blog_h3"><span class="graybg">ffi.typeof </span></div>
<p>创建一个ctype对象。调用格式：<pre class="crayon-plain-tag">ctype = ffi.typeof(ct)</pre>。示例：</p>
<pre class="crayon-plain-tag">local uintptr_t = ffi.typeof("uintptr_t")
local c_str_t = ffi.typeof("const char*")
local int_t = ffi.typeof("int")
local int_array_t = ffi.typeof("int[?]")

-- 示例：
local int_t = ffi.typeof("int")
local i = int_t(10)
print(i)  -- cdata&lt;int&gt;: 0x401258e8</pre>
<div class="blog_h3"><span class="graybg">ffi.new </span></div>
<p>在堆内存中开辟空间，创建指定类型的ct。调用格式：</p>
<pre class="crayon-plain-tag">-- 从指定的ct创建cdata，对于VLA/VLS类型，需要指定元素个数（nelem）
cdata = ffi.new(ct [,nelem] [,init...])
-- 等价方式：
cdata = ctype([nelem,] [init...])</pre>
<p>使用ffi.new分配的 cdata 对象指向的内存块是由<span style="background-color: #c0c0c0;">垃圾回收器 LuaJIT GC 自动管理</span>的，不需要用户去释放内存。</p>
<p>使用 <pre class="crayon-plain-tag">ffi.C.malloc()</pre>分配的空间不使用 LuaJIT 自己的分配器，需要开发人员自己负责释放。但是<span style="background-color: #c0c0c0;">ffi.C.malloc返回的cdata对象仍然由LuaJIT GC管理</span>。</p>
<p>你可以通过<pre class="crayon-plain-tag">ffi.gc()</pre>为ffi.C.malloc返回的cdata对象注册析构函数，并在此函数中调用<pre class="crayon-plain-tag">ffi.C.free()</pre>。这样的话，当cdata对象被GC后，C内存也可以被释放。</p>
<p>ADM64上LuaJIT能够管理的内存量在2G这个级别，如果要分配非常大的内存，请使用ffi.C.malloc()</p>
<p>示例代码：</p>
<pre class="crayon-plain-tag">local int_array_t = ffi.typeof("int[?]")
local array = ffi.new(int_array_t, 100)</pre>
<div class="blog_h3"><span class="graybg">ffi.fill</span></div>
<p>填充内存。调用格式：<pre class="crayon-plain-tag">ffi.fill(dst, len [,c])</pre>。 类似于C函数<pre class="crayon-plain-tag">memset(dst, c, len)</pre></p>
<div class="blog_h3"><span class="graybg">ffi.copy</span></div>
<p>拷贝内存。调用格式：</p>
<pre class="crayon-plain-tag">-- src作为const void *看待
-- dst作为void *看待
ffi.copy(dst, src, len)
ffi.copy(dst, str) </pre>
<div class="blog_h3"><span class="graybg">ffi.cast</span></div>
<p>将指定的值强制转换为cdata。调用格式：<pre class="crayon-plain-tag">cdata = ffi.cast(ct, init)</pre></p>
<p>cdata的初始值从init得到，使用C类型转换约定。示例：</p>
<pre class="crayon-plain-tag">local c_str_t = ffi.typeof("const char*")
local c_str = ffi.cast(c_str_t, "Hello")</pre>
<div class="blog_h3"><span class="graybg">ffi.gc</span></div>
<p>注册一个析构函数（finalizer）。调用格式：<pre class="crayon-plain-tag">cdata = ffi.gc(cdata, finalizer)</pre></p>
<p>示例：</p>
<pre class="crayon-plain-tag">-- 返回注册了析构函数之后的cdata
local p = ffi.gc(ffi.C.malloc(n), ffi.C.free)

p = nil -- 消除最后一个引用
-- 下一个GC周期中，会自动调用ffi.C.free(p)</pre>
<div class="blog_h3"><span class="graybg">ffi.errno</span></div>
<p>获取上一次C函数调用的error。调用格式：<pre class="crayon-plain-tag">err = ffi.errno([newerr])</pre>。如果指定newerr，则返回旧error，并将error设置为新值。</p>
<div class="blog_h3"><span class="graybg">ffi.string</span></div>
<p>从指针创建Lua字符串。调用格式：<pre class="crayon-plain-tag">str = ffi.string(ptr [,len])</pre></p>
<p>其中ptr为C指针。如果：</p>
<ol>
<li>省略len，则ptr自动作为char*看待，也就是\0结束的字符串</li>
<li>否则，ptr被作为void*看待</li>
</ol>
<div class="blog_h2"><span class="graybg">回调</span></div>
<p>每当Lua函数被转换为C函数指针时，LuaJIT FFI 都会自动创建特殊的callback。这种情况可以：</p>
<ol>
<li>隐式发生，例如在把Lua函数传递给C函数的函数指针参数时</li>
<li>显式发生，你可以调用 <pre class="crayon-plain-tag">ffi.cast()</pre>来强制转换Lua函数为C函数指针</li>
</ol>
<p>目前，仅仅特定的C函数类型可以作为用作回调。</p>
<p>FFI为回调提供了两个特殊方法。</p>
<div class="blog_h3"><span class="graybg">cb:free()</span></div>
<p>释放和回调相关的资源，关联的Lua函数将可以被GC。对应的函数指针不再有效，不能再被调用。</p>
<div class="blog_h3"><span class="graybg">cb:set(func)</span></div>
<p>将回调关联到一个新的Lua函数，此回调的C类型、回调的函数指针保持不变。</p>
<div class="blog_h2"><span class="graybg">垃圾回收</span></div>
<p>不管隐式创建的cdata，还是ffi.new(), ffi.cast()显式创建的cdata，都可以被垃圾回收。使用cdata时，你必须确保在Lua栈、upvalue或Lua table上保留对cdata的有效引用，一旦最后一个引用失效，则在下一个GC周期中cdata会被自动释放。</p>
<p>如果你要分配一个数组类型的cdata给一个指针的话，必须保证cdata有额外的引用：</p>
<pre class="crayon-plain-tag">ffi.cdef[[
typedef struct { int *a; } foo_t; // 包含指针成员的结构
]]

local s = ffi.new("foo_t", ffi.new("int[10]")) -- 错误

local a = ffi.new("int[10]")   -- 正确，自己持有cdata
local s = ffi.new("foo_t", a)  -- 然后传递给C指针
-- 直到不需要了，你才能销毁引用a</pre>
<div class="blog_h2"><span class="graybg">调用C函数</span></div>
<p>在LuaJIT中调用C函数非常的方便，例如：</p>
<pre class="crayon-plain-tag">local ffi = require("ffi")
ffi.cdef [[
    int printf(const char *fmt, ...);
]]
-- 标准C库不需要ffi.load，可以直接使用
ffi.C.printf("Hello %s!", "world")</pre>
<p>更加复杂的代码示例：</p>
<pre class="crayon-plain-tag">-- 调用zlib库的例子
local ffi = require("ffi")
ffi.cdef [[
    unsigned long compressBound(unsigned long sourceLen);
    int compress2(uint8_t *dest, unsigned long *destLen, const uint8_t *source, unsigned long sourceLen, int level);
    int uncompress(uint8_t *dest, unsigned long *destLen, const uint8_t *source, unsigned long sourceLen);
]]
-- 不同操作系统中库的名称不一样
-- 将C共享库绑定到名字空间zlib
local zlib = ffi.load(ffi.os == "Windows" and "zlib1" or "z")

-- 压缩缓冲区txt
local function compress(txt)
    -- 将缓冲区长度传递给zlib
    local n = zlib.compressBound(#txt)
    -- 创建缓冲区cdata。uint8_t[?]为变长数组，其实际长度为n
    local buf = ffi.new("uint8_t[?]", n)
    -- C语言中可以传递变量地址（指针），但是Lua中没有对应的东西
    -- 因此只能使用长度为1的数组代替之
    local buflen = ffi.new("unsigned long[1]", n)
    -- 执行压缩
    -- buflen在C函数中是一个指针，实际使用buf的长度，需要通过buflen传递回来
    local res = zlib.compress2(buf, buflen, txt, #txt, 9)
    assert(res == 0)
    -- 从指定的C缓冲区创建Lua字符串，长度为buflen[0]
    return ffi.string(buf, buflen[0])
end </pre>
<div class="blog_h2"><span class="graybg">使用C数据结构</span></div>
<p>cdata用于保存任何C类型到Lua变量中。这种变量实际上指向一块原生的内存区域。除了赋值和相同性判断，LuaJIT没有为cdata预定义任何的操作。</p>
<p>你可以通过元表为cdata自定义一组操作，并调用这些操作：</p>
<pre class="crayon-plain-tag">local ffi = require("ffi")
ffi.cdef [[
    // 定义一个C结构
    typedef struct { double x, y; } point_t;
]]

-- 一个元表
local mt = {
    __add = function(a, b) return point(a.x + b.x, a.y + b.y) end,
    -- # 操作符支持
    __len = function(a) return math.sqrt(a.x * a.x + a.y * a.y) end,
    __index = {
        -- :area方法
        area = function(a) return a.x * a.x + a.y * a.y end,
    },
}
-- 为ct设置元表，返回设置后的ctype
-- ctype = ffi.metatype(ct, metatable)
local point = ffi.metatype("point_t", mt)

local a = point(3, 4)
print(a.x, a.y)                 -- 3	4
print(#a)                       -- 5
print(a:area())                 -- 25
local b = a + point(0.5, 8)  
print(#b)                       -- 12.5</pre>
<p>注意：元表与 C 类型的关联是永久的，而且不允许被修改，__index 元方法也是 。</p>
<div class="blog_h2"><span class="graybg">语法对应关系</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">C</td>
<td style="text-align: center;">Lua</td>
</tr>
</thead>
<tbody>
<tr>
<td>x = *p</td>
<td>x = p[0]</td>
</tr>
<tr>
<td>*p = y</td>
<td>p[0] = y</td>
</tr>
<tr>
<td>x = p[i]</td>
<td>x = p[i]</td>
</tr>
<tr>
<td>p[i+1] = y</td>
<td>p[i+1] = y</td>
</tr>
<tr>
<td>x = a[i]</td>
<td>x = a[i]</td>
</tr>
<tr>
<td>a[i+1] = y</td>
<td>a[i+1] = y</td>
</tr>
<tr>
<td>x = s.field</td>
<td>x = s.field</td>
</tr>
<tr>
<td>s.field = y</td>
<td>s.field = y</td>
</tr>
<tr>
<td>x = sp-&gt;field</td>
<td>x = sp.field</td>
</tr>
<tr>
<td>sp-&gt;field = y</td>
<td>s.field = y</td>
</tr>
<tr>
<td>y = p - i</td>
<td>y = p - i</td>
</tr>
<tr>
<td>x = p1 - p2</td>
<td>x = p1 - p2</td>
</tr>
<tr>
<td>x = &amp;a[i]</td>
<td>x = a + i</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">包管理器</span></div>
<p>LuaRocks是一个包管理器，它可以创建、安装Lua模块，将其打包为自包含的包，这种包称为rocks。</p>
<div class="blog_h2"><span class="graybg">安装</span></div>
<pre class="crayon-plain-tag">wget https://luarocks.org/releases/luarocks-2.4.3.tar.gz
tar zxpf luarocks-2.4.3.tar.gz 
cd luarocks-2.4.3
./configure --prefix=/home/alex/Lua/sdk/5.3.4
# 将LuaRock作为一个Rock安装
make bootstrap

# 查看环境变量，将其拷贝到~/.profile中
luarocks path



# 配合LuaJIT安装
export PATH=/home/alex/Lua/LuaJIT/2.0.5/bin:$PATH
./configure --prefix=/home/alex/Lua/LuaJIT/2.0.5 --lua-suffix=jit \
            --with-lua-include=/home/alex/Lua/LuaJIT/2.0.5/include/luajit-2.0</pre>
<div class="blog_h2"><span class="graybg">安装Rocks</span></div>
<p>示例：</p>
<pre class="crayon-plain-tag">luarocks install luasocket</pre>
<p> Rock会被安装到：/home/alex/Lua/sdk/5.3.4下。</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/lua-study-note">Lua学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/lua-study-note/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
