Eloquent JavaScript 笔记 十七:HTTP

来源:互联网 发布:php bin2hex写入文件 编辑:程序博客网 时间:2024/06/06 00:11

1. The Protocol

在browser的地址栏输入 eloquentjavascript.net/17_http.html,browser会查找 eloquentjavascript.net 这个server,尝试与它的80端口建立TCP连接。

request:

如果连接成功,发送如下请求:

  GET /17_http.html HTTP/1.1
  Host: eloquentjavascript.net
  User-Agent: Your browser's name

第一行:

  method: GET, POST, DELETE, PUT

  path: server 上某个资源的路径。是磁盘上的文件还是临时生成的html,由server决定。

  version: HTTP 协议的版本号。

第二行及后面的都是header,以键值对方式出现。 Host 是必须的属性。

如果是POST,在header之后会有一个空行,空行之后是要上传的数据。

response:

server的应答如下:

  HTTP/1.1 200 OK
  Content-Length: 65585
  Content-Type: text/html
  Last-Modified: Wed, 09 Apr 2014 10:48:09 GMT

  <!doctype html>
  ... the rest of the document

第一行:

  version: HTTP 协议版本

  200: status code。 2xx 代表请求成功,4xx 代表 request 有问题(例如:404, 请求的资源不存在),5xx 代表服务器发生错误。

  OK:status code对应的说明,便于人类阅读。

第二行及后面的都是header,以键值对方式出现。

空行以后是数据,通常叫做body。就是我们请求的html文件,browser会把它显示出来。

2. Browsers and HTTP

<form method="GET" action="example/message.html">  <p>Name: <input type="text" name="name"></p>  <p>Message:<br><textarea name="message"></textarea></p>  <p><button type="submit">Send</button></p></form>
点击Send按钮,会发起request:

  GET /example/message.html?name=Jean&message=Yes%3F HTTP/1.1

看看如何通过form生成这个path:

  1. method="GET" 对应request中的 GET

  2. action="example/message.html" 对应request中的path

  3. <input type="text" name="name"> 对应 request中问号后面的第一个参数 name=Jean 。 我们假设用户在testfield 中输入了: Jean 。

  4. <textarea name="message"> 对应request中的第二个参数 message=Yes%3F。我们假设用户在textarea中输入了: Yes? 。

  5. ?name=Jean&message=Yes%3F 这叫querystring。

在url中一些字符有特殊的含义,例如:?,&,= 等等,遇到这些字符需要escape,如上面的 %3F,实际上它是个 ?。这叫 URL encoding。

如果我们自己的js代码中需要做URL encoding 或 decoding,可以使用js提供的两个函数:

console.log(encodeURIComponent("Hello & goodbye"));// → Hello%20%26%20goodbyeconsole.log(decodeURIComponent("Hello%20%26%20goodbye"));// → Hello & goodbye

如果<form method="POST" ...>,name=Jean&message=Yes%3F 会作为request的body发送,而不是作为path中的querystring。

  POST /example/message.html HTTP/1.1
  Content-length: 24
  Content-type: application/x-www-form-urlencoded

  name=Jean&message=Yes%3F

3. XMLHttpRequest

使用XMLHttpRequest可以通过js代码发送http request。不用刷新整个html页面就可以更新页面的一部分。是Microsoft发明的,最初用在IE中。后来主流浏览器都实现了这个功能。

一个应用实例:搜索引擎的备选列表


4. Sending a Request

var req = new XMLHttpRequest();req.open("GET", "example/data.txt", false);req.send(null);console.log(req.responseText);// → This is the content of data.txt
说明:

  1. open() 的第一个参数,request method

  2. open() 的第二个参数 "example/data.txt",这是一个相对地址,server上相对于当前页面的路径。如果以 / 开头,指server的根目录。

  3. open() 的第三个参数,fasle - 同步请求,收到server的response之后,才会req.send() 才会返回。 true - 异步请求。

  4. send() 发送数据,body,对于GET,可以是null。

  5. req.responseText,response的body。

XMLHttpRequest 还提供了其他一些属性:

var req = new XMLHttpRequest();req.open("GET", "example/data.txt", false);req.send(null);console.log(req.status, req.statusText);// → 200 OKconsole.log(req.getResponseHeader("content-type"));// → text/plain
req.status, req.statusText, getResponseHeader() 等等。

注意,header名字是不区分大小写的。content-type 和 Content-Type 一样。

在发送请求前,我们可以用 setRequestHeader() 添加额外的 header。

5. Asynchronous Requests

鉴于网络环境的不确定性,最好使用异步请求:

var req = new XMLHttpRequest();req.open("GET", "example/data.txt", true);req.addEventListener("load", function() {  console.log("Done:", req.status);});req.send(null);
说明:

  1. open() 的第三个参数是true。

  2. send() 函数立即返回,而此时还没有收到server的response,所以requestText为空。

  3. 添加 load 事件句柄,收到server的response时,该事件会被触发。

6. Fetching XML Data

XMLHttpRequest 之所以叫这个名字,是因为上世纪末,微软大力推广XML,期望XML成为互联网数据传输的标准。XMLHttpRequest为方便的处理XML文档,提供了辅助函数:

var req = new XMLHttpRequest();req.open("GET", "example/fruit.xml", false);req.send(null);console.log(req.responseXML.querySelectorAll("fruit").length);// → 3
req.responseXML

但时至今日,人们更倾向于使用JSON:

var req = new XMLHttpRequest();req.open("GET", "example/fruit.json", false);req.send(null);console.log(JSON.parse(req.responseText));// → {banana: "yellow", lemon: "yellow", cherry: "red"}

7. HTTP SandBoxing

XMLHttpRequest 可以向任意url发送请求吗?

基于安全原因,它只能向同一网站发送请求。这也叫 sandbox。

如果server认为可以允许向其他网站发送请求,可以在自己的response中添加如下header:

  Access-Control-Allow-Origin: *

8. Abstracting Requests

第十章讲AMD时,提到过 backgroundReadFile(),可以这么实现:

function backgroundReadFile(url, callback) {  var req = new XMLHttpRequest();  req.open("GET", url, true);  req.addEventListener("load", function() {    if (req.status < 400)      callback(req.responseText);  });  req.send(null);}
上面的代码没有做错误处理。 错误处理有两种:

  1. 可预期的,如上面的 req.status,在 if 语句后面需要加上else。

  2. 异常,对于异步请求,我们无法使用try catch捕获异常,因为,我们无法把异步代码用try包起来,send() 函数会立即返回,真正与服务器通信的代码不再我们控制范围之内。

try {  backgroundReadFile("example/data.txt", function(text) {    if (text != "expected")      throw new Error("That was unexpected");  });} catch (e) {  console.log("Hello from the catch block");}
这个try只能捕获 Error("That was unexpected"); ,因为真正的通信代码都不在try块之内。

我们把backgroundReadFile() 写的更通用一些 getURL(),为了处理可预期的error,callback() 需要增加一个参数:

function getURL(url, callback) {  var req = new XMLHttpRequest();  req.open("GET", url, true);  req.addEventListener("load", function() {    if (req.status < 400)      callback(req.responseText);    else      callback(null, new Error("Request failed: " +                               req.statusText));  });  req.addEventListener("error", function() {    callback(null, new Error("Network error"));  });  req.send(null);}
getURL的使用方法:

getURL("data/nonsense.txt", function(content, error) {  if (error != null)    console.log("Failed to fetch nonsense.txt: " + error);  else    console.log("nonsense.txt: " + content);});
这么写对于异常的捕获没有任何帮助。

9. Promises

异步函数的异常如何处理? 如果有多个异步功能需要一个接一个的执行,代码应该怎么写? 想一想就头大。

还好,为了解决这个问题,有人写了一个库 promise (www.promisejs.org) 。 而且,这个功能已经被下一个js版本采纳。但现在,我们还是只能包含这个第三方库。

先去官网下载 promise.js,包含在自己的html中。

用Promise改写上面的例子:

function get(url) {  return new Promise(function(succeed, fail) {    var req = new XMLHttpRequest();    req.open("GET", url, true);    req.addEventListener("load", function() {      if (req.status < 400)        succeed(req.responseText);      else        fail(new Error("Request failed: " + req.statusText));    });    req.addEventListener("error", function() {      fail(new Error("Network error"));    });    req.send(null);  });}
使用方法:

get("example/data.txt").then(function(text) {  console.log("data.txt: " + text);}, function(error) {  console.log("Failed to fetch data.txt: " + error);});
说明:

  “new Promise(function(succeed, fail) { ”  这里面的succeed和fail都是function,就是then() 函数传入的两个function。

看一个串联多个异步行为的例子:

    function showMessage(msg) {        var elt = document.createElement("div");        elt.textContent = msg;        return document.body.appendChild(elt);    }    function getJSON(url) {        return get(url).then(JSON.parse);    }    var loading = showMessage("Loading ...");    getJSON("example/bert.json").then(function (bert) {        return getJSON(bert.spouse);    }).then(function (spouse) {        return getJSON(spouse.mother);    }).then(function (mother) {        showMessage("The name is " + mother.name);    }).catch(function (error) {        showMessage(String(error));    }).then(function () {        document.body.removeChild(loading);    });

功能流程: 

  1. 显示loading信息。

  2. 读取 bert.json,

  3. 完成后,读取他配偶的数据,

  4. 完成后,读取配偶妈妈的数据,

  5. 完成后,显示妈妈的名字。

  6. 如果出错,显示error。

  7. 最后,无论成功还是失败,移除loading信息。

如果一个Promise(叫它PromiseA)的then() 返回一个Promise(叫它PromiseB),那么,PromiseA执行完之后,就会执行PromiseB。上个例子中串联了多个异步调用。 

其中任何一个异步调用出错,都会跳转到catch。 最后一个then类似于异常处理中的finally,不论发生任何情况,都会被执行。

Promise 的原理和接口远不止这些,需要找一个专门的文档看一看。

10. Appreciating HTTP

bla bla bla,没啥好看的。

11. Security and HTTPS

通过HTTP访问网络时,中间不知道要经过多少台路由器、网关、服务器,而HTTP协议本身传输的内容都是明文(文本、字符串),如果有敏感/重要数据的话,这是很危险的,中途可能被人劫持、篡改。例如,银行账号、密码。

为了提高安全性,需要用HTTPS。通过HTTPS协议传输的数据都是加密的,使用非对称加密算法。由大家信任的机构分发证书,发送数据时,使用证书中的密钥加密。由于算法足够复杂,破解很困难,从而避免了数据劫持。

12. Exercise: Content Negotiation

客户端给server发送请求时,可以添加一种header: 

   accept: type

type就是文档类型,例如:text/html, appplication/json 等。

server根据type,发送不同的response。

function requestAuthor(type) {  var req = new XMLHttpRequest();  req.open("GET", "http://eloquentjavascript.net/author", false);  req.setRequestHeader("accept", type);  req.send(null);  return req.responseText;}var types = ["text/plain",             "text/html",             "application/json",             "application/rainbows+unicorns"];types.forEach(function(type) {  try {    console.log(type + ":\n", requestAuthor(type), "\n");  } catch (e) {    console.log("Raised error: " + e);  }});

13. Waiting for Multiple Promises

对promise还不熟,以后再看。


原创粉丝点击