rack-prerender 1.6.2.1 → 1.6.2.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ffd2dd81b8255a205be1cd51795128c9e062eadad3ec443f0a65154ccdaab134
4
- data.tar.gz: 8044f53b969b4a6050add6727ff4a83418726defa88125e395bea05c469f5729
3
+ metadata.gz: e647c21bd7b1376d4fe43b9ca37c8f59b03c46e6d4e8552300666415781cd758
4
+ data.tar.gz: e4e9bb35c6cd6770438df47cbc8d4b34f28c42c613216b8b4767e26e746d157e
5
5
  SHA512:
6
- metadata.gz: 918cce61d4efa7e346123a8f2f5a7fe8438517675fd9656624fd1473eb19f2eba59c422b368599b309e21fada596faf9a1c63476607363c1ca54c2853a199dc6
7
- data.tar.gz: bcab98539951ba7af5518f4015647ef3e4bb64626b7efa37956f25ca0b9ed6a9c960b67cd46887c22d71f674013aa7dfd8e39adef34c1bffbc659fa9c1331e3a
6
+ metadata.gz: 5fbcd1f83fb88c38bbd8a402c056dee3493785d192f83e493820f0626cc58ee95fe15b889893901929f024f5675b58ea547175a5c1aa543d20cc3ed25dbe235c
7
+ data.tar.gz: 452d3a01ecd28f604a5a11765ceb054cf2256b48c31d2c888fcf272e84e3db9c6ad7b527012147d4fd8b0bb4f264c1938fe3eee1b9e9b8dac1a97eceac8c06db
@@ -10,13 +10,14 @@ module Rack
10
10
  @extensions_to_ignore = cast_to_regexp(opts[:extensions_to_ignore] || EXTENSIONS_TO_IGNORE)
11
11
  end
12
12
 
13
+ # This is run on every request, so performance matters here.
13
14
  def matches?(env)
14
15
  return false if env['REQUEST_METHOD'] != 'GET' ||
15
16
  env['HTTP_X_PRERENDER'] ||
16
17
  (user_agent = env['HTTP_USER_AGENT']).nil?
17
18
 
18
19
  query = env['QUERY_STRING'].to_s
19
- return false unless crawler_user_agents.match?(user_agent.downcase) ||
20
+ return false unless crawler_user_agents.match?(user_agent) ||
20
21
  env['HTTP_X_BUFFERBOT'] ||
21
22
  query.include?('_escaped_fragment_')
22
23
 
@@ -25,7 +26,7 @@ module Rack
25
26
  return false if extensions_to_ignore.match?(fullpath)
26
27
  return false if whitelist && !whitelist.match?(fullpath)
27
28
  return false if blacklist && (blacklist.match?(fullpath) ||
28
- blacklist.match?(env['HTTP_REFERER'].to_s.downcase))
29
+ blacklist.match?(env['HTTP_REFERER'].to_s))
29
30
 
30
31
  true
31
32
  end
@@ -33,14 +34,16 @@ module Rack
33
34
  private
34
35
 
35
36
  def cast_to_regexp(list_arg, escape: true)
36
- case list_arg
37
- when Regexp, nil
38
- list_arg
39
- when Array
40
- escape ? Regexp.union(list_arg) : Regexp.new(list_arg.join('|'))
41
- else
42
- Regexp.new(escape ? Regexp.escape(list_arg) : list_arg)
43
- end
37
+ regexp =
38
+ case list_arg
39
+ when Regexp, nil
40
+ list_arg
41
+ when Array
42
+ escape ? Regexp.union(list_arg) : Regexp.new(list_arg.join('|'))
43
+ else
44
+ Regexp.new(escape ? Regexp.escape(list_arg) : list_arg)
45
+ end
46
+ regexp && Regexp.new(regexp.source, Regexp::IGNORECASE)
44
47
  end
45
48
 
46
49
  CRAWLER_USER_AGENTS = [
@@ -1,3 +1,5 @@
1
+ require 'zlib'
2
+
1
3
  module Rack
2
4
  class Prerender
3
5
  class Fetcher
@@ -7,18 +9,31 @@ module Rack
7
9
  @options = options
8
10
  end
9
11
 
12
+ # Entry point for automatic fetching via the middleware.
10
13
  def call(env)
11
14
  cached_response = before_render(env)
12
15
  return cached_response.finish if cached_response
13
16
 
14
- if prerendered_response = fetch(env)
17
+ if prerendered_response = fetch_env(env)
15
18
  response = build_rack_response_from_prerender(prerendered_response)
16
19
  after_render(env, prerendered_response)
17
20
  return response.finish
18
21
  end
22
+
19
23
  nil
20
24
  end
21
25
 
26
+ # Entry point for manual fetching. Does not run callbacks.
27
+ def fetch(arg)
28
+ case arg
29
+ when String, Symbol, URI then fetch_url(arg.to_s)
30
+ when Hash then fetch_env(arg)
31
+ when Rack::Request then fetch_env(arg.env)
32
+ else
33
+ raise ArgumentError, "expected a URL, Request or env, got #{arg.class}"
34
+ end
35
+ end
36
+
22
37
  def before_render(env)
23
38
  return unless callback = options[:before_render]
24
39
 
@@ -33,60 +48,98 @@ module Rack
33
48
  end
34
49
  end
35
50
 
36
- def fetch(env)
37
- url = URI.parse(api_url(env))
38
- headers = {
39
- 'User-Agent' => env['HTTP_USER_AGENT'],
40
- 'Accept-Encoding' => 'gzip'
41
- }
42
- headers['X-Prerender-Token'] = ENV['PRERENDER_TOKEN'] if ENV['PRERENDER_TOKEN']
43
- headers['X-Prerender-Token'] = options[:prerender_token] if options[:prerender_token]
44
- req = Net::HTTP::Get.new(url.request_uri, headers)
45
- req.basic_auth(ENV['PRERENDER_USERNAME'], ENV['PRERENDER_PASSWORD']) if options[:basic_auth]
46
- http = Net::HTTP.new(url.host, url.port)
47
- http.use_ssl = true if url.scheme == 'https'
48
- response = http.request(req)
49
- if response['Content-Encoding'] == 'gzip'
50
- response.body = ActiveSupport::Gzip.decompress(response.body)
51
- response['Content-Length'] = response.body.bytesize
52
- response.delete('Content-Encoding')
51
+ def fetch_env(env)
52
+ fetch_url(request_url(env), as: env['HTTP_USER_AGENT'])
53
+ end
54
+
55
+ def fetch_url(url, as: nil)
56
+ uri = URI.parse(api_url(url))
57
+ fetch_api_uri(uri, as: as)
58
+ end
59
+
60
+ # This is just horrible, but replacing net/http would break compatibility
61
+ # because the response object is leaked to several callbacks :(
62
+ def fetch_api_uri(uri, as: nil)
63
+ req = Net::HTTP::Get.new(uri.request_uri, headers(user_agent: as))
64
+ if options[:basic_auth]
65
+ req.basic_auth(ENV['PRERENDER_USERNAME'], ENV['PRERENDER_PASSWORD'])
53
66
  end
54
- response
67
+ http = Net::HTTP.new(uri.host, uri.port)
68
+ http.use_ssl = true if uri.scheme == 'https'
69
+ response = http.request(req)
70
+ decompress(response)
55
71
  rescue
56
72
  nil
57
73
  end
58
74
 
59
- def api_url(env)
60
- new_env = env
61
- if env['CF-VISITOR']
62
- match = /"scheme":"(http|https)"/.match(env['CF-VISITOR'])
63
- new_env['HTTPS'] = true and new_env['rack.url_scheme'] = "https" and new_env['SERVER_PORT'] = 443 if (match && match[1] == 'https')
64
- new_env['HTTPS'] = false and new_env['rack.url_scheme'] = "http" and new_env['SERVER_PORT'] = 80 if (match && match[1] == 'http')
75
+ def api_url(url)
76
+ if service_url.match?(/[=\/]$/)
77
+ "#{service_url}#{url}"
78
+ else
79
+ "#{service_url}/#{url}"
65
80
  end
81
+ end
66
82
 
67
- if env['X-FORWARDED-PROTO']
68
- new_env['HTTPS'] = true and new_env['rack.url_scheme'] = "https" and new_env['SERVER_PORT'] = 443 if env["X-FORWARDED-PROTO"].split(',')[0] == 'https'
69
- new_env['HTTPS'] = false and new_env['rack.url_scheme'] = "http" and new_env['SERVER_PORT'] = 80 if env["X-FORWARDED-PROTO"].split(',')[0] == 'http'
83
+ def service_url
84
+ options[:prerender_service_url] || ENV['PRERENDER_SERVICE_URL'] ||
85
+ 'http://service.prerender.io'
86
+ end
87
+
88
+ def headers(user_agent: nil)
89
+ {
90
+ 'Accept-Encoding' => 'gzip',
91
+ 'User-Agent' => user_agent,
92
+ 'X-Prerender-Token' => token,
93
+ }.compact
94
+ end
95
+
96
+ def token
97
+ options[:prerender_token] || ENV['PRERENDER_TOKEN']
98
+ end
99
+
100
+ def request_url(env)
101
+ if env['CF-VISITOR'] && protocol = env['CF-VISITOR'][/"scheme":"(http|https)"/, 1]
102
+ configure_protocol(env, protocol)
103
+ end
104
+
105
+ if env['X-FORWARDED-PROTO'] && protocol = env["X-FORWARDED-PROTO"].split(',')[0]
106
+ configure_protocol(env, protocol)
70
107
  end
71
108
 
72
- if options[:protocol]
73
- new_env['HTTPS'] = true and new_env['rack.url_scheme'] = "https" and new_env['SERVER_PORT'] = 443 if options[:protocol] == 'https'
74
- new_env['HTTPS'] = false and new_env['rack.url_scheme'] = "http" and new_env['SERVER_PORT'] = 80 if options[:protocol] == 'http'
109
+ if protocol = options[:protocol]
110
+ configure_protocol(env, protocol)
75
111
  end
76
112
 
77
- url = Rack::Request.new(new_env).url
78
- prerender_url = prerender_service_url()
79
- forward_slash = prerender_url[-1, 1] == '/' ? '' : '/'
80
- "#{prerender_url}#{forward_slash}#{url}"
113
+ Rack::Request.new(env).url
81
114
  end
82
115
 
83
- def prerender_service_url
84
- options[:prerender_service_url] || ENV['PRERENDER_SERVICE_URL'] || 'http://service.prerender.io/'
116
+ def configure_protocol(env, protocol)
117
+ return unless protocol == 'http' || protocol == 'https'
118
+
119
+ env['rack.url_scheme'] = protocol
120
+ env['HTTPS'] = protocol == 'https'
121
+ env['SERVER_PORT'] = protocol == 'https' ? 443 : 80
122
+ end
123
+
124
+ def decompress(response)
125
+ if response['Content-Encoding'] == 'gzip'
126
+ response.body =
127
+ Zlib::GzipReader.wrap(StringIO.new(response.body), &:read)
128
+ response['Content-Length'] = response.body.bytesize
129
+ response.delete('Content-Encoding')
130
+ end
131
+ response
85
132
  end
86
133
 
87
134
  def build_rack_response_from_prerender(prerendered_response)
88
- response = Rack::Response.new(prerendered_response.body, prerendered_response.code, prerendered_response)
89
- options[:build_rack_response_from_prerender].call(response, prerendered_response) if options[:build_rack_response_from_prerender]
135
+ response = Rack::Response.new(
136
+ prerendered_response.body,
137
+ prerendered_response.code,
138
+ prerendered_response,
139
+ )
140
+ if callback = options[:build_rack_response_from_prerender]
141
+ callback.call(response, prerendered_response)
142
+ end
90
143
  response
91
144
  end
92
145
 
@@ -0,0 +1,19 @@
1
+ module Rack
2
+ class Prerender
3
+ RecacheJob =
4
+ if defined?(::ActiveJob::Base)
5
+ Class.new(::ActiveJob::Base)
6
+ elsif defined?(::Sidekiq::Worker)
7
+ Class.new do
8
+ include ::Sidekiq::Worker
9
+ instance_eval { alias perform_later perform_async }
10
+ end
11
+ else
12
+ raise NameError, 'requires ActiveJob or Sidekiq'
13
+ end.class_eval do
14
+ def perform(url, options)
15
+ ::Rack::Prerender::Recacher.new(options).call(url)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,29 @@
1
+ module Rack
2
+ class Prerender
3
+ class Recacher
4
+ attr_reader :options
5
+
6
+ def initialize(options = {})
7
+ @options = options
8
+ end
9
+
10
+ def call(cached_url)
11
+ uri = URI(api_url)
12
+ Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
13
+ request = Net::HTTP::Post.new(uri, 'Content-Type' => 'application/json')
14
+ request.body = %({"prerenderToken":"#{token}","url":"#{cached_url}"})
15
+ http.request(request) # => Net::HTTPResponse object
16
+ end
17
+ end
18
+
19
+ def api_url
20
+ options[:prerender_recache_url] || ENV['PRERENDER_RECACHE_URL'] ||
21
+ 'http://api.prerender.io/recache'
22
+ end
23
+
24
+ def token
25
+ options[:prerender_token] || ENV['PRERENDER_TOKEN']
26
+ end
27
+ end
28
+ end
29
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Rack
4
4
  class Prerender
5
- VERSION = '1.6.2.1'
5
+ VERSION = '1.6.2.2'
6
6
  end
7
7
  end
@@ -3,18 +3,36 @@ module Rack
3
3
  require 'net/http'
4
4
  require_relative 'prerender/constraint'
5
5
  require_relative 'prerender/fetcher'
6
+ require_relative 'prerender/recacher'
6
7
  require_relative 'prerender/version'
7
8
 
8
- attr_reader :app, :constraint, :fetcher
9
+ attr_accessor :app, :constraint, :fetcher
9
10
 
10
11
  def initialize(app, options = {})
11
12
  @app = app
12
13
  @constraint = Constraint.new(options)
13
14
  @fetcher = Fetcher.new(options)
15
+ @@options = options
14
16
  end
15
17
 
16
18
  def call(env)
17
19
  constraint.matches?(env) && fetcher.call(env) || app.call(env)
18
20
  end
21
+
22
+ # utility methods
23
+
24
+ def self.fetch(arg, **options)
25
+ Fetcher.new(@@options.to_h.merge(options)).fetch(arg)
26
+ end
27
+
28
+ def self.recache_now(url, **options)
29
+ Recacher.new(@@options.to_h.merge(options)).call(url)
30
+ end
31
+
32
+ def self.recache_later(url, **options)
33
+ # require on demand, so ActiveJob/Sidekiq can come later in load order
34
+ require_relative 'prerender/recache_job'
35
+ RecacheJob.perform_later(url, @@options.to_h.merge(options))
36
+ end
19
37
  end
20
38
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-prerender
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.2.1
4
+ version: 1.6.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Todd Hooper
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2020-03-30 00:00:00.000000000 Z
12
+ date: 2020-04-15 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rack
@@ -25,6 +25,20 @@ dependencies:
25
25
  - - ">="
26
26
  - !ruby/object:Gem::Version
27
27
  version: '0'
28
+ - !ruby/object:Gem::Dependency
29
+ name: activejob
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
28
42
  - !ruby/object:Gem::Dependency
29
43
  name: bundler
30
44
  requirement: !ruby/object:Gem::Requirement
@@ -39,6 +53,20 @@ dependencies:
39
53
  - - ">="
40
54
  - !ruby/object:Gem::Version
41
55
  version: '0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: byebug
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
42
70
  - !ruby/object:Gem::Dependency
43
71
  name: rake
44
72
  requirement: !ruby/object:Gem::Requirement
@@ -93,6 +121,8 @@ files:
93
121
  - lib/rack/prerender.rb
94
122
  - lib/rack/prerender/constraint.rb
95
123
  - lib/rack/prerender/fetcher.rb
124
+ - lib/rack/prerender/recache_job.rb
125
+ - lib/rack/prerender/recacher.rb
96
126
  - lib/rack/prerender/version.rb
97
127
  homepage: https://github.com/jaynetics/rack-prerender
98
128
  licenses: