try chunk hashing

This commit is contained in:
wiaamm
2025-11-29 22:08:38 +02:00
parent b1cb48d6a7
commit f900f8a68e

View File

@@ -185,41 +185,42 @@ function NanoHandler.body_filter(conf)
-- Initialize chunk counter and timeout tracking on first call -- Initialize chunk counter and timeout tracking on first call
if not ctx.body_buffer_chunk then if not ctx.body_buffer_chunk then
ctx.body_buffer_chunk = 0 ctx.body_buffer_chunk = 0
ctx.body_filter_start_time = ngx.now() * 1000 -- Current time in milliseconds ctx.body_filter_start_time = ngx.now() * 1000
ctx.body_filter_timeout = false ctx.body_filter_timeout = false
ctx.failed_nano_send = false
ctx.num_body_filter_calls = 0
ctx.last_chunk_hash = nil
end end
-- Check if we've exceeded 150ms timeout ctx.num_body_filter_calls = ctx.num_body_filter_calls + 1
local elapsed_time = (ngx.now() * 1000) - ctx.body_filter_start_time
if elapsed_time > 150000 then if ctx.failed_nano_send then
if not ctx.body_filter_timeout then return
ctx.body_filter_timeout = true end
kong.log.warn("body_filter timeout exceeded (150ms), failing open - no more chunks sent to nano-agent")
end local current_time = ngx.now() * 1000
-- Fail-open: pass through remaining chunks without inspection if current_time - ctx.body_filter_start_time > 150000 then
if not ctx.body_filter_timeout then
ctx.body_filter_timeout = true
ctx.failed_nano_send = true
kong.log.warn("body_filter timeout exceeded (2.5 minutes), failing open")
end
return return
end end
-- Get the current chunk from ngx.arg[1] (this is how Kong streams body data)
local chunk = ngx.arg[1] local chunk = ngx.arg[1]
local eof = ngx.arg[2] -- true on last chunk local eof = ngx.arg[2]
-- Try Kong API for small in-memory bodies (backward compatibility)
local full_body = kong.response.get_raw_body() local full_body = kong.response.get_raw_body()
-- Determine if we're dealing with a full body or streaming chunks
local is_streaming = (full_body == nil and chunk ~= nil) local is_streaming = (full_body == nil and chunk ~= nil)
-- If no body content at all (no full_body, no chunk, and no EOF), just return
-- This prevents sending empty traffic to nano-agent
if not full_body and not chunk and not eof then if not full_body and not chunk and not eof then
return return
end end
kong.log.debug("body_filter call #", ctx.num_body_filter_calls, ", sent: ", ctx.body_buffer_chunk, " chunks")
if full_body and not ctx.body_seen then if full_body and not ctx.body_seen then
-- Small response body - use Kong API (original behavior)
ctx.body_seen = true ctx.body_seen = true
kong.log.debug("Processing in-memory response body, size: ", #full_body)
local ok, result = pcall(function() local ok, result = pcall(function()
return {nano.send_body(session_id, session_data, full_body, nano.HttpChunkType.HTTP_RESPONSE_BODY)} return {nano.send_body(session_id, session_data, full_body, nano.HttpChunkType.HTTP_RESPONSE_BODY)}
@@ -247,57 +248,55 @@ function NanoHandler.body_filter(conf)
else else
kong.log.warn("nano.send_body failed for in-memory body: ", result) kong.log.warn("nano.send_body failed for in-memory body: ", result)
ctx.body_buffer_chunk = ctx.body_buffer_chunk + 1 ctx.body_buffer_chunk = ctx.body_buffer_chunk + 1
ctx.failed_nano_send = true
end end
elseif is_streaming and chunk and type(chunk) == "string" and #chunk > 0 then elseif is_streaming and chunk and type(chunk) == "string" and #chunk > 0 then
-- Large response body - streaming chunks (file-buffered or large in-memory)
ctx.body_seen = true ctx.body_seen = true
local chunk_size = #chunk local chunk_hash = ngx.crc32_long(chunk)
kong.log.debug("Processing response body chunk #", ctx.body_buffer_chunk, ", size: ", chunk_size, ", eof: ", eof)
if chunk_hash == ctx.last_chunk_hash then
kong.log.debug("Duplicate chunk detected (same hash as previous), skipping")
return
end
local ok, result = pcall(function() local ok, result = pcall(function()
return {nano.send_body(session_id, session_data, chunk, nano.HttpChunkType.HTTP_RESPONSE_BODY)} return {nano.send_body(session_id, session_data, chunk, nano.HttpChunkType.HTTP_RESPONSE_BODY)}
end) end)
if ok and result and result[1] then if ok and result and result[1] then
ctx.last_chunk_hash = chunk_hash
ctx.body_buffer_chunk = ctx.body_buffer_chunk + 1
local verdict = result[1] local verdict = result[1]
local response = result[2] local response = result[2]
local modifications = result[3] local modifications = result[3]
-- Apply modifications to this chunk
if modifications then if modifications then
chunk = nano.handle_body_modifications(chunk, modifications, ctx.body_buffer_chunk) chunk = nano.handle_body_modifications(chunk, modifications, ctx.body_buffer_chunk - 1)
end end
ctx.body_buffer_chunk = ctx.body_buffer_chunk + 1
if verdict == nano.AttachmentVerdict.DROP then if verdict == nano.AttachmentVerdict.DROP then
nano.fini_session(session_data) nano.fini_session(session_data)
ctx.session_finalized = true ctx.session_finalized = true
ngx.arg[1] = nil -- Clear the output ngx.arg[1] = nil
ngx.arg[2] = true -- Force EOF ngx.arg[2] = true
local custom_result = nano.handle_custom_response(session_data, response) local custom_result = nano.handle_custom_response(session_data, response)
nano.cleanup_all() nano.cleanup_all()
return custom_result return custom_result
end end
-- Update the chunk that will be sent to client (CRITICAL for streaming)
ngx.arg[1] = chunk ngx.arg[1] = chunk
else else
kong.log.warn("nano.send_body failed for chunk #", ctx.body_buffer_chunk, ": ", result) kong.log.warn("nano.send_body failed for chunk #", ctx.body_buffer_chunk + 1, ": ", result)
ctx.body_buffer_chunk = ctx.body_buffer_chunk + 1 ctx.body_buffer_chunk = ctx.body_buffer_chunk + 1
-- Fail-open: pass chunk through unmodified ctx.failed_nano_send = true
-- ngx.arg[1] is already set to the original chunk
end end
end end
-- Finalize session on last chunk (EOF) or when no body expected
if eof or (ctx.expect_body == false and not ctx.body_seen) then if eof or (ctx.expect_body == false and not ctx.body_seen) then
kong.log.debug("Finalizing response inspection, body_seen: ", ctx.body_seen, ", eof: ", eof, ", timeout: ", ctx.body_filter_timeout) if not ctx.body_filter_timeout and not ctx.failed_nano_send then
-- Only send end_inspection if we haven't timed out
if not ctx.body_filter_timeout then
local ok, result = pcall(function() local ok, result = pcall(function()
return {nano.end_inspection(session_id, session_data, nano.HttpChunkType.HTTP_RESPONSE_END)} return {nano.end_inspection(session_id, session_data, nano.HttpChunkType.HTTP_RESPONSE_END)}
end) end)
@@ -309,8 +308,8 @@ function NanoHandler.body_filter(conf)
if verdict == nano.AttachmentVerdict.DROP then if verdict == nano.AttachmentVerdict.DROP then
nano.fini_session(session_data) nano.fini_session(session_data)
ctx.session_finalized = true ctx.session_finalized = true
ngx.arg[1] = nil -- Clear any remaining output ngx.arg[1] = nil
ngx.arg[2] = true -- Force EOF ngx.arg[2] = true
local custom_result = nano.handle_custom_response(session_data, response) local custom_result = nano.handle_custom_response(session_data, response)
nano.cleanup_all() nano.cleanup_all()
return custom_result return custom_result
@@ -318,8 +317,6 @@ function NanoHandler.body_filter(conf)
else else
kong.log.warn("nano.end_inspection failed: ", result) kong.log.warn("nano.end_inspection failed: ", result)
end end
else
kong.log.debug("Skipping end_inspection due to timeout - failing open")
end end
nano.fini_session(session_data) nano.fini_session(session_data)