ajax 是前端用于发送接口请求的技术,它是异步的,需要等待结果返回后执行在发送 ajax 请求时,我们可能会这样去写。ajax({ url: '', method: '', data: {}, params: {}, success: function (res) {}, error: function (err) {}})url: 接口请求地址;method: 接口请求方法,如:get、post 等;data: 请求时使用 body 传输的数据,一般用于 post 请求中;params: 请求时使用 url 传递的数据,一般用于 get 请求中;success: 接口请求成功时的回调,参数为接口成功的返回值;error: 接口请求失败时的回调,参数为抛出异常时的调用栈等信息。XMLHttpRequest 是浏览器提供的对象,用于进行后台与服务端的数据进行交互
交互过程中,发送请求是第一步。那么,我们将如何构造一个请求呢?这一章节,我们将一步一步来构建一个 Ajax 请求。学习本节,你将学会:如何通过 XMLHttpRequest 和 ActiveXObject 来构造一个通用的 xhr 对象。如何通过 xhr 对象来发送 GET、 POST 等请求。Content-type 在 Ajax 数据发送中的作用。那么,接下来让我们进入本节的学习吧。
如图所示,请求正确接口的 Ajax 请求都得到了正确的返回。而访问服务端暂时没有的接口则返回了 404 错误。同时,GET 请求中没有显式提供 method,默认配置也能够及时生效,默认为 GET。
大部分开发者都会合理、巧妙的运用 this 关键字。初学者容易在 this 指向上犯错,如下面这个 Vue 组件:<div id="app"></div><script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.9/vue.min.js"></script><script> // 发送post请求 const post = (cb) => { // 假装发了请求并在200ms后返回了服务端响应的内容 setTimeout(function() { cb([ { id: 1, name: '小红', }, { id: 2, name: '小明', } ]); }); }; new Vue({ el: '#app', data: function() { return { list: [], }; }, mounted: function() { this.getList(); }, methods: { getList: function() { post(function(data) { this.list = data; console.log(this); this.log(); // 报错:this.log is not a function }); }, log: function() { console.log('输出一下 list:', this.list); }, }, });</script>这是初学 Vue 的同学经常碰到的问题,为什么这个 this.log() 会抛出异常,打印了 this.list 似乎也是正常的。这其实是因为传递给 post 方法的回调函数,拥有自己的 this,有关内容可以查阅 this章节。不光在这个场景下,其他类似的场景也要注意,在写回调函数的时候,如果在回调函数内要用到 this,就要特别注意一下这个 this 的指向。可以使用 ES6 的箭头函数 或者将需要的 this 赋值给一个变量,再通过作用域链的特性访问即可:<div id="app"></div><script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.9/vue.min.js"></script><script> // 发送post请求 const post = (cb) => { // 假装发了请求并在200ms后返回了服务端响应的内容 setTimeout(function() { cb([ { id: 1, name: '小红', }, { id: 2, name: '小明', } ]); }); }; new Vue({ el: '#app', data: function() { return { list: [], }; }, mounted: function() { this.getList(); }, methods: { getList: function() { // 传递箭头函数 post((data) => { this.list = data; console.log(this); this.log(); // 报错:this.log is not a function }); // 使用保留 this 的做法 // var _this = this; // post(function(data) { // _this.list = data; // console.log(this); // _this.log(); // 报错:this.log is not a function // }); }, log: function() { console.log('输出一下 list:', this.list); }, }, });</script>这个问题通常初学者都会碰到,之后慢慢就会形成习惯,会非常自然的规避掉这个问题。
简单来说,跨域请求就是一个域下的资源请求另外一个域下的资源。同一个域,指的是,协议名、域名、端口号都一致。 举个例子来说,假如 “http://www.a.com” 下的 JavaScript 脚本发起 Ajax 请求 “http://www.a.com/ajax” ,由于 协议名 http 、域名 www.a.com 和 端口号(默认都是 80)三者都是一致的,因此都属于同一个域,不造成跨域请求。而假如其中任一元素不相同,则造成跨域请求。与此同时,浏览器出于安全考虑,基于同源策略则会做一定的限制:比方说:无法获取不同域的 Cookie、LocalStorage 等等。无法获取不同域的 DOM 对象。无法向不同域发送 Ajax 请求。
话不多说,上代码:xhr.open("POST", "http://localhost:8080/simple/post");xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");xhr.send("mk=慕课网&class=ajax");查看效果图:基本和上面 GET 请求类似,这里我们构造了一个 POST 请求,请求的 url 为 http://localhost:8080/simple/post,发送请求的参数有两个,分别为 mk=慕课网 和 class=ajax。从浏览器的控制台面板上可以看到,在 Headers 上,Form Data 部分正是我们要发送的数据,数据发送正常。这里两个地方需要注意:send 方法接受可选参数作为请求主体,即发送到服务器的内容。Content-type 需要设置为请求主体类型, 这是因为如果不设置的话会采取默认值,在很多时候服务端可能无法解析参数。XMLHttpRequest.setRequestHeader() 是请求HTTP 请求头部的方法,因此设置 Content-type 自然也是通过调用这个方法来实现。该方法需要在 open() 和 send() 之间使用。
function ajaxError(){ alert('ajax error');}function ajaxSuccess(result){ if (result.error) { alert('操作失败'); return; } location.reload();}客户端使用 ajax 技术请求服务端的服务。当 ajax 请求失败时,调用 ajaxError,提示用户 ajax 请求服务失败;当 ajax 请求成功时,调用 ajaxSuccess,提示用户 ajax 请求服务成功。
// 服务端现有接口,进行 post 请求Ajax({ method: 'post', url: '/simple/post', data: { a:1, b:2 }}).then(data => { console.log(data)}).catch(e => { console.log('/simple/post', e)})// 服务端暂时没有的接口, 进行 post 请求Ajax({ method: 'post', url: '/test/post', data: { a:1, b:2 }}).then(data => { console.log(data)}).catch(e => { console.log('/test/post', e)})// 服务端现有接口, 进行 get 请求Ajax({ url: '/simple/get', params: { c:1, d:2 }}).then(data => { console.log(data)}).catch(e => { console.log('/simple/get', e)})
function ajaxError(){ alert('ajax error');}function ajaxSuccess(result){ if (result.error) { alert('操作失败'); return; } location.reload();}在 RESTful 架构中,客户端使用 ajax 技术请求服务端的服务。当 ajax 请求失败时,调用 ajaxError,提示用户 ajax 请求服务失败;当 ajax 请求成功时,调用 ajaxSuccess,提示用户 ajax 请求服务成功。在网站的首页展示所有的联系人,当新增、修改、删除联系人后,需要刷新首页面,因此,在第 12 行,当 ajax 调用服务成功后,需要 location.reload() 刷新页面,从服务端重新加载所有的联系人。
Ajax 相当于用户端和服务端的一个中间层,用来处理异步化的数据交互。这里涉及到两个重要的点:XMLHttpRequest 和 异步。先讲异步,异步指的是用户无需等待,异步操作不会阻碍当前用户的活动。在早期,对于我们的 Web 交互,每次用户发送请求,都会阻塞当前活动,进行页面重载。而在服务器响应请求之前,浏览器只能是一片空白。体验极其差!然而,使用异步就能够解决这个问题,浏览器端发送请求,但是不会阻塞用户当前的活动,也不会丢弃当前页面,数据一样可以进行交互和刷新。体验效果极佳!另一个 XMLHttpRequest 可以说是 Ajax 的核心技术。使用 XMLHttpRequest ,我们可以通过 JavaScript 向服务端发送请求,并且获取和处理服务端返回的数据,却不会阻塞用户活动,而仅仅只是局部动态更新,更加不会导致 web 页面频繁重载。Ajax 工作过程大致如下:JavaScript 通过 XMLHttpRequest 向后端发起异步请求,可以是 get 或者 post 等;服务端接收请求,处理并返回数据;JavaScript 通过 XMLHttpRequest 获取并解析服务端返回的数据内容;JavaScript 通过动态更新 DOM 或者执行其他操作。
Ajax 的缺点主要有如下几点:破坏浏览器的后退与加入收藏书签功能。这也是为大家所诟病的一点,因为通过 Ajax 进行页面动态更新,用户无法回到上一页的状态。网络延迟造成用户体验差。在请求到响应的这段时间,可长可短,在网络延迟的情况下,留给用户的就是一个页面无反应,造成的结果可能是用户的体验极差。解决这个问题一般是提供一个 Loading 组件告诉用户正在等待。Ajax 造成的竞态关系。当然这也不是 Ajax 的锅,异步本身就会有这个问题。假设多个 Ajax 同时更新到同一个数据,那么这个数据是按照什么规则更新呢?这就需要我们对竞态进行一定的考量和规范了。
首先,在利用动态规划算法之前,我们需要清楚哪些问题适合用动态规划算法求解。一般而言,能够利用动态规划算法求解的问题都会具备以下两点性质:最优子结构: 利用动态规划算法求解问题的第一步就是需要刻画问题最优解的结构,并且如果一个问题的最优解包含其子问题的最优解,则此问题具备最优子结构的性质。因此,判断某个问题是否适合用动态规划算法,需要判断该问题是否具有最优子结构。Tips: 最优子结构的定义主要是在于当前问题的最优解可以从子问题的最优解得出,当子问题满足最优解之后,才可以通过子问题的最优解获得原问题的最优解。重叠子问题: 适合用动态规划算法去求解的最优化问题应该具备的第二个性质是问题的子问题空间必须足够” 小 “,也就是说原问题递归求解时会重复相同的子问题,而不是一直生成新的子问题。如果原问题的递归算法反复求解相同的子问题,我们就称该最优化问题具有重叠子问题。Tips: 在这里,我们需要注意是,与适用动态规划算法去求解的问题具备重叠子问题性质相反,前面我们介绍的分治算法递归解决问题时,问题的子问题都是互不影响,相互独立的,这个也是我们在选用动态规划算法还是分治法解决问题时的一个判断条件。
首先,这里我们考虑背包的容量为 30,并给出这个问题中我们考虑到的各类物品及对应的重量和价值,如下:物品 n (i)12345 重量 w (i)105151020 价值 v (i)2030152510回顾一下我们在贪心算法介绍中提到的,能够应用贪心算法求解的问题需要满足两个条件:最优子结构和贪心选择,接下来,我们就具体来看看在背包问题中的最优子结构和贪心选择分别是什么。首先,如果一个问题的最优解包含其子问题的最优解,则此问题具备最优子结构的性质。问题的最优子结构性质是该问题是否可以用贪心算法求解的关键所在。对于背包问题,很显然它是满足最优子结构性质的,因为一个容量为 c 的背包问题必然包含容量小于 c 的背包问题的最优解的。其次,我们需要考虑在背包问题中,我们应该按照什么样的贪心选择进行选取。很显然,如果要使得最终的价值最大,那么必定需要使得选择的单位重量的物品的价值最大。所以背包问题的贪心选择策略是:优先选择单位重量价值最大的物品,当这个物品选择完之后,继续选择其他价值最大的物品。这里的背包问题可以用贪心算法实现,是因为背包选择放入的物品可以进行拆分,即并不需要放入整个物品,可以选择放入部分物品,我们这样的背包选择问题为部分背包问题(可以只选择物品的部分),与之对应的另一种背包问题为 0-1 背包问题,这个时候整个物品不可以拆分,只可以选择放入或者不放入,0-1 背包问题用贪心算法并不能求得准确的解,需要用动态规划算法求解。背包问题求解详解:在这里,我们按照优先选择单位重量价值最大的物品,所以第一步需要计算单位每个物品的单位价值,如下:unitValue(1) = 20/10 = 2unitValue(2) = 30/5 = 6unitValue(3) = 15/15 = 1unitValue(4) = 25/10 = 2.5unitValue(5) = 10/20 = 0.5所以我们有:unitValue(2) > unitValue(4) > unitValue(1) > unitValue(3) > unitValue(5)当考虑背包的容量为 30 时, 我们发现可以按照物品的单位价值进行依次放入,直至背包容量放满或者物品放完为止,放入的过程如下:物品类型放入重量背包使用容量背包剩余容量 25525410151511025535300按照如上步骤我们简单分析了一下背包问题的求解过程,接下来,我们看一下如何用代码实现背包问题。
面试官提问: HTTP 协议的请求方法有哪些,有啥区别?题目解析:序号方法说明 1GET 请求服务器上的资源,请求体不会包含请求数据,参数可以通过 URL 传输。2POST 用户传输信息到服务器,请求方式类似 GET 请求,比如提交表单。3PUT 用户传输信息到服务器,请求方式类似 POST 请求,比如提交文件。4DELETE 请求服务器删除某个资源,和 POST 请求作用相反。5OPTIONS 查询 URL 支持的 HTTP 方法。6HEAD 请求方式类似 GET 请求,但是服务器不会返回消息体,一般用于检查网页是否被修改、检查 URL 是否有效。除此之外,HTTP 协议还有 TRACE、CONNECT 等方法,但是在日常开发中基本不会用到,所以不用关注。面试官常常会将 POST 和 GET 方法进行对比,我们需要注意以下几个不同:(1)GET 请求主要是为了从服务器获取资源,POST 请求主要是为了向服务器发送资源。(2)GET 请求是通过 URL 传参,形式是 field = value,多个参数使用 & 进行分割,例如 http://127.0.0.1/login?username=xiaoming&password=123456。POST 请求是通过请求体传参,即信息存放到 Request Body 中。(3)GET 请求传输的信息量少,POST 请求能够传输的信息量多。(4)GET 请求参数在 URL 明文,容易被爬虫直接获取,POST 请求参数不直接可见,安全性更高,例如在表单提交密码时,必须使用 POST 请求。
Ajax 技术的优势有如下几点:无刷新更新页面。抛弃了早期重载页面的方式,加快了请求的速度,提升了用户体验。减少客户端的内存消耗。采用更加轻量的数据提取做法,避免了客户端大量的冗余请求,减少了不必要的内存消耗。将部分传统技术中原本在服务端的工作转移到客户端来进行。 使用Ajax,一些数据的处理能够在客户端进行,减轻了服务端的压力。兼容性极好,几乎所有的浏览器都支持。不需要额外插件或者虚拟机即可使用。当然,Ajax 带来的最大的优势还是通过异步请求和处理数据的方式,取代了通过原始 Form 表单提交来更新数据及页面的方式,从而使得我们的 Web 应用成为了可能。
构建了 xhr 对象之后,我们可以通过方法的调用来进行请求的发送。xhr.open('GET', 'http://www.example.com');xhr.send();这是最简单最典型的发送请求的做法。只需要短短 2 行代码,我们就可以执行一个请求发送动作。实际上 XMLHttpRequest.open 这个方法的参数不止两个这么少,一共有 5 个参数:xhrReq.open(method, url, async, user, password);这些参数分别代表着:method: 代表HTTP请求的方法名,比如 GET、POST、 PUT 和 DELETE。url: 一个DomString,代表着要想向其发送请求的 url。async: 表示是否异步。user:用户名,用于认证用途。password:密码,用于认证用途。其中,user 和 password 都是用于认证用途。而前 3 个参数是我们经常都会使用到的。这里着重说的是参数 async。默认情况下,async 为 true,代表着请求将是异步的。当然我们也可以设置为 false,这样我们就可以同步请求了。然而,事实上我们应该尽量不这么做,因为同步的请求会阻塞我们的UI和一切用户活动,造成的体验非常不好。到目前为止,如果你也跟着做的话应该能看到已经可以发送一个 Ajax 请求了,虽然它是失败的,因为你并没有正确的服务能够处理这个请求。如果你在浏览器上运行,打开控制台,你应该会得到这样的一个效果:
function ajax(options) { const { url, method, data, params, success, error } = options; const xhr = new XMLHttpRequest(); xhr.onreadystatechange = function () { // readyState为4的时候已接收完毕 if (xhr.readyState === 4) { // 状态码200表示成功 if (xhr.status === 200) { console.log(xhr.responseText); success.call(this, xhr.responseText); } else { console.log(xhr.status); error.call(this, xhr.status) } } }; // get 请求 if (method === 'get' || method === 'GET') { if (typeof params === 'object') { // params拆解成字符串 params = Object.keys(params).map(function (key) { return encodeURIComponent(key) + '=' + encodeURIComponent(params[key]); }).join('&'); } url = params ? `${url}?${params}` : url; xhr.open(method, url, true); xhr.send(); } // post 请求 if (method === 'post' || method === 'POST') { xhr.open(method, url, true); xhr.setRequestHeader("Content-type", "application/json; charset=utf-8"); xhr.send(JSON.stringify(params)); }}使用 promise 进行封装function ajax(url, method, params) { return new Promise((resolve, reject) => { // 创建XMLHttpRequest对象 const xhr = new XMLHttpRequest(); // 状态改变时的回调 xhr.onreadystatechange = function () { // readyState为4的时候已接收完毕 if (xhr.readyState === 4) { // 状态码200表示成功 if (xhr.status === 200) { resolve(xhr.responseText); } else { reject(xhr.status); } } }; // ... });}
在我们搭建好 RabbitMQ 集群之后,无论我们采用哪种搭建方式,我们本身都需要知道一些集群中容易出现的问题,这样我们在遇到集群出现问题的时候,我们才不会那么慌张。问题一:集群宕机集群宕机这一问题,是无论搭建什么样的集群,都是比较容易出现的一种问题,这种问题出现的频率高,同时,也是最容易修复的一种问题。那么什么是集群宕机呢?集群宕机其实指的就是我们的 RabbitMQ 集群,所在的服务器节点,由于种种原因导致我们的 RabbitMQ Server 服务非自然停止,或意外退出的一种现象。如果我们的 RabbitMQ Server 宕机了,就表明,我们的 RabbitMQ 集群中有一个或多个 RabbitMQ Server 服务节点不能够再继续提供服务了,那么所有有关 RabbitMQ Server 服务的请求都要在正常运行的服务节点上进行处理,可想而知,这种情况下,RabbitMQ 服务器所承受的压力有多大。上述是集群宕机之后所造成的一个直接结果,还有一种间接结果也是非常致命的,那就是由于集群节点服务的宕机,用户请求只能在可用的集群节点上进行处理,如果这个节点的服务器配置较低,那么很可能由于处理请求数量的激增,导致这个节点的服务器直接崩掉,也就是直接关机,或者服务器不再返回任何响应,这种情况也是比较严重的。问题二:集群间通信延迟集群间通信延迟也是比较容易出现的一种问题,这种问题我们从字面意思上来看,基本上可以知道个大概。集群间通信延迟问题,指的就是,RabbitMQ 集群中的一个节点向另一个节点传递数据,或者从另一个节点中获取数据时,目标节点不能及时将所需数据进行返回,导致的请求节点出现长时间等待的一种现象。那么集群间通信延迟问题造成的直接影响就是,用户的数据不能同步更新,导致用户看到的数据是不准确的,这极大影响了用户的使用体验。集群间通信延迟问题造成的间接影响就是,如果我们的目标节点迟迟不能返回响应数据,就会导致请求节点一直等待,那么位于请求节点上后续的用户请求就不能得到处理,这就是请求阻塞现象。问题三:集群数据文件丢失集群数据文件丢失问题相对而言,出现的频率较低,但是我们应该也要进行简单的了解。集群数据文件丢失问题,指的就是,位于服务器上的 RabbitMQ Server 节点突然出现的一种服务器磁盘数据丢失现象, 这种问题不出现还好,一旦出现,一般都是致命性的问题,往往恢复的时间也是最长的。集群数据文件丢失问题的出现,会直接影响用户无法打开我们的项目或应用,给用户造成非常严重的体验问题,同时,也会间接导致用户关键数据的丢失,造成我们的项目或者应用用户的一个流失。以上三种问题类型,是 RabbitMQ 集群中比较常见的三种问题类型,无论出现哪种问题,我们都应该知道一个大概的解决措施,或者恢复方案,下面让我们先来分析一下产生这三种问题的常见原因。
Asynchronous JavaScript + XML(异步JavaScript和XML), 其本身不是一种新技术,而是一个在 2005年被Jesse James Garrett提出的新术语,用来描述一种使用现有技术集合的‘新’方法。(MDN)AJAX 是2005年提出的一种术语,并不代表某个特定的技术。其译名 异步JavaScript和XML 描述出了核心,就是使用 JavaScript 发送异步 HTTP 请求,这样就摆脱了想要和服务端交互,必须刷新页面的痛点。学习 AJAX 相关内容前,建议有一些简单的 HTTP 相关知识的储备,否则很难理解其工作流程。
我们先从这么一个问题来引入我们本章节的学习 —— 什么是跨域请求?
从第二行开始,都是请求头部的信息,比如我第二行的 Host: www.imooc.com,用来表明请求的地址是什么。还有一些其他的信息,比如浏览器可接受的语言类型,字体编码,Cookie 等信息。接下来是空行,不必多说。最后面的一部分是我们的请求的具体内容,请求内容可以是表单,也可以是 JSON 等格式。
我们都知道 JavaScript 异步使用的是回调函数,下面我们来看一个 ajax 请求的实例,下面的 ajax 方法是一个伪代码,可以看作是请求接口的方法,接口请求的库可以参考 jQuery 的 $.ajax 和 axios。// ajax请求的伪代码function ajax(url, sucessCallback, failCallback) { // url:请求的url // sucessCallback:成功的回调函数 // failCallback:失败的回调函数}ajax(url1, (res1) => { ajax(url2, (res2) => { ajax(url3, (res3) => { doSomething(res1, res2, res3) }) })})上面的 ajax 请求我们可以理解为,在调用 doSomething 方法时需要前面三个请求的结果作为参数,所以只有前一个 ajax 请求得到结果后才能发起第二个请求。这样前后有依赖的嵌套被称为回调地狱。对于比较复杂逻辑的情况来说,回调地狱会使程序出问题的概率大大增加。另外,这样做有个很严重的问题,就是接口请求的时间是三个请求的和,不能进行并发操作,当然我们也可以做一些优化操作,如下:let out = after(3, function (data){ doSomething(...data)})ajax(url1, (res1) => { out(res1)})ajax(url2, (res2) => { out(res2)})ajax(url3, (res3) => { out(res3)})function after(times, callback) { const arr = []; return function (value){ arr.push(value); if (--times==0) { callback(arr); } }}上面的代码很优雅地解决了回调嵌套的问题,但同时我们需要手动维护一个计数器来控制最后的回调。这无疑增加了程序的复杂度,我们更希望的是关注我的业务,而不是写更多的逻辑来优化。针对这种情况,社区提供了很多这类优化的库,而 Promise 则是其中最亮眼的。对上面的情况,Promise 怎么解决的呢?看如下的实现方式:function request(url) { return new Promise((resolve, reject) => { ajax(url, (res) => { resolve(res) }) })}Promise.all([request(url1), request(url1), request(url1)]).then((result) => { doSomething(...result)}).catch((error) => { console.log(error)})上面的代码中我们封装了一个 request 请求的方法,通过 Promise.all() 来并发请求这些接口,当接口都正确返回才会执行 then 方法中的回调,有一个错误都会抛出异常。这种方式比较好的是,我们对请求进行了封装,不要再关注每一步请求是否完成做对应的逻辑处理,让我们在开发过程中更加关注业务逻辑,使开发效率更快。
HTTP 协议是一个简单的请求-响应协议,它指定了客户端可能发送给服务器什么样的消息以及得到什么样的响应。请求和响应消息的头以 ASCII 码形式给出;而消息内容则具有一个类似 MIME 的格式。浏览器向 Web 服务器发出请求时,它向服务器传递了一个请求信息,HTTP 请求信息由 3 部分组成:请求行;请求头;请求正文。以下是一个 HTTP 请求消息的例子:GET / HTTP/1.1Host: www.imooc.comConnection: keep-aliveUser-Agent: Mozilla/5.0 AppleWebKit/537.36 Chrome/81.0Accept: text/html,application/xml;image/webp,image/png,*/*;...省略...第一行是请求行,用来说明请求方法,要访问的资源,以及所使用的 HTTP 版本。在这个例子中,请求方法是 GET,要访问的资源是 /,HTTP 版本是 1.1,表示要获取网站首页 / 的内容。紧接着请求行之后的部分是请求头部,用来说明服务器要使用的附加信息。例如:Host 指出请求的主机名,这里是 www.imooc.com。请求头部之后是请求正文,在请求正文中可以添加任意的其他数据,这个例子的请求正文为空。
面试官提问: HTTPS 的请求流程和 HTTP 协议的请求流程有什么区别?题目解析:参考 HTTPS 的官方文档,我们将整个请求的流程简单抽象为以下几个步骤,抓住其中的核心步骤: (HTTPS 简化通信模型)步骤(1):客户端发送一个 HTTPS 请求,例如请求 https://imooc.com,连接到服务器端的 443 端口(和 HTTP 协议不同,HTTP 默认 80 端口)。步骤(2):服务器端收到握手信息,使用预先配置好的数字证书,即图中的公钥和私钥。如果是自己颁发的证书,那么需要客户端通过浏览器的弹窗验证,如果是组织申请获得,默认直接通过。步骤(3):传输证书给客户端,证书组装了多种信息,包含证书的颁发机构、证书有效时间、服务器端的公钥,证书签名等。步骤(4):客户端解析证书,也就是通过 TLS/SSL 协议,判定公钥是否有效,如果发现异常,会弹出警告框。如果校验没有问题,那么客户端会生成一个随机数,然后用上一步传输过来的公钥对随机数进行加密。步骤(5):客户端将上个步骤随机数加密后的内容传输给服务器端,这个随机数就是两端通信的核心。步骤(6):服务器端用自己的私钥进行解密,获取解密前的随机数。然后组装会话秘钥,这里私钥和客户端会话秘钥是相同的。步骤(7):将服务器端用私钥加密后的内容传输给客户端,在客户端用之前生成的随机数组装私钥还原。步骤(8):客户端用之前的私钥解密获取的信息,也就获取了通信内容。上述过程中,SSL 和 TLS 协议是核心模块,具体的证书交互流程相对复杂,面试场景基本不会涉及。我们需要关注的是为什么 HTTPS 同时使用非对称加密和对称加密,有两个原因:(1)对称加密流程两边需要使用相同的密钥,单纯使用对称加密,无法实现密钥交换。(2)非对称加密:满足安全要求,但是非对称加密的计算耗时高于对称加密的 2-3 个数量级(相同安全加密级别),对于实际的应用场景,例如电商网站,对网络交互高耗时容忍度是非常低的。所以 HTTPS 才先使用非对称交换密钥,之后再使用对称加密通信。
我们先来看下第一部分,请求行:GET / HTTP/1.1请求行里的 GET 是请求方法。请求方法主要是告诉服务器端,客户端要对资源实行什么样的具体操作,方便服务器进行响应的处理。HTTP 1.0 规定的方法: GET,POST,HEAD;HTTP 1.1 新增的请求方法:OPTIONS,PUT,DELETE,TRACE,CONNECT;HTTP 规定的主要请求如下表所示,我们主要使用的实际上就是 get,post 这两个请求。常用的请求方法序号请求方法方法描述1GET用来获取服务器的信息。2POST用于创建一个文件,请求是非幂等的。3HEAD通过这个来获取响应的报头文件,不包含的具体内容。4PUT主要是用来更新文件,这个方法对服务器来讲,应该是幂等的。5DELETE这个命令是用来请求让服务器端来删除特定的信息。6OPTIONS这个方法可以让客户端可以查看服务器可以提供的请求方法等信息。7TRACE这个主要用于测试和诊断,可以回显服务器的信息。8CONNECTHTTP/1.1协议中预留的请求方法,不常使用。Get 后面的 / 是来标明请求的资源信息,我们这里是想访问慕课网的主页,所以写 /。 HTTP/1.1 指的是 HTTP 的协议版本。Tips:HTTP 是在 1990 左右提出的协议,距今已经有几十年的历史了。广泛使用的版本有 1.0,1.1,现在也有 2.0 的版本,不过还没有普及。除此之外,对安全要求高的一些网站,也有的开始采用 HTTPS 协议进行传输。HTTPS 提供了更多的安全校验,是利用 SSL/TLS 技术进行加密的,相对于普通的 HTTP,更加安全,隐私更不容易泄露。好了说完了请求行,让我们来介绍一下请求头部。
说这么多,那么什么是 Ajax 呢?简单来讲,Ajax 就是 JavaScript 基于 XMLHttpRequest 对象与服务端进行交互,向服务端发送一个请求,并且获取和处理服务器返回的内容。在这个过程中,我们可以使用 XML ,HTML 和 JSON 等格式的数据进行交互。并且,Ajax 拥有异步特质,我们可以在不刷新页面的情况下,通过交互数据,在页面上做局部的刷新等数据处理。
下面列举了一些 Request 请求对象中方法的说明:方法名功能与作用描述host当前访问域名或者IPscheme当前访问协议port当前访问的端口remotePort当前请求的REMOTE_PORTprotocol当前请求的SERVER_PROTOCOLcontentType当前请求的CONTENT_TYPEdomain当前包含协议的域名subDomain当前访问的子域名panDomain当前访问的泛域名rootDomain当前访问的根域名url当前完整URLbaseUrl当前URL(不含QUERY_STRING)query当前请求的QUERY_STRING参数baseFile当前执行的文件rootURL访问根地址rootUrlURL访问根目录pathinfo当前请求URL的pathinfo信息(含URL后缀)ext当前URL的访问后缀time获取当前请求的时间type当前请求的资源类型method当前请求类型rule当前请求的路由对象实例
在浏览器发送 Ajax 请求之后,下一步骤自然是服务器响应。服务器在接收到请求之后会进行一系列处理步骤,最终返回结果。而与此同时,客户端会在接收到返回的结果之后进行界面的展示或者数据的处理。本章节主讲 Ajax 收到返回数据后处理服务器响应过程。
在数据交互中,我们经常会使用 GET 请求来查询数据,现在假设我们有一个简单的GET请求,查询接口 http://localhost:8080/simple/get,附带 query参数 为 mk=慕课网,那么我们可以构建代码块:xhr.open("GET", "http://localhost:8080/simple/get?mk=慕课网");xhr.send();查看效果图:从上图可以看到,浏览器控制台面板上显示,我们进行 GET 发送请求的过程中,在 Headers 上,可以看到 Query String Parameters 附带的信息完全正确,我们的 GET 请求构造成功。
上一个小节中,我们简单的介绍了 HTTP 协议,但是,并没有针对 HTTP 的请求和响应进行更详尽的描述。但是,分析请求和响应信息是我们进行爬虫工作中的重要步骤,因此,有必要详细的介绍这两个步骤。我们还是复用之前的访问慕课网的例子进行 HTTP 协议的解析。关于怎么获取请求头和响应头的信息的内容,我们会在后面讲解第一个爬虫的时候进行讲解。使用 get 方法请求慕课网的请求信息如下:GET / HTTP/1.1Host: www.imooc.comConnection: keep-aliveCache-Control: max-age=0HTTP 请求主要有四部分组成。分别是请求行,请求头部,空行和请求数据。