From d954b8d138a5b66195b978e560055ab83a899209 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=BD=D0=B0=D0=B1?= Date: Tue, 15 Jul 2025 07:31:42 +0200 Subject: [PATCH] StreamingApiServerService: handle websocket errors from client gracefully MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit $ nc -C tarta.nabijaczleweli.xyz 12122 GET /streaming?_t=1752534314122 HTTP/1.1 Host: tarta.nabijaczleweli.xyz:12122 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:142.0) Gecko/20100101 Firefox/142.0 Accept: */* Accept-Language: en-GB,en;q=0.5 Accept-Encoding: gzip, deflate Sec-WebSocket-Version: 13 Sec-WebSocket-Extensions: permessage-deflate Sec-WebSocket-Key: AAAAAAAAAAAAAAAAAAAAAA== Connection: keep-alive, Upgrade Pragma: no-cache Cache-Control: no-cache Upgrade: websocket the parameters almost don't matter so long as the server replies with an upgrade, then press enter once or twice, at which point the server will have crashed after INFO 1 [core nest] NestFactory: Starting Nest application... ERR * [core] Uncaught exception (uncaughtException): RangeError: Invalid WebSocket frame: invalid opcode 13 at Receiver.getInfo (/srv/Sharkey/node_modules/.pnpm/ws@8.18.1_bufferutil@4.0.9_utf-8-validate@6.0.5/node_modules/ws/lib/receiver.js:311:26) at Receiver.startLoop (/srv/Sharkey/node_modules/.pnpm/ws@8.18.1_bufferutil@4.0.9_utf-8-validate@6.0.5/node_modules/ws/lib/receiver.js:155:16) at Receiver._write (/srv/Sharkey/node_modules/.pnpm/ws@8.18.1_bufferutil@4.0.9_utf-8-validate@6.0.5/node_modules/ws/lib/receiver.js:94:10) at writeOrBuffer (node:internal/streams/writable:572:12) at _write (node:internal/streams/writable:501:10) at Writable.write (node:internal/streams/writable:510:10) at Socket.socketOnData (/srv/Sharkey/node_modules/.pnpm/ws@8.18.1_bufferutil@4.0.9_utf-8-validate@6.0.5/node_modules/ws/lib/websocket.js:1355:35) at Socket.emit (node:events:518:28) at addChunk (node:internal/streams/readable:561:12) at readableAddChunkPushByteMode (node:internal/streams/readable:512:3) at Readable.push (node:internal/streams/readable:392:5) at TCP.onStreamRead (node:internal/stream_base_commons:189:23) { code: 'WS_ERR_INVALID_OPCODE', [Symbol(status-code)]: 1002 } INFO * [core] The process is going to exit with code 1 node:events:496 throw er; // Unhandled 'error' event ^ RangeError: Invalid WebSocket frame: invalid opcode 13 at Receiver.getInfo (/srv/Sharkey/node_modules/.pnpm/ws@8.18.1_bufferutil@4.0.9_utf-8-validate@6.0.5/node_modules/ws/lib/receiver.js:311:26) at Receiver.startLoop (/srv/Sharkey/node_modules/.pnpm/ws@8.18.1_bufferutil@4.0.9_utf-8-validate@6.0.5/node_modules/ws/lib/receiver.js:155:16) at Receiver._write (/srv/Sharkey/node_modules/.pnpm/ws@8.18.1_bufferutil@4.0.9_utf-8-validate@6.0.5/node_modules/ws/lib/receiver.js:94:10) at writeOrBuffer (node:internal/streams/writable:572:12) at _write (node:internal/streams/writable:501:10) at Writable.write (node:internal/streams/writable:510:10) at Socket.socketOnData (/srv/Sharkey/node_modules/.pnpm/ws@8.18.1_bufferutil@4.0.9_utf-8-validate@6.0.5/node_modules/ws/lib/websocket.js:1355:35) at Socket.emit (node:events:518:28) at addChunk (node:internal/streams/readable:561:12) at readableAddChunkPushByteMode (node:internal/streams/readable:512:3) at Readable.push (node:internal/streams/readable:392:5) at TCP.onStreamRead (node:internal/stream_base_commons:189:23) Emitted 'error' event on WebSocket instance at: at Receiver.receiverOnError (/srv/Sharkey/node_modules/.pnpm/ws@8.18.1_bufferutil@4.0.9_utf-8-validate@6.0.5/node_modules/ws/lib/websocket.js:1199:15) at Receiver.emit (node:events:518:28) at emitErrorNT (node:internal/streams/destroy:170:8) at emitErrorCloseNT (node:internal/streams/destroy:129:3) at process.processTicksAndRejections (node:internal/process/task_queues:90:21) { code: 'WS_ERR_INVALID_OPCODE', [Symbol(status-code)]: 1002 } Node.js v22.14.0  ELIFECYCLE  Command failed with exit code 1. This works through some reverse proxies (HAProxy, Caddy), but not through others (Cloudflare, nginx, Apache(?)) Instead, just hang up if the client violates protocol Fixes https://101010.pl/@nabijaczleweli/114854334401159070 --- .../backend/src/server/api/StreamingApiServerService.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/backend/src/server/api/StreamingApiServerService.ts b/packages/backend/src/server/api/StreamingApiServerService.ts index a3ac8f5447..231ba7bfbf 100644 --- a/packages/backend/src/server/api/StreamingApiServerService.ts +++ b/packages/backend/src/server/api/StreamingApiServerService.ts @@ -12,6 +12,7 @@ import { DI } from '@/di-symbols.js'; import type { UsersRepository, MiAccessToken, MiUser, NoteReactionsRepository, NotesRepository, NoteFavoritesRepository } from '@/models/_.js'; import type { Config } from '@/config.js'; import type { Keyed, RateLimit } from '@/misc/rate-limit-utils.js'; +import { renderInlineError } from '@/misc/render-inline-error.js'; import { NotificationService } from '@/core/NotificationService.js'; import { bindThis } from '@/decorators.js'; import { CacheService } from '@/core/CacheService.js'; @@ -20,6 +21,7 @@ import { UserService } from '@/core/UserService.js'; import { ChannelFollowingService } from '@/core/ChannelFollowingService.js'; import { getIpHash } from '@/misc/get-ip-hash.js'; import { LoggerService } from '@/core/LoggerService.js'; +import type Logger from '@/logger.js'; import { SkRateLimiterService } from '@/server/SkRateLimiterService.js'; import { QueryService } from '@/core/QueryService.js'; import { AuthenticateService, AuthenticationError } from './AuthenticateService.js'; @@ -38,6 +40,7 @@ export class StreamingApiServerService implements OnApplicationShutdown { #connectionsByClient = new Map>(); // key: IP / user ID -> value: connection #cleanConnectionsIntervalId: NodeJS.Timeout | null = null; readonly #globalEv = new EventEmitter(); + #logger: Logger; constructor( @Inject(DI.redisForSub) @@ -69,6 +72,7 @@ export class StreamingApiServerService implements OnApplicationShutdown { private config: Config, ) { this.redisForSub.on('message', this.onRedis); + this.#logger = loggerService.getLogger('streaming', 'coral'); } @bindThis @@ -222,6 +226,11 @@ export class StreamingApiServerService implements OnApplicationShutdown { } }); + ws.once('error', (e) => { + this.#logger.error(`Unhandled error in Streaming Api: ${renderInlineError(e)}`); + ws.terminate(); + }); + this.#wss.emit('connection', ws, request, { stream, user, app, });