Software /
code /
prosody
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 |
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 }; |