Skip to content

fix: preserve binary response bodies through adapter chain#16

Merged
Blankll merged 3 commits into
masterfrom
fix/binary-response-handling
Jun 30, 2026
Merged

fix: preserve binary response bodies through adapter chain#16
Blankll merged 3 commits into
masterfrom
fix/binary-response-handling

Conversation

@Blankll

@Blankll Blankll commented Jun 30, 2026

Copy link
Copy Markdown
Member

Problem

Binary response bodies (e.g. image/png tiles from Hono) were corrupted when proxied through the adapter chain. Bytes >= 0x80 were replaced with UTF-8 replacement character U+FFFD (0xEF 0xBF 0xBD), destroying the PNG signature.

Root Cause

Two corruption points in the chain:

  1. serverlessResponse.ts: The mock socket's write() method called getString() which converts Buffer to UTF-8 string via toString('utf8'). Binary bytes >= 0x80 become U+FFFD.

  2. transport.ts: buildResponse() always called body.toString('utf8') regardless of content type.

Additionally, index.ts was overwriting the response's isBase64Encoded with the request's value.

Fix

  1. serverlessResponse.ts: Use Buffer.indexOf('\r\n\r\n') to find the HTTP header boundary in raw bytes. Body data is added as Buffer, never converted to string. Removed the now-unused getString() helper.

  2. transport.ts: Detect binary content types (image/, audio/, video/, application/octet-stream, application/pdf, font/) and base64-encode them with isBase64Encoded: true. Text responses continue to use UTF-8.

  3. index.ts: Pass builtResponse directly to provider.formatResponse() without overriding its isBase64Encoded field.

Testing

  • All existing tests pass
  • Manually verified: 4 different zoom levels of tile PNGs return correct \x89PNG signatures through the full chain (Hono → adapter → si-local → HTTP response)
  • Binary data bytes match direct Tianditu API responses exactly

When a framework (Hono) returns a binary response body (e.g. image/png),
the adapter chain was corrupting the data at two points:

1. serverlessResponse.ts: The mock socket's write() method used getString()
   which converts Buffer to UTF-8 string via toString('utf8'), replacing
   bytes >= 0x80 with U+FFFD (EF BF BD). Fixed by using Buffer.indexOf()
   to detect the HTTP header boundary directly in raw bytes.

2. transport.ts: buildResponse() always converted the accumulated body
   buffer to string with toString('utf8'), regardless of content type.
   Fixed by detecting binary content types (image/, audio/, video/,
   application/octet-stream, application/pdf, font/) and base64-encoding
   the body with isBase64Encoded:true in those cases.

3. index.ts: The response's isBase64Encoded (correctly set by buildResponse
   based on content-type) was being overwritten by the request's
   isBase64Encoded value. Removed the override.

Also removed the now-unused getString() helper and headerEnd constant.
- transport.test.ts: 12 new tests for binary content-type detection
  (image/audio/video/pdf/font → base64, text/json/html → utf8)
- serverlessResponse.test.ts: 3 new tests for binary data preservation
  through mock socket write
- index-hono.spec.test.ts: 3 new integration tests verifying
  isBase64Encoded flag set correctly for image/png vs text/html

Also fixed: removed application/octet-stream from binary detection
(too generic — Express defaults to it for Buffer responses).
@codecov

codecov Bot commented Jun 30, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.11%. Comparing base (392bf8b) to head (0f8c867).

Additional details and impacted files
@@            Coverage Diff             @@
##           master      #16      +/-   ##
==========================================
+ Coverage   91.01%   93.11%   +2.09%     
==========================================
  Files          13       13              
  Lines         334      334              
  Branches       76       78       +2     
==========================================
+ Hits          304      311       +7     
+ Misses         20       15       -5     
+ Partials       10        8       -2     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Blankll added a commit to geek-fun/serverlessinsight that referenced this pull request Jun 30, 2026
…200)

## Problem

When `@geek-fun/serverless-adapter` returns a response with
`isBase64Encoded: true` for binary content types (e.g. `image/png`),
`transformFCResponse` was converting the base64-decoded data to a UTF-8
string via `Buffer.from(body, 'base64').toString('utf-8')`.

This corrupted binary data — bytes \>= 0x80 became U+FFFD (`0xEF 0xBF
0xBD`). The result was then passed to `respondRaw()` which called
`res.end(string)`, sending corrupt UTF-8 instead of raw binary bytes.

## Fix

Detect binary content types by checking the `content-type` header:

- `image/`, `audio/`, `video/`, `application/octet-stream`,
`application/pdf`, `font/` → keep decoded body as **Buffer**
- All other types (text, JSON, etc.) → convert to **UTF-8 string** as
before

This allows `respondRaw()` to call `res.end(buffer)` with raw bytes for
binary content, while preserving backward compatibility for text/JSON
responses.

## Changed Files

- `src/stack/localStack/aliyunFc.ts`: Binary content-type detection +
Buffer preservation
- `tests/unit/stack/localStack/aliyunFc.unit.test.ts`: Updated PNG test
to verify Buffer output with `Buffer.equals()` byte equality check

## Depends On

- geek-fun/serverless-adapter#16 (binary
response base64 encoding)

---------

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
@Blankll Blankll merged commit cfe0ed2 into master Jun 30, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant