0
点赞
收藏
分享

微信扫一扫

【网络】HTTP协议详解

玉新行者 2022-09-20 阅读 240

😀大家好,我是白晨,一个不是很能熬夜😫,但是也想日更的人✈。如果喜欢这篇文章,点个赞👍,关注一下👀白晨吧!你的支持就是我最大的动力!💪💪💪

文章目录

📙前言


哟,大家好,我是白晨。距离上一次更新已经过了一段时间了,属实是当鸽子当惯了🤣。

上一篇文章我们全面讲解了网络中的基础知识(没看过上篇文章的的可以先去看上一篇文章【网络】网络基础知识详解),这篇文章白晨准备从应用层中的一个及其重要的协议开始讲起,它就是——HTTP协议。HTTP协议由于简单、快速而成为了应用最广泛的web文档传递协议协议,在理解了HTTP协议的种种行为后,再自顶向下去学习传输层,网络层以及数据链路层将会好理解许多。


📗HTTP协议


🏡1. HTTP背景介绍



⛪2. HTTP知识预备


2.1 TCP/IP协议

在这里插入图片描述

上篇文章我们曾经讲过,TCP/IP模型将网络分为了四层,将不同的任务划分,每层只要完成自己的任务即可,完成了每层间的解耦,同时上层依赖下层的服务以实现自己的功能。HTTP是一个建立在TCP/IP协议族上的应用层协议,所以,要利用HTTP发送数据,就要通过上图过程。

我们用 HTTP 举例来说明,

  • 首先作为发送端的客户端在应用层(HTTP 协议)发出一个想看某个 Web 页面的 HTTP 请求。
  • 接着,为了传输方便,在传输层(TCP 协议)把从应用层处收到的数据(HTTP 请求报文)进行分割,并在各个报文上打上标记序号及端口号后转发给网络层。
  • 在网络层(IP 协议),增加作为通信目的地的 MAC 地址后转发给链路层。这样一来,发往网络的通信请求就准备齐全了。
  • 接收端的服务器在链路层接收到数据, 按序往上层发送,一直到应用层。当传输到应用层,才能算真正接收到由客户端发送过来的 HTTP。

在这里插入图片描述

2.2 URI 和 URL

2.3 DNS服务


🕋3. HTTP协议格式


请求报文

POST /data/index HTTP/1.1
Host: baichen.com
Connection: keep-alive
Content-Type: text/html; charset=utf8
Content-Length: 16

name=chen&age=37

请求报文的构成为:

在这里插入图片描述

由此,我们可以得到请求报文的格式为:

  • 请求行: 方法 + URL + 协议版本

  • 请求头部: 请求的属性, 冒号分割的键值对;每组属性之间使用\n分隔。

  • 空行:空行用于标识请求头部结束,请求正文开始。

  • 请求正文: 空行后面的内容都是请求正文,内容为应发送的数据。请求正文允许为空字符串。如果请求正文存在, 则在请求头部中会有一个Content-Length属性来标识请求正文的长度。

在这里插入图片描述

这里要解释一个问题,为什么前文讲解URL的结构这么复杂,但是在上面的请求报文中却好像只有目录结构,而没有协议、域名等部分?

响应报文

HTTP/1.1 200 OK
Date: Tue, 10 Jul 2012 06:50:15 GMT
Content-Length: 362
Content-Type: text/html

<html>
…

响应报文的构成为:

在这里插入图片描述

由此,我们可以得到响应报文的格式为:

  • 相应行: 协议版本 + 状态码 + 状态码描述
  • 响应头部: 请求的属性, 冒号分割的键值对;每组属性之间使用\n分隔。
  • 空行:空行用于标识响应头部结束,响应正文开始。
  • 响应正文: 空行后面的内容都是响应正文,内容为应发送的数据。响应正文允许为空字符串。如果响应正文存在,则在响应头部中会有一个Content-Length属性来标识响应正文的长度;如果服务器返回了一个html页面,那么html页面内容就是在响应正文中。

在这里插入图片描述


🕌4. HTTP的方法


  • HTTP支持的方法详见下表:
方法说明支持的HTTP协议版本
GET获取资源1.0、1.1
POST传输实体主体1.0、1.1
PUT传输文件1.0、1.1
HEAD获得报文首部1.0、1.1
DELETE删除文件1.0、1.1
OPTIONS询问支持方法1.1
TRACE追踪路径1.1
CONNECT要求用隧道协议连接代理1.1
LINK建立和资源之间的联系1.0
UNLINE断开连接关系1.0

LINK 和 UNLINK 方法已经被 HTTP/1.1 弃用,这两个方法只需了解即可。

GET:获取资源

请求报文:
GET / HTTP/1.1
Host: www.baichen.com
Connection: keep-alive
User-Agent: XXXXX

响应报文:
HTTP/1.1 200 OK
Content-Type: text/plain

hello, i'm from the futurn

上面就是一个经典的使用GET方法的例子,GET方法是一个使用非常频繁的方法,用于获取指定 URL 的资源,当我们访问一个网站的时候,第一次使用的方法基本都是GET,以获取网页信息。

POST:传输实体主体

POST / HTTP/1.1
Host: www.baichen.com
Content-Length: 1560(1560字节的数据)

PUT:传输文件

PUT / HTTP/1.1
Host: www.baichen.com
Content-Type: text/html
Content-Length: 1560(1560字节的数据)

HEAD:获得报文首部

HEAD /index.html HTTP/1.1
Host: www.baichen.com

DELETE:删除文件

DELETE /index.html HTTP/1.1
Host: www.baichen.com

OPTIONS:询问支持的方法

请求:
OPTIONS * HTTP/1.1
Host: www.baichen.com

返回:
HTTP/1.1 200 OK
Allow: GET, POST, HEAD, OPTIONS
(返回服务器支持的方法)

TRACE:追踪路径

请求:
TRACE / HTTP/1.1
Host: WWW.baichen.com
Max-Forwards: 2

返回:
HTTP/1.1 200 OK
Content-Type: message/http
Content-Length: 1024

TRACE / HTTP/1.1
Host: www.baichen.com
Max-Forwards: 2(返回响应包含请求内容)

CONNECT:要求用隧道协议连接代理

CONNECT方法使用格式:
CONNECT 代理服务器名:端口号 HTTP版本

eg.
CONNECT 101.58.2.1:8080 HTTP/1.1
Host: www.baichen.com

🛕5. HTTP的状态码


5.1 状态码分类


状态码类别原因短语
1XXInformational(信息性状态码)接收的请求正在处理
2XXSuccess(成功状态码)请求正常处理完毕
3XXRedirection(重定向状态码)需要进行附加操作以完成请求
4XXClient Error(客户端错误状态码)服务器无法处理请求
5XXServer Error(服务器错误状态码)服务器处理请求出错

RFC2016上规定的状态码有40余种,再加上WebDAV等的状态码,一共有60余种。但是,常用的状态码大概就14种,下面由我向大家介绍这14种状态码。


5.2 2XX 成功


200 OK

204 No Content

206 Partial Content


5.3 3XX 重定向


301 Moved Permanently

302 Found

303 See Other

:当 301、302、303 响应状态码返回时,几乎所有的浏览器都会把 POST 改成 GET,并删除请求报文内的主体,之后请求会自动再次发送。301、302 标准是禁止将 POST 方法改变成 GET 方法的,但实际使用时大家都会这么做。

304 Not Modified

307 Temporary Redirect


5.4 4XX 客户端错误


400 Bad Request

401 Unauthorized

403 Forbidden

404 Not Found


5.5 5XX服务器错误


500 Internal Server Error

503 Service Unavailable


状态码的本意是用来快速获取请求状态,但是在很多场景下,我们会发现状态码和出错的地方根本就对不上。归根到底,状态码并不是一种强制性的命令,而是一种建议式的反馈,很多浏览器对于同一状态码的处理可能都千差万别,比如,404 状态码在谷歌浏览器上,如果返回有实体主体,那么谷歌浏览器并不会理会 404 状态码,而是显示返回的实体主体的内容,但是404 状态码在Edge浏览器上就会强制执行 404 Not Fount。

虽然网络上的状态码鱼龙混杂,但是我们自己写的状态码一定要符合标准,这样有利于养成良好的代码习惯和排查错误。


🕍6. HTTP的首部


HTTP 首部字段分类

  • 请求报文

在这里插入图片描述

  • 响应报文

在这里插入图片描述

HTTP 首部字段结构

首部字段名: 字段值
eg.
Content-Type: text/plain, charset=utf-8  -- 报文主体的对象类型,字体协议为utf-8协议

HTTP 首部字段类型

  • 请求首部字段
  • 响应首部字段
  • 通用首部字段
  • 实体首部字段

HTTP 首部字段概览

  • 通用首部字段

在这里插入图片描述

  • 请求首部字段

在这里插入图片描述

  • 响应首部字段

在这里插入图片描述

  • 实体首部字段

在这里插入图片描述

Connection

Cookie

Cookie其实是一种特别我们经常使用的字段,因为HTTP协议是一种无状态的协议,也就意味着:本次会话和上一次会话之间没有任何关系,互不影响。但是,我们日常使用B站等视频网站时,却能够保持登录状态,这是因为HTTP协议不是无状态的吗?

当然不是,HTTP协议引入了 Cookie 值作为“会话保持”的媒介,那么 cookie 到底是什么呢?

在这里插入图片描述

简单来说,Cookie 是一种保存在内存或者本地磁盘上的数据文件,其中保存着客户端的各种信息。

  • Set-Cookie

Set-Cookie 字段的属性:

属性说明
NAME=VALUE赋予Cookie的名称和其值(必需项)
expires=DATECookie的有效期(若不明确指定则默认为浏览器关闭前为止)
path=PATH将服务器上的文件目录作为Cookie的适用对象(若不指定则 默认为文档所在的文件目录)
domain=域名作为Cookie适用对象的域名 (若不指定则默认为创建 Cookie 的服务器的域名)
Secure仅在HTTPS安全通信时才会发送Cookie
HttpOnly加以限制,使Cookie不能被JavaScript脚本访问

eg.

Set-Cookie: id=1; password=123456; expires=Tue, 05 Jul 2023 18:43:32 GMT; path=/; domain=baichen.com;
  • Cookie

eg.

Cookie: id=1; password=123456;
  • Set-Cookie 和 Cookie 实例

本会话的源代码见简易HTTP服务器,以下报文都是基于下文代码获得,大家可以自行验证。

请求:
GET / HTTP/1.1
Host: baichen.com
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: xxx
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6

响应:
HTTP/1.0 200 OK
Set-Cookie: id=1111       -- 设置Cookie
Set-Cookie: password=2222 -- 设置Cookie
Content-Type: text/html, charset=utf-8
Content-Length: XXX

.....

请求:
POST / HTTP/1.1
Host: baichen.com
Connection: keep-alive
Content-Length: 37
Content-Type: application/x-www-form-urlencoded
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Cookie: id=1111; password=2222 --- Cookie值

name=%E7%99%BD%E6%99%A8&passwd=123456

Session

  • Cookie 和 Session 对比
    1. Cookie 数据存放在客户端上,Session 数据放在服务器上。
    2. Cookie不是很安全,别人可以分析存放在本地的 Cookie 并进行 Cookie 欺骗。
    3. Session 对比 Cookie 来说更安全,Session 会在一定时间内保存在服务器上,但是当访问增多,会比较占用服务器的性能。
    4. 一般的方案是:CookieSession 配合使用,以达到安全和会话保持的平衡。

⛩️7. 搭建简易HTTP服务器


  • 首先,封装一个套接字库。
#pragma once

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>


class Sock
{
public:
    static int Socket()
    {
        int sock = socket(AF_INET, SOCK_STREAM, 0);
        if(sock < 0)
        {
            std::cerr << "socket failed" << std::endl;
            exit(1);
        }
    }

    static void Bind(int sock, uint16_t port)
    {
        struct sockaddr_in local;
        local.sin_family = AF_INET;
        local.sin_port = htons(port);
        local.sin_addr.s_addr = INADDR_ANY;
        
        if(bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0)
        {
            std::cerr << "bind failed" << std::endl;
            exit(2);
        }
    }

    static void Listen(int sock)
    {
        const int backlog = 5;
        if(listen(sock, backlog) < 0)
        {
            std::cerr << "listen failed" << std::endl;
            exit(3);
        }
    }

    // tcp通信直接在套接字中写就可以,所以可以不用返回客户端的信息
    static int Accept(int sock)
    {
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        
        int new_sock = accept(sock, (struct sockaddr*)&peer, &len);
        if(new_sock < 0)
        {
            std::cerr << "accept failed" << std::endl;
            exit(4);
        }

        return new_sock;
    }

    static void Connect(int sock, const std::string& ip, uint16_t port)
    {
        struct sockaddr_in server;
        memset((void*)&server, 0, sizeof(server));
        server.sin_family = AF_INET;
        server.sin_port = htons(port);
        server.sin_addr.s_addr = inet_addr(ip.c_str());

        if(connect(sock, (struct sockaddr*)&server, sizeof(server)) < 0)
        {
            std::cerr << "connect failed" << std::endl;
            exit(5);
        }
        else
        {
            std::cout << "connect succeed" << std::endl;
        }
    }
};
  • HTTP服务:
#include "Sock.hpp"
#include <fstream>
#include <sys/stat.h>

// 设置网页根目录
#define WWWROOT "./wwwroot/"
#define HOME_PAGE "index.html"

void Usage(std::string args)
{
    std::cout << "Usage : " << args << " server_port" << std::endl;
}

void *handler(void *args)
{
    pthread_detach(pthread_self());

    int sock = *(int *)args;
    delete (int *)args;

#define NUM 1024 * 10
    char buf[NUM];
    memset(buf, 0, sizeof(NUM));
    // 接收请求报文
    ssize_t sz = recv(sock, buf, sizeof(buf) - 1, 0);
    if (sz > 0)
    {
        buf[sz] = 0;
        std::cout << buf << std::endl;

        std::string fileloc = WWWROOT;
        fileloc += HOME_PAGE;
        // fileloc += "a/b";
        // 读取网页信息
        std::ifstream ifs(fileloc);

        if (!ifs.is_open())
        {
            std::string http_response = "HTTP/1.0 404 NotFount";
            http_response += "Content-Type: text/html, charset=utf-8";
            http_response += "\n";
            http_response += "<html><p> 你访问的资源不存在 </p></html>";

            send(sock, http_response.c_str(), http_response.size(), 0);
        }
        else
        {
            // 构建响应报文
            std::string line;
            std::string echo;

            while (std::getline(ifs, line))
            {
                echo += line;
            }

            struct stat fst;
            stat(fileloc.c_str(), &fst);

            std::string http_response = "HTTP/1.0 200 OK\n";
            // std::string http_response = "HTTP/1.0 404 NotFount";
            // std::string http_response = "HTTP/1.0 302 Temporarily Move\n";
            // http_response += "Location: http://www.bilibili.com";

            http_response += "Set-Cookie: id=1111\n";
            http_response += "Set-Cookie: password=2222\n";
            http_response += "Content-Type: text/html, charset=utf-8\n";
            http_response += "Content-Length: ";
            http_response += std::to_string(fst.st_size);
            http_response += "\n\n";
            http_response += echo;
            // http_response += "\n";
            http_response += "\n";

            // std::string http_response = "HTTP/1.0 200 OK\n";
            // http_response += "Content-Type: text/plain\n";
            // http_response += "\n";
            // http_response += "hello, i'm from the futurn\n";
            
            // 发送响应报文
            send(sock, http_response.c_str(), http_response.size(), 0);
        }
    }
    close(sock);
    return nullptr;
}

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(-1);
    }
    int sock = Sock::Socket();

    Sock::Bind(sock, atoi(argv[1]));
    Sock::Listen(sock);

    while (true)
    {
        pthread_t tid;
        int new_sock = Sock::Accept(sock);
        // int *psock = new int(new_sock);

        // pthread_create(&tid, nullptr, handler, psock);
    }

    return 0;
}
  • index网页构建:
<!DOCTYPE html>

<html>
    <head>
        <meta charset="utf-8">
    </head>
    <body>
        <h5>hello, i'm baichen!</h5>
        <h5>hello, 我是表单!</h5>
        <!-- action是数据提交到哪 method是提交数据时使用什么方法,可自行测试GET、POST方法传递信息的不同 -->
        <form action="/a/handler_form" method="POST">
            姓名:<input type="text" name="name"><br/>
            密码:<input type="password" name="passwd"><br/>
            <input type="submit" value="登录">
        </form>
    </body>
</html>


📕后记


本篇文章白晨讲解了HTTP的背景知识、协议结构、状态码、相关技术知识以及服务器的编写,也算是讲解的非常全面了。其中也有一些知识由于篇幅问题,白晨并没有展开说明,比如HTTP的首部字段等,需要大家在这篇文章的基础上自行进行相关学习。下一篇文章,白晨准备讲解HTTP协议的升级版——HTTPS协议,通过学习HTTPS的学习,相信你会对HTTP协议有更深的理解。

如果大家有什么想和白晨交流的,欢迎私信白晨🎊。


如果讲解有不对之处还请指正,我会尽快修改,多谢大家的包容。

如果大家喜欢这个系列,还请大家多多支持啦😋!

如果这篇文章有帮到你,还请给我一个大拇指 👍和小星星 ⭐️支持一下白晨吧!喜欢白晨【网络】系列的话,不如关注👀白晨,以便看到最新更新哟!!!

我是不太能熬夜的白晨,我们下篇文章见。

举报

相关推荐

0 条评论