【网络】应用层-HTTP协议

网络-应用层-HTTP协议

文章目录

  • 网络-应用层-HTTP协议
    • HTTP协议
      • 什么是HTTP协议
        • URL
        • urlencode和urldecode
      • HTTP基本特征
      • HTTP协议版本
      • HTTP报文结构
        • HTTP请求报文和响应报文
      • HTTP常见的请求方法(GET/POST)
      • HTTP状态码
      • HTTP常见首部字段
      • HTTP Cookie和Session
        • Cookie
        • Session
    • 实现简单的HTTP服务器

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

HTTP协议

什么是HTTP协议

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

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

【网络】应用层-HTTP协议_第1张图片

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

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

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


URL

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

【网络】应用层-HTTP协议_第2张图片

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

urlencode和urldecode

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

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

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

【网络】应用层-HTTP协议_第3张图片

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

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

urldecode就是urlencode的逆过程

urlencode工具


HTTP基本特征

  • 无连接

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

  • 无状态

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

【网络】应用层-HTTP协议_第4张图片

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协议_第5张图片

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

【网络】应用层-HTTP协议_第6张图片

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

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

【网络】应用层-HTTP协议_第7张图片

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

管线化

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

【网络】应用层-HTTP协议_第8张图片

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


HTTP报文结构

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

HTTP请求报文和响应报文

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

【网络】应用层-HTTP协议_第9张图片

【网络】应用层-HTTP协议_第10张图片


报文结构分为以下

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

【网络】应用层-HTTP协议_第11张图片

报文首部包括首行和Header

【网络】应用层-HTTP协议_第12张图片

【网络】应用层-HTTP协议_第13张图片

【网络】应用层-HTTP协议_第14张图片

【网络】应用层-HTTP协议_第15张图片

【网络】应用层-HTTP协议_第16张图片

【网络】应用层-HTTP协议_第17张图片


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

【网络】应用层-HTTP协议_第18张图片

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


HTTP状态码

【网络】应用层-HTTP协议_第19张图片

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

【网络】应用层-HTTP协议_第20张图片

最常见的状态码, 比如 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 值后发送出去

【网络】应用层-HTTP协议_第21张图片

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

【网络】应用层-HTTP协议_第22张图片

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

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

Session

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

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

【网络】应用层-HTTP协议_第23张图片

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

实现简单的HTTP服务器

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

HttpServer.hpp

#ifndef __HTTP_SERVER_H__
#define __HTTP_SERVER_H__

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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 += "\
                         \
                         \
                         \
                         HTTPTEST\
                         \
                         \
                         

HTTP TEST

\

Hello World!

\ \ \ "
; //响应信息发送给客户端 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

【网络】应用层-HTTP协议_第24张图片

  • 在浏览器端访问服务器

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

【网络】应用层-HTTP协议_第25张图片

【网络】应用层-HTTP协议_第26张图片

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