AJAX傳輸二進制數據


FormData對象

  1. 將整個表單元素映射成一個對象,可實現自動拼接表單對象數據成請求參數的格式
  2. 可以上傳二進制數據

利用FormData上傳文本數據的表單:

前端網頁核心代碼:

<form id="myform"> <!-- form屬於塊元素 -->
	用戶名 = <input type="text" name="username" value=""> <br>
	密碼 = <input type="password" name="pwd" value=""> <br>
	<input type="button" name="go" value="提交" onclick="doit()">
</form>
<script>
"use strict"
var myform = document.getElementById('myform');
var formdata; // 如果現在執行`formdata = new FormData(myform)`會導致formdata中的表單數據是空
function doit(){
	formdata = new FormData(myform);
	// FormDate API:https://developer.mozilla.org/zh-CN/docs/Web/API/FormData
	var xhr = new XMLHttpRequest();
	if (formdata.get('username') == 'nat'){ // IE11支持FormData構造器但是不支持get、set等一些方法
		formdata.set('usernameOrigin', 'nat');
		formdata.set('username', 'natasha'); // 同名的鍵會覆蓋
		formdata.delete('pwd');
		formdata.append('username', 'natashe'); // 同名的鍵會保留
	}
	for (let x of formdata){
		console.log(x); // 如果'username'傳入的是'nat'那么輸出如下:
		// Array [ "username", "natasha" ]
		// Array [ "usernameOrigin", "nat" ]
		// Array [ "username", "natashe" ]
		// 如果服務器不進行特殊設置,那么username只能獲取到最后一次的值,即'natashe'(設置略)
	}
	xhr.open('POST', '/postdata');
	xhr.send(formdata); // 會自動將'Content-Type'設置成'multipart/form-data; boundary=---------------------------20808984378436579741074229525'
	// 注意,FormData數據在Node的Express框架中不能直接通過中間件body-parser獲取,嘗試通過`req.body`獲取始終返回空對象,而是通過`cnpm install --save formidable`這個模塊獲取,但是它不是中間件
	xhr.onload = function(){
		if (xhr.status == 200){
			console.log(xhr.responseText);
		}else{
			console.log('Something error happened: ', xhr);
		}
	}
}
</script>

后端Node核心代碼:

const formidable = require('formidable');
app.post('/postdata', (req,res)=>{
	var form = new formidable.IncomingForm();
	form.parse(req, (err, fields, files)=>{
		// fields是普通的表單數據(文本)
		// files是上傳的文件數據
		console.log(fields);
		res.send('ok');
	});
});

利用FormData上傳二進制數據的本地文件:

前端網頁核心代碼:

<form id="myform">
	昵稱 = <input type="text" name="nickname"> <br>
	文件 = <input type="file" name="doc"> <br>
	<input type="button" name="go" value="提交" onclick="doit()">
</form>
<script>
"use strict"
var myform = document.getElementById('myform');
var formdata;
function doit(){
	formdata = new FormData(myform);
	var xhr = new XMLHttpRequest();
	xhr.open('POST', '/postdata');
	xhr.upload.onprogress = function(e){
		// 這個事件必須在send之前監聽有效,在send之后監聽不會觸發
		console.log(`當前進度:${Math.floor(e.loaded / e.total * 100)}%`);
	}
	xhr.send(formdata);
	xhr.onload = function(){
		if (xhr.status == 200){
			console.log(xhr.responseText);
		}else{
			console.log('Something error happened: ', xhr);
		}
	}
}
</script>

后端Node核心代碼:

app.post('/postdata', (req,res)=>{
	var form = new formidable.IncomingForm();
	form.uploadDir = './webroot/uploads'; // 上傳的文件保存在哪個目錄
	form.keepExtensions = true; // 保留上傳時的后綴名
	form.parse(req, (err, fields, files)=>{
		// fields是普通的表單數據(文本)
		// files是上傳的文件數據
		console.log(fields);
		console.log(files.doc.path); // 'webroot\\uploads\\upload_4242811d6678445365bbc7c55c0a968f.dat'
		res.send('ok');
	});
});

XHR上傳和下載二進制數據(TypedArray和Blob)

接收和發送二進制數據的MDN文檔

TypedArray

前端傳給后端

// 前端
var data = new ArrayBuffer(64);
var dataview = new Uint8Array(data);
for (let i=0; i<data.byteLength-1; i++){
	dataview[i] = 97 + (i % 26); // 將64字節的二進制數據初始化成[a-z]的小寫字母
}
dataview[data.byteLength-1] = 0;
function doit(){
	var xhr = new XMLHttpRequest();
	xhr.open('POST', '/postdata');
	// xhr.setRequestHeader('Content-Type', 'application/octet-stream'); // 表示通用二進制流,可不寫
	xhr.send(data);
	xhr.onload = function(){
		if (xhr.status == 200){
			console.log(xhr.responseText);
		}else{
			console.log('Something error happened: ', xhr);
		}
	}
}
// 通過抓包,send出去的報文就是ArrayBuffer的二進制數據
// Node原生http服務器收到的POST數據默認是Buffer類型
// 后端
app.post('/postdata', (req,res)=>{
	var tmpchunk = [];
	var data;
	req.on('data', (chunk)=>{
		tmpchunk.push(chunk);
	});
	req.on('end', ()=>{
		data = Buffer.concat(tmpchunk);
		console.log(data);
		res.send('ok');
	})
});

后端傳給前端

// 前端
function doit(){
	var xhr = new XMLHttpRequest();
	xhr.open('GET', '/getdata');
	xhr.responseType = 'arraybuffer';
	xhr.send();
	xhr.onload = function(){
		if (xhr.status == 200){
			console.log(xhr.response); // ArrayBuffer 字節長度:8
		}else{
			console.log('Something error happened: ', xhr);
		}
	}
}
// 后端
app.get('/getdata', (req,res)=>{
	res.set('Content-Type','application/octet-stream');
	var data = new ArrayBuffer(8);
	var dataview = new Uint8Array(data);
	dataview[1] = 11;
	dataview[3] = 22;
	dataview[5] = 33;
	dataview[7] = 44;
	data = Buffer.from(data); // 將ArrayBuffer轉換成Buffer
	res.send(data); // 如果send的是ArrayBuffer,會被JSON化成'{}'(注意,實際沒有單引號,這里只做分隔符),只能傳入Buffer才能使send方法正確發送二進制數據,通過抓包得到的響應體的數據是:00 0b 00 16 00 21 00 2c
});

Blob

前端傳給后端

var data = new Blob(['haha']);
function doit(){
	var xhr = new XMLHttpRequest();
	xhr.open('POST', '/postdata');
	// xhr.setRequestHeader('Content-Type', 'application/octet-stream'); // 表示通用二進制流,可不寫
	xhr.send(data);
	xhr.onload = function(){
		if (xhr.status == 200){
			console.log(xhr.responseText);
		}else{
			console.log('Something error happened: ', xhr);
		}
	}
}
// 原理同ArrayBuffer,send出去的報文就是Blob的二進制數據,報文的請求體:h a h a

后端傳給前端

// 注意,Node不存在Blob,只需要傳遞Buffer即可,故此后端代碼同【TypedArray的后端傳給前端】
// 前端:將xhr.responseType = 'arraybuffer' -> 'blob',獲取到的xhr.response返回`Blob{size: 8, type: "application/json"}`,其中的type會被自動設置成響應頭中的'Content-Type'

舊時代的二進制傳輸

在xhr.responseType還沒有'arraybuffer'、'blob'甚至'json'的時代,AJAX還沒有傳遞二進制功能,但是可以hack出來(目前已經不行了):

function doit(){
	// 沒設置responseType的話(默認值是""),相當於設置了"text"
	// 如果響應報文存在`Content-Type = text/plain; charset=someCharset`,瀏覽器嘗試使用此編碼來解析響應數據
	// 如果Content-Type不是text或指定的編碼不支持,瀏覽器嘗試使用默認的UTF-8編碼來解析響應數據
	var xhr = new XMLHttpRequest();
	xhr.open('GET', 'http://localhost:3000/getdata');
	xhr.overrideMimeType('Content-Type', 'text/plain; charset=x-user-defined'); // 告訴瀏覽器不要嘗試解析數據,直接返回未處理過的字節數據,目前瀏覽器(測試瀏覽器:Chrome>=69, FF>=80)即便設置了此方法也無法覆蓋對應的響應頭字段,已經無效
	xhr.send(null);
	xhr.onload = function(){
		if (xhr.status == 200){
			// console.log(xhr.getAllResponseHeaders()); // 即便上面已經覆蓋了,但是取到的還是原始的響應頭
			// 也就是下面的代碼已經無法使用了,overrideMimeType方法被忽略的最主要原因可能是現代的AJAX已經支持了原生二進制數據傳遞
			var data = xhr.response; // 值是'\u0000\u0061\u007F\uFFFD\uFFFD\uFFFD',因為瀏覽器嘗試將收到的數據當做UTF-8編碼的文本來解析,而解析失敗的字符被填充'\uFFFD'
			// 128 = 1000,0000 而UTF-8編碼表沒有10起始的字符,故轉換失敗被填充0xFFFD
			// 同理,254 = 1111,1110 和 255 = 1111,1111 都不符合UTF-8編碼,都轉0XFFFD
			var len = data.length;
			var received = new ArrayBuffer(len);
			var receivedview = new Uint8Array(received);
			for (let i=0; i<len; i++){
				receivedview[i] = data.charCodeAt(i);
			}
			console.log(receivedview); // 輸出Uint8Array(6)[0, 97, 127, 253, 253, 253]
			// '\uFFFD'.charCodeAt(0) = 65533
			// 將65533存入Uint8Array時,將發生轉換,轉換的結果便是253
		}else{
			console.log('Something error happened: ', xhr);
		}
	}
}
// 后端
app.get('/getdata', (req,res)=>{
	var data = Buffer.allocUnsafe(6);
	data[0] = 0; data[1] = 97; data[2] = 127; data[3] = 128; data[4] = 254; data[5] = 255;
	res.setHeader('Content-Type', 'application/octet-stream');
	res.send(data);
});


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM