← trace_events.createTracing 用法 util.isDeepStrictEqual 用法 →

Node.js urlObject.port用法:端口解析规则、默认端口空字符串与WHATWG赋值行为

著
原创 2026-10-20 Node.js 已有人查阅

一、两套API,两种port语义

urlObject.port这个属性名在Node.js中指向两套不同的URL处理体系,它们的取值规则并不一致。这一点在查阅资料时容易被混淆,因为许多教程将两者混在一起讨论。

旧版API(url.parse()):url.parse()返回的对象中,port是字符串类型,如果URL中未指定端口则值为null。官方文档已将该方法标记为Deprecated,并指出它使用宽松的非标准解析算法,存在主机名欺骗等安全隐患,不建议用于不可信输入。

WHATWGAPI(newURL()):这是当前推荐的URL解析方式。urlObject.port同样是字符串,但默认端口的处理方式与旧版不同。当URL使用协议对应的默认端口时(例如https://example.com:443),port返回的是空字符串'',而非'443'。

这个差异在代码中会导致行为分歧。如果代码假设port始终返回数字字符串,在WHATWGAPI下遇到默认端口时会拿到空字符串,后续的parseInt或数值比较可能产生意外结果。

二、WHATWG的赋值规则:比预期更宽松

newURL()返回的URL对象的port属性是可写的。赋值时的转换规则值得单独说明,因为它的行为与多数人对“端口必须是0-65535整数”的直觉不同。

赋值时,值通过toString()转换为字符串。如果该字符串无效但以数字开头,开头的数字部分会被采用。如果数字超出0到65535的范围,赋值被忽略,端口保持原值。

const u = new URL('https://example.org:8888');

u.port = '5678abcd';  // 开头的数字被采用
console.log(u.port);   // '5678'

u.port = 1234.5678;    // 非整数被截断
console.log(u.port);   // '1234'

u.port = 'abcd';       // 无效的字符串被忽略
console.log(u.port);   // '1234'(保持上一次有效值)

u.port = 1e10;         // 超出范围,被忽略
console.log(u.port);   // '1234'

将port设为该协议对应的默认端口时,值会变为空字符串。例如对https://开头的URL设置port='443',读回时得到的是''。这个设计使得“显式写出默认端口”和“不写端口”在对象层面不可区分,序列化后的href也不会包含冗余的默认端口。

三、默认端口的查表与缺失判断

由于port可能返回空字符串,判断“URL是否指定了非默认端口”时,不能只检查port是否有值。需要结合协议查表。

协议 默认端口
http: 80
https: 443
ws: 80
wss: 443
ftp: 21

当urlObject.port===''时,实际连接使用的端口取决于协议。如果业务逻辑需要拿到“最终用于连接的端口”,应当做协议到默认端口的映射,而非直接使用urlObject.port。

function resolvePort(urlObject) {
  if (urlObject.port) return Number(urlObject.port);
  const defaults = { 'http:': 80, 'https:': 443, 'ws:': 80, 'wss:': 443, 'ftp:': 21 };
  return defaults[urlObject.protocol] ?? null;
}

对于file:协议,没有默认端口的概念,port为空字符串是正常状态,不应强行套用HTTP的默认值逻辑。

四、示例:两套API的port行为对比

以下示例在同一组URL上对比url.parse()与newURL()的port返回值。示例中的code_number为代码号学习编程的标识。

const url = require('node:url');

const cases = [
  'http://code_number.local:8080/path',
  'https://code_number.local:443/secure',
  'https://code_number.local/secure',
  'http://code_number.local/plain'
];

for (const raw of cases) {
  const legacy = url.parse(raw);
  const whatwg = new URL(raw);
  console.log({
    raw,
    legacyPort: legacy.port,
    whatwgPort: whatwg.port,
    whatwgProtocol: whatwg.protocol
  });
}

输出中值得关注的是https://code_number.local:443/secure这一行:旧版API的port为'443',WHATWGAPI的port为''。同一个URL字符串,两套API给出了不同的“端口是否存在”的信号。如果代码从旧版迁移到WHATWG,且依赖port的非空判断来区分“自定义端口”和“默认端口”,迁移后需要调整逻辑。

五、边界情况与常见错误

赋值null或undefined:port的setter会将值转为字符串'null'或'undefined'。'null'不以数字开头,赋值被忽略;'undefined'同样被忽略。不要依赖赋值null来“清除端口”,正确的方式是赋值为''或协议对应的默认端口。

负数端口:赋值为负数时,转换为字符串后以-开头,不以数字开头,赋值被忽略。没有显式错误抛出。

科学计数法:u.port=4.567e21会被转为字符串'4.567e21',开头的4被采用,port变为'4'。这个行为在正常业务中极少出现,但在从配置或环境变量透传端口值时,如果来源未经校验,可能出现意料之外的截断结果。

旧版url.parse()的port为null:当URL中未显式指定端口时,旧版API返回null,而非空字符串。null与''在条件判断中都是falsy,但如果代码用===''做判断,会漏掉null的情况。迁移期间需要统一判断方式。

六、测试中的断言策略

单元测试中验证port行为时,需要同时覆盖两套API的差异,避免用一套的预期去断言另一套。

对于WHATWGnewURL(),核心断言包括:非默认端口返回数字字符串;默认端口返回空字符串;赋值时前导数字提取与范围忽略。以下是一个使用node:test的示例:

const { test } = require('node:test');
const assert = require('node:assert');

test('WHATWG port 赋值规则', () => {
  const u = new URL('https://code_number.local:8888');
  assert.strictEqual(u.port, '8888');

  u.port = '443';
  assert.strictEqual(u.port, '');

  u.port = '9090';
  assert.strictEqual(u.port, '9090');

  u.port = '5678abc';
  assert.strictEqual(u.port, '5678');

  u.port = 1e10;
  assert.strictEqual(u.port, '5678');
});

这段测试的价值在于将文档中的规则固化为可执行的断言。当Node.js版本升级时,如果port的赋值语义发生变化,测试会直接失败,而非在业务代码中以隐蔽的方式出错。

七、与相邻属性的职责边界

urlObject.host包含主机名和端口(如code_number.local:8080),urlObject.hostname只含主机名(如code_number.local),urlObject.port只含端口数字部分。三者分工明确,不应混用。

在旧版API中,修改port后直接调用url.format()可能不会按预期更新输出,因为format()优先使用host属性。需要同时删除或更新host才能使修改生效。这是旧版API的一个已知不便之处,也是推荐迁移到WHATWGAPI的理由之一:newURL()的toString()始终基于当前属性值重新组装,修改port后立即反映在输出中。

八、常见误区速查

以为port始终返回数字字符串。WHATWG下默认端口返回'',旧版API下缺失端口返回null。两者都不是数字字符串。

以为newURL('https://a.com:443').port返回'443'。返回''。默认端口被规范化为空字符串。

以为赋值无效值会抛出错误。不会。无效赋值被静默忽略,port保持原值。只有在前导数字有效时才会部分采用。

以为urlObject.port能反映实际连接的端口。它只反映URL字符串中是否出现了端口以及解析后的值。实际连接端口由协议默认值或显式指定的值共同决定,需要自行做协议映射。

九、练习与思考

  1. 对于URLhttp://code_number.local:80/path,newURL()的port返回什么?url.parse()的port返回什么?如果代码需要判断“用户是否显式写了端口80”,用port属性能否可靠区分?

  2. newURL('https://code_number.local:8443').port=''之后,href是什么?实际连接时应该使用哪个端口?

  3. 在一个从环境变量读取端口并拼接URL的配置模块中,如果环境变量值为'8080abc'或'0'或'65536',newURL的port赋值会分别产生什么结果?模块应当如何校验输入以避免静默截断或忽略?

十、延伸阅读与参考文献

Node.js官方文档URL类url.port属性,nodejs.org,详述WHATWGport的赋值转换规则、默认端口空字符串语义以及范围忽略行为,并附有5678abcd、1234.5678、1e10等边界示例。https://nodejs.org/api/url.html

Node.js官方文档旧版urlObject.port,nodejs.cn中文镜像,说明旧版API中port为host组件中的数字部分,以及url.format()对port和host的组装优先级。https://url.nodejs.cn/api-v16/url.html

Node.js文档提交doc:clarifythatnewURL().portcouldbeanemptystring,GitHubnodejs/node,记录了WHATWGport文档从“数字或字符串”修正为“默认端口变为空字符串”的变更过程,是理解该行为来源的直接材料。https://github.com/nodejs/node/commit/9142935eb2

Node.js文档url.parse()稳定性说明,nodejs.org,标注该方法已废弃,并指出其宽松解析算法存在主机名欺骗风险,不建议用于不可信输入。https://nodejs.org/api/url.html

Stack Overflow讨论“How do I change the port in a (legacy) URL object and stringify it?”,记录旧版API中修改port后url.format()不更新的实际案例,以及删除host属性作为变通方式的讨论,对迁移决策有参考价值。https://stackoverflow.com/questions/49390766

← trace_events.createTracing 用法 util.isDeepStrictEqual 用法 →
分享笔记 (共有 篇笔记)
验证码:
微信公众号