zebra 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+