面经百遍,不如玩他一遍!get/post/options

138 阅读6分钟

面试中几乎必考题get与post的区别,网上都是千篇一律的回答,但是每次说出来心里都没底,生怕问点啥深入的,反正都不会,于是决定自己玩一玩这东西。

搭建前后端

起一个express服务,同时为了方便操作,把html文件返回到浏览器。

const express = require("express");
const app = express();
const port = 3000;

app.get("/", (req, res) => {
  res.sendFile(__dirname + "/httpclient.html");
});
app.get("/get", (req, res) => {
  res.send();
});
app.post("/post", (req, res) => {
  res.send();
});

app.listen(port, () => {
  console.log("runing in port" + port);
});

html页面里面简单放两个操作按钮,然后定义一下请求操作

<!--httpclient.html-->
<!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>
  <div>
    <button id="btn1">get请求</button>
  </div>
  <div>
    <button id="btn2">post请求</button>
  </div>
</body>
<script>
  document.querySelector('#btn1').onclick = () => {
    const xhr = new XMLHttpRequest()
    xhr.open('get', 'get')
    xhr.send()
    xhr.onreadystatechange = () => {
      if(xhr.readyState === 4 && xhr.status === 200){
        console.log(xhr.responseText)
      }
    }
  }
  document.querySelector('#btn2').onclick = () => {
    const xhr = new XMLHttpRequest()
    xhr.open('post', 'post')
    xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded')
    xhr.send()
    xhr.onreadystatechange = () => {
      if(xhr.readyState === 4 && xhr.status === 200){
        console.log(xhr.responseText)
      }
    }
  }
</script>
</html>

此时的页面

image.png

get/post的大小

啥也不返回的时候

  • 分别点击两个按钮发送最简单的get和post请求,发现两个请求的大小是一样的,都是145B

image.png

此时的get和post请求的响应头都是:

image.png 但是get的请求头为

image.png post的请求头为:

image.png post请求的请求头比get的请求头多了三个字段: Content-Length, Content-type, Origin

发送空消息的时候

  • 接着,我在get和post的接口中都返回一个空字符串: res.send(''),两个请求的大小都变成了226B

image.png 这时候的get和post的响应头都多了一点东西:

image.png 多了一个Content-Type,一个Etag

但是请求头没有变

这时候再次点击get和post请求,get命中了浏览器缓存返回的304,post还是发的请求:

image.png 这个时候再看看get请求的请求头和响应头:

image.png 请求头里面多了一个If-None-Math,所以命中缓存

设置请求头的时候

  • 再接着,我们只在post请求里面,响应头里面设置cookie:

res.cookie("name",'zhangsan',{maxAge: 900000, httpOnly: true});

  • 然后先点击get请求,再点击post请求

image.png

这时候get请求还是226B,post请求的响应头里面多了一个Set-Cookie字段,大小变为了323B,再次点击get和post请求,get请求直接304,post请求这次的请求头里面多了一个cookie

总结: 由此可见,get请求和post请求在大小上是一样的,大小和返回的数据,响应头的大小有关系,和get post本身没有关系。

但是我们本来只想探究一下get post的大小问题,但是在对这几个请求头响应头的对比分析中,发现一个小秘密,那就是post请求不会被浏览器缓存命中,没有缓存这一说,但是get请求,第一次发过去的响应头里面就会带上ETag,下一次就会走协商缓存,命中的话就直接返回304,而不是200

这里有时候可能会遇到一个Chrome的小bug,那就是,明明命中了协商缓存,但是返回的还是200,其实返回的应该是304

浏览器缓存

前面不小心遇到了浏览器缓存的事情,可以看见,get请求会被浏览器主动cache,那么接下来,我们就对着强缓存和协商缓存一探究竟

强缓存的触发

我们先在请求头里面都设置一下Cache-Control: max-age=3600,然后发送两次请求

image.png

并没有触发强缓存

那么我们看一下在响应头里面设置

image.png

可以发现,第二次的get请求直接走的disk cache,但是post没有走缓存

那么请求头对是否走缓存真的一点都控制不了吗,虽然他控制不了走缓存,但是他可以控制不走缓存

我们在请求头里面设置'Cache-Control':'no-cache',v这时,虽然响应头设置了强缓存,但是这时候的get请求就永远都不会命中强缓存,而是发送正常的请求

协商缓存的触发

看过了强缓存,那我们再看一下协商缓存 这里不贴图了,发现无论是在请求头还是响应头里面设置,都无法触发post的缓存

options

对于options请求,一直不太清楚,这次刚好一并弄清楚 首先看面经,知道触发options请求的条件有

  • 必须是跨域请求
  • 使用了PUT/DELETE/CONNECT/OPTIONS/TRACE/PATCH任一方法
  • 自定义了请求头
  • Content-Type的值不等于application/x-www-form-urlencoded、multipart/form-data、text/plain options请求可以主动发,能够检测服务器支持哪些HTTP方法,但是遇到以上情况时,浏览器会自动发一个预检请求,获知服务端是否允许改请求。

没有跨域的时候

首先,来验证一下不跨域行不行:


  1. httpclient.html中加一个发options请求的按钮
document.querySelector('#btn3').onclick = () => {
    const xhr = new XMLHttpRequest()
    // xhr.open('options', 'http://localhost:8000/options')
    xhr.open('options', 'options')
    xhr.send()
    xhr.onreadystatechange = () => {
      if(xhr.readyState === 4 && xhr.status === 200){
        console.log(xhr.responseText)
      }
    }
  }
  1. httpserve.js中新加一个options的接口
app.get("/options", (req, res) => {
  res.send('');
});
  1. 浏览器工具这里不能只显示fetch/chr,要切换到全部,不然就算有options也看不见

image.png

  1. 依次点击三个请求

get和post请求和之前一样,options请求我们写的虽然是返回的空字符串,但是实际上返回的是支持的http方法

image.png

这里的options是有返回的,也有大小

image.png

options的请求头和get也是一样的,但是响应头里面多了一个allow: get, head

可见不跨域的时候无法触发自动的options请求

简单跨域的时候

  1. 新建一个serve2.js, 里面开启一个8000端口的服务
  2. 利用cors中间件处理一下跨域
  3. 把页面三个按钮对应请求的地址都改为断口为8000的接口
  4. 依次发送请求 结果如下:

image.png

get和post没有引起options请求,这是因为我们虽然跨域了,但是没有符合上面提到的那些要求,所以是简单跨域,不会引起额外的options预检,但是下面的options本身报了一个错误,同时引发了一次options预检,同时注意到,两个options请求的类型是不一样的,一个是XHR,一个是preflight。

看一下options请求的响应和信息

image.png

返回的204,204的意思是请求成功执行,但是没有数据。 响应头里面多了一个Access-Content-Allow-Methods: GET,HEAD,PUT,PATCH,POST,DELETE

这个字段的意思是: 响应首部 Access-Control-Allow-Methods 在对 preflight request.(预检请求)的应答中明确了客户端所要访问的资源允许使用的方法或方法列表。

可以看见里面并没有options方法,所以会报错。

复杂跨域请求

下面我们触发一下复杂跨域请求

  • get请求里面设置额外的请求头xhr.setRequestHeader('token', 'abc')
  • post请求里面设置Content-Type为application/json 再次尝试:

image.png 可以看见get,post都触发了一次options请求。options报错的原因也和简单跨域时候一样,不支持这个方法。

总结

经过这么一折腾,get,post的区别,以及options的相关就更熟悉了,比死记硬背效果要好,看别人的文章背下来的,终究不如自己试验一下来的清楚!!!

参考文章

什么时候会发送options请求