danbooru::http: factor out cache feature.
Fixes a bug with cookies stored by the `session` feature not being sent with cached requests.
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
require "danbooru/http/html_adapter"
|
require "danbooru/http/html_adapter"
|
||||||
require "danbooru/http/xml_adapter"
|
require "danbooru/http/xml_adapter"
|
||||||
|
require "danbooru/http/cache"
|
||||||
require "danbooru/http/redirector"
|
require "danbooru/http/redirector"
|
||||||
require "danbooru/http/retriable"
|
require "danbooru/http/retriable"
|
||||||
require "danbooru/http/session"
|
require "danbooru/http/session"
|
||||||
@@ -12,7 +13,7 @@ module Danbooru
|
|||||||
DEFAULT_TIMEOUT = 10
|
DEFAULT_TIMEOUT = 10
|
||||||
MAX_REDIRECTS = 5
|
MAX_REDIRECTS = 5
|
||||||
|
|
||||||
attr_accessor :cache, :max_size, :http
|
attr_accessor :max_size, :http
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
delegate :get, :head, :put, :post, :delete, :cache, :follow, :max_size, :timeout, :auth, :basic_auth, :headers, :cookies, :use, :public_only, :download_media, to: :new
|
delegate :get, :head, :put, :post, :delete, :cache, :follow, :max_size, :timeout, :auth, :basic_auth, :headers, :cookies, :use, :public_only, :download_media, to: :new
|
||||||
@@ -49,10 +50,6 @@ module Danbooru
|
|||||||
request(:delete, url, **options)
|
request(:delete, url, **options)
|
||||||
end
|
end
|
||||||
|
|
||||||
def cache(expiry)
|
|
||||||
dup.tap { |o| o.cache = expiry.to_i }
|
|
||||||
end
|
|
||||||
|
|
||||||
def follow(*args)
|
def follow(*args)
|
||||||
dup.tap { |o| o.http = o.http.follow(*args) }
|
dup.tap { |o| o.http = o.http.follow(*args) }
|
||||||
end
|
end
|
||||||
@@ -85,6 +82,10 @@ module Danbooru
|
|||||||
dup.tap { |o| o.http = o.http.use(*args) }
|
dup.tap { |o| o.http = o.http.use(*args) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def cache(expires_in)
|
||||||
|
use(cache: { expires_in: expires_in })
|
||||||
|
end
|
||||||
|
|
||||||
# allow requests only to public IPs, not to local or private networks.
|
# allow requests only to public IPs, not to local or private networks.
|
||||||
def public_only
|
def public_only
|
||||||
dup.tap do |o|
|
dup.tap do |o|
|
||||||
@@ -128,11 +129,7 @@ module Danbooru
|
|||||||
protected
|
protected
|
||||||
|
|
||||||
def request(method, url, **options)
|
def request(method, url, **options)
|
||||||
if @cache.present?
|
http.send(method, url, **options)
|
||||||
cached_request(method, url, **options)
|
|
||||||
else
|
|
||||||
raw_request(method, url, **options)
|
|
||||||
end
|
|
||||||
rescue ValidatingSocket::ProhibitedIpError
|
rescue ValidatingSocket::ProhibitedIpError
|
||||||
fake_response(597, "")
|
fake_response(597, "")
|
||||||
rescue HTTP::Redirector::TooManyRedirectsError
|
rescue HTTP::Redirector::TooManyRedirectsError
|
||||||
@@ -141,21 +138,6 @@ module Danbooru
|
|||||||
fake_response(599, "")
|
fake_response(599, "")
|
||||||
end
|
end
|
||||||
|
|
||||||
def cached_request(method, url, **options)
|
|
||||||
key = Cache.hash({ method: method, url: url, headers: http.default_options.headers.to_h, **options }.to_json)
|
|
||||||
|
|
||||||
cached_response = Cache.get(key, @cache) do
|
|
||||||
response = raw_request(method, url, **options)
|
|
||||||
{ status: response.status, body: response.to_s, headers: response.headers.to_h, version: "1.1" }
|
|
||||||
end
|
|
||||||
|
|
||||||
::HTTP::Response.new(**cached_response)
|
|
||||||
end
|
|
||||||
|
|
||||||
def raw_request(method, url, **options)
|
|
||||||
http.send(method, url, **options)
|
|
||||||
end
|
|
||||||
|
|
||||||
def fake_response(status, body)
|
def fake_response(status, body)
|
||||||
::HTTP::Response.new(status: status, version: "1.1", body: ::HTTP::Response::Body.new(body))
|
::HTTP::Response.new(status: status, version: "1.1", body: ::HTTP::Response::Body.new(body))
|
||||||
end
|
end
|
||||||
|
|||||||
30
app/logical/danbooru/http/cache.rb
Normal file
30
app/logical/danbooru/http/cache.rb
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
module Danbooru
|
||||||
|
class Http
|
||||||
|
class Cache < HTTP::Feature
|
||||||
|
HTTP::Options.register_feature :cache, self
|
||||||
|
|
||||||
|
attr_reader :expires_in
|
||||||
|
|
||||||
|
def initialize(expires_in:)
|
||||||
|
@expires_in = expires_in
|
||||||
|
end
|
||||||
|
|
||||||
|
def perform(request, &block)
|
||||||
|
::Cache.get(cache_key(request), expires_in) do
|
||||||
|
response = yield request
|
||||||
|
|
||||||
|
# XXX hack to remove connection state from response body so we can serialize it for caching.
|
||||||
|
response.flush
|
||||||
|
response.body.instance_variable_set(:@connection, nil)
|
||||||
|
response.body.instance_variable_set(:@stream, nil)
|
||||||
|
|
||||||
|
response
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def cache_key(request)
|
||||||
|
"http:" + ::Cache.hash({ method: request.verb, url: request.uri.to_s, headers: request.headers.sort }.to_json)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -62,14 +62,37 @@ class DanbooruHttpTest < ActiveSupport::TestCase
|
|||||||
resp4 = http.cookies(def: 3, ghi: 4).get("https://httpbin.org/cookies")
|
resp4 = http.cookies(def: 3, ghi: 4).get("https://httpbin.org/cookies")
|
||||||
assert_equal({ abc: "1", def: "3", ghi: "4" }, resp4.parse["cookies"].symbolize_keys)
|
assert_equal({ abc: "1", def: "3", ghi: "4" }, resp4.parse["cookies"].symbolize_keys)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
should "cache requests" do
|
context "cache feature" do
|
||||||
response1 = Danbooru::Http.cache(1.minute).get("https://httpbin.org/uuid")
|
should "cache multiple requests to the same url" do
|
||||||
|
http = Danbooru::Http.cache(1.hour)
|
||||||
|
|
||||||
|
response1 = http.get("https://httpbin.org/uuid")
|
||||||
assert_equal(200, response1.status)
|
assert_equal(200, response1.status)
|
||||||
|
|
||||||
response2 = Danbooru::Http.cache(1.minute).get("https://httpbin.org/uuid")
|
response2 = http.get("https://httpbin.org/uuid")
|
||||||
assert_equal(200, response2.status)
|
assert_equal(200, response2.status)
|
||||||
assert_equal(response2.body, response1.body)
|
assert_equal(response2.to_s, response1.to_s)
|
||||||
|
end
|
||||||
|
|
||||||
|
should "cache cookies correctly" do
|
||||||
|
http = Danbooru::Http.cache(1.hour)
|
||||||
|
|
||||||
|
resp1 = http.get("https://httpbin.org/cookies")
|
||||||
|
resp2 = http.get("https://httpbin.org/cookies/set/abc/1")
|
||||||
|
resp3 = http.get("https://httpbin.org/cookies/set/def/2")
|
||||||
|
resp4 = http.get("https://httpbin.org/cookies")
|
||||||
|
|
||||||
|
assert_equal(200, resp1.status)
|
||||||
|
assert_equal(200, resp2.status)
|
||||||
|
assert_equal(200, resp3.status)
|
||||||
|
assert_equal(200, resp4.status)
|
||||||
|
|
||||||
|
assert_equal({}, resp1.parse["cookies"].symbolize_keys)
|
||||||
|
assert_equal({ abc: "1" }, resp2.parse["cookies"].symbolize_keys)
|
||||||
|
assert_equal({ abc: "1", def: "2" }, resp3.parse["cookies"].symbolize_keys)
|
||||||
|
assert_equal({ abc: "1", def: "2" }, resp4.parse["cookies"].symbolize_keys)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user