├── README.md ├── _example ├── Dockerfile └── nginx │ ├── conf │ ├── nginx.conf │ └── root-certs.pem │ ├── html │ ├── error.html │ └── org-members.html │ └── lualib │ ├── example │ └── gh.lua │ └── resty │ ├── core.lua │ ├── core │ ├── base.lua │ ├── base64.lua │ ├── ctx.lua │ ├── exit.lua │ ├── hash.lua │ ├── misc.lua │ ├── regex.lua │ ├── request.lua │ ├── response.lua │ ├── shdict.lua │ ├── time.lua │ ├── uri.lua │ ├── var.lua │ └── worker.lua │ ├── http.lua │ ├── http_headers.lua │ ├── shcache.lua │ └── template.lua ├── alpine └── Dockerfile └── debian └── Dockerfile /README.md: -------------------------------------------------------------------------------- 1 | # OpenResty Docker image 2 | 3 | This repository contains Dockerfiles for [ficusio/openresty](https://hub.docker.com/r/ficusio/openresty/) image, which has two flavors. 4 | 5 | ### Flavors 6 | 7 | The main one is [Alpine linux](https://hub.docker.com/_/alpine/)-based `ficusio/openresty:latest`. Its virtual size is just 31MB, yet it contains a fully functional [OpenResty](http://openresty.org) bundle v1.9.3.1 and [`apk` package manager](http://wiki.alpinelinux.org/wiki/Alpine_Linux_package_management), which allows you to easily install [lots of pre-built packages](https://pkgs.alpinelinux.org/packages). 8 | 9 | The other flavor is `ficusio/openresty:debian`. It is based on `debian:wheezy` and thus is much bigger in size (256MB). It is mostly useful for NginX profiling, as it may not be easy to build different profiling tools with [musl libc](http://www.musl-libc.org/), which is used in Alpine Linux. 10 | 11 | ### Paths & config 12 | 13 | NginX is configured with `/opt/openresty/nginx` [prefix path](http://nginx.org/en/docs/configure.html), which means that, by default, it loads configuration from `/opt/openresty/nginx/conf/nginx.conf` file. The default HTML root path is `/opt/openresty/nginx/html/`. 14 | 15 | OpenResty bundle includes several useful Lua modules located in `/opt/openresty/lualib/` directory. This directory is already present in Lua package path, so you don't need to specify it in NginX `lua_package_path` directive. 16 | 17 | The Lua NginX module is built with LuaJIT 2.1, which is also available as stand-alone `lua` binary. 18 | 19 | NginX stores various temporary files in `/var/nginx/` directory. If you wish to launch the container in [read-only mode](https://github.com/docker/docker/pull/10093), you need to convert that directory into volume to make it writable: 20 | 21 | ```sh 22 | # To launch container 23 | docker run --name nginx --read-only -v /var/nginx ... ficusio/openresty 24 | 25 | # To remove container and its volume 26 | docker rm -v nginx 27 | ``` 28 | 29 | See [this PR](https://github.com/ficusio/openresty/pull/7) for background. 30 | 31 | ### `ONBUILD` hook 32 | 33 | This image uses [`ONBUILD` hook](http://docs.docker.com/engine/reference/builder/#onbuild) that automatically copies all files and subdirectories from the `nginx/` directory located at the root of Docker build context (i.e. next to your `Dockerfile`) into `/opt/openresty/nginx/`. The minimal configuration needed to get NginX running is the following: 34 | 35 | ```coffee 36 | project_root/ 37 | ├ nginx/ # all subdirs/files will be copied to /opt/openresty/nginx/ 38 | | └ conf/ 39 | | └ nginx.conf # your NginX configuration file 40 | └ Dockerfile 41 | ``` 42 | 43 | Dockerfile: 44 | 45 | ```dockerfile 46 | FROM ficusio/openresty:latest 47 | EXPOSE 8080 48 | ``` 49 | 50 | Check [the sample application](https://github.com/ficusio/openresty/tree/master/_example) for more useful example. 51 | 52 | ### Command-line parameters 53 | 54 | NginX is launched with the `nginx -g 'daemon off; error_log /dev/stderr info;'` command. This means that you should not specify the `daemon` directive in your `nginx.conf` file, because it will lead to NginX config check error (duplicate directive). 55 | 56 | No-daemon mode is needed to allow host OS' service manager, like `systemd`, or [Docker itself](http://docs.docker.com/engine/reference/commandline/cli/#restart-policies) to detect that NginX has exited and restart the container. Otherwise in-container service manager would be required. 57 | 58 | Error log is redirected to `stderr` to simplify debugging and log collection with [Docker logging drivers](https://docs.docker.com/engine/reference/logging/overview/) or tools like [logspout](https://github.com/gliderlabs/logspout). 59 | 60 | If you wish to run it with different command-line options, you can add `CMD` directive to your Dockerfile. It will override the command provided in this image. Another option is to pass a command to `docker run` directly: 61 | 62 | ```text 63 | $ docker run --rm -it --name test ficusio/openresty bash 64 | root@06823698db68:/opt/openresty/nginx $ ls -l 65 | total 12 66 | drwxr-xr-x 2 root root 4096 Feb 1 14:48 conf 67 | drwxr-xr-x 2 root root 4096 Feb 1 14:48 html 68 | drwxr-xr-x 2 root root 4096 Feb 1 14:48 sbin 69 | ``` 70 | 71 | ### Usage during development 72 | 73 | To avoid rebuilding your Docker image after each modification of Lua code or NginX config, you can add a simple script that mounts config/content directories to appropriate locations and starts NginX: 74 | 75 | ```bash 76 | #!/usr/bin/env bash 77 | 78 | exec docker run --rm -it \ 79 | --name my-app-dev \ 80 | -v "$(pwd)/nginx/conf":/opt/openresty/nginx/conf \ 81 | -v "$(pwd)/nginx/lualib":/opt/openresty/nginx/lualib \ 82 | -p 8080:8080 \ 83 | ficusio/openresty:debian "$@" 84 | 85 | # you may add more -v options to mount another directories, e.g. nginx/html/ 86 | 87 | # do not do -v "$(pwd)/nginx":/opt/openresty/nginx because it will hide 88 | # the NginX binary located at /opt/openresty/nginx/sbin/nginx 89 | ``` 90 | 91 | Place it next to your `Dockerfile`, make executable and use during development. You may also want to temporarily disable [Lua code cache](https://github.com/openresty/lua-nginx-module#lua_code_cache) to allow testing code modifications without re-starting NginX. 92 | -------------------------------------------------------------------------------- /_example/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ficusio/openresty:latest 2 | EXPOSE 8080 -------------------------------------------------------------------------------- /_example/nginx/conf/nginx.conf: -------------------------------------------------------------------------------- 1 | worker_processes 2; 2 | 3 | events { 4 | worker_connections 1024; 5 | } 6 | 7 | http { 8 | access_log off; 9 | 10 | keepalive_timeout 65; 11 | resolver 8.8.8.8; 12 | 13 | lua_ssl_trusted_certificate "root-certs.pem"; 14 | lua_ssl_verify_depth 2; 15 | lua_package_path "$prefix/lualib/?.lua;;"; 16 | 17 | lua_shared_dict locks 1M; 18 | lua_shared_dict cache 10M; 19 | 20 | # see https://github.com/openresty/lua-resty-core 21 | init_by_lua ' 22 | require "resty.core" 23 | '; 24 | 25 | server { 26 | listen 8080; 27 | default_type text/html; 28 | 29 | location = / { 30 | content_by_lua ' 31 | ngx.say "Hello from Lua-land! Try this link :)" 32 | '; 33 | } 34 | 35 | # /show-org/orgname[?nocache] - show GitHib organization members 36 | # 37 | location ~* ^/show-org/([\w\d]+)/?$ { 38 | content_by_lua ' 39 | local orgname = ngx.var[1] 40 | local nocache = ngx.req.get_uri_args().nocache ~= nil 41 | local gh, template = require "example.gh", require "resty.template" 42 | local org, err, status 43 | if nocache then 44 | org, err, status = gh.getOrg(orgname) 45 | else 46 | org, err, status = gh.getOrgCached(orgname) 47 | end 48 | if err then 49 | ngx.status = status or 500 50 | template.render("error.html", { message = err }) 51 | else 52 | template.render("org-members.html", { org = org }) 53 | end 54 | '; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /_example/nginx/html/error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Sorry 5 | 6 | 7 |

Error: {{message}}.

8 | 9 | -------------------------------------------------------------------------------- /_example/nginx/html/org-members.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Public members of {{org.name}} organization 5 | 40 | 41 | 42 |

Public members of {{org.name}} organization:

43 | {% for _, member in pairs(org.members) do %} 44 | 45 | {{member.login}} 46 | 47 | {% end %} 48 | 49 | -------------------------------------------------------------------------------- /_example/nginx/lualib/example/gh.lua: -------------------------------------------------------------------------------- 1 | local http = require "resty.http" 2 | local json = require "cjson.safe" 3 | local shcache = require "resty.shcache" 4 | local spawn = ngx.thread.spawn 5 | local wait = ngx.thread.wait 6 | 7 | 8 | local _M = {} 9 | 10 | 11 | local function requestJSON(uri) 12 | local httpc = http.new() 13 | local res, err = httpc:request_uri(uri) 14 | if err or not res then 15 | return nil, "failed to send request: " .. (err or "unknown error") 16 | end 17 | if res.status ~= 200 then 18 | local result, err = json.decode(res.body) 19 | local msg = not err and result.message or ("unexpected status " .. res.status) 20 | return nil, msg, res.status 21 | end 22 | local result, err = json.decode(res.body) 23 | if err then 24 | return nil, "cannot parse response: " .. err 25 | end 26 | return result, nil, res.status 27 | end 28 | 29 | 30 | function _M.getOrgInfo(orgname) 31 | return requestJSON("https://api.github.com/orgs/" .. orgname) 32 | end 33 | 34 | 35 | function _M.getOrgMembers(orgname) 36 | return requestJSON("https://api.github.com/orgs/" .. orgname .. "/members") 37 | end 38 | 39 | 40 | function _M.getOrg(orgname) 41 | -- perform both requests in parallel 42 | local tInfo = spawn(_M.getOrgInfo, orgname) 43 | local tMembers = spawn(_M.getOrgMembers, orgname) 44 | local ok, info, err, status = wait(tInfo) 45 | if not ok or err then 46 | return nil, err or "terminated", status 47 | end 48 | local ok, members, err, status = wait(tMembers) 49 | if not ok or err then 50 | return nil, err or "terminated", status 51 | end 52 | info.members = members 53 | return info, nil, status 54 | end 55 | 56 | 57 | function _M.getOrgCached(orgname) 58 | local cache, err = shcache:new(ngx.shared.cache, { 59 | external_lookup = _M._orgLookup, 60 | external_lookup_arg = orgname, 61 | encode = json.encode, 62 | decode = json.decode 63 | }, { 64 | positive_ttl = 15, 65 | negative_ttl = 5, 66 | locks_shdict = "locks", 67 | name = "orgs" 68 | }) 69 | if err or not cache then 70 | return nil, "cannot create cache: " .. (err or "error not provided") 71 | end 72 | local data = cache:load(orgname) 73 | if not data then 74 | return nil, "weird error: nil data" 75 | end 76 | return data.org, data.err, data.status 77 | end 78 | 79 | 80 | function _M._orgLookup(orgname) 81 | local org, err, status = _M.getOrg(orgname) 82 | local ttl = nil 83 | if err then 84 | print("lookup error: " .. err) 85 | ttl = 5 86 | end 87 | return { org = org, err = err, status = status }, nil, ttl 88 | end 89 | 90 | 91 | return _M 92 | -------------------------------------------------------------------------------- /_example/nginx/lualib/resty/core.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (C) Yichun Zhang (agentzh) 2 | 3 | 4 | require "resty.core.uri" 5 | require "resty.core.hash" 6 | require "resty.core.base64" 7 | require "resty.core.regex" 8 | require "resty.core.exit" 9 | require "resty.core.shdict" 10 | require "resty.core.var" 11 | require "resty.core.ctx" 12 | require "resty.core.misc" 13 | require "resty.core.request" 14 | require "resty.core.response" 15 | require "resty.core.time" 16 | require "resty.core.worker" 17 | 18 | 19 | local base = require "resty.core.base" 20 | 21 | 22 | return { 23 | version = base.version 24 | } 25 | -------------------------------------------------------------------------------- /_example/nginx/lualib/resty/core/base.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (C) Yichun Zhang (agentzh) 2 | 3 | 4 | local ffi = require 'ffi' 5 | local ffi_new = ffi.new 6 | local error = error 7 | local setmetatable = setmetatable 8 | local floor = math.floor 9 | local ceil = math.ceil 10 | 11 | 12 | local str_buf_size = 4096 13 | local str_buf 14 | local size_ptr 15 | local FREE_LIST_REF = 0 16 | 17 | 18 | if not ngx.config 19 | or not ngx.config.ngx_lua_version 20 | or ngx.config.ngx_lua_version < 9011 21 | then 22 | error("ngx_lua 0.9.11+ required") 23 | end 24 | 25 | 26 | if string.find(jit.version, " 2.0") then 27 | ngx.log(ngx.WARN, "use of lua-resty-core with LuaJIT 2.0 is " 28 | .. "not recommended; use LuaJIT 2.1+ instead") 29 | end 30 | 31 | 32 | local ok, new_tab = pcall(require, "table.new") 33 | if not ok then 34 | new_tab = function (narr, nrec) return {} end 35 | end 36 | 37 | 38 | local ok, clear_tab = pcall(require, "table.clear") 39 | if not ok then 40 | clear_tab = function (tab) 41 | for k, _ in pairs(tab) do 42 | tab[k] = nil 43 | end 44 | end 45 | end 46 | 47 | 48 | -- XXX for now LuaJIT 2.1 cannot compile require() 49 | -- so we make the fast code path Lua only in our own 50 | -- wrapper so that most of the require() calls in hot 51 | -- Lua code paths can be JIT compiled. 52 | do 53 | local orig_require = require 54 | local pkg_loaded = package.loaded 55 | local function my_require(name) 56 | local mod = pkg_loaded[name] 57 | if mod then 58 | return mod 59 | end 60 | return orig_require(name) 61 | end 62 | getfenv(0).require = my_require 63 | end 64 | 65 | 66 | if not pcall(ffi.typeof, "ngx_str_t") then 67 | ffi.cdef[[ 68 | typedef struct { 69 | size_t len; 70 | const unsigned char *data; 71 | } ngx_str_t; 72 | ]] 73 | end 74 | 75 | 76 | if not pcall(ffi.typeof, "ngx_http_request_t") then 77 | ffi.cdef[[ 78 | struct ngx_http_request_s; 79 | typedef struct ngx_http_request_s ngx_http_request_t; 80 | ]] 81 | end 82 | 83 | 84 | if not pcall(ffi.typeof, "ngx_http_lua_ffi_str_t") then 85 | ffi.cdef[[ 86 | typedef struct { 87 | int len; 88 | const unsigned char *data; 89 | } ngx_http_lua_ffi_str_t; 90 | ]] 91 | end 92 | 93 | 94 | local c_buf_type = ffi.typeof("char[?]") 95 | 96 | 97 | local _M = new_tab(0, 16) 98 | 99 | 100 | _M.version = "0.1.0" 101 | _M.new_tab = new_tab 102 | _M.clear_tab = clear_tab 103 | 104 | 105 | local errmsg 106 | 107 | 108 | function _M.get_errmsg_ptr() 109 | if not errmsg then 110 | errmsg = ffi_new("char *[1]") 111 | end 112 | return errmsg 113 | end 114 | 115 | 116 | if not ngx then 117 | return error("no existing ngx. table found") 118 | end 119 | 120 | 121 | function _M.set_string_buf_size(size) 122 | if size <= 0 then 123 | return 124 | end 125 | if str_buf then 126 | str_buf = nil 127 | end 128 | str_buf_size = ceil(size) 129 | end 130 | 131 | 132 | function _M.get_string_buf_size() 133 | return str_buf_size 134 | end 135 | 136 | 137 | function _M.get_size_ptr() 138 | if not size_ptr then 139 | size_ptr = ffi_new("size_t[1]") 140 | end 141 | 142 | return size_ptr 143 | end 144 | 145 | 146 | function _M.get_string_buf(size, must_alloc) 147 | -- ngx.log(ngx.ERR, "str buf size: ", str_buf_size) 148 | if size > str_buf_size or must_alloc then 149 | return ffi_new(c_buf_type, size) 150 | end 151 | 152 | if not str_buf then 153 | str_buf = ffi_new(c_buf_type, str_buf_size) 154 | end 155 | 156 | return str_buf 157 | end 158 | 159 | 160 | function _M.ref_in_table(tb, key) 161 | if key == nil then 162 | return -1 163 | end 164 | local ref = tb[FREE_LIST_REF] 165 | if ref and ref ~= 0 then 166 | tb[FREE_LIST_REF] = tb[ref] 167 | 168 | else 169 | ref = #tb + 1 170 | end 171 | tb[ref] = key 172 | 173 | -- print("ref key_id returned ", ref) 174 | return ref 175 | end 176 | 177 | 178 | _M.FFI_OK = 0 179 | _M.FFI_NO_REQ_CTX = -100 180 | _M.FFI_BAD_CONTEXT = -101 181 | _M.FFI_ERROR = -1 182 | _M.FFI_BUSY = -3 183 | _M.FFI_DONE = -4 184 | _M.FFI_DECLINED = -5 185 | 186 | 187 | return _M 188 | -------------------------------------------------------------------------------- /_example/nginx/lualib/resty/core/base64.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (C) Yichun Zhang (agentzh) 2 | 3 | 4 | local ffi = require 'ffi' 5 | local base = require "resty.core.base" 6 | 7 | local ffi_string = ffi.string 8 | local ffi_new = ffi.new 9 | local C = ffi.C 10 | local setmetatable = setmetatable 11 | local ngx = ngx 12 | local type = type 13 | local tostring = tostring 14 | local error = error 15 | local get_string_buf = base.get_string_buf 16 | local get_size_ptr = base.get_size_ptr 17 | local floor = math.floor 18 | local print = print 19 | local tonumber = tonumber 20 | 21 | 22 | ffi.cdef[[ 23 | size_t ngx_http_lua_ffi_encode_base64(const unsigned char *src, 24 | size_t len, unsigned char *dst); 25 | 26 | int ngx_http_lua_ffi_decode_base64(const unsigned char *src, 27 | size_t len, unsigned char *dst, 28 | size_t *dlen); 29 | ]] 30 | 31 | 32 | local function base64_encoded_length(len) 33 | return floor((len + 2) / 3) * 4 34 | end 35 | 36 | 37 | ngx.encode_base64 = function (s) 38 | if type(s) ~= 'string' then 39 | if not s then 40 | s = '' 41 | else 42 | s = tostring(s) 43 | end 44 | end 45 | local slen = #s 46 | local dlen = base64_encoded_length(slen) 47 | -- print("dlen: ", tonumber(dlen)) 48 | local dst = get_string_buf(dlen) 49 | dlen = C.ngx_http_lua_ffi_encode_base64(s, slen, dst) 50 | return ffi_string(dst, dlen) 51 | end 52 | 53 | 54 | local function base64_decoded_length(len) 55 | return floor((len + 3) / 4) * 3 56 | end 57 | 58 | 59 | ngx.decode_base64 = function (s) 60 | if type(s) ~= 'string' then 61 | return error("string argument only") 62 | end 63 | local slen = #s 64 | local dlen = base64_decoded_length(slen) 65 | -- print("dlen: ", tonumber(dlen)) 66 | local dst = get_string_buf(dlen) 67 | local pdlen = get_size_ptr() 68 | local ok = C.ngx_http_lua_ffi_decode_base64(s, slen, dst, pdlen) 69 | if ok == 0 then 70 | return nil 71 | end 72 | return ffi_string(dst, pdlen[0]) 73 | end 74 | 75 | 76 | return { 77 | version = base.version 78 | } 79 | -------------------------------------------------------------------------------- /_example/nginx/lualib/resty/core/ctx.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (C) Yichun Zhang (agentzh) 2 | 3 | 4 | local ffi = require 'ffi' 5 | local debug = require 'debug' 6 | local base = require "resty.core.base" 7 | local misc = require "resty.core.misc" 8 | 9 | 10 | local register_getter = misc.register_ngx_magic_key_getter 11 | local register_setter = misc.register_ngx_magic_key_setter 12 | local registry = debug.getregistry() 13 | local new_tab = base.new_tab 14 | local ref_in_table = base.ref_in_table 15 | local getfenv = getfenv 16 | local C = ffi.C 17 | local FFI_NO_REQ_CTX = base.FFI_NO_REQ_CTX 18 | local FFI_OK = base.FFI_OK 19 | 20 | 21 | ffi.cdef[[ 22 | int ngx_http_lua_ffi_get_ctx_ref(ngx_http_request_t *r); 23 | int ngx_http_lua_ffi_set_ctx_ref(ngx_http_request_t *r, int ref); 24 | ]] 25 | 26 | 27 | local _M = { 28 | _VERSION = base.version 29 | } 30 | 31 | 32 | local function get_ctx_table() 33 | local r = getfenv(0).__ngx_req 34 | 35 | if not r then 36 | return error("no request found") 37 | end 38 | 39 | local ctx_ref = C.ngx_http_lua_ffi_get_ctx_ref(r) 40 | if ctx_ref == FFI_NO_REQ_CTX then 41 | return error("no request ctx found") 42 | end 43 | 44 | local ctxs = registry.ngx_lua_ctx_tables 45 | if ctx_ref < 0 then 46 | local ctx = new_tab(0, 4) 47 | ctx_ref = ref_in_table(ctxs, ctx) 48 | if C.ngx_http_lua_ffi_set_ctx_ref(r, ctx_ref) ~= FFI_OK then 49 | return nil 50 | end 51 | return ctx 52 | end 53 | return ctxs[ctx_ref] 54 | end 55 | register_getter("ctx", get_ctx_table) 56 | 57 | 58 | local function set_ctx_table(ctx) 59 | local r = getfenv(0).__ngx_req 60 | 61 | if not r then 62 | return error("no request found") 63 | end 64 | 65 | local ctx_ref = C.ngx_http_lua_ffi_get_ctx_ref(r) 66 | if ctx_ref == FFI_NO_REQ_CTX then 67 | return error("no request ctx found") 68 | end 69 | 70 | local ctxs = registry.ngx_lua_ctx_tables 71 | if ctx_ref < 0 then 72 | ctx_ref = ref_in_table(ctxs, ctx) 73 | C.ngx_http_lua_ffi_set_ctx_ref(r, ctx_ref) 74 | return 75 | end 76 | ctxs[ctx_ref] = ctx 77 | end 78 | register_setter("ctx", set_ctx_table) 79 | 80 | 81 | return _M 82 | -------------------------------------------------------------------------------- /_example/nginx/lualib/resty/core/exit.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (C) Yichun Zhang (agentzh) 2 | 3 | 4 | local ffi = require 'ffi' 5 | local ffi_string = ffi.string 6 | local C = ffi.C 7 | local ngx = ngx 8 | local tostring = tostring 9 | local error = error 10 | local base = require "resty.core.base" 11 | local get_string_buf = base.get_string_buf 12 | local get_size_ptr = base.get_size_ptr 13 | local base = require "resty.core.base" 14 | local getfenv = getfenv 15 | local co_yield = coroutine._yield 16 | 17 | 18 | ffi.cdef[[ 19 | int ngx_http_lua_ffi_exit(ngx_http_request_t *r, int status, 20 | unsigned char *err, size_t *errlen); 21 | ]] 22 | 23 | 24 | local ERR_BUF_SIZE = 128 25 | local FFI_DONE = base.FFI_DONE 26 | 27 | 28 | ngx.exit = function (rc) 29 | local err = get_string_buf(ERR_BUF_SIZE) 30 | local errlen = get_size_ptr() 31 | local r = getfenv(0).__ngx_req 32 | if r == nil then 33 | return error("no request found") 34 | end 35 | errlen[0] = ERR_BUF_SIZE 36 | local rc = C.ngx_http_lua_ffi_exit(r, rc, err, errlen) 37 | if rc == 0 then 38 | -- print("yielding...") 39 | return co_yield() 40 | end 41 | if rc == FFI_DONE then 42 | return 43 | end 44 | return error(ffi_string(err, errlen[0])) 45 | end 46 | 47 | 48 | return { 49 | version = base.version 50 | } 51 | -------------------------------------------------------------------------------- /_example/nginx/lualib/resty/core/hash.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (C) Yichun Zhang (agentzh) 2 | 3 | 4 | local ffi = require 'ffi' 5 | local ffi_string = ffi.string 6 | local ffi_new = ffi.new 7 | local C = ffi.C 8 | local setmetatable = setmetatable 9 | local ngx = ngx 10 | local type = type 11 | local tostring = tostring 12 | local error = error 13 | local base = require "resty.core.base" 14 | 15 | 16 | ffi.cdef[[ 17 | void ngx_http_lua_ffi_md5_bin(const unsigned char *src, size_t len, 18 | unsigned char *dst); 19 | 20 | void ngx_http_lua_ffi_md5(const unsigned char *src, size_t len, 21 | unsigned char *dst); 22 | 23 | int ngx_http_lua_ffi_sha1_bin(const unsigned char *src, size_t len, 24 | unsigned char *dst); 25 | ]] 26 | 27 | 28 | local MD5_DIGEST_LEN = 16 29 | local md5_buf = ffi_new("unsigned char[?]", MD5_DIGEST_LEN) 30 | 31 | ngx.md5_bin = function (s) 32 | if type(s) ~= 'string' then 33 | if not s then 34 | s = '' 35 | else 36 | s = tostring(s) 37 | end 38 | end 39 | C.ngx_http_lua_ffi_md5_bin(s, #s, md5_buf) 40 | return ffi_string(md5_buf, MD5_DIGEST_LEN) 41 | end 42 | 43 | 44 | local MD5_HEX_DIGEST_LEN = MD5_DIGEST_LEN * 2 45 | local md5_hex_buf = ffi_new("unsigned char[?]", MD5_HEX_DIGEST_LEN) 46 | 47 | ngx.md5 = function (s) 48 | if type(s) ~= 'string' then 49 | if not s then 50 | s = '' 51 | else 52 | s = tostring(s) 53 | end 54 | end 55 | C.ngx_http_lua_ffi_md5(s, #s, md5_hex_buf) 56 | return ffi_string(md5_hex_buf, MD5_HEX_DIGEST_LEN) 57 | end 58 | 59 | 60 | local SHA_DIGEST_LEN = 20 61 | local sha_buf = ffi_new("unsigned char[?]", SHA_DIGEST_LEN) 62 | 63 | ngx.sha1_bin = function (s) 64 | if type(s) ~= 'string' then 65 | if not s then 66 | s = '' 67 | else 68 | s = tostring(s) 69 | end 70 | end 71 | local ok = C.ngx_http_lua_ffi_sha1_bin(s, #s, sha_buf) 72 | if ok == 0 then 73 | return error("SHA-1 support missing in Nginx") 74 | end 75 | return ffi_string(sha_buf, SHA_DIGEST_LEN) 76 | end 77 | 78 | 79 | return { 80 | version = base.version 81 | } 82 | -------------------------------------------------------------------------------- /_example/nginx/lualib/resty/core/misc.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (C) Yichun Zhang (agentzh) 2 | 3 | 4 | local base = require "resty.core.base" 5 | local ffi = require "ffi" 6 | 7 | 8 | local FFI_NO_REQ_CTX = base.FFI_NO_REQ_CTX 9 | local FFI_BAD_CONTEXT = base.FFI_BAD_CONTEXT 10 | local new_tab = base.new_tab 11 | local C = ffi.C 12 | local getmetatable = getmetatable 13 | local ngx_magic_key_getters = new_tab(0, 4) 14 | local ngx_magic_key_setters = new_tab(0, 2) 15 | local ngx = ngx 16 | local getfenv = getfenv 17 | local type = type 18 | 19 | 20 | local _M = new_tab(0, 3) 21 | _M._VERSION = base.version 22 | 23 | 24 | local function register_getter(key, func) 25 | ngx_magic_key_getters[key] = func 26 | end 27 | _M.register_ngx_magic_key_getter = register_getter 28 | 29 | 30 | local function register_setter(key, func) 31 | ngx_magic_key_setters[key] = func 32 | end 33 | _M.register_ngx_magic_key_setter = register_setter 34 | 35 | 36 | local mt = getmetatable(ngx) 37 | 38 | 39 | local old_index = mt.__index 40 | mt.__index = function (tb, key) 41 | local f = ngx_magic_key_getters[key] 42 | if f then 43 | return f() 44 | end 45 | return old_index(tb, key) 46 | end 47 | 48 | 49 | local old_newindex = mt.__newindex 50 | mt.__newindex = function (tb, key, ctx) 51 | local f = ngx_magic_key_setters[key] 52 | if f then 53 | return f(ctx) 54 | end 55 | return old_newindex(tb, key, ctx) 56 | end 57 | 58 | 59 | ffi.cdef[[ 60 | int ngx_http_lua_ffi_get_resp_status(ngx_http_request_t *r); 61 | int ngx_http_lua_ffi_set_resp_status(ngx_http_request_t *r, int r); 62 | int ngx_http_lua_ffi_is_subrequest(ngx_http_request_t *r); 63 | int ngx_http_lua_ffi_headers_sent(ngx_http_request_t *r); 64 | ]] 65 | 66 | 67 | -- ngx.status 68 | 69 | local function get_status() 70 | local r = getfenv(0).__ngx_req 71 | 72 | if not r then 73 | return error("no request found") 74 | end 75 | 76 | local rc = C.ngx_http_lua_ffi_get_resp_status(r) 77 | 78 | if rc == FFI_BAD_CONTEXT then 79 | return error("API disabled in the current context") 80 | end 81 | 82 | return rc 83 | end 84 | register_getter("status", get_status) 85 | 86 | 87 | local function set_status(status) 88 | local r = getfenv(0).__ngx_req 89 | 90 | if not r then 91 | return error("no request found") 92 | end 93 | 94 | if type(status) ~= 'number' then 95 | status = tonumber(status) 96 | end 97 | 98 | local rc = C.ngx_http_lua_ffi_set_resp_status(r, status) 99 | 100 | if rc == FFI_BAD_CONTEXT then 101 | return error("API disabled in the current context") 102 | end 103 | 104 | return 105 | end 106 | register_setter("status", set_status) 107 | 108 | 109 | -- ngx.is_subrequest 110 | 111 | local function is_subreq() 112 | local r = getfenv(0).__ngx_req 113 | 114 | if not r then 115 | return error("no request found") 116 | end 117 | 118 | local rc = C.ngx_http_lua_ffi_is_subrequest(r) 119 | 120 | if rc == FFI_BAD_CONTEXT then 121 | return error("API disabled in the current context") 122 | end 123 | 124 | return rc == 1 and true or false 125 | end 126 | register_getter("is_subrequest", is_subreq) 127 | 128 | 129 | -- ngx.headers_sent 130 | 131 | local function headers_sent() 132 | local r = getfenv(0).__ngx_req 133 | 134 | if not r then 135 | return error("no request found") 136 | end 137 | 138 | local rc = C.ngx_http_lua_ffi_headers_sent(r) 139 | 140 | if rc == FFI_NO_REQ_CTX then 141 | return error("no request ctx found") 142 | end 143 | 144 | if rc == FFI_BAD_CONTEXT then 145 | return error("API disabled in the current context") 146 | end 147 | 148 | return rc == 1 149 | end 150 | register_getter("headers_sent", headers_sent) 151 | 152 | 153 | return _M 154 | -------------------------------------------------------------------------------- /_example/nginx/lualib/resty/core/regex.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (C) Yichun Zhang (agentzh) 2 | 3 | 4 | local ffi = require 'ffi' 5 | local base = require "resty.core.base" 6 | local bit = require "bit" 7 | require "resty.core.time" -- for ngx.now used by resty.lrucache 8 | local lrucache = require "resty.lrucache" 9 | 10 | local lrucache_get = lrucache.get 11 | local lrucache_set = lrucache.set 12 | local ffi_string = ffi.string 13 | local ffi_new = ffi.new 14 | local ffi_gc = ffi.gc 15 | local ffi_copy = ffi.copy 16 | local ffi_cast = ffi.cast 17 | local C = ffi.C 18 | local bor = bit.bor 19 | local band = bit.band 20 | local lshift = bit.lshift 21 | local sub = string.sub 22 | local fmt = string.format 23 | local byte = string.byte 24 | local setmetatable = setmetatable 25 | local concat = table.concat 26 | local ngx = ngx 27 | local type = type 28 | local tostring = tostring 29 | local error = error 30 | local get_string_buf = base.get_string_buf 31 | local get_string_buf_size = base.get_string_buf_size 32 | local get_size_ptr = base.get_size_ptr 33 | local new_tab = base.new_tab 34 | local floor = math.floor 35 | local print = print 36 | local tonumber = tonumber 37 | local ngx_log = ngx.log 38 | local ngx_ERR = ngx.ERR 39 | 40 | 41 | if not ngx.re then 42 | ngx.re = {} 43 | end 44 | 45 | 46 | local MAX_ERR_MSG_LEN = 128 47 | 48 | 49 | local FLAG_COMPILE_ONCE = 0x01 50 | local FLAG_DFA = 0x02 51 | local FLAG_JIT = 0x04 52 | local FLAG_DUPNAMES = 0x08 53 | local FLAG_NO_UTF8_CHECK = 0x10 54 | 55 | 56 | local PCRE_CASELESS = 0x0000001 57 | local PCRE_MULTILINE = 0x0000002 58 | local PCRE_DOTALL = 0x0000004 59 | local PCRE_EXTENDED = 0x0000008 60 | local PCRE_ANCHORED = 0x0000010 61 | local PCRE_UTF8 = 0x0000800 62 | local PCRE_DUPNAMES = 0x0080000 63 | local PCRE_JAVASCRIPT_COMPAT = 0x2000000 64 | 65 | 66 | local PCRE_ERROR_NOMATCH = -1 67 | 68 | 69 | local regex_match_cache 70 | local regex_sub_func_cache = new_tab(0, 4) 71 | local regex_sub_str_cache = new_tab(0, 4) 72 | local max_regex_cache_size 73 | local regex_cache_size = 0 74 | local script_engine 75 | 76 | 77 | ffi.cdef[[ 78 | typedef struct { 79 | ngx_str_t value; 80 | void *lengths; 81 | void *values; 82 | } ngx_http_lua_complex_value_t; 83 | 84 | typedef struct { 85 | void *pool; 86 | unsigned char *name_table; 87 | int name_count; 88 | int name_entry_size; 89 | 90 | int ncaptures; 91 | int *captures; 92 | 93 | void *regex; 94 | void *regex_sd; 95 | 96 | ngx_http_lua_complex_value_t *replace; 97 | 98 | const char *pattern; 99 | } ngx_http_lua_regex_t; 100 | 101 | ngx_http_lua_regex_t * 102 | ngx_http_lua_ffi_compile_regex(const unsigned char *pat, 103 | size_t pat_len, int flags, 104 | int pcre_opts, unsigned char *errstr, 105 | size_t errstr_size); 106 | 107 | int ngx_http_lua_ffi_exec_regex(ngx_http_lua_regex_t *re, int flags, 108 | const unsigned char *s, size_t len, int pos); 109 | 110 | void ngx_http_lua_ffi_destroy_regex(ngx_http_lua_regex_t *re); 111 | 112 | int ngx_http_lua_ffi_compile_replace_template(ngx_http_lua_regex_t *re, 113 | const unsigned char 114 | *replace_data, 115 | size_t replace_len); 116 | 117 | struct ngx_http_lua_script_engine_s; 118 | typedef struct ngx_http_lua_script_engine_s *ngx_http_lua_script_engine_t; 119 | 120 | ngx_http_lua_script_engine_t *ngx_http_lua_ffi_create_script_engine(void); 121 | 122 | void ngx_http_lua_ffi_init_script_engine(ngx_http_lua_script_engine_t *e, 123 | const unsigned char *subj, 124 | ngx_http_lua_regex_t *compiled, 125 | int count); 126 | 127 | void ngx_http_lua_ffi_destroy_script_engine( 128 | ngx_http_lua_script_engine_t *e); 129 | 130 | size_t ngx_http_lua_ffi_script_eval_len(ngx_http_lua_script_engine_t *e, 131 | ngx_http_lua_complex_value_t *cv); 132 | 133 | size_t ngx_http_lua_ffi_script_eval_data(ngx_http_lua_script_engine_t *e, 134 | ngx_http_lua_complex_value_t *cv, 135 | unsigned char *dst); 136 | 137 | uint32_t ngx_http_lua_ffi_max_regex_cache_size(void); 138 | ]] 139 | 140 | 141 | local c_str_type = ffi.typeof("const char *") 142 | 143 | local cached_re_opts = new_tab(0, 4) 144 | 145 | local _M = { 146 | version = base.version 147 | } 148 | 149 | 150 | local buf_grow_ratio = 2 151 | 152 | function _M.set_buf_grow_ratio(ratio) 153 | buf_grow_ratio = ratio 154 | end 155 | 156 | 157 | local function get_max_regex_cache_size() 158 | if max_regex_cache_size then 159 | return max_regex_cache_size 160 | end 161 | max_regex_cache_size = C.ngx_http_lua_ffi_max_regex_cache_size() 162 | return max_regex_cache_size 163 | end 164 | 165 | 166 | local function parse_regex_opts(opts) 167 | local t = cached_re_opts[opts] 168 | if t then 169 | return t[1], t[2] 170 | end 171 | 172 | local flags = 0 173 | local pcre_opts = 0 174 | local len = #opts 175 | 176 | for i = 1, len do 177 | local opt = byte(opts, i) 178 | if opt == byte("o") then 179 | flags = bor(flags, FLAG_COMPILE_ONCE) 180 | 181 | elseif opt == byte("j") then 182 | flags = bor(flags, FLAG_JIT) 183 | 184 | elseif opt == byte("i") then 185 | pcre_opts = bor(pcre_opts, PCRE_CASELESS) 186 | 187 | elseif opt == byte("s") then 188 | pcre_opts = bor(pcre_opts, PCRE_DOTALL) 189 | 190 | elseif opt == byte("m") then 191 | pcre_opts = bor(pcre_opts, PCRE_MULTILINE) 192 | 193 | elseif opt == byte("u") then 194 | pcre_opts = bor(pcre_opts, PCRE_UTF8) 195 | 196 | elseif opt == byte("U") then 197 | pcre_opts = bor(pcre_opts, PCRE_UTF8) 198 | flags = bor(flags, FLAG_NO_UTF8_CHECK) 199 | 200 | elseif opt == byte("x") then 201 | pcre_opts = bor(pcre_opts, PCRE_EXTENDED) 202 | 203 | elseif opt == byte("d") then 204 | flags = bor(flags, FLAG_DFA) 205 | 206 | elseif opt == byte("a") then 207 | pcre_opts = bor(pcre_opts, PCRE_ANCHORED) 208 | 209 | elseif opt == byte("D") then 210 | pcre_opts = bor(pcre_opts, PCRE_DUPNAMES) 211 | flags = bor(flags, FLAG_DUPNAMES) 212 | 213 | elseif opt == byte("J") then 214 | pcre_opts = bor(pcre_opts, PCRE_JAVASCRIPT_COMPAT) 215 | 216 | else 217 | return error(fmt('unknown flag "%s" (flags "%s")', 218 | sub(opts, i, i), opts)) 219 | end 220 | end 221 | 222 | cached_re_opts[opts] = {flags, pcre_opts} 223 | return flags, pcre_opts 224 | end 225 | 226 | 227 | local function collect_named_captures(compiled, flags, res) 228 | local name_count = compiled.name_count 229 | local name_table = compiled.name_table 230 | local entry_size = compiled.name_entry_size 231 | 232 | local ind = 0 233 | local dup_names = (band(flags, FLAG_DUPNAMES) ~= 0) 234 | for i = 1, name_count do 235 | local n = bor(lshift(name_table[ind], 8), name_table[ind + 1]) 236 | -- ngx.say("n = ", n) 237 | local name = ffi_string(name_table + ind + 2) 238 | local cap = res[n] 239 | if cap then 240 | if dup_names then 241 | local old = res[name] 242 | if old then 243 | old[#old + 1] = cap 244 | else 245 | res[name] = {cap} 246 | end 247 | else 248 | res[name] = cap 249 | end 250 | end 251 | 252 | ind = ind + entry_size 253 | end 254 | end 255 | 256 | 257 | local function collect_captures(compiled, rc, subj, flags, res) 258 | local cap = compiled.captures 259 | local name_count = compiled.name_count 260 | 261 | if not res then 262 | res = new_tab(rc, name_count) 263 | end 264 | 265 | local i = 0 266 | local n = 0 267 | while i < rc do 268 | local from = cap[n] 269 | if from >= 0 then 270 | local to = cap[n + 1] 271 | res[i] = sub(subj, from + 1, to) 272 | end 273 | i = i + 1 274 | n = n + 2 275 | end 276 | 277 | if name_count > 0 then 278 | collect_named_captures(compiled, flags, res) 279 | end 280 | 281 | return res 282 | end 283 | 284 | 285 | local function destroy_compiled_regex(compiled) 286 | C.ngx_http_lua_ffi_destroy_regex(ffi_gc(compiled, nil)) 287 | end 288 | 289 | 290 | local function re_match_compile(regex, opts) 291 | local flags = 0 292 | local pcre_opts = 0 293 | 294 | if opts then 295 | flags, pcre_opts = parse_regex_opts(opts) 296 | else 297 | opts = "" 298 | end 299 | 300 | local compiled, key 301 | local compile_once = (band(flags, FLAG_COMPILE_ONCE) == 1) 302 | 303 | -- FIXME: better put this in the outer scope when fixing the ngx.re API's 304 | -- compatibility in the init_by_lua* context. 305 | if not regex_match_cache then 306 | local sz = get_max_regex_cache_size() 307 | if sz <= 0 then 308 | compile_once = false 309 | else 310 | regex_match_cache = lrucache.new(sz) 311 | end 312 | end 313 | 314 | if compile_once then 315 | key = regex .. '\0' .. opts 316 | compiled = lrucache_get(regex_match_cache, key) 317 | end 318 | 319 | -- compile the regex 320 | 321 | if compiled == nil then 322 | -- print("compiled regex not found, compiling regex...") 323 | local errbuf = get_string_buf(MAX_ERR_MSG_LEN) 324 | 325 | compiled = C.ngx_http_lua_ffi_compile_regex(regex, #regex, 326 | flags, pcre_opts, 327 | errbuf, MAX_ERR_MSG_LEN) 328 | 329 | if compiled == nil then 330 | return nil, ffi_string(errbuf) 331 | end 332 | 333 | ffi_gc(compiled, C.ngx_http_lua_ffi_destroy_regex) 334 | 335 | -- print("ncaptures: ", compiled.ncaptures) 336 | 337 | if compile_once then 338 | -- print("inserting compiled regex into cache") 339 | lrucache_set(regex_match_cache, key, compiled) 340 | end 341 | end 342 | 343 | return compiled, compile_once, flags 344 | end 345 | 346 | 347 | local function re_match_helper(subj, regex, opts, ctx, want_caps, res, nth) 348 | local compiled, compile_once, flags = re_match_compile(regex, opts) 349 | if compiled == nil then 350 | -- compiled_once holds the error string 351 | if not want_caps then 352 | return nil, nil, compile_once 353 | end 354 | return nil, compile_once 355 | end 356 | 357 | -- exec the compiled regex 358 | 359 | local rc 360 | do 361 | local pos 362 | if ctx then 363 | pos = ctx.pos 364 | if not pos or pos <= 0 then 365 | pos = 0 366 | else 367 | pos = pos - 1 368 | end 369 | 370 | else 371 | pos = 0 372 | end 373 | 374 | rc = C.ngx_http_lua_ffi_exec_regex(compiled, flags, subj, #subj, pos) 375 | end 376 | 377 | if rc == PCRE_ERROR_NOMATCH then 378 | if not compile_once then 379 | destroy_compiled_regex(compiled) 380 | end 381 | return nil 382 | end 383 | 384 | if rc < 0 then 385 | if not compile_once then 386 | destroy_compiled_regex(compiled) 387 | end 388 | if not want_caps then 389 | return nil, nil, "pcre_exec() failed: " .. rc 390 | end 391 | return nil, "pcre_exec() failed: " .. rc 392 | end 393 | 394 | if rc == 0 then 395 | if band(flags, FLAG_DFA) == 0 then 396 | if not want_caps then 397 | return nil, nil, "capture size too small" 398 | end 399 | return nil, "capture size too small" 400 | end 401 | 402 | rc = 1 403 | end 404 | 405 | -- print("cap 0: ", compiled.captures[0]) 406 | -- print("cap 1: ", compiled.captures[1]) 407 | 408 | if ctx then 409 | ctx.pos = compiled.captures[1] + 1 410 | end 411 | 412 | if not want_caps then 413 | if not nth or nth < 0 then 414 | nth = 0 415 | end 416 | 417 | if nth > compiled.ncaptures then 418 | return nil, nil, "nth out of bound" 419 | end 420 | 421 | if nth >= rc then 422 | return nil, nil 423 | end 424 | 425 | local from = compiled.captures[nth * 2] + 1 426 | local to = compiled.captures[nth * 2 + 1] 427 | 428 | if from < 0 or to < 0 then 429 | return nil, nil 430 | end 431 | 432 | return from, to 433 | end 434 | 435 | res = collect_captures(compiled, rc, subj, flags, res) 436 | 437 | if not compile_once then 438 | destroy_compiled_regex(compiled) 439 | end 440 | 441 | return res 442 | end 443 | 444 | 445 | function ngx.re.match(subj, regex, opts, ctx, res) 446 | return re_match_helper(subj, regex, opts, ctx, true, res) 447 | end 448 | 449 | 450 | function ngx.re.find(subj, regex, opts, ctx, nth) 451 | return re_match_helper(subj, regex, opts, ctx, false, nil, nth) 452 | end 453 | 454 | 455 | local function new_script_engine(subj, compiled, count) 456 | if not script_engine then 457 | script_engine = C.ngx_http_lua_ffi_create_script_engine() 458 | if script_engine == nil then 459 | return nil 460 | end 461 | ffi_gc(script_engine, C.ngx_http_lua_ffi_destroy_script_engine) 462 | end 463 | 464 | C.ngx_http_lua_ffi_init_script_engine(script_engine, subj, compiled, 465 | count) 466 | return script_engine 467 | end 468 | 469 | 470 | local function check_buf_size(buf, buf_size, pos, len, new_len, must_alloc) 471 | if new_len > buf_size then 472 | buf_size = buf_size * buf_grow_ratio 473 | if buf_size < new_len then 474 | buf_size = new_len 475 | end 476 | local new_buf = get_string_buf(buf_size, must_alloc) 477 | ffi_copy(new_buf, buf, len) 478 | buf = new_buf 479 | pos = buf + len 480 | end 481 | return buf, buf_size, pos, new_len 482 | end 483 | 484 | 485 | local function re_sub_compile(regex, opts, replace, func) 486 | local flags = 0 487 | local pcre_opts = 0 488 | 489 | if opts then 490 | flags, pcre_opts = parse_regex_opts(opts) 491 | else 492 | opts = "" 493 | end 494 | 495 | local compiled 496 | local compile_once = (band(flags, FLAG_COMPILE_ONCE) == 1) 497 | if compile_once then 498 | if func then 499 | local subcache = regex_sub_func_cache[opts] 500 | if subcache then 501 | -- print("cache hit!") 502 | compiled = subcache[regex] 503 | end 504 | 505 | else 506 | local subcache = regex_sub_str_cache[opts] 507 | if subcache then 508 | local subsubcache = subcache[regex] 509 | if subsubcache then 510 | -- print("cache hit!") 511 | compiled = subsubcache[replace] 512 | end 513 | end 514 | end 515 | end 516 | 517 | -- compile the regex 518 | 519 | if compiled == nil then 520 | -- print("compiled regex not found, compiling regex...") 521 | local errbuf = get_string_buf(MAX_ERR_MSG_LEN) 522 | 523 | compiled = C.ngx_http_lua_ffi_compile_regex(regex, #regex, flags, 524 | pcre_opts, errbuf, 525 | MAX_ERR_MSG_LEN) 526 | 527 | if compiled == nil then 528 | return nil, ffi_string(errbuf) 529 | end 530 | 531 | ffi_gc(compiled, C.ngx_http_lua_ffi_destroy_regex) 532 | 533 | if func == nil then 534 | local rc = 535 | C.ngx_http_lua_ffi_compile_replace_template(compiled, 536 | replace, #replace) 537 | if rc ~= 0 then 538 | if not compile_once then 539 | destroy_compiled_regex(compiled) 540 | end 541 | return nil, "failed to compile the replacement template" 542 | end 543 | end 544 | 545 | -- print("ncaptures: ", compiled.ncaptures) 546 | 547 | if compile_once then 548 | if regex_cache_size < get_max_regex_cache_size() then 549 | -- print("inserting compiled regex into cache") 550 | if func then 551 | local subcache = regex_sub_func_cache[opts] 552 | if not subcache then 553 | regex_sub_func_cache[opts] = {[regex] = compiled} 554 | 555 | else 556 | subcache[regex] = compiled 557 | end 558 | 559 | else 560 | local subcache = regex_sub_str_cache[opts] 561 | if not subcache then 562 | regex_sub_str_cache[opts] = 563 | {[regex] = {[replace] = compiled}} 564 | 565 | else 566 | local subsubcache = subcache[regex] 567 | if not subsubcache then 568 | subcache[regex] = {[replace] = compiled} 569 | 570 | else 571 | subsubcache[replace] = compiled 572 | end 573 | end 574 | end 575 | 576 | regex_cache_size = regex_cache_size + 1 577 | else 578 | compile_once = false 579 | end 580 | end 581 | end 582 | 583 | return compiled, compile_once, flags 584 | end 585 | 586 | 587 | local function re_sub_func_helper(subj, regex, replace, opts, global) 588 | local compiled, compile_once, flags = 589 | re_sub_compile(regex, opts, nil, replace) 590 | if not compiled then 591 | -- error string is in compile_once 592 | return nil, nil, compile_once 593 | end 594 | 595 | -- exec the compiled regex 596 | 597 | local subj_len = #subj 598 | local count = 0 599 | local pos = 0 600 | local cp_pos = 0 601 | 602 | local dst_buf_size = get_string_buf_size() 603 | -- Note: we have to always allocate the string buffer because 604 | -- the user might call whatever resty.core's API functions recursively 605 | -- in the user callback function. 606 | local dst_buf = get_string_buf(dst_buf_size, true) 607 | local dst_pos = dst_buf 608 | local dst_len = 0 609 | 610 | while true do 611 | local rc = C.ngx_http_lua_ffi_exec_regex(compiled, flags, subj, 612 | subj_len, pos) 613 | if rc == PCRE_ERROR_NOMATCH then 614 | break 615 | end 616 | 617 | if rc < 0 then 618 | if not compile_once then 619 | destroy_compiled_regex(compiled) 620 | end 621 | return nil, nil, "pcre_exec() failed: " .. rc 622 | end 623 | 624 | if rc == 0 then 625 | if band(flags, FLAG_DFA) == 0 then 626 | if not compile_once then 627 | destroy_compiled_regex(compiled) 628 | end 629 | return nil, nil, "capture size too small" 630 | end 631 | 632 | rc = 1 633 | end 634 | 635 | count = count + 1 636 | local prefix_len = compiled.captures[0] - cp_pos 637 | 638 | local res = collect_captures(compiled, rc, subj, flags) 639 | 640 | local bit = replace(res) 641 | local bit_len = #bit 642 | 643 | local new_dst_len = dst_len + prefix_len + bit_len 644 | dst_buf, dst_buf_size, dst_pos, dst_len = 645 | check_buf_size(dst_buf, dst_buf_size, dst_pos, dst_len, 646 | new_dst_len, true) 647 | 648 | if prefix_len > 0 then 649 | ffi_copy(dst_pos, ffi_cast(c_str_type, subj) + cp_pos, 650 | prefix_len) 651 | dst_pos = dst_pos + prefix_len 652 | end 653 | 654 | if bit_len > 0 then 655 | ffi_copy(dst_pos, bit, bit_len) 656 | dst_pos = dst_pos + bit_len 657 | end 658 | 659 | cp_pos = compiled.captures[1] 660 | pos = cp_pos 661 | if pos == compiled.captures[0] then 662 | pos = pos + 1 663 | if pos > subj_len then 664 | break 665 | end 666 | end 667 | 668 | if not global then 669 | break 670 | end 671 | end 672 | 673 | if not compile_once then 674 | destroy_compiled_regex(compiled) 675 | end 676 | 677 | if count > 0 then 678 | if pos < subj_len then 679 | local suffix_len = subj_len - cp_pos 680 | 681 | local new_dst_len = dst_len + suffix_len 682 | dst_buf, dst_buf_size, dst_pos, dst_len = 683 | check_buf_size(dst_buf, dst_buf_size, dst_pos, dst_len, 684 | new_dst_len, true) 685 | 686 | ffi_copy(dst_pos, ffi_cast(c_str_type, subj) + cp_pos, 687 | suffix_len) 688 | end 689 | return ffi_string(dst_buf, dst_len), count 690 | end 691 | 692 | return subj, 0 693 | end 694 | 695 | 696 | local function re_sub_str_helper(subj, regex, replace, opts, global) 697 | local compiled, compile_once, flags = 698 | re_sub_compile(regex, opts, replace, nil) 699 | if not compiled then 700 | -- error string is in compile_once 701 | return nil, nil, compile_once 702 | end 703 | 704 | -- exec the compiled regex 705 | 706 | local subj_len = #subj 707 | local count = 0 708 | local pos = 0 709 | local cp_pos = 0 710 | 711 | local dst_buf_size = get_string_buf_size() 712 | local dst_buf = get_string_buf(dst_buf_size) 713 | local dst_pos = dst_buf 714 | local dst_len = 0 715 | 716 | while true do 717 | local rc = C.ngx_http_lua_ffi_exec_regex(compiled, flags, subj, 718 | subj_len, pos) 719 | if rc == PCRE_ERROR_NOMATCH then 720 | break 721 | end 722 | 723 | if rc < 0 then 724 | if not compile_once then 725 | destroy_compiled_regex(compiled) 726 | end 727 | return nil, nil, "pcre_exec() failed: " .. rc 728 | end 729 | 730 | if rc == 0 then 731 | if band(flags, FLAG_DFA) == 0 then 732 | if not compile_once then 733 | destroy_compiled_regex(compiled) 734 | end 735 | return nil, nil, "capture size too small" 736 | end 737 | 738 | rc = 1 739 | end 740 | 741 | count = count + 1 742 | local prefix_len = compiled.captures[0] - cp_pos 743 | 744 | local cv = compiled.replace 745 | if cv.lengths ~= nil then 746 | local e = new_script_engine(subj, compiled, rc) 747 | if e == nil then 748 | return nil, nil, "failed to create script engine" 749 | end 750 | 751 | local bit_len = C.ngx_http_lua_ffi_script_eval_len(e, cv) 752 | local new_dst_len = dst_len + prefix_len + bit_len 753 | dst_buf, dst_buf_size, dst_pos, dst_len = 754 | check_buf_size(dst_buf, dst_buf_size, dst_pos, dst_len, 755 | new_dst_len) 756 | 757 | if prefix_len > 0 then 758 | ffi_copy(dst_pos, ffi_cast(c_str_type, subj) + cp_pos, 759 | prefix_len) 760 | dst_pos = dst_pos + prefix_len 761 | end 762 | 763 | if bit_len > 0 then 764 | C.ngx_http_lua_ffi_script_eval_data(e, cv, dst_pos) 765 | dst_pos = dst_pos + bit_len 766 | end 767 | 768 | else 769 | local bit_len = cv.value.len 770 | 771 | dst_buf, dst_buf_size, dst_pos, dst_len = 772 | check_buf_size(dst_buf, dst_buf_size, dst_pos, dst_len, 773 | dst_len + prefix_len + bit_len) 774 | 775 | if prefix_len > 0 then 776 | ffi_copy(dst_pos, ffi_cast(c_str_type, subj) + cp_pos, 777 | prefix_len) 778 | dst_pos = dst_pos + prefix_len 779 | end 780 | 781 | if bit_len > 0 then 782 | ffi_copy(dst_pos, cv.value.data, bit_len) 783 | dst_pos = dst_pos + bit_len 784 | end 785 | end 786 | 787 | cp_pos = compiled.captures[1] 788 | pos = cp_pos 789 | if pos == compiled.captures[0] then 790 | pos = pos + 1 791 | if pos > subj_len then 792 | break 793 | end 794 | end 795 | 796 | if not global then 797 | break 798 | end 799 | end 800 | 801 | if not compile_once then 802 | destroy_compiled_regex(compiled) 803 | end 804 | 805 | if count > 0 then 806 | if pos < subj_len then 807 | local suffix_len = subj_len - cp_pos 808 | 809 | local new_dst_len = dst_len + suffix_len 810 | dst_buf, dst_buf_size, dst_pos, dst_len = 811 | check_buf_size(dst_buf, dst_buf_size, dst_pos, dst_len, 812 | new_dst_len) 813 | 814 | ffi_copy(dst_pos, ffi_cast(c_str_type, subj) + cp_pos, 815 | suffix_len) 816 | end 817 | return ffi_string(dst_buf, dst_len), count 818 | end 819 | 820 | return subj, 0 821 | end 822 | 823 | 824 | local function re_sub_helper(subj, regex, replace, opts, global) 825 | local repl_type = type(replace) 826 | if repl_type == "function" then 827 | return re_sub_func_helper(subj, regex, replace, opts, global) 828 | end 829 | 830 | if repl_type ~= "string" then 831 | replace = tostring(replace) 832 | end 833 | 834 | return re_sub_str_helper(subj, regex, replace, opts, global) 835 | end 836 | 837 | 838 | function ngx.re.sub(subj, regex, replace, opts) 839 | return re_sub_helper(subj, regex, replace, opts, false) 840 | end 841 | 842 | 843 | function ngx.re.gsub(subj, regex, replace, opts) 844 | return re_sub_helper(subj, regex, replace, opts, true) 845 | end 846 | 847 | 848 | return _M 849 | -------------------------------------------------------------------------------- /_example/nginx/lualib/resty/core/request.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (C) Yichun Zhang (agentzh) 2 | 3 | 4 | local ffi = require 'ffi' 5 | local base = require "resty.core.base" 6 | 7 | 8 | local FFI_BAD_CONTEXT = base.FFI_BAD_CONTEXT 9 | local FFI_DECLINED = base.FFI_DECLINED 10 | local FFI_OK = base.FFI_OK 11 | local new_tab = base.new_tab 12 | local C = ffi.C 13 | local ffi_cast = ffi.cast 14 | local ffi_str = ffi.string 15 | local get_string_buf = base.get_string_buf 16 | local get_size_ptr = base.get_size_ptr 17 | local setmetatable = setmetatable 18 | local gsub = ngx.re.gsub 19 | local lower = string.lower 20 | local rawget = rawget 21 | local ngx = ngx 22 | local getfenv = getfenv 23 | local type = type 24 | local error = error 25 | local tostring = tostring 26 | local tonumber = tonumber 27 | 28 | 29 | ffi.cdef[[ 30 | typedef struct { 31 | ngx_http_lua_ffi_str_t key; 32 | ngx_http_lua_ffi_str_t value; 33 | } ngx_http_lua_ffi_table_elt_t; 34 | 35 | int ngx_http_lua_ffi_req_get_headers_count(ngx_http_request_t *r, 36 | int max); 37 | 38 | int ngx_http_lua_ffi_req_get_headers(ngx_http_request_t *r, 39 | ngx_http_lua_ffi_table_elt_t *out, int count, int raw); 40 | 41 | int ngx_http_lua_ffi_req_get_uri_args_count(ngx_http_request_t *r, 42 | int max); 43 | 44 | size_t ngx_http_lua_ffi_req_get_querystring_len(ngx_http_request_t *r); 45 | 46 | int ngx_http_lua_ffi_req_get_uri_args(ngx_http_request_t *r, 47 | unsigned char *buf, ngx_http_lua_ffi_table_elt_t *out, int count); 48 | 49 | double ngx_http_lua_ffi_req_start_time(ngx_http_request_t *r); 50 | 51 | int ngx_http_lua_ffi_req_get_method(ngx_http_request_t *r); 52 | 53 | int ngx_http_lua_ffi_req_get_method_name(ngx_http_request_t *r, 54 | char *name, size_t *len); 55 | 56 | int ngx_http_lua_ffi_req_set_method(ngx_http_request_t *r, int method); 57 | 58 | int ngx_http_lua_ffi_req_header_set_single_value(ngx_http_request_t *r, 59 | const unsigned char *key, size_t key_len, const unsigned char *value, 60 | size_t value_len); 61 | ]] 62 | 63 | 64 | local table_elt_type = ffi.typeof("ngx_http_lua_ffi_table_elt_t*") 65 | local table_elt_size = ffi.sizeof("ngx_http_lua_ffi_table_elt_t") 66 | local req_headers_mt = { 67 | __index = function (tb, key) 68 | return rawget(tb, (gsub(lower(key), '_', '-', "jo"))) 69 | end 70 | } 71 | 72 | 73 | function ngx.req.get_headers(max_headers, raw) 74 | local r = getfenv(0).__ngx_req 75 | if not r then 76 | return error("no request found") 77 | end 78 | 79 | if not max_headers then 80 | max_headers = -1 81 | end 82 | 83 | if not raw then 84 | raw = 0 85 | else 86 | raw = 1 87 | end 88 | 89 | local n = C.ngx_http_lua_ffi_req_get_headers_count(r, max_headers) 90 | if n == FFI_BAD_CONTEXT then 91 | return error("API disabled in the current context") 92 | end 93 | 94 | if n == 0 then 95 | return {} 96 | end 97 | 98 | local raw_buf = get_string_buf(n * table_elt_size) 99 | local buf = ffi_cast(table_elt_type, raw_buf) 100 | 101 | local rc = C.ngx_http_lua_ffi_req_get_headers(r, buf, n, raw) 102 | if rc == 0 then 103 | local headers = new_tab(0, n) 104 | for i = 0, n - 1 do 105 | local h = buf[i] 106 | 107 | local key = h.key 108 | key = ffi_str(key.data, key.len) 109 | 110 | local value = h.value 111 | value = ffi_str(value.data, value.len) 112 | 113 | local existing = headers[key] 114 | if existing then 115 | if type(existing) == "table" then 116 | existing[#existing + 1] = value 117 | else 118 | headers[key] = {existing, value} 119 | end 120 | 121 | else 122 | headers[key] = value 123 | end 124 | end 125 | if raw == 0 then 126 | return setmetatable(headers, req_headers_mt) 127 | end 128 | return headers 129 | end 130 | 131 | return nil 132 | end 133 | 134 | 135 | function ngx.req.get_uri_args(max_args) 136 | local r = getfenv(0).__ngx_req 137 | if not r then 138 | return error("no request found") 139 | end 140 | 141 | if not max_args then 142 | max_args = -1 143 | end 144 | 145 | local n = C.ngx_http_lua_ffi_req_get_uri_args_count(r, max_args) 146 | if n == FFI_BAD_CONTEXT then 147 | return error("API disabled in the current context") 148 | end 149 | 150 | if n == 0 then 151 | return {} 152 | end 153 | 154 | local args_len = C.ngx_http_lua_ffi_req_get_querystring_len(r) 155 | 156 | local strbuf = get_string_buf(args_len + n * table_elt_size) 157 | local kvbuf = ffi_cast(table_elt_type, strbuf + args_len) 158 | 159 | local nargs = C.ngx_http_lua_ffi_req_get_uri_args(r, strbuf, kvbuf, n) 160 | 161 | local args = new_tab(0, nargs) 162 | for i = 0, nargs - 1 do 163 | local arg = kvbuf[i] 164 | 165 | local key = arg.key 166 | key = ffi_str(key.data, key.len) 167 | 168 | local value = arg.value 169 | local len = value.len 170 | if len == -1 then 171 | value = true 172 | else 173 | value = ffi_str(value.data, len) 174 | end 175 | 176 | local existing = args[key] 177 | if existing then 178 | if type(existing) == "table" then 179 | existing[#existing + 1] = value 180 | else 181 | args[key] = {existing, value} 182 | end 183 | 184 | else 185 | args[key] = value 186 | end 187 | end 188 | return args 189 | end 190 | 191 | 192 | function ngx.req.start_time() 193 | local r = getfenv(0).__ngx_req 194 | if not r then 195 | return error("no request found") 196 | end 197 | 198 | return tonumber(C.ngx_http_lua_ffi_req_start_time(r)) 199 | end 200 | 201 | 202 | do 203 | local methods = { 204 | [0x0002] = "GET", 205 | [0x0004] = "HEAD", 206 | [0x0008] = "POST", 207 | [0x0010] = "PUT", 208 | [0x0020] = "DELETE", 209 | [0x0040] = "MKCOL", 210 | [0x0080] = "COPY", 211 | [0x0100] = "MOVE", 212 | [0x0200] = "OPTIONS", 213 | [0x0400] = "PROPFIND", 214 | [0x0800] = "PROPPATCH", 215 | [0x1000] = "LOCK", 216 | [0x2000] = "UNLOCK", 217 | [0x4000] = "PATCH", 218 | [0x8000] = "TRACE", 219 | } 220 | 221 | function ngx.req.get_method() 222 | local r = getfenv(0).__ngx_req 223 | if not r then 224 | return error("no request found") 225 | end 226 | 227 | do 228 | local id = C.ngx_http_lua_ffi_req_get_method(r) 229 | if id == FFI_BAD_CONTEXT then 230 | return error("API disabled in the current context") 231 | end 232 | 233 | local method = methods[id] 234 | if method then 235 | return method 236 | end 237 | end 238 | 239 | local buf = get_string_buf(32) 240 | local sizep = get_size_ptr() 241 | sizep[0] = 32 242 | 243 | local rc = C.ngx_http_lua_ffi_req_get_method_name(r, buf, sizep) 244 | if rc ~= 0 then 245 | return nil 246 | end 247 | 248 | return ffi_str(buf, sizep[0]) 249 | end 250 | end -- do 251 | 252 | 253 | function ngx.req.set_method(method) 254 | local r = getfenv(0).__ngx_req 255 | if not r then 256 | return error("no request found") 257 | end 258 | 259 | if type(method) ~= "number" then 260 | return error("bad method number") 261 | end 262 | 263 | local rc = C.ngx_http_lua_ffi_req_set_method(r, method) 264 | if rc == FFI_OK then 265 | return 266 | end 267 | 268 | if rc == FFI_BAD_CONTEXT then 269 | return error("API disabled in the current context") 270 | end 271 | 272 | if rc == FFI_DECLINED then 273 | return error("unsupported HTTP method: " .. method) 274 | end 275 | 276 | return error("unknown error: " .. rc) 277 | end 278 | 279 | 280 | do 281 | local orig_func = ngx.req.set_header 282 | 283 | function ngx.req.set_header(name, value) 284 | if type(value) == "table" then 285 | return orig_func(name, value) 286 | end 287 | 288 | local r = getfenv(0).__ngx_req 289 | if not r then 290 | return error("no request found") 291 | end 292 | 293 | if type(name) ~= "string" then 294 | name = tostring(name) 295 | end 296 | 297 | local rc 298 | if not value then 299 | rc = C.ngx_http_lua_ffi_req_header_set_single_value(r, name, 300 | #name, nil, 0) 301 | 302 | else 303 | if type(value) ~= "string" then 304 | value = tostring(value) 305 | end 306 | 307 | rc = C.ngx_http_lua_ffi_req_header_set_single_value(r, name, 308 | #name, value, #value) 309 | end 310 | 311 | if rc == FFI_OK or rc == FFI_DECLINED then 312 | return 313 | end 314 | 315 | if rc == FFI_BAD_CONTEXT then 316 | return error("API disabled in the current context") 317 | end 318 | 319 | return error("error") 320 | end 321 | end -- do 322 | 323 | 324 | function ngx.req.clear_header(name, value) 325 | local r = getfenv(0).__ngx_req 326 | if not r then 327 | return error("no request found") 328 | end 329 | 330 | if type(name) ~= "string" then 331 | name = tostring(name) 332 | end 333 | 334 | local rc = C.ngx_http_lua_ffi_req_header_set_single_value(r, name, #name, 335 | nil, 0) 336 | 337 | if rc == FFI_OK or rc == FFI_DECLINED then 338 | return 339 | end 340 | 341 | if rc == FFI_BAD_CONTEXT then 342 | return error("API disabled in the current context") 343 | end 344 | 345 | return error("error") 346 | end 347 | 348 | 349 | return { 350 | version = base.version 351 | } 352 | -------------------------------------------------------------------------------- /_example/nginx/lualib/resty/core/response.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (C) Yichun Zhang (agentzh) 2 | 3 | 4 | local ffi = require 'ffi' 5 | local base = require "resty.core.base" 6 | 7 | 8 | local C = ffi.C 9 | local ffi_new = ffi.new 10 | local ffi_cast = ffi.cast 11 | local ffi_str = ffi.string 12 | local new_tab = base.new_tab 13 | local FFI_BAD_CONTEXT = base.FFI_BAD_CONTEXT 14 | local FFI_NO_REQ_CTX = base.FFI_NO_REQ_CTX 15 | -- local FFI_ERROR = base.FFI_ERROR 16 | local FFI_DECLINED = base.FFI_DECLINED 17 | local get_string_buf = base.get_string_buf 18 | local getmetatable = getmetatable 19 | local type = type 20 | local tostring = tostring 21 | local getfenv = getfenv 22 | local error = error 23 | local ngx = ngx 24 | 25 | 26 | local MAX_HEADER_VALUES = 100 27 | local errmsg = base.get_errmsg_ptr() 28 | local ffi_str_type = ffi.typeof("ngx_http_lua_ffi_str_t*") 29 | local ffi_str_size = ffi.sizeof("ngx_http_lua_ffi_str_t") 30 | 31 | 32 | ffi.cdef[[ 33 | int ngx_http_lua_ffi_set_resp_header(ngx_http_request_t *r, 34 | const char *key_data, size_t key_len, int is_nil, 35 | const char *sval, size_t sval_len, ngx_http_lua_ffi_str_t *mvals, 36 | size_t mvals_len, char **errmsg); 37 | 38 | int ngx_http_lua_ffi_get_resp_header(ngx_http_request_t *r, 39 | const unsigned char *key, size_t key_len, 40 | unsigned char *key_buf, ngx_http_lua_ffi_str_t *values, 41 | int max_nvalues); 42 | ]] 43 | 44 | 45 | local function set_resp_header(tb, key, value) 46 | local r = getfenv(0).__ngx_req 47 | if not r then 48 | return error("no request found") 49 | end 50 | 51 | if type(key) ~= "string" then 52 | key = tostring(key) 53 | end 54 | 55 | local rc 56 | if value == nil then 57 | rc = C.ngx_http_lua_ffi_set_resp_header(r, key, #key, true, nil, 0, 58 | nil, 0, errmsg) 59 | else 60 | local sval, sval_len, mvals, mvals_len, buf 61 | 62 | if type(value) == "table" then 63 | mvals_len = #value 64 | buf = get_string_buf(ffi_str_size * mvals_len) 65 | mvals = ffi_cast(ffi_str_type, buf) 66 | for i = 1, mvals_len do 67 | local s = value[i] 68 | if type(s) ~= "string" then 69 | s = tostring(s) 70 | value[i] = s 71 | end 72 | local str = mvals[i - 1] 73 | str.data = s 74 | str.len = #s 75 | end 76 | 77 | sval_len = 0 78 | 79 | else 80 | if type(value) ~= "string" then 81 | sval = tostring(value) 82 | else 83 | sval = value 84 | end 85 | sval_len = #sval 86 | 87 | mvals_len = 0 88 | end 89 | 90 | rc = C.ngx_http_lua_ffi_set_resp_header(r, key, #key, false, sval, 91 | sval_len, mvals, mvals_len, 92 | errmsg) 93 | end 94 | 95 | if rc == 0 or rc == FFI_DECLINED then 96 | return 97 | end 98 | 99 | if rc == FFI_NO_REQ_CTX then 100 | return error("no request ctx found") 101 | end 102 | 103 | if rc == FFI_BAD_CONTEXT then 104 | return error("API disabled in the current context") 105 | end 106 | 107 | -- rc == FFI_ERROR 108 | return error(ffi_str(errmsg[0])) 109 | end 110 | 111 | 112 | local function get_resp_header(tb, key) 113 | local r = getfenv(0).__ngx_req 114 | if not r then 115 | return error("no request found") 116 | end 117 | 118 | if type(key) ~= "string" then 119 | key = tostring(key) 120 | end 121 | 122 | local key_len = #key 123 | 124 | local key_buf = get_string_buf(key_len + ffi_str_size * MAX_HEADER_VALUES) 125 | local values = ffi_cast(ffi_str_type, key_buf + key_len) 126 | local n = C.ngx_http_lua_ffi_get_resp_header(r, key, key_len, key_buf, 127 | values, MAX_HEADER_VALUES) 128 | 129 | -- print("retval: ", n) 130 | 131 | if n == FFI_BAD_CONTEXT then 132 | return error("API disabled in the current context") 133 | end 134 | 135 | if n == 0 then 136 | return nil 137 | end 138 | 139 | if n == 1 then 140 | local v = values[0] 141 | return ffi_str(v.data, v.len) 142 | end 143 | 144 | if n > 0 then 145 | local ret = new_tab(n, 0) 146 | for i = 1, n do 147 | local v = values[i - 1] 148 | ret[i] = ffi_str(v.data, v.len) 149 | end 150 | return ret 151 | end 152 | 153 | -- n == FFI_ERROR 154 | return error("no memory") 155 | end 156 | 157 | 158 | do 159 | local mt = getmetatable(ngx.header) 160 | mt.__newindex = set_resp_header 161 | mt.__index = get_resp_header 162 | end 163 | 164 | 165 | return { 166 | version = base.version 167 | } 168 | -------------------------------------------------------------------------------- /_example/nginx/lualib/resty/core/shdict.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (C) Yichun Zhang (agentzh) 2 | 3 | 4 | local ffi = require 'ffi' 5 | local base = require "resty.core.base" 6 | 7 | local ffi_new = ffi.new 8 | local ffi_str = ffi.string 9 | local C = ffi.C 10 | local get_string_buf = base.get_string_buf 11 | local get_string_buf_size = base.get_string_buf_size 12 | local get_size_ptr = base.get_size_ptr 13 | local tonumber = tonumber 14 | local tostring = tostring 15 | local next = next 16 | local type = type 17 | local error = error 18 | local ngx_shared = ngx.shared 19 | local getmetatable = getmetatable 20 | 21 | 22 | ffi.cdef[[ 23 | int ngx_http_lua_ffi_shdict_get(void *zone, const unsigned char *key, 24 | size_t key_len, int *value_type, unsigned char **str_value_buf, 25 | size_t *str_value_len, double *num_value, int *user_flags, 26 | int get_stale, int *is_stale); 27 | 28 | int ngx_http_lua_ffi_shdict_incr(void *zone, const unsigned char *key, 29 | size_t key_len, double *value, char **err); 30 | 31 | int ngx_http_lua_ffi_shdict_store(void *zone, int op, 32 | const unsigned char *key, size_t key_len, int value_type, 33 | const unsigned char *str_value_buf, size_t str_value_len, 34 | double num_value, int exptime, int user_flags, char **errmsg, 35 | int *forcible); 36 | 37 | int ngx_http_lua_ffi_shdict_flush_all(void *zone); 38 | ]] 39 | 40 | 41 | if not pcall(function () return C.free end) then 42 | ffi.cdef[[ 43 | void free(void *ptr); 44 | ]] 45 | end 46 | 47 | 48 | local value_type = ffi_new("int[1]") 49 | local user_flags = ffi_new("int[1]") 50 | local num_value = ffi_new("double[1]") 51 | local is_stale = ffi_new("int[1]") 52 | local forcible = ffi_new("int[1]") 53 | local str_value_buf = ffi_new("unsigned char *[1]") 54 | local errmsg = base.get_errmsg_ptr() 55 | 56 | 57 | local function shdict_store(zone, op, key, value, exptime, flags) 58 | if not zone or type(zone) ~= "userdata" then 59 | return error('bad "zone" argument') 60 | end 61 | 62 | if not exptime then 63 | exptime = 0 64 | end 65 | 66 | if not flags then 67 | flags = 0 68 | end 69 | 70 | if key == nil then 71 | return nil, "nil key" 72 | end 73 | 74 | if type(key) ~= "string" then 75 | key = tostring(key) 76 | end 77 | 78 | local key_len = #key 79 | if key_len == 0 then 80 | return nil, "empty key" 81 | end 82 | if key_len > 65535 then 83 | return nil, "key too long" 84 | end 85 | 86 | local str_value_buf 87 | local str_value_len = 0 88 | local num_value = 0 89 | local valtyp = type(value) 90 | 91 | -- print("value type: ", valtyp) 92 | -- print("exptime: ", exptime) 93 | 94 | if valtyp == "string" then 95 | valtyp = 4 -- LUA_TSTRING 96 | str_value_buf = value 97 | str_value_len = #value 98 | 99 | elseif valtyp == "number" then 100 | valtyp = 3 -- LUA_TNUMBER 101 | num_value = value 102 | 103 | elseif value == nil then 104 | valtyp = 0 -- LUA_TNIL 105 | 106 | elseif valtyp == "boolean" then 107 | valtyp = 1 -- LUA_TBOOLEAN 108 | num_value = value and 1 or 0 109 | 110 | else 111 | return nil, "bad value type" 112 | end 113 | 114 | local rc = C.ngx_http_lua_ffi_shdict_store(zone, op, key, key_len, 115 | valtyp, str_value_buf, 116 | str_value_len, num_value, 117 | exptime * 1000, flags, errmsg, 118 | forcible) 119 | 120 | -- print("rc == ", rc) 121 | 122 | if rc == 0 then -- NGX_OK 123 | return true, nil, forcible[0] == 1 124 | end 125 | 126 | -- NGX_DECLINED or NGX_ERROR 127 | return false, ffi_str(errmsg[0]), forcible[0] == 1 128 | end 129 | 130 | 131 | local function shdict_set(zone, key, value, exptime, flags) 132 | return shdict_store(zone, 0, key, value, exptime, flags) 133 | end 134 | 135 | 136 | local function shdict_safe_set(zone, key, value, exptime, flags) 137 | return shdict_store(zone, 0x0004, key, value, exptime, flags) 138 | end 139 | 140 | 141 | local function shdict_add(zone, key, value, exptime, flags) 142 | return shdict_store(zone, 0x0001, key, value, exptime, flags) 143 | end 144 | 145 | 146 | local function shdict_safe_add(zone, key, value, exptime, flags) 147 | return shdict_store(zone, 0x0005, key, value, exptime, flags) 148 | end 149 | 150 | 151 | local function shdict_replace(zone, key, value, exptime, flags) 152 | return shdict_store(zone, 0x0002, key, value, exptime, flags) 153 | end 154 | 155 | 156 | local function shdict_delete(zone, key) 157 | return shdict_set(zone, key, nil) 158 | end 159 | 160 | 161 | local function shdict_get(zone, key) 162 | if not zone or type(zone) ~= "userdata" then 163 | return error('bad "zone" argument') 164 | end 165 | 166 | if key == nil then 167 | return nil, "nil key" 168 | end 169 | 170 | if type(key) ~= "string" then 171 | key = tostring(key) 172 | end 173 | 174 | local key_len = #key 175 | if key_len == 0 then 176 | return nil, "empty key" 177 | end 178 | if key_len > 65535 then 179 | return nil, "key too long" 180 | end 181 | 182 | local size = get_string_buf_size() 183 | local buf = get_string_buf(size) 184 | str_value_buf[0] = buf 185 | local value_len = get_size_ptr() 186 | value_len[0] = size 187 | 188 | local rc = C.ngx_http_lua_ffi_shdict_get(zone, key, key_len, value_type, 189 | str_value_buf, value_len, 190 | num_value, user_flags, 0, 191 | is_stale) 192 | if rc ~= 0 then 193 | return error("failed to get the key") 194 | end 195 | 196 | local typ = value_type[0] 197 | 198 | if typ == 0 then -- LUA_TNIL 199 | return nil 200 | end 201 | 202 | local flags = tonumber(user_flags[0]) 203 | 204 | local val 205 | 206 | if typ == 4 then -- LUA_TSTRING 207 | if str_value_buf[0] ~= buf then 208 | -- ngx.say("len: ", tonumber(value_len[0])) 209 | buf = str_value_buf[0] 210 | val = ffi_str(buf, value_len[0]) 211 | C.free(buf) 212 | else 213 | val = ffi_str(buf, value_len[0]) 214 | end 215 | 216 | elseif typ == 3 then -- LUA_TNUMBER 217 | val = tonumber(num_value[0]) 218 | 219 | elseif typ == 1 then -- LUA_TBOOLEAN 220 | val = (tonumber(buf[0]) ~= 0) 221 | 222 | else 223 | return error("unknown value type: " .. typ) 224 | end 225 | 226 | if flags ~= 0 then 227 | return val, flags 228 | end 229 | 230 | return val 231 | end 232 | 233 | 234 | local function shdict_get_stale(zone, key) 235 | if not zone or type(zone) ~= "userdata" then 236 | return error("bad \"zone\" argument") 237 | end 238 | 239 | if key == nil then 240 | return nil, "nil key" 241 | end 242 | 243 | if type(key) ~= "string" then 244 | key = tostring(key) 245 | end 246 | 247 | local key_len = #key 248 | if key_len == 0 then 249 | return nil, "empty key" 250 | end 251 | if key_len > 65535 then 252 | return nil, "key too long" 253 | end 254 | 255 | local size = get_string_buf_size() 256 | local buf = get_string_buf(size) 257 | str_value_buf[0] = buf 258 | local value_len = get_size_ptr() 259 | value_len[0] = size 260 | 261 | local rc = C.ngx_http_lua_ffi_shdict_get(zone, key, key_len, value_type, 262 | str_value_buf, value_len, 263 | num_value, user_flags, 1, 264 | is_stale) 265 | if rc ~= 0 then 266 | return error("failed to get the key") 267 | end 268 | 269 | local typ = value_type[0] 270 | 271 | if typ == 0 then -- LUA_TNIL 272 | return nil 273 | end 274 | 275 | local flags = tonumber(user_flags[0]) 276 | local val 277 | 278 | if typ == 4 then -- LUA_TSTRING 279 | if str_value_buf[0] ~= buf then 280 | -- ngx.say("len: ", tonumber(value_len[0])) 281 | buf = str_value_buf[0] 282 | val = ffi_str(buf, value_len[0]) 283 | C.free(buf) 284 | else 285 | val = ffi_str(buf, value_len[0]) 286 | end 287 | 288 | elseif typ == 3 then -- LUA_TNUMBER 289 | val = tonumber(num_value[0]) 290 | 291 | elseif typ == 1 then -- LUA_TBOOLEAN 292 | val = (tonumber(buf[0]) ~= 0) 293 | 294 | else 295 | return error("unknown value type: " .. typ) 296 | end 297 | 298 | if flags ~= 0 then 299 | return val, flags, is_stale[0] == 1 300 | end 301 | 302 | return val, nil, is_stale[0] == 1 303 | end 304 | 305 | 306 | local function shdict_incr(zone, key, value) 307 | if not zone or type(zone) ~= "userdata" then 308 | return error("bad \"zone\" argument") 309 | end 310 | 311 | if key == nil then 312 | return nil, "nil key" 313 | end 314 | 315 | if type(key) ~= "string" then 316 | key = tostring(key) 317 | end 318 | 319 | local key_len = #key 320 | if key_len == 0 then 321 | return nil, "empty key" 322 | end 323 | if key_len > 65535 then 324 | return nil, "key too long" 325 | end 326 | 327 | if type(value) ~= "number" then 328 | value = tonumber(value) 329 | end 330 | num_value[0] = value 331 | 332 | local rc = C.ngx_http_lua_ffi_shdict_incr(zone, key, key_len, num_value, 333 | errmsg) 334 | if rc ~= 0 then -- ~= NGX_OK 335 | return nil, ffi_str(errmsg[0]) 336 | end 337 | 338 | return tonumber(num_value[0]) 339 | end 340 | 341 | 342 | local function shdict_flush_all(zone) 343 | if not zone or type(zone) ~= "userdata" then 344 | return error("bad \"zone\" argument") 345 | end 346 | 347 | C.ngx_http_lua_ffi_shdict_flush_all(zone) 348 | end 349 | 350 | 351 | if ngx_shared then 352 | local name, dict = next(ngx_shared, nil) 353 | if dict then 354 | local mt = getmetatable(dict) 355 | if mt then 356 | mt = mt.__index 357 | if mt then 358 | mt.get = shdict_get 359 | mt.get_stale = shdict_get_stale 360 | mt.incr = shdict_incr 361 | mt.set = shdict_set 362 | mt.safe_set = shdict_safe_set 363 | mt.add = shdict_add 364 | mt.safe_add = shdict_safe_add 365 | mt.replace = shdict_replace 366 | mt.delete = shdict_delete 367 | mt.flush_all = shdict_flush_all 368 | end 369 | end 370 | end 371 | end 372 | 373 | 374 | return { 375 | version = base.version 376 | } 377 | -------------------------------------------------------------------------------- /_example/nginx/lualib/resty/core/time.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (C) Yichun Zhang (agentzh) 2 | 3 | 4 | local ffi = require 'ffi' 5 | local base = require "resty.core.base" 6 | 7 | 8 | local tonumber = tonumber 9 | local C = ffi.C 10 | local ngx = ngx 11 | 12 | 13 | ffi.cdef[[ 14 | double ngx_http_lua_ffi_now(void); 15 | long ngx_http_lua_ffi_time(void); 16 | ]] 17 | 18 | 19 | function ngx.now() 20 | return tonumber(C.ngx_http_lua_ffi_now()) 21 | end 22 | 23 | 24 | function ngx.time() 25 | return tonumber(C.ngx_http_lua_ffi_time()) 26 | end 27 | 28 | 29 | return { 30 | version = base.version 31 | } 32 | -------------------------------------------------------------------------------- /_example/nginx/lualib/resty/core/uri.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (C) Yichun Zhang (agentzh) 2 | 3 | 4 | local ffi = require 'ffi' 5 | local ffi_string = ffi.string 6 | local ffi_new = ffi.new 7 | local C = ffi.C 8 | local setmetatable = setmetatable 9 | local ngx = ngx 10 | local type = type 11 | local tostring = tostring 12 | local error = error 13 | local base = require "resty.core.base" 14 | local get_string_buf = base.get_string_buf 15 | local get_size_ptr = base.get_size_ptr 16 | local print = print 17 | local tonumber = tonumber 18 | 19 | 20 | ffi.cdef[[ 21 | size_t ngx_http_lua_ffi_uri_escaped_length(const unsigned char *src, 22 | size_t len); 23 | 24 | void ngx_http_lua_ffi_escape_uri(const unsigned char *src, size_t len, 25 | unsigned char *dst); 26 | 27 | size_t ngx_http_lua_ffi_unescape_uri(const unsigned char *src, 28 | size_t len, unsigned char *dst); 29 | ]] 30 | 31 | 32 | ngx.escape_uri = function (s) 33 | if type(s) ~= 'string' then 34 | if not s then 35 | s = '' 36 | else 37 | s = tostring(s) 38 | end 39 | end 40 | local slen = #s 41 | local dlen = C.ngx_http_lua_ffi_uri_escaped_length(s, slen) 42 | -- print("dlen: ", tonumber(dlen)) 43 | if dlen == slen then 44 | return s 45 | end 46 | local dst = get_string_buf(dlen) 47 | C.ngx_http_lua_ffi_escape_uri(s, slen, dst) 48 | return ffi_string(dst, dlen) 49 | end 50 | 51 | 52 | ngx.unescape_uri = function (s) 53 | if type(s) ~= 'string' then 54 | if not s then 55 | s = '' 56 | else 57 | s = tostring(s) 58 | end 59 | end 60 | local slen = #s 61 | local dlen = slen 62 | local dst = get_string_buf(dlen) 63 | dlen = C.ngx_http_lua_ffi_unescape_uri(s, slen, dst) 64 | return ffi_string(dst, dlen) 65 | end 66 | 67 | 68 | return { 69 | version = base.version, 70 | } 71 | -------------------------------------------------------------------------------- /_example/nginx/lualib/resty/core/var.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (C) Yichun Zhang (agentzh) 2 | 3 | 4 | local ffi = require 'ffi' 5 | local base = require "resty.core.base" 6 | 7 | local ffi_new = ffi.new 8 | local ffi_str = ffi.string 9 | local C = ffi.C 10 | local type = type 11 | local getfenv = getfenv 12 | local get_string_buf = base.get_string_buf 13 | local get_size_ptr = base.get_size_ptr 14 | local error = error 15 | local tostring = tostring 16 | local ngx_var = ngx.var 17 | local getmetatable = getmetatable 18 | 19 | 20 | ffi.cdef[[ 21 | int ngx_http_lua_ffi_var_get(ngx_http_request_t *r, 22 | const char *name_data, size_t name_len, char *lowcase_buf, 23 | int capture_id, char **value, size_t *value_len, char **err); 24 | 25 | int ngx_http_lua_ffi_var_set(ngx_http_request_t *r, 26 | const unsigned char *name_data, size_t name_len, 27 | unsigned char *lowcase_buf, const unsigned char *value, 28 | size_t value_len, unsigned char *errbuf, size_t errlen); 29 | ]] 30 | 31 | 32 | local value_ptr = ffi_new("unsigned char *[1]") 33 | local errmsg = base.get_errmsg_ptr() 34 | 35 | 36 | local function var_get(self, name) 37 | local r = getfenv(0).__ngx_req 38 | if not r then 39 | return error("no request found") 40 | end 41 | 42 | local value_len = get_size_ptr() 43 | local rc 44 | if type(name) == "number" then 45 | rc = C.ngx_http_lua_ffi_var_get(r, nil, 0, nil, name, value_ptr, 46 | value_len, errmsg) 47 | 48 | else 49 | if type(name) ~= "string" then 50 | return error("bad variable name") 51 | end 52 | 53 | local name_len = #name 54 | local lowcase_buf = get_string_buf(name_len) 55 | 56 | rc = C.ngx_http_lua_ffi_var_get(r, name, name_len, lowcase_buf, 0, 57 | value_ptr, value_len, errmsg) 58 | end 59 | 60 | -- ngx.log(ngx.WARN, "rc = ", rc) 61 | 62 | if rc == 0 then -- NGX_OK 63 | return ffi_str(value_ptr[0], value_len[0]) 64 | end 65 | 66 | if rc == -5 then -- NGX_DECLINED 67 | return nil 68 | end 69 | 70 | if rc == -1 then -- NGX_ERROR 71 | return error(ffi_str(errmsg[0])) 72 | end 73 | end 74 | 75 | 76 | local function var_set(self, name, value) 77 | local r = getfenv(0).__ngx_req 78 | if not r then 79 | return error("no request found") 80 | end 81 | 82 | if type(name) ~= "string" then 83 | return error("bad variable name") 84 | end 85 | local name_len = #name 86 | 87 | local errlen = 256 88 | local lowcase_buf = get_string_buf(name_len + errlen) 89 | 90 | local value_len 91 | if value == nil then 92 | value_len = 0 93 | else 94 | if type(value) ~= 'string' then 95 | value = tostring(value) 96 | end 97 | value_len = #value 98 | end 99 | 100 | local errbuf = lowcase_buf + name_len 101 | local rc = C.ngx_http_lua_ffi_var_set(r, name, name_len, lowcase_buf, 102 | value, value_len, errbuf, errlen) 103 | 104 | -- ngx.log(ngx.WARN, "rc = ", rc) 105 | 106 | if rc == 0 then -- NGX_OK 107 | return 108 | end 109 | 110 | if rc == -1 then -- NGX_ERROR 111 | return error(ffi_str(errbuf, errlen)) 112 | end 113 | end 114 | 115 | 116 | if ngx_var then 117 | local mt = getmetatable(ngx_var) 118 | if mt then 119 | mt.__index = var_get 120 | mt.__newindex = var_set 121 | end 122 | end 123 | 124 | 125 | return { 126 | version = base.version 127 | } 128 | -------------------------------------------------------------------------------- /_example/nginx/lualib/resty/core/worker.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (C) Yichun Zhang (agentzh) 2 | 3 | 4 | local ffi = require 'ffi' 5 | local base = require "resty.core.base" 6 | 7 | 8 | local C = ffi.C 9 | 10 | 11 | ffi.cdef[[ 12 | int ngx_http_lua_ffi_worker_pid(void); 13 | int ngx_http_lua_ffi_worker_exiting(void); 14 | ]] 15 | 16 | 17 | function ngx.worker.exiting() 18 | return C.ngx_http_lua_ffi_worker_exiting() ~= 0 and true or false 19 | end 20 | 21 | 22 | function ngx.worker.pid() 23 | return C.ngx_http_lua_ffi_worker_pid() 24 | end 25 | 26 | 27 | return { 28 | _VERSION = base.version 29 | } 30 | -------------------------------------------------------------------------------- /_example/nginx/lualib/resty/http.lua: -------------------------------------------------------------------------------- 1 | local http_headers = require "resty.http_headers" 2 | 3 | local ngx_socket_tcp = ngx.socket.tcp 4 | local ngx_req = ngx.req 5 | local ngx_req_socket = ngx_req.socket 6 | local ngx_req_get_headers = ngx_req.get_headers 7 | local ngx_req_get_method = ngx_req.get_method 8 | local str_gmatch = string.gmatch 9 | local str_lower = string.lower 10 | local str_upper = string.upper 11 | local str_find = string.find 12 | local str_sub = string.sub 13 | local str_gsub = string.gsub 14 | local tbl_concat = table.concat 15 | local tbl_insert = table.insert 16 | local ngx_encode_args = ngx.encode_args 17 | local ngx_re_match = ngx.re.match 18 | local ngx_log = ngx.log 19 | local ngx_DEBUG = ngx.DEBUG 20 | local ngx_ERR = ngx.ERR 21 | local ngx_NOTICE = ngx.NOTICE 22 | local ngx_var = ngx.var 23 | local co_yield = coroutine.yield 24 | local co_create = coroutine.create 25 | local co_status = coroutine.status 26 | local co_resume = coroutine.resume 27 | 28 | 29 | -- http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.1 30 | local HOP_BY_HOP_HEADERS = { 31 | ["connection"] = true, 32 | ["keep-alive"] = true, 33 | ["proxy-authenticate"] = true, 34 | ["proxy-authorization"] = true, 35 | ["te"] = true, 36 | ["trailers"] = true, 37 | ["transfer-encoding"] = true, 38 | ["upgrade"] = true, 39 | ["content-length"] = true, -- Not strictly hop-by-hop, but Nginx will deal 40 | -- with this (may send chunked for example). 41 | } 42 | 43 | 44 | -- Reimplemented coroutine.wrap, returning "nil, err" if the coroutine cannot 45 | -- be resumed. This protects user code from inifite loops when doing things like 46 | -- repeat 47 | -- local chunk, err = res.body_reader() 48 | -- if chunk then -- <-- This could be a string msg in the core wrap function. 49 | -- ... 50 | -- end 51 | -- until not chunk 52 | local co_wrap = function(func) 53 | local co = co_create(func) 54 | if not co then 55 | return nil, "could not create coroutine" 56 | else 57 | return function(...) 58 | if co_status(co) == "suspended" then 59 | return select(2, co_resume(co, ...)) 60 | else 61 | return nil, "can't resume a " .. co_status(co) .. " coroutine" 62 | end 63 | end 64 | end 65 | end 66 | 67 | 68 | local _M = { 69 | _VERSION = '0.05', 70 | } 71 | 72 | local mt = { __index = _M } 73 | 74 | 75 | local HTTP = { 76 | [1.0] = " HTTP/1.0\r\n", 77 | [1.1] = " HTTP/1.1\r\n", 78 | } 79 | 80 | local USER_AGENT = "Resty/HTTP " .. _M._VERSION .. " (Lua)" 81 | 82 | local DEFAULT_PARAMS = { 83 | method = "GET", 84 | path = "/", 85 | version = 1.1, 86 | } 87 | 88 | 89 | function _M.new(self) 90 | local sock, err = ngx_socket_tcp() 91 | if not sock then 92 | return nil, err 93 | end 94 | return setmetatable({ sock = sock, keepalive = true }, mt) 95 | end 96 | 97 | 98 | function _M.set_timeout(self, timeout) 99 | local sock = self.sock 100 | if not sock then 101 | return nil, "not initialized" 102 | end 103 | 104 | return sock:settimeout(timeout) 105 | end 106 | 107 | 108 | function _M.ssl_handshake(self, ...) 109 | local sock = self.sock 110 | if not sock then 111 | return nil, "not initialized" 112 | end 113 | 114 | return sock:sslhandshake(...) 115 | end 116 | 117 | 118 | function _M.connect(self, ...) 119 | local sock = self.sock 120 | if not sock then 121 | return nil, "not initialized" 122 | end 123 | 124 | self.host = select(1, ...) 125 | self.keepalive = true 126 | 127 | return sock:connect(...) 128 | end 129 | 130 | 131 | function _M.set_keepalive(self, ...) 132 | local sock = self.sock 133 | if not sock then 134 | return nil, "not initialized" 135 | end 136 | 137 | if self.keepalive == true then 138 | return sock:setkeepalive(...) 139 | else 140 | -- The server said we must close the connection, so we cannot setkeepalive. 141 | -- If close() succeeds we return 2 instead of 1, to differentiate between 142 | -- a normal setkeepalive() failure and an intentional close(). 143 | local res, err = sock:close() 144 | if res then 145 | return 2, "connection must be closed" 146 | else 147 | return res, err 148 | end 149 | end 150 | end 151 | 152 | 153 | function _M.get_reused_times(self) 154 | local sock = self.sock 155 | if not sock then 156 | return nil, "not initialized" 157 | end 158 | 159 | return sock:getreusedtimes() 160 | end 161 | 162 | 163 | function _M.close(self) 164 | local sock = self.sock 165 | if not sock then 166 | return nil, "not initialized" 167 | end 168 | 169 | return sock:close() 170 | end 171 | 172 | 173 | local function _should_receive_body(method, code) 174 | if method == "HEAD" then return nil end 175 | if code == 204 or code == 304 then return nil end 176 | if code >= 100 and code < 200 then return nil end 177 | return true 178 | end 179 | 180 | 181 | function _M.parse_uri(self, uri) 182 | local m, err = ngx_re_match(uri, [[^(http[s]*)://([^:/]+)(?::(\d+))?(.*)]], 183 | "jo") 184 | 185 | if not m then 186 | if err then 187 | return nil, "failed to match the uri: " .. err 188 | end 189 | 190 | return nil, "bad uri" 191 | else 192 | if not m[3] then 193 | if m[1] == "https" then 194 | m[3] = 443 195 | else 196 | m[3] = 80 197 | end 198 | end 199 | if not m[4] then m[4] = "/" end 200 | return m, nil 201 | end 202 | end 203 | 204 | 205 | local function _format_request(params) 206 | local version = params.version 207 | local headers = params.headers or {} 208 | 209 | local query = params.query or "" 210 | if query then 211 | if type(query) == "table" then 212 | query = "?" .. ngx_encode_args(query) 213 | end 214 | end 215 | 216 | -- Initialize request 217 | local req = { 218 | str_upper(params.method), 219 | " ", 220 | params.path, 221 | query, 222 | HTTP[version], 223 | -- Pre-allocate slots for minimum headers and carriage return. 224 | true, 225 | true, 226 | true, 227 | } 228 | local c = 6 -- req table index it's faster to do this inline vs table.insert 229 | 230 | -- Append headers 231 | for key, values in pairs(headers) do 232 | if type(values) ~= "table" then 233 | values = {values} 234 | end 235 | 236 | key = tostring(key) 237 | for _, value in pairs(values) do 238 | req[c] = key .. ": " .. tostring(value) .. "\r\n" 239 | c = c + 1 240 | end 241 | end 242 | 243 | -- Close headers 244 | req[c] = "\r\n" 245 | 246 | return tbl_concat(req) 247 | end 248 | 249 | 250 | local function _receive_status(sock) 251 | local line, err = sock:receive("*l") 252 | if not line then 253 | return nil, nil, err 254 | end 255 | 256 | return tonumber(str_sub(line, 10, 12)), tonumber(str_sub(line, 6, 8)) 257 | end 258 | 259 | 260 | 261 | local function _receive_headers(sock) 262 | local headers = http_headers.new() 263 | 264 | repeat 265 | local line, err = sock:receive("*l") 266 | if not line then 267 | return nil, err 268 | end 269 | 270 | for key, val in str_gmatch(line, "([%w%-]+)%s*:%s*(.+)") do 271 | if headers[key] then 272 | if type(headers[key]) ~= "table" then 273 | headers[key] = { headers[key] } 274 | end 275 | tbl_insert(headers[key], tostring(val)) 276 | else 277 | headers[key] = tostring(val) 278 | end 279 | end 280 | until str_find(line, "^%s*$") 281 | 282 | return headers, nil 283 | end 284 | 285 | 286 | local function _chunked_body_reader(sock, default_chunk_size) 287 | return co_wrap(function(max_chunk_size) 288 | local max_chunk_size = max_chunk_size or default_chunk_size 289 | local remaining = 0 290 | local length 291 | 292 | repeat 293 | -- If we still have data on this chunk 294 | if max_chunk_size and remaining > 0 then 295 | 296 | if remaining > max_chunk_size then 297 | -- Consume up to max_chunk_size 298 | length = max_chunk_size 299 | remaining = remaining - max_chunk_size 300 | else 301 | -- Consume all remaining 302 | length = remaining 303 | remaining = 0 304 | end 305 | else -- This is a fresh chunk 306 | 307 | -- Receive the chunk size 308 | local str, err = sock:receive("*l") 309 | if not str then 310 | co_yield(nil, err) 311 | end 312 | 313 | length = tonumber(str, 16) 314 | 315 | if not length then 316 | co_yield(nil, "unable to read chunksize") 317 | end 318 | 319 | if max_chunk_size and length > max_chunk_size then 320 | -- Consume up to max_chunk_size 321 | remaining = length - max_chunk_size 322 | length = max_chunk_size 323 | end 324 | end 325 | 326 | if length > 0 then 327 | local str, err = sock:receive(length) 328 | if not str then 329 | co_yield(nil, err) 330 | end 331 | 332 | max_chunk_size = co_yield(str) or default_chunk_size 333 | 334 | -- If we're finished with this chunk, read the carriage return. 335 | if remaining == 0 then 336 | sock:receive(2) -- read \r\n 337 | end 338 | else 339 | -- Read the last (zero length) chunk's carriage return 340 | sock:receive(2) -- read \r\n 341 | end 342 | 343 | until length == 0 344 | end) 345 | end 346 | 347 | 348 | local function _body_reader(sock, content_length, default_chunk_size) 349 | return co_wrap(function(max_chunk_size) 350 | local max_chunk_size = max_chunk_size or default_chunk_size 351 | 352 | if not content_length and max_chunk_size then 353 | -- We have no length, but wish to stream. 354 | -- HTTP 1.0 with no length will close connection, so read chunks to the end. 355 | repeat 356 | local str, err, partial = sock:receive(max_chunk_size) 357 | if not str and err == "closed" then 358 | max_chunk_size = co_yield(partial, err) or default_chunk_size 359 | end 360 | 361 | max_chunk_size = co_yield(str) or default_chunk_size 362 | until not str 363 | 364 | elseif not content_length then 365 | -- We have no length but don't wish to stream. 366 | -- HTTP 1.0 with no length will close connection, so read to the end. 367 | co_yield(sock:receive("*a")) 368 | 369 | elseif not max_chunk_size then 370 | -- We have a length and potentially keep-alive, but want everything. 371 | co_yield(sock:receive(content_length)) 372 | 373 | else 374 | -- We have a length and potentially a keep-alive, and wish to stream 375 | -- the response. 376 | local received = 0 377 | repeat 378 | local length = max_chunk_size 379 | if received + length > content_length then 380 | length = content_length - received 381 | end 382 | 383 | if length > 0 then 384 | local str, err = sock:receive(length) 385 | if not str then 386 | max_chunk_size = co_yield(nil, err) or default_chunk_size 387 | end 388 | received = received + length 389 | 390 | max_chunk_size = co_yield(str) or default_chunk_size 391 | end 392 | 393 | until length == 0 394 | end 395 | end) 396 | end 397 | 398 | 399 | local function _no_body_reader() 400 | return nil 401 | end 402 | 403 | 404 | local function _read_body(res) 405 | local reader = res.body_reader 406 | 407 | if not reader then 408 | -- Most likely HEAD or 304 etc. 409 | return nil, "no body to be read" 410 | end 411 | 412 | local chunks = {} 413 | local c = 1 414 | 415 | local chunk, err 416 | repeat 417 | chunk, err = reader() 418 | 419 | if err then 420 | return nil, err, tbl_concat(chunks) -- Return any data so far. 421 | end 422 | if chunk then 423 | chunks[c] = chunk 424 | c = c + 1 425 | end 426 | until not chunk 427 | 428 | return tbl_concat(chunks) 429 | end 430 | 431 | 432 | local function _trailer_reader(sock) 433 | return co_wrap(function() 434 | co_yield(_receive_headers(sock)) 435 | end) 436 | end 437 | 438 | 439 | local function _read_trailers(res) 440 | local reader = res.trailer_reader 441 | if not reader then 442 | return nil, "no trailers" 443 | end 444 | 445 | local trailers = reader() 446 | setmetatable(res.headers, { __index = trailers }) 447 | end 448 | 449 | 450 | local function _send_body(sock, body) 451 | if type(body) == 'function' then 452 | repeat 453 | local chunk, err, partial = body() 454 | 455 | if chunk then 456 | local ok,err = sock:send(chunk) 457 | 458 | if not ok then 459 | return nil, err 460 | end 461 | elseif err ~= nil then 462 | return nil, err, partial 463 | end 464 | 465 | until chunk == nil 466 | elseif body ~= nil then 467 | local bytes, err = sock:send(body) 468 | 469 | if not bytes then 470 | return nil, err 471 | end 472 | end 473 | return true, nil 474 | end 475 | 476 | 477 | local function _handle_continue(sock, body) 478 | local status, version, err = _receive_status(sock) 479 | if not status then 480 | return nil, err 481 | end 482 | 483 | -- Only send body if we receive a 100 Continue 484 | if status == 100 then 485 | local ok, err = sock:receive("*l") -- Read carriage return 486 | if not ok then 487 | return nil, err 488 | end 489 | _send_body(sock, body) 490 | end 491 | return status, version, err 492 | end 493 | 494 | 495 | function _M.send_request(self, params) 496 | -- Apply defaults 497 | setmetatable(params, { __index = DEFAULT_PARAMS }) 498 | 499 | local sock = self.sock 500 | local body = params.body 501 | local headers = http_headers.new() 502 | 503 | local params_headers = params.headers 504 | if params_headers then 505 | -- We assign one by one so that the metatable can handle case insensitivity 506 | -- for us. You can blame the spec for this inefficiency. 507 | for k,v in pairs(params_headers) do 508 | headers[k] = v 509 | end 510 | end 511 | 512 | -- Ensure minimal headers are set 513 | if type(body) == 'string' and not headers["Content-Length"] then 514 | headers["Content-Length"] = #body 515 | end 516 | if not headers["Host"] then 517 | headers["Host"] = self.host 518 | end 519 | if not headers["User-Agent"] then 520 | headers["User-Agent"] = USER_AGENT 521 | end 522 | if params.version == 1.0 and not headers["Connection"] then 523 | headers["Connection"] = "Keep-Alive" 524 | end 525 | 526 | params.headers = headers 527 | 528 | -- Format and send request 529 | local req = _format_request(params) 530 | ngx_log(ngx_DEBUG, "\n", req) 531 | local bytes, err = sock:send(req) 532 | 533 | if not bytes then 534 | return nil, err 535 | end 536 | 537 | -- Send the request body, unless we expect: continue, in which case 538 | -- we handle this as part of reading the response. 539 | if headers["Expect"] ~= "100-continue" then 540 | local ok, err, partial = _send_body(sock, body) 541 | if not ok then 542 | return nil, err, partial 543 | end 544 | end 545 | 546 | return true 547 | end 548 | 549 | 550 | function _M.read_response(self, params) 551 | local sock = self.sock 552 | 553 | local status, version, err 554 | 555 | -- If we expect: continue, we need to handle this, sending the body if allowed. 556 | -- If we don't get 100 back, then status is the actual status. 557 | if params.headers["Expect"] == "100-continue" then 558 | local _status, _version, _err = _handle_continue(sock, params.body) 559 | if not _status then 560 | return nil, _err 561 | elseif _status ~= 100 then 562 | status, version, err = _status, _version, _err 563 | end 564 | end 565 | 566 | -- Just read the status as normal. 567 | if not status then 568 | status, version, err = _receive_status(sock) 569 | if not status then 570 | return nil, err 571 | end 572 | end 573 | 574 | 575 | local res_headers, err = _receive_headers(sock) 576 | if not res_headers then 577 | return nil, err 578 | end 579 | 580 | -- Determine if we should keepalive or not. 581 | local ok, connection = pcall(str_lower, res_headers["Connection"]) 582 | if ok then 583 | if (version == 1.1 and connection == "close") or 584 | (version == 1.0 and connection ~= "keep-alive") then 585 | self.keepalive = false 586 | end 587 | end 588 | 589 | local body_reader = _no_body_reader 590 | local trailer_reader, err = nil, nil 591 | local has_body = false 592 | 593 | -- Receive the body_reader 594 | if _should_receive_body(params.method, status) then 595 | local ok, encoding = pcall(str_lower, res_headers["Transfer-Encoding"]) 596 | if ok and version == 1.1 and encoding == "chunked" then 597 | body_reader, err = _chunked_body_reader(sock) 598 | has_body = true 599 | else 600 | 601 | local ok, length = pcall(tonumber, res_headers["Content-Length"]) 602 | if ok then 603 | body_reader, err = _body_reader(sock, length) 604 | has_body = true 605 | end 606 | end 607 | end 608 | 609 | if res_headers["Trailer"] then 610 | trailer_reader, err = _trailer_reader(sock) 611 | end 612 | 613 | if err then 614 | return nil, err 615 | else 616 | return { 617 | status = status, 618 | headers = res_headers, 619 | has_body = has_body, 620 | body_reader = body_reader, 621 | read_body = _read_body, 622 | trailer_reader = trailer_reader, 623 | read_trailers = _read_trailers, 624 | } 625 | end 626 | end 627 | 628 | 629 | function _M.request(self, params) 630 | local res, err = self:send_request(params) 631 | if not res then 632 | return res, err 633 | else 634 | return self:read_response(params) 635 | end 636 | end 637 | 638 | 639 | function _M.request_pipeline(self, requests) 640 | for i, params in ipairs(requests) do 641 | if params.headers and params.headers["Expect"] == "100-continue" then 642 | return nil, "Cannot pipeline request specifying Expect: 100-continue" 643 | end 644 | 645 | local res, err = self:send_request(params) 646 | if not res then 647 | return res, err 648 | end 649 | end 650 | 651 | local responses = {} 652 | for i, params in ipairs(requests) do 653 | responses[i] = setmetatable({ 654 | params = params, 655 | response_read = false, 656 | }, { 657 | -- Read each actual response lazily, at the point the user tries 658 | -- to access any of the fields. 659 | __index = function(t, k) 660 | local res, err 661 | if t.response_read == false then 662 | res, err = _M.read_response(self, t.params) 663 | t.response_read = true 664 | 665 | if not res then 666 | ngx_log(ngx_ERR, err) 667 | else 668 | for rk, rv in pairs(res) do 669 | t[rk] = rv 670 | end 671 | end 672 | end 673 | return rawget(t, k) 674 | end, 675 | }) 676 | end 677 | return responses 678 | end 679 | 680 | 681 | function _M.request_uri(self, uri, params) 682 | if not params then params = {} end 683 | 684 | local parsed_uri, err = self:parse_uri(uri) 685 | if not parsed_uri then 686 | return nil, err 687 | end 688 | 689 | local scheme, host, port, path = unpack(parsed_uri) 690 | if not params.path then params.path = path end 691 | 692 | local c, err = self:connect(host, port) 693 | if not c then 694 | return nil, err 695 | end 696 | 697 | if scheme == "https" then 698 | local verify = true 699 | if params.ssl_verify == false then 700 | verify = false 701 | end 702 | local ok, err = self:ssl_handshake(nil, host, verify) 703 | if not ok then 704 | return nil, err 705 | end 706 | end 707 | 708 | local res, err = self:request(params) 709 | if not res then 710 | return nil, err 711 | end 712 | 713 | local body, err = res:read_body() 714 | if not body then 715 | return nil, err 716 | end 717 | 718 | res.body = body 719 | 720 | local ok, err = self:set_keepalive() 721 | if not ok then 722 | ngx_log(ngx_ERR, err) 723 | end 724 | 725 | return res, nil 726 | end 727 | 728 | 729 | function _M.get_client_body_reader(self, chunksize) 730 | local chunksize = chunksize or 65536 731 | local ok, sock, err = pcall(ngx_req_socket) 732 | 733 | if not ok then 734 | return nil, sock -- pcall err 735 | end 736 | 737 | if not sock then 738 | if err == "no body" then 739 | return nil 740 | else 741 | return nil, err 742 | end 743 | end 744 | 745 | local headers = ngx_req_get_headers() 746 | local length = headers.content_length 747 | local encoding = headers.transfer_encoding 748 | if length then 749 | return _body_reader(sock, tonumber(length), chunksize) 750 | elseif encoding and str_lower(encoding) == 'chunked' then 751 | -- Not yet supported by ngx_lua but should just work... 752 | return _chunked_body_reader(sock, chunksize) 753 | else 754 | return nil 755 | end 756 | end 757 | 758 | 759 | function _M.proxy_request(self, chunksize) 760 | return self:request{ 761 | method = ngx_req_get_method(), 762 | path = ngx_var.uri .. ngx_var.is_args .. (ngx_var.query_string or ""), 763 | body = self:get_client_body_reader(chunksize), 764 | headers = ngx_req_get_headers(), 765 | } 766 | end 767 | 768 | 769 | function _M.proxy_response(self, response, chunksize) 770 | if not response then 771 | ngx_log(ngx_ERR, "no response provided") 772 | return 773 | end 774 | 775 | ngx.status = response.status 776 | 777 | -- Filter out hop-by-hop headeres 778 | for k,v in pairs(response.headers) do 779 | if not HOP_BY_HOP_HEADERS[str_lower(k)] then 780 | ngx.header[k] = v 781 | end 782 | end 783 | 784 | local reader = response.body_reader 785 | repeat 786 | local chunk, err = reader(chunksize) 787 | if err then 788 | ngx_log(ngx_ERR, err) 789 | break 790 | end 791 | 792 | if chunk then 793 | ngx.print(chunk) 794 | end 795 | until not chunk 796 | end 797 | 798 | 799 | return _M 800 | -------------------------------------------------------------------------------- /_example/nginx/lualib/resty/http_headers.lua: -------------------------------------------------------------------------------- 1 | local rawget, rawset, setmetatable = 2 | rawget, rawset, setmetatable 3 | 4 | local str_gsub = string.gsub 5 | local str_lower = string.lower 6 | 7 | 8 | local _M = { 9 | _VERSION = '0.01', 10 | } 11 | 12 | 13 | -- Returns an empty headers table with internalised case normalisation. 14 | -- Supports the same cases as in ngx_lua: 15 | -- 16 | -- headers.content_length 17 | -- headers["content-length"] 18 | -- headers["Content-Length"] 19 | function _M.new(self) 20 | local mt = { 21 | normalised = {}, 22 | } 23 | 24 | 25 | mt.__index = function(t, k) 26 | local k_hyphened = str_gsub(k, "_", "-") 27 | local matched = rawget(t, k) 28 | if matched then 29 | return matched 30 | else 31 | local k_normalised = str_lower(k_hyphened) 32 | return rawget(t, mt.normalised[k_normalised]) 33 | end 34 | end 35 | 36 | 37 | -- First check the normalised table. If there's no match (first time) add an entry for 38 | -- our current case in the normalised table. This is to preserve the human (prettier) case 39 | -- instead of outputting lowercased header names. 40 | -- 41 | -- If there's a match, we're being updated, just with a different case for the key. We use 42 | -- the normalised table to give us the original key, and perorm a rawset(). 43 | mt.__newindex = function(t, k, v) 44 | -- we support underscore syntax, so always hyphenate. 45 | local k_hyphened = str_gsub(k, "_", "-") 46 | 47 | -- lowercase hyphenated is "normalised" 48 | local k_normalised = str_lower(k_hyphened) 49 | 50 | if not mt.normalised[k_normalised] then 51 | mt.normalised[k_normalised] = k_hyphened 52 | rawset(t, k_hyphened, v) 53 | else 54 | rawset(t, mt.normalised[k_normalised], v) 55 | end 56 | end 57 | 58 | return setmetatable({}, mt) 59 | end 60 | 61 | 62 | return _M 63 | -------------------------------------------------------------------------------- /_example/nginx/lualib/resty/shcache.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (C) 2013 Matthieu Tourne 2 | -- @author Matthieu Tourne 3 | 4 | -- small overlay over shdict, smart cache load mechanism 5 | 6 | -- TODO (rdsharma): remove this line once we have tracked down bugs 7 | jit.off(true, true) 8 | 9 | local _M = {} 10 | 11 | local resty_lock = require("resty.lock") 12 | local conf = {} 13 | 14 | local DEBUG = conf.DEBUG or false 15 | 16 | -- defaults in secs 17 | local DEFAULT_POSITIVE_TTL = 10 -- cache for, successful lookup 18 | local DEFAULT_NEGATIVE_TTL = 2 -- cache for, failed lookup 19 | local DEFAULT_ACTUALIZE_TTL = 2 -- stale data, actualize data for 20 | 21 | -- default lock options, in secs 22 | local DEFAULT_LOCK_EXPTIME = 1 -- max wait if failing to call unlock() 23 | local DEFAULT_LOCK_TIMEOUT = 0.5 -- max waiting time of lock() 24 | local DEFAULT_LOCK_MAXSTEP = 0.1 -- max sleeping interval 25 | 26 | if conf then 27 | DEFAULT_NEGATIVE_TTL = conf.DEFAULT_NEGATIVE_TTL or DEFAULT_NEGATIVE_TTL 28 | DEFAULT_ACTUALIZE_TTL = conf.DEFAULT_ACTUALIZE_TTL or DEFAULT_ACTUALIZE_TTL 29 | end 30 | 31 | local bit = require("bit") 32 | local band = bit.band 33 | local bor = bit.bor 34 | local st_format = string.format 35 | 36 | -- there are only really 5 states total 37 | -- is_stale is_neg is_from_cache 38 | local MISS_STATE = 0 -- 0 0 0 39 | local HIT_POSITIVE_STATE = 1 -- 0 0 1 40 | local HIT_NEGATIVE_STATE = 3 -- 0 1 1 41 | local STALE_POSITIVE_STATE = 5 -- 1 0 1 42 | 43 | -- stale negative doesn't really make sense, use HIT_NEGATIVE instead 44 | -- local STALE_NEGATIVE_STATE = 7 -- 1 1 1 45 | 46 | -- xor to set 47 | local NEGATIVE_FLAG = 2 48 | local STALE_FLAG = 4 49 | 50 | local STATES = { 51 | [MISS_STATE] = 'MISS', 52 | [HIT_POSITIVE_STATE] = 'HIT', 53 | [HIT_NEGATIVE_STATE] = 'HIT_NEGATIVE', 54 | [STALE_POSITIVE_STATE] = 'STALE', 55 | -- [STALE_NEGATIVE_STATE] = 'STALE_NEGATIVE', 56 | } 57 | 58 | local function get_status(flags) 59 | return STATES[flags] or st_format('UNDEF (0x%x)', flags) 60 | end 61 | 62 | local EMPTY_DATA = '_EMPTY_' 63 | 64 | -- install debug functions 65 | if DEBUG then 66 | local resty_lock_lock = resty_lock.lock 67 | 68 | resty_lock.lock = function (...) 69 | local _, key = ... 70 | print("lock key: ", tostring(key)) 71 | return resty_lock_lock(...) 72 | end 73 | 74 | local resty_lock_unlock = resty_lock.unlock 75 | 76 | resty_lock.unlock = function (...) 77 | print("unlock") 78 | return resty_lock_unlock(...) 79 | end 80 | end 81 | 82 | 83 | -- store the object in the context 84 | -- useful for debugging and tracking cache status 85 | local function _store_object(self, name) 86 | if DEBUG then 87 | print('storing shcache: ', name, ' into ngx.ctx') 88 | end 89 | 90 | local ngx_ctx = ngx.ctx 91 | 92 | if not ngx_ctx.shcache then 93 | ngx_ctx.shcache = {} 94 | end 95 | ngx_ctx.shcache[name] = self 96 | end 97 | 98 | local obj_mt = { 99 | __index = _M, 100 | } 101 | 102 | -- default function for callbacks.encode / decode. 103 | local function _identity(data) 104 | return data 105 | end 106 | 107 | -- shdict: ngx.shared.DICT, created by the lua_shared_dict directive 108 | -- callbacks: see shcache state machine for user defined functions 109 | -- * callbacks.external_lookup is required 110 | -- * callbacks.external_lookup_arg is the (opaque) user argument for 111 | -- external_lookup 112 | -- * callbacks.encode : optional encoding before saving to shmem 113 | -- * callbacks.decode : optional decoding when retreiving from shmem 114 | -- opts: 115 | -- 116 | -- The TTL values are passed directly to the ngx.shared.DICT.set 117 | -- function (see 118 | -- http://wiki.nginx.org/HttpLuaModule#ngx.shared.DICT.set for the 119 | -- documentation). But note that the value 0 does not mean "do not 120 | -- cache at all", it means "no expiry time". So, for example, setting 121 | -- opts.negative_ttl to 0 means that a failed lookup will be cached 122 | -- forever. 123 | -- 124 | -- * opts.positive_ttl : save a valid external lookup for, in seconds 125 | -- * opts.negative_ttl : save a invalid lookup for, in seconds 126 | -- * opts.actualize_ttl : re-actualize a stale record for, in seconds 127 | -- 128 | -- * opts.lock_options : set option to lock see: 129 | -- http://github.com/agentzh/lua-resty-lock 130 | -- for more details. 131 | -- * opts.locks_shdict : specificy the name of the shdict containing 132 | -- the locks 133 | -- (useful if you might have locks key collisions) 134 | -- uses "locks" by default. 135 | -- * opts.name : if shcache object is named, it will automatically 136 | -- register itself in ngx.ctx.shcache 137 | -- (useful for logging). 138 | local function new(self, shdict, callbacks, opts) 139 | if not shdict then 140 | return nil, "shdict does not exist" 141 | end 142 | 143 | -- check that callbacks.external_lookup is set 144 | if not callbacks then 145 | return nil, "no callbacks argument" 146 | end 147 | 148 | local ext_lookup = callbacks.external_lookup 149 | if not ext_lookup then 150 | return nil, "no external_lookup callback specified" 151 | end 152 | 153 | local ext_udata = callbacks.external_lookup_arg 154 | 155 | local encode = callbacks.encode 156 | if not encode then 157 | encode = _identity 158 | end 159 | 160 | local decode = callbacks.decode 161 | if not decode then 162 | decode = _identity 163 | end 164 | 165 | local opts = opts or {} 166 | 167 | -- merge default lock options with the ones passed to new() 168 | local lock_options = opts.lock_options or {} 169 | if not lock_options.exptime then 170 | lock_options.exptime = DEFAULT_LOCK_EXPTIME 171 | end 172 | if not lock_options.timeout then 173 | lock_options.timeout = DEFAULT_LOCK_TIMEOUT 174 | end 175 | if not lock_options.max_step then 176 | lock_options.max_step = DEFAULT_LOCK_MAXSTEP 177 | end 178 | 179 | local name = opts.name 180 | 181 | local obj = { 182 | shdict = shdict, 183 | 184 | encode = encode, 185 | decode = decode, 186 | ext_lookup = ext_lookup, 187 | ext_udata = ext_udata, 188 | 189 | positive_ttl = opts.positive_ttl or DEFAULT_POSITIVE_TTL, 190 | negative_ttl = opts.negative_ttl or DEFAULT_NEGATIVE_TTL, 191 | 192 | -- ttl to actualize stale data to 193 | actualize_ttl = opts.actualize_ttl or DEFAULT_ACTUALIZE_TTL, 194 | 195 | lock_options = lock_options, 196 | 197 | locks_shdict = opts.lock_shdict or "locks", 198 | 199 | -- positive ttl specified by external lookup function 200 | lookup_ttl = nil, 201 | 202 | -- STATUS -- 203 | 204 | from_cache = false, 205 | cache_status = 'UNDEF', 206 | cache_state = MISS_STATE, 207 | lock_status = 'NO_LOCK', 208 | 209 | -- shdict:set() pushed out another value 210 | forcible_set = false, 211 | 212 | -- cache hit on second attempt (post lock) 213 | hit2 = false, 214 | 215 | name = name, 216 | } 217 | 218 | local locks = ngx.shared[obj.locks_shdict] 219 | 220 | -- check for existence, locks is not directly used 221 | if not locks then 222 | ngx.log(ngx.CRIT, 'shared mem locks is missing.\n', 223 | '## add to you lua conf: lua_shared_dict locks 5M; ##') 224 | return nil 225 | end 226 | 227 | local self = setmetatable(obj, obj_mt) 228 | 229 | -- if the shcache object is named 230 | -- keep track of the object in the context 231 | -- (useful for gathering stats at log phase) 232 | if name then 233 | _store_object(self, name) 234 | end 235 | 236 | return self 237 | end 238 | _M.new = new 239 | 240 | local function _enter_critical_section(self, key) 241 | if DEBUG then 242 | print('Entering critical section, shcache: ', self.name or '') 243 | end 244 | 245 | self.in_critical_section = true 246 | 247 | local critical_sections = ngx.ctx.critical_sections 248 | if not critical_sections then 249 | critical_sections = { 250 | count = 1, 251 | die = false, 252 | workers = { [self] = key }, 253 | } 254 | ngx.ctx.critical_sections = critical_sections 255 | return 256 | end 257 | 258 | -- TODO (mtourne): uncomment when ngx.thread.exit api exists. 259 | 260 | -- prevents new thread to enter a critical section if we're set to die. 261 | -- if critical_sections.die then 262 | -- ngx.thread.exit() 263 | -- end 264 | 265 | critical_sections.count = critical_sections.count + 1 266 | critical_sections.workers[self] = key 267 | 268 | if DEBUG then 269 | print('critical sections count: ', critical_sections.count) 270 | end 271 | end 272 | 273 | local function _exit_critical_section(self) 274 | if DEBUG then 275 | print('Leaving critical section, shcache: ', self.name or '') 276 | end 277 | 278 | local critical_sections = ngx.ctx.critical_sections 279 | if not critical_sections then 280 | ngx.log(ngx.ERR, 'weird state: ngx.ctx.critical_sections missing') 281 | return 282 | end 283 | 284 | critical_sections.count = critical_sections.count - 1 285 | critical_sections.workers[self] = nil 286 | 287 | if DEBUG then 288 | print('die: ', critical_sections.die, ', count: ', 289 | critical_sections.count) 290 | end 291 | 292 | local status = critical_sections.die 293 | if status and critical_sections.count <= 0 then 294 | -- safe to exit. 295 | if DEBUG then 296 | print('Last critical section, exiting.') 297 | end 298 | ngx.exit(status) 299 | end 300 | end 301 | 302 | -- acquire a lock 303 | local function _get_lock(self) 304 | local lock = self.lock 305 | if not lock then 306 | lock = resty_lock:new(self.locks_shdict, self.lock_options) 307 | self.lock = lock 308 | end 309 | return lock 310 | end 311 | 312 | -- remove the lock if there is any 313 | local function _unlock(self) 314 | local lock = self.lock 315 | if lock then 316 | local ok, err = lock:unlock() 317 | if not ok then 318 | ngx.log(ngx.ERR, "failed to unlock :" , err) 319 | end 320 | self.lock = nil 321 | end 322 | end 323 | 324 | local function _return(self, data, flags) 325 | -- make sure we remove the locks if any before returning data 326 | _unlock(self) 327 | 328 | -- set cache status 329 | local cache_status = get_status(self.cache_state) 330 | 331 | if cache_status == 'MISS' and not data then 332 | cache_status = 'NO_DATA' 333 | end 334 | 335 | self.cache_status = cache_status 336 | 337 | if self.in_critical_section then 338 | -- data has been cached, and lock on key is removed 339 | -- this is the end of the critical section. 340 | _exit_critical_section(self) 341 | self.in_critical_section = false 342 | end 343 | 344 | return data, self.from_cache 345 | end 346 | 347 | local function _set(self, key, data, ttl, flags) 348 | if DEBUG then 349 | print("saving key: ", key, ", for: ", ttl) 350 | end 351 | 352 | local ok, err, forcible = self.shdict:set(key, data, ttl, flags) 353 | 354 | self.forcible_set = forcible 355 | 356 | if not ok then 357 | ngx.log(ngx.ERR, 'failed to set key: ', key, ', err: ', err) 358 | end 359 | 360 | return ok 361 | end 362 | 363 | -- check if the data returned by :get() is considered empty 364 | local function _is_empty(data, flags) 365 | return flags and band(flags, NEGATIVE_FLAG) and data == EMPTY_DATA 366 | end 367 | 368 | -- save positive, encode the data if needed before :set() 369 | local function _save_positive(self, key, data, ttl) 370 | if DEBUG then 371 | if ttl then 372 | print("key: ", key, ". save positive, lookup ttl: ", ttl) 373 | else 374 | print("key: ", key, ". save positive, ttl: ", self.positive_ttl) 375 | end 376 | end 377 | 378 | data = self.encode(data) 379 | 380 | if ttl then 381 | self.lookup_ttl = ttl 382 | return _set(self, key, data, ttl, HIT_POSITIVE_STATE) 383 | end 384 | 385 | return _set(self, key, data, self.positive_ttl, HIT_POSITIVE_STATE) 386 | end 387 | 388 | -- save negative, no encoding required (no data actually saved) 389 | local function _save_negative(self, key) 390 | if DEBUG then 391 | print("key: ", key, ". save negative, ttl: ", self.negative_ttl) 392 | end 393 | return _set(self, key, EMPTY_DATA, self.negative_ttl, HIT_NEGATIVE_STATE) 394 | end 395 | 396 | -- save actualize, will boost a stale record to a live one 397 | local function _save_actualize(self, key, data, flags) 398 | local new_flags = bor(flags, STALE_FLAG) 399 | 400 | if DEBUG then 401 | print("key: ", key, ". save actualize, ttl: ", self.actualize_ttl, 402 | ". new state: ", get_status(new_flags)) 403 | end 404 | 405 | _set(self, key, data, self.actualize_ttl, new_flags) 406 | return new_flags 407 | end 408 | 409 | local function _process_cached_data(self, data, flags) 410 | if DEBUG then 411 | print("data: ", data, st_format(", flags: %x", flags)) 412 | end 413 | 414 | self.cache_state = flags 415 | self.from_cache = true 416 | 417 | if _is_empty(data, flags) then 418 | -- empty cached data 419 | return nil 420 | else 421 | return self.decode(data) 422 | end 423 | end 424 | 425 | -- wrapper to get data from the shdict 426 | local function _get(self, key) 427 | -- always call get_stale() as it does not free element 428 | -- like get does on each call 429 | local data, flags, stale = self.shdict:get_stale(key) 430 | 431 | if data and stale then 432 | if DEBUG then 433 | print("found stale data for key : ", key) 434 | end 435 | 436 | self.stale_data = { data, flags } 437 | 438 | return nil, nil 439 | end 440 | 441 | return data, flags 442 | end 443 | 444 | local function _get_stale(self) 445 | local stale_data = self.stale_data 446 | if stale_data then 447 | return unpack(stale_data) 448 | end 449 | 450 | return nil, nil 451 | end 452 | 453 | local function load(self, key) 454 | -- start: check for existing cache 455 | -- clear previous data stored in stale_data 456 | self.stale_data = nil 457 | local data, flags = _get(self, key) 458 | 459 | -- hit: process_cache_hit 460 | if data then 461 | data = _process_cached_data(self, data, flags) 462 | return _return(self, data) 463 | end 464 | 465 | -- miss: set lock 466 | 467 | -- lock: set a lock before performing external lookup 468 | local lock = _get_lock(self) 469 | local elapsed, err = lock:lock(key) 470 | 471 | if not elapsed then 472 | -- failed to acquire lock, still proceed normally to external_lookup 473 | -- unlock() might fail. 474 | local timeout 475 | local opts = self.lock_options 476 | if opts then 477 | timeout = opts.timeout 478 | end 479 | ngx.log(ngx.ERR, "failed to acquire the lock on key \"", key, "\" for ", 480 | timeout, " sec: ", err) 481 | self.lock_status = 'ERROR' 482 | -- _unlock won't try to unlock() without a valid lock 483 | self.lock = nil 484 | else 485 | -- lock acquired successfuly 486 | 487 | if elapsed > 0 then 488 | 489 | -- elapsed > 0 => waited lock (other thread might have :set() the data) 490 | -- (more likely to get a HIT on cache_load 2) 491 | self.lock_status = 'WAITED' 492 | 493 | else 494 | 495 | -- elapsed == 0 => immediate lock 496 | -- it is less likely to get a HIT on cache_load 2 497 | -- but still perform it (race condition cases) 498 | self.lock_status = 'IMMEDIATE' 499 | end 500 | 501 | -- perform cache_load 2 502 | data, flags = _get(self, key) 503 | if data then 504 | -- hit2 : process cache hit 505 | 506 | self.hit2 = true 507 | 508 | -- unlock before de-serializing cached data 509 | _unlock(self) 510 | data = _process_cached_data(self, data, flags) 511 | return _return(self, data) 512 | end 513 | 514 | -- continue to external lookup 515 | end 516 | 517 | -- mark the beginning of the critical section 518 | -- (we want to wait for the data to be looked up and cached successfully) 519 | _enter_critical_section(self, key) 520 | 521 | -- perform external lookup 522 | local data, err, ttl = self.ext_lookup(self.ext_udata) 523 | 524 | if data then 525 | -- succ: save positive and return the data 526 | _save_positive(self, key, data, ttl) 527 | return _return(self, data) 528 | else 529 | ngx.log(ngx.WARN, 'external lookup failed: ', err) 530 | end 531 | 532 | -- external lookup failed 533 | -- attempt to load stale data 534 | data, flags = _get_stale(self) 535 | if data and not _is_empty(data, flags) then 536 | -- hit_stale + valid (positive) data 537 | 538 | flags = _save_actualize(self, key, data, flags) 539 | -- unlock before de-serializing data 540 | _unlock(self) 541 | data = _process_cached_data(self, data, flags) 542 | return _return(self, data) 543 | end 544 | 545 | if DEBUG and data then 546 | -- there is data, but it failed _is_empty() => stale negative data 547 | print('STALE_NEGATIVE data => cache as a new HIT_NEGATIVE') 548 | end 549 | 550 | -- nothing has worked, save negative and return empty 551 | _save_negative(self, key) 552 | return _return(self, nil) 553 | end 554 | _M.load = load 555 | 556 | return _M 557 | -------------------------------------------------------------------------------- /_example/nginx/lualib/resty/template.lua: -------------------------------------------------------------------------------- 1 | local setmetatable = setmetatable 2 | local tostring = tostring 3 | local setfenv = setfenv 4 | local concat = table.concat 5 | local assert = assert 6 | local write = io.write 7 | local open = io.open 8 | local load = load 9 | local type = type 10 | local dump = string.dump 11 | local find = string.find 12 | local gsub = string.gsub 13 | local byte = string.byte 14 | local sub = string.sub 15 | 16 | local HTML_ENTITIES = { 17 | ["&"] = "&", 18 | ["<"] = "<", 19 | [">"] = ">", 20 | ['"'] = """, 21 | ["'"] = "'", 22 | ["/"] = "/" 23 | } 24 | 25 | local CODE_ENTITIES = { 26 | ["{"] = "{", 27 | ["}"] = "}", 28 | ["&"] = "&", 29 | ["<"] = "<", 30 | [">"] = ">", 31 | ['"'] = """, 32 | ["'"] = "'", 33 | ["/"] = "/" 34 | } 35 | 36 | local ok, newtab = pcall(require, "table.new") 37 | if not ok then newtab = function() return {} end end 38 | 39 | local caching, ngx_var, ngx_capture, ngx_null = true 40 | local template = newtab(0, 13); 41 | 42 | template._VERSION = "1.5-dev" 43 | template.cache = {} 44 | template.concat = concat 45 | 46 | local function rpos(view, s) 47 | while s > 0 do 48 | local c = sub(view, s, s) 49 | if c == " " or c == "\t" or c == "\0" or c == "\x0B" then 50 | s = s - 1 51 | else 52 | break; 53 | end 54 | end 55 | return s 56 | end 57 | 58 | local function read_file(path) 59 | local file = open(path, "rb") 60 | if not file then return nil end 61 | local content = file:read "*a" 62 | file:close() 63 | return content 64 | end 65 | 66 | local function load_lua(path) 67 | return read_file(path) or path 68 | end 69 | 70 | local function load_ngx(path) 71 | local file, location = path, ngx_var.template_location 72 | if sub(file, 1) == "/" then file = sub(file, 2) end 73 | if location and location ~= "" then 74 | if sub(location, -1) == "/" then location = sub(location, 1, -2) end 75 | local res = ngx_capture(location .. '/' .. file) 76 | if res.status == 200 then return res.body end 77 | end 78 | local root = ngx_var.template_root or ngx_var.document_root 79 | if sub(root, -1) == "/" then root = sub(root, 1, -2) end 80 | return read_file(root .. "/" .. file) or path 81 | end 82 | 83 | if ngx then 84 | template.print = ngx.print or write 85 | template.load = load_ngx 86 | ngx_var, ngx_capture, ngx_null = ngx.var, ngx.location.capture, ngx.null 87 | else 88 | template.print = write 89 | template.load = load_lua 90 | end 91 | 92 | local load_chunk 93 | 94 | if _VERSION == "Lua 5.1" then 95 | local context = { __index = function(t, k) 96 | return t.context[k] or t.template[k] or _G[k] 97 | end } 98 | if jit then 99 | load_chunk = function(view) 100 | return assert(load(view, nil, "tb", setmetatable({ template = template }, context))) 101 | end 102 | else 103 | load_chunk = function(view) 104 | local func = assert(loadstring(view)) 105 | setfenv(func, setmetatable({ template = template }, context)) 106 | return func 107 | end 108 | end 109 | else 110 | local context = { __index = function(t, k) 111 | return t.context[k] or t.template[k] or _ENV[k] 112 | end } 113 | load_chunk = function(view) 114 | return assert(load(view, nil, "tb", setmetatable({ template = template }, context))) 115 | end 116 | end 117 | 118 | function template.caching(enable) 119 | if enable ~= nil then caching = enable == true end 120 | return caching 121 | end 122 | 123 | function template.output(s) 124 | if s == nil or s == ngx_null then return "" end 125 | if type(s) == "function" then return template.output(s()) end 126 | return tostring(s) 127 | end 128 | 129 | function template.escape(s, c) 130 | if type(s) == "string" then 131 | if c then return gsub(s, "[}{\">/<'&]", CODE_ENTITIES) end 132 | return gsub(s, "[\">/<'&]", HTML_ENTITIES) 133 | end 134 | return template.output(s) 135 | end 136 | 137 | function template.new(view, layout) 138 | assert(view, "view was not provided for template.new(view, layout).") 139 | local render, compile = template.render, template.compile 140 | if layout then 141 | return setmetatable({ render = function(self, context) 142 | local context = context or self 143 | context.blocks = context.blocks or {} 144 | context.view = compile(view)(context) 145 | render(layout, context) 146 | end }, { __tostring = function(self) 147 | local context = context or self 148 | context.blocks = context.blocks or {} 149 | context.view = compile(view)(context) 150 | return compile(layout)(context) 151 | end }) 152 | end 153 | return setmetatable({ render = function(self, context) 154 | render(view, context or self) 155 | end }, { __tostring = function(self) 156 | return compile(view)(context or self) 157 | end }) 158 | end 159 | 160 | function template.precompile(view, path, strip) 161 | local chunk = dump(template.compile(view), strip ~= false) 162 | if path then 163 | local file = open(path, "wb") 164 | file:write(chunk) 165 | file:close() 166 | end 167 | return chunk 168 | end 169 | 170 | function template.compile(view, key, plain) 171 | assert(view, "view was not provided for template.compile(view, key, plain).") 172 | if key == "no-cache" then 173 | return load_chunk(template.parse(view, plain)), false 174 | end 175 | key = key or view 176 | local cache = template.cache 177 | if cache[key] then return cache[key], true end 178 | local func = load_chunk(template.parse(view, plain)) 179 | if caching then cache[key] = func end 180 | return func, false 181 | end 182 | 183 | function template.parse(view, plain) 184 | assert(view, "view was not provided for template.parse(view, plain).") 185 | local concat, rpos, find, byte, sub = concat, rpos, find, byte, sub 186 | if not plain then 187 | view = template.load(view) 188 | if byte(sub(view, 1, 1)) == 27 then return view end 189 | end 190 | local c = {[[ 191 | context=... or {} 192 | local function include(v, c) 193 | return template.compile(v)(c or context) 194 | end 195 | local ___,blocks,layout={},blocks or {} 196 | ]]} 197 | local i, s = 1, find(view, "{", 1, true) 198 | while s do 199 | local t, p, d, z, r = sub(view, s + 1, s + 1), s + 2 200 | if t == "{" then 201 | local e = find(view, "}}", p, true) 202 | if e then 203 | d = concat{"___[#___+1]=template.escape(", sub(view, p, e - 1), ")\n" } 204 | z = e + 1 205 | end 206 | elseif t == "*" then 207 | local e = (find(view, "*}", p, true)) 208 | if e then 209 | d = concat{"___[#___+1]=template.output(", sub(view, p, e - 1), ")\n" } 210 | z = e + 1 211 | end 212 | elseif t == "%" then 213 | local e = find(view, "%}", p, true) 214 | if e then 215 | local n = e + 2 216 | if sub(view, n, n) == "\n" then 217 | n = n + 1 218 | end 219 | d = concat{sub(view, p, e - 1), "\n" } 220 | z, r = n - 1, true 221 | end 222 | elseif t == "(" then 223 | local e = find(view, ")}", p, true) 224 | if e then 225 | local f = sub(view, p, e - 1) 226 | local x = (find(f, ",", 2, true)) 227 | if x then 228 | d = concat{"___[#___+1]=include([=[", sub(f, 1, x - 1), "]=],", sub(f, x + 1), ")\n"} 229 | else 230 | d = concat{"___[#___+1]=include([=[", f, "]=])\n" } 231 | end 232 | z = e + 1 233 | end 234 | elseif t == "[" then 235 | local e = find(view, "]}", p, true) 236 | if e then 237 | d = concat{"___[#___+1]=include(", sub(view, p, e - 1), ")\n" } 238 | z = e + 1 239 | end 240 | elseif t == "-" then 241 | local e = find(view, "-}", p, true) 242 | if e then 243 | local x, y = find(view, sub(view, s, e + 1), e + 2, true) 244 | if x then 245 | y = y + 1 246 | x = x - 1 247 | if sub(view, y, y) == "\n" then 248 | y = y + 1 249 | end 250 | if sub(view, x, x) == "\n" then 251 | x = x - 1 252 | end 253 | d = concat{'blocks["', sub(view, p, e - 1), '"]=include[=[', sub(view, e + 2, x), "]=]\n"} 254 | z, r = y - 1, true 255 | end 256 | end 257 | elseif t == "#" then 258 | local e = find(view, "#}", p, true) 259 | if e then 260 | e = e + 2 261 | if sub(view, e, e) == "\n" then 262 | e = e + 1 263 | end 264 | d = "" 265 | z, r = e - 1, true 266 | end 267 | end 268 | if d then 269 | c[#c+1] = concat{"___[#___+1]=[=[\n", sub(view, i, r and rpos(view, s - 1) or s - 1), "]=]\n" } 270 | if d ~= "" then 271 | c[#c+1] = d 272 | end 273 | s, i = z, z + 1 274 | end 275 | s = find(view, "{", s + 1, true) 276 | end 277 | c[#c+1] = concat{"___[#___+1]=[=[\n", sub(view, i), "]=]\n"} 278 | c[#c+1] = "return layout and include(layout,setmetatable({view=template.concat(___),blocks=blocks},{__index=context})) or template.concat(___)" 279 | return concat(c) 280 | end 281 | 282 | function template.render(view, context, key, plain) 283 | assert(view, "view was not provided for template.render(view, context, key, plain).") 284 | return template.print(template.compile(view, key, plain)(context)) 285 | end 286 | 287 | return template 288 | -------------------------------------------------------------------------------- /alpine/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.3 2 | 3 | ENV OPENRESTY_VERSION 1.9.7.3 4 | ENV OPENRESTY_PREFIX /opt/openresty 5 | ENV NGINX_PREFIX /opt/openresty/nginx 6 | ENV VAR_PREFIX /var/nginx 7 | 8 | # NginX prefix is automatically set by OpenResty to $OPENRESTY_PREFIX/nginx 9 | # look for $ngx_prefix in https://github.com/openresty/ngx_openresty/blob/master/util/configure 10 | 11 | RUN echo "==> Installing dependencies..." \ 12 | && apk update \ 13 | && apk add --virtual build-deps \ 14 | make gcc musl-dev \ 15 | pcre-dev openssl-dev zlib-dev ncurses-dev readline-dev \ 16 | curl perl \ 17 | && mkdir -p /root/ngx_openresty \ 18 | && cd /root/ngx_openresty \ 19 | && echo "==> Downloading OpenResty..." \ 20 | && curl -sSL http://openresty.org/download/openresty-${OPENRESTY_VERSION}.tar.gz | tar -xvz \ 21 | && cd openresty-* \ 22 | && echo "==> Configuring OpenResty..." \ 23 | && readonly NPROC=$(grep -c ^processor /proc/cpuinfo 2>/dev/null || 1) \ 24 | && echo "using upto $NPROC threads" \ 25 | && ./configure \ 26 | --prefix=$OPENRESTY_PREFIX \ 27 | --http-client-body-temp-path=$VAR_PREFIX/client_body_temp \ 28 | --http-proxy-temp-path=$VAR_PREFIX/proxy_temp \ 29 | --http-log-path=$VAR_PREFIX/access.log \ 30 | --error-log-path=$VAR_PREFIX/error.log \ 31 | --pid-path=$VAR_PREFIX/nginx.pid \ 32 | --lock-path=$VAR_PREFIX/nginx.lock \ 33 | --with-luajit \ 34 | --with-pcre-jit \ 35 | --with-ipv6 \ 36 | --with-http_ssl_module \ 37 | --without-http_ssi_module \ 38 | --without-http_userid_module \ 39 | --without-http_uwsgi_module \ 40 | --without-http_scgi_module \ 41 | -j${NPROC} \ 42 | && echo "==> Building OpenResty..." \ 43 | && make -j${NPROC} \ 44 | && echo "==> Installing OpenResty..." \ 45 | && make install \ 46 | && echo "==> Finishing..." \ 47 | && ln -sf $NGINX_PREFIX/sbin/nginx /usr/local/bin/nginx \ 48 | && ln -sf $NGINX_PREFIX/sbin/nginx /usr/local/bin/openresty \ 49 | && ln -sf $OPENRESTY_PREFIX/bin/resty /usr/local/bin/resty \ 50 | && ln -sf $OPENRESTY_PREFIX/luajit/bin/luajit-* $OPENRESTY_PREFIX/luajit/bin/lua \ 51 | && ln -sf $OPENRESTY_PREFIX/luajit/bin/luajit-* /usr/local/bin/lua \ 52 | && apk del build-deps \ 53 | && apk add \ 54 | libpcrecpp libpcre16 libpcre32 openssl libssl1.0 pcre libgcc libstdc++ \ 55 | && rm -rf /var/cache/apk/* \ 56 | && rm -rf /root/ngx_openresty 57 | 58 | WORKDIR $NGINX_PREFIX/ 59 | 60 | ONBUILD RUN rm -rf conf/* html/* 61 | ONBUILD COPY nginx $NGINX_PREFIX/ 62 | 63 | CMD ["nginx", "-g", "daemon off; error_log /dev/stderr info;"] 64 | -------------------------------------------------------------------------------- /debian/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:wheezy 2 | 3 | RUN apt-get update \ 4 | && apt-get install -y --no-install-recommends \ 5 | curl perl make build-essential procps \ 6 | libreadline-dev libncurses5-dev libpcre3-dev libssl-dev \ 7 | && rm -rf /var/lib/apt/lists/* 8 | 9 | ENV OPENRESTY_VERSION 1.9.7.3 10 | ENV OPENRESTY_PREFIX /opt/openresty 11 | ENV NGINX_PREFIX /opt/openresty/nginx 12 | ENV VAR_PREFIX /var/nginx 13 | 14 | # NginX prefix is automatically set by OpenResty to $OPENRESTY_PREFIX/nginx 15 | # look for $ngx_prefix in https://github.com/openresty/ngx_openresty/blob/master/util/configure 16 | 17 | RUN cd /root \ 18 | && echo "==> Downloading OpenResty..." \ 19 | && curl -sSL http://openresty.org/download/openresty-${OPENRESTY_VERSION}.tar.gz | tar -xvz \ 20 | && echo "==> Configuring OpenResty..." \ 21 | && cd openresty-* \ 22 | && readonly NPROC=$(grep -c ^processor /proc/cpuinfo 2>/dev/null || 1) \ 23 | && echo "using upto $NPROC threads" \ 24 | && ./configure \ 25 | --prefix=$OPENRESTY_PREFIX \ 26 | --http-client-body-temp-path=$VAR_PREFIX/client_body_temp \ 27 | --http-proxy-temp-path=$VAR_PREFIX/proxy_temp \ 28 | --http-log-path=$VAR_PREFIX/access.log \ 29 | --error-log-path=$VAR_PREFIX/error.log \ 30 | --pid-path=$VAR_PREFIX/nginx.pid \ 31 | --lock-path=$VAR_PREFIX/nginx.lock \ 32 | --with-luajit \ 33 | --with-pcre-jit \ 34 | --with-ipv6 \ 35 | --with-http_ssl_module \ 36 | --without-http_ssi_module \ 37 | --without-http_userid_module \ 38 | --without-http_uwsgi_module \ 39 | --without-http_scgi_module \ 40 | -j${NPROC} \ 41 | && echo "==> Building OpenResty..." \ 42 | && make -j${NPROC} \ 43 | && echo "==> Installing OpenResty..." \ 44 | && make install \ 45 | && echo "==> Finishing..." \ 46 | && ln -sf $NGINX_PREFIX/sbin/nginx /usr/local/bin/nginx \ 47 | && ln -sf $NGINX_PREFIX/sbin/nginx /usr/local/bin/openresty \ 48 | && ln -sf $OPENRESTY_PREFIX/bin/resty /usr/local/bin/resty \ 49 | && ln -sf $OPENRESTY_PREFIX/luajit/bin/luajit-* $OPENRESTY_PREFIX/luajit/bin/lua \ 50 | && ln -sf $OPENRESTY_PREFIX/luajit/bin/luajit-* /usr/local/bin/lua \ 51 | && rm -rf /root/ngx_openresty* 52 | 53 | WORKDIR $NGINX_PREFIX/ 54 | 55 | ONBUILD RUN rm -rf conf/* html/* 56 | ONBUILD COPY nginx $NGINX_PREFIX/ 57 | 58 | CMD ["nginx", "-g", "daemon off; error_log /dev/stderr info;"] 59 | --------------------------------------------------------------------------------