Skip to content

THRIFT-6203: Replace deprecated url.parse and fs.exists in the Node.js library - #3821

Open
slachiewicz wants to merge 1 commit into
apache:masterfrom
slachiewicz:fix-node-deprecations
Open

THRIFT-6203: Replace deprecated url.parse and fs.exists in the Node.js library#3821
slachiewicz wants to merge 1 commit into
apache:masterfrom
slachiewicz:fix-node-deprecations

Conversation

@slachiewicz

@slachiewicz slachiewicz commented Sep 8, 2026

Copy link
Copy Markdown
Member

JIRA: THRIFT-6203
Client: nodejs

The library called two APIs Node has deprecated, so an application embedding it printed DEP0169 and DEP0006 on every supported runtime: url.parse() and fs.exists() in web_server.js. They become the WHATWG URL class and fs.stat().

Note: this revision originally also replaced require("constants") (DEP0063) in server.js and connection.js, but that was dropped on rebase because upstream ec1066ab4 already removed that code and now leaves the TLS defaults to Node.

Two behaviour changes come with that and are worth reviewing on their own terms.

A malformed request target now gets a 400. url.parse() accepts one and returns a partly-parsed path; new URL() throws. processPost and processGet catch it and answer 400 Bad Request rather than letting the exception reach the request handler. Only the pathname is used, so the base is a fixed http://localhost: built from the Host header, a value that is not a valid authority (example.com:abc, an unbracketed IPv6 address) made new URL() throw and produced a 400 that url.parse() never did.

options.services defaults to {}. A server serving only static files has no reason to pass it, and createWebServer read it without a default. The registration loop does not catch that, because for...in over undefined is a no-op rather than an error, so the first POST failed instead:

TypeError: Cannot read properties of undefined (reading '/')

The WebSocket upgrade listener is unaffected: since 31f5897cb on master it refuses an empty services object with a 403, and web_server_ws.test.js covers that case.

The fs.exists() replacement also drops a redundant fs.statSync() that ran immediately after the async check.

Also carried here: the comment in thrift_4987_xhr_protocol.test.mjs now names Buffer.from(string), the wording left over from the Buffer() replacement in #3819.

Verified: node -e "for (var u in undefined) {}" is a no-op, while indexing undefined throws the TypeError above — the failure could only surface on a request, not at construction.

The new cases in web_server_ws.test.js cover static file serving, a 404, and a request routed to an unregistered service.

  • Did you create an Apache Jira ticket? (THRIFT-6203)
  • If a ticket exists: Does your pull request title follow the pattern "THRIFT-NNNN: describe my issue"?
  • Did you squash your changes to a single commit?
  • Did you do your best to avoid breaking changes?

This change was created with AI assistance.

@mergeable mergeable Bot added the nodejs label Sep 8, 2026
@slachiewicz
slachiewicz force-pushed the fix-node-deprecations branch from ae17a95 to a42ea0d Compare September 8, 2026 11:41
@slachiewicz slachiewicz changed the title Replace deprecated Node.js APIs in web_server, server, and connection THRIFT-6203: Replace deprecated url.parse, fs.exists and constants in the Node.js library Sep 8, 2026
@slachiewicz
slachiewicz force-pushed the fix-node-deprecations branch 2 times, most recently from 3b960da to 466b138 Compare September 9, 2026 15:21
@slachiewicz slachiewicz changed the title THRIFT-6203: Replace deprecated url.parse, fs.exists and constants in the Node.js library THRIFT-6203: Replace deprecated url.parse and fs.exists in the Node.js library Sep 9, 2026
@Jens-G

Jens-G commented Sep 11, 2026

Copy link
Copy Markdown
Member

Code review

Found 1 issue:

  1. Defaulting services to {} changes how the WebSocket upgrade listener handles a server created without services. That listener only notices "no services" when Object.keys(services) throws inside its try block. Object.keys(undefined) throws, so today such a request gets 403 No Apache Thrift Service available. Object.keys({}) does not throw, so with this change svc stays undefined, the listener writes 101 Switching Protocols and then throws TypeError: Cannot read properties of undefined (reading 'transport') at svc.transport.DEFAULT_MAX_LENGTH. The test this PR adds ("HTTP static file serving and URL handling") creates exactly such a server (files only, no services) but only sends GET and POST requests, so the suite does not see this. I reproduced it with that configuration: 403 on the merge base, the TypeError on this branch. An explicit if (!svc) check before the handshake would bring back the 403, and an upgrade request in the new test would cover it.

//Setup all of the services
var services = options.services || {};
for (var uri in services) {

//Lookup service
var svc;
try {
svc = services[Object.keys(services)[0]];
} catch (e) {
socket.write("HTTP/1.1 403 No Apache Thrift Service available\r\n\r\n");
return;
}
//Perform upgrade

One suggestion, below the bar for the list above but verified:

  • Only the pathname of the parsed URL is used, so the base does not need to come from the Host header. Built from "http://" + request.headers.host, a Host value that is not a valid authority (for example example.com:abc, or an IPv6 address without brackets) makes new URL throw, and the request gets a 400; url.parse never looked at that header. A fixed base such as http://localhost avoids this.

var uri;
try {
uri = new URL(
request.url,
"http://" + (request.headers.host || "localhost"),
).pathname;
} catch (e) {
response.writeHead(400, "Bad Request", {});
response.end();
return;
}

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

@Jens-G

Jens-G commented Sep 11, 2026

Copy link
Copy Markdown
Member

Follow-up to the review above: master now refuses a WebSocket upgrade when no service is configured, an empty services object included (31f5897, with tests in web_server_ws.test.js). After a rebase onto master, the services || {} default no longer reaches svc.transport, so point 1 needs no change in this PR.

🤖 Generated with Claude Code

@slachiewicz
slachiewicz marked this pull request as draft September 11, 2026 16:43
@slachiewicz

Copy link
Copy Markdown
Member Author

Branch rewritten (70cae7b): rebased on current master, which carries the upgrade-listener fix from 31f5897, so point 1 needs nothing here as you said. The suggestion is taken: both new URL() calls now use the fixed base http://localhost, so a Host header that is not a valid authority no longer turns into a 400. The comment cleanup from #3819 (Buffer.from(string) in thrift_4987_xhr_protocol.test.mjs) rides along.

Verified: web_server_ws.test.js → 10 passed on the rebased branch; eslint lib/nodejs/test clean.

This comment was created with AI assistance.

…b_server

Client: nodejs

- Replace url.parse() (DEP0169) with the WHATWG URL class in web_server.js.
  Only the pathname is used, so the base is a fixed http://localhost: built
  from the Host header, a value that is not a valid authority made new URL
  throw and turned into a 400 that url.parse never produced.
- Replace fs.exists() (DEP0006) with fs.stat() in web_server.js, dropping
  the redundant statSync
- Default options.services to {} in createWebServer so a static-only server
  does not fail with a TypeError on its first POST. The WebSocket upgrade
  listener already refuses an empty services object since 31f5897.
- Add HTTP static file serving and URL handling test in web_server_ws.test.js
- Name Buffer.from(string) in the thrift_4987_xhr_protocol.test.mjs comment,
  left over from the Buffer() replacement in THRIFT-5224

Note: the require("constants") (DEP0063) replacements in server.js and
connection.js from the original revision were dropped on rebase because
upstream ec1066a already removed that code and now leaves TLS defaults
to Node.

Co-Authored-By: Gemini 3.8 Flash <noreply@google.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@slachiewicz
slachiewicz marked this pull request as ready for review September 14, 2026 09:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants