Request timeouts
Web endpoint (a.k.a webhook) requests should complete quickly, ideally within a
few seconds. All webhook Function types
(webhook
, asgi
, wsgi
) have a maximum
HTTP request timeout of 150 seconds enforced. However, the underlying Modal
function can have a longer timeout.
In case the function takes more than 150s to complete, a HTTP status 301
redirect response is returned pointing at the original URL with an appended
__modal_function_call_id=fc-123abc
query parameter corresponding to the
triggered webhook. Most web browsers allow for up to 20 such redirects,
effectively allowing up to 50 min (20 * 150s) runtime for webhooks before the
request times out. Some libraries and tools might require you to add a flag or
option in order to follow redirects automatically, e.g. curl -L ...
or
http --follow ...
.
The eventual landing URL can be reloaded without triggering a new request, and will instead fetch the result of the original call.
wait_for_response=False
In cases when you want a webhook to trigger and return the result URL
immediately, you can add the wait_for_response=False
flag to your
webhook/asgi/wsgi decorator. The decorated function will then be immediately
triggered and a 202 accepted HTTP response will be returned, along with a JSON
payload containing the aforementioned result URL:
{"result_url": "...?__modal_function_call_id=..."
}. This can be useful if you
for example want to store the result URL somewhere for later access, or
immediately redirect a web browser to a URL that won’t trigger a new function
call on manual refresh.
Polling solutions
Sometimes it can be useful to be able to poll for results rather than wait for a
long running HTTP request. The easiest way to do this is to have your webhook
spawn a modal.Function
call and return the function call id that another
endpoint can use to poll the submitted function’s status. Here is an example:
import fastapi
from modal.functions import FunctionCall
web_app = fastapi.FastAPI()
@stub.asgi()
def fastapi_app():
return web_app
@stub.function()
def slow_operation():
...
@web_app.post("/webhook")
async def accept_job(request: fastapi.Request):
call = slow_operation.spawn()
return {"call_id": call.object_id}
@web_app.get("/result/{call_id}")
async def poll_results(call_id: str):
function_call = FunctionCall.from_id(call_id)
try:
return function_call.get(timeout=0)
except TimeoutError:
http_accepted_code = 202
return fastapi.responses.JSONResponse({}, status_code=http_accepted_code)
Document OCR Web App is an example that uses this pattern.