iodine 0.1.21 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of iodine might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.gitignore +3 -2
- data/.travis.yml +23 -2
- data/CHANGELOG.md +9 -2
- data/README.md +232 -179
- data/Rakefile +13 -1
- data/bin/config.ru +63 -0
- data/bin/console +6 -0
- data/bin/echo +42 -32
- data/bin/http-hello +62 -0
- data/bin/http-playground +124 -0
- data/bin/playground +62 -0
- data/bin/poc/Gemfile.lock +23 -0
- data/bin/poc/README.md +37 -0
- data/bin/poc/config.ru +66 -0
- data/bin/poc/gemfile +1 -0
- data/bin/poc/www/index.html +57 -0
- data/bin/raw-rbhttp +35 -0
- data/bin/raw_broadcast +66 -0
- data/bin/test_with_faye +40 -0
- data/bin/ws-broadcast +108 -0
- data/bin/ws-echo +108 -0
- data/exe/iodine +59 -0
- data/ext/iodine/base64.c +264 -0
- data/ext/iodine/base64.h +72 -0
- data/ext/iodine/bscrypt-common.h +109 -0
- data/ext/iodine/bscrypt.h +49 -0
- data/ext/iodine/extconf.rb +41 -0
- data/ext/iodine/hex.c +123 -0
- data/ext/iodine/hex.h +70 -0
- data/ext/iodine/http.c +200 -0
- data/ext/iodine/http.h +128 -0
- data/ext/iodine/http1.c +402 -0
- data/ext/iodine/http1.h +56 -0
- data/ext/iodine/http1_simple_parser.c +473 -0
- data/ext/iodine/http1_simple_parser.h +59 -0
- data/ext/iodine/http_request.h +128 -0
- data/ext/iodine/http_response.c +1606 -0
- data/ext/iodine/http_response.h +393 -0
- data/ext/iodine/http_response_http1.h +374 -0
- data/ext/iodine/iodine_core.c +641 -0
- data/ext/iodine/iodine_core.h +70 -0
- data/ext/iodine/iodine_http.c +615 -0
- data/ext/iodine/iodine_http.h +19 -0
- data/ext/iodine/iodine_websocket.c +430 -0
- data/ext/iodine/iodine_websocket.h +21 -0
- data/ext/iodine/libasync.c +552 -0
- data/ext/iodine/libasync.h +117 -0
- data/ext/iodine/libreact.c +347 -0
- data/ext/iodine/libreact.h +244 -0
- data/ext/iodine/libserver.c +912 -0
- data/ext/iodine/libserver.h +435 -0
- data/ext/iodine/libsock.c +950 -0
- data/ext/iodine/libsock.h +478 -0
- data/ext/iodine/misc.c +181 -0
- data/ext/iodine/misc.h +76 -0
- data/ext/iodine/random.c +193 -0
- data/ext/iodine/random.h +48 -0
- data/ext/iodine/rb-call.c +127 -0
- data/ext/iodine/rb-call.h +60 -0
- data/ext/iodine/rb-libasync.h +79 -0
- data/ext/iodine/rb-rack-io.c +389 -0
- data/ext/iodine/rb-rack-io.h +17 -0
- data/ext/iodine/rb-registry.c +213 -0
- data/ext/iodine/rb-registry.h +33 -0
- data/ext/iodine/sha1.c +359 -0
- data/ext/iodine/sha1.h +85 -0
- data/ext/iodine/sha2.c +825 -0
- data/ext/iodine/sha2.h +138 -0
- data/ext/iodine/siphash.c +136 -0
- data/ext/iodine/siphash.h +15 -0
- data/ext/iodine/spnlock.h +235 -0
- data/ext/iodine/websockets.c +696 -0
- data/ext/iodine/websockets.h +120 -0
- data/ext/iodine/xor-crypt.c +189 -0
- data/ext/iodine/xor-crypt.h +107 -0
- data/iodine.gemspec +25 -18
- data/lib/iodine.rb +57 -58
- data/lib/iodine/http.rb +0 -189
- data/lib/iodine/protocol.rb +36 -245
- data/lib/iodine/version.rb +1 -1
- data/lib/rack/handler/iodine.rb +145 -2
- metadata +115 -37
- data/bin/core_http_test +0 -51
- data/bin/em playground +0 -56
- data/bin/hello_world +0 -75
- data/bin/setup +0 -7
- data/lib/iodine/client.rb +0 -5
- data/lib/iodine/core.rb +0 -102
- data/lib/iodine/core_init.rb +0 -143
- data/lib/iodine/http/hpack.rb +0 -553
- data/lib/iodine/http/http1.rb +0 -251
- data/lib/iodine/http/http2.rb +0 -507
- data/lib/iodine/http/rack_support.rb +0 -108
- data/lib/iodine/http/request.rb +0 -462
- data/lib/iodine/http/response.rb +0 -474
- data/lib/iodine/http/session.rb +0 -143
- data/lib/iodine/http/websocket_client.rb +0 -335
- data/lib/iodine/http/websocket_handler.rb +0 -101
- data/lib/iodine/http/websockets.rb +0 -336
- data/lib/iodine/io.rb +0 -56
- data/lib/iodine/logging.rb +0 -46
- data/lib/iodine/settings.rb +0 -158
- data/lib/iodine/ssl_connector.rb +0 -48
- data/lib/iodine/timers.rb +0 -95
data/lib/iodine/http/session.rb
DELETED
@@ -1,143 +0,0 @@
|
|
1
|
-
module Iodine
|
2
|
-
module Http
|
3
|
-
# session management for Iodine's Http Server
|
4
|
-
module SessionManager
|
5
|
-
|
6
|
-
module MemSessionStorage
|
7
|
-
class SessionObject < Hash
|
8
|
-
def initialize id
|
9
|
-
super()
|
10
|
-
self[:__session_id] = id
|
11
|
-
end
|
12
|
-
# returns the session id (the session cookie value).
|
13
|
-
def id
|
14
|
-
self[:__session_id]
|
15
|
-
end
|
16
|
-
end
|
17
|
-
@mem_storage = {}
|
18
|
-
def self.fetch key
|
19
|
-
@mem_storage[key] ||= SessionObject.new(key)
|
20
|
-
end
|
21
|
-
end
|
22
|
-
module FileSessionStorage
|
23
|
-
class SessionObject
|
24
|
-
# called by the Plezi framework to initiate a session with the id requested
|
25
|
-
def initialize id
|
26
|
-
@filename = File.join Dir.tmpdir, "iodine_#{Iodine::Http.session_token}_#{id}"
|
27
|
-
@data ||= {}
|
28
|
-
@id = id
|
29
|
-
end
|
30
|
-
# returns the session id (the session cookie value).
|
31
|
-
def id
|
32
|
-
@id
|
33
|
-
end
|
34
|
-
# Get a key from the session data store.
|
35
|
-
#
|
36
|
-
# Due to different considirations, all keys will be converted to strings, so that `"name" == :name` and `1234 == "1234"`.
|
37
|
-
# If you store two keys that evaluate as the same string, they WILL override each other.
|
38
|
-
def [] key
|
39
|
-
key = key.to_s
|
40
|
-
load
|
41
|
-
@data[key]
|
42
|
-
end
|
43
|
-
alias :fetch :[]
|
44
|
-
|
45
|
-
# Stores a key in the session's data store.
|
46
|
-
#
|
47
|
-
# Due to different considirations, all keys will be converted to strings, so that `"name" == :name` and `1234 == "1234"`.
|
48
|
-
# If you store two keys that evaluate as the same string, they WILL override each other.
|
49
|
-
def []= key, value
|
50
|
-
return delete key if value.nil?
|
51
|
-
key = key.to_s
|
52
|
-
load
|
53
|
-
@data[key] = value
|
54
|
-
save
|
55
|
-
value
|
56
|
-
end
|
57
|
-
alias :store :[]=
|
58
|
-
|
59
|
-
# @return [Hash] returns a shallow copy of the current session data as a Hash.
|
60
|
-
def to_h
|
61
|
-
load
|
62
|
-
@data.dup
|
63
|
-
end
|
64
|
-
|
65
|
-
# @return [String] returns the Session data in YAML format.
|
66
|
-
def to_s
|
67
|
-
load
|
68
|
-
@data.to_yaml
|
69
|
-
end
|
70
|
-
|
71
|
-
# Removes a key from the session's data store.
|
72
|
-
def delete key
|
73
|
-
load
|
74
|
-
ret = @data.delete key.to_s
|
75
|
-
save
|
76
|
-
ret
|
77
|
-
end
|
78
|
-
|
79
|
-
# Clears the session's data.
|
80
|
-
def clear
|
81
|
-
@data.clear
|
82
|
-
nil
|
83
|
-
end
|
84
|
-
|
85
|
-
# Forced the session's data to be reloaded
|
86
|
-
def refresh
|
87
|
-
load
|
88
|
-
end
|
89
|
-
protected
|
90
|
-
# def destroy
|
91
|
-
# # save data to tmp-file
|
92
|
-
# File.delete @filename if ::File.file?(@filename) # && !::File.directory?(@filename)
|
93
|
-
# end
|
94
|
-
def save
|
95
|
-
# save data to tmp-file
|
96
|
-
IO.write @filename, @data.to_yaml
|
97
|
-
end
|
98
|
-
def load
|
99
|
-
@data = YAML.load IO.read(@filename) if ::File.file?(@filename)
|
100
|
-
end
|
101
|
-
end
|
102
|
-
def self.fetch key
|
103
|
-
SessionObject.new key
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
module_function
|
108
|
-
# returns a session object
|
109
|
-
def get id
|
110
|
-
storage.fetch(id)
|
111
|
-
end
|
112
|
-
# Sets the session storage system, to allow for different storage systems.
|
113
|
-
#
|
114
|
-
# A Session Storage system must answer only one methods:
|
115
|
-
# fetch(id):: returns a Hash like session object with all the session's data or a fresh session object if the session object did not exist before
|
116
|
-
#
|
117
|
-
# The Session Object should update the storage whenever data is saved to the session Object.
|
118
|
-
# This is important also because a websocket 'session' could exist simultaneously with other HTTP requests (multiple browser windows) and the data should be kept updated at all times.
|
119
|
-
def storage= session_storage = nil
|
120
|
-
case session_storage
|
121
|
-
when :file, nil
|
122
|
-
@storage = Iodine::Http::SessionManager::FileSessionStorage
|
123
|
-
when :mem
|
124
|
-
@storage = Iodine::Http::SessionManager::MemSessionStorage
|
125
|
-
else
|
126
|
-
@storage = session_storage
|
127
|
-
end
|
128
|
-
end
|
129
|
-
|
130
|
-
# returns the current session storage system.
|
131
|
-
def storage
|
132
|
-
@storage ||= Iodine::Http::SessionManager::FileSessionStorage
|
133
|
-
end
|
134
|
-
end
|
135
|
-
end
|
136
|
-
end
|
137
|
-
# A hash like interface for storing request session data.
|
138
|
-
# The store must implement:
|
139
|
-
# store(key, value) (aliased as []=);
|
140
|
-
# fetch(key, default = nil) (aliased as []);
|
141
|
-
# delete(key);
|
142
|
-
# clear;
|
143
|
-
# id(); (returns the session id)
|
@@ -1,335 +0,0 @@
|
|
1
|
-
|
2
|
-
module Iodine
|
3
|
-
module Http
|
4
|
-
# Create a simple Websocket Client(!).
|
5
|
-
#
|
6
|
-
# This should be done from within an Iodine task, or the callbacks will not be called.
|
7
|
-
#
|
8
|
-
# Use {Iodine::Http::WebsocketClient.connect} to initialize a client with all the callbacks needed.
|
9
|
-
class WebsocketClient
|
10
|
-
|
11
|
-
attr_accessor :response, :request, :params
|
12
|
-
|
13
|
-
def initialize options
|
14
|
-
@response = nil
|
15
|
-
@options = options
|
16
|
-
@on_message = @options[:on_message]
|
17
|
-
raise "Websocket client must have an #on_message Proc or handler." unless @on_message && @on_message.respond_to?(:call)
|
18
|
-
@on_open = @options[:on_open]
|
19
|
-
@on_close = @options[:on_close]
|
20
|
-
@on_error = @options[:on_error]
|
21
|
-
@renew = @options[:renew].to_i
|
22
|
-
@options[:url] = URI.parse(@options[:url]) unless @options[:url].is_a?(URI)
|
23
|
-
@connection_lock = Mutex.new
|
24
|
-
raise TypeError, "Websocket Client `:send` should be either a String or a Proc object." if @options[:send] && !(@options[:send].is_a?(String) || @options[:send].is_a?(Proc))
|
25
|
-
on_close && (@io || raise("Connection error, cannot create websocket client")) unless connect
|
26
|
-
end
|
27
|
-
|
28
|
-
def on event_name, &block
|
29
|
-
return false unless block
|
30
|
-
case event_name
|
31
|
-
when :message
|
32
|
-
@on_message = block
|
33
|
-
when :close
|
34
|
-
@on_close = block
|
35
|
-
when :open
|
36
|
-
raise 'The on_open even is invalid at this point.'
|
37
|
-
end
|
38
|
-
|
39
|
-
end
|
40
|
-
|
41
|
-
def on_message(data = nil, &block)
|
42
|
-
unless data
|
43
|
-
@on_message = block if block
|
44
|
-
return @on_message
|
45
|
-
end
|
46
|
-
begin
|
47
|
-
instance_exec( data, &@on_message)
|
48
|
-
rescue => e
|
49
|
-
@on_error ? @on_error.call(e) : raise(e)
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
def on_open
|
54
|
-
raise 'The on_open even is invalid at this point.' if block_given?
|
55
|
-
@renew = @options[:renew].to_i
|
56
|
-
@io = @request[:io]
|
57
|
-
Iodine::Http::Request.parse @request
|
58
|
-
begin
|
59
|
-
instance_exec(&@on_open) if @on_open
|
60
|
-
rescue => e
|
61
|
-
@on_error ? @on_error.call(e) : raise(e)
|
62
|
-
end
|
63
|
-
if @options[:every] && @options[:send]
|
64
|
-
Iodine.run_every @options[:every], self, @options do |ws, client_params, timer|
|
65
|
-
if ws.closed?
|
66
|
-
timer.stop!
|
67
|
-
next
|
68
|
-
end
|
69
|
-
if client_params[:send].is_a?(String)
|
70
|
-
ws.write client_params[:send]
|
71
|
-
elsif client_params[:send].is_a?(Proc)
|
72
|
-
begin
|
73
|
-
ws.instance_exec(&client_params[:send])
|
74
|
-
rescue => e
|
75
|
-
@on_error ? @on_error.call(e) : raise(e)
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
def on_close(&block)
|
83
|
-
return @on_close = block if block
|
84
|
-
if @renew > 0
|
85
|
-
renew_proc = Proc.new do
|
86
|
-
@io = nil
|
87
|
-
begin
|
88
|
-
raise unless connect
|
89
|
-
rescue
|
90
|
-
@renew -= 1
|
91
|
-
if @renew <= 0
|
92
|
-
Iodine.fatal "WebsocketClient renewal FAILED for #{@options[:url]}"
|
93
|
-
on_close
|
94
|
-
else
|
95
|
-
Iodine.warn "WebsocketClient renewal failed for #{@options[:url]}, #{@renew} attempts left"
|
96
|
-
renew_proc.call
|
97
|
-
end
|
98
|
-
false
|
99
|
-
end
|
100
|
-
end
|
101
|
-
@connection_lock.synchronize { renew_proc.call }
|
102
|
-
else
|
103
|
-
begin
|
104
|
-
instance_exec(&@on_close) if @on_close
|
105
|
-
rescue => e
|
106
|
-
@on_error ? @on_error.call(e) : raise(e)
|
107
|
-
end
|
108
|
-
end
|
109
|
-
end
|
110
|
-
def on_error(error = nil, &block)
|
111
|
-
return @on_error = block if block
|
112
|
-
instance_exec(error, &@on_error) if @on_error
|
113
|
-
on_close unless @io # if the connection was initialized, :on_close will be called by Iodine
|
114
|
-
end
|
115
|
-
|
116
|
-
def on_shutdown
|
117
|
-
@renew = 0
|
118
|
-
end
|
119
|
-
|
120
|
-
# closes the connection, if open
|
121
|
-
def close
|
122
|
-
@renew = 0
|
123
|
-
@io.close if @io
|
124
|
-
end
|
125
|
-
|
126
|
-
# checks if the socket is open (if the websocket was terminated abnormally, this might return true for a while after it should be false).
|
127
|
-
def closed?
|
128
|
-
(@io && @io.io) ? @io.io.closed? : true
|
129
|
-
end
|
130
|
-
|
131
|
-
# checks if this is an SSL websocket connection.
|
132
|
-
def ssl?
|
133
|
-
@request.ssl?
|
134
|
-
end
|
135
|
-
|
136
|
-
# return a Hash with the HTTP cookies recieved during the HTTP's handshake.
|
137
|
-
def cookies
|
138
|
-
@request.cookies
|
139
|
-
end
|
140
|
-
|
141
|
-
# Sends data through the websocket, after client side masking.
|
142
|
-
#
|
143
|
-
# @return [true, false] Returns the true if the data was actually sent or nil if no data was sent.
|
144
|
-
def write data, op_code = nil, fin = true, ext = 0
|
145
|
-
return false if !data || data.empty?
|
146
|
-
data = data.dup # needed?
|
147
|
-
unless op_code # apply extenetions to the message as a whole
|
148
|
-
op_code = (data.encoding == ::Encoding::UTF_8 ? 1 : 2)
|
149
|
-
# @ws_extentions.each { |ex| ext |= ex.edit_message data } if @ws_extentions
|
150
|
-
end
|
151
|
-
byte_size = data.bytesize
|
152
|
-
if byte_size > (::Iodine::Http::Websockets::FRAME_SIZE_LIMIT+2)
|
153
|
-
# sections = byte_size/FRAME_SIZE_LIMIT + (byte_size % ::Iodine::Http::Websockets::FRAME_SIZE_LIMIT ? 1 : 0)
|
154
|
-
ret = write( data.slice!( 0...::Iodine::Http::Websockets::FRAME_SIZE_LIMIT ), op_code, data.empty?, ext) && (ext = op_code = 0) until data.empty?
|
155
|
-
return ret # avoid sending an empty frame.
|
156
|
-
end
|
157
|
-
# @ws_extentions.each { |ex| ext |= ex.edit_frame data } if @ws_extentions
|
158
|
-
header = ( (fin ? 0b10000000 : 0) | (op_code & 0b00001111) | ext).chr.force_encoding(::Encoding::ASCII_8BIT)
|
159
|
-
|
160
|
-
if byte_size < 125
|
161
|
-
header << (byte_size | 128).chr
|
162
|
-
elsif byte_size.bit_length <= 16
|
163
|
-
header << 254.chr
|
164
|
-
header << [byte_size].pack('S>'.freeze)
|
165
|
-
else
|
166
|
-
header << 255.chr
|
167
|
-
header << [byte_size].pack('Q>'.freeze)
|
168
|
-
end
|
169
|
-
@@make_mask_proc ||= Proc.new {Random.rand(251) + 1}
|
170
|
-
mask = Array.new(4, &(@@make_mask_proc))
|
171
|
-
header << mask.pack('C*'.freeze)
|
172
|
-
@connection_lock.synchronize do
|
173
|
-
return false if @io.nil? || @io.closed?
|
174
|
-
@io.write header
|
175
|
-
i = -1;
|
176
|
-
@io.write(data.bytes.map! {|b| (b ^ mask[i = (i + 1)%4]) } .pack('C*'.freeze)) && true
|
177
|
-
end
|
178
|
-
end
|
179
|
-
alias :<< :write
|
180
|
-
|
181
|
-
protected
|
182
|
-
|
183
|
-
def connect
|
184
|
-
return false if @io && !@io.closed?
|
185
|
-
socket = nil
|
186
|
-
url = @options[:url]
|
187
|
-
@options[:renew] ||= 5 if @options[:every] && @options[:send]
|
188
|
-
|
189
|
-
ssl = url.scheme == "https" || url.scheme == "wss"
|
190
|
-
|
191
|
-
url.port ||= ssl ? 443 : 80
|
192
|
-
url.path = '/' if url.path.to_s.empty?
|
193
|
-
socket = TCPSocket.new(url.host, url.port)
|
194
|
-
if ssl
|
195
|
-
context = OpenSSL::SSL::SSLContext.new
|
196
|
-
context.cert_store = OpenSSL::X509::Store.new
|
197
|
-
context.cert_store.set_default_paths
|
198
|
-
context.set_params verify_mode: (@options[:verify_mode] || OpenSSL::SSL::VERIFY_NONE) # OpenSSL::SSL::VERIFY_PEER #OpenSSL::SSL::VERIFY_NONE
|
199
|
-
ssl = OpenSSL::SSL::SSLSocket.new(socket, context)
|
200
|
-
ssl.sync_close = true
|
201
|
-
ssl.connect
|
202
|
-
end
|
203
|
-
# prep custom headers
|
204
|
-
custom_headers = String.new
|
205
|
-
custom_headers = @options[:headers] if @options[:headers].is_a?(String)
|
206
|
-
@options[:headers].each {|k, v| custom_headers << "#{k.to_s}: #{v.to_s}\r\n"} if @options[:headers].is_a?(Hash)
|
207
|
-
@options[:cookies].each {|k, v| raise 'Illegal cookie name' if k.to_s.match(/[\x00-\x20\(\)<>@,;:\\\"\/\[\]\?\=\{\}\s]/.freeze); custom_headers << "Cookie: #{ k }=#{ Iodine::Http::Request.encode_url v }\r\n"} if @options[:cookies].is_a?(Hash)
|
208
|
-
|
209
|
-
# send protocol upgrade request
|
210
|
-
websocket_key = [(Array.new(16) {rand 255} .pack 'c*'.freeze )].pack('m0*'.freeze)
|
211
|
-
(ssl || socket).write "GET #{url.path}#{url.query.to_s.empty? ? ''.freeze : ("?#{url.query}")} HTTP/1.1\r\nHost: #{url.host}#{url.port ? (":#{url.port.to_s}") : ''.freeze}\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nOrigin: #{ssl ? 'https'.freeze : 'http'.freeze}://#{url.host}\r\nSec-WebSocket-Key: #{websocket_key}\r\nSec-WebSocket-Version: 13\r\n#{custom_headers}\r\n"
|
212
|
-
# wait for answer - make sure we don't over-read
|
213
|
-
# (a websocket message might be sent immidiately after connection is established)
|
214
|
-
reply = String.new.force_encoding(::Encoding::ASCII_8BIT)
|
215
|
-
stop_time = Time.now + (@options[:timeout] || 5)
|
216
|
-
stop_reply = "\r\n\r\n".freeze
|
217
|
-
until reply[-4..-1] == stop_reply
|
218
|
-
begin
|
219
|
-
reply << ( ssl ? ssl.read_nonblock(1) : socket.recv_nonblock(1) )
|
220
|
-
rescue Errno::EWOULDBLOCK, OpenSSL::SSL::SSLErrorWaitReadable => e
|
221
|
-
raise "Websocket client handshake timed out (HTTP reply not recieved)\n\n Got Only: #{reply}" if Time.now >= stop_time
|
222
|
-
IO.select [socket], nil, nil, (@options[:timeout] || 5)
|
223
|
-
retry
|
224
|
-
end
|
225
|
-
raise "Connection failed" if socket.closed?
|
226
|
-
end
|
227
|
-
# review reply
|
228
|
-
raise "Connection Refused. Reply was:\r\n #{reply}" unless reply.lines[0].match(/^HTTP\/[\d\.]+ 101/i.freeze)
|
229
|
-
raise 'Websocket Key Authentication failed.' unless reply.match(/^Sec-WebSocket-Accept:[\s]*([^\s]*)/i.freeze) && reply.match(/^Sec-WebSocket-Accept:[\s]*([^\s]*)/i.freeze)[1] == Digest::SHA1.base64digest(websocket_key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
|
230
|
-
# read the body's data and parse any incoming data.
|
231
|
-
@request = Iodine::Http::Request.new
|
232
|
-
@request[:method] = 'GET'.freeze
|
233
|
-
@request['host'.freeze] = "#{url.host}:#{url.port}"
|
234
|
-
@request[:query] = url.path
|
235
|
-
@request[:version] = '1.1'.freeze
|
236
|
-
reply = StringIO.new reply
|
237
|
-
reply.gets
|
238
|
-
|
239
|
-
until reply.eof?
|
240
|
-
until @request[:headers_complete] || (l = reply.gets).nil?
|
241
|
-
if l.include? ':'
|
242
|
-
l = l.strip.split(/:[\s]?/.freeze, 2)
|
243
|
-
l[0].strip! ; l[0].downcase!;
|
244
|
-
@request[l[0]] ? (@request[l[0]].is_a?(Array) ? (@request[l[0]] << l[1]) : @request[l[0]] = [@request[l[0]], l[1] ]) : (@request[l[0]] = l[1])
|
245
|
-
elsif l =~ /^[\r]?\n/.freeze
|
246
|
-
@request[:headers_complete] = true
|
247
|
-
else
|
248
|
-
#protocol error
|
249
|
-
raise 'Protocol Error, closing connection.'
|
250
|
-
return close
|
251
|
-
end
|
252
|
-
end
|
253
|
-
end
|
254
|
-
reply.string.clear
|
255
|
-
|
256
|
-
return Iodine::Http::Websockets.new( ( ssl || socket), handler: self, request: @request )
|
257
|
-
|
258
|
-
rescue => e
|
259
|
-
(ssl || socket).tap {|io| next if io.nil?; io.close unless io.closed?}
|
260
|
-
if @options[:on_error]
|
261
|
-
@options[:on_error].call(e)
|
262
|
-
return false
|
263
|
-
end
|
264
|
-
raise e unless @io
|
265
|
-
end
|
266
|
-
|
267
|
-
# Create a simple Websocket Client(!).
|
268
|
-
#
|
269
|
-
# This method accepts two parameters:
|
270
|
-
# url:: a String representing the URL of the websocket. i.e.: 'ws://foo.bar.com:80/ws/path'
|
271
|
-
# options:: a Hash with options to be used. The options will be used to define the connection's details (i.e. ssl etc') and the Websocket callbacks (i.e. on_open(ws), on_close(ws), on_message(ws))
|
272
|
-
# &block:: an optional block that accepts one parameter (data) and will be used as the `#on_message(data)`
|
273
|
-
#
|
274
|
-
# Acceptable options are:
|
275
|
-
# on_open:: the on_open callback - Must be an objects that answers `call()`, usually a Proc.
|
276
|
-
# on_message:: the on_message callback - Must be an objects that answers `call(data)`, usually a Proc.
|
277
|
-
# on_close:: the on_close callback - Must be an objects that answers `call()`, usually a Proc. The method is called when the connection is closed and isn't renewed.
|
278
|
-
# on_error:: the on_error callback - Must be an objects that answers `call(err)`, usually a Proc. This is called whenever a connection fails to be established or an exception is raised by any of the callbacks. This is NOT the disconnection websocket message. dafaults to raising the error (error pass-through).
|
279
|
-
# headers:: a Hash of custom HTTP headers to be sent with the request. Header data, including cookie headers, should be correctly encoded.
|
280
|
-
# cookies:: a Hash of cookies to be sent with the request. cookie data will be encoded before being sent.
|
281
|
-
# timeout:: the number of seconds to wait before the connection is established. Defaults to 5 seconds.
|
282
|
-
# every:: this option, together with `:send` and `:renew`, implements a polling websocket. :every is the number of seconds between each polling event. without `:send`, this option will be ignored. defaults to nil.
|
283
|
-
# send:: a String to be sent or a Proc to be performed each polling interval. This option, together with `:every` and `:renew`, implements a polling websocket. without `:every`, this option will be ignored. defaults to nil. If `:send` is a Proc, it will be executed within the context of the websocket client object, with acess to the websocket client's instance variables and methods.
|
284
|
-
# renew:: the number of times to attempt to renew the connection if the connection is terminated by the remote server. Attempts are made in 2 seconds interval. The default for a polling websocket is 5 attempts to renew. For all other clients, the default is 0 (no renewal).
|
285
|
-
#
|
286
|
-
# The method will block until the connection is established or until 5 seconds have passed (the timeout). The method will either return a WebsocketClient instance object or raise an exception it the connection was unsuccessful.
|
287
|
-
#
|
288
|
-
# Use Iodine::Http.ws_connect for a non-blocking initialization.
|
289
|
-
#
|
290
|
-
# An #on_close callback will only be called if the connection isn't or cannot be renewed. If the connection is renewed,
|
291
|
-
# the #on_open callback will be called again for a new Websocket client instance - but the #on_close callback will NOT be called.
|
292
|
-
#
|
293
|
-
# Due to this design, the #on_open and #on_close methods should NOT be used for opening IO resources (i.e. file handles) nor for cleanup IF the `:renew` option is enabled.
|
294
|
-
#
|
295
|
-
# An on_message Proc must be defined, or the method will fail.
|
296
|
-
#
|
297
|
-
# The on_message Proc can be defined using the optional block:
|
298
|
-
#
|
299
|
-
# Iodine::Http::WebsocketClient.connect("ws://localhost:3000/") {|data| write data} #echo example
|
300
|
-
#
|
301
|
-
# OR, the on_message Proc can be defined using the options Hash:
|
302
|
-
#
|
303
|
-
# Iodine::Http::WebsocketClient.connect("ws://localhost:3000/", on_open: -> {}, on_message: -> {|data| write data })
|
304
|
-
#
|
305
|
-
# The #on_message(data), #on_open and #on_close methods will be executed within the context of the WebsocketClient
|
306
|
-
# object, and will have native acess to the Websocket response object.
|
307
|
-
#
|
308
|
-
# After the WebsocketClient had been created, it's possible to update the #on_message and #on_close methods:
|
309
|
-
#
|
310
|
-
# # updates #on_message
|
311
|
-
# wsclient.on_message do |data|
|
312
|
-
# response << "I'll disconnect on the next message!"
|
313
|
-
# # updates #on_message again.
|
314
|
-
# on_message {|data| disconnect }
|
315
|
-
# end
|
316
|
-
#
|
317
|
-
#
|
318
|
-
# !!please be aware that the Websockt Client will not attempt to verify SSL certificates,
|
319
|
-
# so that even SSL connections are vulnerable to a possible man in the middle attack.
|
320
|
-
#
|
321
|
-
# @return [Iodine::Http::WebsocketClient] this method returns the connected {Iodine::Http::WebsocketClient} or raises an exception if something went wrong (such as a connection timeout).
|
322
|
-
def self.connect url, options={}, &block
|
323
|
-
options = url if url.is_a?(Hash) && options.empty?
|
324
|
-
options[:renew] ||= 5 if options[:every] && options[:send]
|
325
|
-
options[:url] ||= url
|
326
|
-
options[:on_message] ||= block
|
327
|
-
client = self.new(options)
|
328
|
-
return client unless client.closed?
|
329
|
-
false
|
330
|
-
end
|
331
|
-
end
|
332
|
-
end
|
333
|
-
end
|
334
|
-
|
335
|
-
|