├── .DS_Store ├── .gitignore ├── Doc └── graph.png ├── DockerResources ├── .DS_Store ├── build.sh ├── lua │ ├── lib │ │ ├── disque │ │ │ └── disque.lua │ │ ├── sha1 │ │ │ └── sha1.lua │ │ └── ws │ │ │ ├── derivedfrom.txt │ │ │ ├── websocketProtocol.lua │ │ │ └── websocketServer.lua │ └── sample_disque_client.lua └── nginx.conf ├── README.md ├── centos.dockerfile ├── client.html └── ubuntu.dockerfile /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sassembla/nginx-luajit-ws/7c833aede554e173310cb605eb03761a38e13f03/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | logs/.DS_Store 2 | logs/error.log 3 | logs/host.access.log 4 | logs/nginx.pid 5 | -------------------------------------------------------------------------------- /Doc/graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sassembla/nginx-luajit-ws/7c833aede554e173310cb605eb03761a38e13f03/Doc/graph.png -------------------------------------------------------------------------------- /DockerResources/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sassembla/nginx-luajit-ws/7c833aede554e173310cb605eb03761a38e13f03/DockerResources/.DS_Store -------------------------------------------------------------------------------- /DockerResources/build.sh: -------------------------------------------------------------------------------- 1 | PROJECT_PATH=$(pwd) 2 | 3 | NGINX_VERSION=1.11.9 4 | NGX_DEVEL_KIT="dependencies/ngx_devel_kit-0.3.0" 5 | LUA_NGX_MOD="dependencies/lua-nginx-module-0.10.7" 6 | 7 | export LUAJIT_LIB=/usr/local/lib 8 | export LUAJIT_INC=/usr/local/include/luajit-2.1 9 | 10 | # make & install nginx to PROJECT_PATH/NGINX_VERSION 11 | ./configure \ 12 | --with-ld-opt="-Wl,-rpath,$LUAJIT_LIB" \ 13 | --prefix=$PROJECT_PATH/$NGINX_VERSION \ 14 | --add-module=$NGX_DEVEL_KIT \ 15 | --add-module=$LUA_NGX_MOD 16 | 17 | make -j2 18 | make install -------------------------------------------------------------------------------- /DockerResources/lua/lib/disque/disque.lua: -------------------------------------------------------------------------------- 1 | -- Disque client for lua. 2 | 3 | local sub = string.sub 4 | local byte = string.byte 5 | local tcp = ngx.socket.tcp 6 | local concat = table.concat 7 | local null = ngx.null 8 | local pairs = pairs 9 | local unpack = unpack 10 | local setmetatable = setmetatable 11 | local tonumber = tonumber 12 | local error = error 13 | 14 | 15 | local ok, new_tab = pcall(require, "table.new") 16 | if not ok or type(new_tab) ~= "function" then 17 | new_tab = function (narr, nrec) return {} end 18 | end 19 | 20 | 21 | local _M = new_tab(0, 155) 22 | _M._VERSION = '0.1' 23 | 24 | 25 | local commands = { 26 | "addjob", "getjob", "fastack" 27 | } 28 | 29 | 30 | local mt = { __index = _M } 31 | 32 | 33 | function _M.new(self) 34 | local sock, err = tcp() 35 | if not sock then 36 | return nil, err 37 | end 38 | return setmetatable({ sock = sock }, mt) 39 | end 40 | 41 | 42 | function _M.set_timeout(self, timeout) 43 | local sock = self.sock 44 | if not sock then 45 | return nil, "not initialized" 46 | end 47 | 48 | return sock:settimeout(timeout) 49 | end 50 | 51 | 52 | function _M.connect(self, ...) 53 | local sock = self.sock 54 | if not sock then 55 | return nil, "not initialized" 56 | end 57 | 58 | self.subscribed = nil 59 | 60 | return sock:connect(...) 61 | end 62 | 63 | 64 | function _M.set_keepalive(self, ...) 65 | local sock = self.sock 66 | if not sock then 67 | return nil, "not initialized" 68 | end 69 | 70 | if self.subscribed then 71 | return nil, "subscribed state" 72 | end 73 | 74 | return sock:setkeepalive(...) 75 | end 76 | 77 | 78 | function _M.get_reused_times(self) 79 | local sock = self.sock 80 | if not sock then 81 | return nil, "not initialized" 82 | end 83 | 84 | return sock:getreusedtimes() 85 | end 86 | 87 | 88 | local function close(self) 89 | local sock = self.sock 90 | if not sock then 91 | return nil, "not initialized" 92 | end 93 | 94 | return sock:close() 95 | end 96 | _M.close = close 97 | 98 | 99 | local function _read_reply(self, sock) 100 | local line, err = sock:receive() 101 | if not line then 102 | if err == "timeout" and not self.subscribed then 103 | sock:close() 104 | end 105 | return nil, err 106 | end 107 | 108 | local prefix = byte(line) 109 | 110 | if prefix == 36 then -- char '$' 111 | -- print("bulk reply") 112 | 113 | local size = tonumber(sub(line, 2)) 114 | if size < 0 then 115 | return null 116 | end 117 | 118 | local data, err = sock:receive(size) 119 | if not data then 120 | if err == "timeout" then 121 | sock:close() 122 | end 123 | return nil, err 124 | end 125 | 126 | local dummy, err = sock:receive(2) -- ignore CRLF 127 | if not dummy then 128 | return nil, err 129 | end 130 | 131 | return data 132 | 133 | elseif prefix == 43 then -- char '+' 134 | -- ngx.log(ngx.ERR, "status reply line:", line) 135 | 136 | return sub(line, 2) 137 | 138 | elseif prefix == 42 then -- char '*' 139 | local n = tonumber(sub(line, 2)) 140 | 141 | -- ngx.log(ngx.ERR, "multi-bulk reply: ", n) 142 | if n < 0 then 143 | return null 144 | end 145 | 146 | local vals = new_tab(n, 0); 147 | local nvals = 0 148 | for i = 1, n do 149 | local res, err = _read_reply(self, sock) 150 | if res then 151 | nvals = nvals + 1 152 | vals[nvals] = res 153 | 154 | elseif res == nil then 155 | return nil, err 156 | 157 | else 158 | -- be a valid redis error value 159 | nvals = nvals + 1 160 | vals[nvals] = {false, err} 161 | end 162 | end 163 | 164 | return vals 165 | 166 | elseif prefix == 58 then -- char ':' 167 | -- ngx.log(ngx.ERR, "integer reply") 168 | return tonumber(sub(line, 2)) 169 | 170 | elseif prefix == 45 then -- char '-' 171 | -- ngx.log(ngx.ERR, "error reply: ", n) 172 | 173 | return false, sub(line, 2) 174 | 175 | else 176 | return nil, "unkown prefix: \"" .. prefix .. "\"" 177 | end 178 | end 179 | 180 | 181 | local function _gen_req(args) 182 | local nargs = #args 183 | 184 | local req = new_tab(nargs + 1, 0) 185 | req[1] = "*" .. nargs .. "\r\n" 186 | local nbits = 1 187 | 188 | for i = 1, nargs do 189 | local arg = args[i] 190 | nbits = nbits + 1 191 | 192 | if not arg then 193 | req[nbits] = "$-1\r\n" 194 | 195 | else 196 | if type(arg) ~= "string" then 197 | arg = tostring(arg) 198 | end 199 | req[nbits] = "$" .. #arg .. "\r\n" .. arg .. "\r\n" 200 | end 201 | end 202 | 203 | -- it is faster to do string concatenation on the Lua land 204 | return concat(req) 205 | end 206 | 207 | 208 | local function _do_cmd(self, ...) 209 | local args = {...} 210 | 211 | local sock = self.sock 212 | if not sock then 213 | return nil, "not initialized" 214 | end 215 | 216 | local req = _gen_req(args) 217 | 218 | local reqs = self._reqs 219 | if reqs then 220 | reqs[#reqs + 1] = req 221 | return 222 | end 223 | 224 | -- ngx.log(ngx.ERR, "request:", req) 225 | 226 | local bytes, err = sock:send(req) 227 | if not bytes then 228 | return nil, err 229 | end 230 | 231 | -- ngx.log(ngx.ERR, "result bytes:", bytes, "err:", err) 232 | 233 | return _read_reply(self, sock) 234 | end 235 | 236 | 237 | local function _check_subscribed(self, res) 238 | if type(res) == "table" 239 | and (res[1] == "unsubscribe" or res[1] == "punsubscribe") 240 | and res[3] == 0 241 | then 242 | self.subscribed = nil 243 | end 244 | end 245 | 246 | 247 | function _M.read_reply(self) 248 | local sock = self.sock 249 | if not sock then 250 | return nil, "not initialized" 251 | end 252 | 253 | if not self.subscribed then 254 | return nil, "not subscribed" 255 | end 256 | 257 | local res, err = _read_reply(self, sock) 258 | _check_subscribed(self, res) 259 | 260 | return res, err 261 | end 262 | 263 | 264 | for i = 1, #commands do 265 | local cmd = commands[i] 266 | 267 | _M[cmd] = 268 | function (self, ...) 269 | return _do_cmd(self, cmd, ...) 270 | end 271 | end 272 | 273 | 274 | 275 | function _M.hmset(self, hashname, ...) 276 | local args = {...} 277 | if #args == 1 then 278 | local t = args[1] 279 | 280 | local n = 0 281 | for k, v in pairs(t) do 282 | n = n + 2 283 | end 284 | 285 | local array = new_tab(n, 0) 286 | 287 | local i = 0 288 | for k, v in pairs(t) do 289 | array[i + 1] = k 290 | array[i + 2] = v 291 | i = i + 2 292 | end 293 | -- print("key", hashname) 294 | return _do_cmd(self, "hmset", hashname, unpack(array)) 295 | end 296 | 297 | -- backwards compatibility 298 | return _do_cmd(self, "hmset", hashname, ...) 299 | end 300 | 301 | 302 | function _M.init_pipeline(self, n) 303 | self._reqs = new_tab(n or 4, 0) 304 | end 305 | 306 | 307 | function _M.cancel_pipeline(self) 308 | self._reqs = nil 309 | end 310 | 311 | 312 | function _M.commit_pipeline(self) 313 | local reqs = self._reqs 314 | if not reqs then 315 | return nil, "no pipeline" 316 | end 317 | 318 | self._reqs = nil 319 | 320 | local sock = self.sock 321 | if not sock then 322 | return nil, "not initialized" 323 | end 324 | 325 | local bytes, err = sock:send(reqs) 326 | if not bytes then 327 | return nil, err 328 | end 329 | 330 | local nvals = 0 331 | local nreqs = #reqs 332 | local vals = new_tab(nreqs, 0) 333 | for i = 1, nreqs do 334 | local res, err = _read_reply(self, sock) 335 | if res then 336 | nvals = nvals + 1 337 | vals[nvals] = res 338 | 339 | elseif res == nil then 340 | if err == "timeout" then 341 | close(self) 342 | end 343 | return nil, err 344 | 345 | else 346 | -- be a valid redis error value 347 | nvals = nvals + 1 348 | vals[nvals] = {false, err} 349 | end 350 | end 351 | 352 | return vals 353 | end 354 | 355 | 356 | function _M.array_to_hash(self, t) 357 | local n = #t 358 | -- print("n = ", n) 359 | local h = new_tab(0, n / 2) 360 | for i = 1, n, 2 do 361 | h[t[i]] = t[i + 1] 362 | end 363 | return h 364 | end 365 | 366 | 367 | function _M.add_commands(...) 368 | local cmds = {...} 369 | for i = 1, #cmds do 370 | local cmd = cmds[i] 371 | _M[cmd] = 372 | function (self, ...) 373 | return _do_cmd(self, cmd, ...) 374 | end 375 | end 376 | end 377 | 378 | 379 | return _M 380 | -------------------------------------------------------------------------------- /DockerResources/lua/lib/sha1/sha1.lua: -------------------------------------------------------------------------------- 1 | local sha1 = { 2 | _VERSION = "sha.lua 0.5.0", 3 | _URL = "https://github.com/kikito/sha.lua", 4 | _DESCRIPTION = [[ 5 | SHA-1 secure hash computation, and HMAC-SHA1 signature computation in Lua (5.1) 6 | Based on code originally by Jeffrey Friedl (http://regex.info/blog/lua/sha1) 7 | And modified by Eike Decker - (http://cube3d.de/uploads/Main/sha1.txt) 8 | ]], 9 | _LICENSE = [[ 10 | MIT LICENSE 11 | 12 | Copyright (c) 2013 Enrique García Cota + Eike Decker + Jeffrey Friedl 13 | 14 | Permission is hereby granted, free of charge, to any person obtaining a 15 | copy of this software and associated documentation files (the 16 | "Software"), to deal in the Software without restriction, including 17 | without limitation the rights to use, copy, modify, merge, publish, 18 | distribute, sublicense, and/or sell copies of the Software, and to 19 | permit persons to whom the Software is furnished to do so, subject to 20 | the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included 23 | in all copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 26 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 27 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 28 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 29 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 30 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 31 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 32 | ]] 33 | } 34 | 35 | ----------------------------------------------------------------------------------- 36 | 37 | -- loading this file (takes a while but grants a boost of factor 13) 38 | local PRELOAD_CACHE = true 39 | 40 | local BLOCK_SIZE = 64 -- 512 bits 41 | 42 | -- local storing of global functions (minor speedup) 43 | local floor,modf = math.floor,math.modf 44 | local char,format,rep = string.char,string.format,string.rep 45 | 46 | -- merge 4 bytes to an 32 bit word 47 | local function bytes_to_w32(a,b,c,d) return a*0x1000000+b*0x10000+c*0x100+d end 48 | -- split a 32 bit word into four 8 bit numbers 49 | local function w32_to_bytes(i) 50 | return floor(i/0x1000000)%0x100,floor(i/0x10000)%0x100,floor(i/0x100)%0x100,i%0x100 51 | end 52 | 53 | -- shift the bits of a 32 bit word. Don't use negative values for "bits" 54 | local function w32_rot(bits,a) 55 | local b2 = 2^(32-bits) 56 | local a,b = modf(a/b2) 57 | return a+b*b2*(2^(bits)) 58 | end 59 | 60 | -- caching function for functions that accept 2 arguments, both of values between 61 | -- 0 and 255. The function to be cached is passed, all values are calculated 62 | -- during loading and a function is returned that returns the cached values (only) 63 | local function cache2arg(fn) 64 | if not PRELOAD_CACHE then return fn end 65 | local lut = {} 66 | for i=0,0xffff do 67 | local a,b = floor(i/0x100),i%0x100 68 | lut[i] = fn(a,b) 69 | end 70 | return function(a,b) 71 | return lut[a*0x100+b] 72 | end 73 | end 74 | 75 | -- splits an 8-bit number into 8 bits, returning all 8 bits as booleans 76 | local function byte_to_bits(b) 77 | local b = function(n) 78 | local b = floor(b/n) 79 | return b%2==1 80 | end 81 | return b(1),b(2),b(4),b(8),b(16),b(32),b(64),b(128) 82 | end 83 | 84 | -- builds an 8bit number from 8 booleans 85 | local function bits_to_byte(a,b,c,d,e,f,g,h) 86 | local function n(b,x) return b and x or 0 end 87 | return n(a,1)+n(b,2)+n(c,4)+n(d,8)+n(e,16)+n(f,32)+n(g,64)+n(h,128) 88 | end 89 | 90 | -- bitwise "and" function for 2 8bit number 91 | local band = cache2arg (function(a,b) 92 | local A,B,C,D,E,F,G,H = byte_to_bits(b) 93 | local a,b,c,d,e,f,g,h = byte_to_bits(a) 94 | return bits_to_byte( 95 | A and a, B and b, C and c, D and d, 96 | E and e, F and f, G and g, H and h) 97 | end) 98 | 99 | -- bitwise "or" function for 2 8bit numbers 100 | local bor = cache2arg(function(a,b) 101 | local A,B,C,D,E,F,G,H = byte_to_bits(b) 102 | local a,b,c,d,e,f,g,h = byte_to_bits(a) 103 | return bits_to_byte( 104 | A or a, B or b, C or c, D or d, 105 | E or e, F or f, G or g, H or h) 106 | end) 107 | 108 | -- bitwise "xor" function for 2 8bit numbers 109 | local bxor = cache2arg(function(a,b) 110 | local A,B,C,D,E,F,G,H = byte_to_bits(b) 111 | local a,b,c,d,e,f,g,h = byte_to_bits(a) 112 | return bits_to_byte( 113 | A ~= a, B ~= b, C ~= c, D ~= d, 114 | E ~= e, F ~= f, G ~= g, H ~= h) 115 | end) 116 | 117 | -- bitwise complement for one 8bit number 118 | local function bnot(x) 119 | return 255-(x % 256) 120 | end 121 | 122 | -- creates a function to combine to 32bit numbers using an 8bit combination function 123 | local function w32_comb(fn) 124 | return function(a,b) 125 | local aa,ab,ac,ad = w32_to_bytes(a) 126 | local ba,bb,bc,bd = w32_to_bytes(b) 127 | return bytes_to_w32(fn(aa,ba),fn(ab,bb),fn(ac,bc),fn(ad,bd)) 128 | end 129 | end 130 | 131 | -- create functions for and, xor and or, all for 2 32bit numbers 132 | local w32_and = w32_comb(band) 133 | local w32_xor = w32_comb(bxor) 134 | local w32_or = w32_comb(bor) 135 | 136 | -- xor function that may receive a variable number of arguments 137 | local function w32_xor_n(a,...) 138 | local aa,ab,ac,ad = w32_to_bytes(a) 139 | for i=1,select('#',...) do 140 | local ba,bb,bc,bd = w32_to_bytes(select(i,...)) 141 | aa,ab,ac,ad = bxor(aa,ba),bxor(ab,bb),bxor(ac,bc),bxor(ad,bd) 142 | end 143 | return bytes_to_w32(aa,ab,ac,ad) 144 | end 145 | 146 | -- combining 3 32bit numbers through binary "or" operation 147 | local function w32_or3(a,b,c) 148 | local aa,ab,ac,ad = w32_to_bytes(a) 149 | local ba,bb,bc,bd = w32_to_bytes(b) 150 | local ca,cb,cc,cd = w32_to_bytes(c) 151 | return bytes_to_w32( 152 | bor(aa,bor(ba,ca)), bor(ab,bor(bb,cb)), bor(ac,bor(bc,cc)), bor(ad,bor(bd,cd)) 153 | ) 154 | end 155 | 156 | -- binary complement for 32bit numbers 157 | local function w32_not(a) 158 | return 4294967295-(a % 4294967296) 159 | end 160 | 161 | -- adding 2 32bit numbers, cutting off the remainder on 33th bit 162 | local function w32_add(a,b) return (a+b) % 4294967296 end 163 | 164 | -- adding n 32bit numbers, cutting off the remainder (again) 165 | local function w32_add_n(a,...) 166 | for i=1,select('#',...) do 167 | a = (a+select(i,...)) % 4294967296 168 | end 169 | return a 170 | end 171 | -- converting the number to a hexadecimal string 172 | local function w32_to_hexstring(w) return format("%08x",w) end 173 | 174 | local function hex_to_binary(hex) 175 | return hex:gsub('..', function(hexval) 176 | return string.char(tonumber(hexval, 16)) 177 | end) 178 | end 179 | 180 | -- building the lookuptables ahead of time (instead of littering the source code 181 | -- with precalculated values) 182 | local xor_with_0x5c = {} 183 | local xor_with_0x36 = {} 184 | for i=0,0xff do 185 | xor_with_0x5c[char(i)] = char(bxor(i,0x5c)) 186 | xor_with_0x36[char(i)] = char(bxor(i,0x36)) 187 | end 188 | 189 | ----------------------------------------------------------------------------- 190 | 191 | -- calculating the SHA1 for some text 192 | function sha1.sha1(msg) 193 | local H0,H1,H2,H3,H4 = 0x67452301,0xEFCDAB89,0x98BADCFE,0x10325476,0xC3D2E1F0 194 | local msg_len_in_bits = #msg * 8 195 | 196 | local first_append = char(0x80) -- append a '1' bit plus seven '0' bits 197 | 198 | local non_zero_message_bytes = #msg +1 +8 -- the +1 is the appended bit 1, the +8 are for the final appended length 199 | local current_mod = non_zero_message_bytes % 64 200 | local second_append = current_mod>0 and rep(char(0), 64 - current_mod) or "" 201 | 202 | -- now to append the length as a 64-bit number. 203 | local B1, R1 = modf(msg_len_in_bits / 0x01000000) 204 | local B2, R2 = modf( 0x01000000 * R1 / 0x00010000) 205 | local B3, R3 = modf( 0x00010000 * R2 / 0x00000100) 206 | local B4 = 0x00000100 * R3 207 | 208 | local L64 = char( 0) .. char( 0) .. char( 0) .. char( 0) -- high 32 bits 209 | .. char(B1) .. char(B2) .. char(B3) .. char(B4) -- low 32 bits 210 | 211 | msg = msg .. first_append .. second_append .. L64 212 | 213 | assert(#msg % 64 == 0) 214 | 215 | local chunks = #msg / 64 216 | 217 | local W = { } 218 | local start, A, B, C, D, E, f, K, TEMP 219 | local chunk = 0 220 | 221 | while chunk < chunks do 222 | -- 223 | -- break chunk up into W[0] through W[15] 224 | -- 225 | start,chunk = chunk * 64 + 1,chunk + 1 226 | 227 | for t = 0, 15 do 228 | W[t] = bytes_to_w32(msg:byte(start, start + 3)) 229 | start = start + 4 230 | end 231 | 232 | -- 233 | -- build W[16] through W[79] 234 | -- 235 | for t = 16, 79 do 236 | -- For t = 16 to 79 let Wt = S1(Wt-3 XOR Wt-8 XOR Wt-14 XOR Wt-16). 237 | W[t] = w32_rot(1, w32_xor_n(W[t-3], W[t-8], W[t-14], W[t-16])) 238 | end 239 | 240 | A,B,C,D,E = H0,H1,H2,H3,H4 241 | 242 | for t = 0, 79 do 243 | if t <= 19 then 244 | -- (B AND C) OR ((NOT B) AND D) 245 | f = w32_or(w32_and(B, C), w32_and(w32_not(B), D)) 246 | K = 0x5A827999 247 | elseif t <= 39 then 248 | -- B XOR C XOR D 249 | f = w32_xor_n(B, C, D) 250 | K = 0x6ED9EBA1 251 | elseif t <= 59 then 252 | -- (B AND C) OR (B AND D) OR (C AND D 253 | f = w32_or3(w32_and(B, C), w32_and(B, D), w32_and(C, D)) 254 | K = 0x8F1BBCDC 255 | else 256 | -- B XOR C XOR D 257 | f = w32_xor_n(B, C, D) 258 | K = 0xCA62C1D6 259 | end 260 | 261 | -- TEMP = S5(A) + ft(B,C,D) + E + Wt + Kt; 262 | A,B,C,D,E = w32_add_n(w32_rot(5, A), f, E, W[t], K), 263 | A, w32_rot(30, B), C, D 264 | end 265 | -- Let H0 = H0 + A, H1 = H1 + B, H2 = H2 + C, H3 = H3 + D, H4 = H4 + E. 266 | H0,H1,H2,H3,H4 = w32_add(H0, A),w32_add(H1, B),w32_add(H2, C),w32_add(H3, D),w32_add(H4, E) 267 | end 268 | local f = w32_to_hexstring 269 | return f(H0) .. f(H1) .. f(H2) .. f(H3) .. f(H4) 270 | end 271 | 272 | 273 | function sha1.binary(msg) 274 | return hex_to_binary(sha1.sha1(msg)) 275 | end 276 | 277 | function sha1.hmac(key, text) 278 | assert(type(key) == 'string', "key passed to sha1.hmac should be a string") 279 | assert(type(text) == 'string', "text passed to sha1.hmac should be a string") 280 | 281 | if #key > BLOCK_SIZE then 282 | key = sha1.binary(key) 283 | end 284 | 285 | local key_xord_with_0x36 = key:gsub('.', xor_with_0x36) .. string.rep(string.char(0x36), BLOCK_SIZE - #key) 286 | local key_xord_with_0x5c = key:gsub('.', xor_with_0x5c) .. string.rep(string.char(0x5c), BLOCK_SIZE - #key) 287 | 288 | return sha1.sha1(key_xord_with_0x5c .. sha1.binary(key_xord_with_0x36 .. text)) 289 | end 290 | 291 | function sha1.hmac_binary(key, text) 292 | return hex_to_binary(sha1.hmac(key, text)) 293 | end 294 | 295 | setmetatable(sha1, {__call = function(_,msg) return sha1.sha1(msg) end }) 296 | 297 | return sha1 298 | -------------------------------------------------------------------------------- /DockerResources/lua/lib/ws/derivedfrom.txt: -------------------------------------------------------------------------------- 1 | https://github.com/openresty/lua-resty-websocket 2 | 3 | This module is licensed under the BSD license. 4 | 5 | Copyright (C) 2013-2017, by Yichun Zhang (agentzh) agentzh@gmail.com, OpenResty Inc. 6 | 7 | All rights reserved. 8 | 9 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 10 | 11 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 12 | 13 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 16 | 17 | 18 | added: continue frame feature. -------------------------------------------------------------------------------- /DockerResources/lua/lib/ws/websocketProtocol.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (C) Yichun Zhang (agentzh) 2 | 3 | local bit = require "bit" 4 | 5 | local M = {[0] = "websocketProtocol"} 6 | 7 | local types = { 8 | [0x0] = "continuation", 9 | [0x1] = "text", 10 | [0x2] = "binary", 11 | [0x8] = "close", 12 | [0x9] = "ping", 13 | [0xa] = "pong", 14 | } 15 | 16 | function M.recv_frame(sock, max_payload_len, force_masking) 17 | local data, err = sock:receive(2) 18 | if not data then 19 | return nil, nil, "failed to receive the first 2 bytes: " .. err 20 | end 21 | 22 | local fst, snd = string.byte(data, 1, 2) 23 | 24 | local fin = bit.band(fst, 0x80) ~= 0 25 | 26 | if bit.band(fst, 0x70) ~= 0 then 27 | return nil, nil, "bad RSV1, RSV2, or RSV3 bits" 28 | end 29 | 30 | local opcode = bit.band(fst, 0x0f) 31 | 32 | if opcode >= 0x3 and opcode <= 0x7 then 33 | return nil, nil, "reserved non-control frames" 34 | end 35 | 36 | if opcode >= 0xb and opcode <= 0xf then 37 | return nil, nil, "reserved control frames" 38 | end 39 | 40 | local mask = bit.band(snd, 0x80) ~= 0 41 | 42 | if force_masking and not mask then 43 | return nil, nil, "frame unmasked" 44 | end 45 | 46 | local payload_len = bit.band(snd, 0x7f) 47 | 48 | if payload_len == 126 then 49 | local data, err = sock:receive(2) 50 | if not data then 51 | return nil, nil, "failed to receive the 2 byte payload length: " 52 | .. (err or "unknown") 53 | end 54 | 55 | payload_len = bit.bor(bit.lshift(string.byte(data, 1), 8), string.byte(data, 2)) 56 | 57 | elseif payload_len == 127 then 58 | local data, err = sock:receive(8) 59 | if not data then 60 | return nil, nil, "failed to receive the 8 byte payload length: " 61 | .. (err or "unknown") 62 | end 63 | 64 | if string.byte(data, 1) ~= 0 65 | or string.byte(data, 2) ~= 0 66 | or string.byte(data, 3) ~= 0 67 | or string.byte(data, 4) ~= 0 68 | then 69 | return nil, nil, "payload len too large" 70 | end 71 | 72 | local fifth = string.byte(data, 5) 73 | if bit.band(fifth, 0x80) ~= 0 then 74 | return nil, nil, "payload len too large" 75 | end 76 | 77 | payload_len = bit.bor(bit.lshift(fifth, 24), 78 | bit.lshift(string.byte(data, 6), 16), 79 | bit.lshift(string.byte(data, 7), 8), 80 | string.byte(data, 8)) 81 | end 82 | 83 | if bit.band(opcode, 0x8) ~= 0 then 84 | -- being a control frame 85 | if payload_len > 125 then 86 | return nil, nil, "too long payload for control frame" 87 | end 88 | 89 | if not fin then 90 | return nil, nil, "fragmented control frame" 91 | end 92 | end 93 | 94 | -- print("payload len: ", payload_len, ", max payload len: ", 95 | -- max_payload_len) 96 | 97 | if payload_len > max_payload_len then 98 | return nil, nil, "exceeding max payload len" 99 | end 100 | 101 | local rest 102 | if mask then 103 | rest = payload_len + 4 104 | 105 | else 106 | rest = payload_len 107 | end 108 | -- print("rest: ", rest) 109 | 110 | local data 111 | if rest > 0 then 112 | data, err = sock:receive(rest) 113 | if not data then 114 | return nil, nil, "failed to read masking-len and payload: " 115 | .. (err or "unknown") 116 | end 117 | else 118 | data = "" 119 | end 120 | 121 | -- print("received rest") 122 | 123 | if opcode == 0x8 then 124 | -- being a close frame 125 | if payload_len > 0 then 126 | if payload_len < 2 then 127 | return nil, nil, "close frame with a body must carry a 2-byte" 128 | .. " status code" 129 | end 130 | 131 | local msg, code 132 | if mask then 133 | local fst = bit.bxor(byte(data, 4 + 1), string.byte(data, 1)) 134 | local snd = bit.bxor(byte(data, 4 + 2), string.byte(data, 2)) 135 | code = bit.bor(bit.lshift(fst, 8), snd) 136 | 137 | if payload_len > 2 then 138 | -- TODO string.buffer optimizations 139 | local bytes = table.new(payload_len - 2, 0) 140 | for i = 3, payload_len do 141 | bytes[i - 2] = string.char(bit.bxor(string.byte(data, 4 + i), 142 | string.byte(data, 143 | (i - 1) % 4 + 1))) 144 | end 145 | msg = table.concat(bytes) 146 | 147 | else 148 | msg = "" 149 | end 150 | 151 | else 152 | local fst = string.byte(data, 1) 153 | local snd = string.byte(data, 2) 154 | code = bit.bor(bit.lshift(fst, 8), snd) 155 | 156 | -- print("parsing unmasked close frame payload: ", payload_len) 157 | 158 | if payload_len > 2 then 159 | msg = string.sub(data, 3) 160 | 161 | else 162 | msg = "" 163 | end 164 | end 165 | 166 | return msg, "close", code 167 | end 168 | 169 | return "", "close", nil 170 | end 171 | 172 | local msg 173 | if mask then 174 | -- TODO string.buffer optimizations 175 | local bytes = table.new(payload_len, 0) 176 | for i = 1, payload_len do 177 | bytes[i] = string.char(bit.bxor(string.byte(data, 4 + i), string.byte(data, (i - 1) % 4 + 1))) 178 | end 179 | msg = table.concat(bytes) 180 | 181 | else 182 | msg = data 183 | end 184 | 185 | return msg, types[opcode], not fin and "again" or nil 186 | end 187 | 188 | 189 | local function build_frame(fin, opcode, payload_len, payload, masking) 190 | -- TODO optimize this when we have string.buffer in LuaJIT 2.1 191 | local fst 192 | if fin then 193 | fst = bit.bor(0x80, opcode) 194 | else 195 | fst = opcode 196 | end 197 | 198 | local snd, extra_len_bytes 199 | if payload_len <= 125 then 200 | snd = payload_len 201 | extra_len_bytes = "" 202 | 203 | elseif payload_len <= 65535 then 204 | snd = 126 205 | extra_len_bytes = string.char(bit.band(bit.rshift(payload_len, 8), 0xff), 206 | bit.band(payload_len, 0xff)) 207 | 208 | else 209 | if bit.band(payload_len, 0x7fffffff) < payload_len then 210 | return nil, "payload too big" 211 | end 212 | 213 | snd = 127 214 | -- XXX we only support 31-bit length here 215 | extra_len_bytes = string.char(0, 0, 0, 0, bit.band(bit.rshift(payload_len, 24), 0xff), 216 | bit.band(bit.rshift(payload_len, 16), 0xff), 217 | bit.band(bit.rshift(payload_len, 8), 0xff), 218 | bit.band(payload_len, 0xff)) 219 | end 220 | 221 | local masking_key 222 | if masking then 223 | -- set the mask bit 224 | snd = bit.bor(snd, 0x80) 225 | local key = math.random(0xffffffff) 226 | masking_key = string.char(bit.band(bit.rshift(key, 24), 0xff), 227 | bit.band(bit.rshift(key, 16), 0xff), 228 | bit.band(bit.rshift(key, 8), 0xff), 229 | bit.band(key, 0xff)) 230 | 231 | -- TODO string.buffer optimizations 232 | local bytes = table.new(payload_len, 0) 233 | for i = 1, payload_len do 234 | bytes[i] = string.char(bit.bxor(string.byte(payload, i), 235 | string.byte(masking_key, (i - 1) % 4 + 1))) 236 | end 237 | payload = table.concat(bytes) 238 | 239 | else 240 | masking_key = "" 241 | end 242 | 243 | return string.char(fst, snd) .. extra_len_bytes .. masking_key .. payload 244 | end 245 | M.build_frame = build_frame 246 | 247 | 248 | function M.send_frame(sock, fin, opcode, payload, max_payload_len, masking) 249 | if not payload then 250 | payload = "" 251 | 252 | elseif type(payload) ~= "string" then 253 | payload = tostring(payload) 254 | end 255 | 256 | local payload_len = #payload 257 | 258 | if payload_len > max_payload_len then 259 | return nil, "payload too big" 260 | end 261 | 262 | if bit.band(opcode, 0x8) ~= 0 then 263 | -- being a control frame 264 | if payload_len > 125 then 265 | return nil, "too much payload for control frame" 266 | end 267 | if not fin then 268 | return nil, "fragmented control frame" 269 | end 270 | end 271 | 272 | local frame, err = build_frame(fin, opcode, payload_len, payload, 273 | masking) 274 | if not frame then 275 | return nil, "failed to build frame: " .. err 276 | end 277 | 278 | local bytes, err = sock:send(frame) 279 | if not bytes then 280 | return nil, "failed to send frame: " .. err 281 | end 282 | return bytes 283 | end 284 | 285 | 286 | return M 287 | -------------------------------------------------------------------------------- /DockerResources/lua/lib/ws/websocketServer.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (C) Yichun Zhang (agentzh) 2 | 3 | local sha1 = require 'sha1.sha1' 4 | local bit = require "bit" 5 | local protocol = require "ws.websocketProtocol" 6 | 7 | local M = {[0] = "websocketServer"} 8 | local mt = { __index = M } 9 | 10 | local connecting = false 11 | 12 | function M.new(self, opts) 13 | if ngx.headers_sent then 14 | return nil, "response header already sent" 15 | end 16 | 17 | -- construct WebSocket connect from server to client. 18 | 19 | -- discard body 20 | ngx.req.read_body() 21 | 22 | -- check header 23 | if ngx.req.http_version() ~= 1.1 then 24 | return nil, "bad http version" 25 | end 26 | 27 | local headers = ngx.req.get_headers() 28 | 29 | local val = headers.upgrade 30 | if type(val) == "table" then 31 | val = val[1] 32 | end 33 | if not val or string.lower(val) ~= "websocket" then 34 | return nil, "bad \"upgrade\" request header" 35 | end 36 | 37 | val = headers.connection 38 | if type(val) == "table" then 39 | val = val[1] 40 | end 41 | if not val or not string.find(string.lower(val), "upgrade", 1, true) then 42 | return nil, "bad \"connection\" request header" 43 | end 44 | 45 | local key = headers["sec-websocket-key"] 46 | if type(key) == "table" then 47 | key = key[1] 48 | end 49 | if not key then 50 | return nil, "bad \"sec-websocket-key\" request header" 51 | end 52 | 53 | local ver = headers["sec-websocket-version"] 54 | if type(ver) == "table" then 55 | ver = ver[1] 56 | end 57 | if not ver or ver ~= "13" then 58 | return nil, "bad \"sec-websocket-version\" request header" 59 | end 60 | 61 | local protocols = headers["sec-websocket-protocol"] 62 | if type(protocols) == "table" then 63 | protocols = protocols[1] 64 | end 65 | 66 | if protocols then 67 | ngx.header["Sec-WebSocket-Protocol"] = protocols 68 | end 69 | ngx.header["Upgrade"] = "websocket" 70 | 71 | local sha1 = sha1.binary(key .. "258EAFA5-E914-47DA-95CA-C5AB0DC85B11") 72 | ngx.header["Sec-WebSocket-Accept"] = ngx.encode_base64(sha1) 73 | 74 | ngx.header["Content-Type"] = nil 75 | 76 | ngx.status = 101 77 | local ok, err = ngx.send_headers() 78 | if not ok then 79 | return nil, "failed to send response header: " .. (err or "unknonw") 80 | end 81 | ok, err = ngx.flush(true) 82 | if not ok then 83 | return nil, "failed to flush response header: " .. (err or "unknown") 84 | end 85 | 86 | local sock 87 | sock, err = ngx.req.socket(true) 88 | if not sock then 89 | return nil, err 90 | end 91 | 92 | local max_payload_len, send_masked, timeout 93 | if opts then 94 | max_payload_len = opts.max_payload_len 95 | send_masked = opts.send_masked 96 | timeout = opts.timeout 97 | 98 | if timeout then 99 | sock:settimeout(timeout) 100 | end 101 | end 102 | 103 | connecting = true 104 | 105 | return setmetatable({ 106 | sock = sock, 107 | max_payload_len = max_payload_len or 65535, 108 | send_masked = send_masked, 109 | }, mt) 110 | end 111 | 112 | 113 | function M.set_timeout(self, time) 114 | local sock = self.sock 115 | if not sock then 116 | return nil, nil, "not initialized yet" 117 | end 118 | 119 | return sock:settimeout(time) 120 | end 121 | 122 | 123 | function M.recv_frame(self) 124 | if self.fatal then 125 | return nil, nil, "fatal error already happened" 126 | end 127 | 128 | local sock = self.sock 129 | if not sock then 130 | return nil, nil, "not initialized yet" 131 | end 132 | 133 | local data, typ, err = protocol.recv_frame(sock, self.max_payload_len, true) 134 | if not data and not string.find(err, ": timeout", 1, true) then 135 | self.fatal = true 136 | end 137 | return data, typ, err 138 | end 139 | 140 | 141 | local function send_frame(self, fin, opcode, payload) 142 | if self.fatal then 143 | return nil, "fatal error already happened" 144 | end 145 | 146 | local sock = self.sock 147 | if not sock then 148 | return nil, "not initialized yet" 149 | end 150 | 151 | local bytes, err = protocol.send_frame(sock, fin, opcode, payload, 152 | self.max_payload_len, self.send_masked) 153 | if not bytes then 154 | self.fatal = true 155 | end 156 | return bytes, err 157 | end 158 | 159 | 160 | M.send_frame = send_frame 161 | 162 | 163 | function M.send_continue(self, data) 164 | return send_frame(self, true, 0x0, data) 165 | end 166 | 167 | 168 | function M.send_text(self, data) 169 | return send_frame(self, true, 0x1, data) 170 | end 171 | 172 | 173 | function M.send_binary(self, data) 174 | return send_frame(self, true, 0x2, data) 175 | end 176 | 177 | 178 | function M.send_close(self, code, msg) 179 | local payload 180 | if code then 181 | if type(code) ~= "number" or code > 0x7fff then 182 | end 183 | payload = string.char(bit.band(bit.rshift(code, 8), 0xff), bit.band(code, 0xff)) 184 | .. (msg or "") 185 | end 186 | 187 | connecting = false 188 | 189 | return send_frame(self, true, 0x8, payload) 190 | end 191 | 192 | 193 | function M.send_ping(self, data) 194 | return send_frame(self, true, 0x9, data) 195 | end 196 | 197 | 198 | function M.send_pong(self, data) 199 | return send_frame(self, true, 0xa, data) 200 | end 201 | 202 | function M.is_connecting(self) 203 | return connecting 204 | end 205 | 206 | 207 | return M 208 | -------------------------------------------------------------------------------- /DockerResources/lua/sample_disque_client.lua: -------------------------------------------------------------------------------- 1 | -- get identity of game from url. e.g. http://somewhere/game_key -> game_key 2 | local identity = string.gsub (ngx.var.uri, "/", "") 3 | 4 | -- generate identity of queue for target context. 5 | IDENTIFIER_CONTEXT = identity .. "_context" 6 | 7 | 8 | STATE_CONNECT = 1 9 | STATE_STRING_MESSAGE = 2 10 | STATE_BINARY_MESSAGE = 3 11 | STATE_DISCONNECT_INTENT = 4 12 | STATE_DISCONNECT_ACCIDT = 5 13 | STATE_DISCONNECT_DISQUE_ACKFAILED = 6 14 | STATE_DISCONNECT_DISQUE_ACCIDT_SENDFAILED = 7 15 | 16 | local the_id = ngx.req.get_headers()["id"] 17 | if not the_id then 18 | the_id = "_empty_" 19 | end 20 | 21 | 22 | ip = "127.0.0.1"-- localhost. 23 | port = 7711 24 | 25 | 26 | -- entrypoint for WebSocket client connection. 27 | 28 | -- setup Disque get-add 29 | local disque = require "disque.disque" 30 | 31 | -- connectionId is nginx's request id. that len is 32 + 4. 32 | local connectionId = ngx.var.request_id .. "0000" 33 | 34 | receiveJobConn = disque:new() 35 | local ok, err = receiveJobConn:connect(ip, port) 36 | if not ok then 37 | ngx.log(ngx.ERR, "connection:", connectionId, " failed to generate receiveJob client") 38 | return 39 | end 40 | 41 | receiveJobConn:set_timeout(1000 * 60 * 60) 42 | 43 | 44 | addJobCon = disque:new() 45 | local ok, err = addJobCon:connect(ip, port) 46 | if not ok then 47 | ngx.log(ngx.ERR, "connection:", connectionId, " failed to generate addJob client") 48 | return 49 | end 50 | 51 | local maxLen = 1024 52 | 53 | -- setup websocket client 54 | local wsServer = require "ws.websocketServer" 55 | 56 | ws, wErr = wsServer:new{ 57 | timeout = 10000000,-- this should be set good value. 58 | max_payload_len = maxLen 59 | } 60 | 61 | if not ws then 62 | ngx.log(ngx.ERR, "connection:", connectionId, " failed to new websocket: ", wErr) 63 | return 64 | end 65 | 66 | ngx.log(ngx.ERR, "connection:", connectionId, " start connect.") 67 | 68 | function connectWebSocket() 69 | -- start receiving message from context. 70 | ngx.thread.spawn(contextReceiving) 71 | 72 | ngx.log(ngx.ERR, "connection:", connectionId, " established. the_id:", the_id, " to context:", IDENTIFIER_CONTEXT) 73 | 74 | -- send connected to gameContext. 75 | local data = STATE_CONNECT..connectionId..the_id 76 | addJobCon:addjob(IDENTIFIER_CONTEXT, data, 0) 77 | 78 | -- start websocket serving. 79 | while true do 80 | local recv_data, typ, err = ws:recv_frame() 81 | 82 | if ws.fatal then 83 | ngx.log(ngx.ERR, "connection:", connectionId, " closing accidentially. ", err) 84 | local data = STATE_DISCONNECT_ACCIDT..connectionId..the_id 85 | addJobCon:addjob(IDENTIFIER_CONTEXT, data, 0) 86 | break 87 | end 88 | 89 | if not recv_data then 90 | ngx.log(ngx.ERR, "connection:", connectionId, " received empty data.") 91 | -- log only. do nothing. 92 | end 93 | 94 | if typ == "close" then 95 | ngx.log(ngx.ERR, "connection:", connectionId, " closing intentionally.") 96 | local data = STATE_DISCONNECT_INTENT..connectionId..the_id 97 | addJobCon:addjob(IDENTIFIER_CONTEXT, data, 0) 98 | 99 | -- start close. 100 | break 101 | elseif typ == "ping" then 102 | local bytes, err = ws:send_pong(recv_data) 103 | -- ngx.log(ngx.ERR, "connection:", serverId, " ping received.") 104 | if not bytes then 105 | ngx.log(ngx.ERR, "connection:", serverId, " failed to send pong: ", err) 106 | break 107 | end 108 | 109 | elseif typ == "pong" then 110 | ngx.log(ngx.INFO, "client ponged") 111 | 112 | elseif typ == "text" then 113 | -- post message to central. 114 | local data = STATE_STRING_MESSAGE..connectionId..recv_data 115 | addJobCon:addjob(IDENTIFIER_CONTEXT, data, 0) 116 | elseif typ == "binary" then 117 | -- post binary data to central. 118 | local binData = STATE_BINARY_MESSAGE..connectionId..recv_data 119 | addJobCon:addjob(IDENTIFIER_CONTEXT, binData, 0) 120 | end 121 | end 122 | 123 | ws:send_close() 124 | ngx.log(ngx.ERR, "connection:", connectionId, " connection closed") 125 | 126 | ngx.exit(200) 127 | end 128 | 129 | -- loop for receiving messages from game context. 130 | function contextReceiving () 131 | local localWs = ws 132 | local localMaxLen = maxLen 133 | while true do 134 | -- receive message from disque queue, through connectionId. 135 | -- game context will send message via connectionId. 136 | local res, err = receiveJobConn:getjob("from", connectionId) 137 | 138 | if not res then 139 | ngx.log(ngx.ERR, "err:", err) 140 | break 141 | else 142 | local datas = res[1] 143 | -- ngx.log(ngx.ERR, "client datas1:", datas[1])-- connectionId 144 | -- ngx.log(ngx.ERR, "client datas2:", datas[2])-- messageId 145 | -- ngx.log(ngx.ERR, "client datas3:", datas[3])-- data 146 | local messageId = datas[2] 147 | local sendingData = datas[3] 148 | 149 | -- fastack to disque 150 | local ackRes, ackErr = receiveJobConn:fastack(messageId) 151 | if not ackRes then 152 | ngx.log(ngx.ERR, "disque, ackに失敗したケース connection:", connectionId, " ackErr:", ackErr) 153 | local data = STATE_DISCONNECT_DISQUE_ACKFAILED..connectionId..the_id 154 | addJobCon:addjob(IDENTIFIER_CONTEXT, data, 0) 155 | break 156 | end 157 | -- ngx.log(ngx.ERR, "messageId:", messageId, " ackRes:", ackRes) 158 | 159 | -- というわけで、ここまででデータは取得できているが、ここで先頭を見て、、みたいなのが必要になってくる。 160 | -- 入れる側にもなんかデータ接続が出ちゃうんだなあ。うーん、、まあでもサーバ側なんでいいや。CopyがN回増えるだけだ。 161 | -- 残る課題は、ここでヘッダを見る、ってことだね。 162 | 163 | if (localMaxLen < #sendingData) then 164 | local count = math.floor(#sendingData / localMaxLen) 165 | local rest = #sendingData % localMaxLen 166 | 167 | local index = 1 168 | local failed = false 169 | for i = 1, count do 170 | -- send. from index to index + localMaxLen. 171 | local continueData = string.sub(sendingData, index, index + localMaxLen - 1) 172 | 173 | local bytes, err = localWs:send_continue(continueData) 174 | if not bytes then 175 | ngx.log(ngx.ERR, "disque, continue送付の失敗。 connection:", connectionId, " failed to send text to client. err:", err) 176 | local data = STATE_DISCONNECT_DISQUE_ACCIDT_SENDFAILED..connectionId..sendingData 177 | addJobCon:addjob(IDENTIFIER_CONTEXT, data, 0) 178 | failed = true 179 | break 180 | end 181 | index = index + localMaxLen 182 | end 183 | 184 | if failed then 185 | break 186 | end 187 | 188 | -- send rest data as binary. 189 | 190 | local lastData = string.sub(sendingData, index) 191 | 192 | local bytes, err = localWs:send_binary(lastData) 193 | if not bytes then 194 | ngx.log(ngx.ERR, "disque, continue送付の失敗。 connection:", connectionId, " failed to send text to client. err:", err) 195 | local data = STATE_DISCONNECT_DISQUE_ACCIDT_SENDFAILED..connectionId..sendingData 196 | addJobCon:addjob(IDENTIFIER_CONTEXT, data, 0) 197 | break 198 | end 199 | 200 | else 201 | -- send data to client 202 | local bytes, err = localWs:send_binary(sendingData) 203 | 204 | if not bytes then 205 | ngx.log(ngx.ERR, "disque, 未解決の、送付失敗時にすべきこと。 connection:", connectionId, " failed to send text to client. err:", err) 206 | local data = STATE_DISCONNECT_DISQUE_ACCIDT_SENDFAILED..connectionId..sendingData 207 | addJobCon:addjob(IDENTIFIER_CONTEXT, data, 0) 208 | break 209 | end 210 | end 211 | end 212 | end 213 | 214 | ngx.log(ngx.ERR, "connection:", connectionId, " connection closed by disque error.") 215 | ngx.exit(200) 216 | end 217 | 218 | connectWebSocket() 219 | 220 | 221 | -- 別の話、ここに受け入れバッファを持つことは可能か 222 | 223 | -- -> なんか切断時コンテキスト混同イレギュラーがあったんだよな〜〜あれの原因探さないとなー 224 | -- 何が起きていたかっていうと、切断確認が別のクライアントのものをつかんでいた、っていうやつで、 225 | -- 受け取り時にコネクション状態を見るとおかしくなっている、ていうやつ。 226 | -- 、、、コネクション状態に関して見るフラッグをngx.thread内で扱ってはいけない、みたいなのがありそう。 227 | -- ということは、それ以外であれば混同しないのでは。 228 | 229 | -- それが解消したらできそうかな?できそうだな。 230 | -- パラメータを保持させて、か、、まあ親のインスタンスのパラメータに触れるのはしんどいんで、やっぱりluaだと厳しいねっていう話になるのがいい気がする。 231 | -- 本当にあると嬉しいのは、TCP以外が喋れる、フロントになれるメッセージキューか。まあErlangにはあるんだけどな。 232 | 233 | 234 | 235 | 236 | 237 | -------------------------------------------------------------------------------- /DockerResources/nginx.conf: -------------------------------------------------------------------------------- 1 | #user nobody; 2 | worker_processes auto; 3 | 4 | error_log logs/error.log; 5 | # error_log logs/error.log notice; 6 | # error_log logs/error.log info; 7 | 8 | pid logs/nginx.pid; 9 | 10 | worker_rlimit_nofile 2048; 11 | 12 | events { 13 | worker_connections 1024; 14 | } 15 | 16 | 17 | http { 18 | include mime.types; 19 | default_type application/octet-stream; 20 | 21 | #log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 22 | # '$status $body_bytes_sent "$http_referer" ' 23 | # '"$http_user_agent" "$http_x_forwarded_for"'; 24 | 25 | #access_log logs/access.log main; 26 | 27 | sendfile on; 28 | #tcp_nopush on; 29 | 30 | #keepalive_timeout 0; 31 | keepalive_timeout 65; 32 | 33 | #gzip on; 34 | 35 | # lua_code_cache off; 36 | 37 | # set search paths for pure Lua external libraries (';;' is the default path): 38 | lua_package_path ";;$prefix/lua/lib/?.lua;"; 39 | 40 | # set search paths for Lua external libraries written in C (can also use ';;'): 41 | # lua_package_cpath ';;$prefix/lua/shared/?.so;'; 42 | 43 | server { 44 | listen 80; 45 | server_name localhost; 46 | 47 | access_log logs/host.access.log; 48 | 49 | location / { 50 | root html; 51 | index index.html index.htm; 52 | } 53 | 54 | # sample disque client route. 55 | location /sample_disque_client { 56 | content_by_lua_file lua/sample_disque_client.lua; 57 | } 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nginx-luajit-ws 2 | 3 |  4 | 5 | nginxでluaを使ってWebSocketを受け付ける。 6 | 7 | 8 | 1. nginx上のluaでClient-ServerをWebSocket接続 9 | 2. すべての接続がmessageQueueを介して一箇所のcontextに収束 10 | 3. contextはmessageQueueにアクセスできさえすれば要件を満たせる。どんな言語でも環境でも書けるはず 11 | 4. contextとWebSocket接続が疎結合なので、接続保ったままcontextの更新が可能(単に別なだけ) 12 | 13 | 14 | # Build image 15 | 16 | ```shellscript 17 | docker build -f ubuntu.dockerfile -t nginx-luajit-ubuntu . 18 | ``` 19 | 20 | # Create container from image 21 | 22 | ```shellscript 23 | docker run -ti -d --name nginx_luajit -p 8080:80 -v $(pwd)/logs:/nginx-1.11.9/1.11.9/logs nginx-luajit-ubuntu 24 | ``` 25 | 26 | # Connect to connnection server 27 | 28 | open client.html by web browser. 29 | 30 | 31 | # Logs 32 | 33 | all nginx logs are located in ./logs folder. -------------------------------------------------------------------------------- /centos.dockerfile: -------------------------------------------------------------------------------- 1 | FROM centos 2 | 3 | ENV NGINX_VERSION 1.11.9 4 | ENV LUAJIT_VERSION 2.1.0-beta2 5 | ENV NGINX_DEVEL_KIT_VERSION v0.3.0 6 | ENV NGINX_LUAJIT_VERSION v0.10.7 7 | 8 | # ready tools. 9 | RUN yum -y install \ 10 | gcc \ 11 | gcc-c++ \ 12 | make \ 13 | zlib-devel \ 14 | pcre-devel \ 15 | openssl-devel \ 16 | wget \ 17 | unzip 18 | 19 | # download nginx. 20 | RUN wget http://nginx.org/download/nginx-$NGINX_VERSION.tar.gz && tar -xzvf nginx-$NGINX_VERSION.tar.gz && rm nginx-$NGINX_VERSION.tar.gz 21 | 22 | # add luajit module. 23 | RUN wget http://luajit.org/download/LuaJIT-$LUAJIT_VERSION.tar.gz && tar -xzvf LuaJIT-$LUAJIT_VERSION.tar.gz && rm LuaJIT-$LUAJIT_VERSION.tar.gz && cd LuaJIT-$LUAJIT_VERSION/ && make && make install 24 | 25 | # add nginx tools and lua module. 26 | RUN mkdir nginx-$NGINX_VERSION/dependencies && cd nginx-$NGINX_VERSION/dependencies && \ 27 | wget https://github.com/simpl/ngx_devel_kit/archive/$NGINX_DEVEL_KIT_VERSION.zip && unzip $NGINX_DEVEL_KIT_VERSION.zip && rm $NGINX_DEVEL_KIT_VERSION.zip && \ 28 | wget https://github.com/openresty/lua-nginx-module/archive/$NGINX_LUAJIT_VERSION.zip && unzip $NGINX_LUAJIT_VERSION.zip && rm $NGINX_LUAJIT_VERSION.zip 29 | 30 | # add shell. 31 | COPY ./DockerResources/build.sh nginx-$NGINX_VERSION/build.sh 32 | 33 | # build nginx. 34 | RUN cd nginx-$NGINX_VERSION && sh build.sh 35 | 36 | # download and make disque. 37 | RUN wget https://github.com/antirez/disque/archive/master.zip && unzip master.zip && rm master.zip && ls -l && cd disque-master/src && make 38 | 39 | 40 | # add lua sources. 41 | RUN mkdir nginx-$NGINX_VERSION/$NGINX_VERSION/lua && ls -l 42 | COPY ./DockerResources/lua nginx-$NGINX_VERSION/$NGINX_VERSION/lua 43 | 44 | # overwrite nginx conf. 45 | COPY ./DockerResources/nginx.conf nginx-$NGINX_VERSION/$NGINX_VERSION/conf/ 46 | 47 | 48 | # run nginx & disque-server. 49 | ENTRYPOINT /nginx-$NGINX_VERSION/$NGINX_VERSION/sbin/nginx && /disque-master/src/disque-server 50 | -------------------------------------------------------------------------------- /client.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 58 | 59 | 60 | 61 | 62 | 63 |