应用层协议 ---- HTTP协议

HTTP协议

HTTP简介

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

认识URL

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

在这里插入图片描述

一、协议方案名

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 SecureSocket Layer)协议:安全数据传输协议。
  • SMTP(Simple Mail Transfer Protocol)协议:电子邮件传输协议。
  • POP3(Post Office Protocol - Version 3)协议:邮件读取协议。
  • SNMP(Simple Network Management Protocol)协议:简单网络管理协议。
  • TFTP(Trivial File Transfer Protocol)协议:简单文件传输协议。

二、登录信息

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

三、服务器地址

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

需要注意的是,我们用IP地址标识公网内的一台主机,但IP地址本身并不适合给用户看。比如说我们可以通过ping命令,分别获得www.baidu.com域名解析后的IP地址。

在这里插入图片描述
实际我们可以认为域名和IP地址是等价的,在计算机当中使用的时候既可以使用域名,也可以使用IP地址。但URL呈现出来是可以让用户看到的,因此URL当中是以域名的形式表示服务器地址的。

四、服务器端口号

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

五、带层次的文件路径

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

六、查询字符串

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

比如我们在百度上面搜索HTTP,此时可以看到URL中有很多参数,而在这众多的参数当中有一个参数wd(word),表示的就是我们搜索时的搜索关键字wd=HTTP。
在这里插入图片描述

七、片段标识符

在这里插入图片描述

urlencode和urldecode

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

转义的规则如下:

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

例如:** “+” 被转义成了 “%2B”**
在这里插入图片描述

HTTP协议格式

应用层常见的协议有HTTP和HTTPS,传输层常见的协议有TCP,网络层常见的协议是IP,数据链路层对应就是MAC帧了。其中下三层是由操作系统或者驱动帮我们完成的,它们主要负责的是通信细节。如果应用层不考虑下三层,在应用层自己的心目当中,它就可以认为自己是在和对方的应用层在直接进行数据交互。
在这里插入图片描述
HTTP是基于请求和响应的应用层服务,作为客户端,你可以向服务器发起request,服务器收到这个request后,会对这个request做数据分析,得出你想要访问什么资源,然后服务器再构建response,完成这一次HTTP的请求。这种基于request&response这样的工作方式,我们称之为cs或bs模式,其中c表示client,s表示server,b表示browser。

由于HTTP是基于请求和响应的应用层访问,因此我们必须要知道HTTP对应的请求格式和响应格式,这就是学习HTTP的重点。

HTTP请求协议格式

在这里插入图片描述
HTTP请求由以下四部分组成:

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

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

在这里插入图片描述

如何将HTTP请求的报头与有效载荷进行分离?

根据HTTP请求当中的空行来进行分离,当服务器收到一个HTTP请求后,就可以按行进行读取,如果读取到空行则说明已经将报头读取完毕,实际HTTP请求当中的空行就是用来分离报头和有效载荷的。

获取浏览器的HTTP请求

由于我们的服务器是直接用TCP套接字读取浏览器发来的HTTP请求,此时在服务端没有应用层对这个HTTP请求进行过任何解析,因此我们可以直接将浏览器发来的HTTP请求进行打印输出,此时就能看到HTTP请求的基本构成。

在这里插入图片描述
因此下面我们编写一个简单的TCP服务器,这个服务器要做的就是把浏览器发来的HTTP请求进行打印即可。

// ./server port
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        std::cout << "Usage : " << argv[0] << " port" << std::endl;
        return 0;
    }
    uint16_t localport = std::stoi(argv[1]);

    // 创建套接字
    int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_sock < 0)
    {
        std::cerr << "socket error!" << std::endl;
        return 1;
    }
    // 绑定
    struct sockaddr_in local;
    memset(&local, 0, sizeof(local));
    local.sin_family = AF_INET;
    local.sin_port = htons(localport);
    local.sin_addr.s_addr = INADDR_ANY;
    if (bind(listen_sock,(struct sockaddr*)&local, sizeof(local)) < 0)
    {
        std::cerr << "bind error!" << std::endl;
        return 2;
    }

    // 监听
    if (listen(listen_sock, 5) < 0)
    {
        std::cerr << "listen error!" << std::endl;
        return 3;
    }
    // 启动服务器
    struct sockaddr_in peer;
    memset(&peer, 0, sizeof(peer));
    socklen_t len = sizeof(peer);
    for (;;)
    {
        int sock = accept(listen_sock, (struct sockaddr *)&peer, &len);
        if (sock < 0)
        {
            std::cerr << "accept error!" << std::endl;
            continue;
        }

        if (fork() == 0) // 爷爷进程
        {
            close(listen_sock);
            if (fork() > 0) // 爸爸进程
                exit(0);
            // 孙子进程
            char buffer[1024];
            recv(sock, buffer, sizeof(buffer), 0); // 读取HTTP请求
            std::cout << "--------------------------http request begin--------------------------" << std::endl;
            std::cout << buffer << std::endl;
            std::cout << "---------------------------http request end---------------------------" << std::endl;
            close(sock);
            exit(0);
        }
        //爷爷进程
        close(sock);
        waitpid(-1,nullptr,0);//等待爸爸进程
    }
    return 0;
}

运行服务器程序后,然后用浏览器进行访问,此时我们的服务器就会收到浏览器发来的HTTP请求,并将收到的HTTP请求进行打印输出。
在这里插入图片描述
在这里插入图片描述
说明:

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

其中请求行当中的url一般是不携带域名以及端口号的,因为在请求报头中的Host字段当中会进行指明,请求行当中的url表示你要访问这个服务器上的哪一路径下的资源。如果浏览器在访问我们的服务器时指明要访问的资源路径,那么此时浏览器发起的HTTP请求当中的url也会跟着变成该路径。
在这里插入图片描述
在这里插入图片描述
而请求报头当中全部都是以key: value形式按行陈列的各种请求属性,请求属性陈列完后紧接着的就是一个空行,空行后的就是本次HTTP请求的请求正文,此时请求正文为空字符串,因此这里有两个空行。

HTTP响应协议格式

在这里插入图片描述
HTTP响应由以下四部分组成:

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

构建HTTP响应给浏览器

接下来我们可以构建一个HTTP请求给浏览器,鉴于现在还没有办法分析浏览器发来的HTTP请求,这里我们可以给浏览器返回一个固定的HTTP响应。我们就将当前服务程序所在的路径作为我们的web根目录,我们可以在该目录下创建一个html文件,然后编写一个简单的html作为当前服务器的首页。

在这里插入图片描述

// ./server port
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        std::cout << "Usage : " << argv[0] << " port" << std::endl;
        return 0;
    }
    uint16_t localport = std::stoi(argv[1]);

    // 创建套接字
    int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_sock < 0)
    {
        std::cerr << "socket error!" << std::endl;
        return 1;
    }
    // 绑定
    struct sockaddr_in local;
    memset(&local, 0, sizeof(local));
    local.sin_family = AF_INET;
    local.sin_port = htons(localport);
    local.sin_addr.s_addr = INADDR_ANY;
    if (bind(listen_sock,(struct sockaddr*)&local, sizeof(local)) < 0)
    {
        std::cerr << "bind error!" << std::endl;
        return 2;
    }

    // 监听
    if (listen(listen_sock, 5) < 0)
    {
        std::cerr << "listen error!" << std::endl;
        return 3;
    }
    // 启动服务器
    struct sockaddr_in peer;
    memset(&peer, 0, sizeof(peer));
    socklen_t len = sizeof(peer);
    for (;;)
    {
        int sock = accept(listen_sock, (struct sockaddr *)&peer, &len);
        if (sock < 0)
        {
            std::cerr << "accept error!" << std::endl;
            continue;
        }

        if (fork() == 0) // 爷爷进程
        {
            close(listen_sock);
            if (fork() > 0) // 爸爸进程
                exit(0);
            // 孙子进程
            char buffer[1024];
            recv(sock, buffer, sizeof(buffer), 0); // 读取HTTP请求
            std::cout << "--------------------------http request begin--------------------------" << std::endl;
            std::cout << buffer << std::endl;
            std::cout << "---------------------------http request end---------------------------" << std::endl;

#define PATH "index.html" //网站首页
            //读取index.html文件
            std::ifstream in(PATH);
            if(in.is_open())
            {
                in.seekg(0,in.end);
                int len = in.tellg();
                in.seekg(0,in.beg);
                char* file = new char[len];
                in.read(file,len);
                in.close();

                //构建HTTP响应
				std::string status_line = "http/1.1 200 OK\n"; //状态行
				std::string response_header = "Content-Length: " + std::to_string(len) + "\n"; //响应报头
				std::string blank = "\n"; //空行
				std::string response_text = file; //响应正文
				std::string response = status_line + response_header + blank + response_text; //响应报文

                //响应HTTP请求
                send(sock,response.c_str(),response.size(),0);
                delete[] file;
            }
            close(sock);
            exit(0);
        }
        //爷爷进程
        close(sock);
        waitpid(-1,nullptr,0);//等待爸爸进程
    }
    return 0;
}

因此当浏览器访问我们的服务器时,服务器会将这个index.html文件响应给浏览器,而该html文件被浏览器解释后就会显示出相应的内容。
在这里插入图片描述
在这里插入图片描述
说明一下:

  • 实际我们在进行网络请求的时候,如果不指明请求资源的路径,此时默认你想访问的就是目标网站的首页,也就是web根目录下的index.html文件。
  • 由于只是作为示例,我们在构建HTTP响应时,在响应报头当中只添加了一个属性信息Content-Length,表示响应正文的长度,实际HTTP响应报头当中的属性信息还有很多。

HTTP为什么要交互版本?

HTTP请求当中的请求行和HTTP响应当中的状态行,当中都包含了http的版本信息。其中HTTP请求是由客户端发的,因此HTTP请求当中表明的是客户端的http版本,而HTTP响应是由服务器发的,因此HTTP响应当中表明的是服务器的http版本。

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

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

HTTP的方法

在这里插入图片描述
其中最常用的就是GET方法和POST方法。

GET方法和POST方法

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

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

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

从GET方法和POST方法的传参形式可以看出,POST方法能传递更多的参数,因为url的长度是有限制的,POST方法通过正文传参就可以携带更多的数据。

此外,使用POST方法传参更加私密,因为POST方法不会将你的参数回显到url当中,此时也就不会被别人轻易看到。不能说POST方法比GET方法更安全,但是实际上POST方法和GET方法实际都不安全,要做到安全只能通过加密来完成。

HTTP的状态码

在这里插入图片描述
最常见的状态码,比如200(OK),404(Not Found),403(Forbidden请求权限不够),302(Redirect),504(Bad Gateway)。

Redirection(重定向状态码)

重定向又可分为临时重定向和永久重定向,其中状态码301表示的就是永久重定向,而状态码302和307表示的是临时重定向。

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

临时重定向演示

// ./server port
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        std::cout << "Usage : " << argv[0] << " port" << std::endl;
        return 0;
    }
    uint16_t localport = std::stoi(argv[1]);

    // 创建套接字
    int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_sock < 0)
    {
        std::cerr << "socket error!" << std::endl;
        return 1;
    }
    // 绑定
    struct sockaddr_in local;
    memset(&local, 0, sizeof(local));
    local.sin_family = AF_INET;
    local.sin_port = htons(localport);
    local.sin_addr.s_addr = INADDR_ANY;
    if (bind(listen_sock,(struct sockaddr*)&local, sizeof(local)) < 0)
    {
        std::cerr << "bind error!" << std::endl;
        return 2;
    }

    // 监听
    if (listen(listen_sock, 5) < 0)
    {
        std::cerr << "listen error!" << std::endl;
        return 3;
    }
    // 启动服务器
    struct sockaddr_in peer;
    memset(&peer, 0, sizeof(peer));
    socklen_t len = sizeof(peer);
    for (;;)
    {
        int sock = accept(listen_sock, (struct sockaddr *)&peer, &len);
        if (sock < 0)
        {
            std::cerr << "accept error!" << std::endl;
            continue;
        }

        if (fork() == 0) // 爷爷进程
        {
            close(listen_sock);
            if (fork() > 0) // 爸爸进程
                exit(0);
            // 孙子进程
            char buffer[1024];
            recv(sock, buffer, sizeof(buffer), 0); // 读取HTTP请求
            std::cout << "--------------------------http request begin--------------------------" << std::endl;
            std::cout << buffer << std::endl;
            std::cout << "---------------------------http request end---------------------------" << std::endl;

#define PATH "index.html" //网站首页
            //读取index.html文件
            std::ifstream in(PATH);
            if(in.is_open())
            {
                in.seekg(0,in.end);
                int len = in.tellg();
                in.seekg(0,in.beg);
                char* file = new char[len];
                in.read(file,len);
                in.close();

                //构建HTTP响应
				std::string status_line = "http/1.1 307 OK\n"; //状态行
				//std::string response_header = "Content-Length: " + std::to_string(len) + "\n"; //响应报头
                std::string response_header = "Location: https://www.csdn.net/\n"; //响应报头
				std::string blank = "\n"; //空行
				std::string response_text = file; //响应正文
				std::string response = status_line + response_header + blank + response_text; //响应报文

                //响应HTTP请求
                send(sock,response.c_str(),response.size(),0);
                delete[] file;
            }
            close(sock);
            exit(0);
        }
        //爷爷进程
        close(sock);
        waitpid(-1,nullptr,0);//等待爸爸进程
    }
    return 0;
}

此时当浏览器访问我们的服务器时,就会立马跳转到CSDN的首页。
在这里插入图片描述
在这里插入图片描述

HTTP常见的Header

在这里插入图片描述

Host

Host字段表明了客户端要访问的服务的IP和端口,比如当浏览器访问我们的服务器时,浏览器发来的HTTP请求当中的Host字段填的就是我们的IP和端口。但客户端不就是要访问服务器吗?为什么客户端还要告诉服务器它要访问的服务对应的IP和端口?

因为有些服务器实际提供的是一种代理服务,也就是代替客户端向其他服务器发起请求,然后将请求得到的结果再返回给客户端。在这种情况下客户端就必须告诉代理服务器它要访问的服务对应的IP和端口,此时Host提供的信息就有效了。

Cookie和Session

当你登录一次CSDN后,就算你把CSDN网站关了甚至是重启电脑,当你再次打开CSDN网站时,CSDN并没有要求你再次输入账号和密码,这实际上是通过cookie技术实现的,点击浏览器当中锁的标志就可以看到对应网站的各种cookie数据。
在这里插入图片描述

cookie是什么呢?

当我们第一次登录某个网站时,需要输入我们的账号和密码进行身份认证,此时如果服务器经过数据比对后判定你是一个合法的用户,那么为了让你后续在进行某些网页请求时不用重新输入账号和密码,此时服务器就会进行Set-Cookie的设置。(Set-Cookie也是HTTP报头当中的一种属性信息)

当认证通过并在服务端进行Set-Cookie设置后,服务器在对浏览器进行HTTP响应时就会将这个Set-Cookie响应给浏览器。而浏览器收到响应后会自动提取出Set-Cookie的值,将其保存在浏览器的cookie文件当中,此时就相当于我的账号和密码信息保存在本地浏览器的cookie文件当中。
在这里插入图片描述
从第一次登录认证之后,浏览器再向该网站发起的HTTP请求当中就会自动包含一个cookie字段,其中携带的就是我第一次的认证信息,此后对端服务器需要对你进行认证时就会直接提取出HTTP请求当中的cookie字段,而不会重新让你输入账号和密码了。

内存级别&文件级别

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

  • 将浏览器关掉后再打开,访问之前登录过的网站,如果需要你重新输入账号和密码,说明你之前登录时浏览器当中保存的cookie信息是内存级别的。
  • 将浏览器关掉甚至将电脑重启再打开,访问之前登录过的网站,如果不需要你重新输入账户和密码,说明你之前登录时浏览器当中保存的cookie信息是文件级别的。
  • 30
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值