1 /* 2 * Copyright (C) 2016 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17 package com.googlecode.android_scripting; 18 19 import com.google.common.collect.Lists; 20 21 import org.json.JSONException; 22 import org.json.JSONObject; 23 24 import java.io.BufferedReader; 25 import java.io.IOException; 26 import java.io.InputStreamReader; 27 import java.io.PrintWriter; 28 import java.net.BindException; 29 import java.net.Inet4Address; 30 import java.net.InetAddress; 31 import java.net.InetSocketAddress; 32 import java.net.NetworkInterface; 33 import java.net.ServerSocket; 34 import java.net.Socket; 35 import java.net.SocketException; 36 import java.net.UnknownHostException; 37 import java.util.Collections; 38 import java.util.Enumeration; 39 import java.util.List; 40 import java.util.concurrent.ConcurrentHashMap; 41 //import com.googlecode.android_scripting.jsonrpc.RpcReceiverManager; 42 43 /** 44 * A simple server. 45 * @author Damon Kohler (damonkohler@gmail.com) 46 */ 47 public abstract class SimpleServer { 48 private static int threadIndex = 0; 49 private final ConcurrentHashMap<Integer, ConnectionThread> mConnectionThreads = 50 new ConcurrentHashMap<Integer, ConnectionThread>(); 51 private final List<SimpleServerObserver> mObservers = Lists.newArrayList(); 52 private volatile boolean mStopServer = false; 53 private ServerSocket mServer; 54 private Thread mServerThread; 55 56 public interface SimpleServerObserver { onConnect()57 public void onConnect(); onDisconnect()58 public void onDisconnect(); 59 } 60 handleConnection(Socket socket)61 protected abstract void handleConnection(Socket socket) throws Exception; handleRPCConnection(Socket socket, Integer UID, BufferedReader reader, PrintWriter writer)62 protected abstract void handleRPCConnection(Socket socket, 63 Integer UID, 64 BufferedReader reader, 65 PrintWriter writer) throws Exception; 66 67 /** Adds an observer. */ addObserver(SimpleServerObserver observer)68 public void addObserver(SimpleServerObserver observer) { 69 mObservers.add(observer); 70 } 71 72 /** Removes an observer. */ removeObserver(SimpleServerObserver observer)73 public void removeObserver(SimpleServerObserver observer) { 74 mObservers.remove(observer); 75 } 76 notifyOnConnect()77 private void notifyOnConnect() { 78 for (SimpleServerObserver observer : mObservers) { 79 observer.onConnect(); 80 } 81 } 82 notifyOnDisconnect()83 private void notifyOnDisconnect() { 84 for (SimpleServerObserver observer : mObservers) { 85 observer.onDisconnect(); 86 } 87 } 88 89 private final class ConnectionThread extends Thread { 90 private final Socket mmSocket; 91 private final BufferedReader reader; 92 private final PrintWriter writer; 93 private final Integer UID; 94 private final boolean isRpc; 95 ConnectionThread(Socket socket, boolean rpc, Integer uid, BufferedReader reader, PrintWriter writer)96 private ConnectionThread(Socket socket, boolean rpc, Integer uid, BufferedReader reader, PrintWriter writer) { 97 setName("SimpleServer ConnectionThread " + getId()); 98 mmSocket = socket; 99 this.UID = uid; 100 this.reader = reader; 101 this.writer = writer; 102 this.isRpc = rpc; 103 } 104 105 @Override run()106 public void run() { 107 Log.v("Server thread " + getId() + " started."); 108 try { 109 if(isRpc) { 110 Log.d("Handling RPC connection in "+getId()); 111 handleRPCConnection(mmSocket, UID, reader, writer); 112 }else{ 113 Log.d("Handling Non-RPC connection in "+getId()); 114 handleConnection(mmSocket); 115 } 116 } catch (Exception e) { 117 if (!mStopServer) { 118 Log.e("Server error.", e); 119 } 120 } finally { 121 close(); 122 mConnectionThreads.remove(this.UID); 123 notifyOnDisconnect(); 124 Log.v("Server thread " + getId() + " stopped."); 125 } 126 } 127 close()128 private void close() { 129 if (mmSocket != null) { 130 try { 131 mmSocket.close(); 132 } catch (IOException e) { 133 Log.e(e.getMessage(), e); 134 } 135 } 136 } 137 } 138 139 /** Returns the number of active connections to this server. */ getNumberOfConnections()140 public int getNumberOfConnections() { 141 return mConnectionThreads.size(); 142 } 143 getPrivateInetAddress()144 public static InetAddress getPrivateInetAddress() throws UnknownHostException, SocketException { 145 146 InetAddress candidate = null; 147 Enumeration<NetworkInterface> nets = NetworkInterface.getNetworkInterfaces(); 148 for (NetworkInterface netint : Collections.list(nets)) { 149 if (!netint.isLoopback() || !netint.isUp()) { // Ignore if localhost or not active 150 continue; 151 } 152 Enumeration<InetAddress> addresses = netint.getInetAddresses(); 153 for (InetAddress address : Collections.list(addresses)) { 154 if (address instanceof Inet4Address) { 155 Log.d("local address " + address); 156 return address; // Prefer ipv4 157 } 158 candidate = address; // Probably an ipv6 159 } 160 } 161 if (candidate != null) { 162 return candidate; // return ipv6 address if no suitable ipv6 163 } 164 return InetAddress.getLocalHost(); // No damn matches. Give up, return local host. 165 } 166 getPublicInetAddress()167 public static InetAddress getPublicInetAddress() throws UnknownHostException, SocketException { 168 169 InetAddress candidate = null; 170 Enumeration<NetworkInterface> nets = NetworkInterface.getNetworkInterfaces(); 171 for (NetworkInterface netint : Collections.list(nets)) { 172 if (netint.isLoopback() || !netint.isUp()) { // Ignore if localhost or not active 173 continue; 174 } 175 Enumeration<InetAddress> addresses = netint.getInetAddresses(); 176 for (InetAddress address : Collections.list(addresses)) { 177 if (address instanceof Inet4Address) { 178 return address; // Prefer ipv4 179 } 180 candidate = address; // Probably an ipv6 181 } 182 } 183 if (candidate != null) { 184 return candidate; // return ipv6 address if no suitable ipv6 185 } 186 return InetAddress.getLocalHost(); // No damn matches. Give up, return local host. 187 } 188 189 /** 190 * Starts the RPC server bound to the localhost address. 191 * 192 * @param port 193 * the port to bind to or 0 to pick any unused port 194 * 195 * @return the port that the server is bound to 196 * @throws IOException 197 */ startLocal(int port)198 public InetSocketAddress startLocal(int port) { 199 InetAddress address; 200 try { 201 // address = InetAddress.getLocalHost(); 202 address = getPrivateInetAddress(); 203 mServer = new ServerSocket(port, 5, address); 204 } catch (BindException e) { 205 Log.e("Port " + port + " already in use."); 206 try { 207 address = getPrivateInetAddress(); 208 mServer = new ServerSocket(0, 5, address); 209 } catch (IOException e1) { 210 e1.printStackTrace(); 211 return null; 212 } 213 } catch (Exception e) { 214 Log.e("Failed to start server.", e); 215 return null; 216 } 217 int boundPort = start(); 218 return InetSocketAddress.createUnresolved(mServer.getInetAddress().getHostAddress(), boundPort); 219 } 220 221 /** 222 * data Starts the RPC server bound to the public facing address. 223 * 224 * @param port 225 * the port to bind to or 0 to pick any unused port 226 * 227 * @return the port that the server is bound to 228 */ startPublic(int port)229 public InetSocketAddress startPublic(int port) { 230 InetAddress address; 231 try { 232 // address = getPublicInetAddress(); 233 address = null; 234 mServer = new ServerSocket(port, 5 /* backlog */, address); 235 } catch (Exception e) { 236 Log.e("Failed to start server.", e); 237 return null; 238 } 239 int boundPort = start(); 240 return InetSocketAddress.createUnresolved(mServer.getInetAddress().getHostAddress(), boundPort); 241 } 242 243 /** 244 * data Starts the RPC server bound to all interfaces 245 * 246 * @param port 247 * the port to bind to or 0 to pick any unused port 248 * 249 * @return the port that the server is bound to 250 */ startAllInterfaces(int port)251 public InetSocketAddress startAllInterfaces(int port) { 252 try { 253 mServer = new ServerSocket(port, 5 /* backlog */); 254 } catch (Exception e) { 255 Log.e("Failed to start server.", e); 256 return null; 257 } 258 int boundPort = start(); 259 return InetSocketAddress.createUnresolved(mServer.getInetAddress().getHostAddress(), boundPort); 260 } 261 start()262 private int start() { 263 mServerThread = new Thread() { 264 @Override 265 public void run() { 266 while (!mStopServer) { 267 try { 268 Socket sock = mServer.accept(); 269 if (!mStopServer) { 270 startConnectionThread(sock); 271 } else { 272 sock.close(); 273 } 274 } catch (IOException e) { 275 if (!mStopServer) { 276 Log.e("Failed to accept connection.", e); 277 } 278 } catch (JSONException e) { 279 if (!mStopServer) { 280 Log.e("Failed to parse request.", e); 281 } 282 } 283 } 284 } 285 }; 286 mServerThread.start(); 287 Log.v("Bound to " + mServer.getInetAddress()); 288 return mServer.getLocalPort(); 289 } 290 startConnectionThread(final Socket sock)291 private void startConnectionThread(final Socket sock) throws IOException, JSONException { 292 BufferedReader reader = 293 new BufferedReader(new InputStreamReader(sock.getInputStream()), 8192); 294 PrintWriter writer = new PrintWriter(sock.getOutputStream(), true); 295 String data; 296 if((data = reader.readLine()) != null) { 297 Log.v("Received: " + data); 298 JSONObject request = new JSONObject(data); 299 if(request.has("cmd") && request.has("uid")) { 300 String cmd = request.getString("cmd"); 301 int uid = request.getInt("uid"); 302 JSONObject result = new JSONObject(); 303 if(cmd.equals("initiate")) { 304 Log.d("Initiate a new session"); 305 threadIndex += 1; 306 int mUID = threadIndex; 307 ConnectionThread networkThread = new ConnectionThread(sock,true,mUID,reader,writer); 308 mConnectionThreads.put(mUID, networkThread); 309 networkThread.start(); 310 notifyOnConnect(); 311 result.put("uid", mUID); 312 result.put("status",true); 313 result.put("error", null); 314 }else if(cmd.equals("continue")) { 315 Log.d("Continue an existing session"); 316 Log.d("keys: "+mConnectionThreads.keySet().toString()); 317 if(!mConnectionThreads.containsKey(uid)) { 318 result.put("uid", uid); 319 result.put("status",false); 320 result.put("error", "Session does not exist."); 321 }else{ 322 ConnectionThread networkThread = new ConnectionThread(sock,true,uid,reader,writer); 323 mConnectionThreads.put(uid, networkThread); 324 networkThread.start(); 325 notifyOnConnect(); 326 result.put("uid", uid); 327 result.put("status",true); 328 result.put("error", null); 329 } 330 }else { 331 result.put("uid", uid); 332 result.put("status",false); 333 result.put("error", "Unrecognized command."); 334 } 335 writer.write(result + "\n"); 336 writer.flush(); 337 Log.v("Sent: " + result); 338 }else{ 339 ConnectionThread networkThread = new ConnectionThread(sock,false,0,reader,writer); 340 mConnectionThreads.put(0, networkThread); 341 networkThread.start(); 342 notifyOnConnect(); 343 } 344 } 345 } 346 shutdown()347 public void shutdown() { 348 // Stop listening on the server socket to ensure that 349 // beyond this point there are no incoming requests. 350 mStopServer = true; 351 try { 352 mServer.close(); 353 } catch (IOException e) { 354 Log.e("Failed to close server socket.", e); 355 } 356 // Since the server is not running, the mNetworkThreads set can only 357 // shrink from this point onward. We can just stop all of the running helper 358 // threads. In the worst case, one of the running threads will already have 359 // shut down. Since this is a CopyOnWriteList, we don't have to worry about 360 // concurrency issues while iterating over the set of threads. 361 for (ConnectionThread connectionThread : mConnectionThreads.values()) { 362 connectionThread.close(); 363 } 364 for (SimpleServerObserver observer : mObservers) { 365 removeObserver(observer); 366 } 367 } 368 } 369