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.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/HISTORY.md +49 -0
  3. data/LICENSE +1 -1
  4. data/README.md +41 -31
  5. data/TODO +15 -10
  6. data/bin/bane +8 -11
  7. data/lib/bane/arguments_parser.rb +75 -0
  8. data/lib/bane/behavior_maker.rb +41 -0
  9. data/lib/bane/behaviors/responders/close_after_pause.rb +23 -0
  10. data/lib/bane/behaviors/responders/close_immediately.rb +16 -0
  11. data/lib/bane/behaviors/responders/deluge_response.rb +29 -0
  12. data/lib/bane/behaviors/responders/echo_response.rb +18 -0
  13. data/lib/bane/behaviors/responders/exported.rb +11 -0
  14. data/lib/bane/behaviors/responders/fixed_response.rb +27 -0
  15. data/lib/bane/behaviors/responders/for_each_line.rb +20 -0
  16. data/lib/bane/behaviors/responders/http_refuse_all_credentials.rb +33 -0
  17. data/lib/bane/behaviors/responders/never_respond.rb +29 -0
  18. data/lib/bane/behaviors/responders/newline_response.rb +20 -0
  19. data/lib/bane/behaviors/responders/random_response.rb +26 -0
  20. data/lib/bane/behaviors/responders/slow_response.rb +34 -0
  21. data/lib/bane/behaviors/servers/exported.rb +20 -0
  22. data/lib/bane/behaviors/servers/responder_server.rb +52 -0
  23. data/lib/bane/behaviors/servers/timeout_in_listen_queue.rb +53 -0
  24. data/lib/bane/command_line_configuration.rb +41 -0
  25. data/lib/bane/extensions.rb +9 -0
  26. data/lib/bane/launcher.rb +9 -8
  27. data/lib/bane/naive_http_response.rb +2 -0
  28. data/lib/bane/version.rb +5 -0
  29. data/lib/bane.rb +21 -6
  30. metadata +104 -102
  31. data/Rakefile +0 -60
  32. data/examples/simple_port_and_class_as_constant.rb +0 -9
  33. data/examples/simple_port_and_class_as_string.rb +0 -7
  34. data/examples/specify_behavior_options.rb +0 -16
  35. data/examples/specify_ports.rb +0 -15
  36. data/lib/bane/behaviors.rb +0 -151
  37. data/lib/bane/compatibility.rb +0 -36
  38. data/lib/bane/configuration.rb +0 -50
  39. data/lib/bane/configuration_parser.rb +0 -74
  40. data/lib/bane/delegating_gserver.rb +0 -42
  41. data/lib/bane/service_registry.rb +0 -17
  42. data/test/bane/behaviors_test.rb +0 -137
  43. data/test/bane/configuration_parser_test.rb +0 -124
  44. data/test/bane/configuration_test.rb +0 -52
  45. data/test/bane/delegating_gserver_test.rb +0 -56
  46. data/test/bane/integration_test.rb +0 -67
  47. data/test/bane/launcher_test.rb +0 -16
  48. data/test/bane/naive_http_response_test.rb +0 -69
  49. data/test/bane/service_registry_test.rb +0 -20
  50. 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
@@ -1,4 +1,4 @@
1
- Copyright (c) 2010, Daniel J. Wellman
1
+ Copyright (c) 2021, Daniel J. Wellman
2
2
  All rights reserved.
3
3
 
4
4
  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
data/README.md CHANGED
@@ -1,15 +1,23 @@
1
1
  # Bane
2
2
 
3
+ [![Build Status](https://secure.travis-ci.org/danielwellman/bane.png)](http://travis-ci.org/danielwellman/bane) [![Code Climate](https://codeclimate.com/badge.png)](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
- `sudo gem install bane`
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 initialize Bane with a Hash of port numbers, behavior names, and configuration parameters. For example, you might want to specify a response for the FixedResponse behavior:
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
- require 'bane'
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
- include Bane
36
- include Behaviors
56
+ ## Listening on all hosts
37
57
 
38
- launcher = Launcher.new(Configuration(
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
- See `examples/specify_behavior_options.rb` for another example. For a list of options supported by the
46
- basic behaviors, see the source for the behaviors in `Bane::Behaviors` at `lib/bane/behaviors.rb`.
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; which may require a C or C++ extension.
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
- ## Design
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
- To enable support of different types of behaviors such as HTTP responses or low-level TCP packet
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
- - Remove some duplication / clarify the ConfigurationParser class as needed
2
- - Dynamically create new behaviors like "SlowResponseForEachLine" by using Module.const_missing
3
- - Use ActiveSupport or create simple extensions for time durations (seconds, minutes, etc.)
4
- - Write the bad TCP/IP behaviors - using something like C/C++.
5
- - Write the remaining bad HTTP behaviors. In addition, we may want to replace the NaiveHttpResponse with something
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
- Ideas:
12
- - Make the Launcher use a Factory to create the server given a dependency, rather than depending directly on the
13
- DelegatingGServer class. This would make it possible to swap the base server implementation more easily.
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
- if ARGV.empty?
7
- puts "Usage: bane port_number <servers>"
8
- puts
9
- puts "All behaviors:"
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,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bane
4
+ module Behaviors
5
+ module Responders
6
+
7
+ # Closes the connection immediately after a connection is made.
8
+ class CloseImmediately
9
+ def serve(io)
10
+ # do nothing
11
+ end
12
+ end
13
+
14
+ end
15
+ end
16
+ 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,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bane
4
+ module Behaviors
5
+ module Responders
6
+
7
+ class EchoResponse
8
+ def serve(io)
9
+ while (input = io.gets)
10
+ io.write(input)
11
+ end
12
+ io.close
13
+ end
14
+ end
15
+
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bane
4
+ module Behaviors
5
+ module Responders
6
+
7
+ EXPORTED = self.constants.map { |name| self.const_get(name) }.grep(Class)
8
+
9
+ end
10
+ end
11
+ 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