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>
按我观看执行结果。