prerendercloud 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +50 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +45 -0
- data/LICENSE +21 -0
- data/README.md +26 -0
- data/Rakefile +12 -0
- data/lib/prerendercloud.rb +253 -0
- data/prerendercloud.gemspec +24 -0
- data/rack_sample_app.rb +23 -0
- data/test/lib/prerendercloud.rb +239 -0
- data/test/test_helper.rb +4 -0
- metadata +127 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5682cc335eaeab97282c40c13b90856a311a2067
|
4
|
+
data.tar.gz: 8ad8486d91b632356e9cddc5b10a6777f4c0a65e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 70531d251e9001ea5d585fa74a54b64980c03cdd2e4b9cc63eb2c89efc8852b6e4d075483b720bab310b3808ebcf59c082a9f3da37248b3b802da20044b8fb86
|
7
|
+
data.tar.gz: 3c276ac603ba71eb5777251f59ea9214d91ff9b6c1bd574c0d860719a566923117a6157a9e35989d1176e6dcc8b0a59427938c40d938e64edd260cbbf4e1f36f
|
data/.gitignore
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
/.config
|
4
|
+
/coverage/
|
5
|
+
/InstalledFiles
|
6
|
+
/pkg/
|
7
|
+
/spec/reports/
|
8
|
+
/spec/examples.txt
|
9
|
+
/test/tmp/
|
10
|
+
/test/version_tmp/
|
11
|
+
/tmp/
|
12
|
+
|
13
|
+
# Used by dotenv library to load environment variables.
|
14
|
+
# .env
|
15
|
+
|
16
|
+
## Specific to RubyMotion:
|
17
|
+
.dat*
|
18
|
+
.repl_history
|
19
|
+
build/
|
20
|
+
*.bridgesupport
|
21
|
+
build-iPhoneOS/
|
22
|
+
build-iPhoneSimulator/
|
23
|
+
|
24
|
+
## Specific to RubyMotion (use of CocoaPods):
|
25
|
+
#
|
26
|
+
# We recommend against adding the Pods directory to your .gitignore. However
|
27
|
+
# you should judge for yourself, the pros and cons are mentioned at:
|
28
|
+
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
29
|
+
#
|
30
|
+
# vendor/Pods/
|
31
|
+
|
32
|
+
## Documentation cache and generated files:
|
33
|
+
/.yardoc/
|
34
|
+
/_yardoc/
|
35
|
+
/doc/
|
36
|
+
/rdoc/
|
37
|
+
|
38
|
+
## Environment normalization:
|
39
|
+
/.bundle/
|
40
|
+
/vendor/bundle
|
41
|
+
/lib/bundler/man/
|
42
|
+
|
43
|
+
# for a library or gem, you might want to ignore these files since the code is
|
44
|
+
# intended to run in multiple environments; otherwise, check them in:
|
45
|
+
# Gemfile.lock
|
46
|
+
# .ruby-version
|
47
|
+
# .ruby-gemset
|
48
|
+
|
49
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
50
|
+
.rvmrc
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
prerendercloud (0.1.0)
|
5
|
+
activesupport
|
6
|
+
rack
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: https://rubygems.org/
|
10
|
+
specs:
|
11
|
+
activesupport (4.2.8)
|
12
|
+
i18n (~> 0.7)
|
13
|
+
minitest (~> 5.1)
|
14
|
+
thread_safe (~> 0.3, >= 0.3.4)
|
15
|
+
tzinfo (~> 1.1)
|
16
|
+
addressable (2.5.1)
|
17
|
+
public_suffix (~> 2.0, >= 2.0.2)
|
18
|
+
crack (0.4.3)
|
19
|
+
safe_yaml (~> 1.0.0)
|
20
|
+
hashdiff (0.3.4)
|
21
|
+
i18n (0.8.4)
|
22
|
+
minitest (5.10.2)
|
23
|
+
public_suffix (2.0.5)
|
24
|
+
rack (1.6.8)
|
25
|
+
rake (12.0.0)
|
26
|
+
safe_yaml (1.0.4)
|
27
|
+
thread_safe (0.3.6)
|
28
|
+
tzinfo (1.2.3)
|
29
|
+
thread_safe (~> 0.1)
|
30
|
+
webmock (3.0.1)
|
31
|
+
addressable (>= 2.3.6)
|
32
|
+
crack (>= 0.3.2)
|
33
|
+
hashdiff
|
34
|
+
|
35
|
+
PLATFORMS
|
36
|
+
ruby
|
37
|
+
|
38
|
+
DEPENDENCIES
|
39
|
+
bundler (~> 1.3)
|
40
|
+
prerendercloud!
|
41
|
+
rake
|
42
|
+
webmock
|
43
|
+
|
44
|
+
BUNDLED WITH
|
45
|
+
1.14.3
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2017 San Francesco
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
![image](https://cloud.githubusercontent.com/assets/22159102/21554484/9d542f5a-cdc4-11e6-8c4c-7730a9e9e2d1.png)
|
2
|
+
|
3
|
+
# prerendercloud-ruby
|
4
|
+
|
5
|
+
Rack middleware for server-side rendering (prerendering) JavaScript web pages/apps (single page apps or SPA) with [https://www.prerender.cloud/](https://www.prerender.cloud/)
|
6
|
+
|
7
|
+
|
8
|
+
## Rails Usage
|
9
|
+
|
10
|
+
`Gemfile`
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
gem 'prerendercloud'
|
14
|
+
```
|
15
|
+
|
16
|
+
`config/environment/production.rb`
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
|
20
|
+
config.middleware.use Rack::Prerendercloud
|
21
|
+
# either hard code your secret token:
|
22
|
+
# config.middleware.use Rack::Prerendercloud, prerender_token: 'YOUR_TOKEN'
|
23
|
+
# or set the PRERENDER_TOKEN environment variable
|
24
|
+
|
25
|
+
|
26
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1,253 @@
|
|
1
|
+
module Rack
|
2
|
+
class Prerendercloud
|
3
|
+
require 'net/http'
|
4
|
+
require 'active_support'
|
5
|
+
|
6
|
+
class << self
|
7
|
+
|
8
|
+
def header_whitelist
|
9
|
+
# preserve (and send to client) these headers from service.prerender.cloud
|
10
|
+
# which originally came from the origin server
|
11
|
+
return [
|
12
|
+
"vary",
|
13
|
+
"content-type",
|
14
|
+
"cache-control",
|
15
|
+
"strict-transport-security",
|
16
|
+
"content-security-policy",
|
17
|
+
"public-key-pins",
|
18
|
+
"x-frame-options",
|
19
|
+
"x-xss-protection",
|
20
|
+
"x-content-type-options"
|
21
|
+
];
|
22
|
+
end
|
23
|
+
|
24
|
+
def normalize_headers(headers)
|
25
|
+
filtered = headers.select { |key, value| header_whitelist.any? { |h| key.to_s.match(/#{h}/i) } }
|
26
|
+
|
27
|
+
mapped = filtered.map do |k, v|
|
28
|
+
[k, if v.is_a? Array then v.join("\n") else v end]
|
29
|
+
end
|
30
|
+
Utils::HeaderHash.new Hash[mapped]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def initialize(app, options={})
|
35
|
+
# googlebot, yahoo, and bingbot are not in this list because
|
36
|
+
# we support _escaped_fragment_ and want to ensure people aren't
|
37
|
+
# penalized for cloaking.
|
38
|
+
@crawler_user_agents = [
|
39
|
+
'googlebot',
|
40
|
+
'yahoo',
|
41
|
+
'bingbot',
|
42
|
+
'baiduspider',
|
43
|
+
'facebookexternalhit',
|
44
|
+
'twitterbot',
|
45
|
+
'rogerbot',
|
46
|
+
'linkedinbot',
|
47
|
+
'embedly',
|
48
|
+
'bufferbot',
|
49
|
+
'quora link preview',
|
50
|
+
'showyoubot',
|
51
|
+
'outbrain',
|
52
|
+
'pinterest/0.',
|
53
|
+
'developers.google.com/+/web/snippet',
|
54
|
+
'www.google.com/webmasters/tools/richsnippets',
|
55
|
+
'slackbot',
|
56
|
+
'vkShare',
|
57
|
+
'W3C_Validator',
|
58
|
+
'redditbot',
|
59
|
+
'Applebot',
|
60
|
+
'WhatsApp',
|
61
|
+
'flipboard',
|
62
|
+
'tumblr',
|
63
|
+
'bitlybot',
|
64
|
+
'SkypeUriPreview',
|
65
|
+
'nuzzel',
|
66
|
+
'Discordbot',
|
67
|
+
'Google Page Speed',
|
68
|
+
'Qwantify'
|
69
|
+
]
|
70
|
+
|
71
|
+
@options = options
|
72
|
+
@options[:whitelist] = [@options[:whitelist]] if @options[:whitelist].is_a? String
|
73
|
+
@options[:blacklist] = [@options[:blacklist]] if @options[:blacklist].is_a? String
|
74
|
+
@crawler_user_agents = @options[:crawler_user_agents] if @options[:crawler_user_agents]
|
75
|
+
@app = app
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
def call(env)
|
80
|
+
if should_show_prerendered_page(env)
|
81
|
+
|
82
|
+
cached_response = before_render(env)
|
83
|
+
|
84
|
+
if cached_response
|
85
|
+
return cached_response.finish
|
86
|
+
end
|
87
|
+
|
88
|
+
prerendered_response = get_prerendered_page_response(env)
|
89
|
+
|
90
|
+
if prerendered_response
|
91
|
+
response = build_rack_response_from_prerender(prerendered_response)
|
92
|
+
after_render(env, prerendered_response)
|
93
|
+
return response.finish
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
@app.call(env)
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
def should_show_prerendered_page(env)
|
102
|
+
user_agent = env['HTTP_USER_AGENT']
|
103
|
+
buffer_agent = env['HTTP_X_BUFFERBOT']
|
104
|
+
is_requesting_prerendered_page = false
|
105
|
+
|
106
|
+
return false if !user_agent
|
107
|
+
return false if env['REQUEST_METHOD'] != 'GET'
|
108
|
+
return false if user_agent.match(/prerendercloud/i);
|
109
|
+
|
110
|
+
request = Rack::Request.new(env)
|
111
|
+
|
112
|
+
return false if !prerenderable_extension(request.fullpath);
|
113
|
+
|
114
|
+
#if it is a bot...show prerendered page
|
115
|
+
if (@options[:bots_only])
|
116
|
+
is_requesting_prerendered_page = true if @crawler_user_agents.any? { |crawler_user_agent| user_agent.downcase.include?(crawler_user_agent.downcase) }
|
117
|
+
|
118
|
+
#if it is BufferBot...show prerendered page
|
119
|
+
is_requesting_prerendered_page = true if buffer_agent
|
120
|
+
else
|
121
|
+
is_requesting_prerendered_page = true
|
122
|
+
end
|
123
|
+
|
124
|
+
is_requesting_prerendered_page = true if Rack::Utils.parse_query(request.query_string).has_key?('_escaped_fragment_')
|
125
|
+
|
126
|
+
# if whitelist exists and path is not whitelisted...don't prerender
|
127
|
+
return false if @options[:whitelist].is_a?(Array) && @options[:whitelist].all? { |whitelisted| !Regexp.new(whitelisted).match(request.fullpath) }
|
128
|
+
|
129
|
+
# if blacklist exists and path is blacklisted(url or referer)...don't prerender
|
130
|
+
if @options[:blacklist].is_a?(Array) && @options[:blacklist].any? { |blacklisted|
|
131
|
+
blacklistedUrl = false
|
132
|
+
blacklistedReferer = false
|
133
|
+
regex = Regexp.new(blacklisted)
|
134
|
+
|
135
|
+
blacklistedUrl = !!regex.match(request.fullpath)
|
136
|
+
blacklistedReferer = !!regex.match(request.referer) if request.referer
|
137
|
+
|
138
|
+
blacklistedUrl || blacklistedReferer
|
139
|
+
}
|
140
|
+
return false
|
141
|
+
end
|
142
|
+
|
143
|
+
return is_requesting_prerendered_page
|
144
|
+
end
|
145
|
+
|
146
|
+
def prerenderable_extension(fullpath)
|
147
|
+
|
148
|
+
path = URI.parse(fullpath).path;
|
149
|
+
|
150
|
+
# doesn't detect index.whatever.html (multiple dots)
|
151
|
+
hasHtmlOrNoExtension = !!path.match(/^(([^.]|\.html?)+)$/);
|
152
|
+
|
153
|
+
return true if hasHtmlOrNoExtension
|
154
|
+
|
155
|
+
# hack to handle basenames with multiple dots: index.whatever.html
|
156
|
+
endsInHtml = !!path.match(/.html?$/);
|
157
|
+
|
158
|
+
return true if endsInHtml
|
159
|
+
|
160
|
+
return false;
|
161
|
+
|
162
|
+
end
|
163
|
+
|
164
|
+
def get_prerendered_page_response(env)
|
165
|
+
begin
|
166
|
+
url = URI.parse(build_api_url(env))
|
167
|
+
headers = {
|
168
|
+
'User-Agent' => env['HTTP_USER_AGENT'],
|
169
|
+
'Accept-Encoding' => 'gzip'
|
170
|
+
}
|
171
|
+
headers['X-Prerender-Token'] = ENV['PRERENDER_TOKEN'] if ENV['PRERENDER_TOKEN']
|
172
|
+
headers['X-Prerender-Token'] = @options[:prerender_token] if @options[:prerender_token]
|
173
|
+
req = Net::HTTP::Get.new(url.request_uri, headers)
|
174
|
+
http = Net::HTTP.new(url.host, url.port)
|
175
|
+
http.use_ssl = true if url.scheme == 'https'
|
176
|
+
response = http.request(req)
|
177
|
+
if response['Content-Encoding'] == 'gzip'
|
178
|
+
response.body = ActiveSupport::Gzip.decompress(response.body)
|
179
|
+
response['Content-Length'] = response.body.length
|
180
|
+
response.delete('Content-Encoding')
|
181
|
+
end
|
182
|
+
response
|
183
|
+
rescue => e
|
184
|
+
puts "Error: #{e.class.name}"
|
185
|
+
nil
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
|
190
|
+
def build_api_url(env)
|
191
|
+
new_env = env
|
192
|
+
if env["CF-VISITOR"]
|
193
|
+
match = /"scheme":"(http|https)"/.match(env['CF-VISITOR'])
|
194
|
+
new_env["HTTPS"] = true and new_env["rack.url_scheme"] = "https" and new_env["SERVER_PORT"] = 443 if (match && match[1] == "https")
|
195
|
+
new_env["HTTPS"] = false and new_env["rack.url_scheme"] = "http" and new_env["SERVER_PORT"] = 80 if (match && match[1] == "http")
|
196
|
+
end
|
197
|
+
|
198
|
+
if env["X-FORWARDED-PROTO"]
|
199
|
+
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"
|
200
|
+
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"
|
201
|
+
end
|
202
|
+
|
203
|
+
if @options[:protocol]
|
204
|
+
new_env["HTTPS"] = true and new_env["rack.url_scheme"] = "https" and new_env["SERVER_PORT"] = 443 if @options[:protocol] == "https"
|
205
|
+
new_env["HTTPS"] = false and new_env["rack.url_scheme"] = "http" and new_env["SERVER_PORT"] = 80 if @options[:protocol] == "http"
|
206
|
+
end
|
207
|
+
|
208
|
+
url = Rack::Request.new(new_env).url
|
209
|
+
puts "Requesting prerendered page for #{url}"
|
210
|
+
prerender_url = get_prerender_service_url()
|
211
|
+
forward_slash = prerender_url[-1, 1] == '/' ? '' : '/'
|
212
|
+
"#{prerender_url}#{forward_slash}#{url}"
|
213
|
+
end
|
214
|
+
|
215
|
+
|
216
|
+
def get_prerender_service_url
|
217
|
+
@options[:prerender_service_url] || ENV['PRERENDER_SERVICE_URL'] || 'http://service.prerender.cloud/'
|
218
|
+
end
|
219
|
+
|
220
|
+
|
221
|
+
def build_rack_response_from_prerender(prerendered_response)
|
222
|
+
|
223
|
+
headers = (prerendered_response.respond_to?(:headers) && prerendered_response.headers) || self.class.normalize_headers(prerendered_response.to_hash)
|
224
|
+
|
225
|
+
response = Rack::Response.new(prerendered_response.body, prerendered_response.code, headers)
|
226
|
+
|
227
|
+
|
228
|
+
@options[:build_rack_response_from_prerender].call(response, prerendered_response) if @options[:build_rack_response_from_prerender]
|
229
|
+
|
230
|
+
response
|
231
|
+
end
|
232
|
+
|
233
|
+
def before_render(env)
|
234
|
+
return nil unless @options[:before_render]
|
235
|
+
|
236
|
+
cached_render = @options[:before_render].call(env)
|
237
|
+
|
238
|
+
if cached_render && cached_render.is_a?(String)
|
239
|
+
Rack::Response.new(cached_render, 200, { 'Content-Type' => 'text/html; charset=utf-8' })
|
240
|
+
elsif cached_render && cached_render.is_a?(Rack::Response)
|
241
|
+
cached_render
|
242
|
+
else
|
243
|
+
nil
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
|
248
|
+
def after_render(env, response)
|
249
|
+
return true unless @options[:after_render]
|
250
|
+
@options[:after_render].call(env, response)
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = "prerendercloud"
|
5
|
+
spec.version = "0.1.0"
|
6
|
+
spec.authors = ["Jonathan Otto"]
|
7
|
+
spec.email = ["support@prerender.cloud"]
|
8
|
+
spec.description = %q{Rack middleware to server-side render your JavaScript apps by prerender.cloud}
|
9
|
+
spec.summary = %q{Rack middleware to server-side render your JavaScript apps by prerender.cloud}
|
10
|
+
spec.homepage = "https://github.com/sanfrancesco/prerendercloud-ruby"
|
11
|
+
spec.license = "MIT"
|
12
|
+
|
13
|
+
spec.files = `git ls-files`.split($/)
|
14
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
15
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
16
|
+
spec.require_paths = ["lib"]
|
17
|
+
|
18
|
+
spec.add_dependency 'rack', '>= 0'
|
19
|
+
spec.add_dependency 'activesupport', '>= 0'
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "webmock"
|
24
|
+
end
|
data/rack_sample_app.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'rack'
|
2
|
+
|
3
|
+
require_relative './lib/prerendercloud'
|
4
|
+
|
5
|
+
class RackSampleApp
|
6
|
+
def call(env)
|
7
|
+
req = Rack::Request.new(env)
|
8
|
+
case req.path_info
|
9
|
+
when /main.js/
|
10
|
+
[200, {"Content-Type" => "application/javascript"}, ["document.getElementById('root').innerHTML = 'js-app';"]]
|
11
|
+
else
|
12
|
+
[200, {"Content-Type" => "text/html"}, ["<html><body><div id='root'></div><script src='/main.js'></script></body></html>"]]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
app = Rack::Builder.new do
|
19
|
+
use Rack::Prerendercloud
|
20
|
+
run RackSampleApp.new
|
21
|
+
end
|
22
|
+
|
23
|
+
Rack::Handler::WEBrick.run app
|
@@ -0,0 +1,239 @@
|
|
1
|
+
require_relative '../test_helper'
|
2
|
+
|
3
|
+
describe Rack::Prerendercloud do
|
4
|
+
|
5
|
+
bot = 'Baiduspider+(+http://www.baidu.com/search/spider.htm)'
|
6
|
+
user = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.76 Safari/537.36'
|
7
|
+
|
8
|
+
before :each do
|
9
|
+
@app = lambda do |params|
|
10
|
+
[200, {}, ""]
|
11
|
+
end
|
12
|
+
|
13
|
+
@prerender = Rack::Prerendercloud.new(@app)
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
it "should return a prerendered response for a crawler with the returned status code and headers" do
|
18
|
+
request = Rack::MockRequest.env_for "/", "HTTP_USER_AGENT" => bot
|
19
|
+
stub_request(:get, @prerender.build_api_url(request)).with(:headers => { 'User-Agent' => bot }).to_return(:body => "<html></html>", :status => 301, :headers => { 'Location' => 'http://google.com'})
|
20
|
+
response = Rack::Prerendercloud.new(@app).call(request)
|
21
|
+
|
22
|
+
assert_equal response[2].body, ["<html></html>"]
|
23
|
+
assert_equal response[2].status, 301
|
24
|
+
assert_equal( { 'Content-Length' => '13'}, response[2].headers )
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
it "should return a prerendered reponse if user is a bot by checking for _escaped_fragment_" do
|
29
|
+
request = Rack::MockRequest.env_for "/path?_escaped_fragment_=", "HTTP_USER_AGENT" => user
|
30
|
+
stub_request(:get, @prerender.build_api_url(request)).with(:headers => { 'User-Agent' => user }).to_return(:body => "<html></html>")
|
31
|
+
response = Rack::Prerendercloud.new(@app).call(request)
|
32
|
+
|
33
|
+
assert_equal ["<html></html>"], response[2].body
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should return a prerendered reponse if the url is for .html file" do
|
37
|
+
request = Rack::MockRequest.env_for "/path.html", "HTTP_USER_AGENT" => bot
|
38
|
+
stub_request(:get, @prerender.build_api_url(request)).with(:headers => { 'User-Agent' => bot }).to_return(:body => "<html></html>")
|
39
|
+
response = Rack::Prerendercloud.new(@app).call(request)
|
40
|
+
|
41
|
+
assert_equal ["<html></html>"], response[2].body
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should continue to app routes if the url is for .js file" do
|
45
|
+
request = Rack::MockRequest.env_for "/path.js", "HTTP_USER_AGENT" => bot
|
46
|
+
stub_request(:get, @prerender.build_api_url(request)).with(:headers => { 'User-Agent' => bot }).to_return(:body => "<html></html>")
|
47
|
+
response = Rack::Prerendercloud.new(@app).call(request)
|
48
|
+
|
49
|
+
assert_equal "", response[2]
|
50
|
+
end
|
51
|
+
|
52
|
+
it "with bots_only, should continue to app routes if the url is a bad url with _escaped_fragment_" do
|
53
|
+
request = Rack::MockRequest.env_for "/path?query=string?_escaped_fragment_=", "HTTP_USER_AGENT" => user
|
54
|
+
response = Rack::Prerendercloud.new(@app, bots_only: true).call(request)
|
55
|
+
|
56
|
+
assert_equal "", response[2]
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
it "should continue to app routes if the request is not a GET" do
|
61
|
+
request = Rack::MockRequest.env_for "/path?_escaped_fragment_=", { "HTTP_USER_AGENT" => user, "REQUEST_METHOD" => "POST" }
|
62
|
+
response = Rack::Prerendercloud.new(@app).call(request)
|
63
|
+
|
64
|
+
assert_equal "", response[2]
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
it "with bots_only option, should continue to app routes if user is not a bot by checking agent string" do
|
69
|
+
request = Rack::MockRequest.env_for "/", "HTTP_USER_AGENT" => user
|
70
|
+
response = Rack::Prerendercloud.new(@app, bots_only: true).call(request)
|
71
|
+
|
72
|
+
assert_equal "", response[2]
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
it "should continue to app routes if prerendercloud user-agent" do
|
77
|
+
request = Rack::MockRequest.env_for "/path?_escaped_fragment_=", "HTTP_USER_AGENT" => 'prerendercloud'
|
78
|
+
response = Rack::Prerendercloud.new(@app).call(request)
|
79
|
+
|
80
|
+
assert_equal "", response[2]
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
it "should continue to app routes if user is a bot, but the bot is requesting a resource file" do
|
85
|
+
request = Rack::MockRequest.env_for "/main.js?anyQueryParam=true", "HTTP_USER_AGENT" => bot
|
86
|
+
response = Rack::Prerendercloud.new(@app).call(request)
|
87
|
+
|
88
|
+
assert_equal "", response[2]
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
it "should continue to app routes if the url is not part of the regex specific whitelist" do
|
93
|
+
request = Rack::MockRequest.env_for "/saved/search/blah?_escaped_fragment_=", "HTTP_USER_AGENT" => bot
|
94
|
+
response = Rack::Prerendercloud.new(@app, whitelist: ['^/search', '/help']).call(request)
|
95
|
+
|
96
|
+
assert_equal "", response[2]
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
it "should set use_ssl to true for https prerender_service_url" do
|
101
|
+
@prerender = Rack::Prerendercloud.new(@app, prerender_service_url: 'https://service.prerender.cloud/')
|
102
|
+
|
103
|
+
request = Rack::MockRequest.env_for "/search/things/123/page?_escaped_fragment_=", "HTTP_USER_AGENT" => bot
|
104
|
+
stub_request(:get, @prerender.build_api_url(request)).to_return(:body => "<html></html>")
|
105
|
+
response = @prerender.call(request)
|
106
|
+
|
107
|
+
assert_equal ["<html></html>"], response[2].body
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
it "should return a prerendered response if the url is part of the regex specific whitelist" do
|
112
|
+
request = Rack::MockRequest.env_for "/search/things/123/page?_escaped_fragment_=", "HTTP_USER_AGENT" => bot
|
113
|
+
stub_request(:get, @prerender.build_api_url(request)).to_return(:body => "<html></html>")
|
114
|
+
response = Rack::Prerendercloud.new(@app, whitelist: ['^/search.*page', '/help']).call(request)
|
115
|
+
|
116
|
+
assert_equal ["<html></html>"], response[2].body
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
it "should continue to app routes if the url is part of the regex specific blacklist" do
|
121
|
+
request = Rack::MockRequest.env_for "/search/things/123/page", "HTTP_USER_AGENT" => bot
|
122
|
+
response = Rack::Prerendercloud.new(@app, blacklist: ['^/search', '/help']).call(request)
|
123
|
+
|
124
|
+
assert_equal "", response[2]
|
125
|
+
end
|
126
|
+
|
127
|
+
it "should continue to app routes if the hashbang url is part of the regex specific blacklist" do
|
128
|
+
request = Rack::MockRequest.env_for "?_escaped_fragment_=/search/things/123/page", "HTTP_USER_AGENT" => bot
|
129
|
+
response = Rack::Prerendercloud.new(@app, blacklist: ['/search', '/help']).call(request)
|
130
|
+
|
131
|
+
assert_equal "", response[2]
|
132
|
+
end
|
133
|
+
|
134
|
+
it "should return a prerendered response if the url is not part of the regex specific blacklist" do
|
135
|
+
request = Rack::MockRequest.env_for "/profile/search/blah", "HTTP_USER_AGENT" => bot
|
136
|
+
stub_request(:get, @prerender.build_api_url(request)).to_return(:body => "<html></html>")
|
137
|
+
response = Rack::Prerendercloud.new(@app, blacklist: ['^/search', '/help']).call(request)
|
138
|
+
|
139
|
+
assert_equal ["<html></html>"], response[2].body
|
140
|
+
end
|
141
|
+
|
142
|
+
|
143
|
+
it "should continue to app routes if the referer is part of the regex specific blacklist" do
|
144
|
+
request = Rack::MockRequest.env_for "/api/results", "HTTP_USER_AGENT" => bot, "HTTP_REFERER" => '/search'
|
145
|
+
response = Rack::Prerendercloud.new(@app, blacklist: ['^/search', '/help']).call(request)
|
146
|
+
|
147
|
+
assert_equal "", response[2]
|
148
|
+
end
|
149
|
+
|
150
|
+
|
151
|
+
it "should return a prerendered response if the referer is not part of the regex specific blacklist" do
|
152
|
+
request = Rack::MockRequest.env_for "/api/results", "HTTP_USER_AGENT" => bot, "HTTP_REFERER" => '/profile/search'
|
153
|
+
stub_request(:get, @prerender.build_api_url(request)).to_return(:body => "<html></html>")
|
154
|
+
response = Rack::Prerendercloud.new(@app, blacklist: ['^/search', '/help']).call(request)
|
155
|
+
|
156
|
+
assert_equal ["<html></html>"], response[2].body
|
157
|
+
end
|
158
|
+
|
159
|
+
|
160
|
+
it "should return a prerendered response if a string is returned from before_render" do
|
161
|
+
request = Rack::MockRequest.env_for "/", "HTTP_USER_AGENT" => bot
|
162
|
+
response = Rack::Prerendercloud.new(@app, before_render: Proc.new do |env| '<html>cached</html>' end).call(request)
|
163
|
+
|
164
|
+
assert_equal ["<html>cached</html>"], response[2].body
|
165
|
+
end
|
166
|
+
|
167
|
+
|
168
|
+
it "should return a prerendered response if a response is returned from before_render" do
|
169
|
+
request = Rack::MockRequest.env_for "/", "HTTP_USER_AGENT" => bot
|
170
|
+
response = Rack::Prerendercloud.new(@app, before_render: Proc.new do |env| Rack::Response.new('<html>cached2</html>', 200, { 'test' => 'test2Header'}) end).call(request)
|
171
|
+
|
172
|
+
assert_equal ["<html>cached2</html>"], response[2].body
|
173
|
+
assert_equal response[2].status, 200
|
174
|
+
assert_equal( { 'test' => 'test2Header', "Content-Length"=>"20"}, response[2].headers )
|
175
|
+
end
|
176
|
+
|
177
|
+
|
178
|
+
describe '#buildApiUrl' do
|
179
|
+
it "should build the correct api url with the default url" do
|
180
|
+
request = Rack::MockRequest.env_for "https://google.com/search?q=javascript"
|
181
|
+
ENV['PRERENDER_SERVICE_URL'] = nil
|
182
|
+
assert_equal 'http://service.prerender.cloud/https://google.com/search?q=javascript', @prerender.build_api_url(request)
|
183
|
+
end
|
184
|
+
|
185
|
+
|
186
|
+
it "should build the correct api url with an environment variable url" do
|
187
|
+
ENV['PRERENDER_SERVICE_URL'] = 'http://prerenderurl.com'
|
188
|
+
request = Rack::MockRequest.env_for "https://google.com/search?q=javascript"
|
189
|
+
assert_equal 'http://prerenderurl.com/https://google.com/search?q=javascript', @prerender.build_api_url(request)
|
190
|
+
ENV['PRERENDER_SERVICE_URL'] = nil
|
191
|
+
end
|
192
|
+
|
193
|
+
|
194
|
+
it "should build the correct api url with an initialization variable url" do
|
195
|
+
@prerender = Rack::Prerendercloud.new(@app, prerender_service_url: 'http://prerenderurl.com')
|
196
|
+
request = Rack::MockRequest.env_for "https://google.com/search?q=javascript"
|
197
|
+
assert_equal 'http://prerenderurl.com/https://google.com/search?q=javascript', @prerender.build_api_url(request)
|
198
|
+
end
|
199
|
+
|
200
|
+
|
201
|
+
it "should build the correct https api url with an initialization variable url" do
|
202
|
+
@prerender = Rack::Prerendercloud.new(@app, prerender_service_url: 'https://prerenderurl.com')
|
203
|
+
request = Rack::MockRequest.env_for "https://google.com/search?q=javascript"
|
204
|
+
assert_equal 'https://prerenderurl.com/https://google.com/search?q=javascript', @prerender.build_api_url(request)
|
205
|
+
end
|
206
|
+
|
207
|
+
|
208
|
+
# Check CF-Visitor header in order to Work behind CloudFlare with Flexible SSL (https://support.cloudflare.com/hc/en-us/articles/200170536)
|
209
|
+
it "should build the correct api url for the Cloudflare Flexible SSL support" do
|
210
|
+
request = Rack::MockRequest.env_for "http://google.com/search?q=javascript", { 'CF-VISITOR' => '"scheme":"https"'}
|
211
|
+
ENV['PRERENDER_SERVICE_URL'] = nil
|
212
|
+
assert_equal 'http://service.prerender.cloud/https://google.com/search?q=javascript', @prerender.build_api_url(request)
|
213
|
+
end
|
214
|
+
|
215
|
+
|
216
|
+
# Check X-Forwarded-Proto because Heroku SSL Support terminates at the load balancer
|
217
|
+
it "should build the correct api url for the Heroku SSL Addon support with single value" do
|
218
|
+
request = Rack::MockRequest.env_for "http://google.com/search?q=javascript", { 'X-FORWARDED-PROTO' => 'https'}
|
219
|
+
ENV['PRERENDER_SERVICE_URL'] = nil
|
220
|
+
assert_equal 'http://service.prerender.cloud/https://google.com/search?q=javascript', @prerender.build_api_url(request)
|
221
|
+
end
|
222
|
+
|
223
|
+
|
224
|
+
# Check X-Forwarded-Proto because Heroku SSL Support terminates at the load balancer
|
225
|
+
it "should build the correct api url for the Heroku SSL Addon support with double value" do
|
226
|
+
request = Rack::MockRequest.env_for "http://google.com/search?q=javascript", { 'X-FORWARDED-PROTO' => 'https,http'}
|
227
|
+
ENV['PRERENDER_SERVICE_URL'] = nil
|
228
|
+
assert_equal 'http://service.prerender.cloud/https://google.com/search?q=javascript', @prerender.build_api_url(request)
|
229
|
+
end
|
230
|
+
|
231
|
+
it "should build the correct api url for the protocol option" do
|
232
|
+
@prerender = Rack::Prerendercloud.new(@app, protocol: 'https')
|
233
|
+
request = Rack::MockRequest.env_for "http://google.com/search?q=javascript"
|
234
|
+
ENV['PRERENDER_SERVICE_URL'] = nil
|
235
|
+
assert_equal 'http://service.prerender.cloud/https://google.com/search?q=javascript', @prerender.build_api_url(request)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: prerendercloud
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jonathan Otto
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-06-19 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rack
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activesupport
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.3'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.3'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: webmock
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description: Rack middleware to server-side render your JavaScript apps by prerender.cloud
|
84
|
+
email:
|
85
|
+
- support@prerender.cloud
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- ".gitignore"
|
91
|
+
- Gemfile
|
92
|
+
- Gemfile.lock
|
93
|
+
- LICENSE
|
94
|
+
- README.md
|
95
|
+
- Rakefile
|
96
|
+
- lib/prerendercloud.rb
|
97
|
+
- prerendercloud.gemspec
|
98
|
+
- rack_sample_app.rb
|
99
|
+
- test/lib/prerendercloud.rb
|
100
|
+
- test/test_helper.rb
|
101
|
+
homepage: https://github.com/sanfrancesco/prerendercloud-ruby
|
102
|
+
licenses:
|
103
|
+
- MIT
|
104
|
+
metadata: {}
|
105
|
+
post_install_message:
|
106
|
+
rdoc_options: []
|
107
|
+
require_paths:
|
108
|
+
- lib
|
109
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - ">="
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '0'
|
114
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - ">="
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '0'
|
119
|
+
requirements: []
|
120
|
+
rubyforge_project:
|
121
|
+
rubygems_version: 2.6.10
|
122
|
+
signing_key:
|
123
|
+
specification_version: 4
|
124
|
+
summary: Rack middleware to server-side render your JavaScript apps by prerender.cloud
|
125
|
+
test_files:
|
126
|
+
- test/lib/prerendercloud.rb
|
127
|
+
- test/test_helper.rb
|