Annotate

net/websocket.lua @ 13014:06453c564141

util.startup: Add prosody.started promise to easily execute code after startup To avoid a race where server-started fires before the promise function body is run (on next tick), I moved server-started to fire on the next tick, which seems sensible anyway. Errors are logged, I'm not sure if we ought to be doing something more here. I'm sure we'll find out.
author Matthew Wild <mwild1@gmail.com>
date Sat, 01 Apr 2023 11:56:38 +0100
parent 12974:ba409c67353b
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
1 -- Prosody IM
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
2 -- Copyright (C) 2012 Florian Zeitz
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
3 -- Copyright (C) 2014 Daurnimator
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
4 --
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
5 -- This project is MIT/X11 licensed. Please see the
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
6 -- COPYING file in the source package for more information.
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
7 --
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
8
6396
17b54f523796 Check Sec-WebSocket-Protocol header
Florian Zeitz <florob@babelmonkeys.de>
parents: 6395
diff changeset
9 local t_concat = table.concat;
17b54f523796 Check Sec-WebSocket-Protocol header
Florian Zeitz <florob@babelmonkeys.de>
parents: 6395
diff changeset
10
12974
ba409c67353b net: Prefix module imports with prosody namespace
Kim Alvefur <zash@zash.se>
parents: 10453
diff changeset
11 local http = require "prosody.net.http";
ba409c67353b net: Prefix module imports with prosody namespace
Kim Alvefur <zash@zash.se>
parents: 10453
diff changeset
12 local frames = require "prosody.net.websocket.frames";
ba409c67353b net: Prefix module imports with prosody namespace
Kim Alvefur <zash@zash.se>
parents: 10453
diff changeset
13 local base64 = require "prosody.util.encodings".base64;
ba409c67353b net: Prefix module imports with prosody namespace
Kim Alvefur <zash@zash.se>
parents: 10453
diff changeset
14 local sha1 = require "prosody.util.hashes".sha1;
ba409c67353b net: Prefix module imports with prosody namespace
Kim Alvefur <zash@zash.se>
parents: 10453
diff changeset
15 local random_bytes = require "prosody.util.random".bytes;
ba409c67353b net: Prefix module imports with prosody namespace
Kim Alvefur <zash@zash.se>
parents: 10453
diff changeset
16 local timer = require "prosody.util.timer";
ba409c67353b net: Prefix module imports with prosody namespace
Kim Alvefur <zash@zash.se>
parents: 10453
diff changeset
17 local log = require "prosody.util.logger".init "websocket";
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
18
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
19 local close_timeout = 3; -- Seconds to wait after sending close frame until closing connection.
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
20
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
21 local websockets = {};
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
22
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
23 local websocket_listeners = {};
8892
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8189
diff changeset
24 function websocket_listeners.ondisconnect(conn, err)
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8189
diff changeset
25 local s = websockets[conn];
10453
926d6086a95a net.websocket: Fix traceback in case of ondisconnect being called twice
Matthew Wild <mwild1@gmail.com>
parents: 10113
diff changeset
26 if not s then return; end
8892
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8189
diff changeset
27 websockets[conn] = nil;
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
28 if s.close_timer then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
29 timer.stop(s.close_timer);
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
30 s.close_timer = nil;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
31 end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
32 s.readyState = 3;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
33 if s.close_code == nil and s.onerror then s:onerror(err); end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
34 if s.onclose then s:onclose(s.close_code, s.close_message or err); end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
35 end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
36
8892
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8189
diff changeset
37 function websocket_listeners.ondetach(conn)
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8189
diff changeset
38 websockets[conn] = nil;
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
39 end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
40
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
41 local function fail(s, code, reason)
8189
a3565d7dd304 net.websocket: Remove stray module api reference, shouldn't be used in here
Kim Alvefur <zash@zash.se>
parents: 7759
diff changeset
42 log("warn", "WebSocket connection failed, closing. %d %s", code, reason);
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
43 s:close(code, reason);
8892
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8189
diff changeset
44 s.conn:close();
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
45 return false
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
46 end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
47
8892
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8189
diff changeset
48 function websocket_listeners.onincoming(conn, buffer, err) -- luacheck: ignore 212/err
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8189
diff changeset
49 local s = websockets[conn];
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
50 s.readbuffer = s.readbuffer..buffer;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
51 while true do
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
52 local frame, len = frames.parse(s.readbuffer);
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
53 if frame == nil then break end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
54 s.readbuffer = s.readbuffer:sub(len+1);
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
55
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
56 log("debug", "Websocket received frame: opcode=%0x, %i bytes", frame.opcode, #frame.data);
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
57
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
58 -- Error cases
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
59 if frame.RSV1 or frame.RSV2 or frame.RSV3 then -- Reserved bits non zero
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
60 return fail(s, 1002, "Reserved bits not zero");
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
61 end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
62
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
63 if frame.opcode < 0x8 then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
64 local databuffer = s.databuffer;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
65 if frame.opcode == 0x0 then -- Continuation frames
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
66 if not databuffer then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
67 return fail(s, 1002, "Unexpected continuation frame");
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
68 end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
69 databuffer[#databuffer+1] = frame.data;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
70 elseif frame.opcode == 0x1 or frame.opcode == 0x2 then -- Text or Binary frame
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
71 if databuffer then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
72 return fail(s, 1002, "Continuation frame expected");
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
73 end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
74 databuffer = {type=frame.opcode, frame.data};
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
75 s.databuffer = databuffer;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
76 else
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
77 return fail(s, 1002, "Reserved opcode");
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
78 end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
79 if frame.FIN then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
80 s.databuffer = nil;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
81 if s.onmessage then
6396
17b54f523796 Check Sec-WebSocket-Protocol header
Florian Zeitz <florob@babelmonkeys.de>
parents: 6395
diff changeset
82 s:onmessage(t_concat(databuffer), databuffer.type);
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
83 end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
84 end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
85 else -- Control frame
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
86 if frame.length > 125 then -- Control frame with too much payload
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
87 return fail(s, 1002, "Payload too large");
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
88 elseif not frame.FIN then -- Fragmented control frame
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
89 return fail(s, 1002, "Fragmented control frame");
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
90 end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
91 if frame.opcode == 0x8 then -- Close request
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
92 if frame.length == 1 then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
93 return fail(s, 1002, "Close frame with payload, but too short for status code");
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
94 end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
95 local status_code, message = frames.parse_close(frame.data);
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
96 if status_code == nil then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
97 --[[ RFC 6455 7.4.1
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
98 1005 is a reserved value and MUST NOT be set as a status code in a
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
99 Close control frame by an endpoint. It is designated for use in
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
100 applications expecting a status code to indicate that no status
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
101 code was actually present.
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
102 ]]
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
103 status_code = 1005
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
104 elseif status_code < 1000 then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
105 return fail(s, 1002, "Closed with invalid status code");
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
106 elseif ((status_code > 1003 and status_code < 1007) or status_code > 1011) and status_code < 3000 then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
107 return fail(s, 1002, "Closed with reserved status code");
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
108 end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
109 s.close_code, s.close_message = status_code, message;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
110 s:close(1000);
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
111 return true;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
112 elseif frame.opcode == 0x9 then -- Ping frame
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
113 frame.opcode = 0xA;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
114 frame.MASK = true; -- RFC 6455 6.1.5: If the data is being sent by the client, the frame(s) MUST be masked
8892
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8189
diff changeset
115 conn:write(frames.build(frame));
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
116 elseif frame.opcode == 0xA then -- Pong frame
10113
66a9bc2d5c8d net.websocket: Fix log call to pass data via format string instead of concatenation
Kim Alvefur <zash@zash.se>
parents: 10112
diff changeset
117 log("debug", "Received unexpected pong frame: %s", frame.data);
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
118 else
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
119 return fail(s, 1002, "Reserved opcode");
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
120 end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
121 end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
122 end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
123 return true;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
124 end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
125
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
126 local websocket_methods = {};
7759
69706084bdfe net.websocket: Ignore unused argument warnings [luacheck]
Kim Alvefur <zash@zash.se>
parents: 6455
diff changeset
127 local function close_timeout_cb(now, timerid, s) -- luacheck: ignore 212/now 212/timerid
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
128 s.close_timer = nil;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
129 log("warn", "Close timeout waiting for server to close, closing manually.");
8892
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8189
diff changeset
130 s.conn:close();
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
131 end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
132 function websocket_methods:close(code, reason)
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
133 if self.readyState < 2 then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
134 code = code or 1000;
10112
b327f2870382 net.*: Remove tostring call from logging
Kim Alvefur <zash@zash.se>
parents: 8893
diff changeset
135 log("debug", "closing WebSocket with code %i: %s" , code , reason);
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
136 self.readyState = 2;
8892
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8189
diff changeset
137 local conn = self.conn;
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8189
diff changeset
138 conn:write(frames.build_close(code, reason, true));
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
139 -- Do not close socket straight away, wait for acknowledgement from server.
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
140 self.close_timer = timer.add_task(close_timeout, close_timeout_cb, self);
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
141 elseif self.readyState == 2 then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
142 log("debug", "tried to close a closing WebSocket, closing the raw socket.");
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
143 -- Stop timer
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
144 if self.close_timer then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
145 timer.stop(self.close_timer);
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
146 self.close_timer = nil;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
147 end
8892
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8189
diff changeset
148 local conn = self.conn;
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8189
diff changeset
149 conn:close();
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
150 else
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
151 log("debug", "tried to close a closed WebSocket, ignoring.");
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
152 end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
153 end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
154 function websocket_methods:send(data, opcode)
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
155 if self.readyState < 1 then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
156 return nil, "WebSocket not open yet, unable to send data.";
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
157 elseif self.readyState >= 2 then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
158 return nil, "WebSocket closed, unable to send data.";
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
159 end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
160 if opcode == "text" or opcode == nil then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
161 opcode = 0x1;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
162 elseif opcode == "binary" then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
163 opcode = 0x2;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
164 end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
165 local frame = {
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
166 FIN = true;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
167 MASK = true; -- RFC 6455 6.1.5: If the data is being sent by the client, the frame(s) MUST be masked
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
168 opcode = opcode;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
169 data = tostring(data);
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
170 };
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
171 log("debug", "WebSocket sending frame: opcode=%0x, %i bytes", frame.opcode, #frame.data);
8892
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8189
diff changeset
172 return self.conn:write(frames.build(frame));
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
173 end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
174
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
175 local websocket_metatable = {
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
176 __index = websocket_methods;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
177 };
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
178
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
179 local function connect(url, ex, listeners)
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
180 ex = ex or {};
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
181
6395
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
182 --[[RFC 6455 4.1.7:
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
183 The request MUST include a header field with the name
6395
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
184 |Sec-WebSocket-Key|. The value of this header field MUST be a
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
185 nonce consisting of a randomly selected 16-byte value that has
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
186 been base64-encoded (see Section 4 of [RFC4648]). The nonce
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
187 MUST be selected randomly for each connection.
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
188 ]]
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
189 local key = base64.encode(random_bytes(16));
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
190
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
191 -- Either a single protocol string or an array of protocol strings.
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
192 local protocol = ex.protocol;
6396
17b54f523796 Check Sec-WebSocket-Protocol header
Florian Zeitz <florob@babelmonkeys.de>
parents: 6395
diff changeset
193 if type(protocol) == "string" then
6407
4bbd198cf3e6 net.websocket: Fix handling of 'protocol' argument
Kim Alvefur <zash@zash.se>
parents: 6398
diff changeset
194 protocol = { protocol, [protocol] = true };
4bbd198cf3e6 net.websocket: Fix handling of 'protocol' argument
Kim Alvefur <zash@zash.se>
parents: 6398
diff changeset
195 elseif type(protocol) == "table" and protocol[1] then
4bbd198cf3e6 net.websocket: Fix handling of 'protocol' argument
Kim Alvefur <zash@zash.se>
parents: 6398
diff changeset
196 for _, v in ipairs(protocol) do
4bbd198cf3e6 net.websocket: Fix handling of 'protocol' argument
Kim Alvefur <zash@zash.se>
parents: 6398
diff changeset
197 protocol[v] = true;
4bbd198cf3e6 net.websocket: Fix handling of 'protocol' argument
Kim Alvefur <zash@zash.se>
parents: 6398
diff changeset
198 end
4bbd198cf3e6 net.websocket: Fix handling of 'protocol' argument
Kim Alvefur <zash@zash.se>
parents: 6398
diff changeset
199 else
4bbd198cf3e6 net.websocket: Fix handling of 'protocol' argument
Kim Alvefur <zash@zash.se>
parents: 6398
diff changeset
200 protocol = nil;
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
201 end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
202
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
203 local headers = {
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
204 ["Upgrade"] = "websocket";
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
205 ["Connection"] = "Upgrade";
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
206 ["Sec-WebSocket-Key"] = key;
6407
4bbd198cf3e6 net.websocket: Fix handling of 'protocol' argument
Kim Alvefur <zash@zash.se>
parents: 6398
diff changeset
207 ["Sec-WebSocket-Protocol"] = protocol and t_concat(protocol, ", ");
6395
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
208 ["Sec-WebSocket-Version"] = "13";
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
209 ["Sec-WebSocket-Extensions"] = ex.extensions;
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
210 }
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
211 if ex.headers then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
212 for k,v in pairs(ex.headers) do
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
213 headers[k] = v;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
214 end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
215 end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
216
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
217 local s = setmetatable({
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
218 readbuffer = "";
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
219 databuffer = nil;
8892
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8189
diff changeset
220 conn = nil;
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
221 close_code = nil;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
222 close_message = nil;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
223 close_timer = nil;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
224 readyState = 0;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
225 protocol = nil;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
226
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
227 url = url;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
228
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
229 onopen = listeners.onopen;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
230 onclose = listeners.onclose;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
231 onmessage = listeners.onmessage;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
232 onerror = listeners.onerror;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
233 }, websocket_metatable);
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
234
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
235 local http_url = url:gsub("^(ws)", "http");
7759
69706084bdfe net.websocket: Ignore unused argument warnings [luacheck]
Kim Alvefur <zash@zash.se>
parents: 6455
diff changeset
236 local http_req = http.request(http_url, { -- luacheck: ignore 211/http_req
6395
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
237 method = "GET";
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
238 headers = headers;
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
239 sslctx = ex.sslctx;
8893
eb710675f7f8 net.websocket: Honour ex.insecure to match net.http's new parameter for that
Matthew Wild <mwild1@gmail.com>
parents: 8892
diff changeset
240 insecure = ex.insecure;
6395
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
241 }, function(b, c, r, http_req)
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
242 if c ~= 101
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
243 or r.headers["connection"]:lower() ~= "upgrade"
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
244 or r.headers["upgrade"] ~= "websocket"
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
245 or r.headers["sec-websocket-accept"] ~= base64.encode(sha1(key .. "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))
6407
4bbd198cf3e6 net.websocket: Fix handling of 'protocol' argument
Kim Alvefur <zash@zash.se>
parents: 6398
diff changeset
246 or (protocol and not protocol[r.headers["sec-websocket-protocol"]])
6395
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
247 then
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
248 s.readyState = 3;
10112
b327f2870382 net.*: Remove tostring call from logging
Kim Alvefur <zash@zash.se>
parents: 8893
diff changeset
249 log("warn", "WebSocket connection to %s failed: %s", url, b);
6395
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
250 if s.onerror then s:onerror("connecting-failed"); end
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
251 return;
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
252 end
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
253
6395
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
254 s.protocol = r.headers["sec-websocket-protocol"];
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
255
6395
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
256 -- Take possession of socket from http
8892
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8189
diff changeset
257 local conn = http_req.conn;
6395
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
258 http_req.conn = nil;
8892
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8189
diff changeset
259 s.conn = conn;
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8189
diff changeset
260 websockets[conn] = s;
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8189
diff changeset
261 conn:setlistener(websocket_listeners);
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
262
6395
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
263 log("debug", "WebSocket connected successfully to %s", url);
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
264 s.readyState = 1;
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
265 if s.onopen then s:onopen(); end
8892
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8189
diff changeset
266 websocket_listeners.onincoming(conn, b);
6395
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
267 end);
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
268
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
269 return s;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
270 end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
271
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
272 return {
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
273 connect = connect;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
274 };