Реагируйте на загрузку отдельных чанков в CDN ... отправка и получение несоответствия размера файла.Как рассчитать размер, который получит сервер? - PullRequest
0 голосов
/ 21 ноября 2018

Наше приложение React Native позволяет пользователю записывать видео (от 3 до 90 секунд), а затем наше приложение выполняет загрузку по частям в наш сервис CDN (Cloudinary).Как часть фрагментированного запроса на загрузку, каждая загрузка должна включать заголовок, который указывает диапазон байтов и общий размер.Например: 'bytes: 0-5999999 / 6634945'

В React Native не существует простого способа выполнить загрузку по частям, но мы нашли библиотеку rn-fetch-blob (https://github.com/joltup/rn-fetch-blob),это позволяет нам читать записанный файл как поток base64 в указанном нами размере буфера (6 000 000 в нашем случае), а затем мы используем их функцию выборки для загрузки фрагментов.

Для того, чтобы мы могли использоватьнад библиотекой мы предпринимаем следующие шаги:

  1. чтение потока видеофайлов в количестве 6 000 000 штук (эти 6 000 000 штук предоставлены нам в виде строки base64. Длина строки составляет 8 000 000)
  2. Чтобы отправить base64 в нашу CDN, нам нужно добавить «data: video / mp4; base64» к строке base64, а затем URI экранировать строку
  3. Мы отправляем данные и некоторыесвяжите информацию тела с CDN как multipart / form-data

Однако мы наблюдаем расхождения в размере. Мы отправляем не то, что они получают. Например, двоичный фрагмент размером 6 000 000 байт (который идетTчерез вышеупомянутые преобразования) получает на стороне CDN как нечто большее.

Разные блоки размером 6 МБ приводят к разным размерам на сервере (один и тот же кусок данных дает одинаковую разницу в размерах на сервере ... это делаетсмысл).Например, когда мы отправляем фрагмент размером 6 МБ, сервер получает его как 6 209 890 или 6 220 496 и т. Д. Соотношение полученных: отправленных из того, что я наблюдал, составляет 1,031: 1,038.

Я даже протестировалЧастичные загрузки с сервером Express, который я настроил локально.Я использую библиотеку Multer.Я вижу те же несоответствия размера и те же отношения.

Вот некоторая информация со стороны React Native, которую я регистрирую:

** stat() of file:  Object {lastModified: 1542825371000, size: 6634945, type: "file", path: "/storage/emulated/0/Movies/VID_20181121_103601.mp4", filename: "VID_20181121_103601.mp4"}
** readSTreamProm:  Promise {_40: 0, _65: 1, _55: RNFetchBlobReadStream, _72: null}
** we got a stream:  RNFetchBlobReadStream {tick: 10, encoding: "base64", bufferSize: 6000000, path: "file:///storage/emulated/0/Movies/VID_20181121_103601.mp4", closed: false…}
------------START--------------
** we got a chunk: number:  0
** last 2 bytes of base64 chunk L W
** binary bytes calculation: 0-5999999/6634945
** reading binary chunk: 6000000
** base64 chunk.length:  8000000
** uri_encoded chunk size: 8505440 ... total size: 8505440
------------END--------------
------------START--------------
** we got a chunk: number:  1
** last 2 bytes of base64 chunk = =
** binary bytes calculation: 6000000-6634944/6634945
** reading binary chunk: 634945
** base64 chunk.length:  846596
** uri_encoded chunk size: 901714 ... total size: 9407154
------------END--------------

Вот console.log информации, которую дает multerнас из нашего запроса извлечения multipart / form-data выше (я добавил 2 комментария, подчеркивающих разницу в размере ... ищите '<=='): </p>

req.body:  { timestamp: '1542825377',
  signature: 'hidden_signature',
  eager: 'sp_full_hd_wifi/m3u8',
  eager_async: 'true',
  api_key: 'hidden_api_key' }
req.file:  { fieldname: 'file',
  originalname: 'upload.mp4',
  encoding: '7bit',
  mimetype: 'application/octet-stream',
  destination: 'uploads/',
  filename: '86cdf589e71f70fe9e09663066b1f635',
  path: 'uploads/86cdf589e71f70fe9e09663066b1f635',
  size: 655624 } <== the 634,945 is received as 655,624
req.headers:  { 'content-range': 'bytes 8505440-9407153/9407154',
  'x-unique-upload-id': 'ed4e59a2-cf41-4e62-b665-5297cb3aaa8b',
  'content-type': 'multipart/form-data; boundary=RNFetchBlob-qz6pzbtgv0novbetjxs9o',
  'content-length': '656482',
  host: 'hidden_host',
  'accept-encoding': 'gzip',
  'user-agent': 'okhttp/3.6.0',
  'x-forwarded-for': 'hidden_ip' }


req.body:  { timestamp: '1542825377',
  signature: 'hidden_signature',
  eager: 'sp_full_hd_wifi/m3u8',
  eager_async: 'true',
  api_key: 'hidden_api_key' }
req.file:  { fieldname: 'file',
  originalname: 'upload.mp4',
  encoding: '7bit',
  mimetype: 'application/octet-stream',
  destination: 'uploads/',
  filename: 'b988d1b514994c92b52d01fe0941eae3',
  path: 'uploads/b988d1b514994c92b52d01fe0941eae3',
  size: 6189548 } . <== the 6,000,000 is received as 6,189,548
req.headers:  { 'content-range': 'bytes 0-8505439/9407154',
  'x-unique-upload-id': 'ed4e59a2-cf41-4e62-b665-5297cb3aaa8b',
  'content-type': 'multipart/form-data; boundary=RNFetchBlob-u1xdhdoviih2udhzeocag',
  'content-length': '6190406',
  host: 'hidden_host',
  'accept-encoding': 'gzip',
  'user-agent': 'okhttp/3.6.0',
  'x-forwarded-for': 'hidden_ip' }

Что вызывает несоответствие размера междуотправляющий и получающий конец?Выяснение этого поможет мне рассчитать фактический размер для включения в заголовок для наших фрагментированных загрузок в CDN.

Вот код React Native ...

import RNFetchBlob from 'rn-fetch-blob'

export function upload_to_cloudinary(uri) {
  let base64_chunks = [] // we need to save it so that we can calculate new size and chunk sizes.
  const sending_chunk_size = 6000000 //each chunk must be larger than 5 Mb, except for the last one

  RNFetchBlob.fs.exists(uri)
  .then((exist) => {
    if (exist) {
      RNFetchBlob.fs.stat(uri)
      .then( stats => {
        console.log('** stat() of file: ', stats)

        const binary_total_size = stats.size

        let total_size = 0
        let chunk_num = 0
        let start_byte_num = 0
        let end_byte_num = 0

        // Now read in streams as base64
        const readStreamProm = RNFetchBlob.fs.readStream(uri, 'base64', sending_chunk_size)
        readStreamProm.then((stream) => {
          console.log('** we got a stream: ', stream)

          stream.open()
          stream.onData((chunk) => {
            console.warn('------------START--------------')
            console.log('** we got a chunk: number: ', chunk_num)
            console.warn('** last 2 bytes of base64 chunk', chunk[chunk.length-2],  chunk[chunk.length-1] )

            start_byte_num = (sending_chunk_size * chunk_num)
            end_byte_num = start_byte_num + sending_chunk_size  // need to account for last chunk
            end_byte_num = (end_byte_num > binary_total_size) ? binary_total_size : end_byte_num
            end_byte_num -= 1

            console.log(`** binary bytes calculation: ${start_byte_num}-${end_byte_num}/${binary_total_size}`)
            console.log(`** reading binary chunk: ${end_byte_num - start_byte_num + 1}`)
            console.log('** base64 chunk.length: ', chunk.length)

            chunk_num += 1

            const uri_encoded_chunk = encodeURIComponent('data:video/mp4;base64,' + chunk)
            total_size += uri_encoded_chunk.length
            base64_chunks.push(uri_encoded_chunk) //collect it
            console.log(`** uri_encoded chunk size: ${uri_encoded_chunk.length} ... total size: ${total_size}`)

            console.warn('------------END--------------')
          })

          stream.onEnd(() => {
            console.log('** finished reading streamed data')
            const shared_unique_id = uuid.v4()

            let start_byte_num = 0

            base64_chunks.forEach( (chunk, idx) => {
              // we're sending the start, end, and total bytes based on the base64 uri encoded data but this is not affecting the discrepancy on the server side
              upload_chunk(start_byte_num, start_byte_num+chunk.length-1, total_size, chunk, shared_unique_id, 'video')
              start_byte_num = start_byte_num+chunk.length
            })

          })
          // not related code intentionally left out
  })

}


function upload_chunk( start_byte_num, end_byte_num, total_size, uri_encoded_base64_data, shared_unique_id, type) {

  let timestamp = (Date.now() / 1000 | 0).toString()
  let hash_string = 'our_hash'
  let signature = CryptoJS.SHA1(hash_string).toString()
  let upload_url = 'our_cloudinary_url`

  return RNFetchBlob.fetch(
    'POST',
    upload_url,
    {
      'Content-Type': 'multipart/form-data',
      'X-Unique-Upload-Id': shared_unique_id, // The unique id associates all the chunks from the same file with each other.
      'Content-Range': `bytes ${start_byte_num}-${end_byte_num}/${total_size}`
    },
    [
    {
      name: 'file',
      filename: 'upload.mp4',
      data: uri_encoded_base64_data, //the mime type is in the data packet
    },
    {name: 'timestamp', data: timestamp },
    {name: 'signature', data: signature},
    {name: 'eager', data: 'sp_full_hd_wifi/m3u8'},
    {name: 'eager_async', data: 'true'},
    {name: 'api_key', data: our_api_key_hidden}
    ]
  )
}
...