miniproxy 0.2.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3f44f0daaa7c8c5897ee86428737bb7bafba78d60935943056d8daed9baa2111
4
+ data.tar.gz: 4ec7b5cf714b7afe7d81dda425a6a8f3095caefac4c9511405a189678df71910
5
+ SHA512:
6
+ metadata.gz: 5410429d11d0cdb1f97cf5423d97d8f3d3d88b80a39bab27de5fd265d2bd02e779c5946d7e28d0ac9ab618758afc1900e1cc294b3b9c7f19ff6c800bd96d0c71
7
+ data.tar.gz: 2c987fde657f840ae2e6c976ea6bae574aa9ec31dfff19a48fe128f348b97d3aa89a6dedc2d2cc82fd8f497183065b3e02fb277ff2da2ffebe63d337a4995f91
data/lib/miniproxy.rb ADDED
@@ -0,0 +1,53 @@
1
+ require "drb"
2
+ require "timeout"
3
+ require "miniproxy/remote"
4
+
5
+ module MiniProxy
6
+ DRB_SERVICE_TIMEOUT = 5
7
+
8
+ def self.reset
9
+ remote.clear
10
+ end
11
+
12
+ def self.stop
13
+ remote.stop if Remote.drb_process_alive?
14
+ end
15
+
16
+ def self.port
17
+ remote.port
18
+ end
19
+
20
+ def self.host
21
+ "127.0.0.1"
22
+ end
23
+
24
+ def self.ignore_all_requests
25
+ reset
26
+
27
+ %w(GET POST PUT PATCH DELETE).each do |method|
28
+ stub_request(method: method, url: /.*/)
29
+ end
30
+ end
31
+
32
+ def self.stub_request(method:, url:, response: {})
33
+ remote.stub_request(method: method, url: url, response: response)
34
+ end
35
+
36
+ private_class_method def self.remote
37
+ Timeout.timeout(DRB_SERVICE_TIMEOUT) do
38
+ begin
39
+ remote = DRbObject.new(nil, Remote.server)
40
+
41
+ until remote.started?
42
+ sleep 0.01
43
+ end
44
+
45
+ remote.drain_messages.each(&method(:puts))
46
+
47
+ remote
48
+ rescue
49
+ retry
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,38 @@
1
+ require "webrick/https" # This is required to create a HTTPS server
2
+ # https://ruby-doc.org/stdlib-2.0.0/libdoc/webrick/rdoc/WEBrick.html#module-WEBrick-label-HTTPS
3
+
4
+ module MiniProxy
5
+ # MiniProxy fake SSL enabled server, which receives relayed requests from the ProxyServer
6
+ #
7
+ class FakeSSLServer < WEBrick::HTTPServer
8
+ ALLOWED_HOSTS = ["127.0.0.1", "localhost"].freeze
9
+
10
+ def initialize(config = {}, default = WEBrick::Config::HTTP)
11
+ config = config.merge({
12
+ Logger: WEBrick::Log.new(nil, 0), # silence logging
13
+ AccessLog: [], # silence logging
14
+ SSLEnable: true,
15
+ SSLCertificate: OpenSSL::X509::Certificate.new(certificate_file("cert.pem")),
16
+ SSLPrivateKey: OpenSSL::PKey::RSA.new(certificate_file("cert.key")),
17
+ SSLVerifyClient: OpenSSL::SSL::VERIFY_NONE,
18
+ })
19
+
20
+ super(config, default)
21
+ end
22
+
23
+ def service(req, res)
24
+ if ALLOWED_HOSTS.include?(req.host)
25
+ super(req, res)
26
+ else
27
+ self.config[:MockHandlerCallback].call(req, res)
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def certificate_file(filename)
34
+ filename = File.join( File.dirname(__FILE__), "../../ssl/#{filename}")
35
+ File.open(filename).read
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,55 @@
1
+ require "webrick/httpproxy"
2
+
3
+ module MiniProxy
4
+ # MiniProxy server, which boots a WEBrick proxy server
5
+ #
6
+ class ProxyServer < WEBrick::HTTPProxyServer
7
+ ALLOWED_HOSTS = ["127.0.0.1", "localhost"].freeze
8
+
9
+ attr_accessor :requests
10
+
11
+ def initialize(config = {}, default = WEBrick::Config::HTTP)
12
+ config = config.merge({
13
+ Logger: WEBrick::Log.new(nil, 0), # silence logging
14
+ AccessLog: [], # silence logging
15
+ })
16
+
17
+ super(config, default)
18
+ end
19
+
20
+ def do_PUT(req, res)
21
+ perform_proxy_request(req, res) do |http, path, header|
22
+ http.put(path, req.body || "", header)
23
+ end
24
+ end
25
+
26
+ def do_DELETE(req, res)
27
+ perform_proxy_request(req, res) do |http, path, header|
28
+ http.delete(path, header)
29
+ end
30
+ end
31
+
32
+ def do_PATCH(req, res)
33
+ perform_proxy_request(req, res) do |http, path, header|
34
+ http.patch(path, req.body || "", header)
35
+ end
36
+ end
37
+
38
+ def service(req, res)
39
+ if ALLOWED_HOSTS.include?(req.host)
40
+ super(req, res)
41
+ else
42
+ if req.request_method == "CONNECT"
43
+ # If something is trying to initiate an SSL connection, rewrite
44
+ # the URI to point to our fake server.
45
+ req.instance_variable_set(:@unparsed_uri, "localhost:#{self.config[:FakeServerPort]}")
46
+ super(req, res)
47
+ else
48
+ # Otherwise, call our handler to respond with an appropriate
49
+ # mock for the request.
50
+ self.config[:MockHandlerCallback].call(req, res)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,127 @@
1
+ require "miniproxy/stub/request"
2
+ require "miniproxy/stub/response"
3
+ require "miniproxy/proxy_server"
4
+ require "miniproxy/fake_ssl_server"
5
+
6
+ module MiniProxy
7
+ # Controls the remote DRb service, which provides a communcation mechanism
8
+ #
9
+ class Remote
10
+ SERVER_DYNAMIC_PORT_RANGE = (12345..32768).to_a.freeze
11
+ SERVER_START_TIMEOUT = 10
12
+
13
+ def self.pid
14
+ @pid
15
+ end
16
+
17
+ def self.drb_process_alive?
18
+ pid && Process.kill(0, pid) == 1
19
+ rescue Errno::ESRCH
20
+ false
21
+ end
22
+
23
+ def self.server
24
+ @unix_socket_uri ||= begin
25
+ tempfile = Tempfile.new("mini_proxy")
26
+ socket_path = tempfile.path
27
+ tempfile.close!
28
+ "drbunix:///#{socket_path}"
29
+ end
30
+
31
+ return @unix_socket_uri if drb_process_alive?
32
+
33
+ @pid = fork do
34
+ remote = Remote.new
35
+
36
+ Timeout.timeout(SERVER_START_TIMEOUT) do
37
+ begin
38
+ fake_server_port = SERVER_DYNAMIC_PORT_RANGE.sample
39
+ fake_server = FakeSSLServer.new(
40
+ Port: fake_server_port,
41
+ MockHandlerCallback: remote.method(:handler),
42
+ )
43
+ Thread.new { fake_server.start }
44
+ rescue Errno::EADDRINUSE
45
+ retry
46
+ end
47
+
48
+ begin
49
+ remote.port = ENV["MINI_PROXY_PORT"] || SERVER_DYNAMIC_PORT_RANGE.sample
50
+ proxy = MiniProxy::ProxyServer.new(
51
+ Port: remote.port,
52
+ FakeServerPort: fake_server_port,
53
+ MockHandlerCallback: remote.method(:handler),
54
+ )
55
+ Thread.new { proxy.start }
56
+ rescue Errno::EADDRINUSE
57
+ retry
58
+ end
59
+ end
60
+
61
+ DRb.start_service(@unix_socket_uri, remote)
62
+ DRb.thread.join
63
+
64
+ Process.exit!
65
+ end
66
+
67
+ Process.detach(@pid)
68
+ @unix_socket_uri
69
+ end
70
+
71
+ def handler(req, res)
72
+ if (request = @stubs.detect { |mock_request| mock_request.match?(req) })
73
+ response = request.response
74
+ res.status = response.code
75
+ response.headers.each { |key, value| res[key] = value }
76
+ res.body = response.body
77
+ else
78
+ res.status = 200
79
+ res.body = ""
80
+ queue_message "WARN: external request to #{req.host}#{req.path} not mocked"
81
+ queue_message %Q{Stub with: MiniProxy.stub_request(method: "#{req.request_method}", url: "#{req.host}#{req.path}")}
82
+ end
83
+ end
84
+
85
+ def stub_request(method:, url:, response:)
86
+ response = MiniProxy::Stub::Response.new(headers: response[:headers], body: response[:body])
87
+ request = MiniProxy::Stub::Request.new(method: method, url: url, response: response)
88
+ @stubs.push(request)
89
+ end
90
+
91
+ def port
92
+ @port
93
+ end
94
+
95
+ def port=(value)
96
+ @port = value
97
+ end
98
+
99
+ def stop
100
+ DRb.stop_service
101
+ end
102
+
103
+ def clear
104
+ @stubs.clear
105
+ end
106
+
107
+ def drain_messages
108
+ @messages.slice!(0, @messages.length)
109
+ end
110
+
111
+ def started?
112
+ current_server = DRb.current_server()
113
+ current_server && current_server.alive?
114
+ end
115
+
116
+ private
117
+
118
+ def queue_message(msg)
119
+ @messages.push msg
120
+ end
121
+
122
+ def initialize
123
+ @stubs = []
124
+ @messages = []
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,24 @@
1
+ module MiniProxy
2
+ module Stub
3
+ # MiniProxy stub request to match and stub external URLs with a stubbed response
4
+ #
5
+ class Request
6
+ attr_reader :response
7
+
8
+ # @param [String] method
9
+ # @param [Regexp, String] url
10
+ # @param [MiniProxy::Response] response
11
+ def initialize(method:, url:, response:)
12
+ @method = method
13
+ @response = response
14
+ @url = url
15
+ end
16
+
17
+ # @param [WEBrick::HTTPRequest] http_request
18
+ def match?(http_request)
19
+ request_uri = http_request.host + http_request.path
20
+ http_request.request_method == @method && request_uri.match?(@url)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,24 @@
1
+ module MiniProxy
2
+ module Stub
3
+ # MiniProxy stub response, so stubbed requests can return a custom response
4
+ #
5
+ class Response
6
+ def initialize(headers:, body:)
7
+ @body = body
8
+ @headers = headers
9
+ end
10
+
11
+ def body
12
+ @body || ""
13
+ end
14
+
15
+ def code
16
+ 200
17
+ end
18
+
19
+ def headers
20
+ @headers || { "Content-Type" => "text/html" }
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,3 @@
1
+ module MiniProxy
2
+ VERSION = "0.2.0".freeze
3
+ end
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: miniproxy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - The Conversation Dev Team
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-07-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: capybara
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: selenium-webdriver
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: A tool which allows you to easily stub requests to external sites for
56
+ your browser tests.
57
+ email:
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - lib/miniproxy.rb
63
+ - lib/miniproxy/fake_ssl_server.rb
64
+ - lib/miniproxy/proxy_server.rb
65
+ - lib/miniproxy/remote.rb
66
+ - lib/miniproxy/stub/request.rb
67
+ - lib/miniproxy/stub/response.rb
68
+ - lib/miniproxy/version.rb
69
+ homepage: https://github.com/conversation/miniproxy
70
+ licenses:
71
+ - MIT
72
+ metadata: {}
73
+ post_install_message:
74
+ rdoc_options: []
75
+ require_paths:
76
+ - lib
77
+ required_ruby_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ required_rubygems_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ requirements: []
88
+ rubyforge_project:
89
+ rubygems_version: 2.7.3
90
+ signing_key:
91
+ specification_version: 4
92
+ summary: Easily stub external requests for your browser tests.
93
+ test_files: []