PDA

View Full Version : WebSocket demo working for anyone?


bhs
09-23-2010, 02:58 PM
I posted about this a couple days ago but I've not seen it show up.

In any event, I am curious if the Resin 4 WebSocket demo is working for anyone. I've tried it on Firefox 4 Beta 6 and the latest Chrome dev build. Neither show any send or receive activity. Watching the WebSocket state in the browser's debugger, the state transitions directly from CONNECTING (0) to CLOSING (2). It's never OPEN.

I've tried the demo as hosted here:

http://www.caucho.com/resin-4.0/examples/websocket-java/websocket.php

... and also running locally. When running locally, the Resin log reports:

[10-09-22 11:14:47.592] {resin-WebSocketContextImpl-read-2} WebSocket unexpected initial byte: 0x14
[10-09-22 11:14:47.593] {resin-WebSocketContextImpl-read-2} WebSocket unexpected initial byte: 0x16
... several more ...

I get the feeling that Resin thinks the socket is OPEN but the browser doesn't agree and these unexpected bytes are residue of the negotiation process.

Does the demo work for anyone? Thanks!

Littlejenny
09-24-2010, 02:09 AM
may be you should try it on IE :-? i think the demo ver is worked for everyone :-?

bhs
09-24-2010, 02:35 PM
Could you confirm that it's working for you? Visit the URL at the Caucho site above.

I've tried it in Firefox 4 Beta 6 and the Chrome dev version, both being browsers that support the WebSocket API. IE doesn't support WebSockets, to my understanding, so I wouldn't actually expect the demo to work in IE at all.

I've asked friends and colleagues to test the WebSocket demo cited above in WebSocket-capable browsers and they have confirmed that it doesn't work for them either.

To recap: I can't get Resin 4's WebSocket functionality to work in any context, and I'm citing the demo hosted at caucho.com as an example. Since you say it "worked for everyone," could you please confirm that it is still working for you?

Thanks!

bhs
09-27-2010, 03:10 PM
Quickly skimming the source code, the bit that struck me was WebSocketOutputStream sending an 0x80 and then two bytes that encode the length of the "chunk." As far as I can tell, this code was written in roughly December 2009. Considering the WebSocket API is still--here in nearly October 2010--an Editor's Draft, I am not surprised that it may have changed since then.

Perhaps the WebSocket code in Resin just needs to be refreshed to match the latest draft?

emil
09-27-2010, 04:44 PM
Hi bhs,

Yes, the code currently in Resin from an earlier draft of the WebSockets protocol. The protocol is under constant revision at this time, so we'll be waiting until it reaches a more stable point before updating.

Thanks,
Emil

bhs
09-27-2010, 05:01 PM
Thanks! Looking forward to it. Hopefully the process wraps up soon.

bhs
11-03-2010, 09:21 PM
Silly spammer. I was excited to see my thread about WebSockets see some activity. Disappointed it was just some spam.

ferg
11-22-2010, 09:52 PM
We'll be updating the websockets with 4.0.14.

Since the IETF specification is still stuck, we're focusing that release on non-browser applications, specifically android-style clients.

jon.hulka
01-11-2011, 12:01 AM
This should do the trick. I borrowed some code from modules/resin/src/com/caucho/server/http/HttpServletRequestImpl.java and modified it a bit.

This article (http://blog.new-bamboo.co.uk/2010/6/7/living-on-the-edge-of-the-websocket-protocol) helped a lot.

I haven't implemented the closing handshake for websocket v.76 yet.

for ControlServlet:

public void service(ServletRequest req, ServletResponse res)throws IOException, ServletException
{
HttpServletRequestImpl wsReq = (HttpServletRequestImpl) req;
new WebSocketUpgrade(wsReq,new MyHandler()).start();
}


WebSocketUpgrade.java:

import com.caucho.servlet.WebSocketContext;
import com.caucho.servlet.WebSocketListener;

import java.io.IOException;
import com.caucho.server.http.HttpServletRequestImpl;
import com.caucho.server.http.AbstractHttpRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.caucho.util.L10N;
import com.caucho.network.listen.SocketLinkDuplexControll er;
import com.caucho.network.listen.SocketLinkDuplexListener ;
import com.caucho.server.http.HttpServletResponseImpl;
import java.io.InputStream;
import java.io.OutputStream;
import com.caucho.network.listen.SocketLinkDuplexListener ;


import java.security.NoSuchAlgorithmException;
import java.security.MessageDigest;
import java.util.Enumeration;
import java.io.BufferedReader;
import java.io.InputStreamReader;

public class WebSocketUpgrade implements WebSocketContext, SocketLinkDuplexListener
{
private AbstractHttpRequest request;
private WebSocketListener listener;
private HttpServletResponseImpl response=null;
private String origin;
private String contextPath;
private String servletPath;
private SocketLinkDuplexController controller;
//Specification version (75 or 76)
private int wsVersion;
//Flag to be set when validation is complete
private boolean validated=false;
private String key1,key2,protocol;
private byte [] key3=new byte[8];

private static final Logger log
= Logger.getLogger(ControlServlet.class.getName());
private static final L10N L = new L10N(WebSocketUpgrade.class);

WebSocketUpgrade(HttpServletRequestImpl request,
WebSocketListener listener)
{
this.request=request.getAbstractHttpRequest();
this.response=request.getResponse();
this.contextPath=request.getContextPath();
this.servletPath=request.getServletPath();
this.listener = listener;
}

public void start()
{
if (log.isLoggable(Level.FINE))
log.fine(this + " upgrade HTTP to WebSocket " + listener);
String connection = request.getHeader("Connection");
String upgrade = request.getHeader("Upgrade");
if (! "WebSocket".equals(upgrade)) {
throw new IllegalStateException(L.l("HTTP Upgrade header '{0}' must be 'WebSocket', because the WebSocket protocol requires an Upgrade: WebSocket header.",
upgrade));
}

if (! "Upgrade".equalsIgnoreCase(connection)) {
throw new IllegalStateException(L.l("HTTP Connection header '{0}' must be 'Upgrade', because the WebSocket protocol requires a Connection: Upgrade header.",
connection));
}

origin = request.getHeader("Origin");

if (origin == null) {
throw new IllegalStateException(L.l("HTTP Origin header is required, because the WebSocket protocol requires an Origin header."));
}

//Check for version 76
key1=request.getHeader("Sec-WebSocket-Key1");
wsVersion=(key1==null?75:76);
protocol=null;

switch(wsVersion)
{
case 75:
protocol = request.getHeader("WebSocket-Protocol");
break;
case 76:
protocol = request.getHeader("Sec-WebSocket-Protocol");
key2=request.getHeader("Sec-Websocket-Key2");
break;
}

//v.76:"WebSocket", v.75:"Web Socket"
response.setStatus(101, wsVersion==76?"WebSocket Protocol Handshake":"Web Socket Protocol Handshake");
response.setHeader("Upgrade", "WebSocket");

response.setContentLength(0);

StringBuilder sb = new StringBuilder();
if (request.isSecure())
sb.append("wss://");
else
sb.append("ws://");
sb.append(request.getServerName());
if (! request.isSecure() &;&; request.getServerPort() != 80 || request.isSecure() &;&; request.getServerPort() != 443) {
sb.append(":");
sb.append(request.getServerPort());
}

sb.append(contextPath);
if (servletPath != null)
sb.append(servletPath);

String url = sb.toString();

//v.76:"Sec-" prefix
String pfx=wsVersion==76?"Sec-":"";
response.setHeader(pfx+"WebSocket-Location", url);
response.setHeader(pfx+"WebSocket-Origin", origin.toLowerCase());
if (protocol != null) response.setHeader(pfx+"WebSocket-Protocol", protocol);

try
{
setController(request.startDuplex(this));
response.getOutputStream().flush();
onStart();
}
catch (IOException e) {throw new RuntimeException(e);}

//v.75: nothing more to do
validated=wsVersion==75;
}

//For v.76, builds and sends the required response
private void doResponseBody()
{
if(wsVersion==76)
{
try
{
//Build the challenge sequence
byte [] challenge=new byte[16];
parseKey(key1,challenge,0);
parseKey(key2,challenge,4);
for(int i=0;i<8;i++)challenge[i+8]=key3[i];
//Send the response
OutputStream os=response.getOutputStream();
os.write(MessageDigest.getInstance("MD5").digest(challenge));
os.flush();
validated=true;
}
catch (IOException e)
{
log.info(e.toString());
throw new RuntimeException(e);
}
catch(NoSuchAlgorithmException e)
{
log.info(e.toString());
throw new RuntimeException(e);
}
catch(Exception e){log.info(e.toString());}
}
}

private void parseKey(String key,byte[] result,int offset)
{
StringBuilder sb=new StringBuilder();
long spaces=0,value=0,len=key.length();
//Extract digits and count spaces
for(int i=0; i<len; i++)
{
char c=key.charAt(i);
if(Character.getType(c)==Character.DECIMAL_DIGIT_N UMBER) {sb.append(c);}
else if(c==' ')spaces++;
}
if(sb.length()>0)value=Long.parseLong(sb.toString());
//Verify the key
if(value%spaces!=0) throw new IllegalStateException(L.l("Invalid key: key-number must be an integral multiple of key spaces.",key));
int r=(int)(value/spaces);
//Convert to big-endian and store in result
for(int i=0; i<4; i++)
{
result[3-i+offset]=(byte)(r&;0x0ff);
r>>=8;
}
}

public void setController(SocketLinkDuplexController controller)
{
this.controller = controller;
}

public void setTimeout(long timeout)
{
controller.setIdleTimeMax(timeout);
}

public long getTimeout()
{
return controller.getIdleTimeMax();
}

public InputStream getInputStream()
throws IOException
{
return controller.getReadStream();
}

public OutputStream getOutputStream()
throws IOException
{
return controller.getWriteStream();
}

public void complete()
{
controller.complete();
}

void onStart()
throws IOException
{
listener.onStart(this);
}

public void onRead(SocketLinkDuplexController duplex)
throws IOException
{
if(validated)
{
do {
listener.onRead(this);
} while (request.getAvailable() > 0);
}
else if(wsVersion==76)
{
InputStream is=controller.getReadStream();
//Read the 3rd part of the challenge
is.read(key3,0,8);
//Just in case: this shouldn't happen.
while(request.getAvailable() >0)log.info("extra byte: " + Integer.toHexString(0x0ff&;(int)is.read()));
doResponseBody();
}
}

public void onComplete(SocketLinkDuplexController duplex)
throws IOException
{
listener.onComplete(this);
}

public void onTimeout(SocketLinkDuplexController duplex)
throws IOException
{
listener.onTimeout(this);
}

@Override
public String toString()
{
return getClass().getSimpleName() + "[" + listener + "]";
}

/* (non-Javadoc)
* @see com.caucho.network.listen.SocketLinkDuplexListener #onStart(com.caucho.network.listen.SocketLinkDuple xController)
*/
@Override
public void onStart(SocketLinkDuplexController context) throws IOException
{
}
}