├── .gitignore ├── README.md ├── nginx.conf ├── resty ├── aes.lua ├── http.lua ├── md5.lua ├── memcached.lua ├── mysql.lua ├── random.lua ├── redis.lua ├── sha.lua ├── sha1.lua ├── sha224.lua ├── sha256.lua ├── sha384.lua ├── sha512.lua ├── string.lua ├── upload.lua ├── url.lua └── weedfs.lua └── weedfs.lua /.gitignore: -------------------------------------------------------------------------------- 1 | trash 2 | *.iml 3 | .idea -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | lua-resty-weedfs 2 | ================ 3 | 4 | weefs,lua,nginx and file post processing with ffmpeg and graphicsmagick 5 | 6 | 7 | #start weedfs with 1 replica 8 | 9 | ` 10 | ./weed master -ip="0.0.0.0" -defaultReplication="001" -mdir="." & 11 | 12 | ./weed volume -max=100 -mserver="localhost:9333" -dir="./data/v1" -port=8083 -ip="119.254.xx.xx" -dataCenter="dc1" -rack="rack1" & 13 | 14 | ./weed volume -max=100 -mserver="localhost:9333" -dir="./data/v2" -port=8084 -ip="119.254.xx.xx" -dataCenter="dc1" -rack="rack1" & 15 | 16 | ./weed volume -max=100 -mserver="localhost:9333" -dir="./data/v3" -port=8085 -ip="119.254.xx.xx" -dataCenter="dc1" -rack="rack1" & 17 | ` 18 | 19 | 20 | #upload file to weedfs 21 | curl http://localhost:9333/dir/assign 22 | 23 | curl -F file=@/home/chris/myphoto.jpg http://127.0.0.1:8080/3,01637037d6 24 | 25 | curl http://localhost:9333/dir/status?pretty=y 26 | 27 | curl http://localhost:9333/dir/assign?dataCenter=dc1 28 | 29 | curl http://localhost:9333/dir/assign?collection=pictures 30 | 31 | curl http://localhost:9333/dir/assign?collection=documents 32 | 33 | 34 | #access the file 35 | 36 | http://domain/img/orig/123,123123 37 | 38 | http://domain/img/80x80/123,123123 39 | 40 | http://domain/img/200x200/123,123123 41 | 42 | ... 43 | -------------------------------------------------------------------------------- /nginx.conf: -------------------------------------------------------------------------------- 1 | user www www; 2 | worker_processes 8; 3 | daemon on; 4 | master_process off; 5 | 6 | error_log logs/error.log error; 7 | #error_log logs/error.log info; 8 | 9 | 10 | 11 | pid logs/nginx.pid; 12 | 13 | 14 | env MOCKEAGAIN_VERBOSE; 15 | env MOCKEAGAIN_WRITE_TIMEOUT_PATTERN; 16 | env LD_PRELOAD; 17 | env DYLD_INSERT_LIBRARIES; 18 | 19 | worker_rlimit_nofile 65535; 20 | events { 21 | worker_connections 65535; 22 | } 23 | 24 | 25 | http { 26 | include mime.types; 27 | default_type application/octet-stream; 28 | # default_type text/plain; 29 | 30 | log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 31 | '$status $body_bytes_sent "$http_referer" ' 32 | '"$http_user_agent" "$http_x_forwarded_for"'; 33 | 34 | access_log logs/access.log main; 35 | 36 | 37 | server_names_hash_bucket_size 128; 38 | client_header_buffer_size 32k; 39 | large_client_header_buffers 4 32k; 40 | client_max_body_size 8m; 41 | 42 | 43 | sendfile on; 44 | tcp_nopush on; 45 | tcp_nodelay on; 46 | 47 | fastcgi_connect_timeout 300; 48 | fastcgi_send_timeout 300; 49 | fastcgi_read_timeout 300; 50 | fastcgi_buffer_size 64k; 51 | fastcgi_buffers 4 64k; 52 | fastcgi_busy_buffers_size 128k; 53 | fastcgi_temp_file_write_size 128k; 54 | 55 | 56 | ##cache## 57 | proxy_connect_timeout 5; 58 | proxy_read_timeout 60; 59 | proxy_send_timeout 5; 60 | proxy_buffer_size 16k; 61 | proxy_buffers 4 64k; 62 | proxy_busy_buffers_size 128k; 63 | proxy_temp_file_write_size 128k; 64 | proxy_temp_path temp_dir; 65 | proxy_cache_path cache levels=1:2 keys_zone=cache_one:200m inactive=1d max_size=30g; 66 | ##end## 67 | 68 | 69 | open_file_cache max=1000 inactive=20s; 70 | open_file_cache_min_uses 5; 71 | open_file_cache_valid 30s; 72 | 73 | gzip on; 74 | gzip_min_length 1k; 75 | gzip_buffers 4 16k; 76 | gzip_http_version 1.1; 77 | gzip_comp_level 2; 78 | gzip_types text/plain application/x-javascript text/css application/xml; 79 | gzip_disable "MSIE [1-6]\."; 80 | gzip_vary on; 81 | 82 | keepalive_timeout 65; 83 | #problematic 84 | #lua_code_cache off; 85 | lua_package_path "/usr/local/openresty/lualib/?.lua;;"; 86 | resolver 8.8.8.8; 87 | 88 | 89 | server { 90 | listen 80; 91 | server_name localhost; 92 | 93 | rewrite_log on; 94 | 95 | 96 | 97 | charset utf-8,gbk; 98 | 99 | #access_log logs/host.access.log main; 100 | 101 | #note:must end with“/” 102 | set $weed_audio_root_url "http://127.0.0.1:8080/"; 103 | set $weed_img_root_url "http://127.0.0.1:8080/"; 104 | set $local_img_fs_root "/home/wwwroot/weedfs/"; 105 | set $local_audio_fs_root "/home/wwwroot/weedfs/"; 106 | 107 | 108 | location / { 109 | root /home/wwwroot/weedfs/; 110 | index index.html index.htm; 111 | } 112 | 113 | 114 | #sample:/_img/?size=orig&volumn=1&id=1234 115 | location /_img/{ 116 | default_type image/jpeg; 117 | if ($request_method = 'DELETE' ) { 118 | return 405; 119 | } 120 | if ($request_method = 'PUT' ) { 121 | return 405; 122 | } 123 | if ($request_method = 'POST' ) { 124 | return 405; 125 | } 126 | 127 | content_by_lua_file conf/weedfs.lua; 128 | expires 30d; 129 | # access_log off; 130 | } 131 | 132 | 133 | # location /img/orig/{ 134 | # proxy_set_header X-Real-IP $remote_addr; 135 | # proxy_set_header X-Forwarded-For $remote_addr; 136 | # proxy_set_header Host $http_host; 137 | # 138 | # # if ($uri ~* "/img/orig/([0-9]+)/([a-z0-9]+)(\.[a-z]+)?") { 139 | # # rewrite "/img/orig/([0-9]+)/([a-z0-9]+)(\.[a-z]+)" /img/orig/$1,$2$3 permanent; 140 | # # break; 141 | # # } 142 | # 143 | # proxy_pass http://192.168.2.100:8080/; 144 | # expires 7d; 145 | # break; 146 | # } 147 | 148 | location /img/{ 149 | rewrite "/img/([0-9]+x[0-9]+s?)/([0-9]+)/([a-z0-9]+)(\.[a-z]+)?" /_img/?type=img&size=$1&volumn=$2&id=$3 last; 150 | rewrite "/img/([0-9]+x[0-9]+s?)/([0-9]+),([a-z0-9]+)(\.[a-z]+)?" /_img/?type=img&size=$1&volumn=$2&id=$3 last; 151 | rewrite "/img/orig/([0-9]+)[,/]([a-z0-9]+)(\.[a-z]+)?" /_img/?type=img&size=orig&volumn=$1&id=$2 last; 152 | expires 30d; 153 | # access_log off; 154 | } 155 | 156 | location /_audio/{ 157 | default_type audio/mp3; 158 | if ($request_method = 'DELETE' ) { 159 | return 405; 160 | } 161 | if ($request_method = 'PUT' ) { 162 | return 405; 163 | } 164 | if ($request_method = 'POST' ) { 165 | return 405; 166 | } 167 | 168 | content_by_lua_file conf/weedfs.lua; 169 | expires 30d; 170 | # access_log off; 171 | } 172 | 173 | #if you specified audio_fs_root separately,you should change this. 174 | #location /audios{ 175 | # default_type audio/mp3; 176 | # root /home/wwwroot/audios; 177 | # expires 30d; 178 | # access_log off; 179 | # } 180 | 181 | location /audio/{ 182 | rewrite "/audio/(mp3)/([0-9]+)/([a-z0-9]+)(\.[a-z]+)?" /_audio/?type=audio&size=$1&volumn=$2&id=$3 last; 183 | rewrite "/audio/(mp3)/([0-9]+),([a-z0-9]+)(\.[a-z]+)?" /_audio/?type=audio&size=$1&volumn=$2&id=$3 last; 184 | rewrite "/audio/orig/([0-9]+),([a-z0-9]+)(\.[a-z]+)?" /_audio/?type=audio&size=orig&volumn=$1&id=$2 last; 185 | expires 30d; 186 | # access_log off; 187 | } 188 | 189 | # location /upload{ 190 | # if ($request_method = 'DELETE' ) { 191 | # return 405; 192 | # } 193 | # if ($request_method = 'PUT' ) { 194 | # return 405; 195 | # } 196 | # 197 | # proxy_pass $weed_xxx_root_url; 198 | # proxy_redirect default ; 199 | # } 200 | 201 | 202 | location /favicon.ico{ 203 | root /home/wwwroot/; 204 | # access_log off; 205 | } 206 | 207 | 208 | error_page 404 /404.html; 209 | error_page 500 502 503 504 /50x.html; 210 | location = /50x.html { 211 | root html; 212 | } 213 | location ~ /\.ht { 214 | deny all; 215 | } 216 | 217 | 218 | location /status { 219 | stub_status on; 220 | access_log off; 221 | } 222 | 223 | } 224 | } 225 | 226 | 227 | -------------------------------------------------------------------------------- /resty/aes.lua: -------------------------------------------------------------------------------- 1 | module("resty.aes", package.seeall) 2 | 3 | _VERSION = '0.06' 4 | 5 | --local asn1 = require "resty.asn1" 6 | local ffi = require "ffi" 7 | local ffi_new = ffi.new 8 | local ffi_gc = ffi.gc 9 | local ffi_str = ffi.string 10 | local ffi_copy = ffi.copy 11 | local C = ffi.C 12 | 13 | local mt = { __index = resty.aes } 14 | 15 | ffi.cdef[[ 16 | typedef struct engine_st ENGINE; 17 | 18 | typedef struct evp_cipher_st EVP_CIPHER; 19 | typedef struct evp_cipher_ctx_st 20 | { 21 | const EVP_CIPHER *cipher; 22 | ENGINE *engine; 23 | int encrypt; 24 | int buf_len; 25 | 26 | unsigned char oiv[16]; 27 | unsigned char iv[16]; 28 | unsigned char buf[32]; 29 | int num; 30 | 31 | void *app_data; 32 | int key_len; 33 | unsigned long flags; 34 | void *cipher_data; 35 | int final_used; 36 | int block_mask; 37 | unsigned char final[32]; 38 | } EVP_CIPHER_CTX; 39 | 40 | typedef struct env_md_ctx_st EVP_MD_CTX; 41 | typedef struct env_md_st EVP_MD; 42 | 43 | const EVP_MD *EVP_md5(void); 44 | const EVP_MD *EVP_sha(void); 45 | const EVP_MD *EVP_sha1(void); 46 | const EVP_MD *EVP_sha224(void); 47 | const EVP_MD *EVP_sha256(void); 48 | const EVP_MD *EVP_sha384(void); 49 | const EVP_MD *EVP_sha512(void); 50 | 51 | const EVP_CIPHER *EVP_aes_128_ecb(void); 52 | const EVP_CIPHER *EVP_aes_128_cbc(void); 53 | const EVP_CIPHER *EVP_aes_128_cfb1(void); 54 | const EVP_CIPHER *EVP_aes_128_cfb8(void); 55 | const EVP_CIPHER *EVP_aes_128_cfb128(void); 56 | const EVP_CIPHER *EVP_aes_128_ofb(void); 57 | const EVP_CIPHER *EVP_aes_128_ctr(void); 58 | const EVP_CIPHER *EVP_aes_192_ecb(void); 59 | const EVP_CIPHER *EVP_aes_192_cbc(void); 60 | const EVP_CIPHER *EVP_aes_192_cfb1(void); 61 | const EVP_CIPHER *EVP_aes_192_cfb8(void); 62 | const EVP_CIPHER *EVP_aes_192_cfb128(void); 63 | const EVP_CIPHER *EVP_aes_192_ofb(void); 64 | const EVP_CIPHER *EVP_aes_192_ctr(void); 65 | const EVP_CIPHER *EVP_aes_256_ecb(void); 66 | const EVP_CIPHER *EVP_aes_256_cbc(void); 67 | const EVP_CIPHER *EVP_aes_256_cfb1(void); 68 | const EVP_CIPHER *EVP_aes_256_cfb8(void); 69 | const EVP_CIPHER *EVP_aes_256_cfb128(void); 70 | const EVP_CIPHER *EVP_aes_256_ofb(void); 71 | 72 | void EVP_CIPHER_CTX_init(EVP_CIPHER_CTX *a); 73 | int EVP_CIPHER_CTX_cleanup(EVP_CIPHER_CTX *a); 74 | 75 | int EVP_EncryptInit_ex(EVP_CIPHER_CTX *ctx,const EVP_CIPHER *cipher, 76 | ENGINE *impl, unsigned char *key, const unsigned char *iv); 77 | 78 | int EVP_EncryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl, 79 | const unsigned char *in, int inl); 80 | 81 | int EVP_EncryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl); 82 | 83 | int EVP_DecryptInit_ex(EVP_CIPHER_CTX *ctx,const EVP_CIPHER *cipher, 84 | ENGINE *impl, unsigned char *key, const unsigned char *iv); 85 | 86 | int EVP_DecryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl, 87 | const unsigned char *in, int inl); 88 | 89 | int EVP_DecryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *outm, int *outl); 90 | 91 | int EVP_BytesToKey(const EVP_CIPHER *type,const EVP_MD *md, 92 | const unsigned char *salt, const unsigned char *data, int datal, 93 | int count, unsigned char *key,unsigned char *iv); 94 | ]] 95 | 96 | local ctx_ptr_type = ffi.typeof("EVP_CIPHER_CTX[1]") 97 | 98 | hash = { 99 | md5 = C.EVP_md5(), 100 | sha1 = C.EVP_sha1(), 101 | sha224 = C.EVP_sha224(), 102 | sha256 = C.EVP_sha256(), 103 | sha384 = C.EVP_sha384(), 104 | sha512 = C.EVP_sha512() 105 | } 106 | 107 | 108 | function cipher(size, _cipher) 109 | local _size = size or 128 110 | local _cipher = _cipher or "cbc" 111 | local func = "EVP_aes_" .. _size .. "_" .. _cipher 112 | if C[func] then 113 | return { size=_size, cipher=_cipher, method=C[func]()} 114 | else 115 | return nil 116 | end 117 | end 118 | 119 | 120 | function new(self, key, salt, _cipher, _hash, hash_rounds) 121 | local encrypt_ctx = ffi_new(ctx_ptr_type) 122 | local decrypt_ctx = ffi_new(ctx_ptr_type) 123 | local _cipher = _cipher or cipher() 124 | local _hash = _hash or hash.md5 125 | local hash_rounds = hash_rounds or 1 126 | local _cipherLength = _cipher.size/8 127 | local gen_key = ffi_new("unsigned char[?]",_cipherLength) 128 | local gen_iv = ffi_new("unsigned char[?]",_cipherLength) 129 | 130 | if type(_hash) == "table" then 131 | if not _hash.iv or #_hash.iv ~= 16 then 132 | return nil 133 | end 134 | 135 | if not _hash.method and #key ~= _cipherLength then 136 | return nil 137 | end 138 | 139 | if _hash.method then 140 | local tmp_key = _hash.method(key) 141 | 142 | if #tmp_key ~= _cipherLength then 143 | return nil 144 | end 145 | 146 | ffi_copy(gen_key, tmp_key, _cipherLength) 147 | end 148 | 149 | ffi_copy(gen_iv, _hash.iv, 16) 150 | else 151 | if C.EVP_BytesToKey(_cipher.method, _hash, salt, key, #key, 152 | hash_rounds, gen_key, gen_iv) ~= _cipherLength then 153 | return nil 154 | end 155 | end 156 | 157 | 158 | C.EVP_CIPHER_CTX_init(encrypt_ctx) 159 | C.EVP_CIPHER_CTX_init(decrypt_ctx) 160 | 161 | if C.EVP_EncryptInit_ex(encrypt_ctx, _cipher.method, nil, 162 | gen_key, gen_iv) == 0 or 163 | C.EVP_DecryptInit_ex(decrypt_ctx, _cipher.method, nil, 164 | gen_key, gen_iv) == 0 then 165 | return nil 166 | end 167 | 168 | ffi_gc(encrypt_ctx, C.EVP_CIPHER_CTX_cleanup) 169 | ffi_gc(decrypt_ctx, C.EVP_CIPHER_CTX_cleanup) 170 | 171 | return setmetatable({ 172 | _encrypt_ctx = encrypt_ctx, 173 | _decrypt_ctx = decrypt_ctx 174 | }, mt) 175 | end 176 | 177 | 178 | function encrypt(self, s) 179 | local s_len = #s 180 | local max_len = s_len + 16 181 | local buf = ffi_new("unsigned char[?]", max_len) 182 | local out_len = ffi_new("int[1]") 183 | local tmp_len = ffi_new("int[1]") 184 | local ctx = self._encrypt_ctx 185 | 186 | if C.EVP_EncryptInit_ex(ctx, nil, nil, nil, nil) == 0 then 187 | return nil 188 | end 189 | 190 | if C.EVP_EncryptUpdate(ctx, buf, out_len, s, s_len) == 0 then 191 | return nil 192 | end 193 | 194 | if C.EVP_EncryptFinal_ex(ctx, buf + out_len[0], tmp_len) == 0 then 195 | return nil 196 | end 197 | 198 | return ffi_str(buf, out_len[0] + tmp_len[0]) 199 | end 200 | 201 | 202 | function decrypt(self, s) 203 | local s_len = #s 204 | local buf = ffi_new("unsigned char[?]", s_len) 205 | local out_len = ffi_new("int[1]") 206 | local tmp_len = ffi_new("int[1]") 207 | local ctx = self._decrypt_ctx 208 | 209 | if C.EVP_DecryptInit_ex(ctx, nil, nil, nil, nil) == 0 then 210 | return nil 211 | end 212 | 213 | if C.EVP_DecryptUpdate(ctx, buf, out_len, s, s_len) == 0 then 214 | return nil 215 | end 216 | 217 | if C.EVP_DecryptFinal_ex(ctx, buf + out_len[0], tmp_len) == 0 then 218 | return nil 219 | end 220 | 221 | return ffi_str(buf, out_len[0] + tmp_len[0]) 222 | end 223 | 224 | 225 | -- to prevent use of casual module global variables 226 | getmetatable(resty.aes).__newindex = function (table, key, val) 227 | error('attempt to write to undeclared variable "' .. key .. '": ' 228 | .. debug.traceback()) 229 | end 230 | 231 | -------------------------------------------------------------------------------- /resty/http.lua: -------------------------------------------------------------------------------- 1 | module("resty.http", package.seeall) 2 | 3 | _VERSION = '0.2' 4 | 5 | -- constants 6 | -- connection timeout in seconds 7 | local TIMEOUT = 60 8 | -- default port for document retrieval 9 | local PORT = 80 10 | -- user agent field sent in request 11 | local USERAGENT = 'resty.http/' .. _VERSION 12 | 13 | -- default url parts 14 | local default = { 15 | host = "", 16 | port = PORT, 17 | path ="/", 18 | scheme = "http" 19 | } 20 | 21 | 22 | -- global variables 23 | local url = require("resty.url") 24 | 25 | local mt = { __index = resty.http } 26 | 27 | local tcp = ngx.socket.tcp 28 | 29 | 30 | local function adjusturi(reqt) 31 | local u = reqt 32 | -- if there is a proxy, we need the full url. otherwise, just a part. 33 | if not reqt.proxy and not PROXY then 34 | u = { 35 | path = reqt.path, 36 | params = reqt.params, 37 | query = reqt.query, 38 | fragment = reqt.fragment 39 | } 40 | end 41 | return url.build(u) 42 | end 43 | 44 | 45 | local function adjustheaders(reqt) 46 | -- default headers 47 | local lower = { 48 | ["user-agent"] = USERAGENT, 49 | ["host"] = reqt.host, 50 | ["connection"] = "close, TE", 51 | ["te"] = "trailers" 52 | } 53 | -- if we have authentication information, pass it along 54 | if reqt.user and reqt.password then 55 | lower["authorization"] = 56 | "Basic " .. (mime.b64(reqt.user .. ":" .. reqt.password)) 57 | end 58 | -- override with user headers 59 | for i,v in pairs(reqt.headers or lower) do 60 | lower[string.lower(i)] = v 61 | end 62 | return lower 63 | end 64 | 65 | 66 | local function adjustproxy(reqt) 67 | local proxy = reqt.proxy or PROXY 68 | if proxy then 69 | proxy = url.parse(proxy) 70 | return proxy.host, proxy.port or 3128 71 | else 72 | return reqt.host, reqt.port 73 | end 74 | end 75 | 76 | 77 | local function adjustrequest(reqt) 78 | -- parse url if provided 79 | local nreqt = reqt.url and url.parse(reqt.url, default) or {} 80 | -- explicit components override url 81 | for i,v in pairs(reqt) do nreqt[i] = v end 82 | 83 | if nreqt.port == "" then nreqt.port = 80 end 84 | 85 | -- compute uri if user hasn't overriden 86 | nreqt.uri = reqt.uri or adjusturi(nreqt) 87 | -- ajust host and port if there is a proxy 88 | nreqt.host, nreqt.port = adjustproxy(nreqt) 89 | -- adjust headers in request 90 | nreqt.headers = adjustheaders(nreqt) 91 | 92 | nreqt.timeout = reqt.timeout or TIMEOUT * 1000; 93 | 94 | nreqt.fetch_size = reqt.fetch_size or 16*1024 -- 16k 95 | nreqt.max_body_size = reqt.max_body_size or 1024*1024*1024 -- 1024mb 96 | 97 | if reqt.keepalive then 98 | nreqt.headers['connection'] = 'keep-alive' 99 | end 100 | 101 | return nreqt 102 | end 103 | 104 | 105 | local function receivestatusline(sock) 106 | local status_reader = sock:receiveuntil("\r\n") 107 | 108 | local data, err, partial = status_reader() 109 | if not data then 110 | return nil, "read status line failed " .. err 111 | end 112 | 113 | local t1, t2, code = string.find(data, "HTTP/%d*%.%d* (%d%d%d)") 114 | 115 | return tonumber(code), data 116 | end 117 | 118 | 119 | local function receiveheaders(sock, headers) 120 | local line, name, value, err, tmp1, tmp2 121 | headers = headers or {} 122 | -- get first line 123 | line, err = sock:receive() 124 | if err then return nil, err end 125 | -- headers go until a blank line is found 126 | while line ~= "" do 127 | -- get field-name and value 128 | tmp1, tmp2, name, value = string.find(line, "^(.-):%s*(.*)") 129 | if not (name and value) then return nil, "malformed reponse headers" end 130 | name = string.lower(name) 131 | -- get next line (value might be folded) 132 | line, err = sock:receive() 133 | if err then return nil, err end 134 | -- unfold any folded values 135 | while string.find(line, "^%s") do 136 | value = value .. line 137 | line = sock:receive() 138 | if err then return nil, err end 139 | end 140 | -- save pair in table 141 | if headers[name] then headers[name] = headers[name] .. ", " .. value 142 | else headers[name] = value end 143 | end 144 | return headers 145 | end 146 | 147 | local function read_body_data(sock, size, fetch_size, callback) 148 | local p_size = fetch_size 149 | while size and size > 0 do 150 | if size < p_size then 151 | p_size = size 152 | end 153 | local data, err, partial = sock:receive(p_size) 154 | if not err then 155 | if data then 156 | callback(data) 157 | end 158 | elseif err == "closed" then 159 | if partial then 160 | callback(partial) 161 | end 162 | return 1 -- 'closed' 163 | else 164 | return nil, err 165 | end 166 | size = size - p_size 167 | end 168 | return 1 169 | end 170 | 171 | local function receivebody(sock, headers, nreqt) 172 | local t = headers["transfer-encoding"] -- shortcut 173 | local body = '' 174 | local callback = nreqt.body_callback 175 | if not callback then 176 | local function bc(data, chunked_header, ...) 177 | if chunked_header then return end 178 | body = body .. data 179 | end 180 | callback = bc 181 | end 182 | if t and t ~= "identity" then 183 | -- chunked 184 | while true do 185 | local chunk_header = sock:receiveuntil("\r\n") 186 | local data, err, partial = chunk_header() 187 | if not err then 188 | if data == "0" then 189 | return body -- end of chunk 190 | else 191 | local length = tonumber(data, 16) 192 | 193 | -- TODO check nreqt.max_body_size !! 194 | 195 | local ok, err = read_body_data(sock,length, nreqt.fetch_size, callback) 196 | if err then 197 | return nil,err 198 | end 199 | end 200 | end 201 | end 202 | elseif headers["content-length"] ~= nil and headers["content-length"] ~= "0" then 203 | -- content length 204 | local length = tonumber(headers["content-length"]) 205 | if length > nreqt.max_body_size then 206 | ngx.log(ngx.INFO, 'content-length > nreqt.max_body_size !! Tail it !') 207 | length = nreqt.max_body_size 208 | end 209 | 210 | local ok, err = read_body_data(sock,length, nreqt.fetch_size, callback) 211 | if not ok then 212 | return nil,err 213 | end 214 | else 215 | -- connection close 216 | local ok, err = read_body_data(sock,nreqt.max_body_size, nreqt.fetch_size, callback) 217 | if not ok then 218 | return nil,err 219 | end 220 | end 221 | return body 222 | end 223 | 224 | local function shouldredirect(reqt, code, headers) 225 | return headers.location and 226 | string.gsub(headers.location, "%s", "") ~= "" and 227 | (reqt.redirect ~= false) and 228 | (code == 301 or code == 302) and 229 | (not reqt.method or reqt.method == "GET" or reqt.method == "HEAD") 230 | and (not reqt.nredirects or reqt.nredirects < 5) 231 | end 232 | 233 | 234 | local function shouldreceivebody(reqt, code) 235 | if reqt.method == "HEAD" then return nil end 236 | if code == 204 or code == 304 then return nil end 237 | if code >= 100 and code < 200 then return nil end 238 | return 1 239 | end 240 | 241 | 242 | function new(self) 243 | return setmetatable({}, mt) 244 | end 245 | 246 | 247 | function request(self, reqt) 248 | local code, headers, status, body, bytes, ok, err 249 | 250 | local nreqt = adjustrequest(reqt) 251 | 252 | local sock = tcp() 253 | if not sock then 254 | return nil, "create sock failed" 255 | end 256 | 257 | sock:settimeout(nreqt.timeout) 258 | 259 | -- connect 260 | ok, err = sock:connect(nreqt.host, nreqt.port) 261 | if err then 262 | return nil, "sock connected failed " .. err 263 | end 264 | 265 | -- check type of req_body, maybe string, file, function 266 | local req_body = nreqt.body 267 | local req_body_type = nil 268 | if req_body then 269 | req_body_type = type(req_body) 270 | if req_body_type == 'string' then -- fixed Content-Length 271 | nreqt.headers['Content-Length'] = req_body 272 | end 273 | end 274 | 275 | -- send request line and headers 276 | local reqline = string.format("%s %s HTTP/1.1\r\n", nreqt.method or "GET", nreqt.uri) 277 | local h = "\r\n" 278 | for i, v in pairs(nreqt.headers) do 279 | -- fix cookie is a table value 280 | if type(v) == "table" and i == "cookie" then 281 | v = table.concat(v, "; ") 282 | end 283 | h = i .. ": " .. v .. "\r\n" .. h 284 | end 285 | 286 | h = h .. '\r\n' -- close headers 287 | 288 | bytes, err = sock:send(reqline .. h) 289 | if err then 290 | sock:close() 291 | return nil, err 292 | end 293 | 294 | -- send req_body, if exists 295 | if req_body_type == 'string' then 296 | bytes, err = sock:send(req_body) 297 | if err then 298 | sock:close() 299 | return nil, err 300 | end 301 | elseif req_body_type == 'file' then 302 | local buf = nil 303 | while true do -- TODO chunked maybe better 304 | buf = req_body:read(8192) 305 | if not buf then break end 306 | bytes, err = sock:send(buf) 307 | if err then 308 | sock:close() 309 | return nil, err 310 | end 311 | end 312 | elseif req_body_type == 'function' then 313 | err = req_body(sock) -- as callback(sock) 314 | if err then 315 | return err 316 | end 317 | end 318 | 319 | -- receive status line 320 | code, status = receivestatusline(sock) 321 | if not code then 322 | sock:close() 323 | if not status then 324 | return nil, "read status line failed " 325 | else 326 | return nil, "read status line failed " .. status 327 | end 328 | end 329 | 330 | -- ignore any 100-continue messages 331 | while code == 100 do 332 | headers, err = receiveheaders(sock, {}) 333 | code, status = receivestatusline(sock) 334 | end 335 | 336 | -- notify code_callback 337 | if nreqt.code_callback then 338 | nreqt.code_callback(code) 339 | end 340 | 341 | -- receive headers 342 | headers, err = receiveheaders(sock, {}) 343 | if err then 344 | sock:close() 345 | return nil, "read headers failed " .. err 346 | end 347 | 348 | -- notify header_callback 349 | if nreqt.header_callback then 350 | nreqt.header_callback(headers) 351 | end 352 | 353 | -- TODO rediret check 354 | 355 | -- receive body 356 | if shouldreceivebody(nreqt, code) then 357 | body, err = receivebody(sock, headers, nreqt) 358 | if err then 359 | sock:close() 360 | return nil, "read body failed " .. err 361 | end 362 | end 363 | 364 | if nreqt.keepalive then 365 | sock:setkeepalive(nreqt.keepalive) 366 | else 367 | sock:close() 368 | end 369 | 370 | return 1, code, headers, status, body 371 | end 372 | 373 | function proxy_pass(self, reqt) 374 | local nreqt = {} 375 | for i,v in pairs(reqt) do nreqt[i] = v end 376 | 377 | if not nreqt.code_callback then 378 | nreqt.code_callback = function(code, ...) 379 | ngx.status = code 380 | end 381 | end 382 | 383 | if not nreqt.header_callback then 384 | nreqt.header_callback = function (headers, ...) 385 | for i, v in pairs(headers) do 386 | ngx.header[i] = v 387 | end 388 | end 389 | end 390 | 391 | if not nreqt.body_callback then 392 | nreqt.body_callback = function (data, ...) 393 | ngx.print(data) -- Will auto package as chunked format!! 394 | end 395 | end 396 | return request(self, nreqt) 397 | end 398 | 399 | -- to prevent use of casual module global variables 400 | getmetatable(resty.http).__newindex = function (table, key, val) 401 | error('attempt to write to undeclared variable "' .. key .. '": ' 402 | .. debug.traceback()) 403 | end 404 | 405 | -------------------------------------------------------------------------------- /resty/md5.lua: -------------------------------------------------------------------------------- 1 | module("resty.md5", package.seeall) 2 | 3 | _VERSION = '0.06' 4 | 5 | local ffi = require "ffi" 6 | local ffi_new = ffi.new 7 | local ffi_str = ffi.string 8 | local C = ffi.C 9 | 10 | local mt = { __index = resty.md5 } 11 | 12 | 13 | ffi.cdef[[ 14 | typedef unsigned long MD5_LONG ; 15 | 16 | enum { 17 | MD5_CBLOCK = 64, 18 | MD5_LBLOCK = MD5_CBLOCK/4 19 | }; 20 | 21 | typedef struct MD5state_st 22 | { 23 | MD5_LONG A,B,C,D; 24 | MD5_LONG Nl,Nh; 25 | MD5_LONG data[MD5_LBLOCK]; 26 | unsigned int num; 27 | } MD5_CTX; 28 | 29 | int MD5_Init(MD5_CTX *c); 30 | int MD5_Update(MD5_CTX *c, const void *data, size_t len); 31 | int MD5_Final(unsigned char *md, MD5_CTX *c); 32 | ]] 33 | 34 | local buf = ffi_new("char[16]") 35 | local ctx_ptr_type = ffi.typeof("MD5_CTX[1]") 36 | 37 | 38 | function new(self) 39 | local ctx = ffi_new(ctx_ptr_type) 40 | if C.MD5_Init(ctx) == 0 then 41 | return nil 42 | end 43 | 44 | return setmetatable({ _ctx = ctx }, mt) 45 | end 46 | 47 | 48 | function update(self, s) 49 | return C.MD5_Update(self._ctx, s, #s) == 1 50 | end 51 | 52 | 53 | function final(self) 54 | if C.MD5_Final(buf, self._ctx) == 1 then 55 | return ffi_str(buf, 16) 56 | end 57 | 58 | return nil 59 | end 60 | 61 | 62 | function reset(self) 63 | return C.MD5_Init(self._ctx) == 1 64 | end 65 | 66 | 67 | -- to prevent use of casual module global variables 68 | getmetatable(resty.md5).__newindex = function (table, key, val) 69 | error('attempt to write to undeclared variable "' .. key .. '": ' 70 | .. debug.traceback()) 71 | end 72 | 73 | -------------------------------------------------------------------------------- /resty/memcached.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (C) 2012 Zhang "agentzh" Yichun (章亦春) 2 | 3 | module("resty.memcached", package.seeall) 4 | 5 | _VERSION = '0.07' 6 | 7 | local mt = { __index = resty.memcached } 8 | 9 | local sub = string.sub 10 | local escape_uri = ngx.escape_uri 11 | local unescape_uri = ngx.unescape_uri 12 | local match = string.match 13 | local tcp = ngx.socket.tcp 14 | local strlen = string.len 15 | 16 | 17 | function new(self) 18 | return setmetatable({ sock = tcp() }, mt) 19 | end 20 | 21 | 22 | function set_timeout(self, timeout) 23 | local sock = self.sock 24 | if not sock then 25 | return nil, "not initialized" 26 | end 27 | 28 | return sock:settimeout(timeout) 29 | end 30 | 31 | 32 | function connect(self, ...) 33 | local sock = self.sock 34 | if not sock then 35 | return nil, "not initialized" 36 | end 37 | 38 | return sock:connect(...) 39 | end 40 | 41 | 42 | function get(self, key) 43 | if type(key) == "table" then 44 | return _multi_get(self, key) 45 | end 46 | 47 | local sock = self.sock 48 | if not sock then 49 | return nil, nil, "not initialized" 50 | end 51 | 52 | local cmd = {"get ", escape_uri(key), "\r\n"} 53 | local bytes, err = sock:send(cmd) 54 | if not bytes then 55 | return nil, nil, "failed to send command: " .. (err or "") 56 | end 57 | 58 | local line, err = sock:receive() 59 | if not line then 60 | return nil, nil, "failed to receive 1st line: " .. (err or "") 61 | end 62 | 63 | if line == 'END' then 64 | return nil, nil, nil 65 | end 66 | 67 | local flags, len = match(line, '^VALUE %S+ (%d+) (%d+)$') 68 | if not flags then 69 | return nil, nil, "bad line: " .. line 70 | end 71 | 72 | -- print("len: ", len, ", flags: ", flags) 73 | 74 | local data, err = sock:receive(len) 75 | if not data then 76 | return nil, nil, "failed to receive data chunk: " .. (err or "") 77 | end 78 | 79 | line, err = sock:receive(2) -- discard the trailing CRLF 80 | if not line then 81 | return nil, nil, "failed to receive CRLF: " .. (err or "") 82 | end 83 | 84 | line, err = sock:receive() -- discard "END\r\n" 85 | if not line then 86 | return nil, nil, "failed to receive END CRLF: " .. (err or "") 87 | end 88 | 89 | return data, flags 90 | end 91 | 92 | 93 | function _multi_get(self, keys) 94 | local sock = self.sock 95 | if not sock then 96 | return nil, "not initialized" 97 | end 98 | 99 | if #keys == 0 then 100 | return {}, nil 101 | end 102 | 103 | local cmd = {"get"} 104 | for i, key in ipairs(keys) do 105 | table.insert(cmd, " ") 106 | table.insert(cmd, escape_uri(key)) 107 | end 108 | table.insert(cmd, "\r\n") 109 | 110 | -- print("multi get cmd: ", cmd) 111 | 112 | local bytes, err = sock:send(cmd) 113 | if not bytes then 114 | return nil, err 115 | end 116 | 117 | local results = {} 118 | while true do 119 | local line, err = sock:receive() 120 | if not line then 121 | return nil, err 122 | end 123 | 124 | if line == 'END' then 125 | break 126 | end 127 | 128 | local key, flags, len = match(line, '^VALUE (%S+) (%d+) (%d+)$') 129 | -- print("key: ", key, "len: ", len, ", flags: ", flags) 130 | 131 | if key then 132 | 133 | local data, err = sock:receive(len) 134 | if not data then 135 | return nil, err 136 | end 137 | 138 | results[unescape_uri(key)] = {data, flags} 139 | 140 | data, err = sock:receive(2) -- discard the trailing CRLF 141 | if not data then 142 | return nil, err 143 | end 144 | end 145 | end 146 | 147 | return results 148 | end 149 | 150 | 151 | function gets(self, key) 152 | if type(key) == "table" then 153 | return _multi_gets(self, key) 154 | end 155 | 156 | local sock = self.sock 157 | if not sock then 158 | return nil, nil, nil, "not initialized" 159 | end 160 | 161 | local cmd = {"gets ", escape_uri(key), "\r\n"} 162 | local bytes, err = sock:send(cmd) 163 | if not bytes then 164 | return nil, nil, err 165 | end 166 | 167 | local line, err = sock:receive() 168 | if not line then 169 | return nil, nil, nil, err 170 | end 171 | 172 | if line == 'END' then 173 | return nil, nil, nil, nil 174 | end 175 | 176 | local flags, len, cas_uniq = match(line, '^VALUE %S+ (%d+) (%d+) (%d+)$') 177 | if not flags then 178 | return nil, nil, nil, line 179 | end 180 | 181 | -- print("len: ", len, ", flags: ", flags) 182 | 183 | local data, err = sock:receive(len) 184 | if not data then 185 | return nil, nil, nil, err 186 | end 187 | 188 | line, err = sock:receive(2) -- discard the trailing CRLF 189 | if not line then 190 | return nil, nil, nil, err 191 | end 192 | 193 | line, err = sock:receive() -- discard "END\r\n" 194 | if not line then 195 | return nil, nil, nil, err 196 | end 197 | 198 | return data, flags, cas_uniq 199 | end 200 | 201 | 202 | function _multi_gets(self, keys) 203 | local sock = self.sock 204 | if not sock then 205 | return nil, "not initialized" 206 | end 207 | 208 | if #keys == 0 then 209 | return {}, nil 210 | end 211 | 212 | local cmd = {"gets"} 213 | for i, key in ipairs(keys) do 214 | table.insert(cmd, " ") 215 | table.insert(cmd, escape_uri(key)) 216 | end 217 | table.insert(cmd, "\r\n") 218 | 219 | -- print("multi get cmd: ", cmd) 220 | 221 | local bytes, err = sock:send(cmd) 222 | if not bytes then 223 | return nil, err 224 | end 225 | 226 | local results = {} 227 | while true do 228 | local line, err = sock:receive() 229 | if not line then 230 | return nil, err 231 | end 232 | 233 | if line == 'END' then 234 | break 235 | end 236 | 237 | local key, flags, len, cas_uniq = 238 | match(line, '^VALUE (%S+) (%d+) (%d+) (%d+)$') 239 | 240 | -- print("key: ", key, "len: ", len, ", flags: ", flags) 241 | 242 | if key then 243 | 244 | local data, err = sock:receive(len) 245 | if not data then 246 | return nil, err 247 | end 248 | 249 | results[unescape_uri(key)] = {data, flags, cas_uniq} 250 | 251 | data, err = sock:receive(2) -- discard the trailing CRLF 252 | if not data then 253 | return nil, err 254 | end 255 | end 256 | end 257 | 258 | return results 259 | end 260 | 261 | 262 | function set(self, ...) 263 | return _store(self, "set", ...) 264 | end 265 | 266 | 267 | function add(self, ...) 268 | return _store(self, "add", ...) 269 | end 270 | 271 | 272 | function replace(self, ...) 273 | return _store(self, "replace", ...) 274 | end 275 | 276 | 277 | function append(self, ...) 278 | return _store(self, "append", ...) 279 | end 280 | 281 | 282 | function prepend(self, ...) 283 | return _store(self, "prepend", ...) 284 | end 285 | 286 | 287 | function _value_len(value) 288 | if type(value) == "table" then 289 | local len = 0 290 | for _, v in ipairs(value) do 291 | len = len + _value_len(v) 292 | end 293 | return len 294 | end 295 | 296 | return strlen(value) 297 | end 298 | 299 | 300 | function _store(self, cmd, key, value, exptime, flags) 301 | if not exptime then 302 | exptime = 0 303 | end 304 | 305 | if not flags then 306 | flags = 0 307 | end 308 | 309 | local sock = self.sock 310 | if not sock then 311 | return nil, "not initialized" 312 | end 313 | 314 | local req = {cmd, " ", escape_uri(key), " ", flags, " ", exptime, " ", 315 | _value_len(value), "\r\n", value, "\r\n"} 316 | 317 | local bytes, err = sock:send(req) 318 | if not bytes then 319 | return nil, err 320 | end 321 | 322 | local data, err = sock:receive() 323 | if not data then 324 | return nil, err 325 | end 326 | 327 | if data == "STORED" then 328 | return 1 329 | end 330 | 331 | return nil, data 332 | end 333 | 334 | 335 | function cas(self, key, value, cas_uniq, exptime, flags) 336 | if not exptime then 337 | exptime = 0 338 | end 339 | 340 | if not flags then 341 | flags = 0 342 | end 343 | 344 | local sock = self.sock 345 | if not sock then 346 | return nil, "not initialized" 347 | end 348 | 349 | local req = {"cas ", escape_uri(key), " ", flags, " ", exptime, " ", 350 | string.len(value), " ", cas_uniq, "\r\n", value, "\r\n"} 351 | 352 | -- local cjson = require "cjson" 353 | -- print("request: ", cjson.encode(req)) 354 | 355 | local bytes, err = sock:send(req) 356 | if not bytes then 357 | return nil, err 358 | end 359 | 360 | local line, err = sock:receive() 361 | if not line then 362 | return nil, err 363 | end 364 | 365 | -- print("response: [", line, "]") 366 | 367 | if line == "STORED" then 368 | return 1 369 | end 370 | 371 | return nil, line 372 | end 373 | 374 | 375 | function delete(self, key) 376 | local sock = self.sock 377 | if not sock then 378 | return nil, "not initialized" 379 | end 380 | 381 | key = escape_uri(key) 382 | 383 | local req = {"delete ", key, "\r\n"} 384 | 385 | local bytes, err = sock:send(req) 386 | if not bytes then 387 | return nil, err 388 | end 389 | 390 | local res, err = sock:receive() 391 | if not res then 392 | return nil, err 393 | end 394 | 395 | if res ~= 'DELETED' then 396 | return nil, res 397 | end 398 | 399 | return 1 400 | end 401 | 402 | 403 | function set_keepalive(self, ...) 404 | local sock = self.sock 405 | if not sock then 406 | return nil, "not initialized" 407 | end 408 | 409 | return sock:setkeepalive(...) 410 | end 411 | 412 | 413 | function get_reused_times(self) 414 | local sock = self.sock 415 | if not sock then 416 | return nil, "not initialized" 417 | end 418 | 419 | return sock:getreusedtimes() 420 | end 421 | 422 | 423 | function flush_all(self, time) 424 | local sock = self.sock 425 | if not sock then 426 | return nil, "not initialized" 427 | end 428 | 429 | local req 430 | if time then 431 | req = {"flush_all ", time, "\r\n"} 432 | else 433 | req = "flush_all\r\n" 434 | end 435 | 436 | local bytes, err = sock:send(req) 437 | if not bytes then 438 | return nil, err 439 | end 440 | 441 | local res, err = sock:receive() 442 | if not res then 443 | return nil, err 444 | end 445 | 446 | if res ~= 'OK' then 447 | return nil, res 448 | end 449 | 450 | return 1 451 | end 452 | 453 | 454 | function _incr_decr(self, cmd, key, value) 455 | local sock = self.sock 456 | if not sock then 457 | return nil, "not initialized" 458 | end 459 | 460 | local req = {cmd, " ", escape_uri(key), " ", value, "\r\n"} 461 | 462 | local bytes, err = sock:send(req) 463 | if not bytes then 464 | return nil, err 465 | end 466 | 467 | local line, err = sock:receive() 468 | if not line then 469 | return nil, err 470 | end 471 | 472 | if not match(line, '^%d+$') then 473 | return nil, line 474 | end 475 | 476 | return line 477 | end 478 | 479 | 480 | function incr(self, key, value) 481 | return _incr_decr(self, "incr", key, value) 482 | end 483 | 484 | 485 | function decr(self, key, value) 486 | return _incr_decr(self, "decr", key, value) 487 | end 488 | 489 | 490 | function stats(self, args) 491 | local sock = self.sock 492 | if not sock then 493 | return nil, "not initialized" 494 | end 495 | 496 | local req 497 | if args then 498 | req = {"stats ", args, "\r\n"} 499 | else 500 | req = "stats\r\n" 501 | end 502 | 503 | local bytes, err = sock:send(req) 504 | if not bytes then 505 | return nil, err 506 | end 507 | 508 | local lines = {} 509 | while true do 510 | local line, err = sock:receive() 511 | if not line then 512 | return nil, err 513 | end 514 | 515 | if line == 'END' then 516 | return lines, nil 517 | end 518 | 519 | if not match(line, "ERROR") then 520 | table.insert(lines, line) 521 | else 522 | return nil, line 523 | end 524 | end 525 | 526 | -- cannot reach here... 527 | return lines 528 | end 529 | 530 | 531 | function version(self) 532 | local sock = self.sock 533 | if not sock then 534 | return nil, "not initialized" 535 | end 536 | 537 | local bytes, err = sock:send("version\r\n") 538 | if not bytes then 539 | return nil, err 540 | end 541 | 542 | local line, err = sock:receive() 543 | if not line then 544 | return nil, err 545 | end 546 | 547 | local ver = match(line, "^VERSION (.+)$") 548 | if not ver then 549 | return nil, ver 550 | end 551 | 552 | return ver 553 | end 554 | 555 | 556 | function quit(self) 557 | local sock = self.sock 558 | if not sock then 559 | return nil, "not initialized" 560 | end 561 | 562 | local bytes, err = sock:send("quit\r\n") 563 | if not bytes then 564 | return nil, err 565 | end 566 | 567 | return 1 568 | end 569 | 570 | 571 | function verbosity(self, level) 572 | local sock = self.sock 573 | if not sock then 574 | return nil, "not initialized" 575 | end 576 | 577 | local bytes, err = sock:send({"verbosity ", level, "\r\n"}) 578 | if not bytes then 579 | return nil, err 580 | end 581 | 582 | local line, err = sock:receive() 583 | if not line then 584 | return nil, err 585 | end 586 | 587 | if line ~= 'OK' then 588 | return nil, line 589 | end 590 | 591 | return 1 592 | end 593 | 594 | 595 | function close(self) 596 | local sock = self.sock 597 | if not sock then 598 | return nil, "not initialized" 599 | end 600 | 601 | return sock:close() 602 | end 603 | 604 | 605 | -- to prevent use of casual module global variables 606 | getmetatable(resty.memcached).__newindex = function (table, key, val) 607 | error('attempt to write to undeclared variable "' .. key .. '": ' 608 | .. debug.traceback()) 609 | end 610 | 611 | -------------------------------------------------------------------------------- /resty/mysql.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (C) 2012 Zhang "agentzh" Yichun (章亦春) 2 | 3 | module("resty.mysql", package.seeall) 4 | 5 | _VERSION = '0.07' 6 | 7 | local bit = require "bit" 8 | 9 | 10 | -- constants 11 | 12 | local STATE_CONNECTED = 1 13 | local STATE_COMMAND_SENT = 2 14 | 15 | local COM_QUERY = 0x03 16 | 17 | local SERVER_MORE_RESULTS_EXISTS = 8 18 | 19 | 20 | -- global variables 21 | 22 | local mt = { __index = resty.mysql } 23 | 24 | local sub = string.sub 25 | local tcp = ngx.socket.tcp 26 | local insert = table.insert 27 | local strlen = string.len 28 | local strbyte = string.byte 29 | local strchar = string.char 30 | local strfind = string.find 31 | local strrep = string.rep 32 | local null = ngx.null 33 | local band = bit.band 34 | local bxor = bit.bxor 35 | local bor = bit.bor 36 | local lshift = bit.lshift 37 | local rshift = bit.rshift 38 | local tohex = bit.tohex 39 | local sha1 = ngx.sha1_bin 40 | local concat = table.concat 41 | 42 | 43 | -- mysql field value type converters 44 | local converters = {} 45 | 46 | for i = 0x01, 0x05 do 47 | -- tiny, short, long, float, double 48 | converters[i] = tonumber 49 | end 50 | converters[0x08] = tonumber -- long long 51 | converters[0x09] = tonumber -- int24 52 | converters[0x0d] = tonumber -- year 53 | 54 | 55 | local function _get_byte2(data, i) 56 | local a, b = strbyte(data, i, i + 1) 57 | return bor(a, lshift(b, 8)), i + 2 58 | end 59 | 60 | 61 | local function _get_byte3(data, i) 62 | local a, b, c = strbyte(data, i, i + 2) 63 | return bor(a, lshift(b, 8), lshift(c, 16)), i + 3 64 | end 65 | 66 | 67 | local function _get_byte4(data, i) 68 | local a, b, c, d = strbyte(data, i, i + 3) 69 | return bor(a, lshift(b, 8), lshift(c, 16), lshift(d, 24)), i + 4 70 | end 71 | 72 | 73 | local function _get_byte8(data, i) 74 | local a, b, c, d, e, f, g, h = strbyte(data, i, i + 7) 75 | return bor(a, lshift(b, 8), lshift(c, 16), lshift(d, 24), lshift(e, 32), 76 | lshift(f, 40), lshift(g, 48), lshift(h, 56)), i + 8 77 | end 78 | 79 | 80 | local function _set_byte2(n) 81 | return strchar(band(n, 0xff), band(rshift(n, 8), 0xff)) 82 | end 83 | 84 | 85 | local function _set_byte3(n) 86 | return strchar(band(n, 0xff), band(rshift(n, 8), 0xff), 87 | band(rshift(n, 16), 0xff)) 88 | end 89 | 90 | 91 | local function _set_byte4(n) 92 | return strchar(band(n, 0xff), band(rshift(n, 8), 0xff), 93 | band(rshift(n, 16), 0xff), band(rshift(n, 24), 0xff)) 94 | end 95 | 96 | 97 | local function _from_cstring(data, i) 98 | local last = strfind(data, "\0", i, true) 99 | if not last then 100 | return nil, nil 101 | end 102 | 103 | return sub(data, i, last), last + 1 104 | end 105 | 106 | 107 | local function _to_cstring(data) 108 | return {data, "\0"} 109 | end 110 | 111 | 112 | local function _to_binary_coded_string(data) 113 | return {strchar(strlen(data)), data} 114 | end 115 | 116 | 117 | local function _dump(data) 118 | local bytes = {} 119 | for i = 1, #data do 120 | insert(bytes, strbyte(data, i, i)) 121 | end 122 | return concat(bytes, " ") 123 | end 124 | 125 | 126 | local function _dumphex(data) 127 | local bytes = {} 128 | for i = 1, #data do 129 | insert(bytes, tohex(strbyte(data, i), 2)) 130 | end 131 | return concat(bytes, " ") 132 | end 133 | 134 | 135 | local function _compute_token(password, scramble) 136 | if password == "" then 137 | return "" 138 | end 139 | 140 | local stage1 = sha1(password) 141 | local stage2 = sha1(stage1) 142 | local stage3 = sha1(scramble .. stage2) 143 | local bytes = {} 144 | for i = 1, #stage1 do 145 | insert(bytes, 146 | bxor(strbyte(stage3, i), strbyte(stage1, i))) 147 | end 148 | 149 | return strchar(unpack(bytes)) 150 | end 151 | 152 | 153 | function _send_packet(self, req, size) 154 | local sock = self.sock 155 | 156 | self.packet_no = self.packet_no + 1 157 | 158 | --print("packet no: ", self.packet_no) 159 | 160 | local packet = { 161 | _set_byte3(size), 162 | strchar(self.packet_no), 163 | req 164 | } 165 | 166 | --print("sending packet...") 167 | 168 | return sock:send(packet) 169 | end 170 | 171 | 172 | function _recv_packet(self) 173 | local sock = self.sock 174 | 175 | local data, err = sock:receive(4) -- packet header 176 | if not data then 177 | return nil, nil, "failed to receive packet header: " .. err 178 | end 179 | 180 | --print("packet header: ", _dump(data)) 181 | 182 | local len, pos = _get_byte3(data, 1) 183 | 184 | --print("packet length: ", len) 185 | 186 | if len == 0 then 187 | return nil, nil, "empty packet" 188 | end 189 | 190 | if len > self._max_packet_size then 191 | return nil, nil, "packet size too big: " .. len 192 | end 193 | 194 | local num = strbyte(data, pos) 195 | 196 | --print("recv packet: packet no: ", num) 197 | 198 | self.packet_no = num 199 | 200 | data, err = sock:receive(len) 201 | 202 | --print("receive returned") 203 | 204 | if not data then 205 | return nil, nil, "failed to read packet content: " .. err 206 | end 207 | 208 | --print("packet content: ", _dump(data)) 209 | --print("packet content (ascii): ", data) 210 | 211 | local field_count = strbyte(data, 1) 212 | 213 | local typ 214 | if field_count == 0x00 then 215 | typ = "OK" 216 | elseif field_count == 0xff then 217 | typ = "ERR" 218 | elseif field_count == 0xfe then 219 | typ = "EOF" 220 | elseif field_count <= 250 then 221 | typ = "DATA" 222 | end 223 | 224 | return data, typ 225 | end 226 | 227 | 228 | local function _from_length_coded_bin(data, pos) 229 | local first = strbyte(data, pos) 230 | 231 | --print("LCB: first: ", first) 232 | 233 | if not first then 234 | return nil, pos 235 | end 236 | 237 | if first >= 0 and first <= 250 then 238 | return first, pos + 1 239 | end 240 | 241 | if first == 251 then 242 | return null, pos + 1 243 | end 244 | 245 | if first == 252 then 246 | pos = pos + 1 247 | return _get_byte2(data, pos) 248 | end 249 | 250 | if first == 253 then 251 | pos = pos + 1 252 | return _get_byte3(data, pos) 253 | end 254 | 255 | if first == 254 then 256 | pos = pos + 1 257 | return _get_byte8(data, pos) 258 | end 259 | 260 | return false, pos + 1 261 | end 262 | 263 | 264 | local function _from_length_coded_str(data, pos) 265 | local len 266 | len, pos = _from_length_coded_bin(data, pos) 267 | if len == nil or len == null then 268 | return null, pos 269 | end 270 | 271 | return sub(data, pos, pos + len - 1), pos + len 272 | end 273 | 274 | 275 | local function _parse_ok_packet(packet) 276 | local res = {} 277 | local pos 278 | 279 | res.affected_rows, pos = _from_length_coded_bin(packet, 2) 280 | 281 | --print("affected rows: ", res.affected_rows, ", pos:", pos) 282 | 283 | res.insert_id, pos = _from_length_coded_bin(packet, pos) 284 | 285 | --print("insert id: ", res.insert_id, ", pos:", pos) 286 | 287 | res.server_status, pos = _get_byte2(packet, pos) 288 | 289 | --print("server status: ", res.server_status, ", pos:", pos) 290 | 291 | res.warning_count, pos = _get_byte2(packet, pos) 292 | 293 | --print("warning count: ", res.warning_count, ", pos: ", pos) 294 | 295 | local message = sub(packet, pos) 296 | if message and message ~= "" then 297 | res.message = message 298 | end 299 | 300 | --print("message: ", res.message, ", pos:", pos) 301 | 302 | return res 303 | end 304 | 305 | 306 | local function _parse_eof_packet(packet) 307 | local pos = 2 308 | 309 | local warning_count, pos = _get_byte2(packet, pos) 310 | local status_flags = _get_byte2(packet, pos) 311 | 312 | return warning_count, status_flags 313 | end 314 | 315 | 316 | local function _parse_err_packet(packet) 317 | local errno, pos = _get_byte2(packet, 2) 318 | local marker = sub(packet, pos, pos) 319 | local sqlstate 320 | if marker == '#' then 321 | -- with sqlstate 322 | pos = pos + 1 323 | sqlstate = sub(packet, pos, pos + 5 - 1) 324 | pos = pos + 5 325 | end 326 | 327 | local message = sub(packet, pos) 328 | return errno, message, sqlstate 329 | end 330 | 331 | 332 | local function _parse_result_set_header_packet(packet) 333 | local field_count, pos = _from_length_coded_bin(packet, 1) 334 | 335 | local extra 336 | extra = _from_length_coded_bin(packet, pos) 337 | 338 | return field_count, extra 339 | end 340 | 341 | 342 | local function _parse_field_packet(data) 343 | local col = {} 344 | local catalog, db, table, orig_table, orig_name, charsetnr, length 345 | local pos 346 | catalog, pos = _from_length_coded_str(data, 1) 347 | 348 | --print("catalog: ", col.catalog, ", pos:", pos) 349 | 350 | db, pos = _from_length_coded_str(data, pos) 351 | table, pos = _from_length_coded_str(data, pos) 352 | orig_table, pos = _from_length_coded_str(data, pos) 353 | col.name, pos = _from_length_coded_str(data, pos) 354 | 355 | orig_name, pos = _from_length_coded_str(data, pos) 356 | 357 | pos = pos + 1 -- ignore the filler 358 | 359 | charsetnr, pos = _get_byte2(data, pos) 360 | 361 | length, pos = _get_byte4(data, pos) 362 | 363 | col.type = strbyte(data, pos) 364 | 365 | --[[ 366 | pos = pos + 1 367 | 368 | col.flags, pos = _get_byte2(data, pos) 369 | 370 | col.decimals = strbyte(data, pos) 371 | pos = pos + 1 372 | 373 | local default = sub(data, pos + 2) 374 | if default and default ~= "" then 375 | col.default = default 376 | end 377 | --]] 378 | 379 | return col 380 | end 381 | 382 | 383 | local function _parse_row_data_packet(data, cols) 384 | local row = {} 385 | local pos = 1 386 | for i = 1, #cols do 387 | local value 388 | value, pos = _from_length_coded_str(data, pos) 389 | local col = cols[i] 390 | local typ = col.type 391 | local name = col.name 392 | 393 | --print("row field value: ", value, ", type: ", typ) 394 | 395 | if value ~= null then 396 | local conv = converters[typ] 397 | if conv then 398 | value = conv(value) 399 | end 400 | -- insert(row, value) 401 | end 402 | 403 | row[name] = value 404 | end 405 | 406 | return row 407 | end 408 | 409 | 410 | local function _recv_field_packet(self) 411 | local packet, typ, err = _recv_packet(self) 412 | if not packet then 413 | return nil, err 414 | end 415 | 416 | if typ == "ERR" then 417 | local errno, msg, sqlstate = _parse_err_packet(packet) 418 | return nil, msg, errno, sqlstate 419 | end 420 | 421 | if typ ~= 'DATA' then 422 | return nil, "bad field packet type: " .. typ 423 | end 424 | 425 | -- typ == 'DATA' 426 | 427 | return _parse_field_packet(packet) 428 | end 429 | 430 | 431 | function new(self) 432 | return setmetatable({ sock = tcp() }, mt) 433 | end 434 | 435 | 436 | function set_timeout(self, timeout) 437 | local sock = self.sock 438 | if not sock then 439 | return nil, "not initialized" 440 | end 441 | 442 | return sock:settimeout(timeout) 443 | end 444 | 445 | 446 | function connect(self, opts) 447 | local sock = self.sock 448 | if not sock then 449 | return nil, "not initialized" 450 | end 451 | 452 | local max_packet_size = opts.max_packet_size 453 | if not max_packet_size then 454 | max_packet_size = 1024 * 1024 -- default 1 MB 455 | end 456 | self._max_packet_size = max_packet_size 457 | 458 | local ok, err 459 | 460 | local host = opts.host 461 | if host then 462 | ok, err = sock:connect(host, opts.port or '3306') 463 | 464 | else 465 | local path = opts.path 466 | if not path then 467 | return nil, 'neither "host" nor "path" options are specified' 468 | end 469 | 470 | ok, err = sock:connect("unix:" .. opts.path) 471 | end 472 | 473 | if not ok then 474 | return nil, 'failed to connect: ' .. err 475 | end 476 | 477 | local reused = sock:getreusedtimes() 478 | 479 | if reused and reused > 0 then 480 | self.state = STATE_CONNECTED 481 | return 1 482 | end 483 | 484 | local packet, typ, err = _recv_packet(self) 485 | if not packet then 486 | return nil, err 487 | end 488 | 489 | if typ == "ERR" then 490 | local errno, msg, sqlstate = _parse_err_packet(packet) 491 | return nil, msg, errno, sqlstate 492 | end 493 | 494 | self.protocol_ver = strbyte(packet) 495 | 496 | --print("protocol version: ", self.protocol_ver) 497 | 498 | local server_ver, pos = _from_cstring(packet, 2) 499 | if not server_ver then 500 | return nil, "bad handshake initialization packet: bad server version" 501 | end 502 | 503 | --print("server version: ", server_ver) 504 | 505 | self._server_ver = server_ver 506 | 507 | local thread_id, pos = _get_byte4(packet, pos) 508 | 509 | --print("thread id: ", thread_id) 510 | 511 | local scramble = sub(packet, pos, pos + 8 - 1) 512 | if not scramble then 513 | return nil, "1st part of scramble not found" 514 | end 515 | 516 | pos = pos + 9 -- skip filler 517 | 518 | -- two lower bytes 519 | self._server_capabilities, pos = _get_byte2(packet, pos) 520 | 521 | --print("server capabilities: ", self._server_capabilities) 522 | 523 | self._server_lang = strbyte(packet, pos) 524 | pos = pos + 1 525 | 526 | --print("server lang: ", self._server_lang) 527 | 528 | self._server_status, pos = _get_byte2(packet, pos) 529 | 530 | --print("server status: ", self._server_status) 531 | 532 | local more_capabilities 533 | more_capabilities, pos = _get_byte2(packet, pos) 534 | 535 | self._server_capabilities = bor(self._server_capabilities, lshift(more_capabilities, 16)) 536 | 537 | --print("server capabilities: ", self._server_capabilities) 538 | 539 | -- local len = strbyte(packet, pos) 540 | local len = 21 - 8 - 1 541 | 542 | --print("scramble len: ", len) 543 | 544 | pos = pos + 1 + 10 545 | 546 | local scramble_part2 = sub(packet, pos, pos + len - 1) 547 | if not scramble_part2 then 548 | return nil, "2nd part of scramble not found" 549 | end 550 | 551 | scramble = scramble .. scramble_part2 552 | --print("scramble: ", _dump(scramble)) 553 | 554 | local password = opts.password or "" 555 | local database = opts.database or "" 556 | local user = opts.user or "" 557 | 558 | local token = _compute_token(password, scramble) 559 | 560 | -- local client_flags = self._server_capabilities 561 | local client_flags = 260047; 562 | 563 | --print("token: ", _dump(token)) 564 | 565 | local req = { 566 | _set_byte4(client_flags), 567 | _set_byte4(self._max_packet_size), 568 | "\0", -- TODO: add support for charset encoding 569 | strrep("\0", 23), 570 | _to_cstring(user), 571 | _to_binary_coded_string(token), 572 | _to_cstring(database) 573 | } 574 | 575 | local packet_len = 4 + 4 + 1 + 23 + strlen(user) + 1 576 | + strlen(token) + 1 + strlen(database) + 1 577 | 578 | -- print("packet content length: ", packet_len) 579 | -- print("packet content: ", _dump(concat(req, ""))) 580 | 581 | local bytes, err = _send_packet(self, req, packet_len) 582 | if not bytes then 583 | return nil, "failed to send client authentication packet: " .. err 584 | end 585 | 586 | --print("packet sent ", bytes, " bytes") 587 | 588 | local packet, typ, err = _recv_packet(self) 589 | if not packet then 590 | return nil, "failed to receive the result packet: " .. err 591 | end 592 | 593 | if typ == 'ERR' then 594 | local errno, msg, sqlstate = _parse_err_packet(packet) 595 | return nil, msg, errno, sqlstate 596 | end 597 | 598 | if typ == 'EOF' then 599 | return nil, "old pre-4.1 authentication protocol not supported" 600 | end 601 | 602 | if typ ~= 'OK' then 603 | return nil, "bad packet type: " .. typ 604 | end 605 | 606 | self.state = STATE_CONNECTED 607 | 608 | return 1 609 | end 610 | 611 | 612 | function set_keepalive(self, ...) 613 | local sock = self.sock 614 | if not sock then 615 | return nil, "not initialized" 616 | end 617 | 618 | if self.state ~= STATE_CONNECTED then 619 | return nil, "cannot be reused in the current connection state: " 620 | .. (self.state or "nil") 621 | end 622 | 623 | self.state = nil 624 | return sock:setkeepalive(...) 625 | end 626 | 627 | 628 | function get_reused_times(self) 629 | local sock = self.sock 630 | if not sock then 631 | return nil, "not initialized" 632 | end 633 | 634 | return sock:getreusedtimes() 635 | end 636 | 637 | 638 | function close(self) 639 | local sock = self.sock 640 | if not sock then 641 | return nil, "not initialized" 642 | end 643 | 644 | self.state = nil 645 | 646 | return sock:close() 647 | end 648 | 649 | 650 | function server_ver(self) 651 | return self._server_ver 652 | end 653 | 654 | 655 | function send_query(self, query) 656 | if self.state ~= STATE_CONNECTED then 657 | return nil, "cannot send query in the current context: " .. (self.state or "nil") 658 | end 659 | 660 | local sock = self.sock 661 | if not sock then 662 | return nil, "not initialized" 663 | end 664 | 665 | self.packet_no = -1 666 | 667 | local cmd_packet = {strchar(COM_QUERY), query} 668 | local packet_len = 1 + strlen(query) 669 | 670 | local bytes, err = _send_packet(self, cmd_packet, packet_len) 671 | if not bytes then 672 | return nil, err 673 | end 674 | 675 | self.state = STATE_COMMAND_SENT 676 | 677 | --print("packet sent ", bytes, " bytes") 678 | 679 | return bytes 680 | end 681 | 682 | 683 | function read_result(self) 684 | if self.state ~= STATE_COMMAND_SENT then 685 | return nil, "cannot read result in the current context: " .. self.state 686 | end 687 | 688 | local sock = self.sock 689 | if not sock then 690 | return nil, "not initialized" 691 | end 692 | 693 | local packet, typ, err = _recv_packet(self) 694 | if not packet then 695 | return nil, err 696 | end 697 | 698 | if typ == "ERR" then 699 | self.state = STATE_CONNECTED 700 | 701 | local errno, msg, sqlstate = _parse_err_packet(packet) 702 | return nil, msg, errno, sqlstate 703 | end 704 | 705 | if typ == 'OK' then 706 | local res = _parse_ok_packet(packet) 707 | if res and band(res.server_status, SERVER_MORE_RESULTS_EXISTS) ~= 0 then 708 | return res, "again" 709 | end 710 | 711 | self.state = STATE_CONNECTED 712 | return res 713 | end 714 | 715 | if typ ~= 'DATA' then 716 | self.state = STATE_CONNECTED 717 | 718 | return nil, "packet type " .. typ .. " not supported" 719 | end 720 | 721 | -- typ == 'DATA' 722 | 723 | --print("read the result set header packet") 724 | 725 | local field_count, extra = _parse_result_set_header_packet(packet) 726 | 727 | --print("field count: ", field_count) 728 | 729 | local cols = {} 730 | for i = 1, field_count do 731 | local col, err, errno, sqlstate = _recv_field_packet(self) 732 | if not col then 733 | return nil, err, errno, sqlstate 734 | end 735 | 736 | insert(cols, col) 737 | end 738 | 739 | local packet, typ, err = _recv_packet(self) 740 | if not packet then 741 | return nil, err 742 | end 743 | 744 | if typ ~= 'EOF' then 745 | return nil, "unexpected packet type " .. typ .. " while eof packet is " 746 | .. "expected" 747 | end 748 | 749 | -- typ == 'EOF' 750 | 751 | local rows = {} 752 | while true do 753 | --print("reading a row") 754 | 755 | packet, typ, err = _recv_packet(self) 756 | if not packet then 757 | return nil, err 758 | end 759 | 760 | if typ == 'EOF' then 761 | local warning_count, status_flags = _parse_eof_packet(packet) 762 | 763 | --print("status flags: ", status_flags) 764 | 765 | if band(status_flags, SERVER_MORE_RESULTS_EXISTS) ~= 0 then 766 | return rows, "again" 767 | end 768 | 769 | break 770 | end 771 | 772 | -- if typ ~= 'DATA' then 773 | -- return nil, 'bad row packet type: ' .. typ 774 | -- end 775 | 776 | -- typ == 'DATA' 777 | 778 | local row = _parse_row_data_packet(packet, cols) 779 | insert(rows, row) 780 | end 781 | 782 | self.state = STATE_CONNECTED 783 | 784 | return rows 785 | end 786 | 787 | 788 | function query(self, query) 789 | local bytes, err = self:send_query(query) 790 | if not bytes then 791 | return nil, "failed to send query: " .. err 792 | end 793 | 794 | return self:read_result() 795 | end 796 | 797 | 798 | -- to prevent use of casual module global variables 799 | getmetatable(resty.mysql).__newindex = function (table, key, val) 800 | error('attempt to write to undeclared variable "' .. key .. '": ' 801 | .. debug.traceback()) 802 | end 803 | 804 | -------------------------------------------------------------------------------- /resty/random.lua: -------------------------------------------------------------------------------- 1 | module("resty.random", package.seeall) 2 | 3 | _VERSION = '0.06' 4 | 5 | local ffi = require "ffi" 6 | local ffi_new = ffi.new 7 | local ffi_str = ffi.string 8 | local C = ffi.C 9 | 10 | ffi.cdef[[ 11 | int RAND_bytes(unsigned char *buf, int num); 12 | int RAND_pseudo_bytes(unsigned char *buf, int num); 13 | ]] 14 | 15 | 16 | function bytes(len, strong) 17 | local buf = ffi_new("char[?]", len) 18 | if strong then 19 | if C.RAND_bytes(buf, len) == 0 then 20 | return nil 21 | end 22 | else 23 | C.RAND_pseudo_bytes(buf,len) 24 | end 25 | 26 | return ffi_str(buf, len) 27 | end 28 | 29 | -- to prevent use of casual module global variables 30 | getmetatable(resty.random).__newindex = function (table, key, val) 31 | error('attempt to write to undeclared variable "' .. key .. '": ' 32 | .. debug.traceback()) 33 | end 34 | 35 | -------------------------------------------------------------------------------- /resty/redis.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (C) 2012 Zhang "agentzh" Yichun (章亦春) 2 | 3 | module("resty.redis", package.seeall) 4 | 5 | _VERSION = '0.09' 6 | 7 | local commands = { 8 | "append", "auth", "bgrewriteaof", 9 | "bgsave", "blpop", "brpop", 10 | "brpoplpush", "config", "dbsize", 11 | "debug", "decr", "decrby", 12 | "del", "discard", "echo", 13 | "eval", "exec", "exists", 14 | "expire", "expireat", "flushall", 15 | "flushdb", "get", "getbit", 16 | "getrange", "getset", "hdel", 17 | "hexists", "hget", "hgetall", 18 | "hincrby", "hkeys", "hlen", 19 | "hmget", --[[ "hmset", ]] "hset", 20 | "hsetnx", "hvals", "incr", 21 | "incrby", "info", "keys", 22 | "lastsave", "lindex", "linsert", 23 | "llen", "lpop", "lpush", 24 | "lpushx", "lrange", "lrem", 25 | "lset", "ltrim", "mget", 26 | "monitor", "move", "mset", 27 | "msetnx", "multi", "object", 28 | "persist", "ping", "psubscribe", 29 | "publish", "punsubscribe", "quit", 30 | "randomkey", "rename", "renamenx", 31 | "rpop", "rpoplpush", "rpush", 32 | "rpushx", "sadd", "save", 33 | "scard", "sdiff", "sdiffstore", 34 | "select", "set", "setbit", 35 | "setex", "setnx", "setrange", 36 | "shutdown", "sinter", "sinterstore", 37 | "sismember", "slaveof", "slowlog", 38 | "smembers", "smove", "sort", 39 | "spop", "srandmember", "srem", 40 | "strlen", "subscribe", "sunion", 41 | "sunionstore", "sync", "ttl", 42 | "type", "unsubscribe", "unwatch", 43 | "watch", "zadd", "zcard", 44 | "zcount", "zincrby", "zinterstore", 45 | "zrange", "zrangebyscore", "zrank", 46 | "zrem", "zremrangebyrank", "zremrangebyscore", 47 | "zrevrange", "zrevrangebyscore", "zrevrank", 48 | "zscore", "zunionstore", "evalsha" 49 | } 50 | 51 | 52 | local mt = { __index = resty.redis } 53 | 54 | local sub = string.sub 55 | local tcp = ngx.socket.tcp 56 | local insert = table.insert 57 | local len = string.len 58 | local null = ngx.null 59 | 60 | 61 | function new(self) 62 | return setmetatable({ sock = tcp() }, mt) 63 | end 64 | 65 | 66 | function set_timeout(self, timeout) 67 | local sock = self.sock 68 | if not sock then 69 | return nil, "not initialized" 70 | end 71 | 72 | return sock:settimeout(timeout) 73 | end 74 | 75 | 76 | function connect(self, ...) 77 | local sock = self.sock 78 | if not sock then 79 | return nil, "not initialized" 80 | end 81 | 82 | return sock:connect(...) 83 | end 84 | 85 | 86 | function set_keepalive(self, ...) 87 | local sock = self.sock 88 | if not sock then 89 | return nil, "not initialized" 90 | end 91 | 92 | return sock:setkeepalive(...) 93 | end 94 | 95 | 96 | function get_reused_times(self) 97 | local sock = self.sock 98 | if not sock then 99 | return nil, "not initialized" 100 | end 101 | 102 | return sock:getreusedtimes() 103 | end 104 | 105 | 106 | function close(self) 107 | local sock = self.sock 108 | if not sock then 109 | return nil, "not initialized" 110 | end 111 | 112 | return sock:close() 113 | end 114 | 115 | 116 | local function _read_reply(sock) 117 | local line, err = sock:receive() 118 | if not line then 119 | return nil, err 120 | end 121 | 122 | local prefix = sub(line, 1, 1) 123 | 124 | if prefix == "$" then 125 | -- print("bulk reply") 126 | 127 | local size = tonumber(sub(line, 2)) 128 | if size < 0 then 129 | return null 130 | end 131 | 132 | local data, err = sock:receive(size) 133 | if not data then 134 | return nil, err 135 | end 136 | 137 | local dummy, err = sock:receive(2) -- ignore CRLF 138 | if not dummy then 139 | return nil, err 140 | end 141 | 142 | return data 143 | 144 | elseif prefix == "+" then 145 | -- print("status reply") 146 | 147 | return sub(line, 2) 148 | 149 | elseif prefix == "*" then 150 | local n = tonumber(sub(line, 2)) 151 | 152 | -- print("multi-bulk reply: ", n) 153 | if n < 0 then 154 | return null 155 | end 156 | 157 | local vals = {}; 158 | for i = 1, n do 159 | local res, err = _read_reply(sock) 160 | if res then 161 | insert(vals, res) 162 | 163 | elseif res == nil then 164 | return nil, err 165 | 166 | else 167 | -- be a valid redis error value 168 | insert(vals, {false, err}) 169 | end 170 | end 171 | return vals 172 | 173 | elseif prefix == ":" then 174 | -- print("integer reply") 175 | return tonumber(sub(line, 2)) 176 | 177 | elseif prefix == "-" then 178 | -- print("error reply: ", n) 179 | 180 | return false, sub(line, 2) 181 | 182 | else 183 | return nil, "unkown prefix: \"" .. prefix .. "\"" 184 | end 185 | end 186 | 187 | 188 | local function _gen_req(args) 189 | local req = {"*", #args, "\r\n"} 190 | 191 | for i, arg in ipairs(args) do 192 | if not arg then 193 | insert(req, "$-1\r\n") 194 | else 195 | insert(req, "$") 196 | insert(req, len(arg)) 197 | insert(req, "\r\n") 198 | insert(req, arg) 199 | insert(req, "\r\n") 200 | end 201 | end 202 | 203 | return req 204 | end 205 | 206 | 207 | local function _do_cmd(self, ...) 208 | local args = {...} 209 | 210 | local sock = self.sock 211 | if not sock then 212 | return nil, "not initialized" 213 | end 214 | 215 | local req = _gen_req(args) 216 | 217 | local reqs = self._reqs 218 | if reqs then 219 | insert(reqs, req) 220 | return 221 | end 222 | 223 | -- print("request: ", table.concat(req, "")) 224 | 225 | local bytes, err = sock:send(req) 226 | if not bytes then 227 | return nil, err 228 | end 229 | 230 | return _read_reply(sock) 231 | end 232 | 233 | 234 | for i, cmd in ipairs(commands) do 235 | resty.redis[cmd] = 236 | function (self, ...) 237 | return _do_cmd(self, cmd, ...) 238 | end 239 | end 240 | 241 | 242 | function hmset(self, hashname, ...) 243 | local args = {...} 244 | if #args == 1 then 245 | local t = args[1] 246 | local array = {} 247 | for k,v in pairs(t) do 248 | insert(array, k) 249 | insert(array, v) 250 | end 251 | -- print("key", hashname) 252 | return _do_cmd(self, "hmset", hashname, unpack(array)) 253 | end 254 | 255 | -- backwards compatibility 256 | return _do_cmd(self, "hmset", hashname, ...) 257 | end 258 | 259 | 260 | function init_pipeline(self) 261 | self._reqs = {} 262 | end 263 | 264 | 265 | function cancel_pipeline(self) 266 | self._reqs = nil 267 | end 268 | 269 | 270 | function commit_pipeline(self) 271 | local reqs = self._reqs 272 | if not reqs then 273 | return nil, "no pipeline" 274 | end 275 | 276 | self._reqs = nil 277 | 278 | local sock = self.sock 279 | if not sock then 280 | return nil, "not initialized" 281 | end 282 | 283 | local bytes, err = sock:send(reqs) 284 | if not bytes then 285 | return nil, err 286 | end 287 | 288 | local vals = {} 289 | for i = 1, #reqs do 290 | local res, err = _read_reply(sock) 291 | if res then 292 | insert(vals, res) 293 | 294 | elseif res == nil then 295 | return nil, err 296 | 297 | else 298 | -- be a valid redis error value 299 | insert(vals, {false, err}) 300 | end 301 | end 302 | 303 | return vals 304 | end 305 | 306 | 307 | -- to prevent use of casual module global variables 308 | getmetatable(resty.redis).__newindex = function (table, key, val) 309 | error('attempt to write to undeclared variable "' .. key .. '": ' 310 | .. debug.traceback()) 311 | end 312 | 313 | -------------------------------------------------------------------------------- /resty/sha.lua: -------------------------------------------------------------------------------- 1 | module("resty.sha", package.seeall) 2 | 3 | _VERSION = '0.06' 4 | 5 | local ffi = require "ffi" 6 | 7 | ffi.cdef[[ 8 | typedef unsigned long SHA_LONG; 9 | typedef unsigned long long SHA_LONG64; 10 | 11 | enum { 12 | SHA_LBLOCK = 16 13 | }; 14 | ]]; 15 | 16 | -------------------------------------------------------------------------------- /resty/sha1.lua: -------------------------------------------------------------------------------- 1 | module("resty.sha1", package.seeall) 2 | 3 | _VERSION = '0.06' 4 | 5 | local sha = require "resty.sha" 6 | local ffi = require "ffi" 7 | local ffi_new = ffi.new 8 | local ffi_str = ffi.string 9 | local C = ffi.C 10 | 11 | local mt = { __index = resty.sha1 } 12 | 13 | 14 | ffi.cdef[[ 15 | typedef struct SHAstate_st 16 | { 17 | SHA_LONG h0,h1,h2,h3,h4; 18 | SHA_LONG Nl,Nh; 19 | SHA_LONG data[SHA_LBLOCK]; 20 | unsigned int num; 21 | } SHA_CTX; 22 | 23 | int SHA1_Init(SHA_CTX *c); 24 | int SHA1_Update(SHA_CTX *c, const void *data, size_t len); 25 | int SHA1_Final(unsigned char *md, SHA_CTX *c); 26 | ]] 27 | 28 | local digest_len = 20 29 | 30 | local buf = ffi_new("char[?]", digest_len) 31 | local ctx_ptr_type = ffi.typeof("SHA_CTX[1]") 32 | 33 | 34 | function new(self) 35 | local ctx = ffi_new(ctx_ptr_type) 36 | if C.SHA1_Init(ctx) == 0 then 37 | return nil 38 | end 39 | 40 | return setmetatable({ _ctx = ctx }, mt) 41 | end 42 | 43 | 44 | function update(self, s) 45 | return C.SHA1_Update(self._ctx, s, #s) == 1 46 | end 47 | 48 | 49 | function final(self) 50 | if C.SHA1_Final(buf, self._ctx) == 1 then 51 | return ffi_str(buf, digest_len) 52 | end 53 | 54 | return nil 55 | end 56 | 57 | 58 | function reset(self) 59 | return C.SHA1_Init(self._ctx) == 1 60 | end 61 | 62 | 63 | -- to prevent use of casual module global variables 64 | getmetatable(resty.sha1).__newindex = function (table, key, val) 65 | error('attempt to write to undeclared variable "' .. key .. '": ' 66 | .. debug.traceback()) 67 | end 68 | 69 | -------------------------------------------------------------------------------- /resty/sha224.lua: -------------------------------------------------------------------------------- 1 | module("resty.sha224", package.seeall) 2 | 3 | _VERSION = '0.06' 4 | 5 | local sha256 = require "resty.sha256" 6 | local ffi = require "ffi" 7 | local ffi_new = ffi.new 8 | local ffi_str = ffi.string 9 | local C = ffi.C 10 | 11 | local mt = { __index = resty.sha224 } 12 | 13 | 14 | ffi.cdef[[ 15 | int SHA224_Init(SHA256_CTX *c); 16 | int SHA224_Update(SHA256_CTX *c, const void *data, size_t len); 17 | int SHA224_Final(unsigned char *md, SHA256_CTX *c); 18 | ]] 19 | 20 | local digest_len = 28 21 | 22 | local buf = ffi_new("char[?]", digest_len) 23 | local ctx_ptr_type = ffi.typeof("SHA256_CTX[1]") 24 | 25 | 26 | function new(self) 27 | local ctx = ffi_new(ctx_ptr_type) 28 | if C.SHA224_Init(ctx) == 0 then 29 | return nil 30 | end 31 | 32 | return setmetatable({ _ctx = ctx }, mt) 33 | end 34 | 35 | 36 | function update(self, s) 37 | return C.SHA224_Update(self._ctx, s, #s) == 1 38 | end 39 | 40 | 41 | function final(self) 42 | if C.SHA224_Final(buf, self._ctx) == 1 then 43 | return ffi_str(buf, digest_len) 44 | end 45 | 46 | return nil 47 | end 48 | 49 | 50 | function reset(self) 51 | return C.SHA224_Init(self._ctx) == 1 52 | end 53 | 54 | 55 | -- to prevent use of casual module global variables 56 | getmetatable(resty.sha224).__newindex = function (table, key, val) 57 | error('attempt to write to undeclared variable "' .. key .. '": ' 58 | .. debug.traceback()) 59 | end 60 | 61 | -------------------------------------------------------------------------------- /resty/sha256.lua: -------------------------------------------------------------------------------- 1 | module("resty.sha256", package.seeall) 2 | 3 | _VERSION = '0.06' 4 | 5 | local sha = require "resty.sha" 6 | local ffi = require "ffi" 7 | local ffi_new = ffi.new 8 | local ffi_str = ffi.string 9 | local C = ffi.C 10 | 11 | local mt = { __index = resty.sha256 } 12 | 13 | 14 | ffi.cdef[[ 15 | typedef struct SHA256state_st 16 | { 17 | SHA_LONG h[8]; 18 | SHA_LONG Nl,Nh; 19 | SHA_LONG data[SHA_LBLOCK]; 20 | unsigned int num,md_len; 21 | } SHA256_CTX; 22 | 23 | int SHA256_Init(SHA256_CTX *c); 24 | int SHA256_Update(SHA256_CTX *c, const void *data, size_t len); 25 | int SHA256_Final(unsigned char *md, SHA256_CTX *c); 26 | ]] 27 | 28 | local digest_len = 32 29 | 30 | local buf = ffi_new("char[?]", digest_len) 31 | local ctx_ptr_type = ffi.typeof("SHA256_CTX[1]") 32 | 33 | 34 | function new(self) 35 | local ctx = ffi_new(ctx_ptr_type) 36 | if C.SHA256_Init(ctx) == 0 then 37 | return nil 38 | end 39 | 40 | return setmetatable({ _ctx = ctx }, mt) 41 | end 42 | 43 | 44 | function update(self, s) 45 | return C.SHA256_Update(self._ctx, s, #s) == 1 46 | end 47 | 48 | 49 | function final(self) 50 | if C.SHA256_Final(buf, self._ctx) == 1 then 51 | return ffi_str(buf, digest_len) 52 | end 53 | 54 | return nil 55 | end 56 | 57 | 58 | function reset(self) 59 | return C.SHA256_Init(self._ctx) == 1 60 | end 61 | 62 | 63 | -- to prevent use of casual module global variables 64 | getmetatable(resty.sha256).__newindex = function (table, key, val) 65 | error('attempt to write to undeclared variable "' .. key .. '": ' 66 | .. debug.traceback()) 67 | end 68 | 69 | -------------------------------------------------------------------------------- /resty/sha384.lua: -------------------------------------------------------------------------------- 1 | module("resty.sha384", package.seeall) 2 | 3 | _VERSION = '0.06' 4 | 5 | local sha512 = require "resty.sha512" 6 | local ffi = require "ffi" 7 | local ffi_new = ffi.new 8 | local ffi_str = ffi.string 9 | local C = ffi.C 10 | 11 | local mt = { __index = resty.sha384 } 12 | 13 | 14 | ffi.cdef[[ 15 | int SHA384_Init(SHA512_CTX *c); 16 | int SHA384_Update(SHA512_CTX *c, const void *data, size_t len); 17 | int SHA384_Final(unsigned char *md, SHA512_CTX *c); 18 | ]] 19 | 20 | local digest_len = 48 21 | 22 | local buf = ffi_new("char[?]", digest_len) 23 | local ctx_ptr_type = ffi.typeof("SHA512_CTX[1]") 24 | 25 | 26 | function new(self) 27 | local ctx = ffi_new(ctx_ptr_type) 28 | if C.SHA384_Init(ctx) == 0 then 29 | return nil 30 | end 31 | 32 | return setmetatable({ _ctx = ctx }, mt) 33 | end 34 | 35 | 36 | function update(self, s) 37 | return C.SHA384_Update(self._ctx, s, #s) == 1 38 | end 39 | 40 | 41 | function final(self) 42 | if C.SHA384_Final(buf, self._ctx) == 1 then 43 | return ffi_str(buf, digest_len) 44 | end 45 | 46 | return nil 47 | end 48 | 49 | 50 | function reset(self) 51 | return C.SHA384_Init(self._ctx) == 1 52 | end 53 | 54 | 55 | -- to prevent use of casual module global variables 56 | getmetatable(resty.sha384).__newindex = function (table, key, val) 57 | error('attempt to write to undeclared variable "' .. key .. '": ' 58 | .. debug.traceback()) 59 | end 60 | 61 | -------------------------------------------------------------------------------- /resty/sha512.lua: -------------------------------------------------------------------------------- 1 | module("resty.sha512", package.seeall) 2 | 3 | _VERSION = '0.06' 4 | 5 | local sha = require "resty.sha" 6 | local ffi = require "ffi" 7 | local ffi_new = ffi.new 8 | local ffi_str = ffi.string 9 | local C = ffi.C 10 | 11 | local mt = { __index = resty.sha512 } 12 | 13 | 14 | ffi.cdef[[ 15 | enum { 16 | SHA512_CBLOCK = SHA_LBLOCK*8 17 | }; 18 | 19 | typedef struct SHA512state_st 20 | { 21 | SHA_LONG64 h[8]; 22 | SHA_LONG64 Nl,Nh; 23 | union { 24 | SHA_LONG64 d[SHA_LBLOCK]; 25 | unsigned char p[SHA512_CBLOCK]; 26 | } u; 27 | unsigned int num,md_len; 28 | } SHA512_CTX; 29 | 30 | int SHA512_Init(SHA512_CTX *c); 31 | int SHA512_Update(SHA512_CTX *c, const void *data, size_t len); 32 | int SHA512_Final(unsigned char *md, SHA512_CTX *c); 33 | ]] 34 | 35 | local digest_len = 64 36 | 37 | local buf = ffi_new("char[?]", digest_len) 38 | local ctx_ptr_type = ffi.typeof("SHA512_CTX[1]") 39 | 40 | 41 | function new(self) 42 | local ctx = ffi_new(ctx_ptr_type) 43 | if C.SHA512_Init(ctx) == 0 then 44 | return nil 45 | end 46 | 47 | return setmetatable({ _ctx = ctx }, mt) 48 | end 49 | 50 | 51 | function update(self, s) 52 | return C.SHA512_Update(self._ctx, s, #s) == 1 53 | end 54 | 55 | 56 | function final(self) 57 | if C.SHA512_Final(buf, self._ctx) == 1 then 58 | return ffi_str(buf, digest_len) 59 | end 60 | 61 | return nil 62 | end 63 | 64 | 65 | function reset(self) 66 | return C.SHA512_Init(self._ctx) == 1 67 | end 68 | 69 | 70 | -- to prevent use of casual module global variables 71 | getmetatable(resty.sha512).__newindex = function (table, key, val) 72 | error('attempt to write to undeclared variable "' .. key .. '": ' 73 | .. debug.traceback()) 74 | end 75 | 76 | -------------------------------------------------------------------------------- /resty/string.lua: -------------------------------------------------------------------------------- 1 | module("resty.string", package.seeall) 2 | 3 | _VERSION = '0.06' 4 | 5 | local ffi = require "ffi" 6 | local ffi_new = ffi.new 7 | local ffi_str = ffi.string 8 | local C = ffi.C 9 | 10 | ffi.cdef[[ 11 | typedef unsigned char u_char; 12 | 13 | u_char * ngx_hex_dump(u_char *dst, const u_char *src, size_t len); 14 | 15 | intptr_t ngx_atoi(const unsigned char *line, size_t n); 16 | ]] 17 | 18 | local str_type = ffi.typeof("uint8_t[?]") 19 | 20 | 21 | function to_hex(s) 22 | local len = #s * 2 23 | local buf = ffi_new(str_type, len) 24 | C.ngx_hex_dump(buf, s, #s) 25 | return ffi_str(buf, len) 26 | end 27 | 28 | 29 | function atoi(s) 30 | return tonumber(C.ngx_atoi(s, #s)) 31 | end 32 | 33 | 34 | -- to prevent use of casual module global variables 35 | getmetatable(resty.string).__newindex = function (table, key, val) 36 | error('attempt to write to undeclared variable "' .. key .. '": ' 37 | .. debug.traceback()) 38 | end 39 | 40 | -------------------------------------------------------------------------------- /resty/upload.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (C) 2012 Zhang "agentzh" Yichun (章亦春) 2 | 3 | module("resty.upload", package.seeall) 4 | 5 | _VERSION = '0.03' 6 | 7 | local MAX_LINE_SIZE = 512 8 | 9 | local STATE_BEGIN = 1 10 | local STATE_READING_HEADER = 2 11 | local STATE_READING_BODY = 3 12 | local STATE_EOF = 4 13 | 14 | local mt = { __index = resty.upload } 15 | 16 | local sub = string.sub 17 | local req_socket = ngx.req.socket 18 | local insert = table.insert 19 | local len = string.len 20 | local null = ngx.null 21 | local state_handlers 22 | 23 | function new(self, chunk_size) 24 | local boundary = get_boundary() 25 | if not boundary then 26 | return nil, "no boundary defined in Content-Type" 27 | end 28 | 29 | -- print('boundary: "', boundary, '"') 30 | 31 | local sock, err = req_socket() 32 | if not sock then 33 | return nil, err 34 | end 35 | 36 | 37 | local read2boundary, err = sock:receiveuntil("--" .. boundary) 38 | if not read2boundary then 39 | return nil, err 40 | end 41 | 42 | local read_line, err = sock:receiveuntil("\r\n") 43 | if not read_line then 44 | return nil, err 45 | end 46 | 47 | return setmetatable({ 48 | sock = sock, 49 | size = chunk_size or 4096, 50 | read2boundary = read2boundary, 51 | read_line = read_line, 52 | boundary = boundary, 53 | state = STATE_BEGIN 54 | }, mt) 55 | end 56 | 57 | 58 | function set_timeout(self, timeout) 59 | local sock = self.sock 60 | if not sock then 61 | return nil, "not initialized" 62 | end 63 | 64 | return sock:settimeout(timeout) 65 | end 66 | 67 | 68 | function read(self) 69 | local size = self.size 70 | 71 | local handler = state_handlers[self.state] 72 | if handler then 73 | return handler(self) 74 | end 75 | 76 | return nil, nil, "bad state: " .. self.state 77 | end 78 | 79 | 80 | function read_preamble(self) 81 | local sock = self.sock 82 | if not sock then 83 | return nil, nil, "not initialized" 84 | end 85 | 86 | local size = self.size 87 | local read2boundary = self.read2boundary 88 | 89 | while true do 90 | local preamble, err = read2boundary(size) 91 | if not preamble then 92 | break 93 | end 94 | 95 | -- discard the preamble data chunk 96 | -- print("read preamble: ", preamble) 97 | end 98 | 99 | local ok, err = discard_line(self) 100 | if not ok then 101 | return nil, nil, err 102 | end 103 | 104 | local read2boundary, err = sock:receiveuntil("\r\n--" .. self.boundary) 105 | if not read2boundary then 106 | return nil, nil, err 107 | end 108 | 109 | self.read2boundary = read2boundary 110 | 111 | self.state = STATE_READING_HEADER 112 | return read_header(self) 113 | end 114 | 115 | 116 | function discard_line(self) 117 | local read_line = self.read_line 118 | 119 | local line, err = self.read_line(MAX_LINE_SIZE) 120 | if not line then 121 | return nil, err 122 | end 123 | 124 | local dummy, err = self.read_line(1) 125 | if dummy then 126 | return nil, table.concat({"line too long: ", line, dummy, 127 | "..."}, "") 128 | end 129 | 130 | if err then 131 | return nil, err 132 | end 133 | 134 | return 1 135 | end 136 | 137 | 138 | function discard_rest(self) 139 | local sock = self.sock 140 | local size = self.size 141 | 142 | while true do 143 | local dummy, err = sock:receive(size) 144 | if err and err ~= 'closed' then 145 | return nil, err 146 | end 147 | 148 | if not dummy then 149 | return 1 150 | end 151 | end 152 | end 153 | 154 | 155 | function read_header(self) 156 | local read_line = self.read_line 157 | 158 | local line, err = read_line(MAX_LINE_SIZE) 159 | if err then 160 | return nil, nil, err 161 | end 162 | 163 | local dummy, err = read_line(1) 164 | if dummy then 165 | return nil, nil, table.concat({"line too long: ", line, dummy, 166 | "..."}, "") 167 | end 168 | 169 | if err then 170 | return nil, nil, err 171 | end 172 | 173 | -- print("read line: ", line) 174 | 175 | if line == "" then 176 | -- after the last header 177 | self.state = STATE_READING_BODY 178 | return read_body_part(self) 179 | end 180 | 181 | local key, value = string.match(line, "([^: \t]+)%s*:%s*(.+)") 182 | if not key then 183 | return 'header', line 184 | end 185 | 186 | return 'header', {key, value, line} 187 | end 188 | 189 | 190 | function read_body_part(self) 191 | local read2boundary = self.read2boundary 192 | 193 | local chunk, err = read2boundary(self.size) 194 | if err then 195 | return nil, nil, err 196 | end 197 | 198 | if not chunk then 199 | local sock = self.sock 200 | 201 | local data = sock:receive(2) 202 | if data == "--" then 203 | local ok, err = discard_rest(self) 204 | if not ok then 205 | return nil, nil, err 206 | end 207 | 208 | self.state = STATE_EOF 209 | return "part_end" 210 | end 211 | 212 | if data ~= "\r\n" then 213 | ok, err = discard_line(self) 214 | if not ok then 215 | return nil, nil, err 216 | end 217 | end 218 | 219 | self.state = STATE_READING_HEADER 220 | return "part_end" 221 | end 222 | 223 | return "body", chunk 224 | end 225 | 226 | 227 | function eof() 228 | return "eof", nil 229 | end 230 | 231 | 232 | function get_boundary() 233 | local header = ngx.var.content_type 234 | if not header then 235 | return nil 236 | end 237 | 238 | return string.match(header, ";%s+boundary=(%S+)") 239 | end 240 | 241 | 242 | state_handlers = { 243 | read_preamble, 244 | read_header, 245 | read_body_part, 246 | eof 247 | } 248 | 249 | 250 | -- to prevent use of casual module global variables 251 | getmetatable(resty.upload).__newindex = function (table, key, val) 252 | error('attempt to write to undeclared variable "' .. key .. '": ' 253 | .. debug.traceback()) 254 | end 255 | 256 | -------------------------------------------------------------------------------- /resty/url.lua: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------------- 2 | -- URI parsing, composition and relative URL resolution 3 | -- LuaSocket toolkit. 4 | -- Author: Diego Nehab 5 | -- RCS ID: $Id: url.lua,v 1.38 2006/04/03 04:45:42 diego Exp $ 6 | ----------------------------------------------------------------------------- 7 | 8 | ----------------------------------------------------------------------------- 9 | -- Declare module 10 | ----------------------------------------------------------------------------- 11 | local string = require("string") 12 | local base = _G 13 | local table = require("table") 14 | module("resty.url", package.seeall) 15 | 16 | ----------------------------------------------------------------------------- 17 | -- Module version 18 | ----------------------------------------------------------------------------- 19 | _VERSION = "URL 1.0.1" 20 | 21 | ----------------------------------------------------------------------------- 22 | -- Encodes a string into its escaped hexadecimal representation 23 | -- Input 24 | -- s: binary string to be encoded 25 | -- Returns 26 | -- escaped representation of string binary 27 | ----------------------------------------------------------------------------- 28 | function escape(s) 29 | return string.gsub(s, "([^A-Za-z0-9_])", function(c) 30 | return string.format("%%%02x", string.byte(c)) 31 | end) 32 | end 33 | 34 | ----------------------------------------------------------------------------- 35 | -- Protects a path segment, to prevent it from interfering with the 36 | -- url parsing. 37 | -- Input 38 | -- s: binary string to be encoded 39 | -- Returns 40 | -- escaped representation of string binary 41 | ----------------------------------------------------------------------------- 42 | local function make_set(t) 43 | local s = {} 44 | for i,v in base.ipairs(t) do 45 | s[t[i]] = 1 46 | end 47 | return s 48 | end 49 | 50 | -- these are allowed withing a path segment, along with alphanum 51 | -- other characters must be escaped 52 | local segment_set = make_set { 53 | "-", "_", ".", "!", "~", "*", "'", "(", 54 | ")", ":", "@", "&", "=", "+", "$", ",", 55 | } 56 | 57 | local function protect_segment(s) 58 | return string.gsub(s, "([^A-Za-z0-9_])", function (c) 59 | if segment_set[c] then return c 60 | else return string.format("%%%02x", string.byte(c)) end 61 | end) 62 | end 63 | 64 | ----------------------------------------------------------------------------- 65 | -- Encodes a string into its escaped hexadecimal representation 66 | -- Input 67 | -- s: binary string to be encoded 68 | -- Returns 69 | -- escaped representation of string binary 70 | ----------------------------------------------------------------------------- 71 | function unescape(s) 72 | return string.gsub(s, "%%(%x%x)", function(hex) 73 | return string.char(base.tonumber(hex, 16)) 74 | end) 75 | end 76 | 77 | ----------------------------------------------------------------------------- 78 | -- Builds a path from a base path and a relative path 79 | -- Input 80 | -- base_path 81 | -- relative_path 82 | -- Returns 83 | -- corresponding absolute path 84 | ----------------------------------------------------------------------------- 85 | local function absolute_path(base_path, relative_path) 86 | if string.sub(relative_path, 1, 1) == "/" then return relative_path end 87 | local path = string.gsub(base_path, "[^/]*$", "") 88 | path = path .. relative_path 89 | path = string.gsub(path, "([^/]*%./)", function (s) 90 | if s ~= "./" then return s else return "" end 91 | end) 92 | path = string.gsub(path, "/%.$", "/") 93 | local reduced 94 | while reduced ~= path do 95 | reduced = path 96 | path = string.gsub(reduced, "([^/]*/%.%./)", function (s) 97 | if s ~= "../../" then return "" else return s end 98 | end) 99 | end 100 | path = string.gsub(reduced, "([^/]*/%.%.)$", function (s) 101 | if s ~= "../.." then return "" else return s end 102 | end) 103 | return path 104 | end 105 | 106 | ----------------------------------------------------------------------------- 107 | -- Parses a url and returns a table with all its parts according to RFC 2396 108 | -- The following grammar describes the names given to the URL parts 109 | -- ::= :///;?# 110 | -- ::= @: 111 | -- ::= [:] 112 | -- :: = {/} 113 | -- Input 114 | -- url: uniform resource locator of request 115 | -- default: table with default values for each field 116 | -- Returns 117 | -- table with the following fields, where RFC naming conventions have 118 | -- been preserved: 119 | -- scheme, authority, userinfo, user, password, host, port, 120 | -- path, params, query, fragment 121 | -- Obs: 122 | -- the leading '/' in {/} is considered part of 123 | ----------------------------------------------------------------------------- 124 | function parse(url, default) 125 | -- initialize default parameters 126 | local parsed = {} 127 | for i,v in base.pairs(default or parsed) do parsed[i] = v end 128 | -- empty url is parsed to nil 129 | if not url or url == "" then return nil, "invalid url" end 130 | -- remove whitespace 131 | -- url = string.gsub(url, "%s", "") 132 | -- get fragment 133 | url = string.gsub(url, "#(.*)$", function(f) 134 | parsed.fragment = f 135 | return "" 136 | end) 137 | -- get scheme 138 | url = string.gsub(url, "^([%w][%w%+%-%.]*)%:", 139 | function(s) parsed.scheme = s; return "" end) 140 | -- get authority 141 | url = string.gsub(url, "^//([^/]*)", function(n) 142 | parsed.authority = n 143 | return "" 144 | end) 145 | -- get query stringing 146 | url = string.gsub(url, "%?(.*)", function(q) 147 | parsed.query = q 148 | return "" 149 | end) 150 | -- get params 151 | url = string.gsub(url, "%;(.*)", function(p) 152 | parsed.params = p 153 | return "" 154 | end) 155 | -- path is whatever was left 156 | if url ~= "" then parsed.path = url end 157 | local authority = parsed.authority 158 | if not authority then return parsed end 159 | authority = string.gsub(authority,"^([^@]*)@", 160 | function(u) parsed.userinfo = u; return "" end) 161 | authority = string.gsub(authority, ":([^:]*)$", 162 | function(p) parsed.port = p; return "" end) 163 | if authority ~= "" then parsed.host = authority end 164 | local userinfo = parsed.userinfo 165 | if not userinfo then return parsed end 166 | userinfo = string.gsub(userinfo, ":([^:]*)$", 167 | function(p) parsed.password = p; return "" end) 168 | parsed.user = userinfo 169 | return parsed 170 | end 171 | 172 | ----------------------------------------------------------------------------- 173 | -- Rebuilds a parsed URL from its components. 174 | -- Components are protected if any reserved or unallowed characters are found 175 | -- Input 176 | -- parsed: parsed URL, as returned by parse 177 | -- Returns 178 | -- a stringing with the corresponding URL 179 | ----------------------------------------------------------------------------- 180 | function build(parsed) 181 | local ppath = parse_path(parsed.path or "") 182 | local url = build_path(ppath) 183 | if parsed.params then url = url .. ";" .. parsed.params end 184 | if parsed.query then url = url .. "?" .. parsed.query end 185 | local authority = parsed.authority 186 | if parsed.host then 187 | authority = parsed.host 188 | if parsed.port then authority = authority .. ":" .. parsed.port end 189 | local userinfo = parsed.userinfo 190 | if parsed.user then 191 | userinfo = parsed.user 192 | if parsed.password then 193 | userinfo = userinfo .. ":" .. parsed.password 194 | end 195 | end 196 | if userinfo then authority = userinfo .. "@" .. authority end 197 | end 198 | if authority then url = "//" .. authority .. url end 199 | if parsed.scheme then url = parsed.scheme .. ":" .. url end 200 | if parsed.fragment then url = url .. "#" .. parsed.fragment end 201 | -- url = string.gsub(url, "%s", "") 202 | return url 203 | end 204 | 205 | ----------------------------------------------------------------------------- 206 | -- Builds a absolute URL from a base and a relative URL according to RFC 2396 207 | -- Input 208 | -- base_url 209 | -- relative_url 210 | -- Returns 211 | -- corresponding absolute url 212 | ----------------------------------------------------------------------------- 213 | function absolute(base_url, relative_url) 214 | if base.type(base_url) == "table" then 215 | base_parsed = base_url 216 | base_url = build(base_parsed) 217 | else 218 | base_parsed = parse(base_url) 219 | end 220 | local relative_parsed = parse(relative_url) 221 | if not base_parsed then return relative_url 222 | elseif not relative_parsed then return base_url 223 | elseif relative_parsed.scheme then return relative_url 224 | else 225 | relative_parsed.scheme = base_parsed.scheme 226 | if not relative_parsed.authority then 227 | relative_parsed.authority = base_parsed.authority 228 | if not relative_parsed.path then 229 | relative_parsed.path = base_parsed.path 230 | if not relative_parsed.params then 231 | relative_parsed.params = base_parsed.params 232 | if not relative_parsed.query then 233 | relative_parsed.query = base_parsed.query 234 | end 235 | end 236 | else 237 | relative_parsed.path = absolute_path(base_parsed.path or "", 238 | relative_parsed.path) 239 | end 240 | end 241 | return build(relative_parsed) 242 | end 243 | end 244 | 245 | ----------------------------------------------------------------------------- 246 | -- Breaks a path into its segments, unescaping the segments 247 | -- Input 248 | -- path 249 | -- Returns 250 | -- segment: a table with one entry per segment 251 | ----------------------------------------------------------------------------- 252 | function parse_path(path) 253 | local parsed = {} 254 | path = path or "" 255 | --path = string.gsub(path, "%s", "") 256 | string.gsub(path, "([^/]+)", function (s) table.insert(parsed, s) end) 257 | for i = 1, table.getn(parsed) do 258 | parsed[i] = unescape(parsed[i]) 259 | end 260 | if string.sub(path, 1, 1) == "/" then parsed.is_absolute = 1 end 261 | if string.sub(path, -1, -1) == "/" then parsed.is_directory = 1 end 262 | return parsed 263 | end 264 | 265 | ----------------------------------------------------------------------------- 266 | -- Builds a path component from its segments, escaping protected characters. 267 | -- Input 268 | -- parsed: path segments 269 | -- unsafe: if true, segments are not protected before path is built 270 | -- Returns 271 | -- path: corresponding path stringing 272 | ----------------------------------------------------------------------------- 273 | function build_path(parsed, unsafe) 274 | local path = "" 275 | local n = table.getn(parsed) 276 | if unsafe then 277 | for i = 1, n-1 do 278 | path = path .. parsed[i] 279 | path = path .. "/" 280 | end 281 | if n > 0 then 282 | path = path .. parsed[n] 283 | if parsed.is_directory then path = path .. "/" end 284 | end 285 | else 286 | for i = 1, n-1 do 287 | path = path .. protect_segment(parsed[i]) 288 | path = path .. "/" 289 | end 290 | if n > 0 then 291 | path = path .. protect_segment(parsed[n]) 292 | if parsed.is_directory then path = path .. "/" end 293 | end 294 | end 295 | if parsed.is_absolute then path = "/" .. path end 296 | return path 297 | end 298 | -------------------------------------------------------------------------------- /resty/weedfs.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Created by IntelliJ IDEA. 3 | -- User: Medcl 4 | -- Date: 12-9-15 5 | -- Time: 上午11:14 6 | -- 7 | ----------------------------------------------------------------------------- 8 | -- WeedFS 9 | -- Author: Medcl 10 | -- RCS ID: $Id: weedfs.lua,v 1.0.0 2012/09/15 11:20:00 11 | ----------------------------------------------------------------------------- 12 | 13 | ----------------------------------------------------------------------------- 14 | -- Declare module 15 | ----------------------------------------------------------------------------- 16 | local string = require("string") 17 | local url = require("resty") 18 | module("resty.weedfs", package.seeall) 19 | 20 | ----------------------------------------------------------------------------- 21 | -- Module version 22 | ----------------------------------------------------------------------------- 23 | _VERSION = "URL 1.0.0" 24 | 25 | 26 | function escape(s) 27 | return string.gsub(s, "([^A-Za-z0-9_])", function(c) 28 | return string.format("%%%02x", string.byte(c)) 29 | end) 30 | end -------------------------------------------------------------------------------- /weedfs.lua: -------------------------------------------------------------------------------- 1 | --author:medcl,m@medcl.net,http://log.medcl.net 2 | function table.contains(table, element) 3 | for _, value in pairs(table) do 4 | if value == element then 5 | return true 6 | end 7 | end 8 | return false 9 | end 10 | 11 | function file_exists(name) 12 | local f=io.open(name,"r") 13 | if f~=nil then io.close(f) return true else return false end 14 | end 15 | 16 | function exit_with_code(code) 17 | -- ngx.say(code) 18 | ngx.exit(code) 19 | return 20 | end 21 | 22 | function req_orig_file(file_url) 23 | local http = require"resty.http" 24 | local hc = http:new() 25 | local ok, code, headers, status, body = hc:request{ 26 | url = file_url, 27 | timeout = 3000, 28 | } 29 | 30 | if code == 301 or code == 302 then 31 | file_url = string.match(body,'"(.+)"') 32 | ok, code, headers, status, body = hc:request{ 33 | url = file_url, 34 | timeout = 3000, 35 | } 36 | end 37 | 38 | if code ~= 200 then 39 | return exit_with_code(404) 40 | else 41 | if body == nil then 42 | return exit_with_code(404) 43 | else 44 | if (body..'a') == 'a' then 45 | return exit_with_code(404) 46 | else 47 | ngx.say(body) 48 | ngx.flush(true) 49 | exit_with_code(200) 50 | return 51 | end 52 | end 53 | end 54 | end 55 | 56 | 57 | function save_orig_file(file_url,local_file_folder,local_file_path) 58 | local http = require"resty.http" 59 | local hc = http:new() 60 | local ok, code, headers, status, body = hc:request{ 61 | url = file_url, 62 | timeout = 3000, 63 | } 64 | 65 | 66 | if code == 301 or code == 302 then 67 | file_url = string.match(body,'"(.+)"') 68 | ok, code, headers, status, body = hc:request{ 69 | url = file_url, 70 | timeout = 3000, 71 | } 72 | end 73 | 74 | if code ~= 200 then 75 | return exit_with_code(404) 76 | else 77 | if body == nil then 78 | return exit_with_code(404) 79 | else 80 | if (body..'a') == 'a' then 81 | return exit_with_code(404) 82 | else 83 | local mkdir_command ="mkdir "..local_file_folder.." -p >/dev/null 2>&1 " 84 | os.execute(mkdir_command) 85 | file = io.open(local_file_path, "w"); 86 | if (file) then 87 | file:write(body); 88 | file:close(); 89 | else 90 | return exit_with_code(500) 91 | end 92 | end 93 | end 94 | end 95 | end 96 | 97 | function req_volume_server() 98 | -- TODO,get from weedfs,curl http://localhost:9333/dir/lookup?volumeId=3 99 | end 100 | 101 | 102 | function process_img(file_volumn,file_id,file_size,file_url) 103 | local image_sizes = { "100x100", "80x80", "800x600", "40x40" ,"480x320","360x200","320x210","640x420","160x160","800x400","200x200"}; 104 | local scale_image_sizes = { "100x100s", "80x80s", "800x600s", "40x40s" ,"480x320s","360x200s","320x210s","640x420s","160x160s","800x400s","200x200s"}; 105 | local local_file_root = ngx.var.local_img_fs_root .."images/"; 106 | local local_file_in_folder = local_file_root .."orig/".. file_volumn .."/"; 107 | local local_file_in_path = local_file_in_folder.. file_id ..".jpg"; 108 | 109 | local local_file_out_folder = local_file_root.. file_size .."/" .. file_volumn .."/"; 110 | local local_file_out_path = local_file_out_folder.. file_id ..".jpg"; 111 | local local_file_out_rel_path = "/images/".. file_size .."/" .. file_volumn .."/".. file_id ..".jpg"; 112 | 113 | local mkdir_command ="mkdir "..local_file_out_folder.." -p >/dev/null 2>&1 " 114 | local convert_command; 115 | 116 | --return if has a local copy 117 | if(file_exists(local_file_out_path))then 118 | local file = io.open(local_file_out_path, "r"); 119 | if (file) then 120 | local content= file:read("*a"); 121 | file:close(); 122 | ngx.say(content) 123 | ngx.flush(true) 124 | return 125 | end 126 | end 127 | 128 | --get original file 129 | if file_size == "orig" then 130 | return req_orig_file(file_url) 131 | end 132 | 133 | if table.contains(scale_image_sizes, file_size) then 134 | file_size=string.sub(file_size, 1, -2) 135 | convert_command = "gm convert " .. local_file_in_path .. " -resize '" .. file_size .. "' -quality 90 " .. local_file_out_path .. ">/dev/null 2>&1 "; 136 | elseif (table.contains(image_sizes, file_size)) then 137 | convert_command = "gm convert " .. local_file_in_path .. " -thumbnail " .. file_size .. "^ -quality 90 -gravity center -extent " .. file_size .. " " .. local_file_out_path .. ">/dev/null 2>&1 "; 138 | else 139 | return exit_with_code(404) 140 | end 141 | --ngx.say('enter') 142 | if(not file_exists(local_file_in_path))then 143 | save_orig_file(file_url,local_file_in_folder,local_file_in_path) 144 | end 145 | 146 | os.execute(mkdir_command) 147 | os.execute(convert_command) 148 | 149 | if(file_exists(local_file_out_path))then 150 | local file = io.open(local_file_out_path, "r"); 151 | if (file) then 152 | local content= file:read("*a"); 153 | file:close(); 154 | ngx.say(content) 155 | ngx.flush(true) 156 | else 157 | return exit_with_code(500) 158 | end 159 | end 160 | 161 | end 162 | 163 | function process_audio(file_volumn,file_id,file_size,file_url) 164 | 165 | local audio_sizes = { "mp3" }; 166 | local local_file_root = ngx.var.local_audio_fs_root .."audios/"; 167 | local local_file_in_folder = local_file_root .."orig/".. file_volumn .."/"; 168 | local local_file_in_path = local_file_in_folder.. file_id ..".mp3"; 169 | 170 | local local_file_out_folder = local_file_root.. file_size .."/" .. file_volumn .."/"; 171 | local local_file_out_path = local_file_out_folder.. file_id ..".mp3"; 172 | local local_file_out_rel_path = "/audios/".. file_size .."/" .. file_volumn .."/".. file_id ..".mp3"; 173 | 174 | if(file_exists(local_file_out_path))then 175 | local file = io.open(local_file_out_path, "r"); 176 | if (file) then 177 | local content= file:read("*a"); 178 | file:close(); 179 | ngx.say(content) 180 | ngx.flush(true) 181 | return 182 | end 183 | end 184 | 185 | --get original file 186 | if file_size == "orig" then 187 | return req_orig_file(file_url) 188 | end 189 | 190 | if table.contains(audio_sizes, file_size) then 191 | if(not file_exists(local_file_in_path))then 192 | save_orig_file(file_url,local_file_in_folder,local_file_in_path) 193 | end 194 | 195 | if(file_exists(local_file_in_path))then 196 | local mkdir_command ="mkdir "..local_file_out_folder.." -p >/dev/null 2>&1 " 197 | local convert_command = "ffmpeg -i " .. local_file_in_path .. " -ab 64 " .. local_file_out_path .. " >/dev/null 2>&1 "; 198 | os.execute(mkdir_command) 199 | os.execute(convert_command) 200 | if(file_exists(local_file_out_path))then 201 | local file = io.open(local_file_out_path, "r"); 202 | if (file) then 203 | local content= file:read("*a"); 204 | file:close(); 205 | ngx.say(content) 206 | ngx.flush(true) 207 | else 208 | return exit_with_code(500) 209 | end 210 | end 211 | end 212 | else 213 | return exit_with_code(404) 214 | end 215 | end 216 | 217 | local file_volumn = ngx.var.arg_volumn 218 | local file_id = ngx.var.arg_id 219 | local file_url = ngx.var.weed_img_root_url .. file_volumn .. "," .. file_id 220 | local process_type = ngx.var.arg_type or "na"; 221 | local file_size = ngx.var.arg_size or "na"; 222 | 223 | if ngx.var.arg_size == nil or ngx.var.arg_volumn == nil or ngx.var.arg_id == nil then 224 | return exit_with_code(400) 225 | end 226 | 227 | if(process_type == "img") then 228 | process_img(file_volumn,file_id,file_size,file_url) 229 | elseif(process_type == "audio")then 230 | process_audio(file_volumn,file_id,file_size,file_url) 231 | end 232 | --------------------------------------------------------------------------------