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 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
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Klenod
4
+ module Rack
5
+ VERSION = "0.0.1"
6
+ end
7
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "rack/version"
4
+ require_relative "rack/asset_app"
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: []