Class: Lutaml::Store::HttpHeaderProcessor

Inherits:
Object
  • Object
show all
Defined in:
lib/lutaml/store/http_header_processor.rb

Class Method Summary collapse

Class Method Details

.build_conditional_headers(cache_entry, original_headers) ⇒ Object

Build conditional request headers



117
118
119
120
121
122
123
124
125
# File 'lib/lutaml/store/http_header_processor.rb', line 117

def self.build_conditional_headers(cache_entry, original_headers)
  conditional_headers = original_headers.dup

  conditional_headers["If-None-Match"] = cache_entry.etag if cache_entry.etag

  conditional_headers["If-Modified-Since"] = cache_entry.last_modified.httpdate if cache_entry.last_modified

  conditional_headers
end

.cache_entry_matches?(cache_entry, request_headers, config) ⇒ Boolean

Check if cache entry matches request (considering Vary)

Returns:

  • (Boolean)


139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/lutaml/store/http_header_processor.rb', line 139

def self.cache_entry_matches?(cache_entry, request_headers, config)
  return true if cache_entry.vary_headers.empty?

  # Extract vary headers from current request
  current_vary = extract_vary_headers(request_headers, cache_entry.vary_headers)

  # Compare with cached vary headers
  cache_entry.vary_headers.each do |header_name|
    cached_value = cache_entry.request_headers[header_name.downcase]
    current_value = current_vary[header_name.downcase]

    # Skip comparison for ignored headers
    next if config.should_ignore_vary_header?(header_name)

    return false if cached_value != current_value
  end

  true
end

.calculate_expiry(response_headers, cached_at, default_ttl) ⇒ Object

Calculate expiry time based on HTTP headers



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/lutaml/store/http_header_processor.rb', line 31

def self.calculate_expiry(response_headers, cached_at, default_ttl)
  cache_control = parse_cache_control(response_headers["cache-control"])

  # Check for explicit expiry
  if (expires_header = response_headers["expires"])
    begin
      return Time.parse(expires_header)
    rescue StandardError
      nil
    end
  end

  # Check for max-age
  if (max_age = cache_control["max-age"])
    return cached_at + max_age
  end

  # Fall back to default TTL
  cached_at + default_ttl
end

.extract_vary_headers(request_headers, vary_header_names) ⇒ Object

Extract vary headers from request headers



95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/lutaml/store/http_header_processor.rb', line 95

def self.extract_vary_headers(request_headers, vary_header_names)
  return {} if vary_header_names.empty?

  vary_headers = {}
  vary_header_names.each do |header_name|
    normalized_name = header_name.downcase
    # Find header with case-insensitive matching
    actual_header = request_headers.find { |k, _| k.downcase == normalized_name }
    vary_headers[normalized_name] = actual_header[1] if actual_header
  end
  vary_headers
end

.fresh?(cached_at, max_age, expires_at) ⇒ Boolean

Check if response is fresh based on age

Returns:

  • (Boolean)


109
110
111
112
113
114
# File 'lib/lutaml/store/http_header_processor.rb', line 109

def self.fresh?(cached_at, max_age, expires_at)
  return false if expires_at && Time.now > expires_at
  return false if max_age && (Time.now - cached_at) > max_age

  true
end

.generate_cache_key(method, url, vary_headers = {}, ignore_params = []) ⇒ Object

Generate cache key from request details



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/lutaml/store/http_header_processor.rb', line 64

def self.generate_cache_key(method, url, vary_headers = {}, ignore_params = [])
  uri = URI.parse(url)

  # Normalize query parameters
  if uri.query
    params = URI.decode_www_form(uri.query)
    # Filter out ignored parameters
    params = params.reject { |key, _| ignore_params.include?(key) }
    params = params.sort
    uri.query = params.empty? ? nil : URI.encode_www_form(params)
  end

  base_key = "#{method.upcase}:#{uri}"

  # Add vary headers to key if present
  if vary_headers.any?
    vary_suffix = vary_headers.sort.map { |k, v| "#{k}:#{v}" }.join("|")
    base_key += "|#{Digest::SHA256.hexdigest(vary_suffix)[0..8]}"
  end

  base_key
end

.has_caching_directives?(response_headers) ⇒ Boolean

Check if response has caching directives

Returns:

  • (Boolean)


165
166
167
168
169
170
171
172
# File 'lib/lutaml/store/http_header_processor.rb', line 165

def self.has_caching_directives?(response_headers)
  return true if response_headers["cache-control"]
  return true if response_headers["expires"]
  return true if response_headers["etag"]
  return true if response_headers["last-modified"]

  false
end

.normalize_header_name(name) ⇒ Object

Normalize header name for consistent storage



160
161
162
# File 'lib/lutaml/store/http_header_processor.rb', line 160

def self.normalize_header_name(name)
  name.to_s.downcase.strip
end

.parse_cache_control(header_value) ⇒ Object

Parse Cache-Control header according to RFC 7234



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/lutaml/store/http_header_processor.rb', line 11

def self.parse_cache_control(header_value)
  return {} unless header_value

  directives = {}
  header_value.split(",").each do |directive|
    key, value = directive.strip.split("=", 2)
    key = key.strip.downcase

    if value
      # Handle quoted values
      value = value.strip.gsub(/^"(.*)"$/, '\1')
      directives[key] = value.match?(/^\d+$/) ? value.to_i : value
    else
      directives[key] = true
    end
  end
  directives
end

.parse_last_modified(header_value) ⇒ Object

Parse Last-Modified header



128
129
130
131
132
133
134
135
136
# File 'lib/lutaml/store/http_header_processor.rb', line 128

def self.parse_last_modified(header_value)
  return nil unless header_value

  begin
    Time.parse(header_value)
  rescue StandardError
    nil
  end
end

.parse_vary_header(vary_header) ⇒ Object

Parse Vary header



88
89
90
91
92
# File 'lib/lutaml/store/http_header_processor.rb', line 88

def self.parse_vary_header(vary_header)
  return [] unless vary_header

  vary_header.split(",").map(&:strip).map(&:downcase)
end

.should_cache_response?(status_code, response_headers) ⇒ Boolean

Determine if response should be cached

Returns:

  • (Boolean)


53
54
55
56
57
58
59
60
61
# File 'lib/lutaml/store/http_header_processor.rb', line 53

def self.should_cache_response?(status_code, response_headers)
  return false if status_code < 200 || status_code >= 400

  cache_control = parse_cache_control(response_headers["cache-control"])
  return false if cache_control["no-store"]
  return false if cache_control["private"] # Unless explicitly allowing private

  true
end