experella-proxy 0.0.6
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/.gitignore +15 -0
- data/Gemfile +3 -0
- data/README.md +219 -0
- data/Rakefile +25 -0
- data/TODO.txt +20 -0
- data/bin/experella-proxy +54 -0
- data/config/default/404.html +16 -0
- data/config/default/503.html +18 -0
- data/config/default/config.rb +64 -0
- data/config/default/ssl/certs/experella-proxy.pem +18 -0
- data/config/default/ssl/private/experella-proxy.key +28 -0
- data/dev/experella-proxy +62 -0
- data/experella-proxy.gemspec +39 -0
- data/lib/experella-proxy/backend.rb +58 -0
- data/lib/experella-proxy/backend_server.rb +100 -0
- data/lib/experella-proxy/configuration.rb +154 -0
- data/lib/experella-proxy/connection.rb +557 -0
- data/lib/experella-proxy/connection_manager.rb +167 -0
- data/lib/experella-proxy/globals.rb +37 -0
- data/lib/experella-proxy/http_status_codes.rb +45 -0
- data/lib/experella-proxy/proxy.rb +61 -0
- data/lib/experella-proxy/request.rb +106 -0
- data/lib/experella-proxy/response.rb +204 -0
- data/lib/experella-proxy/server.rb +68 -0
- data/lib/experella-proxy/version.rb +15 -0
- data/lib/experella-proxy.rb +93 -0
- data/spec/echo-server/echo_server.rb +49 -0
- data/spec/experella-proxy/backend_server_spec.rb +101 -0
- data/spec/experella-proxy/configuration_spec.rb +27 -0
- data/spec/experella-proxy/connection_manager_spec.rb +159 -0
- data/spec/experella-proxy/experella-proxy_spec.rb +471 -0
- data/spec/experella-proxy/request_spec.rb +88 -0
- data/spec/experella-proxy/response_spec.rb +44 -0
- data/spec/fixtures/404.html +16 -0
- data/spec/fixtures/503.html +18 -0
- data/spec/fixtures/spec.log +331 -0
- data/spec/fixtures/test_config.rb +34 -0
- data/spec/spec.log +235 -0
- data/spec/spec_helper.rb +35 -0
- data/test/sinatra/hello_world_server.rb +17 -0
- data/test/sinatra/server_one.rb +89 -0
- data/test/sinatra/server_two.rb +89 -0
- metadata +296 -0
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'eventmachine'
|
3
|
+
require 'http_parser.rb'
|
4
|
+
require 'experella-proxy/globals'
|
5
|
+
require 'experella-proxy/http_status_codes'
|
6
|
+
require 'experella-proxy/configuration'
|
7
|
+
require 'experella-proxy/server'
|
8
|
+
require 'experella-proxy/connection_manager'
|
9
|
+
require 'experella-proxy/backend_server'
|
10
|
+
require 'experella-proxy/connection'
|
11
|
+
require 'experella-proxy/proxy'
|
12
|
+
require 'experella-proxy/backend'
|
13
|
+
require 'experella-proxy/request'
|
14
|
+
require 'experella-proxy/response'
|
15
|
+
|
16
|
+
# Namespace Module for the Proxy Server
|
17
|
+
#
|
18
|
+
# Startup code and options parser is defined here
|
19
|
+
#
|
20
|
+
# @example startup
|
21
|
+
# ARGV << '--help' if ARGV.empty?
|
22
|
+
#
|
23
|
+
# options = {}
|
24
|
+
# OptionParser.new do |opt|
|
25
|
+
# opt.banner = "Usage: experella-proxy <command> <options> -- <application options>\n\n"
|
26
|
+
# opt.banner << "where <applicaion options> are"
|
27
|
+
#
|
28
|
+
# opt.on("-c", "--config=CONFIGFILE", "start server with config in given filepath") do |v|
|
29
|
+
# options[:configfile] = File.expand_path(v)
|
30
|
+
# ExperellaProxy.run(options)
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# end.parse!
|
34
|
+
#
|
35
|
+
# @see file:README.md README
|
36
|
+
# @author Dennis-Florian Herr 2014 @Experteer GmbH
|
37
|
+
module ExperellaProxy
|
38
|
+
|
39
|
+
# Initializes ExperellaProxy's {Configuration} and {ConnectionManager}
|
40
|
+
#
|
41
|
+
# @param [Hash] options Hash passed to the configuration
|
42
|
+
# @option option [String] :configfile the config filepath
|
43
|
+
# @return [Boolean] true if successful false if NoConfigError was raised
|
44
|
+
def self.init(options={})
|
45
|
+
begin
|
46
|
+
Configuration.new(options)
|
47
|
+
rescue Configuration::NoConfigError => e
|
48
|
+
puts e.message
|
49
|
+
puts e.backtrace.join("\n\t")
|
50
|
+
return false
|
51
|
+
end
|
52
|
+
@connection_manager = ConnectionManager.new
|
53
|
+
true
|
54
|
+
end
|
55
|
+
|
56
|
+
# Creates Server object and calls {Server#run} if {ExperellaProxy#init}
|
57
|
+
#
|
58
|
+
# @param [Hash] options Hash passed to the configuration
|
59
|
+
# @option option [String] :configfile the config filepath
|
60
|
+
def self.run(options={})
|
61
|
+
@server = Server.new(options).run if ExperellaProxy.init(options)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Fresh restarts ExperellaProxy with same options.
|
65
|
+
#
|
66
|
+
# Loses all buffered data
|
67
|
+
def self.restart
|
68
|
+
opts = @server.options
|
69
|
+
self.stop
|
70
|
+
Server.new(opts).run if ExperellaProxy.init(opts)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Stops ExperellaProxy
|
74
|
+
#
|
75
|
+
def self.stop
|
76
|
+
Proxy.stop
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
#startup
|
82
|
+
ARGV << '--help' if ARGV.empty?
|
83
|
+
|
84
|
+
options = {}
|
85
|
+
OptionParser.new do |opt|
|
86
|
+
opt.banner = "Usage: experella-proxy <command> <options> -- <application options>\n\n"
|
87
|
+
opt.banner << "where <applicaion options> are"
|
88
|
+
|
89
|
+
opt.on("-c", "--config=CONFIGFILE", "start server with config in given filepath") do |v|
|
90
|
+
options[:configfile] = File.expand_path(v)
|
91
|
+
ExperellaProxy.run(options)
|
92
|
+
end
|
93
|
+
end.parse!
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
require 'http_parser.rb'
|
3
|
+
module EchoServer
|
4
|
+
|
5
|
+
def post_init
|
6
|
+
@parser = Http::Parser.new
|
7
|
+
@buffer = String.new
|
8
|
+
|
9
|
+
@parser.on_headers_complete = proc do |h|
|
10
|
+
if @parser.request_path == "/chunked"
|
11
|
+
@chunked = true
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
@parser.on_message_complete = proc do
|
16
|
+
if @chunked
|
17
|
+
answer = "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\nConnection: close\r\n\r\n"
|
18
|
+
answer << "9\r\nchunk one\r\n"
|
19
|
+
answer << "a\r\n chunk two\r\n"
|
20
|
+
answer << "d\r\n chunk three \r\n"
|
21
|
+
answer << "b\r\nchunk four \r\n"
|
22
|
+
answer << "15\r\nchunk 123456789abcdef\r\n0\r\n\r\n"
|
23
|
+
else
|
24
|
+
@buffer = "you sent: " + @buffer.dump
|
25
|
+
answer = "HTTP/1.1 200 OK\r\nContent-Length: #{@buffer.length}\r\nConnection: close\r\n\r\n"
|
26
|
+
answer << @buffer
|
27
|
+
end
|
28
|
+
send_data answer
|
29
|
+
close_connection_after_writing
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def receive_data data
|
34
|
+
@buffer << data
|
35
|
+
@parser << data
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
EventMachine.run do
|
41
|
+
trap("QUIT") { EM.stop }
|
42
|
+
|
43
|
+
if ARGV.count == 2
|
44
|
+
EventMachine.start_server ARGV.first, ARGV.last.to_i, EchoServer
|
45
|
+
else
|
46
|
+
raise "invalid number of params, expected [server] [port]"
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ExperellaProxy::BackendServer do
|
4
|
+
|
5
|
+
let(:backend) {
|
6
|
+
ExperellaProxy::BackendServer.new("host", "port", {:concurrency => "2", :name => "name",
|
7
|
+
:accepts => {"Host" => "experella"},
|
8
|
+
:mangle => {"Connection" => "close"}
|
9
|
+
})
|
10
|
+
}
|
11
|
+
let(:min_backend) {
|
12
|
+
ExperellaProxy::BackendServer.new("host", "port")
|
13
|
+
}
|
14
|
+
let(:pattern) {
|
15
|
+
backend.message_pattern
|
16
|
+
}
|
17
|
+
|
18
|
+
describe "#new" do
|
19
|
+
|
20
|
+
it "returns a ExperellaProxy::BackendServer" do
|
21
|
+
backend.should be_an_instance_of ExperellaProxy::BackendServer
|
22
|
+
end
|
23
|
+
|
24
|
+
it "has a host" do
|
25
|
+
backend.host.should eql "host"
|
26
|
+
end
|
27
|
+
|
28
|
+
it "has a port" do
|
29
|
+
backend.port.should eql "port"
|
30
|
+
end
|
31
|
+
|
32
|
+
it "has a name" do
|
33
|
+
backend.name.should eql "name"
|
34
|
+
min_backend.name.should eql "host:port"
|
35
|
+
end
|
36
|
+
|
37
|
+
it "has a concurrency" do
|
38
|
+
backend.concurrency.should eql 2
|
39
|
+
min_backend.concurrency.should eql 1
|
40
|
+
end
|
41
|
+
|
42
|
+
it "has a message pattern" do
|
43
|
+
backend.message_pattern.should_not be_nil
|
44
|
+
min_backend.message_pattern.should_not be_nil
|
45
|
+
end
|
46
|
+
|
47
|
+
it "mangle should be nil" do
|
48
|
+
backend.mangle.should == {:Connection => "close"}
|
49
|
+
min_backend.mangle.should be_nil
|
50
|
+
end
|
51
|
+
|
52
|
+
it "has workload zero" do
|
53
|
+
backend.workload.should eql 0
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
shared_examples "the message pattern" do
|
58
|
+
|
59
|
+
it "returns a proc" do
|
60
|
+
pattern.should be_an_instance_of Proc
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
describe "#update_message_pattern" do
|
67
|
+
|
68
|
+
#it "adds the hash parameter to the message_pattern and overwrites values of duplicate keys" do
|
69
|
+
# backend.update_message_pattern({"Host" => "experella.de", "Connection" => "keep-alive"})
|
70
|
+
# pattern = backend.message_pattern
|
71
|
+
# pattern[:Host].should eql Regexp.new("experella.de")
|
72
|
+
# pattern[:Connection].should eql Regexp.new("keep-alive")
|
73
|
+
#end
|
74
|
+
|
75
|
+
it_should_behave_like "the message pattern"
|
76
|
+
end
|
77
|
+
|
78
|
+
describe "#accept?" do
|
79
|
+
|
80
|
+
|
81
|
+
it "returns false if request header doesn't have all keys of message_pattern" do
|
82
|
+
request = ExperellaProxy::Request.new("")
|
83
|
+
request.update_header(:request_url => "/docs")
|
84
|
+
backend.accept?(request).should be_false
|
85
|
+
end
|
86
|
+
|
87
|
+
it "returns false if request headers don't match full message_pattern" do
|
88
|
+
request = ExperellaProxy::Request.new("")
|
89
|
+
request.update_header(:Host => "google.com", :request_url => "/docs")
|
90
|
+
backend.accept?(request).should be_false
|
91
|
+
end
|
92
|
+
|
93
|
+
it "returns true if full message pattern finds matches in request headers" do
|
94
|
+
request = ExperellaProxy::Request.new("")
|
95
|
+
request.update_header(:Host => "experella.com", :request_url => "/docs")
|
96
|
+
backend.accept?(request).should be_true
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ExperellaProxy::Configuration do
|
4
|
+
let(:config) {
|
5
|
+
ExperellaProxy.config
|
6
|
+
}
|
7
|
+
|
8
|
+
let(:no_config){
|
9
|
+
ExperellaProxy::Configuration.new(:configfile => "")
|
10
|
+
}
|
11
|
+
|
12
|
+
it "should load a config file" do
|
13
|
+
config.backends.size.should == 4
|
14
|
+
config.timeout.should == 6.0
|
15
|
+
config.proxy.size.should == 2
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should load error pages" do
|
19
|
+
config.error_pages[404].empty?.should be_false
|
20
|
+
config.error_pages[503].empty?.should be_false
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should raise NoConfigError if config filepath doesn't exist" do
|
24
|
+
lambda { config.read_config_file("/a/non/existing/filepath")
|
25
|
+
}.should raise_error(ExperellaProxy::Configuration::NoConfigError)
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ExperellaProxy::ConnectionManager do
|
4
|
+
|
5
|
+
let(:manager) {
|
6
|
+
ExperellaProxy::ConnectionManager.new
|
7
|
+
}
|
8
|
+
|
9
|
+
class Testconnection
|
10
|
+
|
11
|
+
def initialize()
|
12
|
+
@request = ExperellaProxy::Request.new(self)
|
13
|
+
end
|
14
|
+
|
15
|
+
def get_request
|
16
|
+
@request
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "#backend_count" do
|
21
|
+
it "returns size of the backend_list" do
|
22
|
+
manager.backend_count.should == 0
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "#backend_queue_count" do
|
27
|
+
it "returns size of the backend_queue" do
|
28
|
+
manager.backend_queue_count.should == 0
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "#add_backend" do
|
33
|
+
it "should add a BackendServer to list and queue and return true" do
|
34
|
+
ret = manager.add_backend(ExperellaProxy::BackendServer.new("host", "port"))
|
35
|
+
manager.backend_count.should == 1
|
36
|
+
manager.backend_queue_count.should == 1
|
37
|
+
ret.should == true
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should add a BackendServer to list and return conn if a queued conn matches" do
|
41
|
+
manager.add_backend(ExperellaProxy::BackendServer.new("host", "port"))
|
42
|
+
manager.backend_count.should == 1
|
43
|
+
manager.backend_queue_count.should == 1
|
44
|
+
manager.backend_available?(Testconnection.new().get_request)
|
45
|
+
manager.backend_available?(Testconnection.new().get_request)
|
46
|
+
manager.backend_count.should == 1
|
47
|
+
manager.backend_queue_count.should == 0
|
48
|
+
manager.connection_count.should == 1
|
49
|
+
ret = manager.add_backend(ExperellaProxy::BackendServer.new("hostx", "port"))
|
50
|
+
manager.backend_count.should == 2
|
51
|
+
manager.backend_queue_count.should == 0
|
52
|
+
ret.should be_an_instance_of Testconnection
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "#remove_backend" do
|
58
|
+
it "should return true if a backend was removed" do
|
59
|
+
backend = ExperellaProxy::BackendServer.new("host", "port")
|
60
|
+
manager.add_backend(backend)
|
61
|
+
manager.backend_count.should == 1
|
62
|
+
manager.backend_queue_count.should == 1
|
63
|
+
ret = manager.remove_backend(backend)
|
64
|
+
ret.should be_true
|
65
|
+
manager.backend_count.should == 0
|
66
|
+
manager.backend_queue_count.should == 0
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should return false if no backend was removed" do
|
70
|
+
backend = ExperellaProxy::BackendServer.new("host", "port")
|
71
|
+
manager.remove_backend(backend).should be_false
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe "#free_backend" do
|
76
|
+
it "should return a connection if any queued conn matches" do
|
77
|
+
backend = ExperellaProxy::BackendServer.new("host", "port")
|
78
|
+
manager.add_backend(backend)
|
79
|
+
manager.backend_available?(Testconnection.new().get_request)
|
80
|
+
manager.backend_available?(Testconnection.new().get_request)
|
81
|
+
ret = manager.free_backend(backend)
|
82
|
+
ret.should be_an_instance_of Testconnection
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should return nil and requeue backend if no conn matches" do
|
86
|
+
backend = ExperellaProxy::BackendServer.new("host", "port")
|
87
|
+
manager.add_backend(backend)
|
88
|
+
manager.backend_available?(Testconnection.new().get_request)
|
89
|
+
backend_queue = manager.backend_queue_count
|
90
|
+
workload = backend.workload
|
91
|
+
ret = manager.free_backend(backend)
|
92
|
+
ret.should == nil
|
93
|
+
manager.backend_queue_count.should == (backend_queue + 1)
|
94
|
+
backend.workload.should == (workload - 1)
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
describe "#free_connection" do
|
100
|
+
it "should remove connection from queue" do
|
101
|
+
backend = ExperellaProxy::BackendServer.new("host", "port")
|
102
|
+
manager.add_backend(backend)
|
103
|
+
connections = []
|
104
|
+
connections[1] = Testconnection.new()
|
105
|
+
connections[2] = Testconnection.new()
|
106
|
+
manager.backend_available?(connections[1].get_request)
|
107
|
+
manager.backend_available?(connections[2].get_request)
|
108
|
+
count_before = manager.connection_count
|
109
|
+
manager.free_connection(connections[2])
|
110
|
+
manager.connection_count.should == (count_before - 1)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
describe "#backend_available?" do
|
115
|
+
before(:each) do
|
116
|
+
manager.add_backend(ExperellaProxy::BackendServer.new("host", "port", {:concurrency => 3, :accepts => {"Host" => "test"}}))
|
117
|
+
manager.add_backend(ExperellaProxy::BackendServer.new("host2", "port", {:accepts => {"Host" => "bla"}}))
|
118
|
+
manager.add_backend(ExperellaProxy::BackendServer.new("host3", "port", {:accepts => {"Host" => "blax"}}))
|
119
|
+
end
|
120
|
+
it "returns first matching backend and removes it from queue" do
|
121
|
+
conn = Testconnection.new
|
122
|
+
conn.get_request.update_header("Host" => "loremblaxipsum")
|
123
|
+
before_count = manager.backend_queue_count
|
124
|
+
ret = manager.backend_available?(conn.get_request)
|
125
|
+
ret.name.should == "host2:port"
|
126
|
+
manager.backend_queue_count.should == (before_count - 1)
|
127
|
+
end
|
128
|
+
|
129
|
+
it "should requeue matching backend if concurrency is smaller than workload" do
|
130
|
+
conn = Testconnection.new
|
131
|
+
conn.get_request.update_header("Host" => "loremtestipsum")
|
132
|
+
before_count = manager.backend_queue_count
|
133
|
+
ret = manager.backend_available?(conn.get_request)
|
134
|
+
ret.name.should == "host:port"
|
135
|
+
manager.backend_queue_count.should == before_count
|
136
|
+
end
|
137
|
+
|
138
|
+
it "returns :queued if no matching backend is currently available" do
|
139
|
+
conn = Testconnection.new
|
140
|
+
conn.get_request.update_header("Host" => "loremblaipsum")
|
141
|
+
manager.backend_available?(conn.get_request)
|
142
|
+
before_count = manager.backend_queue_count
|
143
|
+
ret = manager.backend_available?(conn.get_request)
|
144
|
+
ret.should == :queued
|
145
|
+
manager.backend_queue_count.should == before_count
|
146
|
+
end
|
147
|
+
|
148
|
+
it "returns false if no registered backend matches" do
|
149
|
+
conn = Testconnection.new
|
150
|
+
conn.get_request.update_header("Host" => "loremipsum")
|
151
|
+
before_count = manager.backend_queue_count
|
152
|
+
ret = manager.backend_available?(conn.get_request)
|
153
|
+
ret.should be_false
|
154
|
+
manager.backend_queue_count.should == before_count
|
155
|
+
end
|
156
|
+
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|