experella-proxy 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/.gitignore +15 -0
  2. data/Gemfile +3 -0
  3. data/README.md +219 -0
  4. data/Rakefile +25 -0
  5. data/TODO.txt +20 -0
  6. data/bin/experella-proxy +54 -0
  7. data/config/default/404.html +16 -0
  8. data/config/default/503.html +18 -0
  9. data/config/default/config.rb +64 -0
  10. data/config/default/ssl/certs/experella-proxy.pem +18 -0
  11. data/config/default/ssl/private/experella-proxy.key +28 -0
  12. data/dev/experella-proxy +62 -0
  13. data/experella-proxy.gemspec +39 -0
  14. data/lib/experella-proxy/backend.rb +58 -0
  15. data/lib/experella-proxy/backend_server.rb +100 -0
  16. data/lib/experella-proxy/configuration.rb +154 -0
  17. data/lib/experella-proxy/connection.rb +557 -0
  18. data/lib/experella-proxy/connection_manager.rb +167 -0
  19. data/lib/experella-proxy/globals.rb +37 -0
  20. data/lib/experella-proxy/http_status_codes.rb +45 -0
  21. data/lib/experella-proxy/proxy.rb +61 -0
  22. data/lib/experella-proxy/request.rb +106 -0
  23. data/lib/experella-proxy/response.rb +204 -0
  24. data/lib/experella-proxy/server.rb +68 -0
  25. data/lib/experella-proxy/version.rb +15 -0
  26. data/lib/experella-proxy.rb +93 -0
  27. data/spec/echo-server/echo_server.rb +49 -0
  28. data/spec/experella-proxy/backend_server_spec.rb +101 -0
  29. data/spec/experella-proxy/configuration_spec.rb +27 -0
  30. data/spec/experella-proxy/connection_manager_spec.rb +159 -0
  31. data/spec/experella-proxy/experella-proxy_spec.rb +471 -0
  32. data/spec/experella-proxy/request_spec.rb +88 -0
  33. data/spec/experella-proxy/response_spec.rb +44 -0
  34. data/spec/fixtures/404.html +16 -0
  35. data/spec/fixtures/503.html +18 -0
  36. data/spec/fixtures/spec.log +331 -0
  37. data/spec/fixtures/test_config.rb +34 -0
  38. data/spec/spec.log +235 -0
  39. data/spec/spec_helper.rb +35 -0
  40. data/test/sinatra/hello_world_server.rb +17 -0
  41. data/test/sinatra/server_one.rb +89 -0
  42. data/test/sinatra/server_two.rb +89 -0
  43. 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