experella-proxy 0.0.6 → 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -11,5 +11,13 @@
11
11
 
12
12
  .rspec
13
13
 
14
+ #rvm
15
+ .ruby-version
16
+ .ruby-gemset
17
+
18
+ #code coverage
19
+ coverage
20
+
21
+
14
22
  Gemfile.lock
15
23
  *.gem
data/README.md CHANGED
@@ -1,6 +1,9 @@
1
1
  #Experella-Proxy
2
2
 
3
- A balancing EventMachine reverse proxy based on [em-proxy](https://github.com/igrigorik/em-proxy)
3
+ [![Gem Version](https://badge.fury.io/rb/experella-proxy.png)](http://badge.fury.io/rb/experella-proxy)
4
+
5
+ A balancing EventMachine reverse proxy based on [em-proxy](https://github.com/igrigorik/em-proxy).
6
+ See our [presentation](http://experteer.github.io/experella-proxy/index.html) for a more detailed overview.
4
7
 
5
8
  Configurable in pure ruby!
6
9
 
@@ -17,7 +20,7 @@ Supports:
17
20
  Proxy uses [http_parser](https://github.com/tmm1/http_parser.rb) to parse http data and is thereby subject to the parsers restrictions
18
21
 
19
22
  The proxy is build for low proxy to server latency and does not support persistent connections to servers. Keep that in mind
20
- as i can severely influence proxy performance overhead.
23
+ as it can severely influence proxy performance overhead.
21
24
 
22
25
  It balances for every single http-request and not per client/connection.
23
26
 
@@ -205,6 +208,18 @@ Override server's run function
205
208
  end
206
209
  ```
207
210
 
211
+ ## Development
212
+
213
+ In the dev folder a development binary is provided which allows execution without installation as gem.
214
+
215
+ The test folder provides simple sinatra servers for testing/debugging which can be run with rake tasks.
216
+
217
+ Additionally you can activate simplecov code coverage analysis for specs by setting COVERAGE=true
218
+
219
+ ```
220
+ $> COVERAGE=true rake spec
221
+ ```
222
+
208
223
  ## Additional Information
209
224
 
210
225
  + [em-proxy](https://github.com/igrigorik/em-proxy)
data/Rakefile CHANGED
@@ -1,3 +1,4 @@
1
+ require 'bundler/gem_tasks'
1
2
  require 'rake'
2
3
  require 'rspec/core/rake_task'
3
4
  require 'yard'
data/bin/experella-proxy CHANGED
@@ -1,5 +1,13 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ if ENV["COVERAGE"]
4
+ #simplecov support for integration specs
5
+ require 'simplecov'
6
+ SimpleCov.start do
7
+ command_name ENV["TESTNAME"] || "integration-test"
8
+ end
9
+ end
10
+
3
11
  lib = File.expand_path(File.join(File.dirname(__FILE__), '../lib/'))
4
12
 
5
13
  require 'fileutils'
@@ -1,5 +1,5 @@
1
1
  #Backend Server
2
- base_backend_port=(ENV["BASE_PORT"] : 4000).to_i
2
+ base_backend_port=(ENV["BASE_PORT"] || 4000).to_i
3
3
  backend1_port=base_backend_port+1
4
4
  backend2_port=base_backend_port+2
5
5
 
@@ -47,7 +47,7 @@ logger.level = Logger::WARN
47
47
  # you can add one pair per call to set_proxy(hsh)
48
48
  # additionally you can activate ssl with provided private_key and cert_chain files
49
49
  set_proxy(:host => "localhost", :port => base_backend_port)
50
- set_proxy(:host => "localhost", :port => 443,
50
+ set_proxy(:host => "localhost", :port => base_backend_port + 443,
51
51
  :options => {:tls => true,
52
52
  :private_key_file => 'ssl/private/experella-proxy.key',
53
53
  :cert_chain_file => 'ssl/certs/experella-proxy.pem'}
data/dev/experella-proxy CHANGED
@@ -7,6 +7,14 @@ Bundler.setup
7
7
 
8
8
  #NOTE: This is exactly the same as /bin/experella-proxy from here
9
9
 
10
+ if ENV["COVERAGE"]
11
+ #simplecov support for integration specs
12
+ require 'simplecov'
13
+ SimpleCov.start do
14
+ command_name ENV["TESTNAME"] || "integration-test"
15
+ end
16
+ end
17
+
10
18
  lib = File.expand_path(File.join(File.dirname(__FILE__), '../lib/'))
11
19
 
12
20
  require 'fileutils'
@@ -28,6 +28,8 @@ Gem::Specification.new do |spec|
28
28
  # documentation tool
29
29
  spec.add_development_dependency "yard", "~> 0.8.7.3"
30
30
  spec.add_development_dependency "redcarpet", "~> 2.3.0"
31
+ #code coverage
32
+ spec.add_development_dependency "simplecov", "~> 0.7.1"
31
33
 
32
34
  spec.files = Dir["bin/*"] + Dir["dev/*"] + Dir["lib/**/*"] + Dir["config/default/**/*"]
33
35
  spec.files += Dir["spec/**/*"] + Dir["test/sinatra/*"]
@@ -27,7 +27,7 @@ module ExperellaProxy
27
27
  #
28
28
  # @param data [String] Opaque response data
29
29
  def receive_data(data)
30
- log.debug [:receive_backend, @name, data]
30
+ log.debug [:receive_backend, @name]
31
31
  @plexer.relay_from_backend(@name, data)
32
32
  end
33
33
 
@@ -2,13 +2,13 @@ module ExperellaProxy
2
2
 
3
3
  # BackendServer objects contain information on available BackendServers
4
4
  #
5
- # Accepts Requests based on Request header information matched to it's message_pattern
5
+ # Accepts Requests based on Request header information and it's message_matcher
6
6
  #
7
7
  # See {#initialize}
8
8
  class BackendServer
9
9
 
10
10
  attr_accessor :host, :port, :concurrency, :workload, :name
11
- attr_reader :message_pattern, :mangle
11
+ attr_reader :message_matcher, :mangle
12
12
 
13
13
  # Constructor of the BackendServer
14
14
  #
@@ -39,8 +39,8 @@ module ExperellaProxy
39
39
  # @param [Hash] options
40
40
  # @option options [String] :name name used in logs and for storage. will use Host:Port if no name is specified
41
41
  # @option options [String] :concurrency concurrency. will use 1 as default
42
- # @option options [Hash|Proc] :accepts message_pattern for request headers this BackendServer accepts or a lambda where the request is passed
43
- # Keys get symbolized and values create Regexp objects. Empty Hash is default
42
+ # @option options [Hash|Proc] :accepts message_pattern that will be converted to a message_matcher or an arbitrary message_matcher as proc
43
+ # Empty Hash is default
44
44
  # @option options [Hash] :mangle Hash which can modify request headers. Keys get symbolized. nil is default
45
45
  def initialize(host, port, options = {})
46
46
  @host = host #host URL as string
@@ -53,7 +53,7 @@ module ExperellaProxy
53
53
  end
54
54
  @workload = 0
55
55
 
56
- update_message_pattern(options[:accepts])
56
+ make_message_matcher(options[:accepts])
57
57
 
58
58
  #mangle can be nil
59
59
  @mangle = options[:mangle]
@@ -61,22 +61,21 @@ module ExperellaProxy
61
61
  @mangle = @mangle.inject({}) { |memo, (k, v)| memo[k.to_sym] = v; memo } unless @mangle.nil?
62
62
  end
63
63
 
64
- # compares Backend servers accepted message_pattern to request object
64
+ # compares Backend servers message_matcher to request object
65
65
  #
66
66
  # @param request [Request] a request object
67
67
  # @return [Boolean] true if BackendServer accepts the Request, false otherwise
68
68
  def accept?(request)
69
- res=@message_pattern.call(request)
69
+ res=@message_matcher.call(request)
70
70
  #puts "#{name} #{request.header['request_url']} #{res}"
71
71
  res
72
72
  end
73
73
 
74
- # Updates the message_pattern
74
+ # Makes a message matching block from the message_pattern.
75
75
  #
76
- # @param hsh [Hash] hash containing additional message_pattern information. Keys get symbolized and values create
77
- # Regexp objects. Values of duplicate keys will be overwritten
78
- def update_message_pattern(obj)
79
- @message_pattern =if obj.respond_to?(:call)
76
+ # @param obj [Hash|Proc] hash containing a message_pattern that will be converted to a message_matcher proc or an arbitrary own message_matcher
77
+ def make_message_matcher(obj)
78
+ @message_matcher =if obj.respond_to?(:call)
80
79
  obj
81
80
  else
82
81
  #precompile message pattern keys to symbols and values to regexp objects
@@ -105,20 +105,10 @@ module ExperellaProxy
105
105
  def connect_backendserver(backend)
106
106
  @backend = backend
107
107
  connection_manager.free_connection(self)
108
- #mangle http header if backend wants to
109
- unless @backend.mangle.nil?
110
- @backend.mangle.each do |k, v|
111
- if v.respond_to?(:call)
112
- get_request.update_header({k => v.call(get_request.header[k])})
113
- else
114
- get_request.update_header({k => v})
115
- end
116
- end
117
- end
118
-
108
+ #mangle http headers
109
+ mangle
119
110
  # reconstruct the request header
120
111
  get_request.reconstruct_header
121
-
122
112
  #special web support for unknown hosts
123
113
  if @backend.name.eql?("web")
124
114
  xport = get_request.header[:Host].match(/:[0-9]+/)
@@ -197,7 +187,7 @@ module ExperellaProxy
197
187
  # @param data [String] opaque response data
198
188
  def relay_from_backend(name, data)
199
189
  log.info msec + 'on_response'.ljust(12) + " @" + @signature.to_s + " from #{name}"
200
- log.debug msec + "#{name.inspect} " + data
190
+ log.debug [msec, "#{name.inspect}", data]
201
191
  @got_response = true
202
192
  data = @on_response.call(name, data) if @on_response
203
193
  get_request.response << data
@@ -449,8 +439,12 @@ module ExperellaProxy
449
439
 
450
440
  # remove all hop-by-hop header fields
451
441
  unless h["Connection"].nil?
452
- h["Connection"].each do |s|
453
- h.delete(s)
442
+ if h["Connection"].is_a?(String)
443
+ h.delete(h["Connection"])
444
+ else
445
+ h["Connection"].each do |s|
446
+ h.delete(s)
447
+ end
454
448
  end
455
449
  end
456
450
  HOP_HEADERS.each do |s|
@@ -514,6 +508,20 @@ module ExperellaProxy
514
508
 
515
509
  end
516
510
 
511
+ # Mangles http headers based on backend specific mangle configuration
512
+ #
513
+ def mangle
514
+ unless @backend.mangle.nil?
515
+ @backend.mangle.each do |k, v|
516
+ if v.respond_to?(:call)
517
+ get_request.update_header({k => v.call(get_request.header[k])})
518
+ else
519
+ get_request.update_header({k => v})
520
+ end
521
+ end
522
+ end
523
+ end
524
+
517
525
  # This method sends the first requests send_buffer to the backend server, if
518
526
  # any backend is set and there is request data to dispatch
519
527
  #
@@ -53,7 +53,6 @@ module ExperellaProxy
53
53
  #
54
54
  def self.stop
55
55
  if EM.reactor_running?
56
- log.info("Terminating experella-proxy")
57
56
  EventMachine::stop_event_loop
58
57
  end
59
58
  end
@@ -18,6 +18,8 @@ module ExperellaProxy
18
18
  @conn = request.conn
19
19
  @header = {}
20
20
  @status_code = 500
21
+ @no_length = false #set true if no content-length or transfer-encoding given
22
+ @keep_parsing = true #used for special no length case
21
23
  @chunked = false # if true the parsed body will be chunked
22
24
  @buffer = false # default is false, so incoming data will be streamed,
23
25
  # used for http1.0 clients and transfer-encoding chunked backend responses
@@ -35,7 +37,15 @@ module ExperellaProxy
35
37
  # @param str [String] data as string
36
38
  def <<(str)
37
39
  begin
38
- @response_parser << str
40
+ if @keep_parsing
41
+ offset = @response_parser << str
42
+
43
+ #edge case for message without content-length and transfer encoding
44
+ @conn.send_data str[offset..-1] unless @keep_parsing
45
+ else
46
+ @conn.send_data str
47
+ end
48
+
39
49
  rescue Http::Parser::Error
40
50
  log.warn ["Parser error caused by invalid response data", "@#{@conn.signature}"]
41
51
  # on error unbind response_parser object, so additional data doesn't get parsed anymore
@@ -128,6 +138,7 @@ module ExperellaProxy
128
138
  # if no transfer-encoding and no content-length is present, set Connection: close
129
139
  if h["Content-Length"].nil?
130
140
  @request.keep_alive = false
141
+ @no_length = true
131
142
  @header[:Connection] = "close"
132
143
  end
133
144
  #chunked encoded
@@ -144,8 +155,12 @@ module ExperellaProxy
144
155
 
145
156
  # remove all hop-by-hop header fields
146
157
  unless h["Connection"].nil?
147
- h["Connection"].each do |s|
148
- h.delete(s)
158
+ if h["Connection"].is_a?(String)
159
+ h.delete(h["Connection"])
160
+ else
161
+ h["Connection"].each do |s|
162
+ h.delete(s)
163
+ end
149
164
  end
150
165
  end
151
166
  HOP_HEADERS.each do |s|
@@ -161,7 +176,6 @@ module ExperellaProxy
161
176
  end
162
177
  @header[:Via] = via
163
178
 
164
-
165
179
  update_header(h)
166
180
  unless @buffer
167
181
  # called before any data is put into send_buffer
@@ -197,8 +211,11 @@ module ExperellaProxy
197
211
  @send_buffer << body
198
212
  @conn.send_data flush
199
213
  end
200
- end
201
214
 
215
+ if @no_length
216
+ @keep_parsing = false
217
+ end
218
+ end
202
219
  end
203
220
  end
204
221
  end
@@ -1,5 +1,15 @@
1
1
  module ExperellaProxy
2
2
  # ExperellaProxy Gemversion
3
+ # 0.0.9
4
+ # * added simplecov code coverage support.
5
+ # * removed an unescaped duplicate debug log output
6
+ # 0.0.8
7
+ # * fixed a parsing bug where no data was send when backend used connection close to indicate message end
8
+ # 0.0.7
9
+ # * fixed minor issues with ruby 2.0
10
+ # * fixed a typo in default config
11
+ # * refactored mangling in own method
12
+ # * refactored message pattern and matching
3
13
  # 0.0.6
4
14
  # * updated homepage
5
15
  # 0.0.5
@@ -11,5 +21,5 @@ module ExperellaProxy
11
21
  # * added self-signed SSL certificate for TLS/HTTPS
12
22
  # * added config template init functionality
13
23
  #
14
- VERSION = "0.0.6"
24
+ VERSION = "0.0.9"
15
25
  end
@@ -4,15 +4,15 @@ describe ExperellaProxy::BackendServer do
4
4
 
5
5
  let(:backend) {
6
6
  ExperellaProxy::BackendServer.new("host", "port", {:concurrency => "2", :name => "name",
7
- :accepts => {"Host" => "experella"},
7
+ :accepts => {"Host" => "experella", :path => "ella"},
8
8
  :mangle => {"Connection" => "close"}
9
9
  })
10
10
  }
11
11
  let(:min_backend) {
12
12
  ExperellaProxy::BackendServer.new("host", "port")
13
13
  }
14
- let(:pattern) {
15
- backend.message_pattern
14
+ let(:matcher) {
15
+ backend.message_matcher
16
16
  }
17
17
 
18
18
  describe "#new" do
@@ -39,9 +39,9 @@ describe ExperellaProxy::BackendServer do
39
39
  min_backend.concurrency.should eql 1
40
40
  end
41
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
42
+ it "has a message matcher" do
43
+ backend.message_matcher.should_not be_nil
44
+ min_backend.message_matcher.should_not be_nil
45
45
  end
46
46
 
47
47
  it "mangle should be nil" do
@@ -54,45 +54,53 @@ describe ExperellaProxy::BackendServer do
54
54
  end
55
55
  end
56
56
 
57
- shared_examples "the message pattern" do
57
+ shared_examples "the message matcher" do
58
58
 
59
59
  it "returns a proc" do
60
- pattern.should be_an_instance_of Proc
60
+ matcher.should be_an_instance_of Proc
61
61
  end
62
62
 
63
63
  end
64
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
65
  describe "#accept?" do
79
66
 
80
67
 
81
- it "returns false if request header doesn't have all keys of message_pattern" do
68
+ it "returns false if desired request header missing" do
82
69
  request = ExperellaProxy::Request.new("")
83
70
  request.update_header(:request_url => "/docs")
84
71
  backend.accept?(request).should be_false
85
72
  end
86
73
 
87
- it "returns false if request headers don't match full message_pattern" do
74
+
75
+ it "returns false if request headers doesn't match the message_matcher" do
88
76
  request = ExperellaProxy::Request.new("")
89
- request.update_header(:Host => "google.com", :request_url => "/docs")
77
+ request.update_header(:Host => "experella.com", :request_url => "/docs")
78
+ backend.accept?(request).should be_false
79
+ request.uri.update(:path => "godzilla")
90
80
  backend.accept?(request).should be_false
91
81
  end
92
82
 
93
- it "returns true if full message pattern finds matches in request headers" do
83
+ it "accepts a block as message matcher" do
84
+ lambdaBackend = ExperellaProxy::BackendServer.new("host", "port", {:concurrency => "2", :name => "name",
85
+ :accepts => lambda { |req|
86
+ if req.header[:Host] == "google.com"
87
+ true
88
+ else
89
+ false
90
+ end
91
+ }
92
+ })
94
93
  request = ExperellaProxy::Request.new("")
94
+ request.update_header(:Host => "google.com", :request_url => "/docs")
95
+ lambdaBackend.accept?(request).should be_true
95
96
  request.update_header(:Host => "experella.com", :request_url => "/docs")
97
+ lambdaBackend.accept?(request).should be_false
98
+ end
99
+
100
+ it "returns true if full message_matcher finds matches in request headers" do
101
+ request = ExperellaProxy::Request.new("")
102
+ request.update_header(:Host => "experella.com")
103
+ request.uri.update(:path => "experella")
96
104
  backend.accept?(request).should be_true
97
105
  end
98
106
 
@@ -5,10 +5,6 @@ describe ExperellaProxy::Configuration do
5
5
  ExperellaProxy.config
6
6
  }
7
7
 
8
- let(:no_config){
9
- ExperellaProxy::Configuration.new(:configfile => "")
10
- }
11
-
12
8
  it "should load a config file" do
13
9
  config.backends.size.should == 4
14
10
  config.timeout.should == 6.0
@@ -16,7 +16,20 @@ describe ExperellaProxy do
16
16
  File.expand_path("../../../bin/experella-proxy", __FILE__)
17
17
  }
18
18
 
19
-
19
+ # static testnames send to spawned experella for simplecov
20
+ ENV_TESTNAMES = {
21
+ "should get response from the echoserver via the proxy" => "response",
22
+ "should respond with 404" => "404",
23
+ "should respond with 400 on malformed request" => "400",
24
+ "should respond with 503" => "503",
25
+ "should reuse keep-alive connections" => "keep-alive",
26
+ "should handle chunked post requests and strip invalid Content-Length" => "chunked-request",
27
+ "should rechunk and stream Transfer-Encoding chunked responses" => "chunked-response",
28
+ "should timeout inactive connections after config.timeout" => "timeout",
29
+ "should handle pipelined requests correctly" => "pipelined",
30
+ "should accept requests on all set proxy domains" => "multiproxy",
31
+ "should be able to handle post requests" => "post"
32
+ }
20
33
 
21
34
  describe "EchoServer" do
22
35
  before :each do
@@ -46,7 +59,10 @@ describe ExperellaProxy do
46
59
  end
47
60
 
48
61
  describe "Proxy" do
49
- before :each do
62
+ before :each do |test|
63
+ if ENV["COVERAGE"]
64
+ ENV["TESTNAME"] = ENV_TESTNAMES[test.example.description]
65
+ end
50
66
  @pid = spawn("ruby", "#{echo_server}", "127.0.0.10", "7654")
51
67
  @pid2 = spawn("#{experella_proxy}", "run", "--", "--config=#{File.join(File.dirname(__FILE__),"/../fixtures/test_config.rb")}")
52
68
  sleep(0.8) #let the server startup, specs may fail if this is set to low
@@ -335,6 +351,7 @@ describe ExperellaProxy do
335
351
  req1 = conn.get({:connect_timeout => 1, :inactivity_timeout => config.timeout + 5,
336
352
  :keepalive => true, :head => {"Host" => "experella.com"}})
337
353
  req1.errback {
354
+ #this shouldnt happen, but when it does it should at least be because of a timeout
338
355
  time = Time.now - time
339
356
  time.should >= config.timeout
340
357
  time.should < config.timeout + 5
@@ -14,7 +14,7 @@ backend( :name => "exp proxy", :host => "127.0.0.11", :port => "7655", :concurre
14
14
  :accepts => {"Host" => "experella", "request_url" => "/(#{request_part})($|/)"}
15
15
  )
16
16
 
17
- backend( :name => "web", :host => "0.0.0.0", :port => "80", :concurrency => "1000",
17
+ backend( :name => "web", :host_port => "0.0.0.0:80", :concurrency => "1000",
18
18
  :accepts => {"Host" => "^((?!(experella|127)).)*$"}
19
19
  )
20
20
 
data/spec/spec_helper.rb CHANGED
@@ -8,6 +8,15 @@ require 'rubygems'
8
8
  require 'bundler'
9
9
  Bundler.setup
10
10
 
11
+ if ENV["COVERAGE"]
12
+ require "simplecov"
13
+ SimpleCov.start do
14
+ add_group "Proxy", "lib"
15
+ add_group "Specs", "spec"
16
+ end
17
+ end
18
+
19
+
11
20
  require 'pathname'
12
21
 
13
22
  LIB_ROOT= Pathname.new(File.dirname(__FILE__) +"/../")
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: experella-proxy
3
3
  version: !ruby/object:Gem::Version
4
- hash: 19
4
+ hash: 13
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 6
10
- version: 0.0.6
9
+ - 9
10
+ version: 0.0.9
11
11
  platform: ruby
12
12
  authors:
13
13
  - Dennis-Florian Herr
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2014-02-12 00:00:00 +01:00
18
+ date: 2014-04-07 00:00:00 +02:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -191,6 +191,22 @@ dependencies:
191
191
  version: 2.3.0
192
192
  type: :development
193
193
  version_requirements: *id011
194
+ - !ruby/object:Gem::Dependency
195
+ name: simplecov
196
+ prerelease: false
197
+ requirement: &id012 !ruby/object:Gem::Requirement
198
+ none: false
199
+ requirements:
200
+ - - ~>
201
+ - !ruby/object:Gem::Version
202
+ hash: 1
203
+ segments:
204
+ - 0
205
+ - 7
206
+ - 1
207
+ version: 0.7.1
208
+ type: :development
209
+ version_requirements: *id012
194
210
  description: a balancing & routing proxy, see README for more details
195
211
  email:
196
212
  - dennis.herr@experteer.com