zebra 1.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.
@@ -0,0 +1,6 @@
1
+ *.gem
2
+ *.log
3
+ *.pid
4
+ .bundle
5
+ Gemfile.lock
6
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in zebra.gemspec
4
+ gemspec
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift(File.expand_path('.')) # Ruby 1.9 doesn't have . in the load path...
3
+ $:.push(File.expand_path('lib/'))
4
+ $0 = __FILE__ + " " + ARGV.join(" ")
5
+
6
+ require 'rubygems'
7
+ require 'zebra'
8
+
9
+ command_line = Zebra::CommandLine.new
10
+ command_line.execute
@@ -0,0 +1 @@
1
+ #log_file: "/tmp/zebra.log"
@@ -0,0 +1,21 @@
1
+ class Hash
2
+ def symbolize_keys!
3
+ t=self.dup
4
+ self.clear
5
+ t.each_pair do |k,v|
6
+ case v
7
+ when Hash
8
+ v.symbolize_keys!
9
+ when Array
10
+ v.each do |e|
11
+ if e.kind_of?(Hash)
12
+ e.symbolize_keys!
13
+ end
14
+ end
15
+ end
16
+ self[k.to_sym] = v
17
+ self
18
+ end
19
+ self
20
+ end
21
+ end
@@ -0,0 +1,29 @@
1
+ require 'core_ext/hash'
2
+ require 'zebra/config'
3
+ require 'zebra/command_line'
4
+ require 'zebra/proxy_server'
5
+ require 'zebra/proxy_worker'
6
+ require 'zebra/queue'
7
+
8
+ module Zebra
9
+ @@config = Config.instance
10
+ @@log = Logger.new(STDERR)
11
+
12
+ def self.config=(config)
13
+ @@config=config
14
+ end
15
+
16
+ def self.config
17
+ @@config
18
+ end
19
+
20
+ def self.log=(log)
21
+ @@log=log
22
+ end
23
+
24
+ def self.log
25
+ @@log
26
+ end
27
+ end
28
+
29
+
@@ -0,0 +1,137 @@
1
+ require 'optparse'
2
+ require 'daemons'
3
+
4
+ module Zebra
5
+
6
+ class MissingArgumentException < Exception; end
7
+ class UnsupportedArgumentException < Exception; end
8
+
9
+ #Loads command line options and assigns them to singleton Config object
10
+ class CommandLine
11
+ attr_reader :controller
12
+
13
+ def initialize
14
+ Zebra.config.chdir = File.dirname(__FILE__)
15
+ Zebra.config.tmp_dir = '/tmp'
16
+ Zebra.config.daemonize = false
17
+
18
+ begin
19
+ @parser = OptionParser.new() do |opts|
20
+ opts.banner = "Usage: #{$0} --config CONFIG [worker|server|queue]"
21
+
22
+ opts.on('-c', '--config CONFIG', 'Configuration file') do |config_file|
23
+ Zebra.config.config_file = config_file
24
+ end
25
+
26
+ opts.on('-d', '--daemonize', 'Daemonize the process') do |daemonize|
27
+ Zebra.config.daemonize = daemonize
28
+ end
29
+
30
+ opts.on('-p', '--pid-file PID-FILE', 'Pid-File to save the process id') do |pid_file|
31
+ Zebra.config.pid_file = pid_file
32
+ end
33
+
34
+ opts.on('-l', '--log-file LOG-FILE', 'Log File') do |log_file|
35
+ Zebra.config.log_file = log_file
36
+ end
37
+ end
38
+ @parser.parse!
39
+ Zebra.config.mode = ARGV.shift if ARGV.length > 0
40
+ raise MissingArgumentException.new("Missing --config parameter") unless Zebra.config.config_file?
41
+ raise MissingArgumentException.new("Missing mode of operation: server|proxy|queue") unless Zebra.config.mode?
42
+ rescue MissingArgumentException => e
43
+ puts usage(e)
44
+ exit 1
45
+ rescue ArgumentError => e
46
+ puts usage(e)
47
+ exit 1
48
+ rescue Exception => e
49
+ puts e.message
50
+ puts e.backtrace.join("\n\t")
51
+ exit 1
52
+ end
53
+ end
54
+
55
+ def usage(e = nil)
56
+ output = ''
57
+ case e
58
+ when MissingArgumentException
59
+ output += "#{e.message}\n"
60
+ when Exception
61
+ output += "#{e.class}: #{e.message}\n"
62
+ when Nil
63
+ # Do nothing
64
+ end
65
+ output += @parser.to_s
66
+ output
67
+ end
68
+
69
+ def daemonize
70
+ # Become a daemon
71
+ if RUBY_VERSION < "1.9"
72
+ exit if fork
73
+ Process.setsid
74
+ exit if fork
75
+ Dir.chdir "/"
76
+ STDIN.reopen "/dev/null"
77
+ STDOUT.reopen "/dev/null", "a"
78
+ STDERR.reopen "/dev/null", "a"
79
+ else
80
+ Process.daemon
81
+ end
82
+ end
83
+
84
+ def execute
85
+ #If log file is specified logs messages to that file, else on stdout
86
+ log_file = Zebra.config.log_file
87
+ fh = nil
88
+ if log_file
89
+ fh = File.open(log_file, 'a')
90
+ else
91
+ fh = STDERR
92
+ end
93
+
94
+ fh.sync = true
95
+ Zebra.log = Logger.new(fh)
96
+
97
+ Zebra.log.datetime_format = "%Y-%m-%d %H:%M:%S"
98
+ Zebra.log.formatter = proc { |severity, datetime, progname, msg| sprintf "%-15s | %5s | %s\n", datetime.strftime(Zebra.log.datetime_format), severity, msg }
99
+ Zebra.config.namespace ||= $0.to_s
100
+
101
+ # ZMQ sockets are not thread/process safe
102
+ daemonize if Zebra.config.daemonize
103
+
104
+ begin
105
+ case Zebra.config.mode.to_sym
106
+ when :server
107
+ config = Zebra.config.server || {}
108
+ config[:logger] = Zebra.log
109
+ @controller = ProxyServer.new
110
+ when :worker
111
+ config = Zebra.config.worker || {}
112
+ config[:logger] = Zebra.log
113
+ @controller = ProxyWorker.new(config)
114
+ when :queue
115
+ config = Zebra.config.queue || {}
116
+ config[:logger] = Zebra.log
117
+ @controller = Queue.new(config)
118
+ else
119
+ raise UnsupportedArgumentException.new("Cannot handle #{Zebra.config.mode} mode")
120
+ end
121
+
122
+
123
+ if Zebra.config.pid_file?
124
+ Zebra.log.debug("Writing pid file #{Zebra.config.pid_file}")
125
+ File.open(Zebra.config.pid_file, 'w') do |f|
126
+ f.write(Process.pid)
127
+ end
128
+ end
129
+
130
+
131
+ @controller.dispatch
132
+ rescue Interrupt => e
133
+ Zebra.log.info e.message
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,65 @@
1
+ require 'singleton'
2
+ require 'deep_merge'
3
+ require 'yaml'
4
+ require 'fileutils'
5
+ require 'net/http'
6
+
7
+ module Zebra
8
+ class UnsupportedURISchemeException < Exception; end
9
+ class ResourceNotFoundException < Exception; end
10
+
11
+ class Config
12
+ include Singleton
13
+ @config = nil
14
+
15
+ def initialize
16
+ @config = {}
17
+ end
18
+
19
+ def read_config_file(config_file)
20
+ if File.exists?(config_file)
21
+ yaml = File.read(config_file)
22
+ else
23
+ raise ResourceNotFoundException.new("Unable to open #{config_file}")
24
+ end
25
+ return yaml
26
+ end
27
+
28
+ def config_file= config_file
29
+ @config[:config_file] = config_file
30
+
31
+ yaml = read_config_file(config_file)
32
+ config = YAML.load(yaml)
33
+ @config.deep_merge!(config)
34
+ @config.symbolize_keys!
35
+ return @config[:config_file]
36
+ end
37
+
38
+ def base_path
39
+ File.expand_path(File.dirname(__FILE__) + '/../../')
40
+ end
41
+
42
+ def nil?
43
+ return @config.empty?
44
+ end
45
+
46
+ def empty?
47
+ return @config.empty?
48
+ end
49
+
50
+ def method_missing(id, *args)
51
+ return nil unless @config.instance_of?(Hash)
52
+
53
+ method_name = id.id2name
54
+ if method_name =~ /^(.*?)\?$/
55
+ return @config.has_key?($1.to_sym) && !@config[$1.to_sym].nil?
56
+ elsif method_name =~ /^(.*?)\=$/
57
+ return @config[$1.to_sym] = args[0]
58
+ elsif @config.has_key?(method_name.to_sym)
59
+ return @config[method_name.to_sym]
60
+ else
61
+ return nil
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,106 @@
1
+ require 'goliath'
2
+ require 'goliath/plugins/latency'
3
+ require 'em-synchrony'
4
+ require 'em-zeromq'
5
+ require 'yajl'
6
+ require 'json'
7
+ require 'base64'
8
+
9
+ module Zebra
10
+ trap('INT') do
11
+ EM::stop() if EM::reactor_running?
12
+ end
13
+
14
+ class Goliath::Server
15
+ def load_config(file = nil)
16
+ config[:context] = EM::ZeroMQ::Context.new(1)
17
+ config[:connection_pool] = EM::Synchrony::ConnectionPool.new(:size => 20) do
18
+ config[:context].socket(ZMQ::REQ) do |socket|
19
+ socket.connect("tcp://localhost:5559")
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ class Goliath::Runner
26
+ def run
27
+ $LOADED_FEATURES.unshift(File.basename($0))
28
+ Dir.chdir(File.expand_path(Zebra.config.chdir))
29
+ @port = 8000
30
+ if Zebra.config.server?
31
+ @port = ::Zebra.config.server[:port]
32
+ end
33
+ run_server
34
+ end
35
+ end
36
+
37
+ class ProxyServer < Goliath::API
38
+ attr_accessor :log
39
+
40
+ use Goliath::Rack::Tracer # log trace statistics
41
+ use Goliath::Rack::DefaultMimeType # cleanup accepted media types
42
+ use Goliath::Rack::Render, 'json' # auto-negotiate response format
43
+ use Goliath::Rack::Params # parse & merge query and body parameters
44
+ use Goliath::Rack::Heartbeat # respond to /status with 200, OK (monitoring, etc)
45
+
46
+ # If you are using Golaith version <=0.9.1 you need to Goliath::Rack::ValidationError
47
+ # to prevent the request from remaining open after an error occurs
48
+ #use Goliath::Rack::ValidationError
49
+ use Goliath::Rack::Validation::RequestMethod, %w(GET POST PUT DELETE) # allow GET and POST requests only
50
+
51
+ plugin Goliath::Plugin::Latency # output reactor latency every second
52
+
53
+ def response(env)
54
+ #ap env
55
+ json = env.select { |k,v| v.instance_of?(String) }.to_json
56
+ @log.debug "Sending #{json}"
57
+
58
+ config[:connection_pool].execute(false) do |conn|
59
+ handler = EM::Protocols::ZMQConnectionHandler.new(conn)
60
+ reply = handler.send_msg(json).first
61
+ #ap reply
62
+ if reply.eql?('null')
63
+ response = [500, {}, 'Server Error']
64
+ else
65
+ response = JSON.parse(reply)
66
+ response[2] = Base64.decode64(response[2])
67
+ end
68
+ #ap response
69
+ return response
70
+ #[200, {}, resp]
71
+ end
72
+ end
73
+
74
+ def initialize(config={})
75
+ super
76
+ @log = config[:logger] || Logger.new(STDERR)
77
+ end
78
+
79
+ def dispatch
80
+ # Don't need to do anything, handled by Goliath
81
+ end
82
+ end
83
+
84
+ class EM::Protocols::ZMQConnectionHandler
85
+ attr_reader :received
86
+
87
+ def initialize(connection)
88
+ @connection = connection
89
+ @client_fiber = Fiber.current
90
+ @connection.setsockopt(ZMQ::IDENTITY, "req-#{@client_fiber.object_id}")
91
+ @connection.handler = self
92
+ end
93
+
94
+ def send_msg(*parts)
95
+ queued = @connection.send_msg(*parts)
96
+ @connection.register_readable
97
+ messages = Fiber.yield
98
+ messages.map(&:copy_out_string)
99
+ end
100
+
101
+ def on_readable(socket, messages)
102
+ @client_fiber.resume(messages)
103
+ end
104
+ end
105
+ end
106
+
@@ -0,0 +1,125 @@
1
+ require 'rubygems'
2
+ require 'ffi-rzmq'
3
+ require 'json'
4
+ require 'logger'
5
+ require 'uuid'
6
+ require 'em-zeromq'
7
+ require 'em-synchrony'
8
+ require 'em-synchrony/em-http'
9
+ require 'em-synchrony/fiber_iterator'
10
+ require 'fiber'
11
+ require 'base64'
12
+ require 'preforker'
13
+
14
+ module Zebra
15
+ class ProxyWorkerReceiveMessageHandler
16
+ attr_reader :received
17
+
18
+ def initialize(config)
19
+ @config = config
20
+ @logger = config[:logger] || Logger.new(STDERR)
21
+ @conns = {}
22
+ end
23
+
24
+ def to_headers(response_headers)
25
+ raw_headers = {}
26
+ response_headers.select { |k,v| k =~ /^[A-Z0-9_]+$/ }.each_pair { |k,v| raw_headers[ k.downcase.split('_').collect { |e| e.capitalize }.join('-') ] = v}
27
+ raw_headers
28
+ end
29
+
30
+ def get_conn(url)
31
+ uri = URI.parse(url)
32
+ conn_key = uri.scheme + '://' + uri.host
33
+ return EM::HttpRequest.new(conn_key, :connect_timeout => 1, :inactivity_timeout => 1)
34
+ if @conns.has_key?(conn_key)
35
+ return @conns[conn_key]
36
+ else
37
+ return @conns[conn_key] = EM::HttpRequest.new(conn_key, :connect_timeout => 1, :inactivity_timeout => 1)
38
+ end
39
+ end
40
+
41
+ def fetch(method, url, request_headers = {})
42
+ @response = nil
43
+ @logger.debug "Proxying #{method} #{url} #{request_headers.inspect}"
44
+ t_start = Time.now
45
+ uri = URI.parse(url)
46
+ conn = get_conn(url)
47
+ request_headers['Host'] = uri.host
48
+ http = conn.send(method, path: uri.path, query: uri.query, head: request_headers, :keepalive => true)
49
+ @logger.debug "Request finished"
50
+ response_headers = to_headers(http.response_header)
51
+ #ap response_headers
52
+ response_headers['X-Proxied-By'] = 'Zebra'
53
+ response_headers.delete('Connection')
54
+ response_headers.delete('Content-Length')
55
+ response_headers.delete('Transfer-Encoding')
56
+ t_end = Time.now
57
+ elapsed = t_end.to_f - t_start.to_f
58
+ @logger.info "#{elapsed} elapsed"
59
+ @logger.info "Received #{http.response_header.status} from server, #{http.response.length} bytes"
60
+ [http.response_header.status, response_headers, Base64.encode64(http.response)]
61
+ end
62
+
63
+ def on_writeable(socket)
64
+ @logger.debug("Writable")
65
+ end
66
+
67
+ def handle_message(m)
68
+ #ap m.copy_out_string
69
+ env = JSON.parse(m.copy_out_string)
70
+ #url = 'http://' + env['HTTP_HOST'] + env['REQUEST_URI']
71
+ url = env['REQUEST_URI']
72
+ method = env['REQUEST_METHOD'].downcase.to_sym
73
+ fetch(method, url)
74
+ end
75
+
76
+ def on_readable(socket, messages)
77
+ @logger.debug "on_readable #{messages.inspect}"
78
+ fiber = Fiber.new do
79
+ m = messages.first
80
+ response = handle_message(m).to_json
81
+ socket.send_msg response
82
+ end
83
+ fiber.resume
84
+ @logger.debug "Finished on_readable"
85
+ end
86
+ end
87
+
88
+ class ProxyWorker
89
+ attr_accessor :log, :workers, :app_name, :timeout
90
+
91
+ def initialize(config = {})
92
+ @log = config[:logger] || Logger.new(STDERR)
93
+ @workers = config[:workers] || 10
94
+ @timeout = config[:timeout] || 3600
95
+ @app_name = config[:app_name] || File.basename($0.split(/ /)[0], '.rb')
96
+ end
97
+
98
+ def dispatch
99
+ params = { :workers => @workers,
100
+ :app_name => @app_name,
101
+ :logger => @log,
102
+ :timeout => @timeout }
103
+ workers = Preforker.new(params) do |master|
104
+ config = {:logger => @log}
105
+ handler = ProxyWorkerReceiveMessageHandler.new(config)
106
+ while master.wants_me_alive? do
107
+ EM.synchrony do
108
+ master.logger.info "Server started..."
109
+
110
+ timer = EventMachine::PeriodicTimer.new(5) do
111
+ master.logger.info "ping #{Process.pid}"
112
+ end
113
+
114
+ context = EM::ZeroMQ::Context.new(1)
115
+ # connection_pool = EM::Synchrony::ConnectionPool.new(:size => 1) do
116
+ socket = context.socket(ZMQ::REP, handler)
117
+ socket.connect('tcp://localhost:5560')
118
+ # end
119
+ end
120
+ end
121
+ end
122
+ workers.run
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,41 @@
1
+ require 'ffi-rzmq'
2
+
3
+ module Zebra
4
+ class Queue
5
+ attr_accessor :context, :frontend, :backend, :poller, :log, :frontend_uri, :backend_uri
6
+ def initialize(config)
7
+ @log = config[:logger] || Logger.new(STDERR)
8
+ @frontend_uri = config[:frontend] || 'tcp://*:5559'
9
+ @backend_uri = config[:backend] || 'tcp://*:5560'
10
+ @context = ZMQ::Context.new
11
+
12
+ # Socket facing clients
13
+ @frontend = context.socket(ZMQ::ROUTER)
14
+
15
+ # Socket facing services
16
+ @backend = context.socket(ZMQ::DEALER)
17
+ trap("INT") do
18
+ @log.info "Shutting down."
19
+ @frontend.close unless @frontend.nil?
20
+ @backend.close unless @backend.nil?
21
+ @context.terminate unless @context.nil?
22
+ exit
23
+ end
24
+ end
25
+
26
+ def dispatch
27
+ @frontend.bind(@frontend_uri)
28
+ @backend.bind(@backend_uri)
29
+ begin
30
+ # Start built-in device
31
+ @poller = ZMQ::Device.new(ZMQ::QUEUE,frontend,backend)
32
+ rescue Interrupt => e
33
+ @log.info("Caught interrupt signal...")
34
+ end
35
+
36
+ @frontend.close
37
+ @backend.close
38
+ @context.terminate
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,3 @@
1
+ module Zebra
2
+ VERSION = '1.0.0'
3
+ end
@@ -0,0 +1,32 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "zebra/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "zebra"
7
+ s.version = Zebra::VERSION
8
+ s.authors = ["osterman"]
9
+ s.email = ["e@osterman.com"]
10
+ s.homepage = "https://github.com/osterman/zebra"
11
+ s.summary = %q{A Goliath ZMQ Reverse HTTP Proxy Implementation}
12
+ s.description = %q{A proxy that uses ZMQ as the wire protocol between HTTP proxy gateway and backend worker nodes. This allows for each load distribution to worker nodes behind a firewall.}
13
+
14
+ s.rubyforge_project = "zebra"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_runtime_dependency "bundler"
22
+ s.add_runtime_dependency "deep_merge", ">= 1.0.0"
23
+ s.add_runtime_dependency "em-synchrony", ">= 1.0.0"
24
+ s.add_runtime_dependency "em-http-request", ">=1.0.2"
25
+ s.add_runtime_dependency "em-zeromq", ">= 0.3.0"
26
+ s.add_runtime_dependency "ffi-rzmq", ">= 0.9.3"
27
+ s.add_runtime_dependency "goliath", ">= 0.9.4"
28
+ s.add_runtime_dependency "json", ">= 1.6.5"
29
+ s.add_runtime_dependency "preforker", ">= 0.1.1"
30
+ s.add_runtime_dependency "uuid", ">= 2.3.5"
31
+ s.add_runtime_dependency "yajl-ruby", ">= 1.1.0"
32
+ end
metadata ADDED
@@ -0,0 +1,254 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: zebra
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease: false
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 0
10
+ version: 1.0.0
11
+ platform: ruby
12
+ authors:
13
+ - osterman
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-07-03 00:00:00 -07:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: bundler
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: deep_merge
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 23
44
+ segments:
45
+ - 1
46
+ - 0
47
+ - 0
48
+ version: 1.0.0
49
+ type: :runtime
50
+ version_requirements: *id002
51
+ - !ruby/object:Gem::Dependency
52
+ name: em-synchrony
53
+ prerelease: false
54
+ requirement: &id003 !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ hash: 23
60
+ segments:
61
+ - 1
62
+ - 0
63
+ - 0
64
+ version: 1.0.0
65
+ type: :runtime
66
+ version_requirements: *id003
67
+ - !ruby/object:Gem::Dependency
68
+ name: em-http-request
69
+ prerelease: false
70
+ requirement: &id004 !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ hash: 19
76
+ segments:
77
+ - 1
78
+ - 0
79
+ - 2
80
+ version: 1.0.2
81
+ type: :runtime
82
+ version_requirements: *id004
83
+ - !ruby/object:Gem::Dependency
84
+ name: em-zeromq
85
+ prerelease: false
86
+ requirement: &id005 !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ hash: 19
92
+ segments:
93
+ - 0
94
+ - 3
95
+ - 0
96
+ version: 0.3.0
97
+ type: :runtime
98
+ version_requirements: *id005
99
+ - !ruby/object:Gem::Dependency
100
+ name: ffi-rzmq
101
+ prerelease: false
102
+ requirement: &id006 !ruby/object:Gem::Requirement
103
+ none: false
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ hash: 61
108
+ segments:
109
+ - 0
110
+ - 9
111
+ - 3
112
+ version: 0.9.3
113
+ type: :runtime
114
+ version_requirements: *id006
115
+ - !ruby/object:Gem::Dependency
116
+ name: goliath
117
+ prerelease: false
118
+ requirement: &id007 !ruby/object:Gem::Requirement
119
+ none: false
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ hash: 51
124
+ segments:
125
+ - 0
126
+ - 9
127
+ - 4
128
+ version: 0.9.4
129
+ type: :runtime
130
+ version_requirements: *id007
131
+ - !ruby/object:Gem::Dependency
132
+ name: json
133
+ prerelease: false
134
+ requirement: &id008 !ruby/object:Gem::Requirement
135
+ none: false
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ hash: 5
140
+ segments:
141
+ - 1
142
+ - 6
143
+ - 5
144
+ version: 1.6.5
145
+ type: :runtime
146
+ version_requirements: *id008
147
+ - !ruby/object:Gem::Dependency
148
+ name: preforker
149
+ prerelease: false
150
+ requirement: &id009 !ruby/object:Gem::Requirement
151
+ none: false
152
+ requirements:
153
+ - - ">="
154
+ - !ruby/object:Gem::Version
155
+ hash: 25
156
+ segments:
157
+ - 0
158
+ - 1
159
+ - 1
160
+ version: 0.1.1
161
+ type: :runtime
162
+ version_requirements: *id009
163
+ - !ruby/object:Gem::Dependency
164
+ name: uuid
165
+ prerelease: false
166
+ requirement: &id010 !ruby/object:Gem::Requirement
167
+ none: false
168
+ requirements:
169
+ - - ">="
170
+ - !ruby/object:Gem::Version
171
+ hash: 9
172
+ segments:
173
+ - 2
174
+ - 3
175
+ - 5
176
+ version: 2.3.5
177
+ type: :runtime
178
+ version_requirements: *id010
179
+ - !ruby/object:Gem::Dependency
180
+ name: yajl-ruby
181
+ prerelease: false
182
+ requirement: &id011 !ruby/object:Gem::Requirement
183
+ none: false
184
+ requirements:
185
+ - - ">="
186
+ - !ruby/object:Gem::Version
187
+ hash: 19
188
+ segments:
189
+ - 1
190
+ - 1
191
+ - 0
192
+ version: 1.1.0
193
+ type: :runtime
194
+ version_requirements: *id011
195
+ description: A proxy that uses ZMQ as the wire protocol between HTTP proxy gateway and backend worker nodes. This allows for each load distribution to worker nodes behind a firewall.
196
+ email:
197
+ - e@osterman.com
198
+ executables:
199
+ - zebra
200
+ extensions: []
201
+
202
+ extra_rdoc_files: []
203
+
204
+ files:
205
+ - .gitignore
206
+ - Gemfile
207
+ - Rakefile
208
+ - bin/zebra
209
+ - contrib/config.yml
210
+ - lib/core_ext/hash.rb
211
+ - lib/zebra.rb
212
+ - lib/zebra/command_line.rb
213
+ - lib/zebra/config.rb
214
+ - lib/zebra/proxy_server.rb
215
+ - lib/zebra/proxy_worker.rb
216
+ - lib/zebra/queue.rb
217
+ - lib/zebra/version.rb
218
+ - zebra.gemspec
219
+ has_rdoc: true
220
+ homepage: https://github.com/osterman/zebra
221
+ licenses: []
222
+
223
+ post_install_message:
224
+ rdoc_options: []
225
+
226
+ require_paths:
227
+ - lib
228
+ required_ruby_version: !ruby/object:Gem::Requirement
229
+ none: false
230
+ requirements:
231
+ - - ">="
232
+ - !ruby/object:Gem::Version
233
+ hash: 3
234
+ segments:
235
+ - 0
236
+ version: "0"
237
+ required_rubygems_version: !ruby/object:Gem::Requirement
238
+ none: false
239
+ requirements:
240
+ - - ">="
241
+ - !ruby/object:Gem::Version
242
+ hash: 3
243
+ segments:
244
+ - 0
245
+ version: "0"
246
+ requirements: []
247
+
248
+ rubyforge_project: zebra
249
+ rubygems_version: 1.3.7
250
+ signing_key:
251
+ specification_version: 3
252
+ summary: A Goliath ZMQ Reverse HTTP Proxy Implementation
253
+ test_files: []
254
+