# 网络

### 脚本化 HTTP

超文本传输协议规定 Web 浏览器如何从 Web 服务器获取文档和向 Web 服务器提交表单内容，以及 Web 服务器如何响应这些请求和提交。

术语 Ajax 描述了一种主要使用脚本操纵 HTTP 的 Web 应用架构。Ajax 应用的主要特点是使用脚本操纵 HTTP 和 Web 服务器进行数据交换，不会导致页面重载。避免页面重载的能力使 Web 应用感觉更像传统的桌面应用。Web 应用可以使用 Ajax 技术把用户的交互数据记录到服务器中；也可以开始只显示简单页面，之后按需加载额外的数据和页面组件来提升应用的启动时间。

实现 Ajax 的方式有很多种，而这些底层的实现有时称为传输协议。例如 img 元素的 src 属性，当脚本设置这个属性为 URL 时，浏览器发起的`HTTP GET`请求会从这个 URL 下载图片。因此，脚本通过设置 img 元素的 src 属性，且把信息作为图片 URL 的查询字符串部分，就能经过编码信息传递给 Web 服务器。Web 服务器实际上必须返回某个图片来作为请求结果。但它不一定要可见，例如，一个`1 × 1`像素的透明图片。这种类型的图片也称为网页信标，当网页信标不是当期网页服务器而是其他服务器交流信息时，会担心隐私内容。这种第三方网页信标的方式常用于统计点击次数和网站流量分析。

img 元素无法实现完整的 Ajax 传输协议，因为数据交换是单向的，客户端能发送数据到服务器，但服务器的响应一直是张图片导致客户端无法轻易从中提取信息。

`script`元素的`src`属性能设置 URL 并发起 `HTTP GET` 请求。使用 script 元素实现脚本操纵 HTTP 是非常吸引人的，因为它们可以跨域通信而不受限于同源策略。通常，使用基于 script 的 Ajax 传输协议时，服务器的响应采用 JSON 编码的数据格式，当执行脚本时，javascript 解析器能自动将其“解码”。由于它使用 JSON 数据格式，因此这种 Ajax 传输协议也叫做“JSONP”。

所有浏览器都支持 `XMLHttpRequest` 对象，它定义了用脚本操纵 HTTP 的 API。除了常用的 GET 请求，这个 API 还包含实现 `POST` 请求的能力，同时它能用文本或 `Document` 对象的形式返回服务器的响应。

### 使用 Ajax

浏览器在 `XMLHttpRequest` 类上定义了它们的 HTTP API。这个类的每个实例都表示一个独立的请求/响应对，并且这个对象的属性和方法允许指定请求细节和提取响应数据。

使用这个 HTTP API 必须做的第一件事就是实例化 `XMLHttpRequest` 对象：

```javascript
var request = new XMLHttpRequest()
```

你可以重用已存在的 `XMLHttpRequest`，但注意这将会终止之前通过该对象挂起的任何请求。

一个 HTTP 请求由 4 部分组成：

1. HTTP 请求方法或“动作”
2. 正在请求的 URL
3. 一个可选的请求头合集，其中可能包括身份验证信息。
4. 一个可选的主体。

服务器返回的 HTTP 响应包含 3 部分：

1. 一个数字和文字组成的状态码，用来显示请求的成功和失败。
2. 一个响应头集合。
3. 响应主体。

#### 指定请求

创建 `XMLHttpRequest` 对象之后，发起 HTTP 请求的下一步是调用 `XMLHttpRequest` 对象的 `open()`方法指定这个请求的两个必需部分：方法和 URL。

```javascript
request.open("GET",      // 开始一个HTTP GET请求
        "data.csv"  // URL的内容
```

除了`GET`和`POST`之外，`XMLHttpRequest` 规范也允许把`DELETE`、`HEAD`、`OPTIONS`和`PUT`作为 `open()`第一个参数。

`open()`第二个参数是 URL，它是请求的主题。这是相对于文档的 URL，这个文档包含调用 `open()`的脚本。如果指定绝对 URL、协议、主机和端口通常必须匹配所在文档的对应内容：跨域的请求通常会报错。(但是当服务器明确允许跨域请求时、2 级 `XMLHttpRequest` 规范允许它)。

如果有请求头的话，请求进程的下个步骤是设置它。例如，`POST` 请求需要`Content-Type`头指定请求主题的 `MIME` 类型：

```javascript
request.setRequestHeader('Content-Type', 'text/plain')
```

如果对相同的头调用 `setRequestHeader()`多次，新值不会取代之前指定的值，相反，`HTTP` 请求将包含这个头的多个副本或这个头将指定多个值。

自己不能指定`Content-Length`、`Date`、`Referer`或`User-Agent`头，`XMLHttpRequest` 将自动添加这些头而防止伪造它们。类似地，`XMLHttpRequest` 对象自动处理 `cookie`。连接时间、字符集和编码判断。

使用 `XMLHttpRequest` 发起 `HTTP` 请求的最后一步是指定可选的请求主体并向服务器发送它。使用 `send()`方法像如下这样做：

```javascript
request.send(null)
```

GET 请求绝对没有主体，所以应该传递 `null` 或省略这个参数。`send()`方法启动请求，然后返回，当它等待服务器的响应时并不阻塞。

一个完整的 HTTP 响应状态码、响应集合和响应主体组成。这些都可以通过 `XMLHttpRequest` 对象属性和方法使用：

1. `status` 和 `statusText` 属性以数字和文本的形式返回 HTTP 状态码。这些属性保存标准的 HTTP，像 `200` 和`OK`表示成功请求、`404` 和`Not Found`表示 URL 不能匹配服务器上的任何资源。
2. 使用 `getResponseHeader()`和 `getResponseHeaders()`能查询响应头。`XMLHttpRequest` 会自动处理 `cookie`：它会从 `getAllResponseHeaders()`头返回集合中过滤掉 `cookie` 头，而如果给 `getResponseHeader()`传递`Set-Cookie`和`Set-Cookie2`则返回 `null`。
3. 响应主体可以从 `responseText` 属性中得到文本形式的，从 `responseXML` 属性中得到 `Document` 形式的。

`XMLHttpRequest` 对象通常异步使用：发送请求后，`send()`方法立即返回，直到响应返回，前面列出的响应方法和属性才有效。为了在响应准备就绪时立即返回得到通知，必须监听 `XMLHttpRequest` 对象上的 `readystatechange` 事件。

`readyState` 是一个整数，它指定了 HTTP 请求的状态，下表列出了可能的值：

1. 常量 `UNSENT`，值 `0`，含义 `open()`尚未调用；
2. 常量 `OPENED`，值 `1`，含义 `open()`以调用；
3. 常量 `HEADERS_RECEIVED`，值 `2`，含义接受到头信息；
4. 常量 `LOADING`,值 `3`，含义接受到响应主体；
5. 常量 `DONE`，值 `4`，含义响应完成。

理论上，每次 `readyState` 属性改变都会触发 `readystatechange` 事件。实际中，当 `readyState` 改变为 `0` 或 `1` 时可能没有触发这个事件。当调用 `send()`时，即使 `readyState` 仍处于 `OPENED` 状态，也通常触发它。某些浏览器在 `LOADING`状态时能触发多次事件来给出进度反馈。当 `readyState` 值改变为 `4` 或服务器的响应完成时，所有的浏览器都触发 `readstatechange` 事件。因为在响应完成之前也会触发事件，所以事件处理程序应该一直检验 `readyState` 值。下面给出实例：

```javascript
function getText(url) {
  var request = new XMLHttpRequest()
  request.open('GET', url, true)
  request.onreadystatechange = function() {
    if (request.readyState === 4 && request.status === 200) {
      console.log(request.responseText)
    }
  }
  request.send(null)
}
getText('/test/text.php')
```

#### 同步响应

`XMLHttpRequest` 也支持同步响应。如果把 `false` 作为第 `3` 个参数传递给 `open()`，那么 `send()`方法将阻塞直到请求完成。在这种情况下，不需要事件处理程序：一旦 `send()`返回，仅需要检查 `XMLHttpRequest` 对象的 `status` 和 `responseText` 属性。在日常使用中应避免使用同步请求。

#### 编码求情主体

默认情况下，HTML 表单通过 `POST` 方法发送给服务器，而编码后的表单数据则用做请求主体。对表单数据使用的编码方案相对简单：对每个单元素的名字和执行普通的 URL 编码，使用等号把编码后的名字和值分开，并使用`&`符号分开名/值对。一个简单的编码如下：

```javascript
find=pizza&zipcode=02134&radius=1km
```

表单数据编码格式有一个正式地 `MIME` 类型：

```javascript
application / x - www - form - urlencode
```

当使用 `POST` 方法提交这种顺序的表单数据时，必须设置`Content-Type`请求头为这个值。

表单数据同样可以通过 `GET` 请求来提交，既然表单提交的目的是为了执行只读查询，因此 `GET` 请求比 `POST` 请求更合适。`GET` 请求从没有主体，所以需要发送给服务器的表单编码数据“负载”要作为 URL 的查询部分。

#### 上传文件

XHR2 API 允许通过向 `send()`方法传入 `FIle` 对象来事件上传文件。

#### multipart/form-data 请求

当 HTML 表单同时包含文件上传元素和其他元素时，浏览器不能使用普通的表单编码而必须使用称为`multipart/form-data`的特殊 `Content-Type` 来用 `POST` 方法来提交表单，这种编码包括使用长“边界”字符串把请求主体分离成多个部分。对于文本数据。

`XHR2` 定义了新的 FormData API，它容易实现多部分请求主体。首先，使用 `FormData()`构造函数创建 `FromData` 对象，然后按需要多次调用这个对象的 `append()`方法把个体“部分”添加到请求中。最后，把 `FormData` 对象传递给 `send()`方法。`send()`方法将对其请求定义合适的边界字符串和设置`Content-Type`头。

#### HTTP 进度事件

在支持 `XHR2` 的浏览器中，这些新事件会像如下这样触发。当调用 `send()`时，触发单个 `loadstart` 事件。当正在加载服务器的响应时，`XMLHttpRequest` 对象会发生 progress 事件，通常每隔 50 毫秒左右，所以可以使用这些事件给用户反馈请求进度。如果请求快速完成，它可能从不会触发 `progress` 事件。当事件完成，会触发 load 事件。

一个完成的请求不一定是成功的请求，例如，`load` 事件的处理程序应该检查 `XMLHttpRequest` 对象的 `status` 状态码来确定收到的是`200 OK`而不是`404 Not Found`的 HTTP 响应。

对于任何具体请求，浏览器将只会触发 `load`、`abort`、`timeout` 和 `error` 事件中的一个。`XHR2` 规范草案指出一旦这些事件中的一个发生后，浏览器应该触发 `loadend` 事件。

与 `progress` 事件相关的事件对象还有 `3` 个有用的属性。`loaded` 属性是目前传输的字节数值。`total` 属性是自`Content-Length`头传输的数据的整体长度(单位是字节)，如果不知道内容长度则为 `0`.最后，如果知道内容长度则 `lengthComputable` 属性为 `true`；否则 `false`。。用法如下：

```javascript
    request.onprogress = function(e) {
          if (e.lengthComputable)
            progress.innerHTML = Math.round(100*e.loaded/e.total)+"完成"；
    }
```

#### 上传进度事件

除了为监控 HTTp 响应的加载定义的这些有用的事件外，XHR2 也给出了用于监控 HTTP 请求上传的事件。在实现这些特性的浏览器中，`XMLHttpRequest` 对象将有 `upload` 属性。`upload` 属性值是一个对象，它定义了 `addEventListener()`方法和整个 `progress` 事件集合，比如 `onprogress` 和 `onload`。

你可以像使用常见的 `progress` 事件处理程序一样使用 `upload` 事件处理程序。对于 `XMLHttpRequest` 对象 `x`，设置 `x.onprogress` 以监控响应的下载进度，并且设置 `x.upload.onprogress` 以监控请求的上传进度。

#### 中止请求和超时

可以通过调用 `XMLHttpRequest` 对象的 `abort()`方法来取消正在进行的 HTTP 请求。

XHR2 定义了 `timeout` 属性来指定请求自动中止后的毫秒数，也定义了 `timeout` 事件用于当超时发生时触发。

#### 跨域 HTTP 请求

XHR2 通过在 HTTP 响应中发送合适的 CORS 允许跨域访问网站。虽然实现 CORS 支持的跨域请求工作不需要做任何事情，但有一些安全细节需要了解。如果给 `XMLHttpRequest` 的 `open()`方法传入用户名和密码，那么它们绝不会通过跨域请求发送。除外，跨域请求通常也不会包含其他任何用户证书：cookie 和 HTTP 身份验证令牌通常不会作为请求的内容部分送且任何作为跨域响应来接收的 `cookie` 都会丢弃。如果跨域请求需要这几种凭借属性才能成功，那么必须在用 `send()`发送请求前设置 `XMLHttpRequest` 的 `withCredentials` 属性为 `true`。这样做不常见但测试 `withCredentials` 的存在性是测试浏览器是否支持 CORS 的一种方法。

### JSONP

JSONP 作为一种 Ajax 传输机制：只须设置 script 元素的 src 属性(假如它还没有插入到 document 中，需要插入进去)，然后浏览器就会发送一个 HTTP 请求以下载 src 属性所指向的 URL。使用 script 元素进行 Ajax 传输的一个主要援用是，它不受同源策略的影响，因此使用它们从其他的服务器请求数据，第二个原因是包含 JSON 编码数据的响应体会自动解码(即，执行)。

出于脚本和安全性考虑，对于不可信的服务器不可以使用 JSONP。

响应内容格式：

```javascript
handleResponse(
  [1,2,{"buckle":"my shoe"}]
}
```

为了可行起见，我们必须通过某种方式告诉服务，它正从一个 script 元素调用，必须返回一个 JSONP 响应，而不是普通的 JSON 响应。这个可以通过在 URL 中添加一个查询参数来实现：例如，追加`？json`(或\&json)。

个人对 JSONP 的理解，在客户端事先定义好函数，比如 `myfunction`，然后从服务器请求一个 js 文件，文件会直接调用 `myfunction` 函数，传入的参数就是你所请求的数据。
