ابتدائی ویب سادہ متن (plain text) پر چلتی تھی۔ آپ کسی فارم میں صارف کا نام یا کوئی تبصرہ ٹائپ کرتے، سبمٹ کا بٹن دباتے، اور ایک مختصر سی اسٹرنگ HTTP کے ذریعے سرور تک پہنچ جاتی۔ اس سادہ ریکویسٹ-رسپانس سائیکل نے انفراسٹرکچر کی بنیاد رکھی۔ جب لوگوں نے تصاویر، دستاویزات اور ویڈیوز شیئر کرنے کی خواہش ظاہر کی، تو انجینئرز کو یہ سوچنا پڑا کہ مکمل طور پر قابلِ مطالعہ متن کے لیے بنائے گئے سسٹم کے ذریعے خام بائنری ڈیٹا (raw binary data) کیسے منتقل کیا جائے۔

اگر آپ کسی JSON آبجیکٹ میں JPEG ڈالنے کی کوشش کریں، تو آپ کو ایک بنیادی رکاوٹ کا سامنا کرنا پڑے گا۔ JSON ایک ٹیکسٹ پروٹوکول ہے۔ یہ درست Unicode حروف، کوٹس (quotes)، بریکٹس (braces) اور صحیح طریقے سے اسکیپ شدہ اسٹرنگز (escaped strings) کی توقع رکھتا ہے۔ ایک بائنری فائل محض بائٹس کا ایک طویل سلسلہ ہوتی ہے، جن میں سے بہت سے حروف پرنٹ کرنے کے قابل نہیں ہوتے۔ اگر آپ ان بائٹس کو JSON اسٹرنگ میں ٹھونس دیں تو پارسر خراب ہو جاتا ہے، اسکیپ سیکوئنسز پے لوڈ (payload) کو بگاڑ دیتے ہیں، اور پورا پیغام دوسری طرف ناقابلِ فہم ہو جاتا ہے۔

Base64 انکوڈنگ ایک واضح متبادل کے طور پر سامنے آئی۔ یہ بائنری ڈیٹا کو ساٹھ چار (sixty-four) پرنٹ ایبل ASCII حروف کے ایک محدود سیٹ میں دوبارہ ترتیب دیتی ہے۔ بائنری کے ہر تین بائٹس ٹیکسٹ کے چار حروف بن جاتے ہیں۔ اب پے لوڈ قانونی JSON بن چکا ہے، جس کا مطلب ہے کہ یہ کسی بھی معیاری API کے ذریعے سفر کے دوران برقرار رہے گا۔ لیکن اس کی قیمت فوری طور پر ادا کرنی پڑتی ہے۔ یہ ری-انکوڈنگ فائل کے سائز کو تقریباً تینتیس فیصد بڑھا دیتی ہے۔ تین میگا بائٹ کی تصویر وائر پر چار میگا بائٹ کی ہو جاتی ہے۔ کلائنٹ اور سرور دونوں ڈیٹا کو آگے پیچھے تبدیل کرنے کے لیے اضافی CPU سائیکلز استعمال کرتے ہیں۔ اس سے بھی اہم بات یہ ہے کہ بہت سے JSON سرور فریم ورکس پارسنگ سے پہلے پورے باڈی کو میموری میں لوڈ کر لیتے ہیں۔ ایک ساتھ کئی بڑی فائلیں اپ لوڈ ہونے سے ایک معمولی سرور بھی مفلوج ہو سکتا ہے کیونکہ ہر فائل کو ڈسک پر محفوظ ہونے سے پہلے ایک بھاری ٹیکسٹ اسٹرنگ کے طور پر RAM میں رکھا جاتا ہے۔ Base64 مشکل وقت میں کام تو آ سکتا ہے، لیکن اسے پروڈکشن کے پیمانے پر بھاری فائلیں لے جانے کے لیے کبھی ڈیزائن نہیں کیا گیا تھا۔

اس کا بہتر جواب multipart/form-data ہے۔ یہ فارمیٹ ایک ہی HTTP ریکویسٹ کو الگ الگ حصوں کے مجموعے کے طور پر دیکھتا ہے، جن میں سے ہر حصہ ایک منفرد باؤنڈری اسٹرنگ (boundary string) سے الگ ہوتا ہے۔ ایک حصہ سادہ ٹیکسٹ فیلڈ ہو سکتا ہے۔ اگلا حصہ ایک خام بائنری امیج ہو سکتا ہے، جس کے اپنے Content-Type اور Content-Disposition ہیڈرز ہوں۔ سرور آنے والے اسٹریم کو ترتیب وار پڑھتا ہے، باؤنڈری مارکرز کا انتظار کرتا ہے، اور ہر حصے کو متعلقہ ہینڈلر کے حوالے کر دیتا ہے، بغیر اس کے کہ پورے پے لوڈ کو ٹیکسٹ کے ایک ہی بلاک کے طور پر سمجھنا پڑے۔

Node.js میں، یہ فرق خاص طور پر اہم ہے۔ express.json() مڈل ویئر JSON باڈیز کو پارس کرنا جانتا ہے، لیکن یہ فائل اسٹریمز کو ہینڈل نہیں کرتا۔ multipart اپ لوڈز کو پروسیس کرنے کے لیے، آپ کو Multer یا Busboy جیسے اسٹریمنگ پارسر کی ضرورت ہوتی ہے۔ یہ ٹولز خام ریکویسٹ اسٹریم سے جڑ جاتے ہیں اور اسے ٹکڑے ٹکڑے (chunk by chunk) کر کے پڑھتے ہیں۔ مثال کے طور پر، Multer آپ کو یہ منتخب کرنے کی اجازت دیتا ہے کہ آنے والی فائلوں کو ڈسک پر ایک عارضی فولڈر میں لکھا جائے یا چھوٹی فائلوں کو میموری میں رکھا جائے۔ یہ کنفیگریشن کا فیصلہ بہت اہمیت رکھتا ہے۔ اگر آپ سب کچھ میموری میں رکھتے ہیں اور آپ کی ایپ کو اچانک کئی بڑی فائلیں موصول ہوتی ہیں، تو آپ کا پروسیس ہیپ سپیس (heap space) ختم ہونے کی وجہ سے کریش ہو سکتا ہے۔ ڈسک پر لکھنا استحکام کے لیے I/O کے بدلے سمجھوتہ کرتا ہے، لیکن یہ صفائی (cleanup) اور پاتھ سیکیورٹی کے بارے میں اپنے سوالات بھی پیدا کرتا ہے۔

ایک چھوٹی ایپلی کیشن کے لیے، فائلوں کو ./uploads جیسے مقامی فولڈر میں محفوظ کرنا فطری اور تیز محسوس ہوتا ہے۔ فائل اسی مشین پر پڑتی ہے جہاں آپ کا کوڈ چل رہا ہوتا ہے، اور اسے واپس فراہم کرنا محض صحیح پاتھ کی طرف اشارہ کرنے کا معاملہ ہے۔ یہ تب تک کام کرتا ہے جب تک کہ یہ کام کرنا چھوڑ نہ دے۔

جس لمحے آپ دوسرے ایپلی کیشن سرور کے سامنے لوڈ بیلنسر (load balancer) لگاتے ہیں، مقامی اسٹوریج ایک مسئلہ (bug) بن جاتا ہے۔ ایک صارف پروفائل پکچر اپ لوڈ کرتا ہے۔ لوڈ بیلنسر ریکویسٹ کو Server A کی طرف بھیجتا ہے، اور فائل Server A کی ڈسک پر لکھی جاتی ہے۔ بعد میں، وہی صارف تصویر دیکھنے کی درخواست کرتا ہے، لیکن لوڈ بیلنسر ریکویسٹ کو Server B کی طرف بھیج دیتا ہے۔ Server B اپنے فائل سسٹم کو چیک کرتا ہے اور اسے کچھ نہیں ملتا۔ فائل عملی طور پر غائب ہو چکی ہوتی ہے۔ آپ صارف کو ایک ہی مشین تک محدود رکھنے کے لیے 'اسٹکی سیشنز' (sticky sessions) کا استعمال کر سکتے ہیں، لیکن یہ ایک کمزور حل ہے۔ اگر Server A ری اسٹارٹ ہوتا ہے، دوبارہ ڈیپلائے ہوتا ہے، یا آٹو اسکیلنگ انسٹنس سے تبدیل ہو جاتا ہے، تو ڈیٹا غائب ہو جاتا ہے۔ کنٹینرائزڈ ماحول میں، مقامی ڈسک مزید عارضی (ephemeral) ہوتی ہے۔ Docker کنٹینر کا فائل سسٹم عارضی ہونے کے لیے بنایا گیا ہے۔ اسے مستقل اسٹوریج کے طور پر استعمال کرنا صارف کا ڈیٹا کھونے کا ایک یقینی طریقہ ہے۔

معیاری حل یہ ہے کہ کمپیوٹ (compute) کو اسٹوریج سے الگ کر دیا جائے۔ آپ اپنے ایپلی کیشن سرورز کو اسٹیٹ لیس (stateless) رکھتے ہیں اور اپ لوڈ کی گئی فائلوں کو AWS S3 یا Google Cloud Storage جیسی مخصوص آبجیکٹ اسٹوریج پر بھیج دیتے ہیں۔ یہ سروسز پائیداری، جغرافیائی تقسیم، اور بڑے پیمانے پر بیک وقت کام کرنے (concurrency) کے لیے بنائی گئی ہیں۔ ایپلی کیشن سرور ریکویسٹ کو ہینڈل کرتا ہے، میٹا ڈیٹا کی تصدیق کرتا ہے، اور پھر بائٹس کو اس انفراسٹرکچر کے حوالے کر دیتا ہے جو خاص طور پر انہیں محفوظ رکھنے کے لیے ڈیزائن کیا گیا ہے۔

تاہم، اگر آپ اسے لاپرواہی سے نافذ کرتے ہیں تو یہ پیٹرن بھی ایک رکاوٹ (bottleneck) پیدا کرتا ہے۔ بہت سی ٹیمیں اس طرح آغاز کرتی ہیں کہ براؤزر فائل کو بیک اینڈ پر اپ لوڈ کرتا ہے، اور پھر بیک اینڈ ہر ایک بائٹ کو آبجیکٹ اسٹوریج (object storage) پر آگے بھیجتا ہے۔ اگر کوئی صارف پانچ سو میگا بائٹ کی ویڈیو اپ لوڈ کرتا ہے، تو آپ کا سرور ایک درمیانی آدمی (middleman) بن جاتا ہے۔ یہ فائل کو اندر کھینچنے کے لیے بینڈوڈت (bandwidth) استعمال کرتا ہے، اور پھر اسے S3 پر بھیجنے کے لیے مزید بینڈوڈت استعمال کرتا ہے۔ ٹرانسفر کے پورے دورانیے کے دوران کنکشن کھلا رہتا ہے۔ خراب نیٹ ورک کنڈیشنز والے صارفین کی طرف سے سست اپ لوڈز منٹوں تک سرور کنکشنز کو مصروف رکھ سکتے ہیں۔ اگر سرور اسٹریم کو بفّر (buffer) کرتا ہے تو میموری کا استعمال بڑھا رہتا ہے، اور اگر آپ میٹرڈ ہوسٹنگ (metered hosting) پر چل رہے ہیں، تو آپ ایک ہی ڈیٹا ٹرانسفر کے لیے دو بار ادائیگی کر رہے ہوتے ہیں۔ ہوریزونٹل اسکیلنگ (Horizontal scaling) اس کا حل نہیں ہے، کیونکہ آپ جو بھی اضافی سرور شامل کرتے ہیں وہ اب بھی ان بائٹس کو لے جانے میں پھنس جاتا ہے جنہیں دیکھنے کی اسے ضرورت نہیں ہے۔

جدید سسٹمز ڈیٹا پاتھ سے بیک اینڈ کو مکمل طور پر ہٹا کر اس مسئلے کو حل کرتے ہیں۔ فائل قبول کرنے کے بجائے، بیک اینڈ صرف اپ لوڈ کرنے کی اجازت کے لیے ایک درخواست قبول کرتا ہے۔ یہ عمل کچھ اس طرح نظر آتا ہے:

  • براؤزر بیک اینڈ سے اپ لوڈ شروع کرنے کے لیے کہتا ہے، عام طور پر صرف فائل کا نام، فائل کی قسم، اور مقصد بھیجتا ہے۔
  • بیک اینڈ صارف کی تصدیق (authenticate) کرتا ہے، بزنس رولز کے مطابق درخواست کی توثیق کرتا ہے، اور آبجیکٹ اسٹوریج فراہم کنندہ سے ایک عارضی presigned URL تیار کرنے کے لیے SDK کا استعمال کرتا ہے۔
  • بیک اینڈ وہ URL براؤزر کو واپس کر دیتا ہے۔ یہ URL ایک مخصوص bucket اور key تک محدود ہوتا ہے، پانچ یا پندرہ منٹ جیسے مختصر وقت کے لیے کارآمد ہوتا ہے، اور ایک ایسے ٹوکن کے ساتھ سائن شدہ ہوتا ہے جو صرف ضروری اجازتیں دیتا ہے۔
  • براؤزر ایک معیاری PUT یا POST کا استعمال کرتے ہوئے فائل کو براہ راست S3 یا GCS پر اپ لوڈ کرتا ہے۔ بائٹس صارف کے ڈیوائس سے براہ راست