render_static 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "selenium-webdriver"
4
+ gem "headless"
5
+
6
+ group :development do
7
+ gem "rspec"
8
+ gem "shoulda", ">= 0"
9
+ gem "rdoc", "~> 3.12"
10
+ gem "bundler"
11
+ gem "jeweler", "~> 1.8.4"
12
+ end
@@ -0,0 +1,64 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ activesupport (3.2.13)
5
+ i18n (= 0.6.1)
6
+ multi_json (~> 1.0)
7
+ addressable (2.3.3)
8
+ bourne (1.4.0)
9
+ mocha (~> 0.13.2)
10
+ childprocess (0.3.5)
11
+ ffi (~> 1.0, >= 1.0.6)
12
+ diff-lcs (1.1.3)
13
+ ffi (1.1.5)
14
+ git (1.2.5)
15
+ headless (1.0.1)
16
+ i18n (0.6.1)
17
+ jeweler (1.8.4)
18
+ bundler (~> 1.0)
19
+ git (>= 1.2.5)
20
+ rake
21
+ rdoc
22
+ json (1.7.7)
23
+ libwebsocket (0.1.5)
24
+ addressable
25
+ metaclass (0.0.1)
26
+ mocha (0.13.3)
27
+ metaclass (~> 0.0.1)
28
+ multi_json (1.7.2)
29
+ rake (10.0.4)
30
+ rdoc (3.12.2)
31
+ json (~> 1.4)
32
+ rspec (2.12.0)
33
+ rspec-core (~> 2.12.0)
34
+ rspec-expectations (~> 2.12.0)
35
+ rspec-mocks (~> 2.12.0)
36
+ rspec-core (2.12.2)
37
+ rspec-expectations (2.12.1)
38
+ diff-lcs (~> 1.1.3)
39
+ rspec-mocks (2.12.1)
40
+ rubyzip (0.9.9)
41
+ selenium-webdriver (2.25.0)
42
+ childprocess (>= 0.2.5)
43
+ libwebsocket (~> 0.1.3)
44
+ multi_json (~> 1.0)
45
+ rubyzip
46
+ shoulda (3.4.0)
47
+ shoulda-context (~> 1.0, >= 1.0.1)
48
+ shoulda-matchers (~> 1.0, >= 1.4.1)
49
+ shoulda-context (1.1.1)
50
+ shoulda-matchers (1.5.6)
51
+ activesupport (>= 3.0.0)
52
+ bourne (~> 1.3)
53
+
54
+ PLATFORMS
55
+ ruby
56
+
57
+ DEPENDENCIES
58
+ bundler
59
+ headless
60
+ jeweler (~> 1.8.4)
61
+ rdoc (~> 3.12)
62
+ rspec
63
+ selenium-webdriver
64
+ shoulda
@@ -0,0 +1,22 @@
1
+ (The MIT License)
2
+
3
+ Copyright (c) 2013 Herval Freire
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 NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,48 @@
1
+ = render_static
2
+
3
+ * http://github.com/herval/render_static
4
+
5
+ == DESCRIPTION:
6
+
7
+ render_static allows you to make your single-page apps (Backbone, Angular, etc) built on Rails SEO-friendly. It works by injecting a small rack middleware that will render pages as plain html, when the requester has one of the following user-agent headers:
8
+
9
+ Googlebot
10
+ Googlebot-Mobile
11
+ AdsBot-Google
12
+ Mozilla/5.0 (compatible; Ask Jeeves/Teoma; +http://about.ask.com/en/docs/about/webmasters.shtml)
13
+ Baiduspider
14
+ Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)
15
+
16
+ Please note that, in order for this to work, you need more than one thread/process of your webserver running, as the middleware will effectively make a second call back to your own app and render the content, streaming it back to the requester (crawler/bot).
17
+
18
+
19
+ == TO DO/PROBLEMS:
20
+
21
+ * Caching support
22
+ * Support for other drivers
23
+ * Log what's going on
24
+
25
+ == INSTALL:
26
+
27
+ Add the following to your Gemfile:
28
+
29
+ gem 'render_static'
30
+
31
+ In order to serve a set of routes as a single-page app, your routes.rb usually contains a catch-all route that will direct /* or /something/* to the same index.html file (the root of your js app). In order to allow the render_static middleware to intercept the right routes, you need to add this to your app initialization:
32
+
33
+ render_static.rb
34
+ RenderStatic::Middleware.base_path = "/" # replace / for whichever path matches your app's index.html
35
+
36
+ render_static will, by default, try to use Firefox as the driver to navigate to content. It will also try to run headlessly - if on a *nix box, you should install xvfb for that to work properly.
37
+
38
+ And you're done! The middleware will only try to static-render requests made by bots AND that would render application/html content.
39
+
40
+ You can test that everything is working as expected by CURLing:
41
+
42
+ # this will render the usual blank slate client-side apps serve
43
+ curl http://localhost:3000/some/backbone/route
44
+
45
+ # this will render a static representation of your page (just like it would look like in a browser)
46
+ curl -A "Googlebot" http://localhost:3000/some/backbone/route
47
+
48
+
@@ -0,0 +1,43 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "render_static"
18
+ gem.homepage = "http://github.com/herval/render_static"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{ render_static allows you to make your single-page apps (Backbone, Angular, etc) built on Rails SEO-friendly. }
21
+ gem.description = %Q{ render_static allows you to make your single-page apps (Backbone, Angular, etc) built on Rails SEO-friendly. It works by injecting a small rack middleware that will render pages as plain html, when the requester is one of the most common crawlers/bots out there (Google, Yahoo Baidu and Bing) }
22
+ gem.email = "hervalfreire@gmail.com"
23
+ gem.authors = ["herval"]
24
+ # dependencies defined in Gemfile
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
27
+
28
+ require 'rspec/core/rake_task'
29
+
30
+ RSpec::Core::RakeTask.new(:spec)
31
+
32
+ task :default => :spec
33
+
34
+
35
+ require 'rdoc/task'
36
+ Rake::RDocTask.new do |rdoc|
37
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
38
+
39
+ rdoc.rdoc_dir = 'rdoc'
40
+ rdoc.title = "render_static #{version}"
41
+ rdoc.rdoc_files.include('README*')
42
+ rdoc.rdoc_files.include('lib/**/*.rb')
43
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.0
@@ -0,0 +1 @@
1
+ require 'render_static/middleware'
@@ -0,0 +1,49 @@
1
+ require 'render_static/rails/railtie' if defined?(Rails)
2
+ require 'render_static/renderer'
3
+
4
+ module RenderStatic
5
+ class NotSeoFriendly < Exception
6
+ end
7
+
8
+ class Middleware
9
+ class << self
10
+ attr_accessor :base_path
11
+ end
12
+
13
+ def initialize(app)
14
+ @app = app
15
+ end
16
+
17
+ def call(env)
18
+ if will_render?(env)
19
+ RenderStatic::Renderer.render(env)
20
+ else
21
+ @app.call(env)
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def will_render?(env)
28
+ is_bot?(env) && is_renderable?(env)
29
+ end
30
+
31
+ def is_bot?(env)
32
+ [
33
+ "Googlebot",
34
+ "Googlebot-Mobile",
35
+ "AdsBot-Google",
36
+ "Mozilla/5.0 (compatible; Ask Jeeves/Teoma; +http://about.ask.com/en/docs/about/webmasters.shtml)",
37
+ "Baiduspider",
38
+ "Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)",
39
+ ].include?(env["HTTP_USER_AGENT"])
40
+ end
41
+
42
+ def is_renderable?(env)
43
+ path = env["PATH_INFO"]
44
+ content_type = path.index(".") && path.split(".").last
45
+
46
+ path.start_with?(self.class.base_path) & [nil, "htm", "html"].include?(content_type) && env["REQUEST_METHOD"] == "GET"
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,9 @@
1
+ require 'rails'
2
+
3
+ module RenderStatic
4
+ class Railtie < Rails::Railtie
5
+ initializer "render_static.insert_middleware" do |app|
6
+ app.config.middleware.use RenderStatic::Middleware
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,16 @@
1
+ require 'selenium-webdriver'
2
+ require 'headless'
3
+
4
+ module RenderStatic
5
+ class Renderer
6
+
7
+ def self.render(env)
8
+ Headless.ly do
9
+ browser = Selenium::WebDriver.for(:firefox)
10
+ path = "#{env["rack.url_scheme"]}://#{env["HTTP_HOST"]}#{env["REQUEST_PATH"]}"
11
+ browser.navigate.to(path)
12
+ [200, { "Content-Type" => "text/html" }, [browser.page_source]] # TODO status code not supported by selenium
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,70 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "render_static"
8
+ s.version = "0.0.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["herval"]
12
+ s.date = "2013-05-08"
13
+ s.description = " render_static allows you to make your single-page apps (Backbone, Angular, etc) built on Rails SEO-friendly. It works by injecting a small rack middleware that will render pages as plain html, when the requester is one of the most common crawlers/bots out there (Google, Yahoo Baidu and Bing) "
14
+ s.email = "hervalfreire@gmail.com"
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README.txt"
18
+ ]
19
+ s.files = [
20
+ "Gemfile",
21
+ "Gemfile.lock",
22
+ "LICENSE.txt",
23
+ "README.txt",
24
+ "Rakefile",
25
+ "VERSION",
26
+ "lib/render_static.rb",
27
+ "lib/render_static/middleware.rb",
28
+ "lib/render_static/rails/railtie.rb",
29
+ "lib/render_static/renderer.rb",
30
+ "render_static.gemspec",
31
+ "spec/middleware_spec.rb",
32
+ "spec/renderer_spec.rb"
33
+ ]
34
+ s.homepage = "http://github.com/herval/render_static"
35
+ s.licenses = ["MIT"]
36
+ s.require_paths = ["lib"]
37
+ s.rubygems_version = "1.8.15"
38
+ s.summary = "render_static allows you to make your single-page apps (Backbone, Angular, etc) built on Rails SEO-friendly."
39
+
40
+ if s.respond_to? :specification_version then
41
+ s.specification_version = 3
42
+
43
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
44
+ s.add_runtime_dependency(%q<selenium-webdriver>, [">= 0"])
45
+ s.add_runtime_dependency(%q<headless>, [">= 0"])
46
+ s.add_development_dependency(%q<rspec>, [">= 0"])
47
+ s.add_development_dependency(%q<shoulda>, [">= 0"])
48
+ s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
49
+ s.add_development_dependency(%q<bundler>, [">= 0"])
50
+ s.add_development_dependency(%q<jeweler>, ["~> 1.8.4"])
51
+ else
52
+ s.add_dependency(%q<selenium-webdriver>, [">= 0"])
53
+ s.add_dependency(%q<headless>, [">= 0"])
54
+ s.add_dependency(%q<rspec>, [">= 0"])
55
+ s.add_dependency(%q<shoulda>, [">= 0"])
56
+ s.add_dependency(%q<rdoc>, ["~> 3.12"])
57
+ s.add_dependency(%q<bundler>, [">= 0"])
58
+ s.add_dependency(%q<jeweler>, ["~> 1.8.4"])
59
+ end
60
+ else
61
+ s.add_dependency(%q<selenium-webdriver>, [">= 0"])
62
+ s.add_dependency(%q<headless>, [">= 0"])
63
+ s.add_dependency(%q<rspec>, [">= 0"])
64
+ s.add_dependency(%q<shoulda>, [">= 0"])
65
+ s.add_dependency(%q<rdoc>, ["~> 3.12"])
66
+ s.add_dependency(%q<bundler>, [">= 0"])
67
+ s.add_dependency(%q<jeweler>, ["~> 1.8.4"])
68
+ end
69
+ end
70
+
@@ -0,0 +1,69 @@
1
+ require "render_static/middleware"
2
+
3
+ describe RenderStatic::Middleware do
4
+ let(:app) { stub }
5
+ let(:middleware) { RenderStatic::Middleware.new(app) }
6
+ let(:request) {
7
+ {
8
+ "PATH_INFO" => "/somewhere/",
9
+ "REQUEST_METHOD" => "GET"
10
+ }
11
+ }
12
+
13
+ before do
14
+ RenderStatic::Middleware.base_path = "/somewhere/"
15
+ end
16
+
17
+ describe "a non-bot user agent" do
18
+ it "passes-through" do
19
+ env = request.merge("HTTP_USER_AGENT" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_3) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.65 Safari/537.31")
20
+
21
+ app.should_receive(:call).with(env)
22
+ RenderStatic::Renderer.should_not_receive(:render)
23
+
24
+ middleware.call(env)
25
+ end
26
+ end
27
+
28
+ describe "a bot user agent" do
29
+ it "does not render if path doesn't match" do
30
+ env = request.merge("HTTP_USER_AGENT" => "Googlebot", "PATH_INFO" => "/somewhere_else/a.html")
31
+
32
+ app.should_receive(:call).with(env)
33
+ RenderStatic::Renderer.should_not_receive(:render)
34
+ middleware.call(env)
35
+ end
36
+
37
+ it "requests the same url and renders it" do
38
+ env = request.merge("HTTP_USER_AGENT" => "Googlebot", "PATH_INFO" => "/somewhere/index.html")
39
+
40
+ app.should_not_receive(:call)
41
+ RenderStatic::Renderer.should_receive(:render).with(env)
42
+ middleware.call(env)
43
+ end
44
+
45
+ it "renders content without an explicit type" do
46
+ env = request.merge("HTTP_USER_AGENT" => "Googlebot", "PATH_INFO" => "/somewhere/index")
47
+
48
+ app.should_not_receive(:call)
49
+ RenderStatic::Renderer.should_receive(:render).with(env)
50
+ middleware.call(env)
51
+ end
52
+
53
+ it "only renders GETs" do
54
+ env = request.merge("REQUEST_METHOD" => "POST", "PATH_INFO" => "/somewhere/index")
55
+
56
+ app.should_receive(:call)
57
+ RenderStatic::Renderer.should_not_receive(:render)
58
+ middleware.call(env)
59
+ end
60
+
61
+ it "does not render non-html content" do
62
+ env = request.merge("HTTP_USER_AGENT" => "Googlebot", "PATH_INFO" => "/somewhere/a.js")
63
+
64
+ app.should_receive(:call).with(env)
65
+ RenderStatic::Renderer.should_not_receive(:render)
66
+ middleware.call(env)
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,27 @@
1
+ require "render_static/renderer"
2
+
3
+ describe RenderStatic::Renderer do
4
+ class Headless
5
+ def self.ly &block
6
+ yield
7
+ end
8
+ end
9
+
10
+ describe ".render" do
11
+ it "requests the content" do
12
+ env = { "HTTP_HOST" => "localhost:3000", "REQUEST_PATH" => "/abc", "rack.url_scheme" => "https" }
13
+
14
+ navigate = stub
15
+ browser = stub(navigate: navigate, page_source: "loaded page")
16
+ Selenium::WebDriver.should_receive(:for).with(:firefox) { browser }
17
+
18
+ navigate.should_receive(:to).with("https://localhost:3000/abc")
19
+
20
+ response = RenderStatic::Renderer.render(env)
21
+
22
+ response[0].should == 200
23
+ response[1].should == {"Content-Type"=>"text/html"}
24
+ response[2].should == ["loaded page"]
25
+ end
26
+ end
27
+ end
metadata ADDED
@@ -0,0 +1,144 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: render_static
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - herval
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-05-08 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: selenium-webdriver
16
+ requirement: &2165685120 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *2165685120
25
+ - !ruby/object:Gem::Dependency
26
+ name: headless
27
+ requirement: &2165684400 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *2165684400
36
+ - !ruby/object:Gem::Dependency
37
+ name: rspec
38
+ requirement: &2165683640 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *2165683640
47
+ - !ruby/object:Gem::Dependency
48
+ name: shoulda
49
+ requirement: &2165721660 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *2165721660
58
+ - !ruby/object:Gem::Dependency
59
+ name: rdoc
60
+ requirement: &2165720560 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ~>
64
+ - !ruby/object:Gem::Version
65
+ version: '3.12'
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *2165720560
69
+ - !ruby/object:Gem::Dependency
70
+ name: bundler
71
+ requirement: &2165718900 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: *2165718900
80
+ - !ruby/object:Gem::Dependency
81
+ name: jeweler
82
+ requirement: &2165718360 !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ~>
86
+ - !ruby/object:Gem::Version
87
+ version: 1.8.4
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: *2165718360
91
+ description: ! ' render_static allows you to make your single-page apps (Backbone,
92
+ Angular, etc) built on Rails SEO-friendly. It works by injecting a small rack middleware
93
+ that will render pages as plain html, when the requester is one of the most common
94
+ crawlers/bots out there (Google, Yahoo Baidu and Bing) '
95
+ email: hervalfreire@gmail.com
96
+ executables: []
97
+ extensions: []
98
+ extra_rdoc_files:
99
+ - LICENSE.txt
100
+ - README.txt
101
+ files:
102
+ - Gemfile
103
+ - Gemfile.lock
104
+ - LICENSE.txt
105
+ - README.txt
106
+ - Rakefile
107
+ - VERSION
108
+ - lib/render_static.rb
109
+ - lib/render_static/middleware.rb
110
+ - lib/render_static/rails/railtie.rb
111
+ - lib/render_static/renderer.rb
112
+ - render_static.gemspec
113
+ - spec/middleware_spec.rb
114
+ - spec/renderer_spec.rb
115
+ homepage: http://github.com/herval/render_static
116
+ licenses:
117
+ - MIT
118
+ post_install_message:
119
+ rdoc_options: []
120
+ require_paths:
121
+ - lib
122
+ required_ruby_version: !ruby/object:Gem::Requirement
123
+ none: false
124
+ requirements:
125
+ - - ! '>='
126
+ - !ruby/object:Gem::Version
127
+ version: '0'
128
+ segments:
129
+ - 0
130
+ hash: 1724286605502739496
131
+ required_rubygems_version: !ruby/object:Gem::Requirement
132
+ none: false
133
+ requirements:
134
+ - - ! '>='
135
+ - !ruby/object:Gem::Version
136
+ version: '0'
137
+ requirements: []
138
+ rubyforge_project:
139
+ rubygems_version: 1.8.15
140
+ signing_key:
141
+ specification_version: 3
142
+ summary: render_static allows you to make your single-page apps (Backbone, Angular,
143
+ etc) built on Rails SEO-friendly.
144
+ test_files: []