it-swarm.com.ru

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

UPDATE: Начиная с сборка Webkit r230963 , эта проблема была решена в Webkit.

===========

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

Запускается обратный вызов error для ajax, и консоль Safari содержит следующее сообщение: Failed to load resource: The operation couldn’t 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 перед вызовом ajax, и все работает как ожидалось/до этого:

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

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

38
Matt.

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

5
Matt.

ОБНОВЛЕНИЕ: Старый ответ НЕ работает в 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 (в ответе Равичандры Адиги) лучше, потому что она не манипулирует 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
}

Живой пример здесь: https://jsfiddle.net/ypresto/y6v333bq/

Для Rails, смотрите здесь: https://github.com/Rails/rails/issues/32440#issuecomment-381185380

(ПРИМЕЧАНИЕ. В iOS 11.3 Safari есть эта проблема, а в 11.2 нет.)

18
ypresto

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

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

Я работал над тем же вопросом в Perl-программе

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

Обходной путь - удаление элементов формы перед назначением данных формы: 

$('#myForm').find("input[type='file']").each(function(){
    if ($(this).get(0).files.length === 0) {$(this).remove();}
});
var fData = new FormData($('#myForm')[0]);
...
2
Roger Van Montfort
    var fileNames = formData.getAll("filename[]");
    formData.delete("filename[]");
    jQuery.each(fileNames, function (key, fileNameObject) {
        if (fileNameObject.name) {
            formData.append("filename[]", fileNameObject);
        }
    });

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

1
Ravichandra Adiga

Это работает для меня, чтобы проверить, является ли поле ввода пустым. Если пусто, отключите поле ввода перед созданием FormData. После создания FormData удалите атрибут «disabled». Разница с другими ответами в том, что я ищу "input [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);
0
Martin