iodine 0.0.2 → 0.0.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4562f69fd3e73d156c4007d70400f07973c143ea
4
- data.tar.gz: 14264f438e3e200ad52f18f63881087854da88b1
3
+ metadata.gz: 75cc90d10afaecdc798bdf39ec5d4a1063431faf
4
+ data.tar.gz: da27288f1c90ac8604c07c8f3d2ffb796c09e453
5
5
  SHA512:
6
- metadata.gz: ab840a1c507e08b328b257e42038ec1dad3eaaaaad8703b1d0d1085707a20dddfb21b7e290eeec80008eb49c8beb8eb94901a17e0c9efa5648a9107c1d36d943
7
- data.tar.gz: e4f5a83897e9bf8eb33225278010abc1be7bb0787eeec3eed77704b7f2f383448becad6c0b870988c77aecec5e3edba1248b1c46765376bff07cfc3f2ba9a406
6
+ metadata.gz: ce19f80d81def9ad8784648625007cca05a9513d6498dbff030b10a28a43e96ab7c3a54553c275393c2803b9bc5e47b47307ac03b2c1a7fc127646d396eeb017
7
+ data.tar.gz: 145b4f0cb2aeec486af8a09d3cf0d91819a7e97449a3bbb8a5a56e7324a2e9c4977a1a8119c52ac780a7b34b338ee8d3e934dd5a48bc0d0da757588b80e1a99a
data/README.md CHANGED
@@ -1,4 +1,6 @@
1
1
  # Iodine
2
+ [![Gem Version](https://badge.fury.io/rb/iodine.svg)](https://badge.fury.io/rb/iodine)
3
+ [![Inline docs](http://inch-ci.org/github/boazsegev/iodine.svg?branch=master)](http://www.rubydoc.info/github/boazsegev/iodine/master/frames)
2
4
 
3
5
  Iodine makes writing Object Oriented evented server applications easy to write.
4
6
 
@@ -30,6 +32,7 @@ Iodine starts to work once you app is finished setting all the tasks up (upon ex
30
32
 
31
33
  To see how that works, open your `irb` terminal an try this:
32
34
 
35
+
33
36
  ```ruby
34
37
  require 'iodine'
35
38
 
@@ -62,6 +65,7 @@ To initiate this mode, simply set: `Iodine.protocol = :timers`
62
65
 
63
66
  In example:
64
67
 
68
+
65
69
  ```ruby
66
70
  require 'iodine'
67
71
 
@@ -83,9 +87,45 @@ exit
83
87
 
84
88
  In this mode, Iodine will continue running until it receives a kill signal (i.e. `^C`). Once the kill signal had been received, Iodine will start shutting down, allowing up to ~20-25 seconds to complete any pending tasks (timeout).
85
89
 
86
- ## Server Usage: an Http and Websocket (as well as Rack) server
90
+ ## Server Usage: an Http and Websocket server
91
+
92
+ Using Iodine and leveraging Ruby's Object Oriented approach, is super fun to write our own network protocols and servers... This is Ioding itself includes an _optional_ Http and websocket server. Say "Hello World":
93
+
94
+ ```ruby
95
+ # require the 'iodine/http' module if you want to use Iodine's Http server.
96
+ require 'iodine/http'
97
+ # returning a string will automatically append it to the response.
98
+ Iodine::Http.on_http = { |request, response| "Hello World!" }
99
+ ```
87
100
 
101
+ Iodine's Http server includes experimental support for Http/2 right out of the box as well as a Websocket server.
88
102
 
103
+ Here's a quick chatroom server (use [www.websocket.org](http://www.websocket.org/echo.html) to check it out):
104
+
105
+ ```ruby
106
+ # require the 'iodine/http' module if you want to use Iodine's Websocket server.
107
+ require 'iodine/http'
108
+ # create an object that will follow the Iodine Websocket API.
109
+ class WSChatServer < Iodine::Http::WebsocketHandler
110
+ def on_open
111
+ @nickname = request.params[:nickname] || "unknown"
112
+ broadcast "#{@nickname} has joined the chat!"
113
+ write "Welcome #{@nickname}, you have joined the chat!"
114
+ end
115
+ def on_message data
116
+ broadcast "#{@nickname} >> #{data}"
117
+ write ">> #{data}"
118
+ end
119
+ def on_broadcast data
120
+ write data
121
+ end
122
+ def on_close
123
+ broadcast "#{@nickname} has left the chat!"
124
+ end
125
+ end
126
+
127
+ Iodine::Http.on_websocket WSChatServer
128
+ ```
89
129
 
90
130
  ## Server Usage: Plug in your network protocol
91
131
 
@@ -25,6 +25,18 @@ module Iodine
25
25
  self
26
26
  end
27
27
 
28
+ # @return [nil] Signals Iodine to exit if it was running on Server or Timer mode. Tasks will rundown pending timeout.
29
+ def signal_exit
30
+ Process.kill("INT", 0) unless @stop
31
+ nil
32
+ end
33
+
34
+ # forces Iodine to start prematurely and asyncronously. This might case Iodine to exit abruptly, depending how the hosting application behaves.
35
+ def force_start!
36
+ thread = Thread.new { startup true }
37
+ Kernel.at_exit {thread.raise("stop"); thread.join}
38
+ end
39
+
28
40
  protected
29
41
 
30
42
  @queue = Queue.new
@@ -58,12 +70,18 @@ module Iodine
58
70
  end
59
71
  end
60
72
 
61
- Kernel.at_exit do
73
+ def startup use_rescue = false, hide_message = false
62
74
  threads = []
63
75
  @thread_count.times { threads << Thread.new { cycle } }
64
76
  unless @stop
65
- catch(:stop) { sleep }
66
- @logger << "\nShutting down Iodine. Setting shutdown timeout to 25 seconds.\n"
77
+ if use_rescue
78
+ sleep rescue true
79
+ else
80
+ old_int_trap = trap('INT') { throw :stop; trap('INT', old_int_trap) if old_int_trap }
81
+ old_term_trap = trap('TERM') { throw :stop; trap('TERM', old_term_trap) if old_term_trap }
82
+ catch(:stop) { sleep }
83
+ end
84
+ log "\nShutting down Iodine. Setting shutdown timeout to 25 seconds.\n" unless hide_message
67
85
  @stop = true
68
86
  # setup exit timeout.
69
87
  threads.each {|t| Thread.new {sleep 25; t.kill; t.kill } }
@@ -71,6 +89,10 @@ module Iodine
71
89
  threads.each {|t| t.join rescue true }
72
90
  end
73
91
 
92
+ Kernel.at_exit do
93
+ startup
94
+ end
95
+
74
96
  # performed once - the shutdown sequence.
75
97
  def shutdown
76
98
  return if @done
@@ -18,7 +18,7 @@ require 'iodine/http/hpack'
18
18
  require 'iodine/http/http2'
19
19
 
20
20
  require 'iodine/http/websockets'
21
- # require 'iodine/http/websockets_handler'
21
+ require 'iodine/http/websocket_handler'
22
22
  require 'iodine/http/websocket_client'
23
23
 
24
24
  require 'iodine/http/rack_support'
@@ -39,11 +39,14 @@ module Iodine
39
39
  #
40
40
  # require 'iodine/http'
41
41
  # class WSChatServer
42
- # def initialize nickname
42
+ # def initialize nickname, response
43
43
  # @nickname = nickname || "unknown"
44
+ # @response = response
45
+ # # @response.io # => Http Protocol
44
46
  # end
45
- # def on_open protocol
46
- # @io = protocol
47
+ # def on_open
48
+ # # only now is the response.io pointing at the Websocket Protocol
49
+ # @io = @response.io
47
50
  # @io.broadcast "#{@nickname} has joined the chat!"
48
51
  # @io << "Welcome #{@nickname}, you have joined the chat!"
49
52
  # end
@@ -52,14 +55,17 @@ module Iodine
52
55
  # @io << ">> #{data}"
53
56
  # end
54
57
  # def on_broadcast data
55
- # @io << data
58
+ # # the http response can also be used to send websocket data.
59
+ # @response << data
56
60
  # end
57
61
  # def on_close
58
62
  # @io.broadcast "#{@nickname} has left the chat!"
59
63
  # end
60
64
  # end
61
65
  #
62
- # Iodine::Http.on_websocket { |request, response| WSChatServer.new request.params[:name]}
66
+ # Iodine::Http.on_websocket { |request, response| WSChatServer.new request.params[:name], response}
67
+ #
68
+ # See {Iodine::Http::WebsocketHandler} for a good starting point or inherit {Iodine::Http::WebsocketHandler} in your handler.
63
69
  #
64
70
  class Http < Iodine::Protocol
65
71
  # Sets or gets the Http callback.
@@ -98,17 +104,23 @@ module Iodine
98
104
  # i.e.:
99
105
  #
100
106
  # require 'iodine/http'
107
+ # # don't start the server
108
+ # Iodine.protocol = :timer
101
109
  # options = {}
102
110
  # options[:on_open] = Proc.new { write "Hello there!"}
103
111
  # options[:on_message] = Proc.new do |data|
104
112
  # puts ">> #{data}";
105
113
  # write "Bye!";
106
114
  # # It's possible to update the callback midstream.
107
- # on_message {|data| puts "-- Goodbye message: #{data}"; close}
115
+ # on_message {|data| puts "-- Goodbye message: #{data}"; close;}
108
116
  # end
109
- # options[:on_close] = Proc.new { puts "disconnected"}
117
+ # # After closing we will call `Iodine.signal_exit` to signal Iodine to finish up.
118
+ # options[:on_close] = Proc.new { puts "disconnected"; Iodine.signal_exit }
110
119
  #
111
120
  # Iodine::Http.ws_connect "ws://echo.websocket.org", options
121
+ #
122
+ # #if running from irb:
123
+ # exit
112
124
  #
113
125
  def self.ws_connect url, options={}, &block
114
126
  ::Iodine.run { ::Iodine::Http::WebsocketClient.connect url, options, &block }
@@ -121,11 +121,6 @@ module Iodine
121
121
  HTTP_METHODS = %w{GET HEAD POST PUT DELETE TRACE OPTIONS CONNECT PATCH}
122
122
  HTTP_METHODS_REGEXP = /\A#{HTTP_METHODS.join('|')}/i
123
123
 
124
- def parse data
125
-
126
- end
127
-
128
-
129
124
  def dispatch request, data
130
125
  return data.string.clear if @io.closed? || @refuse_requests
131
126
  ::Iodine::Http::Request.parse request
@@ -209,7 +204,7 @@ module Iodine
209
204
  request = response.request
210
205
  return if Iodine.logger.nil? || request[:no_log]
211
206
  t_n = Time.now
212
- Iodine.logger << "#{request[:client_ip]} [#{t_n.utc}] \"#{request[:method]} #{request[:original_path]} #{request[:scheme]}\/#{request[:version]}\" #{response.status} #{response.bytes_written.to_s} #{((t_n - request[:time_recieved])*1000).round(2)}ms\n"
207
+ Iodine.log("#{request[:client_ip]} [#{t_n.utc}] \"#{request[:method]} #{request[:original_path]} #{request[:scheme]}\/#{request[:version]}\" #{response.status} #{response.bytes_written.to_s} #{((t_n - request[:time_recieved])*1000).round(2)}ms\n").clear
213
208
  end
214
209
  end
215
210
  end
@@ -122,6 +122,8 @@ module Iodine
122
122
  self.new(io).on_message data
123
123
  true
124
124
  end
125
+
126
+
125
127
  protected
126
128
 
127
129
  # logs the sent response.
@@ -129,7 +131,7 @@ module Iodine
129
131
  request = response.request
130
132
  return if Iodine.logger.nil? || request[:no_log]
131
133
  t_n = Time.now
132
- Iodine.logger << "#{request[:client_ip]} [#{t_n.utc}] #{request[:method]} #{request[:original_path]} #{request[:scheme]}\/2 #{response.status} #{response.bytes_written.to_s} #{((t_n - request[:time_recieved])*1000).round(2)}ms\n"
134
+ Iodine.log("#{request[:client_ip]} [#{t_n.utc}] #{request[:method]} #{request[:original_path]} #{request[:scheme]}\/2 #{response.status} #{response.bytes_written.to_s} #{((t_n - request[:time_recieved])*1000).round(2)}ms\n").clear
133
135
  end
134
136
 
135
137
  # Sends an HTTP frame with the requested payload
@@ -368,7 +370,7 @@ module Iodine
368
370
  # Iodine.info "Should Process request #{request.select { |k,v| k != :io } }"
369
371
  @last_stream = request[:sid] if request[:sid] > @last_stream
370
372
  # emit_frame [HTTP_1_1_REQUIRED].pack('N'), request[:sid], 0x3, 0
371
- Iodine.run request, &(::Iodine::Http::Http2.dispatch)
373
+ ::Iodine.run request, &(::Iodine::Http::Http2.dispatch)
372
374
 
373
375
  end
374
376
 
@@ -407,7 +409,7 @@ module Iodine
407
409
  #
408
410
  # @return [true, false, nil] returns true if connection handling can continue of false (or nil) for a fatal error.
409
411
  def connection_error type
410
- Iodine.warn "HTTP/2 error #{type}."
412
+ ::Iodine.warn "HTTP/2 error #{type}."
411
413
  go_away type
412
414
  # case type
413
415
  # when NO_ERROR
@@ -1,6 +1,6 @@
1
1
  module Iodine
2
2
 
3
- module Base
3
+ class Http < ::Iodine::Protocol
4
4
  # This (will be) a Rack handler for the Iodine HTTP server.
5
5
  module Rack
6
6
  module_function
@@ -90,7 +90,7 @@ begin
90
90
  rescue Exception => e
91
91
 
92
92
  end
93
- ::Rack::Handler.register( 'iodine', 'Iodine::Base::Rack') if defined?(::Rack)
93
+ ::Rack::Handler.register( 'iodine', 'Iodine::Http::Rack') if defined?(::Rack)
94
94
 
95
95
  ######
96
96
  ## example requests
@@ -332,7 +332,7 @@ module Iodine
332
332
  # read the body's data and parse any incoming data.
333
333
  def self.read_body request
334
334
  # save body for Rack, if applicable
335
- request[:rack_input] = StringIO.new(request[:body].dup.force_encoding(::Encoding::ASCII_8BIT)) if request[:io].params[:http_handler] == ::Iodine::Base::Rack
335
+ request[:rack_input] = StringIO.new(request[:body].dup.force_encoding(::Encoding::ASCII_8BIT)) if ::Iodine::Http.on_http == ::Iodine::Http::Rack
336
336
  # parse content
337
337
  case request['content-type'.freeze].to_s
338
338
  when /x-www-form-urlencoded/
@@ -14,8 +14,6 @@ module Iodine
14
14
  attr_reader :flash
15
15
  # the response's body buffer container (an array). This object is removed once the headers are sent and all write operations hang after that point.
16
16
  attr_accessor :body
17
- # the io through which the response will be sent.
18
- attr_reader :io
19
17
  # the request.
20
18
  attr_accessor :request
21
19
  # Logs the number of bytes written.
@@ -34,7 +32,6 @@ module Iodine
34
32
  @body = content || []
35
33
  @request.cookies.set_response self
36
34
  @cookies = {}
37
- @io = request.io
38
35
  @bytes_written = 0
39
36
  @keep_alive = @http_sblocks_count = false
40
37
  # propegate flash object
@@ -46,6 +43,11 @@ module Iodine
46
43
  end
47
44
  end
48
45
 
46
+ # returns the active protocol for the request.
47
+ def io
48
+ @request[:io]
49
+ end
50
+
49
51
  # returns true if headers were already sent
50
52
  def headers_sent?
51
53
  @headers.frozen?
@@ -79,7 +81,7 @@ module Iodine
79
81
  raise "Block required." unless block
80
82
  start_streaming unless @http_sblocks_count
81
83
  @http_sblocks_count += 1
82
- @stream_proc ||= Proc.new { |block| raise "IO closed. Streaming failed." if io.io.closed?; block.call; @http_sblocks_count -= 1; finish_streaming }
84
+ @stream_proc ||= Proc.new { |block| raise "IO closed. Streaming failed." if request[:io].io.closed?; block.call; @http_sblocks_count -= 1; finish_streaming }
83
85
  Iodine.run block, &@stream_proc
84
86
  end
85
87
 
@@ -150,7 +152,7 @@ module Iodine
150
152
  #
151
153
  # If the headers were already sent, this will also send the data and hang until the data was sent.
152
154
  def << str
153
- ( @body ? @body.push(str) : ( (@body = str.dup) && @io.stream_response(self) ) ) if str
155
+ ( @body ? @body.push(str) : ( (@body = str.dup) && request[:io].stream_response(self) ) ) if str
154
156
  self
155
157
  end
156
158
 
@@ -234,12 +236,12 @@ module Iodine
234
236
 
235
237
  # attempts to write a non-streaming response to the IO. This can be done only once and will quitely fail subsequently.
236
238
  def finish
237
- @io.send_response self
239
+ request[:io].send_response self
238
240
  end
239
241
 
240
242
  # Returns the connection's UUID.
241
243
  def uuid
242
- io.id
244
+ request[:io].id
243
245
  end
244
246
 
245
247
  # response status codes, as defined.
@@ -338,7 +340,7 @@ module Iodine
338
340
  def start_streaming
339
341
  raise "Cannot start streaming after headers were sent!" if headers_sent?
340
342
  @http_sblocks_count ||= 0
341
- @io.stream_response self
343
+ request[:io].stream_response self
342
344
  end
343
345
 
344
346
  # Sends the complete response signal for a streaming response.
@@ -346,7 +348,7 @@ module Iodine
346
348
  # Careful - sending the completed response signal more than once might case disruption to the HTTP connection.
347
349
  def finish_streaming
348
350
  return unless @http_sblocks_count == 0
349
- @io.stream_response self, true
351
+ request[:io].stream_response self, true
350
352
  end
351
353
  end
352
354
  end
@@ -1,78 +1,78 @@
1
1
  module Iodine
2
- module Base
3
- module MemSessionStorage
4
- @mem_storage = {}
5
- def self.fetch key
6
- @mem_storage[key] ||= {}
7
- end
8
- end
9
- module FileSessionStorage
10
- class SessionObject
11
- # called by the Plezi framework to initiate a session with the id requested
12
- def initialize id
13
- @filename = File.join Dir.tmpdir, "iodine_#{Iodine::Http.session_token}_#{id}"
14
- @data ||= {}
15
- end
16
- # Get a key from the session data store.
17
- #
18
- # Due to different considirations, all keys will be converted to strings, so that `"name" == :name` and `1234 == "1234"`.
19
- # If you store two keys that evaluate as the same string, they WILL override each other.
20
- def [] key
21
- key = key.to_s
22
- load
23
- @data[key]
24
- end
25
- alias :fetch :[]
2
+ class Http < ::Iodine::Protocol
3
+ module SessionManager
26
4
 
27
- # Stores a key in the session's data store.
28
- #
29
- # Due to different considirations, all keys will be converted to strings, so that `"name" == :name` and `1234 == "1234"`.
30
- # If you store two keys that evaluate as the same string, they WILL override each other.
31
- def []= key, value
32
- key = key.to_s
33
- load
34
- @data[key] = value
35
- save
36
- value
5
+ module MemSessionStorage
6
+ @mem_storage = {}
7
+ def self.fetch key
8
+ @mem_storage[key] ||= {}
37
9
  end
38
- alias :store :[]=
10
+ end
11
+ module FileSessionStorage
12
+ class SessionObject
13
+ # called by the Plezi framework to initiate a session with the id requested
14
+ def initialize id
15
+ @filename = File.join Dir.tmpdir, "iodine_#{Iodine::Http.session_token}_#{id}"
16
+ @data ||= {}
17
+ end
18
+ # Get a key from the session data store.
19
+ #
20
+ # Due to different considirations, all keys will be converted to strings, so that `"name" == :name` and `1234 == "1234"`.
21
+ # If you store two keys that evaluate as the same string, they WILL override each other.
22
+ def [] key
23
+ key = key.to_s
24
+ load
25
+ @data[key]
26
+ end
27
+ alias :fetch :[]
39
28
 
40
- # @return [Hash] returns a shallow copy of the current session data as a Hash.
41
- def to_h
42
- load
43
- @data.dup
44
- end
29
+ # Stores a key in the session's data store.
30
+ #
31
+ # Due to different considirations, all keys will be converted to strings, so that `"name" == :name` and `1234 == "1234"`.
32
+ # If you store two keys that evaluate as the same string, they WILL override each other.
33
+ def []= key, value
34
+ key = key.to_s
35
+ load
36
+ @data[key] = value
37
+ save
38
+ value
39
+ end
40
+ alias :store :[]=
45
41
 
46
- # Removes a key from the session's data store.
47
- def delete key
48
- load
49
- ret = @data.delete key
50
- save
51
- ret
52
- end
42
+ # @return [Hash] returns a shallow copy of the current session data as a Hash.
43
+ def to_h
44
+ load
45
+ @data.dup
46
+ end
53
47
 
54
- # Clears the session's data.
55
- def clear
56
- @data.clear
57
- save
58
- nil
59
- end
60
- protected
61
- def save
62
- # save data to tmp-file
63
- IO.write @filename, @data.to_yaml
48
+ # Removes a key from the session's data store.
49
+ def delete key
50
+ load
51
+ ret = @data.delete key
52
+ save
53
+ ret
54
+ end
55
+
56
+ # Clears the session's data.
57
+ def clear
58
+ @data.clear
59
+ save
60
+ nil
61
+ end
62
+ protected
63
+ def save
64
+ # save data to tmp-file
65
+ IO.write @filename, @data.to_yaml
66
+ end
67
+ def load
68
+ @data = YAML.load IO.read(@filename) if File.exists?(@filename)
69
+ end
64
70
  end
65
- def load
66
- @data = YAML.load IO.read(@filename) if File.exists?(@filename)
71
+ def self.fetch key
72
+ SessionObject.new key
67
73
  end
68
74
  end
69
- def self.fetch key
70
- SessionObject.new key
71
- end
72
- end
73
- end
74
- class Http < Iodine::Protocol
75
- module SessionManager
75
+
76
76
  module_function
77
77
  # returns a session object
78
78
  def get id
@@ -89,9 +89,9 @@ module Iodine
89
89
  def storage= session_storage = nil
90
90
  case session_storage
91
91
  when :file, nil
92
- @storage = Iodine::Base::FileSessionStorage
92
+ @storage = Iodine::Http::SessionManager::FileSessionStorage
93
93
  when :mem
94
- @storage = Iodine::Base::MemSessionStorage
94
+ @storage = Iodine::Http::SessionManager::MemSessionStorage
95
95
  else
96
96
  @storage = session_storage
97
97
  end
@@ -99,7 +99,7 @@ module Iodine
99
99
 
100
100
  # returns the current session storage system.
101
101
  def storage
102
- @storage ||= Iodine::Base::FileSessionStorage
102
+ @storage ||= Iodine::Http::SessionManager::FileSessionStorage
103
103
  end
104
104
  end
105
105
  end