<?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; Nginx</title>
	<atom:link href="https://blog.gmem.cc/tag/nginx/feed" rel="self" type="application/rss+xml" />
	<link>https://blog.gmem.cc</link>
	<description></description>
	<lastBuildDate>Mon, 13 Apr 2026 08:03:10 +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-69de22c927fca616779754/] 起步 <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>Nginx知识集锦</title>
		<link>https://blog.gmem.cc/nginx-faq</link>
		<comments>https://blog.gmem.cc/nginx-faq#comments</comments>
		<pubDate>Sat, 09 Jan 2016 16:07:10 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[LB]]></category>
		<category><![CDATA[Nginx]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=17534</guid>
		<description><![CDATA[<p>Nginx简介 Nginx读作Engine X，在被广泛的用作： HTTP服务器 反向代理服务器 邮件代理服务器 通用的TCP/UDP代理 Nginx服务由一个Master进程和多个Worker进程构成。Master进程的主要职责是读取、解析配置文件，并维护工作进程。工作进程则负责实际的请求处理。为了高效的在Worker之间分发请求，Nginx引入了依赖于操作系统的、高效的事件驱动模型。Worker进程的数量常常根据CPU核心数设置。 对比Apache2   Apache2 Nginx Web服务器 适合处理动态请求 稳定，功能强 更强大的rewrite 超多的模块 更少的资源消耗：10000非活跃Keep-Alive连接仅仅消耗2.5M内存 更多的并发连接，理论上不受限制，取决于内存，10W没问题 静态处理性能比Apache2高3倍 简单，效率高 热部署：Master管理进程和Worker工作进程分离，可以在不停机的前提下升级二进制文件、修改配置、更换日志文件 <a class="read-more" href="https://blog.gmem.cc/nginx-faq">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/nginx-faq">Nginx知识集锦</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">Nginx简介</span></div>
<p>Nginx读作Engine X，在被广泛的用作：</p>
<ol>
<li style="font-size: 14px;">HTTP服务器</li>
<li>反向代理服务器</li>
<li>邮件代理服务器</li>
<li>通用的TCP/UDP代理</li>
</ol>
<p>Nginx服务由一个Master进程和多个Worker进程构成。Master进程的主要职责是读取、解析配置文件，并维护工作进程。工作进程则负责实际的请求处理。为了高效的在Worker之间分发请求，Nginx引入了依赖于操作系统的、高效的事件驱动模型。Worker进程的数量常常根据CPU核心数设置。</p>
<div class="blog_h2"><span class="graybg">对比Apache2</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 15%; text-align: center;"> </td>
<td style="text-align: center;">Apache2</td>
<td style="text-align: center;">Nginx</td>
</tr>
</thead>
<tbody>
<tr>
<td>Web服务器</td>
<td>
<p><span style="color: #1a1a1a;">适合处理动态请求</span></p>
<p>稳定，功能强</p>
<p>更强大的<span style="color: #1a1a1a;">rewrite</span></p>
<p>超多的模块</p>
</td>
<td>
<p>更少的资源消耗：10000非活跃Keep-Alive连接仅仅消耗2.5M内存</p>
<p>更多的并发连接，理论上不受限制，取决于内存，10W没问题</p>
<p>静态处理性能比Apache2高3倍</p>
<p>简单，效率高</p>
<p>热部署：Master管理进程和Worker工作进程分离，可以在不停机的前提下升级二进制文件、修改配置、更换日志文件</p>
</td>
</tr>
<tr>
<td><span style="color: #1a1a1a;">负载均衡器</span></td>
<td>通常不作为NLB</td>
<td>
<p><span style="color: #1a1a1a;">作为反向代理抗住并发 —— 异步模型</span></p>
</td>
</tr>
<tr>
<td><span style="color: #1a1a1a;">通信模型</span></td>
<td><span style="color: #1a1a1a;">同步多进程模型，一个连接对应一个进程</span></td>
<td><span style="color: #1a1a1a;">异步模型，多个连接（万级别）可以对应一个进程 </span></td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">安装</span></div>
<div class="blog_h2"><span class="graybg">安装</span></div>
<pre class="crayon-plain-tag">sudo apt-get install libpcre3 libpcre3-dev
sudo apt-get install zlib1g zlib1g.dev
sudo apt-get install openssl libssl-dev

wget https://nginx.org/download/nginx-1.13.8.tar.gz
tar xzf nginx-1.13.8.tar.gz  &amp;&amp; rm nginx-1.13.8.tar.gz 
cd nginx-1.13.8/
./configure --prefix=/usr
make -j8
sudo make install</pre>
<div class="blog_h3"><span class="graybg">configure参数</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">参数</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>prefix</td>
<td>安装前缀</td>
</tr>
<tr>
<td>sbin-path</td>
<td>二进制文件安装前缀，默认$prefix/sbin/nginx</td>
</tr>
<tr>
<td>conf-path</td>
<td>Nginx主配置文件位置，默认$prefix/conf/nginx.conf.</td>
</tr>
<tr>
<td>pid-path</td>
<td>PID文件位置</td>
</tr>
<tr>
<td>error-log-path</td>
<td>主错误日志位置，默认$prefix/logs/error.log</td>
</tr>
<tr>
<td>http-log-path</td>
<td>主请求日志位置，默认$prefix/logs/access.log</td>
</tr>
<tr>
<td>user<br />group</td>
<td>Nginx工作进程的用户、组</td>
</tr>
<tr>
<td>with-*_module<br />without-*_module</td>
<td>
<p>启用/禁用特定模块</p>
<p>--with-stream 启用第四层代理/LB，即ngx_stream_core_module模块</p>
</td>
</tr>
<tr>
<td>with-*</td>
<td>从指定位置寻找依赖库基础</td>
</tr>
<tr>
<td>add-module</td>
<td>安装第三方模块到Nginx的二进制文件中</td>
</tr>
<tr>
<td>add-dynamic-module</td>
<td>指定动态模块目录</td>
</tr>
</tbody>
</table>
<p>配置示例：</p>
<pre class="crayon-plain-tag">./configure
    --sbin-path=/usr/bin/nginx
    --conf-path=/etc/nginx/nginx.conf
    --pid-path=/var/run/nginx/nginx.pid
    --with-http_ssl_module
    --with-pcre=../pcre-8.41
    --with-zlib=../zlib-1.2.11</pre>
<p>如果前缀设置为/usr，则主要路径如下：</p>
<pre class="crayon-plain-tag">nginx path prefix: "/usr"
nginx binary file: "/usr/sbin/nginx"
nginx modules path: "/usr/modules"
nginx configuration prefix: "/usr/conf"
nginx configuration file: "/usr/conf/nginx.conf"
nginx pid file: "/usr/logs/nginx.pid"
nginx error log file: "/usr/logs/error.log"
nginx http access log file: "/usr/logs/access.log" </pre>
<div class="blog_h2"><span class="graybg">启动</span></div>
<pre class="crayon-plain-tag"># nginx [-?hvVtTq] [-s signal] [-c filename] [-p prefix] [-g directives]
#  -V            : 显示版本和构建配置选项，然后退出
#  -t            : 测试配置然后退出
#  -T            : 测试并输出配置，然后退出
#  -q            : 测试配置时抑制非错误类信息
#  -s signal     : 向Master进程发送信号：
#                     stop     快速关闭
#                     quit     优雅关闭
#                     reopen   重新打开日志文件
#                     reload   重新载入配置文件
#  -c filename   :指定配置文件
#  -g directives : 设置全局指令


# 启动Nginx并在后台运行
sudo nginx
# 检查服务器是否成功启动
curl http://localhost:80


# 停止服务器
sudo nginx -s stop</pre>
<div class="blog_h2"><span class="graybg">添加模块</span></div>
<div class="blog_h3"><span class="graybg">安装模块</span></div>
<p>可以在编译Nginx期间，把模块编译进来：</p>
<pre class="crayon-plain-tag"># 目录/path/to/echo-nginx-module中存放echo模块的源代码
./configure --prefix=/opt/nginx \ --add-module=/path/to/echo-nginx-module</pre>
<div class="blog_h3"><span class="graybg">动态模块</span></div>
<p>从1.9.11版本开始，Nginx支持动态模块。在编译Nginx时，你可以指定动态模块的存放目录： </p>
<pre class="crayon-plain-tag">./configure --add-dynamic-module=/usr/local/nginx/modules</pre>
<p>需要添加动态模块时，首先独立编译模块，然后在Nginx配置文件中指定：</p>
<pre class="crayon-plain-tag"># 加载动态模块
load_module /path/to/modules/ngx_http_echo_module.so;</pre>
<div class="blog_h1"><span class="graybg">理解配置文件</span></div>
<div class="blog_h2"><span class="graybg">配置文件结构</span></div>
<p>Nginx由很多模块组成，这些模块由控制文件中的指令（Directives）控制。指令可以分为：简单指令、块指令：</p>
<ol>
<li>简单指令格式：<pre class="crayon-plain-tag">directive-name param1 param2;</pre></li>
<li> 块指令格式：<br />
<pre class="crayon-plain-tag"># 跨指令体使用花括号包围
directive param1 {
    # 块指令内部可以包含其它指令
    directive {

    }
}</pre></p>
<p>块指令中可以包含其它指令，外部的指令称为上下文（Context）。不位于任何{}内的指令，可以认为是位于主上下文（Main Context）。例如events、http指令位于主上下文中，server可以位于http中，location可以位于server中</p>
</li>
<li>
<p>以#号开头的部分表示注释</p>
</li>
</ol>
<div class="blog_h2"><span class="graybg">变量</span></div>
<p>你可以在Nginx配置文件中读写变量，Nginx的变量只有一种类型 —— 字符串。</p>
<pre class="crayon-plain-tag"># 创建变量并赋值
set $a "hello world";
# 支持插值
set $b "$a, $a";
# 用花括号包围变量名，可以防止歧义
set $b "${first}world";

# 要使用$符号本身，可以使用ngx_geo模块的指令geo
geo $dollar {
    default "$";
}

server {
    location /test {
        # ngx_echo没看的echo指令可以将指定的内容输出为响应
        echo "This is a dollar sign: $dollar";
    }
}</pre>
<p>注意：</p>
<ol>
<li><span style="background-color: #c0c0c0;">变量的作用域是全局性的</span>，所有Nginx配置共享之</li>
<li>每个<span style="background-color: #c0c0c0;">HTTP请求都具有任何变量的独立副本</span>，类似于线程本地变量</li>
<li>变量的创建发生在Nginx配置文件加载阶段，赋值则发生在实际请求处理阶段</li>
<li>即使发生<span style="background-color: #c0c0c0;">内部跳转</span>（即跳转到不同location处理），变量仍然是<span style="background-color: #c0c0c0;">同一副本</span></li>
</ol>
<p>多个Nginx模块提供了预定义变量。其中<span style="background-color: #c0c0c0;">很多预定义变量都是只读</span>的，尝试修改会导致意外的后果。</p>
<div class="blog_h3"><span class="graybg">存取处理程序</span></div>
<p>在读写变量时，Nginx会执行一小段代码，分别称为读处理程序（get handler）、写处理程序（set handler）。不同的模块可能为它们的变量准备不同的处理程序，从而影响变量读写的行为。</p>
<p>ngx_map模块提供的map指令，可以设置用户定义变量之间的映射关系，并且自动缓存结果：</p>
<pre class="crayon-plain-tag"># 相当于为$foo变量注册取处理程序
# 该变量的默认值是0，如果$args=debug则该变量的值为1
# 仅仅在需要取$foo的值时，下面的指令才被计算，且一旦计算结果就被缓存，请求处理期间不会重新计算
map $args $foo {
    default     0;
    debug       1;
}</pre>
<div class="blog_h3"><span class="graybg">变量和父子请求</span></div>
<p>Nginx变量的生命周期是和请求绑定的。在Nginx中存在两种请求：</p>
<ol>
<li>主请求：由HTTP客户端发给Nginx的请求</li>
<li>子请求：Nginx在处理主请求时，内部发起的请求。这些请求类似于HTTP请求但是不牵涉任何网络通信。任何主请求/子请求都可以串行、并行发起多个子请求，甚至递归的向自己发起子请求</li>
</ol>
<p>子请求的示例：</p>
<pre class="crayon-plain-tag">http {
    server {
        listen 80;
        location /var {
            # 发起两个子请求
            echo_location /hello;
            echo_location /world "p1=v1&amp;p2=v2";
        }
        location /hello {
            echo Greetings;
        }
        location /world {
            echo World;
        }
    }
}</pre>
<p>关于父子请求中的变量，需要注意：</p>
<ol>
<li><span style="background-color: #c0c0c0;">当前请求（不管是主、子请求）都具有变量的独立副本</span>，通常不会相互干扰</li>
<li>大部分预定义变量都具有针对当前请求的副本。一些例外情况包括：$request_method总是返回主请求的HTTP方法</li>
</ol>
<p>注意子请求和内部跳转（rewrite指令可以引发）不同，后者不会产生新的变量副本。</p>
<p>ngx_echo模块的echo_location可以产生子请求，ngx_auth_request模块也可以产生子请求。ngx_auth_request比较特殊的地方是共享父请求的变量。</p>
<div class="blog_h2"><span class="graybg">指令执行阶段</span></div>
<p>Nginx配置文件虽然类似于编程语言，但是它在整体上是声明性的，而非过程性的。</p>
<p>处理每一个用户请求时，Nginx都是<span style="background-color: #c0c0c0;">按照若干个不同阶段（phase）依次处理</span>的，一般的<span style="background-color: #c0c0c0;">配置指令仅仅会注册并运行在某一个阶段</span>。这些阶段一共有11个。</p>
<p>即使在同一个阶段内，也不能对不同指令的执行顺序进行假设（通常先加载的模块，其指令先执行）。例如more_set_input_headers、rewrite_by_lua都在rewrite阶段的尾部执行，你不能假设其中哪个会先执行。</p>
<div class="blog_h3"><span class="graybg">post-read</span></div>
<p>于Nginx读取并解析完请求头后执行此阶段。模块ngx_realip的指令 set_real_ip_from、real_ip_header运行在此阶段：</p>
<pre class="crayon-plain-tag">http {
    server {
        listen 80;
        # 如果请求来自于本机，则改写其$remote_addr。支持CIDR
        set_real_ip_from 127.0.0.1;
        # 该指令可以指定多次
        set_real_ip_from 127.0.0.0/24;
        # 将$remote_addr改写为自定义头中的值
        real_ip_header   X-Original-Addr;
        location /test {
            # rewrite阶段位于post-read之后，因此这里读取到的是篡改后的$remote_addr
            set $addr $remote_addr;
            echo "from: $addr";
        }
    }
}

# curl -H 'X-Original-Addr: 8.8.8.8' $url
# 输出：
# from: 8.8.8.8</pre>
<div class="blog_h3"><span class="graybg">server-rewrite</span></div>
<p>当ngx_rewrite模块的指令配置在server块中时，则这些指令在此阶段执行。</p>
<div class="blog_h3"><span class="graybg">find-config</span></div>
<p>不支持Nginx模块在此阶段注册处理程序（指令）。在此阶段，Nginx 核心完成当前请求与 location 配置块之间的配对工作。 </p>
<p>在此阶段后，location中的指令才可能生效。</p>
<div class="blog_h3"><span class="graybg">rewrite</span></div>
<p>这个阶段的配置指令一般用来<span style="background-color: #c0c0c0;">对当前请求进行各种修改</span>，例如修改URL或者请求参数。运行在该阶段的指令例如：set、set_by_lua、rewrite_by_lua等。</p>
<p>ngx_set_misc模块、ngx_encrypted_session模块提供的指令、 set_by_lua指令都在此阶段执行，且可以ngx_rewrite模块提供的指令（例如set、rewrite）混合使用，不需要担心执行顺序的问题。</p>
<p>rewrite_by_lua总是在rewrite阶段的最后执行。</p>
<div class="blog_h3"><span class="graybg"> post-rewrite </span></div>
<p>不支持Nginx模块在此阶段注册处理程序（指令）。在此阶段，Nginx 核心完成rewrite阶段所声明的<span style="background-color: #c0c0c0;">内部跳转</span>操作。</p>
<div class="blog_h3"><span class="graybg">preaccess</span></div>
<p>标准模块ngx_limit_req、ngx_limit_zone运行在此阶段，前者控制访问的频度，后者控制访问的并发度。</p>
<div class="blog_h3"><span class="graybg">access</span></div>
<p>这个阶段的配置指令一般用于进行访问控制，例如检查用户访问权限、检查来源IP地址。运行在该阶段的指令例如：allow、deny、ngx_auth_request、access_by_lua等。</p>
<p>标准模块ngx_access提供的allow和deny指令可以控制IP地址的访问权限：</p>
<pre class="crayon-plain-tag">location /hello {
    # ngx_access模块的指令按照配置顺序执行，遇到第一条满足条件的allow/deny指令就不再检查后续的allow/deny
    allow 169.200.179.4/24;
    allow 127.0.0.1;
    deny all;

    echo "hello world";
}</pre>
<p>ngx_lua模块提供的指令access_by_lua，在此阶段的最后执行，可以在allow/deny指令检查之后执行更加复杂的验证逻辑。示例：</p>
<pre class="crayon-plain-tag">location /hello {
    access_by_lua '
        # 使用ngx.var前缀来访问Nginx变量
        if ngx.var.remote_addr == "127.0.0.1" then
            return
        end

        ngx.exit(403)
    ';
}</pre>
<div class="blog_h3"><span class="graybg">post-access</span></div>
<p>配合access阶段，实现ngx_http_core模块的 satisfy 指令的功能。如果在access阶段注册了多个处理程序，则satisfy可以取值为：</p>
<ol>
<li>all 必须所有处理程序都验证通过</li>
<li>any 只需要一个处理程序验证通过</li>
</ol>
<div class="blog_h3"><span class="graybg"> try-files<br /></span></div>
<p>这个阶段实现try_files指令的功能。</p>
<div class="blog_h3"><span class="graybg">content</span></div>
<p>这个阶段的配置指令负责响应内容的生成。运行在该阶段的指令例如：echo、content_by_lua、proxy_pass等。来自不同模块的content阶段指令通常不能声明在同一个location中。</p>
<p>echo指令支持调用多次，而content_by_lua则仅仅支持调用一次，这些细节由具体模块规定。</p>
<p>ngx_echo模块提供的指令echo_before_body、echo_after_body可以和其它运行在content阶段的指令协同工作，因为它工作在Nginx的输出过滤器（output filter，不属于11个请求处理阶段的特殊阶段）中。</p>
<p>如果一个location中没有<span style="background-color: #c0c0c0;">任何content阶段指令，则Nginx把请求映射到静态资源服务模块</span>。通常Nginx会配置三个静态资源服务模块，按照在content阶段的执行顺序，依次是ngx_index、ngx_autoindex、ngx_static。其中ngx_index、ngx_autoindex仅仅会处理以 / 结尾的URL，而ngx_static则相反，处理非/结尾的URL。</p>
<p>ngx_index主要用于在文件系统中自动查找首页文件，例如：</p>
<pre class="crayon-plain-tag">location / {
    # 自动在此目录下，依次寻找index.htm、index.html文件。如果找不到，由下一个content阶段指令处理
    root /var/www/;
    index index.htm index.html;
}</pre>
<div class="blog_h1"><span class="graybg">基础配置块</span></div>
<pre class="crayon-plain-tag"># 在前台运行
daemon off;
# 运行Nginx的用户
user  nobody;
# 工作进程数量
worker_processes  1;

# 错误日志的位置（相对于Prefix）
# error_log file [level];
# 日志级别：debug, info, notice, warn, error, crit, alert, emerg
# 其中，要使用debug，则必须以--with-debug构建Nginx
error_log  logs/error.log;
error_log  logs/error.log  notice;
# 将日志输出到标准错误
error_log stderr debug;

# PID文件位置
pid        logs/nginx.pid;


events {
    # 每个工作进程的最大客户端连接数
    # HTTP服务最大连接数 worker_processes * worker_connections
    # 反向代理最大连接数 worker_processes * worker_connections / 4
    worker_connections  1024;
    # 在接收到新连接通知后，让工作进程尽可能接受多的连接请求
    multi_accept on;
    # 明确指定连接处理方法
    # epoll为Linux 2.6+的高效方式；kqueue为FreeBSD 4.1+, OpenBSD 2.9+, NetBSD 2.0, macOS的高效方式
    use epoll;
} </pre>
<div class="blog_h1"><span class="graybg">第七层配置</span></div>
<p>作为HTTP服务器使用时，Nginx具有以下主要特性：</p>
<ol>
<li>支持静态文件、自动索引、文件描述符缓存</li>
<li>支持缓存的反向代理、负载均衡、容错</li>
<li>支持缓存FastCGI, uwsgi, SCGI</li>
<li>模块化架构。支持gzipping、byte ranges、chunked responses、XSLT、SSI（Server Side Includes，服务器端包含）、图像转换等过滤器。单个页面中的多个SSI可以被并行的包含进来</li>
<li>SSL和TLS支持</li>
<li>支持HTTP/2</li>
</ol>
<p>其它特性包括：</p>
<ol>
<li>基于名称或者IP的虚拟服务器</li>
<li>Keep-alive和管道化连接（pipelined connections）支持</li>
<li>日志：定制格式、缓冲写入、快速轮换、syslog支持</li>
<li>3xx-5xx错误码重定向</li>
<li>URL重写，支持基于正则式的重写</li>
<li>根据客户端地址执行不同函数</li>
<li>基于客户端地址、密码、子请求结果的身份验证</li>
<li>验证HTTP referer</li>
<li>支持PUT, DELETE, MKCOL, COPY, MOVE等HTTP方法</li>
<li>FLV和MP4流媒体支持</li>
<li>响应限速</li>
<li>限制同一IP地址的并发连接数、请求数</li>
<li>基于IP地址获取地理位置信息</li>
<li>支持A/B测试</li>
<li>请求镜像</li>
<li>内嵌Perl</li>
<li>nginScript，一个JavaScript子集语言</li>
</ol>
<div class="blog_h2"><span class="graybg">Web服务器配置</span></div>
<div class="blog_h3"><span class="graybg">HTTP配置</span></div>
<pre class="crayon-plain-tag">http {
    # 在此处包含其它配置文件，mime.types包含了MIME类型和扩展名之间的映射关系
    include       mime.types;
    # 如果不匹配任何MIME类型，使用下面的默认值
    default_type  application/octet-stream;


    # 定义一个日志格式，注意多行字符串的语法
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    '$status $body_bytes_sent "$http_referer" '
    '"$http_user_agent" "$http_x_forwarded_for"';

    # access_log path [format [buffer=size] [gzip[=level]] [flush=time] [if=condition]];
    # access_log off;
    # 指定访问日志位置和格式
    access_log  logs/access.log  main;
    # 访问日志可以输出到标准输出
    access_log /dev/stdout;


    # 这个将为打开文件指定缓存，默认是没有启用的，max 指定缓存数量，
    # 建议和打开文件数一致，inactive 是指经过多长时间文件没被请求后删除缓存。
    open_file_cache max=204800 inactive=20s;
    # 在上一条配置的inactive时间段内，文件被使用的最少次数，不小于该次数，则认为是active
    open_file_cache_min_uses 1;
    # 多长时间检查一次缓存的有效信息
    open_file_cache_valid 30s;


    # 是否启用基于内核态完成的文件描述符之间的数据拷贝
    sendfile        on;


    # 是否启用SOCKET选项 TCP_CORK
    tcp_nopush     on;
    # 启用TCP_NODELAY，即禁用Nagle算法，适用于低延迟需求、小数据量场景
    tcp_nodelay on;
    # 客户端连接的保活时间
    keepalive_timeout  65;

    # 启用GZIP压缩，默认关闭，开启可以节约流量，但是消耗CPU
    gzip  on;
    # 插入Vary: Accept-Encoding响应头
    gzip_vary on;
    # 根据指定正则式匹配的UA，禁用压缩
    gzip_disable "MSIE [1-6]\.(?!.*SV1)";
    # 启用GZIP的MIME类型
    gzip_types text/plain application/x-javascript text/css application/xml;

    # 启用服务器端包含
    ssi on;
    # 抑制SSI处理出错时输出的[an error occurred while processing the directive]信息
    ssi_silent_errors on;
    # 在text/html的基础上，附加的需要处理SSI命令的MIME类型
    ssi_types text/shtml;

    server {
        # 服务器名称 + 监听端口唯一的确定一个服务器
        # listen 127.0.0.1:8000;
        # listen 127.0.0.1;
        # listen 8000;
        # listen *:8000;
        # listen localhost:8000;
        listen       80;
        # 根据虚拟服务器（Virtual Server）的定义，Nginx会比对请求Host头和下面的配置项，选择第一个匹配的server
        # 比对按照如下优先级进行
        # 1、全限定的静态server_name
        # 2、前导通配，例如*.gmem.cc形式的server_name
        # 3、后缀通配，例如www.gmem.*形式的server_name配置
        # 4、正则式定义的server_name
        # 如果没有任何server.server_name匹配，则按下面的规则Fallback：
        # 1、寻找标记为默认服务器的server
        # 2、寻找第一个listen和请求端口匹配的server
        server_name  localhost;

        # 用于设置响应头Content-Type
        charset utf-8;

        # 为本服务器指定访问日志
        access_log  logs/host.access.log  main;

        # URL到文件系统的映射
        location / {
            # / 映射到html目录
            # 指定请求的根目录
            root   html;
            # Index文件列表
            index  index.html index.htm;
        }

        # 定义错误页面
        error_page  404              /404.html;
        error_page   500 502 503 504  /50x.html;

        # try_files file ... uri; 或者 try_files file ... =code;
        # 逐个判断文件是否存在，使用第一个找到的文件来处理请求
        # file的完整路径依赖于root和alias
        # 如果要检查目录的存在性，需要用 / 结尾，例如$uri/
        # 如果所有文件都不存在，则向uri发起一个内部跳转
        location /images/ {
            # 如果请求的URI不存在，则使用默认图片代替
            try_files $uri /images/default.gif;
        }
        # Wordpress伪静态配置
        try_files $uri $uri/ /index.php?$args;

        # URL可以精确到文件
        location = /50x.html {
            root   html;
        }

        location /i/ {
            # 请求/i/top.gif则返回/data/w3/images/top.gif这个文件
            # alias指令为指定的location定义一个代替的位置
            # alias必须以 / 结尾，且只能用于location块内部
            alias /data/w3/images/;
        }
    }
}</pre>
<div class="blog_h3"><span class="graybg">HTTPS配置</span></div>
<pre class="crayon-plain-tag">http {
    server {
        listen       443 ssl;
        server_name  localhost;
        # 数字证书
        ssl_certificate      cert.pem;
        # 私钥
        ssl_certificate_key  cert.key;

        # SSL会话缓存时间和超时时间
        ssl_session_cache    shared:SSL:1m;
        ssl_session_timeout  5m;

        # 声明服务器支持的用于建立安全连接的密码算法
        ssl_ciphers  HIGH:!aNULL:!MD5;
        # 优先使用服务器端的密码算法
        ssl_prefer_server_ciphers  on;

        location / {
            root   html;
            index  index.html index.htm;
        }
    }

}</pre>
<div class="blog_h3"><span class="graybg">预定义变量</span></div>
<p>模块ngx_http_core定义了以下变量：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 30%; text-align: center;">变量</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>$uri</td>
<td>
<p>经过解码，不包含请求参数的URL</p>
<p>当你请求http://localhost:8080/media/时$uri为/media/</p>
</td>
</tr>
<tr>
<td>$request_uri</td>
<td>原始URL，未经解码</td>
</tr>
<tr>
<td>$args</td>
<td>请求的URL参数部分，支持写：<pre class="crayon-plain-tag">set $args "a=3&amp;b=4";</pre></td>
</tr>
<tr>
<td>$arg_XXX</td>
<td>名字为XXX（大小写不敏感）的未经解码的URL参数</td>
</tr>
<tr>
<td>$cookie_XXX</td>
<td>名字为XXX的Cookie</td>
</tr>
<tr>
<td>$http_XXX</td>
<td>名字为XXX的请求头</td>
</tr>
<tr>
<td>$sent_http_XXX</td>
<td>名字为XXX的响应头</td>
</tr>
<tr>
<td>$request_method</td>
<td>请求使用的HTTP方法</td>
</tr>
<tr>
<td>$remote_addr</td>
<td>请求客户端的IP地址</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">location</span></div>
<p>到底哪个location匹配请求，优先级如下:</p>
<ol>
<li>检查具有 = 前缀的location，如果找到匹配，停止搜索</li>
<li>检查具有 ^~ 前缀的location，如果找到匹配，停止搜索</li>
<li>按照声明顺序检查 ~ ~*前缀的location，如果多个匹配，选取正则式最长的那个</li>
<li>常规匹配，也就是URL前缀匹配</li>
</ol>
<p>各种Pattern的说明如下：</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>location = /uri</td>
<td>= 表示精确匹配，只有完全匹配上才能生效</td>
</tr>
<tr>
<td>location ^~ /uri</td>
<td>^~ 开头对URL路径进行前缀匹配，并且优先于正则式匹配</td>
</tr>
<tr>
<td>location ~ pattern</td>
<td>表示区分大小写的正则匹配</td>
</tr>
<tr>
<td>location ~* pattern</td>
<td>表示不区分大小写的正则匹配</td>
</tr>
<tr>
<td>location /uri</td>
<td>不带任何修饰符，也表示前缀匹配，但是优先级比正则式低</td>
</tr>
<tr>
<td>location /</td>
<td>默认匹配，任何未匹配到其它location的请求都会匹配到这里</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">access_log</span></div>
<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>$remote_addr $http_x_forwarded_for</td>
<td>记录客户端IP地址</td>
</tr>
<tr>
<td>$remote_user</td>
<td>记录客户端用户名称</td>
</tr>
<tr>
<td>$request</td>
<td>记录请求的URI和HTTP协议</td>
</tr>
<tr>
<td>$status</td>
<td>记录请求状态</td>
</tr>
<tr>
<td>$body_bytes_sent</td>
<td>发送给客户端的字节数，不包括响应头的大小</td>
</tr>
<tr>
<td>$bytes_sent</td>
<td>发送给客户端的总字节数</td>
</tr>
<tr>
<td>$connection</td>
<td>连接的序列号</td>
</tr>
<tr>
<td>$connection_requests</td>
<td>当前通过一个连接获得的请求数量</td>
</tr>
<tr>
<td>$msec</td>
<td>日志写入时间。单位为秒，精度是毫秒</td>
</tr>
<tr>
<td>$pipe</td>
<td>如果请求是通过HTTP流水线发送</td>
</tr>
<tr>
<td>$http_referer</td>
<td>记录从哪个页面链接访问过来的</td>
</tr>
<tr>
<td>$http_user_agent</td>
<td>记录客户端浏览器相关信息</td>
</tr>
<tr>
<td>$request_length</td>
<td>请求的长度（包括请求行，请求头和请求正文）</td>
</tr>
<tr>
<td>$request_time</td>
<td>请求处理时间，单位为秒，精度毫秒</td>
</tr>
<tr>
<td>$time_iso8601</td>
<td>ISO8601标准格式下的本地时间</td>
</tr>
<tr>
<td>$time_local</td>
<td>记录访问时间与时区</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">反向代理</span></div>
<p>反向代理功能主要由模块ngx_http_proxy_module提供。反向代理的很多指令以在http块中直接声明。</p>
<div class="blog_h3"><span class="graybg">简单代理</span></div>
<pre class="crayon-plain-tag">http {
    server {

        # 反向代理，默认所有请求转发给 8080端口处理
        location / {
            proxy_pass http://localhost:8080;
        }

        # 反向代理，将PHP请求转发给8000端口
        location ~ \.php {
            proxy_pass http://127.0.0.1:8000;
        }

        # gif/jpg/png文件不走反向代理
        # =  前缀表示精确匹配
        # ~  前缀表示大小写敏感的正则式匹配
        # ~* 前缀表示大小写不敏感的正则式匹配
        location ~ \.(gif|jpg|png)$ {
            root /data/images;
        }

        # 联用Rewrite
        # http://localhost/v2/xxx 重写为 http://localhost/v2/google_containers/xxx
        # 然后转发给 https://gcr.azk8s.cn/v2/google_containers/xxx
        location /v2/ {
            rewrite /v2/(.*) /v2/google_containers/$1 break;
            proxy_pass https://gcr.azk8s.cn;
            proxy_set_header Host gcr.azk8s.cn;
        }
    }

}</pre>
<div class="blog_h3"><span class="graybg">非Web服务器</span></div>
<p>要将请求转发给非HTTP的被代理服务器，选择适当的***_pass指令：</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>fastcgi_pass</td>
<td>将请求转发给FastCGI服务器</td>
</tr>
<tr>
<td>uwsgi_pass</td>
<td>将请求转发给uwsgi服务器</td>
</tr>
<tr>
<td>scgi_pass</td>
<td>将请求转发给SCGI服务器</td>
</tr>
<tr>
<td>memcached_pass</td>
<td>将请求转发给Memcached服务器</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">传递请求头</span></div>
<p>默认情况下Nginx会修改两个请求头：Host、Connection并清除值为空的请求头，然后再转发给被代理服务器。</p>
<p>Host默认被设置为$proxy_host变量，Connection默认被设置为Close。你可以改变这些默认行为：</p>
<pre class="crayon-plain-tag">location /some/path/ {
    # 改写、添加转发给被代理服务器的请求头
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    # X-Forwarded-Fo不是标准请求头
    # 格式：X-Forwarded-For: client1, proxy1, proxy2，第一个是真实客户端IP，后续的是经过的代理
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    # 要阻止某个请求头被转发，将其设置为空
    proxy_set_header Accept-Encoding "";

    proxy_pass http://localhost:8000;
}</pre>
<div class="blog_h3"><span class="graybg">传递响应头</span></div>
<p>默认情况下Nginx不会把Date、Server、X-Pad、X-Accel-*响应头转发给客户端，使用proxy_hide_header指令可以指定额外的需要隐藏的响应头。示例：</p>
<pre class="crayon-plain-tag">proxy_hide_header X-Powered-By;</pre>
<div class="blog_h3"><span class="graybg">缓冲配置</span></div>
<p>默认情况下，Nginx接收被代理服务器的响应并放入内部缓冲，直到整个响应都接收到了才发送给客户端。这可以避免同步传输时因为客户端网速太慢，而拖累被代理服务器。</p>
<pre class="crayon-plain-tag">location / {
    # 为单个请求分配的缓冲的大小和数量
    proxy_buffers 16 4k;
    # 被代理服务器返回的第一部分被存放在下面的缓冲中
    proxy_buffer_size 2k;
    proxy_pass http://localhost:8000;
}</pre>
<p>如果禁用缓冲，则响应被同步的发送给客户端。在某些交互式应用场景下可能需要禁用缓冲以获得最快响应时间：</p>
<pre class="crayon-plain-tag">location / {
    proxy_buffering off;
    proxy_pass http://localhost:8000;
}</pre>
<div class="blog_h3"><span class="graybg">选择出口IP</span></div>
<p>如果Nginx有多重网络可以到达被代理服务器，你可能需要明确指定使用哪个网络接口：</p>
<pre class="crayon-plain-tag">location /app1/ {
    # 通过127.0.0.1向被代理服务器转发请求
    proxy_bind 127.0.0.1;
    proxy_pass http://gmem.cc/app1/;
}

location /app2/ {
    # 变量$server_addr为接收原始请求的网络接口的IP地址
    proxy_bind $server_addr;
    proxy_pass http://gmem.cc/app2/;
} </pre>
<div class="blog_h3"><span class="graybg">常用指令</span></div>
<pre class="crayon-plain-tag"># 代理的HTTP协议版本，默认1.0，如果需要使用Keepalive则设置为1.1
proxy_http_version 1.1;
# 连接到被代理服务器的超时时间
proxy_connect_timeout 50;
# 从被代理服务器读取响应的超时时间，仅仅用于两次连续的读操作，而不是整个响应内容的传输所消耗的时间 
proxy_read_timeout 20;
# 向被代理服务器发送请求的超时时间，仅仅用于两次连续的写操作，如果在此时间内被代理服务器每有接收任何数据，则关闭连接
proxy_send_timeout 20;
# 是否缓冲来自被代理服务器的响应
proxy_buffering on;
# 响应的一部分缓冲缓冲的大小，
proxy_buffer_size 32k;
# 缓冲区数量和大小
proxy_buffers 4 32k;
# busy_buffers是缓冲的一部分，通常设置为单个缓冲区的2倍大小
# 这部分缓冲用于存放向客户端返回的响应数据，满了则写入到临时文件
proxy_busy_buffers_size 64k;
# 临时文件（硬盘缓冲）的大小，默认是内存缓冲总大小的2倍，设置为0则关闭硬盘缓冲
proxy_temp_file_write_size 256k;
# 用于修改被代理服务器的响应的location头
proxy_redirect ~^http://10.0.0.1:8080(.*) http://zircon.gmem.cc$1;
proxy_redirect off;
# 设置proxy_hide_header、proxy_set_header指令使用的哈希表的大小
proxy_headers_hash_max_size 51200;
proxy_headers_hash_bucket_size 6400;</pre>
<div class="blog_h3"><span class="graybg">预定义变量</span></div>
<p>模块ngx_http_proxy_module定义了以下变量：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 30%; text-align: center;">变量</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>$proxy_host</td>
<td>被代理服务器的名字和端口</td>
</tr>
<tr>
<td>$proxy_port</td>
<td>被代理服务器的端口</td>
</tr>
<tr>
<td>$proxy_add_x_forwarded_for</td>
<td>客户端的请求头X-Forwarded-For后缀以$remote_addr，如果请求头没有X-Forwarded-For字段则该变量等于$remote_addr</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">静态缓存</span></div>
<p>模块ngx_http_proxy_module提供了基础的缓存功能：</p>
<pre class="crayon-plain-tag"># /path/to/cache/ 用于缓存的本地磁盘目录
# levels=1:2 设置一个两级层次结构的目录，大量的文件放置在单个目录中会导致访问缓慢，推荐使用两级目录
# keys_zone 设置一个共享内存区，该内存区用于存储缓存键和元数据。可以让Nginx不需要读取磁盘即确定缓存HIT or MISS
#     10MB 为共享内存区大小，平均1MB可以存放8000键
# max_size=10g 缓存占用磁盘的最大空间，如果不指定则无限增长
# inactive=60m 文件多长时间不被访问，则从内存移除
proxy_cache_path /path/to/cache levels=1:2 keys_zone=my_cache:10m max_size=10g inactive=60m
# 默认情况下，Nginx先把缓存写到一个临时区域，然后再拷贝到缓存目录
# 建议关闭临时区域，避免不必要的拷贝
use_temp_path=off;

server {
    location / {
        # 此命令启动匹配此location的URL的缓存，其键存放到my_cache
        proxy_cache my_cache;
        # 被代理的上游服务器
        proxy_pass http://my_upstream;
        # 无法从原始服务器获取最新的内容时，Nginx 可以分发缓存中的过期（Stale）数据给客户端
        # 当上游服务器返回错误、超时、50X状态码时，即使缓存陈旧了，仍然使用
        # 所谓陈旧，依据上游服务器响应中设置的过期时间
        proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;
    }

}</pre>
<p>你可以把缓存文件分发到多个磁盘上：</p>
<pre class="crayon-plain-tag">proxy_cache_path /path/to/hdd1 levels=1:2 keys_zone=my_cache_hdd1:10m max_size=10g;
proxy_cache_path /path/to/hdd2 levels=1:2 keys_zone=my_cache_hdd2:10m max_size=10g;
split_clients $request_uri $my_cache {
    # 两个缓存区域分别负担一半缓存文件
    50% "my_cache_hdd1";
    50% "my_cache_hdd2";
}

server {
    location / {
        proxy_cache $my_cache;
    }
}</pre>
<div class="blog_h2"><span class="graybg">正向代理</span></div>
<p>Nginx本身不支持正向代理，可以使用第三方模块：<a href="https://github.com/chobits/ngx_http_proxy_connect_module">ngx_http_proxy_connect_module</a>。</p>
<p>下载模块源码后，需要对Nginx本身源码进行patch：</p>
<pre class="crayon-plain-tag"># 注意选择和Nginx版本匹配的patch
patch -p1 &lt;  /root/CPP/tools/ngx_http_proxy_connect_module/patch/proxy_connect_rewrite_1018.patch
./configure  --add-module=/root/CPP/tools/ngx_http_proxy_connect_module
make</pre>
<p>下面是将Nginx作为一个HTTPS代理的配置示例：</p>
<pre class="crayon-plain-tag">http {
    server {
        listen 8088 ssl;
        resolver 8.8.8.8 ipv6=off;
        server_name proxy.gmem.cc;

        ssl_certificate_key /etc/nginx/certs/proxy.gmem.cc.key;
        ssl_certificate /etc/nginx/certs/proxy.gmem.cc_bundle.pem;

        proxy_connect;
        proxy_connect_allow            all;
        proxy_connect_connect_timeout  10s;
        proxy_connect_read_timeout     10s;
        proxy_connect_send_timeout     10s;
    }
}</pre>
<div class="blog_h2"><span class="graybg">负载均衡</span></div>
<p>模块ngx_http_upstream_module为Nginx提供了负载均衡的支持。使用该模块，你可以定义一个服务器组，并在proxy_pass, fastcgi_pass, uwsgi_pass, scgi_pass,memcached_pass等指令中引用这些组。</p>
<div class="blog_h3"><span class="graybg">upstream</span></div>
<p>定义一个服务器组，服务器可以监听不同的端口，甚至可以监听UNIX域套接字。</p>
<p>默认情况下，请求会<span style="background-color: #c0c0c0;">根据权重，使用round-robin方式在组内各服务器之间分发</span>。如果和一个<span style="background-color: #c0c0c0;">服务器通信失败，会转而尝试下一个服务器</span>，如果所有服务器都不能获得正确响应，则最后一个服务器的结果被发送给客户端。</p>
<pre class="crayon-plain-tag">resolver 10.96.0.10;

upstream backend {
    # upstream指令中可以包含多个server指令，其格式为：server address [parameters];
    # 此服务器的权重为5
    server backend1.example.com       weight=5;
    # 此服务器的权重为默认值1，30s内最多失败3次，最多允许100个同时的连接
    server 127.0.0.1:8080       max_fails=3 fail_timeout=30s max_conns=100;
    server unix:/tmp/backend3;
    # 此服务器为备份服务器，仅当主服务器不可用时，请求才发送到备份服务器
    server backup1.example.com:8080   backup;
    # 此服务器被标记为永久不可用
    server down.example.com           down;
    # 访问DNS服务器，监控服务器域名对应的IP地址，自动更新服务器对应的IP地址
    # 必须在http块中添加resolver指令才能生效
    # slow_start，当服务从不健康变为健康、不可用变为可用时，其权重恢复正常值所需要的时间
    # drain，此模式下的服务器，仅仅bound到该服务器的请求才发送给它
    # route，上游服务器的route名，用于会话绑定
    server media-api.dev.svc.k8s.gmem.cc resolve slow_start=10 drain route=string;

    # 指定存放动态配置的组的配置信息的文件
    state /var/lib/nginx/state/servers.conf;

    # 缓存到上游服务器的TCP连接，每个工作进程缓存32个连接，如果超过此数量则使用LRU算法清除
    # 需要配合proxy_http_version 1.1使用
    keepalive 32;

    # 如果处理请求时，不能立刻选取适用的上游服务器，可以让请求排队
    # 如果排队满了，或者超时，则返回502给客户端
    queue number [timeout=time];

    # 定义一个名为name，大小为size的共享内存区域。此区域存放此服务器组的配置、运行时状态
    # 所有Worker进程共享此内存区域。多个服务器组可以共享单个区域（name相同），这种情况下size只需要声明一次
    zone name [size];
}</pre>
<div class="blog_h3"><span class="graybg">负载均衡算法</span></div>
<p>ngx_http_upstream_module支持多种负载均衡算法，这些算法对应不同的指令。所有指令均必须位于upstream块中：</p>
<pre class="crayon-plain-tag"># 负载均衡算法：客户端和上游服务器的对应关系依赖于key的哈希值
# key可以包含文本、变量，或者两者的组合
# 默认情况下不使用一致性哈希，添加服务器节点后可能导致大量的key重新映射到不同服务器
# 如果指定consistent参数，则采用一致性哈希算法。添加服务器后，仅少量key重新映射，可以保证缓存服务器的命中率
hash key [consistent];

# 负载均衡算法：基于客户端的IP地址来映射请求到上游服务器
# 可以保证来自一个客户端的请求，总是由同一服务器处理，除非目标服务器不可用
ip_hash;

# 负载即均衡算法：将请求发送给具有最少活动连接数的上游服务器，同时考虑服务器的权重
least_conn;

# 负载均衡算法：将请求转发给具有最短响应时间+最少活动连接数的服务器，同时考虑服务器的权重
# 格式：least_time header | last_byte [inflight];
# header以收到请求头时间计，last_byte以收到完整响应时间计
least_time;</pre>
<div class="blog_h3"><span class="graybg">sticky</span></div>
<p>该指令用于实现会话绑定（session affinity），必须位于upstream块中。指令格式：</p>
<pre class="crayon-plain-tag"># 基于Cookie的绑定，会话绑定到的目标服务器信息，由Nginx自动产生的Cookie传递
# 对于尚未绑定到特定服务器的请求，Nginx会使用负载均衡算法为其选取一个上游服务器，并种植Cookie
sticky cookie name [expires=time] [domain=domain] [httponly] [secure] [path=path];
# 示例：
sticky cookie srv_id expires=1h domain=.example.com path=/;

# 基于Route的绑定
# 被代理服务器第一次接收到客户端请求时，会为客户端分配一个route信息，此客户端的所有后续请求均在
# Cookie或者URI中附带route信息。此信息和server的route参数进行比较，从而确定哪个服务器负
# 责处理请求
# 如果没有为server指定route参数，则其route为IP和端口的MD5的HEX形式
sticky route $variable ...;
# 示例：
map $cookie_jsessionid $route_cookie {
    ~.+\.(?P\w+)$ $route;
}
map $request_uri $route_uri {
    ~jsessionid=.+\.(?P\w+)$ $route;
}
upstream backend {
    server backend1.example.com route=a;
    server backend2.example.com route=b;
    # 优先从Cookie JSESSIONID中获取route信息，如果没有则从URI参数jsessionid中获取
    sticky route $route_cookie $route_uri;
}


# 由Nginx来分析上游服务器的响应，从中学习到服务器初始化的会话（通常以Cookie的形式传递给客户端）
# create，lookup分别用于获取服务器创建的会话ID、客户端传递的会话ID。这两个指令都可以声明多次
# 第一个不为空的变量生效
# 会话和上游服务器的对应关系，被存放在共享内存区域zone中。1MB的共享内存在64bit机器上可以存放8000会话
# 连续timeout没有被访问的会话，自动重共享内存区域中清除，默认timeout=10分钟
# header用于从上游服务器接收到指定的响应头后，创建一个session
sticky learn create=$variable lookup=$variable zone=name:size [timeout=time] [header];
# 示例：
upstream backend {
   server backend1.example.com:8080;
   server backend2.example.com:8081;

   sticky learn
          # 上游服务器将会话ID存放在Cookie EXAMPLECOOKIE中
          create=$upstream_cookie_examplecookie
          # 读取客户端请求的Cookie EXAMPLECOOKIE来确定其上游服务器
          lookup=$cookie_examplecookie
          # 会话-服务器对应关系存放在名为client_essions的共享内存中
          zone=client_sessions:1m;
}</pre>
<div class="blog_h3"><span class="graybg">预定义变量</span></div>
<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>$upstream_addr</td>
<td>
<p>上游服务器的地址端口。如果处理请求过程中牵涉到多个上游服务器，则用逗号分隔，例如</p>
<p style="padding-left: 30px;">192.168.1.1:80, 192.168.1.2:80, unix:/tmp/sock</p>
<p>如果发生跨越多组服务器的内部跳转，则不同组的服务器用分号分隔，例如</p>
<p style="padding-left: 30px;">192.168.1.2:80, unix:/tmp/sock : 192.168.10.1:80</p>
</td>
</tr>
<tr>
<td>$upstream_bytes_received</td>
<td>从上游服务器接收到的字节数</td>
</tr>
<tr>
<td>$upstream_cache_status</td>
<td>存放响应缓存的状态，取值：MISS、BYPASS、EXPIRED、STALE、UPDATING、REVALIDATED、HIT</td>
</tr>
<tr>
<td>$upstream_connect_time</td>
<td>与上游服务器创建连接所消耗的时间</td>
</tr>
<tr>
<td>$upstream_cookie_name</td>
<td>上游服务器通过Set-Cookie响应头种植的名为name的Cookie的值</td>
</tr>
<tr>
<td>$upstream_header_time</td>
<td>上游服务器通过响应头设置的时间</td>
</tr>
<tr>
<td>$upstream_response_length</td>
<td>上游服务器的响应长度</td>
</tr>
<tr>
<td>$upstream_response_time</td>
<td>接收上游服务器响应所消耗的时间</td>
</tr>
<tr>
<td>$upstream_status</td>
<td>上游服务器的响应状态码</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">完整示例</span></div>
<p>下面是一个L7负载均衡配合SSL Termination的例子：</p>
<pre class="crayon-plain-tag">http {
    upstream ceph-dashboard {
        server ceph-1.gmem.cc:8443;
        server ceph-2.gmem.cc:8443 backup;
    }
    server {
        listen                443 ssl;
        server_name           ceph.gmem.cc;
        ssl_certificate       /etc/ssl/gmem.cc/fullchain.pem;
        ssl_certificate_key   /etc/ssl/gmem.cc/privkey.pem;
        location / {
            proxy_pass https://ceph-dashboard;
            proxy_ssl_verify off;
        }
    }
} </pre>
<div class="blog_h2"><span class="graybg">URL重写</span></div>
<p>模块ngx_http_rewrite_module提供了URL重写功能。该模块的指令的执行顺序如下：</p>
<ol>
<li>位于server块的指令依次顺序执行</li>
<li>循环执行：
<ol>
<li>根据请求URL找到匹配的location</li>
<li>顺序执行location块中的指令</li>
<li>如果URL被重写，则返回到2.1重复执行，但是最多不超过10次</li>
</ol>
</li>
</ol>
<div class="blog_h3"><span class="graybg">break</span></div>
<p>停止继续为当前请求处理ngx_http_rewrite_module模块的指令。</p>
<div class="blog_h3"><span class="graybg">rewrite</span></div>
<p>执行URL重写：<pre class="crayon-plain-tag">rewrite regex replacement [flag];</pre></p>
<p><span style="background-color: #c0c0c0;">如果regex匹配请求URL（去除http://host:port剩余的部分）</span>，则将URL改写为replacement。rewrite指令按照其声明的顺序依次执行。flag可以是：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 15%; text-align: center;">flag</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>last</td>
<td>停止处理当前location中的ngx_http_rewrite_module指令。并且，如果URL被改写了，寻找匹配的新location进行处理</td>
</tr>
<tr>
<td>break</td>
<td>停止处理当前location中的ngx_http_rewrite_module指令。行为类似于break指令</td>
</tr>
<tr>
<td>redirect</td>
<td>
<p>使用302状态码返回一个临时的重定向（24-48小时之内临时发生的网页转移），replacement必须以http://、https://或$scheme开头</p>
</td>
</tr>
<tr>
<td>permanent</td>
<td>使用301状态码返回永久重定向</td>
</tr>
</tbody>
</table>
<p>如果replacement以http://、https://或$scheme开头，则表示进行重定向。示例：</p>
<pre class="crayon-plain-tag">http {
    # 永久重定向
    server {
        listen       80;
        rewrite / https://blog.gmem.cc permanent;
    }
}</pre>
<div class="blog_h3"><span class="graybg">if</span></div>
<p>在满足条件的情况下，执行花括号内的指令：<pre class="crayon-plain-tag">if (condition) { ... }</pre></p>
<p>应当仅仅在花括号内使用return、rewrite、last等rewrite指令，否则可能导致意外行为，甚至出现段错误SIGSEGV。</p>
<p>其中condition可以是以下之一：</p>
<ol>
<li>$变量名，如果变量值为0或空串则condition为false</li>
<li>基于 <pre class="crayon-plain-tag">=</pre> 或 <pre class="crayon-plain-tag">!=</pre> 操作符比较变量和字符串</li>
<li>基于 <pre class="crayon-plain-tag">~</pre> 或 <pre class="crayon-plain-tag">*~</pre>操作符（以及<pre class="crayon-plain-tag">!~</pre>或<pre class="crayon-plain-tag">!~*</pre>）来匹配变量和正则式，*~表示大小写不敏感。正则式中可以包含捕获，并在随后通过$1..$9引用</li>
<li>基于 <pre class="crayon-plain-tag">-f</pre> 或 <pre class="crayon-plain-tag">!-f</pre> 操作符检查文件是否存在</li>
<li>基于 <pre class="crayon-plain-tag">-x</pre> 或 <pre class="crayon-plain-tag">!-x</pre> 操作符检查可执行文件是否存在</li>
<li>基于 <pre class="crayon-plain-tag">-d</pre> 或 <pre class="crayon-plain-tag">!-d</pre> 操作符检查目录是否存在</li>
<li>基于 <pre class="crayon-plain-tag">-e</pre> 或 <pre class="crayon-plain-tag">!-e</pre> 操作符检查目录、文件、符号连接是否存在</li>
</ol>
<p>示例：</p>
<pre class="crayon-plain-tag"># 如果UA为IE
if ($http_user_agent ~ MSIE) {
    # 则将URL前缀以/msie
    rewrite ^(.*)$ /msie/$1 break;
    # 可以重写为外部URL
    rewrite ^(.*)$ https://nginx.gmem.cc/grafana/avatar.png;
}

# 如果Cookie中有id则设置变量
if ($http_cookie ~* "id=([^;]+)(?:;|$)") {
    set $id $1;
}
# 如果HTTP方法为POST则返回405状态码
if ($request_method = POST) {
    return 405;
}
# 如果缓慢则限速
if ($slow) {
    limit_rate 10k;
}

# 根据扩展名来设置缓存过期时间
location ~* \.(js|css|jpg|jpeg|gif|png|swf)$ {
    # 如果文件存在，则设置过期时间为1小时
    if (-f $request_filename) {
        expires 1h;
        break;
    }
}

# 防盗链
location ~* \.(gif|jpg|swf)$ {
    valid_referers none blocked www.gmem.cc;
    if ($invalid_referer) {
       rewrite ^/ http://$host/logo.png;
    }
}</pre>
<div class="blog_h3"><span class="graybg">return</span></div>
<p>停止处理并返回状态码给客户端，格式：</p>
<pre class="crayon-plain-tag">return code [text];
return code URL;
return URL;</pre>
<div class="blog_h3"><span class="graybg">rewrite_log </span></div>
<p>如果设置为on则记录URL重写日志</p>
<div class="blog_h2"><span class="graybg">TLS Termination</span></div>
<pre class="crayon-plain-tag">http {
    client_max_body_size 4g;
    server {
        listen                443 ssl;
        server_name           git.gmem.cc;
        ssl_certificate       /etc/letsencrypt/live/gmem.cc/fullchain.pem;
        ssl_certificate_key   /etc/letsencrypt/live/gmem.cc/privkey.pem;
        location / {
            proxy_pass https://127.0.0.1:2443;
            proxy_ssl_verify off;
        }
    }

    server {
        listen                443 ssl;
        server_name           harbor.gmem.cc;
        ssl_certificate       /etc/letsencrypt/live/gmem.cc/fullchain.pem;
        ssl_certificate_key   /etc/letsencrypt/live/gmem.cc/privkey.pem;
        client_max_body_size  0;
        location / {
            proxy_pass https://127.0.0.1:4443;
            proxy_ssl_verify off;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_buffering off;
            proxy_request_buffering off;
        }
    }
    server {
        listen                443 ssl default_server;
        ssl_certificate       /etc/letsencrypt/live/gmem.cc/fullchain.pem;
        ssl_certificate_key   /etc/letsencrypt/live/gmem.cc/privkey.pem;
        return 404;
    }
}</pre>
<div class="blog_h2"><span class="graybg">最佳实践</span></div>
<div class="blog_h3"><span class="graybg">root放到server</span></div>
<p>而不是放到location中，这样可以让所有location使用相同的root</p>
<div class="blog_h3"><span class="graybg">避免重复的index</span></div>
<pre class="crayon-plain-tag">http {
    index index.php index.htm index.html;
}</pre>
<div class="blog_h3"><span class="graybg">避免滥用if</span></div>
<p>要检查文件是否存在，使用try_files。if仅仅用于配合rewrite模块的指令使用。 </p>
<div class="blog_h3"><span class="graybg">避免代理一切</span></div>
<pre class="crayon-plain-tag">server {
    location / {
        # 先尝试本地静态文件，然后才发送给代理
        try_files $uri $uri/ @proxy;
    }
    location @proxy {
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_pass unix:/tmp/phpcgi.socket;
    }
}</pre>
<div class="blog_h1"><span class="graybg">第四层配置</span></div>
<div class="blog_h2"><span class="graybg">简介</span></div>
<p>作为TCP/UDP代理服务器时，Ngin具有以下特性：</p>
<ol>
<li>可以作为通用的TCP/UDP代理</li>
<li>为TCP添加SSL和TLS SNI支持</li>
<li>负载均衡和容错</li>
<li>基于客户端地址的访问控制</li>
<li>根据客户端地址执行不同函数</li>
<li>限制同一IP地址的并发连接数</li>
<li>日志：定制格式、缓冲写入、快速轮换、syslog支持</li>
<li>基于IP地址获取地理位置信息</li>
<li>支持A/B测试</li>
<li>nginScript</li>
</ol>
<p>在1.9.0之前的版本，基于TCP的代理和负载均衡需要依赖第三方补丁nginx_tcp_proxy_module。</p>
<p>从1.9.0开始，可以在构建时<span style="background-color: #c0c0c0;">指定<pre class="crayon-plain-tag">--with-stream</pre>选项，启用</span>内置第四层代理/负载均衡。第四层代理的功能由ngx_stream_core_module模块提供。</p>
<div class="blog_h2"><span class="graybg">工作流程</span></div>
<p>Nginx处理客户端的TCP/UDP会话，由以下阶段组成。</p>
<div class="blog_h3"><span class="graybg">Post-accept</span></div>
<p>此阶段，Nginx刚刚接受了客户端连接。ngx_stream_realip_module模块参与到此阶段。</p>
<div class="blog_h3"><span class="graybg">Pre-access</span></div>
<p>访问预检查。ngx_stream_limit_conn_module 模块参与到此阶段。</p>
<div class="blog_h3"><span class="graybg">Access</span></div>
<p>访问权限检查。ngx_stream_access_module模块参与到此阶段。</p>
<div class="blog_h3"><span class="graybg">SSL</span></div>
<p>TLS/SSL termination。ngx_stream_ssl_module模块参与到此阶段。</p>
<div class="blog_h3"><span class="graybg">Preread</span></div>
<p>预读取，读取客户端数据的初始字节到预读取缓冲中。允许ngx_stream_ssl_preread_module等模块对数据进行分析。</p>
<div class="blog_h3"><span class="graybg">Content</span></div>
<p>实际处理数据的阶段，调用被代理服务器，或者返回一个值给客户端。</p>
<div class="blog_h3"><span class="graybg">Log</span></div>
<p>记录客户端会话的处理结果，ngx_stream_log_module模块参与此阶段。</p>
<div class="blog_h2"><span class="graybg">配置项解释</span></div>
<pre class="crayon-plain-tag"># 定义一个第四层代理
stream {

    # TCP_NODELAY
    tcp_nodelay on;

    upstream mysqld {
        hash $remote_addr consistent;
        server 192.168.1.42:3306 weight=5 max_fails=1 fail_timeout=10s;
        server 192.168.1.43:3306 weight=5 max_fails=1 fail_timeout=10s;
        # 可以通过UNIX Domain Socket连接上游
        server unix:/var/run/mysql.sock;
    }

    server {
        # 该指令可以指定多次
        #
        # listen address:port [ssl] [udp] [proxy_protocol] [backlog=number] [rcvbuf=size]
        #                       [sndbuf=size] [bind] [ipv6only=on|off] [reuseport]
        #                       [so_keepalive=on|off|[keepidle]:[keepintvl]:[keepcnt]];
        #
        # address:port 代理服务器的监听端口，可以指定 *:3306、10.0.0.1:3306等形式
        #
        # ssl 指定此端口上所有的连接必须工作在SSL模式下
        #
        # udp 指定此端口工作在UDP协议下
        #
        # proxy_protocol 指定此端口上的所有连接必须使用PROXY协议
        # 关于此协议，参考http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt
        # PROXY 2 从1.13.11开始被支持
        #
        # backlog 最大排队等待被接受的连接数
        #
        # rcvbuf SO_RCVBUF
        # sndbuf SO_SNDBUF
        #
        # bind 提示针对每个给出的address:port，分别执行执行bind()调用
        #
        # ipv6only IPV6_V6ONLY
        # reuseport 基于SO_REUSEPORT选项，为每个Worker创建独立的监听套接字。内核将会分别入站请求
        #           给每个Worker，要求Linux 3.9+
        #
        # so_keepalive TCP保活选项：TCP_KEEPIDLE、TCP_KEEPINTVL、TCP_KEEPCNT
        #
        listen 3306;

        # 预读缓冲大小
        preread_buffer_size 16k;
        # 预读超时
        preread_timeout 30s;

        # 读取PROXY协议头的超时时间，如果超过此时间仍然没有传输完整的PROXY头则连接关闭
        proxy_protocol_timeout 30s;

        # 用来解析Upstream的DNS服务器，DNS的缓存有效期
        resolver 127.0.0.1 [::1]:5353 valid=30s;
        # DNS解析超时
        resolver_timeout 5s;

        proxy_connect_timeout 1s;
        proxy_timeout 3s;
        proxy_pass mysqld;

        # 来自模块ngx_stream_limit_conn_module的配置
        # limit_conn zone number;
        # 限制每个地址仅仅允许同时1个连接
        limit_conn addr 1;
        limit_conn_log_level error;
    }

}</pre>
<div class="blog_h2"><span class="graybg">变量列表</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">变量</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>$binary_remote_addr</td>
<td>二进制形式的客户端地址</td>
</tr>
<tr>
<td>$bytes_received</td>
<td>从客户端接收到的字节数</td>
</tr>
<tr>
<td>$bytes_sent</td>
<td>发送到客户端的字节数</td>
</tr>
<tr>
<td>$connection</td>
<td>连接串号</td>
</tr>
<tr>
<td>$hostname</td>
<td>主机名</td>
</tr>
<tr>
<td>$msec</td>
<td>当前时间，毫秒精度</td>
</tr>
<tr>
<td>$pid</td>
<td>当前工作进程PID</td>
</tr>
<tr>
<td>$protocol</td>
<td>当前使用的协议，TCP或UDP</td>
</tr>
<tr>
<td>$proxy_protocol_addr</td>
<td>PROXY协议头中的客户端地址</td>
</tr>
<tr>
<td>$proxy_protocol_port</td>
<td>PROXY协议头中的客户端端口</td>
</tr>
<tr>
<td>$remote_addr</td>
<td rowspan="2">客户端地址、端口</td>
</tr>
<tr>
<td>$remote_port</td>
</tr>
<tr>
<td>$server_addr</td>
<td rowspan="2">接受连接的服务地址、端口</td>
</tr>
<tr>
<td>$server_port</td>
</tr>
<tr>
<td>$session_time</td>
<td>会话持续时间，毫秒精度</td>
</tr>
<tr>
<td>$status</td>
<td>
<p>会话状态，取值：</p>
<p style="padding-left: 30px;">200 会话成功完成<br />400 客户端数据无法解析，例如PROXY头无法无法识别<br />403 访问被拒绝<br />500 内部服务器错误<br />502 网关失败，例如上游服务器无法访问<br />503 服务不可用，例如超过连接数限制</p>
</td>
</tr>
<tr>
<td>$time_iso8601 </td>
<td>ISO 8601格式的本地时间</td>
</tr>
<tr>
<td>$time_local</td>
<td>本地时间 </td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">配置示例</span></div>
<div class="blog_h3"><span class="graybg">SSH转发</span></div>
<pre class="crayon-plain-tag">stream {
    upstream ssh {
        hash $remote_addr consistent;
        server 192.168.1.42:22 weight=5;
    }

    server {
        listen 2222;
        proxy_pass ssh;
    }
}</pre>
<div class="blog_h3"><span class="graybg">MySQL负载均衡</span></div>
<pre class="crayon-plain-tag">stream {
    upstream mysqld {
        hash $remote_addr consistent;
        server 192.168.1.42:3306 weight=5 max_fails=1 fail_timeout=10s;
        server 192.168.1.43:3306 weight=5 max_fails=1 fail_timeout=10s;
    }

    server {
        listen 3306;
        proxy_connect_timeout 1s;
        proxy_timeout 3s;
        proxy_pass mysqld;
    }
} </pre>
<div class="blog_h3"><span class="graybg">SSL后端</span></div>
<p>需要额外编译配置项：<pre class="crayon-plain-tag">--with-stream_ssl_preread_module</pre>。示例：</p>
<pre class="crayon-plain-tag">daemon off;
worker_processes 4;
pid /var/run/nginx/nginx.pid;
worker_rlimit_nofile 74363;
worker_shutdown_timeout 10s ;
events {
    multi_accept        on;
    worker_connections  16384;
    use                 epoll;
}

stream {
    # 连接日志
    log_format log_stream '$remote_addr [$time_local] $protocol [$ssl_preread_server_name] [$ssl_preread_alpn_protocols] [$name] $status $bytes_sent $bytes_received $session_time';       
    access_log  /usr/logs/stream.log log_stream;
    map $ssl_preread_server_name $name {
        blog.gmem.cc apache;
        git.gmem.cc  gitea;
        default apache;
    }

    upstream apache {
        server 127.0.0.1:8443;
    }

    upstream gitea {
        server ::1:3443;
    }

    server {
        listen 0.0.0.0:443;
        proxy_pass $name;
        ssl_preread on;
    }
}</pre>
<div class="blog_h2"><span class="graybg">真实客户端IP</span></div>
<p>L4配置，上游服务器看到的客户端地址是127.0.0.1，要让上游服务器看到真实客户端IP，有几种方法。</p>
<div class="blog_h3"><span class="graybg">代理协议</span></div>
<p>这种方式，需要后端支持代理协议（作为协议的服务器端）。对于Apache，启用代理协议的方法参考<a href="https://blog.gmem.cc/apache-http-server-faq#proxy-protocol">Apache HTTP Server知识集锦</a>。</p>
<p>Nginx的配置如下：</p>
<pre class="crayon-plain-tag">stream {
    server {
        listen 0.0.0.0:443;
        proxy_protocol on;
        proxy_pass $name;
        ssl_preread on;
    }
}</pre>
<div class="blog_h1"><span class="graybg">常见问题</span></div>
<div class="blog_h2"><span class="graybg">静态转发</span></div>
<p>在被代理服务中，设置响应头X-Accel-Redirect，可以提示Nginx进行静态转发，例如：</p>
<pre class="crayon-plain-tag">X-Accel-Redirect: /media/encryp//10/57/1497089.html</pre>
<p>Nginx配置：</p>
<pre class="crayon-plain-tag">server {
    root  /datastore/;
}</pre>
<p>则Nginx自动从 /datastore/media/encryp//10/57/1497089.html获取静态文件。</p>
<p>&nbsp;</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/nginx-faq">Nginx知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/nginx-faq/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
