CapProxy
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.
It is tested in Ruby MRI (1.9, 2.0, 2.1) and Rubinius. Check out the Travis page to see the current status on every platform. We plan to support JRuby when some concurrency issues are managed.
Usage
Installation
Install the gem with
$ gem install cap_proxy
Or add it to your Gemfile
group :test do
gem "cap_proxy"
endFinally, load it into your application
require "cap_proxy/server"Create a proxy instance
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.
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 is running.
proxy = CapProxy::Server.new("localhost:1234", "localhost:5678")
EM.run do
proxy.run!
endYou can use a logger with the proxy:
logger = Logger.new(STDOUT)
proxy = CapProxy::Server.new("localhost:1234", "localhost:5678", logger)
EM.run do
proxy.run!
endNow, every connection to http://localhost:1234 will be forwarded to http://localhost:5678.
Testing
For your convenience, if you are using RSpec you can use CapProxy::TestWrapper to wrap every example.
require "cap_proxy/testkit"
# ...
around :each do |example|
CapProxy::TestWrapper.run(example, "localhost:4001", "localhost:3000") do |proxy|
@proxy = proxy
end
endCapProxy::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.
Capturing and manipulating requests
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.
@proxy.capture(method: "get", path: "/users") do |client, request|
client.respond 404, {}, "Dummy response"
endDetails about how to capture requests, and to generate a response for it, are in the section Capturing requests.
Capturing requests
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.
Defining a filter
The easiest way to define a filter is with a hash, which can contain any of the following items:
:method:path:headers
:method can accept any string to represent a HTTP method ("get" and "GET" are equivalent).
@proxy.capture(method: "get") { ... }:path can be defined with a string (full URI has to match), or with a regular expression (matched with the =~ operator).
@proxy.capture(path: "/users") { ... }
@proxy.capture(method: "post", path: %r{/users/\d+}) { ... }:headers expects a hash with the headers to be matched. The value of every header can be a string or a regular expression.
@proxy.capture(path: "/", headers: { "content-type" => /json/ }) do
...
endCustom filters
If you need more control to filter the request, you can create your own filter
class MyFilter < CapProxy::Filter
def apply?(request)
# Conditions
end
endThe #apply? method receives an instance of Http::Parser, from the http_parser.rb gem. You can use methods like http_method, request_url or headers to query info about the request.
If #apply? returns false or nil, the filter will skip this request.
To use your filter, create an instance of it:
@proxy.capture(MyFilter.new) { ... }Generating responses
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.
The simplest way to generate a response is calling to client.respond(status, headers, body).
@proxy.capture(path: "/users", method: "post") do |client, request|
client.respond 201, {"Content-Type" => "application/json"}, %q[{"id": 123}]
endChunked responses
You can generate a response in multiple chunks.
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
@proxy.capture(path: "/chunks") do |client, request|
client.chunks_start 200, "content-type" => "text/plain"
EM.add_timer(0.4) { client.chunks_send("Cap") }
EM.add_timer(0.8) { client.chunks_send("Proxy\n") }
EM.add_timer(1.2) { client.chunks_finish }
endExample
require "cap_proxy/server"
require "cap_proxy/testkit"
describe MyApp do
around :each do |test|
CapProxy::TestWrapper.run(test, "localhost:4001", "localhost:3000") do |proxy|
@proxy = proxy
end
end
it "should do it" do
@proxy.capture(method: "get", path: "/users") do |client, request|
request.request_url.should include("foo_id")
client.respond 404, {}, "Dummy response"
end
end
end