iodine 0.0.3 → 0.0.4

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: 75cc90d10afaecdc798bdf39ec5d4a1063431faf
4
- data.tar.gz: da27288f1c90ac8604c07c8f3d2ffb796c09e453
3
+ metadata.gz: cbb450cc38cab2612710d70cff54bc0bea8685b0
4
+ data.tar.gz: 5165d905229eb83143eb0c2fde06a91b8223069d
5
5
  SHA512:
6
- metadata.gz: ce19f80d81def9ad8784648625007cca05a9513d6498dbff030b10a28a43e96ab7c3a54553c275393c2803b9bc5e47b47307ac03b2c1a7fc127646d396eeb017
7
- data.tar.gz: 145b4f0cb2aeec486af8a09d3cf0d91819a7e97449a3bbb8a5a56e7324a2e9c4977a1a8119c52ac780a7b34b338ee8d3e934dd5a48bc0d0da757588b80e1a99a
6
+ metadata.gz: fa8b08cd218f03436b2a82ec5adf5df579108393b51dc5cf199ab11328e55717575102aa7c048d54aaffa2ad2a4f7ba2ed0a2e561630328c783b3fc4e041b704
7
+ data.tar.gz: c1d9d2a99ca9132a82ddb28df1747fdc2f532b033f344373406b466d99eda0055c753deca2bc9e2802d99416abbc00bd6a3c1caa0532e5ed02d0554682743a26
data/README.md CHANGED
@@ -95,10 +95,10 @@ Using Iodine and leveraging Ruby's Object Oriented approach, is super fun to wri
95
95
  # require the 'iodine/http' module if you want to use Iodine's Http server.
96
96
  require 'iodine/http'
97
97
  # returning a string will automatically append it to the response.
98
- Iodine::Http.on_http = { |request, response| "Hello World!" }
98
+ Iodine::Http.on_http { |request, response| "Hello World!" }
99
99
  ```
100
100
 
101
- Iodine's Http server includes experimental support for Http/2 right out of the box as well as a Websocket server.
101
+ Iodine's Http server includes comes right out of the box with Websocket support as well as an experimental support for Http/2 (it's just a start, no `push` support just yet, but you can try it out).
102
102
 
103
103
  Here's a quick chatroom server (use [www.websocket.org](http://www.websocket.org/echo.html) to check it out):
104
104
 
@@ -176,6 +176,61 @@ exit
176
176
 
177
177
  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).
178
178
 
179
+ ## Server Usage: IP address & port, SSL/TLS and other command line options
180
+
181
+ Iodine automatically respects certain command line options that make it easier to use the same script over and over again with different results and making writing a `Procfile` (or similar setup files) a breeze.
182
+
183
+ Let `./script.rb` be an Iodine ruby script, may an easy one such as our Hello World:
184
+
185
+ ```ruby
186
+ #!/usr/bin/env ruby
187
+
188
+ # script.rb
189
+ require 'iodine/http'
190
+
191
+ Iodine::Http.on_http do |request, response|
192
+ response << "Hello World!"
193
+ end
194
+
195
+ ```
196
+
197
+ Here are different command line options that Iodine recognizes automatically when running our script:
198
+
199
+ | purpose | flag | example |
200
+ --------------------------------------------------|:------:|------------------------------------------|
201
+ | Set the server's port. | `-p` | `ruby ./script.rb -p 4000` |
202
+ | Limit the server's binding to a specific IP. | `-ip` | `ruby ./script.rb -p 4000 -ip 127.0.0.1` |
203
+ | Use SSL/TLS on a specific port. | `ssl` | `ruby ./script.rb -p 3030 ssl` |
204
+ | Try out the experimental Http2 extention. | `http2`| `ruby ./script.rb -p 3030 ssl http2` |
205
+
206
+ ## Server Usage: Running more than one server
207
+
208
+ On some machines, Iodine will allow you to run more than a single server, by forking the main process while still running the script. This is more of a hack to be used in development environments, since runnig multiple instances of the script is the prefered way to use Iodine in production.
209
+
210
+ i.e.:
211
+
212
+ ```ruby
213
+ require 'iodine/http'
214
+
215
+ # We'll use a simple hello world with a slight "tweek" for this example.
216
+ Iodine::Http.on_http do |request, response|
217
+ response << "Hello World!"
218
+ response << " We're on SSL/TLS!" if request.ssl?
219
+ end
220
+
221
+ Iodine.ssl = false
222
+
223
+ Process.fork do
224
+ Iodine.ssl = true
225
+ Iodine.port = 3030
226
+ # # we can also change network behavior, so we could have used:
227
+ # Iodine::Http.on_http { "Hello World! We're on SSL/TLS! - no `if` required ;-)" }
228
+ end if Process.respond_to? :fork
229
+
230
+ # if using irb
231
+ exit
232
+ ```
233
+
179
234
  ## Development
180
235
 
181
236
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -184,7 +239,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
184
239
 
185
240
  ## Contributing
186
241
 
187
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/iodine.
242
+ Bug reports and pull requests are welcome on GitHub at https://github.com/boazsegev/iodine.
188
243
 
189
244
 
190
245
  ## License
@@ -12,8 +12,6 @@ require 'stringio'
12
12
  # ab -n 10000 -c 200 -k http://localhost:3000/
13
13
  # ~/ruby/wrk/wrk -c400 -d10 -t12 http://localhost:3000/
14
14
 
15
-
16
-
17
15
  class MiniServer < Iodine::Protocol
18
16
  def on_open
19
17
  @headers = {}
File without changes
@@ -40,14 +40,20 @@ end
40
40
 
41
41
  Iodine::Http.on_websocket WSChatServer
42
42
 
43
+ Iodine::Http.http2 = true
44
+
43
45
  Process.fork do
44
46
 
45
- Iodine.ssl = true
47
+ Iodine.ssl = true
46
48
  Iodine.port = 3030
47
49
  Iodine.protocol.on_http do |req, res|
48
50
  res.session[:count] ||= 0
49
51
  res.session[:count] += 1
50
- res << "Visits: #{res.session[:count]}\n\nRequest object:\n\n#{req.to_s}"
52
+ res['content-type'] = 'text/plain'
53
+ res.cookies['testing'] = !res.cookies['testing']
54
+ res << "Visits: #{res.session[:count]}\n\nRequest object:\n\n#{req.to_s}"
55
+ # puts "Setting :testing cookie was #{ res.cookies[:testing] }, setting to: #{res.cookies[:testing] = !res.cookies[:testing]}"
56
+ # puts "Setting 'testing' cookie was #{ res.cookies['testing'] }, setting to: #{res.cookies['testing'] = !res.cookies['testing']}"
51
57
  end
52
58
 
53
59
  end
data/lib/iodine/http.rb CHANGED
@@ -33,6 +33,7 @@ module Iodine
33
33
  #
34
34
  # require 'iodine/http'
35
35
  # Iodine::Http.on_http { |request, response| 'Hello World!' }
36
+ # exit # only if running from irb
36
37
  #
37
38
  # To start a Websocket server, require `iodine/http` (which isn't required by default), create a Websocket handling Class and set up
38
39
  # your Websocket callback. i.e.:
@@ -97,6 +98,15 @@ module Iodine
97
98
  @session_token
98
99
  end
99
100
 
101
+ # Sets whether Iodine will allow connections to the experiemntal Http2 protocol. Defaults to false unless the `http2` command line flag is present.
102
+ def self.http2= allow
103
+ @http2 = allow && true
104
+ end
105
+ # Returns true if Iodine will require that new connection be encrypted.
106
+ def self.http2
107
+ @http2
108
+ end
109
+
100
110
  # Creates a websocket client within a new task (non-blocking).
101
111
  #
102
112
  # Make sure to setup all the callbacks (as needed) prior to starting the connection. See {::Iodine::Http::WebsocketClient.connect}
@@ -126,6 +136,8 @@ module Iodine
126
136
  ::Iodine.run { ::Iodine::Http::WebsocketClient.connect url, options, &block }
127
137
  end
128
138
 
139
+ @http2 = (ARGV.index('http2') && true)
140
+
129
141
  @websocket_app = @http_app = NOT_IMPLEMENTED = Proc.new { |i,o| false }
130
142
  @session_token = "#{File.basename($0, '.*')}_uuid"
131
143
  end
@@ -133,7 +145,7 @@ module Iodine
133
145
  @queue.tap do |q|
134
146
  arr =[];
135
147
  arr << q.pop until q.empty?;
136
- run { Iodine.ssl_protocols = { 'h2' => Iodine::Http::Http2, 'http/1.1' => Iodine::Http } if @ssl && @ssl_protocols.empty? }
148
+ run { Iodine.ssl_protocols = { 'h2' => Iodine::Http::Http2, 'http/1.1' => Iodine::Http } if @ssl && @ssl_protocols.empty? && ::Iodine::Http.http2 }
137
149
  run do
138
150
  if Iodine.protocol == ::Iodine::Http && ::Iodine::Http.on_http == ::Iodine::Http::NOT_IMPLEMENTED && ::Iodine::Http.on_websocket == ::Iodine::Http::NOT_IMPLEMENTED
139
151
  ::Iodine.protocol = :http_not_initialized
@@ -15,7 +15,7 @@ module Iodine
15
15
  def [] index
16
16
  raise "HPACK Error - invalid header index: 0" if index == 0
17
17
  return STATIC_LIST[index] if index < STATIC_LENGTH
18
- raise "HPACK Error - invalid header index: #{index}" if @list.count <= (index - STATIC_LENGTH)
18
+ raise "HPACK Error - invalid header index: #{index}" if index >= ( @list.count + STATIC_LENGTH )
19
19
  @list[index - STATIC_LENGTH]
20
20
  end
21
21
  alias :get_index :[]
@@ -72,9 +72,9 @@ module Iodine
72
72
  results
73
73
  end
74
74
  def encode headers = {}
75
- buffer = ''
75
+ buffer = ''.force_encoding(::Encoding::ASCII_8BIT)
76
76
  headers.each {|k, v| buffer << encode_field( (k.is_a?(String) ? k : ":#{k.to_s}".freeze) ,v) if v}
77
- buffer
77
+ buffer.force_encoding(::Encoding::ASCII_8BIT)
78
78
  end
79
79
  def resize max
80
80
  @decoding_list.resize max
@@ -121,25 +121,35 @@ module Iodine
121
121
  if value.is_a?(Array)
122
122
  return (value.map {|v| encode_field name, v} .join)
123
123
  end
124
+ raise "Http/2 headers must be LOWERCASE Strings!" if name[0] =~ /[A-Z]/n
125
+ value = value.to_s
124
126
  if name == 'set-cookie'
125
- buffer = ''
126
- buffer << pack_number( 55, 16, 4)
127
+ buffer = ''.force_encoding ::Encoding::ASCII_8BIT
128
+ buffer << pack_number( 55, 1, 4)
127
129
  buffer << pack_string(value)
128
130
  return buffer
129
131
  end
130
132
  index = @encoding_list.find(name, value)
131
133
  return pack_number( index, 1, 1) if index
132
134
  index = @encoding_list.find_name name
133
- @encoding_list.insert name, value
134
- buffer = ''
135
+ buffer = ''.force_encoding(::Encoding::ASCII_8BIT)
135
136
  if index
136
- buffer << pack_number( index, 64, 2)
137
+ buffer << pack_number( index, 1, 2)
137
138
  else
138
- buffer << pack_number( 0, 64, 2)
139
- buffer << pack_string(name.to_s)
139
+ raise "Http/2 headers whould be Strings! or allowed Psedo-Header Symbol Only!" if name[0] == ':'
140
+ buffer << pack_number( 0, 1, 2)
141
+ buffer << pack_string(name)
140
142
  end
141
143
  buffer << pack_string(value)
144
+ @encoding_list.insert name, value
142
145
  buffer
146
+ rescue
147
+ puts "HPACK failure data dump:"
148
+ puts "buffer: #{buffer} - #{buffer.encoding}"
149
+ puts "value: #{value} - #{value.encoding}"
150
+ puts "packed #{pack_string(value)} - #{pack_string(value)}"
151
+ puts "packed #{pack_string(value)} - #{pack_string(value)}"
152
+ raise
143
153
  end
144
154
  def extract_number data, prefix, prefix_length
145
155
  mask = 255 >> prefix_length
@@ -152,13 +162,11 @@ module Iodine
152
162
  count += 1
153
163
  end
154
164
  prefix + mask
155
- # rescue e =>
156
- # raise "HPACK Error - number input invalid"
157
165
  end
158
166
  def pack_number number, prefix, prefix_length
159
167
  n_length = 8-prefix_length
160
168
  if (number + 1 ).bit_length <= n_length
161
- return ((prefix << n_length) | number).chr
169
+ return ((prefix << n_length) | number).chr.force_encoding(::Encoding::ASCII_8BIT)
162
170
  end
163
171
  prefix = [(prefix << n_length) | (2**n_length - 1)]
164
172
  number -= 2**n_length - 1
@@ -167,11 +175,11 @@ module Iodine
167
175
  number = number >> 7
168
176
  break if number == 0
169
177
  end
170
- (prefix << (prefix.pop & 127)).pack('C*'.freeze)
178
+ (prefix << (prefix.pop & 127)).pack('C*'.freeze).force_encoding(::Encoding::ASCII_8BIT)
171
179
  end
172
180
  def pack_string string, deflate = true
173
- string = deflate(string) if deflate
174
- (pack_number(string.bytesize, (deflate ? 1 : 0), 1) + string).force_encoding ::Encoding::ASCII_8BIT
181
+ string = deflate ? deflate(string) : string.dup.force_encoding(::Encoding::ASCII_8BIT)
182
+ (pack_number(string.bytesize, (deflate ? 1 : 0), 1) + string ).force_encoding ::Encoding::ASCII_8BIT
175
183
  end
176
184
  def extract_string data
177
185
  byte = data.getbyte
@@ -201,19 +209,21 @@ module Iodine
201
209
  str
202
210
  end
203
211
  def deflate data
204
- str = ''
205
- buffer = ''
212
+ str = ''.force_encoding ::Encoding::ASCII_8BIT
213
+ buffer = ''.force_encoding ::Encoding::ASCII_8BIT
206
214
  data.bytes.each do |i|
207
215
  buffer << HUFFMAN.key(i)
208
- if (buffer % 8) == 0
209
- str << [buffer].pack('b*')
216
+ if (buffer.bytesize % 8) == 0
217
+ str << [buffer].pack('B*'.freeze)
210
218
  buffer.clear
211
219
  end
212
220
  end
213
- (8-(buffer.bytesize % 8)).times { buffer << '1'}
214
- str << [buffer].pack('b*')
215
- buffer.clear
216
- str
221
+ unless buffer.empty?
222
+ (8-(buffer.bytesize % 8)).times { buffer << '1'} if (buffer.bytesize % 8)
223
+ str << [buffer].pack('B*'.freeze)
224
+ buffer.clear
225
+ end
226
+ str.force_encoding ::Encoding::ASCII_8BIT
217
227
  end
218
228
  STATIC_LIST = [ nil,
219
229
  [":authority"],
@@ -8,7 +8,7 @@ module Iodine
8
8
  end
9
9
  def on_message data
10
10
  return if @refuse_requests
11
- @http2_pri_review ||= ( ::Iodine::Http::Http2.pre_handshake(self, data) && (return true) ) || true
11
+ @http2_pri_review ||= ( ::Iodine::Http.http2 && ::Iodine::Http::Http2.pre_handshake(self, data) && (return true) ) || true
12
12
 
13
13
  data = ::StringIO.new data
14
14
  until data.eof?
@@ -70,7 +70,7 @@ module Iodine
70
70
  request[:body_complete] = true
71
71
  end
72
72
  end
73
- (@request = ::Iodine::Http::Request.new(self)) && ( ::Iodine::Http::Http2.handshake(request, self, data) || dispatch(request, data) ) if request.delete :body_complete
73
+ (@request = ::Iodine::Http::Request.new(self)) && ( (::Iodine::Http.http2 && ::Iodine::Http::Http2.handshake(request, self, data)) || dispatch(request, data) ) if request.delete :body_complete
74
74
  end
75
75
  end
76
76
 
@@ -160,21 +160,6 @@ module Iodine
160
160
 
161
161
  def send_headers response
162
162
  return false if response.headers.frozen?
163
- # remove old flash cookies
164
- response.cookies.keys.each do |k|
165
- if k.to_s.start_with? 'magic_flash_'.freeze
166
- response.set_cookie k, nil
167
- flash.delete k
168
- end
169
- end
170
- #set new flash cookies
171
- response.flash.each do |k,v|
172
- response.set_cookie "magic_flash_#{k.to_s}", v
173
- end
174
- response.raw_cookies.freeze
175
- # response.cookies.set_response nil
176
- response.flash.freeze
177
-
178
163
  request = response.request
179
164
  headers = response.headers
180
165
 
@@ -187,7 +172,7 @@ module Iodine
187
172
  # unless @headers['connection'] || (@request[:version].to_f <= 1 && (@request['connection'].nil? || !@request['connection'].match(/^k/i))) || (@request['connection'] && @request['connection'].match(/^c/i))
188
173
  headers.each {|k,v| out << "#{k.to_s}: #{v}\r\n"}
189
174
  out << "Cache-Control: max-age=0, no-cache\r\n".freeze unless headers['cache-control'.freeze]
190
- response.raw_cookies.each {|k,v| out << "Set-Cookie: #{k.to_s}=#{v.to_s}\r\n"}
175
+ response.extract_cookies.each {|cookie| out << "Set-Cookie: #{cookie}\r\n"}
191
176
  out << "\r\n"
192
177
 
193
178
  response.bytes_written += (write(out) || 0)
@@ -4,14 +4,19 @@ module Iodine
4
4
  def initialize io, original_request = nil
5
5
  super(io)
6
6
  return unless original_request
7
- original_request[:stream_id] = 1
7
+ ::Iodine.warn "Http/2: upgrade handshake settings not implemented. upgrade request:\n#{original_request}"
8
+ @last_stream = original_request[:stream_id] = 1
8
9
  original_request[:io] = self
9
- process_request original_request
10
+ # deal with the request['http2-settings'] - NO ACK
11
+ # HTTP2-Settings: <base64url encoding of HTTP/2 SETTINGS payload>
12
+
13
+ # dispatch the original request
14
+ ::Iodine.run original_request, &(::Iodine::Http::Http2.dispatch)
10
15
  end
11
16
  def on_open
12
- # do stuff, i.e. related to the header:
13
- # HTTP2-Settings: <base64url encoding of HTTP/2 SETTINGS payload>
14
- ::Iodine.warn "HTTP/2 requested - support is still experimental."
17
+ # not fully fanctional.
18
+ ::Iodine.warn "Http/2 requested - support is still experimental."
19
+
15
20
  # update the timeout to 15 seconds (ping will be sent whenever timeout is reached).
16
21
  set_timeout 15
17
22
 
@@ -22,6 +27,7 @@ module Iodine
22
27
  @header_buffer = ''
23
28
  @header_end_stream = false
24
29
  @header_sid = nil
30
+ @frame_locker = Mutex.new
25
31
 
26
32
  # frame parser starting posotion
27
33
  @frame = {}
@@ -55,26 +61,17 @@ module Iodine
55
61
  end
56
62
 
57
63
  def send_response response
58
- return false if response.headers.frozen?
59
-
60
64
  request = response.request
61
- headers = response.headers
65
+ return false unless send_headers response, request
66
+ return nil if request.head?
62
67
  body = response.extract_body
63
- headers[:status] = response.status.to_s
64
- emit_payload @hpack.encode(headers), request[:sid], 1, (request.head? ? 1 : 0)
65
- headers.freeze
66
68
  return (log_finished(response) && body.clear) if request.head?
67
- (response.bytes_written += emit_payload(body, request[:sid], 0, 1) ) && (body.frozen? || body.clear) if body
69
+ (response.bytes_written += emit_payload(body.to_s, request[:sid], 0, 1) ) && (body.frozen? || body.clear) if body
68
70
  log_finished response
69
71
  end
70
72
  def stream_response response, finish = false
71
73
  request = response.request
72
- headers = response.headers
73
- unless response.headers.frozen? # send headers
74
- headers[:status] = response.status.to_s
75
- emit_payload @hpack.encode(headers), request[:sid], 1, (request.head? ? 1 : 0)
76
- headers.freeze
77
- end
74
+ send_headers response, request
78
75
  return nil if request.head?
79
76
  body = response.extract_body
80
77
  # puts "should stream #{body}"
@@ -83,7 +80,7 @@ module Iodine
83
80
  end
84
81
 
85
82
  def ping
86
- emit_frame "pniodine", 0, 6
83
+ @frame_locker.synchronize { emit_frame "pniodine", 0, 6 }
87
84
  end
88
85
 
89
86
  def push request
@@ -97,7 +94,7 @@ module Iodine
97
94
 
98
95
  def go_away error_code
99
96
  return false if @io.closed?
100
- emit_frame [@last_stream, error_code].pack('N*'), 0, 7
97
+ @frame_locker.synchronize { emit_frame [@last_stream, error_code].pack('N*'), 0, 7 }
101
98
  close
102
99
  # Iodine.info "HTTP/2 connection closed with code #{error_code}"
103
100
  end
@@ -134,12 +131,22 @@ module Iodine
134
131
  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
135
132
  end
136
133
 
134
+ def send_headers response, request
135
+ headers = response.headers
136
+ return false if headers.frozen?
137
+ # headers[:status] = response.status.to_s
138
+ headers['set-cookie'] = response.extract_cookies
139
+ headers.freeze
140
+ emit_payload (@hpack.encode(status: response.status.to_s) + @hpack.encode(headers)), request[:sid], 1, (request.head? ? 1 : 0)
141
+ return true
142
+ end
143
+
137
144
  # Sends an HTTP frame with the requested payload
138
145
  #
139
146
  # @return [true, false] returns true if the frame was sent and false if the frame couldn't be sent (i.e. payload too big, connection closed etc').
140
147
  def emit_frame payload, sid = 0, type = 0, flags = 0
141
148
  # puts "Sent: #{[payload.bytesize, type, flags, sid, payload].pack('N C C N a*'.freeze)[1..-1].inspect}"
142
- @io.write( [payload.bytesize, type, flags, sid, payload].pack('N C C N a*'.freeze)[1..-1] )
149
+ @io.write( [payload.bytesize, type, flags, sid, payload].pack('N C C N a*'.freeze)[1..-1] ) #.tap {|s| next if type !=1 ;puts "Frame: #{s.class.name} - #{s.encoding}"; puts s.inspect } )
143
150
  end
144
151
 
145
152
  # Sends an HTTP frame group with the requested payload. This means the group will not be interrupted and will be sent as one unit.
@@ -148,17 +155,19 @@ module Iodine
148
155
  def emit_payload payload, sid = 0, type = 0, flags = 0
149
156
  max_frame_size = @settings[SETTINGS_MAX_FRAME_SIZE]
150
157
  max_frame_size = 131_072 if max_frame_size > 131_072
151
- return emit_frame(payload, sid, type, ( type == 0x1 ? (flags | 0x4) : flags ) ) if payload.bytesize <= max_frame_size
158
+ return @frame_locker.synchronize { emit_frame(payload, sid, type, ( (type == 0x1 || type == 0x5) ? (flags | 0x4) : flags ) ) } if payload.bytesize <= max_frame_size
152
159
  sent = 0
153
160
  payload = StringIO.new payload
154
- if type == 0x1
155
- sent += emit_frame(payload.read(max_frame_size), sid, 0x1, flags & 254)
156
- sent += emit_frame(payload.read(max_frame_size), sid, 0x9, 0) while payload.size - payload.pos > max_frame_size
157
- sent += emit_frame(payload.read(max_frame_size), sid, 0x9, (0x4 | (flags & 0x1)) )
161
+ if type == 0x1 || type == 0x5
162
+ @frame_locker.synchronize do
163
+ sent += emit_frame(payload.read(max_frame_size), sid, 0x1, flags & 254)
164
+ sent += emit_frame(payload.read(max_frame_size), sid, 0x9, 0) while payload.size - payload.pos > max_frame_size
165
+ sent += emit_frame(payload.read(max_frame_size), sid, 0x9, (0x4 | (flags & 0x1)) )
166
+ end
158
167
  return sent
159
168
  end
160
- sent += emit_frame(payload.read(max_frame_size), sid, type, (flags & 254)) while payload.size - payload.pos > max_frame_size
161
- sent += emit_frame(payload.read(max_frame_size), sid, type, flags)
169
+ sent += @frame_locker.synchronize { emit_frame(payload.read(max_frame_size), sid, type, (flags & 254)) } while payload.size - payload.pos > max_frame_size
170
+ sent += @frame_locker.synchronize { emit_frame(payload.read(max_frame_size), sid, type, flags) }
162
171
  end
163
172
 
164
173
  def parse_preface data
@@ -230,8 +239,8 @@ module Iodine
230
239
  process_ping frame
231
240
  when 7 # GOAWAY
232
241
  go_away NO_ERROR
242
+ Iodine.error "Http2 Disconnection with error (#{frame[:flags].to_s}): #{frame[:body].strip}" unless frame[:flags] == 0 && frame[:body] == ''
233
243
  when 8 # WINDOW_UPDATE
234
- when 9 #
235
244
  else # Error, frame not recognized
236
245
  end
237
246
 
@@ -294,7 +303,9 @@ module Iodine
294
303
  @header_buffer.clear
295
304
  @header_end_stream = false
296
305
  @header_sid = nil
297
-
306
+ rescue => e
307
+ connection_error 5
308
+ Iodine.warn e
298
309
  end
299
310
  def process_data frame
300
311
  if frame[:flags][3] == 1 # padded
@@ -340,10 +351,10 @@ module Iodine
340
351
  return connection_error PROTOCOL_ERROR unless frame[:sid] == 0 && (frame[:body].bytesize % 6) == 0
341
352
  settings = StringIO.new frame[:body]
342
353
  until settings.eof?
343
- key = settings.read(2).unpack('n')[0]
344
- value = settings.read(4).unpack('N')[0]
354
+ key = settings.read(2).unpack('n'.freeze)[0]
355
+ value = settings.read(4).unpack('N'.freeze)[0]
345
356
  Iodine.info "HTTP/2 set #{key}=>#{value} for SID #{frame[:sid]}"
346
- case frame[:body][0..1].unpack('n')[0]
357
+ case frame[:body][0..1].unpack('n'.freeze)[0]
347
358
  when SETTINGS_HEADER_TABLE_SIZE
348
359
  return connection_error ENHANCE_YOUR_CALM if value > 4096
349
360
  @hpack.resize(value)
@@ -441,7 +452,6 @@ module Iodine
441
452
  response = ::Iodine::Http::Response.new request
442
453
  response[:Allow] = 'GET,HEAD,POST,PUT,DELETE,OPTIONS'.freeze
443
454
  response['access-control-allow-origin'.freeze] = '*'
444
- response['content-length'.freeze] = 0
445
455
  response.finish
446
456
  return false
447
457
  end
@@ -456,7 +466,8 @@ module Iodine
456
466
  response.finish
457
467
  rescue => e
458
468
  ::Iodine.error e
459
- ::Iodine::Http::Response.new(request, 500, {}).finish
469
+ # request[:io].emit_frame [INTERNAL_ERROR].pack('N'.freeze), request[:sid], 3
470
+ ::Iodine::Http::Response.new(request, 500, {}, ::Iodine::Http::Response::STATUS_CODES[500]).finish
460
471
  end
461
472
  end
462
473
  end
@@ -46,6 +46,8 @@ module Iodine
46
46
  env['HTTP_VERSION'.freeze] = "HTTP/#{request[:version].to_s}"
47
47
  env['QUERY_STRING'.freeze] ||= ''
48
48
  env['rack.errors'.freeze] = StringIO.new('')
49
+ # should unchain cookies from Array to String
50
+ env['HTTP_COOKIE'] = env['HTTP_COOKIE'].join '; ' if env['HTTP_COOKIE'].is_a?(Array)
49
51
  env
50
52
  end
51
53
 
@@ -28,7 +28,16 @@ module Iodine
28
28
  elsif self.has_key?( key.to_s.to_sym)
29
29
  key = key.to_s.to_sym
30
30
  end
31
- @response.set_cookie key, (val ? val.to_s.dup : nil)
31
+ @response.set_cookie key, (val.nil? ? nil : val.to_s.dup)
32
+ super
33
+ end
34
+ # overrides th [] method to allow Symbols and Strings to mix and match
35
+ def [] key
36
+ if key.is_a?(Symbol) && self.has_key?( key.to_s)
37
+ key = key.to_s
38
+ elsif self.has_key?( key.to_s.to_sym)
39
+ key = key.to_s.to_sym
40
+ end
32
41
  super
33
42
  end
34
43
  end
@@ -118,7 +127,6 @@ module Iodine
118
127
  self[:session]
119
128
  end
120
129
 
121
-
122
130
  # method recognition
123
131
 
124
132
  HTTP_GET = 'GET'.freeze
@@ -203,9 +203,12 @@ module Iodine
203
203
  raise 'Cannot set cookies after the headers had been sent.' if headers_sent?
204
204
  name = name.to_s
205
205
  raise 'Illegal cookie name' if name =~ COOKIE_NAME_REGEXP
206
- params[:expires] = (Time.now - 315360000) unless value
207
- value ||= 'deleted'.freeze
208
- params[:expires] ||= (Time.now + 315360000) unless params[:max_age]
206
+ if value.nil?
207
+ params[:expires] = (Time.now - 315360000)
208
+ value = 'deleted'.freeze
209
+ else
210
+ params[:expires] ||= (Time.now + 315360000) unless params[:max_age]
211
+ end
209
212
  params[:path] ||= '/'.freeze
210
213
  value = Iodine::Http::Request.encode_url(value)
211
214
  if params[:max_age]
@@ -328,6 +331,29 @@ module Iodine
328
331
  end
329
332
  end
330
333
 
334
+ # This will return an array of cookie settings to be appended to `set-cookie` headers.
335
+ def extract_cookies
336
+ unless @cookies.frozen?
337
+ # remove old flash cookies
338
+ @request.cookies.keys.each do |k|
339
+ if k.to_s.start_with? 'magic_flash_'.freeze
340
+ set_cookie k, nil
341
+ flash.delete k
342
+ end
343
+ end
344
+ #set new flash cookies
345
+ @flash.each do |k,v|
346
+ set_cookie "magic_flash_#{k.to_s}", v
347
+ end
348
+ @cookies.freeze
349
+ # response.cookies.set_response nil
350
+ @flash.freeze
351
+ end
352
+ [].tap do |arr|
353
+ @cookies.each {|k, v| arr << "#{k.to_s}=#{v.to_s}"}
354
+ end
355
+ end
356
+
331
357
  protected
332
358
 
333
359
  # Sets the http streaming flag and sends the responses headers, so that the response could be handled asynchronously.
@@ -10,7 +10,6 @@ module Iodine
10
10
  end
11
11
  # continue to initialize the websocket protocol.
12
12
  def on_open
13
- set_timeout 45
14
13
  @parser = {body: '', stage: 0, step: 0, mask_key: [], len_bytes: []}
15
14
  set_timeout = self.class.default_timeout
16
15
  @handler.on_open if @handler.respond_to? :on_open
@@ -58,7 +58,7 @@ module Iodine
58
58
  ## functionality and helpers
59
59
 
60
60
 
61
- # returns true id the protocol is using an encrypted connection (the IO is an OpenSSL::SSL::SSLSocket).
61
+ # returns true if the protocol is using an encrypted connection (the IO is an OpenSSL::SSL::SSLSocket).
62
62
  def ssl?
63
63
  @io.is_a?(OpenSSL::SSL::SSLSocket) # io.npn_protocol
64
64
  end
@@ -109,7 +109,7 @@ module Iodine
109
109
  @id ||= @io.to_io.object_id.to_s(16)
110
110
  end
111
111
 
112
- # returns an [Enumerable](http://ruby-doc.org/core-2.2.3/Enumerable.html) with all the active connections.
112
+ # @return [Enumerable] returns an Enumerable with all the active connections (instances of THIS Protocol or it's children).
113
113
  #
114
114
  # if a block is passed, than this method exceutes the block.
115
115
  def self.each
@@ -23,14 +23,22 @@ module Iodine
23
23
  @spawn_count = count
24
24
  end
25
25
 
26
- # Sets the server port. Defaults to the runtime `-p` argument, or the ENV['PORT'] or 3000 (in this order).
26
+ # Sets Iodine's server port. Defaults to the command line `-p` argument, or the ENV['PORT'] or 3000 (in this order).
27
27
  def port= port
28
28
  @port = port
29
29
  end
30
- # Sets the IP binding address. Defaults to the runtime `-ip` argument, or the ENV['IP'] or 0.0.0.0 (in this order).
30
+ # Gets Iodine's server port. Defaults to the command line `-p` argument, or the ENV['PORT'] or 3000 (in this order).
31
+ def port
32
+ @port
33
+ end
34
+ # Sets the IP binding address. Defaults to the command line `-ip` argument, or the ENV['IP'] or 0.0.0.0 (in this order).
31
35
  def bind= address
32
36
  @bind = address
33
37
  end
38
+ # Gets the IP binding address. Defaults to the command line `-ip` argument, or the ENV['IP'] or 0.0.0.0 (in this order).
39
+ def bind
40
+ @bind
41
+ end
34
42
 
35
43
  # Sets the Protocol the Iodine Server will use. Should be a child of {Iodine::Protocol}. Defaults to nil (no server).
36
44
  #
@@ -45,6 +53,7 @@ module Iodine
45
53
  end
46
54
 
47
55
  # Sets the SSL flag, so that Iodine will require that new connection be encrypted.
56
+ # Defaults to false unless the `ssl` command line flag is present.
48
57
  def ssl= required
49
58
  @ssl = required && true
50
59
  end
@@ -1,3 +1,3 @@
1
1
  module Iodine
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.4"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: iodine
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Boaz Segev
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2015-10-20 00:00:00.000000000 Z
11
+ date: 2015-10-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -66,6 +66,10 @@ files:
66
66
  - README.md
67
67
  - Rakefile
68
68
  - bin/console
69
+ - bin/core_http_test
70
+ - bin/echo
71
+ - bin/em playground
72
+ - bin/hello_world
69
73
  - bin/setup
70
74
  - iodine.gemspec
71
75
  - lib/iodine.rb
@@ -89,10 +93,6 @@ files:
89
93
  - lib/iodine/timers.rb
90
94
  - lib/iodine/version.rb
91
95
  - lib/rack/handler/iodine.rb
92
- - manual tests/core_http_test
93
- - manual tests/echo
94
- - manual tests/em playground
95
- - manual tests/hello_world
96
96
  homepage: https://github.com/boazsegev/iodine
97
97
  licenses:
98
98
  - MIT