HTTP协议

目录

  • HTTP协议
    • HTTP协议简介
    • 认识URL
    • urlencode和urldecode
  • HTTP协议格式
    • HTTP请求协议格式
    • HTTP响应协议格式
    • HTTP的方法
    • HTTP的状态码
    • HTTP常见的Header
    • Cookie和Session

HTTP协议

HTTP协议简介

HTTP(Hyper Text Transfer Protocol)协议又叫做超文本传输协议,是一个简单的请求-响应协议,HTTP通常运行在TCP之上。

认识URL

URL(Uniform Resource Lacator)叫做统一资源定位符,也就是我们通常所说的网址,是因特网的万维网服务程序上用于指定信息位置的表示方法。

一个URL大概由以下几部分组成:
HTTP协议_第1张图片

1、协议方案名

http://表示的是协议名称,表示请求时需要使用的协议,通常使用的是HTTP协议或安全协议HTTPS。HTTPS是以安全为目标的HTTP通道,在HTTP的基础上通过传输加密和身份认证保证了传输过程的安全性。

常见的应用层协议:

  • DNS(Domain Name System)协议:域名系统。
  • FTP(File Transfer Protocol)协议:文件传输协议。
  • TELNET(Telnet)协议:远程终端协议。
  • HTTP(Hyper Text Transfer Protocol)协议:超文本传输协议。
  • HTTPS(Hyper Text Transfer Protocol over SecureSocketLayer)协议:安全数据传输协议。
  • SMTP(Simple Mail Transfer Protocol)协议:电子邮件传输协议。
  • POP3(Post Office Protocol - Version 3)协议:邮件读取协议。
  • SNMP(Simple Network Management Protocol)协议:简单网络管理协议。
  • TFTP(Trivial File Transfer Protocol)协议:简单文件传输协议。

2、登录信息

usr:pass表示的是登录认证信息,包括登录用户的用户名和密码。虽然登录认证信息可以在URL中体现出来,但绝大多数URL的这个字段都是被省略的,因为登录信息可以通过其他方案交付给服务器。

3、服务器地址

www.example.jp表示的是服务器地址,也叫做域名,比如www.alibaba.comwww.qq.comwww.baidu.com

我们用IP地址标识公网内的一台主机,但IP地址本身并不适合给用户看。比如说我们可以通过ping命令,获得www.baidu.com域名解析后的IP地址。
HTTP协议_第2张图片
用户在使用计算机过程中看到IP地址并不知道他是干什么的,但是通过www.alibaba.com这个域名就可以知道对应的信息。

4、服务器端口号

80表示的是服务器端口号。HTTP协议和套接字编程一样都是位于应用层的,在进行套接字编程时我们需要给服务器绑定对应的IP和端口,而这里的应用层协议也同样需要有明确的端口号。

常见协议对应的端口号:

协议名称 对应端口号
HTTP 80
HTTPS 443
SSH 22

当我们使用某种协议时,该协议实际就是在为我们提供服务,现在这些常用的服务与端口号之间的对应关系都是明确的,所以我们在使用某种协议时实际是不需要指明该协议对应的端口号的,因此在URL当中,服务器的端口号一般也是被省略的。

5、带层次的文件路径

/dir/index.htm表示的是要访问的资源所在的路径。访问服务器的目的是获取服务器上的某种资源,通过前面的域名和端口已经能够找到对应的服务器进程了,此时要做的就是指明该资源所在的路径。

比如我们打开浏览器输入360的域名后,此时浏览器就帮我们获取到了360的首页。
HTTP协议_第3张图片
我们发起网页请求本质是获得了这样的一张网页信息,然后浏览器对这张网页信息进行解释,最后就呈现出了对应的网页。
HTTP协议_第4张图片
我们可以将这种资源称为网页资源,此外我们还会向服务器请求视频、音频、网页、图片等资源。HTTP之所以叫做超文本传输协议,而不叫做文本传输协议,就是因为有很多资源实际并不是普通的文本资源。因此在URL当中就有这样一个字段,用于表示要访问的资源所在的路径。此外我们可以看到,这里的路径分隔符是/,而不是\,这也就证明了实际很多服务都是部署在Linux上的。

6、查询字符串

uid=1表示的是请求时提供的额外的参数,这些参数是以键值对的形式,通过&符号分隔开的。

比如我们在百度上面搜索HTTP,此时可以看到URL中有很多参数,而在这众多的参数当中有一个参数wd(word),表示的就是我们搜索时的搜索关键字wd=HTTP。因此双方通过URL进行用户数据传送的。
HTTP协议_第5张图片

7、片段标识符

ch1表示的是片段标识符,是对资源的部分补充。

urlencode和urldecode

/ ?: 等这样的字符,已经被url当做特殊意义理解了,因此这些字符不能随意出现。比如某个参数中需要带有这些特殊字符,就必须先对特殊字符进行转义。

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

例如我们在百度上搜索C++:
HTTP协议_第6张图片
URL当中除了会对这些特殊符号做编码,对中文也会进行编码。

HTTP协议格式

应用层常见的协议有HTTP和HTTPS,传输层常见的协议有TCP,网络层常见的协议是IP,数据链路层对应就是MAC帧了。其中下三层是由操作系统或者驱动帮我们完成的,它们主要负责的是通信细节。如果应用层不考虑下三层,在应用层自己的心目当中,它就可以认为自己是在和对方的应用层在直接进行数据交互。
HTTP协议_第7张图片
下三层主要完成的是通信细节的问题,应用层负责的是如何使用传输过来的数据,因为下三层已经完成了通信细节,而如何使用传输过来的数据就需要我们去定制协议,这里最典型的就是HTTP协议。

HTTP是基于请求和响应的应用层服务,作为客户端,你可以向服务器发起request,服务器收到这个request后,会对这个request做数据分析,得出你想要访问什么资源,然后服务器再构建response,完成这一次HTTP的请求。这种基于request&response这样的工作方式,我们称之为cs或bs模式,其中c表示client,s表示server,b表示browser。

HTTP请求协议格式

HTTP协议_第8张图片

HTTP请求由以下四部分组成:

  • 请求行:[请求方法]+[url]+[http版本]
  • 请求报头:请求的属性,这些属性都是以key: value的形式按行陈列的。
  • 空行:遇到空行表示请求报头结束。
  • 请求正文:请求正文允许为空字符串,如果请求正文存在,则在请求报头中会有一个Content-Length属性来标识请求正文的长度。

其中,前面三部分是一般是HTTP协议自带的,是由HTTP协议自行设置的,而请求正文一般是用户的相关信息或数据,如果用户在请求时没有信息要上传给服务器,此时请求正文就为空字符串。

HTTP请求的报头与有效载荷进行分离

对于HTTP来讲,这里的请求行和请求报头就是HTTP的报头信息,请求正文实际上就是HTTP的正文信息,当应用层收到一个HTTP请求时,我们就必须将请求的报头与有效载荷进行分离。

我们就可以根据HTTP请求当中的空行来进行分离,当服务器接收到HTTP请求后,就可以按行读取,如果读取到空行就说明一行的数据已经读取完毕。

我们可以将HTTP的请求想象成一个线性结构,每一行的内容都是由\n进行分隔的,我们就可以根据\n来进行报头与有效荷载的分离了。

log.hpp

#pragma once

#include 
#include 
#include 
#include 
#include 

// 日志是有日志级别的
#define DEBUG   0
#define NORMAL  1
#define WARNING 2
#define ERROR   3
#define FATAL   4

const char *gLevelMap[] = {
    "DEBUG",
    "NORMAL",
    "WARNING",
    "ERROR",
    "FATAL"
};

#define LOGFILE "./threadpool.log"

// 完整的日志功能,至少: 日志等级 时间 支持用户自定义(日志内容, 文件行,文件名)
void logMessage(int level, const char *format, ...)
{
#ifndef DEBUG_SHOW
    if(level== DEBUG) return;
#endif

    char stdBuffer[1024]; //标准部分
    time_t timestamp = time(nullptr);
    snprintf(stdBuffer, sizeof stdBuffer, "[%s] [%ld] ", gLevelMap[level], timestamp);

    char logBuffer[1024]; //自定义部分
    va_list args;
    va_start(args, format);

    vsnprintf(logBuffer, sizeof logBuffer, format, args);
    va_end(args);

    printf("%s%s\n", stdBuffer, logBuffer);
}

Sock.hpp

#pragma once

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "log.hpp"

class Sock
{
private:
    const static int gbacklog = 20;

public:
    Sock()
    {
    }

    int Socket()
    {
        int listensock = socket(AF_INET, SOCK_STREAM, 0);
        if (listensock < 0)
        {
            logMessage(ERROR, "create socket error:%d:%s", errno, strerror(errno));
            exit(0);
        }

        logMessage(NORMAL, "create socket success, listensock:%d", listensock);
        
        return listensock;
    }

    void Bind(int sock, uint16_t port, std::string ip = "0.0.0.0")
    {
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(port);
        inet_pton(AF_INET, ip.c_str(), &local.sin_addr);

        if (bind(sock, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            logMessage(ERROR, "bind error:%d:%s", errno, strerror(errno));
            exit(1);
        }
    }

    void Listen(int sock)
    {
        if (listen(sock, gbacklog) < 0)
        {
            logMessage(ERROR, "listen error:%d:%s", errno, strerror(errno));
            exit(2);
        }

        logMessage(NORMAL, "init server success...");
    }

    int Accept(int listensock, uint16_t *port, std::string *ip)
    {
        struct sockaddr_in src;
        socklen_t len = sizeof(src);

        int servicesock = accept(listensock, (struct sockaddr *)&src, &len);
        if (servicesock < 0)
        {
            logMessage(ERROR, "accept error:%d:%s", errno, strerror);
            exit(3);
        }

        if (port)
            *port = htons(src.sin_port);
        if (ip)
            *ip = inet_ntoa(src.sin_addr);

        return servicesock;
    }

    bool Connect(int sock, const uint16_t &server_port, const std::string &server_ip)
    {
        struct sockaddr_in server;
        memset(&server, 0, sizeof(server));
        socklen_t len = sizeof(server);

        server.sin_family = AF_INET;
        server.sin_port = htons(server_port);
        server.sin_addr.s_addr = inet_addr(server_ip.c_str());

        if (connect(sock, (struct sockaddr *)&server, len) == 0)
            return true;
        else
            return false;
    }

    ~Sock()
    {
    }
};

HttpServer.hpp

#pragma once

#include 
#include "Sock.hpp"

class HttpServer
{
public:
    using func_t = std::function<void(int)>;

public:
    HttpServer(const uint16_t &port, func_t func) : port_(port), func_(func)
    {
        listensock_ = sock_.Socket();
        sock_.Bind(listensock_, port);
        sock_.Listen(listensock_);
    }

    void Start()
    {
        signal(SIGCHLD, SIG_IGN);

        for (;;)
        {
            uint16_t client_port = 0;
            std::string client_ip;

            int sockfd = sock_.Accept(listensock_, &client_port, &client_ip);
            if (sockfd < 0)
                continue;

            if (fork() == 0)
            {
                close(listensock_);
                func_(sockfd);
                close(sockfd);
                exit(0);
            }
            close(sockfd);
        }
    }

    ~HttpServer()
    {
        if (listensock_ >= 0)
            close(listensock_);
    }

private:
    Sock sock_;
    int listensock_;
    uint16_t port_;
    func_t func_;
};

HttpServer.cc

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "HttpServer.hpp"
#include "Usage.hpp"
#include "Util.hpp"

void HandlerHttpRequest(int sockfd)
{
    // 1. 读取请求 for test
    char buffer[10240];
    ssize_t s = recv(sockfd, buffer, sizeof(buffer) - 1, 0);
    if (s > 0)
    {
        buffer[s] = 0;
        // std::cout << buffer << "--------------------\n"
        //           << std::endl;
    }
}

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(0);
    }
    std::unique_ptr<HttpServer> httpserver(new HttpServer(atoi(argv[1]), HandlerHttpRequest));
    httpserver->Start();
    return 0;
}

Usage.hpp

#pragma once

#include 
#include 

void Usage(const std::string &proc)
{
    std::cout << "\nUsage" << proc << " port\n"
              << std::endl;
}

运行服务器程序后,然后用浏览器进行访问,此时我们的服务器就会收到浏览器发来的HTTP请求,并将收到的HTTP请求进行打印输出。
HTTP协议_第9张图片
这里我们需要注意的是:

  • 浏览器向我们的服务器发起HTTP请求后,因为我们的服务器没有对进行响应,此时浏览器就会认为服务器没有收到,然后再不断发起新的HTTP请求,因此虽然我们只用浏览器访问了一次,但会受到多次HTTP请求;
  • 由于浏览器发起请求时默认用的就是HTTP协议,因此我们在浏览器的url框当中输入网址时可以不用指明HTTP协议;
  • url当中的/不能称之为我们云服务器上根目录,这个/表示的是web根目录,这个web根目录可以是你的机器上的任何一个目录,这个是可以自己指定的,不一定就是Linux的根目录。

比如我们此时需要提取出请求行的信息,我们只需要创建一个切割函数,在稍微修改我们客户端代码即可:

Util.hpp

#pragma once

#include 
#include 

class Util
{
    // aaaa\nbbbbb\nccc\n\n
public:
    static void cutString(std::string s, const std::string &sep, std::vector<std::string> *out)
    {
        size_t start = 0;
        while (start < s.size())
        {
            auto pos = s.find(sep, start);
            if (pos == std::string::npos)
                break;

            std::string sub = s.substr(start, pos - start);
            out->push_back(sub);
            start += pos;
            start += sep.size();
        }

        if (start < s.size())
            out->push_back(s.substr(start));
    }
};

HttpServer.cc

void HandlerHttpRequest(int sockfd)
{
    // 1. 读取请求 for test
    char buffer[10240];
    ssize_t s = recv(sockfd, buffer, sizeof(buffer) - 1, 0);
    if (s > 0)
    {
        buffer[s] = 0;
        // std::cout << buffer << "--------------------\n"
                //   << std::endl;
    }

    std::vector<std::string> vline;
    Util::cutString(buffer, "\n", &vline);

    std::vector<std::string> vblock;
    Util::cutString(vline[0], " ", &vblock);

    std::cout << "####start################" << std::endl;
    for (auto &iter : vblock)
    {
        std::cout << "---" << iter << "\n"
                  << std::endl;
    }
    std::cout << "#####end###############" << std::endl;
}

此时运行服务端,用浏览器进行访问,我们会发现我们请求行的信息就被顺利提取出来了。
HTTP协议_第10张图片

HTTP响应协议格式

HTTP协议_第11张图片
HTTP响应由以下四部分组成:

  • 状态行:[http版本]+[状态码]+[状态码描述]
  • 响应报头:响应的属性,这些属性都是以key: value的形式按行陈列的。
  • 空行:遇到空行表示响应报头结束。
  • 响应正文:响应正文允许为空字符串,如果响应正文存在,则响应报头中会有一个Content-Length属性来标识响应正文的长度。比如服务器返回了一个html页面,那么这个html页面的内容就是在响应正文当中的。

HTTP响应的报头与有效荷载分离与HTTP请求相同。

构建HTTP响应给浏览器

服务器读取到客户端发来的HTTP请求后,需要对这个HTTP请求进行各种数据分析,然后构建成对应的HTTP响应发回给客户端。而我们的服务器连接到客户端后,实际就只读取了客户端发来的HTTP请求就将连接断开了。

我们给浏览器返回一个固定的HTTP响应,一般情况下我们都会在当前路径下创建一个目录当多Web根目录。

void HandlerHttpRequest(int sockfd)
{
    // 1. 读取请求 for test
    char buffer[10240];
    ssize_t s = recv(sockfd, buffer, sizeof(buffer) - 1, 0);
    if (s > 0)
    {
        buffer[s] = 0;
        // std::cout << buffer << "--------------------\n"
        //   << std::endl;
    }

    std::vector<std::string> vline;
    Util::cutString(buffer, "\n", &vline);

    std::vector<std::string> vblock;
    Util::cutString(vline[0], " ", &vblock);

    std::string file = vblock[1];
    std::string target = ROOT;

    if (file == "/")
        file = "/index.html";
    target += file;
    std::cout << target << std::endl;

    std::string content;
    std::ifstream in(target);
    if (in.is_open())
    {
        std::string line;
        while (std::getline(in, line))
        {
            content += line;
        }
        in.close();
    }

    std::string HttpResponse;
    if (content.empty())
        HttpResponse = "HTTP/1.1 404 NotFound\n";
    else
        HttpResponse = "HTTP/1.1 200 OK\n";
    HttpResponse += "\n";
    HttpResponse += content;
    // 2. 试着构建一个http的响应
    send(sockfd, HttpResponse.c_str(), HttpResponse.size(), 0);
}

HTTP协议_第12张图片
因此当浏览器访问我们的服务器时,服务器会将这个index.html文件响应给浏览器,而该html文件被浏览器解释后就会显示出相应的内容。
HTTP协议_第13张图片

HTTP为什么要交互版本

HTTP请求当中的请求行和HTTP响应当中的状态行,当中都包含了http的版本信息。HTTP请求当中表明的是客户端的http版本,HTTP响应当中表明的是服务器的http版本。客户端和服务器双方在进行通信时会交互双方http版本,主要还是为了兼容性的问题。因为服务器和客户端使用的可能是不同的http版本,为了让不同版本的客户端都能享受到对应的服务,此时就要求通信双方需要进行版本协商。

客户端在发起HTTP请求时告诉服务器自己所使用的http版本,此时服务器就可以根据客户端使用的http版本,为客户端提供对应的服务,而不至于因为双方使用的http版本不同而导致无法正常通信。因此为了保证良好的兼容性,通信双方需要交互一下各自的版本信息。

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
UNLINK 断开连接关系 1.0

其中最常用的就是GET方法和POST方法。

GET方法一般用于获取某种资源信息,而POST方法一般用于将数据上传给服务器。但实际我们上传数据时也有可能使用GET方法,比如百度提交数据时实际使用的就是GET方法。

GET方法和POST方法都可以带参:

  • GET方法是通过url传参的。
  • POST方法是通过正文传参的。

但是GET方法会将参数回显到url中,POST方法不会将你的参数回显到url当中。

接下来我们可以在index.html当中再加入两个表单,用作用户名和密码的输入,然后再新增一个提交按钮,此时就可以让浏览器提交参数了。
HTTP协议_第14张图片
当前我们是用GET方法提交参数的,当我们填充完用户名和密码进行提交时,我们的用户名和密码就会自动被同步到url当中。
HTTP协议_第15张图片

同时在服务器这边也通过url收到了刚才我们在浏览器提交的参数。

HTTP协议_第16张图片

如果我们将提交表单的方法改为POST方法,此时当我们填充完用户名和密码进行提交时,对应提交的参数就不会在url当中体现出来,而会通过正文将这两个参数传递给了服务器。
HTTP协议_第17张图片

此时用户名和密码就通过正文的形式传递给服务器了。

HTTP协议_第18张图片
注意

对于私密的数据传输,我们可以彩印POST方法,并不是因为POST方法是安全的,他只是对于GET方法来说私密性更好,GET方法和POST方法都属于明文传输,所以都不安全,但是POST方法更为私密。

HTTP的状态码

HTTP的状态码如下:

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

最常见的状态码,比如200(OK),404(Not Found),403(Forbidden请求权限不够),302(Redirect),504(Bad Gateway)。

重定向状态码(Redirection)

重定向就是通过各种方法将各种网络请求重新定个方向转到其它位置,此时这个服务器相当于提供了一个引路的服务,重定向又可分为临时重定向和永久重定向,其中状态码301表示的就是永久重定向,而状态码302和307表示的是临时重定向。

临时重定向和永久重定向本质是影响客户端的标签,决定客户端是否需要更新目标地址。如果某个网站是永久重定向,那么第一次访问该网站时由浏览器帮你进行重定向,但后续再访问该网站时就不需要浏览器再进行重定向了,此时你访问的直接就是重定向后的网站。如果某个网站是临时重定向,那么每次访问该网站时如果需要进行重定向,都需要浏览器来帮我们完成重定向跳转到目标网站。

接下来我们来惊喜临时重定向的演示:

这里要演示临时重定向,可以将HTTP响应当中的状态码改为307,然后跟上对应的状态码描述,此外,还需要在HTTP响应报头当中添加Location字段,这个Location后面跟的就是你需要重定向到的网页,比如我们这里将其设置为百度的首页。

#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include "HttpServer.hpp"
#include "Usage.hpp"
#include "Util.hpp"

// 一般http都要有自己的web根目录
#define ROOT "./wwwroot" 
// 如果客户端只请求了一个/,我们返回默认首页
#define HOMEPAGE "index.html"

void HandlerHttpRequest(int sockfd)
{
    // 1. 读取请求 
    char buffer[10240];
    ssize_t s = recv(sockfd, buffer, sizeof(buffer) - 1, 0);
    if (s > 0)
    {
        buffer[s] = 0;
        std::cout << buffer << "--------------------\n"
                  << std::endl;
    }

    std::vector<std::string> vline;
    Util::cutString(buffer, "\n", &vline);

    std::vector<std::string> vblock;
    Util::cutString(vline[0], " ", &vblock);

    std::string file = vblock[1];
    std::string target = ROOT;

    if (file == "/")
        file = "/index.html";
    target += file;
    std::cout << target << std::endl;

    std::string content;
    std::ifstream in(target);
    if (in.is_open())
    {
        std::string line;
        while (std::getline(in, line))
        {
            content += line;
        }
        in.close();
    }

    std::string HttpResponse;
    if (content.empty())
    {
        HttpResponse = "HTTP/1.1 307 Moved Permanently\n";
        HttpResponse += "Location: https://www.baidu.com/\n";
    }
    else
    {
        HttpResponse = "HTTP/1.1 200 OK\n";
    }
    HttpResponse += "\n";
    HttpResponse += content;

    // 2. 试着构建一个http的响应
    send(sockfd, HttpResponse.c_str(), HttpResponse.size(), 0);
}

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(0);
    }
    std::unique_ptr<HttpServer> httpserver(new HttpServer(atoi(argv[1]), HandlerHttpRequest));
    httpserver->Start();
    return 0;
}

此时使用我们的浏览器访问服务器,浏览器收到这个HTTP响应后,还会对这个HTTP响应进行分析,当浏览器识别到状态码是307后就会提取出Location后面的网址,然后继续自动对该网站继续发起请求,此时就完成了页面跳转这样的功能,这样就完成了重定向功能。
HTTP协议_第19张图片

HTTP常见的Header

HTTP常见的Header如下:

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

Content-Type和Content-Length

比如我们此时想知道数据类型和正文长度,我们只需要获取就可以了。

void HandlerHttpRequest(int sockfd)
{
    // 1. 读取请求
    char buffer[10240];
    ssize_t s = recv(sockfd, buffer, sizeof(buffer) - 1, 0);
    if (s > 0)
    {
        buffer[s] = 0;
        std::cout << buffer << "--------------------\n"
                  << std::endl;
    }

    std::vector<std::string> vline;
    Util::cutString(buffer, "\n", &vline);

    std::vector<std::string> vblock;
    Util::cutString(vline[0], " ", &vblock);

    std::string file = vblock[1];
    std::string target = ROOT;

    if (file == "/")
        file = "/index.html";
    target += file;
    std::cout << target << std::endl;

    std::string content;
    std::ifstream in(target);
    if (in.is_open())
    {
        std::string line;
        while (std::getline(in, line))
        {
            content += line;
        }
        in.close();
    }

    std::string HttpResponse;
    if (content.empty())
    {
        HttpResponse = "HTTP/1.1 307 Moved Permanently\n";
        HttpResponse += "Location: https://www.baidu.com/\n";
    }
    else
    {
        HttpResponse = "HTTP/1.1 200 OK\n";
        HttpResponse += ("Content-Type: text/html\n");
        HttpResponse += ("Content-Length: " + std::to_string(content.size()) + "\n");
    }
    HttpResponse += "\n";
    HttpResponse += content;

    // 2. 试着构建一个http的响应
    send(sockfd, HttpResponse.c_str(), HttpResponse.size(), 0);
}

使用telnet命令连接服务器,我们就会发现我们的数据类型和正文长度就显示出来:
HTTP协议_第20张图片

Host

Host字段表明了客户端要访问的服务的IP和端口,比如当浏览器访问我们的服务器时,浏览器发来的HTTP请求当中的Host字段填的就是我们的IP和端口。

User-Agent

User-Agent代表的是客户端对应的操作系统和浏览器的版本信息。

比如当我们用电脑下载某些软件时,它会自动向我们展示与我们操作系统相匹配的版本,这实际就是因为我们在向目标网站发起请求的时候,User-Agent字段当中包含了我们的主机信息,此时该网站就会向你推送相匹配的软件版本。

比如我们使用手机浏览器访问服务端,此时就显示出了操作系统和对应的版本信息。
HTTP协议_第21张图片

Referer

Referer代表的是你当前是从哪一个页面跳转过来的。Referer记录上一个页面的好处一方面是方便回退,另一方面可以知道我们当前页面与上一个页面之间的相关性。

Keep-Alive(长连接)

HTTP/1.0是通过request和reponse来进行请求和响应的,客户端与服务端建立连接,然后客户端发起请求给服务端,服务端进行响应,然后进行端口连接。此时进行交互,如果建立一次连接以后只进行一次交互,就关闭连接,此时就显得太浪费了,所以HTTP/1.0是支持长连接的,建立连接后,客户端可以不断的向服务器一次写入多个HTTP请求,而服务器在上层依次读取这些请求就行了,此时一条连接就可以传送大量的请求和响应,这就是长连接。

Cookie和Session

HTTP实际上是一种无状态协议,HTTP的每次请求/响应之间是没有任何关系的,但是我们我们在日常使用中发现并不是这样的。

比如我们平时登录B站,我们登录上去之后关闭浏览器,我们会发现我们在进入的时候并没有让我们再次登录,这就是使用 Cookie文件来实现的。

HTTP协议_第22张图片

Cookie是什么?

就像上面我们登录B站一样,如果我们是VIP用户,那么我们每一次点击页面都需要输入一次用户名和密码进行VIP用户认证,这样就显得太麻烦了,而我们的Cookie就支持记录用户状态,在某个时间段内我们并不需要频繁的进行登录操作,Cookie文件已经将我们的用户状态记录下来。

HTTP协议_第23张图片
当客户端发出request请求以后,服务端就会做出响应,此时服务器就会认证进行set-cookie设置,认证通过以后,就会响应给客户端,服务端收到响应后会自动提取出Set-Cookie的值,将其保存在浏览器的cookie文件当中,此时就相当于我的账号和密码信息保存在本地浏览器的cookie文件当中。

此后对端服务器需要对你进行认证时就会直接提取出HTTP请求当中的cookie字段,而不会重新让你输入账号和密码了。

内存级别&文件级别

cookie就是在浏览器当中的一个小文件,文件里记录的就是用户的私有信息。cookie文件可以分为两种,一种是内存级别的cookie文件,另一种是文件级别的cookie文件。

  • 将浏览器关掉后再打开,访问之前登录过的网站,如果需要你重新输入账号和密码,说明你之前登录时浏览器当中保存的cookie信息是内存级别的。
  • 将浏览器关掉甚至将电脑重启再打开,访问之前登录过的网站,如果不需要你重新输入账户和密码,说明你之前登录时浏览器当中保存的cookie信息是文件级别的。

也就是说,如果我们平时如果我们的cookie信息被非法盗取了,就意味着别人就可以通过你的身份去访问你曾今访问过的网站。

SessionID

单纯的使用cookie是非常不安全的,因为此时cookie文件当中就保存的是你的私密信息,一旦cookie文件泄漏你的隐私信息也就泄漏。

所以就引入了SessionID这个概念,当我们第一次访问服务器时就会认证成功就会生成一个唯一的SessionID,这个SessionID与用户信息是不相关的。系统会将所有登录用户的SessionID值统一维护起来。

此后服务端在对客户端进行HTTP响应时,就会将SessionID响应给客户端,客户端会将SessionID的值提取出来,保存到cookie文件当中,后续访问该服务器时,对应的HTTP请求当中就会自动携带上这个SessionID。
HTTP协议_第24张图片
但是有了SessionID以后并不意味着就是安全,尽管此时别人不会得到我们的账户和密码了,但是别人可以通过获取SessionID来去访问我们曾今访问过的服务器,所以说这也是相对安全的。

你可能感兴趣的:(http,网络,linux)