bane 0.1.1 → 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.
- checksums.yaml +7 -0
- data/HISTORY.md +49 -0
- data/LICENSE +1 -1
- data/README.md +41 -31
- data/TODO +15 -10
- data/bin/bane +8 -11
- data/lib/bane/arguments_parser.rb +75 -0
- data/lib/bane/behavior_maker.rb +41 -0
- data/lib/bane/behaviors/responders/close_after_pause.rb +23 -0
- data/lib/bane/behaviors/responders/close_immediately.rb +16 -0
- data/lib/bane/behaviors/responders/deluge_response.rb +29 -0
- data/lib/bane/behaviors/responders/echo_response.rb +18 -0
- data/lib/bane/behaviors/responders/exported.rb +11 -0
- data/lib/bane/behaviors/responders/fixed_response.rb +27 -0
- data/lib/bane/behaviors/responders/for_each_line.rb +20 -0
- data/lib/bane/behaviors/responders/http_refuse_all_credentials.rb +33 -0
- data/lib/bane/behaviors/responders/never_respond.rb +29 -0
- data/lib/bane/behaviors/responders/newline_response.rb +20 -0
- data/lib/bane/behaviors/responders/random_response.rb +26 -0
- data/lib/bane/behaviors/responders/slow_response.rb +34 -0
- data/lib/bane/behaviors/servers/exported.rb +20 -0
- data/lib/bane/behaviors/servers/responder_server.rb +52 -0
- data/lib/bane/behaviors/servers/timeout_in_listen_queue.rb +53 -0
- data/lib/bane/command_line_configuration.rb +41 -0
- data/lib/bane/extensions.rb +9 -0
- data/lib/bane/launcher.rb +9 -8
- data/lib/bane/naive_http_response.rb +2 -0
- data/lib/bane/version.rb +5 -0
- data/lib/bane.rb +21 -6
- metadata +104 -102
- data/Rakefile +0 -60
- data/examples/simple_port_and_class_as_constant.rb +0 -9
- data/examples/simple_port_and_class_as_string.rb +0 -7
- data/examples/specify_behavior_options.rb +0 -16
- data/examples/specify_ports.rb +0 -15
- data/lib/bane/behaviors.rb +0 -151
- data/lib/bane/compatibility.rb +0 -36
- data/lib/bane/configuration.rb +0 -50
- data/lib/bane/configuration_parser.rb +0 -74
- data/lib/bane/delegating_gserver.rb +0 -42
- data/lib/bane/service_registry.rb +0 -17
- data/test/bane/behaviors_test.rb +0 -137
- data/test/bane/configuration_parser_test.rb +0 -124
- data/test/bane/configuration_test.rb +0 -52
- data/test/bane/delegating_gserver_test.rb +0 -56
- data/test/bane/integration_test.rb +0 -67
- data/test/bane/launcher_test.rb +0 -16
- data/test/bane/naive_http_response_test.rb +0 -69
- data/test/bane/service_registry_test.rb +0 -20
- data/test/test_helper.rb +0 -9
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 6f238270f71940d8a67a36a5a57f99c0ba77f05ba8c5af3a9ea9805c1d527d12
|
4
|
+
data.tar.gz: 7da73073b9fcd740d5c64e609d963191ccd411b20dd2fbd700618b9c21744cd1
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 59df1b491b22c50e6ab0cd4adeaa633f6d3089c05b489fafeffad196c5ce75376940454575d3d208e65efb7b5cf618ceedaae0fd9cec87de0f37108149cab0d8
|
7
|
+
data.tar.gz: 662f09c29e1dbe1da105fac39891ee68893afc014dd1f56f49f099986c13ef8c3d7a0964b470bd495900310b104e8a34f28cc1d86d30337b202e5a377a4ddf2e
|
data/HISTORY.md
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# master
|
2
|
+
|
3
|
+
## 1.0.0
|
4
|
+
|
5
|
+
### Changed
|
6
|
+
* Support Ruby 2.5+. This meant adding a dependency on 'gserver', which was moved out of the standard library.
|
7
|
+
|
8
|
+
## 0.4.0
|
9
|
+
|
10
|
+
### Added
|
11
|
+
* The EchoResponse behavior which replies with each line sent
|
12
|
+
* The TimeoutInListenQueue server which binds to a port but never calls listen(2)
|
13
|
+
* Trap SIGINT to gracefully stop servers without a messy exception stacktrace
|
14
|
+
|
15
|
+
### Removed
|
16
|
+
* The fancy, flexible ConfigurationParser has been deleted. Command-line invocation now uses the CommandLineConfiguration parser. For programmatic invocation, see the examples.
|
17
|
+
* Ruby 1.8.7 support
|
18
|
+
|
19
|
+
### Changed
|
20
|
+
* Rearranged packages to create Bane::Behaviors::Servers and Bane::Beaviors::Responders. Servers may be started and stopped; Responders simply interact with an already connected socket.
|
21
|
+
* Added Bane::Behaviors::Servers::LOCALHOST (127.0.0.1) and deprecated Bane::Behaviors::Servers::DEFAULT_HOST; please use LOCALHOST when specifying a host to listen on.
|
22
|
+
|
23
|
+
## 0.3.0
|
24
|
+
|
25
|
+
### Added
|
26
|
+
* Servers can now listen on all hosts or localhost via the command-line options -a / --listen-on-all-hosts or -l / --listen-on-localhost. The default is to listen on localhost.
|
27
|
+
|
28
|
+
|
29
|
+
### Changed
|
30
|
+
* Behaviors receive their parameters through their constructors instead of being passed via the serve method. That is,
|
31
|
+
the serve(io, options) method has been changed to serve(io). Behaviors that need to accept user-specified parameters
|
32
|
+
should accept them via constructor arguments, and should provide a default version since the command-line interface
|
33
|
+
does not specify options. e.g.
|
34
|
+
|
35
|
+
```
|
36
|
+
class MyBehavior
|
37
|
+
def initialize(options = {})
|
38
|
+
...
|
39
|
+
```
|
40
|
+
|
41
|
+
* BehaviorServer no longer accepts options; instead these are created with the Behavior objects.
|
42
|
+
* Configuration() and ConfigurationParser class are deprecated and will be removed in the next release. Instead of
|
43
|
+
using these classes, please directly instantiate a BehaviorServer with the required arguments. This class is being
|
44
|
+
deprecated and removed because the flexibility of the code creates a structure that is harder to read and maintain.
|
45
|
+
I'm also not sure anyone is using this method -- if so, please open a GitHub Issue and let me know if you're using
|
46
|
+
it -- and if so, how.
|
47
|
+
|
48
|
+
|
49
|
+
|
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -1,15 +1,23 @@
|
|
1
1
|
# Bane
|
2
2
|
|
3
|
+
[](http://travis-ci.org/danielwellman/bane) [](https://codeclimate.com/github/danielwellman/bane)
|
4
|
+
|
3
5
|
Bane is a test harness used to test your application's interaction with other servers. It is based upon the material from Michael Nygard's ["Release It!"](http://www.pragprog.com/titles/mnee/release-it) book as described in the "Test Harness" chapter.
|
4
6
|
|
7
|
+
## Why Use Bane?
|
8
|
+
|
9
|
+
If you are building an application, you may depend on third-party servers or web services for your data. Most of the time these services are reliable, but at some point they will behave in an unusual manner - such as connecting but never respond, sending data very slowly, or sending an unexpected response. To ensure your application survives these scenarios, you should test your application against these bad behaviors. Bane helps you recreate these scenarios by standing in for your third-party servers and responding in several nefarious ways.
|
10
|
+
|
5
11
|
## Setup
|
6
12
|
|
7
|
-
Bane is available as a gem. Install it with
|
13
|
+
Bane is available as a Ruby gem. Install it with
|
8
14
|
|
9
|
-
`
|
15
|
+
`gem install bane`
|
10
16
|
|
11
17
|
Note that Bane installs an executable, `bane`. Simply invoke `bane` with no arguments to get a usage description.
|
12
18
|
|
19
|
+
Bane requires Ruby 2.5 or later.
|
20
|
+
|
13
21
|
## Usage
|
14
22
|
|
15
23
|
Bane is designed with a few usage scenarios in mind:
|
@@ -26,24 +34,38 @@ Bane is designed with a few usage scenarios in mind:
|
|
26
34
|
|
27
35
|
Example: `$ bane 3000`
|
28
36
|
|
29
|
-
4. Advanced Configuration using Ruby. If you want to modify some of the defaults used in the included behaviors, you can
|
37
|
+
4. Advanced Configuration using Ruby. If you want to modify some of the defaults used in the included behaviors, you can create a Ruby script to invoke Bane. For example, you might want to specify a response for the FixedResponse behavior:
|
30
38
|
|
31
39
|
Example:
|
32
40
|
|
33
|
-
|
41
|
+
```
|
42
|
+
require 'bane'
|
43
|
+
|
44
|
+
include Bane
|
45
|
+
|
46
|
+
launcher = Launcher.new(
|
47
|
+
[BehaviorServer.new(3000, Behaviors::Responders::FixedResponse.new(message: "Shall we play a game?"))])
|
48
|
+
launcher.start
|
49
|
+
launcher.join
|
50
|
+
```
|
51
|
+
|
52
|
+
See the `examples` directory for more examples. For a list of options supported by the
|
53
|
+
included behaviors, see the source for the behaviors in `lib/bane/behaviors`; note that you will find both
|
54
|
+
servers (that bind to sockets directly) and responders (that assume a running TCP server and communicate with a socket connection).
|
34
55
|
|
35
|
-
|
36
|
-
include Behaviors
|
56
|
+
## Listening on all hosts
|
37
57
|
|
38
|
-
|
39
|
-
3000 => {:behavior => FixedResponse, :message => "Shall we play a game?"},
|
40
|
-
)
|
41
|
-
)
|
42
|
-
launcher.start
|
43
|
-
launcher.join
|
58
|
+
By default, Bane will listen only to connections on localhost (127.0.0.1).
|
44
59
|
|
45
|
-
|
46
|
-
|
60
|
+
To listen on all hosts (0.0.0.0), start Bane from the command line with the `-a` or `--listen-on-all-hosts` option. For more command line help, run `bane -h` or `bane --help`.
|
61
|
+
|
62
|
+
## Keeping the Connection Open
|
63
|
+
|
64
|
+
By default, the socket behaviors that send any data will close the connection immediately after sending the response. There are variations of these behaviors available that end with `ForEachLine` which will wait for a line of input (using IO's `gets`), respond, then return to the waiting for input state.
|
65
|
+
|
66
|
+
For example, if you want to send a static response and then close the connection immediately, use `FixedResponse`. If you want to keep the connection open and respond to every line of input with the same data, use `FixedResponseForEachLine`. Note that these behaviors will never close the connection; they will happily respond to every line of input until you stop Bane.
|
67
|
+
|
68
|
+
If you are implementing a new behavior, you should consider whether or not you would like to provide another variation which keeps a connection open and responds after every line of input. If so, create the basic behavior which responds and closes the connection immediately, then create another behavior which includes the `ForEachLine` module. See the source in `lib/bane/behaviors/responders/fixed_response.rb` for some examples.
|
47
69
|
|
48
70
|
## Background
|
49
71
|
|
@@ -58,6 +80,7 @@ Note that these are simple protocol-independent socket behaviors:
|
|
58
80
|
* The server accepts a connection and then drops it immediately (CloseImmediately)
|
59
81
|
* The service can send megabytes when kilobytes are expected. (rough approximation with the DelugeReponse)
|
60
82
|
* The service can refuse all authentication credentials. (HttpRefuseAllCredentials)
|
83
|
+
* The request can sit in a listen queue until the caller times out. (TimeoutInListenQueue)
|
61
84
|
|
62
85
|
The following behaviors are not yet supported; they require the configuration of an HTTP server.
|
63
86
|
See the implementation of HttpRefuseAllCredentials for a simple example of an HTTP behavior.
|
@@ -66,30 +89,17 @@ See the implementation of HttpRefuseAllCredentials for a simple example of an HT
|
|
66
89
|
* The service can send a response of HTML instead of the expected XML.
|
67
90
|
|
68
91
|
The following behaviors are not yet supported. These require the ability to manipulate
|
69
|
-
TCP packets at a low level
|
92
|
+
TCP packets at a low level, which may require a C or C++ extension or raw sockets.
|
70
93
|
|
71
94
|
* The connection can be refused.
|
72
|
-
* The request can sit in a listen queue until the caller times out.
|
73
95
|
* The remote end can reply with a SYN/ACK and then never send any data.
|
74
96
|
* The remote end can send nothing but RESET packets.
|
75
97
|
* The remote end can report a full receive window and never drain the data.
|
76
98
|
* The connection can be established, but packets could be lost causing retransmit delays
|
77
99
|
* The connection can be established, but the remote end never acknowledges receiving a packet, causing endless retransmits
|
78
100
|
|
79
|
-
##
|
80
|
-
|
81
|
-
Bane Behaviors are simple objects which implement the Strategy pattern. This makes them
|
82
|
-
simple to unit test and allows them to be independent of the underlying server implementation.
|
83
|
-
Bane currently serves all Behaviors using Ruby's built-in GServer, which provides a simple
|
84
|
-
multi-threaded TCP server. Behaviors currently:
|
85
|
-
|
86
|
-
* Accept an IO stream used to read from or send a response.
|
87
|
-
* Accept a hash of configuration options to allow overriding of default behavior parameters.
|
88
|
-
* Provide a meaningful name to appear in the Bane log. This is especially helpful if your application
|
89
|
-
under test dies and you'd like to identify which behavior killed it.
|
101
|
+
## Support
|
90
102
|
|
91
|
-
|
92
|
-
munging, a different base server instead of GServer may be required. In that case, it should
|
93
|
-
be possible to change how Behaviors are associated with a server, perhaps by making
|
94
|
-
Behaviors extend a server base class.
|
103
|
+
<a href="http://www.cyrusinnovation.com/"><img src="http://www.cyrusinnovation.com/marketing/logo.png" alt="Cyrus"></a>
|
95
104
|
|
105
|
+
Thank you to [Cyrus](http://www.cyrusinnovation.com/) for supporting the development of this project.
|
data/TODO
CHANGED
@@ -1,13 +1,18 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
-
|
5
|
-
-
|
6
|
-
from the standard Ruby library, so that there's less code in this project, and so we know that we're
|
7
|
-
following the HTTP protocol.
|
1
|
+
Features:
|
2
|
+
|
3
|
+
Design questions / Ideas:
|
4
|
+
- Figure out where the logger configuration logic belongs in the Launcher/BehaviorServer relationship
|
5
|
+
- Should the default logger go to STDERR or STDOUT?
|
8
6
|
- Log every request to the server/behavior, in addition to open, close. For this to work, it would have to be the
|
9
7
|
behavior's responsibility, since GServer#serve gets called only once for the lifetime of the connection.
|
8
|
+
- If we extract a commong logger, we might use that to test for proper disconnection in NeverRespondTest#test_disconnects_after_client_closes_connection
|
10
9
|
|
11
|
-
|
12
|
-
-
|
13
|
-
|
10
|
+
Future Behaviors:
|
11
|
+
- Create a more configurable version of the DelugeResponse which allows for a header, footer, content and times to repeat.
|
12
|
+
- Write the remaining bad HTTP behaviors. In addition, we may want to replace the NaiveHttpResponse with something
|
13
|
+
from the standard Ruby library, so that there's less code in this project, and so we know that we're
|
14
|
+
following the HTTP protocol.
|
15
|
+
- Have a tiny listen queue (queue size 0) and a connection never gets into the queue
|
16
|
+
- Have a giant listen queue (queue size max?) and only one thread processing -- times out in the listen queue
|
17
|
+
- Close the write part of the connection, but not the read
|
18
|
+
- Close the read part of the connection, but not the write
|
data/bin/bane
CHANGED
@@ -1,17 +1,14 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
$LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
|
4
3
|
require 'bane'
|
5
4
|
|
6
|
-
|
7
|
-
|
8
|
-
puts
|
9
|
-
|
10
|
-
behavior_names = Bane::ServiceRegistry.all_servers.map(&:simple_name)
|
11
|
-
behavior_names.sort.each { |behavior| puts " - #{behavior}" }
|
12
|
-
else
|
13
|
-
launcher = Bane::Launcher.new(Configuration(*ARGV))
|
14
|
-
launcher.start
|
15
|
-
launcher.join
|
5
|
+
parser = Bane::CommandLineConfiguration.new(Bane.find_makeables)
|
6
|
+
servers = parser.process(ARGV) do |error_message|
|
7
|
+
puts error_message
|
8
|
+
exit 1
|
16
9
|
end
|
17
10
|
|
11
|
+
launcher = Bane::Launcher.new(servers)
|
12
|
+
launcher.start
|
13
|
+
trap("SIGINT") { Thread.new { launcher.stop; exit } }
|
14
|
+
launcher.join
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
|
5
|
+
module Bane
|
6
|
+
class ArgumentsParser
|
7
|
+
def initialize(makeable_names)
|
8
|
+
@makeable_names = makeable_names
|
9
|
+
@options = {host: default_host}
|
10
|
+
@option_parser = init_option_parser
|
11
|
+
end
|
12
|
+
|
13
|
+
def parse(args)
|
14
|
+
@option_parser.parse!(args)
|
15
|
+
|
16
|
+
raise ConfigurationError, "Missing arguments" if args.empty?
|
17
|
+
|
18
|
+
port = parse_port(args[0])
|
19
|
+
behaviors = args.drop(1)
|
20
|
+
ParsedArguments.new(port, @options[:host], behaviors)
|
21
|
+
rescue OptionParser::InvalidOption => io
|
22
|
+
raise ConfigurationError, io.message
|
23
|
+
end
|
24
|
+
|
25
|
+
def usage
|
26
|
+
@option_parser.help
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def init_option_parser
|
32
|
+
OptionParser.new do |opts|
|
33
|
+
opts.banner = 'Usage: bane [options] port [behaviors]'
|
34
|
+
opts.separator ''
|
35
|
+
opts.on('-l', '--listen-on-localhost',
|
36
|
+
"Listen on localhost, (#{default_host}). [default]") do
|
37
|
+
@options[:host] = default_host
|
38
|
+
end
|
39
|
+
opts.on('-a', '--listen-on-all-hosts', "Listen on all interfaces, (#{all_interfaces})") do
|
40
|
+
@options[:host] = all_interfaces
|
41
|
+
end
|
42
|
+
opts.separator ''
|
43
|
+
opts.separator 'All behaviors:'
|
44
|
+
opts.separator @makeable_names.sort.map { |title| " - #{title}" }.join("\n")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def parse_port(port)
|
49
|
+
Integer(port)
|
50
|
+
rescue ArgumentError
|
51
|
+
raise ConfigurationError, "Invalid port number: #{port}"
|
52
|
+
end
|
53
|
+
|
54
|
+
def all_interfaces
|
55
|
+
Behaviors::Servers::ALL_INTERFACES
|
56
|
+
end
|
57
|
+
|
58
|
+
def default_host
|
59
|
+
Behaviors::Servers::LOCALHOST
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class ParsedArguments
|
64
|
+
|
65
|
+
attr_reader :port, :host, :behaviors
|
66
|
+
|
67
|
+
def initialize(port, host, behaviors)
|
68
|
+
@host = host
|
69
|
+
@port = port
|
70
|
+
@behaviors = behaviors
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bane
|
4
|
+
|
5
|
+
class BehaviorMaker
|
6
|
+
def initialize(makeables)
|
7
|
+
@makeables = makeables
|
8
|
+
end
|
9
|
+
|
10
|
+
def create(behavior_names, starting_port, host)
|
11
|
+
behavior_names
|
12
|
+
.map { |behavior| makeables.fetch(behavior) { raise UnknownBehaviorError.new(behavior) } }
|
13
|
+
.map.with_index { |maker, index| maker.make(starting_port + index, host) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def create_all(starting_port, host)
|
17
|
+
makeables.sort.map.with_index { |name_maker_pair, index| name_maker_pair.last.make(starting_port + index, host) }
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
attr_reader :makeables
|
23
|
+
end
|
24
|
+
|
25
|
+
class UnknownBehaviorError < RuntimeError
|
26
|
+
def initialize(name)
|
27
|
+
super "Unknown behavior: #{name}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class ResponderMaker
|
32
|
+
def initialize(responder)
|
33
|
+
@responder = responder
|
34
|
+
end
|
35
|
+
|
36
|
+
def make(port, host)
|
37
|
+
Behaviors::Servers::ResponderServer.new(port, @responder.new, host)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bane
|
4
|
+
module Behaviors
|
5
|
+
module Responders
|
6
|
+
|
7
|
+
# Accepts a connection, pauses a fixed duration, then closes the connection.
|
8
|
+
#
|
9
|
+
# Options:
|
10
|
+
# - duration: The number of seconds to wait before disconnect. Default: 30
|
11
|
+
class CloseAfterPause
|
12
|
+
def initialize(options = {})
|
13
|
+
@options = {duration: 30}.merge(options)
|
14
|
+
end
|
15
|
+
|
16
|
+
def serve(io)
|
17
|
+
sleep(@options[:duration])
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bane
|
4
|
+
module Behaviors
|
5
|
+
module Responders
|
6
|
+
|
7
|
+
# Sends a large response. Response consists of a repeated 'x' character.
|
8
|
+
#
|
9
|
+
# Options
|
10
|
+
# - length: The size in bytes of the response to send. Default: 1,000,000 bytes
|
11
|
+
class DelugeResponse
|
12
|
+
def initialize(options = {})
|
13
|
+
@options = {length: 1_000_000}.merge(options)
|
14
|
+
end
|
15
|
+
|
16
|
+
def serve(io)
|
17
|
+
length = @options[:length]
|
18
|
+
|
19
|
+
length.times { io.write('x') }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class DelugeResponseForEachLine < DelugeResponse
|
24
|
+
include ForEachLine
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bane
|
4
|
+
module Behaviors
|
5
|
+
module Responders
|
6
|
+
|
7
|
+
# Sends a static response.
|
8
|
+
#
|
9
|
+
# Options:
|
10
|
+
# - message: The response message to send. Default: "Hello, world!"
|
11
|
+
class FixedResponse
|
12
|
+
def initialize(options = {})
|
13
|
+
@options = {message: "Hello, world!"}.merge(options)
|
14
|
+
end
|
15
|
+
|
16
|
+
def serve(io)
|
17
|
+
io.write @options[:message]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class FixedResponseForEachLine < FixedResponse
|
22
|
+
include ForEachLine
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bane
|
4
|
+
module Behaviors
|
5
|
+
module Responders
|
6
|
+
|
7
|
+
# This module can be used to wrap another behavior with
|
8
|
+
# a "while(io.gets)" loop, which reads a line from the input and
|
9
|
+
# then performs the given behavior.
|
10
|
+
module ForEachLine
|
11
|
+
def serve(io)
|
12
|
+
while (io.gets)
|
13
|
+
super(io)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bane
|
4
|
+
module Behaviors
|
5
|
+
module Responders
|
6
|
+
|
7
|
+
# Sends an HTTP 401 response (Unauthorized) for every request. This
|
8
|
+
# attempts to mimic an HTTP server by reading a line (the request)
|
9
|
+
# and then sending the response. This behavior responds to all
|
10
|
+
# incoming request URLs on the running port.
|
11
|
+
class HttpRefuseAllCredentials
|
12
|
+
UNAUTHORIZED_RESPONSE_BODY = <<EOF
|
13
|
+
<!DOCTYPE html>
|
14
|
+
<html>
|
15
|
+
<head>
|
16
|
+
<title>Bane Server</title>
|
17
|
+
</head>
|
18
|
+
<body>
|
19
|
+
<h1>Unauthorized</h1>
|
20
|
+
</body>
|
21
|
+
</html>
|
22
|
+
EOF
|
23
|
+
|
24
|
+
def serve(io)
|
25
|
+
io.gets # Read the request before responding
|
26
|
+
response = NaiveHttpResponse.new(401, "Unauthorized", "text/html", UNAUTHORIZED_RESPONSE_BODY)
|
27
|
+
io.write(response.to_s)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bane
|
4
|
+
module Behaviors
|
5
|
+
module Responders
|
6
|
+
|
7
|
+
# Accepts a connection and never sends a byte of data. The connection is
|
8
|
+
# left open indefinitely.
|
9
|
+
class NeverRespond
|
10
|
+
READ_TIMEOUT_IN_SECONDS = 2
|
11
|
+
MAXIMUM_BYTES_TO_READ = 4096
|
12
|
+
|
13
|
+
def serve(io)
|
14
|
+
loop do
|
15
|
+
begin
|
16
|
+
io.read_nonblock(MAXIMUM_BYTES_TO_READ)
|
17
|
+
rescue Errno::EAGAIN
|
18
|
+
IO.select([io], nil, nil, READ_TIMEOUT_IN_SECONDS)
|
19
|
+
retry # Ignore the result of IO select since we retry reads regardless of if there's data to be read or not
|
20
|
+
rescue EOFError
|
21
|
+
break
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bane
|
4
|
+
module Behaviors
|
5
|
+
module Responders
|
6
|
+
|
7
|
+
# Sends a newline character as the only response
|
8
|
+
class NewlineResponse
|
9
|
+
def serve(io)
|
10
|
+
io.write "\n"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class NewlineResponseForEachLine < NewlineResponse
|
15
|
+
include ForEachLine
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bane
|
4
|
+
module Behaviors
|
5
|
+
module Responders
|
6
|
+
|
7
|
+
# Sends a random response.
|
8
|
+
class RandomResponse
|
9
|
+
def serve(io)
|
10
|
+
io.write random_string
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
def random_string
|
15
|
+
(1..rand(26)+1).map { |i| ('a'..'z').to_a[rand(26)] }.join
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
class RandomResponseForEachLine < RandomResponse
|
21
|
+
include ForEachLine
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bane
|
4
|
+
module Behaviors
|
5
|
+
module Responders
|
6
|
+
|
7
|
+
# Sends a fixed response character-by-character, pausing between each character.
|
8
|
+
#
|
9
|
+
# Options:
|
10
|
+
# - message: The response to send. Default: "Hello, world!"
|
11
|
+
# - pause_duration: The number of seconds to pause between each character. Default: 10 seconds
|
12
|
+
class SlowResponse
|
13
|
+
def initialize(options = {})
|
14
|
+
@options = {message: "Hello, world!", pause_duration: 10}.merge(options)
|
15
|
+
end
|
16
|
+
|
17
|
+
def serve(io)
|
18
|
+
message = @options[:message]
|
19
|
+
pause_duration = @options[:pause_duration]
|
20
|
+
|
21
|
+
message.each_char do |char|
|
22
|
+
io.write char
|
23
|
+
sleep pause_duration
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class SlowResponseForEachLine < SlowResponse
|
29
|
+
include ForEachLine
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|