Skip to content

网络请求

早期的网页都是通过后端渲染来完成的:服务器端渲染(SSR,server side render):

优点:

  1. 提高开发效率

    前后端各负其责, 前端和后端都做自己擅长的事情,不互相依赖,开发效率更快,而且分工比较均衡,会大大提高开发效率

  2. 用户访问速度快,提升页面性能,优化用户体验

    没有页面之间的跳转,资源都在同一个页面里面,无刷新加载数据,页面片段间的切换快,使用户体验上升了一大截;前后端不分离,稍不留神会触发浏览器的重排和重绘,加载速度慢,降低用户的体验

  3. 增强代码可维护性,降低维护成本,改善代码的质量

    前后端不分离,代码较为繁杂,维护起来难度大,成本高

  4. 减轻了后端服务器的请求压力

    公共资源只需要加载一次,减少了HTTP请求数

  5. 一套后端程序代码,不用修改就可以用于Web界面、手机、平板等多种客户端

缺点:

  • 首屏渲染的时间长

    常见的单页面应用,如果不对路由进行处理,在加载首页的时候,就会将所有组件全部加载,并向服务器请求数据,这必将拖慢加载速度;

    1. 将多个页面的资源打包糅合到一个页面,这个页面一开始需要加载的东西会非常多,而网速是一定的,所以会导致首屏渲染时间很长,

      一般都会使用路由懒加载,当新跳转页面得时候才会进行加载响应得js

    2. 首屏渲染后,就是无刷新更新,用户体验相对较好

  • 不利于搜索引擎的优化(SEO)

    现有的搜索引擎都是通过爬虫工具来爬取各个网站的信息,这些爬虫工具一般只能爬取页面上(HTML)的内容,而前后端分离,前端的数据基本上都是存放在行为逻辑(JavaScript)文件中,爬虫工具无法爬取,无法分析出你网站到底有什么内容,无法与用户输入的关键词做关联,最终排名就低

  • 不能使用浏览器里面的前进后退功能

  • 一些版本较低的浏览器对其支持度不足

服务端渲染:在服务器直接生产渲染好的对应的HTML页面,返回给客户端进行展示

当请求一个URL,URL会发送到服务器, 服务器会对该URL进行匹配,并进行一系列的处理,服务器渲染好整个页面生成一个最终的HTML页面,这个HTML页面不需要单独加载任何的js和css,可以直接交给浏览器展示。

后端路由:后端处理URL和页面之间的映射关系,当我们页面中需要请求不同的路径内容时, 交给服务器来进行处理, 服务器渲染好整个页面, 并且将页面返回给客户顿.

优点:

  1. 有利于SEO优化

因为后端渲染爬虫发送请求的时候,接口直接回返回一个html的页面

  1. 响应快,前端耗时少

前端耗时少,因为后端拼接完了HTML,不需要先下载一堆JS和CSS 后才能看到页面,即减少了首屏时间,浏览器只需要直接渲染出来

  1. 缓存

后端生成静态化文件。即生成缓存片段,这样就可以减少数据库查询浪费的时间了,且对于数据变化不大的页面非常高效

缺点:

一种情况是整个页面的模块由后端人员来编写和维护的. 另一种情况是前端开发人员如果要开发页面, 需要通过PHP和Java等语言来编写页面代码. 而且通常情况下HTML代码和数据以及对应的逻辑会混在一起, 编写和维护都是非常糟糕的事情

**超文本传输协议(英语:HyperText Transfer Protocol,缩写:HTTP)**是一种用于分布式、协作式和超媒体信息系统的应用层协议;

  1. HTTP是万维网的数据通信的基础,设计HTTP最初的目的是为了提供一种发布和接收HTML页面的方法;
  2. 通过HTTP或者HTTPS协议请求的资源由统一资源标识符(Uniform Resource Identifiers,URI)来标识;
  • HTTP是一个客户端(用户)和服务端(网站)之间请求和响应的标准。
    1. 通过使用网页浏览器、网络爬虫或者其它的工具,客户端发起一个HTTP请求到服务器上指定端口(默认端口为80);
      • 我们称这个客户端为用户代理程序(user agent);
    2. 响应的服务器上存储着一些资源,比如HTML文件和图像
      • 我们称这个响应服务器为源服务器(origin server)

我们网页中的资源通常是被放在Web资源服务器中,由浏览器自动发送HTTP请求来获取、解析、展示的。

  • 目前我们页面中很多数据是动态展示的:
    • 比如页面中的数据展示、搜索数据、表单验证等等,也是通过在JavaScript中发送HTTP请求获取的;

一次HTTP请求主要包括:请求(Request)和响应(Response)

无连接的含义http1.0之前是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。

随着时间的推移,网页变得越来越复杂,里面可能嵌入了很多图片,这时候每次访问图片都需要建立一次 TCP 连接就显得很低效

  • http1.1 版本新增了 keep-alive采用了持久链接,多个请求可以共用一个TCP链接,不同的web服务器会有不同的keep-alive 的时间,node默认5s 指定的时间内保持链接,http1.0如果想要保持链接的话只要在,请求头和响应头中添加 connection:keep-alive

无状态是指协议对于事务处理没有记忆能力,服务器不知道客户端是什么状态。即我们给服务器发送 HTTP 请求之后,服务器根据请求,会给我们发送数据过来,但是,发送完,不会记录任何信息。

  • HTTP 是一个无状态协议,这意味着每个请求都是独立的

灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type(Content-Type是HTTP包中用来表示内容类型的标识)加以标记。

支持客户/服务器模式。

简单快速:客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有GET、HEAD、POST。每种方法规定了客户与服务器联系的类型不同。由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。

HTTP 的请求报文分为三个部分 请求行、请求头和 (请求空行 请求体),

请求行(Request Line)分为三个部分:请求方法、请求地址和协议及版本,以CRLF(rn)结束。

RFC (Request For Comments)-意即“请求评议”。定义了一组请求方式,来表示要对给定资源执行的操作:

HTTP/1.1 定义的请求方法有8种:GET、POST、PUT、DELETE、PATCH、HEAD、OPTIONS、TRACE,

  • 最常的两种GET和POST如果是RESTful接口的话一般会用到GET、POST、DELETE、PUT
  1. GET:GET 方法请求一个指定资源的表示形式,使用 GET 的请求应该只被用于获取数据。

  2. HEAD:HEAD 方法请求一个与 GET 请求的响应相同的响应,但没有响应体。

    比如在准备下载一个文件前,先获取文件的大小,再决定是否进行下载;

  3. POST:POST 方法用于将实体提交到指定的资源。

  4. PUT:PUT 方法用请求有效载荷(payload)替换目标资源的所有当前表示;

    印象中PUT的默认 contype-type: application/x-www-form-urlencoded 方式提交数据。

  5. DELETE:DELETE 方法删除指定的资源;

  6. PATCH:PATCH 方法用于对资源应部分修改;

  7. CONNECT:CONNECT 方法建立一个到目标资源标识的服务器的隧道,通常用在代理服务器,网页开发很少用到。

  8. TRACE:TRACE 方法沿着到目标资源的路径执行一个消息环回测试。

  • 注意,仅有POST、PUT以及PATCH这三个动词时会包含请求体,而GETHEADDELETECONNECTTRACEOPTIONS这几个动词时不包含请求体。
  • HTTP/0.9
    • 发布于1991年;
    • 只支持GET请求方法获取文本数据,当时主要是为了获取HTML页面内容;
  • HTTP/1.0
    • 发布于1996年;
    • 支持POST、HEAD等请求方法,支持请求头、响应头等,支持更多种数据类型(不再局限于文本数据) ;
    • 但是浏览器的每次请求都需要与服务器建立一个TCP连接,请求处理完成后立即断开TCP连接,每次建立连接增加了性能损耗;
  • HTTP/1.1(目前使用最广泛的版本)
    • 发布于1997年;
    • 增加了PUT、DELETE等请求方法;
    • 采用持久连接(Connection: keep-alive),多个请求可以共用同一个TCP连接;
  • 2015年,HTTP/2.0
  • 2018年,HTTP/3.0

在request对象的header中也包含很多有用的信息,客户端会默认传递过来一些信息:

1. content-type是这次请求携带的数据的类型
Section titled “1. content-type是这次请求携带的数据的类型”

主要功能就是给后台返回的是一个什么样类型的数据,是文本类型还是json类型

get请求,设置就有不设置就没有 默认在 FromData里面

post请求,如果有请求数据传入后台,浏览器默认加入一个Content-Type: text/plain;charset=UTF-8

  1. application/x-www-form-urlencoded:表示数据被编码成以 ’&’ 分隔的键 - 值对,同时以 ’=’ 分隔键和值

  2. application/json:表示是一个json类型;

    请求头中的 Content-type 是告诉服务器返回的数据类型

  3. text/plain:表示是文本类型;

  4. application/xml:表示是xml类型;

  5. multipart/form-data:表示是上传文件;

    FormData 对象,以 multipart/form-data 形式发送数据

2. Accept(接受) 指定客户端可以接收的内容类型
Section titled “2. Accept(接受) 指定客户端可以接收的内容类型”
  1. Accept: text/plain,
  2. text/html,
  3. application/json

http是基于TCP协议的,但是通常在进行一次请求和响应结束后会立刻中断;

  • 在http1.0中,如果想要继续保持连接:

    1. 浏览器需要在请求头中添加 connection: keep-alive;
    2. 服务器需要在响应头中添加 connection:keey-alive;
    3. 当客户端再次放请求时,就会使用同一个连接,直接一方中断连接;
  • 在http1.1中,所有连接默认是 connection: keep-alive的

    1. 不同的Web服务器会有不同的保持 keep-alive的时间;
    2. Node中默认是5s中;
4. Accept-Charset 浏览器可以接受的字符编码集。
Section titled “4. Accept-Charset 浏览器可以接受的字符编码集。”

Accept-Charset: iso-8859-5

指定浏览器可以支持的web服务器返回内容压缩编码类型。 Accept-Encoding: compress, gzip

6. Accept-Language 浏览器可接受的语言
Section titled “6. Accept-Language 浏览器可接受的语言”

Accept-Language: en,zh

7. Authorization HTTP授权的授权证书
Section titled “7. Authorization HTTP授权的授权证书”

Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==

8. User-Agent User-Agent的内容包含发出请求的用户信息
Section titled “8. User-Agent User-Agent的内容包含发出请求的用户信息”

User-Agent: Mozilla/5.0 (Linux; X11)

Content-Length首部指示出报文中实体主体的字节大小. 这个大小是包含了所有内容编码的, 比如, 对文本文件进行了gzip压缩的话, Content-Length首部指的就是压缩后的大小而不是原始大小.

根据应用场景的不同,HTTP请求的请求体有三种不同的形式。

  • 知识点:

    1. Request Payload更准确的说是http request的payload body

      一般用在数据通过POST请求或者PUT请求。

    2. FormDate 是get 和 form表单 就是查询字符串

    • 所以区别就是,他们只是因为Content-Type设置的不同,并不是数据提交方式的不同,这两种提交都会将数据放在message-body中
  1. application/json

    这个 Content-Type 作为响应头大家肯定不陌生。用来告诉服务端消息主体是序列化后的 JSON 字符串

    特别适合 RESTful 的接口。

  2. text/xml

    XML-RPC(XML Remote Procedure Call)

    XML 作为编码方式的远程调用规范。

  3. Query String:application/x-www-form-urlencoded 查询字符串

    这算是最常见的 POST 提交数据的方式了。浏览器的原生 <form> 表单,如果不设置 enctype 属性,那么最终就会以 application/x-www-form-urlencoded 方式提交数据。请求类似于下面这样(无关的请求头在本文中都省略掉了):

类似于请求消息中的请求头,其格式和请求头信息格式一样,即响应头:响应头值。

  • response 代表响应, 可以通过该对象分别设置Http响应: 状态行 响应头 响应体

Xj5NnK.png

  • 状态行格式: HTTP版本协议 状态码 状态码原因短语

  • 举例:HTTP/1.1 200 OK

  • 状态码用于表示服务器对请求的处理结果,它是一个三位的十进制数。响应状态码分为5类,如下所示:

    Http状态码非常多,可以根据不同的情况,给客户端返回不同的状态码;

  1. 表示请求成功,要求客户端继续提交下一次请求才能完成整个处理过程(100199)
  2. 成功响应 (200299)
  3. 重定向消息 为完成请求,客户需进一步细化请求。例如,请求的资源已经移动一个新地址,常用302、307和304(300399)
  4. 客户端错误响应 (400499)
  5. 服务端错误响应 (500599)
  • 常见HTTP状态码 状态描述 信息说明
Header解释
200OK 客户端请求成功
201Created POST请求,创建新的资源
301Moved Permanently 请求资源的URL已经修改,响应中会给出新的URL
400Bad Request 客户端的错误,服务器无法或者不进行处理
401Unauthorized 未授权的错误,必须携带请求的身份信息
403Forbidden 客户端没有权限访问,被拒接
404Not Found 服务器找不到请求的资源。
500Internal Server Error 服务器遇到了不知道如何处理的情况。
503Service Unavailable 服务器不可用,可能处理维护或者重载状态,暂时无法访问

响应的header中包括一些服务器给客户端的信息

  • 用于在响应消息中向客户端传递附加信息,包括服务程序的名称,要求客户端进行认证的方式,请求的资源已移动到新地址等.

    主要: Location , Server , WWW-Authenticate(认证头)

  1. Connection:处理完这次请求后,是断开连接还是继续保持连接;

  2. Content-Encoding:服务器通过这个头,告诉游览器数据的压缩格式;

  3. Content-Length:服务器通过这个头,告诉游览器回送数据的长度;

  4. Content-Type:服务器通过这个头,告诉游览器回送数据的类型;

    这里的 content-Type:的作用就是服务器告诉浏览器返回的数据格式,是服务器来设置的

    浏览器可能会对不同的格式来进行处理

    问题:设置了content-type之后浏览器会根据不同类型来显示到页面上例如text/html,后端做法,但是ajax 则不需要,正文过来什么就是什么, 通过ajax 或者JSON 进行转换json对象,至于 text/html 和 application/json 的区别是什么后面在说

    • 三种组成部分
    • media type:声明传输数据的媒体类型( MIME );
    • charset:声明传输数据采用的是何种字符集;
    • boundary:数据分界符,有多部分数据实体时(multipart/form-data、multipart/byteranges),该指令是必需的,用于封装消息的多个部分的边界;其由 1 到 70 个字符组成,浏览器中会自动生成,该字符集对于通过网关鲁棒性良好,不以空白结尾。
  5. Date:当前时间值;

  6. Server:服务器通过这个头,告诉游览器服务器的类型;

    7.Vary:Accept-Encoding----明确告知缓存服务器Accept-Encoding字段的内容,分别缓存不同的版本,参考:https://imququ.com/post/vary-header-in-http.html

8.X-Powered-By:服务器告知客户机网站是用何种语言或框架编写的。

即响应正文

  • 响应头之后紧跟着一个空行,然后接响应体。响应体就是Web服务器发送到客户端的实际内容。除网页外,响应体还可以是诸如Word、Excel或PDF等其他类型的文档,具体是哪种文档类型由Content-Type指定的MIME类型决定。

JAX 是异步的 JavaScript 和 XML(Asynchronous JavaScript And XML)

  • 它可以使用 JSON,XML,HTML 和 text 文本等格式发送和接收数据;
  • 第一步:创建网络请求的AJAX对象(使用XMLHttpRequest)

  • 第二步:监听XMLHttpRequest对象状态的变化,或者监听onload事件(请求完成时触发);

  • 第三步:配置网络请求(通过open方法);**默认异步请求 设置同步请求第三个参数为:false **

  • 第四步:发送send网络请求

    const url = "http://123.207.32.32:8000/home/multidata"
    //1、创建XMLHttpRequest对象
    const xhr = new XMLHttpRequest()
    //2、监听状态等待响应
    xhr.onreadystatechange = function() {
    //监听XMLHttpRequest对象状态的变化
    if(xhr.readyState !== XMLHttpRequest.DONE) return
    console.log(xhr.readyState)
    }
    //3、method, url, 是否为异步async
    //同步 true
    xhr.open("get",url, true)
    //4、发送请求
    xhr.send()

在一次网络请求中看到状态发生了很多次变化,这是因为对于一次请求来说包括如下的状态:

  • Api: xhr.onreadystatechange = function() {} 回调函数
  • 注意:这里是记录的XMLHttpRequest对象的状态变化
  • 准备状态 readyState
状态描述
0UNSENT代理被创建,但尚未调用 open() 方法。
1OPENEDopen() 方法已经被调用。
2HEADERS_RECEIVEDsend() 方法已经被调用,并且头部和状态已经可获得。
3LOADING 下载中;responseText 属性已经包含部分数据。
4DONE下载操作已完成。

除了onreadystatechange还有其他的事件可以监听

  1. loadstart:请求开始。

  2. progress: 一个响应数据包到达,此时整个 response body 都在 response 中。

  3. abort:调用 xhr.abort() 取消了请求。

  4. error:发生连接错误,例如,域错误。不会发生诸如 404 这类的 HTTP 错误。

  5. load:请求成功完成。就是XMLHttpRequest对象的 done状态

    可以使用load事件来监听请求是否成功

    const url = "http://123.207.32.32:8000/home/multidata"
    //1、创建XMLHttpRequest对象
    const xhr = new XMLHttpRequest()
    //method, url, 是否为异步async true
    xhr.open("get",url, true)
    //发送请求
    xhr.send()
    //监听状态
    xhr.onload = function() {
    //准备状态
    console.log(xhr.readyState)
    }
  6. timeout:由于请求超时而取消了该请求(仅发生在设置了 timeout 的情况下)。

  7. loadend:在 load,error,timeout 或 abort 之后触发 加载结束

  • 注意:设置请求头要放在 open 配置请求信息的下面
//XMLHttpRequest 的请求方式
const xhr = new XMLHttpRequest()
xhr.responseType= "json"
xhr.open("post", "http://123.207.32.32:1888/02_param/posturl")
//放在配置请求信息的后面
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded")
xhr.send("name=why&age=18&address=广州市")
xhr.onload = function() {
console.log(xhr.response)
}

XMLHttpRequest 实例对象中的 response 属性返回响应的正文内容

  • 注意:返回的类型取决于responseType的属性设置;

    1. text 作为默认值。
    2. 可以设置为 json
  • 其他属性:responseTextresponseXML

    • 早期通常服务器返回的数据是普通的文本和XML,所以我们通常会通过responseText、 responseXML来获取响应结果;

      起始就是方便不用写 responseType 个人觉得

    • 之后将它们转化成JavaScript对象形式;

    • 目前服务器基本返回的都是json数据,直接设置为json即可;

  • 返回json对象的方式

    在配置请求信息钱配置 responseType,否则默认是文本

    // 告知xhr获取到的数据的类型
    xhr.responseType = "json"

获取响应行的 响应码和 状态码,原因短语

  1. 获取响应码xhr.status
  2. 状态码原因短语xhr.statusText

2、HTTP常见响应行状态码、状态码原因短语

Section titled “2、HTTP常见响应行状态码、状态码原因短语”
常见HTTP状态码状态描述:原因短语信息说明
200OK客户端请求成功
201Created POST请求,创建新的资源
301Moved Permanently请求资源的URL已经修改,响应中会给出新的URL
400Bad Request客户端的错误,服务器无法或者不进行处理
401Unauthorized未授权的错误,必须携带请求的身份信息
403Forbidden客户端没有权限访问,被拒接
404Not Found服务器找不到请求的资源。
500Internal Server Error服务器遇到了不知道如何处理的情况。
503Service Unavailable服务器不可用,可能处理维护或者重载状态,暂时无法访问
  • 常见的传递给服务器数据的方式有如下几种:

  • 注意:

    1. 只有get 查询字符串的方式和 FormData 的实例对象的方式不用设置请求头
    2. 只有post请求有请求体
  • get 也能发送 FormData 对象,会被转成query的形式
//XMLHttpRequest 的请求方式
const xhr = new XMLHttpRequest()
xhr.responseType= "json"
//get 请求和json字符串, 设置响应类型
xhr.open("get", "http://123.207.32.32:1888/02_param/get?name=why&age=18&address=广州市")
xhr.send()
xhr.onload = function() {
console.log(xhr.response)
}

方式二:POST请求 x-www-form-urlencoded 格式

Section titled “方式二:POST请求 x-www-form-urlencoded 格式”
xhr.open("post", "http://123.207.32.32:1888/02_param/posturl")
xhr.setRequestHeader("content-type", "application/x-www-form-urlencoded")
xhr.send("name=why&age=18&address=广州市")
  • 知识点FormData 对象,以 multipart/form-data 形式发送数据

    本接口和此方法都相当简单直接。如果送出时的编码类型被设为 “multipart/form-data”,它会使用和表单一样的格式。

  • 注意:这里的 FormData 的实例对象要和发送请求在统一时机内执行,否则获取不到form表单的value。

    在同一个代码块内执行,避免获取不到value

//form表单元素
const formEl = document.querySelector("form")
const btnEl = document.querySelector(".btn")
btnEl.addEventListener("click", function() {
//这里的 FormData 的实例对象要和发送请求**在统一时机内执行**,否则获取不到form表单的value
const formdata = new FormData(formEl)
xhr.open("post", "http://123.207.32.32:1888/02_param/postform")
xhr.send(formdata)
})
  • 注意事项:记得添加请求头

    要将json对象转成json字符串

btnEl.addEventListener("click", function() {
xhr.open("post", "http://123.207.32.32:1888/02_param/postjson")
xhr.setRequestHeader("content-type", "application/json")
xhr.send(JSON.stringify(obj))
})
  • 知识点

    1. 一个请求伴随着header设置为Content-Type: application/json method 为POST 的时候,请求信息会放在 请求空行里

      POST /some-path HTTP/1.1
      Content-Type: application/json
      { "foo" : "bar", "name" : "John" }
    2. method="post",并且设置了Content-Type: application/x-www-form-urlencoded或者Content-Type: multipart/form-data。也会放在请求空行里

      POST /some-path HTTP/1.1
      Content-Type: application/x-www-form-urlencoded
      foo=bar&name=John
    • 以上两种 都是放在 request的payload里
    1. 传统的Form表单提交,Content-Type默认为 application/x-www-form-urlencoded

      会放在请求头的 FormData 当中

    2. get 请求 Content-Type: application/x-www-form-urlencoded 的时候也是在 FormData 中

网络请求的过程中,为了避免过长的时间服务器无法返回数据,通常我们会为请求设置一个超时时间:timeout。

  • 当达到超时时间后依然没有获取到数据,那么这个请求会自动被取消掉
  • 默认值为0,表示会一直等待服务器的响应
    • 我们也可以通过abort方法强制取消请求
  • 注意:同样在open 配置请求信息前设置
const timeUrl = "http://123.207.32.32:1888/01_basic/timeout";
const xhr = new XMLHttpRequest();
xhr.responseType = "json";
//配置超时间
xhr.timeout = 3000;
xhr.onload = function() {
console.log(xhr.response, xhr.status, xhr.statusText)
}
//监听请求超时
xhr.ontimeout = function() {
console.log("请求超时")
}
xhr.open("get", timeUrl)
xhr.send()
const btnEl = document.querySelector(".btn")
btnEl.addEventListener("click", function() {
//取消请求
xhr.abort()
})
function request({
url,
method,
data = {},
header = {},
success,
failure
} = {}) {
const xhr = new XMLHttpRequest()
xhr.responseType = 'json'
xhr.onload = function() {
if(xhr.status*1 >=200 && xhr.status*1 <= 300) {
success(xhr.response)
} else {
failure(xhr.status)
}
}
//发送get请求
if(method.toUpperCase() == "GET") {
if(Object.keys(data).length !== 0) {
const paramArr = []
for (const key in data) {
if (Object.hasOwnProperty.call(data, key)) {
const el = data[key];
paramArr.push(`${key} = ${el}`)
}
}
const queryString = paramArr.join("&")
xhr.open("get", url+"?"+ queryString)
//设置header
if(Object.keys(header).length !== 0) {
for (const key in header) {
if (Object.hasOwnProperty.call(header, key)) {
const el = header[key];
xhr.setRequestHeader(key, el)
}
}
}
xhr.send()
} else {
xhr.open("get", url)
//设置header
if(Object.keys(header).length !== 0) {
for (const key in header) {
if (Object.hasOwnProperty.call(header, key)) {
const el = header[key];
xhr.setRequestHeader(key, el)
}
}
}
xhr.send()
}
}
//发送post请求
if(method.toUpperCase() == "POST") {
xhr.open("post", url)
//设置header
if(Object.keys(header).length !== 0) {
for (const key in header) {
if (Object.hasOwnProperty.call(header, key)) {
const el = header[key];
xhr.setRequestHeader(key, el)
}
}
}
xhr.send(JSON.stringify(data))
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- 设置 multiple -->
<input class="inp" type="file", name="file" multiple>
<button class="btn">上传</button>
</body>
<script>
const url = "http://123.207.32.32:1888/02_param/upload"
const btnEl = document.querySelector(".btn")
const inpEl = document.querySelector(".inp")
btnEl.addEventListener("click", function() {
// console.log(typeof inpEl.files)
const formdata = new FormData()
for(let i = 0; i < inpEl.files.length; i++) {
console.log(inpEl.files[i])
//批量添加,form 中的 input.files[i]元素,这个要后台配置分段上传
//添加选中一个就是一个
formdata.append(inpEl.files[i].name, inpEl.files[i])
}
const xhr = new XMLHttpRequest();
xhr.onload = function() {
console.log(xhr.response)
}
xhr.open("post", url)
//上传
xhr.send(formdata)
})
</script>
</html>

Fetch可以看做是早期的XMLHttpRequest的替代方案,它提供了一种更加现代的处理方案:

  • 特点:

    1. 返回值是一个Promise,提供了一种更加优雅的处理结果方式

      通过resolve,reject 的方式调用

    2. 不像XMLHttpRequest一样,所有的操作都在一个对象上

  • 参数

    1. 参数一 input:input:定义要获取的资源地址,可以是一个URL字符串,也可以使用一个Request对象(实验性特性)类型;

      就是url

    2. 参数二 init:配置 init 初始化对象 {} 具体参数可以查看mdn

一个配置项对象,包括所有对请求的设置。可选的参数有:

  • method: 请求使用的方法,如 GETPOST

  • headers: 请求的头信息,形式为 Headers 的对象或包含 ByteString 值的对象字面量。

  • body: 请求的 body 信息:可能是一个 BlobBufferSourceFormDataURLSearchParams 或者 USVString 对象。注意 GET 或 HEAD 方法的请求不能包含 body 信息。

    需要传给后台的请求信息,message body

    const formData = new FormData()
    formData.append("name", "why")
    formData.append("age", 18)
    const response = await fetch("http://123.207.32.32:1888/02_param/postform", {
    method: "post",
    //可以是一个form表单对象
    body: formData
    //可以设置请求头,是一个对象
    /*headers: {
    'Content-Type': 'application/json'
    // 'Content-Type': 'application/x-www-form-urlencoded',
    },*/
    //可以是json字符串
    // body: JSON.stringify({
    // name: "why",
    // age: 18
    // })
    })
  • 具体信息查看mdn文档

Fetch的数据响应主要分为两个阶段

  • 阶段一:当服务器返回了响应(response)

    fetch 返回的 promise

    • fetch 无法建立一个 HTTP 请求,例如网络问题,亦或是请求的网址不存在,那么 promise 就会 reject
    • 异常的 HTTP 状态,例如 404 或 500,不会导致出现 error
  • 第二阶段,为了获取 response body,我们需要使用一个其他的方法调用

    • response.text() —— 读取 response,并以文本形式返回 response;
    • response.json() —— 将 response 解析为 JSON;
  • 获取response 的 HTTP 状态

    • tatus:HTTP 状态码,例如 200;
    • ok:布尔值,如果 HTTP 状态码为 200-299,则为 true;
  1. fetch() 返回的 Promise 不会被标记为 reject,即使响应的 HTTP 状态码是 404 或 500。

    就是fetch() 返回的一定是 promise.resolve()

  2. 如果响应的 HTTP 状态码不在 200 - 299 的范围内,则设置 resolve 返回值的 ok 属性为 false

    返回的then 参数对象会有一个ok属性

  3. 获取 JSON 的内容,我们需要使用 json() 方法(该方法返回一个将响应 body 解析成 JSON 的 promise)

  4. 获取文本,使用的 text() 方法 同样返回一个将响应 body 解析成 JSON 的 promise

//fetch的基本使用
const url = "http://123.207.32.32:8000/home/multidata"
fetch(url).then(data => {
//返回 文本类型
console.log(data.text().then(res => console.log(res)))
//返回 json 格式
// data.json().then(res => console.log(res))
}).catch(err => console.log(err))
//使用异步函数
async function foo(url) {
const data = await fetch(url);
const res = data.json()
return res
}
foo(url).then(res => {
console.log(res)
}).catch(err => {
console.log(err)
})

配置查mdn文档就好了

async function getData() {
const data = await fetch(postUrl, {
method:"post",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(obj)
})
return data.json()
}
getData().then(res => console.log(res))
  • FormData 对象,以 multipart/form-data 形式发送数据
<body>
<input type="file" class="inp">
<button class="btn">上传</button>
</body>
<script>
const url = "http://123.207.32.32:1888/02_param/upload"
const btnEl = document.querySelector(".btn")
const inpEl = document.querySelector(".inp")
btnEl.addEventListener("click", function() {
const formdata = new FormData()
formdata.append("above",inpEl.files[0])
fetch(url, {
method: "post",
body: formdata
}).then(res => res.json()).then(res => console.log(res))
})
</script>
  1. 所有的方法都在 xhr 对象里
  2. 可以取消发送请求,设置timeout
  3. 兼容性好
  1. 返回的是promise 基于标准的promise实现的。可以避免回调地狱
  2. 不会接受跨域 cookies;
  3. 默认fetch 不会发送 cookies
  4. 有些浏览器可能不支持。
  5. 不支持超时设置超时事件
  6. 没有办法检测请求进度 XHR可以

如果服务器返回的 response 头包含 “Access-Control-Allow-Origin:*”或者 *为与请求源相同的地址。即服务器支持浏览器跨域访问。若不包含需求修改服务器端,与浏览器端请求。

使用fetch 发起跨域请求时,CORS(跨域资源共享Cross-origin resource sharing)

Section titled “使用fetch 发起跨域请求时,CORS(跨域资源共享Cross-origin resource sharing)”

如果服务器不支持CORS,fetch提供了三种模式,其中no-cors可以继续访问服务器

fetch的mode配置项有3个值,如下:

  1. same-origin:该模式是不允许跨域的,它需要遵守同源策略,否则浏览器会返回一个error告知不能跨域;其对应的response type为basic。

  2. cors: 该模式支持跨域请求,顾名思义它是以CORS的形式跨域;当然该模式也可以同域请求不需要后端额外的CORS支持;其对应的response type为cors。

  3. no-cors: 该模式用于跨域请求但是服务器不带CORS响应头,也就是服务端不支持CORS;这也是fetch的特殊跨域请求方式;其对应的response type为opaque。

    针对跨域请求,cors模式是常见跨域请求实现,但是fetch自带的no-cors跨域请求模式则较为陌生,该模式有一个比较明显的特点:

    该模式允许浏览器发送本次跨域请求,但是不能访问响应返回的内容,这也是其response type为opaque透明的原因。

  • 注意: cors 支持 三种content-type 不支持 application/json
    1. application/x-www-form-urlencoded
    2. multipart/form-data
    3. text/plain

为了能将请求 text/plain的body 解析为json对象,可以参考

http://stackoverflow.com/questions/12345166/how-to-force-parse-request-body-as-plain-text-instead-of-json-in-express

service/request/config.ts
let BASE_URL = ''
const TIME_OUT = 10000
if (process.env.NODE_ENV === 'development') {
  BASE_URL = '开发环境ip'
} else if (process.env.NODE_ENV === 'production') {
  BASE_URL = '生产环境ip'
} else {
  BASE_URL = '测试环境ip'
}
export { BASE_URL, TIME_OUT }

为什么要用class呢,避免网路请求框架修改,这样只用改class就可以了

/service/request/index.ts
import axios from 'axios'
import type { AxiosInstance } from 'axios'
import type { HYRequestInterceptors, HYRequestConfig } from './type'
class YRequest {
  instance: AxiosInstance
  interceptors?: HYRequestInterceptors
  constructor(config: HYRequestConfig) {
    //创建axios实例
    this.instance = axios.create(config)
    //保存基本信息
    this.interceptors = config.interceptors
    //使用拦截器
    //从config钟取出的拦截器是对应的实例的拦截器
    this.instance.interceptors.request.use(
      this.interceptors?.requestInterceptor,
      this.interceptors?.requestInterceptorCatch
    )
    this.instance.interceptors.response.use(
      this.interceptors?.responseInterceptor,
      this.interceptors?.requestInterceptorCatch
    )
    //所有的实例都有的拦截器
    this.instance.interceptors.request.use(
      (config) => {
        console.log('所有的实例都有的拦截器: 请求拦截成功')
        return config
      },
      (err) => {
        console.log('所有的实例都有的拦截器: 请求拦截失败')
        return err
      }
    )
    this.instance.interceptors.response.use(
      (res) => {
        console.log('所有的实例都有的拦截器: 响应拦截成功')
        return res.data
      },
      (err) => {
        console.log('所有的实例都有的拦截器: 响应拦截失败')
        //例子:判断不同httpErrorCode显示不同错误信息
        if (err.response.status === 404) {
          console.log('404错误~')
        }
        return err
      }
    )
  }
  request<T>(config: HYRequestConfig<T>): Promise<T> {
    return new Promise((resolve, reject) => {
      //单个请求拦截config的处理
      if (config.interceptors?.requestInterceptor) {
// 拦截之后会返回新的配置,这个要自定义
        config = config.interceptors.requestInterceptor(config)
      }
      this.instance.request<any, T>(config).then((res) => {
          //单个请求响应拦截
          if (config.interceptors?.responseInterceptor) {
            res = config.interceptors.responseInterceptor(res)
          }
          //将结果返回出去
          resolve(res)
        })
        .catch((err) => {
          reject(err)
          return err
        })
    })
  }
  get<T>(config: HYRequestConfig<T>): Promise<T> {
    return this.request<T>({ ...config, method: 'GET' })
  }
  post<T>(config: HYRequestConfig<T>): Promise<T> {
    return this.request<T>({ ...config, method: 'POST' })
  }
  delete<T>(config: HYRequestConfig<T>): Promise<T> {
    return this.request<T>({ ...config, method: 'DELETE' })
  }
  patch<T>(config: HYRequestConfig<T>): Promise<T> {
    return this.request<T>({ ...config, method: 'PATCH' })
  }
}
export default HYRequest
/service/request/type.ts
import type { AxiosRequestConfig, AxiosResponse } from 'axios'
export interface HYRequestInterceptors<T = AxiosResponse> {
  requestInterceptor?: (config: AxiosRequestConfig) => AxiosRequestConfig
  requestInterceptorCatch?: (error: any) => any
  responseInterceptor?: (res: T) => T
  responseInterceptorCatch?: (error: any) => any
}
export interface HYRequestConfig<T = AxiosResponse> extends AxiosRequestConfig {
  interceptors?: HYRequestInterceptors<T>
}
///index.ts文件
import HYRequest from './request'
import { BASE_URL, TIME_OUT } from './request/config'
const hyRequest = new HYRequest({
  baseURL: BASE_URL,
  timeout: TIME_OUT,
  interceptors: {
    requestInterceptor: (config) => {
      const token = ''
// 这里看情况加一个是否需要认证,其实后端直接去掉就可以,除非是涉及到前端逻辑的情况下
      if (token) {
        config.headers!.Authorization = token
      }
      console.log('请求成功拦截')
      return config
    },
    requestInterceptorCatch: (err) => {
      console.log('请求失败拦截')
      return err
    },
    responseInterceptor: (config) => {
      console.log('响应成功拦截')
      return config
    },
    responseInterceptorCatch: (err) => {
      console.log('响应失败拦截')
      return err
    }
  }
})
export default hyRequest