Class: Faraday::Gzip::Middleware

Inherits:
Middleware
  • Object
show all
Defined in:
lib/faraday/gzip/middleware.rb

Overview

Faraday middleware for decompression

Constant Summary collapse

ACCEPT_ENCODING =
'Accept-Encoding'
CONTENT_ENCODING =
'Content-Encoding'
CONTENT_LENGTH =
'Content-Length'
IDENTITY =
'identity'
BROTLI_SUPPORTED =
optional_dependency 'brotli'
SUPPORTED_ENCODINGS =
supported_encodings.join(',').freeze

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.optional_dependency(lib = nil) ⇒ Object

System method required by Faraday



22
23
24
25
26
27
# File 'lib/faraday/gzip/middleware.rb', line 22

def self.optional_dependency(lib = nil)
  lib ? require(lib) : yield
  true
rescue LoadError, NameError
  false
end

.supported_encodings ⇒ Object

Returns supported encodings, adds brotli if the corresponding dependency is present



33
34
35
36
37
# File 'lib/faraday/gzip/middleware.rb', line 33

def self.supported_encodings
  encodings = %w[gzip deflate]
  encodings << 'br' if BROTLI_SUPPORTED
  encodings
end

Instance Method Details

#brotli_inflate(body) ⇒ Object

Process brotli



115
116
117
# File 'lib/faraday/gzip/middleware.rb', line 115

def brotli_inflate(body)
  Brotli.inflate(body)
end

#call(env) ⇒ Object

Main method to process the response



42
43
44
45
46
47
48
# File 'lib/faraday/gzip/middleware.rb', line 42

def call(env)
  env[:request_headers][ACCEPT_ENCODING] ||= SUPPORTED_ENCODINGS

  @app.call(env).on_complete do |response_env|
    reset_body(response_env, find_processor(response_env))
  end
end

#find_processor(response_env) ⇒ Object

Finds a proper processor



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/faraday/gzip/middleware.rb', line 51

def find_processor(response_env)
  body = response_env[:body]

  encodings = parse_content_encoding(
    response_env[:response_headers][CONTENT_ENCODING]
  )
  return nil if encodings.empty? || encodings.include?(IDENTITY)

  # If body is nil/empty, we still want to normalize headers:
  # Content-Encoding is meaningless without an encoded body.
  return ->(b) { b } if body_nil_or_empty?(body)

  chain = processor_chain(encodings)
  return nil if chain.empty?

  ->(b) { chain.reduce(b) { |acc, fn| fn.call(acc) } }
end

#inflate(body) ⇒ Object

Process deflate



100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/faraday/gzip/middleware.rb', line 100

def inflate(body)
  # Inflate as a DEFLATE (RFC 1950+RFC 1951) stream
  Zlib::Inflate.inflate(body)
rescue Zlib::DataError
  # Fall back to inflating as a "raw" deflate stream which
  # Microsoft servers return
  inflate = Zlib::Inflate.new(-Zlib::MAX_WBITS)
  begin
    inflate.inflate(body)
  ensure
    inflate.close
  end
end

#reset_body(env, processor) ⇒ Object

Calls the proper processor to decompress body



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/faraday/gzip/middleware.rb', line 70

def reset_body(env, processor)
  return if processor.nil?

  body    = env[:body]
  headers = env[:response_headers]

  # Don't touch streaming / IO-like bodies.
  return unless body.is_a?(String) || body_nil_or_empty?(body)

  if body.is_a?(String)
    env[:body] = processor.call(body)
    headers[CONTENT_LENGTH] = env[:body].bytesize
  end

  # Normalize encoding header even for nil/empty body.
  headers.delete(CONTENT_ENCODING)
end

#uncompress_gzip(body) ⇒ Object

Process gzip



89
90
91
92
93
94
95
96
97
# File 'lib/faraday/gzip/middleware.rb', line 89

def uncompress_gzip(body)
  io = StringIO.new(body)
  gzip_reader = Zlib::GzipReader.new(io, encoding: 'ASCII-8BIT')
  begin
    gzip_reader.read
  ensure
    gzip_reader.close
  end
end