使用 GET 请求


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的请求发送,之后会看到若返回XMLJSON等其他数据格式,客户端将有弹性自行决定页面设计方式。

另外要注意的是,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 亦规范了GETPOST在发送请求参数时的编码,大致上也是编码为%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>

按我观看执行结果


展开阅读全文