rack_resize 0.1.0 → 0.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 89ff24cb927e939e377fa9de0e4af8d736e026d2536a5721a94c8884c745de16
4
- data.tar.gz: 13e4ca927f5ef1f058a528ded9158cd324e2eaf0e34c2797b21e1791028a31fc
3
+ metadata.gz: '09480d8110a0fd174b70144ea5df62c1146f2c16399cd9e173a0638d5cf818b7'
4
+ data.tar.gz: 24f936049e0bed57e1ff90c49f588d48480700adae2b2b6829685df011ecb89f
5
5
  SHA512:
6
- metadata.gz: 58f951cd5354359a5a6261a739ded0e7136d346a91572c4bc8ba856530feb57644b1825959acc4ecdc79f91a15ab93e1e38c58d3578d82bd4c803a70ac015fdc
7
- data.tar.gz: 462e57a07904d0e4c845b40611a2d7bc601e4c2fb207b8b7f2cccac9de934d6471bc222550e770ba19e0e58235d977d86b4ed8df0771761ac4b09a884495f106
6
+ metadata.gz: be9835971015420520f16a1332a0ac5664668150f06d6f577148803a7a299142c9edffdd9e33581d64b76b0a0aa8fb2cec9cbd2ce8524cdfa45f966721177610
7
+ data.tar.gz: b48663c5733f65c3e55fff126df2a31ff99f64bc0fceccd3c9fbc10261ea0ed5acfd99e7705333e24486ef6464d776df2d7ab9b92c5d8ab198d8646b1ae0d224
@@ -0,0 +1,29 @@
1
+ name: Test
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ fail-fast: false
13
+ matrix:
14
+ ruby: ['3.3', '3.4', '4.0']
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: Install package
20
+ run: |
21
+ sudo apt-get update
22
+ sudo apt-get install -y libvips42 imagemagick libimlib2 libimlib2-dev
23
+
24
+ - uses: ruby/setup-ruby@v1
25
+ with:
26
+ ruby-version: ${{ matrix.ruby }}
27
+ bundler-cache: true
28
+
29
+ - run: bundle exec rake test
data/.gitignore CHANGED
@@ -1,3 +1,4 @@
1
1
  .idea/
2
2
  Gemfile.lock
3
3
  .DS_Store
4
+ *.gem
data/Gemfile CHANGED
@@ -2,6 +2,9 @@ source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
4
 
5
+ gem 'image_processing'
5
6
  gem 'mini_magick'
6
7
  gem 'ruby-vips'
7
8
  gem 'rszr'
9
+ gem 'minitest'
10
+ gem 'rake'
data/README.md CHANGED
@@ -1,6 +1,108 @@
1
+ [![Gem Version](https://badge.fury.io/rb/rack_resize.svg)](https://badge.fury.io/rb/rack_resize)
1
2
 
2
- RESIZE IMAGE mini_magick samples/big.jpg - {:width=>"150"} - 0.949s
3
- RESIZE IMAGE sips samples/big.jpg - {:width=>"150"} - 0.121s
4
- RESIZE IMAGE vips samples/big.jpg - {:width=>"150"} - 0.116s
3
+ Designed to be local simulation of CDN's resize feature
5
4
 
6
- s.add_runtime_dependency "image_processing", ["> 1.0", "< 3.0"]
5
+ How to use with Rails
6
+
7
+ ```ruby
8
+ group :development do
9
+ gem "rack_resize", require: "rack_resize/rails_autoload"
10
+ end
11
+ ```
12
+
13
+ How to use with Rack
14
+
15
+ ```ruby
16
+ use RackResize::RackApp, processor: :imlib2, assets_folder: "samples"
17
+ use Rack::Static, urls: [""], root: "samples", index: "index.html" # optional
18
+ ```
19
+
20
+ ### URL Formats:
21
+
22
+ Cloudflare format: (can be used with helpers from `carrierwave-cloudflare` gem)
23
+ ```
24
+ /cdn-cgi/image/width=426,format=auto/assets/pets/dog.jpg
25
+ ```
26
+ Fastly and bunny.net: (tbd)
27
+ ```
28
+ /assets/pets/dog.jpg?width=300
29
+ ```
30
+
31
+ ### Configuration:
32
+
33
+ ```ruby
34
+ RackResize.configure do |config|
35
+ config.assets_folder = Rails.root.join('app', 'assets', 'images')
36
+ config.processor = :sips / :vips / :mini_magick / :imlib2
37
+ config.default_quality = 95
38
+ config.save_resized = false
39
+ config.cache_folder = Rails.root.join('tmp', 'rack_resize_cache') # used if save_resized enabled
40
+ config.http_cache_max_age = 86400
41
+ end
42
+ ```
43
+
44
+ ### Supported Processing Backends:
45
+
46
+ <table>
47
+ <tr>
48
+ <th>libaray</th>
49
+ <th>dependency</th>
50
+ <th>config</th>
51
+ </tr>
52
+ <tr>
53
+ <td>mini_magick</td>
54
+ <td>
55
+
56
+ ```ruby
57
+ gem "image_processing"
58
+ gem "mini_magick"
59
+ ```
60
+
61
+ </td>
62
+ <td>
63
+
64
+ `processor: :mini_magick`
65
+
66
+ </td>
67
+ </tr>
68
+ <tr>
69
+ <td>vips</td>
70
+ <td>
71
+
72
+ ```ruby
73
+ gem "image_processing"
74
+ gem "ruby-vips"
75
+ ```
76
+
77
+ </td>
78
+ <td>
79
+
80
+ `processor: :vips`
81
+
82
+ </td>
83
+ </tr>
84
+ <tr>
85
+ <td>sips (MacOS only)</td>
86
+ <td>none</td>
87
+ <td>
88
+
89
+ `processor: :sips`
90
+
91
+ </td>
92
+ </tr>
93
+ <tr>
94
+ <td>imlib2</td>
95
+ <td>
96
+
97
+ ```ruby
98
+ gem "rszr"
99
+ ```
100
+
101
+ </td>
102
+ <td>
103
+
104
+ `processor: :imlib2`
105
+
106
+ </td>
107
+ </tr>
108
+ </table>
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require 'rake/testtask'
2
+
3
+ Rake::TestTask.new(:test) do |t|
4
+ t.libs << 'test'
5
+ t.pattern = 'test/**/*_test.rb'
6
+ t.verbose = true
7
+ end
8
+
9
+ task default: :test
data/config.ru CHANGED
@@ -1,7 +1,13 @@
1
+ #
2
+ # usage:
3
+ # gem install rackup
4
+ # rackup
5
+ #
6
+
1
7
  require_relative "lib/rack_resize"
2
8
  require "rack/static"
3
9
 
4
- use RackResize::RackApp, processor: :imlib2, assets_folder: "samples" # save_resized: false,
10
+ use RackResize::RackApp, processor: :imlib2, assets_folder: "samples"
5
11
 
6
12
  use Rack::Static, urls: [""], root: "samples", index: "index.html"
7
13
 
@@ -4,20 +4,24 @@ class RackResize::Configuration
4
4
  PROCESSORS = %i[sips vips mini_magick imlib2].freeze
5
5
 
6
6
  attr_reader :processor
7
- attr_accessor :save_resized, :default_quality, :cache_folder, :assets_folder
7
+ attr_accessor :save_resized, :default_quality, :cache_folder, :assets_folder, :http_cache_max_age
8
8
 
9
9
  alias_method :save_resized?, :save_resized
10
10
 
11
11
  def initialize(options = {})
12
- @processor = options[:processor] || RUBY_PLATFORM.include?('darwin') ? :sips : :mini_magick
13
- @save_resized = options.key?(:save_resized) ? options[:save_resized] : true
14
- @default_quality = options[:default_quality] || 95
15
- @cache_folder = options[:cache_folder]
16
- @assets_folder = options[:assets_folder] ? Pathname.new(options[:assets_folder]) : nil
12
+ @processor = options[:processor] || RUBY_PLATFORM.include?('darwin') ? :sips : :mini_magick
13
+ @save_resized = options.key?(:save_resized) ? options[:save_resized] : false
14
+ @default_quality = options[:default_quality] || 95
15
+ @cache_folder = options[:cache_folder]
16
+ @assets_folder = options[:assets_folder] ? Pathname.new(options[:assets_folder]) : nil
17
+ @http_cache_max_age = options[:http_cache_max_age] || 86400 # 1 day
17
18
 
18
19
  if defined?(Rails)
19
20
  @cache_folder ||= Rails.root.join('tmp', 'rack_resize_cache')
20
21
  @assets_folder ||= Rails.root.join('app', 'assets', 'images')
22
+ unless options.key?(:save_resized)
23
+ @save_resized = true
24
+ end
21
25
  end
22
26
 
23
27
  if options[:processor]
@@ -13,20 +13,20 @@ class RackResize::RackApp
13
13
 
14
14
  @config = options == {} ? RackResize.config : RackResize::Configuration.new(options)
15
15
  @processing = RackResize::Processing.new(config: @config)
16
- @path_prefix = options[:path_prefix] || "/cdn-cgi/image"
16
+ @cf_path_prefix = options[:cf_path_prefix] || "/cdn-cgi/image"
17
17
  end
18
18
 
19
19
  def call(env)
20
- # process string like this
21
- # /cdn-cgi/image/width=426,format=auto/assets/templates/vancouver-27c47f55.jpg
22
-
23
20
  request = Rack::Request.new(env)
24
21
  fullpath = request.path_info
25
22
 
26
- unless fullpath.start_with?(@path_prefix)
23
+ unless fullpath.start_with?(@cf_path_prefix)
27
24
  return @app.call(env)
28
25
  end
29
26
 
27
+ # process string like this
28
+ # /cdn-cgi/image/width=426,format=auto/assets/templates/vancouver-27c47f55.jpg
29
+
30
30
  fullpath = fullpath.delete_prefix("/cdn-cgi").delete_prefix("/image").delete_prefix("/")
31
31
  file_path_match = fullpath.match(%r{(?<params>[^\/]+)(?<file>\/.+?)(-[\da-f]{8})?(?<ext>\.\w{2,})$})
32
32
 
@@ -58,7 +58,6 @@ class RackResize::RackApp
58
58
 
59
59
  def send_file(asset_file:, file_content: nil)
60
60
  content_type = Rack::Mime.mime_type(File.extname(asset_file), "application/octet-stream")
61
- # file = file_path ? File.open(file_path, "rb") : StringIO.new(file_content)
62
61
 
63
62
  [
64
63
  200,
@@ -66,7 +65,7 @@ class RackResize::RackApp
66
65
  "content-type" => content_type,
67
66
  "content-length" => file_content.size.to_s,
68
67
  "content-disposition" => "inline",
69
- "cache-control" => "max-age=86400" # 1 day
68
+ "cache-control" => "max-age=#{config.http_cache_max_age}" # 1 day
70
69
  },
71
70
  file_content
72
71
  ]
data/rack_resize.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "rack_resize"
3
- s.version = "0.1.0"
3
+ s.version = "0.1.1"
4
4
  s.author = ["Pavel Evstigneev"]
5
5
  s.email = ["pavel.evst@gmail.com"]
6
6
  s.homepage = "https://github.com/paxa/rack_resize"
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,34 @@
1
+ <!DOCTYPE html>
2
+
3
+ <p>
4
+ height 150: <br>
5
+ <img src="/cdn-cgi/image/height=150/samples/image_1.jpeg"/>
6
+ <img src="/cdn-cgi/image/height=150/samples/image_2.jpeg"/>
7
+ <img src="/cdn-cgi/image/height=150/samples/image_3.jpeg"/>
8
+ <img src="/cdn-cgi/image/height=150/samples/image_4.jpeg"/>
9
+ </p>
10
+
11
+ <p>
12
+ width 150: <br>
13
+ <img src="/cdn-cgi/image/width=150/samples/image_1.jpeg"/>
14
+ <img src="/cdn-cgi/image/width=150/samples/image_2.jpeg"/>
15
+ <img src="/cdn-cgi/image/width=150/samples/image_3.jpeg"/>
16
+ <img src="/cdn-cgi/image/width=150/samples/image_4.jpeg"/>
17
+ </p>
18
+
19
+ <p>
20
+ width 50: <br>
21
+ <img src="/cdn-cgi/image/width=50/samples/image_1.jpeg"/>
22
+ <img src="/cdn-cgi/image/width=50/samples/image_2.jpeg"/>
23
+ <img src="/cdn-cgi/image/width=50/samples/image_3.jpeg"/>
24
+ <img src="/cdn-cgi/image/width=50/samples/image_4.jpeg"/>
25
+ </p>
26
+
27
+ <p>
28
+ originals: <br>
29
+ <img src="image_1.jpeg"/>
30
+ <img src="image_2.jpeg"/>
31
+ <img src="image_3.jpeg"/>
32
+ <img src="image_4.jpeg"/>
33
+ </p>
34
+
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'test_helper'
4
+
5
+ class RackAppTest < Minitest::Test
6
+ UPSTREAM = ->(env) { [200, { 'content-type' => 'text/plain' }, ['upstream']] }
7
+
8
+ def setup
9
+ @tmpdir = Dir.mktmpdir('rack_resize_test')
10
+ File.write(File.join(@tmpdir, 'photo.jpg'), 'fake jpeg content')
11
+
12
+ @app = RackResize::RackApp.new(
13
+ UPSTREAM,
14
+ assets_folder: @tmpdir,
15
+ processor: :sips,
16
+ save_resized: false
17
+ )
18
+
19
+ fake_processing = Object.new
20
+ def fake_processing.process!(**) = StringIO.new('processed image data')
21
+ @app.instance_variable_set(:@processing, fake_processing)
22
+ end
23
+
24
+ def teardown
25
+ FileUtils.rm_rf(@tmpdir)
26
+ end
27
+
28
+ # --- error_resp ---
29
+
30
+ def test_error_resp_default_status
31
+ status, headers, body = @app.error_resp('oops')
32
+ assert_equal 404, status
33
+ assert_equal({}, headers)
34
+ assert_equal ['oops'], body
35
+ end
36
+
37
+ def test_error_resp_custom_status
38
+ status, _, body = @app.error_resp('bad input', http_code: 422)
39
+ assert_equal 422, status
40
+ assert_equal ['bad input'], body
41
+ end
42
+
43
+ # --- send_file ---
44
+
45
+ def test_send_file_returns_200_with_headers
46
+ asset_file = Pathname.new(File.join(@tmpdir, 'photo.jpg'))
47
+ content = StringIO.new('hello')
48
+ status, headers, _ = @app.send_file(asset_file:, file_content: content)
49
+
50
+ assert_equal 200, status
51
+ assert_equal 'image/jpeg', headers['content-type']
52
+ assert_equal '5', headers['content-length']
53
+ assert_equal 'inline', headers['content-disposition']
54
+ assert_match(/\d+/, headers['cache-control'])
55
+ end
56
+
57
+ # --- pass-through for non-cdn-cgi paths ---
58
+
59
+ def test_passes_through_unrelated_paths
60
+ env = Rack::MockRequest.env_for('/foo/bar.jpg')
61
+ status, _, body = @app.call(env)
62
+ assert_equal 200, status
63
+ assert_equal ['upstream'], body
64
+ end
65
+
66
+ def test_passes_through_root_path
67
+ env = Rack::MockRequest.env_for('/')
68
+ _, _, body = @app.call(env)
69
+ assert_equal ['upstream'], body
70
+ end
71
+
72
+ # --- path parsing errors ---
73
+
74
+ def test_returns_404_for_unparseable_path
75
+ env = Rack::MockRequest.env_for('/cdn-cgi/image/bad-path-no-extension')
76
+ status, _, body = @app.call(env)
77
+ assert_equal 404, status
78
+ assert_equal ["can't parse file path"], body
79
+ end
80
+
81
+ def test_returns_404_for_path_traversal
82
+ env = Rack::MockRequest.env_for('/cdn-cgi/image/width=100/assets/../etc/photo.jpg')
83
+ status, _, body = @app.call(env)
84
+ assert_equal 404, status
85
+ assert_equal ['.. is not allowed in image path'], body
86
+ end
87
+
88
+ def test_returns_404_for_missing_file
89
+ env = Rack::MockRequest.env_for('/cdn-cgi/image/width=100/assets/missing.jpg')
90
+ status, _, body = @app.call(env)
91
+ assert_equal 404, status
92
+ assert_equal ['file not exists on a server'], body
93
+ end
94
+
95
+ # --- successful processing ---
96
+
97
+ def test_returns_200_for_valid_request
98
+ env = Rack::MockRequest.env_for('/cdn-cgi/image/width=100/assets/photo.jpg')
99
+ status, headers, _ = @app.call(env)
100
+ assert_equal 200, status
101
+ assert_equal 'image/jpeg', headers['content-type']
102
+ end
103
+
104
+ def test_valid_request_with_multiple_params
105
+ env = Rack::MockRequest.env_for('/cdn-cgi/image/width=200,format=auto,quality=80/assets/photo.jpg')
106
+ status, = @app.call(env)
107
+ assert_equal 200, status
108
+ end
109
+
110
+ def test_response_body_is_processed_content
111
+ env = Rack::MockRequest.env_for('/cdn-cgi/image/width=100/assets/photo.jpg')
112
+ _, headers, body = @app.call(env)
113
+ assert_equal 'processed image data'.bytesize.to_s, headers['content-length']
114
+ end
115
+
116
+ # --- fingerprinted filenames (Rails asset digest) ---
117
+
118
+ def test_strips_digest_fingerprint_from_filename
119
+ File.write(File.join(@tmpdir, 'photo.jpg'), 'fake jpeg content')
120
+ env = Rack::MockRequest.env_for('/cdn-cgi/image/width=100/assets/photo-1a2b3c4d.jpg')
121
+ status, _, _ = @app.call(env)
122
+ assert_equal 200, status
123
+ end
124
+
125
+ # --- custom cf_path_prefix ---
126
+
127
+ def test_custom_cf_path_prefix_does_not_intercept_default_prefix
128
+ app = RackResize::RackApp.new(
129
+ UPSTREAM,
130
+ assets_folder: @tmpdir,
131
+ processor: :sips,
132
+ save_resized: false,
133
+ cf_path_prefix: '/img'
134
+ )
135
+ env = Rack::MockRequest.env_for('/cdn-cgi/image/width=100/assets/photo.jpg')
136
+ _, _, body = app.call(env)
137
+ assert_equal ['upstream'], body
138
+ end
139
+
140
+ # --- without upstream app ---
141
+
142
+ def test_initializes_without_upstream_app
143
+ app = RackResize::RackApp.new(assets_folder: @tmpdir, processor: :sips, save_resized: false)
144
+ assert_nil app.instance_variable_get(:@app)
145
+ end
146
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'minitest/autorun'
4
+ require 'rack/mock'
5
+ require_relative '../lib/rack_resize'
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack_resize
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pavel Evstigneev
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-07-15 00:00:00.000000000 Z
10
+ date: 2026-07-16 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rack
@@ -35,9 +35,11 @@ executables: []
35
35
  extensions: []
36
36
  extra_rdoc_files: []
37
37
  files:
38
+ - ".github/workflows/test.yml"
38
39
  - ".gitignore"
39
40
  - Gemfile
40
41
  - README.md
42
+ - Rakefile
41
43
  - config.ru
42
44
  - lib/rack_resize.rb
43
45
  - lib/rack_resize/configuration.rb
@@ -50,6 +52,13 @@ files:
50
52
  - lib/rack_resize/rack_app.rb
51
53
  - lib/rack_resize/rails_autoload.rb
52
54
  - rack_resize.gemspec
55
+ - samples/image_1.jpeg
56
+ - samples/image_2.jpeg
57
+ - samples/image_3.jpeg
58
+ - samples/image_4.jpeg
59
+ - samples/index.html
60
+ - test/rack_app_test.rb
61
+ - test/test_helper.rb
53
62
  homepage: https://github.com/paxa/rack_resize
54
63
  licenses:
55
64
  - MIT