因特网、HTTP协议、Ajax、Fetch API

1.因特网

我们在手机、电脑上所做的一切需要连网的事情,比如搜东西、看文章、看视频、聊天、打游戏……都是在一个叫「因特网」的东西的支持下完成的。什么是因特网呢?

人们经常提到「互联网」这个词,它的意思是能彼此通信的设备组成的网络,就像是人和人之间的关系网。其实,找两台设备连接起来,让它们能够彼此通信,它们两个就形成了一个小小的互联网。「因特网」是一个由全世界几十亿台设备共同组成的超级大的互联网,一般说互联网就是指因特网。

只要你的手机或者电脑连了网,它就接入了因特网。

有个问题:连接两个设备需要一条线,既然你的电脑和因特网上的其他电脑有了连接,那得需要多少根线呢?这个思路肯定不对,几十亿台分布在世界各地的设备,一定要用更好的方法去连接。

2.DNS和IP地址

几十亿台设备就是像上图那样连的,在互联网中的每个设备都有一个唯一的门牌号,这样大家就可以互相准确地分辨了,这个门牌号的正确叫法是IP地址,它是一串数字组成的,不太好记。在百度搜索框输入'ip',就能知道你的设备的IP地址。

你到现在还不知道你电脑的IP是多少,是因为你不需要知道,只有当别人需要连接到你的电脑上时才需要知道你电脑的IP,这和你给别人寄东西时必须知道他的地址是一个道理。你要看到百度的网页,就必须先告诉百度的服务器你要看,所以你在浏览器地址栏输入了www.baidu.com

你知道百度的网址而不知道百度的IP地址,就像你知道朋友的名字而不知道他的电话号码,你得靠通讯录才行。记录网址和IP的通讯录不在你的设备上,而在你的互联网服务提供商(ISP)那里。

你输入网址后,该网址被你的路由器转发给了你的ISP,ISP会根据该网址查询对应的IP地址,然后再把IP地址给你发送回来,这样你的电脑就能把你要看网页的想法发送给百度的服务器了。根据网址查询IP的过程被称为域名解析(Domain Name Resolution),这是由你的ISP提供的DNS(Domain Name System)服务负责的。

3.URL

www.baidu.com只是一串字符,为什么它能准确地帮你带回百度的网页呢?

其实,对你来说它只是一串字符,而对浏览器来它说是一个叫URL(统一资源定位符)的东西。

你看到的网页是百度服务器上存储的一个文件,服务器也是一台电脑。你身在兰州,却能仅凭短短的一串字符就拿到远在北京的某个服务器上某个文件夹里的特定的文件,靠的就是URL的定位功能。

我们用一个详细地址加上姓名就可以把信件准确地发送到某个人手里,这和通过一个网址打开一个网页是一样的道理,在网络的世界里,使用正确的URL就可以准确地定位到一个文件。你输入的www.baidu.com经过浏览器的处理再发送给百度服务器后,就已经足以让它知道该把哪一个网页发给你。

浏览器把这个字符串处理成了正确的URL——加上了一些东西:

  • 协议,就是事先约定的规则,说明了访问这个URL对应的资源需要使用哪个规则。

  • 域名,代表了一个服务器,一串数字肯定没有一个域名那么好记。

  • 路径,就是文件路径,和你的电脑上表示一个文件路径的方法一样,/表示最外层的那个文件夹。

  • 灰色的部分不是必需的。

4.协议

网络里的事情并非那么简单,计算机在背后干了太多脏活累活。

输入格式正确的URL,比如https://www.baidu.com/,就会发起对应协议的网络请求,从而获得URL对应的资源;而输入随意的,比如---://www.baidu.com,就会让浏览器开始搜索这一串字符。

人们在网上会进行各种活动,比如看网页、下载文件、发电子邮件……这些活动都需要使用不同的协议。这些协议由一个机构统一管理颁布,现在已经有30多种协议。在浏览器上主要的活动就是查看和操作网页,你需要关注的是HTTP协议。总之,你给浏览器一个什么协议开头的URL,它就会发起一个什么协议的请求。

一个请求必须要符合某些约定才能被服务器理解和响应,协议就是这些约定的具体条目,虽然还不知道具体内容会是什么样,但是可以想象,这和请假时写假条、借钱时写欠条是一个道理,浏览器要求访问网页就得根据协议去“填表”。

5.HTTP协议

如果把一个HTTP请求看作是浏览器填写的一张表,那么这张表必须得让服务器拿到手才行,所以要先在浏览器和服务器之间建立一条通路,以方便发送请求和获得响应。这条通路叫做TCP连接,它背后的协议叫做TCP协议(传输控制协议)。在TCP连接上,数据会被分割成小块来传送,而且能够确认最终是否送达。

建立了TCP连接之后就会把那张表发过去,那张表其实叫做请求报文

服务器收到之后,就会做出响应,也发来一张表,叫做响应报文

请求方法

请求报文第一行第一个单词就是请求方法, 它指明了这个请求想要在服务器上做什么类型的操作,常用的有:

  • GET:获取一个网页或者一些数据
  • POST:向服务器发送一些数据
  • PUT:替换或创建一个资源
  • PATCH:部分修改一个资源
  • DELETE:删除一个资源

HTTP状态码

服务端响应请求时会用状态码来表示进行中、成功、失败等状态,响应报文第一行的200 OK就是一个状态码。状态被分为5类,用1XX ~ 5XX分别代表不同的含义:

  • 1XX:信息,请求正在处理,码值范围为[100~199]
  • 2XX:状态,请求正常处理完毕,码值范围为[200~299]
  • 3XX:重定向,这会让客户端重新向重定向的地址发起请求,码值范围为[300~399]
  • 4XX:客户端错误,请求本身有问题,服务器无法处理,码值范围为[400~499]
  • 5XX:服务端错误,服务端处理请求时发生内部错误,码值范围为[500~599]

完整的定义可在MDN网站上找到,每类状态都有几个最基本和常用的状态码:

  • 200 OK:一切正常
  • 204 No Content:请求处理成功,但没有什么实体返回,用于只需客户端给服务端发送信息的情况
  • 206 Partial Content:成功处理了部分GET请求,迅雷等HTTP下载工具使用此状态实现断点续传
  • 301 Moved Permanently:表示请求的资源已经永久性地移动到某个新位置,这样客户端就应该使用新的URL了
  • 400 Bad Request:请求报文中有错,服务器无法处理
  • 401 Unauthorized:这个请求没有提供必要的用户认证信息,或者认证失败
  • 403 Forbidden:权限问题,发出请求的IP地址不被允许,或者请求的资源未获得文件系统授权
  • 404 Not Found:表示URL不对应于任何一个资源,也就是说,这是一个错误的地址
  • 410 Gone:请求的资源曾经可用,但现在已经不可用
  • 409 Conflict:常见于PUT请求中,请求提交的资源和服务器上目前的资源发生了冲突,响应应该告知客户冲突的细节,以便客户修正资源后重新提交
  • 500 Internal Server Error:表示服务器内部出错,客户对此毫无办法
  • 503 Service Unavailable:服务器处于超负荷状态或者正在停机维护

6.HTTPS

HTTP协议存在一些不足,而HTTPS能够解决它们。

HTTP协议使用明文通信

HTTP协议是工作在TCP/IP协议族之上的,由于TCP/IP协议族的工作机制,通信线路上的每个环节都可能遭到窃听。抓包和嗅探工具可以获取到你发的HTTP请求报文以及响应报文。

解决的方法就是对通信方式和通信内容进行加密:

  • 加密通信

    HTTP协议中没有安全机制,所以需要其他协议来建立一条安全的通信线路,然后在该线路上发送HTTP请求。

    可以建立安全线路的协议有:安全套接层(SSL, Secure Socket Layer)、传输层安全(Transport Layer Security)

    把SSL协议和HTTP协议组合使用,就成了HTTPS协议(HTTP Secure 或 HTTP over SSL)。

  • 内容加密

    对通信内容加密可以让窃听者犯难,但这并非万无一失,加密和破解本来就是无休止的战争。

任何人都可以发起请求

HTTP协议没有验证请求发起者的步骤,如果服务器没有对IP和端口进行限制,那么只要知道了URL就可以发起请求,另外,客户端也没有办法验证服务端是不是伪造的。

解决方法是查看证书验明身份。

使用SSL协议时,会有一个第三方机构颁发的数字证书,通过它就可以验明通信双方的身份了。

该数字证书是收费的。

HTTP报文可能会被篡改

HTTP协议在发出请求和接收响应的过程中如果报文被拦截并修改,客户端和服务端是无法知道的。

SSL协议会使用有效的手段来保证数据的完整性

HTTP+ 加密 + 认证 + 完整性保护 = HTTPS

7. Ajax

通过网页上的某个部件向服务器发送HTTP请求,获取数据,进而修改页面是一个Web应用最基础的需求。

Ajax是一种能够在无需刷新整个页面的情况下使用远程数据修改页面的技术。

Ajax的核心是XMLHttpRequest对象,如今所有的浏览器都支持该对象,而古老的IE5和IE6则使用ActiveXObject对象。使用XMLHttpRequest对象分为四步:

  • 创建并配置XHR对象
  • 执行XHR.open函数,启动一个请求
  • 执行XHR.setRequestHeader函数,设置该请求的首部,这一步是可选的
  • 执行XHR.send函数,设置请求体并发送请求

创建并配置XHR对象

使用之前必须创建XHR对象,下面是支持所有浏览器的写法:

let XHR = 
    window.XMLHttpRequest ? new XMLHttpRequest() : 
		window.ActiveXObject ? new ActiveXObject("Microsoft.XMLHTTP") : null;

这一步创建了这样一个东西:

收到响应后,响应的数据会填充到这里。

readyState属性是用来指示当前请求的进度状态的,它会自动变化,每次变化都会触发onreadystatechange事件,所以这是让我们执行响应动作的机会——在onreadystatechange事件中读取readyState值,并执行一些代码。

因此需要了解readyState值的含义:

  • 0:尚未调用open函数
  • 1:已经调用open函数,但未调用send函数
  • 2:已经调用send函数,但还没有收到响应
  • 3:已经收到部分响应
  • 4:响应全部完成

现在可以设置回调函数了:

XHR.onreadystatechange = function(){
  if(XHR.readyState === 4 && XHR.status === 200){
    console.log("请求成功!")
  }
}

执行open,启动请求

这一步只是填写了HTTP请求报文的请求行,需要指定请求方法、URL:

//第三个参数表示用同步还是异步方式执行,true为异步
XHR.open("get", "someURL.txt", true)	

执行setRequestHeader,设置首部(可选)

请求首部信息一般是由浏览器来自动填写的,使用XHR的setRequestHeader即可手动设置:

//参数分别为字段名称和字段值
XHR.setRequestHeader("Authorization", token)

执行send,设置请求体,发送请求

send方法用来发送HTTP请求,如果需要发送一些数据实体,也是在这一步设置的:

//参数是数据实体,可以是JSON、数组、文件(blob)等
XHR.send({name: 'abc'})

8.最常用的HTTP请求方法

GET

GET请求最为常见,用于从服务器查询信息。

如果服务器需要一些额外信息,比如搜索的关键字,就需要追加到URL上。URL的?查询部分就是这个作用。

//追加了name和age两个查询字符串
XHR.open("GET", "example.com/path?name=paykan&age=29")

有时候查询字符串可能需要用encodeURIComponent函数来编码一下。

如果使用了jQuery的ajax()方法,那么只要传递一个data参数,它就会把数据拼接到URL上去:

$("button").click(function(){
  $.ajax({
    url:"https://example.com/path", 
    data: {username: 'paykan', password: '123456'}, 
    success:function(result){
      $("#div1").html(result);
    }
  });
});
//请求的URL将会是 https://example.com/path?username=paykan&password=123456

POST

发起POST请求时必须设置请求体,请求体可以是任何数据。如果想用POST请求来提交一个表单,则需要使用FormData函数对这个表单的数据进行序列化:

XHR.open("POST", ...);
let form = document.getElementById('user-info');
XHR.send(new FormData(form))

FormData是JS内置的一个函数,详情可参考这个文档

9.Fetch API

Fetch是Ajax的代替方案,它是JS提供的API,而不是对XMLHttpRequest对象的重新封装,使用Fetch的前提是理解ES6的Promise。

万变不离其宗,发送一个HTTP请求,最核心的就是设置请求、发送请求、处理响应。

最简单的fetch使用可以只提供一个URL,然后fetch函数就会返回一个Promise对象。然而实际应用中肯定需要进行各种设置,fetch函数的第二个参数叫做init对象

function sendData(URL, data){
  return fetch(URL, {
    method: 'POST',	//GET、POST、PUT、DELETE...
    headers: {"Content-Type": "application/json"},
    body: JSON.stringify(data),	//请求体,GET请求不能有
  }).then(response = response.json())
}

sendData('https://example.com/path', {username: 'paykan', password: '123456'})
	.then(data = console.log(data))
	.catch(error = console.log(error))

上面的代码使用async/await改写就更好了:

function sendData(URL, data){
  return fetch(URL, {
    method: 'POST',	//GET、POST、PUT、DELETE...
    headers: {"Content-Type": "application/json"},
    body: JSON.stringify(data),	//请求体,GET请求不能有
  })
}

$('#btn').click(async function(){
    let response = await sendData(
    'https://example.com/path', 
    {username: 'paykan', password: '123456'}
  )
  try {
    let data = response.json();
  	console.log(data)
  } catch (error) {
    console.log(error)
  }
})

错误处理

注意,Promise对象的catch函数或者async/await的trt...catch都是针对Promise的rejected状态进行错误处理,然而fetch只会在网络错误或请求被阻拦时将Promise的状态标为rejected,一般的HTTP错误状态码只会被标为resolved,所以判断是否出错还要看response.ok是不是为true。

fetch('https://example.com/path').then(function(response) {
  if(response.ok) {
    return response.blob();
  }
  throw new Error('Network response was not ok.');
})

关于Fetch,最权威的文档在这里

posted @ 2020-05-16 13:28  Paykan  阅读(412)  评论(0编辑  收藏  举报