/* * Copyright (C) 2010 Copyright 2010 Google Inc. * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. * * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this program; if not, write to the Free Software Foundation, Inc., 59 Temple * Place - Suite 330, Boston, MA 02111-1307, USA. */ package com.googlecode.gwtquake.server; import java.io.IOException; import java.net.InetAddress; import java.util.HashMap; import java.util.LinkedList; import java.util.Map; import javax.servlet.http.HttpServletRequest; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.websocket.WebSocket; import org.eclipse.jetty.websocket.WebSocketHandler; import com.googlecode.gwtquake.shared.common.Compatibility; import com.googlecode.gwtquake.shared.common.NetworkAddress; import com.googlecode.gwtquake.shared.sys.QuakeSocket; import com.googlecode.gwtquake.shared.sys.QuakeSocketFactory; public class ServerWebSocketFactoryImpl implements QuakeSocketFactory { public QuakeSocket bind(String ip, int port) { return new ServerWebSocketImpl(port); } } class ServerWebSocketImpl implements QuakeSocket { private Map<String, MyWebSocket> sockets = new HashMap<String, MyWebSocket>(); private Server server; private final int port; private static LinkedList<Msg> msgQueue = new LinkedList<Msg>(); private static class Msg { public byte[] fromIp; public int fromPort; public String data; public Msg(byte[] fromIp, int fromPort, String data) { this.fromIp = fromIp; this.fromPort = fromPort; this.data = data; } } public ServerWebSocketImpl(int port) { System.out.println("ServerWebSocketImpl(" + port + ")"); this.port = port; server = new Server(port); WebSocketHandler handler = new WebSocketHandler() { @Override protected WebSocket doWebSocketConnect(HttpServletRequest req, String protocol) { String addr = req.getRemoteAddr(); System.out.println("incoming connection from " + addr + "; protocol: " + protocol); // loopback is ipv6 on OSX if (addr.equals("0:0:0:0:0:0:0:1%0")) { addr = "0.0.0.0"; } String from = addr + ":" + protocol; System.err.println("Connect from " + from); MyWebSocket socket = sockets.get(from); // Kind of a hack: Use the protocol to pass the client-side qport. // This allows us to maintain a stable logical connection over // multiple "real" connections. int qport = (protocol != null) ? Integer.parseInt(protocol) : 27901; if (socket == null) { try { socket = new MyWebSocket(InetAddress.getByName(addr).getAddress(), qport); } catch (Exception e) { e.printStackTrace(); } sockets.put(from, socket); } return socket; } }; handler.setBufferSize(65536); server.setHandler(handler); System.out.println("Starting Server"); try { server.start(); } catch (Exception e) { throw new RuntimeException(e); } System.out.println("Server started"); } public void close() { server.destroy(); server = null; sockets = null; } public int receive(NetworkAddress fromAdr, byte[] buf) throws IOException { synchronized (msgQueue) { if (msgQueue.isEmpty()) { return -1; } Msg msg = msgQueue.removeFirst(); String data = msg.data; fromAdr.ip = new byte[4]; System.arraycopy(msg.fromIp, 0, fromAdr.ip, 0, 4); fromAdr.port = msg.fromPort; int len = Compatibility.stringToBytes(data, buf); // System.out.println("receiving " + Lib.hexDump(buf, len, true)); return len; } } public void send(NetworkAddress dstSocket, byte[] data, int len) throws IOException { String targetAddress = InetAddress.getByAddress(dstSocket.ip).getHostAddress() + ":" + dstSocket.port; MyWebSocket target = sockets.get(targetAddress); if (target == null) { System.out.println("Trying to send message to " + dstSocket.toString() + "; address not found. Available addresses: " + sockets.keySet()); return; } // System.out.println("sending to " + targetAddress + ": " + // Lib.hexDump(data, len, true)); target.sendMessage(Compatibility.bytesToString(data, len)); } class MyWebSocket implements WebSocket { private LinkedList<String> outQueue = new LinkedList<String>(); private Outbound outbound; byte[] fromIp; int fromPort; public MyWebSocket(byte[] fromIp, int fromPort) { this.fromIp = fromIp; this.fromPort = fromPort; } public void onConnect(Outbound outbound) { this.outbound = outbound; if (!outQueue.isEmpty()) { for (String msg : outQueue) { sendMessage(msg); } outQueue.clear(); } System.out.println("onConnect"); } public void onDisconnect() { System.out.println("onDisconnect"); } // If you know this: Please add a comment about the required Jetty version at the top // @Override public void onFragment(boolean arg0, byte arg1, byte[] arg2, int arg3, int arg4) { assert false : "Why is this method separate from the other onMessage()?"; } public void onMessage(byte frame, String data) { synchronized (msgQueue) { msgQueue.add(new Msg(fromIp, fromPort, data)); } } public void onMessage(byte frame, byte[] data, int offset, int length) { assert false : "Why is this method separate from the other onMessage()?"; } public void sendMessage(String msg) { if (outbound == null) { outQueue.add(msg); return; } try { outbound.sendMessage((byte) 0, msg); } catch (IOException e) { System.out.println("sendMessage failed (" + fromIp + ":" + fromPort + "): " + e.getMessage()); outQueue.add(msg); outbound = null; } } } public void Shutdown() { try { server.stop(); } catch (Exception e) { } } }