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