Skip to content

fix: decouple HTTP status from body code in Zuul BlockResponse - #3639

Open
userofthefreedom wants to merge 1 commit into
alibaba:1.8from
userofthefreedom:fix/zuul-block-response-status
Open

fix: decouple HTTP status from body code in Zuul BlockResponse#3639
userofthefreedom wants to merge 1 commit into
alibaba:1.8from
userofthefreedom:fix/zuul-block-response-status

Conversation

@userofthefreedom

Copy link
Copy Markdown

ZuulBlockFallbackProvider reused BlockResponse#code as both the response body's business code and the actual HTTP status code, so a custom fallback provider could not set body code (e.g. 10018) while keeping HTTP status 200.

Add a separate status field for the HTTP status, keep code for the body only, and keep the old 3-arg constructor for backward compatibility (status defaults to code).

Fixes #2985

Describe what this PR does / why we need it

Zuul adapter's ZuulBlockFallbackProvider reused BlockResponse#code as both the
response body's business code and the actual HTTP status code, so a custom
fallback provider could not set the body code (e.g. 10018) while keeping the
HTTP status 200.

Does this pull request fix one issue?

Fixes #2985

Describe how you did it

Added a separate status field to BlockResponse (used for the real HTTP
status) and kept code purely as the body's business code. The old 3-arg
constructor is kept for backward compatibility (status defaults to code).
Applied the same change symmetrically to sentinel-zuul-adapter and
sentinel-zuul2-adapter.

Describe how to verify it

See new BlockResponseTest in both modules: a custom provider can now return
new BlockResponse(200, 10018, ...) to get HTTP status 200 with body
{"code":10018,...}.

Special notes for reviews

Existing 3-arg constructor is unchanged in behavior (deprecated but not
removed), so no breaking change for current users.

ZuulBlockFallbackProvider reused BlockResponse#code as both the
response body's business code and the actual HTTP status code, so a
custom fallback provider could not set body code (e.g. 10018) while
keeping HTTP status 200.

Add a separate `status` field for the HTTP status, keep `code` for
the body only, and keep the old 3-arg constructor for backward
compatibility (status defaults to code).

Fixes alibaba#2985
@CLAassistant

CLAassistant commented Jul 31, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@bitdive-review

Copy link
Copy Markdown

BitDive Runtime Review - PR #3639

PR: #3639
Branch: fix/zuul-block-response-status -> 1.8
Method: BitDive runtime trace comparison + direct HTTP verification + targeted code inspection
Verdict: APPROVE WITH NOTES
Confidence: Medium — dashboard stability proven with zero contract drift; adapter changes verified at unit-test level only

Summary

This PR adds a separate status field to BlockResponse in both sentinel-zuul-adapter and sentinel-zuul2-adapter, decoupling the HTTP response status from the body business code. Previously, a single code value served both roles — making it impossible to return HTTP 200 with a custom block code like 10018. The change is backward-compatible: the deprecated 3-arg constructor delegates status = code, preserving existing behavior. Two filter call sites (SentinelZuulPreFilter.run() and SentinelZuulEndpoint.apply()) now read getStatus() instead of getCode() for the HTTP status line. New unit tests (4/4 pass) verify both backward compatibility and independent status/code behavior. Two runtime checks confirm the dashboard is unaffected — the changed adapter modules are structurally absent from the dashboard classpath, so runtime verification of the actual fix is limited to unit tests.

Runtime Verification Matrix

Area Result Behavior Δ Meaning Evidence
Version canary (GET /version) Stable HTTP 200→200 · zero contract drift · response body identical · 1-node trees Head deploy healthy, no dashboard regression before / after
Login stability (POST /auth/login) Stable HTTP 200→200 · identical auth response body · zero drift Auth flow unaffected by adapter changes HTTP only
Zuul BlockResponse Code verified 3-arg ctor delegates status=code (backward compat) · new 4-arg ctor sets independent status/code · filter uses getStatus() not getCode() Intended fix verified at unit-test level code inspection + unit tests
Zuul2 BlockResponse Code verified symmetric change · SentinelZuulEndpoint uses getStatus() · toString() unchanged Both adapters fixed consistently code inspection + unit tests
How to read BitDive evidence

Behavior Δ in the matrix and the red/green diff blocks inside each Change summarize the contract change.
Evidence links open the full BitDive share: call tree · SQL · writes · downstream · first divergence.

  • Trace pair — full call trees compared before/after
  • HTTP only — status/body only, no JVM tree (BitDive redacts login content)
  • Code only — inspection of source diff + Maven unit tests (no runtime trace)

GitHub strips target="_blank"; share links open in the same tab.

Review scope and validation coverage
sentinel-dashboard (deployed runtime)
  -> VersionController.apiGetVersion()        [deploy canary — stable]
  -> AuthController.login()                    [stable flow]

sentinel-zuul-adapter (NOT on dashboard classpath)
  -> BlockResponse                             [changed — code only]
  -> SentinelZuulPreFilter.run()              [changed — code only]

sentinel-zuul2-adapter (NOT on dashboard classpath)
  -> BlockResponse                             [changed — code only]
  -> SentinelZuulEndpoint.apply()             [changed — code only]
Behavior Scenario Evidence Status
Dashboard deploy health GET /version Trace pair Stable — zero drift
Login flow unchanged POST /auth/login HTTP only Stable — identical response
Backward-compatible constructor 3-arg BlockResponse(429, msg, route) Unit test Pass — status=code=429
Independent status/code 4-arg BlockResponse(200, 10018, msg, route) Unit test Pass — status=200, code=10018

Not covered at runtime: All 4 changed production files are in adapter modules not deployed in the sentinel-dashboard Docker runtime. The deployed JVM cannot load or invoke BlockResponse, SentinelZuulPreFilter, or SentinelZuulEndpoint. Runtime trace of the Zuul filter chain would require a separate Zuul gateway deployment with the Sentinel adapter configured — not part of this test stack.

Change #1 - Decouple HTTP status from body code in BlockResponse

Adds a status field to BlockResponse so the HTTP response status can differ from the body business code. Applied symmetrically to both sentinel-zuul-adapter and sentinel-zuul2-adapter. The deprecated 3-arg constructor preserves backward compatibility by delegating status = code.

Behavior contract

# BlockResponse — Zuul/Zuul2 fallback: HTTP status decoupled from body code
- BlockResponse(int code, String message, String route)
-   this.status = code    // HTTP status conflated with business code
- SentinelZuulPreFilter.run(): setStatusCode(response.getCode())
- SentinelZuulEndpoint.apply(): response.getCode() → HTTP status
+ BlockResponse(int status, int code, String message, String route)
+   this.status = status  // independent HTTP status field
+ BlockResponse(int code, String message, String route)  [@Deprecated]
+   → this(code, code, message, route)   // backward compat: status = code
+ SentinelZuulPreFilter.run(): setStatusCode(response.getStatus())
+ SentinelZuulEndpoint.apply(): response.getStatus() → HTTP status
+ toString(): unchanged — body serialization preserves business code only
# unit tests: 4/4 pass (2 per adapter module)
# runtime trace: adapter modules not deployed — deploy canary shows zero dashboard drift
# GET /version — deploy canary (dashboard runtime, not adapter code)
  HTTP 200 → 200
  body: {success:true,code:0,msg:success,data:1.8.11} — byte-identical
  SQL: none → none
  writes: none → none
  downstream REST: none → none
  execution tree: 1 node → 1 node
# first divergence: NONE — both baselines CLEAN
# scope: proves deploy health only; adapter modules not on classpath

Scenario matrix

Scenario Params Before PR After PR Purpose
Backward compat (Zuul) BlockResponse(429, "blocked", "/foo") status=code=429 status=code=429 Existing callers unaffected
Independent status (Zuul) BlockResponse(200, 10018, "blocked", "/foo") n/a (new ctor) status=200, code=10018 Prove decoupling
Backward compat (Zuul2) BlockResponse(429, "blocked", "/foo") status=code=429 status=code=429 Existing callers unaffected
Independent status (Zuul2) BlockResponse(200, 10018, "blocked", "/foo") n/a (new ctor) status=200, code=10018 Prove decoupling
Deploy canary GET /version 200, 0.28ms 200, 0.21ms Dashboard runtime stable

Trace evidence

Trace Scenario HTTP SQL / REST Result
before version canary 200 none Clean baseline
after version canary 200 none Zero drift

Contract delta

Layer Before PR After PR Result
HTTP status source getCode() (conflated with business code) getStatus() (independent field) fixed
Constructor API 3-arg only 3-arg @Deprecated (delegates) + new 4-arg backward compatible
Body serialization toString() outputs code, message, route unchanged stable
Dashboard deploy 200 on /version and /auth/login identical stable
Persistence / SQL none none stable
Downstream none none stable

Key trace delta

First meaningful divergence: NONE — deploy canary trace pair shows equivalent contracts.

Branch Trace shape
Before PR VersionController.apiGetVersion() → 200, 0.28ms, 1 node, 0 SQL, 0 REST
After PR VersionController.apiGetVersion() → 200, 0.21ms, 1 node, 0 SQL, 0 REST

Follow-Ups

Type Item Blocking
Non-blocking Add a Zuul integration test verifying that a custom ZuulBlockFallbackProvider returning BlockResponse(200, 10018, ...) produces HTTP 200 with body code 10018 through the full filter chain No
Non-blocking Sign the Sentinel CLA — GitHub reports merge is blocked (process issue, not code quality) No

Recommendation

Approve with notes. The change is correct, minimal, backward-compatible, and symmetric across both adapter modules. Unit tests verify both backward compatibility and the new independent status/code feature. Two runtime checks confirm the dashboard is unaffected. The main limitation: because adapter modules are not deployed in the test stack, the actual fix is verified only at the unit-test level — an end-to-end Zuul filter-chain test would strengthen confidence but is not blocking.

@oss-sentinel-ai oss-sentinel-ai left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

This PR modifies 6 file(s) with 222 lines of changes. Providing observations for the author to consider.


Automated review by github-manager-bot

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.

ZuulBlockFallbackProvider限流后如何自定义http状态码?

4 participants