应用层——HTTP协议

news2024/11/20 19:32:16

文章目录

  • 一、应用层
    • 1.1 应用层概念
    • 1.2 再谈协议
  • 二、网络版本的计算器
    • 网络计算器编码部分
      • 版本1:原生版本
      • 版本2:引入序列化和反序列化
  • 三、HTTP协议
    • 3.1 URL
    • 3.2 urlencode和urldecode
    • 3.3 HTTP协议格式
      • 3.3.1 请求报文
      • 3.3.2 响应报文
    • 3.4 HTTPDemo
      • 3.4.1改进
      • 3.4.2 HTTP中的版本
      • 3.4.3 HTTP中的常用方法
        • GET方法
        • POST方法
        • 总结GET和POST方法:
      • 3.4.4 HTTP中的状态码
      • 3.4.5 HTTP中的Header
    • 3.5 Cookie和session
      • 3.5.1 概念问题
      • 3.5.2 安全问题
    • 3.6 HTTPS
      • 加密方式
        • 对称加密
        • 非对称加密
        • HTTPS正式讲解

一、应用层

1.1 应用层概念

程序员平时写的代码都是在应用层,为了解决日常所需,但是不同的网络进程之间还需要有不同的通信协议,每个应用层协议都是为了解决某一类问题,而问题的解决又必须通过位于不同主机中的多个应用进程之间的通信和协同工作来完成。应用进程之间的这种通信必须遵守严格的规则,应用进程的具体内容就是精确定义这些通信规则

应用层的协议应当定义

  • 应用进程交换的报文类型,如请求报文和响应报文。
  • 各种报文类型的语法,如报文中的各个字段及其详细描述。
  • 字段的含义,即包含在字段中信息的含义。
  • 进程何时、如何发送报文,以及对报文进行响应的规则。

1.2 再谈协议

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

什么是序列化和反序列化?

在这里插入图片描述
在这里插入图片描述
从一个结构化的数据转化为一个长字符串的过程,我们称之为“序列化”的过程!
长字符串到达对方机器后,会被分析算法将字符串里面的字段信息解析出来,然后再填充信息,“还原”结构化的信息,最终完成数据的传输。其中我们将长字符串再转化为“结构化”数据的过程叫做“反序列化”!

为什么要进行序列化和反序列化?

1️⃣:为了应用层网络通信的方便,结构化的数据是不便于网络传输的。
2️⃣:为了方便上层进行使用内部成员,将应用和网络进行了解耦!(上层用户根本就不关心,数据在网络中的转化过程)

我们之前的UDP、TCP套接字通信,有没有进行序列化和反序列化?

——没有,我们根本就没有实际的应用场景,就没有结构化的数据来供我们传输。

结构化的数据:本质就是协议的表现!

怎么序列化和反序列化?

造轮子:就是我们自己写一个。
用轮子:就是我们使用别人写好的组件(xml,json,protobuff)。
所以接下来我们来一个实际的应用场景:网络版本的计算器。

二、网络版本的计算器

约定方案一:

  • 客户端发送一个形如"1+1"的字符串;
  • 这个字符串中有两个操作数, 都是整形;
  • 两个数字之间会有一个字符是运算符, 运算符只能是 + ;
  • 数字和运算符之间没有空格;

模拟一下序列化和反序列化的过程:
在这里插入图片描述
序列化和反序列化都由我们自己做的话,太过于繁琐和麻烦了。

约定方案二:

  • 定义结构体来表示我们需要交互的信息;
  • 发送数据时将这个结构体按照一个规则转换成字符串, 接收到数据的时候再按照相同的规则把字符串转化回结构体;
  • 这个过程叫做 “序列化” 和 “反序列化”

模拟一下序列化和反序列化的过程:
在这里插入图片描述

网络计算器编码部分

定制协议,这是最主要的部分
定制协议的过程,就是定制结构化数据的过程

业务逻辑:做一个短服务,并不是死循环的长服务
客户端发送一个request ->分析处理->服务器构建response->send(response)发回去->close(sock)

版本1:原生版本

没有明显的序列化和反序列化

Protocol.hpp

#pragma once
#include <iostream>
#include <string>
using namespace std;

// 我们自己定制的协议,客户端和服务器都要遵守!!!
// 这就叫做自定义协议!!!

// 请求格式
typedef struct request
{
    int x;
    int y;
    char op; // +-*/%
} request_t;

// 响应格式
typedef struct response
{
    int code;   // server运算完毕的状态code(0:success -1:div 0)
    int result; // 计算结果 要能区分出正常的计算结果,还是异常的退出结果
} response_t;

Sock.hpp

// 对套接字接口进行封装,方便使用
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

using namespace std;

class Sock
{
public:
    static int Socket()
    {
        int sock = socket(AF_INET, SOCK_STREAM, 0);
        if (sock < 0)
        {
            cerr << "create socket failed!" << endl;
            exit(2);
        }
        return sock;
    }
    static void Bind(int sock, uint16_t port)
    {
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(port);
        local.sin_addr.s_addr = INADDR_ANY;
        if (bind(sock, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            cerr << "bind failed!" << endl;
            exit(3);
        }
    }
    static void Listen(int sock)
    {
        if (listen(sock, 5) < 0)
        {
            cerr << "listen failed!" << endl;
            exit(4);
        }
    }

    static int Accept(int sock)
    {
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        int fd = accept(sock, (struct sockaddr *)&peer, &len);
        if (fd >= 0)
        {
            return fd;
        }
        return -1;
    }

    static void Connect(int sock, std::string ip, uint16_t port)
    {
        struct sockaddr_in server;
        memset(&server, 0, sizeof(server));

        server.sin_family = AF_INET;
        server.sin_port = htons(port);
        server.sin_addr.s_addr = inet_addr(ip.c_str());

        if (connect(sock, (struct sockaddr *)&server, sizeof(server)) == 0)
        {
            cout << "connect sucess!" << endl;
        }
        else
        {
            cout << "connect failed!" << endl;
            exit(-5);
        }
    }
};

CalServer.cc

#include "Protocol.hpp"
#include "Sock.hpp"
#include <pthread.h>
// 我们想这样运行:./CalServer port
static void Usage(string proc)
{
    cout << "./" << proc << " port" << endl;
    exit(1);
}
void *HandlerRequest(void *args)
{
    int sock = *(int *)args;
    delete (int *)args;

    pthread_detach(pthread_self());

    //接下来就是业务逻辑处理
    // 1.读取求情,从客户端读
    request_t req;
    ssize_t s = read(sock, &req, sizeof(req));

    // 2. 分析请求 && 3. 计算结果
    // 4. 构建响应,并进行返回
    if (s == sizeof(req))
    {
        response_t resp = {0, 0};
        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;
            break;
        case '/':
            if (req.y == 0)
                resp.code = -1;
            else
                resp.result = req.x / req.y;
            break;
        case '%':
            if (req.y == 0)
                resp.code = -2;
            else
                resp.result = req.x % req.y;
            break;

        default:
            resp.code = -3;
            break;
        }
        cout << "request: " << req.x << req.op << req.y << endl;
        // 数据处理完毕,写进socket中,写回对端
        write(sock, &resp, sizeof(resp));
        cout<<"服务结束!"<<endl;
    }

    // 5. 关闭链接
    close(sock);
}
int main(int argc, char *argv[])
{
    if (argc != 2)
        Usage(argv[0]);

    uint16_t port = atoi(argv[1]);

    int listen_sock = Sock::Socket();
    Sock::Bind(listen_sock, port);
    Sock::Listen(listen_sock);

    for (;;)
    {
        int sock = Sock::Accept(listen_sock);
        if (sock >= 0)
        {
            cout << "get a new client..." << endl;
            int *pram = new int(sock);
            pthread_t tid;
            pthread_create(&tid, nullptr, HandlerRequest, pram);
        }
    }
    return 0;
}

CalClient.cc

#include "Protocol.hpp"
#include "Sock.hpp"
// 我们想要这样运行:./CalClient server_ip server_port
void Usage(string proc)
{
    cout << "Usage:" << proc << " server_ip server_port" << endl;
}
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[1]);
        exit(-1);
    }
    int sock = Sock::Socket();
    Sock::Connect(sock, argv[1], atoi(argv[2]));

    // 业务逻辑
    request_t req;
    memset(&req, 0, sizeof(req));
    cout << "please enter data one# ";
    cin >> req.x;
    cout << "please enter data two# ";
    cin >> req.y;
    cout << "please enter operator# ";
    cin >> req.op;

    // 将数据写到对端
    ssize_t s = write(sock, &req, sizeof(req));

    // 读回处理结果
    response_t resp;
    s = read(sock, &resp, sizeof(resp));
    if (s == sizeof(resp))
    {
        cout << "code[0:success]: " << resp.code << endl;
        cout << "result: " << resp.result << endl;
    }

    return 0;
}

结果展示:
在这里插入图片描述

版本2:引入序列化和反序列化

上一个版本,我们定义原生结构体,然后利用write/read接口以二进制方式读写,显得太过于麻烦了,这样的方式并不是最佳的。所以我们要引入系列化和反序列化,json的使用。

安装json:sudo yum install -y jsoncpp-devel

testjson.cc

#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>

// 结构化的数据
typedef struct request
{
  int x;
  int y;
  char op; // +-*/%
} request_t;

// 注意编译时要链接json库  -ljsoncpp

int main()
{
  // // 序列化
  // request_t req={10,20,'*'};
  // Json::Value root;//这是一个万精油的对象,可以承装任何对象,json是一个KV式的序列化方案!
  // root["datax"]=req.x;
  // root["datay"]=req.y;
  // root["operator"]=req.op;

  // // 一共有两种Writer
  // // FasterWriter StyledWriter
  // //Json::StyledWriter writer;
  // Json::FastWriter writer;
  // std::string json_string =writer.write(root);
  // std::cout<<json_string<<std::endl;

  // 反序列化的过程
  std::string json_string = R"({"datax":10,"datay":20,"operator":42})";

  Json::Reader reader;
  Json::Value root;

  reader.parse(json_string, root);
  
  request_t req;
  req.x = root["datax"].asInt();
  req.y = root["datay"].asInt();
  req.op = (char)root["operator"].asUInt();

  std::cout << req.x << req.y << req.op << std::endl;

  return 0;
}

Protocol.hpp

#pragma once
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
using namespace std;

// 我们自己定制的协议,客户端和服务器都要遵守!!!
// 这就叫做自定义协议!!!

// 请求格式
typedef struct request
{
    int x;
    int y;
    char op; // +-*/%
} request_t;

// 响应格式
typedef struct response
{
    int code;   // server运算完毕的状态code(0:success -1:div 0)
    int result; // 计算结果 要能区分出正常的计算结果,还是异常的退出结果
} response_t;


//
//序列化和反序列化函数
//
//

// request_t -> string
std::string SerializeRequest(const request_t &req)
{
    Json::Value root;
    root["datax"] = req.x;
    root["datay"] = req.y;
    root["operator"] = req.op;

    Json::FastWriter writer;
    std::string json_string = writer.write(root);
    return json_string;
}

// string -> request_t
void DeserializeRequest(const std::string &json_string, request_t &out)
{
    Json::Reader reader;
    Json::Value root;

    reader.parse(json_string, root);
    out.x = root["datax"].asInt();
    out.y = root["datay"].asInt();
    out.op = (char)root["operator"].asInt();
}


std::string SerializeResponse(const response_t & resp)
{
    Json::Value root;
    root["code"]=resp.code;
    root["result"]=resp.result;

    Json::FastWriter writer;
    std::string res=writer.write(root);
    return res;
}

void DeserializeResponse(const std::string &json_string,response_t &out)
{
    Json::Reader reader;
    Json::Value root;

    reader.parse(json_string,root);

    out.code=root["code"].asInt();
    out.result=root["result"].asInt();
}

CalServer.cc

#include "Protocol.hpp"
#include "Sock.hpp"
#include <pthread.h>
// 我们想这样运行:./CalServer port
static void Usage(string proc)
{
    cout << "./" << proc << " port" << endl;
    exit(1);
}
void *HandlerRequest(void *args)
{
    int sock = *(int *)args;
    delete (int *)args;
    pthread_detach(pthread_self());

    //接下来就是业务逻辑处理
    char buffer[1024];
    request_t req;
    ssize_t s = read(sock, buffer, sizeof(buffer) - 1);

    if (s > 0)
    {
        buffer[s] = 0;
        cout << "get a new request..." << buffer << endl;
        std::string str = buffer;
        DeserializeRequest(str, req);
        response_t resp = {0, 0};
        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;
            break;
        case '/':
            if (req.y == 0)
                resp.code = -1;
            else
                resp.result = req.x / req.y;
            break;
        case '%':
            if (req.y == 0)
                resp.code = -2;
            else
                resp.result = req.x % req.y;
            break;

        default:
            resp.code = -3;
            break;
        }
        cout << "request: " << req.x << req.op << req.y << endl;
        std::string send_string = SerializeResponse(resp);
        write(sock, send_string.c_str(), send_string.size());
        cout << "服务结束: " << send_string << endl;
    }
    // 5. 关闭链接
    close(sock);
}
int main(int argc, char *argv[])
{
    if (argc != 2)
        Usage(argv[0]);

    uint16_t port = atoi(argv[1]);

    int listen_sock = Sock::Socket();
    Sock::Bind(listen_sock, port);
    Sock::Listen(listen_sock);

    for (;;)
    {
        int sock = Sock::Accept(listen_sock);
        if (sock >= 0)
        {
            cout << "get a new client..." << endl;
            int *pram = new int(sock);
            pthread_t tid;
            pthread_create(&tid, nullptr, HandlerRequest, pram);
        }
    }
    return 0;
}

CalClient.cc

#include "Protocol.hpp"
#include "Sock.hpp"
// 我们想要这样运行:./CalClient server_ip server_port
void Usage(string proc)
{
    cout << "Usage:" << proc << " server_ip server_port" << endl;
}
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[1]);
        exit(-1);
    }
    int sock = Sock::Socket();
    Sock::Connect(sock, argv[1], atoi(argv[2]));

    // 业务逻辑
    request_t req;
    memset(&req, 0, sizeof(req));
    cout << "please enter data one# ";
    cin >> req.x;
    cout << "please enter data two# ";
    cin >> req.y;
    cout << "please enter operator# ";
    cin >> req.op;

    // 将数据写到对端
    std::string json_string = SerializeRequest(req);
    ssize_t s = write(sock, json_string.c_str(), json_string.size());

    // 读回处理结果
    char buffer[1024];
    s = read(sock, buffer, sizeof(buffer) - 1);
    if (s > 0)
    {
        response_t resp;
        buffer[s] = 0;
        std::string str = buffer;
        DeserializeResponse(str, resp);

        cout << "code[0:success]: " << resp.code << endl;
        cout << "result: " << resp.result << endl;
    }
    return 0;
}

结果展示:
在这里插入图片描述
总结
我们写的CS模式的网络版本的计算器,本质就是一个应用层网络服务:
1️⃣其中的基本通信代码:是我们自己实现的。
2️⃣序列和反序列化是:利用json组件完成的。
3️⃣业务逻辑是我们自己设定的。
4️⃣请求、结果的格式、code的含义等约定是我们自己定义的。

三、HTTP协议

3.1 URL

统一资源定位符(Uniform Resource Locator)
在这里插入图片描述

我们请求的图片、html、css、js、视频、音频、标签、文档等这些都称之为"资源"!服务器后台,是用Linux做的。IP + Port唯一的确定一个进程。
但是我们无法唯一的确认一个资源!公网IP地址是唯一确认一台主机的,而我们所谓的网络"资源"都一定是存在于网络中的一台Linux机器上!Linux或者传统的操作系统,保存资源的方式,都是以文件的方式保存的。单Linux系统,标识一个唯一资源的方式,通过路径!
所以:IP+Linux路径,就可以唯一的确认一个网络资源!(这是URL存在的意义)
ip通常是以域名的方式呈现的。路径可以通过目录名+分隔符/确认。

3.2 urlencode和urldecode

/ ? : 等这样的字符,已经被url当做特殊意义理解了,因此这些字符不能随意出现.。比如,某个参数中需要带有这些特殊字符,就必须先对特殊字符进行转义。
转义的规则如下:
将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编码成%XY格式。这就叫做urlencode,其次urldecodeurlencode的逆过程。
在这里插入图片描述

3.3 HTTP协议格式

无论是请求还是响应,基本上http都是按照行\n为单位进行构建请求或者构建响应的。无论是请求还是响应,几乎都是由3或者4部分组成。

3.3.1 请求报文

在这里插入图片描述

3.3.2 响应报文

在这里插入图片描述

如何理解普通用户的上网行为?(此处简化处理)
1️⃣从目标服务器拿到数据
2️⃣向目标服务器上传数据
本质:这个过程对于计算机来说就是IO,人的所有上网行为不就是进程间通信吗?

思考一:http如何封装、解包?
空行是一个特殊字符,用空行可以做到,将长字符串一分为2。

思考二:http如何分用?
不是http解决的,是具体的应用代码解决的,http需要有接口来帮助上层获取参数。

思考三:http请求或者响应是如何被读取的?那么它又是如何被发送的?
换言之:http request 和 http response是如何被看待的?
可以将请求和响应整体看做是一个大的字符串!!!

在这里插入图片描述

3.4 HTTPDemo

接下来就是,我想要查看http报文的格式,如何查看?它是什么样的呢?

recv函数与read函数几乎是一样的!只是recv有最后一个参数flags,默认设为0,即可。
send函数与write函数几乎也是一样的,只是send有最后一个参数flags,默认设为0,即可。
这两个函数是Linux特有的,针对TCP协议开发出来的函数。

Http.cc

// 服务器
#include "Sock.hpp"
#include <pthread.h>
// 我们想要这样运行:./Http 8080
void Usage(string proc)
{
    cout << "Usage: " << proc << "port" << endl;
    exit(1);
}
void *HanderHttpRequest(void *args)
{
    int sock = *(int *)args;
    delete (int *)args;

    pthread_detach(pthread_self());
#define SIZE 1024 * 10
    char buffer[SIZE];
    memset(buffer, 0, sizeof(buffer));

    ssize_t s = recv(sock, buffer, sizeof(buffer), 0);
    if (s > 0)
    {
        buffer[s] = 0;
        cout << buffer; //仅仅查看http的请求格式! for test
    }
    close(sock);
    return nullptr;
}
int main(int argc, char *argv[])
{
    if (argc != 2)
        Usage(argv[0]);

    uint16_t port = atoi(argv[1]);
    int listen_sock = Sock::Socket();
    Sock::Bind(listen_sock, port);
    Sock::Listen(listen_sock);

    for (;;)
    {
        int sock = Sock::Accept(listen_sock);
        if (sock > 0)
        {
            pthread_t tid;
            int *pram = new int(sock);
            pthread_create(&tid, nullptr, HanderHttpRequest, pram);
        }
    }
    return 0;
}

我们在输入网址的框中:
在这里插入图片描述
然后回到Xshell中可以查看到:
在这里插入图片描述

我们现在想要回显一点内容,返回给客户端,我们只是单纯的返回一个字符串就可以了吗?

答案:不是的,我们还需要手动构建响应报文的状态行,响应报头才行,因为我们使用的是HTTP协议,必须要遵守大佬制定的规则。

Http.cc

#include "Sock.hpp"
#include <pthread.h>
// 我们想要这样运行:./Http 8080
void Usage(string proc)
{
    cout << "Usage: " << proc << "port" << endl;
    exit(1);
}
void *HanderHttpRequest(void *args)
{
    int sock = *(int *)args;
    delete (int *)args;
    pthread_detach(pthread_self());
    
#define SIZE 1024 * 10

    char buffer[SIZE];
    memset(buffer, 0, sizeof(buffer));
    ssize_t s = recv(sock, buffer, sizeof(buffer), 0);
    if (s > 0)
    {
        buffer[s] = 0;
        cout << buffer; //仅仅查看http的请求格式! for test

        string http_response = "http/1.0 200 OK\n";
        http_response += "Content-Type: text/plain\n"; // text/plain,正文是普通的文本
        http_response += "\n";//以空行来作为报头与有效载荷的分界线
        
        // 接下来构建一个响应 有效载荷 再发送回去,显示在浏览器上面
        http_response += "hello world!"; // 不仅仅是正文,还需要构建响应报头和状态行,因为要满足http协议
        send(sock, http_response.c_str(), http_response.size(), 0);
    }
    close(sock);
    return nullptr;
}
int main(int argc, char *argv[])
{
    if (argc != 2)
        Usage(argv[0]);

    uint16_t port = atoi(argv[1]);
    int listen_sock = Sock::Socket();
    Sock::Bind(listen_sock, port);
    Sock::Listen(listen_sock);

    for (;;)
    {
        int sock = Sock::Accept(listen_sock);
        if (sock > 0)
        {
            pthread_t tid;
            int *pram = new int(sock);
            pthread_create(&tid, nullptr, HanderHttpRequest, pram);
        }
    }
    return 0;
}

结果展示:
在这里插入图片描述

其中常见的媒体格式类型如下:

  • text/html : HTML格式
  • text/plain :纯文本格式
  • text/xml : XML格式
  • image/gif :gif图片格式
  • image/jpeg :jpg图片格式
  • image/png:png图片格式

总结
HTTP协议,如果我们要自己写的话,本质是:我们要根据协议的内容,来进行文本分析!

3.4.1改进

服务器的这种recv的读法是不正确的:

#define SIZE 1024 * 10
char buffer[SIZE];
memset(buffer, 0, sizeof(buffer));
ssize_t s = recv(sock, buffer, sizeof(buffer), 0);

客户端可能会以某种方式,向服务器发送多个请求,我们的缓冲区的大小是固定的,有没有情况下,可能回多读一些报文,甚至少读了一些报文,这都会造成问题。
所以我们要保证两点:
1️⃣我们保证每次都是读取的完整的http_request
2️⃣每次都不要把下一个http请求给读到(涉及到TCP,后面细讲)
在这里插入图片描述
在这里插入图片描述

如何判定将我们的报头部分读完了?
读到空行——“\n\n”,之后我们就能正确提取报头中的各种属性包括: Content-Length!

如何决定后面还有没有正文?
这和请求方法有关

如果有正文,如何保证把正文全部读取完成呢?而且不要把下一个http的部分数据读到呢?
如果有正文,报头部分有一个属性:Content-Length: len,表明正文部分有多少个字节!决定读取多少个len字节的正文!

总结
1️⃣Content-Length帮助我们读取到完整的http请求或者响应(不存在Content-Length的情况,就是你没有正文的时候)。
2️⃣同时,根据空行能够做到将报头和有效载荷进行分离(解包)。

3.4.2 HTTP中的版本

所以我们可以引出connection:keep-alive/close

1.0:较老的版本,是短链接的:一个请求,对应一响应,然后关闭套接字,其中的一次请求,一般就是请求一个资源,完毕后关闭链接。
1.1:支持长链接,循环请求与响应,1.1是现在常用的版本。

一个大网页中一般有很多个资源,每个资源都要发起http请求。当要访问这个大网页时,http/1.0就需要多次进行http请求,由于http协议是基于tcp协议的,所以tcp要通信,我们就要建立连接->传送数据->关闭链接,每一个请求都要进行一次上述的过程,这样请求一个资源使用http/1.0就显得比较耗时了。所以后来有了http/1.1,它是支持长链接的,它主要是为了解决1.0中的耗时问题,通过减少频繁建立tcp链接的方式,来提高效率!
在这里插入图片描述

3.4.3 HTTP中的常用方法

方法说明支持的http版本
GET获取资源1.0、1.1
POST传输实体资源1.0、1.1
HEAD获得报文首部1.0、1.1

我们关注一下 /
在这里插入图片描述
我们可以增加一些路径观察一下:
在这里插入图片描述
其中的/有点像我们Linux下的根目录,其实不是的,这个叫做web根目录。
/:我们一般要请求的一定是一个具体的资源,但如果请求是/,意味着我们要请求该网站的首页!(内部会进行判断:if(method=='/'),path=/wwwroot;,注意并不是根目录下的所有资源)首页一般叫做index.html 或者 index.htm。一般的网站,都要有默认首页。

我们接下来可以做测试:
创建一个wwwroot的web根目录,其下存放文件就是一个一个的资源。

stat函数
功能:根据文件路径,获得文件的指定的属性

int stat(const char *path, struct stat *buf);

GET方法

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>Document</title>
</head>

<body>
    <h5>hello 首页!</h5>
    <h5>hello 表单!</h5>

    <form action="/a/b/hande_form" method="GET">
        姓名:<input type="text" name="name"><br />
        密码:<input type="password" name="passwd"><br />
        <input type="submit">
    </form>
</body>

</html>

运行结果:
在这里插入图片描述
GET 方法如果提交参数,是通过URL的方式提交的,问号?作为分割符,提取出用户名和密码,这样前端的数据,就可以被后端服务器拿到了,进而进行数据处理。

POST方法

<!-- 将首页信息进行返回 -->
<!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>Document</title>
</head>

<body>
    <h5>hello 首页!</h5>
    <h5>hello 表单!</h5>

    <form action="/a/b/hande_form" method="POST"> //POST方法
        姓名:<input type="text" name="name"><br />
        密码:<input type="password" name="passwd"><br />
        <input type="submit">
    </form>
</body>

</html>

运行结果:
在这里插入图片描述
POST 方法是通过正文提交参数的,这里的参数就是你输入框中输入的信息。

抓包原理:
在这里插入图片描述
在这里插入图片描述

总结GET和POST方法:

1️⃣(概念问题)
GET方法叫做——获取,是最常用的方法,默认一般获取所有的网页,都是GET方法,但是如果GET要提交参数(它能提交参数的,不能只看字面意思获取),通过URL来进行参数拼接从而提交给Server端。
POST方法叫做——推送,是提交参数比较常用的方法,但是如果提交参数,一般是通过正文部分提交的,但是你不要忘记,有Content -Length:X 表示参数的长度。
2️⃣(区别)
①参数提交的位置不同POST方法比较私密(私密!=安全),不会回显到浏览器的URL输入框!GET方法不私密,会将重要信息回显到URL的输入框中,增加了被盗取的风险。
GET是通过URL传参的,而URL是有大小限制的!这和具体的浏览器有关!POST方法,是由正文部分传参的,一般大小没有限制!
3️⃣(如何选择)
GET:如果提交的参数,不敏感,数量非常少,可以采用GET
POST:否则,就使用POST方法。
4️⃣http协议处理,本质是文本分析,所谓的文本分析:
①http协议本身的字段(请求行,请求报头)
②提取参数,如果有的话GET或者POST其实是前后端交互的一个重要方式。

注意:这里的参数就是你输入框中输入的信息。

3.4.4 HTTP中的状态码

状态码类别原因短语
1xx表示成功如请求收到了或正在进行处理
2xx表示成功如接收或者知道了
3xx表示重定向如要完成请求还必须采取进一步行动
4xx表示客户端的差错如请求中有错误的语法或不能完成
5xx表示服务器的差错如服务器失效无法完成请求

3xx重定向
1️⃣永久重定向 301
2️⃣临时重定向 302 、307

重定向概念:
1️⃣(永久重定向)当访问某一个网站的时候,会让我们跳转到另一个网址。
2️⃣(临时重定向)等我访问某种资源的时候,提示我登录,跳转到了登录页面,输入完毕密码,登录的时候,会自动跳转回来(登录,美团下单)。

在这里插入图片描述

浏览器会做特殊处理,把老旧的一些缓存更新为新网站的信息,例如书签。永久性重定向通常用来网站搬迁、域名替换。

重定向是需要浏览器给我们提供支持的,浏览器必须识别301,302,307服务器要告诉浏览器,我应该再去哪里?
所以有了这个字段Location:新的地址!
测试:
在这里插入图片描述

3.4.5 HTTP中的Header

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

3.5 Cookie和session

3.5.1 概念问题

在网站中,网站是认识我们的,在进行各种页面跳转的时候,本质其实就是进行各种http请求,网站还是照样认识我们,不用重复输密码也能认识我们。
HTTP是无状态的协议:也就是说,同一个客户第二次访问同一个服务器上的页面时,服务器的响应与第一次被访问时相同,因为服务器并不记得曾经访问过的这个用户,也不记得为该客户服务过多少次。HTTP无状态特性简化了服务器的设计,使服务器更容易支持大量并发的HTTP请求。

HTTP照样认识我这个特性,并不是HTTP协议本身要解决的问题,但是HTTP可以提供一些技术支持,来保持网站具有会话保持的功能。
这个技术就叫做“cookie”!在浏览器和HTTP协议两个角度来看待cookie
1️⃣浏览器:cookie是一个文本文件,该文件里面保存的使我们的用户私密信息。
2️⃣HTTP:一旦该网站对应有cookie,在发起任何请求的时候,都会自动在请求报文中携带该cookie信息!
基本理解
在这里插入图片描述
验证观察:添加cookie信息
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以观察到,首次访问并未携带cookie信息,但是后续访问时,都携带了cookie信息,如此网站就可以认识我们了。

3.5.2 安全问题

如果别人盗取了我们的cookie文件,他就可以
1、以我们的身份认证登录特定的资源。
2、如果保存是账号密码,那么情况就会变得非常糟糕!
所以单纯的使用cookie是具有一定的安全隐患的,所以我们要引出session,但是我们这里讲session并不代表我们不用cookie了,现在我们市面上是cookie+session!
核心思路:将用户的私密信息保存在服务器端!!!
构建http响应时会形成一个唯一的会话ID——session_id,凭借这个ID号可以验证用户,其中服务器磁盘上的session文件保存了用户的私密信息。
后续所有的http请求,都会由浏览器自动携带cookie内容——就是标识唯一性的当前用户的session_id,后续服务器依旧可以认识客户端浏览器,这也是一种会话保持的功能。
在这里插入图片描述
但是cookie文件还有被泄露的风险啊!——是的!!但是这个我们没有办法杜绝,因为这些文件是存储在用户的电脑上的,因为用户对于安全防范的意识和计算机方面的知识涉猎较为少,所以我们无法杜绝,但是衍生出了很多的防御方案!

总结cookie+session本质是为了提升用户访问或者登录平台的体验!

3.6 HTTPS

现在几乎100%都不用HTTP,使用的是HTTPS。
HTTPS=HTTP+TLS/SSL
TLS/SSL是HTTP数据的解密加密层

在这里插入图片描述

加密方式

对称加密

秘钥只有一个X
在这里插入图片描述

非对称加密

有一对秘钥(公钥和私钥),一般而言公钥是向全世界公开的,而私钥是必须自己私有保存的。

可以用公钥加密,但是只能用私钥来解密
可以用私钥加密,但是只能用公钥来解密

如何防止文本中的内容被篡改,以及识别是否被篡改?

首先先形成数字签名!
在这里插入图片描述
发送文本时,添加上数字签名!
在这里插入图片描述
所以接下来就要进行校验:
在这里插入图片描述

HTTPS正式讲解

1、如何选择加密算法?

①对称
②非对称
在这里插入图片描述

如果采用对称加密,假设客户端用秘钥X,那么服务端也要用X,那么对方是怎么知道X的呢?
我们可以在电脑中预装(成本高,不现实),可以提前协商秘钥(但是第一次通信的过程是没有加密的,数据是在裸奔),所以综上,单一的对称加密不可取。
在这里插入图片描述

其他人即使拿到了加密的数据也是没有办法解密的,因为其他人没有私钥。
在这里插入图片描述

所以现在能够保证从客户端到服务器单向的数据安全了,因为前文说明了,可以用公钥加密,但是只能用私钥来解密;可以用私钥加密,但是只能用公钥来解密。
在这里插入图片描述

所以我们再次升级改进,双方都分别生成自己的公钥和私钥,在双方通信阶段,就提前交换双方的公钥!!!
在这里插入图片描述

既然一对非对称加密能够保证单向通信的安全,那么两个非对称加密是不是就能保证数据双向传输的安全性呢!!!但是事实并非如此:
①依旧有非法窃取的风险。
②非对称加密算法,十分耗时。(对称算法是比较节省时间的)

实际做法:非对称+对称方案
服务器端形成对称秘钥X,用公钥S对X进行加密形成X+,此时客户端发送给服务端X+
在这里插入图片描述

什么叫做安全?

——不是让别人拿不到,就叫做安全,而是别人拿到了,也没法处理。

在网络环节中,随时都可能存在中间人来偷窥、修改我们的数据信息。所以返回给客户端公钥S的时候,是会存在风险的:
在这里插入图片描述
client并不知道server发送给自己的报文被 篡改了。
本质问题:client无法判定发来的秘钥协商报文是不是从合法的服务方发来的!
所以网络中就出现了一个非常重要的CA证书机构!

只要一个服务商,经过权威机构认证,该机构就是合法的!
CA机构:1.权威 2.有自己的公钥A和私钥A’(公钥私钥只是算法)
CA的公钥是全世界都知道的,但是CA的私钥只有CA自己知道,换言之世界上,只有CA机构能重新形成对应的数字签名!

在这里插入图片描述

一般,一个正规的服务商要先向CA证书机构申请证书,同时需要提交企业的基本信息(域名、公钥),然后CA机构就可以为之创建证书,证书是由企业的基本信息和基本信息形成的数字签名两部分组成,申请好证书之后,就可以颁发给用户,此时再次进行秘钥协商的时候,就不用直接发送公钥S了,而是发送证书,中间人如果想要截取证书并且修改里面的内容,这是不可以的,因为证书里面携带了数字签名,即使修改了数字签名,中间人没有CA机构的私钥,所以无法生成新的数字签名,当客户端收到证书,会把内容和数字签名拆分出来做校验,这样就可以知道信息是否被篡改过了。
在这里插入图片描述

要求:client必须知道CA机构的公钥信息。
如何获得公钥信息?
1️⃣一般是内置的。
2️⃣访问网址的时候,浏览器可能会提示用户进行安装。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/276.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

这里不适合做技术

6点&#xff0c;の&#xff0c;下班了又是一个差不多一样的星期过去了&#xff0c;又是一个差不多的周末要到来了。我也差不多要离开这家公司了&#xff0c;入职4年多&#xff0c;那时候雄心壮志&#xff0c;决定干一番大事业&#xff0c;那个时候的自己&#xff0c;技术的炉火…

【图像融合】基于 DCT结合拉普拉斯金字塔的图像融合附matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步精进&#xff0c;matlab项目合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信条&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算法 …

论文理解与笔记【CVPR_2022.6】Region-Aware Face Swapping

论文理解与笔记【CVPR_2022】Region-Aware Face Swapping论文的缩写全拼&#xff1a;一、贡献二、介绍三、提出问题&#xff0c;也是论文解决的问题四、具体实现方案五、实验六、最终感想和总结论文地址&#xff1a;传送门或者传送门2 先看看效果&#xff1a; 论文的缩写全拼…

【单片机毕业设计】【mcuclub-jj-003】基于单片机的八层电梯的设计

最近设计了一个项目基于单片机的八层电梯系统&#xff0c;与大家分享一下&#xff1a; 一、基本介绍 项目名&#xff1a;八层电梯 项目编号&#xff1a;mcuclub-jj-003 单片机类型&#xff1a;STC89C52、STM32F103C8T6 功能简介&#xff1a; 1、通过3*4矩阵键盘实现电梯内部…

【Linux修炼】5.vim详解

每一个不曾起舞的日子&#xff0c;都是对生命的辜负。 Linux-yum&vim工具的使用本节目标1. Linux 软件包管理器 yum1.1 什么是软件包1.2 关于rzsz1.3 查看软件包1.4 如何安装软件1.5 如何卸载软件2. Linux编辑器-vim的使用&#xff08;重点&#xff09;2.1 什么是vim&#…

C++内存管理

1.C内存分布 学习C内存分布之前&#xff0c;先小试牛刀一下。 int globalVar 1; static int staticGlobalVar 1; void Test() {static int staticVar 1;int localVar 1;int num1[10] { 1, 2, 3, 4 };char char2[] "abcd";const char* pChar3 "abcd&quo…

webpack--》webpack底层深入讲解,从初识到精通,真正实现从0到1的过程

目录 webpack webpack的基本使用 安装 配置 修改自定义打包的入口与出口 优化js或图片的存放路径 配置webpack中符号的使用 webpack中相关插件安装 webpack-dev-server html-webpack-plugin clean-webpack-plugin webpack中的loader 打包处理css文件 打包处理les…

TPM分析笔记(十二)TPM PCR操作

目录一、PCR初始化&#xff08;Initializing PCR&#xff09;二、PCR的扩展&#xff08;Extend of a PCR&#xff09;2.1 其他PCR命令三、使用PCR Banks进行扩展&#xff08;Using Extend with PCR Banks&#xff09;四、事件记录&#xff08;Recording Events&#xff09;五、…

Java递归实现树形结构的两种方式

目录0、引言1、数据准备2、类型转化3、递归实现方法3.1、Java7及以下纯Java递归实现3.2、Java8及以上借助lamda表达式实现0、引言 在开发的过程中&#xff0c;很多业务场景需要一个树形结构的结果集进行前端展示&#xff0c;也可以理解为是一个无限父子结构&#xff0c;常见的…

【老师见打系列】:我只是写了一个自动回复讨论的脚本~

文章目录&#x1f31f;好久不见⛳️实现过程&#x1f334;老操作了兄弟们~&#x1f422;一步拿捏讨论&#x1f496;美图结束语专栏Python零基础入门篇&#x1f4a5;Python网络蜘蛛&#x1f4a5;Python数据分析Django基础入门宝典&#x1f4a5;小玩意儿&#x1f4a5;Web前端学习…

2022海德堡桂冠论坛(HLF)见闻录

今年9月下旬&#xff0c;我前往德国参加了第九届海德堡桂冠论坛。因疫情原因停摆两年后&#xff0c;海德堡桂冠论坛再次以线下形式举办&#xff0c;会场热闹非凡&#xff0c;作为计算机与数学界的社交盛宴当之无愧。 海德堡桂冠论坛&#xff08;Heidelberg Laureate Forum, HLF…

IGV-GSAman |「功能基因组时代」的高效率科研工具

写在前面 今天周末&#xff0c;转眼10月份只剩一周。万万没想到&#xff0c;一个月下去&#xff0c;我还是花了不少时间在完善「GSAman」。至于为什么本来「两个小时」就干完的事情&#xff0c;可以干成「22天」&#xff1f;到底还是我对前面的版本&#xff0c;不太满意。当然…

Attack Lab

Attack Lab 从CMU官网下载完所需实验包后&#xff0c;内有官方文档以及.tar压缩包&#xff0c;使用tar -xvf targetk.tar解压后&#xff0c;得到如下文件 The fifiles in targetk include: README.txt: A fifile describing the contents of the directory ctarget: An execut…

web自动化测试框架

本文介绍web自动化测试框架 ●Base&#xff1a;用来对Selenium API进行二次封装。 对Selenium API进行二次封装的目的是简化一些复杂的操作&#xff0c;但是千万不要为了封装而封装。 封装好后&#xff0c;其他页面类可以集成basepage&#xff0c;调用这些方法。 from sele…

【iOS】—— 仿写知乎日报第一周总结

目录知乎日报第一周完成情况遇到的问题&#xff1a;1.线程问题&#xff1a;2.SDWebImage加载图片3.实现无限刷新界面4.点击主界面cell进入后的界面知乎日报第一周完成情况 在这周开始了仿写知乎日报的任务&#xff0c;在第一周里&#xff0c;我完成了主界面&#xff0c;以及滚…

新人入手mac折腾过程中遇到的解决方案

本文将长期更新&#xff0c;以记录个人的使用。 终端美化 …还没有美化完&#xff0c;不过推荐看这篇文章Mac终端美化指南 homebrew homebrew是Mac OS X上的强大的包管理工具&#xff0c;可以高效管理各种软件包&#xff0c;官方说法是&#xff1a;The missing package mana…

ArcGIS:如何新建图层组并添加数据、切换数据视图和布局视图、修改符号系统?

目录 01 如何新建图层组&#xff1f; 02 如何在图层组中添加数据 03 如何切换数据视图和布局视图 03 如何修改符号系统&#xff1f; 3.1 如何快捷的修改一下符号样式&#xff1f; 3.2 如何修改符号系统的色带 3.2.1 色带视图的取消 3.3 修改符号系统中的标注显示 3.4 如…

大气湍流退化图像复原技术研究及DSP实现

目录 第一章 绪论 1 1.1 研究背景 1 1.2 国内外研究现状 1 1.3 本文的研究内容 5 1.4 本文的组织结构 5 第二章 大气湍流退化图像复原技术 7 2.1 图像退化及复原数学模型概述 7 2.1.1 图像退化的数学模型 7 2.1.2 图像复原的数学模型 8 2.2 大气湍流退化图像概述 9 2.2.1 大气…

插件内存分析

rtspsrc 1. 当pipleline为rtspsrc加其他插件时&#xff0c;如果让其他插件卡主会发生什么事情呢&#xff1f;以rtspsrcfakesink为例&#xff08;gst-launch-1.0.exe rtspsrc locationrtsp://xxx ! fakesink&#xff09;&#xff0c;修改fakesink的代码&#xff0c;让render中…

基于SSM的图书馆阅览室预约管理系统,高质量论文范例,可直接参考使用,附送源码、数据库脚本

目录 1.项目技术栈 2.适合对象 3.适合课题 4.项目功能概述 4.1 项目功能汇总 4.2 项目功能介绍 5. 高质量论文范例 6. 毕业设计撰写视频教程 6.部分运行截图 1.项目技术栈 前端必学三个基础&#xff0c;"HTML、CSS、JS"&#xff0c;基本每个B/S架构项目都要…