Lazy Async Jobs

Lazy Async Jobs
Photo by Alex West / Unsplash

UPDATE: I've just published a package on Packagist to manage Asyncronous Jobs over HTTP requests.

I have a Laravel application sendings a lot of mail notifications when certain operations are done, so I wanted to delegate mail sending to a Queue to avoid to the users long waits for the actual deliveries to be completed. But, for a number of reasons I will not list here (mostly related to the context of a Job executed on a multi-instance installation of the application), I was not able to do this with classic Queues.

Then, the idea. Provide an API endpoint for execute the long and boring operations, and internally call it without waiting for a response. So the job can run detached from the user flow, wihout blocking it, for all the time it is necessary.

I've adapted the function described in this page and I've placed it into an Helper:

function async_job($action, $data)
{
    $endpoint = route('job.execute');

    $postData = (object) $data;
    $postData->action = $action;
    $postData->auth_key = substr(env('APP_KEY'), -5);
    $postData = json_encode($postData);

    $endpointParts = parse_url($endpoint);
    $endpointParts['path'] = $endpointParts['path'] ?? '/';
    $endpointParts['port'] = $endpointParts['port'] ?? $endpointParts['scheme'] === 'https' ? 443 : 80;

    $contentLength = strlen($postData);

    $request = "POST {$endpointParts['path']} HTTP/1.1\r\n";
    $request .= "Host: {$endpointParts['host']}\r\n";
    $request .= "Content-Length: {$contentLength}\r\n";
    $request .= "Content-Type: application/json\r\n\r\n";
    $request .= $postData;

    $prefix = substr($endpoint, 0, 8) === 'https://' ? 'tls://' : '';
    $socket = fsockopen($prefix . $endpointParts['host'], $endpointParts['port']);
    fwrite($socket, $request);
    fclose($socket);
}

The job.execute route (which has to be excluded from the VerifyCsrfToken middleware) is binded to a dedicated Controller, looking more or less as:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class JobsController extends Controller
{
    public function execute(Request $request)
    {
        $auth_key = $request->input('auth_key');
        if ($auth_key != substr(env('APP_KEY'), -5)) {
            abort(503);
        }

        $action = $request->input('action');

        switch($action) {
            case 'do_this':
                $param1 = $request->input('param1');
                doJobOne($param1);
                break;

            case 'do_that':
                doJobTwo();
                break;
        }
    }
}

When I want to execute a long task, I invoke:

async_job('do_this', ['param1' => 'foo']);

Of course you can use any random value as "authentication code", instead of a portion of the APP_KEY.

No queues, no job schedulers, no processes to run aside the application. A bit brutal, but still fair.