Home | History | Annotate | Download | only in android_scripting
      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 (at) 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 {
     57     public void onConnect();
     58     public void onDisconnect();
     59   }
     60 
     61   protected abstract void handleConnection(Socket socket) throws Exception;
     62   protected abstract void handleRPCConnection(Socket socket,
     63                                               Integer UID,
     64                                               BufferedReader reader,
     65                                               PrintWriter writer) throws Exception;
     66 
     67   /** Adds an observer. */
     68   public void addObserver(SimpleServerObserver observer) {
     69     mObservers.add(observer);
     70   }
     71 
     72   /** Removes an observer. */
     73   public void removeObserver(SimpleServerObserver observer) {
     74     mObservers.remove(observer);
     75   }
     76 
     77   private void notifyOnConnect() {
     78     for (SimpleServerObserver observer : mObservers) {
     79       observer.onConnect();
     80     }
     81   }
     82 
     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 
     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
    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 
    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. */
    140   public int getNumberOfConnections() {
    141     return mConnectionThreads.size();
    142   }
    143 
    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 
    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    */
    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    */
    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    */
    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 
    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 
    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 
    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