In this article we’ll tackle file isolation in a multi-region Laravel Fly App, and display PDFs in a deferred manner. Run your Laravel app with Fly.io, it takes only a few minutes!
Running our Laravel application close to our users reduces geographical latency. What’s more, with Fly.io, we get to easily do global deployments with just a few commands!
However, as we’ve established in Taking Laravel Global, files stored in one region are not automatically available to instances in other regions.
To address this region-isolation dilemma, today we’ll use the fly-replay
response header to direct file retrieval requests to the correct regional instance. Alongside which we’ll use Livewire’s wire:init
to speed up initial loading of pages displaying our files.
Here’s a reference repository you can read and a demo page you can inspect.
The Problem
Let’s say we have a Laravel Fly App with instances running in AMS, FRA, and SIN.
This Laravel application stores PDFs in each instance’s local storage folder and allows users to view these PDFs in the browser window.
If a first_title.pdf
file was stored in the AMS region of our Laravel Fly Application, by default, this PDF file will only be accessible to users accessing the AMS instance, but not from instances in FRA or SIN.
Furthermore, even if instances from different regions get to access files stored in other regions, cross-regional data transfer can affect the hold up in pages displaying the file.
Solution
When it comes to accessing regional-specific storage, we simply need to redirect requests and talk to the right instance containing a requested PDF.
We can easily do so by instructing Fly.io to redirect the request to the correct regional instance with its fly-replay
response header.
Afterwards, to speed up the initial load-up of the page displaying the PDF file, we can use Livewire’s wire:init
directive to defer loading our PDF file until the page has completed loading.
Locally stored, DB tracked, Replay Available
In order to make use of fly-replay
, we need to know the region our PDF file is stored in.
Fly.io provides us a FLY_REGION
environment variable which we can use to identify the region our instance is deployed in. We can add this in our config/app.php:
'fly_region' => env( 'FLY_REGION' ),
Then keep track of this region in the PDF details we store in our database:
Multi-region instances benefit greatly with talking to databases in close proximity. Check out running your Laravel Fly App with Multi-region MySQL or SQLite.
DB::table('file_records')->insert([
'full_path' => 'storage/uploads/'.$fileName,
'region_id' => config('app.fly_region')
]);
Storing a first_title.pdf
file above should give us the following row in our database:
Make sure to persist stored files through the use of volumes. Here’s a quick guide to persist data on the storage folder.
full_path: "storage/uploads/first_title.pdf",
region_id: "ams"
With the above setup, we now have a record of our stored PDF, and the region it is stored in. We can start using fly-replay
to forward retrieval requests to the right instance to get our first_title.pdf
file!
Talking to the right instance with Fly-replay
To make our first_title.pdf
file accessible outside of the AMS region, we’ll have to make sure all retrieval requests for the file “talk” to our instance running in the AMS region.
If the current instance’s region is not the same as the region_id
recorded for first_title.pdf
, we stop processing the request in the current instance, and instead return a fly-replay
response header containing the region to forward the request to.
For visibility on the whole picture, you can read this Controller file for reference.
// Decide replay
if( $pdfDetails->region_id != config('app.fly_region') ){
// Replay to identified region
return response('', 200, [
'fly-replay' => 'region='.$pdfDetails->region_id ,
]);
}else{
// FileName
$fileName = explode('/', $pdfDetails->full_path);
$fileName = $fileName[(count($fileName))-1];
// Accessible File Path
$filePath = Storage::path( $pdfDetails->full_path );
// Respond with File
return response()->file( $filePath );
}
Once the response header is returned by the application instance, Fly.io’s proxy layer will immediately recognize the fly-replay
response header. It will then replay the request to the region specified in its region=<region_id>
value.
Displaying PDF iframes with wire:init
Now that PDF files stored in different regions are accessible from the rest, we can proceed with displaying our PDF files with the use of iframes.
We’ll ask our iframe to retrieve the file from a route containing the file retrieval logic specified in our section above.
<iframe src="{{ URL::to('/files/show/'.$recordId ) }}"></iframe>
Placing this iframe tag in a page will hold up page load until our file gets successfully loaded. We can easily avoid this hold up by using Livewire’s wire:init
directive.
Let’s create a Livewire component to defer loading our iframe tag:
Here’s the whole picture for our Livewire view and Livewire Component.
And a bonus reference on initializing and updating our Livewire component with a PDF record to render.
# In the Livewire View
<div id="showFileComponent">
@if( $loadAllowed==true )
<iframe src="{{ URL::to('/files/show/'.$recordId ) }}"></iframe>
@else
Loading PDF
@endif
</div>
In the case above, we only load the iframe when the public attribute $loadAllowed
is true.
We’ll initially set our $loadAllowed
to false, and only change it to true through the method allowFileLoading
:
# In the Livewire Component
class ShowFile extends Component
{
public $recordId;
public $loadAllowed = false;
public function allowFileLoading()
{
$this->loadAllowed = true;
}
And finally, let’s add in wire:init="allowFileLoading"
to tell the Livewire component that once it completes rendering, it needs to call the allowFileLoading
method.
# In the Livewire View
<div id="showFileComponent" wire:init="allowFileLoading">
As seen above, calling the allowFileLoading
method updates the $loadAllowed
to true. Doing so loads the iframe tag which calls our Laravel route files/show/{recordId}
.
Discussion
Running multi-region instances of our Laravel application means an effort from us to empathize on and close geographical latency for our users across the globe.
At the same time, it also means addressing regional-isolation of files and providing file-accessibility to our users.
Today, we addressed file isolation by talking with the right instance using fly-replay
, and improved loading of pages displaying these cross-regional files through the use of deferred displaying with wire:init
.
Of course, this article only focuses on accessibility for file-isolation, there are other equally important points for consideration.
One is Database consistency and performance optimization. We have guides to address those with multi-region MYSQL with PlanetScale, and multi-region SQLite with LiteFS.
For a checklist to mull over, further reading is advised on Taking Laravel Global.