Unable to receive content from URLConnection client on ServerSocket host in Android

24 Views Asked by At

The following classes are running as threads, invoked from a fairly empty Android application (i.e., the app is really only being used to spawn the threads and then post a message to URLSender). The observed behavior is that the connection is made (i.e., a call to socket.isConnected() returns true), but while(socket.isConnected){} simply loops without any content ever being received on inputStream.read(), so while (bytesRead != -1){} is never entered.

Add Note:

I moved ServerSocketHost and URLSender into a vanilla Java console driver, and the behavior appears to be the same. So, the issue appears to be the actual transfer from the URLConnection sender to the ServerSocket receiver.

Any suggestions?

Implementation of the server receiving communication from the client...

public class ServerSocketHost implements Runnable {

    public  static final int         HOST_PORT   = 45000;
    public  static final byte[]      HOST_IP     = new byte[]{127, 0, 0, 1};
    private static InetSocketAddress hostAddress = null;

    private static final int DEFAULT_TIMEOUT = 5000;
    private static final int CONNECT_BACKLOG = 50;

    private boolean      okToRun      = true;
    private ServerSocket serverSocket = null;
    private int          timeoutValue = -1;

    public ServerSocketHost() throws IOException {
        this(DEFAULT_TIMEOUT);
    }

    public ServerSocketHost(int timeoutValue) throws IOException {
        this.timeoutValue = timeoutValue;
        getServerAddress();
        openServerSocket();
    }

    private void openServerSocket() throws IOException {
        serverSocket = new ServerSocket(HOST_PORT);
        serverSocket.setSoTimeout(timeoutValue);
    }

    public void shutdown() {
        /* will cause run() to terminate execution on evaluation of "while (okToRun)", either after
             input has been read or a timeout has occurred
         */
        okToRun = false;
    }

    @Override
    public void run() {
        BufferedInputStream   inputStream      = null;
        ByteArrayOutputStream byteArrayOS      = null;
        int                   initialArraySize = 1024;
        byte[]                byteInput        = new byte[8]; // '8' is an arbitrary number
        int                   bytesRead        = -1;

        while (okToRun) {
            Socket socket = null;
            try {
                if(serverSocket.isClosed()) {
                    // ServerSocket is closed on timeout, so reopen if needed
                    openServerSocket();
                }

                socket = serverSocket.accept();
                while (socket.isConnected()) {
                    inputStream = new BufferedInputStream(socket.getInputStream());
                    byteArrayOS = new ByteArrayOutputStream(initialArraySize);
                    /* BEGIN CONSTRAINED IMPLEMENTATION
                        the following implementation (using InputStream.read() vice readAllBytes()) is
                      constrained by the target Android API. InputStream.readAllBytes() is not supported
                      until API 33, and this implementation targets API 29.
                     */
                    bytesRead = inputStream.read(byteInput);
                    while (bytesRead != -1) {
                        byteArrayOS.write(byteInput, 0, bytesRead);
                        bytesRead = inputStream.read(byteInput);
                    }
                    byteArrayOS.flush();
                }
            } catch(IOException e) {
                // should handle the exception, but for now just swallow it
                e.printStackTrace();
            } 
        }
    }
}

Implementation of the client, which is posting messages to an internal queue and then posting those messages to the server/host...

public class URLSender implements Runnable {

    private boolean           okToRun          = true;
    private URL               hostURL          = null;
    private List<String>      synchronizedList = null;

    public URLSender() throws MalformedURLException {
        this.hostURL          = new URL( "http://127.0.0.1:45000");
        this.synchronizedList = Collections.synchronizedList(new LinkedList<String>());
    }

    public void shutdown() {
        okToRun = false;
    }

    public void postMessage(String message) {
        synchronized (synchronizedList) {
            synchronizedList.add(message + " (1)\n");
            synchronizedList.add(message + " (2)\n");
            synchronizedList.add(message + " (3)\n");
            synchronizedList.notify();
        }
    }

    public void run() {
        URLConnection      urlConnection      = null;
        OutputStreamWriter outputStreamWriter = null;

        try {
            while (okToRun) {
                if (synchronizedList.isEmpty()) {
                    synchronized (synchronizedList) {
                        synchronizedList.wait();
                    }
                }

                try {
                    urlConnection = hostURL.openConnection();
                    urlConnection.setDoOutput(true);
                    outputStreamWriter = new OutputStreamWriter(urlConnection.getOutputStream());
                } catch(IOException e) {
                    // should handle the exception, but for now just swallow it
                    e.printStackTrace();
                }
                while (synchronizedList.size() > 0) {
                    try {
                        String localMessage = synchronizedList.remove(0);
                        byte[] bytes2write  = localMessage.getBytes();

                        outputStreamWriter.write(localMessage);
                        outputStreamWriter.flush();
                    } catch (MalformedURLException e) {
                        // ?? okay to swallow this exception, as it should have been thrown in constructor
                        e.printStackTrace();
                    } catch (IOException e) {
                        // should handle the exception, but for now just swallow it
                        e.printStackTrace();
                    }
                }
                try {
                    outputStreamWriter.close();
                    ((HttpURLConnection) urlConnection).disconnect();
                } catch(IOException e) {
                    // should handle the exception, but for now just swallow it
                    e.printStackTrace();
                }
            }
        } catch(InterruptedException e) {
            // swallow the exception (i.e., assume thread is being stopped)
        }
    }
}

Android manifest, with entries to support cleartext using HTTP

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

    <!--
          Entries "android:networkSecurityConfig" and "android:usesCleartextTraffic" added to
          support use of http communications
      -->
    <application
        android:networkSecurityConfig="@xml/xml_security_config"
        android:usesCleartextTraffic="true"

        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/Theme.URLConnect"
        tools:targetApi="29">
        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:label="@string/app_name"
            android:theme="@style/Theme.URLConnect.NoActionBar">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

Associated network-security-config...

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <!-- * * *  FOR DEMONSTRATION PURPOSES ONLY  * * *
      Network configuration exists solely to allow the URL socket to be used with plain HTTP
      protocol. In a real-world implementation, communication using HTTPS protocol should be
      employed, with appropriate certificates.
      -->
    <base-config cleartextTrafficPermitted="true"></base-config>

    <domain-config cleartextTrafficPermitted="true">
        <domain includeSubdomains="true">127.0.0.1</domain>
    </domain-config>
</network-security-config>
0

There are 0 best solutions below