首页 > 其他分享 >HTTP协议详解(2万字长文详解)

HTTP协议详解(2万字长文详解)

时间:2024-02-06 11:32:44浏览次数:19  
标签:std HTTP string 详解 path 字长 http include 我们

HTTP协议详解

什么是http/https协议

应用层协议一般都是由我们程序员自己来定义!

但是已经有人针对使用场景,早就已经写好了场景的协议软件,供我们使用

这就是http/https这两个就是最常见的协议!——这两个协议都是工作在应用层

认识URL

平时我们俗称的 "网址" 其实就是说的 URL

image-20231006181018239

==所以我们访问网站本质其实就是——通过域名解析得到ip,通过ip找到对应的主机!然后在这个主机对应的文件路径下的文件打开!,然后将文件内容返回!==

==http协议作用就是从服务端拿各种资源的!例如:文本,图片,视频等等——所以才叫做超文本传输协议!==

image-20231006180421198

这是一个比较标准的URL

但是我们发现我们上面的URL既没有登录信息,没有服务器端口号——这些其实都是浏览器帮我们自动填上的!

那么浏览器是如何知道端口号是什么呢?——看协议!

==端口号和特定的服务是强相关的!==

==一般来说http协议对应的端口号是 80,https协议对应的端口号是443==

==http协议的本质就是从服务器拿下来对应的“资源”==

什么是资源?——凡是我们在网络中看到的一切都是资源!——例如我们看淘宝,我们逛到的网页其实就是html本质就是文件,图片是文件!短视频也是视频文件,听到音乐就是音频文件!......这一切都是资源!——我们==可以将其全部都看做是资源文件!==

==这些文件都是在我们的服务器的磁盘上面!——为了找到这些文件所以我们需要linux系统的路径结构!==

所以http协议的本质就是从服务器拿下对应的文件!——因为文件的种类特别多,http都能搞定,所以http叫做超文本传输协议!“超”字就体现了他能传输的文件十分的多

urlencode和urldecode

像 / ? : 等这样的字符, 已经被url当做特殊意义理解了. 因此这些字符不能随意出现.否则可能干扰URL的正常解析!比如, 某个参数中需要带有这些特殊字,就必须先对特殊字符进行转义

举个例子:

image-20231007115055063

如何编码和如何解码的呢?

转码规则如下:

将需要转码的字符(一个字符占8bit位)转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编码成%XY 格式

image-20231007150557317

解码方式就是将其重新转回二进制,然后根据编码格式看对应的字符!

这个工作要我们自己做吗?——如果是重0开始写一个服务器原则上来说那是要我们自己来实现的!==但是实际上我们可以直接去网上找url encode和decode的源码的!——这就直接能使用了!所以需要就直接在往上搜索即可!==

例如:https://blog.51cto.com/u_15060547/3630550?b=totalstatistic

==如何验证这个过程呢?——我们可以使用往上的urlencode工具来进行解码==

HTTP协议格式

http request协议格式

image-20231007162652576

==这个四个模块整体合起来就是——http请求的报头!==

==这个报头会通过tcp链接,向服务器发送过去!==

image-20231007153843322

==这就是http协议与tcp协议之间的关系==

http response协议格式

image-20231007162838717

==整个http协议==

image-20231007162957950

在了解宏观的结构后!我们现在开始了解一些细节的内容!

  1. ==请求和响应如何保证应用层完整读取完毕了呢?==

我们可以看到无论是请求和响应都是一堆字符串!

a.也就是说,我们至少都能读取到读取到一行!

b.我们可以使用while(读取完整的一行)——读取到所有的请求行+请求报头!这个循环结束的条件是什么呢?——还记得结构上的空行吗?只要当我们读取到空行后我们就结束循环!空行之前全部都是http的按行为单位的,请求行和请求报头!

那么正文我们该如何读取呢?——正文不一定是按行读取!例如是音频视频呢?音频视频如何按行读取呢?

c.我们只能保证把报头全部读完!——而报头是有一个属性的!content_len——这个就是描述正文长度的!!

d.然后解析出现正文长度——我们只要按照这个属性读取完正文即可

==这样子就可以将请求和响应全部读取完毕!==

  1. ==http的请求和响应是如何做到序列化和反序列化的?==

这个是http是自己实现的!实现很简单

第一行+请求/响应包头,只要按照\r\n读取将一个大字符串转化为n个小字符串即可!

正文则不用进行序列化和反序列化!

==我们可以用代码来验证一下==

//Protocol.hpp
#pragma once
#include<iostream>
#include<string>
#include<vector>

class HttpRequest
{
public:
    std::string inbuffer;
};

class HttpResponse
{
public:
    std::string outbuffer;
};
//httpServer.hpp
#pragma once
#include<iostream>
#include<functional>
#include<string>
#include<string.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<unistd.h>
#include<signal.h>
#include"Protocol.hpp"  

namespace server
{
    enum
    {
        USAGE_ERR = 1,
        SOCKET_ERR,
        BIND_ERR,
        LISTEN_ERR,
        ACCEPT_ERR
    };

    const static int gbacklog = 5;
    const static uint16_t gport = 8080;
    using func_t = std::function<void(const HttpRequest &,HttpResponse &)>;

    class httpServer
    {
    public:
        httpServer(func_t func,const uint16_t &port = gport)
            : port_(port), listensock_(-1),func_(func)
        {
        }

        void initServer()
        {
            listensock_ = socket(AF_INET,SOCK_STREAM,0);
            if(listensock_ == -1)
            {
                exit(SOCKET_ERR);
            }

            struct sockaddr_in peer;
            bzero(&peer,sizeof(peer));
            peer.sin_family = AF_INET;
            peer.sin_addr.s_addr = INADDR_ANY;
            peer.sin_port = htons(port_);
            int n = bind(listensock_,(struct sockaddr*)&peer,sizeof(peer));

            if(n == -1)
            {
                exit(BIND_ERR);
            }

           if (listen(listensock_, gbacklog) <  0)
           {
               exit(LISTEN_ERR);
           }

        }

        void start()
        {

            for (;;)
            {
                signal(SIGCHLD, SIG_IGN);
                struct sockaddr_in peer;
                socklen_t len = sizeof(peer);
                int sock = accept(listensock_, (struct sockaddr *)&peer, &len);

                std::cout << sock << std::endl;
                if (sock == -1)
                {
                    continue; 
                }

                pid_t id = fork();
                if (id == 0)
                {
                    close(listensock_);

                    HandlerHttp(sock);


                    close(sock);
                    exit(0);
                }
                close(sock);

            }
        }
        ~httpServer()
        {
        }

    private:
        void HandlerHttp(int sock)
        {
            char buffer[1024];
            ssize_t n = recv(sock,buffer,sizeof(buffer)-1,0);
            HttpRequest req;
            HttpResponse resp;
            if(n>0)
            {
                buffer[n] = 0;
                req.inbuffer = buffer;
                func_(req,resp);

                send(sock,resp.inbuffer.c_str(),resp.inbuffer.size(),0);
            }
        }

    private:
        int listensock_; 
        uint16_t port_;
        func_t func_;

    };


}
//httpServer.cc
#include<memory>

using namespace server;
using namespace std;

static void usage(std::string proc)
{
    std::cout << "\nUsage:\n\t" << proc << " local_port\n\t\n";
}
void GET(const HttpRequest & req, const HttpResponse &resp)
{
    cout << "---------------http begin-----------------------"<<endl;
    cout << req.inbuffer <<endl;
    cout << "---------------http end-----------------------"<<endl;
}
int main(int argc,char* argv[])
{
    if(argc != 2)
    {
        usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port = atoi(argv[1]);//将字符串转换为整数


    unique_ptr<httpServer> tsvr(new httpServer(GET));
    tsvr->initServer();
    tsvr->start();

    return 0;
}

==我们直接使用本地的浏览器作为客户端访问这个服务器!==

image-20231007204140289

==我们就可以看到一个纯净的http的response的协议结构==

image-20231007214142598

或许听说过一种技术就是爬虫——我们在linux下面使用如下的指令将网页给获取到

wget baidu.com

image-20231007213329628

==其所谓的爬虫就是将网页的信息都抓取下来,然后分析信息,然后根据标签语言里面的信息进行进一步的递归式的爬取==

很多公司有反爬虫的策略,而爬虫的人经常在http协议里面加上User-Agent伪装是客户端来爬取

http协议的响应协议结构

//httpServer.cc
#include"httpServer.hpp"
#include<memory>

using namespace server;
using namespace std;

static void usage(std::string proc)
{
    std::cout << "\nUsage:\n\t" << proc << " local_port\n\t\n";
}
void GET(const HttpRequest & req, HttpResponse &resp)
{
    cout << "---------------http begin-----------------------"<<endl;
    cout << req.inbuffer <<endl;
    cout << "---------------http end-----------------------"<<endl;
    
    
    //我们手动的进写一个
    std::string respline ="HTTP/1.1 200 OK\r\n";//就是相当于响应行!
    std::string respheader = "Context-Type: text/html\r\n";//说明转生文本是HTML!没有这个属性会乱码!
    std::string respblank ="\r\n";//空行
    std::string body = "<!DOCTYPE html> <html lang=\"en\"> <head> <meta charset=\"UTF-8\"> <title>for test</title>  hello world <h1> </head> <body> <p>10月7日是中秋国庆八天假期过后的首个工作日。这个假期,中秋与国庆相逢,再叠加亚运会热潮,创造了有监测记录以来最高的旅游热度。高速公路车流不息,景区景点人流如潮,街区商圈人气火爆,持续上涨的热情、不断刷新的数据,释放着中国经济的澎湃活力。这个假期,大家究竟是怎么过的?接下来,我们先从出行数据看活力。</p> </body> </html>";//直接硬编码!

    resp.outbuffer += respline;
    resp.outbuffer += respheader;
    resp.outbuffer +=respblank;
    resp.outbuffer +=body;
}
int main(int argc,char* argv[])
{
    if(argc != 2)
    {
        usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port = atoi(argv[1]);//将字符串转换为整数


    unique_ptr<httpServer> tsvr(new httpServer(GET));
    tsvr->initServer();
    tsvr->start();

    return 0;
}

==我们在GET这里手动的填写一个http的响应==

我们这里介绍一个小的测试工具

telnet

这个指令的作用是能够让我们自己构建一个http请求,发送给我们的服务器!我们服务器就会进行响应

image-20231008164442754

==我们使用浏览器来进行访问!——我们就可以看到如下的页面!==

image-20231008164538603

在我们的日常中我们使用浏览器去访问各种网站!本质也是如此!无非就是将文本换成是各种资源!例如:音频资源,图片资源等待

问题1:现在我们请求的是服务器的web根目录!——那么要返回什么呢?

image-20231008165307988

问题2:我们如何理解 xxx.xxx.xxx.xxx/8080/a/b.html这样的方式进行访问呢?

image-20231008165452981

url根目录

上面我们都是服务器和网页都是硬编码在一起的!现在我们要将服务器和网页分离开来

首先我们就要理解什么是url根目录!——这个根目录到底是一个什么东西!

首先我们就要将协议进行切分!

//"Util.hpp"
#pragma once
#include<iostream>
class Util
{
public:
//这个函数用于读取首行
static std::string getOneLine(std::string& buffer,const std::string sep)
{
auto pos = buffer.find(sep);
if(pos == std::string::npos) return "";
std::string oneline = buffer.substr(0,pos);
buffer.erase(0,oneline.size()+sep.size());//去除第一行+/r/n
return oneline;
}
};

//Protocol.hpp
#pragma once
#include<iostream>
#include<string>
#include<vector>
#include<sstream>
#include"Util.hpp"

const std::string sep = "\r\n";
class HttpRequest
{
 public:
 HttpRequest(){}
 ~HttpRequest(){}

 ///////////////////////////////////////////////////////////////////////////
 //这就是用于分离的函数
 void parse()
 {
     //1.从inbuffer中获取第一行!分隔符 \r\n
     std::string line = Util::getOneLine(inbuffer,sep);
     if(line.empty()) return;
     std::cout << "line:" << line << std::endl;
     //2.从请求行中提取三个字段!
     std::stringstream ss(line);//这个支持根据空格自动分割!
     ss >> method >> url >> httpversion;
 }
 ///////////////////////////////////////////////////////////////////////////////

 public:
 std::string inbuffer;//协议请求都是在inbuffer里面!

 std::string method;
 std::string url;
 std::string httpversion;
};

class HttpResponse
{
 public:
 std::string outbuffer;
};
//httpServer.hpp
#pragma once
#include<iostream>
#include<functional>
#include<string>
#include<string.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<unistd.h>
#include<signal.h>
#include"Protocol.hpp"  

namespace server
{

const static int gbacklog = 5;
const static uint16_t gport = 8080;
using func_t = std::function<void(const HttpRequest &, HttpResponse &)>;

class httpServer
{
public:
//......
private:
void HandlerHttp(int sock)
{
   char buffer[1024];
   ssize_t n = recv(sock,buffer,sizeof(buffer)-1,0);//我们假设大概率直接就能读取到完整的http请求
   HttpRequest req;
   HttpResponse resp;
   if(n>0)
   {
       buffer[n] = 0;
       req.inbuffer = buffer;
       //////////////////////////////////////////////////////
       req.parse();//响应解析
       /////////////////////////////////////////////////////
       func_(req,resp);
       send(sock,resp.outbuffer.c_str(),resp.outbuffer.size(),0);
   }
}

private:
int listensock_; // tcp服务端也是要有自己的socket的!这个套接字的作用不是用于通信的!而是用于监听连接的!
uint16_t port_;//tcp服务器的端口
func_t func_;

};
}
//httpServer.cc
#include"httpServer.hpp"
#include<memory>

using namespace server;
using namespace std;

static void usage(std::string proc)
{
 std::cout << "\nUsage:\n\t" << proc << " local_port\n\t\n";
}
void GET(const HttpRequest & req, HttpResponse &resp)
{
 cout << "---------------http begin-----------------------"<<endl;
 cout << req.inbuffer <<endl;
 std::cout << "method: " << req.method << std::endl;
 std::cout << "url: " << req.url << std::endl;
 std::cout << "httpversion: " << req.httpversion << std::endl;
 cout << "---------------http end-----------------------"<<endl;

 std::string respline ="HTTP/1.1 200 OK\r\n";//就是相当于响应行!
 std::string respheader = "Context-Type: text/html\r\n";//说明转生文本是HTML!没有这个属性会乱码!
 std::string respblank ="\r\n";//空行
 std::string body = "<!DOCTYPE html> <html lang=\"en\"> <head> <meta charset=\"UTF-8\"> <title>for test</title> <h1> hello world <h1> </head> <body> <p>10月7日是中秋国庆八天假期过后的首个工作日。这个假期,中秋与国庆相逢,再叠加亚运会热潮,创造了有监测记录以来最高的旅游热度。高速公路车流不息,景区景点人流如潮,街区商圈人气火爆,持续上涨的热情、不断刷新的数据,释放着中国经济的澎湃活力。这个假期,大家究竟是怎么过的?接下来,我们先从出行数据看活力。</p> </body> </html>";

 resp.outbuffer += respline;
 resp.outbuffer += respheader;
 resp.outbuffer +=respblank;
 resp.outbuffer +=body;
}
int main(int argc,char* argv[])
{
 if(argc != 2)
 {
     usage(argv[0]);
     exit(USAGE_ERR);
 }
 uint16_t port = atoi(argv[1]);//将字符串转换为整数


 unique_ptr<httpServer> tsvr(new httpServer(GET));
 tsvr->initServer();
 tsvr->start();

 return 0;
}

image-20231008175628888

==我们现在已经成功的进行解析分离了!——那么现在就是什么是web根目录?==

我们现在已知url都是以 \ 开始的!

我们一个服务器除了我们源代码之外

image-20231008180707698

==我们可以看到我们还有一个目录!——这个目录就是我们的web根目录!——这个目录里面可以放各种的资源!==

我们可以看到还有以一个http.conf这个可以用来配置web根目录的路径!——但是我们这里就直接定好了

image-20231008180823360

那么我们该如何保证按指定的需求去访问呢路径呢?

==开始添加默认路径!==

//Protocol.hpp
#pragma once
#include<iostream>
#include<string>
#include<vector>
#include<sstream>
#include"Util.hpp"

const std::string sep = "\r\n";
const std::string default_root = "./wwwroot";//web根目录!
const std::string home_page = "index.html";
class HttpRequest
{
public:
    HttpRequest(){}
    ~HttpRequest(){}
private:
    void parse()
    {
        //1.从inbuffer中获取第一行!分隔符 \r\n
        std::string line = Util::getOneLine(inbuffer,sep);
        if(line.empty()) return;
        std::cout << "line:" << line << std::endl;
        //2.从请求行中提取三个字段!
        std::stringstream ss(line);//这个支持根据空格自动分割!
        ss >> method >> url >> httpversion;

        ///////////////////////////////////////////////////////////////////////////////////////
        //3.添加web默认路径!
        path = default_root;//./wwwroot
        path += url;//拼接后就变成了 ./wwwroot/a/b/c.html
        //以后我们访问的资源都是从 ./wwwroot开始进行访问!
        //如果url 是一个 / 那么拼接后就变成了 ./wwwroot/
        //那么这样子不就糟糕了!我们要访问哪一个文件资源?
        //其实每一个服务器都有自己的主页信息 home_page——一般所有的web服务器默认都有一个index.html

        //那么我们该如何判断是一个 / 呢?
        if(path[path.size() -1] == '/') path +=home_page;//我们们这样就可以判断出来了!
        ////////////////////////////////////////////////////////////////////////////////////////

    }

public:
    std::string inbuffer;

    std::string method;
    std::string url;
    std::string httpversion;
    std::string path;
};
class HttpResponse
{
public:
    std::string outbuffer;
};
//httpServer.hpp
void GET(const HttpRequest & req, HttpResponse &resp)
{
    cout << "---------------http begin-----------------------"<<endl;
    cout << req.inbuffer <<endl;
    std::cout << "method: " << req.method << std::endl;
    std::cout << "url: " << req.url << std::endl;
    std::cout << "httpversion: " << req.httpversion << std::endl;
    std::cout << "path" << req.path << std::endl;//多一个打印path
    cout << "---------------http end-----------------------"<<endl;

    std::string respline ="HTTP/1.1 200 OK\r\n";//就是相当于响应行!
    std::string respheader = "Context-Type: text/html\r\n";//说明转生文本是HTML!没有这个属性会乱码!
    std::string respblank ="\r\n";//空行
    std::string body = "<!DOCTYPE html> <html lang=\"en\"> <head> <meta charset=\"UTF-8\"> <title>for test</title> <h1> hello world <h1> </head> <body> <p>10月7日是中秋国庆八天假期过后的首个工作日。这个假期,中秋与国庆相逢,再叠加亚运会热潮,创造了有监测记录以来最高的旅游热度。高速公路车流不息,景区景点人流如潮,街区商圈人气火爆,持续上涨的热情、不断刷新的数据,释放着中国经济的澎湃活力。这个假期,大家究竟是怎么过的?接下来,我们先从出行数据看活力。</p> </body> </html>";

    resp.outbuffer += respline;
    resp.outbuffer += respheader;
    resp.outbuffer +=respblank;
    resp.outbuffer +=body;
}

image-20231008201625769

==所以web根目录的本质就是服务器给我们自动拼上的前缀!==

==接下来我们就要使用这个路径了!==

工具类

//Util.hpp
#pragma once
#include<iostream>
#include<string>
#include<fstream>

class Util
{
public:
    static std::string getOneLine(std::string& buffer,const std::string sep)
    {
        auto pos = buffer.find(sep);
        if(pos == std::string::npos) return "";
        std::string oneline = buffer.substr(0,pos);
        buffer.erase(0,oneline.size()+sep.size());//去除第一行+/r/n
        return oneline;
    }

    //这个用来读取文件!
    static bool readfile(const std::string& resourse,char *buffer,int size)
    {
        std::ifstream in(resourse,std::ios::binary);
        if(!in.is_open()) return false;
        in.read(buffer,size);

        // std::string line;//getline只能获取文本!如果是二进制就不行!如果读取图片之类的就会出现问题!
        // while(std::getline(in,line))
        // {
        //     *out += line;
        // }
        in.close();
        return true;
    }
};

body的真正获取方式

//httpServer.cc
#include"httpServer.hpp"
#include<memory>

using namespace server;
using namespace std;

static void usage(std::string proc)
{
    std::cout << "\nUsage:\n\t" << proc << " local_port\n\t\n";
}
void GET(const HttpRequest & req, HttpResponse &resp)
{
    cout << "---------------http begin-----------------------"<<endl;
    cout << req.inbuffer <<endl;
    std::cout << "method: " << req.method << std::endl;
    std::cout << "url: " << req.url << std::endl;
    std::cout << "httpversion: " << req.httpversion << std::endl;
    std::cout << "path" << req.path << std::endl;
    cout << "---------------http end-----------------------"<<endl;

    std::string respline ="HTTP/1.1 200 OK\r\n";//就是相当于响应行!
    std::string respheader = "Context-Type: text/html\r\n";//说明转生文本是HTML!没有这个属性会乱码!
    std::string respblank ="\r\n";//空行

    /////////////////////////////////////////////////////////////////////////////////////
    //body的真正获取方式
    std::string body;
    body.resize(req.size);
    if(!Util::readfile(req.path,(char*)body.c_str(),body.size()))
    {
        //如果读取失败那么我们应该返回一个网页说是404
        //我们可以在wwwroot下面创建一个404.html这个是一个绝对会存在的文件的!
        Util::readfile(html_404,(char*)body.c_str(),body.size());//这个操作一定能成功!    
    }
    //上面我们都是硬编码!但是实际body的内容是通过文件读取来获得的而是用的路径就是path!
    ///////////////////////////////////////////////////////////////////////////////////////
    resp.outbuffer += respline;
    resp.outbuffer += respheader;
    resp.outbuffer +=respblank;
    resp.outbuffer +=body;
}
int main(int argc,char* argv[])
{
    if(argc != 2)
    {
        usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port = atoi(argv[1]);//将字符串转换为整数


    unique_ptr<httpServer> tsvr(new httpServer(GET));
    tsvr->initServer();
    tsvr->start();

    return 0;
}

//./wwwroot/404.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>资源不存在!</title>
</head>
<body>
    <h1>你所访问的资源并不存在!404! 
</body>
</html>
//./wwwroot/index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>我是首页!</title>
    <a rel="nofollow" href="/test/a.html">新闻</a>//进行网页跳转
    <a rel="nofollow" href="/test/b.html">电商</a>//进行网页跳转!
</head>
<body>
   我是网站的首页! 
</body>
</html>
/wwwroot/test/a.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>a网页</title>
</head>
<body>
    我是a网页,负责新闻的入口! 
</body>
</html>
/wwwroot/test/b.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>我是b网页</title>
</head>
<body>
    我是b网页负责电商! 
</body>
</html>

image-20231008213052526

这是我们网站的目录结构!

//Protocal.hpp
#pragma once
#include<iostream>
#include<string>
#include<vector>
#include<sstream>
#include"Util.hpp"

const std::string sep = "\r\n";
const std::string default_root = "./wwwroot";//web根目录!
const std::string home_page = "index.html";

const std::string html_404 = "./wwwroot/404.html";//在这里加上

image-20231008214718931

image-20231008215436331

image-20231008215450022

当我们使用浏览器访问 /的时候!——这时候就会自动跳转到首页——也就是index.html

我们还能成功的跳转到a,b页面!

image-20231008211850958

当我访问不存在的路径的时候,就会自动的跳转到这个404界面!

==以后只要别人把页面写好!我们只要使用这个逻辑就可以调用别人写的页面!==

所谓前端就是写wwwroot目录下面的编码

响应报头

//httpServer.cc
std::string respheader = "Context-Type: text/html\r\n";//说明转生文本是HTML!没有这个属性会乱码!

我们的请求结构的报头里面——有一个content-Type——这个就是用来 描述资源的类型的!我们刚刚都是使用html所以对应的就是 text/html

而现在我们的资源里面有图片资源了!所以我们也要添加对应的类型!——可以根据Content-type 对照表 来进行查询

image-20231009101103786

我们可以看到png图片对照的的就是imag/png

我们的respheader是为了方便演示才怎么写的!但是实际上根据接收到的body不同,所需要的respheader肯定也是不一样的!

我们要正确的去给客户端返回资源类型!我们首先就要自己知道!——那么我们该如何知道呢?——我们可以根据资源的后缀去得知!

==respheader的真正读取方式是根据path,因为我们所需要的文件资源都是在path里面!我们可以根据path,然后读取里面的文件后缀,然后根据后缀进行映射找到conten-type——最终我们就可以返回正确的类型!==

//Protocol.hpp
#pragma once
#include<iostream>
#include<string>
#include<vector>
#include<sstream>
#include"Util.hpp"

const std::string sep = "\r\n";
const std::string default_root = "wwwroot";//web根目录!
const std::string home_page = "index.html";
const std::string html_404 = "wwwroot/404.html";

class HttpRequest
{
    public:
    HttpRequest(){}
    ~HttpRequest(){}

    void parse()
    {
        //1.从inbuffer中获取第一行!分隔符 \r\n
        std::string line = Util::getOneLine(inbuffer,sep);
        if(line.empty()) return;
        std::cout << "line:" << line << std::endl;
        //2.从请求行中提取三个字段!
        std::stringstream ss(line);//这个支持根据空格自动分割!
        ss >> method >> url >> httpversion;

        //3.添加web默认路径!
        path = default_root;//./wwwroot
        path += url;
        if(path[path.size() -1] == '/') path +=home_page;


        //4. 获取path对应的资源的后缀
        //  ./wwwroot/index.html
        // ./wwwroot/test/a.html
        auto pos = path.rfind(".");//找到这个点就说明找到后缀了!
        if(pos == std::string::npos) suffix = ".html";
        else suffix = path.substr(pos);//获取后缀

    }

    public:
    std::string inbuffer;
    std::string method;
    std::string url;
    std::string httpversion;
    std::string path;
    std::string suffix;//添加一个后缀
};
class HttpResponse
{
    public:
    std::string outbuffer;
};
//httpServer.cc
std::string suffixToDesc(const std::string& suffix)
{
    std::string ct = "Content-Type: ";
    if(suffix == ".html") ct+= "text/html";
    else if (suffix == ".png") ct+="image/png";
    //为了如果想支持更多后缀可以自己继续加
    ct+= "\r\n";
    return ct;
}
void GET(const HttpRequest & req, HttpResponse &resp)
{
    cout << "---------------http begin-----------------------"<<endl;
    cout << req.inbuffer <<endl;
    std::cout << "method: " << req.method << std::endl;
    std::cout << "url: " << req.url << std::endl;
    std::cout << "httpversion: " << req.httpversion << std::endl;
    std::cout << "path: " << req.path << std::endl;
    std::cout << "suffix: " << req.suffix << std::endl;
    cout << "---------------http end-----------------------"<<endl;


    std::string respline ="HTTP/1.1 200 OK\r\n";//就是相当于响应行!
    std::string respheader = suffixToDesc(req.suffix);//通过这个函数来获取后缀对应的Context-Type
    std::string respblank ="\r\n";//空行


    std::string body;
    body.resize(req.size);
    if(!Util::readfile(req.path,(char*)body.c_str(),body.size()))
    {
        Util::readfile(html_404,(char*)body.c_str(),body.size());//这个操作一定能成功!
    }
    
    resp.outbuffer += respline;
    resp.outbuffer += respheader;
    resp.outbuffer +=respblank;
    resp.outbuffer +=body;
}

image-20231009104908992

image-20231009104920089

==我们还需要返回资源的大小!——即正文部分的大小!——我们该如何通过path获取正文部分的大小呢?——我们可以通过一个函数来知道==

stat系统调用

image-20231009113926205

第一个参数是文件路径!

第二个参数是一个输出型参数!我们传入用于获取文件的信息!

image-20231009114217243

==这个输出型参数结构体的里面的参数!——我们可以看到st_size我们就可以获取文件的大小了!==

image-20231009114534179

==返回值是成功返回0,失败返回-1,并设置错误码!==

获取文件大小我们还可以通过我们读取到body的大小来获取到

//Protocol.hpp
#pragma once
#include<iostream>
#include<string>
#include<vector>
#include<sstream>
#include"Util.hpp"
#include<sys/stat.h>
#include<sys/types.h>
#include<unistd.h>

const std::string sep = "\r\n";
const std::string default_root = "wwwroot";//web根目录!
const std::string home_page = "index.html";
const std::string html_404 = "wwwroot/404.html";

class HttpRequest
{
public:
    HttpRequest(){}
    ~HttpRequest(){}

    void parse()
    {
        //1.从inbuffer中获取第一行!分隔符 \r\n
        std::string line = Util::getOneLine(inbuffer,sep);
        if(line.empty()) return;
        std::cout << "line:" << line << std::endl;
        //2.从请求行中提取三个字段!
        std::stringstream ss(line);//这个支持根据空格自动分割!
        ss >> method >> url >> httpversion;

        //3.添加web默认路径!
        path = default_root;//./wwwroot
        path += url;
        if(path[path.size() -1] == '/') path +=home_page;

        //4. 获取path对应的资源的后缀
        auto pos = path.rfind(".");//找到这个点就说明找到后缀了!
        if(pos == std::string::npos) suffix = ".html";
        else suffix = path.substr(pos);


        //5.得到资源的大小!/////////////
        struct stat status;
        int n = stat(path.c_str(),&status);
        if(n == 0) size = status.st_size;
        else size = -1;

    }

public:
    std::string inbuffer;

    std::string method;
    std::string url;
    std::string httpversion;
    std::string path;//请求路径
    std::string suffix;//资源后缀
    int size;//正文的大小!
};
class HttpResponse
{
public:
    std::string outbuffer;
};
//httpServer.cc
void GET(const HttpRequest & req, HttpResponse &resp)
{
    cout << "---------------http begin-----------------------"<<endl;
    cout << req.inbuffer <<endl;
    std::cout << "method: " << req.method << std::endl;
    std::cout << "url: " << req.url << std::endl;
    std::cout << "httpversion: " << req.httpversion << std::endl;
    std::cout << "path: " << req.path << std::endl;
    std::cout << "suffix: " << req.suffix << std::endl;
    std::cout <<"size: " << req.size << std::endl;
    cout << "---------------http end-----------------------"<<endl;


    std::string respline ="HTTP/1.1 200 OK\r\n";//就是相当于响应行!
    std::string respheader = suffixToDesc(req.suffix);


    //如果size成功获取那么就在报头上面加上Content-Length属性!表示资源大小!
    if(req.size > 0 )
    {
        respheader += "Content_-Length: ";//一定要加上:和空格进行分割
        respheader += std::to_string(req.size);
        respheader +="\r\n";
    }
    std::string respblank = "\r\n"; // 空行

    std::string body;
    body.resize(req.size);
    if(!Util::readfile(req.path,(char*)body.c_str(),body.size()))
    {
        Util::readfile(html_404,(char*)body.c_str(),body.size());//这个操作一定能成功!

    }
    resp.outbuffer += respline;
    resp.outbuffer += respheader;
    resp.outbuffer +=respblank;
    resp.outbuffer +=body;
}

image-20231009120324467

==然后我们就能在网页上加上图片==

//./wwwroot/index.html
<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <title>我是首页!</title>
    <img src="/image1.png" alt="图片">
 <a rel="nofollow" href="/test/a.html">新闻</a>//进行网页跳转
 <a rel="nofollow" href="/test/b.html">电商</a>//进行网页跳转!
</head>
<body>
我是网站的首页! 
</body>
</html>

image-20231008220940251

请求这个首页的时候,首先要将这个网页本身加载进来!然后浏览器还会去识别这个网页里面还有一个图片资源,所以还要将网页使用的图片下载下来!

两个资源一组合才能获取显示完整的网页!

所以一个用户看到的网页,可能是多个资源组合而成的!所以要获取一张完整的网页效果!那么浏览器一定会发起多次的http请求!——先获取网页本身,然后根据网页的内容,如果发现有资源!==那么就会继续发起多次请求!请求完后合并才会完成==

http方法

GET方法的特点

我们在请求的时候我们可以发现是有请求方法的!——现在我们看到的就是GET方法!

==那么我们该如何理解这些方法呢?==

当我们在进行网络访问的时候!我们其实是在进行两种行为的!

一,是获取资源!二,上传资源(一般交互网站都有这种功能,例如:图床)

当我们想要上传资源,例如:想要完成一次登录,进行一次搜索,那么我们该如何做呢?

实际上在进行一些网站交互的时候,我们是要通过表单的方式来进行提交的!

<form> 元素

HTML 表单用于收集用户输入。

<form> 元素定义 HTML 表单:

image-20231009210123829

HTML 表单包含表单元素

表单元素指的是不同类型的 input 元素、复选框、单选按钮、提交按钮等等。

==具体看上去就是==

image-20231009211635908

==我们进行数据提交的时候本质就是前端要通过form表单提交的!浏览器会自动将form表单的内容转化为GET/POST方法进行请求!==

==我们现在使用的服务器方法默认都是GET==

我们可以验证一下

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>我是首页!</title>
</head>

<body>
    我是网站的首页!
    <a rel="nofollow" href="/test/a.html">新闻</a>
    <a rel="nofollow" href="/test/b.html">电商</a>
    <form action="/a/b/c.py" method="GET"><!--提交到服务器的那个路径下,method是用什么方法提交-->
        姓名:<br>
         <input type="text" name="xname" value="用户姓名"> <!-- value就是预设内容即预设框的内容 -->
        <br><!--换行-->
        密码:<br>
        <input type="password" name="ypwd" value="用户密码">
        <br><br>
        <input type="submit" value="登录"> <!--value就是登录框上面的字-->
    </form>
</body>

</html>

image-20231009213744866

image-20231009215410893

==当GET在提交参数的是会自动的将参数拼接在url的后面!然后以?作为分隔符!分割父左边是要访问的网站资源,右侧是提交上来的参数!——因为参数有两个所以用&作为分隔符!==

==如果我们把方法修改成POST呢?==

POST方法
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>我是首页!</title>
</head>

<body>
我是网站的首页!
<a rel="nofollow" href="/test/a.html">新闻</a>
<a rel="nofollow" href="/test/b.html">电商</a>
<form action="/a/b/c.py" method="POST"><!--提交到服务器的那个路径下,method是用什么方法提交--><!-- 修改成POST-->
  姓名:<br>
   <input type="text" name="xname" value="用户姓名"> <!-- value就是预设内容即预设框的内容 -->
  <br><!--换行-->
  密码:<br>
  <input type="password" name="ypwd" value="用户密码">
  <br><br>
  <input type="submit" value="登录"> <!--value就是登录框上面的字-->
</form>
</body>

</html>

image-20231009220459826

两种提参数方式的区别!

image-20231009220937850

==那么我们该使用哪一种呢?——因为Post方式通过正文提交参数,所以一般用户看不到!所以私密性更好!(但是私密性不等于安全性!POST不比GET更加的安全!)==

==GET方法不私密!==

但是两种方法都是不安全的!因为都是可以在网上被别人直接抓取到的!要安全就必须加密!——我们使用http都是明文传送的都是不安全的!

==通过URL传参就注定了参数不能太大!——但是POST是通过正文传的!正文可以很大!甚至可以是其他的东西!==

例如:我们上传简历,上传照片,我们总不能将那些二进制数据显示在url里面,那样子就太丑陋了!所以我们可以使用正文传参!

==所以传输大数据,或者需要私密性的行为我们使用POST,其他的使用GET即可!==

当我们使用百度的时候

image-20231009221718596

我们可以看到上面一堆的参数!——说明百度就是使用的是GET方法!

1、我们所谓的提交给指定的路径,有什么意义呢?

无论是url提参,还是正文提参,最终都是服务器来去使用我们的提交的参数去完成,例如:登录,注册,搜索等功能——但是凭什么呢?

我们服务器可以获取到这个数据!但是——==如何处理这个数据呢?以及我们如何得知我们想要怎么的去处理这个数据呢?如何知道你的请求是要登录还是注册呢?亦或者是其他事情呢?==

image-20231010105610482

//Protocol.hpp
#pragma once
#include<iostream>
#include<string>
#include<vector>
#include<sstream>
#include"Util.hpp"
#include<sys/stat.h>
#include<sys/types.h>
#include<unistd.h>

const std::string sep = "\r\n";
const std::string default_root = "wwwroot";//web根目录!
const std::string home_page = "index.html";
const std::string html_404 = "wwwroot/404.html";

class HttpRequest
{
public:
    HttpRequest(){}
    ~HttpRequest(){}

    void parse()
    {
        //1.从inbuffer中获取第一行!分隔符 \r\n
        std::string line = Util::getOneLine(inbuffer,sep);
        if(line.empty()) return;
        std::cout << "line:" << line << std::endl;
        //2.从请求行中提取三个字段!
        std::stringstream ss(line);//这个支持根据空格自动分割!
        ss >> method >> url >> httpversion;

////////////////////////////////////////////////////////////////////////////////////////
        //search?name=zhangsan&pwd=12345
        // 2.1 根据?将左右两边分离
        //如果是POST则本身就是分离的!
        //左边就是PATH,右边就是参数!——像是我们上面处理是没有考虑到参数问题的!   
///////////////////////////////////////////////////////////////////////////////////////
                
        //3.添加web默认路径!
        path = default_root;//./wwwroot
        path += url;
        if(path[path.size() -1] == '/') path +=home_page;


        auto pos = path.rfind(".");
        if(pos == std::string::npos) suffix = ".html";
        else suffix = path.substr(pos);


        //5.得到资源的大小!
        struct stat status;
        int n = stat(path.c_str(),&status);
        if(n == 0) size = status.st_size;
        else size = -1;

    }

public:
    std::string inbuffer;

    std::string method;
    std::string url;
    std::string httpversion;
    std::string path;
    std::string suffix;
    int size;
    std::string parm;
};

class HttpResponse
{
public:
    std::string outbuffer;
};
//httpServer.cc
void GET(const HttpRequest & req, HttpResponse &resp)
{

    if(req.path == "/search")
    {
        //这里就执行的是我们自己写的C++search的方法
        //使用parm作为参数,而不去执行下面的

    }
    else
    {
        //....
    }
}

==这个url不一定是要真实存在的路径!——也可以是一个简单的字符串!——我们可以通过字符串来判断响应的服务!==

image-20231010110713325

==我们可以将使用其他的语言进行服务提供==

//httpServer.cc
void GET(const HttpRequest & req, HttpResponse &resp)
{
    if(req.path == "test.py")
    {
        //建立进程间通信,pipe
        //fork创建子进程!然后使用execl("bin/python3",test.py);替换进程
        //父进程,将req.parm通过管道写入给某些后端语言,py,java,php
    }
    else if(req.path == "/search")
    {
        //这里就执行的是我们自己写的C++search的方法
        //使用parm作为参数,而不去执行下面的
    }
    else
    {
        //....
    }
}
功能路由

既然可以根据不同的路径选择不同的功能!那么我们上面那么写就太麻烦了!功能的耦合度就太高了!我们可以修改一下!

//httpServer.hpp
#pragma once
#include<iostream>
#include<functional>
#include<string>
#include<string.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<unistd.h>
#include<signal.h>
#include<unordered_map>
#include"Protocol.hpp"  

namespace server
{
       enum
       {
           USAGE_ERR = 1,
           SOCKET_ERR,
           BIND_ERR,
           LISTEN_ERR,
           ACCEPT_ERR
       };

       // req是输入型参数,resp是输出型参数!

       //保证解耦
  

       const static int gbacklog = 5;
       const static uint16_t gport = 8080;
       using func_t = std::function<void(const HttpRequest &, HttpResponse &)>;

       class httpServer
       {
       public:
           httpServer(const uint16_t &port = gport)
               : port_(port), listensock_(-1)
               {
               }
           //......
        
           void start()
           {

               for (;;)
               {
                   signal(SIGCHLD, SIG_IGN); // 直接忽略子进程信号,那么操作系统就会自动回收

                   struct sockaddr_in peer;
                   socklen_t len = sizeof(peer);
                   int sock = accept(listensock_, (struct sockaddr *)&peer, &len);

                   std::cout << sock << std::endl;
                   if (sock == -1)
                   {
                       continue; 
                   }

                   pid_t id = fork();
                   if (id == 0)
                   {
                       close(listensock_);

                       HandlerHttp(sock);
                       close(sock);
                       exit(0);
                   }
                   close(sock);

               }
           }
        
           void RegisterCb(std::string servicename, func_t cb)
           {
               //这是我们自己提供的一个注册方法
               funcs.insert({servicename,cb});
           }
           ~httpServer()
           {
           }

       private:

           void HandlerHttp(int sock)
           {
               //1.获取完整的http请求
               //2.对请求进行反序列化获得结构化数据!
               // HttpRequest req;
               //3.对请求进行处理!
               // HttpResponse resp;
               // func_(req,resp);
               //4.对响应进行序列化!
               //send发送请求!
               char buffer[1024];
               ssize_t n = recv(sock,buffer,sizeof(buffer)-1,0);//我们假设大概率直接就能读取到完整的http请求
               HttpRequest req;
               HttpResponse resp;
               if(n>0)
               {
                   buffer[n] = 0;
                   req.inbuffer = buffer;
                   req.parse();

                   funcs[req.path](req,resp);//这相当于可以根据未来的路径来进行绑定服务的!是什么路径就提供什么服务!

                   send(sock,resp.outbuffer.c_str(),resp.outbuffer.size(),0);
               }
           }

       private:
           int listensock_; // tcp服务端也是要有自己的socket的!这个套接字的作用不是用于通信的!而是用于监听连接的!
           uint16_t port_;//tcp服务器的端口
           std::unordered_map<std::string,func_t> funcs;

       };

}
//httpServer.cc
#include"httpServer.hpp"
#include<memory>

using namespace server;
using namespace std;

static void usage(std::string proc)
{
       std::cout << "\nUsage:\n\t" << proc << " local_port\n\t\n";
}
std::string suffixToDesc(const std::string& suffix)
{
       std::string ct = "Content-Type: ";
       if(suffix == ".html") ct+= "text/html";
       else if (suffix == ".png") ct+="image/png";
       //为了如果想支持更多后缀可以自己继续加
       ct+= "\r\n";

       return ct;
    
}
void GET(const HttpRequest & req, HttpResponse &resp)
{
       //...
}

void Search()
{
       //...
}

void Other()
{
       //...
}
int main(int argc,char* argv[])
{
       if(argc != 2)
       {
           usage(argv[0]);
           exit(USAGE_ERR);
       }
       uint16_t port = atoi(argv[1]);
       unique_ptr<httpServer> tsvr(new httpServer());

       //这个就是我们http的功能路由!——通过unordered_map我们可以对不同的路径间设置!调用不同的方法,需要更多的功能就在这里进行插入注册!
       tsvr->RegisterCb("/",GET);
       tsvr->RegisterCb("/search",Search);
       tsvr->RegisterCb("/test.py",Other);
       tsvr->initServer();
       tsvr->start();
    
       return 0;
}
其他的方法

image-20231010114038879

==但是这些方法一般不怎么常用!——一般来说一个http这里只会暴露两种方法一种是GET一种是POST,其他方法是不会显示出来的!==

http状态码

image-20231010114427751

1XX——说白了就是告诉用户,你的信息已经被受理了!请一下(例如:上传大文件,这种服务器无法一时间处理完成,那么就会返回这个)

3XX——常见的有301,302,307

301:Moved Permanently。永久重定向

302:Fount。临时重定向,但是会在重定向的时候改变 method: 把 POST 改成 GET,于是有了 307

307:Temporary Redirect。临时重定向,在重定向时不会改变 method

我们访问某些网站的时候会进行自动跳转

image-20231010162519369

image-20231010165925796

image-20231010170200262

void GET(const HttpRequest & req, HttpResponse &resp)
{

       cout << "---------------http begin-----------------------"<<endl;
       cout << req.inbuffer <<endl;
       std::cout << "method: " << req.method << std::endl;
       std::cout << "url: " << req.url << std::endl;
       std::cout << "httpversion: " << req.httpversion << std::endl;
       std::cout << "path: " << req.path << std::endl;
       std::cout << "suffix: " << req.suffix << std::endl;
       std::cout <<"size: " << req.size << std::endl;
       cout << "---------------http end-----------------------"<<endl;


       std::string respline ="HTTP/1.1 307 Temporary Redirect\r\n";//我们修改这里!让状态码变成307
       std::string respheader = suffixToDesc(req.suffix);

       if(req.size > 0 )
       {
           respheader += "Content_-Length: ";
           respheader += std::to_string(req.size);
           respheader +="\r\n";
       }

       respheader += "Location: https://www.baidu.com/index.html\r\n";//这个Location就是告诉浏览器我们要重定向到哪里!


       std::string respblank = "\r\n"; // 空行
    
       std::string body;
       body.resize(req.size);
       if(!Util::readfile(req.path,(char*)body.c_str(),body.size()))
       {
           Util::readfile(html_404,(char*)body.c_str(),body.size());//这个操作一定能成功!
       }
       resp.outbuffer += respline;
       resp.outbuffer += respheader;
       resp.outbuffer +=respblank;
       resp.outbuffer +=body;
}

image-20231010171430321

4XX——典型的错误是403说明被拒绝访问,404,资源不存在!

==这是属于客户端的错误!——不是服务端!==

举个例子:你回家向你爸要1亿,但是你爸 压根没有那么多钱,所以拒绝了你,你就说:爸,你怎么那么没用,一个亿都给不起我!别人听了肯定会认为是你的错误!而不是你爸的错误!

==我们请求的这个资源服务器本来就没有!你总不能在淘宝说要看动漫,淘宝给不了你这个资源,就说是淘宝的问题吧==

5XX——例如:500服务器错误,504路由器坏了

那么什么是服务器错误呢?——我们服务器里面有很多的创建进程,创建线程,字符串解析等等内容!——当服务器在创建进程,创建线程,申请空间等等行为失败了!那么就是服务错误!

==想知道更详细可以去看http状态码映射表==

但是实际上,我们看到状态码也不一定是是按照上面的来的!实际上很多的互联网公司那么服务器错误,也不会返回5XX,而是返回4XX

HTTP常见Header

  • Content-Type: 数据类型(text/html等)
  • Content-Length: Body的长度
  • Host: 客户端告知服务器, 所请求的资源是在哪个主机的哪个端口上;
  • User-Agent: 声明用户的操作系统和浏览器版本信息;
  • referer: 当前页面是从哪个页面跳转过来的;
  • location: 搭配3xx状态码使用, 告诉客户端接下来要去哪里访问;
  • Cookie: 用于在客户端存储少量信息. 通常用于实现会话(session)的功能;

长链接

其实一张我们看到的网页,实际上可能由多种元素构成!——例如:网页上不仅会有文本,还会有图片等资源!

所以一张完整的网页是要多次的http请求的!

image-20231010204313751

假如网页有100张图片!那么就要http请求100次!

==但是这会引发一个问题!因为频繁的发起http请求,http是基于tcp的!tcp是面向连接的!所以就会导致频繁创建连接的问题!==

以前使用就是都是使用这种方式的!因为很简单!而且网页资源都比较小!那时候,大部分的网站都是属于文本为主,图片也都不是高清的!

但是随着时代的发展,显示器分辨率的上升,对于网站的要求也提高了!

那么此时这种高频繁创建连接的方式就不合适了!

为了减少tcp在进行建立的时候,频繁创建连接的过程!——所以client和server都要支持一项技术——即**==长连接技术==**

==所谓的长连接的就是——建立好一条连接,获取一大份资源的时候,通过一条连接完成!==

==即创建一条连接后,网页发起请求后,回应!但是不释放这个连接!然后网页继续通过这个连接发起请求获取资源!==

在一条连接中http的每一个报头和空行以及有效载荷是可以被完整读取的!——这就注定了连接可以被重复使用

==那么我们该如何区分只请求一条连接和长链接呢?==

在报头里面其实有一个选项

Connection: keep-alive
Connection: close

==Connection写的是keep-alive——就意味着双方都是支持长连接的!==

==如果是close——那么就意味着只能使用短连接!==

http周边会话保持

会话保持严格意义上来说不是http天然具备的!而是后面使用发现需要的!

那么什么是会话保持呢?

我们先看一个现象

如果我们在一个浏览器下面登录b站,在一般情况下,只要我们第一次登录了!后面我们每一次的访问b站,那么我们会发现我们的账号都已经自动的登录了!

但是如果我们换一个浏览器!我们另一个浏览器下的b站登录就失效了!还要重新登录一次!

http协议是无状态的!——这个是什么意思呢?也就是说,我们第一次,第二次,第三次请求,第二次不知道第一次请求过,第三次不知道第二次请求过,也就是说我们历史上请求的同一张图片!浏览器都要帮我们发起http请求!==无状态就是指不去记录历史上的各种状态信息,请求,也不去猜测下一次要访问什么!==http协议只会去执行自己所需要执行的功能!

但是为什么我们发现哪怕我们关闭网站http协议依旧能记住我们呢?——这和http协议有关么?只能说没有直接的关系!

==http是无状态的!但是用户需要!——比如说我们每一次打开b站!一次要加载10张图片,我们每一次打开这个网页我们浏览器都要帮我们去服务端请求资源!而每一次请求刷新,那会不会太慢呢?而且还有一个很重要的一点!当我们在b站进行网页跳转的时候!我们发现b站是永远都是认识我们登录的账号的!如果没有记录每一次跳转我们都要重新登录一次b站那么未免也太麻烦了!——所以对于变化不怎么大的资源会浏览器进行缓存!==

image-20231010213522128

==这就是http是无协议的!但是用户需要,因为用户查看新的网页是常规操作,如果发生了网页跳转,那么新的页面也就无法识别是哪一个用户了!为了用户在一经登录,就可以在整个网站按照自己的身份进行随意访问!——这种现象就是会话保持!==

最后重新强调一遍——http协议只是一个简单的协议!是被使用的,就是帮助别人把资源获取下来!会话这个过程http是没有直接参与的!但是为了支撑这个,http协议间接参与了

那么这是如何做到会话保持呢?

image-20231011104454071

最常使用的是cookie技术——来帮助保持会话!

cookie分为——cookie文件和cookie内存

浏览器本质也是一个进程!——我们关闭浏览器后(杀死进程),重新打开浏览器,我们打开网站依旧保持着会话状态!(仍然登录)——==则说明这个cookie是文件级别的否则,进程退出后,进程的内存就应该被释放!==

还有一种现象——就是我们登录一次后虽然可以不用登录了!但是如果我们退出浏览器(杀死进程)那么就要重新登录了!因为进程的内存都被释放了!——那么这就是内存级cookie

==但是使用cookie技术有一个问题==

image-20231011112254508

其实不止浏览器使用这种方式,有些通信软件也是使用这种保存方式——例如QQ我们登录一次后选择,记住密码!那么qq也在某些文件下面里面保存我们的信息!

每一次我们打开的时候!那么qq就会自动的推送!

==如果聊天软件被盗用——那么影响就会很大了!==

==所以一共有两个问题!——1.是服务器误认的问题!2.是cookie文件里面保存了我们的账号密码!被拿到之后别人就能知道我们的账号密码,即用户信息的泄漏的问题!==

==那么为了解决这种问题!所以提出来一种新的技术==

我们账号会被别人盗走的根本原因是不是因为我们将账号密码放在文件里面了?——其实并不是!而是因为这个文件在客户端文件里面!

如果放在客户端那么不法分子就很有可能通过某些方法获取到——用户本身对自己信息的保存能力是有限的!

==那么使用这种方式的最大区别就在于——用户信息被存储在了服务端!==

我们上面说过开始的本地存储方式有两个问题!——一个是服务器误认,一个是用户信息泄露!——在这里我们可以认为已经大大改善其中一个==用户信息泄露的问题!==——即使黑客拿到了cookie里面也没有我们的用户信息了!

服务器端都是由大型公司维护的!相比于我们个人,这些大型公司在保护技术上至少是更优秀的!而且如果真的发生了严重的信息泄露,那么这就有可能上升到刑事问题了!也会有人为此承担责任,这会让黑客们进行公鸡的可能性降低,所以我们可以认为这个问题是大大

==如果单纯地以今天的我们讲的技术来说——这个问题是无法解决的!==

==所以要配合其他的策略来缓解问题的!==

例如:我们去旅游,一天内从新疆到了广州,进行跨地区,此时我们就会发现,会出现一个ip异常登录的情况!会直接下线要求我们重新输入账号和密码!——因为用户信息没有泄漏了!那么只要服务器一下线就会让session id失效!那这样子就很简单了!==只要服务器在其他策略的配合下发现你是非法用户,直接让其session id失效即可!只有有密码的哪一个可以重新登录!——这就是用户信息存储在服务端的优点!==

其他策略的用途就是甄别那些session id是需要重新失效的!

==client:cookie,sever:session——这就是现在我们主流的使用方案!==

现在又几个新的问题

1.服务器是如何写入cookie信息的?

2.如何验证client会携带cookie信息?

==如何网浏览器里面写入cookie呢?==


void GET(const HttpRequest & req, HttpResponse &resp)
{

    cout << "---------------http begin-----------------------"<<endl;
    cout << req.inbuffer <<endl;
    std::cout << "method: " << req.method << std::endl;
    std::cout << "url: " << req.url << std::endl;
    std::cout << "httpversion: " << req.httpversion << std::endl;
    std::cout << "path: " << req.path << std::endl;
    std::cout << "suffix: " << req.suffix << std::endl;
    std::cout <<"size: " << req.size << std::endl;
    cout << "---------------http end-----------------------"<<endl;

    std::string respline ="HTTP/1.1 200 ok\r\n";
    std::string respheader = suffixToDesc(req.suffix);

    if(req.size > 0 )
    {
        respheader += "Content_-Length: ";
        respheader += std::to_string(req.size);
        respheader +="\r\n";
    }


    std::string respblank = "\r\n"; // 空行

    std::string body;
    body.resize(req.size);
    if(!Util::readfile(req.path,(char*)body.c_str(),body.size()))
    {
        Util::readfile(html_404,(char*)body.c_str(),body.size());//这

    }

    //如何返回cookie信息?
    respheader += "Set-Cookie: name=12345678bcdefg\r\n";//
    //我们后面的串数字以后可以套上认证逻辑,形成session之后将信息保存在session文件里面!然后将session的id直接返回即可!
    //这就是将session信息写入到浏览器中!


    resp.outbuffer += respline;
    resp.outbuffer += respheader;
    resp.outbuffer +=respblank;
    resp.outbuffer +=body;
}

image-20231011204053218

image-20231011204718945

==只要往报头里面加入Set-Cookie行即可!==

 respheader += "Set-Cookie: aaa=xxxxxxxxxxxxxx\r\n";

xxx里面就是要写入的内容!aaa就是形成的cookie文件的名称!

cookie还能设置到期时间!

 respheader += "Set-Cookie: aaa=xxxxxxxxxxxxxx\r\n; Max-Age=60\r\n";

==在这个行的后面加上Max-Age即可!——我们就给cookie设置了一个到期时间60s!==

image-20231011205243845

​ ==我们的服务端也会接收到client发送的cookie!==

image-20231011205548773

==http请求里面就会有一个cookie!以空格作为分隔符!发送个服务端!==

为了就可以从请求中提取cookie重新知道它的内容!然后在后端进行认证!

==往后的每一次http请求,都会自动携带曾经设置的所有cookie!(记住是所有!)帮服务器进行鉴权行为!——进而支持http会话保持!==

可以看到客户端给我返回的cookie里面就包含了我曾经设置的两个cookie!

==我们也可以尝试扎抓取百度的网页==

telnet www.baidu.com 80

image-20231011211243678

==我们可以看到当我们在访问百度的时候!百度就给我们本身设置了很多cookie!也给我们设置了cookie的失效时间!==

标签:std,HTTP,string,详解,path,字长,http,include,我们
From: https://blog.51cto.com/u_15835985/9618483

相关文章

  • pve在执行apt-get update 更新软件包时报错:E: Failed to fetch https://enterprise.pr
    问题原因如下......
  • nginx: 当HTTPS资源引入HTTP导致报错blocked:mixed-content (混合加载/Mixed Content)如
    location/{expires12h;if($request_uri~*"(php|jsp|cgi|asp|aspx)"){expires0;}proxy_passhttp://127.0.0.1:8181;proxy_set_headerHost$host;proxy_set_headerX-Real-IP$remote_addr;proxy_set_headerX-Forw......
  • client-go http trace分析耗时
    klog.InitFlags(nil)flag.Parse()deferklog.Flush()cfg,err:=clientcmd.BuildConfigFromFlags("","/root/.kube/config")iferr!=nil{ klog.Fatalf("Errorbuildingkubeconfig:%s",err.Error())}kubeClient,err:=kubern......
  • Springboot整合redis配置详解
    Springboot整合redis配置详解1.导入依赖<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId></dependency>2.编写properties或者yml配置#Redis本地服务器地址,注意要开启redis服务,即那个redis-s......
  • JAVA之BigDecimal详解
    一、BigDecimal概述Java在java.math包中提供的API类BigDecimal,用来对超过16位有效位的数进行精确的运算。双精度浮点型变量double可以处理16位有效数,但在实际应用中,可能需要对更大或者更小的数进行运算和处理。一般情况下,对于那些不需要准确计算精度的数字,我们可以直接使用Float......
  • 基于BiLSTM-CRF模型的分词、词性标注、信息抽取任务的详解,侧重模型推导细化以及LAC分
    基于BiLSTM-CRF模型的分词、词性标注、信息抽取任务的详解,侧重模型推导细化以及LAC分词实践1.GRU简介GRU(GateRecurrentUnit)门控循环单元,是[循环神经网络](RNN)的变种种,与LSTM类似通过门控单元解决RNN中不能长期记忆和反向传播中的梯度等问题。与LSTM相比,GRU内部的网络架......
  • Kubernetes 服务类型详解
    Kubernetes服务类型详解如今,Kubernetes已成为管理和扩展云原生应用程序的强大工具。组织需要利用高度可扩展且始终可用的功能来保持零停机时间,快速部署他们的软件。随着越来越多的应用程序被容器化和部署,容器的管理也变得越来越复杂。因此,软件的扩展成为一个问题,而这正是Kuber......
  • abc339 详解
    第一篇整场题解纪念我第一次AK的abc!A从后往前找到第一个'.'然后输出'.'到字符串结尾构成的字符串。#include<iostream>usingnamespacestd;intmain(intargc,constchar*argv[]){stringstr;cin>>str;intlen=(int)str.length();stri......
  • 淘宝/天猫商品详情API:返回值参数详解及商业逻辑实现
    在电子商务的高速发展过程中,API接口扮演了至关重要的角色。对于淘宝和天猫这样的大型电商平台,商品详情API是商家与消费者信息沟通的桥梁。本文将深入探讨这一API的返回值参数,并展示如何通过编程利用这些数据实现商业逻辑。一、商品详情API的核心作用商品详情页是电商体验中的重要环......
  • RocketMQ_详细配置与使用详解
    为什么要用MQ 应用解耦系统的耦合性越高,容错性就越低。以电商应用为例,用户创建订单后,如果耦合调用库存系统、物流系统、支付系统,任何一个子系统出了故障或者因为升级等原因暂时不可用,都会造成下单操作异常,影响用户使用体验。 使用消息队列解耦合,系统的耦合性就会提高了。......