Eloquent JavaScript 笔记 十七:HTTP

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%20goodbye
console.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 OK
console.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还不熟,以后再看。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值