fileReader.readAsBinaryString para cargar archivos

83

Tratando de usar fileReader.readAsBinaryString para cargar un archivo PNG al servidor a través de AJAX, código reducido (fileObject es el objeto que contiene información sobre mi archivo);

var fileReader = new FileReader();

fileReader.onload = function(e) {
    var xmlHttpRequest = new XMLHttpRequest();
    //Some AJAX-y stuff - callbacks, handlers etc.
    xmlHttpRequest.open("POST", '/pushfile', true);
    var dashes = '--';
    var boundary = 'aperturephotoupload';
    var crlf = "\r\n";

    //Post with the correct MIME type (If the OS can identify one)
    if ( fileObject.type == '' ){
        filetype = 'application/octet-stream';
    } else {
        filetype = fileObject.type;
    }

    //Build a HTTP request to post the file
    var data = dashes + boundary + crlf + "Content-Disposition: form-data;" + "name=\"file\";" + "filename=\"" + unescape(encodeURIComponent(fileObject.name)) + "\"" + crlf + "Content-Type: " + filetype + crlf + crlf + e.target.result + crlf + dashes + boundary + dashes;

    xmlHttpRequest.setRequestHeader("Content-Type", "multipart/form-data;boundary=" + boundary);

    //Send the binary data
    xmlHttpRequest.send(data);
}

fileReader.readAsBinaryString(fileObject);

Examinar las primeras líneas de un archivo antes de cargarlo (usando VI) me da

ingrese la descripción de la imagen aquí

El mismo archivo después de la carga muestra

ingrese la descripción de la imagen aquí

Entonces parece un problema de formato / codificación en algún lugar, intenté usar una función de codificación UTF8 simple en los datos binarios sin procesar

    function utf8encode(string) {
        string = string.replace(/\r\n/g,"\n");
        var utftext = "";

        for (var n = 0; n < string.length; n++) {

            var c = string.charCodeAt(n);

            if (c < 128) {
                utftext += String.fromCharCode(c);
            }
            else if((c > 127) && (c < 2048)) {
                utftext += String.fromCharCode((c >> 6) | 192);
                utftext += String.fromCharCode((c & 63) | 128);
            }
            else {
                utftext += String.fromCharCode((c >> 12) | 224);
                utftext += String.fromCharCode(((c >> 6) & 63) | 128);
                utftext += String.fromCharCode((c & 63) | 128);
            }

        }

        return utftext;
    )

Luego en el código original

//Build a HTTP request to post the file
var data = dashes + boundary + crlf + "Content-Disposition: form-data;" + "name=\"file\";" + "filename=\"" + unescape(encodeURIComponent(file.file.name)) + "\"" + crlf + "Content-Type: " + filetype + crlf + crlf + utf8encode(e.target.result) + crlf + dashes + boundary + dashes;

lo que me da la salida de

ingrese la descripción de la imagen aquí

Todavía no es lo que era el archivo sin procesar = (

¿Cómo codifico / cargo / proceso el archivo para evitar los problemas de codificación, por lo que el archivo que se recibe en la solicitud HTTP es el mismo que el archivo antes de que se cargara?

Alguna otra información posiblemente útil, si en lugar de usar fileReader.readAsBinaryString () utilizo fileObject.getAsBinary () para obtener los datos binarios, funciona bien. Pero getAsBinary solo funciona en Firefox. He estado probando esto en Firefox y Chrome, ambos en Mac, obteniendo el mismo resultado en ambos. Las cargas de backend las gestiona el módulo de carga de NGINX , que se ejecuta nuevamente en Mac. El servidor y el cliente están en la misma máquina. Lo mismo sucede con cualquier archivo que intento cargar, simplemente elegí PNG porque era el ejemplo más obvio.

Mancha
fuente

Respuestas:

74

Úselo fileReader.readAsDataURL( fileObject ), esto lo codificará en base64, que puede cargar de manera segura en su servidor.

c69
fuente
8
Si bien eso funciona, la versión del archivo guardado en el servidor está codificada en Base64 (como debería estar). ¿No hay forma de transferirlo como datos binarios en lugar de codificados en Base64 (IE como si se hubiera cargado usando un <input type="file">campo normal )?
Smudge
2
Si tiene PHP en el servidor, puede base64_decode (archivo) antes de almacenarlo. Y no, no existe una forma segura de transferir datos binarios sin procesar a través de http.
c69
El uso de readAsDataURL me da este imgur.com/1LHya en el servidor, ejecutándolo de nuevo a través de base64_decode de PHP (en realidad estamos usando Python, pero PHP es una buena prueba) obtengo imgur.com/0uwhy , todavía no los datos binarios originales y no es una imagen válida = (
Mancha
20
@ imgur.com/1LHya ¡ Oh, mi mal! En el servidor, debe dividir la cadena base64 por "," y almacenar solo la segunda parte, para que el tipo mime no se almacene con el contenido real del archivo.
c69
7
no, no es eficiente. esto aumentará el tamaño del archivo en un 137% y generará una sobrecarga del servidor. pero no hay otra forma de apoyar F *** IE
puchu
109

(Lo que sigue es una respuesta tardía pero completa)

Soporte de métodos FileReader


FileReader.readAsBinaryString()está en desuso. ¡No lo uses! Ya no está en el borrador de trabajo de la API de archivos W3C :

void abort();
void readAsArrayBuffer(Blob blob);
void readAsText(Blob blob, optional DOMString encoding);
void readAsDataURL(Blob blob);

NB: Tenga en cuenta que Filees una especie de Blobestructura extendida .

Mozilla todavía lo implementa readAsBinaryString()y lo describe en la documentación de MDN FileApi :

void abort();
void readAsArrayBuffer(in Blob blob); Requires Gecko 7.0
void readAsBinaryString(in Blob blob);
void readAsDataURL(in Blob file);
void readAsText(in Blob blob, [optional] in DOMString encoding);

En readAsBinaryString()mi opinión, la razón detrás de la desaprobación es la siguiente: el estándar para las cadenas de JavaScript son las DOMStringque solo aceptan caracteres UTF-8, NO datos binarios aleatorios. Así que no use readAsBinaryString (), eso no es seguro ni cumple con ECMAScript en absoluto.

Sabemos que no se supone que las cadenas de JavaScript almacenen datos binarios, pero Mozilla de alguna manera sí puede. Eso es peligroso en mi opinión. Bloby typed arrays( ArrayBuffery los que aún no se han implementado pero no necesarios StringView) se inventaron con un propósito: permitir el uso de datos binarios puros, sin restricciones de cadenas UTF-8.

Soporte de carga XMLHttpRequest


XMLHttpRequest.send() tiene las siguientes opciones de invocación:

void send();
void send(ArrayBuffer data);
void send(Blob data);
void send(Document data);
void send(DOMString? data);
void send(FormData data);

XMLHttpRequest.sendAsBinary() tiene las siguientes opciones de invocación:

void sendAsBinary(   in DOMString body );

sendAsBinary () NO es un estándar y puede que no sea compatible con Chrome.

Soluciones


Entonces tienes varias opciones:

  1. send()el FileReader.resultde FileReader.readAsArrayBuffer ( fileObject ). Es más complicado de manipular (tendrá que hacer un send () por separado) pero es el ENFOQUE RECOMENDADO .
  2. send()el FileReader.resultde FileReader.readAsDataURL( fileObject ). Genera sobrecarga inútil y latencia de compresión, requiere un paso de descompresión en el lado del servidor, PERO es fácil de manipular como una cadena en Javascript.
  3. Siendo no estándar y sendAsBinary()el FileReader.resultdeFileReader.readAsBinaryString( fileObject )

MDN afirma que:

La mejor manera de enviar contenido binario (como en la carga de archivos) es usando ArrayBuffers o Blobs junto con el método send (). Sin embargo, si desea enviar una cadena de datos sin procesar, utilice el método sendAsBinary () en su lugar, o la superclase de matrices StringView (no nativas).

KrisWebDev
fuente
10
Lamento profundizar en esto nuevamente, solo quería agregar que probablemente la forma más fácil de enviar datos binarios (etc. un archivo PDF) es a través FileReader.readAsDataURLdel onloadcontrolador en lugar de simplemente enviar event.target.result(que no es una cadena codificada en base64 limpia) límpielo primero con un poco de expresión regular event.target.result = event.target.result.match(/,(.*)$/)[1]y envíe la base64 real al servidor para decodificarla.
Como cualquiera puede editar MDN, probablemente no lo usaría como fuente.
Chris Anderson
4
@ user1299518, mejor uso event.target.result.split(",", 2)[1], no match.
MrKsn
1
@KrisWebDev: En la opción recomendada mencionas la necesidad de hacer un send () separado. ¿Por qué?
Readren
El enfoque recomendado funcionó para cargar un archivo adjunto utilizando la API REST de TFS. ¡Gracias!
RoJaIt
24

La mejor manera en los navegadores que lo admiten es enviar el archivo como un Blob o usar FormData si desea un formulario de varias partes. No necesita un FileReader para eso. Esto es más simple y más eficiente que intentar leer los datos.

Si desea enviarlo específicamente como multipart/form-data, puede usar un objeto FormData:

var xmlHttpRequest = new XMLHttpRequest();
xmlHttpRequest.open("POST", '/pushfile', true);
var formData = new FormData();
// This should automatically set the file name and type.
formData.append("file", file);
// Sending FormData automatically sets the Content-Type header to multipart/form-data
xmlHttpRequest.send(formData);

También puede enviar los datos directamente, en lugar de usar multipart/form-data. Consulte la documentación . Por supuesto, esto también necesitará un cambio del lado del servidor.

// file is an instance of File, e.g. from a file input.
var xmlHttpRequest = new XMLHttpRequest();
xmlHttpRequest.open("POST", '/pushfile', true);

xmlHttpRequest.setRequestHeader("Content-Type", file.type);

// Send the binary data.
// Since a File is a Blob, we can send it directly.
xmlHttpRequest.send(file);

Para obtener compatibilidad con el navegador, consulte: http://caniuse.com/#feat=xhr2 (la mayoría de los navegadores, incluido IE 10+).

Ralf
fuente
xmlHttpRequest.send (formData);
Li-chih Wu
7
Finalmente una respuesta adecuada también sin usar FormData. Parece que todos están usando un formulario, mientras que todo lo que necesitan es cargar un solo archivo ... ¡Gracias!
March
Estuve buscando durante horas cómo hacer que esto funcionara para la carga de un archivo mp3 a través de ajax, ¡este es el truco!
Justin Vincent
Una cosa que creo que es posible que no necesite hacer setRequestHeader ya que se configurará automáticamente enviando formData y se verá algo así "Content-Type: multipart / form-data; boundary = ---- WebKitFormBoundaryQA8d7glpaso6zKsA" En mi caso se rompió CORS a menos que elimine setRequestHeader.
Justin Vincent
Nota: Mi comentario anterior solo se aplica cuando se usa el objeto formData.
Justin Vincent