Search code examples
dockerapacheperlpromisemojolicious

Perl Mojolicious: handling proxy timeouts


I have a Mojolicious App behind a reverse proxy. The Mojo App uses Promises, and in some cases, the synchronous response is returned after 2 minutes, by which time, the reverse proxy times out.

How can I best render the response? (making my subroutine respond sooner, unfortunately, is not an option)

  • Can I show a temporary page, but commit / render the final response afterwards?
  • Can I use Ajax to show a spinner, then render the final response once done?
  • Should I switch to Async, perhaps integrate Mojolicious with Minion, and render a response that checks if the async call is ready via Ajax? (pointers and samples would be appreciated).

I demonstrate this behavior using docker below with a reverse proxy that times out after 5 seconds, and a mojo app that responds after 10 seconds:

1. Create a Dockerfile

FROM docker.io/library/perl:5.36
# Install Apache2
RUN apt-get update && apt-get install -y --no-install-recommends apache2
# Install Mojolicious
RUN cpanm Mojolicious
# Install Apache mod_proxy and configure Apache2 as a reverse proxy to mojo app with 5s timeout
RUN a2enmod proxy_http \
    && perl -i.bak -pe 's#</VirtualHost>#\tProxyPass /mojo http://localhost:3000/ keepalive=On timeout=5\n</VirtualHost>#' /etc/apache2/sites-enabled/000-default.conf
# Generate a mojo app and simulate a timer
RUN mojo generate app \
    && perl -i.bak -pe 's/^.*$/  sleep(10);/ if $. == 6' /my_app/lib/MyApp/Controller/Example.pm
# Expose Apache2 and Mojo ports
EXPOSE 80 3000
# Run Apache 2 in the background and Morbo in the foreground
CMD ["/bin/bash", "-c", "apache2ctl start; /my_app/script/my_app prefork"]

2. Build & run:

docker build -t myapp .
docker run --rm --name myapp -p 8080:80 -p 3000:3000 myapp

3. Browse to:

http://localhost:8080/mojo

Solution

  • I ended up going with an asynchronous approach using Minion as a queue. A full working example of a LinkCheck application is provided at:

    https://github.com/mojolicious/minion/tree/main/examples/linkcheck

    The example requires you either:

    1. Start a Postgres Database, for example using docker:
    docker run --rm --name postgres -e POSTGRES_HOST_AUTH_METHOD=trust -p 5432:5432 postgres:15
    

    OR 2. Replace one line during startup to use SQLite instead of Postgres. (requires Minion::Backend::SQLite plugin )

    $self->plugin(Minion => {SQLite=>'sqlite:minion.db'});
    

    In either cases, you must run a separate a background worker process to handle the job queue, for example:

    perl script/linkcheck minion worker