تنظیم header برای استریم فایل‌های بزرگ در فریم ورک اسپرینگ (SpringFramework)

۱۵ دی ۱۳۹۵

پیش از هر چیز باید بگویم که این نوشته مربوط به برنامه‌نویسی در فریم‌ورک اسپرینگ است؛ اگر با این فریم‌ورک آشنایی ندارید، خواندن این نوشته‌ی من در مورد این چهارچوب برنامه‌نویسی می‌تواند برای شما مفید باشد. فرض کنید که می‌خواهیم از طریق یک rest، یک فایل را که در پایگاه داده یا دایرکتوری فایل وجود دارد را در client نمایش دهیم؛



برای این کار بایستی از HttpServletResponse استفاده کنیم و پس از set کردن ContentType و سایر propertyهای مورد نظر در Header پاسخ، فایل مورد نظر را به نحوی در کلاینت نمایش دهیم. برای Stream کردن فایل مورد نظر استفاده  از پایگاه داده یا دایرکتوری فایل FileInputStream مناسب به نظر می‌رسد؛ برای فایل‌هایی با حجم بالا (به طور مثال چند صد مگابایت، یک یا چند گیگابایت) نمی‌توان فایل را به صورت کامل خواند و در خروجی نوشت؛ در این حالت ما نیازمند یک buffer هستیم که با استفاده از یک حلقه‌ تکرار، Stream فایل را از دایرکتوری فایل به اندازه buffer خوانده و در خروجی بنویسد. قطعه کد زیر را در نظر بگیرید:


FileProvider file = iFileProviderService.loadByName(fileName);
String filePath = file.getFilePath();
FileInputStream inputStream = null;
try {
    inputStream = new FileInputStream(filePath + "/" + fileName);
    byte[] buffer = newbyte[4096];
    int bytesRead = 0;
    do {
        bytesRead = inputStream.read(buffer, 0, buffer.length);
        response.getOutputStream().write(buffer, 0, bytesRead);
    } while (bytesRead == buffer.length);
    response.setContentType(file.getFileType() + ";charset=UTF-8");
    response.setHeader("Content-Disposition", "inline; filename=" + fileName);
    // set other properties of response...
    response.getOutputStream().flush();
} catch (Exception e) {
    response.setStatus(HttpServletResponse.SC_NOT_FOUND);
} finally {
    if (inputStream != null)
       inputStream.close();
}

مشکلی که ممکن است در قطعه کد بالا بوجود آید احتمال set نشدن ContentType است؛ برای مثال فرض کنید یک فایل با فرمت PDF را می‌خواهیم در client نمایش دهیم؛ در مرورگر فایرفاکس همه چیز خوب پیش می‌رود و PDFViewer آن فایل  را به خوبی نمایش می‌دهد؛ اما در مرورگرهایی مثل کروم و اینترنت اکسپلورر، با باز کردن آدرس rest فایل در newTab، با فایل PDF به صورت یک فایل متنی برخورد می‌کنند و ما به جای دیدن فایل PDF، با تعدادی کاراکتر نامفهوم مواجه خواهیم شد. دلیل رخ دادن این مشکل احتمالا به نحوه‌ی برخورد و رندرینگ مرورگرهای مختلف در مواجهه با Response می‌تواند باشد؛ به عبارت دیگر رفتار مرورگرهای مختلف در برخورد با Response دریافتی متفاوت است؛ به عنوان مثال در مرورگر کروم احتمالا یک Header با مقادیر پیش‌فرض برای هر Response درنظر می‌گیرد و چون ما در حلقه‌ی while مشغول Stream فایل هستیم و کروم هیچ اطلاعی نسبت به اندازه‌ی Responseی که در حال دریافت شدن است، ندارد؛ پس ContentType پیش‌فرض که همان text/plain است را برای Header این Response درنظر می‌گیرد؛ حال آنکه در مرورگر فایرفاکس رندرینگ و نحوه‌ی برخورد با Response متفاوت بوده و دقبقا مطابق روند مورد انتظار می‌باشد.

اما راه‌حل پیشنهادی برای حل این مشکل بدین صورت است که قبل از فرستادن بدنه‌ی Response به سمت client ابتدا Header آن را set کنیم، بنابراین قطعه کد بالا به صورت زیر تغییر می‌کند:


FileProvider file = iFileProviderService.loadByName(fileName);
String filePath = file.getFilePath();
FileInputStream inputStream = null;
try {
    inputStream = new FileInputStream(filePath + "/" + fileName);
    byte[] buffer = newbyte[4096];
    int bytesRead = 0;
    response.setContentType(file.getFileType() + ";charset=UTF-8");
    response.setHeader("Content-Disposition", "inline; filename=" + fileName);
    // set other properties of response...
    do {
        bytesRead = inputStream.read(buffer, 0, buffer.length);
        response.getOutputStream().write(buffer, 0, bytesRead);
    } while (bytesRead == buffer.length);
    response.getOutputStream().flush();
} catch (Exception e) {
    response.setStatus(HttpServletResponse.SC_NOT_FOUND);
} finally {
    if (inputStream != null)
       inputStream.close();
}

با این تغییر مشاهده می‌شود که تمامی مرورگرهای یاد شده در این نوشته در برخورد با Response دریافتی که Stream فایل است، رفتار مشابهی دارند.

هیچ نظری موجود نیست: