iodine 0.1.21 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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/bin/hello_world
DELETED
@@ -1,75 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
require 'pathname'
|
4
|
-
Root ||= Pathname.new(File.dirname(__FILE__)).expand_path
|
5
|
-
Dir.chdir Root.join('..').to_s
|
6
|
-
|
7
|
-
require "bundler/setup"
|
8
|
-
require "iodine/http"
|
9
|
-
|
10
|
-
|
11
|
-
# ~/ruby/wrk/wrk -c400 -d10 -t12 http://localhost:3000/
|
12
|
-
|
13
|
-
|
14
|
-
# Iodine.ssl = true
|
15
|
-
# Iodine.treads = 8
|
16
|
-
# Iodine.protocol.on_http do |req, res|
|
17
|
-
# res.session[:count] ||= 0
|
18
|
-
# res.session[:count] += 1
|
19
|
-
# res << "Visits: #{res.session[:count]}\n\nRequest object:\n\n#{req.to_s}"
|
20
|
-
# end
|
21
|
-
|
22
|
-
# Iodine.processes = 4
|
23
|
-
|
24
|
-
HELLO = "Hello World!"
|
25
|
-
Iodine::Http.on_http { HELLO }
|
26
|
-
|
27
|
-
Iodine.logger = nil
|
28
|
-
|
29
|
-
|
30
|
-
class WSChatServer < Iodine::Http::WebsocketHandler
|
31
|
-
def on_open
|
32
|
-
@nickname = request.params[:nickname] || "unknown"
|
33
|
-
broadcast "#{@nickname} has joined the chat!"
|
34
|
-
write "Welcome #{@nickname}, you have joined the chat!"
|
35
|
-
end
|
36
|
-
def on_message data
|
37
|
-
broadcast "#{@nickname} >> #{data}"
|
38
|
-
write ">> #{data}"
|
39
|
-
end
|
40
|
-
def on_broadcast data
|
41
|
-
write data
|
42
|
-
end
|
43
|
-
def on_close
|
44
|
-
broadcast "#{@nickname} has left the chat!"
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
Iodine::Http.on_websocket WSChatServer
|
49
|
-
|
50
|
-
Iodine::Http.http2 = true
|
51
|
-
|
52
|
-
Process.fork do
|
53
|
-
|
54
|
-
Iodine.ssl = true
|
55
|
-
Iodine.port = 3030
|
56
|
-
Iodine::Http.on_http do |req, res|
|
57
|
-
if req.path == '/stream'
|
58
|
-
res.stream_async { sleep 0.2; res << "I was sleeping..."}
|
59
|
-
next
|
60
|
-
end
|
61
|
-
if req.path == '/stream2'
|
62
|
-
res.stream_async { sleep 2; res << "I was sleeping..."}
|
63
|
-
next
|
64
|
-
end
|
65
|
-
res << "Old session visits: #{res.session_old[:count]}\n\n" if res.session_old
|
66
|
-
res.session[:count] ||= 0
|
67
|
-
res.session[:count] += 1
|
68
|
-
res['content-type'] = 'text/plain'
|
69
|
-
res.cookies['testing'] = !res.cookies['testing']
|
70
|
-
res << "Visits: #{res.session[:count]}\n\nRequest object:\n\n#{req.to_s}"
|
71
|
-
# puts "Setting :testing cookie was #{ res.cookies[:testing] }, setting to: #{res.cookies[:testing] = !res.cookies[:testing]}"
|
72
|
-
# puts "Setting 'testing' cookie was #{ res.cookies['testing'] }, setting to: #{res.cookies['testing'] = !res.cookies['testing']}"
|
73
|
-
end
|
74
|
-
|
75
|
-
end
|
data/bin/setup
DELETED
data/lib/iodine/client.rb
DELETED
data/lib/iodine/core.rb
DELETED
@@ -1,102 +0,0 @@
|
|
1
|
-
module Iodine
|
2
|
-
public
|
3
|
-
|
4
|
-
#######################
|
5
|
-
## Events
|
6
|
-
|
7
|
-
# Accepts a block and runs it asynchronously. This method runs asynchronously and returns immediately.
|
8
|
-
#
|
9
|
-
# use:
|
10
|
-
#
|
11
|
-
# Iodine.run(arg1, arg2, arg3 ...) { |arg1, arg2, arg3...| do_something }
|
12
|
-
#
|
13
|
-
# the block will be run within the current context, allowing access to current methods and variables.
|
14
|
-
#
|
15
|
-
# @return [Iodine] always returns the reactor object.
|
16
|
-
def run *args, &block
|
17
|
-
@queue << [block, args]
|
18
|
-
self
|
19
|
-
end
|
20
|
-
alias :run_async :run
|
21
|
-
|
22
|
-
# @return [Iodine] Adds a shutdown tasks. These tasks should be executed in order of creation.
|
23
|
-
def on_shutdown *args, &block
|
24
|
-
@shutdown_queue << [block, args]
|
25
|
-
self
|
26
|
-
end
|
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 cause Iodine's exit sequence to end abruptly, depending how the hosting application behaves.
|
35
|
-
#
|
36
|
-
# calling this method repeatedly will be ignored unless Iodine's threads have all died.
|
37
|
-
#
|
38
|
-
# @return [Iodine] the method will allways return `self` (Iodine).
|
39
|
-
def force_start!
|
40
|
-
return self if @force_running
|
41
|
-
@force_running = true
|
42
|
-
thread = Thread.new do
|
43
|
-
startup true
|
44
|
-
@force_running = false
|
45
|
-
initialize_tasks
|
46
|
-
@stop = false if @protocol
|
47
|
-
end
|
48
|
-
Kernel.at_exit {thread.raise("stop"); thread.join}
|
49
|
-
self
|
50
|
-
end
|
51
|
-
|
52
|
-
protected
|
53
|
-
|
54
|
-
def cycle
|
55
|
-
work until @stop && @queue.empty?
|
56
|
-
@shutdown_mutex.synchronize { shutdown }
|
57
|
-
work until @queue.empty?
|
58
|
-
run { true }
|
59
|
-
end
|
60
|
-
|
61
|
-
def work
|
62
|
-
job = @queue && @queue.pop
|
63
|
-
if job && job[0]
|
64
|
-
begin
|
65
|
-
job[0].call(*job[1])
|
66
|
-
rescue => e
|
67
|
-
error e
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
def startup use_rescue = false, hide_message = false
|
73
|
-
@force_running = true
|
74
|
-
threads = []
|
75
|
-
(@thread_count ||= 1).times { threads << Thread.new { Thread.current[:buffer] ||= String.new; Thread.current[:write_buffer] ||= String.new; cycle } }
|
76
|
-
unless @stop
|
77
|
-
if use_rescue
|
78
|
-
sleep rescue true
|
79
|
-
else
|
80
|
-
old_int_trap = trap('INT') { throw :stop; old_int_trap.respond_to?(:call) && old_int_trap.call }
|
81
|
-
old_term_trap = trap('TERM') { throw :stop; old_term_trap.respond_to?(:call) && old_term_trap.call }
|
82
|
-
catch(:stop) { sleep }
|
83
|
-
end
|
84
|
-
log "\nShutting down #{self == Iodine ? 'Iodine' : "#{self.name} (Iodine)"}. Setting shutdown timeout to 25 seconds.\n" unless hide_message
|
85
|
-
@stop = true
|
86
|
-
# setup exit timeout.
|
87
|
-
threads.each {|t| Thread.new {sleep 25; t.kill; t.kill } }
|
88
|
-
end
|
89
|
-
threads.each {|t| t.join rescue true }
|
90
|
-
end
|
91
|
-
|
92
|
-
# performed once - the shutdown sequence.
|
93
|
-
def shutdown
|
94
|
-
return if @done
|
95
|
-
@stop = @done = true
|
96
|
-
arr = []
|
97
|
-
arr.push @shutdown_queue.pop until @shutdown_queue.empty?
|
98
|
-
@queue.push arr.pop while arr[0]
|
99
|
-
@thread_count.times { run { true } }
|
100
|
-
end
|
101
|
-
|
102
|
-
end
|
data/lib/iodine/core_init.rb
DELETED
@@ -1,143 +0,0 @@
|
|
1
|
-
module Iodine
|
2
|
-
protected
|
3
|
-
|
4
|
-
def extended base
|
5
|
-
base.instance_exec do
|
6
|
-
initialize_core
|
7
|
-
initialize_io
|
8
|
-
initialize_timers
|
9
|
-
initialize_tasks
|
10
|
-
end
|
11
|
-
base.protocol = :cycle unless base == Iodine
|
12
|
-
end
|
13
|
-
|
14
|
-
def initialize_core
|
15
|
-
# initializes all the core components
|
16
|
-
# referenced in `core.rb`
|
17
|
-
@queue = Queue.new
|
18
|
-
@shutdown_queue = Queue.new
|
19
|
-
@stop = true
|
20
|
-
@done = false
|
21
|
-
@logger = Logger.new(STDOUT)
|
22
|
-
@spawn_count = 1
|
23
|
-
@thread_count = nil
|
24
|
-
@ios = {}
|
25
|
-
@io_in = Queue.new
|
26
|
-
@io_out = Queue.new
|
27
|
-
@shutdown_mutex = Mutex.new
|
28
|
-
@servers = {}
|
29
|
-
Kernel.at_exit do
|
30
|
-
if self == Iodine
|
31
|
-
startup
|
32
|
-
else
|
33
|
-
Iodine.protocol ||= :cycle if @timers.any? || @protocol
|
34
|
-
thread = Thread.new { startup true }
|
35
|
-
Iodine.on_shutdown { thread.raise 'stop' ; thread.join }
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
def initialize_io
|
41
|
-
# initializes all the IO components
|
42
|
-
# referenced in `io.rb`
|
43
|
-
@port = ((ARGV.index('-p') && ARGV[ARGV.index('-p') + 1]) || ENV['PORT'] || 3000).to_i
|
44
|
-
@bind = (ARGV.index('-ip') && ARGV[ARGV.index('-ip') + 1]) || ENV['IP'] || "0.0.0.0"
|
45
|
-
@ssl = (ARGV.index('ssl') && true) || (@port == 443)
|
46
|
-
@protocol = nil
|
47
|
-
@ssl_context = nil
|
48
|
-
@ssl_protocols = {}
|
49
|
-
@time = Time.now
|
50
|
-
|
51
|
-
@timeout_proc = Proc.new {|prot| prot.timeout?(@time) }
|
52
|
-
@status_loop = Proc.new {|io| @io_out << io if io.closed? || !(io.stat.readable? rescue false) }
|
53
|
-
@close_callback = Proc.new {|prot| prot.on_close if prot }
|
54
|
-
@reactor = [ (Proc.new do
|
55
|
-
@ios.keys.each(&@status_loop)
|
56
|
-
@ios.values.each(&@timeout_proc)
|
57
|
-
until @io_in.empty?
|
58
|
-
n_io = @io_in.pop
|
59
|
-
@ios[n_io[0]] = n_io[1]
|
60
|
-
end
|
61
|
-
until @io_out.empty?
|
62
|
-
o_io = @io_out.pop
|
63
|
-
o_io.close unless o_io.closed?
|
64
|
-
run @ios.delete(o_io), &@close_callback
|
65
|
-
end
|
66
|
-
if @queue.empty?
|
67
|
-
#clear any closed IO objects.
|
68
|
-
@time = Time.now
|
69
|
-
# react to IO events
|
70
|
-
begin
|
71
|
-
r = IO.select(@ios.keys, nil, nil, 0.15)
|
72
|
-
r[0].each {|io| @queue << [@ios[io]] } if r
|
73
|
-
rescue
|
74
|
-
|
75
|
-
end
|
76
|
-
unless @stop && @queue.empty?
|
77
|
-
# @ios.values.each &@timeout_loop
|
78
|
-
@check_timers && (@queue << @check_timers)
|
79
|
-
@queue << @reactor
|
80
|
-
end
|
81
|
-
else
|
82
|
-
@queue << @reactor
|
83
|
-
end
|
84
|
-
end )]
|
85
|
-
end
|
86
|
-
|
87
|
-
def initialize_timers
|
88
|
-
@timer_locker = Mutex.new
|
89
|
-
@timers = []
|
90
|
-
# cycles through timed jobs, executing and/or deleting them if their time has come.
|
91
|
-
@check_timers = [(Proc.new do
|
92
|
-
@timer_locker.synchronize { @timers.delete_if {|t| t.done? } }
|
93
|
-
end)]
|
94
|
-
end
|
95
|
-
|
96
|
-
def initialize_tasks
|
97
|
-
# initializes actions to be taken when starting to run
|
98
|
-
run do
|
99
|
-
@protocol ||= :cycle if @timers.any?
|
100
|
-
next unless @protocol
|
101
|
-
if @protocol.is_a?( ::Class ) && @protocol.ancestors.include?( ::Iodine::Protocol )
|
102
|
-
begin
|
103
|
-
@server = ::TCPServer.new(@bind, @port)
|
104
|
-
rescue => e
|
105
|
-
fatal e.message
|
106
|
-
fatal "Running existing tasks with #{@thread_count} thread(s) and exiting."
|
107
|
-
@queue << @reactor
|
108
|
-
Process.kill("INT", 0)
|
109
|
-
next
|
110
|
-
end
|
111
|
-
on_shutdown do
|
112
|
-
@server.close unless @server.nil? || @server.closed?
|
113
|
-
log "Stopped listening to port #{@port}.\n"
|
114
|
-
end
|
115
|
-
::Iodine::Base::Listener.accept(@server, false)
|
116
|
-
log "Iodine #{VERSION} is listening on port #{@port}#{ ' (SSL/TLS)' if @ssl} with #{@thread_count} thread(s).\n"
|
117
|
-
if @spawn_count && @spawn_count.to_i > 1 && Process.respond_to?(:fork)
|
118
|
-
log "Server will run using #{@spawn_count.to_i} processes - Spawning #{@spawn_count.to_i - 1 } more processes.\n"
|
119
|
-
(@spawn_count.to_i - 1).times do
|
120
|
-
Process.fork do
|
121
|
-
log "Spawned process: #{Process.pid}.\n"
|
122
|
-
on_shutdown { log "Shutting down process #{Process.pid}.\n" }
|
123
|
-
@queue.clear
|
124
|
-
@queue << @reactor
|
125
|
-
startup false, true
|
126
|
-
end
|
127
|
-
end
|
128
|
-
|
129
|
-
end
|
130
|
-
log "Press ^C to stop the server.\n"
|
131
|
-
else
|
132
|
-
log "#{self == Iodine ? 'Iodine' : "#{self.name} (Iodine)"} #{VERSION} is running with #{@thread_count} thread(s).\n"
|
133
|
-
log "Press ^C to stop the cycling.\n"
|
134
|
-
end
|
135
|
-
on_shutdown do
|
136
|
-
shut_down_proc = Proc.new {|prot| prot.on_shutdown ; prot.close }
|
137
|
-
@ios.values.each {|p| run p, &shut_down_proc }
|
138
|
-
@queue << @reactor
|
139
|
-
end
|
140
|
-
@queue << @reactor
|
141
|
-
end
|
142
|
-
end
|
143
|
-
end
|
data/lib/iodine/http/hpack.rb
DELETED
@@ -1,553 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
module Iodine
|
4
|
-
module Http
|
5
|
-
class Http2 < ::Iodine::Protocol
|
6
|
-
class HPACK
|
7
|
-
class IndexTable
|
8
|
-
attr_reader :size
|
9
|
-
attr_accessor :max_size
|
10
|
-
def initialize
|
11
|
-
@list = []
|
12
|
-
@size = 4_096 # initial defaul size by standard
|
13
|
-
@actual_size = 0
|
14
|
-
end
|
15
|
-
def [] index
|
16
|
-
raise "HPACK Error - invalid header index: 0" if index == 0
|
17
|
-
return STATIC_LIST[index] if index < STATIC_LENGTH
|
18
|
-
raise "HPACK Error - invalid header index: #{index}" if index >= ( @list.count + STATIC_LENGTH )
|
19
|
-
@list[index - STATIC_LENGTH]
|
20
|
-
end
|
21
|
-
alias :get_index :[]
|
22
|
-
def get_name index
|
23
|
-
get_index(index)[0]
|
24
|
-
end
|
25
|
-
def insert *field
|
26
|
-
@list.unshift field
|
27
|
-
field.each {|f| @actual_size += f.to_s.bytesize}; @actual_size += 32
|
28
|
-
resize
|
29
|
-
field
|
30
|
-
end
|
31
|
-
def find *field
|
32
|
-
index = STATIC_LIST.index(field)
|
33
|
-
return index if index
|
34
|
-
index = @list.index(field)
|
35
|
-
index ? (index + STATIC_LENGTH) : nil
|
36
|
-
end
|
37
|
-
def find_name name
|
38
|
-
index = 1
|
39
|
-
while STATIC_LIST[index]
|
40
|
-
return index if STATIC_LIST[index][0] == name
|
41
|
-
index += 1
|
42
|
-
end
|
43
|
-
index = 0
|
44
|
-
while @list[index]
|
45
|
-
return index+STATIC_LENGTH if @list[index][0] == name
|
46
|
-
index += 1
|
47
|
-
end
|
48
|
-
nil
|
49
|
-
end
|
50
|
-
def resize value = nil
|
51
|
-
@size = value if value && value <= 4_096
|
52
|
-
while (@actual_size > @size) && @list.any?
|
53
|
-
@list.pop.each {|i| @actual_size -= i.to_s.bytesize}
|
54
|
-
@actual_size -= 32
|
55
|
-
end
|
56
|
-
self
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
def initialize
|
61
|
-
@decoding_list = IndexTable.new
|
62
|
-
@encoding_list = IndexTable.new
|
63
|
-
end
|
64
|
-
|
65
|
-
def decode data
|
66
|
-
data = StringIO.new data
|
67
|
-
results = {}
|
68
|
-
while (field = decode_field(data))
|
69
|
-
name = (field[0].is_a?(String) && field[0][0] == ':'.freeze) ? field[0][1..-1].to_sym : field[0]
|
70
|
-
results[name] ? (results[name].is_a?(String) ? (results[name] = [results[name], field[1]]) : (results[name] << field[1]) ) : (results[name] = field[1]) if field[1]
|
71
|
-
end
|
72
|
-
results
|
73
|
-
end
|
74
|
-
def encode headers = {}
|
75
|
-
buffer = String.new.force_encoding(::Encoding::ASCII_8BIT)
|
76
|
-
headers.each {|k, v| buffer << encode_field( (k.is_a?(String) ? k : ":#{k.to_s}".freeze) ,v) if v}
|
77
|
-
buffer.force_encoding(::Encoding::ASCII_8BIT)
|
78
|
-
end
|
79
|
-
def resize max
|
80
|
-
@decoding_list.resize max
|
81
|
-
@encoding_list.resize max
|
82
|
-
end
|
83
|
-
|
84
|
-
protected
|
85
|
-
def decode_field data # expects a StringIO or other IO object
|
86
|
-
byte = data.getbyte
|
87
|
-
return nil unless byte
|
88
|
-
if byte[7] == 1 # 0b1000_0000 == 0b1000_0000
|
89
|
-
# An indexed header field starts with the '1' 1-bit pattern, followed by the index of the matching header field, represented as an integer with a 7-bit prefix (see Section 5.1).
|
90
|
-
num = extract_number data, byte, 1
|
91
|
-
@decoding_list[num]
|
92
|
-
elsif byte & 192 == 64 # 0b1100_0000 == 0b0100_0000
|
93
|
-
# A literal header field with incremental indexing representation starts with the '01' 2-bit pattern.
|
94
|
-
# If the header field name matches the header field name of an entry stored in the static table or the dynamic table, the header field name can be represented using the index of that entry. In this case, the index of the entry is represented as an integer with a 6-bit prefix (see Section 5.1). This value is always non-zero.
|
95
|
-
# Otherwise, the header field name is represented as a string literal (see Section 5.2). A value 0 is used in place of the 6-bit index, followed by the header field name.
|
96
|
-
num = extract_number data, byte, 2
|
97
|
-
field_name = (num == 0) ? extract_string(data) : @decoding_list.get_name(num)
|
98
|
-
field_value = extract_string(data)
|
99
|
-
@decoding_list.insert field_name, field_value
|
100
|
-
elsif byte & 224 # 0b1110_0000 == 0
|
101
|
-
# A literal header field without indexing representation starts with the '0000' 4-bit pattern.
|
102
|
-
# If the header field name matches the header field name of an entry stored in the static table or the dynamic table, the header field name can be represented using the index of that entry.
|
103
|
-
# In this case, the index of the entry is represented as an integer with a 4-bit prefix (see Section 5.1). This value is always non-zero.
|
104
|
-
# Otherwise, the header field name is represented as a string literal (see Section 5.2) and a value 0 is used in place of the 4-bit index, followed by the header field name.
|
105
|
-
# OR
|
106
|
-
# A literal header field never-indexed representation starts with the '0001' 4-bit pattern + 4+ bits for index
|
107
|
-
num = extract_number data, byte, 4
|
108
|
-
field_name = (num == 0) ? extract_string(data) : @decoding_list.get_name(num)
|
109
|
-
field_value = extract_string(data)
|
110
|
-
[field_name, field_value]
|
111
|
-
elsif byte & 224 == 32 # 0b1110_0000 == 0b0010_0000
|
112
|
-
# A dynamic table size update starts with the '001' 3-bit pattern
|
113
|
-
# followed by the new maximum size, represented as an integer with a 5-bit prefix (see Section 5.1).
|
114
|
-
@decoding_list.resize extract_number(data, byte, 5)
|
115
|
-
[].freeze
|
116
|
-
else
|
117
|
-
raise "HPACK Error - invalid field indicator."
|
118
|
-
end
|
119
|
-
end
|
120
|
-
def encode_field name, value
|
121
|
-
if value.is_a?(Array)
|
122
|
-
return (value.map {|v| encode_field name, v} .join)
|
123
|
-
end
|
124
|
-
raise "Http/2 headers must be LOWERCASE Strings!" if name[0] =~ /[A-Z]/n.freeze
|
125
|
-
value = value.to_s
|
126
|
-
if name == 'set-cookie'.freeze
|
127
|
-
buffer = String.new.force_encoding ::Encoding::ASCII_8BIT
|
128
|
-
buffer << pack_number( 55, 1, 4)
|
129
|
-
buffer << pack_string(value)
|
130
|
-
return buffer
|
131
|
-
end
|
132
|
-
index = @encoding_list.find(name, value)
|
133
|
-
return pack_number( index, 1, 1) if index
|
134
|
-
index = @encoding_list.find_name name
|
135
|
-
buffer = String.new.force_encoding(::Encoding::ASCII_8BIT)
|
136
|
-
if index
|
137
|
-
buffer << pack_number( index, 1, 2)
|
138
|
-
else
|
139
|
-
raise "Http/2 headers whould be Strings! or allowed Psedo-Header Symbol Only!" if name[0] == ':'.freeze
|
140
|
-
buffer << pack_number( 0, 1, 2)
|
141
|
-
buffer << pack_string(name)
|
142
|
-
end
|
143
|
-
buffer << pack_string(value)
|
144
|
-
@encoding_list.insert name, value
|
145
|
-
buffer
|
146
|
-
rescue
|
147
|
-
puts "HPACK failure data dump:"
|
148
|
-
puts "buffer: #{buffer} - #{buffer.encoding}" if buffer
|
149
|
-
puts "name: #{name} - #{name.encoding}" if name.is_a? String
|
150
|
-
puts "value: #{value} - #{value.encoding}" if value.is_a? String
|
151
|
-
puts "packed #{pack_string(name)} - #{pack_string(value)}" if value
|
152
|
-
raise
|
153
|
-
end
|
154
|
-
def extract_number data, prefix, prefix_length
|
155
|
-
mask = 255 >> prefix_length
|
156
|
-
return prefix & mask unless (prefix & mask) == mask
|
157
|
-
count = prefix = 0
|
158
|
-
loop do
|
159
|
-
c = data.getbyte
|
160
|
-
prefix = prefix | ((c & 127) << (7*count))
|
161
|
-
break if c[7] == 0
|
162
|
-
count += 1
|
163
|
-
end
|
164
|
-
prefix + mask
|
165
|
-
end
|
166
|
-
def pack_number number, prefix, prefix_length
|
167
|
-
n_length = 8-prefix_length
|
168
|
-
if (number + 1 ).bit_length <= n_length
|
169
|
-
return ((prefix << n_length) | number).chr.force_encoding(::Encoding::ASCII_8BIT)
|
170
|
-
end
|
171
|
-
prefix = [(prefix << n_length) | (2**n_length - 1)]
|
172
|
-
number -= 2**n_length - 1
|
173
|
-
loop do
|
174
|
-
prefix << ((number & 127) | 128)
|
175
|
-
number = number >> 7
|
176
|
-
break if number == 0
|
177
|
-
end
|
178
|
-
(prefix << (prefix.pop & 127)).pack('C*'.freeze).force_encoding(::Encoding::ASCII_8BIT)
|
179
|
-
end
|
180
|
-
def pack_string string, deflate = true
|
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
|
183
|
-
end
|
184
|
-
def extract_string data
|
185
|
-
byte = data.getbyte
|
186
|
-
hoffman = byte[7] == 1
|
187
|
-
length = extract_number data, byte, 1
|
188
|
-
if hoffman
|
189
|
-
inflate data.read(length)
|
190
|
-
else
|
191
|
-
data.read length
|
192
|
-
end
|
193
|
-
end
|
194
|
-
def inflate data
|
195
|
-
data = StringIO.new data
|
196
|
-
str = String.new
|
197
|
-
buffer = String.new
|
198
|
-
until data.eof?
|
199
|
-
byte = data.getbyte
|
200
|
-
8.times do |i|
|
201
|
-
buffer << byte[7-i].to_s
|
202
|
-
if HUFFMAN[buffer]
|
203
|
-
str << HUFFMAN[buffer].chr rescue raise("HPACK Error - Huffman EOS found")
|
204
|
-
buffer.clear
|
205
|
-
end
|
206
|
-
end
|
207
|
-
end
|
208
|
-
raise "HPACK Error - Huffman padding too long (#{buffer.length}): #{buffer}" if buffer.length > 29
|
209
|
-
str
|
210
|
-
end
|
211
|
-
def deflate data
|
212
|
-
str = String.new.force_encoding ::Encoding::ASCII_8BIT
|
213
|
-
buffer = String.new.force_encoding ::Encoding::ASCII_8BIT
|
214
|
-
data.bytes.each do |i|
|
215
|
-
buffer << HUFFMAN.key(i)
|
216
|
-
if (buffer.bytesize % 8) == 0
|
217
|
-
str << [buffer].pack('B*'.freeze)
|
218
|
-
buffer.clear
|
219
|
-
end
|
220
|
-
end
|
221
|
-
unless buffer.empty?
|
222
|
-
(8-(buffer.bytesize % 8)).times { buffer << '1'.freeze} if (buffer.bytesize % 8)
|
223
|
-
str << [buffer].pack('B*'.freeze)
|
224
|
-
buffer.clear
|
225
|
-
end
|
226
|
-
str.force_encoding ::Encoding::ASCII_8BIT
|
227
|
-
end
|
228
|
-
STATIC_LIST = [ nil,
|
229
|
-
[":authority"],
|
230
|
-
[":method", "GET" ],
|
231
|
-
[":method", "POST" ],
|
232
|
-
[":path", "/" ],
|
233
|
-
[":path", "/index.html" ],
|
234
|
-
[":scheme", "http" ],
|
235
|
-
[":scheme", "https" ],
|
236
|
-
[":status", "200" ],
|
237
|
-
[":status", "204" ],
|
238
|
-
[":status", "206" ],
|
239
|
-
[":status", "304" ],
|
240
|
-
[":status", "400" ],
|
241
|
-
[":status", "404" ],
|
242
|
-
[":status", "500" ],
|
243
|
-
["accept-charset"],
|
244
|
-
["accept-encoding", "gzip, deflate" ],
|
245
|
-
["accept-language"],
|
246
|
-
["accept-ranges"],
|
247
|
-
["accept"],
|
248
|
-
["access-control-allow-origin"],
|
249
|
-
["age"],
|
250
|
-
["allow"],
|
251
|
-
["authorization"],
|
252
|
-
["cache-control"],
|
253
|
-
["content-disposition"],
|
254
|
-
["content-encoding"],
|
255
|
-
["content-language"],
|
256
|
-
["content-length"],
|
257
|
-
["content-location"],
|
258
|
-
["content-range"],
|
259
|
-
["content-type"],
|
260
|
-
["cookie"],
|
261
|
-
["date"],
|
262
|
-
["etag"],
|
263
|
-
["expect"],
|
264
|
-
["expires"],
|
265
|
-
["from"],
|
266
|
-
["host"],
|
267
|
-
["if-match"],
|
268
|
-
["if-modified-since"],
|
269
|
-
["if-none-match"],
|
270
|
-
["if-range"],
|
271
|
-
["if-unmodified-since"],
|
272
|
-
["last-modified"],
|
273
|
-
["link"],
|
274
|
-
["location"],
|
275
|
-
["max-forwards"],
|
276
|
-
["proxy-authenticate"],
|
277
|
-
["proxy-authorization"],
|
278
|
-
["range"],
|
279
|
-
["referer"],
|
280
|
-
["refresh"],
|
281
|
-
["retry-after"],
|
282
|
-
["server"],
|
283
|
-
["set-cookie"],
|
284
|
-
["strict-transport-security"],
|
285
|
-
["transfer-encoding"],
|
286
|
-
["user-agent"],
|
287
|
-
["vary"],
|
288
|
-
["via"],
|
289
|
-
["www-authenticate"] ].map! {|a| a.map! {|s| s.is_a?(String) ? s.freeze : s } && a.freeze if a}
|
290
|
-
STATIC_LENGTH = STATIC_LIST.length
|
291
|
-
|
292
|
-
HUFFMAN = [
|
293
|
-
"1111111111000",
|
294
|
-
"11111111111111111011000",
|
295
|
-
"1111111111111111111111100010",
|
296
|
-
"1111111111111111111111100011",
|
297
|
-
"1111111111111111111111100100",
|
298
|
-
"1111111111111111111111100101",
|
299
|
-
"1111111111111111111111100110",
|
300
|
-
"1111111111111111111111100111",
|
301
|
-
"1111111111111111111111101000",
|
302
|
-
"111111111111111111101010",
|
303
|
-
"111111111111111111111111111100",
|
304
|
-
"1111111111111111111111101001",
|
305
|
-
"1111111111111111111111101010",
|
306
|
-
"111111111111111111111111111101",
|
307
|
-
"1111111111111111111111101011",
|
308
|
-
"1111111111111111111111101100",
|
309
|
-
"1111111111111111111111101101",
|
310
|
-
"1111111111111111111111101110",
|
311
|
-
"1111111111111111111111101111",
|
312
|
-
"1111111111111111111111110000",
|
313
|
-
"1111111111111111111111110001",
|
314
|
-
"1111111111111111111111110010",
|
315
|
-
"111111111111111111111111111110",
|
316
|
-
"1111111111111111111111110011",
|
317
|
-
"1111111111111111111111110100",
|
318
|
-
"1111111111111111111111110101",
|
319
|
-
"1111111111111111111111110110",
|
320
|
-
"1111111111111111111111110111",
|
321
|
-
"1111111111111111111111111000",
|
322
|
-
"1111111111111111111111111001",
|
323
|
-
"1111111111111111111111111010",
|
324
|
-
"1111111111111111111111111011",
|
325
|
-
"010100",
|
326
|
-
"1111111000",
|
327
|
-
"1111111001",
|
328
|
-
"111111111010",
|
329
|
-
"1111111111001",
|
330
|
-
"010101",
|
331
|
-
"11111000",
|
332
|
-
"11111111010",
|
333
|
-
"1111111010",
|
334
|
-
"1111111011",
|
335
|
-
"11111001",
|
336
|
-
"11111111011",
|
337
|
-
"11111010",
|
338
|
-
"010110",
|
339
|
-
"010111",
|
340
|
-
"011000",
|
341
|
-
"00000",
|
342
|
-
"00001",
|
343
|
-
"00010",
|
344
|
-
"011001",
|
345
|
-
"011010",
|
346
|
-
"011011",
|
347
|
-
"011100",
|
348
|
-
"011101",
|
349
|
-
"011110",
|
350
|
-
"011111",
|
351
|
-
"1011100",
|
352
|
-
"11111011",
|
353
|
-
"111111111111100",
|
354
|
-
"100000",
|
355
|
-
"111111111011",
|
356
|
-
"1111111100",
|
357
|
-
"1111111111010",
|
358
|
-
"100001",
|
359
|
-
"1011101",
|
360
|
-
"1011110",
|
361
|
-
"1011111",
|
362
|
-
"1100000",
|
363
|
-
"1100001",
|
364
|
-
"1100010",
|
365
|
-
"1100011",
|
366
|
-
"1100100",
|
367
|
-
"1100101",
|
368
|
-
"1100110",
|
369
|
-
"1100111",
|
370
|
-
"1101000",
|
371
|
-
"1101001",
|
372
|
-
"1101010",
|
373
|
-
"1101011",
|
374
|
-
"1101100",
|
375
|
-
"1101101",
|
376
|
-
"1101110",
|
377
|
-
"1101111",
|
378
|
-
"1110000",
|
379
|
-
"1110001",
|
380
|
-
"1110010",
|
381
|
-
"11111100",
|
382
|
-
"1110011",
|
383
|
-
"11111101",
|
384
|
-
"1111111111011",
|
385
|
-
"1111111111111110000",
|
386
|
-
"1111111111100",
|
387
|
-
"11111111111100",
|
388
|
-
"100010",
|
389
|
-
"111111111111101",
|
390
|
-
"00011",
|
391
|
-
"100011",
|
392
|
-
"00100",
|
393
|
-
"100100",
|
394
|
-
"00101",
|
395
|
-
"100101",
|
396
|
-
"100110",
|
397
|
-
"100111",
|
398
|
-
"00110",
|
399
|
-
"1110100",
|
400
|
-
"1110101",
|
401
|
-
"101000",
|
402
|
-
"101001",
|
403
|
-
"101010",
|
404
|
-
"00111",
|
405
|
-
"101011",
|
406
|
-
"1110110",
|
407
|
-
"101100",
|
408
|
-
"01000",
|
409
|
-
"01001",
|
410
|
-
"101101",
|
411
|
-
"1110111",
|
412
|
-
"1111000",
|
413
|
-
"1111001",
|
414
|
-
"1111010",
|
415
|
-
"1111011",
|
416
|
-
"111111111111110",
|
417
|
-
"'",
|
418
|
-
"11111111111101",
|
419
|
-
"1111111111101",
|
420
|
-
"1111111111111111111111111100",
|
421
|
-
"11111111111111100110",
|
422
|
-
"1111111111111111010010",
|
423
|
-
"11111111111111100111",
|
424
|
-
"11111111111111101000",
|
425
|
-
"1111111111111111010011",
|
426
|
-
"1111111111111111010100",
|
427
|
-
"1111111111111111010101",
|
428
|
-
"11111111111111111011001",
|
429
|
-
"1111111111111111010110",
|
430
|
-
"11111111111111111011010",
|
431
|
-
"11111111111111111011011",
|
432
|
-
"11111111111111111011100",
|
433
|
-
"11111111111111111011101",
|
434
|
-
"11111111111111111011110",
|
435
|
-
"111111111111111111101011",
|
436
|
-
"11111111111111111011111",
|
437
|
-
"111111111111111111101100",
|
438
|
-
"111111111111111111101101",
|
439
|
-
"1111111111111111010111",
|
440
|
-
"11111111111111111100000",
|
441
|
-
"111111111111111111101110",
|
442
|
-
"11111111111111111100001",
|
443
|
-
"11111111111111111100010",
|
444
|
-
"11111111111111111100011",
|
445
|
-
"11111111111111111100100",
|
446
|
-
"111111111111111011100",
|
447
|
-
"1111111111111111011000",
|
448
|
-
"11111111111111111100101",
|
449
|
-
"1111111111111111011001",
|
450
|
-
"11111111111111111100110",
|
451
|
-
"11111111111111111100111",
|
452
|
-
"111111111111111111101111",
|
453
|
-
"1111111111111111011010",
|
454
|
-
"111111111111111011101",
|
455
|
-
"11111111111111101001",
|
456
|
-
"1111111111111111011011",
|
457
|
-
"1111111111111111011100",
|
458
|
-
"11111111111111111101000",
|
459
|
-
"11111111111111111101001",
|
460
|
-
"111111111111111011110",
|
461
|
-
"11111111111111111101010",
|
462
|
-
"1111111111111111011101",
|
463
|
-
"1111111111111111011110",
|
464
|
-
"111111111111111111110000",
|
465
|
-
"111111111111111011111",
|
466
|
-
"1111111111111111011111",
|
467
|
-
"11111111111111111101011",
|
468
|
-
"11111111111111111101100",
|
469
|
-
"111111111111111100000",
|
470
|
-
"111111111111111100001",
|
471
|
-
"1111111111111111100000",
|
472
|
-
"111111111111111100010",
|
473
|
-
"11111111111111111101101",
|
474
|
-
"1111111111111111100001",
|
475
|
-
"11111111111111111101110",
|
476
|
-
"11111111111111111101111",
|
477
|
-
"11111111111111101010",
|
478
|
-
"1111111111111111100010",
|
479
|
-
"1111111111111111100011",
|
480
|
-
"1111111111111111100100",
|
481
|
-
"11111111111111111110000",
|
482
|
-
"1111111111111111100101",
|
483
|
-
"1111111111111111100110",
|
484
|
-
"11111111111111111110001",
|
485
|
-
"11111111111111111111100000",
|
486
|
-
"11111111111111111111100001",
|
487
|
-
"11111111111111101011",
|
488
|
-
"1111111111111110001",
|
489
|
-
"1111111111111111100111",
|
490
|
-
"11111111111111111110010",
|
491
|
-
"1111111111111111101000",
|
492
|
-
"1111111111111111111101100",
|
493
|
-
"11111111111111111111100010",
|
494
|
-
"11111111111111111111100011",
|
495
|
-
"11111111111111111111100100",
|
496
|
-
"111111111111111111111011110",
|
497
|
-
"111111111111111111111011111",
|
498
|
-
"11111111111111111111100101",
|
499
|
-
"111111111111111111110001",
|
500
|
-
"1111111111111111111101101",
|
501
|
-
"1111111111111110010",
|
502
|
-
"111111111111111100011",
|
503
|
-
"11111111111111111111100110",
|
504
|
-
"111111111111111111111100000",
|
505
|
-
"111111111111111111111100001",
|
506
|
-
"11111111111111111111100111",
|
507
|
-
"111111111111111111111100010",
|
508
|
-
"111111111111111111110010",
|
509
|
-
"111111111111111100100",
|
510
|
-
"111111111111111100101",
|
511
|
-
"11111111111111111111101000",
|
512
|
-
"11111111111111111111101001",
|
513
|
-
"1111111111111111111111111101",
|
514
|
-
"111111111111111111111100011",
|
515
|
-
"111111111111111111111100100",
|
516
|
-
"111111111111111111111100101",
|
517
|
-
"11111111111111101100",
|
518
|
-
"111111111111111111110011",
|
519
|
-
"11111111111111101101",
|
520
|
-
"111111111111111100110",
|
521
|
-
"1111111111111111101001",
|
522
|
-
"111111111111111100111",
|
523
|
-
"111111111111111101000",
|
524
|
-
"11111111111111111110011",
|
525
|
-
"1111111111111111101010",
|
526
|
-
"1111111111111111101011",
|
527
|
-
"1111111111111111111101110",
|
528
|
-
"1111111111111111111101111",
|
529
|
-
"111111111111111111110100",
|
530
|
-
"111111111111111111110101",
|
531
|
-
"11111111111111111111101010",
|
532
|
-
"11111111111111111110100",
|
533
|
-
"11111111111111111111101011",
|
534
|
-
"111111111111111111111100110",
|
535
|
-
"11111111111111111111101100",
|
536
|
-
"11111111111111111111101101",
|
537
|
-
"111111111111111111111100111",
|
538
|
-
"111111111111111111111101000",
|
539
|
-
"111111111111111111111101001",
|
540
|
-
"111111111111111111111101010",
|
541
|
-
"111111111111111111111101011",
|
542
|
-
"1111111111111111111111111110",
|
543
|
-
"111111111111111111111101100",
|
544
|
-
"111111111111111111111101101",
|
545
|
-
"111111111111111111111101110",
|
546
|
-
"111111111111111111111101111",
|
547
|
-
"111111111111111111111110000",
|
548
|
-
"11111111111111111111101110",
|
549
|
-
"111111111111111111111111111111"].each_with_index.with_object({}) {|a, h| h[a[0]] = a[1] }
|
550
|
-
end
|
551
|
-
end
|
552
|
-
end
|
553
|
-
end
|