Skip to content

Commit b383ade

Browse files
authored
fix(brotli): Preserve ETag and Last-Modified headers in Brotli-compressed response (#12853)
1 parent 2e0b6b2 commit b383ade

File tree

2 files changed

+133
-2
lines changed

2 files changed

+133
-2
lines changed

apisix/plugins/brotli.lua

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,31 @@ local function check_accept_encoding(ctx)
152152
end
153153

154154

155+
local function weak_etag_header()
156+
local etag = ngx_header["Etag"]
157+
if not etag then
158+
return
159+
end
160+
161+
local regex = [[^(W/)?"(.*)"$]]
162+
local matched, err = ngx.re.match(etag, regex, "jo")
163+
if not matched or err then
164+
-- not standard etag, no quote
165+
core.response.set_header("Etag", nil)
166+
core.log.error("no standard etag or regex match failed: ", err)
167+
return
168+
end
169+
170+
if not matched[1] then
171+
-- strong etag, downgrade it
172+
core.response.set_header("Etag", [[W/"]] .. matched[2] .. [["]])
173+
else
174+
-- weak etag, keep it
175+
return
176+
end
177+
end
178+
179+
155180
function _M.header_filter(conf, ctx)
156181
if not is_loaded then
157182
core.log.error("please check the brotli library")
@@ -222,8 +247,9 @@ function _M.header_filter(conf, ctx)
222247

223248
ctx.brotli_matched = true
224249
ctx.compressor = compressor
225-
core.response.clear_header_as_body_modified()
226-
core.response.add_header("Content-Encoding", "br")
250+
core.response.set_header("Content-Length", nil)
251+
core.response.set_header("Content-Encoding", "br")
252+
weak_etag_header()
227253
end
228254

229255

t/plugin/brotli.t

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -783,3 +783,108 @@ Vary: upstream
783783
Content-Type: text/html
784784
--- response_body
785785
ok
786+
787+
788+
789+
=== TEST 32: etag
790+
--- config
791+
location /t {
792+
content_by_lua_block {
793+
local t = require("lib.test_admin").test
794+
local code, body = t('/apisix/admin/routes/1',
795+
ngx.HTTP_PUT,
796+
[[{
797+
"uri": "/echo",
798+
"upstream": {
799+
"type": "roundrobin",
800+
"nodes": {
801+
"127.0.0.1:1980": 1
802+
}
803+
},
804+
"plugins": {
805+
"brotli": {
806+
}
807+
}
808+
}]]
809+
)
810+
811+
if code >= 300 then
812+
ngx.status = code
813+
end
814+
ngx.say(body)
815+
}
816+
}
817+
--- response_body
818+
passed
819+
820+
821+
822+
=== TEST 33: not standard etag, clear it
823+
--- request
824+
POST /echo
825+
0123456789
826+
012345678
827+
--- more_headers
828+
Accept-Encoding: br
829+
Content-Type: text/html
830+
Etag: 123456789
831+
--- response_headers
832+
Content-Encoding: br
833+
Vary:
834+
Etag:
835+
Content-Length:
836+
--- error_log
837+
no standard etag or regex match failed:
838+
839+
840+
841+
=== TEST 34: weak etag, keep it
842+
--- request
843+
POST /echo
844+
0123456789
845+
012345678
846+
--- more_headers
847+
Accept-Encoding: br
848+
Content-Type: text/html
849+
Etag: W/"123456789"
850+
--- response_headers
851+
Content-Encoding: br
852+
Vary:
853+
Etag: W/"123456789"
854+
Content-Length:
855+
856+
857+
858+
=== TEST 35: strong etag, downgrade it
859+
--- request
860+
POST /echo
861+
0123456789
862+
012345678
863+
--- more_headers
864+
Accept-Encoding: br
865+
Content-Type: text/html
866+
Etag: "123456789"
867+
--- response_headers
868+
Content-Encoding: br
869+
Vary:
870+
Etag: W/"123456789"
871+
Content-Length:
872+
873+
874+
875+
=== TEST 36: last modified, do nothing
876+
--- request
877+
POST /echo
878+
0123456789
879+
012345678
880+
--- more_headers
881+
Accept-Encoding: br
882+
Content-Type: text/html
883+
Etag: "123456789"
884+
Last-Modified: Thu, 27 Nov 2025 00:32:33 GMT
885+
--- response_headers
886+
Content-Encoding: br
887+
Vary:
888+
Etag: W/"123456789"
889+
Last-Modified: Thu, 27 Nov 2025 00:32:33 GMT
890+
Content-Length:

0 commit comments

Comments
 (0)