feat(webui): fall back to the next free port when the default port is taken - #16
Merged
Merged
Conversation
Collaborator
|
看了没啥大毛病,但是要不要考虑 默认值不设置为8080, 比如换成8090/18090 冲突的概率小一点 |
`mcode-web` / `mcode webui` used to die on a taken port — and because the global uncaughtException handler only logs, the process could also stay alive without listening at all. The default port is now 18090, and it is a preference rather than a promise: when it is taken the server walks forward to the next free port (20 attempts) and logs the port it bound, which is the URL the launcher opens. 8080 was chosen in v0.5.bx-38 for its "web alt" reading, but it collides with desktop clients and other dev servers often enough that the fallback would fire regularly. An explicitly configured port stays exact — `PORT` set, or `--port` passed. That is deliberate: docker port publishing, the container healthcheck and the webui integration tests all address the configured value and cannot discover a fallback. A taken pinned port now exits 1 with an actionable message instead of a stack trace plus a live-but-not-listening process. An out-of-range or non-integer port throws synchronously out of `server.listen`, so that path now reaches the same handler instead of escaping to the global logger. The serving port is exposed through getServingPort() because it is read at request time, not at import time. Otherwise the CORS origin trust set would keep trusting the configured port while the browser talks to the fallback one, and /api/health, the state payload, the LAN share URL and the LAN 403 page would name the wrong port. isPortPinned is the single implementation of the pin rule. Tests: - test/port.test.js: the pin rule, the attempt budget, and the retry mechanics (a free port is used as-is; a taken one walks forward; a pinned one reports EADDRINUSE; an exhausted budget reports EADDRINUSE; an out-of-range port reports ERR_SOCKET_BAD_PORT instead of throwing out of the helper). - test/integration/port-fallback.test.js: a real server.js boot on a taken 18090 reports the next free port, /api/health agrees, and that port's browser Origin is trusted while the configured-but-unused port's is not.
weekbin
force-pushed
the
feat/webui-port-fallback
branch
from
September 22, 2026 05:45
cc0a7bb to
fa29968
Compare
Collaborator
Author
|
采纳了 —— 默认端口从 8080 改成 18090(
另外把"端口"的消费方也收口了:origin 信任集、 实测(本机 18090 由 blocker 占住): 18090 空闲时直接用 18090; 文档(EN + zh-CN,含 webui CHANGELOG)已按新默认值同步; 分支上只有一个提交,我把评审后的改动直接并进去了(force-push,没有叠加 delta commit)—— 如果你更希望单独看一版 delta,我可以拆成两个提交。 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Change
pnpm mcode-web(andmcode webui/mcode web) no longer dies when the default port is already taken, and the default port itself moves off the busiest number.The default is now 18090, and it is a preference rather than a promise — when it is taken the server walks forward to the next free port and logs the port it actually bound, which is the URL the launcher opens:
8080 was chosen in v0.5.bx-38 for its "web alt" reading, but it collides with desktop clients and other dev servers often enough that a fallback would fire regularly — it was already occupied by an unrelated process on the machine this was tested on.
Two supporting fixes came out of the same path:
installGlobalErrorHandlersonly logsuncaughtException, so a taken port left a process that was alive but never listening; the launcher then waited for a listening line that could never arrive. Listen failures now report and exit 1 — including the synchronousERR_SOCKET_BAD_PORTthrow that an out-of-range or non-integer port produces./api/health, the state payload, the LAN share URL and the LAN 403 page are built per request fromgetServingPort(). A snapshot of the configured value would have kept trustinghttp://127.0.0.1:18090while the browser talked to 18091, and would have named the wrong port in share URLs.An explicitly configured port stays exact
PORTset to a positive value, or--portpassed, pins the port: it never moves and a taken port exits 1 with guidance.This is deliberate. Docker publishes and healthchecks
${WEBUI_PORT}, and the webui integration tests dial the port they chose; neither can discover a fallback, so silently moving would break them.docker-compose.ymland theDockerfilehealthcheck pin18080explicitly and are unaffected. Unset, empty,0and non-numericPORTvalues are treated as "not configured" — the same set thatNumber(process.env.PORT) || 18090maps onto the default.Validation
pnpm verify(full profile, Node 24.19.0, Linux, clean tree at the PR headfa29968):check:source,check:tsconfig,export source preview,test:release-tools,typecheck,build,check:standalone,test:artifact,test:capabilities,test:status-contract,test:smokeandtest:byokall pass.test:webuireports 1303 pass / 1 fail — the single failure is the pre-existingupload-limitsflake documented below, not one of the new tests.test:policyis not reached when a gate fails; run on its own it passes (142 tests). The sametest:webuigate passed fully (1303 pass / 0 fail) on this branch's previous revision, andupload-limits.test.jspasses 7/7 when run alone (three consecutive runs) — which is why the flake is context-sensitive rather than a regression here.test:webui:packages/webui/test/port.test.js(11 cases) andpackages/webui/test/integration/port-fallback.test.js(1 case) — all passing in every run.packages/webui/test/port.test.js— the pin rule (positive pins; unset/empty/0/non-numeric do not), the attempt budget, and the retry mechanics against real sockets: a free port is used as-is, a taken port walks forward and serves, a pinned port reportsEADDRINUSEwithout moving, an exhausted budget reportsEADDRINUSEinstead of spinning, an out-of-range port reportsERR_SOCKET_BAD_PORTinstead of throwing out of the helper.packages/webui/test/integration/port-fallback.test.js— a liveserver.jsboot withPORTunset and the default port held: reports the next free port,/api/healthagrees with it, that port's own browserOriginis reflected, and the configured-but-unused port'sOriginis not.node dist/mcode-web.js --no-openloggedtrying 18091andlistening on http://127.0.0.1:18091; with 18090 free it loggedlistening on http://127.0.0.1:18090; with--port 18090held it exited 1 with the pinned-port message above.buildTrustedOrigins({ port: ... })inrouter.jsto the configured port makes the integration test fail, so it pins the behaviour it claims to.README.md/README_ZH.md,docs/webui.md/docs/webui.zh-CN.md,packages/webui/README.md/README.zh-CN.md, and the webui'sDEVELOPMENT,TROUBLESHOOTING,APIandHTTPS-REVERSE-PROXYdocs with their.zh-CNcounterparts, plus anUnreleasedentry inpackages/webui/docs/CHANGELOG.md.packages/webui/scripts/check-docs-alignment.mjspasses.packages/webui/test/lib-settings.test.jspreviously pinned the 403 page to the literal127.0.0.1:8080; it now asserts againstgetServingPort()so the next default change cannot leave it stale.Pre-existing
upload-limitsflake (not introduced here)packages/webui/test/integration/upload-limits.test.jsis timing-sensitive and failed intermittently while this work was in progress onupload-limits: oversized request → 413 mid-stream(write EPIPEbefore the response event, and once abytesWrittenbudget overrun) — the same failure observed while syncing upstreammainin #15, and present onmaintoday. The file andserver.js's upload path are not touched by this PR.Publication and contribution checks
release/public-source.jsonvalid (check:sourcepasses).Maintainer handoff
Publication scope or license changes: none.
Shared-source port: not needed — this is a fork-local Web UI behaviour change.