• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

iresty/nginx-lua-module-zh-wiki: https://github.com/openresty/lua-nginx-module c ...

原作者: [db:作者] 来自: 网络 收藏 邀请

开源软件名称(OpenSource Name):

iresty/nginx-lua-module-zh-wiki

开源软件地址(OpenSource Url):

https://github.com/iresty/nginx-lua-module-zh-wiki

开源编程语言(OpenSource Language):


开源软件介绍(OpenSource Introduction):

Name

ngx_http_lua_module - 嵌入强有力的 Lua 到 Nginx HTTP 服务中。

该模块不是随着 Nginx 源码发行。 更多请看 安装说明

Table of Contents

Status

生产版本可用

Version

该文档描述的 ngx_lua v0.10.7 是2016年11月4号发布。

Synopsis

 # 设置纯 Lua 扩展库的搜寻路径(';;' 是默认路径):
 lua_package_path '/foo/bar/?.lua;/blah/?.lua;;';

 # 设置 C 编写的 Lua 扩展模块的搜寻路径(也可以用 ';;'):
 lua_package_cpath '/bar/baz/?.so;/blah/blah/?.so;;';

 server {
     location /lua_content {
         # 通过 default_type 设置默认的 MIME 类型:
         default_type 'text/plain';

         content_by_lua_block {
             ngx.say('Hello,world!')
         }
     }

     location /nginx_var {
         # 通过 default_type 设置默认的 MIME 类型:
         default_type 'text/plain';

         # 试试访问 /nginx_var?a=hello,world
         content_by_lua_block {
             ngx.say(ngx.var.arg_a)
         }
     }

     location /request_body {
          client_max_body_size 50k;
          client_body_buffer_size 50k;

          content_by_lua_block {
             ngx.req.read_body()  -- explicitly read the req body
             local data = ngx.req.get_body_data()
             if data then
                 ngx.say("body data:")
                 ngx.print(data)
                 return
             end

             -- body may get buffered in a temp file:
             local file = ngx.req.get_body_file()
             if file then
                 ngx.say("body is in file ", file)
             else
                 ngx.say("no body found")
             end
         }
     }

     # 在子请求中直接发起 Lua 非阻塞 I/O 调用
     # (其实,更好的方式是使用 cosockets)
     location /lua {
         # 通过 default_type 设置默认的 MIME 类型:
         default_type 'text/plain';

         content_by_lua_block {
             local res = ngx.location.capture("/some_other_location")
             if res then
                 ngx.say("status: ", res.status)
                 ngx.say("body:")
                 ngx.print(res.body)
             end
         }
     }

     location = /foo {
         rewrite_by_lua_block {
             res = ngx.location.capture("/memc",
                 { args = { cmd = "incr", key = ngx.var.uri } }
             )
         }

         proxy_pass http://blah.blah.com;
     }

     location = /mixed {
         rewrite_by_lua_file /path/to/rewrite.lua;
         access_by_lua_file /path/to/access.lua;
         content_by_lua_file /path/to/content.lua;
     }

     # 在代码中使用 Nginx 变量
     # 注意: Nginx 变量的内容一定要做仔细的过滤,否则会有很大的安全风险
     location ~ ^/app/([-_a-zA-Z0-9/]+) {
         set $path $1;
         content_by_lua_file /path/to/lua/app/root/$path.lua;
     }

     location / {
        lua_need_request_body on;

        client_max_body_size 100k;
        client_body_buffer_size 100k;

        access_by_lua_block {
            -- 检测客户端 IP 地址是否在我们的黑名单中
            if ngx.var.remote_addr == "132.5.72.3" then
                ngx.exit(ngx.HTTP_FORBIDDEN)
            end

            -- 检测客户端 URI 数据是否包含禁用词汇
            if ngx.var.uri and
                   string.match(ngx.var.request_body, "evil")
            then
                return ngx.redirect("/terms_of_use.html")
            end

            -- tests passed
        }

        # proxy_pass/fastcgi_pass/etc settings
     }
 }

返回目录

Description

该模块通过标准 Lua5.1 解释器或 LuaJIT 2.0/2.1,把 Lua 嵌入到 Nginx 里面, 并利用 Nginx 子请求,把强大的 Lua 线程(Lua协程)混合到 Nginx 的事件模型中。

Apache's mod_luaLighttpd's mod_magnet 不同的是, 只要使用这个模块提供的Nginx API for Lua来处理请求上游服务,该模块的 Lua 代码被执行在网络上是 100% 非阻塞的。其中上游请求服务有:MySQL、PostgreSQL、Memcached、Redis或upstream HTTP web 服务等。

至少下面这些 Lua 库、Nginx 模块是可以与 ngx_lua 模块配合使用的:

几乎所有的 Nginx 模块都可以通过 ngx.location.capturengx.location.capture_multi 与 ngx_lua 模块完成调用,但推荐使用类似 lua-resty-* 库,而不是子请求访问 Nginx 上游模块,因为前者更加灵活并且内存效率更高。

在单个 Nginx worker 中,标准 Lua 或 LuaJIT 的实例在所有请求中是共享使用的,但每个请求上下文是通过轻量的 Lua 协程做到隔离的。

在 Nginx worker 进程中加载 Lua 模块,坚持小内存的使用,即使在重负载下依然如此。

该模块是 Nginx 的 HTTP 子系统插件,所以它只能对 HTTP 环境的下游进行对话(例如:HTTP 0.9/1.0/1.1/2.0, WebSockets等)。

如果你想获得通用的 TCP 下游客户端对话能力,这时应使用 ngx_stream_lua 模块,同样它也是兼容 Lua API 的。

返回目录

Typical Uses

列举部分:

  • 在 Lua 中揉和处理各种不同的 Nginx 上游输出(proxy, drizzle, postgres, redis, memcached等)
  • 在请求真正到达上游服务之前,Lua 可以随心所欲的做复杂访问控制和安全检测
  • 随心所欲的操控响应头里面的信息(通过 Lua)
  • 从外部存储服务(比如 redis, memcached, mysql, postgresql)中获取后端信息,并用这些信息来实时选择哪一个后端来完成业务访问
  • 在内容 handler 中随意编写复杂的 web 应用,使用同步非阻塞的方式,访问后端数据库和其他存储
  • 在 rewrite 阶段,通过 Lua 完成非常复杂的 URL dispatch
  • 用 Lua 可以为 Nginx 子请求和任意 location,实现高级缓存机制

本模块会把你带入一个拥有无限可能的服务端开发新世界,你可以把 Nginx 的各种功能进行自由拼接, 更重要的是,开发门槛并不高,这一切都是用强大轻巧的 Lua 语言来操控。

本模块的脚本有充分的灵活性,并且性能和原生 C 语言编程相比毫不逊色,无论是 CPU 时间还是内存占用方面。当然这里需要启用 LuaJIT 2.x。

其他脚本语言的实现通常很难达到类似性能。

Lua state(Lua VM instance)会被共享给单个 nginx worker 内所有的请求,从而达到最小化内存消耗。

返回目录

Nginx Compatibility

最新模块版本和 Nginx 的兼容列表:

  • 1.11.x (最后测试: 1.11.2)
  • 1.10.x
  • 1.9.x (最后测试: 1.9.15)
  • 1.8.x
  • 1.7.x (最后测试: 1.7.10)
  • 1.6.x

比 Nginx 1.6.0 更老的版本 再提供支持。

返回目录

Installation

强烈推荐使用 OpenResty 安装包,它包含了 Nginx, ngx_lua, LuaJIT 2.0/2.1 (或者可选的标准 Lua 5.1解释器),还包含很多强劲、好用的 Nginx 模块。使用一个简单的命令就可以完成基础安装:./configure --with-luajit && make && make install

当然,ngx_lua 也可以手动的编译到 Nginx 中:

  1. 安装LuaJIT 2.0 或 2.1 (推荐) 或 Lua 5.1 (Lua 5.2 暂时还 不支持 )。 LuaJIT可从 The LuaJIT project 站点 获取, Lua 5.1可从 Lua project 站点 获取。
  2. 下载最新版本的 ngx_devel_kit (NDK)开发模块 这里
  3. 下载最新版本的 ngx_lua 这里
  4. 下载最新版本的 Nginx 这里 (查看 Nginx 兼容列表)。

源码编译本模块:

 wget 'http://nginx.org/download/nginx-1.11.2.tar.gz'
 tar -xzvf nginx-1.11.2.tar.gz
 cd nginx-1.11.2/

 # tell nginx's build system where to find LuaJIT 2.0:
 export LUAJIT_LIB=/path/to/luajit/lib
 export LUAJIT_INC=/path/to/luajit/include/luajit-2.0

 # tell nginx's build system where to find LuaJIT 2.1:
 export LUAJIT_LIB=/path/to/luajit/lib
 export LUAJIT_INC=/path/to/luajit/include/luajit-2.1

 # or tell where to find Lua if using Lua instead:
 #export LUA_LIB=/path/to/lua/lib
 #export LUA_INC=/path/to/lua/include

 # Here we assume Nginx is to be installed under /opt/nginx/.
 ./configure --prefix=/opt/nginx \
         --with-ld-opt="-Wl,-rpath,/path/to/luajit-or-lua/lib" \
         --add-module=/path/to/ngx_devel_kit \
         --add-module=/path/to/lua-nginx-module

 make -j2
 make install

返回目录

Building as a dynamic module

从 NGINX 1.9.11 开始,你也能编译动态模块了,使用 --add-dynamic-module=PATH 选项替代 ./configure 命令行的 --add-module=PATH 。然后就能在 nginx.conf 配置中通过 load_module 指令完成模块加载,例如:

load_module /path/to/modules/ndk_http_module.so;  # assuming NDK is built as a dynamic module too
load_module /path/to/modules/ngx_http_lua_module.so;

返回目录

C Macro Configurations

通过 OpenResty 或者 Nginx 内核方式构建该模块,你可以定义下面的 C 宏定义作为可选项提供给 C 编译器:

  • NGX_LUA_USE_ASSERT 声明后,将在ngx_lua C代码中开启断言。推荐用在调试或者测试版本中。启用后,它会引入额外一些(小的)运行时开销。在 v0.9.10 版本中首次引入此选项。

  • NGX_LUA_ABORT_AT_PANIC 当 Lua/LuaJIT 虚拟机出现 panic 错误时,ngx_lua 默认会让当前的工作进程优雅退出。通过指定这个宏定义,ngx_lua 将立即终止当前的 Nginx 工作进程(通常会生成一个core dump文件)。这个选项主要用来调试虚拟机的 panic 错误。在 v0.9.8 版本中首次引入此选项。

  • NGX_LUA_NO_FFI_API 去除 Nginx 中 FFI-based Lua API 需要的的纯 C 函数(例如 lua-resty-core 所需要的)。开启这个宏可以让 Nginx 二进制代码更小。

在 Nginx 或者 OpenResty 启用一个或多个宏定义,只用传给./configure脚本几个额外的C编译选项。例如:

#    ./configure --with-cc-opt="-DNGX_LUA_USE_ASSERT -DNGX_LUA_ABORT_AT_PANIC"

返回目录

Installation on Ubuntu 11.10

注意:这里推荐使用 LuaJIT 2.1 或 LuaJIT 2.0 替换掉标准 Lua 5.1 解释器。

如果不得不使用标准的 Lua 5.1 解释器,在 Ubuntu 上使用这个命令完成安装:

 apt-get install -y lua5.1 liblua5.1-0 liblua5.1-0-dev

应该可以正确被安装,除了一个小 "麻烦":

liblua.so库在 liblua5.1 包中已经发生改变,只能使用liblua5.1.so,并且需要被链接到/usr/lib,这样才可以在 configure 执行阶段被找到。

 ln -s /usr/lib/x86_64-linux-gnu/liblua5.1.so /usr/lib/liblua.so

返回目录

Community

英文邮件列表

英文邮件列表: openresty-en

返回目录

中文邮件列表

中文邮件列表: openresty

返回目录

Code Repository

本项目代码放在github上 openresty/lua-nginx-module

返回目录

Bugs and Patches

提交bug报告、想法或补丁,可通过下面方式:

  1. 创建一个ticket GitHub Issue Tracker
  2. 或者发到这里 OpenResty 社区.

返回目录

Lua/LuaJIT 字节码 support

v0.5.0rc32 release 开始,所有 *_by_lua_file 的配置指令(比如 content_by_lua_file) 都支持直接加载 Lua 5.1 和 LuaJIT 2.0/2.1 的二进制字节码文件。

请注意,LuaJIT 2.0/2.1 生成的二进制格式与标准 Lua 5.1 解析器是不兼容的。 所以如果在 ngx_lua 下使用 LuaJIT 2.0/2.1,那么 LuaJIT 兼容的二进制文件必须是下面这样生成的:

 /path/to/luajit/bin/luajit -b /path/to/input_file.lua /path/to/output_file.luac

-bg 选项是在 LuaJIT 字节码文件中包含调试信息。

 /path/to/luajit/bin/luajit -bg /path/to/input_file.lua /path/to/output_file.luac

对于-b 选项,请参考官方 LuaJIT 文档获取更多细节:

http://luajit.org/running.html#opt_b

同样的,由 LuaJIT 2.1 生成的字节码文件对于 LuaJIT 2.0 也是 兼容的,反之亦然。 第一次对于 LuaJIT 2.1 版本的字节支持,是在 v0.9.3 上完成的。

近似的,如果使用标准 Lua 5.1 解释器,Lua 的兼容字节码文件必须用 luac 命令行来生成:

 luac -o /path/to/output_file.luac /path/to/input_file.lua

与 LuaJIT 不太一样, Lua 5.1 的字节码文件是默认包含调试信息的。这里可以使用 -s 选项去掉调试信息:

 luac -s -o /path/to/output_file.luac /path/to/input_file.lua

在使用 LuaJIT 2.0/2.1 的 ngx_lua 实例中试图加载标准 Lua 5.1 字节码文件(反之亦然),将会在 error.log 中记录一条类似的错误信息:

[error] 13909#0: *1 failed to load Lua inlined code: bad byte-code header in /path/to/test_file.luac

使用 Lua requiredofile 这类原语加载字节码文件,应该总能按照预期工作。

返回目录

System Environment Variable Support

如果你想在 Lua 中通过标准 Lua API os.getenv 来访问系统环境变量,例如foo, 那么你需要在你的 nginx.conf 中,通过 env 指令,把这个环境变量列出来。 例如:

 env foo;

返回目录

HTTP 1.0 support

HTTP 1.0 协议不支持分块输出,当响应体不为空时,需要在响应头中明确指定 Content-Length,以支持 HTTP 1.0 长连接。所以当一个 HTTP 1.0 请求发生,同时把 lua_http10_buffering指令设置为 on 时,ngx_lua 将缓存 ngx.sayngx.print 的所有输出,同时延迟发送响应头直到接收到所有输出内容。这时 ngx_lua 可以计算响应体的总长度且构建一个正确的 Content-Length 响应头返回给 HTTP 1.0 客户端。即使已经将 lua_http10_buffering 指令设置为 on,但如果正在执行的 Lua 代码中设置了 Content-Length 响应头,这种缓冲模式也将被禁用。

对于大型流式响应输出,禁用 lua_http10_buffering 以最小化内存占用非常重要。

请注意,一些常见的 HTTP 性能测试工具,例如 abhttp_load 默认发送 HTTP 1.0 请求。要强制 curl 发送 HTTP 1.0 请求,使用 -0 选项。

返回目录

Statically Linking Pure Lua Modules

当使用 LuaJIT 2.x 时,可以把一个纯 Lua 字节码模块,静态链接到可运行的 Nginx 中。

首先你要使用 LuaJIT 的可执行程序, 把 .lua 的 Lua 模块编译成 .o 的目标文件(包含导出的字节码数据), 然后链接这些 .o 文件到你的 Nginx 构造环境。

用下面这个小例子来印证一下。这里我们的 .lua 文件使用 foo.lua

 -- foo.lua
 local _M = {}

 function _M.go()
     print("Hello from foo")
 end

 return _M

我们把 .lua 文件编译成 foo.o 文件:

/path/to/luajit/bin/luajit -bg foo.lua foo.o

这里重要的是 .lua 文件名, 它决定了这个模块在业务 Lua 中是如何使用的。 文件名 foo.o 除了扩展名是 .o 以外其他都不重要(只是用来告诉 LuaJIT 使用什么格式输出)。 如果你想从输出的字节码中去掉 Lua 调试信息, 你可以用 -b 选项替代原有的 -bg

然后在构建 Nginx 或者 OpenResty时, 传给 ./configure 脚本 --with-ld-opt="foo.o" 选项:

 ./configure --with-ld-opt="/path/to/foo.o" ...

最后,你可以在运行在 ngx_lua 中的任意 Lua 代码中调用:

 local foo = require "foo"
 foo.go()

并且,这段代码再也不会依赖外部的 foo.lua 文件, 因为它已经被编译到了 nginx 程序中。

在调用require时, 如果你想 Lua 模块名中使用点号,如下所示:

 local foo = require "resty.foo"

那么在你使用 LuaJIT 命令行工具把他编译成 .o 文件之前, 你需要把 foo.lua 文件重命名为 resty_foo.lua

.lua 文件编译成 .o 文件,和构建 Nginx + ngx_lua, 这两个过程中,使用完全相同版本的 LuaJIT 是至关重要的。

这是因为 LuaJIT 字节码格式在不同版本之间可能是不兼容的。 当字节码文件出现了不兼容情况,你将看到一行 Lua 运行时错误信息:没找到 Lua 模块。

当你拥有多个 .lua 文件需要链接, 你可以一次指明所有的 .o 文件,并赋给 --with-ld-opt 选项,参考:

 ./configure --with-ld-opt="/path/to/foo.o /path/to/bar.o" ...

如果你有非常多的 .o 文件,把这些文件的名字都写到命令行中不太可行, 这种情况下,对你的 .o 文件可以构建一个静态库(或者归档),参考:

 ar rcus libmyluafiles.a *.o

然后你就可以把 myluafiles 链接到你的 nginx 可执行程序中:

 ./configure \
     --with-ld-opt="-L/path/to/lib -Wl,--whole-archive -lmyluafiles -Wl,--no-whole-archive"

/path/to/lib 目录中应包含 libmyluafiles.a 文件。 应当指出的是,这里要添加链接选项 --whole-archive, 否则我们的归档将被跳过,因为在我们的归档没有导出 nginx 执行需要的函数符号。

返回目录

Data Sharing within an Nginx Worker

在同一个 nginx worker 进程处理的所有请求中共享数据, 需要将共享数据封装进一个 Lua 模块中,并使用 Lua 语言内置的 require 方法加载该模块, 之后就可以在 Lua 中操作共享数据了。 这种方法之所以可行,是因为(在同一个nginx worker中)加载模块的操作仅被执行一次, 所有的协程都会共享同一份拷贝(包括代码和数据)。 但是请注意,Lua的全局变量(注意,不是模块级变量)将因为“每个请求一个协程”的隔离要求而不被保持。

下面是一个完整的例子:

 -- mydata.lua
 local _M = {}

 local data = {
     dog = 3,
     cat = 4,
     pig = 5,
 }

 function _M.get_age(name)
     return data[name]
 end

 return _M

然后通过 nginx.conf 访问:

 location /lua {
     content_by_lua_block {
         local mydata = require "mydata"
         ngx.say(mydata.get_age("dog"))
     }
 }

例子中的 mydata 模块将只在第一个请求到达 /lua 的时候被加载运行, 之后同一个nginx worker进程处理的所有请求,都将使用此模块已经加载的实例,并共享实例中的数据, 直到 nginx 主进程接到 HUP 信号强制重新进行加载。 这种数据共享技术是基于本模块(ngx_lua)的高性能 Lua 应用的基础。

注意,这种数据共享方式是基于worker而不是基于服务器的。 也就是说,当 Nginx 主进程下面有多个 worker 进程时,数据共享不能跨越这些 worker 之间的进程边界。

一般来说,仅推荐使用这种方式共享只读数据。 当计算过程中没有非阻塞性 I/O 操作时(包括 ngx.sleep), 你也可以在 nginx worker 进程内所有并发请求中共享可改变的数据。 只要你不把控制权交还给 nginx 事件循环以及 ngx_lua 的轻量级线程调度器(包括隐含的),它们之间就不会有任何竞争。 因此,当你决定在 worker 中共享可变数据时,一定要非常小心。 错误的优化经常会导致在高负载时产生竞争,这种 bug 非常难以发现。

如果需要在服务器级别共享数据,请使用以下方法:

  1. 使用本模块提供的 ngx.shared.DICT API
  2. 使用单服务器单 nginx worker 进程(当使用多CPU或者多核CPU的服务器时不推荐)
  3. 使用类似 memcached, redis, MySQLPostgreSQL 等数据共享机制。 与本模块相关的 OpenResty 软件包包含了一系列相关的 Nginx 模块以及 Lua 库, 提供与这些数据存储机制的交互界面。

返回目录

Known Issues

返回目录

TCP socket 连接操作遗留问题

tcpsock:connect方法,返回 success 但实际上连接故障,例如出现 Connection Refused 错误。

然而,后面尝试对 cosocket 对象的任何操作都将失败,并返回由失效连接操作所产生实际的错误状态消息。

这个问题是由于在 Nginx 的事件模型的局限性,似乎只影响 Mac OS X 系统。

返回目录

Lua 协程 Yielding/Resuming

无论Lua 5.1 and LuaJIT 2.0/2.1,内建的 dofilerequire 当前都是通过绑定 C 函数的方式,如果Lua文件的加载是dofilerequire 并调用 ngx.location.capture*, ngx.exec, ngx.exit, 或者 Lua 中其他 API 函数的 top-level 范围调用 yielding ,均会得到 "attempt to yield across C-call boundary" 的错误信息。为了避免这个情况,把需要调用 yielding 部分放到你自己的 Lua 函数中,这样在当前文件就不再是 top-level 范围。

对于标准 Lua 5.1 解析器的虚拟机唤醒支持是不完善的,ngx.location.capture, ngx.location.capture_multi, ngx.redirect, ngx.execngx.exit 方法,在 Lua pcall()xpcall() 中是不能使用的。甚至 for ... in ... 小节的第一行在标准 Lua 5.1解析器中都会报错 attempt to yield across metamethod/C-call boundary。 请使用 LuaJIT 2.x,它可以完美支持虚拟机唤醒,避免这些问题。

返回目录

Lua Variable Scope

在代码中导入模块时应注意一些细节,推介使用如下格式:

 local xxx = require('xxx')

而非:

require('xxx')

理由如下:从设计上讲,全局环境的生命周期和一个 Nginx 的请求的生命周期是相同的。为了做到请求隔离,每个请求都有自己的Lua全局变量环境。Lua 模块在第一次请求打到服务器上的时候被加载起来,通过package.loaded表内建的require()完成缓存,为后续代码复用。并且一些 Lua 模块内的module()存在边际问题,对加载完成的模块设置成全局表变量,但是这个全局变量在请求处理最后将被清空,并且每个后续请求都拥有自己(干净)的全局空间。所以它将因为访问nil值收到Lua异常。

一般来说,在 ngx_lua 的上下文中使用 Lua 全局变量真的不是什么好主意:

  1. 滥用全局变量的副作用会对并发场景产生副作用,比如当使用者把这些变量看作是本地变量的时候;
  2. Lua的全局变量需要向上查找一个全局环境(只是一个Lua表),代价比较高;
  3. 一些Lua的全局变量引用只是拼写错误,这会导致出错很难排查。

所以,我们极力推介在使用变量的时候总是使用 local 来定义以限定起生效范围是有理由的。

为了在你的 Lua 代码中找出所有使用 Lua 全局变量的地方,你可以运行 lua-releng tool 把所有 .lua 源文件检测一遍:

$ lua-releng
Checking use of Lua global variables in file lib/foo/bar.lua ...
    1       [1489]  SETGLOBAL       7 -1    ; contains
    55      [1506]  GETGLOBAL       7 -3    ; setvar
    3       [1545]  GETGLOBAL       3 -4    ; varexpand

上述输出说明文件lib/foo/bar.lua的 1489 行写入一个名为contains的全局变量,1506 行读取一个名为setvar的全局变量,1545 行读取一个名为varexpand的全局变量,

这个工具能保证 Lua 模块中的局部变量全部是用 local 关键字定义过的,否则将会抛出一个运行时异常。这样能阻止类似变量这样的资源的竞争。理由请参考 Data Sharing within


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap