├── README.md ├── html └── ws.html ├── lua ├── access.lua ├── login.lua └── ws.lua └── nginx.conf /README.md: -------------------------------------------------------------------------------- 1 | # openresty-chatting-room 2 | 基于openresty websocket实现的聊天室 3 | 4 | 支持不同聊天室:ws://localhost:8888/s/{chat-room-id} 5 | 6 | 如ws://localhost:8888/s/1234 7 | 8 | 9 | 权限控制:如果只允许登录用户使用聊天室,则需要: 10 | 11 | 登录->设置cookie,包括uid,nickname,token 12 | 13 | token是uid、nickname和一个key md5生成。 14 | -------------------------------------------------------------------------------- /html/ws.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | 66 | 72 | 73 | -------------------------------------------------------------------------------- /lua/access.lua: -------------------------------------------------------------------------------- 1 | -local secretkey='123ad$%^&*' 2 | - 3 | -if ngx.var.cookie_uid == nil or ngx.var.cookie_nickname == nil or ngx.var.cookie_token == nil then 4 | - --ngx.exit(403) 5 | - ngx.req.set_header("Check-Login", "No") 6 | - return 7 | -end 8 | - 9 | -local ctoken = ngx.md5('uid:' .. ngx.var.cookie_uid .. '&nickname:' .. ngx.var.cookie_nickname .. '&secretkey:' .. secretkey) 10 | - 11 | -if ctoken == ngx.var.cookie_token then 12 | - ngx.req.set_header("Check-Login", "Yes") 13 | -else 14 | - ngx.req.set_header("Check-Login", "No") 15 | - --ngx.exit(403) 16 | -end 17 | - 18 | -return 19 | -------------------------------------------------------------------------------- /lua/login.lua: -------------------------------------------------------------------------------- 1 | local uid = ngx.time() 2 | local nickname = 'user-' .. uid 3 | 4 | local secretkey='123ad$%^&*' 5 | local token = ngx.md5('uid:' .. uid .. '&nickname:' .. nickname .. '&secretkey:' .. secretkey) 6 | ngx.header['Set-Cookie'] = {"uid="..uid,"nickname="..nickname,"token="..token}; 7 | 8 | ngx.redirect('/ws.html') 9 | -------------------------------------------------------------------------------- /lua/ws.lua: -------------------------------------------------------------------------------- 1 | -- simple chat with redis 2 | local server = require "resty.websocket.server" 3 | local redis = require "resty.redis" 4 | 5 | --获取聊天室id 6 | local len = string.len('/s/') 7 | local channel_id = string.sub(uri,len+1,-1) 8 | 9 | local channel_name = "chat_" .. tostring(channel_id) 10 | 11 | --create connection 12 | local wb, err = server:new{ 13 | timeout = 10000, 14 | max_payload_len = 65535 15 | } 16 | 17 | --create success 18 | if not wb then 19 | ngx.log(ngx.ERR, "failed to new websocket: ", err) 20 | return ngx.exit(444) 21 | end 22 | 23 | 24 | local push = function() 25 | -- --create redis 26 | local red = redis:new() 27 | red:set_timeout(5000) -- 1 sec 28 | local ok, err = red:connect("127.0.0.1", 6379) 29 | if not ok then 30 | ngx.log(ngx.ERR, "failed to connect redis: ", err) 31 | wb:send_close() 32 | return 33 | end 34 | 35 | --sub 36 | local res, err = red:subscribe(channel_name) 37 | if not res then 38 | ngx.log(ngx.ERR, "failed to sub redis: ", err) 39 | wb:send_close() 40 | return 41 | end 42 | 43 | -- loop : read from redis 44 | while true do 45 | local res, err = red:read_reply() 46 | if res then 47 | local item = res[3] 48 | local bytes, err = wb:send_text(tostring(msg_id)..item) 49 | if not bytes then 50 | -- better error handling 51 | ngx.log(ngx.ERR, "failed to send text: ", err) 52 | return ngx.exit(444) 53 | end 54 | end 55 | end 56 | end 57 | 58 | 59 | local co = ngx.thread.spawn(push) 60 | 61 | --main loop 62 | while true do 63 | -- 获取数据 64 | local data, typ, err = wb:recv_frame() 65 | 66 | -- 如果连接损坏 退出 67 | if wb.fatal then 68 | ngx.log(ngx.ERR, "failed to receive frame: ", err) 69 | return ngx.exit(444) 70 | end 71 | 72 | if not data then 73 | local bytes, err = wb:send_ping() 74 | if not bytes then 75 | ngx.log(ngx.ERR, "failed to send ping: ", err) 76 | return ngx.exit(444) 77 | end 78 | --ngx.log(ngx.ERR, "send ping: ", data) 79 | elseif typ == "close" then 80 | break 81 | elseif typ == "ping" then 82 | local bytes, err = wb:send_pong() 83 | if not bytes then 84 | ngx.log(ngx.ERR, "failed to send pong: ", err) 85 | return ngx.exit(444) 86 | end 87 | elseif typ == "pong" then 88 | --ngx.log(ngx.ERR, "client ponged") 89 | elseif typ == "text" then 90 | --send to redis 91 | local red2 = redis:new() 92 | red2:set_timeout(1000) -- 1 sec 93 | local ok, err = red2:connect("127.0.0.1", 6379) 94 | if not ok then 95 | ngx.log(ngx.ERR, "failed to connect redis: ", err) 96 | break 97 | end 98 | local res, err = red2:publish(channel_name, data) 99 | if not res then 100 | ngx.log(ngx.ERR, "failed to publish redis: ", err) 101 | end 102 | end 103 | end 104 | 105 | wb:send_close() 106 | ngx.thread.wait(co) 107 | -------------------------------------------------------------------------------- /nginx.conf: -------------------------------------------------------------------------------- 1 | lua_package_path "/usr/local/openresty/lualib/resty/*.lua;/usr/local/openresty/nginx/lua/?.lua;;"; 2 | lua_socket_log_errors off; 3 | location ~ /s/(.*) { 4 | 5 | content_by_lua_file lua/ws.lua; 6 | } 7 | #模拟登录,随机生成用户 8 | location = /login { 9 | content_by_lua_file lua/login.lua; 10 | } 11 | 12 | location = /ws.html { 13 | #检查用户权限 14 | access_by_lua_file lua/access.lua; 15 | alias html/$1.html; 16 | } 17 | --------------------------------------------------------------------------------