Skip to content

Commit fc29859

Browse files
committed
Merge pull request zendesk#23 from zendesk/cache
etag-cache responses by default
2 parents 61d4cee + e570654 commit fc29859

File tree

7 files changed

+144
-0
lines changed

7 files changed

+144
-0
lines changed

lib/zendesk_api/client.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
require 'zendesk_api/rescue'
66
require 'zendesk_api/configuration'
77
require 'zendesk_api/collection'
8+
require 'zendesk_api/lru_cache'
9+
require 'zendesk_api/middleware/request/etag_cache'
810
require 'zendesk_api/middleware/request/retry'
911
require 'zendesk_api/middleware/request/upload'
1012
require 'zendesk_api/middleware/response/callback'
@@ -135,6 +137,7 @@ def build_connection
135137
builder.use ZendeskAPI::Middleware::Response::Deflate
136138

137139
# request
140+
builder.use ZendeskAPI::Middleware::Request::EtagCache, :cache => config.cache
138141
builder.use ZendeskAPI::Middleware::Request::Upload
139142
builder.request :multipart
140143
builder.request :json

lib/zendesk_api/configuration.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,37 @@ module ZendeskAPI
22
class Configuration
33
# @return [String] The basic auth username.
44
attr_accessor :username
5+
56
# @return [String] The basic auth password.
67
attr_accessor :password
8+
79
# @return [String] The API url. Must be https unless {#allow_http} is set.
810
attr_accessor :url
11+
912
# @return [Boolean] Whether to attempt to retry when rate-limited (http status: 429).
1013
attr_accessor :retry
14+
1115
# @return [Logger] Logger to use when logging requests.
1216
attr_accessor :logger
17+
1318
# @return [Hash] Client configurations (eg ssh config) to pass to Faraday
1419
attr_accessor :client_options
20+
1521
# @return [Symbol] Faraday adapter
1622
attr_accessor :adapter
23+
1724
# @return [Boolean] Whether to allow non-HTTPS connections for development purposes.
1825
attr_accessor :allow_http
1926

27+
# Use this cache instead of default ZendeskAPI::LRUCache.new
28+
# - must respond to read/write/fetch e.g. ActiveSupport::Cache::MemoryStore.new)
29+
# - pass false to disable caching
30+
# @return [ZendeskAPI::LRUCache]
31+
attr_accessor :cache
32+
2033
def initialize
2134
@client_options = {}
35+
self.cache = ZendeskAPI::LRUCache.new(1000)
2236
end
2337

2438
# Sets accept and user_agent headers, and url.

lib/zendesk_api/lru_cache.rb

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
module ZendeskAPI
2+
# http://codesnippets.joyent.com/posts/show/12329
3+
class ZendeskAPI::LRUCache
4+
attr_accessor :size
5+
6+
def initialize(size = 10)
7+
@size = size
8+
@store = {}
9+
@lru = []
10+
end
11+
12+
def write(key, value)
13+
@store[key] = value
14+
set_lru(key)
15+
@store.delete(@lru.pop) if @lru.size > @size
16+
value
17+
end
18+
19+
def read(key)
20+
set_lru(key)
21+
@store[key]
22+
end
23+
24+
def fetch(key)
25+
if @store.has_key? key
26+
read key
27+
else
28+
write key, yield
29+
end
30+
end
31+
32+
private
33+
34+
def set_lru(key)
35+
@lru.unshift(@lru.delete(key) || key)
36+
end
37+
end
38+
end
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
require "faraday/middleware"
2+
3+
module ZendeskAPI
4+
module Middleware
5+
module Request
6+
# Request middleware that caches responses based on etags
7+
# can be removed once this is merged: https://github.com/pengwynn/faraday_middleware/pull/42
8+
class EtagCache < Faraday::Middleware
9+
def initialize(app, options = {})
10+
@app = app
11+
@cache = options[:cache] ||
12+
raise("need :cache option e.g. ActiveSupport::Cache::MemoryStore.new")
13+
@cache_key_prefix = options.fetch(:cache_key_prefix, :faraday_etags)
14+
end
15+
16+
def call(env)
17+
return @app.call(env) unless [:get, :head].include?(env[:method])
18+
cache_key = [@cache_key_prefix, env[:url].to_s]
19+
20+
# send known etag
21+
if cached = @cache.read(cache_key)
22+
env[:request_headers]["If-None-Match"] ||= cached[:response_headers]["Etag"]
23+
end
24+
25+
@app.call(env).on_complete do
26+
if cached && env[:status] == 304 # not modified
27+
env[:body] = cached[:body]
28+
end
29+
30+
if env[:status] == 200 && env[:response_headers]["Etag"] # modified and cacheable
31+
@cache.write(cache_key, env)
32+
end
33+
end
34+
end
35+
end
36+
end
37+
end
38+
end

spec/lru_cache_spec.rb

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
require 'spec_helper'
2+
3+
describe ZendeskAPI::LRUCache do
4+
let(:cache){ ZendeskAPI::LRUCache.new(2) }
5+
6+
it "writes and reads" do
7+
cache.write("x", 1).should == 1
8+
cache.read("x").should == 1
9+
end
10+
11+
it "drops" do
12+
cache.write("x", 1)
13+
cache.write("y", 1)
14+
cache.write("x", 1)
15+
cache.write("z", 1)
16+
cache.read("z").should == 1
17+
cache.read("x").should == 1
18+
cache.read("y").should == nil
19+
end
20+
21+
it "fetches" do
22+
cache.fetch("x"){ 1 }.should == 1
23+
cache.read("x").should == 1
24+
cache.fetch("x"){ 2 }.should == 1
25+
end
26+
end
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
require 'spec_helper'
2+
3+
describe ZendeskAPI::Middleware::Request::EtagCache do
4+
def fake_response(data)
5+
stub_request(:get, %r{blergh}).to_return(:status => 200, :body => data)
6+
response = client.connection.get("blergh")
7+
response.status.should == 200
8+
response
9+
end
10+
11+
it "caches" do
12+
client.config.cache.size = 1
13+
14+
stub_request(:get, %r{blergh}).to_return(:status => 200, :body => '{"x":1}', :headers => {"Etag" => "x"})
15+
response = client.connection.get("blergh")
16+
response.status.should == 200
17+
response.body.should == {"x"=>1}
18+
19+
stub_request(:get, %r{blergh}).to_return(:status => 304, :response_headers => {"Etag" => "x"})
20+
response = client.connection.get("blergh")
21+
response.status.should == 304
22+
response.body.should == {"x"=>1}
23+
end
24+
end

spec/spec_helper.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ def client
4646
end
4747

4848
client.config.logger.level = (ENV["LOG"] ? Logger::INFO : Logger::WARN)
49+
client.config.cache.size = 0
4950

5051
client
5152
end

0 commit comments

Comments
 (0)