Skip to content

router: bound inference request body size and HTTP listener connections - #1477

Open
vivek-gite wants to merge 4 commits into
volcano-sh:mainfrom
vivek-gite:feat/1475-router-requests-limits
Open

router: bound inference request body size and HTTP listener connections#1477
vivek-gite wants to merge 4 commits into
volcano-sh:mainfrom
vivek-gite:feat/1475-router-requests-limits

Conversation

@vivek-gite

Copy link
Copy Markdown
Contributor

What this does

Adds configurable request-size and connection-lifetime bounds to the Kthena Router's HTTP listeners, so a single client cannot exhaust router memory or hold connections open indefinitely.

Fixes #1475

Request body limit ([pkg/kthena-router/router/router.go](pkg/kthena-router/router/router.go)) — ParseModelRequest now wraps the body in http.MaxBytesReader before io.ReadAll, so an oversized payload is rejected with HTTP 413 rather than buffered in full. The response carries only the byte limit, no internal detail.

Listener bounds ([cmd/kthena-router/app/server.go](cmd/kthena-router/app/server.go), [cmd/kthena-router/app/router.go](cmd/kthena-router/app/router.go)) — every listener (default server and each Gateway port) is now built by a shared newHTTPServer that sets ReadHeaderTimeout, IdleTimeout, and MaxHeaderBytes.

Configuration

Env var Helm value (networking.kthenaRouter.requestLimits.*) Default Effect
MAX_REQUEST_BODY_BYTES maxRequestBodyBytes 33554432 (32Mi) HTTP 413 above the limit; 0 or negative disables
READ_HEADER_TIMEOUT readHeaderTimeout 10s Bounds slow-header clients
IDLE_TIMEOUT idleTimeout 120s Closes idle keep-alive connections
MAX_HEADER_BYTES maxHeaderBytes 1048576 (1Mi) HTTP 431 above the limit

Parsing follows the existing DRAIN_TIMEOUT / SESSION_BOOST_TIMEOUT pattern: env var read at startup, invalid values log a warning and fall back to the default, so a bad value can never leave a listener unbounded.

Compatibility

  • WriteTimeout and ReadTimeout are deliberately left unset. A global write deadline would truncate long-running streaming inference responses; a read deadline would cap large body uploads by time rather than size. TestNewHTTPServerAppliesLimits asserts both stay zero so a future change can't silently regress this.
  • MAX_HEADER_BYTES defaults to http.DefaultMaxHeaderBytes, i.e. the value net/http already enforced — this knob only makes it tunable, it doesn't tighten anything by default.
  • The 32Mi body default is sized for multimodal and tool-call payloads. Requests under the limits behave exactly as before.

Design notes

  • The body limit lives in ParseModelRequest rather than a middleware because that is the single point where every proxied request body is read (AuthMiddleware and AccessLogMiddleware never touch the body, and BuildDecodeRequest re-serializes from the already-parsed map). Putting it there guarantees the cap applies before the first byte is buffered, with no second read path to keep in sync.
  • parseDrainTimeout was collapsed onto a new parsePositiveDurationEnv helper rather than copy-pasting its body twice for the new timeouts. Behavior and the log message are unchanged.
  • MaxRequestBodyBytes is an exported package var mirroring the existing EnableFairnessScheduling / EnableSessionBoost convention, which keeps ParseModelRequest's exported signature intact.

Tests

pkg/kthena-router/router:

  • TestParseModelRequestEnforcesMaxRequestBodyBytes — below limit, exactly at limit, one byte above, far above, and limit-disabled. A counting reader asserts an oversized body is never read past limit+1 bytes, proving it isn't fully buffered.
  • TestParseMaxRequestBodyBytes — default, explicit, zero/negative (disable), unparsable.

cmd/kthena-router/app:

  • TestParseServerLimits — defaults, overrides, unparsable, and non-positive values.
  • TestNewHTTPServerAppliesLimits — fields wired correctly; WriteTimeout/ReadTimeout remain zero.
  • TestNewHTTPServerClosesSlowHeaderConnections — raw socket sends a partial header block; the server closes the connection after ReadHeaderTimeout and the handler is never reached.
  • TestNewHTTPServerRejectsOversizedHeaders — oversized header block never reaches the handler (accepts either a 431 or a server-side close, since net/http may hang up while the client is still writing).
  • TestNewHTTPServerAllowsLongStreamingResponses — a 500ms chunked SSE response completes intact under a 100ms ReadHeaderTimeout/IdleTimeout.

Docs

charts/kthena/charts/networking/README.md gains a "Request and Connection Limits" section (values, env-var mapping, fallback behavior). helm-chart-values.md rows were added by hand in helm-docs' alphabetical order — please confirm make gen-docs produces no diff.

Reviewer notes

  • The debug server on localhost:15000 was intentionally left unchanged; the issue scopes this to the public listeners.
  • MaxHeaderBytes in net/http permits a few KiB of slack above the configured value (initialReadLimitSize adds 4096). The chart README says so, and the test overshoots by 16× to avoid depending on that slack.

Copilot AI lite review requested due to automatic review settings August 2, 2026 08:45
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@volcano-sh-bot

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign lizhencheng9527 for approval. For more information see the Kubernetes Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

This update introduces configurable request limits for the kthenaRouter, including maximum request body size, read header timeout, idle timeout, and maximum header bytes. These limits help prevent excessive resource usage by clients and ensure more stable server performance. The new parameters are documented in the Helm chart values and integrated into the router's deployment configuration.

- Added requestLimits section in values.yaml
- Updated README.md to include new parameters
- Implemented limits in the router's HTTP server configuration
- Added tests to validate request body size enforcement and header limits

Signed-off-by: [Gite Vivek Kumar] [vivekkumargite@outlook.com]
Signed-off-by: Gite Vivek Kumar <vivekkumargite@outlook.com>
@vivek-gite
vivek-gite force-pushed the feat/1475-router-requests-limits branch from 2773619 to 68db7d5 Compare August 2, 2026 08:47

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR hardens the Kthena Router’s public HTTP listeners against request/connection exhaustion by adding configurable limits for inference request body size and HTTP server connection/header behavior, with Helm + docs updates and targeted unit tests.

Changes:

  • Enforce a configurable max inference request body size in ParseModelRequest using http.MaxBytesReader, returning HTTP 413 on overflow.
  • Centralize listener construction via newHTTPServer to apply ReadHeaderTimeout, IdleTimeout, and MaxHeaderBytes to default + Gateway listeners.
  • Add Helm values/env wiring and documentation for the new knobs, plus tests covering parsing and server behaviors.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
pkg/kthena-router/router/router.go Adds MaxRequestBodyBytes env parsing and enforces body size via http.MaxBytesReader with 413 handling.
pkg/kthena-router/router/router_test.go Adds unit tests for request body limit enforcement and env parsing behavior.
cmd/kthena-router/app/server.go Introduces serverLimits, parses new timeout/header env vars, and stores them on Server.
cmd/kthena-router/app/server_test.go Adds tests ensuring server limit env parsing defaults/fallbacks work as intended.
cmd/kthena-router/app/router.go Adds newHTTPServer helper and ensures limits are applied to created listeners.
cmd/kthena-router/app/router_listener_test.go Adds integration-style tests for header timeout/size enforcement and streaming compatibility.
docs/kthena/docs/reference/helm-chart-values.md Documents the new Helm chart values for request/connection limits.
charts/kthena/values.yaml Adds top-level chart values for networking.kthenaRouter.requestLimits.*.
charts/kthena/charts/networking/values.yaml Adds subchart defaults for kthenaRouter.requestLimits.*.
charts/kthena/charts/networking/templates/kthena-router/component/deployment.yaml Wires Helm values into router container env vars for the new limits.
charts/kthena/charts/networking/README.md Adds documentation section describing the request/connection limits and env var mapping.
Suppressed comments (1)

cmd/kthena-router/app/router_listener_test.go:339

  • This test uses http.Get with no timeout, which can hang indefinitely on regressions or unexpected network behavior. Use an http.Client with a Timeout so the test fails quickly instead of stalling CI.
	resp, err := http.Get("http://" + addr + "/v1/chat/completions")

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


// The server may reply 431 or close the connection while the client is
// still writing; either way the request must not reach the handler.
resp, err := http.DefaultClient.Do(req)
Comment on lines +278 to +280
if elapsed := time.Since(start); elapsed < readHeaderTimeout {
t.Errorf("connection closed after %v, want at least %v", elapsed, readHeaderTimeout)
}
Copilot AI review requested due to automatic review settings August 2, 2026 08:48

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated no new comments.

Comment thread pkg/kthena-router/router/router.go
Comment thread pkg/kthena-router/router/router.go
Comment thread pkg/kthena-router/router/router.go
Comment thread cmd/kthena-router/app/router.go
Copilot AI review requested due to automatic review settings August 3, 2026 04:51
@volcano-sh-bot

Copy link
Copy Markdown
Contributor

Adding label do-not-merge/contains-merge-commits because PR contains merge commits, which are not allowed in this repository.
Use git rebase to reapply your commits on top of the target branch. Detailed instructions for doing so can be found here.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated no new comments.

Suppressed comments (5)

pkg/kthena-router/router/router.go:626

  • When the request body exceeds MaxRequestBodyBytes, ParseModelRequest discards the original *http.MaxBytesError by returning errors.New(msg). Keeping the wrapped original error improves internal observability/debugging while still sending the sanitized client message.
			return nil, errors.New(msg)

docs/kthena/docs/reference/helm-chart-values.md:36

  • The table says oversized headers are rejected with HTTP 431, but net/http may also close the connection without a 431 (the tests already allow either behavior). The docs should reflect that so operators don't assume a guaranteed status code.
| networking.kthenaRouter.requestLimits.maxHeaderBytes | int | `1048576` | Largest request header block accepted, in bytes.<br/> A larger header block is rejected with HTTP 431. |

charts/kthena/charts/networking/README.md:173

  • This description implies oversized headers always get HTTP 431, but net/http may also close the connection without returning 431 (your tests already accept either outcome). Update wording to avoid promising a specific response code.
| `kthenaRouter.requestLimits.maxHeaderBytes`      | int    | `1048576`  | Largest request header block accepted, in bytes (1Mi). A larger header block is rejected with HTTP 431. `net/http` allows a few KiB of slack above this value. |

charts/kthena/values.yaml:136

  • These values.yaml comments state oversized headers are rejected with HTTP 431, but net/http may also close the connection without returning 431 (as reflected in the tests). Adjust the comment to avoid guaranteeing a specific status code.
      # -- Largest request header block accepted, in bytes.<br/>
      # A larger header block is rejected with HTTP 431.
      maxHeaderBytes: 1048576

charts/kthena/charts/networking/values.yaml:58

  • The comment here guarantees HTTP 431 for oversized headers, but net/http can also close the connection without a 431 (the listener test already allows either). Update wording so the chart values docs match actual server behavior.
    # maxHeaderBytes is the largest request header block accepted, in bytes
    # (default: 1048576, i.e. 1Mi). Larger headers get HTTP 431.
    maxHeaderBytes: 1048576

@vivek-gite
vivek-gite requested a review from YaoZengzeng August 3, 2026 05:08
Copilot AI review requested due to automatic review settings August 3, 2026 07:48

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Signed-off-by: Gite Vivek Kumar <71180467+vivek-gite@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 6, 2026 09:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@hzxuzhonghu

Copy link
Copy Markdown
Member

@vivek-gite please read contibuting guide first to respect the coding convention and not make the pr contains merge commit

Handler: handler,
ReadHeaderTimeout: limits.readHeaderTimeout,
IdleTimeout: limits.idleTimeout,
MaxHeaderBytes: limits.maxHeaderBytes,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think we can define constant timeout unless there is a must to configure

Comment thread charts/kthena/values.yaml
# -- Largest inference request body accepted, in bytes.<br/>
# A larger request is rejected with HTTP 413 before it is buffered.<br/>
# Set to `0` to disable the limit.
maxRequestBodyBytes: 33554432

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

a comment, how many MB

and same apply to below header.

Comment thread charts/kthena/values.yaml
idleTimeout: 120s
# -- Largest request header block accepted, in bytes.<br/>
# A larger header block is rejected with HTTP 431.
maxHeaderBytes: 1048576

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

There is a default 1MB in golang http package

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

reliability(router): bound HTTP headers and inference request body size

5 participants