websocket-driver-kontena 0.6.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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