0

I have python flask back-end and I don't know how to configure it to have a delay between accepting a connection and reading data, or perform any other back-end workaround, so it receives an empty request body all the time.

Likely a minimal example:

package com.acidbro.test.megabyte0.httplib

import android.os.Handler
import android.os.Looper
import android.util.Log
import androidx.core.os.HandlerCompat
import java.io.InputStream
import java.net.HttpURLConnection
import java.net.URL
import java.util.concurrent.*

sealed class Result<out R> {
    data class Success<out T>(val data: T) : Result<T>()
    data class Error(val exception: Exception) : Result<Nothing>()
}

class MyHttpLib(
    private val responseParser: MyHttpResponseParser,
    private val executor: Executor//,
    //private val resultHandler: Handler
)  {
    // Function that makes the network request, blocking the current thread
    fun makeRequest(
        url: String,
        method: String,
        jsonBody: String,
        headers: Map<String,String>,
        resultHandler: Handler?,
        callback: (Result<MyHttpResponse>) -> Unit
    ) {
        executor.execute {
            try {
                val response = makeSynchronousRequest(url,method, jsonBody, headers)
                if (resultHandler!==null) {
                    resultHandler.post { callback(response) }
                } else {
                    callback(response)
                }
            } catch (e: Exception) {
                val errorResult = Result.Error(e)
                if (resultHandler!==null) {
                    resultHandler.post { callback(errorResult) }
                } else {
                    callback(errorResult)
                }
                e.printStackTrace()
            }
        }
    }

    private fun makeSynchronousRequest(
        resourceUrl: String,
        method: String,
        body: String,
        headers: Map<String,String>
    ): Result<MyHttpResponse> {
        val url = URL(resourceUrl)
        val requestHeaders = hashMapOf<String,String>()
        requestHeaders.putAll(headers)
        (url.openConnection() as? HttpURLConnection)?.run {
            Log.d(TAG,"run")
            //https://stackoverflow.com/a/26030149
            instanceFollowRedirects = false
            Log.d(TAG,"instanceFollowRedirects")

            requestMethod = method
            Log.d(TAG,"requestMethod")
            doOutput = method==="POST"
            Log.d(TAG,"doOutput = ${method==="POST"}")
            for ((key,value) in requestHeaders) {
                setRequestProperty(key, value)
            }
            Log.d(TAG,"setRequestProperty")
            if (method==="POST") {
                val bytes = body.toByteArray()
                setFixedLengthStreamingMode(bytes.size)
                //connect()
                outputStream.run {
                    write(bytes)
                    Log.d(TAG, "outputStream.write")
                    flush()
                    Log.d(TAG, "outputStream.flush()")
                    close()
                    Log.d(TAG,"outputStream.close()")
                }
            }

            val myInputStream: InputStream? = (
                    try {inputStream}
                    catch (e: Exception) {null}
                    )

            Log.d(TAG,"$responseCode $headerFields $myInputStream")
            return Result.Success(
                responseParser.parse(
                    responseCode,
                    headerFields,
                    myInputStream
                )
            )
        }
        return Result.Error(Exception("Cannot open HttpURLConnection"))
    }

    companion object {
        val TAG = MyHttpLib::class.simpleName

        val mainThreadHandler: Handler = HandlerCompat.createAsync(Looper.getMainLooper())

        /*
         * Gets the number of available cores
         * (not always the same as the maximum number of cores)
         */
        private val NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors()

        // Instantiates the queue of Runnables as a LinkedBlockingQueue
        private val workQueue: BlockingQueue<Runnable> =
            LinkedBlockingQueue<Runnable>()

        // Sets the amount of time an idle thread waits before terminating
        private val KEEP_ALIVE_TIME = 5L

        // Sets the Time Unit to seconds
        private val KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS

        // Creates a thread pool manager
        val threadPoolExecutor: ThreadPoolExecutor = ThreadPoolExecutor(
            NUMBER_OF_CORES,       // Initial pool size
            NUMBER_OF_CORES,       // Max pool size
            KEEP_ALIVE_TIME,
            KEEP_ALIVE_TIME_UNIT,
            workQueue
        )
        @Volatile
        private var INSTANCE: MyHttpLib? = null

        fun getInstance(): MyHttpLib {
            return INSTANCE ?: synchronized(this) {
                INSTANCE ?: MyHttpLib(
                    MyHttpResponseParser(),
                    threadPoolExecutor//,
                    //mainThreadHandler
                )
                    .also { INSTANCE = it }
            }
        }
    }
}


package com.acidbro.test.megabyte0.httplib

import org.json.JSONException
import org.json.JSONObject
import java.io.InputStream

class MyHttpResponseParser {
    fun parse(
        status: Int,
        headers: Map<String,List<String>>,
        inputStream: InputStream?
    ): MyHttpResponse {
        val data = (
                if (inputStream!==null)
                String(inputStream.readBytes())
                else ""
                )
        return MyHttpResponse(status, headers,
            data,
            try {
                JSONObject(data)
            } catch (e: JSONException) {
                null
            }
        )
    }
}

data class MyHttpResponse(
    val status: Int,
    val headers: Map<String,List<String>>,
    val data: String,
    val json: JSONObject?
) {

}

And the python simple socket server back-end. When I uncomment sleep(0.1) it receives all the data in the first request.recv, if not it receives only the headers and all the body goes in the second request.recv, so does flask, except that flask doesn't get the request body.

import socketserver
import time

class MyTCPHandler(socketserver.BaseRequestHandler):
    """
    The request handler class for our server.

    It is instantiated once per connection to the server, and must
    override the handle() method to implement communication to the
    client.
    """

    def handle(self):
        t0 = time.monotonic()
        #time.sleep(0.02)
        # self.request is the TCP socket connected to the client
        self.data = [self.request.recv(2**16)]#.strip()
        t1 = time.monotonic()
        # just send back the same data, but upper-cased
        # self.request.sendall(self.data.upper())
        self.request.sendall(
rb'''HTTP/1.1 400 BAD REQUEST
Content-Length: 0
Connection: Close

'''.replace(b'\n',b'\r\n'))
        t2 = time.monotonic()
        self.data.append(self.request.recv(2**16))
        t3 = time.monotonic()
        print("{} wrote:".format(self.client_address[0]))
        print([t-t0 for t in [t1,t2,t3]])
        print(self.data)
        print()

if __name__ == "__main__":
    HOST, PORT = "192.168.1.108", 8000

    # Create the server, binding to localhost on port 9999
    with socketserver.TCPServer((HOST, PORT), MyTCPHandler) as server:
        # Activate the server; this will keep running until you
        # interrupt the program with Ctrl-C
        server.serve_forever()

The question is -- is there any android front-end android workaround to force the flask server to receive the request body? Thanks.

EDIT:
responding to comments, deleted setFixedLengthStreamingMode, it's the same story, the thing was added to try to speed things up. The same with connect(), flush() and close() and/or wrapping outputStream into a buffered stream.
The exact values of the delays are like [0.010252761996525805, 0.010276289998728316, 0.010734848998254165] when using setFixedLengthStreamingMode and like [0.04281934200116666, 0.04284564500267152, 0.043687549004971515] when not using it. Either way request is read by the server in two parts, as mentioned above.
Thanks in advance for your time and effort.

Alexey Burdin
  • 122
  • 2
  • 9
  • `setFixedLengthStreamingMode(bytes.size)` Try without. – blackapps Dec 09 '20 at 19:16
  • @blackapps Thanks, doesn't help, edited the question. – Alexey Burdin Dec 09 '20 at 20:04
  • Please tell the exact values of the delays. – blackapps Dec 09 '20 at 21:37
  • `# Create the server, binding to localhost` That looks like a bad idea which would make connections from outside localhost impossible. Dont bind your server to an ip. – blackapps Dec 09 '20 at 21:39
  • Edited with delay values, thanks again. @blackapps – Alexey Burdin Dec 09 '20 at 23:30
  • In seconds? In milli seconds? Hours? Those delays are neglegible. A normal socket will wait for seconds and otherwise set the socket timeout higher. – blackapps Dec 09 '20 at 23:49
  • `Either way request is read by the server in two parts, ` Again: be exact. How many bytes in each part? Its quite normal using tcp that messages come in parts. So what is your problem? – blackapps Dec 09 '20 at 23:51
  • @blackapps In seconds, I modified the python source. The problem is that back-end flask server eats the first part (headers+`\r\n\r\n`) and reports no body in the request, irrespective to `Content-length` header, indicating for the server to read further. I was recommended retrofit or volley instead of simple naked built-in HttpURLConnection because of that, but I'm wondering is there any way to repair HttpURLConnection behaviour to fit the flask server. Thanks. – Alexey Burdin Dec 10 '20 at 01:24

0 Answers0