How do I pair basic http/s authentication and paths with socket.connect? I would think this is the common case for websockets, but I could be wrong. I’ve tried to play with Noobhub to get it to work but I’ve not made much progress.
and I really don’t understand how to make a good connection from that. Noobhub is happy to make a socket to someuri.com but after that it’s not clear how I’m supposed to negotiate the rest of it.
Ok, I fixed my problem by simply sending a message to Corona from noobhub server. Not the most elegant way, but it works and it allmost never happens.
if (socket.channel.substr(0, 6) == 'Match\_') { console.log('\*\*\* Match channel number of player check. Connections in the same channel:'); var numberOfConnections = 0 for (var prop in sockets[socket.channel]) { numberOfConnections++; console.log(numberOfConnections + '. channel: ' + socket.channel + ', conn\_id (key) is: ' + prop + '. Value is: ' + sockets[socket.channel][prop]); } // if to many players, notify each client to cancel the match if (numberOfConnections \> 2) { var message = { "action":"ToManyPlayers\_in\_the\_channel" }; message = JSON.stringify(message); for (var prop in sockets[socket.channel]) { if (sockets[socket.channel].hasOwnProperty(prop)) { sockets[socket.channel][prop].write("\_\_JSON\_\_START\_\_" + message + "\_\_JSON\_\_END\_\_"); } } } } }
Thats impossible. Youre trying HTTP auth, and HTTP is a protocol on top of TCP/IP. Noobhub is TCP/IP library only.
Think of authorisation INSIDE of Noobhub interaction.
I don’t see how it’s possible to do auth inside of the default noobhub. Noobhub starts with a __SUBSCRIBE__ , there’s no chance for authentication. Doesn’t your basic authentication header have to come before your are authorized to subscribe to a channel? Then after that it’s doing __JSON ?
I’ve mostly finished for implementing RFC6455 and integrated with pusher.com -
-------------------- -- PusherHub -- opensource websocket messaging for CoronaSDK and Pusher.com -- -- Authors: Jack9 -- -- License: WTFPL -------------------- local socket = require("socket") local mime = require("mime") local crypto = require("crypto") local self = { readyCallback = nil, server = nil, port = nil, headers = '', key = nil, secret = nil, app\_id = nil, sock = nil, buffer = '', channels = {}, readyState = 3 --0 (connecting), 1 (open), 2 (closing), and 3 (closed). } self.lpad = function(str, len, char) str = tostring(str) if char == nil then char = ' ' end return str .. string.rep(char, len - #str) end self.tobits = function(num) -- returns a table of bits, least significant first. local t={} -- will contain the bits while num\>0 do rest=math.fmod(num,2) t[#t+1]=rest num=(num-rest)/2 end return string.reverse(table.concat(t)) end self.handleBytes = function(byes) --print("some binary msg",byes) local chrs = {} local chr for i=1,string.len(byes) do chr = byes:byte(i) chrs[#chrs+1] = chr end --print(util.xmp(chrs)) return chrs end self.makeFrame = function(str) -- UGLY HARD PART - Assemble Websocket Frame Header -- see http://tools.ietf.org/html/rfc6455#section-5 (5.2) local bitGroups = {} local binStr = "1" -- FIN, why not? 1 binStr = binStr.."000" -- RSV1,RSV2,RESV3, -- the 'Sec-WebSocket-Extensions: x-webkit-deflate-frame' + RSV1 doesn't have an effect binStr = binStr.."0001" -- %x1 denotes a text frame (I guess this means 0001) - confirmed bitGroups[#bitGroups+1] = binStr -- we dump the value and have a byte binStr = "0" -- Not using a mask and starting over on the binStr local strLen = string.len(str) -- message length in bytes --print("strLen",strLen) local pad = 7 -- Spec says default of 7 -- This is a little confusing. spec says ... determine how many bits you need BEFORE assembling. -- If our strLen is over 125, we have to IGNORE the initial 7 because they become a placeholder value. -- If strLen is 126-65536 we can use 16 bits and leave the initial 7 at a placeholder of 126 -- If strLen is more, we leave the initial 7 at 127 and use 64 bits -- use those NEW bits for sizing. if strLen \> 125 then if strLen \<= 65536 then binStr = binStr.."1111110" -- 16 bit time pad = 16 else binStr = binStr.."1111111" -- 64 bit time! pad = 64 end end --print(strLen,self.tobits(strLen)) binStr = binStr..self.lpad(self.tobits(strLen),pad,"0") -- 7 or 7+16 or 7+64 bits to represent message byte length local s = 1 local e = 8 while e \< pad+9 do bitGroups[#bitGroups+1] = string.sub(binStr,s,e) -- we dump the value and have another byte s = s+8 e = e+8 end -- There are many ways to go from here. I chose a way that works. Can probably be improved. local ret = '' for i=1,#bitGroups do -- Now that we've assembled a delicate set of bits, move to bytes ret = ret..string.char(tonumber(bitGroups[i],2)) end --print(table.concat(bitGroups)) -- Leading bytes are the header for the frame. Ta-da return ret..str end self.makePongFrame = function() local char1bits = "10001010" -- this is a pong frame local char2bits = "00000000" -- this is empty in a pong frame local char1byte = tonumber(char1bits,2) -- clean! local char2byte = tonumber(char2bits,2) -- clean! local ascii1 = string.char(char1byte) -- tricky! local ascii2 = string.char(char2byte) -- tricky! -- Leading bytes are the header for the frame. Ta-da return ascii1..ascii2 -- this is a pong frame \*shrug\* end self.websocketresponse6 = function(key) -- This string is hardcoded per websocket specification local magic = key .. "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" return (mime.b64(crypto.digest(crypto.sha1, magic, true))) end self.new = function (params) -- constructor method params = params or {} params.port = params.port or 80 if not params.server or not params.key or not params.secret or not params.app\_id or not params.readyCallback or type(params.readyCallback) ~= "function" then print("PusherHub requires server, key, secret, app\_id, readyCallback function and defaults to port 80") self.err = "Invalid configuration" return self end -- Headers are legitimate, since Chrome uses them params.headers = params.headers or { 'GET /app/'..params.key..'?protocol=6&client=lua&version=1.0&flash=false HTTP/1.1', 'Host: '..params.server, 'Sec-WebSocket-Key: '..self.websocketresponse6(params.app\_id), -- anything is fine, might as well use the app\_id for something 'Upgrade: websocket', 'Connection: Upgrade', 'Sec-WebSocket-Version: 13', 'Pragma: no-cache', --'Authentication: Basic '..(mime.b64(params.key..":"..params.secret)), 'Origin: http://jsonur.com', --'User-Agent: Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.116 Safari/537.36', -- headers based on Chrome 'Sec-WebSocket-Extensions: x-webkit-deflate-frame', 'Cache-Control: no-cache', } self.readyCallback = params.readyCallback self.server = params.server self.port = params.port if params.headers ~= nil then for i=1,#params.headers do self.headers = self.headers..params.headers[i].."\n" end end self.headers = self.headers.."\n" self.key = params.key self.secret = params.secret self.app\_id = params.app\_id self.sock = socket.tcp() if( self.sock == "nil" or self.sock == nil) then self.err = "no connection" return self end self.sock:settimeout(3) -- minimum 1 !!! self.sock:setoption( 'tcp-nodelay', true ) -- Doesn't seem to hurt self.sock:setoption( 'keepalive', true ) -- Doesn't seem to hurt self.readyState = 0 local \_,erro = self.sock:connect(self.server, self.port) if erro then self.err = "Problem resolving servername, contacting server, or accessing port:"..erro self.sock:close() return self end -- Check if headers are good --print(self.headers.."\r\n") local bytesSent = self.sock:send(self.headers) --print("sent bytes:",bytesSent) self.origParams = params return self end self.subscribe = function(params) params.channel = params.channel or 'test\_channel' params.private = false if params.channel\_data and (string.sub(params.channel,1,8) == "private-" or string.sub(params.channel,1,9) == "presence-") then params.private = true end self.channels[params.channel] = { -- this could get ugly if you resubscribe with new events events = params.bindings } local msg = { ["event"] = "pusher:subscribe", ["data"] = { ["channel"] = params.channel, }, } if params.private then --pusher.com docs say HMAC::SHA256.hexdigest(secret, string\_to\_sign) local string\_to\_sign = self.id..":"..params.channel local auth = self.key..":"..crypto.hmac( crypto.sha256, string\_to\_sign, self.secret ) msg["data"]["auth"] = auth msg["data"]["channel\_data"] = json.encode(params.channel\_data) end msg = json.encode(msg) --print(msg) self.publish(msg) return true end self.unsubscribe = function(chan) -- Wipe out bindings, you won't get to see your own parting for i=1,#self.channels[chan]["events"] do self.channels[chan]["events"][i] = nil end self.channels[chan] = nil local msg = { ["event"] = "pusher:unsubscribe", ["data"] = { ["channel"] = chan, }, } msg = json.encode(msg) self.publish(msg) end -- Untested self.reconnect = function() print("PusherHub: attempt to reconnect...") self = self.new(self.origParams) Runtime:addEventListener('enterFrame', self) return self end self.publish = function(msg) print("publishing",msg) local num\_byes = self.sock:send(self.makeFrame(msg)) --print("bytes published",num\_byes) return true end self.enterFrame = function(evt) local chr, str local chrs = {} local got\_something\_new = false local skt, e, p local input,output = socket.select({ self.sock },nil, 0) -- this is a way not to block runtime while reading socket. zero timeout does the trick for i,v in ipairs(input) do ------------- while true do skt, e, p = v:receive() print("buf",skt,e,p) if e == "closed" then self.readyState = 3 self.valid = false Runtime:removeEventListener('enterFrame',self) self.readyState = 4 break end if (p) then --print("p",p) -- Websocket dead due to error local f,l = string.find(p,"Unknown opcode 11") if f ~= nil then self.readyState = 3 print("frame erro",p) Runtime:removeEventListener('enterFrame',self) self.valid = false self.readyState = 4 break end if p ~= '' then got\_something\_new = true self.buffer = self.buffer..p end break elseif (e) then --print("e",e) self.readyState = 3 print("websocket closing",e) Runtime:removeEventListener('enterFrame',self) self.valid = false self.readyState = 4 break elseif (skt) then --print("skt",skt) end end -- /while-do -- now, checking if a message is present in buffer... if got\_something\_new then -- this is for a case of several messages stocker in the buffer --print("somethingnew",self.buffer) local msg -- Startup Connection parsing if not self.valid then local startIdx,endIdx = string.find(self.buffer, '{"event":"pusher:connection\_established","data":"{\\"socket\_id\\":') if startIdx ~= nil and startIdx \> 0 then msg = string.sub(self.buffer,startIdx) msg = json.decode(msg) msg = json.decode(msg["data"]) self.id = msg.socket\_id self.valid = true self.readyState = 1 self.readyCallback() -- should call subscribe, I hope! end end -- Standard message (some bytes header, then json) local b,e = string.find(self.buffer, "{") if b ~= nil then bins = string.sub(self.buffer,1,b-1) chrs = self.handleBytes(bins) msg = string.sub(self.buffer,b)-- have to remember to strip off those frame header bytes! --print(msg) msg = json.decode(msg) if msg.event == "pusher\_internal:subscription\_succeeded" then if type(self.channels[msg.channel]["events"][msg.event]) == "function" then self.channels[msg.channel]["events"][msg.event](msg) end elseif msg.event == "pusher:subscription\_error" then print("sub err") if self.channels[msg.channel] ~= nil and type(self.channels[msg.channel]["events"][msg.event]) == "function" then self.channels[msg.channel]["events"][msg.event](msg) end elseif msg.event == "pusher:error" then print("Nonfatal Err:",msg.data.message) elseif self.channels[msg.channel] ~= nil and type(self.channels[msg.channel]["events"][msg.event]) == "function" then self.channels[msg.channel]["events"][msg.event](msg) end else chrs = self.handleBytes(self.buffer) if chrs[1] == 137 and chrs[2] == 0 then -- this is a ping print("heard ping...sending pong as keepalive") local byes = self.sock:send(self.makePongFrame()) --print("bytes sent:",byes) end end got\_something\_new = false self.buffer = '' end end end -- /enterFrame Runtime:addEventListener('enterFrame', self) return self --[[-- Example Usage print("connecting to chat server...") mychathub = nil -- global mychathub = pusherhub.new({ app\_id = '12345', -- Example key = '278d425bdf160c739803', -- Example http://pusher.com/docs/auth\_signatures secret = '7ad3773142a6692b25b8', -- Example http://pusher.com/docs/auth\_signatures server = "ws.pusherapp.com", port = 80, readyCallback = function() print("Connected to chat server.") print("Attempting to join Gen Chat...") mychathub.subscribe({ channel = "test\_channel", bindings = { ["client-message"] = function(msg1) print("test client-message",msg1) end, ["pusher\_internal:subscription\_succeeded"] = function(msg2) -- Msg2 is a table print("test pusher\_internal:subscription\_succeeded",msg2.event) print("Joined Gen Chat.") end } }) end }) ]]--
Caveats: Using a sandbox (free) account, you don’t want to try a private- or presence- channel. It wont respond to a ping correctly and will close out the connection. You cannot use a secure connection in a sandbox (free) account. The pusher docs are… hard to get through http://pusher.com/docs because the API/Websocket information is mixed together.
My question is not related to noobhub directly, but to node.js.
I did some modifications to the original noobhub server and noticed that the memory increased after more users connected to the server.
I downgraded my version to the original one from github, and still the same problem.
I finally tested with this minimal code, and the memory usage is still increasing with the number of connection (doesn’t matter if they are active or closed).
var server = require('net').createServer() , sockets = {} // this is where we store all current client socket connections , cfg = { port: 1337, buffer\_size: 1024\*8, // buffer is allocated per each socket client verbose: false } , \_log = function(){ if (cfg.verbose) console.log.apply(console, arguments); }; // black magic process.on('uncaughtException', function(err){ \_log('Exception: ' + err); }); server.on('connection', function(socket) { socket.destroy(); socket = null; if(true) return; }); // end of server.on 'connection' server.on('listening', function(){ console.log('NoobHub on ' + server.address().address +':'+ server.address().port); }); server.listen(cfg.port);
Is there some other reason that I miss out why the memory increases?
Since there’s been so much talk about HTTP Authentication lately, can anyone explain to me what it is(I have a limited understanding atm) and what it’s purpose would be, especially in noobhub? I’m just curious to learn more about it.
@beckslash Have you looked at the log files(if any) that are being stored or recorded on your server? There could be some message there you are missing. I’ve been using Noobhub for 9-10 months, since @overtorment became the online hero and posted a link about it in the failing Autolan forum thread :P, and i’ve never had any issue at all with memory, my memory usage is always very low. I have two live apps using Noobhub running on two separate Linux 11.04 installations being kept alive using forever, and I haven’t ran into any issues at all. I would take a look at those logs if any exist and also see if there’s any error with the way you have node installed or something along those lines, because the problem most likely isn’t coming from noobhub itself. That’s my 2 cents.
Websockets are essentially an HTTP connection and are authenticated similarly. This means you usually pass headers as part of the HTTP handshake (common case, see http://tools.ietf.org/html/rfc6455#section-10.5)
Hence, I wondered if there was an easy way to push headers via noobhub and there is. Pass them as a table in params.headers in new() assemble them during connection, pass them in via send()
Anyone hosted Noobhub on VPS but couldn’t connect to it?
In one instance, I was getting the “connection error” messages in Corona SDK, but after I opened all the IP ports in iptables it seemed to work, yet what is supposed to happen normally (where I can open two emulators and connect through the sample Noobhub server) does not occur anymore.
EDIT: Nevermind, it didn’t work when I tried forever start node.js -v but it works with just plain old node node.js with all the ports open.
I have VPS set up at DigitalOcean as well (with two other servers, see below). One thing to keep in mind with them is they only offer KVM and it requires more memory overhead than OpenVZ in general. So make sure you get a plan with more RAM (IMHO at least 1GB for KVM). I’ve seen certain tasks fail with 512MB.
Other VPS providers I use who offer both OpenVZ and KVM are:
I’m actually currently hosting it on a cheap $15/year VPS at https://liquid-solutions.biz/, it works, but was wondering if Heroku servers would be faster latency wise. Thanks for the suggestions
I’m new to this, do you mind telling me the differences between these options and what the jargon means? How does the RAM get used for my game in general?