telvue-prerender-rails 1.6.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 +7 -0
- data/.gitignore +17 -0
- data/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +176 -0
- data/Rakefile +12 -0
- data/lib/prerender_rails.rb +254 -0
- data/prerender_rails.gemspec +24 -0
- data/test/lib/prerender_rails.rb +224 -0
- data/test/test_helper.rb +4 -0
- metadata +129 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 0171b8209477afcf52a4d850b8fc1d5f78543550532a0f3e1214a77ead6b9074
|
4
|
+
data.tar.gz: 599fd645634d6813200041a6048aec9d1055f0a0efe0e401d053bd210bb9acea
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e775e031b72d27cc405466e786bb85634feb8a31c8c95150e0255d5492300cff8df9ca37ad576a7895138cdcac854b4f8a5a7516f0baa966f1a4cbc74b42ca9d
|
7
|
+
data.tar.gz: 0120664a13c7c77b05858f83ad4473396af39edff61ff348c190dcb50339295bf530db9f944ba26d3f60a5414dd430b75724c19e4f77fa561082851c2ed721f2
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Todd Hooper
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,176 @@
|
|
1
|
+
Prerender Rails [](https://travis-ci.org/prerender/prerender_rails) [](http://badge.fury.io/rb/prerender_rails)
|
2
|
+
===========================
|
3
|
+
|
4
|
+
Google, Facebook, Twitter, Yahoo, and Bing are constantly trying to view your website... but they don't execute javascript. That's why we built Prerender. Prerender is perfect for AngularJS SEO, BackboneJS SEO, EmberJS SEO, and any other javascript framework.
|
5
|
+
|
6
|
+
This middleware intercepts requests to your Rails website from crawlers, and then makes a call to the (external) Prerender Service to get the static HTML instead of the javascript for that page.
|
7
|
+
|
8
|
+
Prerender adheres to google's `_escaped_fragment_` proposal, which we recommend you use. It's easy:
|
9
|
+
- Just add <meta name="fragment" content="!"> to the <head> of all of your pages
|
10
|
+
- If you use hash urls (#), change them to the hash-bang (#!)
|
11
|
+
- That's it! Perfect SEO on javascript pages.
|
12
|
+
|
13
|
+
`Note` Make sure you have more than one webserver thread/process running because the prerender service will make a request to your server to render the HTML.
|
14
|
+
|
15
|
+
Add this line to your application's Gemfile:
|
16
|
+
|
17
|
+
gem 'prerender_rails'
|
18
|
+
|
19
|
+
And in `config/environment/production.rb`, add this line:
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
config.middleware.use Rack::Prerender
|
23
|
+
```
|
24
|
+
|
25
|
+
or if you have an account on [prerender.io](http://prerender.io) and want to use your token:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
config.middleware.use Rack::Prerender, prerender_token: 'YOUR_TOKEN'
|
29
|
+
```
|
30
|
+
|
31
|
+
`Note` If you're testing locally, you'll need to run the [prerender server](https://github.com/prerender/prerender) locally so that it has access to your server.
|
32
|
+
|
33
|
+
## Testing
|
34
|
+
|
35
|
+
When testing make sure you're not using a single threaded application server like default WEBrick one, use Puma or Unicorn.
|
36
|
+
|
37
|
+
If your URLs use a hash-bang:
|
38
|
+
|
39
|
+
If you want to see `http://localhost:5000/#!/profiles/1234`
|
40
|
+
Then go to `http://localhost:5000/?_escaped_fragment_=/profiles/1234`
|
41
|
+
|
42
|
+
If your URLs use push-state:
|
43
|
+
|
44
|
+
If you want to see `http://localhost:5000/profiles/1234`
|
45
|
+
Then go to `http://localhost:5000/profiles/1234?_escaped_fragment_=`
|
46
|
+
|
47
|
+
## How it works
|
48
|
+
1. The middleware checks to make sure we should show a prerendered page
|
49
|
+
1. The middleware checks if the request is from a crawler (`_escaped_fragment_` or agent string)
|
50
|
+
2. The middleware checks to make sure we aren't requesting a resource (js, css, etc...)
|
51
|
+
3. (optional) The middleware checks to make sure the url is in the whitelist
|
52
|
+
4. (optional) The middleware checks to make sure the url isn't in the blacklist
|
53
|
+
2. The middleware makes a `GET` request to the [prerender service](https://github.com/prerender/prerender)(phantomjs server) for the page's prerendered HTML
|
54
|
+
3. Return that HTML to the crawler
|
55
|
+
|
56
|
+
# Customization
|
57
|
+
|
58
|
+
### Whitelist
|
59
|
+
|
60
|
+
Whitelist a single url path or multiple url paths. Compares using regex, so be specific when possible. If a whitelist is supplied, only url's containing a whitelist path will be prerendered.
|
61
|
+
```ruby
|
62
|
+
config.middleware.use Rack::Prerender, whitelist: '^/search'
|
63
|
+
```
|
64
|
+
```ruby
|
65
|
+
config.middleware.use Rack::Prerender, whitelist: ['/search', '/users/.*/profile']
|
66
|
+
```
|
67
|
+
|
68
|
+
### Blacklist
|
69
|
+
|
70
|
+
Blacklist a single url path or multiple url paths. Compares using regex, so be specific when possible. If a blacklist is supplied, all url's will be prerendered except ones containing a blacklist path.
|
71
|
+
```ruby
|
72
|
+
config.middleware.use Rack::Prerender, blacklist: '^/search'
|
73
|
+
```
|
74
|
+
```ruby
|
75
|
+
config.middleware.use Rack::Prerender, blacklist: ['/search', '/users/.*/profile']
|
76
|
+
```
|
77
|
+
|
78
|
+
### before_render
|
79
|
+
|
80
|
+
This method is intended to be used for caching, but could be used to save analytics or anything else you need to do for each crawler request. If you return a string from before_render, the middleware will serve that to the crawler instead of making a request to the prerender service.
|
81
|
+
```ruby
|
82
|
+
config.middleware.use Rack::Prerender,
|
83
|
+
before_render: (Proc.new do |env|
|
84
|
+
# do whatever you need to do.
|
85
|
+
end)
|
86
|
+
```
|
87
|
+
|
88
|
+
### after_render
|
89
|
+
|
90
|
+
This method is intended to be used for caching, but could be used to save analytics or anything else you need to do for each crawler request. This method is a noop and is called after the prerender service returns HTML.
|
91
|
+
```ruby
|
92
|
+
config.middleware.use Rack::Prerender,
|
93
|
+
after_render: (Proc.new do |env, response|
|
94
|
+
# do whatever you need to do.
|
95
|
+
end)
|
96
|
+
```
|
97
|
+
|
98
|
+
### build_rack_response_from_prerender
|
99
|
+
|
100
|
+
This method is intended to be used to modify the response before it is sent to the crawler. Use this method to add/remove response headers, or do anything else before the request is sent.
|
101
|
+
```ruby
|
102
|
+
config.middleware.use Rack::Prerender,
|
103
|
+
build_rack_response_from_prerender: (Proc.new do |response, prerender_response|
|
104
|
+
# response is already populated with the prerender status code, html, and headers
|
105
|
+
# prerender_response is the response that came back from the prerender service
|
106
|
+
end)
|
107
|
+
```
|
108
|
+
|
109
|
+
### protocol
|
110
|
+
|
111
|
+
Option to hard-set the protocol for Prerender accessing your site instead of the middleware figuring out the protocol based on the request.
|
112
|
+
```ruby
|
113
|
+
config.middleware.use Rack::Prerender, protocol: 'https'
|
114
|
+
```
|
115
|
+
|
116
|
+
## Caching
|
117
|
+
|
118
|
+
This rails middleware is ready to be used with [redis](http://redis.io/) or [memcached](http://memcached.org/) to return prerendered pages in milliseconds.
|
119
|
+
|
120
|
+
When setting up the middleware in `config/environment/production.rb`, you can add a `before_render` method and `after_render` method for caching.
|
121
|
+
|
122
|
+
Here's an example testing a local redis cache:
|
123
|
+
|
124
|
+
_Put this in `config/environment/development.rb`, and add `gem 'redis'` to your Gemfile._
|
125
|
+
|
126
|
+
```ruby
|
127
|
+
require 'redis'
|
128
|
+
@redis = Redis.new
|
129
|
+
config.middleware.use Rack::Prerender,
|
130
|
+
before_render: (Proc.new do |env|
|
131
|
+
@redis.get(Rack::Request.new(env).url)
|
132
|
+
end),
|
133
|
+
after_render: (Proc.new do |env, response|
|
134
|
+
@redis.set(Rack::Request.new(env).url, response.body)
|
135
|
+
end)
|
136
|
+
```
|
137
|
+
|
138
|
+
## Using your own prerender service
|
139
|
+
|
140
|
+
We host a Prerender server at [prerender.io](http://prerender.io) so that you can work on more important things, but if you've deployed the prerender service on your own... set the `PRERENDER_SERVICE_URL` environment variable so that this middleware points there instead. Otherwise, it will default to the service already deployed by [prerender.io](http://prerender.io).
|
141
|
+
|
142
|
+
$ export PRERENDER_SERVICE_URL=<new url>
|
143
|
+
|
144
|
+
Or on heroku:
|
145
|
+
|
146
|
+
$ heroku config:add PRERENDER_SERVICE_URL=<new url>
|
147
|
+
|
148
|
+
As an alternative, you can pass `prerender_service_url` in the options object during initialization of the middleware
|
149
|
+
|
150
|
+
``` ruby
|
151
|
+
config.middleware.use Rack::Prerender, prerender_service_url: '<new url>'
|
152
|
+
```
|
153
|
+
|
154
|
+
## License
|
155
|
+
|
156
|
+
The MIT License (MIT)
|
157
|
+
|
158
|
+
Copyright (c) 2013 Todd Hooper <todd@prerender.io>
|
159
|
+
|
160
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
161
|
+
of this software and associated documentation files (the "Software"), to deal
|
162
|
+
in the Software without restriction, including without limitation the rights
|
163
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
164
|
+
copies of the Software, and to permit persons to whom the Software is
|
165
|
+
furnished to do so, subject to the following conditions:
|
166
|
+
|
167
|
+
The above copyright notice and this permission notice shall be included in
|
168
|
+
all copies or substantial portions of the Software.
|
169
|
+
|
170
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
171
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
172
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
173
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
174
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
175
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
176
|
+
THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,254 @@
|
|
1
|
+
module Rack
|
2
|
+
class Prerender
|
3
|
+
require 'net/http'
|
4
|
+
require 'active_support'
|
5
|
+
|
6
|
+
def initialize(app, options={})
|
7
|
+
@crawler_user_agents = [
|
8
|
+
'googlebot',
|
9
|
+
'yahoo',
|
10
|
+
'bingbot',
|
11
|
+
'baiduspider',
|
12
|
+
'facebookexternalhit',
|
13
|
+
'twitterbot',
|
14
|
+
'rogerbot',
|
15
|
+
'linkedinbot',
|
16
|
+
'embedly',
|
17
|
+
'bufferbot',
|
18
|
+
'quora link preview',
|
19
|
+
'showyoubot',
|
20
|
+
'outbrain',
|
21
|
+
'pinterest/0.',
|
22
|
+
'developers.google.com/+/web/snippet',
|
23
|
+
'www.google.com/webmasters/tools/richsnippets',
|
24
|
+
'slackbot',
|
25
|
+
'vkShare',
|
26
|
+
'W3C_Validator',
|
27
|
+
'redditbot',
|
28
|
+
'Applebot',
|
29
|
+
'WhatsApp',
|
30
|
+
'flipboard',
|
31
|
+
'tumblr',
|
32
|
+
'bitlybot',
|
33
|
+
'SkypeUriPreview',
|
34
|
+
'nuzzel',
|
35
|
+
'Discordbot',
|
36
|
+
'Google Page Speed',
|
37
|
+
'Qwantify',
|
38
|
+
'Chrome-Lighthouse'
|
39
|
+
]
|
40
|
+
|
41
|
+
@extensions_to_ignore = [
|
42
|
+
'.js',
|
43
|
+
'.css',
|
44
|
+
'.xml',
|
45
|
+
'.less',
|
46
|
+
'.png',
|
47
|
+
'.jpg',
|
48
|
+
'.jpeg',
|
49
|
+
'.gif',
|
50
|
+
'.pdf',
|
51
|
+
'.doc',
|
52
|
+
'.txt',
|
53
|
+
'.ico',
|
54
|
+
'.rss',
|
55
|
+
'.zip',
|
56
|
+
'.mp3',
|
57
|
+
'.rar',
|
58
|
+
'.exe',
|
59
|
+
'.wmv',
|
60
|
+
'.doc',
|
61
|
+
'.avi',
|
62
|
+
'.ppt',
|
63
|
+
'.mpg',
|
64
|
+
'.mpeg',
|
65
|
+
'.tif',
|
66
|
+
'.wav',
|
67
|
+
'.mov',
|
68
|
+
'.psd',
|
69
|
+
'.ai',
|
70
|
+
'.xls',
|
71
|
+
'.mp4',
|
72
|
+
'.m4a',
|
73
|
+
'.swf',
|
74
|
+
'.dat',
|
75
|
+
'.dmg',
|
76
|
+
'.iso',
|
77
|
+
'.flv',
|
78
|
+
'.m4v',
|
79
|
+
'.torrent'
|
80
|
+
]
|
81
|
+
|
82
|
+
@options = options
|
83
|
+
@options[:whitelist] = [@options[:whitelist]] if @options[:whitelist].is_a? String
|
84
|
+
@options[:blacklist] = [@options[:blacklist]] if @options[:blacklist].is_a? String
|
85
|
+
@extensions_to_ignore = @options[:extensions_to_ignore] if @options[:extensions_to_ignore]
|
86
|
+
@crawler_user_agents = @options[:crawler_user_agents] if @options[:crawler_user_agents]
|
87
|
+
@app = app
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
def call(env)
|
92
|
+
if should_show_prerendered_page(env)
|
93
|
+
|
94
|
+
cached_response = before_render(env)
|
95
|
+
|
96
|
+
if cached_response
|
97
|
+
return cached_response.finish
|
98
|
+
end
|
99
|
+
|
100
|
+
prerendered_response = get_prerendered_page_response(env)
|
101
|
+
|
102
|
+
if prerendered_response
|
103
|
+
response = build_rack_response_from_prerender(prerendered_response)
|
104
|
+
after_render(env, prerendered_response)
|
105
|
+
return response.finish
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
@app.call(env)
|
110
|
+
end
|
111
|
+
|
112
|
+
|
113
|
+
def should_show_prerendered_page(env)
|
114
|
+
user_agent = env['HTTP_USER_AGENT']
|
115
|
+
buffer_agent = env['HTTP_X_BUFFERBOT']
|
116
|
+
prerender_agent = env['HTTP_X_PRERENDER']
|
117
|
+
is_requesting_prerendered_page = false
|
118
|
+
|
119
|
+
return false if !user_agent
|
120
|
+
return false if env['REQUEST_METHOD'] != 'GET'
|
121
|
+
|
122
|
+
request = Rack::Request.new(env)
|
123
|
+
|
124
|
+
is_requesting_prerendered_page = true if Rack::Utils.parse_query(request.query_string).has_key?('_escaped_fragment_')
|
125
|
+
|
126
|
+
#if it is a bot...show prerendered page
|
127
|
+
is_requesting_prerendered_page = true if @crawler_user_agents.any? { |crawler_user_agent| user_agent.downcase.include?(crawler_user_agent.downcase) }
|
128
|
+
|
129
|
+
#if it is BufferBot...show prerendered page
|
130
|
+
is_requesting_prerendered_page = true if buffer_agent
|
131
|
+
|
132
|
+
#if it is Prerender...don't show prerendered page
|
133
|
+
is_requesting_prerendered_page = false if prerender_agent
|
134
|
+
|
135
|
+
#if it is a bot and is requesting a resource...dont prerender
|
136
|
+
return false if @extensions_to_ignore.any? { |extension| request.fullpath.include? extension }
|
137
|
+
|
138
|
+
#if it is a bot and not requesting a resource and is not whitelisted...dont prerender
|
139
|
+
return false if @options[:whitelist].is_a?(Array) && @options[:whitelist].all? { |whitelisted| !Regexp.new(whitelisted).match(request.fullpath) }
|
140
|
+
|
141
|
+
#if it is a bot and not requesting a resource and is blacklisted(url or referer)...dont prerender
|
142
|
+
if @options[:blacklist].is_a?(Array) && @options[:blacklist].any? { |blacklisted|
|
143
|
+
blacklistedUrl = false
|
144
|
+
blacklistedReferer = false
|
145
|
+
regex = Regexp.new(blacklisted)
|
146
|
+
|
147
|
+
blacklistedUrl = !!regex.match(request.fullpath)
|
148
|
+
blacklistedReferer = !!regex.match(request.referer) if request.referer
|
149
|
+
|
150
|
+
blacklistedUrl || blacklistedReferer
|
151
|
+
}
|
152
|
+
return false
|
153
|
+
end
|
154
|
+
|
155
|
+
return is_requesting_prerendered_page
|
156
|
+
end
|
157
|
+
|
158
|
+
|
159
|
+
def get_prerendered_page_response(env)
|
160
|
+
begin
|
161
|
+
url = URI.parse(build_api_url(env))
|
162
|
+
headers = {
|
163
|
+
'User-Agent' => env['HTTP_USER_AGENT'],
|
164
|
+
'Accept-Encoding' => 'gzip'
|
165
|
+
}
|
166
|
+
headers['X-Prerender-Token'] = ENV['PRERENDER_TOKEN'] if ENV['PRERENDER_TOKEN']
|
167
|
+
headers['X-Prerender-Token'] = @options[:prerender_token] if @options[:prerender_token]
|
168
|
+
req = Net::HTTP::Get.new(url.request_uri, headers)
|
169
|
+
req.basic_auth(ENV['PRERENDER_USERNAME'], ENV['PRERENDER_PASSWORD']) if @options[:basic_auth]
|
170
|
+
http = Net::HTTP.new(url.host, url.port)
|
171
|
+
http.use_ssl = true if url.scheme == 'https'
|
172
|
+
response = http.request(req)
|
173
|
+
if response['Content-Encoding'] == 'gzip'
|
174
|
+
response.body = ActiveSupport::Gzip.decompress(response.body)
|
175
|
+
response['Content-Length'] = response.body.length
|
176
|
+
response.delete('Content-Encoding')
|
177
|
+
end
|
178
|
+
response
|
179
|
+
rescue
|
180
|
+
nil
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
|
185
|
+
def build_api_url(env)
|
186
|
+
new_env = env
|
187
|
+
if env["CF-VISITOR"]
|
188
|
+
match = /"scheme":"(http|https)"/.match(env['CF-VISITOR'])
|
189
|
+
new_env["HTTPS"] = true and new_env["rack.url_scheme"] = "https" and new_env["SERVER_PORT"] = 443 if (match && match[1] == "https")
|
190
|
+
new_env["HTTPS"] = false and new_env["rack.url_scheme"] = "http" and new_env["SERVER_PORT"] = 80 if (match && match[1] == "http")
|
191
|
+
end
|
192
|
+
|
193
|
+
if env["X-FORWARDED-PROTO"]
|
194
|
+
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"
|
195
|
+
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"
|
196
|
+
end
|
197
|
+
|
198
|
+
if @options[:protocol]
|
199
|
+
new_env["HTTPS"] = true and new_env["rack.url_scheme"] = "https" and new_env["SERVER_PORT"] = 443 if @options[:protocol] == "https"
|
200
|
+
new_env["HTTPS"] = false and new_env["rack.url_scheme"] = "http" and new_env["SERVER_PORT"] = 80 if @options[:protocol] == "http"
|
201
|
+
end
|
202
|
+
|
203
|
+
url = Rack::Request.new(new_env).url
|
204
|
+
prerender_url = get_prerender_service_url()
|
205
|
+
forward_slash = prerender_url[-1, 1] == '/' ? '' : '/'
|
206
|
+
|
207
|
+
|
208
|
+
if env['HTTP_USER_AGENT'].include?('facebookexternalhit')
|
209
|
+
if url.include?('?')
|
210
|
+
url = "#{url}&ua=fb_crawler"
|
211
|
+
else
|
212
|
+
url = "#{url}?ua=fb_crawler"
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
|
217
|
+
"#{prerender_url}#{forward_slash}#{url}"
|
218
|
+
end
|
219
|
+
|
220
|
+
|
221
|
+
def get_prerender_service_url
|
222
|
+
@options[:prerender_service_url] || ENV['PRERENDER_SERVICE_URL'] || 'http://service.prerender.io/'
|
223
|
+
end
|
224
|
+
|
225
|
+
|
226
|
+
def build_rack_response_from_prerender(prerendered_response)
|
227
|
+
response = Rack::Response.new(prerendered_response.body, prerendered_response.code, prerendered_response.header)
|
228
|
+
|
229
|
+
@options[:build_rack_response_from_prerender].call(response, prerendered_response) if @options[:build_rack_response_from_prerender]
|
230
|
+
|
231
|
+
response
|
232
|
+
end
|
233
|
+
|
234
|
+
def before_render(env)
|
235
|
+
return nil unless @options[:before_render]
|
236
|
+
|
237
|
+
cached_render = @options[:before_render].call(env)
|
238
|
+
|
239
|
+
if cached_render && cached_render.is_a?(String)
|
240
|
+
Rack::Response.new(cached_render, 200, { 'Content-Type' => 'text/html; charset=utf-8' })
|
241
|
+
elsif cached_render && cached_render.is_a?(Rack::Response)
|
242
|
+
cached_render
|
243
|
+
else
|
244
|
+
nil
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
|
249
|
+
def after_render(env, response)
|
250
|
+
return true unless @options[:after_render]
|
251
|
+
@options[:after_render].call(env, response)
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = "telvue-prerender-rails"
|
5
|
+
spec.version = "1.6.2"
|
6
|
+
spec.authors = ["Todd Hooper", "Ben Liu"]
|
7
|
+
spec.email = ["todd@prerender.io", "bliu@telvue.com"]
|
8
|
+
spec.description = %q{Rails middleware to prerender your javascript heavy pages on the fly by a phantomjs service}
|
9
|
+
spec.summary = %q{Prerender your backbone/angular/javascript rendered application on the fly when search engines crawl}
|
10
|
+
spec.homepage = "https://github.com/prerender/prerender_rails"
|
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,224 @@
|
|
1
|
+
require_relative '../test_helper'
|
2
|
+
|
3
|
+
describe Rack::Prerender 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::Prerender.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::Prerender.new(@app).call(request)
|
21
|
+
|
22
|
+
assert_equal response[2].body, ["<html></html>"]
|
23
|
+
assert_equal response[2].status, 301
|
24
|
+
assert_equal( { 'location' => 'http://google.com', '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::Prerender.new(@app).call(request)
|
32
|
+
|
33
|
+
assert_equal ["<html></html>"], response[2].body
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
it "should continue to app routes if the url is a bad url with _escaped_fragment_" do
|
38
|
+
request = Rack::MockRequest.env_for "/path?query=string?_escaped_fragment_=", "HTTP_USER_AGENT" => user
|
39
|
+
response = Rack::Prerender.new(@app).call(request)
|
40
|
+
|
41
|
+
assert_equal "", response[2]
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
it "should continue to app routes if the request is not a GET" do
|
46
|
+
request = Rack::MockRequest.env_for "/path?_escaped_fragment_=", { "HTTP_USER_AGENT" => user, "REQUEST_METHOD" => "POST" }
|
47
|
+
response = Rack::Prerender.new(@app).call(request)
|
48
|
+
|
49
|
+
assert_equal "", response[2]
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
it "should continue to app routes if user is not a bot by checking agent string" do
|
54
|
+
request = Rack::MockRequest.env_for "/", "HTTP_USER_AGENT" => user
|
55
|
+
response = Rack::Prerender.new(@app).call(request)
|
56
|
+
|
57
|
+
assert_equal "", response[2]
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
it "should continue to app routes if contains X-Prerender header" do
|
62
|
+
request = Rack::MockRequest.env_for "/path?_escaped_fragment_=", "HTTP_USER_AGENT" => user, "HTTP_X_PRERENDER" => "1"
|
63
|
+
response = Rack::Prerender.new(@app).call(request)
|
64
|
+
|
65
|
+
assert_equal "", response[2]
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
it "should continue to app routes if user is a bot, but the bot is requesting a resource file" do
|
70
|
+
request = Rack::MockRequest.env_for "/main.js?anyQueryParam=true", "HTTP_USER_AGENT" => bot
|
71
|
+
response = Rack::Prerender.new(@app).call(request)
|
72
|
+
|
73
|
+
assert_equal "", response[2]
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
it "should continue to app routes if the url is not part of the regex specific whitelist" do
|
78
|
+
request = Rack::MockRequest.env_for "/saved/search/blah?_escaped_fragment_=", "HTTP_USER_AGENT" => bot
|
79
|
+
response = Rack::Prerender.new(@app, whitelist: ['^/search', '/help']).call(request)
|
80
|
+
|
81
|
+
assert_equal "", response[2]
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
it "should set use_ssl to true for https prerender_service_url" do
|
86
|
+
@prerender = Rack::Prerender.new(@app, prerender_service_url: 'https://service.prerender.io/')
|
87
|
+
|
88
|
+
request = Rack::MockRequest.env_for "/search/things/123/page?_escaped_fragment_=", "HTTP_USER_AGENT" => bot
|
89
|
+
stub_request(:get, @prerender.build_api_url(request)).to_return(:body => "<html></html>")
|
90
|
+
response = @prerender.call(request)
|
91
|
+
|
92
|
+
assert_equal ["<html></html>"], response[2].body
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
it "should return a prerendered response if the url is part of the regex specific whitelist" do
|
97
|
+
request = Rack::MockRequest.env_for "/search/things/123/page?_escaped_fragment_=", "HTTP_USER_AGENT" => bot
|
98
|
+
stub_request(:get, @prerender.build_api_url(request)).to_return(:body => "<html></html>")
|
99
|
+
response = Rack::Prerender.new(@app, whitelist: ['^/search.*page', '/help']).call(request)
|
100
|
+
|
101
|
+
assert_equal ["<html></html>"], response[2].body
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
it "should continue to app routes if the url is part of the regex specific blacklist" do
|
106
|
+
request = Rack::MockRequest.env_for "/search/things/123/page", "HTTP_USER_AGENT" => bot
|
107
|
+
response = Rack::Prerender.new(@app, blacklist: ['^/search', '/help']).call(request)
|
108
|
+
|
109
|
+
assert_equal "", response[2]
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should continue to app routes if the hashbang url is part of the regex specific blacklist" do
|
113
|
+
request = Rack::MockRequest.env_for "?_escaped_fragment_=/search/things/123/page", "HTTP_USER_AGENT" => bot
|
114
|
+
response = Rack::Prerender.new(@app, blacklist: ['/search', '/help']).call(request)
|
115
|
+
|
116
|
+
assert_equal "", response[2]
|
117
|
+
end
|
118
|
+
|
119
|
+
it "should return a prerendered response if the url is not part of the regex specific blacklist" do
|
120
|
+
request = Rack::MockRequest.env_for "/profile/search/blah", "HTTP_USER_AGENT" => bot
|
121
|
+
stub_request(:get, @prerender.build_api_url(request)).to_return(:body => "<html></html>")
|
122
|
+
response = Rack::Prerender.new(@app, blacklist: ['^/search', '/help']).call(request)
|
123
|
+
|
124
|
+
assert_equal ["<html></html>"], response[2].body
|
125
|
+
end
|
126
|
+
|
127
|
+
|
128
|
+
it "should continue to app routes if the referer is part of the regex specific blacklist" do
|
129
|
+
request = Rack::MockRequest.env_for "/api/results", "HTTP_USER_AGENT" => bot, "HTTP_REFERER" => '/search'
|
130
|
+
response = Rack::Prerender.new(@app, blacklist: ['^/search', '/help']).call(request)
|
131
|
+
|
132
|
+
assert_equal "", response[2]
|
133
|
+
end
|
134
|
+
|
135
|
+
|
136
|
+
it "should return a prerendered response if the referer is not part of the regex specific blacklist" do
|
137
|
+
request = Rack::MockRequest.env_for "/api/results", "HTTP_USER_AGENT" => bot, "HTTP_REFERER" => '/profile/search'
|
138
|
+
stub_request(:get, @prerender.build_api_url(request)).to_return(:body => "<html></html>")
|
139
|
+
response = Rack::Prerender.new(@app, blacklist: ['^/search', '/help']).call(request)
|
140
|
+
|
141
|
+
assert_equal ["<html></html>"], response[2].body
|
142
|
+
end
|
143
|
+
|
144
|
+
|
145
|
+
it "should return a prerendered response if a string is returned from before_render" do
|
146
|
+
request = Rack::MockRequest.env_for "/", "HTTP_USER_AGENT" => bot
|
147
|
+
response = Rack::Prerender.new(@app, before_render: Proc.new do |env| '<html>cached</html>' end).call(request)
|
148
|
+
|
149
|
+
assert_equal ["<html>cached</html>"], response[2].body
|
150
|
+
end
|
151
|
+
|
152
|
+
|
153
|
+
it "should return a prerendered response if a response is returned from before_render" do
|
154
|
+
request = Rack::MockRequest.env_for "/", "HTTP_USER_AGENT" => bot
|
155
|
+
response = Rack::Prerender.new(@app, before_render: Proc.new do |env| Rack::Response.new('<html>cached2</html>', 200, { 'test' => 'test2Header'}) end).call(request)
|
156
|
+
|
157
|
+
assert_equal ["<html>cached2</html>"], response[2].body
|
158
|
+
assert_equal response[2].status, 200
|
159
|
+
assert_equal( { 'test' => 'test2Header', "Content-Length"=>"20"}, response[2].headers )
|
160
|
+
end
|
161
|
+
|
162
|
+
|
163
|
+
describe '#buildApiUrl' do
|
164
|
+
it "should build the correct api url with the default url" do
|
165
|
+
request = Rack::MockRequest.env_for "https://google.com/search?q=javascript"
|
166
|
+
ENV['PRERENDER_SERVICE_URL'] = nil
|
167
|
+
assert_equal 'http://service.prerender.io/https://google.com/search?q=javascript', @prerender.build_api_url(request)
|
168
|
+
end
|
169
|
+
|
170
|
+
|
171
|
+
it "should build the correct api url with an environment variable url" do
|
172
|
+
ENV['PRERENDER_SERVICE_URL'] = 'http://prerenderurl.com'
|
173
|
+
request = Rack::MockRequest.env_for "https://google.com/search?q=javascript"
|
174
|
+
assert_equal 'http://prerenderurl.com/https://google.com/search?q=javascript', @prerender.build_api_url(request)
|
175
|
+
ENV['PRERENDER_SERVICE_URL'] = nil
|
176
|
+
end
|
177
|
+
|
178
|
+
|
179
|
+
it "should build the correct api url with an initialization variable url" do
|
180
|
+
@prerender = Rack::Prerender.new(@app, prerender_service_url: 'http://prerenderurl.com')
|
181
|
+
request = Rack::MockRequest.env_for "https://google.com/search?q=javascript"
|
182
|
+
assert_equal 'http://prerenderurl.com/https://google.com/search?q=javascript', @prerender.build_api_url(request)
|
183
|
+
end
|
184
|
+
|
185
|
+
|
186
|
+
it "should build the correct https api url with an initialization variable url" do
|
187
|
+
@prerender = Rack::Prerender.new(@app, prerender_service_url: 'https://prerenderurl.com')
|
188
|
+
request = Rack::MockRequest.env_for "https://google.com/search?q=javascript"
|
189
|
+
assert_equal 'https://prerenderurl.com/https://google.com/search?q=javascript', @prerender.build_api_url(request)
|
190
|
+
end
|
191
|
+
|
192
|
+
|
193
|
+
# Check CF-Visitor header in order to Work behind CloudFlare with Flexible SSL (https://support.cloudflare.com/hc/en-us/articles/200170536)
|
194
|
+
it "should build the correct api url for the Cloudflare Flexible SSL support" do
|
195
|
+
request = Rack::MockRequest.env_for "http://google.com/search?q=javascript", { 'CF-VISITOR' => '"scheme":"https"'}
|
196
|
+
ENV['PRERENDER_SERVICE_URL'] = nil
|
197
|
+
assert_equal 'http://service.prerender.io/https://google.com/search?q=javascript', @prerender.build_api_url(request)
|
198
|
+
end
|
199
|
+
|
200
|
+
|
201
|
+
# Check X-Forwarded-Proto because Heroku SSL Support terminates at the load balancer
|
202
|
+
it "should build the correct api url for the Heroku SSL Addon support with single value" do
|
203
|
+
request = Rack::MockRequest.env_for "http://google.com/search?q=javascript", { 'X-FORWARDED-PROTO' => 'https'}
|
204
|
+
ENV['PRERENDER_SERVICE_URL'] = nil
|
205
|
+
assert_equal 'http://service.prerender.io/https://google.com/search?q=javascript', @prerender.build_api_url(request)
|
206
|
+
end
|
207
|
+
|
208
|
+
|
209
|
+
# Check X-Forwarded-Proto because Heroku SSL Support terminates at the load balancer
|
210
|
+
it "should build the correct api url for the Heroku SSL Addon support with double value" do
|
211
|
+
request = Rack::MockRequest.env_for "http://google.com/search?q=javascript", { 'X-FORWARDED-PROTO' => 'https,http'}
|
212
|
+
ENV['PRERENDER_SERVICE_URL'] = nil
|
213
|
+
assert_equal 'http://service.prerender.io/https://google.com/search?q=javascript', @prerender.build_api_url(request)
|
214
|
+
end
|
215
|
+
|
216
|
+
it "should build the correct api url for the protocol option" do
|
217
|
+
@prerender = Rack::Prerender.new(@app, protocol: 'https')
|
218
|
+
request = Rack::MockRequest.env_for "http://google.com/search?q=javascript"
|
219
|
+
ENV['PRERENDER_SERVICE_URL'] = nil
|
220
|
+
assert_equal 'http://service.prerender.io/https://google.com/search?q=javascript', @prerender.build_api_url(request)
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: telvue-prerender-rails
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.6.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Todd Hooper
|
8
|
+
- Ben Liu
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2019-10-29 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rack
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ">="
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '0'
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '0'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: activesupport
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '0'
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: bundler
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - "~>"
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '1.3'
|
49
|
+
type: :development
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - "~>"
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '1.3'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: rake
|
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'
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: webmock
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
description: Rails middleware to prerender your javascript heavy pages on the fly
|
85
|
+
by a phantomjs service
|
86
|
+
email:
|
87
|
+
- todd@prerender.io
|
88
|
+
- bliu@telvue.com
|
89
|
+
executables: []
|
90
|
+
extensions: []
|
91
|
+
extra_rdoc_files: []
|
92
|
+
files:
|
93
|
+
- ".gitignore"
|
94
|
+
- ".travis.yml"
|
95
|
+
- Gemfile
|
96
|
+
- LICENSE.txt
|
97
|
+
- README.md
|
98
|
+
- Rakefile
|
99
|
+
- lib/prerender_rails.rb
|
100
|
+
- prerender_rails.gemspec
|
101
|
+
- test/lib/prerender_rails.rb
|
102
|
+
- test/test_helper.rb
|
103
|
+
homepage: https://github.com/prerender/prerender_rails
|
104
|
+
licenses:
|
105
|
+
- MIT
|
106
|
+
metadata: {}
|
107
|
+
post_install_message:
|
108
|
+
rdoc_options: []
|
109
|
+
require_paths:
|
110
|
+
- lib
|
111
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
112
|
+
requirements:
|
113
|
+
- - ">="
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: '0'
|
116
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
117
|
+
requirements:
|
118
|
+
- - ">="
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: '0'
|
121
|
+
requirements: []
|
122
|
+
rubygems_version: 3.0.6
|
123
|
+
signing_key:
|
124
|
+
specification_version: 4
|
125
|
+
summary: Prerender your backbone/angular/javascript rendered application on the fly
|
126
|
+
when search engines crawl
|
127
|
+
test_files:
|
128
|
+
- test/lib/prerender_rails.rb
|
129
|
+
- test/test_helper.rb
|