0
点赞
收藏
分享

微信扫一扫

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

HTTP协议详解

什么是http/https协议

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

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

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

认识URL

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

image-20231006181018239

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

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

image-20231006180421198

这是一个比较标准的URL

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

urlencode和urldecode

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

HTTP协议格式

http request协议格式

image-20231007162652576

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

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

image-20231007153843322

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

http response协议格式

image-20231007162838717

==整个http协议==

image-20231007162957950

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

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

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都是以 \ 开始的!

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

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

//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系统调用

http方法

GET方法的特点

我们在请求的时候我们可以发现是有请求方法的!——现在我们看到的就是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是通过正文传的!正文可以很大!甚至可以是其他的东西!==

功能路由

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

//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

==想知道更详细可以去看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是面向连接的!所以就会导致频繁创建连接的问题!==

http周边会话保持

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

那么什么是会话保持呢?

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

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

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

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

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

==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的失效时间!==

举报

相关推荐

0 条评论