drive 0.2.0 → 0.3.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/README.md +4 -1
- data/app/controllers/concerns/rate_limited.rb +4 -3
- data/app/models/crawler.rb +33 -0
- data/lib/drive/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c3a8ca81a5230ba8bad7ae1d5f02c1dced9130c1e4f25068a079e41bb28e082d
|
|
4
|
+
data.tar.gz: 4094ecf0fedb81c77a8764c6aedbc191e89e99400b45369a20b006a514a08663
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b5d1ace9067f6c0ff8f7cd433742e434373ec9f4a755e31a18e37c4acc875e703ea74ce374076ebe028cc919bc347df925df25e76de8802e645111ccb76b7fe2
|
|
7
|
+
data.tar.gz: 2e0fa021cee1892ee83e6c8e335a79794c4cc1abe144e9a562516451dd63a8b7c0e202a25b61b31eb33ff7ab4aa1c791d2ac0584f017e5ac409733dee516ef2e
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,15 @@ For more information about changelogs, check [Keep a Changelog](http://keepachan
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## 0.3.0 - 2026-10-06
|
|
10
|
+
|
|
11
|
+
* [FEATURE] `RateLimited` no longer limits a search engine's own crawler
|
|
12
|
+
|
|
13
|
+
`Crawler.verified?(request)` believes a user agent naming Google's, Bing's or Apple's crawler
|
|
14
|
+
only where its address resolves to their domain and that host back to the address, and
|
|
15
|
+
remembers the answer for a day. Behind a proxy, `request.remote_ip` must be the visitor's:
|
|
16
|
+
list the proxy's ranges in `config.action_dispatch.trusted_proxies`.
|
|
17
|
+
|
|
9
18
|
## 0.2.0 - 2026-10-05
|
|
10
19
|
|
|
11
20
|
* [BREAKING] `drive` is a new gem: a Rails engine for apps that mirror what a source publishes
|
data/README.md
CHANGED
|
@@ -10,7 +10,10 @@ files, a monthly database — and tells another app exactly which rows changed.
|
|
|
10
10
|
- **`Change`**: every row a run inserted, updated or deleted, with the row as it is now.
|
|
11
11
|
- **`FeedsController`**: `GET /feed?after=ID&kind=TABLE`, the next 5,000 changes, as JSON.
|
|
12
12
|
- **`AccessFile`**: stages a table of a Microsoft Access file, read with `mdb-export`.
|
|
13
|
-
- **`RateLimited`**: 20 requests a minute and 200 a day per address, for public pages.
|
|
13
|
+
- **`RateLimited`**: 20 requests a minute and 200 a day per address, for public pages. A search
|
|
14
|
+
engine's own crawler has no limit.
|
|
15
|
+
- **`Crawler`**: whether a request comes from Google's, Bing's or Apple's crawler: its address
|
|
16
|
+
resolves to their domain and back, checked once a day per address.
|
|
14
17
|
|
|
15
18
|
It needs PostgreSQL.
|
|
16
19
|
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
# The limits of a page anybody may ask for, by the address asking: 20 a minute, 200 a day.
|
|
1
|
+
# The limits of a page anybody may ask for, by the address asking: 20 a minute, 200 a day. A search
|
|
2
|
+
# engine's own crawler has none, so it can list every page.
|
|
2
3
|
module RateLimited
|
|
3
4
|
extend ActiveSupport::Concern
|
|
4
5
|
|
|
5
6
|
included do
|
|
6
|
-
rate_limit to: 20, within: 1.minute, name: 'minute'
|
|
7
|
-
rate_limit to: 200, within: 1.day, name: 'day'
|
|
7
|
+
rate_limit to: 20, within: 1.minute, name: 'minute', unless: -> { Crawler.verified? request }
|
|
8
|
+
rate_limit to: 200, within: 1.day, name: 'day', unless: -> { Crawler.verified? request }
|
|
8
9
|
end
|
|
9
10
|
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
require 'resolv'
|
|
2
|
+
|
|
3
|
+
# A search engine's own crawler, told apart from a client that only names one in its user agent:
|
|
4
|
+
# its address resolves to the search engine's domain, and that host back to the address, which is
|
|
5
|
+
# how Google and Bing say to check theirs.
|
|
6
|
+
module Crawler
|
|
7
|
+
# The user agents of the crawlers checked.
|
|
8
|
+
AGENTS = /Googlebot|Google-InspectionTool|Storebot-Google|GoogleOther|bingbot|Applebot/i
|
|
9
|
+
|
|
10
|
+
# The domains their addresses resolve to.
|
|
11
|
+
DOMAINS = %w[.googlebot.com .google.com .search.msn.com .applebot.apple.com].freeze
|
|
12
|
+
|
|
13
|
+
# @param request [ActionDispatch::Request] request a page was asked with.
|
|
14
|
+
# @return [Boolean] whether it came from a crawler, checked once a day for each address.
|
|
15
|
+
def self.verified?(request)
|
|
16
|
+
ip = request.remote_ip
|
|
17
|
+
request.user_agent.to_s.match?(AGENTS) &&
|
|
18
|
+
Rails.cache.fetch(['crawler', ip], expires_in: 1.day) { resolves? ip }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# @param ip [String] address a request came from.
|
|
22
|
+
# @return [Boolean] whether it names a crawler's host, and that host names it back.
|
|
23
|
+
def self.resolves?(ip)
|
|
24
|
+
dns = Resolv::DNS.new.tap { it.timeouts = 2 }
|
|
25
|
+
host = dns.getname(ip).to_s
|
|
26
|
+
DOMAINS.any? { host.end_with? it } && dns.getaddresses(host).map(&:to_s).include?(ip)
|
|
27
|
+
rescue Resolv::ResolvError
|
|
28
|
+
false
|
|
29
|
+
ensure
|
|
30
|
+
dns&.close
|
|
31
|
+
end
|
|
32
|
+
private_class_method :resolves?
|
|
33
|
+
end
|
data/lib/drive/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: drive
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- claudiob
|
|
@@ -69,6 +69,7 @@ files:
|
|
|
69
69
|
- app/models/change.rb
|
|
70
70
|
- app/models/concerns/logged.rb
|
|
71
71
|
- app/models/concerns/mirrored.rb
|
|
72
|
+
- app/models/crawler.rb
|
|
72
73
|
- app/models/drive/current.rb
|
|
73
74
|
- app/models/run.rb
|
|
74
75
|
- db/migrate/20261005100000_create_runs_and_changes.rb
|