dassets 0.14.1 → 0.15.0
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.
- checksums.yaml +7 -7
- data/.ruby-version +1 -0
- data/Gemfile +3 -1
- data/README.md +15 -17
- data/dassets.gemspec +7 -6
- data/lib/dassets.rb +54 -11
- data/lib/dassets/asset_file.rb +14 -12
- data/lib/dassets/cache.rb +27 -33
- data/lib/dassets/config.rb +55 -47
- data/lib/dassets/engine.rb +11 -23
- data/lib/dassets/file_store.rb +27 -27
- data/lib/dassets/server.rb +27 -28
- data/lib/dassets/server/request.rb +43 -41
- data/lib/dassets/server/response.rb +93 -81
- data/lib/dassets/source.rb +13 -8
- data/lib/dassets/source_file.rb +100 -82
- data/lib/dassets/source_proxy.rb +32 -16
- data/lib/dassets/version.rb +3 -1
- data/test/helper.rb +18 -24
- data/test/support/app.rb +3 -5
- data/test/support/factory.rb +1 -2
- data/test/support/{public/nested/file3-d41d8cd98f00b204e9800998ecf8427e.txt → linked_source_files/linked_file.txt} +0 -0
- data/test/support/source_files/linked +1 -0
- data/test/support/source_files/linked_file2.txt +1 -0
- data/test/system/rack_tests.rb +55 -59
- data/test/unit/asset_file_tests.rb +64 -60
- data/test/unit/cache_tests.rb +14 -35
- data/test/unit/config_tests.rb +65 -45
- data/test/unit/dassets_tests.rb +41 -23
- data/test/unit/engine_tests.rb +7 -43
- data/test/unit/file_store_tests.rb +42 -31
- data/test/unit/server/request_tests.rb +48 -53
- data/test/unit/server/response_tests.rb +79 -81
- data/test/unit/server_tests.rb +3 -9
- data/test/unit/source_file_tests.rb +73 -72
- data/test/unit/source_proxy_tests.rb +78 -89
- data/test/unit/source_tests.rb +58 -50
- metadata +78 -70
- data/test/support/public/file2-9bbe1047cffbb590f59e0e5aeff46ae4.txt +0 -1
- data/test/support/public/grumpy_cat-b0d1f399a916f7a25c4c0f693c619013.jpg +0 -0
- data/test/support/public/nested/a-thing.txt-7413d18f2eba9c695a880aff67fde135.no-use +0 -4
data/lib/dassets/engine.rb
CHANGED
@@ -1,31 +1,19 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
module Dassets; end
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
def initialize(opts=nil)
|
8
|
-
@opts = opts || {}
|
9
|
-
end
|
10
|
-
|
11
|
-
def ext(input_ext)
|
12
|
-
raise NotImplementedError
|
13
|
-
end
|
14
|
-
|
15
|
-
def compile(input)
|
16
|
-
raise NotImplementedError
|
17
|
-
end
|
5
|
+
class Dassets::Engine
|
6
|
+
attr_reader :opts
|
18
7
|
|
8
|
+
def initialize(opts = nil)
|
9
|
+
@opts = opts || {}
|
19
10
|
end
|
20
11
|
|
21
|
-
|
22
|
-
|
23
|
-
input_ext
|
24
|
-
end
|
25
|
-
|
26
|
-
def compile(input)
|
27
|
-
input
|
28
|
-
end
|
12
|
+
def ext(input_ext)
|
13
|
+
raise NotImplementedError
|
29
14
|
end
|
30
15
|
|
16
|
+
def compile(input)
|
17
|
+
raise NotImplementedError
|
18
|
+
end
|
31
19
|
end
|
data/lib/dassets/file_store.rb
CHANGED
@@ -1,38 +1,38 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require "thread"
|
4
4
|
|
5
|
-
|
6
|
-
attr_reader :root
|
5
|
+
module Dassets; end
|
7
6
|
|
8
|
-
|
9
|
-
|
10
|
-
@save_mutex = ::Mutex.new
|
11
|
-
end
|
7
|
+
class Dassets::FileStore
|
8
|
+
attr_reader :root
|
12
9
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
File.open(path, "w"){ |f| f.write(block.call) }
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
10
|
+
def initialize(root)
|
11
|
+
@root = root
|
12
|
+
@save_mutex = ::Mutex.new
|
13
|
+
end
|
21
14
|
|
22
|
-
|
23
|
-
|
15
|
+
def save(url_path, &block)
|
16
|
+
@save_mutex.synchronize do
|
17
|
+
store_path(url_path).tap { |path|
|
18
|
+
FileUtils.mkdir_p(File.dirname(path))
|
19
|
+
File.open(path, "w") { |f| f.write(block.call) }
|
20
|
+
}
|
24
21
|
end
|
22
|
+
end
|
25
23
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
def save(url, &block)
|
32
|
-
store_path(url) # no-op, just return the store path like the base does
|
33
|
-
end
|
34
|
-
end
|
24
|
+
def store_path(url_path)
|
25
|
+
File.join(@root, url_path)
|
26
|
+
end
|
27
|
+
end
|
35
28
|
|
29
|
+
class Dassets::NullFileStore < Dassets::FileStore
|
30
|
+
def initialize
|
31
|
+
super("")
|
36
32
|
end
|
37
33
|
|
34
|
+
def save(url_path, &block)
|
35
|
+
# No-op, just return the store path like the base does.
|
36
|
+
store_path(url_path)
|
37
|
+
end
|
38
38
|
end
|
data/lib/dassets/server.rb
CHANGED
@@ -1,37 +1,36 @@
|
|
1
|
-
|
2
|
-
require 'dassets/server/response'
|
1
|
+
# frozen_string_literal: true
|
3
2
|
|
4
|
-
|
3
|
+
require "dassets/server/request"
|
4
|
+
require "dassets/server/response"
|
5
5
|
|
6
|
-
|
7
|
-
class Server
|
6
|
+
# Rack middleware for serving Dassets asset files
|
8
7
|
|
9
|
-
|
10
|
-
|
11
|
-
|
8
|
+
module Dassets; end
|
9
|
+
class Dassets::Server
|
10
|
+
def initialize(app)
|
11
|
+
@app = app
|
12
|
+
end
|
12
13
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
end
|
14
|
+
# The Rack call interface. The receiver acts as a prototype and runs
|
15
|
+
# each request in a clone object unless the +rack.run_once+ variable is
|
16
|
+
# set in the environment. Ripped from:
|
17
|
+
# http://github.com/rtomayko/rack-cache/blob/master/lib/rack/cache/context.rb
|
18
|
+
def call(env)
|
19
|
+
if env["rack.run_once"]
|
20
|
+
call! env
|
21
|
+
else
|
22
|
+
clone.call! env
|
23
23
|
end
|
24
|
+
end
|
24
25
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
end
|
26
|
+
# The real Rack call interface.
|
27
|
+
# if an asset file is being requested, this is an endpoint - otherwise, call
|
28
|
+
# on up to the app as normal
|
29
|
+
def call!(env)
|
30
|
+
if (request = Request.new(env)).for_asset_file?
|
31
|
+
Response.new(env, request.asset_file).to_rack
|
32
|
+
else
|
33
|
+
@app.call(env)
|
34
34
|
end
|
35
|
-
|
36
35
|
end
|
37
36
|
end
|
@@ -1,56 +1,58 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
class Dassets::Server
|
5
|
-
|
6
|
-
class Request < Rack::Request
|
3
|
+
require "rack"
|
7
4
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
5
|
+
module Dassets; end
|
6
|
+
class Dassets::Server; end
|
7
|
+
class Dassets::Server::Request < Rack::Request
|
8
|
+
# The HTTP request method. This is the standard implementation of this
|
9
|
+
# method but is respecified here due to libraries that attempt to modify
|
10
|
+
# the behavior to respect POST tunnel method specifiers. We always want
|
11
|
+
# the real request method.
|
12
|
+
def request_method
|
13
|
+
@env["REQUEST_METHOD"]
|
14
|
+
end
|
13
15
|
|
14
|
-
|
15
|
-
|
16
|
-
|
16
|
+
def path_info
|
17
|
+
@env["PATH_INFO"].sub(dassets_base_url, "")
|
18
|
+
end
|
17
19
|
|
18
|
-
|
19
|
-
|
20
|
-
|
20
|
+
def dassets_base_url
|
21
|
+
Dassets.config.base_url.to_s
|
22
|
+
end
|
21
23
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
24
|
+
# Determine if the request is for an asset file
|
25
|
+
# This will be called on every request so speed is an issue
|
26
|
+
# - first check if the request is a GET or HEAD (fast)
|
27
|
+
# - then check if for a digested asset resource (kinda fast)
|
28
|
+
# - then check if source exists for the digested asset (slower)
|
29
|
+
def for_asset_file?
|
30
|
+
!!((get? || head?) && for_digested_asset? && asset_file.exists?)
|
31
|
+
end
|
30
32
|
|
31
|
-
|
32
|
-
|
33
|
-
|
33
|
+
def asset_path
|
34
|
+
@asset_path ||= path_digest_match.captures.select{ |m| !m.empty? }.join
|
35
|
+
end
|
34
36
|
|
35
|
-
|
36
|
-
|
37
|
-
|
37
|
+
def asset_file
|
38
|
+
@asset_file ||= Dassets.asset_file(asset_path)
|
39
|
+
end
|
38
40
|
|
39
|
-
|
41
|
+
private
|
40
42
|
|
41
|
-
|
42
|
-
|
43
|
-
|
43
|
+
def for_digested_asset?
|
44
|
+
!path_digest_match.captures.empty?
|
45
|
+
end
|
44
46
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
end
|
47
|
+
def path_digest_match
|
48
|
+
@path_digest_match ||= begin
|
49
|
+
path_info.match(/\/(.+)-[a-f0-9]{32}(\..+|)$/i) || NullDigestMatch.new
|
49
50
|
end
|
51
|
+
end
|
50
52
|
|
51
|
-
|
52
|
-
|
53
|
+
class NullDigestMatch
|
54
|
+
def captures
|
55
|
+
[]
|
53
56
|
end
|
54
|
-
|
55
57
|
end
|
56
58
|
end
|
@@ -1,108 +1,120 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rack/response"
|
4
|
+
require "rack/utils"
|
5
|
+
require "rack/mime"
|
4
6
|
|
5
7
|
module Dassets; end
|
6
|
-
class Dassets::Server
|
8
|
+
class Dassets::Server; end
|
9
|
+
class Dassets::Server::Response
|
10
|
+
attr_reader :asset_file, :status, :headers, :body
|
11
|
+
|
12
|
+
def initialize(env, asset_file)
|
13
|
+
@asset_file = asset_file
|
14
|
+
|
15
|
+
mtime = @asset_file.mtime.to_s
|
16
|
+
@status, @headers, @body = if env["HTTP_IF_MODIFIED_SINCE"] == mtime
|
17
|
+
[
|
18
|
+
304,
|
19
|
+
Rack::Utils::HeaderHash.new("Last-Modified" => mtime),
|
20
|
+
[],
|
21
|
+
]
|
22
|
+
elsif !@asset_file.exists?
|
23
|
+
[
|
24
|
+
404,
|
25
|
+
Rack::Utils::HeaderHash.new,
|
26
|
+
["Not Found"],
|
27
|
+
]
|
28
|
+
else
|
29
|
+
@asset_file.digest!
|
30
|
+
body = Body.new(env, @asset_file)
|
31
|
+
[
|
32
|
+
body.partial? ? 206 : 200,
|
33
|
+
Rack::Utils::HeaderHash.new.merge(@asset_file.response_headers).tap do |h|
|
34
|
+
h["Last-Modified"] = mtime.to_s
|
35
|
+
h["Content-Type"] = @asset_file.mime_type.to_s
|
36
|
+
h["Content-Length"] = body.size.to_s
|
37
|
+
h["Content-Range"] = body.content_range if body.partial?
|
38
|
+
end,
|
39
|
+
env["REQUEST_METHOD"] == "HEAD" ? [] : body
|
40
|
+
]
|
41
|
+
end
|
42
|
+
end
|
7
43
|
|
8
|
-
|
44
|
+
def to_rack
|
45
|
+
[@status, @headers.to_hash, @body]
|
46
|
+
end
|
9
47
|
|
10
|
-
|
48
|
+
# This class borrows from the body range handling in Rack::File and adapts
|
49
|
+
# it for use with Dasset's asset files and their generic string content.
|
50
|
+
class Body
|
51
|
+
CHUNK_SIZE = (8*1024).freeze # 8k
|
52
|
+
|
53
|
+
attr_reader :asset_file, :size, :content_range
|
11
54
|
|
12
55
|
def initialize(env, asset_file)
|
13
56
|
@asset_file = asset_file
|
14
|
-
|
15
|
-
|
16
|
-
@status, @headers, @body = if env['HTTP_IF_MODIFIED_SINCE'] == mtime
|
17
|
-
[ 304, Rack::Utils::HeaderHash.new('Last-Modified' => mtime), [] ]
|
18
|
-
elsif !@asset_file.exists?
|
19
|
-
[ 404, Rack::Utils::HeaderHash.new, ["Not Found"] ]
|
20
|
-
else
|
21
|
-
@asset_file.digest!
|
22
|
-
body = Body.new(env, @asset_file)
|
23
|
-
[ body.partial? ? 206 : 200,
|
24
|
-
Rack::Utils::HeaderHash.new.merge(@asset_file.response_headers).tap do |h|
|
25
|
-
h['Last-Modified'] = mtime
|
26
|
-
h['Content-Type'] = @asset_file.mime_type.to_s
|
27
|
-
h['Content-Length'] = body.size.to_s
|
28
|
-
h['Content-Range'] = body.content_range if body.partial?
|
29
|
-
end,
|
30
|
-
env["REQUEST_METHOD"] == "HEAD" ? [] : body
|
31
|
-
]
|
32
|
-
end
|
57
|
+
@range, @content_range = get_range_info(env, @asset_file)
|
58
|
+
@size = self.range_end - self.range_begin + 1
|
33
59
|
end
|
34
60
|
|
35
|
-
def
|
36
|
-
|
61
|
+
def partial?
|
62
|
+
!@content_range.nil?
|
37
63
|
end
|
38
64
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
# it for use with Dasset's asset files and their generic string content.
|
43
|
-
|
44
|
-
CHUNK_SIZE = (8*1024).freeze # 8k
|
45
|
-
|
46
|
-
attr_reader :asset_file, :size, :content_range
|
47
|
-
|
48
|
-
def initialize(env, asset_file)
|
49
|
-
@asset_file = asset_file
|
50
|
-
@range, @content_range = get_range_info(env, @asset_file)
|
51
|
-
@size = self.range_end - self.range_begin + 1
|
52
|
-
end
|
53
|
-
|
54
|
-
def partial?
|
55
|
-
!@content_range.nil?
|
56
|
-
end
|
65
|
+
def range_begin
|
66
|
+
@range.begin
|
67
|
+
end
|
57
68
|
|
58
|
-
|
59
|
-
|
69
|
+
def range_end
|
70
|
+
@range.end
|
71
|
+
end
|
60
72
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
73
|
+
def each
|
74
|
+
StringIO.open(@asset_file.content, "rb") do |io|
|
75
|
+
io.seek(@range.begin)
|
76
|
+
remaining_len = self.size
|
77
|
+
while remaining_len > 0
|
78
|
+
part = io.read([CHUNK_SIZE, remaining_len].min)
|
79
|
+
break if part.nil?
|
68
80
|
|
69
|
-
|
70
|
-
|
71
|
-
end
|
81
|
+
remaining_len -= part.length
|
82
|
+
yield part
|
72
83
|
end
|
73
84
|
end
|
85
|
+
end
|
74
86
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
87
|
+
def inspect
|
88
|
+
"#<#{self.class}:#{"0x0%x" % (self.object_id << 1)} " \
|
89
|
+
"digest_path=#{self.asset_file.digest_path} " \
|
90
|
+
"range_begin=#{self.range_begin} range_end=#{self.range_end}>"
|
91
|
+
end
|
80
92
|
|
81
|
-
|
93
|
+
def ==(other_body)
|
94
|
+
if other_body.is_a?(self.class)
|
82
95
|
self.asset_file == other_body.asset_file &&
|
83
96
|
self.range_begin == other_body.range_begin &&
|
84
97
|
self.range_end == other_body.range_end
|
98
|
+
else
|
99
|
+
super
|
85
100
|
end
|
101
|
+
end
|
86
102
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
end
|
99
|
-
|
100
|
-
def full_size_range_info(content_size)
|
101
|
-
[(0..content_size-1), nil]
|
102
|
-
end
|
103
|
-
|
103
|
+
private
|
104
|
+
|
105
|
+
def get_range_info(env, asset_file)
|
106
|
+
content_size = asset_file.size
|
107
|
+
# legacy rack version, just return full size
|
108
|
+
return full_size_range_info(content_size) if !Rack::Utils.respond_to?(:byte_ranges)
|
109
|
+
ranges = Rack::Utils.byte_ranges(env, content_size)
|
110
|
+
# No ranges or multiple ranges are not supported, just return full size
|
111
|
+
return full_size_range_info(content_size) if ranges.nil? || ranges.empty? || ranges.length > 1
|
112
|
+
# single range
|
113
|
+
[ranges[0], "bytes #{ranges[0].begin}-#{ranges[0].end}/#{content_size}"]
|
104
114
|
end
|
105
115
|
|
116
|
+
def full_size_range_info(content_size)
|
117
|
+
[(0..content_size-1), nil]
|
118
|
+
end
|
106
119
|
end
|
107
|
-
|
108
120
|
end
|