HTTP协议

本文介绍了网络版计算器的模拟实现,通过定义简单的协议处理客户端与服务器的交互。接着,深入讲解了HTTP协议的基本概念,包括URL、urlencode与urldecode、请求格式、响应格式、请求方法、状态码以及常见Header。还探讨了HTTP的特点,如无连接性和无状态性,并对比了HTTP与HTTPS的区别,强调了安全性。最后,展示了简单的HTTP服务器的实现。
摘要由CSDN通过智能技术生成

再谈 “协议”

我们程序员写的一个个解决我们实际问题, 满足我们日常需求的网络程序, 都是在应用层

协议是一种 “约定”. socket api的接口, 在读写数据时, 都是按 “字符串” 的方式来发送接收的. 如果我们要传输一些"结构化的数据" 怎么办呢?

模拟实现网络版计算器

例如, 我们需要实现一个服务器版的计算器. 我们需要客户端把要计算的两个数和符号发过去, 然后由服务器进行计算, 最后再把结果返回给客户端。

实现程序前我们先做约定

  • 客户端依次输入 数字1,数字2,操作符
  • 操作符只能为 加 减 乘 除 取模
  • 除零错误码为1,模零错误码为2,输入错误操作符错误码为3

我们所做的约定就可以理解为协议

协议一定和具体使用场景有关,一般比较简单,鲁棒性(健壮性),可扩展性不强。

互联网应用中,有很多场景是特别高频的,就有一些程序员前辈为我们做好了很多应用层协议,这些场景我们就不用自己设计协议了。我们作为学习者,需要了解别人的协议,适度的学别人的协议细节甚至自己编写一小部分!不排除有些场景,需要自己定义协议如游戏领域,通信领域…

协议文件 protocol.hpp

#pragma once

#include<iostream>
#include<string>
#include<unistd.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<strings.h>

namespace ns_protocol{

    struct Request{
        int x;
        int y;
        char op;

        Request():x(0),y(0),op('+'){}
    };

    struct Response{
        int code;// 状态码
        int result;

        Response():code(0),result(0){}
    };
}

服务端文件 server.hpp

#pragma once

#include"protocol.hpp"
#include<pthread.h>

namespace ns_server{

    class Server{
        private:
            uint16_t port;
            int listen_sock;

        public:
            Server(uint16_t _port):port(_port),listen_sock(-1)
            {}

            void InitServer()
            {
                listen_sock = socket(AF_INET,SOCK_STREAM,0);
                if(listen_sock<0){
                    std::cerr<<"socket error"<<std::endl;
                    exit(2);
                }

                struct sockaddr_in local;
                bzero(&local,sizeof(local));
                local.sin_family=AF_INET;
                local.sin_port=htons(port);
                local.sin_addr.s_addr=INADDR_ANY;

                if(bind(listen_sock,(struct sockaddr*)&local,sizeof(local))<0){
                    std::cerr<<"bind error"<<std::endl;
                    exit(3);
                }

                if(listen(listen_sock,5)<0){
                    std::cerr<<"listen error"<<std::endl;
                    exit(4);
                }
            }

            static void *calc(void *args)
            {
                int sock = *(int*)args;
                delete (int*)args;

                while(true){
                    ns_protocol::Request req;
                    ssize_t s=recv(sock,&req,sizeof(req),0);// read
                    if(s>0){
                        ns_protocol::Response resp;
                        switch(req.op){
                            case '+':
                                resp.result = req.x + req.y;
                                break;
                            case '-':
                                resp.result = req.x + req.y;
                                break;
                            case '*':
                                resp.result = req.x * req.y;
                            case '/':
                                if(0==req.y) resp.code = 1;// 除0
                                else resp.result = req.x / req.y;
                                break;
                            case '%':
                                if(0==req.y) resp.code = 2;// 模0
                                else resp.result = req.x % req.y;
                                break;
                            default:
                                resp.code = 3;
                                break;
                        }
                        send(sock,&resp,sizeof(resp),0);// write
                    }
                    else if(s==0){
                        std::cout<<"client quit,me too"<<std::endl;
                        break;
                    }
                    else{
                        std::cerr<<"client quit,me too"<<std::endl;
                        break;
                    }
                }

                close(sock);
                return nullptr;
            }

            void Loop()
            {
                while(true){
                    struct sockaddr_in peer;
                    socklen_t len = sizeof(peer);
                    int sock = accept(listen_sock,(struct sockaddr*)&peer,&len);
                    if(sock<0){
                        std::cerr<<"accept error"<<std::endl;
                        continue;
                    }

                    pthread_t tid;
                    int *p = new int(sock);
                    pthread_create(&tid,nullptr,calc,p);
                }
            }

            ~Server()
            {
                if(listen_sock>0){
                    close(listen_sock);
                }
            }
    };
}

服务端文件 server.cc

#include"server.hpp"

void Usage(std::string proc)
{
        std::cerr<<"Usage: "<<"\n\t"<<proc<<" port"<<std::endl;
}
int main(int argc,char *argv[])
{
        if(argc!=2){
                Usage(argv[0]);
                exit(1);
        }   

        uint16_t port=atoi(argv[1]);
        ns_server::Server svr(port);

        svr.InitServer();

        svr.Loop();

        return 0;
}

用户端 client.hpp

#pragma once

#include "protocol.hpp"
#include <string>

namespace ns_client{

    class Client{
        private:
            std::string svr_ip;
            uint16_t svr_port;
            int sock;

        public:
            Client(const std::string &_ip,uint16_t _port):svr_ip(_ip),svr_port(_port),sock(-1)
            {}

            void InitClient(){
                sock = socket(AF_INET,SOCK_STREAM,0);
                if(sock<0){
                    std::cerr<<"socket error"<<std::endl;
                    exit(2);
                }
            }

            void Run()
            {
                struct sockaddr_in peer;
                bzero(&peer,sizeof(peer));

                peer.sin_family = AF_INET;
                peer.sin_port = htons(svr_port);
                peer.sin_addr.s_addr = inet_addr(svr_ip.c_str());

                int ret = connect(sock,(struct sockaddr*)&peer,sizeof(peer));
                if(ret<0){
                    std::cerr<<"connect error"<<std::endl;
                    exit(3);
                }

                while(true){
                    ns_protocol::Request req;
                    std::cout << "请输入你的第一个数据# ";
                    std::cin >> req.x;
                    std::cout << "请输入你的第二个数据# ";
                    std::cin >> req.y;
                    std::cout << "请输入要进行的计算<+-*/%># ";
                    std::cin >> req.op;

                    //req是一个结构化的数据,其实也需要进行序列化,暂时忽略
                    send(sock,&req,sizeof(req),0);

                    ns_protocol::Response resp;
                    ssize_t s = recv(sock,&resp,sizeof(resp),0);
                    if(s > 0){
                        //你读到的一定是一个曾经被server序列化的字符串,需要你进行反序列化,暂时忽略
                        std::cout << "code: " << resp.code <<std::endl;
                        std::cout << "result: " << resp.result <<std::endl;
                    }
                }
            }

            ~Client()
            {
                if(sock >= 0) close(sock);
            }
    };
}

客户端文件 client.cc

#include "client.hpp"

static void Usage(std::string proc)
{
    std::cerr << "Usage: " << "\n\t" << proc << " svr_ip svr_port" << std::endl;
}
// ./client svr_ip svr_port
int main(int argc, char *argv[])
{
    if( argc != 3 ){
        Usage(argv[0]);
        exit(1);
    }   

    std::string ip = argv[1];
    uint16_t port = atoi(argv[2]);

    ns_client::Client cli(ip, port);

    cli.InitClient();

    cli.Run();

    return 0;
}

运行结果
在这里插入图片描述

HTTP协议概念

  • HTTP(HyperText Transfer Protocol,超⽂本传输协 )的协议。
  • HTTP是⽆连接, ⽆状态, ⼯作在应⽤层的协议。
  • ⽆连接理解为: http协议本身是没有维护连接信息的, http的数据会交给⽹络协议栈传输层的TCP协议, ⽽TCP是⾯向连接的。
  • ⽆状态: HTTP 协议⾃身不对请求和响应之间的通信状态进⾏保存。也就是说在 HTTP 这个级别,协议对于发送过的请求或响应都不做持久化处理。

认识URL

平时我们俗称的 “网址” 其实就是说的 URL。

在这里插入图片描述

urlencode和urldecode

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

转义的规则如下:

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

例如C++就会被转义成C%2B%2B,这里的"+" 被转义成了 “%2B”

urldecode就是urlencode的逆过程

urldeconde工具

HTTP协议格式

HTTP请求

HTTP 协议规定,请求从客户端发出,最后服务器端响应该请求并返回。换句话说,肯定是先从客户端开始建⽴通信的,服务器端在没有接收到请求之前不会发送响应。

在这里插入图片描述

在这里插入图片描述

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

http的请求为什么会存在空行?

作为一个特殊符号来表示http请求报头的结束

因为每一层协议都必须解决两个问题:
1.将报头和有效载荷进行分离!(解包)特殊字符的方案
2.将自己的有效载荷交付给上层协议!(分用)

HTTP响应

在这里插入图片描述
在这里插入图片描述

  • 首行: [版本号] + [状态码] + [状态码解释]
  • Header: 请求的属性, 冒号分割的键值对;每组属性之间使用\n分隔;遇到空行表示Header部分结束
  • Body: 空行后面的内容都是Body. Body允许为空字符串. 如果Body存在, 则在Header中会有一个Content-Length属性来标识Body的长度; 如果服务器返回了一个html页面, 那么html页面内容就是在body中

HTTP协议请求方法

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

GET vs POST

  1. GET和POST方法都不安全,因为默认都没有加密
  2. GET通常用来进行获取网页等资源,不过也可以提交参数:百度搜索
  3. POST通常用来提交数据

HTTP的状态码

在这里插入图片描述

在这里插入图片描述

HTTP常见Header

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

HTTP的特点

  1. HTTP本身是无连接的

虽然底层是基于TCP协议的,但是http要发起所谓的http request的时候,不会先在http层建立链接,而是直接通过底层的TCP进行传输.

  1. HTTP本身是无状态的

http不会记录自己的发起http请求的上下文,不会对历史请求有任何记忆能力!

这样会给用户造成非常的不方便:比如:当vip用户登录上某些vip类的视频网站的时候,点播某些vip视频,因为http是无状态的,那么也就意味着,新打开的网页,以及里面的vip网页,都要重新登录

我们可以给http添加一些其他的技术特点(cookie+ session),让http具有保存状态和会话能力!

cookie和session

当我们在网页中成功输入账户和密码后,浏览器会提示我们是否保存账户和密码。页面保存是将自己的私密信息保存到浏览器的默认文件中,这里的默认文件就是cookie
浏览器每次发起请求的时候,http请求都会自动给你携带你曾经写到临时文件(默认文件)中的内容,发送给服务器端!

浏览器使用cookie的时候

  • 每一个http request都会携带cookie的信息,发给服务器!一定需要服务器也支持cookie技术(其中认证的环节,不是http完成的,程序员自己完成认证的)
  • cookie"文件":有些浏览器配置的时候,cookie有内存级的,也有文件级别的!

内存级: 当浏览器不关闭时,访问都会直接登录。一旦关闭浏览器,再打开访问就需要输入密码。
文件级: 较长的一段时间内访问都会直接登录,无论是否关闭浏览器。

只用cookie有什么风险!

cookie里面保存的是用户的私密信息,而且有可能是基于文件的,万一我的电脑被注入木马等恶意程序,盗取浏览器的所有cookie信息!比如:拿到了你的访问某个网页的所有cookie信息!,不法分子拿着你的cookie拷贝到自己的浏览器cookie路径下,然后自己访问该网页,就是以你的身份访问了。

所以存在如下风险

1.个人私密信息泄漏的风险
2.盗用个人的身份从事一些违法的事情

那么如何解决上述问题呢?这里我们引入了session文件

session文件是通过复杂的算法将cookie文件转换成唯一的session文件。

session文件保存在用户端,用户的个人私密信息保存在服务端(比较安全),我们在访问时,会用session文件去在服务端找对应的个人信息进行访问。

cookie 和 session 本身都是有时间和空间限制的,比如说:上午在北京访问,下午在南京访问,session会失效,这时就要重新输入用户信息。

http VS https

  1. http请求无论是get或者post都是不安全的,只有经过加密的数据,才能保证在理论上是安全的!

在这里插入图片描述
SSL (Secure Sockets Layer,安全套接字层 )

TLS (Transport Layer Security,传输层安全)

这里所谓的加密解密的软件层,本质是在用户层!
ssl也是分层,分为两层第一层为封装加密解密 , 第一层为https握手。

  1. 加密分为对称加密(只有一个秘钥)和非对称加密(公钥和私钥),非对称加密中,一般公钥是公开的,私钥必须保存!对称加密效率高,非对称加密效率低(需要不断的加密解密操作)

只要加密了,就一定是安全的吗?

所有的密码学加密算法,都是和CPI的算力有关。如果想破解一个加密的数据,只要我们的算力足够强,就大概率可以破解!强大的算力是有成本的,所谓的安全本质:破解的成本>>破解的收益

  1. MD5信息摘要算法,一种被广泛使用的密码散列函数

MD5是将文本提取出一部分内容,进行不可逆算法,形成一串字符串,该字符串我们称之为摘要,接着再对摘要进行加密,形成一串复杂的字符,该字符被称为数据指纹

指纹技术: 凡是对原始文本做任何修改,在进行摘要形成指纹之后,得到的指纹数据,差别会非常大!

用户的密码信息是必须经过加密的!保存在对一个公司的mysql数据库中

用户输入密码的流程

在这里插入图片描述

加密

  1. 选择什么种类的加密算法?对称的,还是非对称的?

http刚开始通信的时候,绝对不能使用对称加密,第一次交换秘钥时会出现无法解密的问题。但我们可以使用一对(2个)公钥和私钥,做到安全通信,我们需要所有的client和server提前内置公钥和私钥。所以解决方法是前期ssl握手期间,使用非对称加密,来交换对称加密的秘钥,通信期间,采用对称加密来保证数据安全。

加密过程可以简单的总结为: 非对称加密解密算法(秘钥协商阶段)+对称加密解密算法(加密通信阶段)

秘钥协商阶段

在这里插入图片描述

这样做有没有什么问题呢?

可能有黑客截获服务端发来的公钥,将自己的公钥发送给客户端。客户端不考虑接收的公钥是否合法,直接通过公钥加密对称秘钥并发送回去。这样黑客就能以客户端的身份和用户端进行交流了。

在这里插入图片描述

如何确认对方发来的公钥是合法的且没有被篡改呢?

这就需要确认对方的主机Server是合法的(有权威机构对网站或者服务器进行合法认证:CA认证)

生成证书的过程

在这里插入图片描述

如何证明证书是合法的

client只要能够用CA的公钥成功解密,证书就是合法的!

在这里插入图片描述

如何证明证书没有被篡改过

通过对比原文摘要和指纹解密之后的携带的摘要是否相等,来判定是否被篡改过!

中间人是否可以只对证书明文内容进行修改

可以

为什么携带了指纹的证书,根本就不怕被中间人修改呢?

根本原因是,任何中间人,都没有曾经形成指纹的时候,CA的私钥!在client认证阶段,client只会只用CA的公钥来进行证书认证,不相信任何人的其他公钥信息!

结论:世界上的任何一份证书=数据+指纹,没有任何人可以修改因为大部分人,没有CA的私钥,无法二次重新生成指纹

为何我们要先认证证书呢?
不是目的,目的是为了相信server发过来的公钥是合法的!

client是如何得知CA的公钥呢?
操作系统or浏览器出厂的时候,内置了各种权威的根认证机构的各种证书(公钥)

对称加密解密算法(加密通信阶段)

在这里插入图片描述

简单的HTTP服务器

http_server.hpp文件

#include <iostream>
#include <string>
#include <fstream>
#include <strings.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>

#define HTTP_VERSION "HTTP/1.0"
#define HOME_PAGE "wwwroot/index.html"

namespace ns_http{

    class HttpServer{
        private:
            uint16_t port;
            int listen_sock;
        public:
            HttpServer(uint16_t _port): port(_port), listen_sock(-1)
            {}

            void InitHttpServer()
            {
                listen_sock = socket(AF_INET, SOCK_STREAM, 0);
                if(listen_sock < 0){
                    exit(2);
                }

                struct sockaddr_in local;
                bzero(&local, sizeof(local));
                local.sin_family = AF_INET;
                local.sin_port = htons(port);
                local.sin_addr.s_addr = INADDR_ANY;

                if(bind(listen_sock, (struct sockaddr*)&local, sizeof(local)) < 0){
                    exit(3);
                }

                if(listen(listen_sock, 5) < 0){
                    exit(4);
                }
            }

            static void *HandlerRequest(void *args)
            {
                pthread_detach(pthread_self());
                int sock = *(int*)args;
                delete (int*)args;

                //debug
                //get request
                std::cout << "######################################## begin: " << sock << "   #####" << std::endl;
                char buffer[4096];
                ssize_t s = recv(sock, buffer, sizeof(buffer), 0);
                if(s > 0){
                    std::cout << buffer;
                }
                std::cout << "######################################## end: " << sock << "   #####" << std::endl;

                std::string response;
                std::string body;
                std::ifstream in(HOME_PAGE, std::ios::in | std::ios::binary);
                if(!in.is_open()){

                }
                else{//成功
                    std::string line;
                    while(std::getline(in, line)){
                        body+=line;
                    }

                    response = HTTP_VERSION;
                    response += " 200 OK\n";
                    response += "Content-Type: text/html\n";
                    response += ("Content-Length: " + std::to_string(body.size()) + "\n");

                    response += "\n"; //传说中的空行,用来区分我们的报头和有效载荷

                    response += body;
                }
                in.close();

                send(sock, response.c_str(), response.size(), 0);
                close(sock);    //基于短链接

                return nullptr;
            }

            void Loop()
            {
                while(true){
                    struct sockaddr_in peer;
                    socklen_t len = sizeof(peer);
                    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, HandlerRequest, p);

                }
            }

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

http_server.hpp文件

#include "http_server.hpp"
#include <unistd.h>
#include <cstdlib>

static void Usage(std::string proc)
{
    std::cerr << "Usage: " << "\n\t" << proc << " port" << std::endl;
}
// ./http_server 8081
int main(int argc, char *argv[])
{
    if(argc != 2){
        Usage(argv[0]);
        exit(1);
    }

    ns_http::HttpServer http_server(atoi(argv[1]));
    http_server.InitHttpServer();

    http_server.Loop();

    return 0;
}

index.html文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>用来测试</title>
</head>
<body>
    <h1>Hello World!</h1>
</body>
</html>

运行结果
在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值