klenod-rack 0.0.1
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 -0
- data/README.md +18 -0
- data/lib/klenod/rack/asset_app.rb +134 -0
- data/lib/klenod/rack/version.rb +7 -0
- data/lib/klenod/rack.rb +4 -0
- metadata +75 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 913a1d05a13144d907d9dd2cb02efd0ecbcbae1d075a69e4e225a06e3d78558f
|
|
4
|
+
data.tar.gz: 3576d918c391b71ea94dac9a8f0375c4511a89e8c2d58ac07b5bcfe395c5a1d8
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 4110c103c9322b684205a6a3b87f6b55a63b5ad812e83f4097ecfacb8a8fe46d7037cbdc86ed2e0e0a0008c96754ea144b6a11789de5a98b16a1703808aa6bc3
|
|
7
|
+
data.tar.gz: 5785c87a96bd297e5d7fc28400a33b6408b2e056b2787a9223642863f4f971d5501b9807ec0248f3bb239590d9fe04e43ce02bdb78e8564bb9387fdc2a70bf81
|
data/README.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# klenod-rack
|
|
2
|
+
|
|
3
|
+
`klenod-rack` provides Rack-compatible serving helpers for
|
|
4
|
+
[Klenod](https://github.com/aalin/klenod) runtime assets.
|
|
5
|
+
|
|
6
|
+
It is intentionally small and framework-agnostic. `Klenod::Rack::AssetApp`:
|
|
7
|
+
|
|
8
|
+
- serves content-hashed assets from a build context or loaded runtime bundle
|
|
9
|
+
- serves precompressed Brotli assets when available
|
|
10
|
+
- can delegate non-asset requests to another Rack application
|
|
11
|
+
|
|
12
|
+
Use this gem when exposing Klenod assets from a Rack-compatible server:
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
require "klenod/rack"
|
|
16
|
+
|
|
17
|
+
assets = Klenod::Rack::AssetApp.new(bundle, assets_dir: "public")
|
|
18
|
+
```
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "http/accept"
|
|
4
|
+
|
|
5
|
+
module Klenod
|
|
6
|
+
module Rack
|
|
7
|
+
Response = Data.define(:status, :headers, :body) do
|
|
8
|
+
def rack_response
|
|
9
|
+
[status, headers, [body]]
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def protocol_response
|
|
13
|
+
require "protocol/http/response"
|
|
14
|
+
|
|
15
|
+
Protocol::HTTP::Response[status, headers, [body]]
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class AssetApp
|
|
20
|
+
CACHE_CONTROL = "public, max-age=31536000, immutable"
|
|
21
|
+
|
|
22
|
+
def initialize(source, app: nil, assets_dir: nil, path_prefix: "/assets/")
|
|
23
|
+
@source = source
|
|
24
|
+
@app = app
|
|
25
|
+
@assets_dir = assets_dir
|
|
26
|
+
@path_prefix = path_prefix
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
attr_reader :source, :app, :assets_dir, :path_prefix
|
|
30
|
+
|
|
31
|
+
def call(env)
|
|
32
|
+
response = response_for(env.fetch("PATH_INFO", ""), env)
|
|
33
|
+
return response.rack_response if response
|
|
34
|
+
return app.call(env) if app
|
|
35
|
+
|
|
36
|
+
not_found.rack_response
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def response_for(path, env = {})
|
|
40
|
+
return nil unless asset_path?(path)
|
|
41
|
+
|
|
42
|
+
asset = source.asset(path)
|
|
43
|
+
brotli_available = brotli_asset_available?(asset)
|
|
44
|
+
compressed = brotli_available && accepts_brotli?(env.fetch("HTTP_ACCEPT_ENCODING", ""))
|
|
45
|
+
body = compressed ? brotli_asset_bytes(asset) : asset_bytes(asset)
|
|
46
|
+
headers = {
|
|
47
|
+
"content-type" => asset.content_type,
|
|
48
|
+
"content-length" => body.bytesize.to_s,
|
|
49
|
+
"cache-control" => CACHE_CONTROL
|
|
50
|
+
}
|
|
51
|
+
link = preload_link_header(asset)
|
|
52
|
+
headers["link"] = link if link
|
|
53
|
+
headers["vary"] = "Accept-Encoding" if brotli_available
|
|
54
|
+
if compressed
|
|
55
|
+
headers["content-encoding"] = "br"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
Response.new(
|
|
59
|
+
200,
|
|
60
|
+
headers,
|
|
61
|
+
body
|
|
62
|
+
)
|
|
63
|
+
rescue KeyError, Errno::ENOENT
|
|
64
|
+
not_found
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
def asset_path?(path)
|
|
70
|
+
path.start_with?(path_prefix)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def asset_bytes(asset)
|
|
74
|
+
if source.respond_to?(:asset_bytes)
|
|
75
|
+
source.asset_bytes(asset.output_path, assets_dir: assets_dir)
|
|
76
|
+
else
|
|
77
|
+
File.binread(asset_disk_path(asset.output_path))
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def brotli_asset_available?(asset)
|
|
82
|
+
return false unless assets_dir
|
|
83
|
+
|
|
84
|
+
File.file?(brotli_asset_disk_path(asset.output_path))
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def accepts_brotli?(header)
|
|
88
|
+
return false if header.to_s.strip.empty?
|
|
89
|
+
|
|
90
|
+
HTTP::Accept::Encodings.parse(header.to_s).any? do |encoding|
|
|
91
|
+
%w[br *].include?(encoding.encoding) && encoding.quality_factor.positive?
|
|
92
|
+
end
|
|
93
|
+
rescue HTTP::Accept::ParseError
|
|
94
|
+
false
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def preload_link_header(asset)
|
|
98
|
+
links =
|
|
99
|
+
Array(asset.metadata[:preload_assets]).map do |preload|
|
|
100
|
+
path = preload.fetch(:path)
|
|
101
|
+
as = preload.fetch(:as)
|
|
102
|
+
%(<#{path}>; rel=preload; as=#{as})
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
links.empty? ? nil : links.join(", ")
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def brotli_asset_bytes(asset)
|
|
109
|
+
File.binread(brotli_asset_disk_path(asset.output_path))
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def brotli_asset_disk_path(output_path)
|
|
113
|
+
"#{asset_disk_path(output_path)}.br"
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def asset_disk_path(output_path)
|
|
117
|
+
raise ArgumentError, "assets_dir is required to serve runtime bundle assets" unless assets_dir
|
|
118
|
+
|
|
119
|
+
File.join(assets_dir, output_path.delete_prefix("/"))
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def not_found
|
|
123
|
+
Response.new(
|
|
124
|
+
404,
|
|
125
|
+
{
|
|
126
|
+
"content-type" => "text/plain",
|
|
127
|
+
"content-length" => "16"
|
|
128
|
+
},
|
|
129
|
+
"Asset not found\n"
|
|
130
|
+
)
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
data/lib/klenod/rack.rb
ADDED
metadata
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: klenod-rack
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Andrés Alin
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: klenod-runtime
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - '='
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: 0.0.1
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - '='
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: 0.0.1
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: http-accept
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '2.2'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '2.2'
|
|
40
|
+
description: Klenod Rack serves content-hashed runtime bundle assets from Rack-compatible
|
|
41
|
+
applications.
|
|
42
|
+
email:
|
|
43
|
+
- andreas.alin@gmail.com
|
|
44
|
+
executables: []
|
|
45
|
+
extensions: []
|
|
46
|
+
extra_rdoc_files: []
|
|
47
|
+
files:
|
|
48
|
+
- README.md
|
|
49
|
+
- lib/klenod/rack.rb
|
|
50
|
+
- lib/klenod/rack/asset_app.rb
|
|
51
|
+
- lib/klenod/rack/version.rb
|
|
52
|
+
homepage: https://github.com/aalin/klenod
|
|
53
|
+
licenses:
|
|
54
|
+
- MIT
|
|
55
|
+
metadata:
|
|
56
|
+
homepage_uri: https://github.com/aalin/klenod
|
|
57
|
+
source_code_uri: https://github.com/aalin/klenod/tree/main/gems/klenod-rack
|
|
58
|
+
rdoc_options: []
|
|
59
|
+
require_paths:
|
|
60
|
+
- lib
|
|
61
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
62
|
+
requirements:
|
|
63
|
+
- - ">="
|
|
64
|
+
- !ruby/object:Gem::Version
|
|
65
|
+
version: 4.0.6
|
|
66
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
67
|
+
requirements:
|
|
68
|
+
- - ">="
|
|
69
|
+
- !ruby/object:Gem::Version
|
|
70
|
+
version: '0'
|
|
71
|
+
requirements: []
|
|
72
|
+
rubygems_version: 4.0.16
|
|
73
|
+
specification_version: 4
|
|
74
|
+
summary: Rack asset serving helpers for Klenod.
|
|
75
|
+
test_files: []
|