关于网络请求:一次网络请求中的流量分发过程-京东云技术团队

1. 摘要古代的企业级或互联网零碎往往须要进行流量布局,达成通明多级分流。流量从客户端收回到服务端解决这个过程里,流经的与性能无关的技术部件有(达成“通明分流”这个指标所采纳的工具与伎俩):客户端缓存、域名服务器、传输链路、内容散发网络、负载均衡器、服务端缓存。通明分流带来的价值:高可用架构、高并发。 本文次要介绍流量布局中的网络申请过程过程及: 第一局部:对一次网络申请的过程作简要介绍,而后介绍本人目前理解到的前端网络组件搭配形式、后端网络组件搭配形式 第二局部:介绍LB负载零碎 、vip与rip 的映射关系 第三局部:介绍内网域名解析及公网域名解析 2. 网络申请过程通用申请过程及申请过程名词解释来源于: https://cf.jd.com/pages/viewpage.action?pageId=766717554 2.1 通用申请过程 2.2 申请过程名词解释rip: 实在ip,指虚拟机或容器ip vip: 虚构ip,不可跨机房,online申请,负载、主动探活等性能,分公网vip与内网vip 内网: 专指机房外部,严格的防火墙策略,内网之间无防火墙,可申请内网vip 提供负载平衡供给用间互访;内网≠办公网 办公网: 办公区个人电脑网络,通过反向代理拜访内网机房利用 公网: 互联网用户网络,通过DNS + 公网vip 拜访内网机房利用 备注(本人了解 ,有可能了解不正确): 公网vip 即公网虚构ip,虚构不是说公网上找不到这个ip,虚构是绝对于服务器ip(rip)来说的,vip不间接承当业务逻辑 公网VIP在公网实在存在、内网vip在公司内网实在存在 2.3 前端网络组件搭配形式援用链接: http://jdthelp.jdos.jd.com/help/bestpractice/multi-web-demo.html 外网拜访需满足如下图架构:VIP前面挂 jen-nginx 来代理前端的主利用,jen代理前面挂动态利用1\~N 测试环境:多个前端我的项目可能存在下列架构形式: 域名 -> VIP -> JEN代理(依据不同域名拜访不同文件夹下的文件)-> 动态利用 2.4 后端网络组件搭配形式后端利用从调用形式来说分为两种 (http调用 JSF调用) 前端利用 ---(协定http或者https)---后端前置利用 --(协定 jsf公有协定)----JSF微服务利用 后端前置利用 -----jsf微服务利用,之间的负载由消费者来决定,所以个别毋庸关注这两者之间的负载。 形式1: vip下的负载(LB负载零碎) + Nginx 形式1备注: VIP下能够间接挂载服务器,这里Nginx不是必须的,除非你须要 nginx 提供的性能(除了负载)。 如果你仅仅是用nginx来做负载,那么请去除nginx组件,没有必要加一这个组件,团体的vip的LB曾经蕴含负载性能,不要减轻零碎的复杂性。 形式2:容器K8S层面的负载vip提供的LB: vip提供的LB下是间接挂机器IP,其中多个ip之间没有分组的概念,并且没有探活机制。 ...

June 2, 2023 · 1 min · jiezi

Glide缓存流程

本文首发于 vivo互联网技术 微信公众号  链接:https://mp.weixin.qq.com/s/cPLkefpEb3w12-uoiqzTig 作者:连凌能Android上图片加载的解决方案有多种,但是官方认可的是Glide。Glide提供简洁易用的api,整个框架也方便扩展,比如可以替换网络请求库,同时也提供了完备的缓存机制,应用层不需要自己去管理图片的缓存与获取,框架会分成内存缓存,文件缓存和远程缓存。本文不会从简单的使用着手,会把重点放在缓存机制的分析上。 一、综述开始之前,关于Glide缓存请先思考几个问题: Glide有几级缓存?Glide内存缓存之间是什么关系?Glide本地文件IO和网络请求是一个线程吗?如果不是,怎么实现线程切换?Glide网络请求回来后数据直接返回给用户还是先存再返回?加载开始入口从Engine.load()开始,先看下对这个方法的注释, 会先检查(Active Resources),如果有就直接返回,Active Resources没有被引用的资源会放入Memory Cache,如果Active Resources没有,会往下走。检查Memory Cache中是否有需要的资源,如果有就返回,Memory Cache中没有就继续往下走。检查当前在运行中的job中是否有改资源的下载,有就在现有的job中直接添加callback返回,不重复下载,当然前提是计算得到的key是一致的,如果还是没有,就会构造一个新的job开始新的工作。* Starts a load for the given arguments.** <p>Must be called on the main thread.** <p>The flow for any request is as follows:* <ul>* <li>Check the current set of actively used resources, return the active resource if* present, and move any newly inactive resources into the memory cache.</li>* <li>Check the memory cache and provide the cached resource if present.</li>* <li>Check the current set of in progress loads and add the cb to the in progress load if* one is present.</li>* <li>Start a new load.</li>* </ul>ok, find the source code. ...

October 14, 2019 · 10 min · jiezi

面试经典之http中get与post的区别

GET和POST是HTTP请求的两种基本方法,要说它们的区别,接触过WEB开发的人都能说出一二。最直观的区别就是GET把参数包含在URL中,POST通过request body传递参数。你可能自己写过无数个GET和POST请求,或者已经看过很多权威网站总结出的他们的区别,你非常清楚知道什么时候该用什么。1、常规答案当你在面试中被问到这个问题,你的内心充满了自信和喜悦。你轻轻松松的给出了一个“标准答案”:-GETPOST后退按钮/刷新无害数据会被重新提交(浏览器应该告知用户数据会被重新提交)。书签可收藏为书签不可收藏为书签缓存能被缓存不能缓存编码类型application/x-www-form-urlencoded 只能进行url编码application/x-www-form-urlencoded 或 multipart/form-data。为二进制数据使用多重编码。历史参数保留在浏览器历史中。参数不会保存在浏览器历史中。对数据长度的限制是的。当发送数据时,GET 方法向 URL 添加数据;URL 的长度是受限制的(URL 的最大长度是 2048 个字符)。无限制。对数据类型的限制只允许 ASCII 字符。没有限制。也允许二进制数据。安全性与 POST 相比,GET 的安全性较差,因为所发送的数据是 URL 的一部分。在发送密码或其他敏感信息时绝不要使用 GET !POST 比 GET 更安全,因为参数不会被保存在浏览器历史或 web 服务器日志中。可见性数据在 URL 中对所有人都是可见的。数据不会显示在 URL 中。“很遗憾,这不是我们要的回答!”请告诉我真相。。。2、本质区别如果我告诉你GET和POST本质上没有区别你信吗? 让我们扒下GET和POST的外衣,坦诚相见吧!GET和POST是什么?HTTP协议中的两种发送请求的方法。HTTP是什么?HTTP是基于TCP/IP的关于数据如何在万维网中如何通信的协议。HTTP的底层是TCP/IP。所以GET和POST的底层也是TCP/IP,也就是说,GET/POST都是TCP链接。GET和POST能做的事情是一样一样的。你要给GET加上request body,给POST带上url参数,技术上是完全行的通的。2.1 那么,“标准答案”里的那些区别是怎么回事?在我大万维网世界中,TCP就像汽车,我们用TCP来运输数据,它很可靠,从来不会发生丢件少件的现象。但是如果路上跑的全是看起来一模一样的汽车,那这个世界看起来是一团混乱,送急件的汽车可能被前面满载货物的汽车拦堵在路上,整个交通系统一定会瘫痪。为了避免这种情况发生,交通规则HTTP诞生了。HTTP给汽车运输设定了好几个服务类别,有GET, POST, PUT, DELETE等等,HTTP规定,当执行GET请求的时候,要给汽车贴上GET的标签(设置method为GET),而且要求把传送的数据放在车顶上(url中)以方便记录。如果是POST请求,就要在车上贴上POST的标签,并把货物放在车厢里。当然,你也可以在GET的时候往车厢内偷偷藏点货物,但是这是很不光彩;也可以在POST的时候在车顶上也放一些数据,让人觉得傻乎乎的。HTTP只是个行为准则,而TCP才是GET和POST怎么实现的基本。但是,我们只看到HTTP对GET和POST参数的传送渠道(url还是requrest body)提出了要求。2.2、“标准答案”里关于参数大小的限制又是从哪来的呢?在我大万维网世界中,还有另一个重要的角色:运输公司。不同的浏览器(发起http请求)和服务器(接受http请求)就是不同的运输公司。 虽然理论上,你可以在车顶上无限的堆货物(url中无限加参数)。但是运输公司可不傻,装货和卸货也是有很大成本的,他们会限制单次运输量来控制风险,数据量太大对浏览器和服务器都是很大负担。业界不成文的规定是,(大多数)浏览器通常都会限制url长度在2K个字节,而(大多数)服务器最多处理64K大小的url。超过的部分,恕不处理。如果你用GET服务,在request body偷偷藏了数据,不同服务器的处理方式也是不同的,有些服务器会帮你卸货,读出数据,有些服务器直接忽略,所以,虽然GET可以带request body,也不能保证一定能被接收到哦。好了,现在你知道,GET和POST本质上就是TCP链接,并无差别。但是由于HTTP的规定和浏览器/服务器的限制,导致他们在应用过程中体现出一些不同。2.3、最终boss你以为本文就这么结束了?我们的大BOSS还等着出场呢。。。这位BOSS有多神秘?当你试图在网上找“GET和POST的区别”的时候,那些你会看到的搜索结果里,从没有提到他。他究竟是什么呢。。。GET和POST还有一个重大区别,简单的说:GET产生一个TCP数据包;POST产生两个TCP数据包。也就是说,GET只需要汽车跑一趟就把货送到了,而POST得跑两趟,第一趟,先去和服务器打个招呼“嗨,我等下要送一批货来,你们打开门迎接我”,然后再回头把货送过去。因为POST需要两步,时间上消耗的要多一点,看起来GET比POST更有效。因此Yahoo团队有推荐用GET替换POST来优化网站性能。但这是一个坑!跳入需谨慎。为什么?GET与POST都有自己的语义,不能随便混用。据研究,在网络环境好的情况下,发一次包的时间和发两次包的时间差别基本可以无视。而在网络环境差的情况下,两次包的TCP在验证数据包完整性上,有非常大的优点。并不是所有浏览器都会在POST中发送两次包,Firefox就只发送一次。好了,看到这里之后再也不怕面试官问get与post请求的区别了。

April 9, 2019 · 1 min · jiezi

全面分析前端的网络请求方式

一、前端进行网络请求的关注点大多数情况下,在前端发起一个网络请求我们只需关注下面几点:传入基本参数(url,请求方式)请求参数、请求参数类型设置请求头获取响应的方式获取响应头、响应状态、响应结果异常处理携带cookie设置跨域请求二、前端进行网络请求的方式form表单、ifream、刷新页面Ajax - 异步网络请求的开山鼻祖jQuery - 一个时代fetch - Ajax的替代者axios、request等众多开源库三、关于网络请求的疑问Ajax的出现解决了什么问题原生Ajax如何使用jQuery的网络请求方式fetch的用法以及坑点如何正确的使用fetch如何选择合适的跨域方式带着以上这些问题、关注点我们对几种网络请求进行一次全面的分析。四、Ajax的出现解决了什么问题在Ajax出现之前,web程序是这样工作的:这种交互的的缺陷是显而易见的,任何和服务器的交互都需要刷新页面,用户体验非常差,Ajax的出现解决了这个问题。Ajax全称Asynchronous JavaScript + XML(异步JavaScript和XML)使用Ajax,网页应用能够快速地将增量更新呈现在用户界面上,而不需要重载(刷新)整个页面。Ajax本身不是一种新技术,而是用来描述一种使用现有技术集合实现的一个技术方案,浏览器的XMLHttpRequest是实现Ajax最重要的对象(IE6以下使用ActiveXObject)。尽管X在Ajax中代表XML, 但由于JSON的许多优势,比如更加轻量以及作为Javascript的一部分,目前JSON的使用比XML更加普遍。五、原生Ajax的用法这里主要分析XMLHttpRequest对象,下面是它的一段基础使用: var xhr = new XMLHttpRequest(); xhr.open(‘post’,‘www.xxx.com’,true) // 接收返回值 xhr.onreadystatechange = function(){ if(xhr.readyState === 4 ){ if(xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){ console.log(xhr.responseText); } } } // 处理请求参数 postData = {“name1”:“value1”,“name2”:“value2”}; postData = (function(value){ var dataString = “”; for(var key in value){ dataString += key+"="+value[key]+"&"; }; return dataString; }(postData)); // 设置请求头 xhr.setRequestHeader(“Content-type”,“application/x-www-form-urlencoded”); // 异常处理 xhr.onerror = function() { console.log(‘Network request failed’) } // 跨域携带cookie xhr.withCredentials = true; // 发出请求 xhr.send(postData);下面分别对XMLHttpRequest对象常用的的函数、属性、事件进行分析。函数open用于初始化一个请求,用法:xhr.open(method, url, async);method:请求方式,如get、posturl:请求的urlasync:是否为异步请求send用于发送 HTTP 请求,即调用该方法后HTTP请求才会被真正发出,用法:xhr.send(param)param:http请求的参数,可以为string、Blob等类型。abort用于终止一个ajax请求,调用此方法后readyState将被设置为0,用法:xhr.abort()setRequestHeader用于设置HTTP请求头,此方法必须在 open() 方法和 send() 之间调用,用法:xhr.setRequestHeader(header, value);getResponseHeader用于获取http返回头,如果在返回头中有多个一样的名称,那么返回的值就会是用逗号和空格将值分隔的字符串,用法:var header = xhr.getResponseHeader(name);属性readyState用来标识当前XMLHttpRequest对象所处的状态,XMLHttpRequest对象总是位于下列状态中的一个:值状态描述0UNSENT代理被创建,但尚未调用 open() 方法。1OPENEDopen() 方法已经被调用。2HEADERS_RECEIVEDsend() 方法已经被调用,并且头部和状态已经可获得。3LOADING下载中; responseText 属性已经包含部分数据。4DONE下载操作已完成。status表示http请求的状态, 初始值为0。如果服务器没有显式地指定状态码, 那么status将被设置为默认值, 即200。responseType表示响应的数据类型,并允许我们手动设置,如果为空,默认为text类型,可以有下面的取值:值描述"“将 responseType 设为空字符串与设置为"text"相同, 是默认类型 (实际上是 DOMString)。“arraybuffer"response 是一个包含二进制数据的 JavaScript ArrayBuffer 。“blob” response 是一个包含二进制数据的 Blob 对象 。“document"response 是一个 HTML Document 或 XML XMLDocument ,这取决于接收到的数据的 MIME 类型。“json” response 是一个 JavaScript 对象。这个对象是通过将接收到的数据类型视为 JSON 解析得到的。“text” response 是包含在 DOMString 对象中的文本。response返回响应的正文,返回的类型由上面的responseType决定。withCredentialsajax请求默认会携带同源请求的cookie,而跨域请求则不会携带cookie,设置xhr的withCredentials的属性为true将允许携带跨域cookie。事件回调onreadystatechange xhr.onreadystatechange = callback;当readyState 属性发生变化时,callback会被触发。onloadstart xhr.onloadstart = callback;在ajax请求发送之前(readyState==1后, readyState==2前),callback会被触发。onprogressxhr.onprogress = function(event){ console.log(event.loaded / event.total);}回调函数可以获取资源总大小total,已经加载的资源大小loaded,用这两个值可以计算加载进度。onload xhr.onload = callback;当一个资源及其依赖资源已完成加载时,将触发callback,通常我们会在onload事件中处理返回值。异常处理onerror xhr.onerror = callback;当ajax资源加载失败时会触发callback。ontimeout xhr.ontimeout = callback;当进度由于预定时间到期而终止时,会触发callback,超时时间可使用timeout属性进行设置。六、jQuery对Ajax的封装在很长一段时间里,人们使用jQuery提供的ajax封装进行网络请求,包括$.ajax、$.get、$.post等,这几个方法放到现在,我依然觉得很实用。$.ajax({ dataType: ‘json’, // 设置返回值类型 contentType: ‘application/json’, // 设置参数类型 headers: {‘Content-Type’,‘application/json’},// 设置请求头 xhrFields: { withCredentials: true }, // 跨域携带cookie data: JSON.stringify({a: [{b:1, a:1}]}), // 传递参数 error:function(xhr,status){ // 错误处理 console.log(xhr,status); }, success: function (data,status) { // 获取结果 console.log(data,status); }})$.ajax只接收一个参数,这个参数接收一系列配置,其自己封装了一个jqXHR对象,有兴趣可以阅读一下jQuary-ajax 源码常用配置:url 当前页地址。发送请求的地址。type 类型:String 请求方式 (“POST” 或 “GET”), 默认为 “GET”。注意:其它 HTTP 请求方法,如 PUT 和 DELETE 也可以使用,但仅部分浏览器支持。timeout 类型:Number 设置请求超时时间(毫秒)。此设置将覆盖全局设置。success 类型:Function 请求成功后的回调函数。jsonp在一个jsonp请求中重写回调函数的名字。这个值用来替代在"callback=?“这种GET或POST请求中URL参数里的"callback"部分。error 类型:Function 。请求失败时调用此函数。注意:源码里对错误的判定:isSuccess = status >= 200 && status < 300 || status === 304;返回值除了这几个状态码都会进error回调。dataType"xml”: 返回 XML 文档,可用 jQuery 处理。“html”: 返回纯文本 HTML 信息;包含的 script 标签会在插入 dom 时执行。“script”: 返回纯文本 JavaScript 代码。不会自动缓存结果。除非设置了 “cache” 参数。注意:在远程请求时(不在同一个域下),所有 POST 请求都将转为 GET 请求。(因为将使用 DOM 的 script标签来加载)“json”: 返回 JSON 数据 。“jsonp”: JSONP 格式。使用 JSONP 形式调用函数时,如 “myurl?callback=?” jQuery 将自动替换 ? 为正确的函数名,以执行回调函数。“text”: 返回纯文本字符串data 类型:String 使用JSON.stringify转码complete类型:Function 请求完成后回调函数 (请求成功或失败之后均调用)。async 类型:Boolean 默认值: true。默认设置下,所有请求均为异步请求。如果需要发送同步请求,请将此选项设置为 false。contentType 类型:String 默认值: “application/x-www-form-urlencoded”。发送信息至服务器时内容编码类型。键值对这样组织在一般的情况下是没有什么问题的,这里说的一般是,不带嵌套类型JSON,也就是 简单的JSON,形如这样:{ a: 1, b: 2, c: 3}但是在一些复杂的情况下就有问题了。 例如在 Ajax 中你要传一个复杂的 json 对像,也就说是对象嵌数组,数组中包括对象,你这样传: application/x-www-form-urlencoded 这种形式是没有办法将复杂的 JSON 组织成键值对形式。{ data: { a: [{ x: 2 }] }}可以用如下方式传递复杂的json对象$.ajax({ dataType: ‘json’, contentType: ‘application/json’, data: JSON.stringify({a: [{b:1, a:1}]})})七、jQuery的替代者近年来前端MV的发展壮大,人们越来越少的使用jQuery,我们不可能单独为了使用jQuery的Ajax api来单独引入他,无可避免的,我们需要寻找新的技术方案。尤雨溪在他的文档中推荐大家用axios进行网络请求。axios基于Promise对原生的XHR进行了非常全面的封装,使用方式也非常的优雅。另外,axios同样提供了在node环境下的支持,可谓是网络请求的首选方案。未来必定还会出现更优秀的封装,他们有非常周全的考虑以及详细的文档,这里我们不多做考究,我们把关注的重点放在更底层的APIfetch。Fetch API 是一个用用于访问和操纵HTTP管道的强大的原生 API。这种功能以前是使用 XMLHttpRequest实现的。Fetch提供了一个更好的替代方法,可以很容易地被其他技术使用,例如 Service Workers。Fetch还提供了单个逻辑位置来定义其他HTTP相关概念,例如CORS和HTTP的扩展。可见fetch是作为XMLHttpRequest的替代品出现的。使用fetch,你不需要再额外加载一个外部资源。但它还没有被浏览器完全支持,所以你仍然需要一个 polyfill。八、fetch的使用一个基本的 fetch请求:const options = { method: “POST”, // 请求参数 headers: { “Content-Type”: “application/json”}, // 设置请求头 body: JSON.stringify({name:‘123’}), // 请求参数 credentials: “same-origin”, // cookie设置 mode: “cors”, // 跨域}fetch(‘http://www.xxx.com’) .then(function(response) { return response.json(); }) .then(function(myJson) { console.log(myJson); // 响应数据 }) .catch(function(err){ console.log(err); // 异常处理 })Fetch API提供了一个全局的fetch()方法,以及几个辅助对象来发起一个网络请求。fetch()fetch()方法用于发起获取资源的请求。它返回一个 promise,这个 promise 会在请求响应后被 resolve,并传回 Response 对象。Headers可以通过 Headers() 构造函数来创建一个你自己的 headers 对象,相当于 response/request 的头信息,可以使你查询到这些头信息,或者针对不同的结果做不同的操作。var myHeaders = new Headers();myHeaders.append(“Content-Type”, “text/plain”);Request通过 Request() 构造函数可以创建一个Request 对象,这个对象可以作为fetch函数的第二个参数。Response在fetch()处理完promises之后返回一个Response 实例,也可以手动创建一个Response实例。九、fetch polyfill源码分析由于fetch是一个非常底层的API,所以我们无法进一步的探究它的底层,但是我们可以借助它的polyfill探究它的基本原理,并找出其中的坑点。代码结构由代码可见,polyfill主要对Fetch API提供的四大对象进行了封装:fetch 封装代码非常清晰:构造一个Promise对象并返回创建一个Request对象创建一个XMLHttpRequest对象取出Request对象中的请求url,请求方发,open一个xhr请求,并将Request对象中存储的headers取出赋给xhrxhr onload后取出response的status、headers、body封装Response对象,调用resolve。异常处理可以发现,调用reject有三种可能:1.请求超时2.请求失败注意:当和服务器建立简介,并收到服务器的异常状态码如404、500等并不能触发onerror。当网络故障时或请求被阻止时,才会标记为 reject,如跨域、url不存在,网络异常等会触发onerror。所以使用fetch当接收到异常状态码都是会进入then而不是catch。这些错误请求往往要手动处理。3.手动终止可以在request参数中传入signal对象,并对signal对象添加abort事件监听,当xhr.readyState变为4(响应内容解析完成)后将signal对象的abort事件监听移除掉。这表示,在一个fetch请求结束之前可以调用signal.abort将其终止。在浏览器中可以使用AbortController()构造函数创建一个控制器,然后使用AbortController.signal属性这是一个实验中的功能,此功能某些浏览器尚在开发中Headers封装在header对象中维护了一个map对象,构造函数中可以传入Header对象、数组、普通对象类型的header,并将所有的值维护到map中。之前在fetch函数中看到调用了header的forEach方法,下面是它的实现:可见header的遍历即其内部map的遍历。另外Header还提供了append、delete、get、set等方法,都是对其内部的map对象进行操作。Request对象Request对象接收的两个参数即fetch函数接收的两个参数,第一个参数可以直接传递url,也可以传递一个构造好的request对象。第二个参数即控制不同配置的option对象。可以传入credentials、headers、method、mode、signal、referrer等属性。这里注意:传入的headers被当作Headers构造函数的参数来构造header对象。cookie处理fetch函数中还有如下的代码: if (request.credentials === ‘include’) { xhr.withCredentials = true } else if (request.credentials === ‘omit’) { xhr.withCredentials = false }默认的credentials类型为same-origin,即可携带同源请求的coodkie。然后我发现这里polyfill的实现和MDN-使用Fetch以及很多资料是不一致的:mdn: 默认情况下,fetch 不会从服务端发送或接收任何 cookies于是我分别实验了下使用polyfill和使用原生fetch携带cookie的情况,发现在不设置credentials的情况下居然都是默认携带同源cookie的,这和文档的说明说不一致的,查阅了许多资料后都是说fetch默认不会携带cookie,下面是使用原生fetch在浏览器进行请求的情况:然后我发现在MDN-Fetch-Request已经指出新版浏览器credentials默认值已更改为same-origin,旧版依然是omit。确实MDN-使用Fetch这里的文档更新的有些不及时,误人子弟了…Response对象Response对象是fetch调用成功后的返回值:回顾下fetch中对Response的操作: xhr.onload = function () { var options = { status: xhr.status, statusText: xhr.statusText, headers: parseHeaders(xhr.getAllResponseHeaders() || '') } options.url = 'responseURL' in xhr ? xhr.responseURL : options.headers.get('X-Request-URL') var body = 'response' in xhr ? xhr.response : xhr.responseText resolve(new Response(body, options)) }Response构造函数:可见在构造函数中主要对options中的status、statusText、headers、url等分别做了处理并挂载到Response对象上。构造函数里面并没有对responseText的明确处理,最后交给了_initBody函数处理,而Response并没有主动声明_initBody属性,代码最后使用Response调用了Body函数,实际上_initBody函数是通过Body函数挂载到Response身上的,先来看看_initBody函数:可见,_initBody函数根据xhr.response的类型(Blob、FormData、String...),为不同的参数进行赋值,这些参数在Body方法中得到不同的应用,下面具体看看Body函数还做了哪些其他的操作:Body函数中还为Response对象挂载了四个函数,text、json、blob、formData,这些函数中的操作就是将_initBody中得到的不同类型的返回值返回。这也说明了,在fetch执行完毕后,不能直接在response中获取到返回值而必须调用text()、json()等函数才能获取到返回值。这里还有一点需要说明:几个函数中都有类似下面的逻辑: var rejected = consumed(this) if (rejected) { return rejected }consumed函数:function consumed(body) { if (body.bodyUsed) { return Promise.reject(new TypeError('Already read')) } body.bodyUsed = true}每次调用text()、json()等函数后会将bodyUsed变量变为true,用来标识返回值已经读取过了,下一次再读取直接抛出TypeError('Already read')。这也遵循了原生fetch的原则:因为Responses对象被设置为了 stream 的方式,所以它们只能被读取一次十、fetch的坑点VUE的文档中对fetch有下面的描述:使用fetch还有很多别的注意事项,这也是为什么大家现阶段还是更喜欢 axios 多一些。当然这个事情在未来可能会发生改变。由于fetch是一个非常底层的API,它并没有被进行很多封装,还有许多问题需要处理:不能直接传递JavaScript对象作为参数需要自己判断返回值类型,并执行响应获取返回值的方法获取返回值方法只能调用一次,不能多次调用无法正常的捕获异常老版浏览器不会默认携带cookie不支持jsonp十一、对fetch的封装请求参数处理支持传入不同的参数类型:function stringify(url, data) { var dataString = url.indexOf('?') == -1 ? '?' : '&amp;'; for (var key in data) { dataString += key + '=' + data[key] + '&amp;'; }; return dataString;}if (request.formData) { request.body = request.data;} else if (/^get$/i.test(request.method)) { request.url = ${request.url}${stringify(request.url, request.data)};} else if (request.form) { request.headers.set('Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8'); request.body = stringify(request.data);} else { request.headers.set('Content-Type', 'application/json;charset=UTF-8'); request.body = JSON.stringify(request.data);}cookie携带fetch在新版浏览器已经开始默认携带同源cookie,但在老版浏览器中不会默认携带,我们需要对他进行统一设置: request.credentials = 'same-origin'; // 同源携带 request.credentials = 'include'; // 可跨域携带异常处理当接收到一个代表错误的 HTTP 状态码时,从 fetch()返回的 Promise 不会被标记为 reject, 即使该 HTTP 响应的状态码是 404 或 500。相反,它会将 Promise 状态标记为 resolve (但是会将 resolve 的返回值的 ok 属性设置为 false ),仅当网络故障时或请求被阻止时,才会标记为 reject。因此我们要对fetch的异常进行统一处理.then(response =&gt; { if (response.ok) { return Promise.resolve(response); }else{ const error = new Error(请求失败! 状态码: ${response.status}, 失败信息: ${response.statusText}`); error.response = response; return Promise.reject(error); }});返回值处理对不同的返回值类型调用不同的函数接收,这里必须提前判断好类型,不能多次调用获取返回值的方法:.then(response => { let contentType = response.headers.get(‘content-type’); if (contentType.includes(‘application/json’)) { return response.json(); } else { return response.text(); }});jsonpfetch本身没有提供对jsonp的支持,jsonp本身也不属于一种非常好的解决跨域的方式,推荐使用cors或者nginx解决跨域,具体请看下面的章节。fetch封装好了,可以愉快的使用了。嗯,axios真好用…十二、跨域总结谈到网络请求,就不得不提跨域。浏览器的同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。通常不允许不同源间的读操作。跨域条件:协议,域名,端口,有一个不同就算跨域。下面是解决跨域的几种方式:nginx使用nginx反向代理实现跨域,参考我这篇文章:前端开发者必备的nginx知识corsCORS是一个W3C标准,全称是"跨域资源共享”(Cross-origin resource sharing)。它允许浏览器向跨源服务器,发出XMLHttpRequest请求。服务端设置 Access-Control-Allow-Origin 就可以开启 CORS。 该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源。app.all(’’, function (req, res, next) { res.header(“Access-Control-Allow-Origin”, “*”); res.header(“Access-Control-Allow-Headers”, “X-Requested-With”); res.header(“Access-Control-Allow-Methods”, “PUT,POST,GET,DELETE,OPTIONS”); next();});jsonpscript标签的src属性中的链接可以访问跨域的js脚本,利用这个特性,服务端不再返回JSON格式的数据,而是返回一段调用某个函数的js代码,在src中进行了调用,这样实现了跨域。jquery对jsonp的支持: $.ajax({ type : “get”, url : “http://xxxx” dataType: “jsonp”, jsonp:“callback”, jsonpCallback: “doo”, success : function(data) { console.log(data); } });fetch、axios等并没有直接提供对jsonp的支持,如果需要使用这种方式,我们可以尝试进行手动封装:(function (window,document) { “use strict”; var jsonp = function (url,data,callback) { // 1.将传入的data数据转化为url字符串形式 // {id:1,name:‘jack’} => id=1&name=jack var dataString = url.indexof(’?’) == -1? ‘?’: ‘&’; for(var key in data){ dataString += key + ‘=’ + data[key] + ‘&’; }; // 2 处理url中的回调函数 // cbFuncName回调函数的名字 :my_json_cb_名字的前缀 + 随机数(把小数点去掉) var cbFuncName = ‘my_json_cb_’ + Math.random().toString().replace(’.’,’’); dataString += ‘callback=’ + cbFuncName; // 3.创建一个script标签并插入到页面中 var scriptEle = document.createElement(‘script’); scriptEle.src = url + dataString; // 4.挂载回调函数 window[cbFuncName] = function (data) { callback(data); // 处理完回调函数的数据之后,删除jsonp的script标签 document.body.removeChild(scriptEle); } document.body.appendChild(scriptEle); } window.$jsonp = jsonp;})(window,document)postMessage跨域postMessage()方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递。//捕获iframevar domain = ‘http://scriptandstyle.com’;var iframe = document.getElementById(‘myIFrame’).contentWindow;//发送消息setInterval(function(){ var message = ‘Hello! The time is: ’ + (new Date().getTime()); console.log(‘blog.local: sending message: ’ + message); //send the message and target URI iframe.postMessage(message,domain); },6000);//响应事件window.addEventListener(‘message’,function(event) { if(event.origin !== ‘http://davidwalsh.name’) return; console.log(‘message received: ’ + event.data,event); event.source.postMessage(‘holla back youngin!’,event.origin);},false);postMessage跨域适用于以下场景:同浏览器多窗口间跨域通信、iframe间跨域通信。WebSocketWebSocket 是一种双向通信协议,在建立连接之后,WebSocket 的 server 与 client 都能主动向对方发送或接收数据而不受同源策略的限制。 function WebSocketTest(){ if (“WebSocket” in window){ alert(“您的浏览器支持 WebSocket!”); // 打开一个 web socket var ws = new WebSocket(“ws://localhost:3000/abcd”); ws.onopen = function(){ // Web Socket 已连接上,使用 send() 方法发送数据 ws.send(“发送数据”); alert(“数据发送中…”); }; ws.onmessage = function (evt) { var received_msg = evt.data; alert(“数据已接收…”); }; ws.onclose = function(){ // 关闭 websocket alert(“连接已关闭…”); }; } else{ // 浏览器不支持 WebSocket alert(“您的浏览器不支持 WebSocket!”); } }文中如有错误,欢迎在评论区指正,谢谢阅读。 ...

March 27, 2019 · 4 min · jiezi

一篇文章让你学会如何选择 JS HTTP 请求库

以前前端提到网络请求通常是指浏览器,但现在随着 Node.js、小程序的出现,网络请求不再局限于浏览器。本文将带你了解不同请求的原理,以及如何为项目选择合适的请求库。1. 请求原理1.1 浏览器浏览器通过 XMLHttpRequest 对象实现 http 请求。远古时代 ie6 是借助 ActiveXObject 对象实现 http 请求,目前已无人使用,不考虑兼容。W3C 标准新提出的 Fecth API,基于 Promise 实现,相对 XMLHttpRequest 对象调用更方便,但旧浏览器不支持 Promise,需要对 Promise 进行 pollyfill。XMLHttpRequestlet xhr = new XMLHttpRequest();xhr.open(‘get’, url, true); xhr.send();xhr.onreadystatechange = function() { if(xhr.readyState === 4 && xhr.status === 200 ) { let response = JSON.parse(xhr.responseText); }}Fetchfetch(url) .then(response => response.json()) .then(data => console.log(data)) .catch(e => console.log(“error”, e))1.2 Node.jsNode.js 发布于 2009 年,是一个基于 Chrome V8 引擎的 JavaScript 运行环境,Node.js 的顶层对象是 global,不存在 window 对象,不能通过 XMLHttpRequest 对象实现 http 请求。Node.js 中通过引入 http/https/http2 模块实现 http 请求,下面为 http 模块实现的例子:const http = require(‘http’);const server = http.createServer((req, res) => { res.end(‘hello world’);});server.on(‘clientError’, (err, socket) => { socket.end(‘HTTP/1.1 400 Bad Request\r\n\r\n’);});server.listen(8000);1.3 React NativeReact Native 是 Facebook 2015 开源跨平台移动应用开发工具。React Native 中已经内置了 XMLHttpRequest API,同时提供了和 web 标准一致的Fetch API,所以大部分在 web 端可以使用的网络请求库在 React Native 中也可以使用。1.4 WeexWeex 是 阿里 2016 开源跨平台移动应用开发工具。Weex 通过封装模块来调用原生功能,提供了 stream 模块来实现网络请求。1.5 小程序2017 年微信小程序上线,随后各大平台都推出自己的小程序。小程序由于要对 http 请求做参数校验、兼容各平台(iOS、Android)或版本问题,所以提供了一套属于自己的 API,不提供 window 对象。下面为微信小程序和支付宝小程序的官网示例:微信小程序wx.request({ url: ’test.php’, // 仅为示例,并非真实的接口地址 data: { x: ‘’, y: ’’ }, header: { ‘content-type’: ‘application/json’ // 默认值 }, success(res) { console.log(res.data) }})支付宝小程序my.httpRequest({ url: ‘http://httpbin.org/post', method: ‘POST’, data: { from: ‘支付宝’, production: ‘AlipayJSAPI’, }, dataType: ‘json’, success: function(res) { my.alert({content: ‘success’}); }, fail: function(res) { my.alert({content: ‘fail’}); }, complete: function(res) { my.hideLoading(); my.alert({content: ‘complete’}); }});2. 请求库从上文可以看出,平台间的请求方式存在各种差异,请求库就是为解决这种差异。下面为目前较火的请求库。2.1 $.ajax(支持浏览器)$.ajax 为 jQuery 对 XMLHttpRequest 对象进行兼容封装。需要补充的是 React Native 可以使用部分浏览器网络请求库,但是不能使用 jQuery,因为 jQuery 中还使用了很多浏览器中才有而 React Native 中没有的东西。此外,现在使用框架的项目中我们通常采用其他请求库,或者自己根据项目对 XMLHttpRequest 或 Fetch 进行封装,不会为了网络请求引入 jQuery。2.2 Request(支持 Node.js)Request 是对 Node.js 的 http/https 模块封装的 http 库。var request = require(‘request’);request(‘http://www.google.com’, function (error, response, body) { console.log(’error:’, error); // Print the error if one occurred console.log(‘statusCode:’, response && response.statusCode); // Print the response status code if a response was received console.log(‘body:’, body); // Print the HTML for the Google homepage.});2.3 SuperAgent(支持 Node.js)SuperAgent 和 Request 类似,都是对 Node.js 的 http/https 模块封装的 http 库。var request = require(‘superagent’)request .post(’/api/pet’) .send({ name: ‘Manny’, species: ‘cat’ }) .set(‘X-API-Key’, ‘foobar’) .set(‘Accept’, ‘application/json’) .then(res => { alert(‘yay got ’ + JSON.stringify(res.body)); });2.4 Axios(支持 React Native,Node,浏览器)Axios 是一个基于 promise 的 HTTP 请求库,可以用在浏览器和 Node.js 中。浏览器中使用 XMLHttpRequest,Node.js 中使用 http/https 模块。下面为请求示例:axios.get(’/user?ID=12345’) .then(function (response) { console.log(response); }) .catch(function (error) { console.log(error); });Vue 2.0 推荐使用 Axios 作为 Vue 的请求库。而且在 SSR 的时候我们在服务端、客户端都需要请求,所以通常会选择 Axios。2.5 Fly.js(支持 Node.js 、微信小程序 、Weex 、React Native 、Quick App 和浏览器)Fly.js 是一个基于 promise 的 HTTP 请求库,可以用在Node.js 、微信小程序 、Weex 、React Native 、Quick App 和浏览器中,对上述平台都做了兼容。fly.get(’/user?id=133’) .then(function (response) { console.log(response); }) .catch(function (error) { console.log(error); });除了 Fly.js,有些小程序开发框架本身提供网络请求库,对平台做了兼容,比如 Taro.request。总结不同请求库之间的 API、使用都会存在区别。项目开始时,根据需要兼容的平台选择合适的请求库,会大大减少以后代码重构的麻烦。本文首发于公众号,更多内容欢迎关注我的公众号: 阿夸漫谈 ...

January 27, 2019 · 2 min · jiezi