websocket-driver-kontena 0.6.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,28 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ require 'eventmachine'
4
+ require 'websocket/driver'
5
+ require 'permessage_deflate'
6
+
7
+ module Connection
8
+ def initialize
9
+ @driver = WebSocket::Driver.server(self)
10
+ @driver.add_extension(PermessageDeflate)
11
+
12
+ @driver.on(:connect) { |e| @driver.start if WebSocket::Driver.websocket? @driver.env }
13
+ @driver.on(:message) { |e| @driver.frame(e.data) }
14
+ @driver.on(:close) { |e| close_connection_after_writing }
15
+ end
16
+
17
+ def receive_data(data)
18
+ @driver.parse(data)
19
+ end
20
+
21
+ def write(data)
22
+ send_data(data)
23
+ end
24
+ end
25
+
26
+ EM.run {
27
+ EM.start_server('127.0.0.1', ARGV[0], Connection)
28
+ }
@@ -0,0 +1,55 @@
1
+ package com.jcoglan.websocket;
2
+
3
+ import java.lang.Long;
4
+ import java.io.IOException;
5
+
6
+ import org.jruby.Ruby;
7
+ import org.jruby.RubyClass;
8
+ import org.jruby.RubyModule;
9
+ import org.jruby.RubyObject;
10
+ import org.jruby.RubyString;
11
+ import org.jruby.anno.JRubyMethod;
12
+ import org.jruby.runtime.ObjectAllocator;
13
+ import org.jruby.runtime.ThreadContext;
14
+ import org.jruby.runtime.builtin.IRubyObject;
15
+ import org.jruby.runtime.load.BasicLibraryService;
16
+
17
+ public class WebsocketMaskService implements BasicLibraryService {
18
+ private Ruby runtime;
19
+
20
+ public boolean basicLoad(Ruby runtime) throws IOException {
21
+ this.runtime = runtime;
22
+ RubyModule websocket = runtime.defineModule("WebSocket");
23
+
24
+ RubyClass webSocketMask = websocket.defineClassUnder("Mask", runtime.getObject(), new ObjectAllocator() {
25
+ public IRubyObject allocate(Ruby runtime, RubyClass rubyClass) {
26
+ return new WebsocketMask(runtime, rubyClass);
27
+ }
28
+ });
29
+
30
+ webSocketMask.defineAnnotatedMethods(WebsocketMask.class);
31
+ return true;
32
+ }
33
+
34
+ public class WebsocketMask extends RubyObject {
35
+ public WebsocketMask(final Ruby runtime, RubyClass rubyClass) {
36
+ super(runtime, rubyClass);
37
+ }
38
+
39
+ @JRubyMethod
40
+ public IRubyObject mask(ThreadContext context, IRubyObject payload, IRubyObject mask) {
41
+ if (mask.isNil()) return payload;
42
+
43
+ byte[] payload_a = ((RubyString)payload).getBytes();
44
+ byte[] mask_a = ((RubyString)mask).getBytes();
45
+ int i, n = payload_a.length;
46
+
47
+ if (n == 0) return payload;
48
+
49
+ for (i = 0; i < n; i++) {
50
+ payload_a[i] ^= mask_a[i % 4];
51
+ }
52
+ return RubyString.newStringNoCopy(runtime, payload_a);
53
+ }
54
+ }
55
+ }
@@ -0,0 +1,4 @@
1
+ require 'mkmf'
2
+ extension_name = 'websocket_mask'
3
+ dir_config(extension_name)
4
+ create_makefile(extension_name)
@@ -0,0 +1,41 @@
1
+ #include <ruby.h>
2
+
3
+ VALUE WebSocket = Qnil;
4
+ VALUE WebSocketMask = Qnil;
5
+
6
+ void Init_websocket_mask();
7
+ VALUE method_websocket_mask(VALUE self, VALUE payload, VALUE mask);
8
+
9
+ void
10
+ Init_websocket_mask()
11
+ {
12
+ WebSocket = rb_define_module("WebSocket");
13
+ WebSocketMask = rb_define_module_under(WebSocket, "Mask");
14
+ rb_define_singleton_method(WebSocketMask, "mask", method_websocket_mask, 2);
15
+ }
16
+
17
+ VALUE
18
+ method_websocket_mask(VALUE self,
19
+ VALUE payload,
20
+ VALUE mask)
21
+ {
22
+ char *payload_s, *mask_s, *unmasked_s;
23
+ long i, n;
24
+ VALUE unmasked;
25
+
26
+ if (mask == Qnil || RSTRING_LEN(mask) != 4) {
27
+ return payload;
28
+ }
29
+
30
+ payload_s = RSTRING_PTR(payload);
31
+ mask_s = RSTRING_PTR(mask);
32
+ n = RSTRING_LEN(payload);
33
+
34
+ unmasked = rb_str_new(0, n);
35
+ unmasked_s = RSTRING_PTR(unmasked);
36
+
37
+ for (i = 0; i < n; i++) {
38
+ unmasked_s[i] = payload_s[i] ^ mask_s[i % 4];
39
+ }
40
+ return unmasked;
41
+ }
@@ -0,0 +1,199 @@
1
+ # Protocol references:
2
+ #
3
+ # * http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75
4
+ # * http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
5
+ # * http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17
6
+
7
+ require 'base64'
8
+ require 'digest/md5'
9
+ require 'digest/sha1'
10
+ require 'securerandom'
11
+ require 'set'
12
+ require 'stringio'
13
+ require 'uri'
14
+ require 'websocket/extensions'
15
+
16
+ module WebSocket
17
+ autoload :HTTP, File.expand_path('../http', __FILE__)
18
+
19
+ class Driver
20
+
21
+ root = File.expand_path('../driver', __FILE__)
22
+
23
+ begin
24
+ # Load C native extension
25
+ require 'websocket_mask'
26
+ rescue LoadError
27
+ # Fall back to pure-Ruby implementation
28
+ require 'websocket/mask'
29
+ end
30
+
31
+
32
+ if RUBY_PLATFORM =~ /java/
33
+ require 'jruby'
34
+ com.jcoglan.websocket.WebsocketMaskService.new.basicLoad(JRuby.runtime)
35
+ end
36
+
37
+ unless Mask.respond_to?(:mask)
38
+ def Mask.mask(payload, mask)
39
+ @instance ||= new
40
+ @instance.mask(payload, mask)
41
+ end
42
+ end
43
+
44
+ MAX_LENGTH = 0x3ffffff
45
+ STATES = [:connecting, :open, :closing, :closed]
46
+
47
+ BINARY = 'ASCII-8BIT'
48
+ UNICODE = 'UTF-8'
49
+
50
+ ConnectEvent = Struct.new(nil)
51
+ OpenEvent = Struct.new(nil)
52
+ MessageEvent = Struct.new(:data)
53
+ CloseEvent = Struct.new(:code, :reason)
54
+
55
+ ProtocolError = Class.new(StandardError)
56
+ URIError = Class.new(ArgumentError)
57
+ ConfigurationError = Class.new(ArgumentError)
58
+
59
+ autoload :Client, root + '/client'
60
+ autoload :Draft75, root + '/draft75'
61
+ autoload :Draft76, root + '/draft76'
62
+ autoload :EventEmitter, root + '/event_emitter'
63
+ autoload :Headers, root + '/headers'
64
+ autoload :Hybi, root + '/hybi'
65
+ autoload :Proxy, root + '/proxy'
66
+ autoload :Server, root + '/server'
67
+ autoload :StreamReader, root + '/stream_reader'
68
+
69
+ include EventEmitter
70
+ attr_reader :protocol, :ready_state
71
+
72
+ def initialize(socket, options = {})
73
+ super()
74
+ Driver.validate_options(options, [:max_length, :masking, :require_masking, :protocols])
75
+
76
+ @socket = socket
77
+ @reader = StreamReader.new
78
+ @options = options
79
+ @max_length = options[:max_length] || MAX_LENGTH
80
+ @headers = Headers.new
81
+ @queue = []
82
+ @ready_state = 0
83
+ end
84
+
85
+ def state
86
+ return nil unless @ready_state >= 0
87
+ STATES[@ready_state]
88
+ end
89
+
90
+ def add_extension(extension)
91
+ false
92
+ end
93
+
94
+ def set_header(name, value)
95
+ return false unless @ready_state <= 0
96
+ @headers[name] = value
97
+ true
98
+ end
99
+
100
+ def start
101
+ return false unless @ready_state == 0
102
+ response = handshake_response
103
+ return false unless response
104
+ @socket.write(response)
105
+ open unless @stage == -1
106
+ true
107
+ end
108
+
109
+ def text(message)
110
+ message = message.encode(UNICODE) unless message.encoding.name == UNICODE
111
+ frame(message, :text)
112
+ end
113
+
114
+ def binary(message)
115
+ false
116
+ end
117
+
118
+ def ping(*args)
119
+ false
120
+ end
121
+
122
+ def pong(*args)
123
+ false
124
+ end
125
+
126
+ def close(reason = nil, code = nil)
127
+ return false unless @ready_state == 1
128
+ @ready_state = 3
129
+ emit(:close, CloseEvent.new(nil, nil))
130
+ true
131
+ end
132
+
133
+ private
134
+
135
+ def open
136
+ @ready_state = 1
137
+ @queue.each { |message| frame(*message) }
138
+ @queue = []
139
+ emit(:open, OpenEvent.new)
140
+ end
141
+
142
+ def queue(message)
143
+ @queue << message
144
+ true
145
+ end
146
+
147
+ def self.client(socket, options = {})
148
+ Client.new(socket, options.merge(:masking => true))
149
+ end
150
+
151
+ def self.server(socket, options = {})
152
+ Server.new(socket, options.merge(:require_masking => true))
153
+ end
154
+
155
+ def self.rack(socket, options = {})
156
+ env = socket.env
157
+ if env['HTTP_SEC_WEBSOCKET_VERSION']
158
+ Hybi.new(socket, options.merge(:require_masking => true))
159
+ elsif env['HTTP_SEC_WEBSOCKET_KEY1']
160
+ Draft76.new(socket, options)
161
+ else
162
+ Draft75.new(socket, options)
163
+ end
164
+ end
165
+
166
+ def self.encode(string, encoding = nil)
167
+ case string
168
+ when Array then
169
+ string = string.pack('C*')
170
+ encoding ||= BINARY
171
+ when String then
172
+ encoding ||= UNICODE
173
+ end
174
+ unless string.encoding.name == encoding
175
+ string = string.dup if string.frozen?
176
+ string.force_encoding(encoding)
177
+ end
178
+ string.valid_encoding? ? string : nil
179
+ end
180
+
181
+ def self.validate_options(options, valid_keys)
182
+ options.keys.each do |key|
183
+ unless valid_keys.include?(key)
184
+ raise ConfigurationError, "Unrecognized option: #{key.inspect}"
185
+ end
186
+ end
187
+ end
188
+
189
+ def self.websocket?(env)
190
+ connection = env['HTTP_CONNECTION'] || ''
191
+ upgrade = env['HTTP_UPGRADE'] || ''
192
+
193
+ env['REQUEST_METHOD'] == 'GET' and
194
+ connection.downcase.split(/ *, */).include?('upgrade') and
195
+ upgrade.downcase == 'websocket'
196
+ end
197
+
198
+ end
199
+ end
@@ -0,0 +1,140 @@
1
+ module WebSocket
2
+ class Driver
3
+
4
+ class Client < Hybi
5
+ VALID_SCHEMES = %w[ws wss]
6
+
7
+ def self.generate_key
8
+ Base64.strict_encode64(SecureRandom.random_bytes(16))
9
+ end
10
+
11
+ attr_reader :status, :headers
12
+
13
+ def initialize(socket, options = {})
14
+ super
15
+
16
+ @ready_state = -1
17
+ @key = Client.generate_key
18
+ @accept = Hybi.generate_accept(@key)
19
+ @http = HTTP::Response.new
20
+
21
+ uri = URI.parse(@socket.url)
22
+ unless VALID_SCHEMES.include?(uri.scheme)
23
+ raise URIError, "#{socket.url} is not a valid WebSocket URL"
24
+ end
25
+
26
+ host = uri.host + (uri.port ? ":#{uri.port}" : '')
27
+ path = (uri.path == '') ? '/' : uri.path
28
+ @pathname = path + (uri.query ? '?' + uri.query : '')
29
+
30
+ @headers['Host'] = host
31
+ @headers['Upgrade'] = 'websocket'
32
+ @headers['Connection'] = 'Upgrade'
33
+ @headers['Sec-WebSocket-Key'] = @key
34
+ @headers['Sec-WebSocket-Version'] = '13'
35
+
36
+ if @protocols.size > 0
37
+ @headers['Sec-WebSocket-Protocol'] = @protocols * ', '
38
+ end
39
+
40
+ if uri.user
41
+ auth = Base64.strict_encode64([uri.user, uri.password] * ':')
42
+ @headers['Authorization'] = 'Basic ' + auth
43
+ end
44
+ end
45
+
46
+ def version
47
+ 'hybi-13'
48
+ end
49
+
50
+ def proxy(origin, options = {})
51
+ Proxy.new(self, origin, options)
52
+ end
53
+
54
+ def start
55
+ return false unless @ready_state == -1
56
+ @socket.write(handshake_request)
57
+ @ready_state = 0
58
+ true
59
+ end
60
+
61
+ def parse(chunk)
62
+ return if @ready_state == 3
63
+ return super if @ready_state > 0
64
+
65
+ @http.parse(chunk)
66
+ return fail_handshake('Invalid HTTP response') if @http.error?
67
+ return unless @http.complete?
68
+
69
+ validate_handshake
70
+ return if @ready_state == 3
71
+
72
+ open
73
+ parse(@http.body)
74
+ end
75
+
76
+ private
77
+
78
+ def handshake_request
79
+ extensions = @extensions.generate_offer
80
+ @headers['Sec-WebSocket-Extensions'] = extensions if extensions
81
+
82
+ start = "GET #{@pathname} HTTP/1.1"
83
+ headers = [start, @headers.to_s, '']
84
+ headers.join("\r\n")
85
+ end
86
+
87
+ def fail_handshake(message)
88
+ message = "Error during WebSocket handshake: #{message}"
89
+ @ready_state = 3
90
+ emit(:error, ProtocolError.new(message))
91
+ emit(:close, CloseEvent.new(ERRORS[:protocol_error], message))
92
+ end
93
+
94
+ def validate_handshake
95
+ @status = @http.code
96
+ @headers = Headers.new(@http.headers)
97
+
98
+ unless @http.code == 101
99
+ return fail_handshake("Unexpected response code: #{@http.code}")
100
+ end
101
+
102
+ upgrade = @http['Upgrade'] || ''
103
+ connection = @http['Connection'] || ''
104
+ accept = @http['Sec-WebSocket-Accept'] || ''
105
+ protocol = @http['Sec-WebSocket-Protocol'] || ''
106
+
107
+ if upgrade == ''
108
+ return fail_handshake("'Upgrade' header is missing")
109
+ elsif upgrade.downcase != 'websocket'
110
+ return fail_handshake("'Upgrade' header value is not 'WebSocket'")
111
+ end
112
+
113
+ if connection == ''
114
+ return fail_handshake("'Connection' header is missing")
115
+ elsif connection.downcase != 'upgrade'
116
+ return fail_handshake("'Connection' header value is not 'Upgrade'")
117
+ end
118
+
119
+ unless accept == @accept
120
+ return fail_handshake('Sec-WebSocket-Accept mismatch')
121
+ end
122
+
123
+ unless protocol == ''
124
+ if @protocols.include?(protocol)
125
+ @protocol = protocol
126
+ else
127
+ return fail_handshake('Sec-WebSocket-Protocol mismatch')
128
+ end
129
+ end
130
+
131
+ begin
132
+ @extensions.activate(@headers['Sec-WebSocket-Extensions'])
133
+ rescue ::WebSocket::Extensions::ExtensionError => error
134
+ return fail_handshake(error.message)
135
+ end
136
+ end
137
+ end
138
+
139
+ end
140
+ end