carbon-copy 0.0.2 → 0.1.0

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