Nahrávání souborů přes JSON

TL;DR
Článek se zabývá problematikou nahrávání souborů prostřednictvím JSONu. Při vytváření administrátorského panelu pro Knížkomat se tým rozhodl nahrávat obálky knih, zvukové soubory a obrázky jako pole bajtů v JSONu. Ačkoli tato metoda fungovala pro malé soubory, narazili na limit 2,1 GB kvůli omezení velikosti pole v jazyce Java. Nakonec si uvědomili, že správným řešením je upravit middleware tak, aby zvládal i jiné formáty než JSON, ačkoli přenosy JSON jsou pro menší soubory použitelné.

Při vytváření administrátorského panelu pro Knížkomat bylo potřeba umožnit správcům nahrávat přebaly knížek, audio nahrávky a obrázky v textu. Všechny tři kategorie jsme se rozhodli implementovat stejně, tedy generické nahrání souboru na server vrátí adresu daného souboru a ta se následně uloží kam je potřeba. Při implementaci jsme však narazili na problém s odesíláním souborů skrz middleware, který v obou směrech počítal s komunikací ve formátu JSON. Když jsme se tedy snažili posílat soubory z frontendu přes standardní multipart-form, vyskakovaly na nás nejrůznější chyby, jak se middleware snažil přeložit tělo požadavku na JSON. Samozřejmě by se nabízelo udělat v middleware výjimku pro požadavky na nahrání souborů, ale my jsme se rozhodli, spíše pro prohloubení našich znalostí, s tímto omezením pracovat.

Převod souboru do JSONu

Uvědomili jsme si, že soubor, přesněji řečeno obsah souboru, není nic jiného, než dlouhý seznam bajtů. Tento seznam jsme schopni v JavaScriptu snadno získat ve formátu tzv. ByteArray a tu lze serializovat do JSONu jako obyčejné pole bajtů. Tímto způsobem jsme ale ztratili informaci o jaký typ souboru se jedná, nejde jen o rozlišení obrázek/zvuk, ale i o přesný typ obrázku, jako JPEG, PNG atd. - to samé platí pro zvukovou stopu. Proto jsme do JSONu přidali ještě příponu původního souboru, díky čemuž můžeme i na backendu zachovat správný formát. Nabízelo by se posílat celý název souboru, ale ten nebyl zapotřebí, jelikož se soubory na backendu přejmenovávají.

Přestože tento přístup neměl úplně důvod nefungovat, přesto nás překvapilo, že se soubory skutečně korektně nahrávaly na server a nijak se při přenosu neporušili. To jsme si tedy alespoň mysleli. Pro relativně malé soubory to tak skutečně i bylo, ale jakmile jsme překročili magickou hranici 2,1 GB (což u audio-knížek není velký problém), začaly se nám ztrácet konce těchto souborů. Abych byl přesnější, magickou hranicí bylo přesně 2 147 483 647 bajtů, což přesně odpovídá maximální velikosti pole v Javě, ve které je napsaný backend Knížkomatu. Toto na první pohled náhodné omezení vychází z toho, že pole je v Javě indexováno čísly ve formátu integer a jejich nejvyšší možná hodnota je 2^31^-1, neboli výše zmíněné 2,1 miliardy bajtů. Jakmile jsme se tedy pokusili vytvořit pole o větší velikosti (přesněji řečeno když se o to pokusil deserializer ve SpringBootu), neměl jinou možnost, než neindexovatelná data zahodit a s tím zahodit i část souboru. Pro zajímavost, maximální velikost pole v Javascriptu je 2^32^-2, protože používá stejnou 32-bitovou indexaci jako Java, ale nepočítá se zápornými čisly.

Správné řešení

Problém nahrávání souborů měl samozřejmě pouze jedno skutečně správné řešení a tím bylo upravit middleware tak, aby uměl pracovat i s jinými formáty, než jenom s JSONem. Díky našemu nekonformnímu řešení jsme však narazili na praktický příklad limitů velikosti polí, na který jsme přišli jen díky tomu, že nezávisle na velikosti nahrávaného souboru byl výsledek vždy maximálně 2,1 GB velký. Přenášení souborů v JSONu však může mít své využití, pokud jsme si jistí, že soubory nebudou příliš velké a z jakéhokoli důvodu nemáme možnost využít jiný formát přenosu.

frontendbackendexperiment

NoxLabs je tým inženýrů a designérů, kteří se specializují na vývoj webových a mobilních aplikací. S nadšením vytváříme krásný software a vítáme nové nápady na projekty.

Chcete s námi spolupracovat? Napište nám