An upgrade request goes through the pre-routing handler before the WebSocket routes are matched. When none matched, it was routed as a plain HTTP request, and routing() ran the handler a second time. Skip it there for an upgrade request.