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 +9 -0
- data/README.md +106 -0
- data/lib/rack/hard/copy.rb +37 -0
- data/lib/rack/hard/load.rb +40 -0
- data/lib/rack/hard/save.rb +46 -0
- data/lib/rack/hard/util.rb +46 -0
- data/lib/rack/hard/version.rb +5 -0
- metadata +86 -0
data/Gemfile
ADDED
data/README.md
ADDED
@@ -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
|
+
|
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: []
|