cap_proxy 0.1.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.
data/README.md ADDED
@@ -0,0 +1,194 @@
1
+ # CapProxy
2
+
3
+ [![Build Status](https://travis-ci.org/ayosec/cap_proxy.svg)](https://travis-ci.org/ayosec/cap_proxy)
4
+
5
+ HTTP proxy with the ability to capture requests and generate a fake response. It is *not* intended to use in production environments, but only in integration tests.
6
+
7
+ It is tested in Ruby MRI (1.9, 2.0, 2.1), JRuby and Rubinius. Check out the [Travis page](https://travis-ci.org/ayosec/cap_proxy) to see the current status on every platform.
8
+
9
+ ## Usage
10
+
11
+ ### Installation
12
+
13
+ Install the gem with
14
+
15
+ $ gem install cap_proxy
16
+
17
+ Or add it to your `Gemfile`
18
+
19
+ ```ruby
20
+ group :test do
21
+ gem "cap_proxy"
22
+ end
23
+ ```
24
+
25
+ Finally, load it into your application
26
+
27
+ ```ruby
28
+ require "cap_proxy/server"
29
+ ```
30
+
31
+ ### Create a proxy instance
32
+
33
+ The main class to use the proxy is `CapProxy::Server`. You have to indicate the address where the proxy will be listening, and the address of the HTTP server which receives every non-captured request.
34
+
35
+ When the proxy is initialized, you have to call to the `#run!` method to start the server. This method has to be invoked when [EventMachine](http://eventmachine.rubyforge.org/) is running.
36
+
37
+ ```ruby
38
+ proxy = CapProxy::Server.new("localhost:1234", "localhost:5678")
39
+
40
+ EM.run do
41
+ proxy.run!
42
+ end
43
+ ```
44
+
45
+ You can use a logger with the proxy:
46
+
47
+ ```ruby
48
+ logger = Logger.new(STDOUT)
49
+ proxy = CapProxy::Server.new("localhost:1234", "localhost:5678", logger)
50
+
51
+ EM.run do
52
+ proxy.run!
53
+ end
54
+ ```
55
+
56
+ Now, every connection to `http://localhost:1234` will be forwarded to `http://localhost:5678`.
57
+
58
+ ### Testing
59
+
60
+ For your convenience, if you are using RSpec you can use `CapProxy::TestWrapper` to wrap every example.
61
+
62
+ ```ruby
63
+ require "cap_proxy/testkit"
64
+
65
+ # ...
66
+
67
+ around :each do |example|
68
+ CapProxy::TestWrapper.run(example, "localhost:4001", "localhost:3000") do |proxy|
69
+ @proxy = proxy
70
+ end
71
+ end
72
+ ```
73
+
74
+ `CapProxy::TestWrapper` will initialize EventMachine, creates a proxy running in it, and launch the example in a different thread. When the example is finished, the EventMachine is stopped.
75
+
76
+ ### Capturing and manipulating requests
77
+
78
+ With `#capture` you can capture any request, and generate a dummy response for it. You have to define a filter, and a block to generate the response.
79
+
80
+ ```ruby
81
+ @proxy.capture(method: "get", path: "/users") do |client, request|
82
+ client.respond 404, {}, "Dummy response"
83
+ end
84
+ ```
85
+
86
+ Details about how to capture requests, and to generate a response for it, are in the section *Capturing requests*.
87
+
88
+ ## Capturing requests
89
+
90
+ The first step is to define a filter. If a request matches multiple filters, it will be managed by the oldest one. If no filter matches the request, it will be forwarded to the default HTTP server.
91
+
92
+ ### Defining a filter
93
+
94
+ The easiest way to define a filter is with a hash, which can contain any of the following items:
95
+
96
+ * `:method`
97
+ * `:path`
98
+ * `:headers`
99
+
100
+ `:method` can accept any string to represent a HTTP method (`"get"` and `"GET"` are equivalent).
101
+
102
+ ```ruby
103
+ @proxy.capture(method: "get") { ... }
104
+ ```
105
+
106
+ `:path` can be defined with a string (full URI has to match), or with a regular expression (matched with the `=~` operator).
107
+
108
+ ```ruby
109
+ @proxy.capture(path: "/users") { ... }
110
+
111
+ @proxy.capture(method: "post", path: %r{/users/\d+}) { ... }
112
+ ```
113
+
114
+ `:headers` expects a hash with the headers to be matched. The value of every header can be a string or a regular expression.
115
+
116
+ ```ruby
117
+ @proxy.capture(path: "/", headers: { "content-type" => /json/ }) do
118
+ ...
119
+ end
120
+ ```
121
+
122
+ ### Custom filters
123
+
124
+ If you need more control to filter the request, you can create your own filter
125
+
126
+ ```ruby
127
+ class MyFilter < CapProxy::Filter
128
+
129
+ def apply?(request)
130
+ # Conditions
131
+ end
132
+
133
+ end
134
+ ```
135
+
136
+ The `#apply?` method receives an instance of `Http::Parser`, from the [http_parser.rb gem](https://github.com/tmm1/http_parser.rb). You can use methods like `http_method`, `request_url` or `headers` to query info about the request.
137
+
138
+ If `#apply?` returns `false` or `nil`, the filter will skip this request.
139
+
140
+ To use your filter, create an instance of it:
141
+
142
+ ```ruby
143
+ @proxy.capture(MyFilter.new) { ... }
144
+ ```
145
+
146
+ ### Generating responses
147
+
148
+ The block of the `capture` method is invoked with two arguments: the first one is an instance of `CapProxy::Client`. The last one is the instance of `HTTP::Parser`.
149
+
150
+ The simplest way to generate a response is calling to `client.respond(status, headers, body)`.
151
+
152
+ ```ruby
153
+ @proxy.capture(path: "/users", method: "post") do |client, request|
154
+ client.respond 201, {"Content-Type" => "application/json"}, %q[{"id": 123}]
155
+ end
156
+ ```
157
+
158
+ ### Chunked responses
159
+
160
+ You can generate a response in [multiple chunks](http://en.wikipedia.org/wiki/Chunked_transfer_encoding).
161
+
162
+ First, you have to call to `client.chunks_start(status, headers)`, to initialize the chunked response. Then, for every chunk, call to `client.chunks_send(data)`. Finally, finish the connection with `client.chunks_finish`
163
+
164
+ ```ruby
165
+ @proxy.capture(path: "/chunks") do |client, request|
166
+ client.chunks_start 200, "content-type" => "text/plain"
167
+ EM.add_timer(0.4) { client.chunks_send("Cap") }
168
+ EM.add_timer(0.8) { client.chunks_send("Proxy\n") }
169
+ EM.add_timer(1.2) { client.chunks_finish }
170
+ end
171
+ ```
172
+
173
+ ## Example
174
+
175
+ ```ruby
176
+ require "cap_proxy/server"
177
+ require "cap_proxy/testkit"
178
+
179
+ describe MyApp do
180
+
181
+ around :each do |test|
182
+ CapProxy::TestWrapper.run(test, "localhost:4001", "localhost:3000") do |proxy|
183
+ @proxy = proxy
184
+ end
185
+ end
186
+
187
+ it "should do it" do
188
+ @proxy.capture(method: "get", path: "/users") do |client, request|
189
+ request.request_url.should include("foo_id")
190
+ client.respond 404, {}, "Dummy response"
191
+ end
192
+ end
193
+ end
194
+ ```
@@ -0,0 +1,90 @@
1
+ require "eventmachine"
2
+ require "http/parser"
3
+ require_relative "remote_connection"
4
+ require_relative "http_codes"
5
+
6
+ module CapProxy
7
+ class Client < EM::Connection
8
+ attr_reader :server, :head
9
+
10
+ def initialize(server)
11
+ @server = server
12
+ @remote = nil
13
+ @data = nil
14
+ @http_parser = HTTP::Parser.new
15
+
16
+ @http_parser.on_headers_complete = proc do
17
+ verify_headers!
18
+ end
19
+ end
20
+
21
+ def unbind
22
+ if @remote
23
+ @remote.close_connection_after_writing
24
+ end
25
+ end
26
+
27
+ def write_head(status, headers)
28
+ head = [ "HTTP/1.1 #{status} #{HTTPCodes[status]}\r\n" ]
29
+
30
+ if headers
31
+ headers.each_pair do |key, value|
32
+ head << "#{key}: #{value}\r\n"
33
+ end
34
+ end
35
+
36
+ head << "\r\n"
37
+ send_data head.join
38
+ end
39
+
40
+ def respond(status, headers, body = nil)
41
+ write_head(status, headers)
42
+ send_data(body) if body
43
+ close_connection_after_writing
44
+ end
45
+
46
+ def verify_headers!
47
+ parser = @http_parser
48
+ filter = server.filters.find {|f| f[:filter].apply?(parser) }
49
+
50
+ server.log.info "#{parser.http_method} #{parser.request_url} - #{filter ? "filtered" : "raw"}" if server.log
51
+ if filter
52
+ filter[:handler].call self, parser
53
+ else
54
+ @remote = EM.connect(server.target_host, server.target_port, RemoteConnection, self)
55
+ @remote.send_data @data
56
+ @data = nil
57
+ @http_parser = nil
58
+ end
59
+ end
60
+
61
+ def receive_data(data)
62
+ if @remote
63
+ @remote.send_data data
64
+ else
65
+ if @data
66
+ @data << data
67
+ else
68
+ @data = data
69
+ end
70
+ @http_parser << data
71
+ end
72
+ end
73
+
74
+ def chunks_start(status, headers = {})
75
+ write_head(status, headers.merge("Transfer-Encoding" => "chunked"))
76
+ end
77
+
78
+ def chunks_send(data)
79
+ send_data "#{data.bytesize.to_s(16)}\r\n"
80
+ send_data data
81
+ send_data "\r\n"
82
+ end
83
+
84
+ def chunks_finish
85
+ send_data "0\r\n\r\n"
86
+ close_connection_after_writing
87
+ end
88
+
89
+ end
90
+ end
@@ -0,0 +1,75 @@
1
+ module CapProxy
2
+
3
+ class InvalidFilterParam < RuntimeError; end
4
+ class InvalidRule < RuntimeError; end
5
+
6
+ class Filter
7
+ def self.from_hash(hash)
8
+ RulesEngine.new(
9
+ hash.each_pair.map do |param, value|
10
+ case param
11
+ when :method
12
+ [ [ :method, value.upcase ] ]
13
+ when :path
14
+ case value
15
+ when Regexp
16
+ [ [ :path_regexp, value ] ]
17
+ when String
18
+ [ [ :path, value ] ]
19
+ else
20
+ raise InvalidFilterParam.new("Invalid value #{value.inspect} for :path")
21
+ end
22
+ when :headers
23
+ value.each_pair.map do |header, value|
24
+ case value
25
+ when String
26
+ [ :header, header.downcase, value ]
27
+ when Regexp
28
+ [ :header_regexp, header.downcase, value ]
29
+ else
30
+ raise InvalidRule.new("Invalid header rule #{value.inspect}")
31
+ end
32
+ end
33
+ else
34
+ raise InvalidFilterParam.new("Invalid item #{param.inspect}")
35
+ end
36
+ end.inject {|a, b| a.concat(b)}
37
+ )
38
+ end
39
+
40
+ def apply?(request)
41
+ raise NotImplementedError.new("apply? has to be implemented by inherited classes")
42
+ end
43
+ end
44
+
45
+ class RulesEngine < Filter
46
+ def initialize(rules)
47
+ raise InvalidRule.new("At least one rule is required") if rules.empty?
48
+ @rules = rules
49
+ end
50
+
51
+ def apply?(request)
52
+ @rules.all? do |rule|
53
+ case rule[0]
54
+ when :method
55
+ rule[1] == request.http_method
56
+ when :path_regexp
57
+ request.request_url =~ rule[1]
58
+ when :path
59
+ request.request_url == rule[1]
60
+ when :header_regexp
61
+ request.headers.each_pair.any? do |rh, rv|
62
+ rh.downcase == rule[1] && rv =~ rule[2]
63
+ end
64
+ when :header
65
+ request.headers.each_pair.any? do |rh, rv|
66
+ rh.downcase == rule[1] && rv == rule[2]
67
+ end
68
+ else
69
+ raise InvalidRule.new("Invalid rule #{rule.inspect}")
70
+ end
71
+ end
72
+ end
73
+ end
74
+
75
+ end
@@ -0,0 +1,48 @@
1
+ module CapProxy
2
+
3
+ HTTPCodes = {
4
+
5
+ 100 => "Continue",
6
+ 101 => "Switch Protocol",
7
+ 200 => "OK",
8
+ 201 => "Created",
9
+ 202 => "Accepted",
10
+ 203 => "Non Authoritative Information",
11
+ 204 => "No Content",
12
+ 205 => "Reset Content",
13
+ 206 => "Partial Content",
14
+ 300 => "Multiple Choice",
15
+ 301 => "Moved Permanently",
16
+ 302 => "Found",
17
+ 303 => "See Other",
18
+ 304 => "Not Modified",
19
+ 305 => "Use Proxy",
20
+ 307 => "Temporary Redirect",
21
+ 400 => "Bad Request",
22
+ 401 => "Unauthorized",
23
+ 402 => "Payment Required",
24
+ 403 => "Forbidden",
25
+ 404 => "Not Found",
26
+ 405 => "Method Not Allowed",
27
+ 406 => "Not Acceptable",
28
+ 407 => "Proxy Authentication Required",
29
+ 408 => "Request Time Out",
30
+ 409 => "Conflict",
31
+ 410 => "Gone",
32
+ 411 => "Length Required",
33
+ 412 => "Precondition Failed",
34
+ 413 => "Request Entity Too Large",
35
+ 414 => "Request URIToo Long",
36
+ 415 => "Unsupported Media Type",
37
+ 416 => "Requested Range Not Satisfiable",
38
+ 417 => "Expectation Failed",
39
+ 500 => "Internal Server Error",
40
+ 501 => "Not Implemented",
41
+ 502 => "Bad Gateway",
42
+ 503 => "Service Unavailable",
43
+ 504 => "Gateway Time Out",
44
+ 505 => "Version Not Supported"
45
+
46
+ }
47
+
48
+ end
@@ -0,0 +1,21 @@
1
+ require "eventmachine"
2
+
3
+ module CapProxy
4
+ class RemoteConnection < EM::Connection
5
+ attr_reader :proxy_connection
6
+
7
+ def initialize(proxy_connection)
8
+ @proxy_connection = proxy_connection
9
+ end
10
+
11
+ def receive_data(data)
12
+ log = proxy_connection.server.log
13
+ log.debug("Closing #{proxy_connection.head}") if log
14
+ proxy_connection.send_data data
15
+ end
16
+
17
+ def unbind
18
+ proxy_connection.close_connection_after_writing
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,49 @@
1
+ require "eventmachine"
2
+ require "thread_safe"
3
+ require_relative "client"
4
+ require_relative "filter"
5
+
6
+ module CapProxy
7
+ class Server
8
+
9
+ attr_reader :proxy_port, :proxy_host, :target_host, :target_port, :log, :filters
10
+
11
+ def initialize(bind_address, target, log = nil)
12
+
13
+ proxy_host, proxy_port = bind_address.split(":", 2)
14
+ target_host, target_port = target.split(":", 2)
15
+
16
+ @log = log
17
+ @proxy_port = proxy_port
18
+ @proxy_host = proxy_host
19
+ @target_host = target_host
20
+ @target_port = target_port
21
+ reset_filters!
22
+ end
23
+
24
+ def reset_filters!
25
+ @filters = ThreadSafe::Array.new
26
+ end
27
+
28
+ def capture(filter_param, &handler)
29
+ filter =
30
+ case filter_param
31
+ when Hash
32
+ Filter.from_hash(filter_param)
33
+ when Filter
34
+ filter_param
35
+ else
36
+ raise RuntimeError("#capture requires a hash or a Filter object")
37
+ end
38
+
39
+ @filters.push filter: filter, handler: handler
40
+ filter
41
+ end
42
+
43
+ def run!
44
+ log.info "CapProxy: Listening on #{proxy_host}:#{proxy_port}" if log
45
+ EM.start_server proxy_host, proxy_port, Client, self
46
+ end
47
+
48
+ end
49
+ end
@@ -0,0 +1 @@
1
+ require_relative "testkit/em_wrapper"
@@ -0,0 +1,45 @@
1
+ require "eventmachine"
2
+ require "uri"
3
+ require "logger"
4
+
5
+ module CapProxy
6
+
7
+ module TestWrapper
8
+
9
+ class SimpleResponder < EM::Connection
10
+ def receive_data(_)
11
+ send_data "TCPServer\n"
12
+ close_connection_after_writing
13
+ end
14
+ end
15
+
16
+ def self.run(test, bind_address, target)
17
+
18
+ EM.run do
19
+
20
+ proxy = nil
21
+
22
+ EM.error_handler do |error|
23
+ STDERR.puts error
24
+ STDERR.puts error.backtrace.map {|l| "\t#{l}" }
25
+ end
26
+
27
+ logger = Logger.new(STDERR)
28
+ logger.level = Logger::ERROR
29
+ proxy = Server.new(bind_address, target, logger)
30
+ proxy.run!
31
+
32
+ if block_given?
33
+ yield proxy
34
+ end
35
+
36
+ Thread.new do
37
+ test.run
38
+ end
39
+ EM.stop_event_loop
40
+ end
41
+
42
+ end
43
+ end
44
+
45
+ end
@@ -0,0 +1,72 @@
1
+ require "spec_helper"
2
+
3
+ describe CapProxy::Filter do
4
+
5
+ context "Filters from hashes" do
6
+
7
+ def hash_filter?(hash, request)
8
+ parser = HTTP::Parser.new
9
+ parser << request
10
+
11
+ filter = CapProxy::Filter.from_hash(hash)
12
+ filter.apply?(parser)
13
+ end
14
+
15
+ it "filter by method and path" do
16
+ hash_filter?(
17
+ {method: "get", path: "/foo/bar"},
18
+ %[GET /foo/bar HTTP/1.0\r\n\r\n]
19
+ ).should be_true
20
+
21
+ hash_filter?(
22
+ {method: "post", path: "/foo/bar"},
23
+ %[GET /foo/bar HTTP/1.0\r\n\r\n]
24
+ ).should be_false
25
+
26
+ hash_filter?(
27
+ {method: "post", path: %r{/foo/bar/(\d+)}},
28
+ %[GET /foo/bar/100 HTTP/1.0\r\n\r\n]
29
+ ).should be_false
30
+ end
31
+
32
+ it "filter by path and headers" do
33
+
34
+ hash_filter?(
35
+ {
36
+ path: "/",
37
+ headers: {
38
+ "content-type" => /json/
39
+ }
40
+ },
41
+ %[GET / HTTP/1.0\r\nContent-Type: application/json\r\n\r\n]
42
+ ).should be_true
43
+
44
+ hash_filter?(
45
+ {
46
+ path: "/",
47
+ headers: {
48
+ "content-type" => /json/,
49
+ "user-agent" => "none"
50
+ }
51
+ },
52
+ %[GET / HTTP/1.0\r\nContent-Type: application/json\r\n\r\n]
53
+ ).should be_false
54
+
55
+ hash_filter?(
56
+ {
57
+ path: "/",
58
+ headers: {
59
+ "user-agent" => "none",
60
+ "Accept" => "*"
61
+ }
62
+ },
63
+ %[GET / HTTP/1.0\r\n] +
64
+ %[User-Agent: none\r\n] +
65
+ %[accept: *\r\n] +
66
+ %[\r\n]
67
+ ).should be_true
68
+ end
69
+
70
+ end
71
+
72
+ end
@@ -0,0 +1,80 @@
1
+ # coding: utf-8
2
+
3
+ require "spec_helper"
4
+ require "net/http"
5
+
6
+ describe CapProxy::Server do
7
+
8
+ around :each do |test|
9
+ CapProxy::TestWrapper.run(test, "localhost:50300", "localhost:50301") do |proxy|
10
+ EM.start_server "localhost", 50301, CapProxy::TestWrapper::SimpleResponder
11
+ @proxy = proxy
12
+ end
13
+ end
14
+
15
+ def proxy_req!(packet)
16
+ conn = TCPSocket.new("localhost", 50300)
17
+ conn.write(packet)
18
+ response = conn.read(512)
19
+ conn.close
20
+ response
21
+ end
22
+
23
+ it "should proxy basic HTTP requests" do
24
+ proxy_req!("GET / HTTP/1.0\r\n\r\n").should == "TCPServer\n"
25
+ end
26
+
27
+ it "should capture requests" do
28
+ @proxy.capture(method: "get", path: /x/) do |client, request|
29
+ request.request_url.should include("x")
30
+ client.respond 404, {}, "foobar"
31
+ end
32
+
33
+ proxy_req!("GET /a/x/ HTTP/1.0\r\n\r\n").should =~ %r[\AHTTP/1.1 404 Not Found\r\n.*foobar\Z]m
34
+ proxy_req!("POST /a/x/ HTTP/1.0\r\n\r\n").should == "TCPServer\n"
35
+ end
36
+
37
+ it "should respond with custom headers" do
38
+ @proxy.capture(method: "put") do |client, request|
39
+ request.http_method.should == "PUT"
40
+ client.respond 200, {"x-foo" => "that"}, "."
41
+ end
42
+
43
+ resp = proxy_req!("PUT / HTTP/1.0\r\n\r\n")
44
+ resp.should =~ %r[\AHTTP/1.1 200 OK\r\n]
45
+ resp.should include("x-foo: that\r\n")
46
+ end
47
+
48
+ it "should use a custom filter" do
49
+ class CaptureAll < CapProxy::Filter
50
+ def apply?(request)
51
+ true
52
+ end
53
+ end
54
+
55
+ @proxy.capture(CaptureAll.new) do |client, request|
56
+ client.respond 200, {}, "-captured-"
57
+ end
58
+
59
+ proxy_req!("PUT / HTTP/1.0\r\n\r\n").should include("-captured-")
60
+ proxy_req!("GET /foo HTTP/1.0\r\n\r\n").should include("-captured-")
61
+ end
62
+
63
+ it "should manage chunked responsed" do
64
+ @proxy.capture(method: "get") do |client, request|
65
+ client.chunks_start 201, "content-type" => "text/foo"
66
+ EM.add_timer(0.1) { client.chunks_send([-30, -100, -108, 97].pack("c*")) }
67
+ EM.add_timer(0.3) { client.chunks_send("abc") }
68
+ EM.add_timer(0.5) { client.chunks_finish }
69
+ end
70
+
71
+ start_time = Time.now.to_f
72
+ resp = Net::HTTP.get_response(URI("http://localhost:50300"))
73
+ resp.code.should == "201"
74
+ resp["Content-Type"].should == "text/foo"
75
+ resp.body.bytes.to_a.should == "✔aabc".bytes.to_a
76
+
77
+ (Time.now.to_f - start_time).should >= 0.5
78
+ end
79
+
80
+ end
@@ -0,0 +1,3 @@
1
+ require "rspec"
2
+ require_relative "../lib/cap_proxy/server"
3
+ require_relative "../lib/cap_proxy/testkit"
metadata ADDED
@@ -0,0 +1,138 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cap_proxy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Ayose Cazorla
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-04-23 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: eventmachine
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 1.0.3
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 1.0.3
30
+ - !ruby/object:Gem::Dependency
31
+ name: http_parser.rb
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: thread_safe
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rake
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: 10.3.0
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: 10.3.0
78
+ - !ruby/object:Gem::Dependency
79
+ name: rspec
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ description: HTTP proxy with the ability to capture requests and generate a fake response.
95
+ It is *not* intended to use in production environments, but only in integration
96
+ tests.
97
+ email: ayosec@gmail.com
98
+ executables: []
99
+ extensions: []
100
+ extra_rdoc_files: []
101
+ files:
102
+ - README.md
103
+ - lib/cap_proxy/client.rb
104
+ - lib/cap_proxy/filter.rb
105
+ - lib/cap_proxy/http_codes.rb
106
+ - lib/cap_proxy/remote_connection.rb
107
+ - lib/cap_proxy/server.rb
108
+ - lib/cap_proxy/testkit.rb
109
+ - lib/cap_proxy/testkit/em_wrapper.rb
110
+ - spec/filters_spec.rb
111
+ - spec/server_spec.rb
112
+ - spec/spec_helper.rb
113
+ homepage: https://github.com/ayosec/cap_proxy
114
+ licenses:
115
+ - MIT
116
+ post_install_message:
117
+ rdoc_options: []
118
+ require_paths:
119
+ - lib
120
+ required_ruby_version: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ required_rubygems_version: !ruby/object:Gem::Requirement
127
+ none: false
128
+ requirements:
129
+ - - ! '>='
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ requirements: []
133
+ rubyforge_project:
134
+ rubygems_version: 1.8.23
135
+ signing_key:
136
+ specification_version: 3
137
+ summary: HTTP Proxy with ability to capture and manipulate requests.
138
+ test_files: []