cap_proxy 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []