carbon-copy 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ .request_cache/
data/README.md ADDED
@@ -0,0 +1,24 @@
1
+ # Carbon Copy - Easily cache them REST calls
2
+
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.
5
+
6
+ ### How it works
7
+
8
+ Install the gem
9
+
10
+ gem install carbon-copy
11
+
12
+ Run the server with a specified port
13
+
14
+ carbon-copy -p 8989
15
+
16
+ Alter your REST calls on the front-end to pipe them locally through carbon-copy
17
+
18
+ $.get('http://localhost:8989/slow.server.com/resource')
19
+
20
+ On the first run, the requests will be cached in the `.request_cache` directory
21
+ in the same path as where you called the server. On subsequent requests, the
22
+ calls will pull from that cache instead of contacting the server.
23
+
24
+ You can restart the cache at any time by deleting the specific cache files.
data/bin/carbon-copy ADDED
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env ruby
2
+ require 'optparse'
3
+ require 'carbon-copy'
4
+
5
+ server = CarbonCopy::CacheServer.new
6
+
7
+ opts = OptionParser.new do |opts|
8
+ opts.banner = "Carbon Copy: Cache them RESTs"
9
+ opts.on("-p", "--port Port", "Port to run server") do |v|
10
+ server.run(v)
11
+ end
12
+ end
13
+ opts.parse!
14
+
15
+ uri = ARGV.shift
16
+
17
+ if uri.to_s.strip.empty?
18
+ puts opts
19
+ exit 1
20
+ end
@@ -0,0 +1,17 @@
1
+ $:.push File.expand_path("../lib", __FILE__)
2
+ require 'carbon-copy'
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Kevin Webster"]
6
+ gem.email = ["me@kdoubleyou.com"]
7
+
8
+ gem.description = "easily cache them REST calls"
9
+ gem.summary = "REST cache"
10
+ gem.homepage = 'https://github.com/rabidpraxis/carbon-copy'
11
+ gem.files = `git ls-files`.split("\n")
12
+ gem.test_files = `git ls-files -- spec/*`.split("\n")
13
+ gem.name = "carbon-copy"
14
+ gem.require_paths = ["lib"]
15
+ gem.version = CarbonCopy::VERSION
16
+ gem.executables = ['carbon-copy']
17
+ end
@@ -0,0 +1,6 @@
1
+ require 'carbon-copy/cache_server'
2
+ require 'carbon-copy/http_cacher'
3
+
4
+ module CarbonCopy
5
+ VERSION = '0.0.2'
6
+ end
@@ -0,0 +1,71 @@
1
+ require 'socket'
2
+ require 'open-uri'
3
+ require 'openssl'
4
+ require 'carbon-copy/http_cacher'
5
+
6
+ module CarbonCopy
7
+ class CacheServer
8
+
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
41
+ 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
+
52
+ /^(\S+): ([^\r\n]+)/.match(line)
53
+ header[$1] = $2
54
+ end
55
+ end
56
+ header
57
+ end
58
+
59
+ def handle(session)
60
+ begin
61
+ req = parse_request(session)
62
+ resp = HTTPCacher.new.connect(req)
63
+ session.write(resp)
64
+ session.close
65
+ rescue => e
66
+ p e.message
67
+ p e.backtrace
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,91 @@
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
@@ -0,0 +1,101 @@
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,27 @@
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
metadata ADDED
@@ -0,0 +1,58 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: carbon-copy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Kevin Webster
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-09-02 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: easily cache them REST calls
15
+ email:
16
+ - me@kdoubleyou.com
17
+ executables:
18
+ - carbon-copy
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - .gitignore
23
+ - README.md
24
+ - bin/carbon-copy
25
+ - carbon-copy.gemspec
26
+ - lib/carbon-copy.rb
27
+ - lib/carbon-copy/cache_server.rb
28
+ - lib/carbon-copy/http_cacher.rb
29
+ - spec/cache_server_spec.rb
30
+ - spec/http_cacher_spec.rb
31
+ homepage: https://github.com/rabidpraxis/carbon-copy
32
+ licenses: []
33
+ post_install_message:
34
+ rdoc_options: []
35
+ require_paths:
36
+ - lib
37
+ required_ruby_version: !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ! '>='
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ required_rubygems_version: !ruby/object:Gem::Requirement
44
+ none: false
45
+ requirements:
46
+ - - ! '>='
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ requirements: []
50
+ rubyforge_project:
51
+ rubygems_version: 1.8.24
52
+ signing_key:
53
+ specification_version: 3
54
+ summary: REST cache
55
+ test_files:
56
+ - spec/cache_server_spec.rb
57
+ - spec/http_cacher_spec.rb
58
+ has_rdoc: