2

I'm struggling with an issue with HapiJS 11.1.4 and replying with a stream to serve the browser a file. The resulting download is corrupt.

The main endpoint is /api/library/export and uses ExcelJS to generate a stream containing data for an XLSX file, which I'm then replying with.

This endpoint works as expected when accessed using Curl and no corruption occurs. It is purely only the browser I'm having issues with.

Code for /api/library/export endpoint:

handle(request, reply) {
  // Invoke XLSX generation and reply
  mapRecordsToXlsx(xlsxOpts).then(stream => {
    console.log(stream.length);
    return reply(stream)
    // .type('application/octet-stream')
    // .type('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
    // .header('Content-Disposition', 'attachment; filename=library-export.xlsx;')
    // .header('Content-Length', stream.length)
    // .encoding('binary')
    // .encoding(null)
  });
}

As you can see, I've tried various combinations of response headers, to no avail.

Code for mapRecordsToXlsx:

export function mapRecordsToXlsx(opts) {
  const workbook = new Excel.stream.xlsx.WorkbookWriter();
  workbook.created = new Date();
  workbook.modified = new Date();

  // Iterate through each set of mappings and records
  opts.forEach(({title, mappings, types, records}) => {
    // Create a worksheet for the current set
    const sheet = workbook.addWorksheet(title);
    // Set column headers from mapping
    const headers = Object.keys(mappings);
    sheet.columns = headers.map(header => {
      return { header, key: mappings[header] };
    });

    // Generate rows for each record in the current sheet
    records.forEach(record => {
      const row = {};
      headers.forEach(header => {
        let value = _.get(record, mappings[header]);
        // Detect custom types and apply appropriate transformations
        if (types[header]) {
          switch (types[header]) {
          case 'date':
            value = new Date(Date.parse(value)); // ExcelJS wants a Date object
            break;
          default:
            break;
          }
        }
        row[mappings[header]] = value;
      });
      sheet.addRow(row).commit();
    });

    sheet.commit();
  });

  return workbook.commit().then(() => {
    return workbook.stream.read();
  });
}

And, on the receiving side (EmberJS):

  exportAll() {
    const searchString = this.get('searchString');
    if (searchString.length > 0) {
      this.set('loading', true);
      this.get('library').exportAll(searchString)
      .then(xlsx => {
        this.set('loading', false);
        const fileName = `Library - ${searchString}.xlsx`;
        // Hack to force download of file
        const a = document.createElement('a');
        document.body.appendChild(a);
        a.style = 'display: none';
        const blob = new Blob([xlsx], {type: 'octet/stream'});
        const url = window.URL.createObjectURL(blob);
        a.href = url;
        a.download = fileName;
        a.click();
        window.URL.revokeObjectURL(url);
        return a.parentNode.removeChild(a);
      });
    }
  }

this.get('library').exportAll(searchString) is purely an abstraction over Ember.$.ajax() which resolve/rejects as a promise.

A comparison of the resulting files, browser (left) vs curl (right):

A comparison of the resulting files: browser (left) vs curl (right)

The encoding looks wrong but I can't figure out how it is happening.

This may also help you to help me:

HTTP/1.1 200 OK
vary: origin,accept-encoding
access-control-allow-origin: http://localhost:4200
access-control-expose-headers: WWW-Authenticate,Server-Authorization
content-type: application/octet-stream
cache-control: no-cache
content-length: 10157
accept-ranges: bytes
Date: Tue, 15 Aug 2017 13:49:50 GMT
Connection: keep-alive

Content-Length matches what is being logged by HapiJS.

kurt343
  • 145
  • 1
  • 13
  • I've noticed since my original post that the filesizes are different despite content-length matching for browser & server. Corrupt was 18KB while the working copy is 10KB. – kurt343 Aug 16 '17 at 14:45
  • The problem is compression. Hapi is not gzipping the stream automatically yet the browser is always gunzipping (which causes the larger file size). I've tested by downloading via browser and then using CLI gzip on the resulting download. – kurt343 Aug 17 '17 at 12:33
  • 1
    Did you manage to fix the problem? – Michał Miszczyszyn Jan 17 '20 at 18:20

0 Answers0