puffing-billy 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- puffing-billy (0.1.1)
4
+ puffing-billy (0.1.3)
5
5
  capybara
6
6
  em-http-request
7
7
  eventmachine
@@ -1,5 +1,6 @@
1
1
  require "billy/version"
2
2
  require "billy/config"
3
3
  require "billy/proxy_request_stub"
4
+ require "billy/cache"
4
5
  require "billy/proxy"
5
6
  require "billy/proxy_connection"
@@ -0,0 +1,39 @@
1
+ require 'resolv'
2
+ require 'uri'
3
+
4
+ module Billy
5
+ class Cache
6
+ def initialize
7
+ reset
8
+ end
9
+
10
+ def cacheable?(url, headers)
11
+ if Billy.config.cache
12
+ host = URI(url).host
13
+ Billy.log(:info, Billy.config.whitelist)
14
+ !Billy.config.whitelist.include?(host)
15
+ # TODO test headers for cacheability
16
+ end
17
+ end
18
+
19
+ def cached?(url)
20
+ !@cache[url].nil?
21
+ end
22
+
23
+ def fetch(url)
24
+ @cache[url]
25
+ end
26
+
27
+ def store(url, status, headers, content)
28
+ @cache[url] = {
29
+ :status => status,
30
+ :headers => headers,
31
+ :content => content
32
+ }
33
+ end
34
+
35
+ def reset
36
+ @cache = {}
37
+ end
38
+ end
39
+ end
@@ -2,10 +2,14 @@ require 'logger'
2
2
 
3
3
  module Billy
4
4
  class Config
5
- attr_accessor :logger
5
+ DEFAULT_WHITELIST = ['127.0.0.1', 'localhost']
6
+
7
+ attr_accessor :logger, :cache, :whitelist
6
8
 
7
9
  def initialize
8
- @logger = defined?(Rails) ? Rails.logger : Logger.new(STDOUT)
10
+ @logger = defined?(Rails) ? Rails.logger : Logger.new(STDOUT)
11
+ @cache = true
12
+ @whitelist = DEFAULT_WHITELIST
9
13
  end
10
14
  end
11
15
 
@@ -6,6 +6,7 @@ module Billy
6
6
  class Proxy
7
7
  def initialize
8
8
  reset
9
+ @cache = Billy::Cache.new
9
10
  end
10
11
 
11
12
  def start(threaded = true)
@@ -48,6 +49,10 @@ module Billy
48
49
  @stubs = []
49
50
  end
50
51
 
52
+ def reset_cache
53
+ @cache.reset
54
+ end
55
+
51
56
  protected
52
57
 
53
58
  def find_stub(method, url)
@@ -66,6 +71,7 @@ module Billy
66
71
 
67
72
  @signature = EM.start_server('127.0.0.1', 0, ProxyConnection) do |p|
68
73
  p.handler = self
74
+ p.cache = @cache
69
75
  end
70
76
 
71
77
  Billy.log(:info, "Proxy listening on #{url}")
@@ -1,3 +1,4 @@
1
+ require 'uri'
1
2
  require 'eventmachine'
2
3
  require 'http/parser'
3
4
  require 'em-http'
@@ -6,6 +7,7 @@ require 'evma_httpserver'
6
7
  module Billy
7
8
  class ProxyConnection < EventMachine::Connection
8
9
  attr_accessor :handler
10
+ attr_accessor :cache
9
11
 
10
12
  def post_init
11
13
  @parser = Http::Parser.new(self)
@@ -77,6 +79,9 @@ module Billy
77
79
  response.headers = result[1].merge('Connection' => 'close')
78
80
  response.content = result[2]
79
81
  response.send_response
82
+ elsif @parser.http_method == 'GET' && cache.cached?(@url)
83
+ Billy.log(:info, "CACHE #{@parser.http_method} #{@url}")
84
+ respond_from_cache
80
85
  else
81
86
  Billy.log(:info, "PROXY #{@parser.http_method} #{@url}")
82
87
  proxy_request
@@ -99,18 +104,34 @@ module Billy
99
104
  req = req.send(@parser.http_method.downcase, req_opts)
100
105
 
101
106
  req.errback do
102
- puts "Request failed: #{@url}"
107
+ Billy.log(:error, "Request failed: #{@url}")
103
108
  close_connection
104
109
  end
105
110
 
106
111
  req.callback do
112
+ res_status = req.response_header.status
113
+ res_headers = req.response_header.raw
114
+ res_headers = res_headers.merge('Connection' => 'close')
115
+ res_headers.delete('Transfer-Encoding')
116
+ res_content = req.response.force_encoding('BINARY')
117
+ if @parser.http_method == 'GET' && cache.cacheable?(@url, res_headers)
118
+ cache.store(@url, res_status, res_headers, res_content)
119
+ end
107
120
  res = EM::DelegatedHttpResponse.new(self)
108
- res.status = req.response_header.status
109
- res.headers = req.response_header.merge('Connection' => 'close')
110
- res.content = req.response.force_encoding('BINARY')
121
+ res.status = res_status
122
+ res.headers = res_headers
123
+ res.content = res_content
111
124
  res.send_response
112
125
  end
113
126
  end
114
127
 
128
+ def respond_from_cache
129
+ cached_res = cache.fetch(@url)
130
+ res = EM::DelegatedHttpResponse.new(self)
131
+ res.status = cached_res[:status]
132
+ res.headers = cached_res[:headers]
133
+ res.content = cached_res[:content]
134
+ res.send_response
135
+ end
115
136
  end
116
137
  end
@@ -1,3 +1,3 @@
1
1
  module Billy
2
- VERSION = "0.1.2"
2
+ VERSION = "0.1.3"
3
3
  end
@@ -1,5 +1,6 @@
1
1
  require 'spec_helper'
2
2
  require 'billy'
3
+ require 'resolv'
3
4
 
4
5
  shared_examples_for 'a proxy server' do
5
6
  it 'should proxy GET requests' do
@@ -15,7 +16,7 @@ shared_examples_for 'a proxy server' do
15
16
  end
16
17
 
17
18
  it 'should proxy HEAD requests' do
18
- http.head('/echo').headers['http_x_echoserver'].should == 'HEAD /echo'
19
+ http.head('/echo').headers['HTTP-X-EchoServer'].should == 'HEAD /echo'
19
20
  end
20
21
 
21
22
  it 'should proxy DELETE requests' do
@@ -55,6 +56,39 @@ shared_examples_for 'a request stub' do
55
56
  end
56
57
  end
57
58
 
59
+ shared_examples_for 'a cache' do
60
+
61
+ context 'whitelisted GET requests' do
62
+ it 'should not be cached' do
63
+ r = http.get('/foo')
64
+ r.body.should == 'GET /foo'
65
+ expect {
66
+ expect {
67
+ r = http.get('/foo')
68
+ }.to change { r.headers['HTTP-X-EchoCount'].to_i }.by(1)
69
+ }.to_not change { r.body }
70
+ end
71
+ end
72
+
73
+ context 'other GET requests' do
74
+ around do |example|
75
+ Billy.configure { |c| c.whitelist = [] }
76
+ example.run
77
+ Billy.configure { |c| c.whitelist = Billy::Config::DEFAULT_WHITELIST }
78
+ end
79
+
80
+ it 'should be cached' do
81
+ r = http.get('/foo')
82
+ r.body.should == 'GET /foo'
83
+ expect {
84
+ expect {
85
+ r = http.get('/foo')
86
+ }.to_not change { r.headers['HTTP-X-EchoCount'] }
87
+ }.to_not change { r.body }
88
+ end
89
+ end
90
+ end
91
+
58
92
  describe Billy::Proxy do
59
93
 
60
94
  before do
@@ -99,4 +133,18 @@ describe Billy::Proxy do
99
133
 
100
134
  end
101
135
 
136
+ context 'caching' do
137
+
138
+ context 'HTTP' do
139
+ let!(:http) { @http }
140
+ it_should_behave_like 'a cache'
141
+ end
142
+
143
+ context 'HTTPS' do
144
+ let!(:http) { @https }
145
+ it_should_behave_like 'a cache'
146
+ end
147
+
148
+ end
149
+
102
150
  end
@@ -21,4 +21,8 @@ RSpec.configure do |config|
21
21
  config.before :all do
22
22
  start_test_servers
23
23
  end
24
+
25
+ config.before :each do
26
+ proxy.reset_cache
27
+ end
24
28
  end
@@ -17,12 +17,19 @@ module Billy
17
17
  q = Queue.new
18
18
  Thread.new do
19
19
  EM.run do
20
+ counter = 0
20
21
  echo = Proc.new do |env|
21
22
  req_body = env['rack.input'].read
22
23
  request_info = "#{env['REQUEST_METHOD']} #{env['PATH_INFO']}"
23
24
  res_body = request_info
24
25
  res_body += "\n#{req_body}" unless req_body.empty?
25
- [200, {'HTTP-X-EchoServer'=>request_info}, [res_body]]
26
+ counter += 1
27
+ [
28
+ 200,
29
+ { 'HTTP-X-EchoServer' => request_info,
30
+ 'HTTP-X-EchoCount' => "#{counter}" },
31
+ [res_body]
32
+ ]
26
33
  end
27
34
 
28
35
  Thin::Logging.silent = true
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: puffing-billy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-10-15 00:00:00.000000000 Z
12
+ date: 2012-11-05 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
@@ -256,6 +256,7 @@ files:
256
256
  - examples/facebook_api.html
257
257
  - examples/tumblr_api.html
258
258
  - lib/billy.rb
259
+ - lib/billy/cache.rb
259
260
  - lib/billy/config.rb
260
261
  - lib/billy/mitm.crt
261
262
  - lib/billy/mitm.key
@@ -307,4 +308,3 @@ test_files:
307
308
  - spec/requests/proxy_spec.rb
308
309
  - spec/spec_helper.rb
309
310
  - spec/support/test_server.rb
310
- has_rdoc: