SignalR client function not called when transport=serverSentEvents - google-chrome

On my machines I find that SignalR client functions are not called in Chrome.
My SignalR test app works fine with with IE9, Firefox and even Safari on my iphone. Looking at Fiddler, these browsers all seem to negotiate transport=longPolling. But Chrome negotiates a connection with transport=serverSentEvents, and I'm assuming this is why client functions are not called in Chrome.
More detail:
I'm using full IIS (not IIS express) on Windows 7. I'm using SignalR version 1.0.0-rc2. I've disabled my AVG firewall and the Windows firewall is not running. Chrome is version 24.0.1312.56, and was up-to-date at the time of writing. The app is invoked on localhost.
On Chrome, the signalR connection seems to take place OK - the $.connection.hub.start().done callback function is invoked. But after that, the client function is never called, even while the other browsers work just fine.
In the client-side code, I've turned on logging with
$.connection.hub.logging = true;
I can see log messages in Chrome's javascript console that correspond to a successful connection.
For reference, those log messages are
[20:22:16 GMT+0800 (W. Australia Standard Time)] SignalR: Negotiating with '/SignalRChat-RC/signalr/negotiate'. jquery.signalR-1.0.0-rc2.js:54
[20:22:16 GMT+0800 (W. Australia Standard Time)] SignalR: Attempting to connect to SSE endpoint 'http://localhost/SignalRChat-RC/signalr/connect?transport=serverSentEvents&…7-22c5dbf27e0d&connectionData=%5B%7B%22name%22%3A%22chathub%22%7D%5D&tid=3' jquery.signalR-1.0.0-rc2.js:54
[20:22:16 GMT+0800 (W. Australia Standard Time)] SignalR: EventSource connected jquery.signalR-1.0.0-rc2.js:54
[20:22:16 GMT+0800 (W. Australia Standard Time)] SignalR: Now monitoring keep alive with a warning timeout of 40000 and a connection lost timeout of 60000 jquery.signalR-1.0.0-rc2.js:54
But there are no messages logged in Chrome's javascript console when a client-side method is invoked.
Interestingly, the send method works OK on Chrome. The other clients display a message sent from Chrome even through Chrome itself can't see it.
The application is pretty much the chat application from the signalR tutorial at http://www.asp.net/signalr/overview/getting-started/tutorial-getting-started-with-signalr
If I explicitly specify longPolling in the start method, i.e.
$.connection.hub.start({ transport: 'longPolling' })
then Chrome works OK. But my expectation was that I should be able to allow the browsers to negotiate their connection, and things would Just Work.
For reference, the relevant part of my client-side code looks like this:
$(function () {
// Turn on logging to the javascript console
$.connection.hub.logging = true;
// set up an error-handling function
$.connection.hub.error(function (err) {
alert("Error signalR:" + JSON.stringify(err));
});
// Declare a proxy to reference the hub.
var chat = $.connection.chatHub;
// Create a function that the hub can call to broadcast messages.
// This function is never called when running in Chrome with the default signalR connection
chat.client.broadcastMessage = function (name, message) {
// Html encode display name and message.
var encodedName = $('<div />').text(name).html();
var encodedMsg = $('<div />').text(message).html();
// Add the message to the page.
$('#discussion').append('<li><strong>' + encodedName
+ '</strong>: ' + encodedMsg + '</li>');
};
// Get the user name and store it to prepend to messages.
$('#displayname').val(prompt('Enter your name:', ''));
// Set initial focus to message input box.
$('#message').focus();
// Start the connection.
// Use $.connection.hub.start({ transport: 'longPolling' }) for reliability
// Use $.connection.hub.start() to demonstrate that Chrome doesn't receive messages
$.connection.hub.start().done(function () {
// Enable the "Send" button
$('#sendmessage').removeAttr('disabled');
$('#sendmessage').click(function () {
// Call the Send method on the hub.
chat.server.send($('#displayname').val(), $('#message').val());
// Clear text box and reset focus for next comment.
$('#message').val('').focus();
});
});
});
Can anybody see what I'm doing wrong?

I tried the sample in Win7 Google Chrome 24 and it works fine.
You can troubleshoot installing Fiddler and setting breakpoints in the javascript
POST /signalr/send?transport=serverSentEvents&connectionId=6ff0bffa-c31e-4d85-9aff-24f4528555ee HTTP/1.1
Host: localhost:43637
Connection: keep-alive
Content-Length: 113
Accept: application/json, text/javascript, */*; q=0.01
Origin:
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.57 Safari/537.17
Content-Type: application/x-www-form-urlencoded
Referer: /index.html
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
data=%7B%22H%22%3A%22chathub%22%2C%22M%22%3A%22Send%22%2C%22A%22%3A%5B%22gus%22%2C%22hello%22%5D%2C%22I%22%3A0%7D

May be it is related with buffering the responses.
https://github.com/SignalR/SignalR/issues/1944

Try setting EnableJSONP = False on the server hub. This fixed a similar issue I was having.

Related

How XMLHttpRequest behaves in combination with same-origin policy

I've a basic HTML, hosted in www.foodomain.com, with a simple script that just tries to make a POST call to a site located to another domain (www.bardomain.com), in order to provoke an action to be performed on that site. The attacker.html file is:
The hosting
<html lang="en">
<head>
<meta charset="utf-8">
<title>Attacker.html</title>
<script language="JavaScript" type="text/javascript">
var http = new XMLHttpRequest();
var url = "http://www.bardomain.com/attacked.php";
var params = "action=deleteAll";
http.open("POST", url, true);
//Send the proper header information along with the request
http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
http.setRequestHeader("Content-length", params.length);
http.setRequestHeader("Connection", "close");
http.onreadystatechange = function() {//Call a function when the state changes.
if (http.readyState == 4 && http.status == 200) {
alert("fet");
}
}
http.send(params);
</script>
</head>
<body>
Some presentation text ...
</body>
</html>
As far as I know that behaviour should be blocked by the web browser due to its same-origin policy, but in fact the POST call to the www.bardomain.com site is done, though the action is never accomplished because the apache server sends an HTTP 302 message:
www.bardomain.com:80 192.168.56.1 - - [14/Dec/2014:12:57:30 +0100] "POST /attacked.php HTTP/1.1" 302 509 "http://www.foodomain/attacker.html" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:34.0) Gecko/20100101 Firefox/34.0"
Since it's a HTTP 302 response the action is not really done, but I didn't even expect the request to be sent by the browser (since it's to another domain). I'd really appreciate if anybody could give me an explanation to this behaviour.
On the other hand, another curious behavior occurs if instead of accessing the attacker.html file from apache, I just load the file in the Eclipse web brower, the POST message is sent and returns an HTTP 200 message, so the action is performed in the www.bardomain.com:
www.bardomain.com:80 192.168.56.1 - - [14/Dec/2014:13:20:52 +0100] "POST /attacked.php HTTP/1.1" 200 1586 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.78.2 (KHTML, like Gecko) Safari/522.0"
Any explanation to those behaviours?
Sending information is not blocked, but the other end may not send you back anything. Also, your browser may not use scripts loaded from outside your host domain if the CORS headers are not found with the returning payload.
In other words, the whole Cross Site Request Security is a two way hand shake before it works. The client (your browser) typically makes a request with the verb OPTIONS to verify it will be allowed to request the script in the first place. The server (other end) then replies with the VERBS you can use to request stuff from outside its domain.
Then, when you actually request the asset, the client provides HOST information for the server to validate and if all is well, other headers will be added to the return telling the client that the information was requested and okay to use.
http://en.wikipedia.org/wiki/Cross-origin_resource_sharing

WebSocket permessage-deflate in Chrome with no context takeover

I have this problem with compression, and I am not sure if it is a bug. My WebSocket server does not support context takeover, and I am having problems sending messages, but not receiving.
The browser issues a request like this:
GET /socket HTTP/1.1
Host: thirdparty.com
Origin: http://example.com
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits, x-webkit-deflate-frame
If the server does not specify any option about context takeover:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Access-Control-Allow-Origin: http://example.com
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Extensions: permessage-deflate
I can read and write the first message, but cannot do subsequent reads or writes, because Chrome expects the server is keeping the context.
So my server provides this answer:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Access-Control-Allow-Origin: http://example.com
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Extensions: permessage-deflate; client_no_context_takeover; server_no_context_takeover
And now I can receive messages without problems, but again, I can only send the first message, the second message fails, and I see an error in Chrome saying that it failed at inflating the frame. I tried to send two identical strings, and I can see how the server is sending twice the same data, but the client fails to decompress it the second time.
So it seems that Chrome accepts the client_no_context_takeover parameter that specify that the client won't use the same compression context for all messages when compressing, but ignores server_no_context_takeover indicates the server won't use the same context.
Is this a bug in Chrome? I am not clear about if I can send options back that have not been offered/requested by the client.
Is there any other option I can use to disable the client context takeover?
UPDATE:
In WebSocketPerMessageDeflate.cpp in the Chromium source code, I can see:
if (clientNoContextTakeover != parameters.end()) {
if (!clientNoContextTakeover->value.isNull()) {
m_failureReason = "Received invalid client_no_context_takeover parameter";
return false;
}
mode = WebSocketDeflater::DoNotTakeOverContext;
++numProcessedParameters;
}
But also:
if (serverNoContextTakeover != parameters.end()) {
if (!serverNoContextTakeover->value.isNull()) {
m_failureReason = "Received invalid server_no_context_takeover parameter";
return false;
}
++numProcessedParameters;
}
In the first snippet, it is setting the "mode" variable, but in the second one is not doing nothing, so it seems it is basically ignoring the parameter.
Cheers.
A server must send a server_no_context_takeover parameter in a response only if the client requested the "no context takeover". In essence, the server acknowledges the client's request.
If a server decides to do "no context takeover" for sending on it's own (without the client having requested it), that's fine. In this case, no parameter is sent by the server.
A deflate sender can always on it's own drop compression context and/or reduce compression window size. There is no need to tell the receiver. The deflate wire format has enough information for the receiver to cope with that.
Here is how configuration and handshake looks with Crossbar.io.
I finally found the problem.
https://datatracker.ietf.org/doc/html/draft-ietf-hybi-permessage-compression-17#section-8.2.3
8.2.3.4. Using a DEFLATE Block with BFINAL Set to 1
Going through the examples in the draft I found my server was sending slightly different payloads. Turned out that the problem was the BFINAL, I need to set it to 0 by adding a 0 byte at the end.
Now it works.

Game + Web Server using ExpressJS

I'm currently trying to develop a simple Flash game which talks to a node.js server.
My question is this:
How might I go about making a server which differentiates web requests from game requests?
Here are the details of what I've done:
Previously, I used the net and static modules to handle requests from the game client and the browser, respectively.
TwoServers.js
// Web server
var file = new staticModule.Server('./public');
http.createServer(function(req, res){
req.addListener('end', function(){
file.serve(req, res, function(err, result){
// do something
});
});
}).listen(port1, "127.0.0.1");
// Game Server
var server = net.createServer(function(socket)
{
// handle messages to/from Flash client
socket.setEncoding('utf8');
socket.write('foo');
socket.on('data', onMessageReceived);
});
server.listen(port2, "127.0.0.1");
I'd like to do the above with just an Express server listening in on a single port, but I'm not sure how to go about doing that.
Here's what I'm thinking it might look like (doesn't actually work):
OneServer.js
var app = express();
app.configure(function()
{
// ...
app.use('/',express.static(path.join(__dirname, 'public'))); // The static server
});
app.get('/', function(req, res) // This is incorrect (expects http requests)
{
// Handle messages to/from Flash client
var socket = req.connection;
socket.setEncoding('utf8');
socket.write('foo');
socket.on('data', onMessageReceived);
});
app.listen(app.get('port')); // Listen in on a single port
But I'd like to be able to differentiate from web page requests and requests from the game.
Note: Actionscript's XMLSocket makes TCP requests, so using app.get('/') is incorrect for two reasons:
When Flash writes to the socket, it isn't using the http protocol, so app.get('/') will not be fired when the game tries to connect.
Since I don't have access to correct the net.Socket object, I cannot expect to be reading or writing from/to the correct socket. Instead, I'll be reading/writing from/to the socket associated with the web page requests.
Any help on this would be much appreciated (especially if I'm reasoning about this the wrong way).
When a TCP connection is opened to a given port, the server (Node + Express) has no way of telling who made that connection (whether it's a browser or your custom client).
Therefore, your custom client must speak HTTP if it wishes to communicate with the Express server sitting on port 80. Otherwise, the data you send over a freshly opened socket (in your custom protocol) will just look like garbage to Express, and it will close the connection.
However, this doesn't mean you can't get a TCP stream to speak a custom protocol over – you just have to speak HTTP first and ask to switch protocols. HTTP provides a mechanism exactly to accomplish this (the Upgrade header), and in fact it is how WebSockets are implemented.
When your Flash client first opens a TCP connection to your server, it should send: (note line breaks MUST be sent as CRLF characters, aka \r\n)
GET /gamesocket HTTP/1.1
Upgrade: x-my-custom-protocol/1.0
Host: example.com
Cache-Control: no-cache
​
The value of Upgrade is your choice, Host MUST be sent for all HTTP requests, and the Cache-Control header ensures no intermediate proxies service this request. Notice the blank line, which indicates the request is complete.
The server responds:
HTTP/1.1 101 Switching Protocols
Upgrade: x-my-custom-protocol/1.0
Connection: Upgrade
​
Again, a blank line indicates the headers are complete, and after that final CRLF, you are now free to send any data you like in any format over the TCP connection.
To implement the server side of this:
app.get('/gamesocket', function(req, res) {
if (req.get('Upgrade') == 'x-my-custom-protocol/1.0') {
res.writeHead(101, { Upgrade: req.get('Upgrade'), Connection: 'Upgrade' });
// `req.connection` is the raw net.Socket object
req.connection.removeAllListeners(); // make sure Express doesn't listen to the data anymore... we've got it from here!
// now you can do whatever with the socket
req.connection.setEncoding('utf8');
req.connection.write('foo');
req.connection.on('data', onMessageReceived);
} else res.send(400); // bad request
});
Of course, remember that TCP is not a message-based protocol, it only provides a stream, and thus the data events of a Socket can either fragment a single logical message into multiple events or even include several logical messages in a single event. Be prepared to manually buffer data.
Your other option here is to use socket.io, which implements a WebSockets server plus its own protocol on top of the WebSockets protocol. The WebSockets protocol is message-based. It mostly works just like I've outlined here, and then after HTTP negotiation adds a message framing layer on top of the TCP connection so that the application doesn't have to worry about the data stream. (Using WebSockets also opens the possibility of connecting to your server from a HTML page if necessary.)
There is a Flash socket.io client available.

AS3 ioError on some browsers only, why is that?

I am making a flash that calls google translate Text-to-speech service through the url:
translate.google.com/translate_tts?tl=en&q=example
I got it to work in firefox, but for some reason it does NOT work in chrome and safari. Where could be the problem?
the error I get is:
[IOErrorEvent type="ioError" bubbles=false cancelable=false eventPhase=2 text="Error #2032: Stream Error. URL: http://translate.google.com/translate_tts?tl=en&q=example"]
but when i copy/paste the URL in the browser, it returns a fil just like it should.
Flash players:
firefox: 10,0,42,34 installed - WORKS
chrome: 11,1,102,55 installed - DOES NOT WORK
safari: 10,0,42,34 installed - DOES NOT WORK
I am completely stunned. Don't know how to debug further.
Please help
UPDATE 1: FLASH CODE
public function say(text:String, language:String):void {
var urlString:String = createGoogleTTSUrl(text, language);
var url:URLRequest = new URLRequest(urlString);
//var context:SoundLoaderContext = new SoundLoaderContext(1000, true);
_sound = new Sound();
_sound.addEventListener(Event.COMPLETE, loadComplete);
_sound.addEventListener(ErrorEvent.ERROR, err);
_sound.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler2);
_soundChannel = new SoundChannel();
_sound.load(url); //, context);
}
private function ioErrorHandler2(event:IOErrorEvent):void {
trace(event);
}
I only later removed the SoundLoaderContext, but that didn't change anything.
UPDATE 2: Other people with same problem:
This tutorial has the same issue. Works in FF, but not in Chrome or Safari. People in comments reporting similar errors
(click the demo button:)
http://active.tutsplus.com/freebies/exclusive/exclusive-freebie-text-to-speech-utility/
The obvious reason for the #2032 error, when sniffing the actual request and response, is that Google is responding with a 404 when called from Flash in Chrome or IE (haven't tested Safari or Opera). But why does it return a 404?
Not a solution, but some troubleshooting - what does Firefox do differently from the others in terms of the request? In the following, ChD = "Chrome directly calling the API with no Flash (which works)"
Accept
FF: Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Ch: Accept: */*
IE: Accept: */*
ChD: Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
This might be it, but it seems unlikely. The two that work send more than just a wildcard Accept header.
User-Agent
Obviously each browser sends along different User-Agents. Except ChD sends the same as Ch - and the former works, so that isn't it.
Referer
Firefox sends no Referer along. The others send:
Referer: http://activetuts.s3.amazonaws.com/freebies/006_textToSpeech/tutorial/text2speech.swf
ChD obviously sends no Referer either, since I typed in the address manually. So the Referer header might be the problem.
Considering that TTS isn't a public API, but a private endpoint (for Google's own translate service), i.e. an endpoint which you're really not allowed to use, that wouldn't be surprising.
Other
Other than that, and some language acceptance details (+ cookie contents - the same set of cookies are sent - and on my machine, it's actually their own cookies for once - Flash used to have a problem where it sent cookies from IE in the Firefox plugin)... Other than that, the requests are identical, but only Firefox's doesn't result in a 404 on my machine.
FlashPlayer versions
FF: 11.0.1.152 Debug
Ch: 11.1.102.55
IE: 11.0.1.152 Debug
Update: IE also sends the Flash version along: x-flash-version: 11,0,1,152 - but none of the other browsers do, so that's not why they don't work.

Websockets Issue, Perhaps Same Origin Policy?

I have a site hosted at localhost:8000. Now, I have a server listening for websocket connections at localhost:8001. I would like my website to connect to this server through the websocket api like
var conn = new WebSocket('ws://localhost:8001');
But I get some errors in Chromium 6.0.472.62 upon calling
conn.send('something');
That looks like: Uncaught Error: INVALID_STATE_ERR: DOM Exception 11.
In Firefox 4 (4.0b8pre), I get the error:
An attempt was made to use an object that is not, or is no longer, usable" code: "11
I thought this was an issue with the handshake not supporting websocket draft76 on the server, but I am using http://github.com/miksago/node-websocket-server/tree/master/lib/ws/ which claims to support draft75 and draft76.
Also, the initial handshake seems to work fine. I can receive a response from the server upon creating the new WebSocket, however, the problems arise on the call to "send" from the client side.
Is this an issue with the same origin policy since my httpserver is on port 8000 and the websocket server is on 8001? If so, how can I work around this?
Perhaps you need to wait for the onopen event to fire?
var conn = new WebSocket('ws://localhost:8001');
conn.onopen = function (e) {
conn.send('something');
}
conn.onmessage = function (e) {
console.log('got something: ' + e.data);
}
Also, it's a good idea to hook the onclose and onerror events too.