web 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,33 @@
1
+ require File.dirname(__FILE__) + '/web/ext/net_http'
2
+ require File.dirname(__FILE__) + '/web/faker'
3
+
4
+ module Web
5
+
6
+ # The adapters
7
+ autoload :RedisCache, File.dirname(__FILE__) + '/web/cache/redis_cache'
8
+ autoload :MemcachedCache, File.dirname(__FILE__) + '/web/cache/memcached_cache'
9
+ autoload :MemoryCache, File.dirname(__FILE__) + '/web/cache/memory_cache'
10
+
11
+ class << self
12
+
13
+ attr_writer :cache
14
+
15
+ # register a url to cache
16
+ def register(regex, options = {})
17
+ options[:regex] = regex
18
+ registered << options
19
+ end
20
+
21
+ # an array of registrations
22
+ def registered
23
+ @registered ||= []
24
+ end
25
+
26
+ # Get the cache we're using
27
+ def cache
28
+ @cache ||= RedisCache.new
29
+ end
30
+
31
+ end
32
+
33
+ end
@@ -0,0 +1,23 @@
1
+ require 'dalli'
2
+
3
+ module Web
4
+
5
+ # a basic cache interface, implemented with memcached
6
+
7
+ class MemcachedCache
8
+
9
+ def initialize(client = nil)
10
+ @client = client || Dalli::Client.new
11
+ end
12
+
13
+ def get(key)
14
+ @client.get(key)
15
+ end
16
+
17
+ def set(key, value, expires = nil)
18
+ expires ? @client.set(key, value, expires) : @client.set(key, value)
19
+ end
20
+
21
+ end
22
+
23
+ end
@@ -0,0 +1,20 @@
1
+ module Web
2
+
3
+ class MemoryCache
4
+
5
+ def initialize
6
+ @hash = {}
7
+ end
8
+
9
+ def get(key)
10
+ @hash[key]
11
+ end
12
+
13
+ def set(key, value, expires = nil)
14
+ raise 'MemoryCache does not support key expiration' if expires
15
+ @hash[key] = value
16
+ end
17
+
18
+ end
19
+
20
+ end
@@ -0,0 +1,24 @@
1
+ require 'redis'
2
+
3
+ module Web
4
+
5
+ # A basic cache interface, implemented with Redis
6
+
7
+ class RedisCache
8
+
9
+ def initialize(client = nil)
10
+ @redis = client || Redis.new
11
+ end
12
+
13
+ def get(key)
14
+ @redis.get key
15
+ end
16
+
17
+ def set(key, value, expires = nil)
18
+ @redis.set key, value
19
+ @redis.expire key, expires if expires
20
+ end
21
+
22
+ end
23
+
24
+ end
@@ -0,0 +1,113 @@
1
+ require 'net/http'
2
+ require 'net/https'
3
+ require 'stringio'
4
+
5
+ require File.dirname(__FILE__) + '/../status_codes'
6
+ require File.dirname(__FILE__) + '/../http_response'
7
+
8
+ module Net
9
+
10
+ class BufferedIO
11
+
12
+ # Catch the socket
13
+ # This method was largely influenced by FakeWeb
14
+ def initialize_with_web(io, debug_output = nil)
15
+ @read_timeout = 60
16
+ @rbuf = ''
17
+ @debug_output = debug_output
18
+ # If io is a socket, use it directly
19
+ # If io is a string that represents a file path, open the file
20
+ # Otherwise, open a StringIO so we can stream
21
+ @io = case io
22
+ when Socket, OpenSSL::SSL::SSLSocket, IO
23
+ io
24
+ when String
25
+ if !io.include?("\0") && File.exists?(io) && !File.directory?(io)
26
+ File.open(io, 'r')
27
+ else
28
+ StringIO.new(io)
29
+ end
30
+ end
31
+ # make sure we have the stream open
32
+ raise 'Unable to create local socket' unless @io
33
+ end
34
+
35
+ alias_method :initialize_without_web, :initialize
36
+ alias_method :initialize, :initialize_with_web
37
+
38
+ end
39
+
40
+ class HTTP
41
+
42
+ class << self
43
+
44
+ def socket_type_with_web
45
+ Web::StubSocket
46
+ end
47
+
48
+ alias_method :socket_type_without_web, :socket_type
49
+ alias_method :socket_type, :socket_type_with_web
50
+
51
+ end
52
+
53
+ def request_with_web(request, body = nil, &block)
54
+ connect
55
+ # get a faker
56
+ headers = {}
57
+ request.each do |key, value|
58
+ headers[key] = value
59
+ end
60
+ web = Web::Faker.new request.method.downcase.to_sym, request_uri_as_string(request), body, headers
61
+ if web.desired? && web_response = web.response_for
62
+ # turn the web response into a Net::HTTPResponse
63
+ response = Net::HTTPResponse.send(:response_class, web_response.code.to_s).new('1.0', web_response.code.to_s, HTTP_STATUS_CODES[web_response.code])
64
+ web_response.headers.each do |name, value|
65
+ if value.respond_to?(:each)
66
+ value.each { |val| response.add_field(name, val) }
67
+ else
68
+ response[name] = value
69
+ end
70
+ end
71
+ response.extend Web::HTTPResponse
72
+ response.instance_variable_set(:@body, web_response.body)
73
+ response.instance_variable_set(:@read, true)
74
+ yield response if block_given?
75
+ # respond cached
76
+ response.instance_variable_set(:@cached, true)
77
+ response
78
+ else
79
+ # get the response and store it if its desirable
80
+ if web.desired?
81
+ response = request_without_web(request, body)
82
+ headers = {}
83
+ response.each do |key, value|
84
+ headers[key] = value
85
+ end
86
+ response.extend Web::HTTPResponse
87
+ web.record response.code.to_i, response.body, headers
88
+ yield response if block_given?
89
+ response
90
+ else
91
+ response = request_without_web(request, body, &block)
92
+ response.extend Web::HTTPResponse
93
+ response
94
+ end
95
+ end
96
+ end
97
+
98
+ alias_method :request_without_web, :request
99
+ alias_method :request, :request_with_web
100
+
101
+ private
102
+
103
+ def request_uri_as_string(request)
104
+ protocol = use_ssl? ? 'https' : 'http'
105
+ path = request.path
106
+ path = URI.parse(request.path).request_uri if request.path =~ /^http/
107
+ # TODO handle basic auth
108
+ "#{protocol}://#{address}:#{port}#{path}"
109
+ end
110
+
111
+ end
112
+
113
+ end
@@ -0,0 +1,46 @@
1
+ require File.dirname(__FILE__) + '/response'
2
+
3
+ module Web
4
+
5
+ # A class for representing one faked response
6
+ class Faker
7
+
8
+ attr_reader :cache, :key
9
+
10
+ def initialize(method, url, body, headers)
11
+ @key = "#{method}:#{url}"
12
+ @cache = Web.cache
13
+ # keep these around
14
+ @url = url
15
+ end
16
+
17
+ # whether or not this is a key we want
18
+ def desired?
19
+ @match = Web.registered.detect do |opt|
20
+ opt[:regex] =~ @url
21
+ end
22
+ end
23
+
24
+ # Given a response, marshall down and record in redis
25
+ def record(code, body, headers)
26
+ # save and return the response
27
+ res = Web::Response.new code, body, headers
28
+ # Allow expireation to be set
29
+ expires = @match.has_key?(:expire) ? @match[:expire].to_i : nil
30
+ cache.set(key, res.dump, expires)
31
+ res
32
+ end
33
+
34
+ # Get the mashalled form from redis and reconstruct
35
+ # into a Web::Response
36
+ def response_for
37
+ if data = cache.get(key)
38
+ Web::Response.load(data)
39
+ else
40
+ nil
41
+ end
42
+ end
43
+
44
+ end
45
+
46
+ end
@@ -0,0 +1,16 @@
1
+ module Web
2
+
3
+ module HTTPResponse
4
+
5
+ def read_body(dist = nil, &block)
6
+ yield @body if block_given?
7
+ @body
8
+ end
9
+
10
+ def cached?
11
+ !!@cached
12
+ end
13
+
14
+ end
15
+
16
+ end
@@ -0,0 +1,27 @@
1
+ require 'json'
2
+
3
+ module Web
4
+
5
+ # A class which represents data from a response
6
+ class Response
7
+
8
+ attr_reader :code, :headers, :body
9
+
10
+ def dump
11
+ { :code => @code, :headers => @headers, :body => @body }.to_json
12
+ end
13
+
14
+ def self.load(data)
15
+ data = JSON::parse(data)
16
+ self.new(data['code'], data['body'], data['headers'])
17
+ end
18
+
19
+ def initialize(code, body, headers)
20
+ @code = code
21
+ @body = body
22
+ @headers = headers || {}
23
+ end
24
+
25
+ end
26
+
27
+ end
@@ -0,0 +1,53 @@
1
+ HTTP_STATUS_CODES = {
2
+ 100 => 'Continue',
3
+ 101 => 'Switching Protocols',
4
+ 102 => 'Processing',
5
+ 200 => 'OK',
6
+ 201 => 'Created',
7
+ 202 => 'Accepted',
8
+ 203 => 'Non-Authoritative Information',
9
+ 204 => 'No Content',
10
+ 205 => 'Reset Content',
11
+ 206 => 'Partial Content',
12
+ 207 => 'Multi-Status',
13
+ 226 => 'IM Used',
14
+ 300 => 'Multiple Choices',
15
+ 301 => 'Moved Permanently',
16
+ 302 => 'Found',
17
+ 303 => 'See Other',
18
+ 304 => 'Not Modified',
19
+ 305 => 'Use Proxy',
20
+ 306 => 'Reserved',
21
+ 307 => 'Temporary Redirect',
22
+ 400 => 'Bad Request',
23
+ 401 => 'Unauthorized',
24
+ 402 => 'Payment Required',
25
+ 403 => 'Forbidden',
26
+ 404 => 'Not Found',
27
+ 405 => 'Method Not Allowed',
28
+ 406 => 'Not Acceptable',
29
+ 407 => 'Proxy Authentication Required',
30
+ 408 => 'Request Timeout',
31
+ 409 => 'Conflict',
32
+ 410 => 'Gone',
33
+ 411 => 'Length Required',
34
+ 412 => 'Precondition Failed',
35
+ 413 => 'Request Entity Too Large',
36
+ 414 => 'Request-URI Too Long',
37
+ 415 => 'Unsupported Media Type',
38
+ 416 => 'Requested Range Not Satisfiable',
39
+ 417 => 'Expectation Failed',
40
+ 422 => 'Unprocessable Entity',
41
+ 423 => 'Locked',
42
+ 424 => 'Failed Dependency',
43
+ 426 => 'Upgrade Required',
44
+ 500 => 'Internal Server Error',
45
+ 501 => 'Not Implemented',
46
+ 502 => 'Bad Gateway',
47
+ 503 => 'Service Unavailable',
48
+ 504 => 'Gateway Timeout',
49
+ 505 => 'HTTP Version Not Supported',
50
+ 506 => 'Variant Also Negotiates',
51
+ 507 => 'Insufficient Storage',
52
+ 510 => 'Not Extended'
53
+ }
@@ -0,0 +1,5 @@
1
+ module Web
2
+
3
+ VERSION = '0.0.1'
4
+
5
+ end
@@ -0,0 +1 @@
1
+ require File.dirname(__FILE__) + '/../lib/web'
metadata ADDED
@@ -0,0 +1,69 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: web
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - John Crepezzi
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-08-21 00:00:00.000000000 -04:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rspec
17
+ requirement: &2154079540 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ type: :development
24
+ prerelease: false
25
+ version_requirements: *2154079540
26
+ description: web is a library for caching HTTP responses
27
+ email: john.crepezzi@gmail.com
28
+ executables: []
29
+ extensions: []
30
+ extra_rdoc_files: []
31
+ files:
32
+ - lib/web/cache/memcached_cache.rb
33
+ - lib/web/cache/memory_cache.rb
34
+ - lib/web/cache/redis_cache.rb
35
+ - lib/web/ext/net_http.rb
36
+ - lib/web/faker.rb
37
+ - lib/web/http_response.rb
38
+ - lib/web/response.rb
39
+ - lib/web/status_codes.rb
40
+ - lib/web/version.rb
41
+ - lib/web.rb
42
+ - spec/spec_helper.rb
43
+ has_rdoc: true
44
+ homepage: http://seejohnrun.github.com/web/
45
+ licenses: []
46
+ post_install_message:
47
+ rdoc_options: []
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ! '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ requirements: []
63
+ rubyforge_project: web
64
+ rubygems_version: 1.6.2
65
+ signing_key:
66
+ specification_version: 3
67
+ summary: cache HTTP responses
68
+ test_files:
69
+ - spec/spec_helper.rb