bane 0.3.0 → 0.4.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 +15 -0
- data/README.md +16 -6
- data/Rakefile +1 -1
- data/TODO +5 -8
- data/bin/bane +4 -12
- data/examples/multiple_behaviors.rb +7 -6
- data/examples/readme_example.rb +2 -1
- data/examples/single_behavior.rb +2 -1
- data/lib/bane.rb +18 -5
- data/lib/bane/arguments_parser.rb +73 -0
- data/lib/bane/behavior_maker.rb +39 -0
- data/lib/bane/behaviors/responders/close_after_pause.rb +21 -0
- data/lib/bane/behaviors/responders/close_immediately.rb +14 -0
- data/lib/bane/behaviors/responders/deluge_response.rb +27 -0
- data/lib/bane/behaviors/responders/echo_response.rb +16 -0
- data/lib/bane/behaviors/responders/exported.rb +9 -0
- data/lib/bane/behaviors/responders/fixed_response.rb +25 -0
- data/lib/bane/behaviors/responders/for_each_line.rb +18 -0
- data/lib/bane/behaviors/responders/http_refuse_all_credentials.rb +31 -0
- data/lib/bane/behaviors/responders/never_respond.rb +27 -0
- data/lib/bane/behaviors/responders/newline_response.rb +18 -0
- data/lib/bane/behaviors/responders/random_response.rb +24 -0
- data/lib/bane/behaviors/responders/slow_response.rb +32 -0
- data/lib/bane/behaviors/servers/exported.rb +18 -0
- data/lib/bane/behaviors/servers/responder_server.rb +50 -0
- data/lib/bane/behaviors/servers/timeout_in_listen_queue.rb +51 -0
- data/lib/bane/command_line_configuration.rb +22 -79
- data/lib/bane/extensions.rb +7 -0
- data/test/bane/acceptance_test.rb +65 -0
- data/test/bane/arguments_parser_test.rb +76 -0
- data/test/bane/bane_test.rb +12 -0
- data/test/bane/behaviors/responders/close_after_pause_test.rb +30 -0
- data/test/bane/behaviors/responders/close_immediately_test.rb +14 -0
- data/test/bane/behaviors/responders/deluge_response_test.rb +20 -0
- data/test/bane/behaviors/responders/echo_response_test.rb +16 -0
- data/test/bane/behaviors/responders/fixed_response_test.rb +14 -0
- data/test/bane/behaviors/responders/for_each_line_test.rb +29 -0
- data/test/bane/behaviors/responders/http_refuse_all_credentials_test.rb +18 -0
- data/test/bane/behaviors/responders/never_respond_test.rb +31 -0
- data/test/bane/behaviors/responders/newline_response_test.rb +14 -0
- data/test/bane/behaviors/responders/random_response_test.rb +15 -0
- data/test/bane/behaviors/responders/slow_response_test.rb +21 -0
- data/test/bane/behaviors/servers/responder_server_test.rb +77 -0
- data/test/bane/behaviors/servers/timeout_in_listen_queue_test.rb +23 -0
- data/test/bane/command_line_configuration_test.rb +54 -72
- data/test/bane/extensions_test.rb +17 -0
- data/test/bane/fake_connection_test.rb +1 -1
- data/test/bane/launchable_role_tests.rb +20 -0
- data/test/bane/naive_http_response_test.rb +1 -1
- data/test/test_helper.rb +42 -1
- metadata +143 -99
- data/lib/bane/behavior_server.rb +0 -47
- data/lib/bane/behaviors.rb +0 -171
- data/lib/bane/compatibility.rb +0 -36
- data/lib/bane/configuration_parser.rb +0 -89
- data/lib/bane/service_registry.rb +0 -21
- data/test/bane/behavior_server_test.rb +0 -59
- data/test/bane/behaviors_test.rb +0 -135
- data/test/bane/configuration_parser_test.rb +0 -111
- data/test/bane/integration_test.rb +0 -92
- data/test/bane/service_registry_test.rb +0 -20
@@ -0,0 +1,18 @@
|
|
1
|
+
module Bane
|
2
|
+
module Behaviors
|
3
|
+
module Responders
|
4
|
+
|
5
|
+
# This module can be used to wrap another behavior with
|
6
|
+
# a "while(io.gets)" loop, which reads a line from the input and
|
7
|
+
# then performs the given behavior.
|
8
|
+
module ForEachLine
|
9
|
+
def serve(io)
|
10
|
+
while (io.gets)
|
11
|
+
super(io)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Bane
|
2
|
+
module Behaviors
|
3
|
+
module Responders
|
4
|
+
|
5
|
+
# Sends an HTTP 401 response (Unauthorized) for every request. This
|
6
|
+
# attempts to mimic an HTTP server by reading a line (the request)
|
7
|
+
# and then sending the response. This behavior responds to all
|
8
|
+
# incoming request URLs on the running port.
|
9
|
+
class HttpRefuseAllCredentials
|
10
|
+
UNAUTHORIZED_RESPONSE_BODY = <<EOF
|
11
|
+
<!DOCTYPE html>
|
12
|
+
<html>
|
13
|
+
<head>
|
14
|
+
<title>Bane Server</title>
|
15
|
+
</head>
|
16
|
+
<body>
|
17
|
+
<h1>Unauthorized</h1>
|
18
|
+
</body>
|
19
|
+
</html>
|
20
|
+
EOF
|
21
|
+
|
22
|
+
def serve(io)
|
23
|
+
io.gets # Read the request before responding
|
24
|
+
response = NaiveHttpResponse.new(401, "Unauthorized", "text/html", UNAUTHORIZED_RESPONSE_BODY)
|
25
|
+
io.write(response.to_s)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Bane
|
2
|
+
module Behaviors
|
3
|
+
module Responders
|
4
|
+
|
5
|
+
# Accepts a connection and never sends a byte of data. The connection is
|
6
|
+
# left open indefinitely.
|
7
|
+
class NeverRespond
|
8
|
+
READ_TIMEOUT_IN_SECONDS = 2
|
9
|
+
MAXIMUM_BYTES_TO_READ = 4096
|
10
|
+
|
11
|
+
def serve(io)
|
12
|
+
loop do
|
13
|
+
begin
|
14
|
+
io.read_nonblock(MAXIMUM_BYTES_TO_READ)
|
15
|
+
rescue Errno::EAGAIN
|
16
|
+
IO.select([io], nil, nil, READ_TIMEOUT_IN_SECONDS)
|
17
|
+
retry # Ignore the result of IO select since we retry reads regardless of if there's data to be read or not
|
18
|
+
rescue EOFError
|
19
|
+
break
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Bane
|
2
|
+
module Behaviors
|
3
|
+
module Responders
|
4
|
+
|
5
|
+
# Sends a newline character as the only response
|
6
|
+
class NewlineResponse
|
7
|
+
def serve(io)
|
8
|
+
io.write "\n"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class NewlineResponseForEachLine < NewlineResponse
|
13
|
+
include ForEachLine
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Bane
|
2
|
+
module Behaviors
|
3
|
+
module Responders
|
4
|
+
|
5
|
+
# Sends a random response.
|
6
|
+
class RandomResponse
|
7
|
+
def serve(io)
|
8
|
+
io.write random_string
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
def random_string
|
13
|
+
(1..rand(26)+1).map { |i| ('a'..'z').to_a[rand(26)] }.join
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
class RandomResponseForEachLine < RandomResponse
|
19
|
+
include ForEachLine
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Bane
|
2
|
+
module Behaviors
|
3
|
+
module Responders
|
4
|
+
|
5
|
+
# Sends a fixed response character-by-character, pausing between each character.
|
6
|
+
#
|
7
|
+
# Options:
|
8
|
+
# - message: The response to send. Default: "Hello, world!"
|
9
|
+
# - pause_duration: The number of seconds to pause between each character. Default: 10 seconds
|
10
|
+
class SlowResponse
|
11
|
+
def initialize(options = {})
|
12
|
+
@options = {message: "Hello, world!", pause_duration: 10}.merge(options)
|
13
|
+
end
|
14
|
+
|
15
|
+
def serve(io)
|
16
|
+
message = @options[:message]
|
17
|
+
pause_duration = @options[:pause_duration]
|
18
|
+
|
19
|
+
message.each_char do |char|
|
20
|
+
io.write char
|
21
|
+
sleep pause_duration
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class SlowResponseForEachLine < SlowResponse
|
27
|
+
include ForEachLine
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Bane
|
2
|
+
module Behaviors
|
3
|
+
module Servers
|
4
|
+
|
5
|
+
EXPORTED = [TimeoutInListenQueue]
|
6
|
+
|
7
|
+
# Listen only on localhost
|
8
|
+
LOCALHOST = '127.0.0.1'
|
9
|
+
|
10
|
+
# Deprecated - use LOCALHOST - Listen only on localhost
|
11
|
+
DEFAULT_HOST = LOCALHOST
|
12
|
+
|
13
|
+
# Listen on all interfaces
|
14
|
+
ALL_INTERFACES = '0.0.0.0'
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'gserver'
|
2
|
+
|
3
|
+
module Bane
|
4
|
+
module Behaviors
|
5
|
+
module Servers
|
6
|
+
|
7
|
+
class ResponderServer < GServer
|
8
|
+
|
9
|
+
def initialize(port, behavior, host = Servers::LOCALHOST)
|
10
|
+
super(port, host)
|
11
|
+
@behavior = behavior
|
12
|
+
self.audit = true
|
13
|
+
end
|
14
|
+
|
15
|
+
def serve(io)
|
16
|
+
@behavior.serve(io)
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
"<Bane::BehaviorServer: port=#{@port}, behavior=#{@behavior.class}>"
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
|
25
|
+
alias_method :original_log, :log
|
26
|
+
|
27
|
+
def log(message)
|
28
|
+
original_log("#{@behavior.class.unqualified_name} #{@host}:#{@port} #{message}")
|
29
|
+
end
|
30
|
+
|
31
|
+
def connecting(client)
|
32
|
+
addr = client.peeraddr
|
33
|
+
log("client:#{addr[1]} #{addr[2]}<#{addr[3]}> connect")
|
34
|
+
end
|
35
|
+
|
36
|
+
def disconnecting(client_port)
|
37
|
+
log("client:#{client_port} disconnect")
|
38
|
+
end
|
39
|
+
|
40
|
+
def starting
|
41
|
+
log('start')
|
42
|
+
end
|
43
|
+
|
44
|
+
def stopping
|
45
|
+
log('stop')
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
module Bane
|
4
|
+
module Behaviors
|
5
|
+
module Servers
|
6
|
+
|
7
|
+
class TimeoutInListenQueue
|
8
|
+
|
9
|
+
def initialize(port, host = Servers::LOCALHOST)
|
10
|
+
@port = port
|
11
|
+
@host = host
|
12
|
+
self.stdlog= $stderr
|
13
|
+
end
|
14
|
+
|
15
|
+
def start
|
16
|
+
@server = Socket.new(:INET, :STREAM)
|
17
|
+
address = Socket.sockaddr_in(port, host)
|
18
|
+
@server.bind(address) # Note that we never call listen
|
19
|
+
|
20
|
+
log 'started'
|
21
|
+
end
|
22
|
+
|
23
|
+
def join
|
24
|
+
sleep
|
25
|
+
end
|
26
|
+
|
27
|
+
def stop
|
28
|
+
@server.close
|
29
|
+
log 'stopped'
|
30
|
+
end
|
31
|
+
|
32
|
+
def stdlog=(logger)
|
33
|
+
@logger = logger
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.make(port, host)
|
37
|
+
new(port, host)
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
attr_reader :host, :port, :logger
|
43
|
+
|
44
|
+
def log(message)
|
45
|
+
logger.puts "[#{Time.new.ctime}] #{self.class.unqualified_name} #{host}:#{port} #{message}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -1,96 +1,39 @@
|
|
1
|
-
require 'optparse'
|
2
|
-
|
3
1
|
module Bane
|
4
|
-
class CommandLineConfiguration
|
5
|
-
def initialize()
|
6
|
-
@options = { :host => BehaviorServer::DEFAULT_HOST }
|
7
|
-
@option_parser = init_option_parser
|
8
|
-
end
|
9
|
-
|
10
|
-
def parse(args)
|
11
|
-
parse_options(@options, args)
|
12
|
-
|
13
|
-
return [] if (args.empty?)
|
14
|
-
|
15
|
-
port = parse_port(args[0])
|
16
|
-
behaviors = parse_behaviors(args.drop(1))
|
17
|
-
|
18
|
-
behaviors = ServiceRegistry.all_servers if behaviors.empty?
|
19
|
-
LinearPortMappedBehaviorConfiguration.new(port, behaviors, @options[:host]).servers
|
20
|
-
end
|
21
|
-
|
22
|
-
def usage
|
23
|
-
@option_parser.help
|
24
|
-
end
|
25
|
-
|
26
|
-
private
|
27
|
-
|
28
|
-
def init_option_parser
|
29
|
-
OptionParser.new do |opts|
|
30
|
-
opts.banner = "Usage: bane [options] port [behaviors]"
|
31
|
-
opts.separator ""
|
32
|
-
opts.on("-l", "--listen-on-localhost",
|
33
|
-
"Listen on localhost, (#{BehaviorServer::DEFAULT_HOST}). [default]") do
|
34
|
-
@options[:host] = BehaviorServer::DEFAULT_HOST
|
35
|
-
end
|
36
|
-
opts.on("-a", "--listen-on-all-hosts", "Listen on all interfaces, (#{BehaviorServer::ALL_INTERFACES})") do
|
37
|
-
@options[:host] = BehaviorServer::ALL_INTERFACES
|
38
|
-
end
|
39
|
-
opts.separator ""
|
40
|
-
opts.separator "All behaviors:"
|
41
|
-
opts.separator ServiceRegistry.all_server_names.map { |title| " - #{title}" }.join("\n")
|
42
2
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
@option_parser.parse!(args)
|
48
|
-
rescue OptionParser::InvalidOption => io
|
49
|
-
raise ConfigurationError, io.message
|
50
|
-
end
|
3
|
+
def self.find_makeables
|
4
|
+
Hash[Bane::Behaviors::Responders::EXPORTED.map { |responder| [responder.unqualified_name, ResponderMaker.new(responder)] }]
|
5
|
+
.merge(Hash[Bane::Behaviors::Servers::EXPORTED.map { |server| [server.unqualified_name, server] }])
|
6
|
+
end
|
51
7
|
|
52
|
-
|
53
|
-
Integer(port)
|
54
|
-
rescue ArgumentError => ae
|
55
|
-
raise ConfigurationError, "Invalid port number: #{port}"
|
56
|
-
end
|
8
|
+
class CommandLineConfiguration
|
57
9
|
|
58
|
-
def
|
59
|
-
|
60
|
-
|
61
|
-
raise ConfigurationError, ube.message
|
10
|
+
def initialize(makeables)
|
11
|
+
@behavior_maker = BehaviorMaker.new(makeables)
|
12
|
+
@arguments_parser = ArgumentsParser.new(makeables.keys)
|
62
13
|
end
|
63
14
|
|
64
|
-
def
|
65
|
-
|
66
|
-
|
15
|
+
def process(args, &error_policy)
|
16
|
+
arguments = @arguments_parser.parse(args)
|
17
|
+
create(arguments.behaviors, arguments.port, arguments.host)
|
18
|
+
rescue ConfigurationError => ce
|
19
|
+
error_policy.call([ce.message, @arguments_parser.usage].join("\n"))
|
67
20
|
end
|
68
21
|
|
69
|
-
|
70
|
-
def initialize(port, behaviors, host)
|
71
|
-
@starting_port = port
|
72
|
-
@behaviors = behaviors
|
73
|
-
@host = host
|
74
|
-
end
|
22
|
+
private
|
75
23
|
|
76
|
-
|
77
|
-
|
78
|
-
@behaviors
|
79
|
-
|
80
|
-
|
81
|
-
configurations
|
24
|
+
def create(behaviors, port, host)
|
25
|
+
if behaviors.any?
|
26
|
+
@behavior_maker.create(behaviors, port, host)
|
27
|
+
else
|
28
|
+
@behavior_maker.create_all(port, host)
|
82
29
|
end
|
83
|
-
|
30
|
+
rescue UnknownBehaviorError => ube
|
31
|
+
raise ConfigurationError, ube.message
|
84
32
|
end
|
85
33
|
|
86
34
|
end
|
87
35
|
|
88
|
-
class ConfigurationError < RuntimeError;
|
89
|
-
|
90
|
-
class UnknownBehaviorError < RuntimeError
|
91
|
-
def initialize(name)
|
92
|
-
super "Unknown behavior: #{name}"
|
93
|
-
end
|
36
|
+
class ConfigurationError < RuntimeError;
|
94
37
|
end
|
95
38
|
|
96
39
|
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require_relative '../test_helper'
|
2
|
+
require 'mocha/setup'
|
3
|
+
|
4
|
+
class BaneAcceptanceTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
include ServerTestHelpers
|
7
|
+
|
8
|
+
TEST_PORT = 4000
|
9
|
+
|
10
|
+
def test_uses_specified_port_and_server
|
11
|
+
run_server_with(TEST_PORT, Bane::Behaviors::Responders::FixedResponse) do
|
12
|
+
with_response_from TEST_PORT do |response|
|
13
|
+
assert !response.empty?, "Should have had a non-empty response"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_serves_http_requests
|
19
|
+
run_server_with(TEST_PORT, Bane::Behaviors::Responders::HttpRefuseAllCredentials) do
|
20
|
+
assert_match /401/, status_returned_from("http://localhost:#{TEST_PORT}/some/url")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_supports_command_line_interface
|
25
|
+
run_server_with_cli_arguments(["--listen-on-localhost", TEST_PORT, "FixedResponse"]) do
|
26
|
+
with_response_from TEST_PORT do |response|
|
27
|
+
assert !response.empty?, "Should have had a non-empty response"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def run_server_with(port, behavior, &block)
|
35
|
+
behavior = Bane::Behaviors::Servers::ResponderServer.new(port, behavior.new)
|
36
|
+
launcher = Bane::Launcher.new([behavior], quiet_logger)
|
37
|
+
launch_and_stop_safely(launcher, &block)
|
38
|
+
sleep 0.1 # Until we can fix the GServer stopping race condition (Issue #7)
|
39
|
+
end
|
40
|
+
|
41
|
+
def run_server_with_cli_arguments(arguments, &block)
|
42
|
+
config = Bane::CommandLineConfiguration.new(Bane.find_makeables)
|
43
|
+
launcher = Bane::Launcher.new(config.process(arguments), quiet_logger) { |error| raise error }
|
44
|
+
launch_and_stop_safely(launcher, &block)
|
45
|
+
sleep 0.1 # Until we can fix the GServer stopping race condition (Issue #7)
|
46
|
+
end
|
47
|
+
|
48
|
+
def quiet_logger
|
49
|
+
StringIO.new
|
50
|
+
end
|
51
|
+
|
52
|
+
def status_returned_from(uri)
|
53
|
+
Net::HTTP.get_response(URI(uri)).code
|
54
|
+
end
|
55
|
+
|
56
|
+
def with_response_from(port)
|
57
|
+
begin
|
58
|
+
connection = TCPSocket.new "localhost", port
|
59
|
+
yield connection.read
|
60
|
+
ensure
|
61
|
+
connection.close if connection
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|