Рекурсивное получение счетов в формате PDF с помощью JavaScript и Node.js (Meteor)

Мы разрабатываем приложение с интеграцией Recurly и пытаемся использовать его функцию PDF Invoice.

Приложение основано на Node.js (платформа Meteor).

Он получает правильный ответ от Recurly с бинарным файлом:

Рекурсивно

Но я не могу правильно сохранить его. Я пробовал два способа: распечатать на стороне клиента в браузере:

var file = window.URL.createObjectURL(new Blob([r.content], {type: "application/pdf"}));
  var a = document.createElement("a");
  a.href = file;
  a.download = "invoicePDF";
  document.body.appendChild(a);
  a.click();

  window.onfocus = function () {
   document.body.removeChild(a)
  }

И сохраните его прямо на сервере (просто для теста):

var fs = require('fs');
var wstream = fs.createWriteStream('C:/recurly.pdf');
wstream.write(result.content);
wstream.end();

Но в обоих случаях я получил нерабочий файл PDF. Acrobat, Foxit reader и Chrome не могут открыть этот файл — он поврежден.

У вас есть предложения, где я ошибаюсь? Может быть, мне нужно преобразовать контент перед его сохранением или что-то еще?

Добавлено

Результат этого запроса я отправил клиенту и распечатал в консоли (изображение выше).

try {
      result = HTTP.call(
        'GET',
        'https://' + Meteor.settings.recurly.SUBDOMAIN + '.recurly.com/v2/invoices/' + invoiceId,
        {
          headers: {
            Authorization: "Basic " + (new Buffer(Meteor.settings.recurly.API_KEY)).toString('base64'),
            Accept: 'application/pdf'
          }
        }
      );
    } catch (err) {
      result = e;
    }

person Nikolay Aleshkovskiy    schedule 27.06.2016    source источник
comment
Как вы получили данные? Вы установили blob responseType?   -  person MasterAM    schedule 28.06.2016
comment
@MasterAM Я использовал recurly docs для получения данных. dev.recurly.com/docs/lookup-invoice-details   -  person Nikolay Aleshkovskiy    schedule 28.06.2016
comment
Это не включает JS API. Можете ли вы опубликовать код?   -  person MasterAM    schedule 28.06.2016
comment
@MasterAM Я добавил пример кода в сообщение   -  person Nikolay Aleshkovskiy    schedule 28.06.2016


Ответы (1)


Проблема в том, что вы пытаетесь получить двоичный файл с запросом, который ожидает закодированную строку в качестве ответа.

Лучше всего сообщить библиотеке запросов, чего она должна ожидать, иначе вам придется вручную извлекать двоичные данные из строки, закодированной UTF-16 или UTF-8.

Существует разница между реализациями клиента и сервера.


сервер

Реализация сервера использует модуль узла request. Вы можете предоставить параметры, используя npmRequestOptions.

Как указано в его документации:

encoding — кодировка, которая будет использоваться в setEncoding данных ответа. Если null, тело возвращается как буфер. Все остальное (включая значение по умолчанию undefined) будет передано в качестве параметра кодирования toString() (это означает, что по умолчанию это фактически utf8). (Примечание: если вы ожидаете двоичные данные, вы должны установить кодировку: null.)

Итак, на сервере вы можете сделать что-то вроде:

try {
  result = HTTP.call(
    'GET',
    'https://' + Meteor.settings.recurly.SUBDOMAIN + '.recurly.com/v2/invoices/' + invoiceId,
    {
      headers: {
        Authorization: "Basic " + (new Buffer(Meteor.settings.recurly.API_KEY)).toString('base64'),
        Accept: 'application/pdf'
      },
      npmRequestOptions: {
        encoding: null // will cause the result to be stored in a binary Buffer
      }
    }
  );

  // will write the file in binary mode
  fs.writeFile(outFileName, res.content, 'binary');
} catch (err) {
  result = e;
}

клиент

Реализация клиента использует XHR.

Для обработки двоичного ответа вам необходимо изменить responseType до (предпочтительно) 'blob'.

К сожалению, я не вижу способа получить двоичный двоичный объект в текущей реализации HTTP-пакета Meteor, поскольку он ожидает, что ответ будет иметь responseText.

Вы можете напрямую использовать объект XMLHttpRequest, но вам может понадобиться добавить некоторый код оболочки для поддержки умирающих браузеров (IE6, я смотрю на вас! - обычный танец new ActiveXObject('Microsoft.XMLHttp');).

Этого можно добиться, используя что-то вроде следующего кода:

var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.setRequestHeader('Authorization', ...);
xhr.responseType = 'blob'; // this is key

xhr.onload = function(e) {
  if (this.status == 200) {
    // this.response is a Blob. If you are sure that it is of the
    // correct content-type, you can use it to construct the URL directly
    let blob = new Blob([this.response], {type: 'application/pdf'});
    let url = URL.createObjectURL(blob);
    let a = document.createElement("a");
    a.href = url;
    a.download = "invoice.pdf";
    document.body.appendChild(a);
    ...
  }
};

xhr.send();

Который отправляет XHR, который кодирует ответ в двоичный BLOB-объект и генерирует ObjectURL с правильными данными.

person MasterAM    schedule 28.06.2016
comment
большое спасибо! encoding: null — был ключ, теперь я корректно получаю pdf сервером. Пока я использую кодировку base64 для результата запроса return new Buffer(result.content).toString('base64'); А затем открываю его с помощью JavaScript на клиенте window.open("data:application/pdf;base64, " + r); Но насколько я знаю, это не лучший вариант? Я исследовал несколько способов передачи двоичных файлов из Node.js в клиент, но пока не нашел решения. У вас есть идеи, как лучше всего передать pdf с клиента на сервер? В любом случае, еще раз спасибо за помощь! - person Nikolay Aleshkovskiy; 28.06.2016