【网络】应用层-HTTP协议

网络-应用层-HTTP协议


以下内容引用于《图解HTTP》

HTTP协议

什么是HTTP协议

当我们在网页浏览器(Web browser)的地址栏中输入 URL 时,Web 页面是如何呈现的?

Web 页面当然不能凭空显示出来。根据 Web 浏览器地址栏中指定的 URL,Web 浏览器从 Web 服务器端获取文件资源(resource)等信息,从而显示出 Web 页面。
像这种通过发送请求获取服务器资源的 Web 浏览器等,都可称为客户端(client)。

在这里插入图片描述

Web 使用一种名为 HTTP(HyperText Transfer Protocol,超文本传输协议 )的协议作为规范,完成从客户端到服务器端等一系列运作流程。而协议是指规则的约定。可以说,Web 是建立在 HTTP 协议上通信的。

设计HTTP最初的目的是为了提供一种发布和接收HTML页面的方法

HTTP有多个版本,目前广泛使用的是HTTP/1.1版本


URL

URL(Uniform Resource Location)又叫统一资源定位符,俗称网址,URL格式如下

在这里插入图片描述

  • 协议方案名:描述通信所使用的协议
  • 认证信息:身份认证信息-用户名和密码,存在安全隐患,现在很少用了
  • 服务器地址(域名):一个便于记忆的字符串,最终通过域名解析得到服务器IP地址
  • 端口HTTP服务默认使用80端口,HTTPS默认使用443端口
  • 资源路径:/代表web根目录,不是服务器的根目录
  • 查询字符串: 客户端提交给服务器的少量数据,KV格式的数据
  • 片段标识符:HTML网页中的一个标签ID,可以让网页直接滑动到指定位置
urlencode和urldecode

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

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

例如我们在百度搜索C++的时候,C++就被转义成了C%2B%2B "+" 被转义成了 "%2B"

在这里插入图片描述

把原始内容转义的过程叫做urlencode

把转义后的内容转回原始内容的过程叫做urldecode

urldecode就是urlencode的逆过程

urlencode工具


HTTP基本特征

  • 无连接

HTTP协议是基于TCP协议之上的,虽然TCP协议是面向连接的,但是HTTP在通信时并不关心底层是如何通信的,TCP在底层已经建立好链接了,所以HTTP在通信时并不需要建立连接。TCP建立连接与HTTP无关,HTTP直接向对方发送 HTTP请求即可。

  • 无状态

HTTP本身是无状态的,并不会记录用户的任何信息

在这里插入图片描述

HTTP 协议自身不对请求和响应之间的通信状态进行保存。也就是说在 HTTP 这个级别,协议对于发送过的请求或响应都不做持久化处理

使用 HTTP 协议,每当有新的请求发送时,就会有对应的新响应产生。协议本身并不保留之前一切的请求或响应报文的信息。这是为了更快地处理大量事务,确保协议的可伸缩性,而特意把 HTTP 协议设计成如此简单的

可是,随着 Web 的不断发展,因无状态而导致业务处理变得棘手的情况增多了。比如,用户登录到一家购物网站,即使他跳转到该站的其他页面后,也需要能继续保持登录状态。针对这个实例,网站为了能够掌握是谁送出的请求,需要保存用户的状态

HTTP/1.1 虽然是无状态协议,但为了实现期望的保持状态功能,于是引入了 Cookie 技术。有了 Cookie 再用 HTTP 协议通信,就可以管理状态了。

  • 简单快速

HTTP协议版本

  • 0.9:不算成熟的版本,只有GET进行超文本数据传输,协议格式不完善
  • 1.0:规范了协议格式,新增支持GET/HEAD/POST方法,支持了多媒体数据流的传输
  • 1.1:支持更多请求方法以及头部字段,有了长连接管理,缓存管理等
  • 2.0:基于HTTP协议的臃肿,重新进行设计,解决了一些典型问题以及性能问题

1.0版本相较于0.9版本,主要规范了协议格式,支持了更多功能以及数据传输方式

1.1版本相较于1.0版本,主要在于性能上的改进,以及其他一些特殊功能的添加

缓存控制:一些资源在没有改变的情况下不需要重新传输

缓存是指代理服务器或客户端本地磁盘内保存的资源副本。利用缓存可减少对源服务器的访问,因此也就节省了通信流量和通信时间

长连接的改进

  • 短连接:http实际上是基于tcp的,短连接是建立连接,发送请求,得到响应,断开连接,非常简单清晰的请求-响应规范,但是跟不上性能

在这里插入图片描述

以当年的通信情况来说,因为都是些容量很小的文本传输,所以即使这样也没有多大问题。可随着 HTTP 的普及,文档中包含大量图片的情况多了起来。比如,使用浏览器浏览一个包含多张图片的 HTML页面时,在发送请求访问 HTML 页面资源的同时,也会请求该 HTML 页面里包含的其他资源。因此,每次的请求都会造成无谓的 TCP 连接建立和断开,增加通信量的开销。

在这里插入图片描述

  • 长连接(keep-alive)在一次连接中可以进行多次请求,并且1.1版本的长连接具有管线化管理思想

长连接的特点是,只要任意一端没有明确提出断开连接,则保持 TCP 连接状态

在这里插入图片描述

持久连接的好处在于减少了 TCP 连接的重复建立和断开所造成的额外开销减轻了服务器端的负载。另外,减少开销的那部分时间,使 HTTP 请求和响应能够更早地结束,这样 Web 页面的显示速度也就相应提高了。在 HTTP/1.1 中,所有的连接默认都是持久连接,但在 HTTP/1.0 内并未标准化。虽然有一部分服务器通过非标准的手段实现了持久连接,但服务器端不一定能够支持持久连接。毫无疑问,除了服务器端,客户端也需要支持持久连接。

管线化

长连接使得多数请求以管线化(pipelining)方式发送成为可能。从前发送请求后需等待并收到响应才能发送下一个请求。管线化技术出现后,不用等待响应亦可直接发送下一个请求。这样就能够做到同时并行发送多个请求,而不需要一个接一个地等待响应了

在这里插入图片描述

比如,当请求一个包含 10 张图片的 HTML Web 页面,与挨个连接相比,用持久连接可以让请求更快结束。而管线化技术则比持久连接还要快。请求数越多,时间差就越明显。


HTTP报文结构

用于 HTTP 协议交互的信息被称为 HTTP 报文。请求端(客户端)的 HTTP 报文叫做请求报文,响应端(服务器端)的叫做响应报文。HTTP 报文本身是由多行(用 CR+LF 也就是回车换行)数据构成的字符串文本。HTTP 报文大致可分为报文首部报文主体两块。两者由最初出现的空行(CR+LF)来划分。通常,并不一定要有报文主体。

HTTP请求报文和响应报文

用Fidder进行抓包,随便找一个请求和响应

在这里插入图片描述

在这里插入图片描述


报文结构分为以下

  • 首行[请求方法] + [url] + [协议版本]
  • Header:请求的属性, 冒号分割的键值对;每组属性之间使用\n分隔;遇到空行表示Header部分结束
  • Body:也就是正文,空行后面的内容都是Body. Body允许为空字符串. 如果Body存在, 则在Header中会有一个Content-Length属性来标识Body的长度;

在这里插入图片描述

报文首部包括首行和Header

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


HTTP常见的请求方法(GET/POST)

在这里插入图片描述

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


HTTP状态码

在这里插入图片描述

响应报头的响应行中就有状态码

在这里插入图片描述

最常见的状态码, 比如 200(OK), 404(Not Found), 403(Forbidden), 302(Redirect, 重定向), 504(Bad Gateway)

  • **200 **- OK - 客户端请求成功
  • 301 - 资源(网页等)被永久转移到其它URL
  • 302 - 临时跳转
  • 400 -Bad Request - 客户端请求有语法错误,不能被服务器所理解
  • 401 -Unauthorized - 请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用
  • 403 -服务器理解客户端请求,但是拒绝执行词请求
  • 404 - 请求资源不存在,可能是输入了错误的URL
  • 500 - 服务器内部发生了不可预期的错误
  • 503 -Server Unavailable - 服务器当前不能处理客户端的请求,一段时间后可能恢复正常。

HTTP常见首部字段

  • Content-Type: 数据类型(text/html等)
  • Content-Length: Body的长度
  • User-Agent: 声明用户的操作系统和浏览器版本信息
  • Host: 客户端告知服务器, 所请求的资源是在哪个主机的哪个端口上
  • location: 搭配3xx状态码使用, 告诉客户端接下来要去哪里访问(重定向后的url)
  • connection:长短连接(keep-alive 是长连接,close是短连接
  • referer:当前页面是从哪个页面跳转过来的
  • Accept-Encoding:接受的编码
  • Accept-Language:接受的语言类型
  • Cookie:在客户端存储少量信息,当客户端下一次访问浏览器时,可以通过Cookie识别是哪一个客户端发来的请求,通常用于实现会话(Session)的功能;

HTTP Cookie和Session

Cookie

HTTP 是无状态协议,它不对之前发生过的请求和响应的状态进行管理。也就是说,无法根据之前的状态进行本次的请求处理。
假设要求登录认证的 Web 页面本身无法进行状态的管理(不记录已登录的状态),那么每次跳转新页面不是要再次登录,就是要在每次请求报文中附加参数来管理登录状态。
不可否认,无状态协议当然也有它的优点。由于不必保存状态,自然可减少服务器的 CPU 及内存资源的消耗。从另一侧面来说,也正是因为 HTTP 协议本身是非常简单的,所以才会被应用在各种场景里。

保留无状态协议这个特征的同时又要解决类似的矛盾问题,于是引入了 Cookie 技术。Cookie 技术通过在请求和响应报文中写入 Cookie 信息来控制客户端的状态。
Cookie 会根据从服务器端发送的响应报文内的一个叫做Set-Cookie 的首部字段信息,通知客户端保存 Cookie。当下次客户端再往该服务器发送请求时,客户端会自动在请求报文中加入 Cookie 值后发送出去

在这里插入图片描述

服务器端发现客户端发送过来的 Cookie 后,会去检查究竟是从哪一个客户端发来的连接请求,然后对比服务器上的记录,最后得到之前的状态信息。

请添加图片描述

Cookie信息被保存到浏览器安装目录的Cookie文件中,而用户的电脑本身是不安全的,要是被黑客攻击,攻击者拿到了用户的Cookie信息,那么Cookie信息中的敏感信息就会泄漏。

为了解决Cookie泄漏敏感信息的问题,需要把敏感信息保存到服务器端,于是就有了Session

Session

Session是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而Session保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是Session。客户端浏览器再次访问时只需要从该Session中查找该客户的状态就可以了。

每个用户访问服务器都会建立一个session,服务器为了标识用户的唯一身份,用户与服务器建立连接的同时,服务器会自动为其分配一个Session ID。

请添加图片描述

这时浏览器端,也就是用户端的Cookie文件中存的是一个Session ID,若用户再次被攻击,Cookie信息泄漏,那么攻击者拿到的Cookie信息里只有Cookie ID,攻击者只能通过服务器的认证访问服务器,并不能拿到用户的敏感信息

实现简单的HTTP服务器

HTTP是基于TCP协议的,而HTTP服务器其实就是在收到请求的时候,给用户返回响应信息,我们自己构建一个简单的响应报头,返回给客户端,实现简单的HTTP通信

HttpServer.hpp

#ifndef __HTTP_SERVER_H__
#define __HTTP_SERVER_H__

#include<iostream>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<stdlib.h>
#include<string>
#include<signal.h>
#include<unistd.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
#define BACKLOG 5
using namespace std;
class HttpServer{
private:
    int port;
    int lsock;
public:
    HttpServer(int _port = 8080):port(_port),lsock(-1)
    {}
    void initServer(){
        //设置SIGCHLD的默认递达方式为忽略
        signal(SIGCHLD,SIG_IGN);
        //创建监听套接字
        lsock = socket(AF_INET,SOCK_STREAM,0);
        if(lsock < 0){
            cerr<<"sock error"<<endl;
            exit(2);
        }
        //填充协议族/ip/端口号到sockaddr_in
        struct sockaddr_in local;
        local.sin_family = AF_INET;
        local.sin_port = htons(port);
        local.sin_addr.s_addr = INADDR_ANY;
        //绑定端口号
        if(bind(lsock,(struct sockaddr*)&local,sizeof(local)) < 0){
            cerr<<"bind error"<<endl;
            exit(3);
        }
        //开始监听
        if(listen(lsock,BACKLOG) < 0 ){
            cerr<<"listen error"<<endl;
            exit(4);
        }
    }
    void Start(){
        cout << "HttpServer start"<<endl;
        struct sockaddr_in peer;
        while(true){
            socklen_t len = sizeof(peer);
            int sock = accept(lsock,(struct sockaddr*)&peer,&len);
            if(sock < 0){
                cerr << "accept error" <<endl;
                continue;
            }
            cout<< "Get a new link"<<endl;
            //创建子进程处理任务
            if(fork() == 0){
                close(lsock);
                EchoHttp(sock);
                exit(0);
            }
            close(sock);
        }       
    }
    void EchoHttp(int sock){
        char request[2048];
        ssize_t s =recv(sock,&request,sizeof(request)-1,0);
        if(s > 0){
            request[s] = 0;
            cout << request <<endl; 
			//构建响应报头
            string response = "HTTP/1.0 200 OK \r\n";
            response += "Client-type : text/html\r\n";
            response += "\r\n";
            response += "\
                         <!DOCTYPE html>\
                         <html>\
                         <head>\
                         <title>HTTPTEST</title>\
                         </head>\
                         <body>\
                         <h1>HTTP TEST</h1>\
                         <p>Hello World!</p>\
                         </body>\
                         </html>\
                          ";
            //响应信息发送给客户端
            send(sock,response.c_str(),response.size(),0);
        }
        close(sock);
    }
    ~HttpServer(){
        if(lsock != -1){
            close(lsock);
        }
    }
};
#endif

HttpServer.cc

#include"HttpServer.hpp"
void Usage(string proc){
    cout<<"Usage\n \t";
    cout<<proc<<" port"<<endl;
}
int main(int argc, char* argv[]){
    if(argc != 2){
        Usage(argv[0]);
        exit(0);
    }
    
    HttpServer* hp = new HttpServer(atoi(argv[1]));
    hp->initServer();
    hp->Start();
    delete hp;
    return 0;
}

效果演示

  • 启动HttpServer

请添加图片描述

  • 在浏览器端访问服务器

可以看到浏览器的请求报头

请添加图片描述

请添加图片描述

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xiaomage1213888

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值