JVMä¸ã® WebSocket ãµã¼ãã¼ããã°ã©ã - Jetty, Grizzly, Netty, EM-WebSocket ã試ã
WebSocket ã®ç°¡åãªãµã¼ãã¼ããã°ã©ã ã Jetty, Grizzly, Netty, EM-WebSocket ããããã使ã£ã¦ãGroovy ã JRuby ã§å®è£ ãã¦ã¿ã¾ããã
WebSocket ã®ãããã³ã«ä»æ§ã¯ç¢ºå®ãã¦ããããäºææ§ã®ç¡ãæ¹è¨ãè¡ããã¦ãããããªã®ã§ãä»å㯠draft-ietf-hybi-thewebsocketprotocol-00 ããµãã¼ããã Google Chrome 12.0.742.100 ã® WebSocket ã¯ã©ã¤ã¢ã³ãã¨æ¥ç¶å¯è½ãªãµã¼ãã¼ããã°ã©ã ãä½æããäºã«ãã¾ãã
å®éã«ãdraft-ietf-hybi-thewebsocketprotocol-00 ã§ä½¿ã Sec-WebSocket-Key1 㨠Sec-WebSocket-Key2 ã¯ãdraft-ietf-hybi-thewebsocketprotocol-06 ã§ä½¿ããªããªã£ã¦ãããããçããµã¼ãã¼ã»ã¯ã©ã¤ã¢ã³ãã§ãµãã¼ããã¦ãããããã³ã«ä»æ§ã«æ³¨æããå¿ è¦ãããã¾ãããï¼ç¾æç¹ã§ã®ææ°ä»æ§ã¯ draft-ietf-hybi-thewebsocketprotocol-09 ã®æ¨¡æ§ï¼
使ç¨ããç°å¢ã¯ä»¥ä¸ã®éãã§ãã
- ã¯ã©ã¤ã¢ã³ã
- Google Chrome 12.0.742.100
- ãµã¼ãã¼
ã¡ãªã¿ã«ãEM-WebSocket ã使ãã®ãæãç°¡å㧠Netty ã使ãã®ãæãé¢åã§ããã
ã¾ããGrizzly 2.1 以é㯠draft-ietf-hybi-thewebsocketprotocol-06 ã«å¯¾å¿ãã¦ãããã®ã®ãdraft-ietf-hybi-thewebsocketprotocol-00 ã«å¯¾å¿ãã¦ããªããããä»åã®ç¨éã§ã¯ä½¿ç¨ã§ãã¾ããã§ããã
ãµã³ãã«ã½ã¼ã¹ã¯ http://github.com/fits/try_samples/tree/master/blog/20110618/
WebSocket ã¯ã©ã¤ã¢ã³ã
ã¾ã㯠Google Chrome ä¸ã§å®è¡ãã WebSocket ã¯ã©ã¤ã¢ã³ãã§ãã
ãã¼ã«ã«ãã¡ã¤ã«ã Chrome ä¸ã§å®è¡ãã¦ãµã¼ãã¼ããã°ã©ã ã®åä½ç¢ºèªã«ä½¿ãã¾ãã
index.html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <script> var ws = new WebSocket("ws://localhost:8080/"); ws.onopen = function(event) { console.log("websocket open"); stateChange("opened") }; ws.onmessage = function(event) { document.getElementById("log").innerHTML += "<li>" + event.data + "</li>"; }; ws.onclose = function(event) { console.log("websocket close"); stateChange("closed") }; ws.onerror = function(event) { console.log("error"); stateChange("error") }; function sendMessage() { var msg = document.getElementById("message").value; ws.send(msg); } function stateChange(state) { document.getElementById("state").innerHTML = state; } </script> </head> <body> <input id="message" type="text" /> <input type="button" value="send" onclick="sendMessage()" /> <span id="state">closed</span> <ul id="log"></ul> </body> </html>
Jetty ã«ãã WebSocket ãµã¼ãã¼
ã¯ã©ã¤ã¢ã³ããéä¿¡ãã¦ããæååã®å é ã« "echo : " ã¨ããæååãå ãã¦è¿ãã ãã®åç´ãªå¦çãå®è£ ãã¾ãã
jetty_groovy/echo_server.groovy
import javax.servlet.http.HttpServletRequest import org.eclipse.jetty.server.Server import org.eclipse.jetty.websocket.WebSocket import org.eclipse.jetty.websocket.WebSocket.Connection import org.eclipse.jetty.websocket.WebSocketHandler class EchoWebSocket implements WebSocket.OnTextMessage { def outbound void onOpen(Connection outbound) { println("onopen : ${this}") this.outbound = outbound } void onMessage(String data) { println("onmessage : ${this} - ${data}") this.outbound.sendMessage("echo: ${data}") } void onClose(int closeCode, String message) { println("onclose : ${this} - ${closeCode}, ${message}") } } def server = new Server(8080) //WebSocketç¨ã® Handler ãè¨å® server.handler = new WebSocketHandler() { WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) { println("websocket connect : ${protocol} - ${request}") new EchoWebSocket() } } server.start() server.join()
ã¦ã¼ã¶ã¼ãã¼ã ãã£ã¬ã¯ããªã® .groovy/lib ãã£ã¬ã¯ããªã« Jetty ã® lib ãã£ã¬ã¯ããªå ã® JAR ãã¡ã¤ã«ãé ç½®ãã¦ããå®è¡ãã¾ããï¼Groovy ã® conf/groovy-starter.conf ãç·¨éãã¦ãå¯ï¼
å®è¡ä¾
> groovy echo_server.groovy
Grizzly ã«ãã WebSocket ãµã¼ãã¼
Grizzly 2.1 㯠draft-ietf-hybi-thewebsocketprotocol-00 ã«å¯¾å¿ãã¦ããªããããGrizzly 2.0.1 ã使ãå¿ è¦ãããã¾ãã
å®è£ å 容㯠Jetty çã¨åããããªæãã§ãã
grizzly_groovy/echo_server.groovy
import org.glassfish.grizzly.http.server.* import org.glassfish.grizzly.http.HttpRequestPacket import org.glassfish.grizzly.websockets.* import org.glassfish.grizzly.websockets.frame.* class EchoWebSocketApplication extends WebSocketApplication { boolean isApplicationRequest(HttpRequestPacket req) { println("${req}") true } void onConnect(WebSocket websocket) { println("onConnect : ${websocket}") super.onConnect(websocket) } void onMessage(WebSocket websocket, Frame data) { println("onMessage : ${data}") websocket.send(Frame.createTextFrame("echo : ${data.asText}")) } void onClose(WebSocket websocket) { println("onClose : ${websocket}") super.onClose(websocket) } } def server = HttpServer.createSimpleServer() server.getListener("grizzly").registerAddOn(new WebSocketAddOn()) WebSocketEngine.engine.registerApplication("/", new EchoWebSocketApplication()) server.start() System.in.read() server.stop()
ã¦ã¼ã¶ã¼ãã¼ã ãã£ã¬ã¯ããªã® .groovy/lib ãã£ã¬ã¯ããªã«ä»¥ä¸ã® JAR ãã¡ã¤ã«ãé ç½®ãã¦ããå®è¡ãã¾ããï¼Groovy ã® conf/groovy-starter.conf ãç·¨éãã¦ãå¯ï¼
å®è¡ä¾
> groovy echo_server.groovy
Netty ã«ãã WebSocket ãµã¼ãã¼
Netty ã§å®è£
ããå ´åãJetty ã Grizzly ã¨ã¯ç°ãªãããã³ãã·ã§ã¤ã¯å¦çãèªåã§å®è£
ããäºã«ãªãã¾ãã
ã¾ã HTTP ã§ãã³ãã·ã§ã¤ã¯ãå¦çããå¾ã« WebSocket ç¨ã«ãã¤ãã©ã¤ã³ã®æ§æãå¤æ´ãã¾ãã
netty_groovy/echo_server.groovy
import java.net.InetSocketAddress import java.security.MessageDigest import java.util.concurrent.Executors import static org.jboss.netty.handler.codec.http.HttpHeaders.* import org.jboss.netty.bootstrap.ServerBootstrap import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory import org.jboss.netty.buffer.ChannelBuffers import org.jboss.netty.channel.* import org.jboss.netty.channel.Channels import org.jboss.netty.handler.codec.http.* import org.jboss.netty.handler.codec.http.websocket.* class ChatServerHandler extends SimpleChannelUpstreamHandler { //ã¡ãã»ã¼ã¸åä¿¡å¦ç public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) { def msg = e.message println("message received : ${msg}") handleRequest(ctx, msg) } //WebSocket draft-ietf-hybi-thewebsocketprotocol-00 ç¨ã® //ãã³ãã·ã§ã¤ã¯å¦çï¼HTTP ãªã¯ã¨ã¹ãã®å¦çï¼ def handleRequest(ChannelHandlerContext ctx, HttpRequest req) { //ãã³ãã·ã§ã¤ã¯ã®ã¬ã¹ãã³ã¹ä½æ def res = new DefaultHttpResponse(HttpVersion.HTTP_1_1, new HttpResponseStatus(101, "Web Socket Protocol Handshake")) res.addHeader(Names.UPGRADE, Values.WEBSOCKET) res.addHeader(Names.CONNECTION, Values.UPGRADE) res.addHeader(Names.SEC_WEBSOCKET_ORIGIN, req.getHeader(Names.ORIGIN)) res.addHeader(Names.SEC_WEBSOCKET_LOCATION, "ws://localhost:8080/") def key1 = req.getHeader(Names.SEC_WEBSOCKET_KEY1) def key2 = req.getHeader(Names.SEC_WEBSOCKET_KEY2) //ãã¼å ã®æ°å¤ã®ã¿ãåãåºãæ°å¤åãããã®ããã¼å ã®ç©ºç½æ°ã§å²ã int key1res = (int)Long.parseLong(key1.replaceAll("[^0-9]", "")) / key1.replaceAll("[^ ]", "").length() int key2res = (int)Long.parseLong(key2.replaceAll("[^0-9]", "")) / key2.replaceAll("[^ ]", "").length() long content = req.content.readLong() def input = ChannelBuffers.buffer(16) input.writeInt(key1res) input.writeInt(key2res) input.writeLong(content) res.content = ChannelBuffers.wrappedBuffer(MessageDigest.getInstance("MD5").digest(input.array)) //æ¥ç¶ãã¢ããã°ã¬ã¼ã //ï¼WebSocket ç¨ã« decoder 㨠encoder ãå¤æ´ããï¼ def pipeline = ctx.channel.pipeline pipeline.replace("decoder", "wsdecoder", new WebSocketFrameDecoder()) //ã¬ã¹ãã³ã¹éä¿¡ ctx.channel.write(res) //encoder ã¯ã¬ã¹ãã³ã¹éä¿¡ã«ä½¿ç¨ããããéä¿¡å¾ã« WebSocket ç¨ã«å¤æ´ pipeline.replace("encoder", "wsencoder", new WebSocketFrameEncoder()) } //WebSocket å¦ç def handleRequest(ChannelHandlerContext ctx, WebSocketFrame msg) { ctx.channel.write(new DefaultWebSocketFrame("echo : ${msg.textData}")) } } def server = new ServerBootstrap(new NioServerSocketChannelFactory( Executors.newCachedThreadPool(), Executors.newCachedThreadPool() )) //WebSocket ã使ãã«ã¯ãã¾ã HTTP ã§å¦çããå¿ è¦ããããã //HTTP ç¨ã® decoder 㨠encoder ã®æ§æãç¨æãã server.setPipelineFactory({ def pipeline = Channels.pipeline() pipeline.addLast("decoder", new HttpRequestDecoder()) pipeline.addLast("encoder", new HttpResponseEncoder()) pipeline.addLast("handler", new ChatServerHandler()) pipeline } as ChannelPipelineFactory) server.bind(new InetSocketAddress(8080))
ã¦ã¼ã¶ã¼ãã¼ã ãã£ã¬ã¯ããªã® .groovy/lib ãã£ã¬ã¯ããªã«ä»¥ä¸ã® JAR ãã¡ã¤ã«ãé ç½®ãã¦ããå®è¡ãã¾ããï¼Groovy ã® conf/groovy-starter.conf ãç·¨éãã¦ãå¯ï¼
- netty-3.2.4.Final.jar
å®è¡ä¾
> groovy echo_server.groovy
EM-WebSocket ã«ãã WebSocket ãµã¼ãã¼
ã¾ããEM-WebSocket ãã¤ã³ã¹ãã¼ã«ãã¦ããã¾ãã
EM-WebSocket ã¤ã³ã¹ãã¼ã«
> gem install em-websocket
EM-WebSocket ã使ã£ã WebSocket ãµã¼ãã¼ã¯ä»¥ä¸ã®ããã«ãªãã¾ããããã¾ã§ã®ãµã³ãã«ã«æ¯ã¹ãã¨é常ã«ç°¡åã«ãªã£ã¦ãã¾ãã
em-websocket_jruby/echo_server.rb
require 'rubygems' require 'em-websocket' EventMachine::WebSocket.start(:host => "localhost", :port => 8080, :debug => true) do |ws| ws.onopen {puts "onopen"} ws.onmessage {|msg| ws.send "echo : #{msg}"} ws.onclose {puts "onclose"} end
å®è¡ä¾
> jruby echo_server.rb