rack-prerender 1.6.2.1 → 1.6.2.2

Sign up to get free protection for your applications and to get access to all the features.
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: