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.
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