Search code examples
pythongoasynchronousctypes

Go/Python asynchronous bridge


I wrote a client to handle lower-level TLS connection parameters like ClientHellos etc.

I did this in Go since it's a lot easier there. My main program (webscraper) is in Python. I connected the Go source to my Python file through a DLL via ctypes. My webscraper structure was asynchronous so far, to handle multiple connections at a time.

Unfortunately, my Go client is not asynchronous. Is there a way I can change it in Python so it asynchronously awaits a response from the ctypes pointer until it is there? Right now it's waiting for a response but blocks all other code executions in the meantime obviously.

EDIT: Code example below

async def request(self, method, url, headers, body=None, rawBody=None, pseudoHeaderOrder=["method", "authority", "scheme", "path"]):
        global httpLib
        global initFunc
        global requestFunc
        global changeProxyFunc
        global freePointerFunc
        config = {
            "id": self.cid,
            "method": method.upper(),
            "timeout": 20000,
            "url": url,
            "pseudoHeaderOrder": pseudoHeaderOrder,
            "headers": headers
        }
        #Critical
        if body:
            config["body"] = body
        if rawBody:
            rawBody = [b for b in bytes(rawBody, "utf-8")]
            config["rawBody"] = rawBody
        config = json.dumps(config)
        #print(config)
        #THIS PART CASTS THE REQUEST
        ptr = requestFunc(config.encode('utf-8'))
        string = ctypes.cast(ptr, ctypes.c_char_p).value.decode("utf-8")
        #THIS PART CLEARS THE POINTER
        freePointerFunc(ptr)
        #...

Solution

  • You can move the blocking call to a separate thread/process using an executor.

    Something like this should work,

    async def request(self, method, url, headers, body=None, rawBody=None, pseudoHeaderOrder=["method", "authority", "scheme", "path"]):
            global httpLib
            global initFunc
            global requestFunc
            global changeProxyFunc
            global freePointerFunc
            config = {
                "id": self.cid,
                "method": method.upper(),
                "timeout": 20000,
                "url": url,
                "pseudoHeaderOrder": pseudoHeaderOrder,
                "headers": headers
            }
            #Critical
            if body:
                config["body"] = body
            if rawBody:
                rawBody = [b for b in bytes(rawBody, "utf-8")]
                config["rawBody"] = rawBody
            config = json.dumps(config)
            
            # Move blocking code to separate function
            def blocking_io():
              ptr = requestFunc(config.encode('utf-8'))
              string = ctypes.cast(ptr, ctypes.c_char_p).value.decode("utf-8")
              freePointerFunc(ptr)
              return string
    
            # Aschronously wait on the result
            loop = asyncio.get_running_loop()
            string = await loop.run_in_executor(None, blocking_io)
            
            #...