web 0.0.1

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