Как проверить длину содержимого данных составной формы в JavaScript?

Я отправляю FormData (включая файлы) на сервер, который отклоняет запросы с длиной содержимого, превышающей определенный предел. Я хотел бы проверить длину содержимого в моем клиенте JavaScript (браузере) перед выполнением запроса, который обречен на отклонение. Как получить длину содержимого моего (multipart/form-data) закодированного объекта FormData?

const formData = new FormData();
formData.append('text', text);
formData.append('file', file);
if (getContentLength(formData) > limit) {
    alert('Content length limit is exceeded');
} else {
    fetch(url, { method: 'POST', body: formData });
}

Изменить: Спасибо за ответ, @Indgalante. Простое использование length строки и size файла не позволяет вычислить правильную длину содержимого.

function getContentLength(formData) {
  const formDataEntries = [...formData.entries()]

  const contentLength = formDataEntries.reduce((acc, [key, value]) => {
    if (typeof value === 'string') return acc + value.length
    if (typeof value === 'object') return acc + value.size

    return acc
  }, 0)

  return contentLength
}

const formData = new FormData();
formData.append('text', 'foo');
alert(`calculated content-length is ${getContentLength(formData)}`);
fetch('https://httpbin.org/post', { method: 'POST', body: formData });

Вы не учли, что данные формы закодированы в запросе. Тем самым, в т.ч. граница добавлена. Расчетная длина содержимого в примере равна 3, но в моем браузере Chrome она должна быть 138.

хром

и 172 в моем браузере Firefox. Я не уверен, как ведут себя другие браузеры.

Файрфокс


comment
Вы решили это? У меня возникает связанный с этим вопрос в Safari.   -  person Soyaine    schedule 18.08.2020
comment
@Soyaine Не совсем. Я использую оценку, как описано в моем ответе stackoverflow.com/a/63471719/1065654.   -  person maiermic    schedule 18.08.2020


Ответы (3)


Я до сих пор не знаю, можно ли вычислить точный размер, но вы можете хотя бы попытаться его оценить:

/**
 * Estimate the content length of (multipart/form-data) encoded form data
 * object (sent in HTTP POST requests).
 * We do not know if you can get the actual content length.
 * Hence, it is estimated by this function.
 * As soon as {@link https://stackoverflow.com/q/62281752/1065654 this}
 * question is answered (correctly), the correct calculation should be used.
 *
 * @param formData
 */
function estimateContentLength(formData: FormData) {
    // Seems to be 44 in WebKit browsers (e.g. Chrome, Safari, etc.),
    // but varies at least in Firefox.
    const baseLength = 50; // estimated max value
    // Seems to be 87 in WebKit browsers (e.g. Chrome, Safari, etc.),
    // but varies at least in Firefox.
    const separatorLength = 115; // estimated max value
    let length = baseLength;
    const entries = formData.entries();
    for (const [key, value] of entries) {
        length += key.length + separatorLength;
        if (typeof value === 'object') {
            length += value.size;
        } else {
            length += String(value).length;
        }
    }
    return length;
}
person maiermic    schedule 18.08.2020

Вот асинхронная версия, которая сначала преобразует FormData в Blob. из которого вы можете получить фактический размер, который будет отправлен на сервер.

Таким образом, вместо публикации данных формы вы отправляете сгенерированный большой двоичный объект.

async function test() {
  // Create some dummy data
  const fd = new FormData()
  fd.set('a', 'b')

  // acquire an actual raw bytes as blob of what the request would send
  const res = new Response(fd)
  const blob = await res.blob()

  blob.text && (
    console.log('what the actual body looks like'), 
    console.log(await blob.text())
  )

  // can't use own blob's type since spec lowercase the blob.type
  // so we get the actual content type
  const type = res.headers.get('content-type')

  // the acutal content-length size that's going to be used
  console.log('content-length before sending', blob.size)

  // verify
  const testRes = await fetch('https://httpbin.org/post', { 
    method: 'POST',
    body: blob, // now send the blob instead of formdata
    headers: { // use the real type (and not the default lowercased blob type)
      'content-type': type
    }
  })
  const json = await testRes.json()
  const headers = new Headers(json.headers)
  console.log('form that got posted:', JSON.stringify(json.form))
  console.log('content-length that was sent', headers.get('content-length'))
}

test()

Однако это не работает в IE и Safari.

all doe, заменив данные формы полифиллированной версией, такой как https://github.com/jimmywarting/FormData может помочь вам преобразовать данные формы в большой двоичный объект напрямую (синхронно) без использования API-интерфейса выборки (с использованием formData._blob()). это если вам нужна более широкая поддержка браузера

person Endless    schedule 19.08.2020
comment
imo я думаю, что это своего рода излишество, оценка также достаточно хороша. - person Endless; 20.08.2020

Одним из подходов может быть FormData.entries() , поэтому вы можете перебрать все свои данные и получить окончательную длину контента. Нам также понадобится spread operator над массивом, или вы можете использовать Array.from() для преобразования итератора, возвращаемого .entries(), в правильный массив.

Пример кода ниже, я не тестировал его с файлами, но дайте мне знать, если у вас есть крайний случай с этим.

function getContentLength(formData) {
  const formDataEntries = [...formData.entries()]

  const contentLength = formDataEntries.reduce((acc, [key, value]) => {
    if (typeof value === 'string') return acc + value.length
    if (typeof value === 'object') return acc + value.size

    return acc
  }, 0)

  return contentLength
}
person lndgalante    schedule 09.06.2020
comment
При этом не учитывается размер границы. - person maiermic; 09.06.2020