web: gzip the pages, stylesheets and script for a browser that asks
/contacts is 500 KB of markup on a 1,000-card book and gzips to 16 KB.
Rack::Deflater, limited to text/html, text/css and text/javascript,
so DAV responses and pictures go out unchanged.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KZeaKvJp7q4WEwoCPqVkZb
diff --git a/sig/gems/rack.rbs b/sig/gems/rack.rbs
index a2fff5b..665db2e 100644
--- a/sig/gems/rack.rbs
+++ b/sig/gems/rack.rbs
@@ -2,7 +2,7 @@
# lib/pro_tacts speaks — the environment, the response triple, and an
# app to delegate to — plus the two constants used directly: the status
# phrases ExchangeLog and RefusalAlerts print, and the middleware web.rb
-# mounts so a body can be read twice.
+# mounts so a body can be read twice and so a page goes out gzipped.
module Rack
# The CGI-ish hash a middleware is called with. Values stay untyped:
# anything upstream may put anything in it, which is why the code
@@ -42,4 +42,7 @@ module Rack
class Middleware
end
end
+
+ class Deflater
+ end
end
diff --git a/test/pro_tacts/test_web.rb b/test/pro_tacts/test_web.rb
index f7c1b09..dc8489f 100644
--- a/test/pro_tacts/test_web.rb
+++ b/test/pro_tacts/test_web.rb
@@ -6,6 +6,7 @@ require "digest"
require "fileutils"
require "pathname"
require "rack/test"
+require "zlib"
require "pro_tacts/config"
require "pro_tacts/vcard"
@@ -203,6 +204,40 @@ class WebTest < Minitest::Test
end
end
+ ## Compression
+
+ # A page and the files it loads go out gzipped to a browser that
+ # asks, and come back to the page's own bytes.
+ def test_a_page_and_its_stylesheet_are_gzipped_for_a_browser
+ with_contacts({"pic" => PhotoCard.photo("pic", bytes: 256)}) do
+ header "Accept-Encoding", "gzip"
+
+ %w[/contacts /admin.css].each do |path|
+ get path
+
+ assert_equal "gzip", last_response["Content-Encoding"], path
+ refute_empty Zlib.gunzip(last_response.body), path
+ end
+ end
+ end
+
+ # What a DAV client is sent stays the bytes it was confirmed
+ # against, whatever it says it accepts, and a picture is not
+ # squeezed twice.
+ def test_dav_responses_and_pictures_go_out_as_written
+ with_contacts({"pic" => PhotoCard.photo("pic", bytes: 256)}) do
+ header "Accept-Encoding", "gzip"
+
+ request "/dav/addressbook/", method: "PROPFIND"
+ assert_equal 207, last_response.status
+ assert_nil last_response["Content-Encoding"]
+
+ get "/contacts/pic/photo"
+ assert_equal 200, last_response.status
+ assert_nil last_response["Content-Encoding"]
+ end
+ end
+
## Refusing a write another site's page sent
# The forged create: a page elsewhere submitting the dashboard's