1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of 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, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.googlecode.android_scripting; 18 19 import com.google.common.collect.Lists; 20 21 import java.io.IOException; 22 import java.net.BindException; 23 import java.net.Inet4Address; 24 import java.net.InetAddress; 25 import java.net.InetSocketAddress; 26 import java.net.NetworkInterface; 27 import java.net.ServerSocket; 28 import java.net.Socket; 29 import java.net.SocketException; 30 import java.net.UnknownHostException; 31 import java.util.Collections; 32 import java.util.Enumeration; 33 import java.util.List; 34 import java.util.concurrent.CopyOnWriteArrayList; 35 36 /** 37 * A simple server. 38 */ 39 public abstract class SimpleServer { 40 private final CopyOnWriteArrayList<ConnectionThread> mConnectionThreads = 41 new CopyOnWriteArrayList<>(); 42 private final List<SimpleServerObserver> mObservers = Lists.newArrayList(); 43 private volatile boolean mStopServer = false; 44 private ServerSocket mServer; 45 private Thread mServerThread; 46 47 /** 48 * An interface for accessing SimpleServer events. 49 */ 50 public interface SimpleServerObserver { 51 /** The function to be called when a ConnectionThread is established.*/ onConnect()52 void onConnect(); 53 /** The function to be called when a ConnectionThread disconnects.*/ onDisconnect()54 void onDisconnect(); 55 } 56 57 /** An abstract method for handling non-RPC connections. */ handleConnection(Socket socket)58 protected abstract void handleConnection(Socket socket) throws Exception; 59 60 /** 61 * Adds an observer. 62 */ addObserver(SimpleServerObserver observer)63 public void addObserver(SimpleServerObserver observer) { 64 mObservers.add(observer); 65 } 66 67 /** 68 * Removes an observer. 69 */ removeObserver(SimpleServerObserver observer)70 public void removeObserver(SimpleServerObserver observer) { 71 mObservers.remove(observer); 72 } 73 74 /** Notifies all subscribers when a new ConnectionThread has been created. 75 * 76 * This applies to both newly instantiated sessions and continued sessions. 77 */ notifyOnConnect()78 private void notifyOnConnect() { 79 for (SimpleServerObserver observer : mObservers) { 80 observer.onConnect(); 81 } 82 } 83 84 /** Notifies all subscribers when a ConnectionThread has been terminated. */ notifyOnDisconnect()85 private void notifyOnDisconnect() { 86 for (SimpleServerObserver observer : mObservers) { 87 observer.onDisconnect(); 88 } 89 } 90 91 /** An implementation of a thread that holds data about its server connection status. */ 92 private final class ConnectionThread extends Thread { 93 /** The socket used for communication. */ 94 private final Socket mmSocket; 95 ConnectionThread(Socket socket)96 private ConnectionThread(Socket socket) { 97 setName("SimpleServer ConnectionThread " + getId()); 98 mmSocket = socket; 99 } 100 101 @Override run()102 public void run() { 103 Log.v("Server thread " + getId() + " started."); 104 try { 105 handleConnection(mmSocket); 106 } catch (Exception e) { 107 if (!mStopServer) { 108 Log.e("Server error.", e); 109 } 110 } finally { 111 close(); 112 mConnectionThreads.remove(this); 113 notifyOnDisconnect(); 114 Log.v("Server thread " + getId() + " stopped."); 115 } 116 } 117 close()118 private void close() { 119 if (mmSocket != null) { 120 try { 121 mmSocket.close(); 122 } catch (IOException e) { 123 Log.e(e.getMessage(), e); 124 } 125 } 126 } 127 } 128 129 /** 130 * Returns the number of active connections to this server. 131 */ getNumberOfConnections()132 public int getNumberOfConnections() { 133 return mConnectionThreads.size(); 134 } 135 136 /** 137 * Returns the private InetAddress 138 * @return the private InetAddress 139 * @throws UnknownHostException If unable to resolve localhost during fallback. 140 * @throws SocketException if an IOError occurs while querying the network interfaces. 141 */ getPrivateInetAddress()142 public static InetAddress getPrivateInetAddress() throws UnknownHostException, SocketException { 143 InetAddress candidate = null; 144 Enumeration<NetworkInterface> nets = NetworkInterface.getNetworkInterfaces(); 145 for (NetworkInterface netint : Collections.list(nets)) { 146 if (!netint.isLoopback() || !netint.isUp()) { // Ignore if localhost or not active 147 continue; 148 } 149 Enumeration<InetAddress> addresses = netint.getInetAddresses(); 150 for (InetAddress address : Collections.list(addresses)) { 151 if (address instanceof Inet4Address) { 152 Log.d("local address " + address); 153 return address; // Prefer ipv4 154 } 155 candidate = address; // Probably an ipv6 156 } 157 } 158 if (candidate != null) { 159 return candidate; // return ipv6 address if no suitable ipv6 160 } 161 return InetAddress.getLocalHost(); // No damn matches. Give up, return local host. 162 } 163 164 /** 165 * Returns the public InetAddress 166 * @return the private InetAddress 167 * @throws UnknownHostException If unable to resolve localhost during fallback. 168 * @throws SocketException if an IOError occurs while querying the network interfaces. 169 */ getPublicInetAddress()170 public static InetAddress getPublicInetAddress() throws UnknownHostException, SocketException { 171 InetAddress candidate = null; 172 Enumeration<NetworkInterface> nets = NetworkInterface.getNetworkInterfaces(); 173 for (NetworkInterface netint : Collections.list(nets)) { 174 // TODO(markdr): The only diff between this and above fn is the ! on the line below. 175 // Merge these two functions. 176 if (netint.isLoopback() || !netint.isUp()) { // Ignore if localhost or not active 177 continue; 178 } 179 Enumeration<InetAddress> addresses = netint.getInetAddresses(); 180 for (InetAddress address : Collections.list(addresses)) { 181 if (address instanceof Inet4Address) { 182 return address; // Prefer ipv4 183 } 184 candidate = address; // Probably an ipv6 185 } 186 } 187 if (candidate != null) { 188 return candidate; // return ipv6 address if no suitable ipv6 189 } 190 return InetAddress.getLocalHost(); // No damn matches. Give up, return local host. 191 } 192 193 /** 194 * Starts the RPC server bound to the localhost address. 195 * 196 * @param port the port to bind to or 0 to pick any unused port 197 * @return the port that the server is bound to 198 * @throws IOException 199 */ startLocal(int port)200 public InetSocketAddress startLocal(int port) { 201 InetAddress address; 202 try { 203 // address = InetAddress.getLocalHost(); 204 address = getPrivateInetAddress(); 205 mServer = new ServerSocket(port, 5, address); 206 } catch (BindException e) { 207 Log.e("Port " + port + " already in use."); 208 try { 209 address = getPrivateInetAddress(); 210 mServer = new ServerSocket(0, 5, address); 211 } catch (IOException e1) { 212 e1.printStackTrace(); 213 return null; 214 } 215 } catch (Exception e) { 216 Log.e("Failed to start server.", e); 217 return null; 218 } 219 int boundPort = start(); 220 return InetSocketAddress.createUnresolved(mServer.getInetAddress().getHostAddress(), 221 boundPort); 222 } 223 224 /** 225 * Starts the RPC server bound to the public facing address. 226 * 227 * @param port the port to bind to or 0 to pick any unused port 228 * @return the port that the server is bound to 229 */ startPublic(int port)230 public InetSocketAddress startPublic(int port) { 231 InetAddress address; 232 try { 233 // address = getPublicInetAddress(); 234 address = null; 235 mServer = new ServerSocket(port, 5 /* backlog */, address); 236 } catch (Exception e) { 237 Log.e("Failed to start server.", e); 238 return null; 239 } 240 int boundPort = start(); 241 return InetSocketAddress.createUnresolved(mServer.getInetAddress().getHostAddress(), 242 boundPort); 243 } 244 245 /** 246 * Starts the RPC server bound to all interfaces. 247 * 248 * @param port the port to bind to or 0 to pick any unused port 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(), 260 boundPort); 261 } 262 start()263 private int start() { 264 mServerThread = new Thread(() -> { 265 while (!mStopServer) { 266 try { 267 Socket sock = mServer.accept(); 268 if (!mStopServer) { 269 startConnectionThread(sock); 270 } else { 271 sock.close(); 272 } 273 } catch (IOException e) { 274 if (!mStopServer) { 275 Log.e("Failed to accept connection.", e); 276 } 277 } 278 } 279 }); 280 mServerThread.start(); 281 Log.v("Bound to " + mServer.getInetAddress()); 282 return mServer.getLocalPort(); 283 } 284 startConnectionThread(final Socket sock)285 protected void startConnectionThread(final Socket sock) { 286 ConnectionThread networkThread = new ConnectionThread(sock); 287 mConnectionThreads.add(networkThread); 288 networkThread.start(); 289 notifyOnConnect(); 290 } 291 292 /** Closes the server, preventing new connections from being added. */ shutdown()293 public void shutdown() { 294 // Stop listening on the server socket to ensure that 295 // beyond this point there are no incoming requests. 296 mStopServer = true; 297 try { 298 mServer.close(); 299 } catch (IOException e) { 300 Log.e("Failed to close server socket.", e); 301 } 302 // Since the server is not running, the mNetworkThreads set can only 303 // shrink from this point onward. We can just stop all of the running helper 304 // threads. In the worst case, one of the running threads will already have 305 // shut down. Since this is a CopyOnWriteList, we don't have to worry about 306 // concurrency issues while iterating over the set of threads. 307 for (ConnectionThread connectionThread : mConnectionThreads) { 308 connectionThread.close(); 309 } 310 for (SimpleServerObserver observer : mObservers) { 311 removeObserver(observer); 312 } 313 } 314 } 315