net/websocket.lua
author Kim Alvefur <zash@zash.se>
Fri, 21 Sep 2018 21:19:44 +0200
changeset 9339 9e8d7d461c7d
parent 8896 eb710675f7f8
child 10116 b327f2870382
permissions -rw-r--r--
mod_http: Hook the host-less event if hooked from a global module
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
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
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    11
local http = require "net.http";
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    12
local frames = require "net.websocket.frames";
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    13
local base64 = require "util.encodings".base64;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    14
local sha1 = require "util.hashes".sha1;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    15
local random_bytes = require "util.random".bytes;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    16
local timer = require "util.timer";
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    17
local log = require "util.logger".init "websocket";
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 = {};
8895
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
    24
function websocket_listeners.ondisconnect(conn, err)
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
    25
	local s = websockets[conn];
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
    26
	websockets[conn] = nil;
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    27
	if s.close_timer then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    28
		timer.stop(s.close_timer);
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    29
		s.close_timer = nil;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    30
	end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    31
	s.readyState = 3;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    32
	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
    33
	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
    34
end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    35
8895
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
    36
function websocket_listeners.ondetach(conn)
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
    37
	websockets[conn] = nil;
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    38
end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    39
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    40
local function fail(s, code, reason)
8192
a3565d7dd304 net.websocket: Remove stray module api reference, shouldn't be used in here
Kim Alvefur <zash@zash.se>
parents: 7762
diff changeset
    41
	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
    42
	s:close(code, reason);
8895
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
    43
	s.conn:close();
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    44
	return false
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    45
end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    46
8895
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
    47
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: 8192
diff changeset
    48
	local s = websockets[conn];
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    49
	s.readbuffer = s.readbuffer..buffer;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    50
	while true do
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    51
		local frame, len = frames.parse(s.readbuffer);
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    52
		if frame == nil then break end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    53
		s.readbuffer = s.readbuffer:sub(len+1);
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    54
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    55
		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
    56
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    57
		-- Error cases
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    58
		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
    59
			return fail(s, 1002, "Reserved bits not zero");
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    60
		end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    61
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    62
		if frame.opcode < 0x8 then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    63
			local databuffer = s.databuffer;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    64
			if frame.opcode == 0x0 then -- Continuation frames
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    65
				if not databuffer then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    66
					return fail(s, 1002, "Unexpected continuation frame");
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    67
				end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    68
				databuffer[#databuffer+1] = frame.data;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    69
			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
    70
				if databuffer then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    71
					return fail(s, 1002, "Continuation frame expected");
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    72
				end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    73
				databuffer = {type=frame.opcode, frame.data};
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    74
				s.databuffer = databuffer;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    75
			else
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    76
				return fail(s, 1002, "Reserved opcode");
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    77
			end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    78
			if frame.FIN then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    79
				s.databuffer = nil;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    80
				if s.onmessage then
6396
17b54f523796 Check Sec-WebSocket-Protocol header
Florian Zeitz <florob@babelmonkeys.de>
parents: 6395
diff changeset
    81
					s:onmessage(t_concat(databuffer), databuffer.type);
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    82
				end
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
		else -- Control frame
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    85
			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
    86
				return fail(s, 1002, "Payload too large");
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    87
			elseif not frame.FIN then -- Fragmented control frame
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    88
				return fail(s, 1002, "Fragmented control frame");
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    89
			end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    90
			if frame.opcode == 0x8 then -- Close request
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    91
				if frame.length == 1 then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    92
					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
    93
				end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    94
				local status_code, message = frames.parse_close(frame.data);
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    95
				if status_code == nil then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    96
					--[[ RFC 6455 7.4.1
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
    97
					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
    98
					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
    99
					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
   100
					code was actually present.
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   101
					]]
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   102
					status_code = 1005
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   103
				elseif status_code < 1000 then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   104
					return fail(s, 1002, "Closed with invalid status code");
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   105
				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
   106
					return fail(s, 1002, "Closed with reserved status code");
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   107
				end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   108
				s.close_code, s.close_message = status_code, message;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   109
				s:close(1000);
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   110
				return true;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   111
			elseif frame.opcode == 0x9 then -- Ping frame
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   112
				frame.opcode = 0xA;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   113
				frame.MASK = true; -- RFC 6455 6.1.5: If the data is being sent by the client, the frame(s) MUST be masked
8895
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
   114
				conn:write(frames.build(frame));
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   115
			elseif frame.opcode == 0xA then -- Pong frame
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   116
				log("debug", "Received unexpected pong frame: " .. tostring(frame.data));
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   117
			else
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   118
				return fail(s, 1002, "Reserved opcode");
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   119
			end
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
	return true;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   123
end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   124
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   125
local websocket_methods = {};
7762
69706084bdfe net.websocket: Ignore unused argument warnings [luacheck]
Kim Alvefur <zash@zash.se>
parents: 6458
diff changeset
   126
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
   127
	s.close_timer = nil;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   128
	log("warn", "Close timeout waiting for server to close, closing manually.");
8895
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
   129
	s.conn:close();
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   130
end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   131
function websocket_methods:close(code, reason)
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   132
	if self.readyState < 2 then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   133
		code = code or 1000;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   134
		log("debug", "closing WebSocket with code %i: %s" , code , tostring(reason));
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   135
		self.readyState = 2;
8895
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
   136
		local conn = self.conn;
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
   137
		conn:write(frames.build_close(code, reason, true));
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   138
		-- 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
   139
		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
   140
	elseif self.readyState == 2 then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   141
		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
   142
		-- Stop timer
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   143
		if self.close_timer then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   144
			timer.stop(self.close_timer);
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   145
			self.close_timer = nil;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   146
		end
8895
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
   147
		local conn = self.conn;
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
   148
		conn:close();
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   149
	else
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   150
		log("debug", "tried to close a closed WebSocket, ignoring.");
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   151
	end
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
function websocket_methods:send(data, opcode)
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   154
	if self.readyState < 1 then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   155
		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
   156
	elseif self.readyState >= 2 then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   157
		return nil, "WebSocket closed, unable to send data.";
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   158
	end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   159
	if opcode == "text" or opcode == nil then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   160
		opcode = 0x1;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   161
	elseif opcode == "binary" then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   162
		opcode = 0x2;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   163
	end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   164
	local frame = {
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   165
		FIN = true;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   166
		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
   167
		opcode = opcode;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   168
		data = tostring(data);
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   169
	};
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   170
	log("debug", "WebSocket sending frame: opcode=%0x, %i bytes", frame.opcode, #frame.data);
8895
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
   171
	return self.conn:write(frames.build(frame));
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   172
end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   173
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   174
local websocket_metatable = {
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   175
	__index = websocket_methods;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   176
};
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
local function connect(url, ex, listeners)
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   179
	ex = ex or {};
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   180
6395
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   181
	--[[RFC 6455 4.1.7:
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   182
		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
   183
	|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
   184
	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
   185
	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
   186
	MUST be selected randomly for each connection.
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   187
	]]
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   188
	local key = base64.encode(random_bytes(16));
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   189
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   190
	-- 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
   191
	local protocol = ex.protocol;
6396
17b54f523796 Check Sec-WebSocket-Protocol header
Florian Zeitz <florob@babelmonkeys.de>
parents: 6395
diff changeset
   192
	if type(protocol) == "string" then
6410
4bbd198cf3e6 net.websocket: Fix handling of 'protocol' argument
Kim Alvefur <zash@zash.se>
parents: 6398
diff changeset
   193
		protocol = { protocol, [protocol] = true };
4bbd198cf3e6 net.websocket: Fix handling of 'protocol' argument
Kim Alvefur <zash@zash.se>
parents: 6398
diff changeset
   194
	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
   195
		for _, v in ipairs(protocol) do
4bbd198cf3e6 net.websocket: Fix handling of 'protocol' argument
Kim Alvefur <zash@zash.se>
parents: 6398
diff changeset
   196
			protocol[v] = true;
4bbd198cf3e6 net.websocket: Fix handling of 'protocol' argument
Kim Alvefur <zash@zash.se>
parents: 6398
diff changeset
   197
		end
4bbd198cf3e6 net.websocket: Fix handling of 'protocol' argument
Kim Alvefur <zash@zash.se>
parents: 6398
diff changeset
   198
	else
4bbd198cf3e6 net.websocket: Fix handling of 'protocol' argument
Kim Alvefur <zash@zash.se>
parents: 6398
diff changeset
   199
		protocol = nil;
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   200
	end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   201
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   202
	local headers = {
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   203
		["Upgrade"] = "websocket";
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   204
		["Connection"] = "Upgrade";
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   205
		["Sec-WebSocket-Key"] = key;
6410
4bbd198cf3e6 net.websocket: Fix handling of 'protocol' argument
Kim Alvefur <zash@zash.se>
parents: 6398
diff changeset
   206
		["Sec-WebSocket-Protocol"] = protocol and t_concat(protocol, ", ");
6395
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   207
		["Sec-WebSocket-Version"] = "13";
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   208
		["Sec-WebSocket-Extensions"] = ex.extensions;
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   209
	}
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   210
	if ex.headers then
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   211
		for k,v in pairs(ex.headers) do
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   212
			headers[k] = v;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   213
		end
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
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   216
	local s = setmetatable({
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   217
		readbuffer = "";
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   218
		databuffer = nil;
8895
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
   219
		conn = nil;
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   220
		close_code = nil;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   221
		close_message = nil;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   222
		close_timer = nil;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   223
		readyState = 0;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   224
		protocol = nil;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   225
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   226
		url = url;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   227
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   228
		onopen = listeners.onopen;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   229
		onclose = listeners.onclose;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   230
		onmessage = listeners.onmessage;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   231
		onerror = listeners.onerror;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   232
	}, websocket_metatable);
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   233
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   234
	local http_url = url:gsub("^(ws)", "http");
7762
69706084bdfe net.websocket: Ignore unused argument warnings [luacheck]
Kim Alvefur <zash@zash.se>
parents: 6458
diff changeset
   235
	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
   236
		method = "GET";
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   237
		headers = headers;
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   238
		sslctx = ex.sslctx;
8896
eb710675f7f8 net.websocket: Honour ex.insecure to match net.http's new parameter for that
Matthew Wild <mwild1@gmail.com>
parents: 8895
diff changeset
   239
		insecure = ex.insecure;
6395
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   240
	}, function(b, c, r, http_req)
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   241
		if c ~= 101
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   242
		   or r.headers["connection"]:lower() ~= "upgrade"
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   243
		   or r.headers["upgrade"] ~= "websocket"
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   244
		   or r.headers["sec-websocket-accept"] ~= base64.encode(sha1(key .. "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))
6410
4bbd198cf3e6 net.websocket: Fix handling of 'protocol' argument
Kim Alvefur <zash@zash.se>
parents: 6398
diff changeset
   245
		   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
   246
		   then
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   247
			s.readyState = 3;
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   248
			log("warn", "WebSocket connection to %s failed: %s", url, tostring(b));
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   249
			if s.onerror then s:onerror("connecting-failed"); end
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   250
			return;
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   251
		end
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   252
6395
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   253
		s.protocol = r.headers["sec-websocket-protocol"];
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   254
6395
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   255
		-- Take possession of socket from http
8895
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
   256
		local conn = http_req.conn;
6395
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   257
		http_req.conn = nil;
8895
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
   258
		s.conn = conn;
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
   259
		websockets[conn] = s;
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
   260
		conn:setlistener(websocket_listeners);
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   261
6395
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   262
		log("debug", "WebSocket connected successfully to %s", url);
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   263
		s.readyState = 1;
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   264
		if s.onopen then s:onopen(); end
8895
fb38b2c77a72 net.websocket: Fix incompatibility with net.http changes
Matthew Wild <mwild1@gmail.com>
parents: 8192
diff changeset
   265
		websocket_listeners.onincoming(conn, b);
6395
e0164b0fcafd net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   266
	end);
6389
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   267
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   268
	return s;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   269
end
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   270
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   271
return {
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   272
	connect = connect;
8eccd07b619c net/websocket: Add new websocket client code
daurnimator <quae@daurnimator.com>
parents:
diff changeset
   273
};