liquid-proxy 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/.rspec +2 -0
- data/.travis.yml +9 -0
- data/Gemfile +10 -0
- data/README.markdown +80 -0
- data/Rakefile +2 -0
- data/bin/liquid-proxy +14 -0
- data/features/change_headers.feature +18 -0
- data/features/step_definition/proxy_steps.rb +27 -0
- data/features/support/bin/appserver +16 -0
- data/features/support/env.rb +35 -0
- data/lib/liquid-proxy.rb +55 -0
- data/lib/liquid-proxy/api_controller.rb +23 -0
- data/lib/liquid-proxy/connection.rb +13 -0
- data/lib/liquid-proxy/connection_processor.rb +47 -0
- data/lib/liquid-proxy/request_builder.rb +15 -0
- data/lib/liquid-proxy/server_relay.rb +9 -0
- data/lib/liquid-proxy/service.rb +27 -0
- data/lib/liquid-proxy/subprocess.rb +19 -0
- data/lib/liquid-proxy/version.rb +3 -0
- data/lib/utils/port_explorer.rb +13 -0
- data/liquid-proxy.gemspec +26 -0
- data/spec/api_controller_spec.rb +51 -0
- data/spec/connection_processor_spec.rb +60 -0
- data/spec/connection_spec.rb +17 -0
- data/spec/liquid-proxy_spec.rb +59 -0
- data/spec/request_builder_spec.rb +12 -0
- data/spec/server_relay_spec.rb +26 -0
- data/spec/service_spec.rb +62 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/subprocess_spec.rb +49 -0
- data/spec/support/reset-singleton.rb +15 -0
- metadata +138 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.markdown
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
# Liquid Proxy
|
2
|
+
|
3
|
+
[![Build status](https://secure.travis-ci.org/artemave/liquid-proxy.png)](https://secure.travis-ci.org/artemave/liquid-proxy)
|
4
|
+
|
5
|
+
## Overview
|
6
|
+
|
7
|
+
Start http proxy that you can control via api in order to inject http headers into requests passing through.
|
8
|
+
|
9
|
+
Why? I needed it to recreate parts of infrustructure in test environment (namely, the app under test was behind the proxy that takes care of authenticating users and passes on its checks in the form of http header).
|
10
|
+
|
11
|
+
## Usage
|
12
|
+
|
13
|
+
This is a ruby gem and it requires ruby >= 1.9.2.
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
# Gemfile
|
17
|
+
gem 'liquid-proxy'
|
18
|
+
```
|
19
|
+
|
20
|
+
The following example shows usage with cucumber/capybara/selenium:
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
# env.rb
|
24
|
+
require 'capybara'
|
25
|
+
require 'selenium-webdriver'
|
26
|
+
require 'liquid-proxy'
|
27
|
+
|
28
|
+
LiquidProxy.start(:port => 9889)
|
29
|
+
|
30
|
+
Capybara.configure do |config|
|
31
|
+
config.default_driver = :selenium
|
32
|
+
config.run_server = false
|
33
|
+
config.app_host = "http://test.example.com"
|
34
|
+
end
|
35
|
+
|
36
|
+
Capybara.register_driver :selenium do |app|
|
37
|
+
profile = Selenium::WebDriver::Firefox::Profile.new
|
38
|
+
profile.proxy = Selenium::WebDriver::Proxy.new(http: 'localhost:9889', type: :manual)
|
39
|
+
|
40
|
+
Capybara::Selenium::Driver.new(app, profile: profile)
|
41
|
+
end
|
42
|
+
|
43
|
+
Before do
|
44
|
+
LiquidProxy.clear
|
45
|
+
end
|
46
|
+
```
|
47
|
+
|
48
|
+
Then in step definitions you can:
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
# set headers to inject
|
52
|
+
LiquidProxy.headers_to_inject = {'Foo' => 'Bar', 'Accept' => 'Cash'}
|
53
|
+
|
54
|
+
# add to headers to inject
|
55
|
+
LiquidProxy.headers_to_inject['X_HACKERY'] = 'BOOM'
|
56
|
+
|
57
|
+
# clear headers to inject
|
58
|
+
LiquidProxy.clear
|
59
|
+
```
|
60
|
+
|
61
|
+
## Standalone usage
|
62
|
+
|
63
|
+
If you are not using ruby, it is possible to run liquid-proxy as a standalone process and controll it via REST api:
|
64
|
+
|
65
|
+
# install
|
66
|
+
bash$ gem install liquid-proxy
|
67
|
+
|
68
|
+
# start
|
69
|
+
bash$ liquid-proxy 8998
|
70
|
+
|
71
|
+
# add headers
|
72
|
+
bash$ curl --data-binary '{"Foo":"Bar","Accept":"Cash"}' http://localhost:8998
|
73
|
+
|
74
|
+
# clear headers
|
75
|
+
bash$ curl -X DELETE http://localhost:8998
|
76
|
+
|
77
|
+
|
78
|
+
## Author
|
79
|
+
|
80
|
+
[Artem Avetisyan](https://github.com/artemave)
|
data/Rakefile
ADDED
data/bin/liquid-proxy
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$: << File.expand_path('../../lib', __FILE__)
|
4
|
+
|
5
|
+
require 'rubygems'
|
6
|
+
require 'em-proxy'
|
7
|
+
require 'liquid-proxy/connection'
|
8
|
+
|
9
|
+
LIQUID_PROXY_PORT = ARGV[0] || 8998
|
10
|
+
HEADERS_TO_INJECT = {}
|
11
|
+
|
12
|
+
Proxy.start(:host => 'localhost', :port => LIQUID_PROXY_PORT) do |conn|
|
13
|
+
LiquidProxy::Connection.setup(conn)
|
14
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
Feature: change HTTP headers
|
2
|
+
In order to create test scenarios that involve changing HTTP headers
|
3
|
+
As developer in test
|
4
|
+
I want to run the app under test via API controllable proxy
|
5
|
+
that can change headers of requests passing through
|
6
|
+
|
7
|
+
Background:
|
8
|
+
Given liquid-proxy is running
|
9
|
+
When I instruct liquid-proxy to add header "BOOM" with value "KABOOM"
|
10
|
+
And an http client makes request to a server via liquid-proxy
|
11
|
+
|
12
|
+
Scenario: add HTTP header
|
13
|
+
Then that server should see header "BOOM" with value "KABOOM" in incoming requests
|
14
|
+
|
15
|
+
Scenario: clear custom headers
|
16
|
+
When I instruct liquid-proxy to clear custom headers
|
17
|
+
And an http client makes request to a server via liquid-proxy
|
18
|
+
Then that server should not see header "BOOM" in incoming requests
|
@@ -0,0 +1,27 @@
|
|
1
|
+
Given /^liquid-proxy is running$/ do
|
2
|
+
LiquidProxy.start(:port => 9889)
|
3
|
+
end
|
4
|
+
|
5
|
+
When /^I instruct liquid\-proxy to add header "([^"]*)" with value "([^"]*)"$/ do |header, value|
|
6
|
+
LiquidProxy.headers_to_inject[header] = value
|
7
|
+
end
|
8
|
+
|
9
|
+
When /^an http client makes request to a server via liquid-proxy$/ do
|
10
|
+
proxy_class = Net::HTTP::Proxy('localhost', LiquidProxy.port)
|
11
|
+
res = proxy_class.start("localhost", TEST_APP_SERVER_PORT) do |http|
|
12
|
+
http.get "/something"
|
13
|
+
end
|
14
|
+
@request = JSON.parse(res.body)
|
15
|
+
end
|
16
|
+
|
17
|
+
Then /^that server should see header "([^"]*)" with value "([^"]*)" in incoming requests$/ do |header, value|
|
18
|
+
@request["HTTP_#{header}"].should == value
|
19
|
+
end
|
20
|
+
|
21
|
+
When /^I instruct liquid\-proxy to clear custom headers$/ do
|
22
|
+
LiquidProxy.headers_to_inject.clear
|
23
|
+
end
|
24
|
+
|
25
|
+
Then /^that server should not see header "([^"]*)" in incoming requests$/ do |header|
|
26
|
+
@request.should_not have_key("HTTP_#{header}")
|
27
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler/setup'
|
5
|
+
require "rack"
|
6
|
+
require 'json'
|
7
|
+
|
8
|
+
app = Proc.new do |env|
|
9
|
+
resp = env.select do |k,v|
|
10
|
+
v.respond_to?(:to_s)
|
11
|
+
end
|
12
|
+
|
13
|
+
[200, {"Content-Type" => "application/json"}, [resp.to_json]]
|
14
|
+
end
|
15
|
+
|
16
|
+
Rack::Handler::Thin.run(app, {:Host => "0.0.0.0", :Port => ARGV[0]})
|
@@ -0,0 +1,35 @@
|
|
1
|
+
$: << File.expand_path("../../../lib", __FILE__)
|
2
|
+
require 'liquid-proxy'
|
3
|
+
require 'net/http'
|
4
|
+
require 'childprocess'
|
5
|
+
require 'awesome_print'
|
6
|
+
require 'json'
|
7
|
+
|
8
|
+
TEST_APP_SERVER_PORT=8999
|
9
|
+
|
10
|
+
def start_test_appserver
|
11
|
+
appserver = ChildProcess.build(File.expand_path("../bin/appserver", __FILE__), TEST_APP_SERVER_PORT.to_s)
|
12
|
+
appserver.io.inherit!
|
13
|
+
appserver.start
|
14
|
+
|
15
|
+
while true
|
16
|
+
begin
|
17
|
+
Net::HTTP.get "localhost", '/', TEST_APP_SERVER_PORT
|
18
|
+
break
|
19
|
+
rescue Errno::ECONNREFUSED
|
20
|
+
puts "Waiting for test appserver to start..."
|
21
|
+
sleep 0.5
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.up?
|
26
|
+
RestClient.get server_address
|
27
|
+
rescue => e
|
28
|
+
!e.is_a?(Errno::ECONNREFUSED)
|
29
|
+
end
|
30
|
+
at_exit do
|
31
|
+
appserver.stop
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
start_test_appserver
|
data/lib/liquid-proxy.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require 'liquid-proxy/service'
|
3
|
+
require 'json'
|
4
|
+
require 'net/http'
|
5
|
+
|
6
|
+
class LiquidProxy
|
7
|
+
include Singleton
|
8
|
+
|
9
|
+
class HeadersToInject
|
10
|
+
def []=(key, value)
|
11
|
+
http.post '/', {key => value}.to_json
|
12
|
+
end
|
13
|
+
|
14
|
+
def set_hash(hash = {})
|
15
|
+
clear
|
16
|
+
http.post '/', hash.to_json unless hash.empty?
|
17
|
+
end
|
18
|
+
|
19
|
+
def clear
|
20
|
+
http.delete '/'
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def http
|
26
|
+
@http ||= Net::HTTP.new('127.0.0.1', LiquidProxy.port)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
attr_reader :port
|
31
|
+
|
32
|
+
def headers_to_inject
|
33
|
+
@headers_to_inject ||= HeadersToInject.new
|
34
|
+
end
|
35
|
+
|
36
|
+
def headers_to_inject=(hash = {})
|
37
|
+
headers_to_inject.set_hash(hash)
|
38
|
+
end
|
39
|
+
|
40
|
+
def start(opts = {:port => 8998})
|
41
|
+
@port = opts[:port]
|
42
|
+
|
43
|
+
Service.start(opts)
|
44
|
+
|
45
|
+
Kernel.sleep 0.5
|
46
|
+
while not Service.up?
|
47
|
+
puts "Waiting for LiquidProxy to start..."
|
48
|
+
Kernel.sleep 0.5
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.method_missing(*args)
|
53
|
+
instance.send(*args)
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'http_tools'
|
3
|
+
|
4
|
+
class LiquidProxy
|
5
|
+
module ApiController
|
6
|
+
def process_api_call
|
7
|
+
if parser.http_method =~ /delete/i
|
8
|
+
headers_to_inject.clear
|
9
|
+
else
|
10
|
+
new_headers = JSON.parse(body) rescue {}
|
11
|
+
headers_to_inject.merge!(new_headers)
|
12
|
+
end
|
13
|
+
|
14
|
+
send_data HTTPTools::Builder.response(:ok)
|
15
|
+
close_connection_after_writing
|
16
|
+
end
|
17
|
+
|
18
|
+
def api_call?
|
19
|
+
host, port = parser.headers['Host'].split(':')
|
20
|
+
host =~ /^(localhost|127.0.0.1)$/ && port == ::LIQUID_PROXY_PORT.to_s
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'http/parser'
|
2
|
+
require 'liquid-proxy/server_relay'
|
3
|
+
require 'liquid-proxy/request_builder'
|
4
|
+
require 'liquid-proxy/api_controller'
|
5
|
+
|
6
|
+
class LiquidProxy
|
7
|
+
module ConnectionProcessor
|
8
|
+
include ServerRelay
|
9
|
+
include ApiController
|
10
|
+
|
11
|
+
def process_data(data)
|
12
|
+
parser << data
|
13
|
+
end
|
14
|
+
|
15
|
+
def parser
|
16
|
+
@parser ||= Http::Parser.new(self)
|
17
|
+
end
|
18
|
+
|
19
|
+
def body
|
20
|
+
@body ||= ''
|
21
|
+
end
|
22
|
+
|
23
|
+
def on_body(chunk)
|
24
|
+
body << chunk
|
25
|
+
end
|
26
|
+
|
27
|
+
def request_builder
|
28
|
+
@request_builder ||= RequestBuilder.new
|
29
|
+
end
|
30
|
+
|
31
|
+
def on_message_complete
|
32
|
+
if api_call?
|
33
|
+
process_api_call
|
34
|
+
else
|
35
|
+
parser.headers.merge!(headers_to_inject)
|
36
|
+
|
37
|
+
new_request = request_builder.build(parser, body)
|
38
|
+
body.clear
|
39
|
+
pass_to_server new_request
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def headers_to_inject
|
44
|
+
@headers_to_inject ||= HEADERS_TO_INJECT
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'http_tools'
|
2
|
+
|
3
|
+
class LiquidProxy
|
4
|
+
class RequestBuilder
|
5
|
+
def build(parser, body = '')
|
6
|
+
new_request = HTTPTools::Builder.request(
|
7
|
+
parser.http_method,
|
8
|
+
parser.headers['Host'],
|
9
|
+
parser.request_url,
|
10
|
+
parser.headers
|
11
|
+
)
|
12
|
+
new_request+= body
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require 'liquid-proxy/subprocess'
|
3
|
+
require 'utils/port_explorer'
|
4
|
+
|
5
|
+
class LiquidProxy
|
6
|
+
class Service
|
7
|
+
include Singleton
|
8
|
+
|
9
|
+
def start(opts = {})
|
10
|
+
if @child and @child.alive?
|
11
|
+
return
|
12
|
+
end
|
13
|
+
|
14
|
+
@port = opts[:port]
|
15
|
+
|
16
|
+
@child = Subprocess.new(opts)
|
17
|
+
end
|
18
|
+
|
19
|
+
def up?
|
20
|
+
!!(@child && @child.alive? && Utils::PortExplorer.port_occupied?(@port))
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.method_missing(*args)
|
24
|
+
instance.send(*args)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'childprocess'
|
2
|
+
|
3
|
+
class LiquidProxy
|
4
|
+
class Subprocess
|
5
|
+
def initialize(opts = {})
|
6
|
+
@child = ChildProcess.build(File.expand_path("../../../bin/liquid-proxy", __FILE__), opts[:port].to_s || "8998")
|
7
|
+
@child.io.inherit!
|
8
|
+
@child.start
|
9
|
+
|
10
|
+
at_exit do
|
11
|
+
@child.stop
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def alive?
|
16
|
+
@child.alive?
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "liquid-proxy/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "liquid-proxy"
|
7
|
+
s.version = LiquidProxy::VERSION
|
8
|
+
s.authors = ["artemave"]
|
9
|
+
s.email = ["artemave@gmail.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{http proxy with api for modifying requests passing through}
|
12
|
+
#s.description = %q{http proxy with api for modifying requests passing through}
|
13
|
+
|
14
|
+
s.rubyforge_project = "liquid-proxy"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# specify any dependencies here; for example:
|
22
|
+
s.add_runtime_dependency "em-proxy"
|
23
|
+
s.add_runtime_dependency "http_tools"
|
24
|
+
s.add_runtime_dependency "http_parser.rb"
|
25
|
+
s.add_runtime_dependency 'childprocess'
|
26
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require_relative "spec_helper"
|
2
|
+
require 'liquid-proxy/api_controller'
|
3
|
+
|
4
|
+
describe LiquidProxy::ApiController do
|
5
|
+
let :conn do
|
6
|
+
o = Object.new
|
7
|
+
o.extend(LiquidProxy::ApiController)
|
8
|
+
o
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'knows when request is an api call' do
|
12
|
+
LIQUID_PROXY_PORT = 9876 unless defined?(LIQUID_PROXY_PORT)
|
13
|
+
|
14
|
+
conn.stub_chain('parser.headers').and_return('Host' => "localhost:#{LIQUID_PROXY_PORT}")
|
15
|
+
conn.api_call?.should == true
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'knows when request is NOT an api call' do
|
19
|
+
conn.stub_chain('parser.headers').and_return('Host' => "localhost:1111")
|
20
|
+
conn.api_call?.should == false
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'adds headers to inject' do
|
24
|
+
new_headers = {'X_HACK' => 'Boom'}
|
25
|
+
conn.stub(:send_data => nil, :close_connection_after_writing => nil, :parser => stub.as_null_object)
|
26
|
+
conn.stub(:body => new_headers.to_json)
|
27
|
+
conn.stub(:headers_to_inject => (headers_to_inject = mock))
|
28
|
+
|
29
|
+
headers_to_inject.should_receive(:merge!).with(new_headers)
|
30
|
+
conn.process_api_call
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'clears off headers to inject if api request method is DELETE' do
|
34
|
+
conn.stub(:send_data => nil, :close_connection_after_writing => nil)
|
35
|
+
conn.stub_chain('parser.http_method').and_return('DELETE')
|
36
|
+
conn.stub(:headers_to_inject => (headers_to_inject = mock))
|
37
|
+
headers_to_inject.should_receive(:clear)
|
38
|
+
conn.process_api_call
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'relays back to client' do
|
42
|
+
response = "response_#{Time.now}"
|
43
|
+
HTTPTools::Builder.stub(:response).with(:ok).and_return(response)
|
44
|
+
conn.stub(:headers_to_inject => stub.as_null_object, :parser => stub.as_null_object)
|
45
|
+
|
46
|
+
conn.should_receive(:send_data).with(response).ordered
|
47
|
+
conn.should_receive(:close_connection_after_writing).ordered
|
48
|
+
|
49
|
+
conn.process_api_call
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require_relative "spec_helper"
|
2
|
+
require 'liquid-proxy/connection_processor'
|
3
|
+
require 'liquid-proxy/server_relay'
|
4
|
+
require 'liquid-proxy/api_controller'
|
5
|
+
|
6
|
+
describe LiquidProxy::ConnectionProcessor do
|
7
|
+
let :conn do
|
8
|
+
o = Object.new
|
9
|
+
o.extend(LiquidProxy::ConnectionProcessor)
|
10
|
+
o.extend(LiquidProxy::ServerRelay)
|
11
|
+
o.extend(LiquidProxy::ApiController)
|
12
|
+
o
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'feeds connection data to parser' do
|
16
|
+
data = 'data'
|
17
|
+
Http::Parser.stub(:new).and_return(parser = mock)
|
18
|
+
parser.should_receive(:<<).with(data)
|
19
|
+
conn.process_data(data)
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'passes request through' do
|
23
|
+
HEADERS_TO_INJECT = {} unless defined?(HEADERS_TO_INJECT)
|
24
|
+
new_request = "new_request#{Time.now}"
|
25
|
+
parser = stub(:headers => {'Host' => 'localhost'})
|
26
|
+
conn.stub(
|
27
|
+
:body => (body = mock),
|
28
|
+
:parser => parser,
|
29
|
+
:request_builder => (request_builder = stub),
|
30
|
+
:api_call? => false
|
31
|
+
)
|
32
|
+
request_builder.stub(:build).with(conn.parser, conn.body).and_return(new_request)
|
33
|
+
|
34
|
+
body.should_receive(:clear)
|
35
|
+
conn.should_receive(:pass_to_server).with(new_request)
|
36
|
+
|
37
|
+
conn.on_message_complete
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'adds custom headers to requests passing through' do
|
41
|
+
headers_to_inject = {'blah' => 'val1', 'boom' => 'val2'}
|
42
|
+
|
43
|
+
conn.stub(:server => nil, :relay_to_servers => nil, :api_call? => false)
|
44
|
+
conn.stub(:headers_to_inject => headers_to_inject)
|
45
|
+
conn.parser.stub(:headers).and_return(headers = mock.as_null_object)
|
46
|
+
|
47
|
+
headers.should_receive(:merge!).with(headers_to_inject)
|
48
|
+
|
49
|
+
conn.on_message_complete
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'treats requests to itself as API calls' do
|
53
|
+
conn.stub(:server => nil, :relay_to_servers => nil)
|
54
|
+
conn.stub(:api_call? => true)
|
55
|
+
|
56
|
+
conn.should_receive(:process_api_call)
|
57
|
+
conn.should_not_receive(:pass_to_server)
|
58
|
+
conn.on_message_complete
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require_relative "spec_helper"
|
2
|
+
require 'liquid-proxy/connection'
|
3
|
+
require 'liquid-proxy/connection_processor'
|
4
|
+
|
5
|
+
describe LiquidProxy::Connection do
|
6
|
+
context "em-proxy connection setup" do
|
7
|
+
it 'feeds data to connection processor' do
|
8
|
+
conn = mock
|
9
|
+
conn.should_receive(:extend).with(LiquidProxy::ConnectionProcessor) # this assert stinks?
|
10
|
+
conn.should_receive(:on_data) do |&block|
|
11
|
+
conn.should_receive(:process_data).with("data")
|
12
|
+
block.call("data")
|
13
|
+
end
|
14
|
+
LiquidProxy::Connection.setup(conn)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require_relative "spec_helper"
|
2
|
+
require 'liquid-proxy'
|
3
|
+
|
4
|
+
describe LiquidProxy do
|
5
|
+
before do
|
6
|
+
LiquidProxy::Service.reset_instance
|
7
|
+
LiquidProxy.reset_instance
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'starts' do
|
11
|
+
LiquidProxy::Service.stub(:up?).and_return(true)
|
12
|
+
LiquidProxy::Service.should_receive(:start).with(:port => 1234)
|
13
|
+
|
14
|
+
LiquidProxy.start(:port => 1234)
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'knows its port' do
|
18
|
+
LiquidProxy::Service.stub(:up?).and_return(true)
|
19
|
+
LiquidProxy::Service.stub(:start)
|
20
|
+
|
21
|
+
LiquidProxy.start(:port => 1234)
|
22
|
+
LiquidProxy.port.should == 1234
|
23
|
+
end
|
24
|
+
|
25
|
+
context 'when starting' do
|
26
|
+
it 'waits for proxy service to come up' do
|
27
|
+
LiquidProxy::Service.stub(:start)
|
28
|
+
LiquidProxy::Service.stub(:up?).and_return(false, false, true)
|
29
|
+
Kernel.should_receive(:sleep).with(0.5).exactly(3).times
|
30
|
+
|
31
|
+
LiquidProxy.start
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'header injection' do
|
36
|
+
let :headers_to_inject do
|
37
|
+
mock('headers_to_inject')
|
38
|
+
end
|
39
|
+
|
40
|
+
before do
|
41
|
+
LiquidProxy::HeadersToInject.stub(:new).and_return(headers_to_inject)
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'allows specify header as hash key' do
|
45
|
+
headers_to_inject.should_receive(:[]=).with('X_HACK', 'KABOOM')
|
46
|
+
LiquidProxy.headers_to_inject['X_HACK'] = 'KABOOM'
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'allows specify headers by assigning hash' do
|
50
|
+
headers_to_inject.should_receive(:set_hash).with('X_HACK' => 'KABOOM')
|
51
|
+
LiquidProxy.headers_to_inject = {'X_HACK' => 'KABOOM'}
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'clears off list headers to inject' do
|
55
|
+
headers_to_inject.should_receive(:clear)
|
56
|
+
LiquidProxy.headers_to_inject.clear
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require_relative "spec_helper"
|
2
|
+
require 'liquid-proxy/request_builder'
|
3
|
+
|
4
|
+
describe LiquidProxy::RequestBuilder do
|
5
|
+
it 'builds HTTP request' do
|
6
|
+
body = '_with_body'
|
7
|
+
parser = stub(:http_method => 'method', :headers => {'Host' => 'host'}, :request_url => 'url')
|
8
|
+
HTTPTools::Builder.stub(:request).with('method', 'host', 'url', {'Host' => 'host'}).and_return('new_request')
|
9
|
+
|
10
|
+
LiquidProxy::RequestBuilder.new.build(parser, body).should == 'new_request_with_body'
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require_relative "spec_helper"
|
2
|
+
require 'liquid-proxy/server_relay'
|
3
|
+
|
4
|
+
describe LiquidProxy::ServerRelay do
|
5
|
+
let :conn do
|
6
|
+
Object.new.extend(LiquidProxy::ServerRelay)
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'relays to server usging host and port from incoming request' do
|
10
|
+
host, port = 'remotehost', 9876
|
11
|
+
conn.stub(:parser => stub(:headers => {'Host' => "#{host}:#{port}"}))
|
12
|
+
|
13
|
+
conn.should_receive(:server).with(anything, :host => host, :port => port).ordered
|
14
|
+
conn.should_receive(:relay_to_servers).with('new_request').ordered
|
15
|
+
|
16
|
+
conn.pass_to_server('new_request')
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'uses port 80 by default' do
|
20
|
+
conn.stub(:relay_to_servers => nil, :parser => stub(:headers => {'Host' => "localhost"}))
|
21
|
+
|
22
|
+
conn.should_receive(:server).with(anything, :host => 'localhost', :port => 80)
|
23
|
+
|
24
|
+
conn.pass_to_server('new_request')
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
require 'utils/port_explorer'
|
3
|
+
require 'liquid-proxy/service'
|
4
|
+
|
5
|
+
describe LiquidProxy::Service do
|
6
|
+
before do
|
7
|
+
LiquidProxy::Service.reset_instance
|
8
|
+
end
|
9
|
+
|
10
|
+
context 'when started' do
|
11
|
+
it 'starts subprocess' do
|
12
|
+
opts = {:host => 'localhost'}
|
13
|
+
|
14
|
+
LiquidProxy::Subprocess.should_receive(:new).with(opts)
|
15
|
+
LiquidProxy::Service.start(opts)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'does nothing if subprocess already running' do
|
19
|
+
LiquidProxy::Subprocess.should_receive(:new).once.and_return(stub(:alive? => true))
|
20
|
+
|
21
|
+
LiquidProxy::Service.start
|
22
|
+
LiquidProxy::Service.start
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'knows when it is up if subprocess is alive and service port is occupied' do
|
27
|
+
port = 1234
|
28
|
+
LiquidProxy::Subprocess.stub(:new).and_return(sp = stub(:alive? => true))
|
29
|
+
|
30
|
+
sp.should_receive(:alive?)
|
31
|
+
Utils::PortExplorer.should_receive(:port_occupied?).with(port).and_return(true)
|
32
|
+
|
33
|
+
LiquidProxy::Service.start(:port => port)
|
34
|
+
LiquidProxy::Service.up?.should == true
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'it knows that it is NOT up' do
|
38
|
+
it 'if it has not been started' do
|
39
|
+
LiquidProxy::Service.up?.should == false
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'if subprocess is not alive' do
|
43
|
+
LiquidProxy::Subprocess.stub(:new).and_return(sp = stub(:alive? => false))
|
44
|
+
|
45
|
+
sp.should_receive(:alive?)
|
46
|
+
LiquidProxy::Service.start
|
47
|
+
LiquidProxy::Service.up?.should == false
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'if service port is not occupied' do
|
51
|
+
port = 1234
|
52
|
+
LiquidProxy::Subprocess.stub(:new).and_return(sp = stub(:alive? => true))
|
53
|
+
|
54
|
+
sp.should_receive(:alive?)
|
55
|
+
Utils::PortExplorer.should_receive(:port_occupied?).with(port).and_return(false)
|
56
|
+
|
57
|
+
LiquidProxy::Service.start(:port => port)
|
58
|
+
LiquidProxy::Service.up?.should == false
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
|
+
# Require this file using `require "spec_helper.rb"` to ensure that it is only
|
4
|
+
# loaded once.
|
5
|
+
#
|
6
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
|
+
|
8
|
+
|
9
|
+
$: << File.expand_path("../../../lib", __FILE__)
|
10
|
+
|
11
|
+
require_relative 'support/reset-singleton'
|
12
|
+
|
13
|
+
RSpec.configure do |config|
|
14
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
15
|
+
config.run_all_when_everything_filtered = true
|
16
|
+
config.filter_run :focus
|
17
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
require_relative 'spec_helper'
|
3
|
+
require 'liquid-proxy/subprocess'
|
4
|
+
|
5
|
+
describe LiquidProxy::Subprocess do
|
6
|
+
let :child do
|
7
|
+
double.as_null_object
|
8
|
+
end
|
9
|
+
|
10
|
+
before do
|
11
|
+
ChildProcess.stub(:build).and_return(child)
|
12
|
+
end
|
13
|
+
|
14
|
+
context 'ChildProcess start' do
|
15
|
+
it 'builds ChildProcess with cmd args from opts' do
|
16
|
+
opts = {:port => 1234}
|
17
|
+
ChildProcess.should_receive(:build) do |cmd, *params|
|
18
|
+
cmd.should =~ %r{bin/liquid-proxy}
|
19
|
+
params.should == ["1234"]
|
20
|
+
end
|
21
|
+
LiquidProxy::Subprocess.new(opts)
|
22
|
+
end
|
23
|
+
it 'inherits io and then starts child' do
|
24
|
+
child.should_receive(:io).ordered.and_return(io = mock)
|
25
|
+
io.should_receive(:inherit!)
|
26
|
+
child.should_receive(:start).ordered
|
27
|
+
LiquidProxy::Subprocess.new
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'knows if it is alive' do
|
32
|
+
child.stub(:alive? => true)
|
33
|
+
p = LiquidProxy::Subprocess.new
|
34
|
+
p.alive?.should == true
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'makes sure ChildProcess does not outlive parent' do
|
38
|
+
res_file = Tempfile.new('res')
|
39
|
+
child.stub(:stop) do
|
40
|
+
res_file.write "stopped"
|
41
|
+
res_file.rewind
|
42
|
+
end
|
43
|
+
fork do
|
44
|
+
LiquidProxy::Subprocess.new
|
45
|
+
end
|
46
|
+
Process.wait
|
47
|
+
res_file.read.should == 'stopped'
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
|
3
|
+
class <<Singleton
|
4
|
+
def included_with_reset(klass)
|
5
|
+
included_without_reset(klass)
|
6
|
+
class <<klass
|
7
|
+
def reset_instance
|
8
|
+
Singleton.send :__init__, self
|
9
|
+
self
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
alias_method :included_without_reset, :included
|
14
|
+
alias_method :included, :included_with_reset
|
15
|
+
end
|
metadata
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: liquid-proxy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- artemave
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-02-07 00:00:00.000000000 +00:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: em-proxy
|
17
|
+
requirement: &2161476040 !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ! '>='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '0'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: *2161476040
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: http_tools
|
28
|
+
requirement: &2161475620 !ruby/object:Gem::Requirement
|
29
|
+
none: false
|
30
|
+
requirements:
|
31
|
+
- - ! '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: *2161475620
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: http_parser.rb
|
39
|
+
requirement: &2161475200 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ! '>='
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '0'
|
45
|
+
type: :runtime
|
46
|
+
prerelease: false
|
47
|
+
version_requirements: *2161475200
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: childprocess
|
50
|
+
requirement: &2161474780 !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ! '>='
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
type: :runtime
|
57
|
+
prerelease: false
|
58
|
+
version_requirements: *2161474780
|
59
|
+
description:
|
60
|
+
email:
|
61
|
+
- artemave@gmail.com
|
62
|
+
executables:
|
63
|
+
- liquid-proxy
|
64
|
+
extensions: []
|
65
|
+
extra_rdoc_files: []
|
66
|
+
files:
|
67
|
+
- .gitignore
|
68
|
+
- .rspec
|
69
|
+
- .travis.yml
|
70
|
+
- Gemfile
|
71
|
+
- README.markdown
|
72
|
+
- Rakefile
|
73
|
+
- bin/liquid-proxy
|
74
|
+
- features/change_headers.feature
|
75
|
+
- features/step_definition/proxy_steps.rb
|
76
|
+
- features/support/bin/appserver
|
77
|
+
- features/support/env.rb
|
78
|
+
- lib/liquid-proxy.rb
|
79
|
+
- lib/liquid-proxy/api_controller.rb
|
80
|
+
- lib/liquid-proxy/connection.rb
|
81
|
+
- lib/liquid-proxy/connection_processor.rb
|
82
|
+
- lib/liquid-proxy/request_builder.rb
|
83
|
+
- lib/liquid-proxy/server_relay.rb
|
84
|
+
- lib/liquid-proxy/service.rb
|
85
|
+
- lib/liquid-proxy/subprocess.rb
|
86
|
+
- lib/liquid-proxy/version.rb
|
87
|
+
- lib/utils/port_explorer.rb
|
88
|
+
- liquid-proxy.gemspec
|
89
|
+
- spec/api_controller_spec.rb
|
90
|
+
- spec/connection_processor_spec.rb
|
91
|
+
- spec/connection_spec.rb
|
92
|
+
- spec/liquid-proxy_spec.rb
|
93
|
+
- spec/request_builder_spec.rb
|
94
|
+
- spec/server_relay_spec.rb
|
95
|
+
- spec/service_spec.rb
|
96
|
+
- spec/spec_helper.rb
|
97
|
+
- spec/subprocess_spec.rb
|
98
|
+
- spec/support/reset-singleton.rb
|
99
|
+
has_rdoc: true
|
100
|
+
homepage: ''
|
101
|
+
licenses: []
|
102
|
+
post_install_message:
|
103
|
+
rdoc_options: []
|
104
|
+
require_paths:
|
105
|
+
- lib
|
106
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
107
|
+
none: false
|
108
|
+
requirements:
|
109
|
+
- - ! '>='
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
112
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
requirements: []
|
119
|
+
rubyforge_project: liquid-proxy
|
120
|
+
rubygems_version: 1.6.2
|
121
|
+
signing_key:
|
122
|
+
specification_version: 3
|
123
|
+
summary: http proxy with api for modifying requests passing through
|
124
|
+
test_files:
|
125
|
+
- features/change_headers.feature
|
126
|
+
- features/step_definition/proxy_steps.rb
|
127
|
+
- features/support/bin/appserver
|
128
|
+
- features/support/env.rb
|
129
|
+
- spec/api_controller_spec.rb
|
130
|
+
- spec/connection_processor_spec.rb
|
131
|
+
- spec/connection_spec.rb
|
132
|
+
- spec/liquid-proxy_spec.rb
|
133
|
+
- spec/request_builder_spec.rb
|
134
|
+
- spec/server_relay_spec.rb
|
135
|
+
- spec/service_spec.rb
|
136
|
+
- spec/spec_helper.rb
|
137
|
+
- spec/subprocess_spec.rb
|
138
|
+
- spec/support/reset-singleton.rb
|