HTTP 定義 GET
應用於安全(Safe)操作,使用者採取的動作必須避免有他們非預期的結果。慣例上,GET 與 HEAD(與 GET 同為取得資訊,不過僅取得回應標頭)對使用者來說就是「取得」資訊,不應該被用來「修改」與使用者相關的資訊,像是進行轉帳之類的動作。
HTTP 的定義中,GET
也應當用於等冪(idempotent)操作,也就是單一請求產生的副作用,與同樣請求進行多次的副作用必須是相同的。
如果使用傳統表單發送 GET
請求,GET
的請求參數會出現在網址列並更新頁面,但使用 XMLHttpRequest
時,GET
的請求參數並不會影響網址列,所以使用者無法直接將請求參數當作書籤網址列的一部份。
要使用非同步物件透過 GET
發送請求參數,只要在第二個 url
參數中以請求參數格式附加,而 send
時不傳入引數設為 null
即可。一個例子如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
</head>
<body>
圖書:<br>
<select id="category">
<option>-- 選擇分類 --</option>
<option value="theory">理論基礎</option>
<option value="language">程式語言</option>
<option value="web">網頁技術</option>
</select><br><br>
採購:<div id="book"></div>
<script type="text/javascript">
document.getElementById('category').onchange = function(evt) {
var request = new XMLHttpRequest();
request.onload = function(evt) {
let req = evt.target;
if(req.status === 200) {
document.getElementById('book').innerHTML = req.responseText;
}
};
let time = new Date().getTime();
let url = `GET-1.php?category=${evt.target.value}&time=${time}`;
request.open('GET', url);
request.send(null);
};
</script>
</body>
</html>
這個例子是連動選單,下一個下拉選單的選項是根據上一個下拉選單的選擇而定,事先不在網頁中寫死第二個選單的選項,而是根據上一個選單所發送的請求參數而定,例如若請求參數為 category=theory
,會傳回以下的HTML片段:
<select>
<option value="algorithm">常見演算</option>
<option value="graphic">電腦圖學</option>
<option value="pattern">設計模式</option>
</select>
當然,直接傳回 HTML 片段,並不是很好的方式,因為伺服端綁死了客戶端的頁面設計。這個範例只是用來示範 GET
的請求發送,之後會看到若傳回 XML
或 JSON
等其他資料格式,客戶端將有彈性自行決定頁面設計方式。
另外要注意的是,GET
請求時若 URL 相同,瀏覽器可能會作快取,為了避免取得舊的資料,可以在 URL 上附加時間戳記,讓每次 URL 不同,以避免瀏覽器作快取的動作。
Web 的世界中,故事往往不會這樣就結束。GET
在發送請求時,必須注意編碼的問題,因為 /
、?
、@
、空白等字元,在 URL 中是保留字,RFC 3986 規範了哪些字作為保留字,如果要在 URL 表達這些保留字或一些非 ASCII 字元,必須使用 %hexhex
編碼形式。例如 url=https://openhome.cc
,若要在 URL 中表示,必須處理為 url=https%3A%2F%2Fopenhome.cc
,其中 %3A%2F%2F
分別就是 ://
三個字元編碼處理後的結果。
在 JavaScript 中,可以使用 encodeURIComponent
作這些字元的編碼,編碼後的結果是遵守 RFC 3986 的規範,然而在 RFC 3986 之前,HTTP 亦規範了 GET
與 POST
在發送請求參數時的編碼,大致上也是編碼為 %hexhex
,不過空白字元是編碼為 +
而不是 RFC 3986 的 %20
。
如果直接透過瀏覽器按下發送按鈕來送出表單,瀏覽器會自動處理編碼(依網頁上指定的編碼來處理),並將空白字元編碼為 +
,但透過 XMLHttpRequest
發送請求參數時,必須自行處理。
發送請求參數時,若使用 encodeURIComponent
編碼後,要再將 %20
取代為 +
,以符合 HTTP 的規範。要注意的是,在字串處理方面,JavaScript 支援 Unicode,內部實作上採用 16 位元編碼每個字串元素,大致上可視為 UCS-2/UTF-16(這當中還有些歷史因素造成的細節,詳見 Effective JavaScript 一書條款七),不過,傳入 encodeURIComponent
的字串最後會以 UTF-8 進行編碼,若將 encodeURIComponent
的結果透過非同步物件發送出去,伺服端必須以 UTF-8 來處理接收到的字串。
下面這個範例是 GET
的另一個示範,在新增書籤時,若 URL 已重複(已有的書籤是 http://caterpillar.onlyfun.net 與 https://openhome.cc)則以訊息提示:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
</head>
<body>
新增書籤:<br>
網址:<input id="url" type="text">
<span id="message" style="color:red"></span><br>
名稱:<input type="text">
<script type="text/javascript">
// 組合與編碼請求參數
function params(paraObj) {
return Object.keys(paraObj)
.map(name => {
let paraName = encodeURIComponent(name);
let paraValue = encodeURIComponent(paraObj[name]);
return `${paraName}=${paraValue}`.replace(/%20/g, '+');
})
.join('&');
}
document.getElementById('url').onblur = function() {
let request = new XMLHttpRequest();
request.onload = function(evt) {
let req = evt.target;
if(req.status === 200 && req.responseText === 'existed') {
document.getElementById('message').innerHTML = 'URL 已存在';
}
};
let reqParams = params({
url : document.getElementById('url').value
});
let time = new Date().getTime();
request.open('GET', `GET-2.php?${reqParams}&time=${time}`);
request.send(null);
};
</script>
</body>
</html>