breakout 0.0.1
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.
- data/.gitignore +7 -0
- data/Gemfile +4 -0
- data/README.md +31 -0
- data/Rakefile +53 -0
- data/_LICENSE +20 -0
- data/breakout.gemspec +21 -0
- data/examples/chat.rb +27 -0
- data/examples/echo.rb +10 -0
- data/examples/ping.rb +9 -0
- data/examples/public/javascripts/chat.js +35 -0
- data/examples/public/javascripts/echo.js +50 -0
- data/examples/public/javascripts/jquery-1.4.2.min.js +154 -0
- data/examples/public/javascripts/ping.js +34 -0
- data/examples/web_app.rb +72 -0
- data/examples/worker_app.rb +13 -0
- data/lib/breakout.rb +13 -0
- data/lib/breakout/api.rb +36 -0
- data/lib/breakout/app.rb +41 -0
- data/lib/breakout/config.rb +50 -0
- data/lib/breakout/railtie.rb +9 -0
- data/lib/breakout/socket.rb +25 -0
- data/lib/breakout/version.rb +4 -0
- data/lib/breakout/web_socket.rb +295 -0
- data/lib/breakout/worker.rb +20 -0
- data/spec/breakout_spec.rb +7 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +9 -0
- metadata +74 -0
data/examples/web_app.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'sinatra'
|
5
|
+
|
6
|
+
#require 'breakout'
|
7
|
+
require File.expand_path("../../lib/breakout", __FILE__)
|
8
|
+
Breakout.load_or_create_config_file('breakout.yml')
|
9
|
+
|
10
|
+
layout = <<-APP
|
11
|
+
<!DOCTYPE html>
|
12
|
+
<html>
|
13
|
+
<head>
|
14
|
+
<title>Example</title>
|
15
|
+
</head>
|
16
|
+
<body>
|
17
|
+
%s
|
18
|
+
</body>
|
19
|
+
</html>
|
20
|
+
APP
|
21
|
+
|
22
|
+
get '/' do
|
23
|
+
layout % <<-HTML
|
24
|
+
<h1><a href="/echo">Echo</a></h1>
|
25
|
+
<h1><a href="/ping">Ping</a></h1>
|
26
|
+
<h1><a href="/chat">Chat</a></h1>
|
27
|
+
HTML
|
28
|
+
end
|
29
|
+
|
30
|
+
get '/echo' do
|
31
|
+
layout % <<-SCRIPT
|
32
|
+
<script type='text/javascript' src='/javascripts/jquery-1.4.2.min.js'></script>
|
33
|
+
<script type='text/javascript'>
|
34
|
+
var echo_url = '#{Breakout.browser_url('echo')}';
|
35
|
+
jQuery.getScript("/javascripts/echo.js");
|
36
|
+
</script>
|
37
|
+
SCRIPT
|
38
|
+
end
|
39
|
+
|
40
|
+
get '/ping' do
|
41
|
+
layout % <<-SCRIPT
|
42
|
+
<script type='text/javascript' src='/javascripts/jquery-1.4.2.min.js'></script>
|
43
|
+
<script type='text/javascript'>
|
44
|
+
jQuery.getScript("/javascripts/ping.js", function() { new Ping('#{Breakout.browser_url('ping')}'); });
|
45
|
+
</script>
|
46
|
+
SCRIPT
|
47
|
+
end
|
48
|
+
|
49
|
+
get '/chat' do
|
50
|
+
layout % <<-SCRIPT
|
51
|
+
<div>
|
52
|
+
<h2>Name</h2>
|
53
|
+
<form action="/chat" method="post">
|
54
|
+
<input type="text" name="name" />
|
55
|
+
<input type="submit" name="submit" />
|
56
|
+
</form>
|
57
|
+
</div>
|
58
|
+
SCRIPT
|
59
|
+
end
|
60
|
+
|
61
|
+
post '/chat' do
|
62
|
+
halt 400 unless name = params[:name]
|
63
|
+
chat_url = Breakout.browser_url('chat', :bid => name, :notify => true)
|
64
|
+
layout % <<-SCRIPT
|
65
|
+
<script type='text/javascript' src='/javascripts/jquery-1.4.2.min.js'></script>
|
66
|
+
<script type='text/javascript'>
|
67
|
+
var chat_url = '#{chat_url}';
|
68
|
+
jQuery.getScript("/javascripts/chat.js");
|
69
|
+
</script>
|
70
|
+
SCRIPT
|
71
|
+
end
|
72
|
+
|
@@ -0,0 +1,13 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
#require 'breakout'
|
4
|
+
require File.expand_path("../../lib/breakout", __FILE__)
|
5
|
+
|
6
|
+
Breakout.load_or_create_config_file('breakout.yml')
|
7
|
+
|
8
|
+
['ping', 'echo', 'chat'].each do |worker|
|
9
|
+
require File.expand_path("../#{worker}", __FILE__)
|
10
|
+
end
|
11
|
+
|
12
|
+
Breakout::Worker::App.new.run()
|
13
|
+
|
data/lib/breakout.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
dir = File.dirname(__FILE__)
|
2
|
+
$LOAD_PATH.unshift dir unless $LOAD_PATH.include?(dir)
|
3
|
+
|
4
|
+
require 'json'
|
5
|
+
require 'yaml'
|
6
|
+
require 'breakout/web_socket'
|
7
|
+
|
8
|
+
require 'breakout/api'
|
9
|
+
require 'breakout/app'
|
10
|
+
require 'breakout/config'
|
11
|
+
require 'lib/breakout/railtie' if defined?(Rails)
|
12
|
+
require 'breakout/socket'
|
13
|
+
require 'breakout/worker'
|
data/lib/breakout/api.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
module Breakout
|
2
|
+
module Worker
|
3
|
+
module API
|
4
|
+
|
5
|
+
# The breakout server API for workers
|
6
|
+
|
7
|
+
# commands are symbols :send_messages, :disconnect, :done_work
|
8
|
+
# commands are sent to breakout server as a hash :command => args
|
9
|
+
# args are
|
10
|
+
# :done_work => requeue? #true or false whether to put worker back on work queue
|
11
|
+
# :send_messages => { message => [bid1, bid2, ...] }
|
12
|
+
# :disconnect => bid
|
13
|
+
#
|
14
|
+
# for messages incoming from the server (in worker.do_work) the notify api
|
15
|
+
# (if the browser url included notify=true)
|
16
|
+
# when the browser socket opens, the message will be "/open"
|
17
|
+
# when the browser socket closes, the mesage will be "/close"
|
18
|
+
|
19
|
+
def disconnect(bid)
|
20
|
+
socket.send :disconnect => bid
|
21
|
+
end
|
22
|
+
|
23
|
+
def send_messages(args)
|
24
|
+
raise Exception unless args.is_a?(Hash)
|
25
|
+
socket.send :send_messages => args
|
26
|
+
end
|
27
|
+
|
28
|
+
alias send_message send_messages
|
29
|
+
|
30
|
+
def done_work(requeue=true)
|
31
|
+
socket.send :done_work => requeue
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/breakout/app.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
module Breakout
|
2
|
+
module Worker
|
3
|
+
class App
|
4
|
+
|
5
|
+
include API
|
6
|
+
attr_accessor :worker_by_route, :socket, :stop
|
7
|
+
|
8
|
+
def dispatch(data)
|
9
|
+
route, bid, message = data.split("\n", 3)
|
10
|
+
raise "#{route}\n#{bid}\n#{message}" unless worker = worker_by_route[route]
|
11
|
+
worker.do_work(bid, message)
|
12
|
+
end
|
13
|
+
|
14
|
+
def run(url=nil)
|
15
|
+
unless url
|
16
|
+
url = Breakout.worker_url
|
17
|
+
end
|
18
|
+
|
19
|
+
self.socket = Socket.new(url)
|
20
|
+
self.worker_by_route = Hash.new
|
21
|
+
Worker::WORKERS.each do |klass|
|
22
|
+
worker = klass.new
|
23
|
+
worker.socket = socket
|
24
|
+
worker_by_route[klass.route] = worker
|
25
|
+
end
|
26
|
+
|
27
|
+
done_work
|
28
|
+
while data = socket.receive() do
|
29
|
+
dispatch(data)
|
30
|
+
if @stop
|
31
|
+
done_work false
|
32
|
+
break
|
33
|
+
end
|
34
|
+
done_work
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'digest'
|
2
|
+
|
3
|
+
module Breakout
|
4
|
+
|
5
|
+
CONFIG = {}
|
6
|
+
|
7
|
+
def self.random_config
|
8
|
+
{
|
9
|
+
:breakout_host => 'localhost',
|
10
|
+
:worker_port => 9001,
|
11
|
+
:browser_port => 9002,
|
12
|
+
:grid => (Digest::SHA2.new << rand.to_s).to_s[0..4],
|
13
|
+
:grid_key => (Digest::SHA2.new << rand.to_s).to_s[0..30]
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.random_bid
|
18
|
+
(Digest::SHA2.new << rand.to_s).to_s[0..10]
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.config(opts={})
|
22
|
+
CONFIG.merge!(opts)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.grid_access_token(route, bid, e, notify, grid_key=nil)
|
26
|
+
grid_key ||= CONFIG[:grid_key]
|
27
|
+
(Digest::SHA2.new << "#{grid_key}#{route}#{bid}#{e}#{notify}").to_s
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.worker_url()
|
31
|
+
"ws://#{CONFIG[:breakout_host]}:#{CONFIG[:worker_port]}/#{CONFIG[:grid]}?grid_key=#{CONFIG[:grid_key]}"
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.browser_url(route, opts={})
|
35
|
+
bid = opts[:bid] || random_bid
|
36
|
+
e = opts[:e] || (Time.now + 3).to_i
|
37
|
+
notify = opts[:notify] || false
|
38
|
+
gat = grid_access_token(route, bid, e, notify)
|
39
|
+
"ws://#{CONFIG[:breakout_host]}:#{CONFIG[:browser_port]}/#{CONFIG[:grid]}?route=#{route}&bid=#{bid}&e=#{e}¬ify=#{notify}&gat=#{gat}"
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.load_or_create_config_file(config_filename)
|
43
|
+
unless File.exist?(config_filename)
|
44
|
+
File.open(config_filename, 'w') do |f|
|
45
|
+
f.write random_config.to_yaml
|
46
|
+
end
|
47
|
+
end
|
48
|
+
config(YAML.load(File.open(config_filename, 'r')))
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Breakout
|
2
|
+
class Socket < WebSocket
|
3
|
+
def initialize(url)
|
4
|
+
#WebSocket.debug = true
|
5
|
+
@ws = WebSocket.new(url)
|
6
|
+
end
|
7
|
+
|
8
|
+
def close
|
9
|
+
@ws.close
|
10
|
+
end
|
11
|
+
|
12
|
+
def receive
|
13
|
+
@ws.receive
|
14
|
+
end
|
15
|
+
|
16
|
+
def send(msg)
|
17
|
+
if msg.is_a?(String)
|
18
|
+
@ws.send(msg)
|
19
|
+
else
|
20
|
+
@ws.send(msg.to_json)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,295 @@
|
|
1
|
+
# Adapted from https://github.com/gimite/web-socket-ruby/blob/abbbe0b674cb71fb7658b2792b4ccc5d98e6f0cd/lib/web_socket.rb
|
2
|
+
|
3
|
+
# Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
|
4
|
+
# Lincense: New BSD Lincense
|
5
|
+
# Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol
|
6
|
+
|
7
|
+
require "socket"
|
8
|
+
require "uri"
|
9
|
+
require "digest/md5"
|
10
|
+
require "openssl"
|
11
|
+
|
12
|
+
|
13
|
+
class WebSocket
|
14
|
+
|
15
|
+
class << self
|
16
|
+
|
17
|
+
attr_accessor(:debug)
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
class Error < RuntimeError
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(arg, params = {})
|
26
|
+
if params[:server] # server
|
27
|
+
|
28
|
+
@server = params[:server]
|
29
|
+
@socket = arg
|
30
|
+
line = gets().chomp()
|
31
|
+
if !(line =~ /\AGET (\S+) HTTP\/1.1\z/n)
|
32
|
+
raise(WebSocket::Error, "invalid request: #{line}")
|
33
|
+
end
|
34
|
+
@path = $1
|
35
|
+
read_header()
|
36
|
+
if @header["sec-websocket-key1"] && @header["sec-websocket-key2"]
|
37
|
+
@key3 = read(8)
|
38
|
+
else
|
39
|
+
# Old Draft 75 protocol
|
40
|
+
@key3 = nil
|
41
|
+
end
|
42
|
+
if !@server.accepted_origin?(self.origin)
|
43
|
+
raise(WebSocket::Error,
|
44
|
+
("Unaccepted origin: %s (server.accepted_domains = %p)\n\n" +
|
45
|
+
"To accept this origin, write e.g. \n" +
|
46
|
+
" WebSocketServer.new(..., :accepted_domains => [%p]), or\n" +
|
47
|
+
" WebSocketServer.new(..., :accepted_domains => [\"*\"])\n") %
|
48
|
+
[self.origin, @server.accepted_domains, @server.origin_to_domain(self.origin)])
|
49
|
+
end
|
50
|
+
@handshaked = false
|
51
|
+
|
52
|
+
else # client
|
53
|
+
|
54
|
+
uri = arg.is_a?(String) ? URI.parse(arg) : arg
|
55
|
+
|
56
|
+
if uri.scheme == "ws"
|
57
|
+
default_port = 80
|
58
|
+
elsif uri.scheme = "wss"
|
59
|
+
default_port = 443
|
60
|
+
else
|
61
|
+
raise(WebSocket::Error, "unsupported scheme: #{uri.scheme}")
|
62
|
+
end
|
63
|
+
|
64
|
+
@path = (uri.path.empty? ? "/" : uri.path) + (uri.query ? "?" + uri.query : "")
|
65
|
+
host = uri.host + (uri.port == default_port ? "" : ":#{uri.port}")
|
66
|
+
origin = params[:origin] || "http://#{uri.host}"
|
67
|
+
key1 = generate_key()
|
68
|
+
key2 = generate_key()
|
69
|
+
key3 = generate_key3()
|
70
|
+
|
71
|
+
socket = TCPSocket.new(uri.host, uri.port || default_port)
|
72
|
+
|
73
|
+
if uri.scheme == "ws"
|
74
|
+
@socket = socket
|
75
|
+
else
|
76
|
+
@socket = ssl_handshake(socket)
|
77
|
+
end
|
78
|
+
|
79
|
+
write(
|
80
|
+
"GET #{@path} HTTP/1.1\r\n" +
|
81
|
+
"Upgrade: WebSocket\r\n" +
|
82
|
+
"Connection: Upgrade\r\n" +
|
83
|
+
"Host: #{host}\r\n" +
|
84
|
+
"Origin: #{origin}\r\n" +
|
85
|
+
"Sec-WebSocket-Key1: #{key1}\r\n" +
|
86
|
+
"Sec-WebSocket-Key2: #{key2}\r\n" +
|
87
|
+
"\r\n" +
|
88
|
+
"#{key3}")
|
89
|
+
flush()
|
90
|
+
|
91
|
+
line = gets().chomp()
|
92
|
+
raise(WebSocket::Error, "bad response: #{line}") if !(line =~ /\AHTTP\/1.1 101 /n)
|
93
|
+
read_header()
|
94
|
+
if (@header["sec-websocket-origin"] || "").downcase() != origin.downcase()
|
95
|
+
raise(WebSocket::Error,
|
96
|
+
"origin doesn't match: '#{@header["sec-websocket-origin"]}' != '#{origin}'")
|
97
|
+
end
|
98
|
+
reply_digest = read(16)
|
99
|
+
expected_digest = security_digest(key1, key2, key3)
|
100
|
+
if reply_digest != expected_digest
|
101
|
+
raise(WebSocket::Error,
|
102
|
+
"security digest doesn't match: %p != %p" % [reply_digest, expected_digest])
|
103
|
+
end
|
104
|
+
@handshaked = true
|
105
|
+
|
106
|
+
end
|
107
|
+
@received = []
|
108
|
+
@buffer = ""
|
109
|
+
@closing_started = false
|
110
|
+
end
|
111
|
+
|
112
|
+
attr_reader(:server, :header, :path)
|
113
|
+
|
114
|
+
def handshake(status = nil, header = {})
|
115
|
+
if @handshaked
|
116
|
+
raise(WebSocket::Error, "handshake has already been done")
|
117
|
+
end
|
118
|
+
status ||= "101 Web Socket Protocol Handshake"
|
119
|
+
sec_prefix = @key3 ? "Sec-" : ""
|
120
|
+
def_header = {
|
121
|
+
"#{sec_prefix}WebSocket-Origin" => self.origin,
|
122
|
+
"#{sec_prefix}WebSocket-Location" => self.location,
|
123
|
+
}
|
124
|
+
header = def_header.merge(header)
|
125
|
+
header_str = header.map(){ |k, v| "#{k}: #{v}\r\n" }.join("")
|
126
|
+
if @key3
|
127
|
+
digest = security_digest(
|
128
|
+
@header["Sec-WebSocket-Key1"], @header["Sec-WebSocket-Key2"], @key3)
|
129
|
+
else
|
130
|
+
digest = ""
|
131
|
+
end
|
132
|
+
# Note that Upgrade and Connection must appear in this order.
|
133
|
+
write(
|
134
|
+
"HTTP/1.1 #{status}\r\n" +
|
135
|
+
"Upgrade: WebSocket\r\n" +
|
136
|
+
"Connection: Upgrade\r\n" +
|
137
|
+
"#{header_str}\r\n#{digest}")
|
138
|
+
flush()
|
139
|
+
@handshaked = true
|
140
|
+
end
|
141
|
+
|
142
|
+
def send(data)
|
143
|
+
if !@handshaked
|
144
|
+
raise(WebSocket::Error, "call WebSocket\#handshake first")
|
145
|
+
end
|
146
|
+
data = force_encoding(data.dup(), "ASCII-8BIT")
|
147
|
+
write("\x00#{data}\xff")
|
148
|
+
flush()
|
149
|
+
end
|
150
|
+
|
151
|
+
def receive()
|
152
|
+
if !@handshaked
|
153
|
+
raise(WebSocket::Error, "call WebSocket\#handshake first")
|
154
|
+
end
|
155
|
+
packet = gets("\xff")
|
156
|
+
return nil if !packet
|
157
|
+
if packet =~ /\A\x00(.*)\xff\z/nm
|
158
|
+
return force_encoding($1, "UTF-8")
|
159
|
+
elsif packet == "\xff" && read(1) == "\x00" # closing
|
160
|
+
if @server
|
161
|
+
@socket.close()
|
162
|
+
else
|
163
|
+
close()
|
164
|
+
end
|
165
|
+
return nil
|
166
|
+
else
|
167
|
+
raise(WebSocket::Error, "input must be either '\\x00...\\xff' or '\\xff\\x00'")
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def tcp_socket
|
172
|
+
return @socket
|
173
|
+
end
|
174
|
+
|
175
|
+
def host
|
176
|
+
return @header["host"]
|
177
|
+
end
|
178
|
+
|
179
|
+
def origin
|
180
|
+
return @header["origin"]
|
181
|
+
end
|
182
|
+
|
183
|
+
def location
|
184
|
+
return "ws://#{self.host}#{@path}"
|
185
|
+
end
|
186
|
+
|
187
|
+
# Does closing handshake.
|
188
|
+
def close()
|
189
|
+
return if @closing_started
|
190
|
+
write("\xff\x00")
|
191
|
+
@socket.close() if !@server
|
192
|
+
@closing_started = true
|
193
|
+
end
|
194
|
+
|
195
|
+
def close_socket()
|
196
|
+
@socket.close()
|
197
|
+
end
|
198
|
+
|
199
|
+
private
|
200
|
+
|
201
|
+
NOISE_CHARS = ("\x21".."\x2f").to_a() + ("\x3a".."\x7e").to_a()
|
202
|
+
|
203
|
+
def read_header()
|
204
|
+
@header = {}
|
205
|
+
while line = gets()
|
206
|
+
line = line.chomp()
|
207
|
+
break if line.empty?
|
208
|
+
if !(line =~ /\A(\S+): (.*)\z/n)
|
209
|
+
raise(WebSocket::Error, "invalid request: #{line}")
|
210
|
+
end
|
211
|
+
@header[$1] = $2
|
212
|
+
@header[$1.downcase()] = $2
|
213
|
+
end
|
214
|
+
if !(@header["upgrade"] =~ /\AWebSocket\z/i)
|
215
|
+
raise(WebSocket::Error, "invalid Upgrade: " + @header["upgrade"])
|
216
|
+
end
|
217
|
+
if !(@header["connection"] =~ /\AUpgrade\z/i)
|
218
|
+
raise(WebSocket::Error, "invalid Connection: " + @header["connection"])
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def gets(rs = $/)
|
223
|
+
line = @socket.gets(rs)
|
224
|
+
$stderr.printf("recv> %p\n", line) if WebSocket.debug
|
225
|
+
return line
|
226
|
+
end
|
227
|
+
|
228
|
+
def read(num_bytes)
|
229
|
+
str = @socket.read(num_bytes)
|
230
|
+
$stderr.printf("recv> %p\n", str) if WebSocket.debug
|
231
|
+
return str
|
232
|
+
end
|
233
|
+
|
234
|
+
def write(data)
|
235
|
+
if WebSocket.debug
|
236
|
+
data.scan(/\G(.*?(\n|\z))/n) do
|
237
|
+
$stderr.printf("send> %p\n", $&) if !$&.empty?
|
238
|
+
end
|
239
|
+
end
|
240
|
+
@socket.write(data)
|
241
|
+
end
|
242
|
+
|
243
|
+
def flush()
|
244
|
+
@socket.flush()
|
245
|
+
end
|
246
|
+
|
247
|
+
def security_digest(key1, key2, key3)
|
248
|
+
bytes1 = websocket_key_to_bytes(key1)
|
249
|
+
bytes2 = websocket_key_to_bytes(key2)
|
250
|
+
return Digest::MD5.digest(bytes1 + bytes2 + key3)
|
251
|
+
end
|
252
|
+
|
253
|
+
def generate_key()
|
254
|
+
spaces = 1 + rand(12)
|
255
|
+
max = 0xffffffff / spaces
|
256
|
+
number = rand(max + 1)
|
257
|
+
key = (number * spaces).to_s()
|
258
|
+
(1 + rand(12)).times() do
|
259
|
+
char = NOISE_CHARS[rand(NOISE_CHARS.size)]
|
260
|
+
pos = rand(key.size + 1)
|
261
|
+
key[pos...pos] = char
|
262
|
+
end
|
263
|
+
spaces.times() do
|
264
|
+
pos = 1 + rand(key.size - 1)
|
265
|
+
key[pos...pos] = " "
|
266
|
+
end
|
267
|
+
return key
|
268
|
+
end
|
269
|
+
|
270
|
+
def generate_key3()
|
271
|
+
return [rand(0x100000000)].pack("N") + [rand(0x100000000)].pack("N")
|
272
|
+
end
|
273
|
+
|
274
|
+
def websocket_key_to_bytes(key)
|
275
|
+
num = key.gsub(/[^\d]/n, "").to_i() / key.scan(/ /).size
|
276
|
+
return [num].pack("N")
|
277
|
+
end
|
278
|
+
|
279
|
+
def force_encoding(str, encoding)
|
280
|
+
if str.respond_to?(:force_encoding)
|
281
|
+
return str.force_encoding(encoding)
|
282
|
+
else
|
283
|
+
return str
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
def ssl_handshake(socket)
|
288
|
+
ssl_context = OpenSSL::SSL::SSLContext.new()
|
289
|
+
ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, ssl_context)
|
290
|
+
ssl_socket.sync_close = true
|
291
|
+
ssl_socket.connect()
|
292
|
+
return ssl_socket
|
293
|
+
end
|
294
|
+
|
295
|
+
end
|