spider-gazelle 1.2.0 → 2.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ca80be46b162d37bbcb510ba2522bbfd0ea6460d
4
- data.tar.gz: fcec86de21cd802686919983912c130a34762ad4
3
+ metadata.gz: 883af96ffb1054dc17b7f623ca7a6a1b6afe48cf
4
+ data.tar.gz: 9dda42f38e4cb802d4c6a11632438c16c2868cde
5
5
  SHA512:
6
- metadata.gz: a3be697d78d1e77e23017ba627b4fd33cc29ecb3bf45d51081daf1321c7956e5e95e521be8253ee2c0572b62af922c9c84a948f6746bc4e8c0bd3c755c68f164
7
- data.tar.gz: 84045ad878fdced61b7c8e4739e099291c52cdcd53d1fc0916cbe802b5a7e76f1a5fc33b0db52ede7f4194864e1dca319887afe38c65368185cb6d0ae81d8edc
6
+ metadata.gz: 848ba774b73a281f074505d8a0c92e8742e78cc6608ed441e283497f26fdfda6b3f992547b2df4eae4960cb3649e93ca135ffdf0173e7b97e22c67f193e3f8ab
7
+ data.tar.gz: 170c737ed7502cbff903f1ef8d04f8050d0632471d50077384125f284608b5362fe3585e8c823faa30973d21f0fb55901154c579c3c861ebd724d32190f0b2d6
data/bin/sg CHANGED
@@ -1,67 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
-
4
3
  require 'spider-gazelle'
5
- require 'optparse'
6
-
7
-
8
- options = {
9
- Host: "0.0.0.0",
10
- Port: 3000,
11
- environment: ENV['RACK_ENV'] || 'development',
12
- rackup: "#{Dir.pwd}/config.ru",
13
- Verbose: false
14
- }
15
-
16
- parser = OptionParser.new do |opts|
17
- opts.on "-p", "--port PORT", Integer, "Define what port TCP port to bind to (default: 3000)" do |arg|
18
- options[:Port] = arg
19
- end
20
-
21
- opts.on "-a", "--address HOST", "bind to HOST address (default: 0.0.0.0)" do |arg|
22
- options[:Host] = arg
23
- end
24
-
25
- opts.on "-v", "--verbose", "loud output" do
26
- options[:Verbose] = true
27
- end
28
-
29
- opts.on "-e", "--environment ENVIRONMENT", "The environment to run the Rack app on (default: development)" do |arg|
30
- options[:environment] = arg
31
- end
32
-
33
- opts.on "-r", "--rackup FILE", "Load Rack config from this file (default: config.ru)" do |arg|
34
- options[:rackup] = arg
35
- end
36
-
37
- opts.on "-l", "--log FILE", "Location of the servers log file (default: logs/server.log)" do |arg|
38
- ENV['SG_LOG'] = arg
39
- end
40
-
41
- opts.on "-m", "--mode MODE", "Either thread, process or no_ipc (default: thread)" do |arg|
42
- ENV['SG_MODE'] = arg
43
- end
44
- end
45
-
46
- parser.banner = "sg <options> <rackup file>"
47
- parser.on_tail "-h", "--help", "Show help" do
48
- puts parser
49
- exit 1
50
- end
51
-
52
- parser.parse!(ARGV)
53
-
54
- if ARGV.last =~ /\.ru$/
55
- options[:rackup] = ARGV.shift
56
- end
57
-
58
- unless File.exists?(options[:rackup])
59
- abort "No rackup found at #{options[:rackup]}"
60
- end
61
-
62
- # Force process mode on Windows (sockets over pipes not working at the moment)
63
- ENV['SG_MODE'] = 'no_ipc' if ::FFI::Platform.windows?
64
-
65
- ::SpiderGazelle::Spider.run options[:rackup], options
66
-
67
- puts "\nSpider-Gazelle leaps through the veldt"
4
+ SpiderGazelle::LaunchControl.instance.exec(ARGV)
@@ -1,37 +1,28 @@
1
1
  require "rack/handler"
2
2
  require "spider-gazelle"
3
- require "spider-gazelle/const"
4
3
 
5
4
  module Rack
6
- module Handler
7
- module SpiderGazelle
8
- DEFAULT_OPTIONS = {
9
- :Host => "0.0.0.0",
10
- :Port => 8080,
11
- :Verbose => false
12
- }
5
+ module Handler
6
+ module SpiderGazelle
7
+ def self.run(app, options = {})
8
+
9
+ # Replace the rackup with app
10
+ options = ::SpiderGazelle::Options::DEFAULTS.merge(options)
11
+ options.delete(:rackup)
12
+ options[:app] = app
13
13
 
14
- def self.run(app, options = {})
15
- options = DEFAULT_OPTIONS.merge(options)
14
+ # Can't pass an object over a pipe
15
+ options[:isolate] = true
16
+ options[:mode] = :thread if options[:mode] == :process
16
17
 
17
- if options[:Verbose]
18
- app = Rack::CommonLogger.new(app, STDOUT)
19
- end
18
+ # Ensure the environment is set
19
+ options[:environment] ||= ENV['RACK_ENV'] || 'development'
20
+ ENV['RACK_ENV'] = options[:environment]
20
21
 
21
- if options[:environment]
22
- ENV["RACK_ENV"] = options[:environment].to_s
22
+ ::SpiderGazelle::LaunchControl.instance.launch([options])
23
+ end
23
24
  end
24
25
 
25
- ::SpiderGazelle::Spider.run app, options
26
- end
27
-
28
- def self.valid_options
29
- { "Host=HOST" => "Hostname to listen on (default: 0.0.0.0)",
30
- "Port=PORT" => "Port to listen on (default: 8080)",
31
- "Quiet" => "Don't report each request" }
32
- end
26
+ register :"spider-gazelle", SpiderGazelle
33
27
  end
34
-
35
- register :"spider-gazelle", SpiderGazelle
36
- end
37
28
  end
@@ -4,36 +4,36 @@ require 'rack/body_proxy'
4
4
  require 'rack/lock' # ensure this loads first
5
5
 
6
6
  module Rack
7
- # Rack::Lock locks every request inside a mutex, so that every request
8
- # will effectively be executed synchronously.
9
- class Lock
10
- # FLAG = 'rack.multithread'.freeze # defined in rack/lock
11
- RACK_MULTITHREAD ||= FLAG
7
+ # Rack::Lock locks every request inside a mutex, so that every request
8
+ # will effectively be executed synchronously.
9
+ class Lock
10
+ # FLAG = 'rack.multithread'.freeze # defined in rack/lock
11
+ RACK_MULTITHREAD ||= FLAG
12
12
 
13
- def initialize(app, mutex = Mutex.new)
14
- @app, @mutex = app, mutex
15
- @sig = ConditionVariable.new
16
- @count = 0
17
- end
13
+ def initialize(app, mutex = Mutex.new)
14
+ @app, @mutex = app, mutex
15
+ @sig = ConditionVariable.new
16
+ @count = 0
17
+ end
18
18
 
19
- def call(env)
20
- @mutex.lock
21
- @count += 1
22
- @sig.wait(@mutex) if @count > 1
23
- response = @app.call(env.merge(RACK_MULTITHREAD => false))
24
- returned = response << BodyProxy.new(response.pop) {
25
- @mutex.synchronize { unlock }
26
- }
27
- ensure
28
- unlock unless returned
29
- @mutex.unlock
30
- end
19
+ def call(env)
20
+ @mutex.lock
21
+ @count += 1
22
+ @sig.wait(@mutex) if @count > 1
23
+ response = @app.call(env.merge(RACK_MULTITHREAD => false))
24
+ returned = response << BodyProxy.new(response.pop) {
25
+ @mutex.synchronize { unlock }
26
+ }
27
+ ensure
28
+ unlock unless returned
29
+ @mutex.unlock
30
+ end
31
31
 
32
- private
32
+ private
33
33
 
34
- def unlock
35
- @count -= 1
36
- @sig.signal
34
+ def unlock
35
+ @count -= 1
36
+ @sig.signal
37
+ end
37
38
  end
38
- end
39
39
  end
@@ -1,22 +1,171 @@
1
- require "http-parser" # C based, fast, http parser
2
- require "libuv" # Ruby Libuv FFI wrapper
3
- require "rack" # Ruby webserver abstraction
4
- require "rack/lock_patch" # Serialize execution in development mode
1
+ require 'thread'
2
+ require 'singleton'
5
3
 
6
- require "spider-gazelle/request" # Holds request information and handles request processing
7
- require "spider-gazelle/connection" # Holds connection information and handles request pipelining
8
- require "spider-gazelle/gazelle" # Processes data received from connections
9
4
 
10
- require "spider-gazelle/app_store" # Holds references to the loaded rack applications
11
- require "spider-gazelle/binding" # Holds a reference to a bound port and associated rack application
12
- require "spider-gazelle/spider" # Accepts connections and offloads them to gazelles
5
+ require 'spider-gazelle/options'
6
+ require 'spider-gazelle/logger'
7
+ require 'spider-gazelle/reactor'
8
+ require 'spider-gazelle/signaller'
13
9
 
14
- # Reactor aware websocket implementation
15
- require "spider-gazelle/upgrades/websocket"
16
10
 
17
11
  module SpiderGazelle
18
- # Delegate pipe used for passing sockets to the gazelles
19
- DELEGATE_PIPE = ENV['SG_DELEGATE_PIPE'] || "/tmp/spider-gazelle.delegate"
20
- # Signal pipe used to pass control signals
21
- SIGNAL_PIPE = ENV['SG_SIGNAL_PIPE'] || "/tmp/spider-gazelle.signal"
12
+ VERSION = '2.0.0'.freeze
13
+ EXEC_NAME = 'sg'.freeze
14
+ INTERNAL_PIPE_BACKLOG = 128
15
+
16
+ # Signaller is used to communicate:
17
+ # * command line requests
18
+ # * Startup and shutdown requests
19
+ # * Live updates (bindings passed by this pipe)
20
+ SIGNAL_SERVER = '/tmp/sg-signaller.pipe'.freeze
21
+
22
+ # Spider server is used to
23
+ # * Track gazelles
24
+ # * Signal shutdown as required
25
+ # * Pass sockets
26
+ SPIDER_SERVER = '/tmp/sg-spider.pipe.'.freeze
27
+
28
+ MODES = [:process, :thread, :no_ipc].freeze
29
+ APP_MODE = [:thread_pool, :fiber_pool, :libuv, :eventmachine, :celluloid]
30
+
31
+
32
+ class LaunchControl
33
+ include Singleton
34
+
35
+
36
+ attr_reader :password, :args
37
+
38
+
39
+ def exec(args)
40
+ options = SpiderGazelle::Options.sanitize(args)
41
+ @args = args
42
+ launch(options)
43
+ end
44
+
45
+ def launch(options)
46
+ # Enable verbose messages if requested
47
+ Logger.instance.verbose! if options[0][:verbose]
48
+
49
+ # Start the Libuv Event Loop
50
+ reactor = ::SpiderGazelle::Reactor.instance
51
+ reactor.run do
52
+
53
+ # Check if SG is already running
54
+ signaller = ::SpiderGazelle::Signaller.instance
55
+
56
+ if options[0][:isolate]
57
+ # This ensures this process will load the spider code
58
+ options[0][:spider] = true
59
+ boot(true, signaller, options)
60
+ else
61
+ signaller.check.then do |running|
62
+ boot(running, signaller, options)
63
+ end
64
+ end
65
+ end
66
+ end
67
+
68
+ def shutdown
69
+ reactor = Reactor.instance
70
+ reactor.thread.schedule do
71
+ reactor.shutdown
72
+ end
73
+ end
74
+
75
+
76
+ # ---------------------------------------
77
+ # SPIDER LAUNCH CONTROL
78
+ # ---------------------------------------
79
+ def launch_spider(args)
80
+ require 'securerandom'
81
+ require 'thread'
82
+
83
+ @password ||= SecureRandom.hex
84
+ #cmd = "#{EXEC_NAME} -s #{@password} #{Shellwords.join(args)}"
85
+ cmd = [EXEC_NAME, '-s', @password] + args
86
+
87
+ Thread.new do
88
+ result = system(*cmd)
89
+
90
+ # TODO:: We need to detect a failed load
91
+ # This is a little more tricky as spiders
92
+ # may come and go without this process exiting
93
+ end
94
+ end
95
+
96
+ # This is called when a spider process starts
97
+ def start_spider(signaller, logger, options)
98
+ logger.set_client signaller.pipe unless options[0][:isolate]
99
+
100
+ require 'spider-gazelle/spider'
101
+ Spider.instance.run!(options)
102
+ end
103
+
104
+
105
+
106
+ # ---------------------------------------
107
+ # GAZELLE LAUNCH CONTROL
108
+ # ---------------------------------------
109
+ def start_gazelle(signaller, logger, options)
110
+ logger.set_client signaller.pipe
111
+
112
+ require 'spider-gazelle/gazelle'
113
+ ::SpiderGazelle::Gazelle.new(logger.thread, :process).run!(options)
114
+ end
115
+
116
+
117
+ # ---------------------------------------
118
+ # TTY SIGNALLING CONTROL
119
+ # ---------------------------------------
120
+ def signal_master(reactor, signaller, logger, options)
121
+ # This is a signal request
122
+ promise = signaller.request(options)
123
+
124
+ promise.then do |result|
125
+ logger.info "signal recieved #{result}"
126
+ end
127
+ promise.catch do |error|
128
+ logger.info "there was an error #{error}"
129
+ end
130
+ promise.finally do
131
+ reactor.shutdown
132
+ end
133
+ end
134
+
135
+
136
+ protected
137
+
138
+
139
+ def boot(running, signaller, options)
140
+ logger = ::SpiderGazelle::Logger.instance
141
+
142
+ begin
143
+ # What do we want to do?
144
+ master = options[0]
145
+
146
+ if running
147
+ if master[:spider]
148
+ logger.verbose "Starting Spider".freeze
149
+ start_spider(signaller, logger, options)
150
+ elsif master[:gazelle]
151
+ logger.verbose "Starting Gazelle".freeze
152
+ start_gazelle(signaller, logger, options)
153
+ else
154
+ logger.verbose "Sending signal to SG Master".freeze
155
+ signal_master(reactor, signaller, logger, options)
156
+ end
157
+
158
+ elsif master[:debug]
159
+ logger.verbose "SG is now running in debug mode".freeze
160
+ else
161
+ logger.verbose "SG was not running, launching Spider".freeze
162
+ launch_spider(@args)
163
+ end
164
+ rescue => e
165
+ logger.verbose "Error performing requested operation".freeze
166
+ logger.print_error(e)
167
+ shutdown
168
+ end
169
+ end
170
+ end
22
171
  end
@@ -1,162 +1,179 @@
1
- require 'spider-gazelle/const'
2
- require 'set'
1
+ require "rack" # Ruby webserver abstraction
2
+ require "rack/lock_patch" # Serialize execution in development mode
3
+ require 'spider-gazelle/gazelle/app_store'
4
+ require 'spider-gazelle/gazelle/http1'
3
5
 
4
- module SpiderGazelle
5
- class Gazelle
6
- include Const
7
6
 
8
- attr_reader :parser_cache, :connections, :logger
7
+ # Reactor aware websocket implementation
8
+ require "spider-gazelle/upgrades/websocket"
9
9
 
10
- def set_instance_type(inst)
11
- inst.type = :request
12
- end
13
10
 
14
- def initialize(loop, logger, mode)
15
- @gazelle = loop
16
- # Set of active connections on this thread
17
- @connections = Set.new
18
- # Stale parser objects cached for reuse
19
- @parser_cache = []
11
+ module SpiderGazelle
12
+ class Gazelle
13
+ SPACE = ' '.freeze
14
+
15
+ def initialize(thread, type)
16
+ raise ArgumentError, "type must be one of #{MODES}" unless MODES.include?(type)
17
+
18
+ @type = type
19
+ @logger = Logger.instance
20
+ @thread = thread
21
+
22
+ @http1_cache = []
23
+ @http2_cache = []
24
+ @return_http1 = method(:return_http1)
25
+ @return_http2 = method(:return_http2)
26
+ @parser_count = 0
27
+
28
+ @on_progress = method(:on_progress)
29
+ @set_protocol = method(:set_protocol)
30
+
31
+ # Register the gazelle with the signaller so we can shutdown elegantly
32
+ if @type == :process
33
+ Signaller.instance.gazelle = self
34
+ end
35
+ end
20
36
 
21
- @mode = mode
22
- @logger = logger
23
- @app_cache = {}
24
- @connection_queue = ::Libuv::Q::ResolvedPromise.new @gazelle, true
37
+ def run!(options)
38
+ @options = options
39
+ @logger.verbose { "Gazelle: #{@type} Pid: #{Process.pid} started" }
25
40
 
26
- # A single parser instance for processing requests for each gazelle
27
- @parser = ::HttpParser::Parser.new self
28
- @set_instance_type = method :set_instance_type
41
+ connect_to_spider unless @type == :no_ipc
29
42
 
30
- # Single progress callback for each gazelle
31
- @on_progress = method :on_progress
32
- end
43
+ load_required_applications
44
+ self
45
+ end
33
46
 
34
- def run
35
- @gazelle.run do |logger|
36
- logger.progress do |level, errorid, error|
37
- begin
38
- msg = "Gazelle log: #{level}: #{errorid}\n#{error.message}\n#{error.backtrace.join("\n") if error.backtrace}\n"
39
- @logger.error msg
40
- rescue Exception
41
- puts 'error in gazelle logger'
42
- end
47
+ def new_app(options)
48
+ # TODO:: load this app into all of the gazelles dynamically
43
49
  end
44
50
 
45
- unless @mode == :no_ipc
46
- # A pipe used to forward connections to different threads
47
- @socket_server = @gazelle.pipe true
48
- @socket_server.connect(DELEGATE_PIPE) do
49
- @socket_server.progress &method(:new_connection)
50
- @socket_server.start_read
51
- end
52
-
53
- # A pipe used to signal various control commands (shutdown, etc)
54
- @signal_server = @gazelle.pipe
55
- @signal_server.connect(SIGNAL_PIPE) do
56
- @signal_server.progress &method(:process_signal)
57
- @signal_server.start_read
58
- end
51
+ def new_connection(data, binding)
52
+ socket = @pipe.check_pending
53
+ return if socket.nil?
54
+ process_connection(socket, data.to_i)
59
55
  end
60
- end
61
- end
62
56
 
63
- # HTTP Parser callbacks:
64
- def on_message_begin(parser)
65
- @connection.start_parsing
66
- end
57
+ def shutdown(finished = nil)
58
+ # Wait for the requests to finish (give them 15 seconds)
59
+ # TODO::
60
+
61
+ @logger.verbose { "Gazelle: #{@type} Pid: #{Process.pid} shutting down" }
62
+
63
+ # Then stop the current thread if we are in threaded mode
64
+ if @type == :thread
65
+ # In threaded mode the gazelle has the power
66
+ @thread.stop
67
+ else
68
+ # Both no_ipc and process need to know when the requests
69
+ # have completed to shutdown
70
+ finished.resolve(true)
71
+ end
72
+ end
67
73
 
68
- def on_url(parser, url)
69
- @connection.parsing.url << url
70
- end
71
74
 
72
- def on_header_field(parser, header)
73
- req = @connection.parsing
74
- req.header.frozen? ? req.header = header : req.header << header
75
- end
75
+ protected
76
+
77
+
78
+ def connect_to_spider
79
+ @pipe = @thread.pipe :ipc
80
+ @pipe.connect(@options[0][:gazelle_ipc]) do |client|
81
+ client.progress method(:new_connection)
82
+ client.start_read
83
+
84
+ authenticate
85
+ end
76
86
 
77
- def on_header_value(parser, value)
78
- req = @connection.parsing
79
- if req.header.frozen?
80
- req.env[req.header] << value
81
- else
82
- header = req.header
83
- header.upcase!
84
- header.gsub!(DASH, UNDERSCORE)
85
- header.prepend(HTTP_META)
86
- header.freeze
87
- if req.env[header]
88
- req.env[header] << COMMA
89
- req.env[header] << value
90
- else
91
- req.env[header] = value
87
+ @pipe.catch do |error|
88
+ @logger.print_error(error)
89
+ end
90
+
91
+ @pipe.finally do
92
+ if @type == :process
93
+ Reactor.instance.shutdown
94
+ else
95
+ # Threaded mode
96
+ shutdown
97
+ end
98
+ end
92
99
  end
93
- end
94
- end
95
100
 
96
- def on_headers_complete(parser)
97
- @connection.parsing.env[REQUEST_METHOD] = @connection.state.http_method.to_s
98
- end
101
+ def authenticate
102
+ @pipe.write "#{@options[0][:gazelle]} #{@type}"
103
+ end
99
104
 
100
- def on_body(parser, data)
101
- @connection.parsing.body << data
102
- end
105
+ def load_required_applications
106
+ @options.each do |app|
107
+ if app[:rackup]
108
+ AppStore.load(app[:rackup], app)
109
+ elsif app[:app]
110
+ AppStore.add(app[:app], app)
111
+ end
112
+ end
113
+ end
103
114
 
104
- def on_message_complete(parser)
105
- @connection.finished_parsing
106
- end
107
115
 
108
- def discard(connection)
109
- @connections.delete(connection)
110
- state = connection.state
111
- state.reset!
112
- @parser_cache << state
113
- end
116
+ # ---------------------
117
+ # Connection Management
118
+ # ---------------------
119
+ def process_connection(socket, app_id)
120
+ # Put application details in the socket storage as we negotiate protocols
121
+ details = AppStore.get(app_id)
122
+ socket.storage = details
123
+ tls = details[-1]
124
+
125
+ # Hook up the socket and kick off TLS if required
126
+ if tls
127
+ socket.on_handshake @set_protocol
128
+ socket.start_tls(tls)
129
+ else
130
+ set_protocol(socket, :http1)
131
+ end
132
+
133
+ socket.start_read
134
+ socket.enable_nodelay
135
+ end
114
136
 
115
- protected
137
+ def on_progress(data, socket)
138
+ # Storage contains the parser for this connection
139
+ parser = socket.storage
140
+ parser.parse(data)
141
+ end
116
142
 
117
- def on_progress(data, socket)
118
- # Keep track of which connection we are processing for the callbacks
119
- @connection = socket.storage
143
+ def set_protocol(socket, version)
144
+ app, app_mode, port, tls = socket.storage
120
145
 
121
- # Check for errors during the parsing of the request
122
- @connection.parsing_error if @parser.parse(@connection.state, data)
123
- end
146
+ parser = if version == :h2
147
+ @http2_cache.pop || new_http2_parser
148
+ else
149
+ @http1_cache.pop || new_http1_parser
150
+ end
124
151
 
125
- def new_connection(data, socket)
126
- # When in no IPC mode @socket_server is nil and the socket parameter is the socket
127
- # If socket server exists then we are expecting sockets over the pipe
128
- if @socket_server
129
- socket = @socket_server.check_pending
130
- return if socket.nil?
131
- end
132
-
133
- # Data == "TLS_indicator Port APP_ID"
134
- tls, port, app_id = data.split(SPACE, 3)
135
- app = @app_cache[app_id.to_sym] ||= AppStore.get(app_id)
136
- inst = @parser_cache.pop || ::HttpParser::Parser.new_instance(&@set_instance_type)
137
-
138
- # process any data coming from the socket
139
- socket.progress @on_progress
140
- # TODO:: Allow some globals for supplying the certs
141
- # --> We could store these in the AppStore
142
- socket.start_tls(:server => true) if tls == USE_TLS
143
-
144
- # Keep track of the connection
145
- connection = Connection.new self, @gazelle, socket, port, inst, app, @connection_queue
146
- @connections.add connection
147
- # This allows us to re-use the one proc for parsing
148
- socket.storage = connection
149
-
150
- socket.start_read
151
- end
152
+ parser.load(socket, port, app, app_mode, tls)
153
+ socket.progress @on_progress
154
+ socket.storage = parser
155
+ end
152
156
 
153
- def process_signal(data, pipe)
154
- shutdown if data == KILL_GAZELLE
155
- end
156
157
 
157
- def shutdown
158
- # TODO:: do this nicely. Need to signal the connections to close
159
- @gazelle.stop
158
+ def new_http1_parser
159
+ @h1_parser_obj ||= Http1::Callbacks.new
160
+
161
+ @parser_count += 1
162
+ Http1.new(@return_http1, @h1_parser_obj, @thread, @logger)
163
+ end
164
+
165
+ def return_http1(parser)
166
+ @http1_cache.push parser
167
+ end
168
+
169
+ def new_http2_parser
170
+ raise NotImplementedError.new 'TODO:: Create HTTP2 parser class'
171
+ @parser_count += 1
172
+ Http2.new(@return_http2)
173
+ end
174
+
175
+ def return_http2(parser)
176
+ @http2_cache << parser
177
+ end
160
178
  end
161
- end
162
179
  end