Трубопровод от бусбоя до запроса сообщения

У меня multipart/form-data, что я отправляю в экспресс-конечную точку /data/upload, разметку формы ниже:

form(enctype="multipart/form-data", action="/data/upload", method="post")
  input(type="file", name="data")

Я использую busboy для чтения потока файлов, который работает нормально. Оттуда я хочу отправить поток снова как multipart/form-data на второй Java-сервер, используя модуль request npm. JS-клиент/код сервера Java ниже:

  req.busboy.on('file', function (fieldName, fileStream, fileName, encoding, mimeType) {

    var reqBody = {
      url: server.baseURL + 'api/data',
      headers: {
        'Connection': 'keep-alive',
        'Content-Type': 'multipart/form-data'
      },
      formData: {
        file: fileStream
      }
    };

    request.post(reqBody, function (err, r, body) {
      // Do rendering stuff, handle callback
    });
 });

Конечная точка Java (api/data)

@POST
@Consumes(MediaType.MULTIPART_FORM_DATA)
public void addData(FormDataMultiPart formDataMultiPart) {
  // Handle multipart data here      
}

Я не думаю, что правильно отправляю файл как multipart/form-data здесь... но мне трудно понять, как по существу передать поток из busboy прямо на request без чтения/запись из временного файла на стороне клиента. Любые идеи?

Трассировка стека Java:

Apr 27, 2016 5:07:12 PM org.glassfish.jersey.filter.LoggingFilter log
INFO: 3 * Server has received a request on thread qtp1631904921-24
3 > POST http://localhost:8080/api/data
3 > Connection: keep-alive
3 > Content-Length: 199
3 > Content-Type: multipart/form-data; boundary=--------------------------331473417509479560313628
3 > Host: localhost:8080

Apr 27, 2016 5:07:12 PM org.glassfish.jersey.filter.LoggingFilter log
INFO: 3 * Server responded with a response on thread qtp1631904921-24
3 < 400

17:07:13.003 [qtp1631904921-24] WARN  org.eclipse.jetty.http.HttpParser parseNext - bad HTTP parsed: 400 No URI for [email protected]{r=1,c=false,a=IDLE,uri=null}

Рекомендуемое изменение Рахата:

 31     var reqBody = {
 32       url: server.baseURL + 'data',
 33       headers: {
 34         'Connection': 'keep-alive',
 35         'Content-Type': 'multipart/form-data'
 36       }
 37     };
 38 
 39     req.pipe(req.busboy.pipe(request.post(reqBody)));

Ошибка сброса:

Error: Cannot pipe. Not readable.
   at Busboy.Writable.pipe (_stream_writable.js:154:22)

Ответ 1

Проблема заключается в том, что вам необходимо предоставить "Content-Length" для многостраничной загрузки вручную, потому что request (и базовый form-data) не могут понять это сами. Таким образом, запрос отправляет недопустимый Content-Length: 199 (тот же для любого входящего размера файла), который разбивает многопараметрический парсер java.

Существует несколько способов обхода:

1) Использовать входящий запрос 'Content-Length'

request.post({
  url: server.baseURL + 'api/data',
  formData: {
    file: {
      value: fileStream,
      options: {
        knownLength: req.headers['content-length']
      }
    }
  }
}, function (err, r, body) {
  // Do rendering stuff, handle callback
})

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

2) Подождите, пока файл полностью буферизуется приложением node, а затем отправьте его в java

var concat = require('concat-stream')
req.busboy.on('file', function (fieldName, fileStream, fileName, encoding, mimeType) {
  fileStream.pipe(concat(function (fileBuffer) {
    request.post({
      url: server.baseURL + 'api/data',
      formData: {
        file: fileBuffer
      }
    }, function (err, r, body) {
      // Do rendering stuff, handle callback
    })
  }))
})

Это увеличит потребление памяти приложения, поэтому вам нужно быть осторожным и рассмотреть возможность использования лимитов busboy

3) Буферный файл на диск перед загрузкой (только для справки)

  • express + multer - я рекомендуем использовать экспресс для веб-серверов, это делает вещи более управляемыми, а multer основан на busboy
  • formidable

Ответ 2

Если возможно, отправьте собственный заголовок с точно размером файла (байты). Заголовок всегда может быть прочитан до потока полезной нагрузки дескриптора. Используйте это вместо заголовка длины содержимого предыдущего ответа, потому что иногда это не работает (с небольшими файлами, я думаю, но я не могу гарантировать, что это работает с большими файлами).

Ответ 3

@Afanasii Kurakin Я следил за опцией "Использовать входящий запрос Content-Length", чтобы отправить файл в файловую службу, и файл был успешно сохранен в файловой службе. Но у меня есть еще одна проблема, вы можете помочь мне решить. Проблема заключается в следующем: я использую multer в файловой службе, чтобы получить файл и сохранить его локально. Но после сохранения файла обратный вызов не запускается после того, как мультер завершил работу. Я покопался в исходном коде multer и обнаружил, что multer использует npm "on-done", чтобы проверить, завершается ли запрос и из-за этого вызывается обратный вызов, и я не знаю, как решить эту проблему.

Ответ 4

За ответ Афанасия Куракина

request.post({
  url: server.baseURL + 'api/data',
  formData: {
    file: {
      value: fileStream,
      options: {
        knownLength: req.headers['content-length']
      }
    }
  }
}, function (err, r, body) {
  // Do rendering stuff, handle callback
})

Вы должны изменить с req.headers['content-length'] на реальный размер файла, обычно длина содержимого из заголовка больше, чем размер файла. Я почувствовал боль из-за длины содержимого, и после использования размера файла все работало отлично.