Safari 11.1: сообщение формы ajax/XHR завершается неудачей, когда вход [type = file] пуст

ОБНОВЛЕНИЕ: по версии Webkit r230963 эта проблема была решена в Webkit.

===========

Начиная с недавнего обновления Safari 11.1 на macOS и iOS, а также в Safari Technology Preview 11.2, вызовы $.ajax в моем веб-приложении терпят неудачу, когда поле input[type=file] не имеет выбранного файла (это не требуется в моей форме). Никакой сбой, если в поле есть выбранный файл.

Обратный вызов error запуска ajax и консоли Safari содержит следующее сообщение: Failed to load resource: The operation couldnt be completed. Protocol error Failed to load resource: The operation couldnt be completed. Protocol error. Я HTTPS и отправляю в место в том же домене (и сервере) также через HTTPS.

Перед обновлением 11.1 вызов $.ajax передавался просто отлично, когда не был выбран файл. В последних версиях Chrome и Firefox нет проблем.

Соответствующие части моего кода:

Вход:

Browse... <input id="file-upload" type="file" name="image" accept=".jpg,.jpeg">

JS:

var formData = new FormData($(this)[0]);
$.ajax({
    type: 'POST',
    enctype: 'multipart/form-data',
    url: '../process.php',
    data: formData,
    contentType: false,
    processData: false,
    cache: false,
    success: function(response) { ... },
    error: function() { //my code reaches here }
});

Как временное (надеюсь) решение, я обнаруживаю пустое поле файла и formData его из formData перед вызовом ajax и все работает как ожидалось/раньше:

$("input[type=file]").each(function() {
    if($(this).val() === "") {
        formData.delete($(this).attr("name"));
    }
});

Я что-то делаю неправильно, есть ли проблема с Safari, или есть изменения в Safari, которые нужно учитывать сейчас в вызовах ajax?

Ответы

Ответ 1

С Webkit build r230963 эта проблема была решена в Webkit. Я загрузил и запустил эту сборку и подтвердил, что проблема решена. Не идея, когда публичный выпуск будет доступен для Safari, который содержит это исправление.

Ответ 2

UPDATE: старый ответ НЕ работает в Firefox.

Firefox возвращает только пустую строку для FormData.get() в пустое поле файла (вместо объекта File в других браузерах). Поэтому при использовании старого обходного пути пустой <input type="file"> будет отправлен как пустой <input type="text">. К сожалению, после создания объекта FormData невозможно отличить пустой файл от пустого текста.

Вместо этого используйте это решение:

var $form = $('form')
var $inputs = $('input[type="file"]:not([disabled])', $form)
$inputs.each(function(_, input) {
  if (input.files.length > 0) return
  $(input).prop('disabled', true)
})
var formData = new FormData($form[0])
$inputs.prop('disabled', false)

Демо-версия: https://jsfiddle.net/ypresto/05Lc45eL/

Для среды, отличной от jQuery:

var form = document.querySelector('form')
var inputs = form.querySelectorAll('input[type="file"]:not([disabled])')
inputs.forEach(function(input) {
  if (input.files.length > 0) return
  input.setAttribute('disabled', '')
})
var formData = new FormData(form)
inputs.forEach(function(input) {
  input.removeAttribute('disabled')
})

Для Rails (rails-ujs/jQuery-ujs): https://gist.github.com/ypresto/cabce63b1f4ab57247e1f836668a00a5


Старый ответ:

Фильтрация FormData (в ответе Ravichandra Adiga) лучше, потому что она не манипулирует никаким DOM.

Но порядок частей в FormData гарантированно будет одним и тем же порядком ввода элементов в форме согласно спецификации <form>. Это может привести к другой ошибке, если кто-то полагается на эту спецификацию.

Ниже фрагмента будет сохранен порядок FormData и пустая часть.

var formDataFilter = function(formData) {
    // Replace empty File with empty Blob.
  if (!(formData instanceof window.FormData)) return
  if (!formData.keys) return // unsupported browser
  var newFormData = new window.FormData()
  Array.from(formData.entries()).forEach(function(entry) {
    var value = entry[1]
    if (value instanceof window.File && value.name === '' && value.size === 0) {
      newFormData.append(entry[0], new window.Blob(), '')
    } else {
      newFormData.append(entry[0], value)
    }
  })
  return newFormData
}

Пример Live здесь: https://jsfiddle.net/ypresto/y6v333bq/

Для Rails см. Здесь: https://github.com/rails/rails/issues/32440#issuecomment-381185380

(ПРИМЕЧАНИЕ: iOS 11.3 Safari имеет эту проблему, но 11.2 - нет.)

Ответ 3

Для обходного пути я полностью удаляю файл типа ввода из DOM с помощью метода jQuery remove().

$("input[type=file]").each(function() {
    if($(this).val() === "") {
        $(this).remove();
    }
});

Ответ 4

Я работал над тем, что похоже на ту же проблему в Perl-программе

Обработка multipart/form-data в Perl вызывает Apache-ошибку с Apple-устройствами, когда элемент файл формы пуст

Обходное решение удаляет элементы формы до назначения formdata:

$('#myForm').find("input[type='file']").each(function(){
    if ($(this).get(0).files.length === 0) {$(this).remove();}
});
var fData = new FormData($('#myForm')[0]);
...

Ответ 5

    var fileNames = formData.getAll("filename[]");
    formData.delete("filename[]");
    jQuery.each(fileNames, function (key, fileNameObject) {
        if (fileNameObject.name) {
            formData.append("filename[]", fileNameObject);
        }
    });

Попробуй это !!

Ответ 6

Это работает для меня, чтобы проверить, пусто ли поле ввода. Если пусто, отключите поле ввода перед созданием FormData. После создания FormData удалите атрибут "disabled". Разница с другими ответами заключается в том, что я ищу "вход [0].files.length == 0".

// get the input field with type="file"
var input = $('#myForm').find("input[type='file']")

// add the "disabled" attribute to the input
if (input[0].files.length == 0) {
  input.prop('disabled', true);
}

// create the formdata  
var formData = new FormData($(this)[0]);

// remove the "disabled" attribute
input.prop('disabled', false);