package javaforce.service; /** * Mini web service * * Created : Aug 23, 2013 */ import java.io.*; import java.net.*; import java.security.*; import javax.net.ssl.*; import javaforce.*; public class Web { private WebHandler api; private WebSocketHandler wsapi; private ServerSocket ss; private boolean active = true; public static boolean config_enable_gzip = true; public boolean start(WebHandler api, int port, boolean secure) { this.api = api; try { if (secure) { ss = SSLServerSocketFactory.getDefault().createServerSocket(port); } else { ss = new ServerSocket(port); } new Server().start(); } catch (Exception e) { JFLog.log(e); return false; } return true; } public void setWebSocketHandler(WebSocketHandler wsapi) { this.wsapi = wsapi; } public void stop() { active = false; try { ss.close(); } catch (Exception e) { } } private class Server extends Thread { public void run() { Socket s; while (active) { try { s = ss.accept(); new Connection(s).start(); } catch (Exception e) { JFLog.log(e); } } } } private class Connection extends Thread { private Socket s; private InputStream is; public void run() { //read request and pass to WebHandler try { StringBuilder request = new StringBuilder(); is = s.getInputStream(); while (s.isConnected()) { int ch = is.read(); if (ch == -1) break; request.append((char)ch); if (request.toString().endsWith("\r\n\r\n")) { WebRequest req = new WebRequest(); req.request = request.toString(); req.fields = req.request.split("\r\n"); req.is = is; req.serverIP = s.getLocalAddress().getHostAddress(); if (req.serverIP.equals("0:0:0:0:0:0:0:1")) req.serverIP = "127.0.0.1"; req.serverPort = s.getLocalPort(); req.remoteIP = s.getInetAddress().getHostAddress(); req.remotePort = s.getPort(); WebResponse res = new WebResponse(); res.os = s.getOutputStream(); req.fields0 = req.fields[0].split(" "); req.init(res); if (isWebSocketRequest(req)) { WebSocket socket = new WebSocket(); socket.is = is; socket.os = res.os; socket.url = req.fields0[1]; if (wsapi != null && wsapi.doWebSocketConnect(socket)) { sendWebSocketAccepted(req, res); processWebSocket(socket); } else { sendWebSocketDenied(req, res); } break; } else if (req.fields0[0].equals("GET")) { req.method = "GET"; api.doGet(req, res); } else if (req.fields0[0].equals("POST")) { req.method = "POST"; api.doPost(req, res); } else { res.setStatus(501, "Error - Unsupported Method"); } res.writeAll(req); if (req.fields0[2].equals("HTTP/1.0")) break; request.setLength(0); } } s.close(); } catch (Exception e) { } } public Connection(Socket s) { this.s = s; } private boolean isWebSocketRequest(WebRequest req) { boolean upgrade = false; boolean websocket = false; boolean haveKey = false; for(int a=0;a<req.fields.length;a++) { String field = req.fields[a]; if (field.startsWith("Connection:") && field.indexOf("Upgrade") != -1) upgrade = true; if (field.startsWith("Upgrade:") && field.indexOf("websocket") != -1) websocket = true; if (field.startsWith("Sec-WebSocket-Key:")) haveKey = true; } return upgrade && websocket && haveKey; } private String encodeKey(String inKey) { //input: Sec-WebSocket-Key: inkey //output: Sec-WebSocket-Accept: outkey // outkey = base64(SHA1(inkey + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')) inKey += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; MessageDigest md = null; try { md = MessageDigest.getInstance("SHA-1"); } catch(NoSuchAlgorithmException e) { JFLog.log(e); } byte sha1[] = md.digest(inKey.getBytes()); String base64 = new String(Base64.encode(sha1)); return base64; } private void sendWebSocketAccepted(WebRequest req, WebResponse res) { res.setStatus(101, "Switching Protocols"); res.addHeader("Upgrade: websocket"); res.addHeader("Connection: Upgrade"); String inKey = null; String protocols[] = null; for(int a=0;a<req.fields.length;a++) { String field = req.fields[a]; if (field.startsWith("Sec-WebSocket-Key:")) { inKey = field.substring(18).trim(); } if (field.startsWith("Sec-WebSocket-Protcol:")) { protocols = field.substring(22).trim().split(","); } } String outKey = encodeKey(inKey); res.addHeader("Sec-WebSocket-Accept: " + outKey); try { res.writeAll(req); } catch (Exception e) { JFLog.log(e); } } private void sendWebSocketDenied(WebRequest req, WebResponse res) { res.setStatus(403, "Access Denied"); try { res.writeAll(req); } catch (Exception e) { JFLog.log(e); } } private void processWebSocket(WebSocket socket) { //keep reading packets and deliver to WebHandler byte maskKey[] = new byte[4]; try { while (true) { int opcode = socket.is.read(); if (opcode == -1) throw new Exception("socket error"); boolean fin = (opcode & 0x80) == 0x80; opcode &= 0xf; if (opcode == WebSocket.TYPE_CLOSE) break; //closed long length = 0; int len7 = socket.is.read(); if (len7 == -1) throw new Exception("socket error"); boolean hasMask = (len7 & WebSocket.MASK) == WebSocket.MASK; len7 &= 0x7f; switch (len7) { case 126: //16bits = payload for(int a=0;a<2;a++) { int len8 = socket.is.read(); if (len8 == -1) throw new Exception("socket error"); length <<= 8; length |= len8; } break; case 127: //64bits = payload for(int a=0;a<8;a++) { long len8 = socket.is.read(); if (len8 == -1) throw new Exception("socket error"); length <<= 8; length |= len8; } break; default: length = len7; } if (hasMask) { for(int a=0;a<4;a++) { int mask8 = socket.is.read(); if (mask8 == -1) throw new Exception("socket error"); maskKey[a] = (byte)mask8; } } else { throw new Exception("WebSocket message without mask"); } if (length > 16777216) { throw new Exception("WebSocket message > 16MB"); } //now read data byte data[] = JF.readAll(socket.is, (int)length); //unmask data for(int a=0;a<length;a++) { data[a] ^= maskKey[a % 4]; } if (opcode == WebSocket.TYPE_PING) { //ping message socket.write(data, WebSocket.TYPE_PONG); continue; } if (opcode > 0x8) continue; //other control message wsapi.doWebSocketMessage(socket, data, opcode); } } catch (Exception e) { JFLog.log(e); } wsapi.doWebSocketClosed(socket); } } }