2012-03-30 2 views
5

Я пытаюсь написать приложение, которое позволит моим пользователям загружать файлы в мою учетную запись Google Cloud Storage. Чтобы предотвратить перезапись и выполнить какую-то специальную обработку и протоколирование на моей стороне, я использую сервер Node.js в качестве посредника для загрузки. Таким образом, процесс:Node.js POST-файл на сервер

  1. добавления пользователя файл на Node.js сервере
  2. Node.js сервер разбирает файл, проверяет тип файла хранит некоторые данные в БД
  3. загрузки
  4. Node.js файловом сервере в GCS
  5. Node.js ответ сервера на запрос пользователя с годен/не годен замечание

Я получаю немного потерял на шаге 3, как именно отправить этот файл в ГКС. This question дает некоторое полезное представление, а также хороший пример, но я все еще смущен.

Я понимаю, что я могу открыть ReadStream для временного файла загрузки и трубы, которая находится в объекте http.request(). Я смущен, так как я могу указать в своем POST-запросе, что данные в каналах - это переменная file. Согласно GCS API Docs, должна быть переменная file, и она должна быть последней.

Итак, как указать имя переменной POST для данных, передаваемых по каналам?

Бонусные баллы, если вы можете сказать мне, как по конвейеру непосредственно от загрузки моего пользователя, а не хранить его во временный файл

ответ

4

Я считаю, что если вы хотите сделать POST, вы должны использовать Content-Type: multipart/form-data;boundary=myboundary заголовок. А потом, в теле, write() что-то подобное для каждого поля строки (разрывы строк должны быть \r\n):

--myboundary 
Content-Disposition: form-data; name="field_name" 

field_value 

А потом сам файл, write() что-то вроде этого к телу:

--myboundary 
Content-Disposition: form-data; name="file"; filename="urlencoded_filename.jpg" 
Content-Type: image/jpeg 
Content-Transfer-Encoding: binary 

binary_file_data 

binary_file_data где вы используете pipe():

var fileStream = fs.createReadStream("path/to/my/file.jpg"); 
fileStream.pipe(requestToGoogle, {end: false}); 
fileStream.on('end, function() { 
    req.end("--myboundary--\r\n\r\n"); 
}); 

{end: false} предотвращает pipe() от автоматического закрытия запроса, потому что вам нужно написать еще одну границу после того, как вы закончите отправку файла. Обратите внимание на дополнительные -- на конце границы.

Большая проблема заключается в том, что Google может потребовать заголовок content-length (весьма вероятно). Если это так, вы не можете передать POST от своего пользователя к POST в Google, потому что вы не сможете достоверно узнать, что такое content-length, пока вы не получите весь файл.

Значение заголовка content-length должно быть единственным номером для всего тела. Простой способ сделать это - вызвать Buffer.byteLength(body) на весь корпус, но это становится уродливым быстро, если у вас большие файлы, а также убивает потоки.В качестве альтернативы можно вычислить его следующим образом:

var body_before_file = "..."; // string fields + boundary and metadata for the file 
var body_after_file = "--myboundary--\r\n\r\n"; 
var fs = require('fs'); 
fs.stat(local_path_to_file, function(err, file_info) { 
    var content_length = Buffer.byteLength(body_before_file) + 
      file_info.size + 
      Buffer.byteLength(body_after_file); 
    // create request to google, write content-length and other headers 
    // write() the body_before_file part, 
    // and then pipe the file and end the request like we did above 

Но, что до сих пор убивает вашу способность течь от пользователя Google, файл должен быть загружен на локальный диск, чтобы определить его длину.

Альтернативный вариант

... теперь, пройдя через все это, PUT может быть вашим другом здесь. Согласно https://developers.google.com/storage/docs/reference-methods#putobject вы можете использовать заголовок transfer-encoding: chunked, поэтому вам не нужно искать длину файла. И, я считаю, что весь текст запроса является всего лишь файлом, поэтому вы можете использовать pipe() и просто разрешить ему завершить запрос, когда это будет сделано. Если вы используете https://github.com/felixge/node-formidable для обработки загрузки, то вы можете сделать что-то вроде этого:

incomingForm.onPart = function(part) { 
    if (part.filename) { 
     var req = ... // create a PUT request to google and set the headers 
     part.pipe(req); 
    } else { 
     // let formidable handle all non-file parts 
     incomingForm.handlePart(part); 
    } 
} 
+0

К сожалению, Google требует, чтобы быть запрос POST для того, чтобы делать то, что я пытаюсь сделать. Тем не менее, БОЛЬШОЙ ответ, и я, безусловно, буду выполнять сделанные вами предложения. – jwegner

+0

Также, кстати, node.js дает вам возможность [остановить канал от закрытия соединения] (http://nodejs.org/api/stream.html#stream_stream_pipe_destination_options) – jwegner

+0

Хорошо, я забыл об этом. Я собираюсь отредактировать ответ на случай, если кто-нибудь еще столкнется с этим. –