rack-hard-copy 0.0.3

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.
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: []