Skip to content

Commit

Permalink
feat: improve decompression middleware
Browse files Browse the repository at this point in the history
closes #598

Signed-off-by: Yordis Prieto <[email protected]>
  • Loading branch information
yordis committed Oct 5, 2023
1 parent c1e68bb commit cac792c
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 7 deletions.
57 changes: 55 additions & 2 deletions lib/tesla/middleware/compression.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ defmodule Tesla.Middleware.Compression do
- `:format` - request compression format, `"gzip"` (default) or `"deflate"`
"""

require Logger

@behaviour Tesla.Middleware

@impl Tesla.Middleware
Expand Down Expand Up @@ -61,13 +63,64 @@ defmodule Tesla.Middleware.Compression do
def decompress({:error, reason}), do: {:error, reason}

def decompress(env) do
codecs = compression_algorithms(Tesla.get_header(env, "content-encoding"))
{decompressed_body, unknown_codecs} = decompress_body(codecs, env.body, [])

env
|> put_decompressed_body(decompressed_body)
|> put_or_delete_content_encoding(unknown_codecs)
end

defp put_or_delete_content_encoding(env, []) do
Tesla.delete_header(env, "content-encoding")
end

defp put_or_delete_content_encoding(env, unknown_codecs) do
Tesla.put_header(env, "content-encoding", Enum.join(unknown_codecs, ", "))
end

defp decompress_body([gzip | rest], body, acc) when gzip in ["gzip", "x-gzip"] do
decompress_body(rest, :zlib.gunzip(body), acc)
end

defp decompress_body(["identity" | rest], body, acc) do
decompress_body(rest, body, acc)
end

defp decompress_body([codec | rest], body, acc) do
decompress_body(rest, body, [codec | acc])
end

defp decompress_body([], body, acc) do
{body, acc}
end

defp compression_algorithms(nil) do
[]
end

defp compression_algorithms(value) do
value
|> String.downcase()
|> String.split(",", trim: true)
|> Enum.map(&String.trim/1)
|> Enum.reverse()
end

defp put_decompressed_body(env, body) do
content_length =
body
|> byte_size()
|> to_string()

env
|> Tesla.put_body(decompress_body(env.body, Tesla.get_header(env, "content-encoding")))
|> Tesla.put_body(body)
|> Tesla.put_header("content-length", content_length)
end

defp decompress_body(<<31, 139, 8, _::binary>> = body, "gzip"), do: :zlib.gunzip(body)

Check warning on line 121 in lib/tesla/middleware/compression.ex

View workflow job for this annotation

GitHub Actions / OTP 25.3 / Elixir 1.14

function decompress_body/2 is unused

Check warning on line 121 in lib/tesla/middleware/compression.ex

View workflow job for this annotation

GitHub Actions / OTP 24.3 / Elixir 1.14

function decompress_body/2 is unused

Check warning on line 121 in lib/tesla/middleware/compression.ex

View workflow job for this annotation

GitHub Actions / OTP 25.3 / Elixir 1.13

function decompress_body/2 is unused

Check warning on line 121 in lib/tesla/middleware/compression.ex

View workflow job for this annotation

GitHub Actions / OTP 24.3 / Elixir 1.13

function decompress_body/2 is unused
defp decompress_body(body, "deflate"), do: :zlib.unzip(body)
defp decompress_body(body, _content_encoding), do: body
defp decompress_body(_body, _content_encoding), do: nil
end

defmodule Tesla.Middleware.CompressRequest do
Expand Down
3 changes: 2 additions & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ defmodule Tesla.Mixfile do
plt_add_apps: [:mix, :inets, :idna, :ssl_verify_fun, :ex_unit],
plt_add_deps: :project
],
docs: docs()
docs: docs(),
preferred_cli_env: [coveralls: :test, "coveralls.html": :test]
]
end

Expand Down
7 changes: 7 additions & 0 deletions test/support/test_support.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
defmodule TestSupport do
def gzip_headers(env) do
env.headers
|> Enum.map_join("|", fn {key, value} -> "#{key}: #{value}" end)
|> :zlib.gzip()
end
end
12 changes: 8 additions & 4 deletions test/tesla/middleware/compression_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ defmodule Tesla.Middleware.CompressionTest do

test "decompress response body (gzip)" do
assert {:ok, env} = CompressionResponseClient.get("/response-gzip")
assert env.headers == [{"content-type", "text/plain"}, {"content-length", "17"}]
assert env.body == "decompressed gzip"
end

Expand All @@ -80,6 +81,7 @@ defmodule Tesla.Middleware.CompressionTest do
test "return unchanged response for unsupported content-encoding" do

Check failure on line 81 in test/tesla/middleware/compression_test.exs

View workflow job for this annotation

GitHub Actions / OTP 25.3 / Elixir 1.14

test return unchanged response for unsupported content-encoding (Tesla.Middleware.CompressionTest)

Check failure on line 81 in test/tesla/middleware/compression_test.exs

View workflow job for this annotation

GitHub Actions / OTP 24.3 / Elixir 1.14

test return unchanged response for unsupported content-encoding (Tesla.Middleware.CompressionTest)

Check failure on line 81 in test/tesla/middleware/compression_test.exs

View workflow job for this annotation

GitHub Actions / OTP 25.3 / Elixir 1.13

test return unchanged response for unsupported content-encoding (Tesla.Middleware.CompressionTest)

Check failure on line 81 in test/tesla/middleware/compression_test.exs

View workflow job for this annotation

GitHub Actions / OTP 24.3 / Elixir 1.13

test return unchanged response for unsupported content-encoding (Tesla.Middleware.CompressionTest)
assert {:ok, env} = CompressionResponseClient.get("/response-identity")
assert env.body == "unchanged"
assert env.headers == [{"content-type", "text/plain"}, {"content-encoding", "identity"}]
end

defmodule CompressRequestDecompressResponseClient do
Expand Down Expand Up @@ -114,7 +116,8 @@ defmodule Tesla.Middleware.CompressionTest do
{status, headers, body} =
case env.url do
"/" ->
{200, [{"content-type", "text/plain"}, {"content-encoding", "gzip"}], env.headers}
{200, [{"content-type", "text/plain"}, {"content-encoding", "gzip"}],
TestSupport.gzip_headers(env)}
end

{:ok, %{env | status: status, headers: headers, body: body}}
Expand All @@ -123,7 +126,7 @@ defmodule Tesla.Middleware.CompressionTest do

test "Compression headers" do
assert {:ok, env} = CompressionHeadersClient.get("/")
assert env.body == [{"accept-encoding", "gzip, deflate"}]
assert env.body == "accept-encoding: gzip, deflate"
end

defmodule DecompressResponseHeadersClient do
Expand All @@ -135,7 +138,8 @@ defmodule Tesla.Middleware.CompressionTest do
{status, headers, body} =
case env.url do
"/" ->
{200, [{"content-type", "text/plain"}, {"content-encoding", "gzip"}], env.headers}
{200, [{"content-type", "text/plain"}, {"content-encoding", "gzip"}],
TestSupport.gzip_headers(env)}
end

{:ok, %{env | status: status, headers: headers, body: body}}
Expand All @@ -144,7 +148,7 @@ defmodule Tesla.Middleware.CompressionTest do

test "Decompress response headers" do
assert {:ok, env} = DecompressResponseHeadersClient.get("/")
assert env.body == [{"accept-encoding", "gzip, deflate"}]
assert env.body == "accept-encoding: gzip, deflate"
end

defmodule CompressRequestHeadersClient do
Expand Down

0 comments on commit cac792c

Please sign in to comment.