Software /
code /
prosody
Comparison
net/websocket.lua @ 6389:8eccd07b619c
net/websocket: Add new websocket client code
author | daurnimator <quae@daurnimator.com> |
---|---|
date | Wed, 03 Sep 2014 15:28:46 -0400 |
child | 6398:ad434f47bfc0 |
comparison
equal
deleted
inserted
replaced
6387:6d769afd0bc5 | 6389:8eccd07b619c |
---|---|
1 -- Prosody IM | |
2 -- Copyright (C) 2012 Florian Zeitz | |
3 -- Copyright (C) 2014 Daurnimator | |
4 -- | |
5 -- This project is MIT/X11 licensed. Please see the | |
6 -- COPYING file in the source package for more information. | |
7 -- | |
8 | |
9 local http = require "net.http"; | |
10 local frames = require "net.websocket.frames"; | |
11 local base64 = require "util.encodings".base64; | |
12 local sha1 = require "util.hashes".sha1; | |
13 local random_bytes = require "util.random".bytes; | |
14 local timer = require "util.timer"; | |
15 local log = require "util.logger".init "websocket"; | |
16 | |
17 local close_timeout = 3; -- Seconds to wait after sending close frame until closing connection. | |
18 | |
19 local websockets = {}; | |
20 | |
21 local websocket_listeners = {}; | |
22 function websocket_listeners.ondisconnect(handler, err) | |
23 local s = websockets[handler]; | |
24 websockets[handler] = nil; | |
25 if s.close_timer then | |
26 timer.stop(s.close_timer); | |
27 s.close_timer = nil; | |
28 end | |
29 s.readyState = 3; | |
30 if s.close_code == nil and s.onerror then s:onerror(err); end | |
31 if s.onclose then s:onclose(s.close_code, s.close_message or err); end | |
32 end | |
33 | |
34 function websocket_listeners.ondetach(handler) | |
35 websockets[handler] = nil; | |
36 end | |
37 | |
38 local function fail(s, code, reason) | |
39 module:log("warn", "WebSocket connection failed, closing. %d %s", code, reason); | |
40 s:close(code, reason); | |
41 s.handler:close(); | |
42 return false | |
43 end | |
44 | |
45 function websocket_listeners.onincoming(handler, buffer, err) | |
46 local s = websockets[handler]; | |
47 s.readbuffer = s.readbuffer..buffer; | |
48 while true do | |
49 local frame, len = frames.parse(s.readbuffer); | |
50 if frame == nil then break end | |
51 s.readbuffer = s.readbuffer:sub(len+1); | |
52 | |
53 log("debug", "Websocket received frame: opcode=%0x, %i bytes", frame.opcode, #frame.data); | |
54 | |
55 -- Error cases | |
56 if frame.RSV1 or frame.RSV2 or frame.RSV3 then -- Reserved bits non zero | |
57 return fail(s, 1002, "Reserved bits not zero"); | |
58 end | |
59 | |
60 if frame.opcode < 0x8 then | |
61 local databuffer = s.databuffer; | |
62 if frame.opcode == 0x0 then -- Continuation frames | |
63 if not databuffer then | |
64 return fail(s, 1002, "Unexpected continuation frame"); | |
65 end | |
66 databuffer[#databuffer+1] = frame.data; | |
67 elseif frame.opcode == 0x1 or frame.opcode == 0x2 then -- Text or Binary frame | |
68 if databuffer then | |
69 return fail(s, 1002, "Continuation frame expected"); | |
70 end | |
71 databuffer = {type=frame.opcode, frame.data}; | |
72 s.databuffer = databuffer; | |
73 else | |
74 return fail(s, 1002, "Reserved opcode"); | |
75 end | |
76 if frame.FIN then | |
77 s.databuffer = nil; | |
78 if s.onmessage then | |
79 s:onmessage(table.concat(databuffer), databuffer.type); | |
80 end | |
81 end | |
82 else -- Control frame | |
83 if frame.length > 125 then -- Control frame with too much payload | |
84 return fail(s, 1002, "Payload too large"); | |
85 elseif not frame.FIN then -- Fragmented control frame | |
86 return fail(s, 1002, "Fragmented control frame"); | |
87 end | |
88 if frame.opcode == 0x8 then -- Close request | |
89 if frame.length == 1 then | |
90 return fail(s, 1002, "Close frame with payload, but too short for status code"); | |
91 end | |
92 local status_code, message = frames.parse_close(frame.data); | |
93 if status_code == nil then | |
94 --[[ RFC 6455 7.4.1 | |
95 1005 is a reserved value and MUST NOT be set as a status code in a | |
96 Close control frame by an endpoint. It is designated for use in | |
97 applications expecting a status code to indicate that no status | |
98 code was actually present. | |
99 ]] | |
100 status_code = 1005 | |
101 elseif status_code < 1000 then | |
102 return fail(s, 1002, "Closed with invalid status code"); | |
103 elseif ((status_code > 1003 and status_code < 1007) or status_code > 1011) and status_code < 3000 then | |
104 return fail(s, 1002, "Closed with reserved status code"); | |
105 end | |
106 s.close_code, s.close_message = status_code, message; | |
107 s:close(1000); | |
108 return true; | |
109 elseif frame.opcode == 0x9 then -- Ping frame | |
110 frame.opcode = 0xA; | |
111 frame.MASK = true; -- RFC 6455 6.1.5: If the data is being sent by the client, the frame(s) MUST be masked | |
112 handler:write(frames.build(frame)); | |
113 elseif frame.opcode == 0xA then -- Pong frame | |
114 log("debug", "Received unexpected pong frame: " .. tostring(frame.data)); | |
115 else | |
116 return fail(s, 1002, "Reserved opcode"); | |
117 end | |
118 end | |
119 end | |
120 return true; | |
121 end | |
122 | |
123 local websocket_methods = {}; | |
124 local function close_timeout_cb(now, timerid, s) | |
125 s.close_timer = nil; | |
126 log("warn", "Close timeout waiting for server to close, closing manually."); | |
127 s.handler:close(); | |
128 end | |
129 function websocket_methods:close(code, reason) | |
130 if self.readyState < 2 then | |
131 code = code or 1000; | |
132 log("debug", "closing WebSocket with code %i: %s" , code , tostring(reason)); | |
133 self.readyState = 2; | |
134 local handler = self.handler; | |
135 handler:write(frames.build_close(code, reason)); | |
136 -- Do not close socket straight away, wait for acknowledgement from server. | |
137 self.close_timer = timer.add_task(close_timeout, close_timeout_cb, self); | |
138 elseif self.readyState == 2 then | |
139 log("debug", "tried to close a closing WebSocket, closing the raw socket."); | |
140 -- Stop timer | |
141 if self.close_timer then | |
142 timer.stop(self.close_timer); | |
143 self.close_timer = nil; | |
144 end | |
145 local handler = self.handler; | |
146 handler:close(); | |
147 else | |
148 log("debug", "tried to close a closed WebSocket, ignoring."); | |
149 end | |
150 end | |
151 function websocket_methods:send(data, opcode) | |
152 if self.readyState < 1 then | |
153 return nil, "WebSocket not open yet, unable to send data."; | |
154 elseif self.readyState >= 2 then | |
155 return nil, "WebSocket closed, unable to send data."; | |
156 end | |
157 if opcode == "text" or opcode == nil then | |
158 opcode = 0x1; | |
159 elseif opcode == "binary" then | |
160 opcode = 0x2; | |
161 end | |
162 local frame = { | |
163 FIN = true; | |
164 MASK = true; -- RFC 6455 6.1.5: If the data is being sent by the client, the frame(s) MUST be masked | |
165 opcode = opcode; | |
166 data = tostring(data); | |
167 }; | |
168 log("debug", "WebSocket sending frame: opcode=%0x, %i bytes", frame.opcode, #frame.data); | |
169 return self.handler:write(frames.build(frame)); | |
170 end | |
171 | |
172 local websocket_metatable = { | |
173 __index = websocket_methods; | |
174 }; | |
175 | |
176 local function connect(url, ex, listeners) | |
177 ex = ex or {}; | |
178 | |
179 --[[ RFC 6455 4.1.7: | |
180 The request MUST include a header field with the name | |
181 |Sec-WebSocket-Key|. The value of this header field MUST be a | |
182 nonce consisting of a randomly selected 16-byte value that has | |
183 been base64-encoded (see Section 4 of [RFC4648]). The nonce | |
184 MUST be selected randomly for each connection. | |
185 ]] | |
186 local key = base64.encode(random_bytes(16)); | |
187 | |
188 -- Either a single protocol string or an array of protocol strings. | |
189 local protocol = ex.protocol; | |
190 if type(protocol) == "table" then | |
191 protocol = table.concat(protocol, ", "); | |
192 end | |
193 | |
194 local headers = { | |
195 ["Upgrade"] = "websocket"; | |
196 ["Connection"] = "Upgrade"; | |
197 ["Sec-WebSocket-Key"] = key; | |
198 ["Sec-WebSocket-Protocol"] = protocol; | |
199 ["Sec-WebSocket-Version"] = "13"; | |
200 ["Sec-WebSocket-Extensions"] = ex.extensions; | |
201 } | |
202 if ex.headers then | |
203 for k,v in pairs(ex.headers) do | |
204 headers[k] = v; | |
205 end | |
206 end | |
207 | |
208 local s = setmetatable({ | |
209 readbuffer = ""; | |
210 databuffer = nil; | |
211 handler = nil; | |
212 close_code = nil; | |
213 close_message = nil; | |
214 close_timer = nil; | |
215 readyState = 0; | |
216 protocol = nil; | |
217 | |
218 url = url; | |
219 | |
220 onopen = listeners.onopen; | |
221 onclose = listeners.onclose; | |
222 onmessage = listeners.onmessage; | |
223 onerror = listeners.onerror; | |
224 }, websocket_metatable); | |
225 | |
226 local http_url = url:gsub("^(ws)", "http"); | |
227 local http_req = http.request(http_url, { | |
228 method = "GET"; | |
229 headers = headers; | |
230 sslctx = ex.sslctx; | |
231 }, function(b, c, r, http_req) | |
232 if c ~= 101 | |
233 or r.headers["connection"]:lower() ~= "upgrade" | |
234 or r.headers["upgrade"] ~= "websocket" | |
235 or r.headers["sec-websocket-accept"] ~= base64.encode(sha1(key .. "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")) | |
236 -- TODO: check "Sec-WebSocket-Protocol" | |
237 then | |
238 s.readyState = 3; | |
239 log("warn", "WebSocket connection to %s failed: %s", url, tostring(b)); | |
240 if s.onerror then s:onerror("connecting-failed"); end | |
241 return | |
242 end | |
243 | |
244 s.protocol = r.headers["sec-websocket-protocol"]; | |
245 | |
246 -- Take possession of socket from http | |
247 http_req.conn = nil; | |
248 local handler = http_req.handler; | |
249 s.handler = handler; | |
250 websockets[handler] = s; | |
251 handler:setlistener(websocket_listeners); | |
252 | |
253 log("debug", "WebSocket connected successfully to %s", url); | |
254 s.readyState = 1; | |
255 if s.onopen then s:onopen(); end | |
256 websocket_listeners.onincoming(handler, b); | |
257 end); | |
258 | |
259 return s; | |
260 end | |
261 | |
262 return { | |
263 connect = connect; | |
264 }; |