rack-hard-copy 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source "http://rubygems.org"
2
+ gem 'rack'
3
+ gem 'mime-types'
4
+
5
+ group :development do
6
+ gem 'rake'
7
+ gem 'pry'
8
+ end
9
+
@@ -0,0 +1,106 @@
1
+ # Rack::Hard::Copy
2
+
3
+ This is a simple middleware to save and load static copies of
4
+ rendered pages. So in other words, this is simple static cache
5
+ which saves in a format that can be used as a backup, or to be
6
+ loaded via a simple http server (e.g. Nginx).
7
+
8
+ ### Installation
9
+
10
+ echo "gem 'rack-static-copy'" >> Gemfile
11
+ bundle
12
+
13
+ ### Usage - Basic
14
+
15
+ # config.ru
16
+ require 'rack/hard/copy'
17
+ use Rack::Hard::Copy, :store => "/tmp/hard_copy",
18
+ :ignores => [ "search", "js", "png", "gif" ],
19
+ :timeout => 600
20
+
21
+ run App
22
+
23
+
24
+ > Note: This should probably be the last middleware loaded.
25
+
26
+ #### Options
27
+
28
+ * `:store` - path to file store
29
+ * `:ignores` - `Array` of keywords to be ignored
30
+ * `:timeout` - `Fixnum` as seconds to expire stored files
31
+ * `:headers` - `Boolean` can be set to `true` to insert custom http headers
32
+ * `X-Rack-Hard-Save => (true|false)`
33
+ * `X-Rack-Hard-Load => (true|false)`
34
+
35
+ ### Another Use
36
+
37
+ Additionally, you can use Rack::Hard::Save alone to simple create a copy
38
+ of your pages in rendered html format (other formats are supported).
39
+
40
+ Exmaple:
41
+
42
+ # config.ru
43
+ require 'rack/hard/save'
44
+ use Rack::Hard::Save, :store => "/path/to/nginx/root/static",
45
+ :ignores => [ "search", "js", "png", "gif" ],
46
+ :timeout => 600
47
+
48
+ run App
49
+
50
+
51
+ From there you can use Nginx to first check and see if the static pages exist
52
+ and if not, load your app normally, which will in turn generate the static
53
+ content.
54
+
55
+ Very large speed enhancments should be see doing this.
56
+
57
+
58
+ ##### Nginx example config:
59
+
60
+ # /etc/nginx/sites-enabled/my_site.conf
61
+ server {
62
+ #listen 80; ## listen for ipv4; this line is default and implied
63
+ #listen [::]:80 default ipv6only=on; ## listen for ipv6
64
+
65
+ root /path/to/nginx/root;
66
+
67
+ server_name localhost;
68
+
69
+ location / {
70
+ try_files /static$uri/index.html /static$uri $uri @app;
71
+ }
72
+
73
+ location @app {
74
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
75
+ proxy_set_header Host $http_host;
76
+ proxy_pass http://0.0.0.0:8080;
77
+ }
78
+ }
79
+
80
+ The key to above lies in `/static$uri/index.html` and `/static$uri`.
81
+
82
+ Rack::Hard::Copy turns `/foo` in to `/foo/index.html`, while simply
83
+ copying anything with an extension. The above `try_files` will look
84
+ for this pattner in your choosen root and store location.
85
+
86
+ ### Load
87
+
88
+ You can also load static files, on your own. This might be good for
89
+ pregenerating large sitemaps, error pages, etc. Why? Well, it might
90
+ be nice to dynamically generate a page, sitemaps in particular, but
91
+ store them staticly to reduce load on your host when they're rather
92
+ large.
93
+
94
+ # config.ru
95
+ require 'rack/hard/load'
96
+ use Rack::Hard::Load, :store => "./hard_copy",
97
+ :timeout => false
98
+
99
+
100
+ Pregeneration examples:
101
+
102
+ $ curl http://yoursite.com/sitemap.xml > ./hard_copy/sitemap.xml
103
+ $ curl http://yoursite.com/main.css > ./hard_copy/main.css
104
+
105
+ > These could be automated as part of your startup.
106
+
@@ -0,0 +1,37 @@
1
+ require ::File.join(::File.dirname(__FILE__), "util")
2
+ require ::File.join(::File.dirname(__FILE__), "save")
3
+ require ::File.join(::File.dirname(__FILE__), "load")
4
+ module Rack
5
+ class Hard
6
+ class Copy
7
+ include Util
8
+ autoload :VERSION, ::File.join(::File.dirname(__FILE__), "version")
9
+ def initialize(app, opts={})
10
+ @app = app
11
+ opts[:store] ||= "./static"
12
+ opts[:ignores] ||= []
13
+ opts[:headers] ||= false
14
+ opts[:timeout] ||= 600
15
+ @options = opts
16
+
17
+ make_dir(@options[:store])
18
+ end
19
+
20
+ def call(env)
21
+ return @app.call(env) if ignored?(@options[:ignores], env["PATH_INFO"].to_s)
22
+ return @app.call(env) unless env["REQUEST_METHOD"] == "GET"
23
+
24
+ logger = env['rack.logger']||nil
25
+ path = generate_path_from(@options[:store], env['PATH_INFO'].to_s)
26
+
27
+ if @options[:timeout] === false || expired?(@options[:timeout], path)
28
+ logger.warn "Rack::Hard::Copy: Warning, Copy without timeout is just a Save." if @options[:timeout] === false
29
+ return Rack::Hard::Save.new(@app, @options).call(env)
30
+ else
31
+ return Rack::Hard::Load.new(@app, @options).call(env)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+
@@ -0,0 +1,40 @@
1
+ require File.join(File.dirname(__FILE__), "util")
2
+ module Rack
3
+ class Hard
4
+ class Load
5
+ include Util
6
+ def initialize(app, opts={})
7
+ @app = app
8
+ setup_variables(opts)
9
+ end
10
+
11
+ def call(env)
12
+ return @app.call(env) if ignored?(@ignores, env["PATH_INFO"].to_s)
13
+ return @app.call(env) unless env["REQUEST_METHOD"] == "GET"
14
+
15
+ logger = env['rack.logger']||nil
16
+ path = generate_path_from(@store, env['PATH_INFO'].to_s)
17
+
18
+ if ::File.exists?(path) && !ignored?(@ignores, path) && (@timeout === false || !expired?(@timeout, path))
19
+ logger.info "Rack::Hard::Load loading: #{path}" rescue nil
20
+ begin
21
+ status = 200
22
+ headers = http_headers(env)
23
+ headers['X-Rack-Hard-Load'] = 'true' if @headers
24
+ response = [ ::File.read(path) ]
25
+ rescue => e
26
+ logger.error "Rack::Hard::Load error creating: #{path}\n#{e}\n#{e.backtrace.join("\n")}" rescue nil
27
+ status, headers, response = @app.call(env)
28
+ headers['X-Rack-Hard-Load'] = "error" if @headers
29
+ end
30
+ else
31
+ logger.info "Rack::Hard::Load passing" rescue nil
32
+ status, headers, response = @app.call(env)
33
+ headers['X-Rack-Hard-Load'] = "false" if @headers
34
+ end
35
+ return Rack::Response.new(response, status, headers)
36
+ end
37
+ end
38
+ end
39
+ end
40
+
@@ -0,0 +1,46 @@
1
+ require ::File.join(::File.dirname(__FILE__), "util")
2
+ module Rack
3
+ class Hard
4
+ class Save
5
+ include Util
6
+ def initialize(app, opts={})
7
+ @app = app
8
+ setup_variables(opts)
9
+ end
10
+
11
+ def call(env)
12
+ return @app.call(env) if ignored?(@ignores, env["PATH_INFO"].to_s)
13
+ return @app.call(env) unless env["REQUEST_METHOD"] == "GET"
14
+
15
+ logger = env['rack.logger']||nil
16
+ path = generate_path_from(@store, env['PATH_INFO'].to_s)
17
+
18
+ status, headers, response = @app.call(env)
19
+
20
+ if (@timeout === false || expired?(@timeout, path)) && !ignored?(@ignores, path) && status == 200
21
+ begin
22
+ make_dir(::File.dirname(path))
23
+ create(path, response.first)
24
+ headers['X-Rack-Hard-Save'] = 'true' if @headers
25
+ logger.info "Rack::Hard::Save creating: #{path}" rescue nil
26
+ rescue => e
27
+ headers['X-Rack-Hard-Save'] = 'error' if @headers
28
+ logger.error "Rack::Hard::Save error creating: #{path}\n#{e}\n#{e.backtrace.join("\n")}" rescue nil
29
+ end
30
+ end
31
+
32
+ headers['X-Rack-Hard-Save'] ||= 'false' if @headers
33
+
34
+ return Rack::Response.new(response, status, headers)
35
+ end
36
+
37
+ private
38
+ def create(f, body)
39
+ ::File.open(f, ::File::WRONLY|::File::TRUNC|::File::CREAT, 0664) do |file|
40
+ file.flock(::File::LOCK_EX)
41
+ file.write(body)
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,46 @@
1
+ require 'fileutils'
2
+ require 'mime/types'
3
+
4
+ module Rack
5
+ class Hard
6
+ module Util
7
+ def setup_variables(opts={})
8
+ @store ||= opts[:store] || "./static"
9
+ @ignores ||= opts[:ignores] || []
10
+ @headers ||= opts[:headers] || false
11
+ @timeout ||= opts[:timeout] || false
12
+ make_dir(@store)
13
+ end
14
+
15
+ def ignored?(ignores, path)
16
+ ignores.each { |ignore| return true if path.end_with?(ignore) } unless ignores.empty?
17
+ return false
18
+ end
19
+
20
+ def generate_path_from store, path_info
21
+ path = ::File.join(store, path_info)
22
+ return ( MIME::Types.type_for("try."+path.split("/").last.split(".").last).empty? ? path.gsub(/\/$/, '')+"/index.html" : path )
23
+ end
24
+
25
+ def http_headers env
26
+ http_headers = {}
27
+ env.select { |k,v| k.start_with?("HTTP_") }.each do |k,v|
28
+ http_headers[k.gsub(/^HTTP_/, '')] = v
29
+ end
30
+ http_headers
31
+ end
32
+
33
+ def expired? timeout, path
34
+ return false if timeout === false
35
+ return true if timeout === true
36
+ return true unless ::File.exists?(path)
37
+ (::File.mtime(path)+timeout) < Time.now
38
+ end
39
+
40
+ def make_dir path
41
+ FileUtils.mkdir_p(path) unless ::File.directory?(path)
42
+ end
43
+ end
44
+ end
45
+ end
46
+
@@ -0,0 +1,5 @@
1
+ module Rack
2
+ class Hard::Copy
3
+ VERSION = "0.0.3"
4
+ end
5
+ end
metadata ADDED
@@ -0,0 +1,86 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-hard-copy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Joshua Mervine
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-04-08 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rack
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 1.5.2
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 1.5.2
30
+ - !ruby/object:Gem::Dependency
31
+ name: mime-types
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '1.22'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '1.22'
46
+ description: Rack Middle to creating static copies of rendered endpoints and reload
47
+ them. It equates to a static file cache with other possible uses. See rubyops.net/gems/rack-hard-copy
48
+ for details.
49
+ email:
50
+ - joshua@mervine.net
51
+ executables: []
52
+ extensions: []
53
+ extra_rdoc_files: []
54
+ files:
55
+ - lib/rack/hard/save.rb
56
+ - lib/rack/hard/copy.rb
57
+ - lib/rack/hard/util.rb
58
+ - lib/rack/hard/version.rb
59
+ - lib/rack/hard/load.rb
60
+ - README.md
61
+ - Gemfile
62
+ homepage: http://www.rubyops.net/gems/rack-hard-copy
63
+ licenses: []
64
+ post_install_message:
65
+ rdoc_options: []
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ! '>='
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ none: false
76
+ requirements:
77
+ - - ! '>='
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ requirements: []
81
+ rubyforge_project:
82
+ rubygems_version: 1.8.25
83
+ signing_key:
84
+ specification_version: 3
85
+ summary: Rack Middle to creating static copies of rendered endpoints and reload them.
86
+ test_files: []