httpserver-websocket.lua 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. --rewrite from https://github.com/creationix/nodemcu-webide
  2. local function decode(chunk)
  3. if #chunk < 2 then return end
  4. local second = string.byte(chunk, 2)
  5. local len = bit.band(second, 0x7f)
  6. local offset
  7. if len == 126 then
  8. if #chunk < 4 then return end
  9. len = bit.bor(
  10. bit.lshift(string.byte(chunk, 3), 8),
  11. string.byte(chunk, 4))
  12. offset = 4
  13. elseif len == 127 then
  14. if #chunk < 10 then return end
  15. len = bit.bor(
  16. -- Ignore lengths longer than 32bit
  17. bit.lshift(string.byte(chunk, 7), 24),
  18. bit.lshift(string.byte(chunk, 8), 16),
  19. bit.lshift(string.byte(chunk, 9), 8),
  20. string.byte(chunk, 10))
  21. offset = 10
  22. else
  23. offset = 2
  24. end
  25. local mask = bit.band(second, 0x80) > 0
  26. if mask then
  27. offset = offset + 4
  28. end
  29. if #chunk < offset + len then return end
  30. local first = string.byte(chunk, 1)
  31. local payload = string.sub(chunk, offset + 1, offset + len)
  32. assert(#payload == len, "Length mismatch")
  33. if mask then
  34. payload = crypto.mask(payload, string.sub(chunk, offset - 3, offset))
  35. end
  36. local extra = string.sub(chunk, offset + len + 1)
  37. local opcode = bit.band(first, 0xf)
  38. return extra, payload, opcode
  39. end
  40. local function encode(payload, opcode)
  41. opcode = opcode or 2
  42. assert(type(opcode) == "number", "opcode must be number")
  43. assert(type(payload) == "string", "payload must be string")
  44. local len = #payload
  45. local head = string.char(
  46. bit.bor(0x80, opcode),
  47. bit.bor(len < 126 and len or len < 0x10000 and 126 or 127)
  48. )
  49. if len >= 0x10000 then
  50. head = head .. string.char(
  51. 0,0,0,0, -- 32 bit length is plenty, assume zero for rest
  52. bit.band(bit.rshift(len, 24), 0xff),
  53. bit.band(bit.rshift(len, 16), 0xff),
  54. bit.band(bit.rshift(len, 8), 0xff),
  55. bit.band(len, 0xff)
  56. )
  57. elseif len >= 126 then
  58. head = head .. string.char(bit.band(bit.rshift(len, 8), 0xff), bit.band(len, 0xff))
  59. end
  60. return head .. payload
  61. end
  62. local guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
  63. local function acceptKey(key)
  64. return crypto.toBase64(crypto.hash("sha1", key .. guid))
  65. end
  66. return function (connection, payload)
  67. local buffer = false
  68. local socket = {}
  69. local queue = {}
  70. local waiting = false
  71. local function onSend()
  72. if queue[1] then
  73. local data = table.remove(queue, 1)
  74. return connection:send(data, onSend)
  75. end
  76. waiting = false
  77. end
  78. function socket.send(...)
  79. local data = encode(...)
  80. if not waiting then
  81. waiting = true
  82. connection:send(data, onSend)
  83. else
  84. queue[#queue + 1] = data
  85. end
  86. collectgarbage()
  87. print(node.heap())
  88. end
  89. connection:on("receive", function(_, chunk)
  90. if buffer then
  91. buffer = buffer .. chunk
  92. while true do
  93. local extra, payload, opcode = decode(buffer)
  94. if not extra then return end
  95. buffer = extra
  96. socket.onmessage(payload, opcode)
  97. end
  98. end
  99. end)
  100. connection:on("sent", function(_, _)
  101. if socket.onsent ~= nil then
  102. socket.onsent()
  103. end
  104. end)
  105. connection:on("disconnection", function(_, _)
  106. if socket.onclose ~= nil then
  107. socket.onclose()
  108. end
  109. end)
  110. local req = dofile("httpserver-request.lc")(payload)
  111. local key = payload:match("Sec%-WebSocket%-Key: ([A-Za-z0-9+/=]+)")
  112. local fileExists = file.open(req.uri.file, "r")
  113. file.close()
  114. if req.method == "GET" and key and fileExists then
  115. connection:send(
  116. "HTTP/1.1 101 Switching Protocols\r\n" ..
  117. "Upgrade: websocket\r\nConnection: Upgrade\r\n" ..
  118. "Sec-WebSocket-Accept: " .. acceptKey(key) .. "\r\n\r\n",
  119. function () dofile(req.uri.file)(socket) end)
  120. buffer = ""
  121. else
  122. connection:send(
  123. "HTTP/1.1 404 Not Found\r\nConnection: Close\r\n\r\n",
  124. connection.close)
  125. end
  126. end