Search code examples
pythonasync-awaithttprequestpython-asynciofastapi

How can I send an HTTP request from my FastAPI app to another site (API)?


I am trying to send 100 requests at a time to a server http://httpbin.org/uuid using the following code snippet

from fastapi import FastAPI
from time import sleep
from time import time
import requests
import asyncio

app = FastAPI()

URL= "http://httpbin.org/uuid"


# @app.get("/")
async def main():
    r = requests.get(URL)
    # print(r.text)
    
    return r.text

async def task():
    tasks = [main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main(),main()]
    # print(tasks)
    # input("stop")
    result = await asyncio.gather(*tasks)
    print (result)

@app.get('/')
def f():
    start = time()
    asyncio.run(task())
    print("time: ",time()-start)

I am using FastAPI with Asyncio to achieve the lowest time possible around 3 seconds or less but using the above method I am getting an overall time of 66 seconds that is more than a minute. I also want to keep the main function for additional operations on r.text. I understand that to achieve such low time, concurrency is required but I am not sure what mistake I'm doing here.


Solution

  • requests is a synchronous library. You need to use an asyncio-based library to make requests asynchronously.

    httpx

    httpx is typically used in FastAPI applications to request external services. It provides synchronous and asynchronous clients which can be used in def and async def path operations appropriately. It is also recommended for asynchronous tests of application. I would advice using it by default.

    from fastapi import FastAPI
    from time import time
    import httpx
    import asyncio
    
    app = FastAPI()
    
    URL = "http://httpbin.org/uuid"
    
    
    async def request(client):
        response = await client.get(URL)
        return response.text
    
    
    async def task():
        async with httpx.AsyncClient() as client:
            tasks = [request(client) for i in range(100)]
            result = await asyncio.gather(*tasks)
            print(result)
    
    
    @app.get('/')
    async def f():
        start = time()
        await task()
        print("time: ", time() - start)
    

    Output

    ['{\n  "uuid": "65c454bf-9b12-4ba8-98e1-de636bffeed3"\n}\n', '{\n  "uuid": "03a48e56-2a44-48e3-bd43-a0b605bef359"\n}\n',...
    time:  0.5911855697631836
    

    aiohttp

    aiohttp can also be used in FastAPI applications, if you prefer one.

    from fastapi import FastAPI
    from time import time
    import aiohttp
    import asyncio
    
    app = FastAPI()
    
    URL = "http://httpbin.org/uuid"
    
    
    async def request(session):
        async with session.get(URL) as response:
            return await response.text()
    
    
    async def task():
        async with aiohttp.ClientSession() as session:
            tasks = [request(session) for i in range(100)]
            result = await asyncio.gather(*tasks)
            print(result)
    
    
    @app.get('/')
    async def f():
        start = time()
        await task()
        print("time: ", time() - start)
    

    If you want to limit the number of requests executing in parallel, you can use asyncio.semaphore like so:

    MAX_IN_PARALLEL = 10
    limit_sem = asyncio.Semaphore(MAX_IN_PARALLEL)
    
    
    async def request(client):
        async with limit_sem:
            response = await client.get(URL)
            return response.text