2011-04-21 17 views
17

me encontré con muchos puestos cuando me preguntó para este problema, pero todos ellos se refieren a la forma de cargar un archivo desde su navegador a un servidor Node.js. Quiero subir un archivo del código node.js a otro servidor. Traté de escribir basado en mi conocimiento limitado de Node.js, pero no funciona.cómo cargar un archivo de Node.js

function (data) { 
    var reqdata = 'file='+data; 
    var request = http.request({ 
    host : HOST_NAME, 
    port : HOST_PORT, 
    path : PATH, 
    method : 'POST', 
    headers : { 
     'Content-Type' : 'multipart/form-data', 
     'Content-Length' : reqdata.length 
    } 
    }, function (response) { 
     var data = ''; 
     response.on('data', function(chunk) { 
     data += chunk.toString(); 
     }); 
     response.on('end', function() { 
     console.log(data); 
     }); 
    }); 

    request.write(reqdata+'\r\n\r\n'); 
    request.end(); 
}) 

La función anterior es invocada por otro código que genera datos.

Intenté cargar el mismo archivo de datos usando curl -F "file = @ <filepath>" y la carga se realizó correctamente. Pero mi código falla. El servidor devuelve un error específico de la aplicación que indica que el archivo cargado no era válido o estaba dañado.

I recoge los datos tcpdump y se analizaron en Wireshark. El paquete enviado desde mi código Node.js carece del límite requerido para los datos de varias partes. Veo este mensaje en paquetes Wireshark

The multipart dissector could not find the required boundary parameter. 

alguna idea de cómo lograr esto en el código Node.js?

Respuesta

7

multiparte es bastante compleja, si se quiere que se vea como cómo un cliente por lo general se ocupa de "multipart/form-data", que tiene que hacer algunas cosas. Primero tiene que seleccionar una clave de límite, esta suele ser una cadena aleatoria para marcar el comienzo y el final de las partes (en este caso, sería solo una parte, ya que desea enviar un solo archivo). Cada parte (o una parte) necesitará un encabezado (inicializado por la clave de límite), establecer el tipo de contenido, el nombre del campo de formulario y la codificación de transferencia. Una vez que se completó la parte (s), es necesario marcar el final de cada parte con la clave límite.

Nunca he trabajado con multiparte, pero creo que así es como se podría hacer. Por favor alguien me corrija si me equivoco:

var boundaryKey = Math.random().toString(16); // random string 
request.setHeader('Content-Type', 'multipart/form-data; boundary="'+boundaryKey+'"'); 
// the header for the one and only part (need to use CRLF here) 
request.write( 
    '--' + boundaryKey + '\r\n' 
    // use your file's mime type here, if known 
    + 'Content-Type: application/octet-stream\r\n' 
    // "name" is the name of the form field 
    // "filename" is the name of the original file 
    + 'Content-Disposition: form-data; name="my_file"; filename="my_file.bin"\r\n' 
    + 'Content-Transfer-Encoding: binary\r\n\r\n' 
); 
fs.createReadStream('./my_file.bin', { bufferSize: 4 * 1024 }) 
    // set "end" to false in the options so .end() isnt called on the request 
    .pipe(request, { end: false }) // maybe write directly to the socket here? 
    .on('end', function() { 
    // mark the end of the one and only part 
    request.end('--' + boundaryKey + '--'); 
    }); 

Una vez más, nunca he hecho esto antes, pero yo creo que es la forma en que podría llevarse a cabo. Tal vez alguien más entendido podría proporcionar más información.

Si desea enviarlo como base64 o una codificación que no sea un archivo binario en bruto, tendrá que hacer todas las tuberías usted mismo. Va a ser más complicado, porque tendrá que pausar la secuencia de lectura y esperar eventos de desagüe en la solicitud para asegurarse de no agotar toda su memoria (si no es un archivo grande, generalmente sin embargo, no tendrían que preocuparse por esto).EDITAR: En realidad, no importa que usted podría simplemente establezca la codificación en las opciones de flujo de lectura.

Me sorprenderé si no hay un módulo de nodo que ya lo haga. Tal vez alguien más informado sobre el tema puede ayudar con los detalles de bajo nivel, pero creo que debería haber un módulo alrededor que lo haga.

+0

He estado intentando algo similar. Voy a probar tus sugerencias más tarde hoy. Encontré este módulo de nodo que se supone que facilita esta tarea. https://github.com/mikeal/request Pero aún no funcionó tan bien para mi propósito. – Jayesh

3

A medida que el mensaje de error indica que se echa en falta el parámetro de límite. Necesita agregar una cadena aleatoria para separar cada archivo del resto de los archivos/formulario-datos.

Aquí es cómo una solicitud podría ser:

El tipo de contenido:

Content-Type:multipart/form-data; boundary=----randomstring1337 

El cuerpo:

------randomstring1337 
Content-Disposition: form-data; name="file"; filename="thefile.txt" 
Content-Type: application/octet-stream 

[data goes here] 

------randomstring1337-- 

Tenga en cuenta que la -- en el comienzo y el final de la cadena aleatoria en el cuerpo es significativa. Esos son parte del protocolo.

Más información aquí http://www.w3.org/Protocols/rfc1341/7_2_Multipart.html

+0

que son impresionantes. –

12

jhcc's answer es casi allí.

Teniendo que conseguir soporte para esto en nuestras pruebas, lo pellizqué ligeramente.

Aquí está la versión modificada que funciona para nosotros:

var boundaryKey = Math.random().toString(16); // random string 
request.setHeader('Content-Type', 'multipart/form-data; boundary="'+boundaryKey+'"'); 
// the header for the one and only part (need to use CRLF here) 
request.write( 
    '--' + boundaryKey + '\r\n' 
    // use your file's mime type here, if known 
    + 'Content-Type: application/octet-stream\r\n' 
    // "name" is the name of the form field 
    // "filename" is the name of the original file 
    + 'Content-Disposition: form-data; name="my_file"; filename="my_file.bin"\r\n' 
    + 'Content-Transfer-Encoding: binary\r\n\r\n' 
); 
fs.createReadStream('./my_file.bin', { bufferSize: 4 * 1024 }) 
    .on('end', function() { 
    // mark the end of the one and only part 
    request.end('\r\n--' + boundaryKey + '--'); 
    }) 
    // set "end" to false in the options so .end() isn't called on the request 
    .pipe(request, { end: false }) // maybe write directly to the socket here? 

Los cambios son los siguientes:

  • ReadableStream.pipereturns the piped-to stream, por lo end nunca se llamó a eso. En su lugar, espere end en la secuencia de lectura de archivos.
  • request.end pone el límite en una nueva línea.
+4

Esto funcionó para mí, pero también tuve que agregar un encabezado Content-Length que requería llamar a fs.stat en el archivo para obtener su tamaño y agregarlo al Buffer.bytesLength de todos los demás datos de cadena enviados. Como obtener toda la longitud del contenido comenzando con la tecla de límite hasta el final '-', incluido el tamaño del archivo. –

+0

Quiero enviar varias partes en esto ¿cómo se puede lograr eso? Intenté agregarlo agregando separadores de límite después de cada parte pero parece que no funciona para mí –

+0

@BjornTipling ¿incluía los encabezados y separadores 'Content-Disposition'? Esperaría que fuera la longitud de todo el cuerpo, incluidos esos. –

1

La forma más rápida que era capaz de hacer esto, que funcionaba, estaba usando el paquete request. El código estaba bien documentado y simplemente funcionó.

(Para mis pruebas que quería un resultado JSON y SSL no estricta - hay muchas otras opciones ...)

var url = "http://"; //you get the idea 
var filePath = "/Users/me/Documents/file.csv"; //absolute path created elsewhere 
var r = request.post({ 
    url: url, 
    json: true, 
    strictSSL: false 
}, function(err, res, data) { 
    //console.log("Finished uploading a file"); 
    expect(err).to.not.be.ok(); 
    expect(data).to.be.ok(); 
    //callback(); //mine was an async test 
}); 
var form = r.form(); 
form.append('csv', fs.createReadStream(filePath));