fileReader.readAsBinaryString để tải lên tệp


83

Đang cố gắng sử dụng fileReader.readAsBinaryString để tải tệp PNG lên máy chủ thông qua AJAX, mã đã bị loại bỏ (tệpObject là đối tượng chứa thông tin trên tệp của tôi);

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);

Kiểm tra vài dòng đầu tiên của tệp trước khi tải lên (sử dụng VI) cho tôi

nhập mô tả hình ảnh ở đây

Cùng một tệp sau khi tải lên hiển thị

nhập mô tả hình ảnh ở đây

Vì vậy, có vẻ như một vấn đề về định dạng / mã hóa ở đâu đó, tôi đã thử sử dụng một hàm mã hóa UTF8 đơn giản trên dữ liệu nhị phân thô

    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;
    )

Sau đó, trong mã gốc

//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;

cho tôi kết quả đầu ra là

nhập mô tả hình ảnh ở đây

Vẫn không phải là tệp thô = (

Tôi làm cách nào để mã hóa / tải / xử lý tệp để tránh các sự cố mã hóa, do đó, tệp đang nhận trong yêu cầu HTTP giống với tệp trước khi được tải lên.

Một số thông tin hữu ích có thể khác, nếu thay vì sử dụng fileReader.readAsBinaryString (), tôi sử dụng fileObject.getAsBinary () để lấy dữ liệu nhị phân, nó hoạt động tốt. Nhưng getAsBinary chỉ hoạt động trong Firefox. Tôi đã thử nghiệm điều này trong Firefox và Chrome, cả trên Mac, đều nhận được kết quả như nhau ở cả hai. Tải lên phần phụ trợ đang được xử lý bởi Mô-đun tải lên NGINX , một lần nữa chạy trên Mac. Máy chủ và máy khách trên cùng một máy. Điều tương tự cũng xảy ra với bất kỳ tệp nào tôi cố gắng tải lên, tôi chỉ chọn PNG vì đó là ví dụ rõ ràng nhất.

Câu trả lời:


74

Sử dụng fileReader.readAsDataURL( fileObject ), điều này sẽ mã hóa nó thành base64, mà bạn có thể tải lên máy chủ của mình một cách an toàn.


8
Trong khi điều đó hoạt động, phiên bản của tệp được lưu trên máy chủ được mã hóa Base64 (như nó phải như vậy). Là không có cách nào để chuyển nó như là dữ liệu nhị phân chứ không phải là Base64 mã hóa (IE như thể nó đã được tải lên bằng cách sử dụng bình thường <input type="file">lĩnh vực)
Smudge

2
Nếu bạn có PHP trên máy chủ, bạn có thể base64_decode (tệp) trước khi lưu trữ nó. Và không - không có cách nào an toàn để chuyển dữ liệu nhị phân thô qua http.
c69

Sử dụng readAsDataURL mang lại cho tôi imgur.com/1LHya này trên máy chủ, chạy nó trở lại thông qua base64_decode của PHP (Chúng tôi thực sự đang sử dụng Python, nhưng PHP là một thử nghiệm tốt) Tôi nhận được imgur.com/0uwhy , vẫn không phải là dữ liệu nhị phân ban đầu và không phải là một hình ảnh hợp lệ = (
Smudge

20
@ imgur.com/1LHya Hỡi ôi! Tại máy chủ, bạn phải chia chuỗi base64 theo "," và chỉ lưu trữ phần thứ hai - vì vậy loại mime sẽ không được lưu trữ cùng với nội dung tệp thực.
c69

7
không, nó không hiệu quả. điều này sẽ làm tăng kích thước tệp 137% và làm cho máy chủ hoạt động. nhưng không có cách nào khác để hỗ trợ F *** IE
Puchu

109

(Sau đây là câu trả lời muộn nhưng đầy đủ)

Hỗ trợ các phương pháp FileReader


FileReader.readAsBinaryString()đang bị phản đối. Đừng sử dụng nó! Nó không còn trong bản nháp làm việc của API tệp W3C nữa :

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

NB: Lưu ý rằng đó Filelà một loại Blobcấu trúc mở rộng .

Mozilla vẫn triển khai readAsBinaryString()và mô tả nó trong tài liệu 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);

Theo tôi, lý do đằng sau việc readAsBinaryString()ngừng sử dụng là như sau: tiêu chuẩn cho các chuỗi JavaScript là DOMStringchỉ chấp nhận các ký tự UTF-8, KHÔNG phải dữ liệu nhị phân ngẫu nhiên. Vì vậy, không sử dụng readAsBinaryString (), điều đó không an toàn và tuân thủ ECMAScript chút nào.

Chúng tôi biết rằng các chuỗi JavaScript không được lưu trữ dữ liệu nhị phân nhưng Mozilla ở một số loại có thể. Điều đó nguy hiểm theo ý kiến ​​của tôi. Blobtyped arrays( ArrayBuffervà chưa được triển khai nhưng không cần thiết StringView) được phát minh cho một mục đích: cho phép sử dụng dữ liệu nhị phân thuần túy, không có giới hạn chuỗi UTF-8.

Hỗ trợ tải lên XMLHttpRequest


XMLHttpRequest.send() có các tùy chọn lời gọi sau:

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

XMLHttpRequest.sendAsBinary() có các tùy chọn lời gọi sau:

void sendAsBinary(   in DOMString body );

sendAsBinary () KHÔNG phải là một tiêu chuẩn và có thể không được hỗ trợ trong Chrome.

Các giải pháp


Vì vậy, bạn có một số tùy chọn:

  1. send()các FileReader.resultcủa FileReader.readAsArrayBuffer ( fileObject ). Nó phức tạp hơn để thao tác (bạn sẽ phải gửi () riêng cho nó) nhưng đó là CÁCH TIẾP CẬN ĐƯỢC KHUYẾN NGHỊ .
  2. send()các FileReader.resultcủa FileReader.readAsDataURL( fileObject ). Nó tạo ra chi phí vô ích và độ trễ nén, yêu cầu một bước giải nén ở phía máy chủ NHƯNG rất dễ thao tác như một chuỗi trong Javascript.
  3. Là phi tiêu chuẩn và sendAsBinary()các FileReader.resultsốFileReader.readAsBinaryString( fileObject )

MDN tuyên bố rằng:

Cách tốt nhất để gửi nội dung nhị phân (như trong tệp tải lên) là sử dụng Bộ đệm mảng hoặc Khối trong liên hợp với phương thức send (). Tuy nhiên, nếu bạn muốn gửi một dữ liệu thô có thể xác định được chuỗi, hãy sử dụng phương thức sendAsBinary () hoặc lớp siêu mảng được nhập StringView (Non native).


10
Tôi rất tiếc phải đào lại vấn đề này, chỉ muốn nói thêm rằng có lẽ cách dễ nhất để gửi dữ liệu nhị phân (v.v. tệp PDF) là thông qua FileReader.readAsDataURLonloadxử lý thay vì chỉ gửi event.target.result(không phải là một chuỗi được mã hóa base64 sạch) bạn trước tiên hãy dọn dẹp nó bằng một số regex như thế event.target.result = event.target.result.match(/,(.*)$/)[1]và gửi base64 thực đến máy chủ để được giải mã.

Vì bất kỳ ai cũng có thể chỉnh sửa MDN nên tôi có thể sẽ không sử dụng nó làm nguồn.
Chris Anderson

4
@ user1299518, sử dụng tốt hơn event.target.result.split(",", 2)[1], không match.
MrKsn

1
@KrisWebDev: Trong tùy chọn được đề xuất, bạn đề cập đến sự cần thiết phải thực hiện gửi () riêng. Tại sao?
Readren

Phương pháp được đề xuất phù hợp để tải lên tệp đính kèm bằng API REST TFS. Cám ơn!
RoJaIt

24

Cách tốt nhất trong các trình duyệt hỗ trợ nó, là gửi tệp dưới dạng Blob hoặc sử dụng FormData nếu bạn muốn có một biểu mẫu nhiều phần. Bạn không cần FileReader cho việc đó. Điều này vừa đơn giản và hiệu quả hơn so với việc cố gắng đọc dữ liệu.

Nếu bạn muốn gửi cụ thể bằng địa chỉ multipart/form-data, bạn có thể sử dụng đối tượng 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);

Bạn cũng có thể gửi dữ liệu trực tiếp thay vì sử dụng multipart/form-data. Xem tài liệu . Tất nhiên, điều này cũng sẽ cần một sự thay đổi từ phía máy chủ.

// 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);

Để được hỗ trợ trình duyệt, hãy xem: http://caniuse.com/#feat=xhr2 (hầu hết các trình duyệt, bao gồm IE 10+).


xmlHttpRequest.send (formData);
Li-chih Wu

7
Cuối cùng một câu trả lời thích hợp cũng không cần sử dụng FormData. Có vẻ như mọi người đang sử dụng một biểu mẫu trong khi tất cả những gì họ cần là tải lên một tệp duy nhất ... Cảm ơn!
Héo

Tôi đã tìm kiếm hàng giờ để làm thế nào để làm cho nó hoạt động cho một tệp mp3 tải lên qua ajax, đây là một mẹo nhỏ!
Justin Vincent

Tôi nghĩ bạn có thể không cần thực hiện setRequestHeader vì nó sẽ được tự động thiết lập bằng cách gửi formData và sẽ trông giống như sau "Content-Type: multiart / form-data; border = ---- WebKitFormBoundaryQA8d7glpaso6zKsA" Trong trường hợp của tôi, nó đã bị hỏng CORS trừ khi tôi đã xóa setRequestHeader.
Justin Vincent

Lưu ý: Nhận xét của tôi ở trên chỉ áp dụng khi sử dụng đối tượng formData.
Justin Vincent
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.