底层揭秘:从前端的角度彻底理解http协议的请求方式

112 阅读7分钟

开始之前先抛出一个疑问

1.GET、POST、DELETE、PUT这些http请求方法之间有什么区别。为什么会有区别

准备的工具

1.Visual Studio code 编辑器

2.Visual Studio code的插件:rest Client

image.png

3.node服务器

//运行之前先安装下面这些依赖
npm install koa @koa/router koa-body @koa/cors koa-static

//使用koa构建了一个测试服务器。
const Koa = require('koa');
const Router = require('@koa/router');
const cors = require('@koa/cors');
const static = require('koa-static'); // 新增静态资源中间件
const path = require('path');
const { koaBody } = require('koa-body')
const app = new Koa();
const router = new Router();
// 新增配置:托管静态资源目录
app.use(static(path.join(__dirname, 'public')));
// 中间件配置
app.use(cors());
app.use(koaBody({
    jsonLimit: '1mb',
    multipart: true, // 启用文件上传支持
    formidable: {
        uploadDir: path.join(__dirname, 'public/uploads'), // 上传目录
        keepExtensions: true, // 保留文件扩展名
        maxFileSize: 10 * 1024 * 1024, // 最大文件大小10MB
        onFileBegin: (name, file) => {
            // 文件名规范化(防止中文乱码)
            file.newFilename = `${Date.now()}-${file.originalFilename}`;
            file.filepath = path.join(__dirname, 'public/uploads', file.newFilename);
        }
    },
    parsedMethods: ['POST', 'PUT', 'PATCH', 'GET']
}));

// RESTful 路由
router.get('/users', (ctx) => {

    const { name, age } = ctx.request.query;

    ctx.body = {
        name,
        age
    }
});
router.post('/users', async (ctx) => {
    const { name, age } = ctx.request.query;
    ctx.body = {
        name,
        age
    }
});

router.get('/info', (ctx) => {
    const { name, age } = ctx.request.body;

    ctx.body = {
        name,
        age,
        url: ctx.request.url,
        method: ctx.request.method,
        ContentType: ctx.request.headers['content-type'],

    }
});

router.post('/info', async (ctx) => {
    const { name, age } = ctx.request.body;

    ctx.body = {
        name,
        age,
        url: ctx.request.url,
        method: ctx.request.method,
        ContentType: ctx.request.headers['content-type'],

    }
});

//post文件上传
router.post('/upload', async (ctx) => {
    const files = ctx.request.files; // 接收的所有文件
    const { name, age } = ctx.request.body;
    const uploadedUrls = [];
    // 多文件遍历处理
    for (const fieldName in files) {
        const fileArr = Array.isArray(files[fieldName]) ?
            files[fieldName] :
            [files[fieldName]];

        fileArr.forEach(file => {
            uploadedUrls.push(`${ctx.origin}/uploads/${file.newFilename}`);
        });
    }

    ctx.body = {
        name,
        age,
        method: ctx.request.method,
        ContentType: ctx.request.headers['content-type'],
        urls: uploadedUrls[0]
    };
});

router.get('/upload', async (ctx) => {
    const files = ctx.request.files; // 接收的所有文件
    const { name, age } = ctx.request.body;
    const uploadedUrls = [];
    // 多文件遍历处理
    for (const fieldName in files) {
        const fileArr = Array.isArray(files[fieldName]) ?
            files[fieldName] :
            [files[fieldName]];

        fileArr.forEach(file => {
            uploadedUrls.push(`${ctx.origin}/uploads/${file.newFilename}`);
        });
    }

    ctx.body = {
        name,
        age,
        method: ctx.request.method,
        ContentType: ctx.request.headers['content-type'],
        urls: uploadedUrls[0]
    };
});


// 注册路由
app.use(router.routes());
app.use(router.allowedMethods());
// 启动服务
const PORT = 3000;
app.listen(PORT, () => {
    console.log(`Koa server running on http://localhost:${PORT}`);
});


工程目录

image.png

传递消息的模式

image.png

HTTP使用了一种极为简单的消息传递模式【请求-响应】模式

发起请求的称之为客户端,接收请求并完成响应的称之为服务器

【请求-响应】完成后,一次交互结束

传递消息的格式

image.png

HTTP的消息各分是一种纯文本的格式,文本分为三部分

1 起始行(请求行、响应行)
2 头部字段(请求体、响应体)
3
4 消息体(请求体、响应体)

注意:HTTP协议的规范。根据RFC 7230,HTTP消息的结构是起始行、头部字段、空行,然后是消息体,每一部分最后结尾都要换行。在window中,换行符为/r/n。mac系统中,换行符为/n

请求端

1.安装rest client插件后
2.启动服务器
3.创建一个 http后缀的文件,例如:test.http

image.png

理解(很重要):
 第一行:请求行
  第一个属性:请求方法--->GET
  第二个属性:请求路径---> /user
  第三个属性:请求协议和版本号---> HTTP/1.1
 第二行:请求头(这里可以写很多参数,例如Host、content-type等等。一个属性一行)
  Host属性:代表连接的源,域名和ip都可以---> localhost:3000
  Content-Type属性:代表传输过去的数据格式---> application/json
 最后就是请求体,注意:请求头和请求体中间一定要空一行(两个换行符)

在这个插件中,我们不需要手动编写换行符,只需要按键盘的Enter键,换行即可。

path传输

在HTTP协议中,请求方法仅有语义的区别,只是表达了这次请求的[愿望]。例如GET请求所完成的所有事情,POST请求也能完成,POST请求能完成的,GET请求也能完成。

例如:我使用GET和POST请求,通过path传输数据
左边是客户端
右边是服务端
中间是响应的结果

1. GET请求 通过path传输name和age

image.png

2. POST请求 通过path传输name和age

image.png

application/json传输

如果有请求体,必须配置相应的请求的方法,才能解析请求体的参数,以下是koa-body中间件的配置

image.png

同理,GET请求也可以通过请求体传输数据

1. GET请求 发送json格式数据

image.png

2. POST请求 发送json格式数据

image.png

注意:浏览器中无法使用GET请求携带请求体发送请求,浏览做了限制。通过apifox也可以进行模拟。

application/x-www-form-urlencoded格式传输

1. GET请求:发送 application/x-www-form-urlencoded 格式数据

image.png

2.POST请求:发送 application/x-www-form-urlencoded 格式数据

image.png

文件传输multipart/form-data

multipart/form-data 格式主要用于文件传输(推荐)

image.png

1. POST请求:传输文件

image.png

2. GET请求:传输文件

image.png

拓展:其实application/json和application/x-www-form-urlencoded两种格式也可以进行文件传输。不过需要把文件的二进制数据转换成base64,然后传输base64。因为在本地把文件二进制数据转换成base64会非常耗时,而且转换之后的数据量会大于原始二进制的数据量,网络通信的数据会变多,更加消耗带宽(不推荐)。

图解:左边为json格式,右边为x-www-form-urlencoded格式

image.png

响应端

image.png

第一行第一个属性表示协议和版本:HTTP的协议,版本是1.1
第一行第二个和第三个属性表示响应码和响应消息,常见的有:

分类分类描述
1**信息,服务器收到请求,需要请求者继续执行操作
2**成功,操作被成功接收并处理
3**重定向,需要进一步的操作以完成请求
4**客户端错误,请求包含语法错误或无法完成请求
5**服务器错误,服务器在处理请求的过程中发生了错误
状态码推荐消息说明
200OK标准成功响应
301Moved Permanently永久重定向
201Created资源创建成功
404Not Found资源未找到
500Internal Error服务器内部错误

从空行到响应行之间的全部都是响应头。空行后面的全部都是响应体

最后总结

所有HTTP请求方法(GET/POST/PUT等)在传输过程中均被规范为统一报文结构:由请求行、报文头、空行、载荷体四部分构成的文本流。请求端将结构化数据(如JSON对象)通过序列化转换为字符串,并按协议规范拼接元数据:请求行声明方法与目标资源,报文头设定编码类型与内容长度,空行作为分隔符,最终以UTF-8编码的二进制字节流通过TCP连接传输。这个过程实质上是将高级数据形态降维到文本协议的平面表达。

通俗一点:无论上层写的是啥,最终都会转成字符串(文本),使用流的形式进行传输。

GET、POST、DELETE、PUT这些http请求方法之间有什么区别。为什么会有区别。

解析:这个问题需要从不同的层面去分析。在http层面其实区别不大,区别逐渐增大是由上层服务自行实现的,比如一些框架呀,一些请求库呀。如果自己开发一个客户端和一个服务端,get和post完全可以相互换着用。只要客户端可以发送,服务端认,完全是ok的。

答案从前端角度出发,从两个层面进行解答:

从http层面去看:
所有的请求方式除去语义的区别,没有任何区别。比如GET用于获取资源,POST用于提交数据等,但技术上这些方法本身并没有限制,服务器可以自由处理。比如,用GET提交数据或者用DELETE创建资源都是可能的,不过这不推荐,因为违背了RESTful的设计原则。

从浏览器层面去看:区别就更细化了,例如:
1.get请求可以直接输入在浏览器进行获取数据,post不行。
2.get请求可以收藏为书签,会有历史记录,post不行。
3.get请求不能携带请求体,post可以。
4.相对而言,post请求比get请求安全。
5.页面刷新get请求会直接重新请求,post会提示重新提交表单
6.get有长度限制,post没有

还有很多就不一一列举了哈哈哈哈。谈到浏览器层面,好像八股文啊。。。。。。