Heroku H15关闭Web套接字错误

时间:2015-09-22 22:28:32

标签: heroku go websocket

我有一个Go服务作为Heroku上的Web套接字服务器。客户端每20秒ping一次服务器,似乎保持连接打开。问题是当套接字连接关闭时,Heroku路由器抛出H15错误,认为请求花费了太多时间。例如,如果Web套接字连接已打开300秒,Heroku日志将显示:

... ... .H15。 dyno = web.1 connect = 1ms service = 300000ms status = 503 bytes = 147 ....

有人经历过这个吗?

4 个答案:

答案 0 :(得分:4)

是的!我经历了这种情况,经过一些深入的调试,我得出的结论是,这只是Heroku Router引擎中的“假阳性”。我的调试过程如下:

  1. 在客户端和在Heroku上运行的WebSocket服务器之间创建WebSocket连接。
  2. 如果您使用的是socket.io或类似的库,则最有可能在将协议切换到WebSocket之前建立HTTP轮询连接。 (您可以在Heroku Router日志中看到这些GET / POST请求。)
  3. 使用WebSocket协议建立连接后,客户端将通过发送心跳使保持连接(实际上服务器也可以这样做)。 最初,您不会在Heroku Router日志中看到此请求,因为该请求仍处于待处理状态(它处于打开状态,每隔几秒钟发送一次数据包,即心跳信号)。您可以使用Chrome DevTools的“网络”标签对此进行监控。
  4. 心跳阻止Heroku Router终止请求,因为每次在客户端和服务器之间Heroku Router timeout timer (55secs) is reset之间传输几个字节时。
  5. 但是,您的客户端何时关闭连接(即:关闭浏览器标签,刷新页面,断开互联网连接等); Heroku路由器检测到该请求已终止,并且(这是我根据调试得出的猜测)因为该请求处于“待处理”状态,因此其响应好像空闲了一样持续了X毫秒,其中X是从收到请求到关闭请求的时间,即您看到这样的日志:service=79859ms status=101

结论:只要您的客户端应用终止WebSocket连接,Heroku Router就会记录一条错误15行,该连接在X毫秒的时间内运行良好。因此,在许多情况下,可能只是用户离开了您的应用。

我希望这对人们有帮助,希望你们可以少担心一件事情:)

答案 1 :(得分:2)

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

答案 2 :(得分:0)

如果有人来这里从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引导中的WebSocket配置,因为我只希望创建连接并将其保持在map中,并带有用户名和会话详细信息。

之后,在Springboot中,主方法仅每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.

答案 3 :(得分:0)

在尝试发送之前检查 yourSocket.READYSTATE === WebSocket.OPEN

如果没有打开,那么通过实现类似 this

的东西来确保它是打开的

仅仅实施乒乓解决方案不足以保持我的连接畅通。