Skip to content

feat: add max_header_len & capture_error_body options - #94

Open
tjasko wants to merge 1 commit into
openresty:masterfrom
tjasko:feat/ws-upgrade-validation
Open

tjasko wants to merge 1 commit into
openresty:masterfrom
tjasko:feat/ws-upgrade-validation

Conversation

@tjasko

@tjasko tjasko commented Mar 15, 2025

Copy link
Copy Markdown

Adds two options to client:new(), both off by default so existing callers are unaffected.

  • max_header_len bounds the size of the handshake response headers, addressing the -- FIXME: check for too big response headers in connect(). Without it, a server can stream headers until the client runs out of memory. The reader is given max_header_len + 1 so an over-long response is detected rather than silently truncated.
  • capture_error_body reads the response body when the upgrade is refused and appends it to the returned error.

Rebased onto the current master

This branch has been rebased. The 101-status rejection it originally proposed as validate_handshake landed independently in 2606072, so that part is dropped; the check is now unconditional, which is better than the opt-in this PR proposed.

What survived is the body. 2606072 reports only the status code, and the body is usually where the server explains why it refused the upgrade (an auth failure, a proxy error page). capture_error_body appends it to the existing message rather than replacing it:

failed websocket handshake: unexpected response status: 400, body: <html>...

It is opt-in for two reasons: reading the body means waiting on the socket again, and the default error text stays byte-for-byte as 2606072 left it, so t/handshake.t passes untouched.

The option was named validate_handshake in the first revision of this PR. Since validation is now unconditional, that name described something it no longer controls, hence the rename.

Review feedback

Both review comments are addressed. The status line is matched with a single ngx.re.match that also captures the code, which is what 2606072 settled on, and t/cs.t ends with a newline again.

Tests

  • t/handshake.t passes unchanged, confirming no regression in the upstream 101 behavior.
  • Two cases added to t/cs.t, one per option.
  • The remaining t/cs.t failures are identical on a bare master checkout and are environmental: they assert on ngx.DEBUG log lines that a non-debug nginx build never emits.

Unrelated: the test certificate

t/cert/test.key is a 1024-bit RSA key, which modern OpenSSL rejects with ee key too small, aborting the suite before it reaches the later tests. That is pre-existing and not touched here, but it does block running t/cs.t locally. Worth its own PR, along with a Makefile target:

$ openssl req -x509 -newkey rsa:2048 -nodes -keyout test.key -out test.crt -days 3650 \
    -subj "/C=US/ST=California/L=San Francisco/O=OpenResty/OU=OpenResty/CN=test.com/emailAddress=agentzh@gmail.com"

Thanks for considering this change!

@tjasko
tjasko force-pushed the feat/ws-upgrade-validation branch 2 times, most recently from 9710364 to 2db11ae Compare March 15, 2025 03:10
Comment thread lib/resty/websocket/client.lua Outdated
m, err = re_match(header, [[^\s*HTTP/1\.1\s+]], "jo")
if not m then
-- Validate HTTP status line.
local status_line_end = header:find("\r?\n")

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.

use nginx.re to get the status code directly.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Thanks for the review. Are you stating that you wish to revert to the previous re_match() behavior & not validate the HTTP status line per HTTP spec?

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.

The original implementation is more efficient and already meets the requirements, so we can keep it as is.

@tjasko tjasko Sep 12, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

This has been fixed.

Comment thread t/cs.t Outdated
^error: "response headers too large \(limit: 1024 bytes\)"
--- no_error_log
[error]
[warn] No newline at end of file

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.

require a newline at the end of file.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Will update, thanks!

@tjasko tjasko Sep 12, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

This has been fixed.

@tjasko
tjasko requested a review from zhuizhuhaomeng June 27, 2025 01:07
Comment thread lib/resty/websocket/client.lua Outdated
m, err = re_match(header, [[^\s*HTTP/1\.1\s+]], "jo")
if not m then
-- Validate HTTP status line.
local status_line_end = header:find("\r?\n")

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.

The original implementation is more efficient and already meets the requirements, so we can keep it as is.

Add two options to client:new(), both off by default so existing
callers are unaffected.

max_header_len bounds the handshake response headers, addressing the
"FIXME: check for too big response headers" in connect(). Without it a
server can stream headers until the client runs out of memory. The
reader is given max_header_len + 1 so an over-long response is detected
rather than silently truncated, and the length check tolerates the nil
the reader returns on a socket error.

capture_error_body reads the response body when the upgrade is refused
and appends it to the error. The 101 check itself now lives upstream,
which reports only the status code; the body usually carries the reason
the upgrade was refused. It is opt-in because reading it means waiting
on the socket again, and because the default error text stays exactly
as upstream has it.
@tjasko
tjasko force-pushed the feat/ws-upgrade-validation branch from 183c9a2 to b02684a Compare September 12, 2026 20:55
@tjasko tjasko changed the title feat: add max_header_len & validate_handshake options feat: add max_header_len & capture_error_body options Sep 12, 2026
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.

2 participants