Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

xhr_streaming infinite loop after connection closed #308

Closed
skozin opened this issue Apr 22, 2016 · 36 comments
Closed

xhr_streaming infinite loop after connection closed #308

skozin opened this issue Apr 22, 2016 · 36 comments

Comments

@skozin
Copy link

skozin commented Apr 22, 2016

Steps to reproduce:

  1. Turn WebSockets off in NodeJS server (websocket: false in options);
  2. On the server, close all SockJS connections after a delay of 500 ms using conn.close(1000, 'init_message_timeout') (the exact code and message don't matter);
  3. Connect to that server using SockJS 1.0.1 (latest) or 0.3.4 from the latest Chrome.

During the first 500 ms, the connection stays alive. After that, it gets closed as expected. However, the xhr_streaming transport starts an infinite loop: it tries to connect to the server, then SockJS server (not the app logic) immediately closes that connection with the code/reason provided in the step 2, and, after that, xhr_streaming attempts to connect again, and so on:

image

Here is HTTP log from the server:

image

This infinite loop takes significant amount of CPU on the client (up to 70%) and on the server and load balancers, effectively causing DDOS when there are multiple such forcefully-closed clients.

It seems to me that xhr_streaming should not attempt to reconnect after it gets user-provided close reason from the server. Or am I misunderstanding something?

This issue hits us in production, and we need to resolve it as fast as possible.

@gravitycode
Copy link

gravitycode commented Apr 29, 2016

We have the same problem. You can reproduce disconnecting internet and then, after a few second reconnect. I could see when my server close de session for lack of communication by the client( c[3000, go away!]), then the loop occurs on the client.

captura de pantalla 2016-04-29 a las 11 15 51

@brycekahle
Copy link
Contributor

I'm having trouble reproducing this on sockjs-client 1.0.3 against sockjs-node 0.3.17, can you try with those versions?

@gravitycode
Copy link

gravitycode commented May 2, 2016

@brycekahle i use Java Spring as backend technology. I can see when disconnect and reconnect how sockjs-client try to use the old and new xhr_streaming sessión. The backend close the connection but again and again receive xhr_streaming with the old session. the backend respond with c[3000, "go away!"].

Note:
crks00qp is the old session.

My backend log:
2016-05-02 21:45:01.861 DEBUG [http-nio-8090-exec-4] o.s.w.s.s.t.h.XhrStreamingTransportHandler - **Connection already closed (but not removed yet) for XhrStreamingSockJsSession[id=crks00qp]** 2016-05-02 21:45:01.943 DEBUG [http-nio-8090-exec-7] o.s.w.s.s.s.WebSocketHandlerMapping - Matching patterns for request [/stomp/026/crks00qp/xhr_streaming] are [/stomp/**] 2016-05-02 21:45:01.943 DEBUG [http-nio-8090-exec-7] o.s.w.s.s.s.WebSocketHandlerMapping - URI Template variables for request [/stomp/026/crks00qp/xhr_streaming] are {} 2016-05-02 21:45:01.943 DEBUG [http-nio-8090-exec-7] o.s.w.s.s.s.WebSocketHandlerMapping - Mapping [/stomp/026/crks00qp/xhr_streaming] to HandlerExecutionChain with handler [org.springframework.web.socket.sockjs.support.SockJsHttpRequestHandler@44e4e737] and 1 interceptor 2016-05-02 21:45:01.943 DEBUG [http-nio-8090-exec-7] o.s.w.s.s.t.h.DefaultSockJsService - Processing transport request: POST http://server:8090/eess-services/stomp/026/crks00qp/xhr_streaming?t=1462218306308

captura de pantalla 2016-05-02 a las 21 58 30

We can see in this screen how sockjs-client have 2 sessions active:
slice 1

@brycekahle Can you explain me where and how sockjs-client manage xhr_streaming invokes? i think that sockjs-client after reconnect (i delete SockJS object in reconnections) maintains the old xhr_streaming petitions (memory leak?)

Edit:
I changed to "xhr -polling" and infinite loop also its occurs:
captura de pantalla 2016-05-03 a las 16 35 20

2016-05-03 16:35:01.000 DEBUG [http-nio-8090-exec-2] o.s.w.s.s.t.h.EventSourceTransportHandler - Connection already closed (but not removed yet) for EventSourceStreamingSockJsSession[id=mcixdipk] 2016-05-03 16:35:01.289 TRACE [clientOutboundChannel-59] o.s.w.s.s.t.s.PollingSockJsSession - 1 message(s) to flush in session cnc34wlw 2016-05-03 16:35:01.289 TRACE [clientOutboundChannel-59] o.s.w.s.s.t.s.PollingSockJsSession - Session is active, ready to flush. 2016-05-03 16:35:01.289 TRACE [clientOutboundChannel-59] o.s.w.s.s.t.s.PollingSockJsSession - Preparing to write SockJsFrame content='a["\n"]' 2016-05-03 16:35:01.289 TRACE [clientOutboundChannel-59] o.s.w.s.s.t.s.PollingSockJsSession - Writing to HTTP response: a["\n"]

@gravitycode
Copy link

gravitycode commented May 4, 2016

I could play in local turning off and starting the server several times (Not always happen). I cant understand how SockJS once reconnected ( We destroy and create the Sockjs instance from 0 ) send xhr-streaming requests with the id of the old session ( Maintains the pre-reconnection id session? ). I have spoken with other colleagues and I all agree in saying that SockJS not managed well the connection drops. I activated the traces of SockJS and this brings me back :

New Sessión Id -> h2pystok
Old session Id -> ypsjtids

`<<< PONG
browser.js:120 sockjs-client:buffered-sender send +45ms "\n"
browser.js:120 sockjs-client:buffered-sender sendSchedule +0ms 1
browser.js:120 sockjs-client:ajax-based create ajax sender +2ms http://localhost/eess-services/stomp/355/h2pystok ["\n"]
browser.js:120 sockjs-client:browser:xhr POST +0ms http://localhost/eess-services/stomp/355/h2pystok/xhr_send
ws.js:216 >>> PING
browser.js:120 sockjs-client:browser:xhr withCredentials +2ms
browser.js:120 sockjs-client:browser:xhr readyState +18ms 4
browser.js:120 sockjs-client:browser:xhr status +1ms 200
browser.js:120 sockjs-client:browser:xhr finish +0ms 200 hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
o
h
h
h
h
h

browser.js:120 sockjs-client:receiver:xhr finish +0ms 200 hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
o
h
h
h
h
h

browser.js:120 sockjs-client:receiver:xhr _chunkHandler +0ms 200
browser.js:120 sockjs-client:receiver:xhr close +0ms network
browser.js:120 sockjs-client:polling close +1ms null network undefined
browser.js:120 sockjs-client:polling _scheduleReceiver +0ms
browser.js:120 sockjs-client:receiver:xhr http://localhost/eess-services/stomp/266/ypsjtids/xhr_streaming +0ms
browser.js:120 sockjs-client:browser:xhr POST +1ms http://localhost/eess-services/stomp/266/ypsjtids/xhr_streaming
browser.js:120 sockjs-client:receiver:xhr _cleanup +0ms
browser.js:120 sockjs-client:browser:xhr cleanup +0ms
browser.js:120 sockjs-client:browser:xhr withCredentials +1ms
browser.js:120 sockjs-client:browser:xhr readyState +18ms 2
browser.js:120 sockjs-client:browser:xhr readyState +1ms 4
browser.js:120 sockjs-client:browser:xhr status +0ms 204
browser.js:120 sockjs-client:browser:xhr finish +0ms 204
browser.js:120 sockjs-client:ajax-based finish +0ms 204
browser.js:120 sockjs-client:buffered-sender sendScheduleWait +1ms
browser.js:120 sockjs-client:browser:xhr cleanup +0ms
browser.js:120 sockjs-client:browser:xhr readyState +25ms 2
browser.js:120 sockjs-client:browser:xhr readyState +0ms 3
browser.js:120 sockjs-client:browser:xhr status +0ms 200
browser.js:120 sockjs-client:browser:xhr chunk +0ms
browser.js:120 sockjs-client:receiver:xhr _chunkHandler +0ms 200
browser.js:120 sockjs-client:browser:xhr readyState +1ms 4
browser.js:120 sockjs-client:browser:xhr status +0ms 200
browser.js:120 sockjs-client:browser:xhr finish +0ms 200 c[3000,"Go away!"]
browser.js:120 sockjs-client:receiver:xhr finish +0ms 200 c[3000,"Go away!"]
browser.js:120 sockjs-client:receiver:xhr _chunkHandler +0ms 200
browser.js:120 sockjs-client:receiver:xhr close +1ms network
browser.js:120 sockjs-client:polling close +0ms null network undefined
browser.js:120 sockjs-client:polling _scheduleReceiver +0ms
browser.js:120 sockjs-client:receiver:xhr http://localhost/eess-services/stomp/266/ypsjtids/xhr_streaming +0ms
browser.js:120 sockjs-client:browser:xhr POST +0ms http://localhost/eess-services/stomp/266/ypsjtids/xhr_streaming
browser.js:120 sockjs-client:receiver:xhr _cleanup +1ms
browser.js:120 sockjs-client:browser:xhr cleanup +0ms
browser.js:120 sockjs-client:buffered-sender timeout +0ms
browser.js:120 sockjs-client:buffered-sender sendSchedule +0ms 0
browser.js:120 sockjs-client:browser:xhr withCredentials +1ms
browser.js:120 sockjs-client:browser:xhr readyState +40ms 2
browser.js:120 sockjs-client:browser:xhr readyState +0ms 3
browser.js:120 sockjs-client:browser:xhr status +0ms 200
browser.js:120 sockjs-client:browser:xhr chunk +0ms
browser.js:120 sockjs-client:receiver:xhr _chunkHandler +0ms 200
browser.js:120 sockjs-client:browser:xhr readyState +1ms 4
browser.js:120 sockjs-client:browser:xhr status +0ms 200
browser.js:120 sockjs-client:browser:xhr finish +0ms 200 c[3000,"Go away!"]
browser.js:120 sockjs-client:receiver:xhr finish +0ms 200 c[3000,"Go away!"]
browser.js:120 sockjs-client:receiver:xhr _chunkHandler +0ms 200
browser.js:120 sockjs-client:receiver:xhr close +0ms network
browser.js:120 sockjs-client:polling close +1ms null network undefined
browser.js:120 sockjs-client:polling _scheduleReceiver +0ms
browser.js:120 sockjs-client:receiver:xhr http://localhost/eess-services/stomp/266/ypsjtids/xhr_streaming +0ms
browser.js:120 sockjs-client:browser:xhr POST +0ms http://localhost/eess-services/stomp/266/ypsjtids/xhr_streaming
browser.js:120 sockjs-client:receiver:xhr _cleanup +0ms
browser.js:120 sockjs-client:browser:xhr cleanup +0ms
browser.js:120 sockjs-client:browser:xhr withCredentials +1ms
browser.js:120 sockjs-client:browser:xhr readyState +37ms 2
browser.js:120 sockjs-client:browser:xhr readyState +1ms 3
browser.js:120 sockjs-client:browser:xhr status +0ms 200
browser.js:120 sockjs-client:browser:xhr chunk +0ms
browser.js:120 sockjs-client:receiver:xhr _chunkHandler +0ms 200
browser.js:120 sockjs-client:browser:xhr readyState +1ms 4
browser.js:120 sockjs-client:browser:xhr status +0ms 200
browser.js:120 sockjs-client:browser:xhr finish +0ms 200 c[3000,"Go away!"]
browser.js:120 sockjs-client:receiver:xhr finish +0ms 200 c[3000,"Go away!"]
browser.js:120 sockjs-client:receiver:xhr _chunkHandler +1ms 200
browser.js:120 sockjs-client:receiver:xhr close +0ms network
browser.js:120 sockjs-client:polling close +0ms null network undefined
browser.js:120 sockjs-client:polling _scheduleReceiver +0ms
browser.js:120 sockjs-client:receiver:xhr http://localhost/eess-services/stomp/266/ypsjtids/xhr_streaming +0ms
browser.js:120 sockjs-client:browser:xhr POST +1ms http://localhost/eess-services/stomp/266/ypsjtids/xhr_streaming
browser.js:120 sockjs-client:receiver:xhr _cleanup +0ms
browser.js:120 sockjs-client:browser:xhr cleanup +0ms
browser.js:120 sockjs-client:browser:xhr withCredentials +1ms
browser.js:120 sockjs-client:browser:xhr readyState +46ms 2
browser.js:120 sockjs-client:browser:xhr readyState +1ms 3
browser.js:120 sockjs-client:browser:xhr status +0ms 200
browser.js:120 sockjs-client:browser:xhr chunk +0ms
browser.js:120 sockjs-client:receiver:xhr _chunkHandler +0ms 200
browser.js:120 sockjs-client:browser:xhr readyState +0ms 4
browser.js:120 sockjs-client:browser:xhr status +0ms 200
browser.js:120 sockjs-client:browser:xhr finish +1ms 200 c[3000,"Go away!"]
browser.js:120 sockjs-client:receiver:xhr finish +0ms 200 c[3000,"Go away!"]`

xhrtransport

Im using:
java spring-websockets
stomp over websockets

@gravitycode
Copy link

I found where is the possible error. In polling.js i have see a special treatment when the close reason is 'network'. When is network again call the function _scheduleReceiver(). When we reconnect the infinite loop occurs. I dont know what is the reason for this treatment but I could try this deleting the special treatment of 'network' and everything works correctly. @skozin Can you try?

if (!self.pollIsClosing) { if (reason === 'network') { self._scheduleReceiver(); } else { self.emit('close', code || 1006, reason); self.removeAllListeners(); } }

the workaround is:

if (!self.pollIsClosing) { self.emit('close', code || 1006, reason); self.removeAllListeners(); }

@skozin
Copy link
Author

skozin commented May 14, 2016

@gravitycode @brycekahle thanks, I'll try your suggestions in a few days. For us, the quickest solution was to temporarily disable our disconnection logic on the backends, but it is not a solution really, so I'll need to fix this pretty soon.

@gravitycode
Copy link

After a few days testing , I have found that in iOS (Iphone 6 with the latest version of iOS ) can not connect . I have solved the infinite loop in desktop but now I can´t connect in mobile

@odiszapc
Copy link

Reproduced on v1.1.0 and with NettoSphere+AtmosphereSockJs on a server side

@milmoe
Copy link

milmoe commented Sep 15, 2016

We are seeing this problem in production..any one has workaround to fix it?

@petr-ujezdsky
Copy link

We have encountered this problem as well. I have successfully replicated it using Fiddler (HTTP(S) transport watcher for Windows):

  • The server is configured to use "xhr_streaming, xhr_polling" only, we use spring-websocket-4.3.3.RELEASE.
  • When the Fiddler is used in "buffering mode" (meaning it buffers the whole response before sending it to the browser), the SockJS goes crazy and starts to send */xhr_streaming POST requests zilion times per second.
  • When the Fiddler is used in "streaming mode" (meaning it forwards response bytes as quickly as they arrives from server), the SockJS functions normally.

This "buffering proxy" should be somehow detected by SockJS and the protocol should be downgraded to the polling one as it is the only one that will work within such environment.

@gravitycode
Copy link

@petr-ujezdsky i detected the same issue with my firewall. The problem was buffering mode in this layer. We had to downgrade the transport protocol to xhr_polling until the firewall provider made the changes to support streaming

@sudpaw
Copy link

sudpaw commented Oct 28, 2016

Hi!

We are heavily hit by this issue. Basically we have a few customers, that is ddos'ing our production servers because the sockjs client is calling in an infinite loop.

Any info on a possible fix?

We will try and implement the workaround mentioned by @gravitycode. But it would be nice to get an official fix.

@gravitycode
Copy link

@sudpaw Remember that my workaround didnt work in my phone (iphone). What kind of transport are you using? websockets, xhr-streaming?

@sudpaw
Copy link

sudpaw commented Nov 4, 2016

@gravitycode We are mainly using websocket. But we have some customers that are behind corporate proxies and they are getting xhr_streaming - this is where it is hurting us.

@gravitycode
Copy link

i could try with only this transports: ['websockets', 'xhr-polling']. xhr-streaming can have many errors with some layer that implement buffering like firewalls, traffic check, etc. I had that problem with my firewall.

@petr-ujezdsky
Copy link

BTW one of my collegues have found another major issue, this time in spring-websocket-4.3.3-RELEASE java library. When you use xhr_polling and the client is currently disconnected (aka. between the polling requests), any messages sent on the server side are lost in vein.
Sadly we have to ditch the SockJS library because of these major issues.

@swinterberger
Copy link

Using websocket protocol after a connection loss sockJS was slamming our server indefinitely. @gravitycode solution worked for us.

We are using spring 4.3 with spring security.

Not too concerned about iPhone so this is great, thanks @gravitycode

@brycekahle
Copy link
Contributor

Hey folks, can you try 1.1.2 and let me know if you can still reproduce the problem?

@swinterberger
Copy link

Looks like 1.1.2 has resolved the issue. Thanks!

@gravitycode
Copy link

The problem has been resolved! Excellent! Thank you so much

@tlavarea
Copy link

Still having this issue even on 1.1.2.

@craiggoldstone
Copy link

craiggoldstone commented Mar 14, 2017

We also still have this issue with a backend using spring-websocket 4.3.2.RELEASE with spring security in front. This issue also seems related to #334.
The workaround proposed by @gravitycode is our only solution to prevent DOS after the user sessions expire!

@brycekahle
Copy link
Contributor

Given that it resolved the issue for other folks, it is possible you are having a different issue. Can you please provide more details and repro steps on your issue?

@kchandrashekaran
Copy link

kchandrashekaran commented Mar 21, 2017

I still have this issue in v1.1.2. We are using "sockjs-client" and "stompjs" on client side and "spring-websocket" v4.2.7 on the server side.

Our app times out after x minutes. after timeout, we disconnect our stomp client and close the websocket. This happens as expected and the app doesn't makes any calls to server for sometime.

After some minutes, they is flood of POST request of 'xhr_streaming' and it stops sometimes after 100 requests, sometimes 25, but the app keeps doing in regular intervals.

@gravitycode's workaround fixed this issue. Thank you.

Connect Code:
var socket = new SockJS(url);
var stompClient = Stomp.over(socket);
stompClient.hearbeat.outgoing = 0;
stompClient.hearbeat.incoming = 0;
stompClient.connect({}, function(frame) {
//sucess handling
}, function(error) {
//error handling
});

Disconnect code:
if (stompClient != null && stompClient ) {
try{
stompClient.disconnect(); //internally closes websocket
stompClient = undefined;
socket = undefined;
} catch(e) {
console.error( "Unable to disconnect", e);
}
}

I checked the code. In polling.js, in this part of code.
if (!self.pollIsClosing) {
if (reason === 'network') {
self._scheduleReceiver();
} else {
self.emit('close', code || 1006, reason);
self.removeAllListeners();
}
}

when websocket is closed, we get reason = 'user' and pollIsClosing = 'true' and control doesn't go inside the first if, as it check whether 'pollIsClosing' is undefined. So, i changed the logic to:
if(!self.pollIsClosing || self.pollIsClosing === true) {

This did not fix the issue, again after sometime, this method is called with reason='network' and floods our server with network requests.
@brycekahle When user closes socket normally, can we kill all polling and trasnport, is it possible? Please help us with this issue.

@manideeppabba1991
Copy link

manideeppabba1991 commented Feb 14, 2018

I had the same issue. My app was running on secure "https://" where as I am trying to fetch the sockjs-0.3.min.js from an "unsecured CDN location" which fails to download the .js file. Which causes it to look for Xhr_Streaming instead of my sockjs methods while disconnecting the socket.
Some references related to http vs https:
https://developers.google.com/web/fundamentals/security/prevent-mixed-content/fixing-mixed-content

I fixed this issue by changing the "unsecured cdn path to secured cdn path" i.e.,
`index.html

<script src="https://cdn.jsdelivr.net/sockjs/0.3.4/sockjs.min.js"></script>`

@anselvo
Copy link

anselvo commented Feb 22, 2018

I have the same problem with sockjs 1.1.4, spring boot websocket starter 1.5.10
I use stomp + sockjs

@AbiriAmir
Copy link

Same problem with sockjs 1.1.4, spring boot websocket

@tim-sysoev
Copy link

tim-sysoev commented Apr 6, 2018

Here is our workaround for this issue:

var sockJs = new SockJS(...);
var _transportClose = sockJs._transportClose;
sockJs._transportClose = function(code, reason) {
  if (this._transport && this._transport.close) {
    this._transport.close();
  }
  _transportClose.call(this, code, reason);
}

In our case, loop was triggered by (occasional) initialization timeout in xhr-polling transport. After the timeout, function _transportTimeout calls _transportClose, and _transportClose removes all listeners from transport, however, transport remains active. When transport receives message from spring with close request (like c[3000,Go away!]), no one will process this message (because there are no listeners!), and transport will loop (because response code is 200).

Update: I see this is already fixed in master, but not in 1.1.4

@bard
Copy link

bard commented Apr 29, 2018

@brycekahle any chance of a release? I encountered this problem using sockjs via Primus (primus/primus#635 (comment)), hand-patching with eef1957 fixed it but it would be nice to be able to rebuild Primus and depend on an official sockjs version.

@VolodymyrPortiankoJR
Copy link

@brycekahle, We also have problems with spawning multiple POST requests.
It would be nice to bring changes in new release.

@luxueyan
Copy link

mark!Have the same problem

@tcpaul
Copy link

tcpaul commented Aug 27, 2018

I have

xhr_streaming hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
a["{"type":"log-level","data":"none"}"]
a["{"type":"hot"}"]
a["{"type":"hash","data":"5502176a3cd3af5160fd"}"]
a["{"type":"ok"}"]

it does it two times when i refresh

@asartem
Copy link

asartem commented Sep 28, 2018

Still can reproduces. Webpack dev server spams with XHR requests. Why it couldnt be fixied somehow? It is a critical ussue, which doesn't allow to use web pack dev server at all.

@brycekahle
Copy link
Contributor

@asartem Is webpack using newer versions of sockjs-client? Do you have code you can share with a reproduction?

@xianshenglu
Copy link

I got this because of my shadowsocks, I turned it off and no more errors.

@zbykovskyi
Copy link

zbykovskyi commented Feb 1, 2022

I spent a lot of time debugging SockJS and Spring code to figure out with that issue and what I have found.

xhr.js file has next method to parse inbound text message:

XhrReceiver.prototype._chunkHandler = function(status, text) {
  debug('_chunkHandler', status);
  if (status !== 200 || !text) {
    return;
  }

  for (var idx = -1; ; this.bufferPosition += idx + 1) {
    var buf = text.slice(this.bufferPosition);
    idx = buf.indexOf('\n');
    if (idx === -1) {
      break;
    }
    var msg = buf.slice(0, idx);
    if (msg) {
      debug('message', msg);
      this.emit('message', msg);
    }
  }
};

That method expects \n at the end of the SockJS text frame.

But in case of "Connection already closed (but not removed yet) forsessionId" Spring Framework sends unformatted SockJsFrame.closeFrameGoAway() without ending \n.

AbstractHttpSendingTransportHandler.class

protected void handleRequestInternal(ServerHttpRequest request, ServerHttpResponse response,
			AbstractHttpSockJsSession sockJsSession) throws SockJsException {

		if (sockJsSession.isNew()) {
			if (logger.isDebugEnabled()) {
				logger.debug(request.getMethod() + " " + request.getURI());
			}
			sockJsSession.handleInitialRequest(request, response, getFrameFormat(request));
		}
		else if (sockJsSession.isClosed()) {
			if (logger.isDebugEnabled()) {
				logger.debug("Connection already closed (but not removed yet) for " + sockJsSession);
			}
			SockJsFrame frame = SockJsFrame.closeFrameGoAway();
			try {
				response.getBody().write(frame.getContentBytes());
			}
			catch (IOException ex) {
				throw new SockJsException("Failed to send " + frame, sockJsSession.getId(), ex);
			}
		}

SockJsFrame

    public static SockJsFrame closeFrame(int code, @Nullable String reason) {
    	return new SockJsFrame("c[" + code + ",\"" + (reason != null ? reason : "") + "\"]");
    }

As a quick and dirty fix I just created copy of org.springframework.web.socket.sockjs.frame.SockJsFrame and put in my classpath to substitute the original file with modified one.

    public static SockJsFrame closeFrame(int code, @Nullable String reason) {
        return new SockJsFrame("c[" + code + ",\"" + (reason != null ? reason : "") + "\"]\n");
    }

But in reality AbstractHttpSendingTransportHandler class should be fixed to apply format to the SockJS frame.

    SockJsFrameFormat frameFormat = this.getFrameFormat(request);
    SockJsFrame frame = SockJsFrame.closeFrameGoAway();
    String formattedFrame = frameFormat.format(frame);
    try {
        response.getBody().write(formattedFrame.getBytes(SockJsFrame.CHARSET));
    }

After that fix all works as expected and c[1000, "Go Away!"] successfully routes to the 'c' section of next switch

    SockJS.prototype._transportMessage = function(msg) {
      ...
      switch (type) {
        case 'a':
          if (Array.isArray(payload)) {
            payload.forEach(function(p) {
              debug('message', self.transport, p);
              self.dispatchEvent(new TransportMessageEvent(p));
            });
          }
          break;
        case 'm':
          debug('message', this.transport, payload);
          this.dispatchEvent(new TransportMessageEvent(payload));
          break;
        case 'c':
          if (Array.isArray(payload) && payload.length === 2) {
            this._close(payload[0], payload[1], true);
          }
          break;
      }
    };

Without that fix message is not parsed as close message.

spring-projects/spring-framework#28000

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests