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