Skip to content

http,inspector: add WebSocket upgrade observability#62933

Open
GrinZero wants to merge 1 commit intonodejs:mainfrom
GrinZero:feat/http-upgrade-websocket-observer
Open

http,inspector: add WebSocket upgrade observability#62933
GrinZero wants to merge 1 commit intonodejs:mainfrom
GrinZero:feat/http-upgrade-websocket-observer

Conversation

@GrinZero
Copy link
Copy Markdown

@GrinZero GrinZero commented Apr 24, 2026

Summary

This patch adds observability for HTTP client WebSocket upgrades in both
diagnostics_channel and the inspector Network domain.

Today, regular HTTP client requests are surfaced through the existing
http.client.* flow, but upgrade-based WebSocket traffic is not represented
cleanly: the HTTP upgrade handshake is not exposed as a first-class lifecycle,
and subsequent WebSocket frames are not emitted through inspector network
events.

This change fills that gap.

Changes

  • add a shared internal WebSocket upgrade observer
  • emit diagnostics channel events for HTTP client upgrade handshakes
  • emit inspector Network.webSocket* events for socket creation, handshake
    request/response, sent frames, received frames, and socket close
  • prevent upgraded WebSocket requests from being double-reported as normal
    HTTP request/response traffic
  • document the new diagnostics channel hooks
  • add parallel coverage for diagnostics channel and inspector behavior

Why This Approach

This is implemented at the HTTP upgrade / inspector plumbing layer rather than
in a specific WebSocket client surface.

That keeps the change aligned with where the underlying lifecycle actually
occurs: the initial HTTP upgrade, the handshake response, and the frame
traffic that follows on the upgraded socket. It also avoids coupling the
observability model to any single user-facing API and makes the behavior
reusable for clients built on the same upgrade path.

Scope

This PR does not add special handling or declarations for built-in
WebSocket or WebSocketStream directly.

Those APIs are a separate surface-area discussion. The goal here is narrower:
add the transport-level observability needed for WebSocket upgrades and frame
traffic first, so higher-level clients can benefit from the same underlying
inspector and diagnostics hooks without expanding this PR beyond that scope.

Testing

Automated:

  • python3 tools/test.py test/parallel/test-diagnostics-channel-http-server-upgrade-websocket.js test/parallel/test-diagnostics-channel-http-upgrade-websocket.js test/parallel/test-inspector-emit-protocol-event-errors.js test/parallel/test-inspector-emit-protocol-event.js test/parallel/test-inspector-network-http-upgrade-websocket.js

Manual e2e:

  • used a small local Koa app with a /ws route
  • /ws opens an outbound ws connection to wss://echo.websocket.org/
  • the route sends an initial message after connect and follow-up frames via
    /ws?message=...
  • verified that inspector surfaces the WebSocket creation, handshake, frame
    send/receive, and close lifecycle end to end

Refs: #53946

@nodejs-github-bot
Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/http
  • @nodejs/inspector
  • @nodejs/net

@nodejs-github-bot nodejs-github-bot added lib / src Issues and PRs related to general changes in the lib or src directory. needs-ci PRs that need a full CI run. labels Apr 24, 2026
@GrinZero
Copy link
Copy Markdown
Author

E2E
image
image

const Koa = require('koa')
const Router = require('koa-router')
const WebSocket = require('ws')

const app = new Koa()
const router = new Router()

let ws
let opened

function ensureWebSocket() {
  if (ws && ws.readyState === WebSocket.OPEN) return
  Promise.resolve()

  ws = new WebSocket('wss://echo.websocket.org/')
  opened = new Promise((resolve) => ws.once('open', resolve))

  ws.on('message', (data) => {
    console.log('WebSocket message:', data.toString())
  })

  ws.on('close', () => {
    console.log('WebSocket connection closed')
    ws = null
    opened = null
  })

  return opened
}

router.get('/ws', async (ctx) => {
  await ensureWebSocket()
  ws.send(ctx.query.message || 'Hello from Koa')
  ctx.body = 'WebSocket: Send'
})

app.use(router.routes())
app.listen(3000, () => {
  console.log('GET http://127.0.0.1:3000/ws?message=ping')
})

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lib / src Issues and PRs related to general changes in the lib or src directory. needs-ci PRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants