【问题标题】:How to validate content length of multipart form data in JavaScript?如何在 JavaScript 中验证多部分表单数据的内容长度?
【发布时间】:2020-06-09 11:39:16
【问题描述】:

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

您没有考虑到表单数据在请求中进行了编码。因此,i.a.添加边界。示例中计算的内容长度为 3,但在我的 Chrome 浏览器中应为 138

和 172 在我的 Firefox 浏览器中。我不确定其他浏览器的行为。

【问题讨论】:

标签: javascript multipartform-data form-data content-length http-content-length


【解决方案1】:

我仍然不知道是否可以计算出确切的大小,但你至少可以尝试估计一下:

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

【讨论】:

    【解决方案2】:

    这是一个异步版本,它首先将 FormData 转换为 Blob。您可以从中检索将要发送到服务器的实际大小。

    所以不是发布表单数据,而是发送生成的 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 中不起作用

    当然,用诸如https://github.com/jimmywarting/FormData 之类的 polyfill 版本替换 formdata 可能会帮助您在不使用 fetch api(使用 formData._blob())的情况下直接(同步)将 formdata 转换为 blob。也就是说,如果您需要更广泛的浏览器支持

    【讨论】:

    • imo 我认为这有点矫枉过正,估计也足够了。
    【解决方案3】:

    一种方法可能是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
    }
    

    【讨论】:

    • 这里不考虑边界的大小。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多