Class: Rack::SmartCompress::Middleware

Inherits:
Object
  • Object
show all
Defined in:
lib/rack/smart_compress/middleware.rb

Constant Summary collapse

ENCODERS =
{
  "zstd" => Encoders::Zstd,
  "br" => Encoders::Brotli,
  "gzip" => Encoders::Gzip,
  "deflate" => Encoders::Deflate
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(app, options = {}) ⇒ Middleware

Returns a new instance of Middleware.



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/rack/smart_compress/middleware.rb', line 25

def initialize(app, options = {})
  @app = app
  default_config = Rack::SmartCompress.configuration.to_h
  @options = default_config.merge(options)

  @min_size = @options.fetch(:min_size, Configuration::DEFAULT_MIN_SIZE)
  @mime_types = @options.fetch(:mime_types, Configuration::DEFAULT_MIME_TYPES)
  @exclude_mime_types = @options.fetch(:exclude_mime_types, Configuration::DEFAULT_EXCLUDED_MIME_TYPES)
  @enabled_encodings = @options.fetch(:encodings, Configuration::DEFAULT_ENCODINGS)
  @if_condition = @options[:if]
  @unless_condition = @options[:unless]
  @dynamic_levels = @options.fetch(:dynamic_levels, false)
  @instrumentation = @options.fetch(:instrumentation, true)

  if @options[:cache] || @options[:cache_size]
    cache_size = @options.is_a?(Integer) ? @options[:cache_size] : (@options[:cache_size] || LruCache::DEFAULT_MAX_SIZE)
    @cache = LruCache.new(cache_size)
  else
    @cache = nil
  end
end

Instance Attribute Details

#appObject (readonly)

Returns the value of attribute app.



23
24
25
# File 'lib/rack/smart_compress/middleware.rb', line 23

def app
  @app
end

#cacheObject (readonly)

Returns the value of attribute cache.



23
24
25
# File 'lib/rack/smart_compress/middleware.rb', line 23

def cache
  @cache
end

#optionsObject (readonly)

Returns the value of attribute options.



23
24
25
# File 'lib/rack/smart_compress/middleware.rb', line 23

def options
  @options
end

Instance Method Details

#call(env) ⇒ Object



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/rack/smart_compress/middleware.rb', line 47

def call(env)
  status, headers, body = @app.call(env)

  return [status, headers, body] if no_body_status?(status)
  return [status, headers, body] if already_encoded?(headers)
  return [status, headers, body] if no_transform?(headers)
  return [status, headers, body] if partial_content?(status, headers)
  return [status, headers, body] unless custom_rules_pass?(env, status, headers)

  accept_encoding = env["HTTP_ACCEPT_ENCODING"].to_s
  encoder_name, encoder_class = negotiate_encoder(accept_encoding)
  return [status, headers, body] unless encoder_class

  content_type = get_header(headers, "content-type")
  return [status, headers, body] unless compressible_mime_type?(content_type)

  level = options[:"#{encoder_name}_level"]
  level = CpuAdvisor.adjusted_level(encoder_name, level) if @dynamic_levels

  # Handle streaming responses
  if streaming_body?(body) || options[:stream]
    new_headers = headers.dup
    set_header(new_headers, "content-encoding", encoder_name)
    delete_header(new_headers, "content-length") # Chunked stream size is dynamic
    append_vary(new_headers, "Accept-Encoding")
    weaken_etag(new_headers)
    stream_wrapper = StreamBody.new(body, encoder_name, encoder_class, level: level)
    return [status, new_headers, stream_wrapper]
  end

  body_string = extract_body_string(body)
  return [status, headers, body] if body_string.bytesize < @min_size

  start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
  compressed_body, compressed_size, cache_hit = compress_with_cache(encoder_name, encoder_class, level, body_string)
  duration_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) * 1000.0).round(3)

  body.close if body.respond_to?(:close)

  new_headers = headers.dup
  set_header(new_headers, "content-encoding", encoder_name)
  set_header(new_headers, "content-length", compressed_size.to_s)
  append_vary(new_headers, "Accept-Encoding")
  weaken_etag(new_headers)

  record_telemetry(encoder_name, body_string.bytesize, compressed_size, duration_ms, cache_hit, env)

  [status, new_headers, compressed_body]
end