为了账号安全,请及时绑定邮箱和手机立即绑定

Web 套接字关闭时的 Heroku H15 错误

Web 套接字关闭时的 Heroku H15 错误

Go
慕标5832272 2021-11-08 18:07:42
我有一个 Go 服务作为 Heroku 上的网络套接字服务器。客户端每 20 秒 ping 服务器一次,它似乎保持连接打开。问题在于,当套接字连接关闭时,Heroku 路由器会抛出 H15 错误,认为请求花费了太多时间。例如,如果 Web 套接字连接已打开 300 秒,Heroku 日志将显示:……H15…… dyno=web.1 connect=1ms service=300000ms status=503 bytes=147....任何人都经历过这种情况?
查看完整描述

3 回答

?
慕哥9229398

TA贡献1877条经验 获得超6个赞

是的!我经历过这种情况,经过一些深度调试后,我得出结论,这只是 Heroku 路由器引擎中的“误报”。我的调试是这样的:

  1. 在客户端和 Heroku 上运行的 WebSocket 服务器之间创建一个 WebSocket 连接。

  2. 如果您使用 socket.io 或类似的库,它很可能会在将协议切换到 WebSocket 之前建立 HTTP 轮询连接。(您可以在 Heroku 路由器日志中看到这些 GET/POST 请求)。

  3. 一旦使用 WebSocket 协议建立连接,客户端将通过发送心跳保持连接(实际上服务器也可以这样做)。最初,您不会在 Heroku 路由器日志中看到此请求,因为它仍处于挂起状态(它处于打开状态并每隔几秒发送一次数据包,即心跳)。您可以使用 Chrome DevTools Network 选项卡来监控这一点。

  4. 心跳会阻止 Heroku 路由器终止请求,因为每次在客户端和服务器之间传输几个字节时,Heroku 路由器超时计时器(55 秒)都会重置

  5. 但是,每当您的客户端关闭连接时(即:关闭浏览器选项卡、刷新页面、断开互联网连接等);Heroku 路由器检测到请求被终止(这是我基于调试的猜测)因为请求是“待处理”,它的反应好像它空闲了 X 毫秒,其中 X 是从收到请求开始的时间直到它关闭,你才会看到这样的日志:service=79859ms status=101

结论:每当您的客户端应用程序终止 WebSocket 连接时Heroku 路由器都会记录错误 15 行,该连接在 X 毫秒内运行良好。因此,在许多情况下,可能只是用户离开了您的应用。

我希望这对人们有所帮助,并且你们可以少担心一件事情去睡觉:)


查看完整回答
反对 回复 2021-11-08
?
白衣非少年

TA贡献1155条经验 获得超0个赞

我通过每秒从服务器发送 ping 来解决这个问题,就像在 heroku nodejs 示例中一样:https : //github.com/heroku-examples/node-websockets/blob/master/server.js


查看完整回答
反对 回复 2021-11-08
?
犯罪嫌疑人X

TA贡献2080条经验 获得超4个赞

如果有人来这里从Java角度寻找解决方案:


@Component

public class SocketHandler extends TextWebSocketHandler {


    public static Map<String, WebSocketSession> sessions = new ConcurrentHashMap<>();


    @Override

    public void afterConnectionEstablished(WebSocketSession session) throws Exception {

        String userName = (String) session.getAttributes().get("userName");

        sessions.put(userName, session);

        super.afterConnectionEstablished(session);

    }


    @Override

    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {

        String userName = (String) session.getAttributes().get("userName");

        sessions.remove(userName);

        super.afterConnectionClosed(session, status);

    }

}



@Configuration

@EnableWebSocket

public class WebSocketConfig implements WebSocketConfigurer {


    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {

        registry.addHandler(new SocketHandler(), "/webSocket/AppName/*").addInterceptors(auctionInterceptor());;

    }


    @Bean

    public HandshakeInterceptor auctionInterceptor() {

        return new HandshakeInterceptor() {

            public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response,

                                           WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {


                // Get the URI segment corresponding to the auction id during handshake

                String path = request.getURI().getPath();

                String userName = path.substring(path.lastIndexOf('/') + 1);


                // This will be added to the websocket session

                attributes.put("userName", userName);

                return true;

            }


            public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response,

                                       WebSocketHandler wsHandler, Exception exception) {

                // Nothing to do after handshake

            }

        };

    }

}

以上是我在 Spring boot 中的 WebSocket 配置,因为我只想创建连接并将其保存在map用户名和会话详细信息中。


之后在 Springboot main 方法中每 30 秒添加一次 ping,如下所示:


public static void main(String[] args) {

    SpringApplication.run(MayAppName.class, args);

    ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);

    Runnable r = () -> {

        if(SocketHandler.sessions != null && !SocketHandler.sessions.isEmpty()) {

            for (Map.Entry<String, WebSocketSession> stringWebSocketSessionEntry : SocketHandler.sessions.entrySet()) {

                try {

                    // Dummy ping to keep connection alive.

                    stringWebSocketSessionEntry.getValue().sendMessage(new TextMessage("Dummy Ping"));

                } catch (IOException e) {

                    e.printStackTrace();

                }

            }


        }

    };


    scheduler.scheduleAtFixedRate(r, 0, 30, TimeUnit.SECONDS);

}

这样做的作用是,它每 30 秒不断 ping 订阅者,以便默认55秒数不会影响 websocket 连接,如 Heroku 文档中所建议的那样。


Timeouts

The normal Heroku HTTP routing timeout rules apply to WebSocket connections. Either client or server can prevent the connection from idling by sending an occasional ping packet over the connection.


查看完整回答
反对 回复 2021-11-08
  • 3 回答
  • 0 关注
  • 151 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信