Заглавието и на мен не ми говори нищо, за това ще се опитам да обясня с прости думи казуса.
Наскоро забелязах, че при изпращането на голям скрийншот в http://screenshoot.me се случват някакви неприятни неща. По-точно грешка 413 Request entity too large. Оказа се, че хостингът ми не позволява изпращане на POST заявки с параметри по-големи от 1-2мб. За сметка на това пък позволява качване на файлове до 20мб. До тук единственото нещо, което ме притесняваше е дали може да “симулирам” изпращане на файл чрез AJAX. Оказа се че може. FUCK YEAH!
Всъщност може да си префасонирате цялата заявка както ви скимне, което е много готино и същевремено много жалко, защото десетките хиляди фронт-енд дивелъпъри, ползващи jQuery и нямащи понятие от javascript никога няма да узнаят за този факт.
Вероятно бъркам, просто се опитвам да си обясня защо на всяко интервю винаги питат “а ти с jQuery знаеш ли как да работиш” все едно манюъла е 500 страници и ти трябва висше :).
Та реших да разширя леката библиотечка (“парче код” е правилната дума), която ползвам за AJAX обаждания и съответно резултата е тук – https://github.com/lucho870601/ajax-blob-upload
Накратко:
jx.generateBoundary = function(fieldsData) { // Функция, която генерира уникален разделител валиден за всеки фрагмент var boundary = parseInt(Math.random()*Math.pow(10, 16)).toString(36) + '' + parseInt(Math.random()*Math.pow(10, 16)).toString(36); for (var i = 0; i < fieldsData.length; i++) { if (fieldsData[i].indexOf(boundary) > -1) { // generate new boundary and check all fields again boundary = parseInt(Math.random()*Math.pow(10, 16)).toString(36) + '' + parseInt(Math.random()*Math.pow(10, 16)).toString(36); i = 0; } } return boundary; }; jx.loadFile = function(url, fileData, fileName, callback, opt) { var http = this.init(); //The XMLHttpRequest object is recreated at every call - to defeat Cache problem in IE if(!http||!url) return; var parts = url.split('?'); var url = parts[0]; var parameters = parts[1] ? parts[1].split('&') : []; var fieldsData = [fileData]; for (var i = 0; i < parameters.length; i++) { fieldsData.push(parameter[i][1]); } var boundary = this.generateBoundary(fieldsData); var body = ''; for (var i = 0; i < parameters.length; i++) { // строим фрагментите за данни var p = parameters[i].split('='); body += "--" + boundary + "\r\n\ Content-Disposition: form-data; name='"+p[0]+"'\r\n\ \r\n\ "+(p[1] || '')+"\r\n"; } // фрагмента за псевдо-файла body += "--" + boundary + "\r\n\ Content-Disposition: form-data; name='" + fileName + "'; filename='" + fileName + "'\r\n\ Content-Type: application/octet-stream\r\n\ \r\n\ "+ fileData + "\r\n\ --" + boundary + "--\r\n"; http.open("POST", url, true); http.setRequestHeader("Content-Type", "multipart/form-data; boundary="+boundary); http.setRequestHeader("Content-Length", body.length); http.setRequestHeader("Connection", "close"); ... http.send(body); };
… и получавате данните си като файл накрая.
Кофтито в цялата история е, че не мога да убедя браузъра ми да не слага “charset” и вероятно заради това blob-а пристига в съвсем друг вид. За това пък решението е доста лесно – кодиране на бинарните данни в base64. Неприятно е, че request-а ще набъбне с 1/3 повече и ще трябва да отворите и decode-нете файла сами. На теория декодирането на файла трябва да стане автоматично с Content-Transfer-Encoding директива, но на практика не стана 😀
Удачно е да ползвате този подход не само при гореописания проблем, но и винаги, когато пращате индустриални количества бинарни данни, защото получаването на файл е по-бързо и ангажира по-малко ресурси отколкото получаването на данните като параметър. Поне така ми се струва 😛
П.П. Преди да имплементирам алтернативното решение се помъчих да увелича лимита на размера на POST заявките с конфигуриране на .htaccess и ini_set на разни магически PHP параметри, но непотръгна.
September 5th, 2015 00:08
Real brain power on diyplas. Thanks for that answer!