写点什么

Linux 网络 -HTTP 协议

作者:可口也可樂
  • 2022-10-25
    湖南
  • 本文字数:7148 字

    阅读完需:约 23 分钟

@TOC

零、前言

在此之前我们对网络套接字编程有了一定的基础和了解,接下来我们将自顶向下学习 Linux 网络分层协议栈,透过对协议栈的深入学习从而加深我们对网络的理解

HTTP 协议

  • 概念及介绍:


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

  2. 在编写网络通信代码时,我们可以自己进行协议的定制,但实际有很多优秀的工程师早就已经写出了许多非常成熟的应用层协议,其中最典型的就是 HTTP 协议

1、认识 URL

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


  • 示图:



  • 一个 URL 大致由如下几部分构成:


  1. 协议方案名称


  1. 协议名称表示请求时需要使用的协议,通常使用的是 HTTP 协议或安全协议 HTTPS

  2. HTTPS 是以安全为目标的 HTTP 通道,在 HTTP 的基础上通过传输加密和身份认证保证了传输过程的安全性


  1. 登录信息


  1. 登录认证信息包括登录用户的用户名和密码,登录认证信息可以在 URL 中体现出来

  2. 绝大多数 URL 的这个字段都是被省略的,因为登录信息可以通过其他方案交付给服务器


  1. 服务器地址


  1. 服务器地址也叫做域名,比如www.alibaba.comwww.qq.comwww.baidu.com

  2. 在计算机的世界中用 IP 地址标识公网内的一台主机,但 IP 地址是一串数字并不适合用户使用,为了方便用户从而有了具有更好的自描述性的域名

  3. 实际上域名和 IP 地址是等价的,在计算机当中使用的时候既可以使用域名,也可以使用 IP 地址


  • ping命令获取域名解析后的 IP 地址:



  1. 服务器端口号


  1. HTTP 协议和套接字编程一样都是位于应用层的,进行网络数据传输时需要主动确定服务端的 ip 和 port

  2. 常用的服务与端口号之间的对应关系都是明确的,所以使用时不要指明该协议对应的端口号的,而 URL 中也通常省略服务器的端口号


  1. 带层次的文件路径


要获取(访问)的应用资源的路径,即资源的存储位置,一般会使用“/”来分级描述


  • 注意:


  1. 比如我们打开浏览器输入百度的域名后,此时浏览器就帮我们获取到了百度的首页,我们可以将这种资源称为网页资源,此外我们还会向服务器请求视频、音频、网页、图片等资源

  2. HTTP 之所以叫做超文本传输协议,而不叫做文本传输协议,就是因为有很多资源实际并不是普通的文本资源

  3. 从这里的路径分隔符,我们可以分辨服务器的平台:Linux 的路径分隔符是/,Windows 的路径分隔符是\


  1. 查询字符串


用于获取资源时,向服务器端传递参数,可以一个或多个,多个则以”&”连接,通常以“?”作为开始符号,例如例子“?q=java”表示传递的搜索参数 java,即该应用 url 表示搜索 java 方面的内容


  1. 片段标识符


也叫做哈希值,通常以 #开始,表示定位到页面某个位置(或者说定位到页面的锚点,熟悉前端的人应该知道锚点是指页面某个部分的 id),这部分内容不传到服务器端,而是用于前端页面定位显示

2、urlencode 和 urldecode

  • 概念及介绍:


  1. 像 / ? : 等这样的字符, 已经被 url 当做特殊意义理解了,因此这些字符不能随意出现

  2. 如某个参数中需要带有这些特殊字符, 就必须先对特殊字符进行转义


  • 示例:



  • 转义规则:


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

3、HTTP 协议格式

1)HTTP 请求

  • 请求格式示图:



  • 请求格式组成:


  1. 首行: [方法] + [url] + [版本]

  2. Header: 请求的属性, 冒号分割的键值对;每组属性之间使用\n 分隔;遇到空行表示 Header 部分结束

  3. Body: 空行后面的内容都是 Body. Body 允许为空字符串. 如果 Body 存在, 则在 Header 中会有一个

  4. Content-Length 属性来标识 Body 的长度

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


  • 示例获取 HTTP 请求:


  1. 用套接字编写一个 TCP 服务器,使用浏览器访问服务器的 ip 和 port,也就是使用浏览器发起 http 请求

  2. 服务端不对这个 HTTP 请求进行过任何解析,直接将 http 请求进行打印输出


  • http 服务器代码:


http_server.hpp:#pragma once#include<iostream>#include<unistd.h>#include<sys/socket.h>#include<sys/types.h>#include<netinet/in.h>#include<arpa/inet.h>#include<pthread.h>#include<strings.h>namespace ns_Http{    class HttpServer    {    private:        uint16_t port;        int listen_sock;    public:        HttpServer(uint16_t _port):port(_port),listen_sock(-1){}        ~HttpServer()        {            if(listen_sock>=0) close(listen_sock);        }        void InitHttpServer()        {            listen_sock=socket(AF_INET,SOCK_STREAM,0);            if(listen_sock<0)                exit(2);            struct sockaddr_in local;            socklen_t len=sizeof(local);            bzero(&local,len);            local.sin_family=AF_INET;            local.sin_port=htons(port);            local.sin_addr.s_addr=INADDR_ANY;            if(bind(listen_sock,(struct sockaddr*)&local,len)<0)                exit(3);            if(listen(listen_sock,5)<0)                exit(4);        }        static void* Routine(void* args)        {            int sock=*((int*)args);            delete (int*)args;            pthread_detach(pthread_self());                        //逻辑处理            std::cout<<">-------------------------------begin-------------------------------<sock:"<<sock<<std::endl;            char buffer[1024]={0};            ssize_t s=recv(sock,buffer,sizeof(buffer)-1,0);            if(s>0)            {                std::cout<<buffer<<std::endl;            }            std::cout<<">--------------------------------end--------------------------------<sock:"<<sock<<std::endl;            close(sock);            return nullptr;        }         void Loop()        {            while(true)            {                struct sockaddr_in peer;                socklen_t len=sizeof(peer);                bzero(&peer,len);                int sock=accept(listen_sock,(struct sockaddr*)&peer,&len);                if(sock<0)                    continue;                pthread_t tid;                int* p=new int(sock);                pthread_create(&tid,nullptr,Routine,p);            }        }    };}http_server.cc:#include "http_server.hpp"
int main(int argc,char* argv[]){ if(argc!=2) { std::cout<<"Usage:\n\t./http_server port"<<std::endl; exit(1); } ns_Http::HttpServer hs(atoi(argv[1])); hs.InitHttpServer(); hs.Loop(); return 0;}
复制代码


  • 效果:



  • 示图:



  • 请求行格式:


[请求方法]  [url]  [版本] (空格分开)
复制代码


  • 请求报头格式:


Name:[空格]内容(一行就是一个属性,这里的“行”是以换行符作为标准)
复制代码


  • 请求报头内容组成:


  1. Host :请求的资源在哪个主机的端口上

  2. Connection:该请求支持长连接(heep_alive)

  3. Content-Length:正文内容长度

  4. Content-Type:数据类型

  5. User-Agent:声明用户的操作系统和浏览器版本信息

  6. Accent:发起了请求

  7. Referer:当前页面是从哪个页面跳转过来的

  8. Accept-Encoding:接受的编码

  9. Accept-Language:接受的语言类型

  10. Cookie:用于在客户端存储少量信息,通常用于实现会话(session)功能


  • HTTP 如何进行解包:


  1. 请求行和请求报头是 HTTP 的报头信息,而这里的请求正文实际就是 HTTP 的有效载荷,而请求当中的空行起到分离报头和有效载荷的作用

  2. 读取一个请求时,通过报头中的 Content-Length(正文的长度)来精准控制读取该请求正文的长度,从而将连续的几个请求进行分开


  • HTTP 如何进行分用:


理论上 HTTP 不需要向上交付,HTTP 已经是最上一层的协议,但是上一层还有用户,需要将正文、请求方法和属性等交给用户

2)HTTP 响应

  • 响应格式示图:



  • 响应格式组成:


  1. 首行:[版本号] + [状态码] + [状态码解释]

  2. Header:请求的属性,冒号分割的键值对;每组属性之间使用\n 分隔;遇到空行表示 Header 部分结束

  3. Body:空行后面的内容都是 Body,Body 允许为空字符串,如果 Body 存在,则在 Header 中会有一个 Content-Length 属性来标识 Body 的长度;如果服务器返回了一个 html 页面,那么 html 页面内容就是在 body 中


  • 获取响应示例:http 服务器代码构建响应


    std::string response;    std::ifstream in(HOME_PAGE,std::ios::in|std::ios::binary);    std::string body;    std::string line;    if(!in.is_open())//打开失败则发送404页面    {          std::ifstream _in(PAGE_404,std::ios::in|std::ios::binary);        while(getline(_in,line))            body+=line;        response += "HTTP/1.0 404 Not Found\n";        response += "Content-Type: text/html\n";        response += ("Content-Length: " + std::to_string(body.size()) + "\n");        response += "\n";        response += body;        send(sock, response.c_str(), response.size(), 0);    }    else//打开成功则发送首页页面    {        while(getline(in,line))            body+=line;        response += "HTTP/1.0 200 OK\n";        response += "Content-Type: text/html\n";        response += ("Content-Length: " + std::to_string(body.size()) + "\n");        response += "\n";        response += body;        send(sock, response.c_str(), response.size(), 0);    }
复制代码


  • 适用网页获取响应:



  • 使用 postman 进行 GET 方法获取响应:



  • 使用 telnet 命令获取响应:



注:客户端在发起 HTTP 请求是会告诉服务器自己所使用的 http 版本,此时服务器就可以根据客户端使用的 http 版本,为客户端提供对应的服务,而不至于因为双方使用的 http 版本不同而导致无法正常通信

4、HTTP 的方法

  • HTTP 常见的方法:



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


  • GET 方法和 POST 方法对比:


  1. GET 方法一般用于获取某种资源信息,而 POST 方法一般用于将数据上传给服务器,上传数据时也有可能使用 GET 方法,比如搜索提交数据时

  2. GET 方法和 POST 方法都可以带参:GET 方法是通过 url 传参的;POST 方法是通过正文传参的

  3. POST 方法通过正文传参能传递更多的参数,而 url 的长度是有限,所以 GET 方式传参有限

  4. POST 方法传参更加私密,因为 GET 方法会将参数回显到 url 当中,POST 方法在正文中不会被别人轻易看到。但是实际两种方法都不安全,POST 方法传参可以被截取,要做到安全只能通过加密来完成


  • 参数提交 GET 和 post 方式演示:



注:表单当中的 method 属性指定参数提交的方法,action 属性表示将表单中的参数提交给服务器上的哪个资源位置


  • GET 方式示图:



  • post 方式示图:


5、HTTP 的状态码

在开发好了网站后,用户通过 URL 对资源进行操作,服务器端要告诉用户交互的结果,比如新增资源是成功还是失败了。一个较好的办法就是遵循 HTTP 协议,使用请求响应的 HTTP状态码(Status Code)来进行判断


  • HTTP 的状态码:



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


  • 常见的状态码有:


  1. 200 OK:客户端请求成功

  2. 301 Permanent Redirect:永久重定向,表示资源已经永久移动到另一个位置

  3. 307/302 Temporary Redirect:临时重定向,表示资源临时移动到了另一个位置

  4. 403 Forbidden:指的是服务器端有能力处理该请求,但是拒绝授权访问

  5. 404 Not Found:请求资源不存在,比如资源被删除了,或用户输入了错误的 URL

  6. 500 Internal Server Error:服务器发生不可预期的错误,一般是代码的 BUG 所导致的

  7. 502 Bad Gateway:表示作为网关或代理角色的服务器,从上游服务器(如 tomcat、php-fpm)中接收到的响应是无效的


  • 重定向状态码:


  1. 重定向就是通过各种方法将各种网络请求重新定个方向转到其它位置,此时这个服务器相当于提供了一个引路的服务

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

  3. 永久重定向第一次访问浏览器进行重定向,并且更新客户端的标签,后续再访问直接就是重定向后的网站;临时重定向,每次访问该网站时都需要浏览器来帮我们完成重定向跳转到目标网站


  • 临时重定向演示:


进行临时重定向时需要用到 Location 字段,Location 字段是 HTTP 报头当中的一个属性信息,该字段表明了你所要重定向到的目标网站


  • 构建临时重定向 http 响应代码:


    //构建HTTP响应    std::string response = "HTTP/1.0 307 Temporary Redirect\n"; //状态行    response += "Location: https://coca1cole.blog.csdn.net/\n"; //跳转页面    response += "\n"; //空行
send(sock, response.c_str(), response.size(), 0); close(sock);
复制代码


  • 效果:



6、HTTP 常见的 Header

  • HTTP 常见的 Header:


  1. Content-Type:数据类型(text/html 等)

  2. Content-Length:正文的长度

  3. Host:客户端告知服务器,所请求的资源是在哪个主机的哪个端口上

  4. 注:Host 字段表明了客户端要访问的服务的 IP 和端口,有些服务器实际提供的是一种代理服务,也就是代替客户端向其他服务器发起请求,然后将请求得到的结果再返回给客户端,在这种情况下客户端就必须告诉代理服务器它要访问的服务对应的 IP 和端口

  5. User-Agent:声明用户的操作系统和浏览器的版本信息

  6. 注:User-Agent 代表的是客户端对应的操作系统和浏览器的版本信息,访问一些网站是就会根据主机的版本和系统推送相匹配的服务

  7. Referer:当前页面是哪个页面跳转过来的

  8. 注:Referer 记录上一个页面的好处一方面是方便回退,另一方面可以知道我们当前页面与上一个页面之间的相关性

  9. Location:搭配 3XX 状态码使用,告诉客户端接下来要去哪里访问

  10. Cookie:用于在客户端存储少量信息,通常用于实现会话(session)的功能

  11. Keep-Alive(长连接):

  12. HTTP/1.0 是通过 request&response 的方式来进行请求和响应的,HTTP/1.0 常见的工作方式就是客户端和服务器先建立链接,然后客户端发起请求给服务器,服务器再对该请求进行响应,然后立马端口连接

  13. 现在主流的 HTTP/1.1 是支持长连接的,所谓的长连接就是建立连接后,客户端可以不断的向服务器一次写入多个 HTTP 请求,而服务器在上层依次读取这些请求就行了,此时一条连接就可以传送大量的请求和响应

7、Cookie 和 Session

  • 概念及介绍:


  1. HTTP 实际上是一种无状态协议,HTTP 的每次请求/响应之间是没有任何关系的,但你在使用浏览器的时候发现并不是这样的

  2. 当你登录一次能某网站账号后,再将网站关了甚至是重启电脑,再次网站时并没有要求你再次输入账号和密码(账号还是登录好的状态),这实际上是通过 cookie 技术实现的


  • cookie 技术原理:


  1. 因为 HTTP 是一种无状态协议,每次进行 http 请求时都不会保存之前的一种页面转态(比如用户登录),所以每当都要需要重新输入账号和密码进行认证(客户端提交账号和密码参数进行认证)

  2. 而 cookie 是内置到 HTTP 协议当中的一种保存状态技术,当认证通过后服务端响应给客户端进行 Set-Cookie,客户端收到响应后会自动将 Set-Cookie 的值保存在 cookie 文件当中,接下来每次进行 http 请求的同时都会将之前页面的 cookie 参数一同进行提交,从而达到了之前状态的保存的效果


  • cookie 的弊端:


cookie 虽然在持久保存客户端数据提供了方便,但是如果 cookie 被人拦截了,那人就可以取得期中的参数信息。如果是账号和密码,那么就存在账号被盗以及账号被利用做坏事


  • session 技术及原理:


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

  2. 当我们第一次登录某个网站输入账号和密码后,服务器认证成功后会生成一个哈希出来的 SessionID,这个 SessionID 与用户信息是不相关的,系统会将所有登录用户的账号和 SessionID 值维护起来

  3. 当认证通过后服务端会将这个生成的 SessionID 值响应给客户端,客户端收到响应后会自动提取出 SessionID 的值并保存在浏览器的 cookie 文件当中,后续访问该服务器时,对应的 HTTP 请求当中就会自动携带上这个 SessionID 进行身份验证

  4. 而服务器识别到 HTTP 当中的 SessionID,再到对应的数据库当中进行对比,对比成功就说明这个用户是曾经登录过的,即认证成功


注:引入 session 技术后,浏览器当中的 cookie 文件保存的是 SessionID,同样的这个 cookie 文件可能被盗取,但是账号和密码并不会被泄漏,而是对应的 SessionID 是会泄漏的,此时非法用户仍然可以盗取我的 SessionID 去访问我曾经访问过的服务器,相当于依旧存在利用 SessionId 进行账号登录并利用账号做坏事


  • cookie 和 session 的区别:


  1. cookie 将数据存放在客户的浏览器上;session 将数据放在服务器中,将 sessionid 存在客户端中

  2. cookie 不是很安全,别人可以分析存放在本地的 cookie 并进行 cookie 欺骗,考虑到安全应当加入 session 技术

  3. session 会在一定时间内保存在服务器上,当访问增多会占用服务器的性能,考虑到减轻服务器性能方面应当使用 cookie

  4. 建议将登陆信息等重要信息存放为 session,其他信息如果需要保留可以放在 cookie 中


  • cookie 技术的演示:


在服务器给客户端的 HTTP 响应当中设置 Set-Cookie 字段,即使用 cookie 技术


  • 构建响应代码:


    response += "HTTP/1.0 200 OK\n";    response += "Content-Type: text/html\n";    response += ("Content-Length: " + std::to_string(body.size()) + "\n");    response += "Set-Cookie: sessionid=123456\n"; //添加Set-Cookie字段    response += "\n";    response += body;    send(sock, response.c_str(), response.size(), 0);
复制代码


  • 运行效果:



发布于: 刚刚阅读数: 3
用户头像

还未添加个人签名 2022-04-28 加入

还未添加个人简介

评论

发布
暂无评论
Linux网络-HTTP协议_Linux_可口也可樂_InfoQ写作社区