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 +4 -4
- data/README.md +58 -3
- data/{manual tests/core_http_test → bin/core_http_test} +0 -2
- data/{manual tests → bin}/echo +0 -0
- data/manual tests/em playground b/data/bin/em → playground +0 -0
- data/{manual tests/hello_world → bin/hello_world} +8 -2
- data/lib/iodine/http.rb +13 -1
- data/lib/iodine/http/hpack.rb +34 -24
- data/lib/iodine/http/http1.rb +3 -18
- data/lib/iodine/http/http2.rb +46 -35
- data/lib/iodine/http/rack_support.rb +2 -0
- data/lib/iodine/http/request.rb +10 -2
- data/lib/iodine/http/response.rb +29 -3
- data/lib/iodine/http/websockets.rb +0 -1
- data/lib/iodine/protocol.rb +2 -2
- data/lib/iodine/settings.rb +11 -2
- data/lib/iodine/version.rb +1 -1
- metadata +6 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cbb450cc38cab2612710d70cff54bc0bea8685b0
|
4
|
+
data.tar.gz: 5165d905229eb83143eb0c2fde06a91b8223069d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
98
|
+
Iodine::Http.on_http { |request, response| "Hello World!" }
|
99
99
|
```
|
100
100
|
|
101
|
-
Iodine's Http server includes
|
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/
|
242
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/boazsegev/iodine.
|
188
243
|
|
189
244
|
|
190
245
|
## License
|
data/{manual tests → bin}/echo
RENAMED
File without changes
|
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
|
-
|
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
|
-
|
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
|
data/lib/iodine/http/hpack.rb
CHANGED
@@ -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
|
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,
|
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
|
-
|
134
|
-
buffer = ''
|
135
|
+
buffer = ''.force_encoding(::Encoding::ASCII_8BIT)
|
135
136
|
if index
|
136
|
-
buffer << pack_number( index,
|
137
|
+
buffer << pack_number( index, 1, 2)
|
137
138
|
else
|
138
|
-
|
139
|
-
buffer <<
|
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)
|
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('
|
216
|
+
if (buffer.bytesize % 8) == 0
|
217
|
+
str << [buffer].pack('B*'.freeze)
|
210
218
|
buffer.clear
|
211
219
|
end
|
212
220
|
end
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
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"],
|
data/lib/iodine/http/http1.rb
CHANGED
@@ -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.
|
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)
|
data/lib/iodine/http/http2.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
#
|
13
|
-
|
14
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
156
|
-
|
157
|
-
|
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
|
-
|
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
|
|
data/lib/iodine/http/request.rb
CHANGED
@@ -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
|
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
|
data/lib/iodine/http/response.rb
CHANGED
@@ -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
|
-
|
207
|
-
|
208
|
-
|
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
|
data/lib/iodine/protocol.rb
CHANGED
@@ -58,7 +58,7 @@ module Iodine
|
|
58
58
|
## functionality and helpers
|
59
59
|
|
60
60
|
|
61
|
-
# returns true
|
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
|
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
|
data/lib/iodine/settings.rb
CHANGED
@@ -23,14 +23,22 @@ module Iodine
|
|
23
23
|
@spawn_count = count
|
24
24
|
end
|
25
25
|
|
26
|
-
# Sets
|
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
|
-
#
|
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
|
data/lib/iodine/version.rb
CHANGED
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.
|
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-
|
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
|