最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

How to validate content length of multipart form data in JavaScript? - Stack Overflow

programmeradmin0浏览0评论

I post FormData (including files) to a server that rejects requests with a content-length that exceeds a specific limit. I'd like to validate the content-length in my JavaScript client (browser) before doing a request that is doomed to be rejected. How do I get the content-length of my (multipart/form-data) encoded FormData object?

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 });
}

Edit: Thanks for your answer, @Indgalante. Just using length of a string and size of a file does not calculate the correct content length.

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('', { method: 'POST', body: formData });

I post FormData (including files) to a server that rejects requests with a content-length that exceeds a specific limit. I'd like to validate the content-length in my JavaScript client (browser) before doing a request that is doomed to be rejected. How do I get the content-length of my (multipart/form-data) encoded FormData object?

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 });
}

Edit: Thanks for your answer, @Indgalante. Just using length of a string and size of a file does not calculate the correct content length.

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/post', { method: 'POST', body: formData });

You did not consider that the form data is encoded in the request. Thereby, i.a. boundary is added. The calculated content-length in the example is 3, but should be 138 in my Chrome browser

and 172 in my Firefox browser. I'm unsure how other browsers behave.

Share Improve this question edited Jun 9, 2020 at 14:30 maiermic asked Jun 9, 2020 at 11:39 maiermicmaiermic 4,9849 gold badges42 silver badges82 bronze badges 2
  • Have you solved this? I face a related question in Safari. – daxvwv Commented Aug 18, 2020 at 10:57
  • @Soyaine Not really. I use an estimation as described in my answer stackoverflow./a/63471719/1065654 – maiermic Commented Aug 18, 2020 at 15:21
Add a ment  | 

3 Answers 3

Reset to default 2

I still don't know if it is possible to calculate the exact size, but you can at least try to estimate it:

/**
 * 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./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;
}

Here is a async version that first converts the FormData into a Blob. from which you can retrieve the actual size that is going to be sent to the server.

So instead of posting the formdata you send the generated blob instead.

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/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()

However this don't work in IE, and Safari

  • IE don't have fetch (but it's dead anyway)
  • Safari has this bugs.
    • https://bugs.webkit/show_bug.cgi?id=161190
    • https://bugs.webkit/show_bug.cgi?id=212858
    • https://bugs.webkit/show_bug.cgi?id=171581

all doe, replacing the formdata with a polyfilled version such as https://github./jimmywarting/FormData might assist you with converting a formdata into a blob directly (synchronous) without using the fetch api (using formData._blob()). that is if you need wider browser support

One approach could be FormData.entries() so you can iterate over all your data and get a final content length. We will also need a spread operator over an array or you could use Array.from() to convert the iterator returned by .entries() into a proper array.

Code example below, I didn't test it with files but let me know if you have any edge case with this one.

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
}
发布评论

评论列表(0)

  1. 暂无评论