前端的你真的了解请求方式和数据格式吗

10,176 阅读19分钟

我在公司请求方式一般都用的get和post,数据格式对应的application/x-www-form-urlencoded和application/json,等一下,我说的这两种数据格式你不会不知道是什么样子吧,可以理解,因为没有深入研究过,没有很深刻的印象。

举个例子,post请求,application/json格式,一般我们会这么写:

  getBankList: (data) => {    
    return api.post(`/blank/feng/`, data).then(      
            (res) => {        
                if (res.status === 0) {          
                    return res.data;        
                } else {          
                    throw openNotification(res.status, res.msg);        
                }      
         },(err) => {        
            throw err;      
            }    
         );  
    }

那么,如果是delete请求,application/json格式,怎么写?这么写不对哦。

  getBankList: (data) => {    
      return api.delete(`/blank/feng/`, data).then(      
        (res) => {        
            if (res.status === 0) {          
                return res.data;        
            } else {          
                throw openNotification(res.status, res.msg);        
            }      
        }, (err) => {           
            throw err;      
        }    
      );  
}

要这么写:

  deleteTicket: (data) => {    
    return api.delete(`/blank/feng/`, { data: data }).then(     
         (res) => {
            return res;      
        },(error) => {
            throw error;      
        }    
       );  
  },

1. 数据格式

这个数据格式一般我们会在HTTP请求中Content-Type(互联网媒体类型)里面看到,表示请求和响应中的媒体类型信息。它用来告诉服务端如何处理请求的数据,以及告诉客户端(一般是浏览器)如何解析响应的数据,比如显示图片,解析并展示html等等。

要注意一点,它只告诉了后端应该用什么类型来解析数据,但是后端并不知道我传了个什么东西给他。当然,传错类型直接会报错。

1.1 application/x-www-form-urlencoded

数据被编码为key/value格式发送到服务器(表单默认的提交数据的格式)。

一般post很常用这种方式,但是我一般是在get中使用。

  getTicketInfo: (params) => {    
    return api.get(`/blank/feng?cinemaId=${params}`).then(      
        (res) => {        
            return res;      
        },(error) => {        
            throw error;      
        }    
    );  
  }

可能我们传了参数

title=test&sub[]=1&sub[]=2&sub[]=3

到请求的时候会发现变成了

title=test&sub%5B%5D=1&sub%5B%5D=2&sub%5B%5D=3

这是因为当我们发送请求的时候会进行编码。

x-www-form-urlencoded的编码规则就是url编码规则,规则比较多,我列了几个比较常见的规则:

1.字符"a"-"z","A"-"Z","0"-"9",".","-","*",和"_" 都不会被编码(中文会被编码);

2.将空格转换为加号 (+) ;

3.将非文本内容转换成"%xy"的形式,xy是两位16进制的数值;

4.在每个 name=value 对之间放置 & 符号。

在线编码解码www.jsons.cn/urlencode/

1.2 application/json

application/json 这个 Content-Type 作为响应头大家肯定不陌生。实际上,现在越来越多的人把它作为请求头,用来告诉服务端消息主体是序列化后的 JSON 字符串。

由于 JSON 规范的流行,除了低版本 IE 之外的各大浏览器都原生支持 JSON.stringify,服务端语言也都有处理 JSON 的函数,使用 JSON 不会遇上什么麻烦。

前端给后端Json,后端给前端Json,默契十足。

我们传数据一般会这么写:

  addOperator: (data) => {
    return api.post(`/blcank/feng`, data).then(
      (res) => {
        if (res.status === 0) {
          return res.data;
        } else {
          throw openNotification(res.status, res.msg);
        }
      },
      (err) => {
        throw err;
      }
    );
  }
---------------------------------调用请求传的参数--------------------------------------------
data={
  "name": "feng",
   "old":  23,
   "帅不帅": Yes,
}

发送请求时会发现,参数会被直接放到请求实体里,没有进行任何处理:

application/json对比application/x-www-form-urlencoded来说,Json格式支持比键值对复杂得多的结构化数据,非常适合 RESTful 的接口。

RESTFUL是一种网络应用程序的设计风格和开发方式

1.3 multipart/form-data

与application/x-www-form-urlencoded和application/json不同,这是一个多部分多媒体类型。

我们平常上传图片时经常会用到这种格式。

首先生成了一个 boundary 用于分割不同的字段,在请求实体里每个参数以------boundary开始,然后是附加信息和参数名,然后是空行,最后是参数内容。多个参数将会有多个boundary块。

可以上传键值对,也可以上传文件。当上传的字段是文件时,会有Content-Type来表名文件类型;content-disposition,用来说明字段的一些信息。

由于有boundary隔离,所以multipart/form-data既可以上传文件,也可以上传键值对,它采用了键值对的方式,所以可以上传多个文件。

可以把boundary理解为网格,数据装在格子中,有很多格子可以装载不同的数据。

1.4 text/xml

这个数据格式比较麻烦,是一种使用HTTP作为传输协议,XML作为编码方式的远程调用规范。

它发送请求是这个样子的:

POST http://www.example.com HTTP/1.1 
Content-Type: text/xml

<?xml version="1.0"?>
<methodCall>
    <methodName>examples.getStateName</methodName>
    <params>
        <param>
            <value><i4>41</i4></value>
        </param>
    </params>
</methodCall>

JavaScript 中,也有现成的库支持以这种方式进行数据交互,能很好的支持这个数据格式。但是 XML 结构还是过于臃肿,一般场景用 JSON 会更灵活方便。

2. 请求方式

HTTP遵循请求(Request)/应答(Response)模型。Web浏览器向Web服务器发送请求,Web服务器处理请求并返回适当的应答。所有HTTP连接都被构造成一套请求和应答。

2.1 GET

这是我们经常会用到的请求方式,根据 HTTP 规范,GET 用于信息获取,请求指定的资源。

而且应该是安全的幂等的。所谓安全的意味着该操作用于获取信息而非修改信息。换句话说,GET 请求一般不应产生副作用。幂等的意味着对同一 URL 的多个请求应该返回同样的结果。

还有一点,GET请求是可缓存的,当我们使用GET请求方法时,请求可能会被浏览器缓存。

当发送请求时,GET 方法向 URL 添加数据;URL 的长度是受限制的(URL 的最大长度是 2048 个字符),所以说GET方法不能传过长的数据。而且,GET方法发送的数据只允许 ASCII 字符。

我说ASCII字符你可能不太理解,简单来说,数字,大写字母,小写字母,符号,空格都属于ASCII字符,中文不是。但是为什么我们使用GET请求可以发送中文?

因为GET有自己的编码规则: 如果数据是英文字母/数字,原样发送,如果是空格,转换为+,如果是中文/其他字符,则直接把字符串用BASE64加密,得出如:

%E4%BD%A0%E5%A5%BD

其中%XX中的XX为该符号以16进制表示的ASCII。

2.2 POST

我们最常用的请求方法应该就是POST和GET了,面试题里也经常会问到POST和GET的区别。

POST用于向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。

上面说GET方法时安全的和幂等的,这个安全的我要从另一个方面来反驳它了,GET的安全说的是操作用于获取信息而非修改信息,但是GET方法中的参数时放在URL中的,这样的话很容易暴露信息。

而POST的安全不仅仅是参数不在URL中,发送的请求也不会被保存在浏览器历史记录中。还有就是CSRF,一种网络黑客技术,使用POST可以极大的避免被攻击。

CSRF(Cross Site Request Forgery),中文是跨站点请求伪造。CSRF攻击者在用户已经登录目标网站之后,诱使用户访问一个攻击页面,利用目标网站对用户的信任,以用户身份在攻击页面对目标网站发起伪造用户操作的请求,达到攻击目的。

菜鸟中GET和POST的对比:

GET

POST

目的

信息获取

提交数据去chu

书签

可收藏为书签

不可收藏为书签

缓存

能被缓存

不能缓存

编码类型

application/x-www-form-urlencoded

application/x-www-form-urlencoded 或 multipart/form-data。为二进制数据使用多重编码。

历史

参数保留在浏览器历史中。

参数不会保存在浏览器历史中。

对数据长度的限制

是的。当发送数据时,GET 方法向 URL 添加数据;URL 的长度是受限制的(URL 的最大长度是 2048 个字符)。

无限制。

对数据类型的限制

只允许 ASCII 字符。

没有限制。也允许二进制数据。

安全性

与 POST 相比,GET 的安全性较差,因为所发送的数据是 URL 的一部分。

在发送密码或其他敏感信息时绝不要使用 GET !

POST 比 GET 更安全,因为参数不会被保存在浏览器历史或 web 服务器日志中。

可见性

数据在 URL 中对所有人都是可见的。

数据不会显示在 URL 中。

然后,我要纠正一个观点,GET中说GET 方法的URL 的长度是受限制的(URL 的最大长度是 2048 个字符),菜鸟中的对比也提到了数据长度的限制,虽然字面意思上没有错误,但是理解一定要正确。

GET方法提交的url参数数据大小没有限制,在http协议中没有对url长度进行限制,这个限制是特定的浏览器及服务器对它的限制。

所以说,GET本身是没有数据长度限制的,是浏览器和服务器对它的限制。

2.3 CONNECT

在 HTTP 协议中,CONNECT 方法可以开启一个客户端与所请求资源之间的双向沟通的通道。它可以用来创建隧道(tunnel)。

这个方法不是很常用,它主要是用来做什么的呢,很重要的功能之一,翻墙。

CONNECT将服务器作为代理,让服务器代替用户去访问其他网页,之后将数据返回给用户。

2.4 DELETE

DELETE 请求方法用于删除指定的资源。

DELETE是一个很朴素的方法,没有请求正文,没有返回主体,也不会缓存,很单纯的告诉你我不需要她了,我要删了她,你只要告诉我删没删成功就行。

如果 DELETE 方法成功执行,那么可能会有以下几种状态码:

  • 状态码 202 (Accepted) 表示请求的操作可能会成功执行,但是尚未开始执行。
  • 状态码 204 (No Content) 表示操作已执行,但是无进一步的相关信息。
  • 状态码 200 (OK) 表示操作已执行,并且响应中提供了相关状态的描述信息。

2.5 HEAD

HEAD 请求资源的头部信息, 并且这些头部与 GET 方法请求时返回的一致。该请求方法的一个使用场景是在下载一个大文件前先获取其大小再决定是否要下载,,以此可以节约带宽资源。

简单的来说,HEAD与GET方法使用起来差不多,可以理解为nimi GET。当我们想要获取这个数据的信息比如它是否存在而不需要获取数据时就可以用到它。

2.6 OPTIONS

OPTIONS 用于获取目的资源所支持的通信选项。客户端可以对特定的 URL 使用 OPTIONS 方法,也可以对整站(通过将 URL 设置为“*”)使用该方法。

估计你没听明白它是做什么的。这个方法很有趣,它用于获取当前URL所支持的方法。若请求成功,则它会在HTTP头中包含一个名为“Allow”的头,值是所支持的方法,如“GET, POST”。

它有一个很大的用处:CORS 中的预检请求

CORS 中,可以使用 OPTIONS 方法发起一个预检请求,以检测实际请求是否可以被服务器所接受。预检请求报文中的 Access-Control-Request-Method 首部字段告知服务器实际请求所使用的 HTTP 方法;Access-Control-Request-Headers 首部字段告知服务器实际请求所携带的自定义首部字段。服务器基于从预检请求获得的信息来判断,是否接受接下来的实际请求。

2.7 PUT

PUT 请求方法使用请求中的负载创建或者替换目标资源。

简单的来说,PUT用于向服务器发送请求,如果URI不存在,则要求服务器根据请求创建资源,如果存在,服务器就接受请求内容,并修改URI资源的原始版本。

在我们平常开发的时候也会用到PUT,使用起来和POST差不多,都是向服务器发送数据,但它们之间有一个重要区别,PUT通常指定了资源的存放位置,而POST则没有,POST的数据存放位置由服务器自己决定。

2.8 PATCH

PATCH 用于对资源进行部分修改。

PATCH方法和PUT方法所请求的目标地址都是直接指向资源的。

PUT 方法已经被用来表示对资源进行整体覆盖, 而 POST 方法则没有对标准的补丁格式的提供支持。不同于 PUT 方法,而与 POST 方法类似,PATCH 方法是非幂等的,这就意味着连续多个的相同请求会产生不同的效果。

PATCH提供的实体则需要根据程序或其它协议的定义,解析后在服务器上执行,以此来修改服务器上的数据。

2.9 TRACE

TRACE 实现沿通向目标资源的路径的消息环回(loop-back)测试 ,提供了一种实用的 debug 机制

我的理解就是一种协议调试方法。

3. 响应报文

报文,一个很重要的东西,之前我对报文一直不是很上心。直到有一次接口给我返回了307,前后端Postman测试都没问题,但是就是请求不成功,最后是从报文中找到了原因。

报文可以给我们带来很多信息,对我们接口联调有一定的帮助,不能说很大的帮助,至少在联调中手足无措的时候,可以注意一下报文给我们的信息。

请求报文就算了,我们研究下响应报文。

客户端向服务端发送请求之后,服务器接收并处理客户端发过来的请求后正常情况下会返回一个HTTP的响应消息,这个就是响应报文

3.1 组成

HTTP响应由四个部分组成,分别是:状态行、消息报头、空行和响应正文。

3.2 响应头

响应头由键值对组成,每行一对,键和值用英文冒号 : 分隔。响应头域允许服务器传递不能放在状态行的附加信息,这些域主要描述服务器的信息和Request-URI进一步的信息。

这个也是我想说的报文中的重点,可以给我们带来很多信息的地方。

列出一些比较常见的响应头:

响应头

说明

Allow

服务器支持哪些请求方法(如GET、POST等)。

Content-Encoding

文档的编码(Encode)方法。只有在解码之后才可以得到Content-Type头指定的内容类型。利用gzip压缩文档能够显著地减少HTML文档的下载时间。Java的GZIPOutputStream可以很方便地进行gzip压缩,但只有Unix上的Netscape和Windows上的IE 4、IE 5才支持它。因此,Servlet应该通过查看Accept-Encoding头(即request.getHeader("Accept-Encoding"))检查浏览器是否支持gzip,为支持gzip的浏览器返回经gzip压缩的HTML页面,为其他浏览器返回普通页面。

Content-Length

表示内容长度。只有当浏览器使用持久HTTP连接时才需要这个数据。如果你想要利用持久连接的优势,可以把输出文档写入 ByteArrayOutputStream,完成后查看其大小,然后把该值放入Content-Length头,最后通过byteArrayStream.writeTo(response.getOutputStream()发送内容。

Content-Type

表示后面的文档属于什么MIME类型。Servlet默认为text/plain,但通常需要显式地指定为text/html。由于经常要设置Content-Type,因此HttpServletResponse提供了一个专用的方法setContentType。

Date

当前的GMT时间。你可以用setDateHeader来设置这个头以避免转换时间格式的麻烦。

Expires

应该在什么时候认为文档已经过期,从而不再缓存它?

Last-Modified

文档的最后改动时间。客户可以通过If-Modified-Since请求头提供一个日期,该请求将被视为一个条件GET,只有改动时间迟于指定时间的文档才会返回,否则返回一个304(Not Modified)状态。Last-Modified也可用setDateHeader方法来设置。

Location

表示客户应当到哪里去提取文档。Location通常不是直接设置的,而是通过HttpServletResponse的sendRedirect方法,该方法同时设置状态代码为302。

Refresh

表示浏览器应该在多少时间之后刷新文档,以秒计。除了刷新当前文档之外,你还可以通过setHeader("Refresh", "5; URL=http://host/path")让浏览器读取指定的页面。
注意这种功能通常是通过设置HTML页面HEAD区的<META HTTP-EQUIV="Refresh" CONTENT="5;URL=http://host/path">实现,这是因为,自动刷新或重定向对于那些不能使用CGI或Servlet的HTML编写者十分重要。但是,对于Servlet来说,直接设置Refresh头更加方便。

注意Refresh的意义是"N秒之后刷新本页面或访问指定页面",而不是"每隔N秒刷新本页面或访问指定页面"。因此,连续刷新要求每次都发送一个Refresh头,而发送204状态代码则可以阻止浏览器继续刷新,不管是使用Refresh头还是<META HTTP-EQUIV="Refresh" ...>。

注意Refresh头不属于HTTP 1.1正式规范的一部分,而是一个扩展,但Netscape和IE都支持它。

Server

服务器名字。Servlet一般不设置这个值,而是由Web服务器自己设置。

Set-Cookie

设置和页面关联的Cookie。Servlet不应使用response.setHeader("Set-Cookie", ...),而是应使用HttpServletResponse提供的专用方法addCookie。参见下文有关Cookie设置的讨论。

WWW-Authenticate

客户应该在Authorization头中提供什么类型的授权信息?在包含401(Unauthorized)状态行的应答中这个头是必需的。例如,response.setHeader("WWW-Authenticate", "BASIC realm=\"executives\"")。
注意Servlet一般不进行这方面的处理,而是让Web服务器的专门机制来控制受密码保护页面的访问(例如.htaccess)。

4. 总结

以前学习HTTP时看的一本书《HTTP图解》,看完了之后一直问自己,我用到这些知识了吗?在工作的不断深入,和后端的不断磨合,感觉到HTTP仿佛是前后端通信的一个桥梁,更深入的了解HTTP才能更穷送的在桥梁上来去自如。