carbon-copy 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source :rubygems
2
+
3
+ gemspec
@@ -0,0 +1,37 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ carbon-copy (0.0.2)
5
+
6
+ GEM
7
+ remote: http://rubygems.org/
8
+ specs:
9
+ diff-lcs (1.1.3)
10
+ mime-types (1.19)
11
+ rack (1.4.1)
12
+ rack-protection (1.2.0)
13
+ rack
14
+ rest-client (1.6.7)
15
+ mime-types (>= 1.16)
16
+ rspec (2.11.0)
17
+ rspec-core (~> 2.11.0)
18
+ rspec-expectations (~> 2.11.0)
19
+ rspec-mocks (~> 2.11.0)
20
+ rspec-core (2.11.1)
21
+ rspec-expectations (2.11.2)
22
+ diff-lcs (~> 1.1.3)
23
+ rspec-mocks (2.11.2)
24
+ sinatra (1.3.3)
25
+ rack (~> 1.3, >= 1.3.6)
26
+ rack-protection (~> 1.2)
27
+ tilt (~> 1.3, >= 1.3.3)
28
+ tilt (1.3.3)
29
+
30
+ PLATFORMS
31
+ ruby
32
+
33
+ DEPENDENCIES
34
+ carbon-copy!
35
+ rest-client
36
+ rspec
37
+ sinatra
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Carbon Copy - Easily cache them REST calls
2
2
 
3
3
  We created Carbon Copy to allow for quick front end (or back end) development by
4
- caching REST responses. We also wanted it to be easy and quick.
4
+ caching REST responses.
5
5
 
6
6
  ### How it works
7
7
 
@@ -2,19 +2,17 @@
2
2
  require 'optparse'
3
3
  require 'carbon-copy'
4
4
 
5
- server = CarbonCopy::CacheServer.new
5
+ cc = CarbonCopy::CarbonCopy.new
6
6
 
7
7
  opts = OptionParser.new do |opts|
8
8
  opts.banner = "Carbon Copy: Cache them RESTs"
9
9
  opts.on("-p", "--port Port", "Port to run server") do |v|
10
- server.run(v)
10
+ cc.port = v
11
+ end
12
+ opts.on("-l", "--cache Cache", "Cache files location") do |v|
13
+ cc.request_cacher = CarbonCopy::RequestCacher(v)
11
14
  end
12
15
  end
13
16
  opts.parse!
14
17
 
15
- uri = ARGV.shift
16
-
17
- if uri.to_s.strip.empty?
18
- puts opts
19
- exit 1
20
- end
18
+ cc.run
@@ -14,4 +14,8 @@ Gem::Specification.new do |gem|
14
14
  gem.require_paths = ["lib"]
15
15
  gem.version = CarbonCopy::VERSION
16
16
  gem.executables = ['carbon-copy']
17
+
18
+ gem.add_development_dependency "rspec"
19
+ gem.add_development_dependency "rest-client"
20
+ gem.add_development_dependency "sinatra"
17
21
  end
@@ -1,6 +1,36 @@
1
1
  require 'carbon-copy/cache_server'
2
- require 'carbon-copy/http_cacher'
2
+ require 'carbon-copy/request_cacher'
3
+
4
+ #### CarbonCopy
5
+ #
6
+ # CarbonCopy is a simple server that sits between your outbound api requests
7
+ # and your file system. It was created in response to a frustration when
8
+ # developing frontend applications on untested backends. CarbonCopy stores your
9
+ # requests locally so if a server stops responding, or is too slow to test on,
10
+ # you can rely on cached data instead of making an http reqest for each
11
+ # refresh.
12
+ #
3
13
 
4
14
  module CarbonCopy
5
- VERSION = '0.0.2'
15
+ VERSION = '0.1.0'
16
+
17
+ # connect all teh pieces
18
+ class CarbonCopy
19
+ attr_writer :request_cacher, :port
20
+
21
+ def run
22
+ CacheServer.new(port, request_cacher).run
23
+ end
24
+
25
+ # default to built in request cacher with current path as the request cache
26
+ # location
27
+ def request_cacher
28
+ @request_cacher || RequestCacher.new(Dir.pwd)
29
+ end
30
+
31
+ # default to port 7979
32
+ def port
33
+ @port || 7979
34
+ end
35
+ end
6
36
  end
@@ -1,66 +1,29 @@
1
1
  require 'socket'
2
- require 'open-uri'
3
2
  require 'openssl'
4
- require 'carbon-copy/http_cacher'
3
+ require 'carbon-copy/request_cacher'
5
4
 
6
5
  module CarbonCopy
7
6
  class CacheServer
8
7
 
9
- def run(port)
10
- webserver = TCPServer.new('127.0.0.1', port)
11
- puts "Running Carbon Copy on localhost port #{port}"
12
- while (session = webserver.accept)
13
- Thread.new(session, &method(:handle))
14
- end
15
- end
16
-
17
- def parse_request(session)
18
- request = session.readline
19
-
20
- parsed = {}
21
- #--- Initial host/uri information -------------------------------------
22
- parsed[:verb] = request[/^\w+/]
23
- parsed[:url] = request[/^#{parsed[:verb]}\s+\/(\S+)/, 1]
24
- parsed[:host] = request[/^#{parsed[:verb]}\s+\/([^\/ ]+)/, 1]
25
- parsed[:version] = request[/HTTP\/(1\.\d)\s*$/, 1]
26
- parsed[:uri] = request[/^#{parsed[:verb]}\s+\/#{parsed[:host]}(\S+)\s+HTTP\/#{parsed[:version]}/, 1] || '/'
27
-
28
- uri = URI::parse(parsed[:uri])
29
- parsed[:request_str] = "#{parsed[:verb]} #{uri.path}?#{uri.query} HTTP/#{parsed[:version]}\r"
30
-
31
- #--- Header and final response text -----------------------------------
32
- parsed[:headers] = parse_headers(session)
33
-
34
- #--- Update header info -----------------------------------------------
35
- parsed[:headers]["Host"] = parsed[:host]
36
-
37
- parsed[:header_str] = parsed[:headers].map{|a, b| "#{a}: #{b}"}.join("\r\n")
38
- parsed[:response] = "#{parsed[:request_str]}\n#{parsed[:header_str]}\r\n\r\n"
39
-
40
- parsed
8
+ def initialize(port, request_cacher)
9
+ @port = port
10
+ @request_cacher = request_cacher
41
11
  end
42
-
43
- def parse_headers(request)
44
- header = {}
45
- unless request.eof?
46
- loop do
47
- line = request.readline
48
- if line.strip.empty?
49
- break
50
- end
51
12
 
52
- /^(\S+): ([^\r\n]+)/.match(line)
53
- header[$1] = $2
54
- end
13
+ def run
14
+ webserver = TCPServer.new('127.0.0.1', @port)
15
+ puts "Running Carbon Copy on localhost port #{@port}"
16
+ while (session = webserver.accept)
17
+ Thread.new(session, &method(:handle))
55
18
  end
56
- header
57
19
  end
58
20
 
59
21
  def handle(session)
60
22
  begin
61
- req = parse_request(session)
62
- resp = HTTPCacher.new.connect(req)
63
- session.write(resp)
23
+ request = Request.new(session)
24
+ request.parse
25
+ response = @request_cacher.connect(request)
26
+ session.write(response)
64
27
  session.close
65
28
  rescue => e
66
29
  p e.message
@@ -0,0 +1,58 @@
1
+ require 'open-uri'
2
+
3
+ module CarbonCopy
4
+ class Request
5
+ attr_accessor :verb, :host, :port, :path, :version, :url, :uri,
6
+ :request_str, :headers, :header_str, :request
7
+
8
+ def initialize(session)
9
+ @session = session
10
+ end
11
+
12
+ def parse
13
+ request = @session.readline
14
+
15
+ #--- Initial host/uri information -------------------------------------
16
+ @verb = request.slice!(/^\w+\s/).strip
17
+ @host = request.slice!(/^\/[^\/: ]+/)[1..-1]
18
+ @port = request.slice!(/^:(\d+)/)
19
+
20
+ @port = ( @port.nil? ) ? '80' : @port[1..-1] # Remove the colon
21
+
22
+ @path = request.slice!(/^(\S)+/)
23
+ @version = request[/HTTP\/(1\.\d)\s*$/, 1]
24
+ @url = "#{@host}#{@path}"
25
+ @uri = "#{@path || '/'}"
26
+
27
+ uri = URI::parse(@uri)
28
+ @request_str = "#{@verb} #{uri.path}?#{uri.query} HTTP/#{@version}\r"
29
+
30
+ #--- Header and final response text -----------------------------------
31
+ @headers = parse_headers(@session)
32
+
33
+ #--- Update header info -----------------------------------------------
34
+ @headers["Host"] = @host
35
+
36
+ @header_str = @headers.map{|a, b| "#{a}: #{b}"}.join("\r\n")
37
+ @request = "#{@request_str}\n#{@header_str}\r\n\r\n"
38
+
39
+ self
40
+ end
41
+
42
+ def parse_headers(request)
43
+ header = {}
44
+ unless request.eof?
45
+ loop do
46
+ line = request.readline
47
+ if line.strip.empty?
48
+ break
49
+ end
50
+
51
+ /^(\S+): ([^\r\n]+)/.match(line)
52
+ header[$1] = $2
53
+ end
54
+ end
55
+ header
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,85 @@
1
+ require 'digest/md5'
2
+
3
+ module CarbonCopy
4
+ class RequestCacher
5
+ attr_reader :base_dir
6
+
7
+ def initialize(base_dir)
8
+ @base_dir = base_dir
9
+ end
10
+
11
+ # Setup cache directory
12
+ def cache_dir
13
+ "#{@base_dir}/.request_cache"
14
+ end
15
+
16
+ # Determine final path
17
+ def path(request)
18
+ uri = ( request.uri == '/' ) ? '' : request.uri.gsub("\/", "_")
19
+ hash = Digest::MD5.new << request.header_str
20
+ # Cache directory structure
21
+ """
22
+ #{cache_dir}/
23
+ #{request.host}/
24
+ #{request.verb.downcase}
25
+ #{uri}_
26
+ #{hash}
27
+ """.gsub(/\n|\s/, '')
28
+ end
29
+
30
+ # Ensure cached directories are created
31
+ def verify_cached_dir(request)
32
+ Dir.mkdir(cache_dir) unless File.exists?(cache_dir)
33
+ host_cache = "#{cache_dir}/#{request.host}"
34
+ Dir.mkdir(host_cache) unless File.exists?(host_cache)
35
+ end
36
+
37
+ def get_response(request)
38
+ a = TCPSocket.new(request.host, request.port)
39
+ a.write(request.request)
40
+
41
+ # Pull request data
42
+ content_len = nil
43
+ buff = ""
44
+ loop do
45
+ line = a.readline
46
+ buff += line
47
+ if line =~ /^Content-Length:\s+(\d+)\s*$/
48
+ content_len = $1.to_i
49
+ end
50
+ break if line.strip.empty?
51
+ end
52
+
53
+ # Pull response
54
+ if content_len
55
+ buff += a.read(content_len)
56
+ else
57
+ loop do
58
+ if a.eof? || line = a.readline || line.strip.empty?
59
+ break
60
+ end
61
+ buff += line
62
+ end
63
+ end
64
+ a.close
65
+
66
+ buff
67
+ end
68
+
69
+ def connect(request)
70
+ verify_cached_dir(request)
71
+ cached_path = path(request)
72
+
73
+ if File.exists?(cached_path) && !File.zero?(cached_path)
74
+ puts "Getting file #{cached_path} from cache"
75
+ IO.read( cached_path )
76
+ else
77
+ resp = get_response(request)
78
+ File.open( cached_path, 'w' ) do |f|
79
+ f.puts resp
80
+ end
81
+ resp
82
+ end
83
+ end
84
+ end
85
+ end
@@ -1,101 +0,0 @@
1
- require File.expand_path('../../lib/carbon-copy', __FILE__)
2
- require 'open-uri'
3
- require 'net/http'
4
-
5
- describe CarbonCopy::CacheServer do
6
- describe '#parse_headers' do
7
- let(:rs) { CarbonCopy::CacheServer.new }
8
-
9
- it 'should parse easy header strings' do
10
- st = StringIO.new
11
- st << "TestHeader: Header Result"
12
- st << "\r\n\r\n"
13
- st.rewind
14
- rs.parse_headers(st).should eq({"TestHeader" => "Header Result"})
15
- end
16
-
17
- it 'should parse multi-line headers' do
18
- st = StringIO.new
19
- st << "TestHeader: Header Result\n"
20
- st << "TestHeaders: Header Result"
21
- st << "\r\n\r\n"
22
- st.rewind
23
- rs.parse_headers(st).should eq({
24
- "TestHeader" => "Header Result",
25
- "TestHeaders" => "Header Result"
26
- })
27
- end
28
- end
29
-
30
- describe '#parse_request' do
31
- let(:rs) { CarbonCopy::CacheServer.new }
32
- let(:req) { rs.parse_request(request) }
33
-
34
- describe 'host with path' do
35
- let(:request) {
36
- req = StringIO.new << "GET /apple.com/google/face/ HTTP/1.1\n"
37
- req.rewind
38
- req
39
- }
40
-
41
- it 'verb' do
42
- req[:verb].should eq("GET")
43
- end
44
-
45
- it 'url with path' do
46
- req[:url].should eq('apple.com/google/face/')
47
- end
48
-
49
- it 'version' do
50
- req[:version].should eq('1.1')
51
- end
52
-
53
- it 'host' do
54
- req[:host].should eq('apple.com')
55
- end
56
-
57
- it 'uri' do
58
- req[:uri].should eq('/google/face/')
59
- end
60
-
61
- it 'request_str' do
62
- req[:request_str].should eq("GET /google/face/? HTTP/1.1\r")
63
- end
64
- end
65
-
66
- describe 'just host' do
67
- let(:request) {
68
- req = StringIO.new << "GET /apple.com HTTP/1.1\n"
69
- req.rewind
70
- req
71
- }
72
- it 'host' do
73
- req[:host].should eq('apple.com')
74
- end
75
-
76
- it 'uri' do
77
- req[:uri].should eq('/')
78
- end
79
- end
80
-
81
- end
82
-
83
- describe '#handle' do
84
- before(:all) do
85
- Thread.new do
86
- CarbonCopy::CacheServer.new.run(7979)
87
- end
88
- end
89
-
90
- it 'caches google.com simple get request' do
91
- url = 'www.apple.com'
92
- o_req = get("http://#{url}").body
93
- req = get("http://localhost:7979/#{url}").body
94
- req.should eq(o_req)
95
- end
96
- end
97
- end
98
-
99
- def get(url)
100
- Net::HTTP.get_response(URI.parse(url))
101
- end
@@ -0,0 +1,59 @@
1
+ require 'spec_helper'
2
+ require 'open-uri'
3
+ require 'net/http'
4
+ require 'sinatra/base'
5
+ require 'rest-client'
6
+
7
+ class TestApp < Sinatra::Base
8
+ get '/awesome' do
9
+ 'This is an awesome get request!'
10
+ end
11
+
12
+ options '/awesome' do
13
+ 'This is an awesome options request!'
14
+ end
15
+ end
16
+
17
+ module CarbonCopy
18
+ describe CarbonCopy do
19
+
20
+ describe '#handle' do
21
+ before(:all) do
22
+ @cache_thread = Thread.new do
23
+ carbon_copy = CarbonCopy.new
24
+ carbon_copy.request_cacher = RequestCacher.new(support_path)
25
+ carbon_copy.port = 7979
26
+ carbon_copy.run
27
+ end
28
+
29
+ @sinatra_thread = Thread.new do
30
+ TestApp.run! host: 'localhost', port: 9898
31
+ end
32
+ sleep 1 # to allow for sinatra to boot
33
+ end
34
+
35
+ after(:all) do
36
+ @cache_thread.kill
37
+ @sinatra_thread.kill
38
+ end
39
+
40
+ it 'caches get request' do
41
+ url = 'localhost:9898/awesome'
42
+ o_req = RestClient.get("http://#{url}").to_str
43
+ req = RestClient.get("http://localhost:7979/#{url}").to_str
44
+ req.should eq(o_req)
45
+ end
46
+
47
+ it 'caches options request' do
48
+ url = 'localhost:9898/awesome'
49
+ o_req = RestClient.options("http://#{url}").to_str
50
+ req = RestClient.options("http://localhost:7979/#{url}").to_str
51
+ req.should eq(o_req)
52
+ end
53
+
54
+ def support_path
55
+ File.expand_path('../support', __FILE__)
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+ require 'digest/md5'
3
+ require 'ostruct'
4
+
5
+ module CarbonCopy
6
+ describe RequestCacher do
7
+ let(:cacher) { RequestCacher.new('') }
8
+ let(:parsed) {
9
+ a = OpenStruct.new
10
+ a.verb = 'GET'
11
+ a.url = 'gist.github.com/74107'
12
+ a.host = 'gist.github.com'
13
+ a.version = '1.1'
14
+ a.uri = '/74107'
15
+ a.header_str = 'Test Header = Test Result'
16
+ a
17
+ }
18
+ it 'should have path with url' do
19
+ hash = Digest::MD5.new << parsed.header_str
20
+ cacher.path(parsed).should match(/\.request_cache\/gist\.github\.com\/get_74107_#{hash}/)
21
+ end
22
+
23
+ it 'should reflect no path' do
24
+ hash = Digest::MD5.new << parsed.header_str
25
+ parsed.url = 'gist.github.com'
26
+ parsed.uri = '/'
27
+ cacher.path(parsed).should match(/\.request_cache\/gist\.github\.com\/get_#{hash}/)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,90 @@
1
+ require 'spec_helper'
2
+ require 'carbon-copy/request'
3
+
4
+ module CarbonCopy
5
+ describe Request do
6
+ describe '#parse_headers' do
7
+ let(:rs) { Request.new(stub.as_null_object) }
8
+
9
+ it 'should parse easy header strings' do
10
+ st = StringIO.new
11
+ st << "TestHeader: Header Result"
12
+ st << "\r\n\r\n"
13
+ st.rewind
14
+ rs.parse_headers(st).should eq({"TestHeader" => "Header Result"})
15
+ end
16
+
17
+ it 'should parse multi-line headers' do
18
+ st = StringIO.new
19
+ st << "TestHeader: Header Result\n"
20
+ st << "TestHeaders: Header Result"
21
+ st << "\r\n\r\n"
22
+ st.rewind
23
+ rs.parse_headers(st).should eq({
24
+ "TestHeader" => "Header Result",
25
+ "TestHeaders" => "Header Result"
26
+ })
27
+ end
28
+ end
29
+
30
+ describe '#parse' do
31
+ let(:rs) { Request.new(request).parse }
32
+
33
+ describe 'just host' do
34
+ let(:request) { create_host_IO("GET /apple.com HTTP/1.1\n") }
35
+
36
+ specify { rs.host.should eq('apple.com') }
37
+ specify { rs.uri.should eq('/') }
38
+ end
39
+
40
+ describe 'host with port' do
41
+ let(:request) { create_host_IO("GET /apple.com:3000 HTTP/1.1\n") }
42
+
43
+ specify { rs.port.should eq('3000') }
44
+ specify { rs.host.should eq('apple.com') }
45
+ specify { rs.url.should eq('apple.com') }
46
+ specify { rs.uri.should eq('/') }
47
+ end
48
+
49
+ describe 'host with port and path' do
50
+ let(:request) { create_host_IO("GET /apple.com:18/awesome HTTP/1.1\n") }
51
+
52
+ specify { rs.port.should eq('18') }
53
+ specify { rs.host.should eq('apple.com') }
54
+ specify { rs.url.should eq('apple.com/awesome') }
55
+ specify { rs.uri.should eq('/awesome') }
56
+ end
57
+
58
+ describe 'host with get string' do
59
+ let(:request) { create_host_IO("POST /fazebook.com/google?p=test HTTP/1.1\n") }
60
+
61
+ specify { rs.verb.should eq('POST') }
62
+ specify { rs.port.should eq('80') }
63
+ specify { rs.url.should eq('fazebook.com/google?p=test') }
64
+ specify { rs.request_str.should eq("POST /google?p=test HTTP/1.1\r") }
65
+ end
66
+
67
+ describe 'host with path' do
68
+ let(:request) { create_host_IO("GET /apple.com/google/face/ HTTP/1.1\n") }
69
+
70
+ specify { rs.verb.should eq('GET') }
71
+ specify { rs.port.should eq('80') }
72
+ specify { rs.url.should eq('apple.com/google/face/') }
73
+ specify { rs.version.should eq('1.1') }
74
+ specify { rs.host.should eq('apple.com') }
75
+ specify { rs.uri.should eq('/google/face/') }
76
+ specify { rs.request_str.should eq("GET /google/face/? HTTP/1.1\r") }
77
+ end
78
+ end
79
+
80
+ def create_host_IO(host)
81
+ req = StringIO.new
82
+ host.split("\n").each do |host|
83
+ req << "#{host}\n"
84
+ end
85
+ req.rewind
86
+ req
87
+ end
88
+
89
+ end
90
+ end
@@ -0,0 +1,20 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # Require this file using `require "spec_helper"` to ensure that it is only
4
+ # loaded once.
5
+ #
6
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
+ require File.expand_path('../../lib/carbon-copy', __FILE__)
8
+
9
+ RSpec.configure do |config|
10
+ config.treat_symbols_as_metadata_keys_with_true_values = true
11
+ config.run_all_when_everything_filtered = true
12
+ config.filter_run :focus
13
+
14
+ # Run specs in random order to surface order dependencies. If you find an
15
+ # order dependency and want to debug it, you can fix the order by providing
16
+ # the seed, which is printed after each run.
17
+ # --seed 1234
18
+ config.order = 'random'
19
+ end
20
+
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: carbon-copy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,8 +9,56 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-09-02 00:00:00.000000000 Z
13
- dependencies: []
12
+ date: 2012-09-06 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rest-client
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: sinatra
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
14
62
  description: easily cache them REST calls
15
63
  email:
16
64
  - me@kdoubleyou.com
@@ -20,14 +68,21 @@ extensions: []
20
68
  extra_rdoc_files: []
21
69
  files:
22
70
  - .gitignore
71
+ - .rspec
72
+ - Gemfile
73
+ - Gemfile.lock
23
74
  - README.md
24
75
  - bin/carbon-copy
25
76
  - carbon-copy.gemspec
26
77
  - lib/carbon-copy.rb
27
78
  - lib/carbon-copy/cache_server.rb
28
- - lib/carbon-copy/http_cacher.rb
79
+ - lib/carbon-copy/request.rb
80
+ - lib/carbon-copy/request_cacher.rb
29
81
  - spec/cache_server_spec.rb
30
- - spec/http_cacher_spec.rb
82
+ - spec/integration_spec.rb
83
+ - spec/request_cacher_spec.rb
84
+ - spec/request_spec.rb
85
+ - spec/spec_helper.rb
31
86
  homepage: https://github.com/rabidpraxis/carbon-copy
32
87
  licenses: []
33
88
  post_install_message:
@@ -54,5 +109,8 @@ specification_version: 3
54
109
  summary: REST cache
55
110
  test_files:
56
111
  - spec/cache_server_spec.rb
57
- - spec/http_cacher_spec.rb
112
+ - spec/integration_spec.rb
113
+ - spec/request_cacher_spec.rb
114
+ - spec/request_spec.rb
115
+ - spec/spec_helper.rb
58
116
  has_rdoc:
@@ -1,91 +0,0 @@
1
- require 'digest/md5'
2
-
3
- module CarbonCopy
4
- class HTTPCacher
5
- attr_reader :base_dir
6
-
7
- def initialize(base_dir = Dir.pwd)
8
- @base_dir = base_dir
9
- end
10
-
11
- #--------------------------------------------------------------------------
12
- # Setup cache directory
13
- #--------------------------------------------------------------------------
14
- def cache_dir
15
- "#{base_dir}/.request_cache"
16
- end
17
-
18
- #--------------------------------------------------------------------------
19
- # Determine final path
20
- #--------------------------------------------------------------------------
21
- def path(parsed)
22
- uri = ( parsed[:uri] == '/' ) ? '' : parsed[:uri].gsub("\/", "_")
23
- hash = Digest::MD5.new << parsed[:header_str]
24
- #--- Cache directory structure ----------------------------------------
25
- """
26
- #{cache_dir}/
27
- #{parsed[:host]}/
28
- #{parsed[:verb].downcase}
29
- #{uri}_
30
- #{hash}
31
- """.gsub(/\n|\s/, '')
32
- end
33
-
34
- #--------------------------------------------------------------------------
35
- # Ensure cached directories are created
36
- #--------------------------------------------------------------------------
37
- def verify_cached_dir(parsed)
38
- Dir.mkdir(cache_dir) unless File.exists?(cache_dir)
39
- host_cache = "#{cache_dir}/#{parsed[:host]}"
40
- Dir.mkdir(host_cache) unless File.exists?(host_cache)
41
- end
42
-
43
- def get_response(parsed)
44
- a = TCPSocket.new(parsed[:host], 80)
45
- a.write(parsed[:response])
46
-
47
- #--- Pull request data ------------------------------------------------
48
- content_len = nil
49
- buff = ""
50
- loop do
51
- line = a.readline
52
- buff += line
53
- if line =~ /^Content-Length:\s+(\d+)\s*$/
54
- content_len = $1.to_i
55
- end
56
- break if line.strip.empty?
57
- end
58
-
59
- #--- Pull response ----------------------------------------------------
60
- if content_len
61
- buff += a.read(content_len)
62
- else
63
- loop do
64
- if a.eof? || line = a.readline || line.strip.empty?
65
- break
66
- end
67
- buff += line
68
- end
69
- end
70
- a.close
71
-
72
- buff
73
- end
74
-
75
- def connect(parsed)
76
- verify_cached_dir(parsed)
77
- cached_path = path(parsed)
78
-
79
- if File.exists?(cached_path) && !File.zero?(cached_path)
80
- puts "Getting file #{cached_path} from cache"
81
- IO.read( cached_path )
82
- else
83
- resp = get_response(parsed)
84
- File.open( cached_path, 'w' ) do |f|
85
- f.puts resp
86
- end
87
- resp
88
- end
89
- end
90
- end
91
- end
@@ -1,27 +0,0 @@
1
- require File.expand_path('../../lib/carbon-copy', __FILE__)
2
- require 'digest/md5'
3
-
4
- describe CarbonCopy::HTTPCacher do
5
- let(:cacher) { CarbonCopy::HTTPCacher.new }
6
- let(:parsed) {
7
- {
8
- verb: 'GET',
9
- url: 'gist.github.com/74107',
10
- host: 'gist.github.com',
11
- version: '1.1',
12
- uri: '/74107',
13
- header_str: 'Test Header: Test Result'
14
- }
15
- }
16
- it 'should have path with url' do
17
- hash = Digest::MD5.new << parsed[:header_str]
18
- cacher.path(parsed).should match(/\.request_cache\/gist\.github\.com\/get_74107_#{hash}/)
19
- end
20
-
21
- it 'should reflect no path' do
22
- hash = Digest::MD5.new << parsed[:header_str]
23
- parsed[:url] = 'gist.github.com'
24
- parsed[:uri] = '/'
25
- cacher.path(parsed).should match(/\.request_cache\/gist\.github\.com\/get_#{hash}/)
26
- end
27
- end