public
Description: Simple library which makes it easy to add caching to all your external API calls.
Homepage: http://mloughran.github.com/api_cache/
Clone URL: git://github.com/mloughran/api_cache.git
api_cache / lib / api_cache / api.rb
100644 98 lines (88 sloc) 2.846 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
require 'net/http'
 
class APICache
  # Wraps up querying the API.
  #
  # Ensures that the API is not called more frequently than every +period+
  # seconds, and times out API requests after +timeout+ seconds.
  #
  class API
    # Takes the following options
    #
    # period:: Maximum frequency to call the API. If set to 0 then there is no
    # limit on how frequently queries can be made to the API.
    # timeout:: Timeout when calling api (either to the proviced url or
    # excecuting the passed block)
    # block:: If passed then the block is excecuted instead of HTTP GET
    # against the provided key
    #
    def initialize(key, options, &block)
      @key, @block = key, block
      @timeout = options[:timeout]
      @period = options[:period]
    end
 
    # Fetch data from the API.
    #
    # If no block is given then the key is assumed to be a URL and which will
    # be queried expecting a 200 response. Otherwise the return value of the
    # block will be used.
    #
    # If the block is unable to fetch the value from the API it should raise
    # APICache::Invalid.
    #
    def get
      check_queryable!
      APICache.logger.debug "Fetching data from the API"
      set_queried_at
      Timeout::timeout(@timeout) do
        if @block
          # This should raise APICache::Invalid if it is not correct
          @block.call
        else
          get_key_via_http
        end
      end
    rescue Timeout::Error, APICache::Invalid => e
      raise APICache::CannotFetch, e.message
    end
 
    private
 
    def get_key_via_http
      response = redirecting_get(@key)
      case response
      when Net::HTTPSuccess
        # 2xx response code
        response.body
      else
        raise APICache::Invalid, "Invalid http response: #{response.code}"
      end
    end
 
    def redirecting_get(url)
      r = Net::HTTP.get_response(URI.parse(url))
      r.header['location'] ? redirecting_get(r.header['location']) : r
    end
 
    # Checks whether the API can be queried (i.e. whether :period has passed
    # since the last query to the API).
    #
    def check_queryable!
      if previously_queried?
        if Time.now - queried_at > @period
          APICache.logger.debug "Queryable: true - retry_time has passed"
        else
          APICache.logger.debug "Queryable: false - queried too recently"
          raise APICache::CannotFetch,
            "Cannot fetch #{@key}: queried too recently"
        end
      else
        APICache.logger.debug "Queryable: true - never used API before"
      end
    end
 
    def previously_queried?
      APICache.store.exists?("#{@key}_queried_at")
    end
 
    def queried_at
      APICache.store.get("#{@key}_queried_at")
    end
 
    def set_queried_at
      APICache.store.set("#{@key}_queried_at", Time.now)
    end
  end
end