DanaDanger-equity 0.3 → 0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/equity +132 -20
- data/bin/equityctl +4 -4
- data/lib/equity/node.rb +27 -4
- metadata +2 -2
data/bin/equity
CHANGED
@@ -1,41 +1,145 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
#
|
3
|
-
# equity 0.
|
3
|
+
# equity 0.4 - queueing software load balancer with transmission inspection
|
4
|
+
# and simple HTTP header rewriting
|
4
5
|
#
|
5
|
-
# Usage: equity [-
|
6
|
-
#
|
7
|
-
# The -d option turns on debug mode. Equity will remain in the foreground and
|
8
|
-
# print status messages.
|
9
|
-
#
|
10
|
-
# Node addresses default to localhost.
|
6
|
+
# Usage: equity [-dp] [-h "Header: Value"] <listen-port> [<node-address>:]<node-port> ...
|
7
|
+
# Run equity with no arguments for detailed usage information.
|
11
8
|
#
|
12
9
|
|
13
10
|
require 'socket'
|
14
11
|
require 'equity/node'
|
15
12
|
require 'equity/manager'
|
13
|
+
require 'getoptlong'
|
14
|
+
|
15
|
+
# Returns a string that can be used to identify a particular node in debugging
|
16
|
+
# messages. At the moment this returns the address and port of the client being
|
17
|
+
# served by the node.
|
18
|
+
def client_id(node)
|
19
|
+
if node.connected?
|
20
|
+
"#{node.client_address}:#{node.client_port}"
|
21
|
+
else
|
22
|
+
"not connected"
|
23
|
+
end
|
24
|
+
end
|
16
25
|
|
17
26
|
# Prints a debug message if debugging is enabled.
|
18
27
|
def debug(socket, message)
|
19
|
-
return unless $debugging
|
20
|
-
|
28
|
+
return unless $debugging
|
29
|
+
if socket
|
30
|
+
if node = Equity::Node.with_socket(socket)
|
31
|
+
client_id = client_id(node)
|
32
|
+
else
|
33
|
+
client_id = nil
|
34
|
+
begin
|
35
|
+
nameinfo = Socket.getnameinfo(socket.getpeername,
|
36
|
+
Socket::NI_NUMERICHOST | Socket::NI_NUMERICSERV)
|
37
|
+
client_id = "#{nameinfo[0]}:#{nameinfo[1]}"
|
38
|
+
rescue
|
39
|
+
client_id = "???:???"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
print "[#{client_id}] "
|
43
|
+
end
|
21
44
|
puts message
|
22
45
|
end
|
23
46
|
|
47
|
+
# Prints data in hex and ASCII if packet dumping is enabled.
|
48
|
+
BYTES_PER_PACKET_LINE = 16
|
49
|
+
SAFE_PACKET_RX = (32..126).to_a.collect! {|n| n.chr}.join("").sub!(/[\[\]]/, '\\\1')
|
50
|
+
def dump_packet(data, src, dst)
|
51
|
+
return unless $dump_packets
|
52
|
+
|
53
|
+
node = Equity::Node.with_socket(src) || return
|
54
|
+
to_server = (src == node.sockets.first)
|
55
|
+
arrow = (to_server ? "--->" : "<---")
|
56
|
+
print "[#{client_id(node)}] #{arrow} #{node.address}:#{node.port}\n\n"
|
57
|
+
|
58
|
+
data = data.dup
|
59
|
+
until data.empty?
|
60
|
+
bytes = data.slice!(0, BYTES_PER_PACKET_LINE)
|
61
|
+
hex = bytes.unpack("H*").first + (" " * (BYTES_PER_PACKET_LINE - bytes.length))
|
62
|
+
hex1 = hex.slice!(0, BYTES_PER_PACKET_LINE)
|
63
|
+
hex2 = hex
|
64
|
+
hex1.gsub!(/(....)/, '\1 ')
|
65
|
+
hex2.gsub!(/(....)/, '\1 ')
|
66
|
+
bytes.gsub!(/[^#{SAFE_PACKET_RX}]/, ".")
|
67
|
+
puts " #{hex1} #{hex2} #{bytes}"
|
68
|
+
end
|
69
|
+
print "\n"
|
70
|
+
end
|
71
|
+
|
72
|
+
# Handles rewriting of HTTP request headers. This method isn't very smart about
|
73
|
+
# how it detects HTTP requests, but should work for most situations.
|
74
|
+
def rewrite_headers!(data, src)
|
75
|
+
return if $header_rewrites.empty?
|
76
|
+
return unless http_slash_idx = data.index(" HTTP/")
|
77
|
+
node = Equity::Node.with_socket(src) || return
|
78
|
+
return unless (src == node.sockets.first)
|
79
|
+
|
80
|
+
debug(src, "rewriting HTTP request headers")
|
81
|
+
|
82
|
+
before_headers = data.slice!(0, data.index("\r\n", http_slash_idx))
|
83
|
+
headers = data.slice!(0, data.index("\r\n\r\n"))
|
84
|
+
after_headers = data
|
85
|
+
|
86
|
+
$header_rewrites.each do |header, value|
|
87
|
+
if headers.sub!(/\r\n#{header}: (.+?)\r\n/m, "\r\n#{header}: #{value}\r\n")
|
88
|
+
debug(src, " #{header}: #{$1} => #{value}")
|
89
|
+
end
|
90
|
+
end
|
91
|
+
data.replace(before_headers + headers + after_headers)
|
92
|
+
rescue
|
93
|
+
# If an exception occurs, ignore it. Rewriting the headers is far less
|
94
|
+
# important than keeping the program alive.
|
95
|
+
end
|
96
|
+
|
24
97
|
|
25
|
-
# Process arguments
|
26
|
-
$debugging
|
98
|
+
# Process arguments.
|
99
|
+
$debugging = !!ARGV.delete('-d')
|
100
|
+
$dump_packets = !!ARGV.delete('-p')
|
101
|
+
$header_rewrites = {}
|
102
|
+
|
103
|
+
options = GetoptLong.new(
|
104
|
+
["-d", "--debug", GetoptLong::NO_ARGUMENT],
|
105
|
+
["-p", "--packets", GetoptLong::NO_ARGUMENT],
|
106
|
+
["-h", "--header", GetoptLong::REQUIRED_ARGUMENT]
|
107
|
+
)
|
108
|
+
options.each do |option, argument|
|
109
|
+
case option
|
110
|
+
when "-d"
|
111
|
+
$debugging = true
|
112
|
+
when "-p"
|
113
|
+
$dump_packets = true
|
114
|
+
when "-h"
|
115
|
+
header = argument.split(/: +/)
|
116
|
+
unless header.length == 2
|
117
|
+
STDERR.puts "I don't understand this header rewrite:"
|
118
|
+
STDERR.puts " #{argument}"
|
119
|
+
STDERR.puts "Run equity with no arguments for help."
|
120
|
+
exit(64) # EX_USAGE
|
121
|
+
end
|
122
|
+
$header_rewrites[header.first] = header.last
|
123
|
+
end
|
124
|
+
end
|
27
125
|
|
28
126
|
if ARGV.length < 2
|
29
|
-
STDERR.puts 'equity 0.
|
30
|
-
STDERR.puts 'Usage: equity [
|
31
|
-
STDERR.print "\n"
|
32
|
-
STDERR.puts 'The -d option turns on debug mode. Equity will remain in the foreground and'
|
33
|
-
STDERR.puts 'print status messages.'
|
127
|
+
STDERR.puts 'equity 0.4'
|
128
|
+
STDERR.puts 'Usage: equity [<options>] <listen-port> [<node-address>:]<node-port> ...'
|
34
129
|
STDERR.print "\n"
|
35
130
|
STDERR.puts 'Node addresses default to localhost.'
|
131
|
+
STDERR.print "\n"
|
132
|
+
STDERR.puts 'The following options can be used:'
|
133
|
+
STDERR.puts '-d Debug mode. Stays in the foreground and prints status messages.'
|
134
|
+
STDERR.puts '-p Packet dumper. Stays in the foreground and displays the raw data being'
|
135
|
+
STDERR.puts ' transferred.'
|
136
|
+
STDERR.puts '-h "<header>: <value>" Rewrite an HTTP request header before retransmitting.'
|
137
|
+
STDERR.puts ' Can be used more than once to rewrite several headers.'
|
138
|
+
STDERR.print "\n"
|
36
139
|
exit(64) # EX_USAGE
|
37
140
|
end
|
38
141
|
|
142
|
+
# Instantiate nodes.
|
39
143
|
$listen_port = ARGV.shift.to_i
|
40
144
|
|
41
145
|
$nodes = []
|
@@ -49,7 +153,7 @@ ARGV.each do |node_spec|
|
|
49
153
|
end
|
50
154
|
|
51
155
|
# Daemonize.
|
52
|
-
unless $debugging
|
156
|
+
unless $debugging || $dump_packets
|
53
157
|
fork && exit
|
54
158
|
Process.setsid
|
55
159
|
trap 'SIGHUP', 'IGNORE'
|
@@ -66,6 +170,11 @@ unless $debugging
|
|
66
170
|
STDERR.reopen STDOUT
|
67
171
|
end
|
68
172
|
|
173
|
+
# Print debugging info for header rewrites.
|
174
|
+
$header_rewrites.each do |header, value|
|
175
|
+
debug(nil, "Will rewrite value of HTTP request header `#{header}' to `#{value}'")
|
176
|
+
end
|
177
|
+
|
69
178
|
# Set up SIGINT handler to print node counters.
|
70
179
|
trap 'SIGINT', Proc.new {
|
71
180
|
debug(nil, "\nNode Counters")
|
@@ -106,7 +215,10 @@ while true
|
|
106
215
|
begin
|
107
216
|
data = socket.recvfrom(65536)[0]
|
108
217
|
raise Errno::EPIPE if data.empty?
|
109
|
-
socket.mate
|
218
|
+
mate = socket.mate
|
219
|
+
rewrite_headers!(data, socket)
|
220
|
+
dump_packet(data, socket, mate)
|
221
|
+
mate.write(data)
|
110
222
|
rescue Errno::EPIPE, Errno::ECONNRESET
|
111
223
|
# Connection closed. Disconnect this node.
|
112
224
|
node.disconnect
|
@@ -127,8 +239,8 @@ while true
|
|
127
239
|
next if node.connected?
|
128
240
|
begin
|
129
241
|
node.connect(client)
|
130
|
-
rescue
|
131
|
-
debug(client, "connection to #{node} failed")
|
242
|
+
rescue Exception => e
|
243
|
+
debug(client, "connection to #{node} failed: #{e.message}")
|
132
244
|
failure_count += 1
|
133
245
|
next
|
134
246
|
end
|
data/bin/equityctl
CHANGED
@@ -23,9 +23,9 @@ if ARGV.length != 2
|
|
23
23
|
STDERR.puts 'The host defaults to localhost.'
|
24
24
|
STDERR.print "\n"
|
25
25
|
STDERR.puts 'Command Description'
|
26
|
-
STDERR.puts 'status Displays each node\'s address, port, connection status,
|
27
|
-
STDERR.puts ' connection counter.'
|
28
|
-
exit(64)
|
26
|
+
STDERR.puts 'status Displays each node\'s address, port, connection status,'
|
27
|
+
STDERR.puts ' connection counter, and failure counter.'
|
28
|
+
exit(64) # EX_USAGE
|
29
29
|
end
|
30
30
|
|
31
31
|
address, port = ARGV.shift.split(':', 2)
|
@@ -45,7 +45,7 @@ begin
|
|
45
45
|
when 'status'
|
46
46
|
nodes = controller.node_status || raise(NoResponseError)
|
47
47
|
nodes.each do |node|
|
48
|
-
puts "#{node} #{node.connected? ? 'C' : '-'} #{node.counter}"
|
48
|
+
puts "#{node} #{node.connected? ? 'C' : '-'} #{node.counter} #{node.failure_counter}"
|
49
49
|
end
|
50
50
|
else
|
51
51
|
STDERR.puts "Unrecognized command: #{command}"
|
data/lib/equity/node.rb
CHANGED
@@ -4,14 +4,18 @@ module Equity
|
|
4
4
|
# Represents a cluster node.
|
5
5
|
class Node
|
6
6
|
attr_reader :counter
|
7
|
+
attr_reader :failure_counter
|
7
8
|
attr_reader :address
|
8
9
|
attr_reader :port
|
10
|
+
attr_reader :client_address
|
11
|
+
attr_reader :client_port
|
9
12
|
|
10
13
|
def initialize(address, port)
|
11
|
-
@client
|
12
|
-
@counter
|
13
|
-
@
|
14
|
-
@
|
14
|
+
@client = nil
|
15
|
+
@counter = 0
|
16
|
+
@failure_counter = 0
|
17
|
+
@address = address.dup
|
18
|
+
@port = port.to_i
|
15
19
|
end
|
16
20
|
|
17
21
|
def connect(client)
|
@@ -22,10 +26,20 @@ module Equity
|
|
22
26
|
@server.extend SocketPairing
|
23
27
|
@server.pair_with(client)
|
24
28
|
@counter += 1
|
29
|
+
|
30
|
+
begin
|
31
|
+
nameinfo = Socket.getnameinfo(@client.getpeername,
|
32
|
+
Socket::NI_NUMERICHOST | Socket::NI_NUMERICSERV)
|
33
|
+
@client_address, @client_port = nameinfo
|
34
|
+
rescue
|
35
|
+
@client_address, @client_port = "???", "???"
|
36
|
+
end
|
37
|
+
|
25
38
|
@server
|
26
39
|
rescue Exception => e
|
27
40
|
@client = nil
|
28
41
|
@server = nil
|
42
|
+
@failure_counter += 1
|
29
43
|
raise e
|
30
44
|
end
|
31
45
|
end
|
@@ -35,6 +49,8 @@ module Equity
|
|
35
49
|
@server.close
|
36
50
|
@client = nil
|
37
51
|
@server = nil
|
52
|
+
@client_address = nil
|
53
|
+
@client_port = nil
|
38
54
|
end
|
39
55
|
|
40
56
|
def connected?
|
@@ -53,6 +69,13 @@ module Equity
|
|
53
69
|
"#{@address}:#{@port}"
|
54
70
|
end
|
55
71
|
|
72
|
+
def self.with_socket(socket)
|
73
|
+
ObjectSpace.each_object(self) do |node|
|
74
|
+
return node if node.owns_socket?(socket)
|
75
|
+
end
|
76
|
+
nil
|
77
|
+
end
|
78
|
+
|
56
79
|
class Static < Node
|
57
80
|
attr_writer :counter
|
58
81
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: DanaDanger-equity
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: "0.
|
4
|
+
version: "0.4"
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- DanaDanger
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date:
|
12
|
+
date: 2009-04-08 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|