prerendercloud 0.1.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 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
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in prerender_rails.gemspec
4
+ gemspec
5
+
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,12 @@
1
+ #!/usr/bin/env rake
2
+ require 'bundler/gem_tasks'
3
+
4
+ require 'rake/testtask'
5
+
6
+ Rake::TestTask.new do |t|
7
+ t.libs << 'lib/prerendercloud'
8
+ t.test_files = FileList['test/lib/prerendercloud.rb']
9
+ t.verbose = true
10
+ end
11
+
12
+ task :default => :test
@@ -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
@@ -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
@@ -0,0 +1,4 @@
1
+ require 'minitest/autorun'
2
+ require 'webmock/minitest'
3
+ require 'rack'
4
+ require File.expand_path('../../lib/prerendercloud.rb', __FILE__)
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