http协议(一)/应用层

学习目标:⭐理解应用层的作用,理解协议,理解序列化和反序列化,并且实现网络版计算器⭐HTTP协议。⭐手写一个简单的http协议。

 

应用层

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

协议/序列化与反序列化

协议是一种约定,在使用socket api接口的的时候,比如网络套接字的博文中写的示例代码,都是按字符串的方式来接收的,但是如果传输的是结构体的数据,就需要考虑以下问题:

结构体的大小是需要内存对齐的,但是每一台机器都是不一样的,有的可能是32位,有的是64位,未来可能会有别的形式,如果直接将这个结构体数据直接传输到目的计算机,就会造成很多问题。

序列化:将结构体数据转化成长字符串。字符串便于网络传输。

反序列化:将传过来的字符串,按照协议,一一对应将数据填入结构体中。也就是将字符串“转换成”结构体数据。

使用TCP协议,实现网络版计算器

代码思路:

服务器:首先对套接字的接口进行封装。然后定制协议跟响应格式。在服务端中,首先创建监听套接字,接着是绑定监听等一系列的操作后,使得服务器处于监听状态,让客户端可以与服务器建立连接。接着创建用于通信的套接字,通过线程分离的方式进行通信。在分离的线程中,线程主要完成的任务是:读取请求、分析请求并计算结果,最后通过把结果写回,写给客户端。

客户端:首先创建套接字,然后使用套接字、ip和端口号与服务器建立连接。连接建立后,客户输入数据(此时在服务器中就会进行读取请求、分析请求和计算结果,然后把结果写回),然后读回数据,最后打印出来。

定制协议:在协议中,有3个变量,数字x、数字y和运算符op。在结果中,有2两个变量,一个是计算结果,一个是判断结果是否合法。在协议当中,需要定制序列化和反序列化。

准备jsoncpp库

在此之前,我们需要在云服务器上下载jsoncpp库,并且简单演示如何操作。

首先,在云服务器上安装jsoncpp库,用于序列化与反序列化。

sudo yum install -y jsoncpp-devel

序列化代码演示:

先写一个结构体,并用结构体创建一个结构体数据。在Json中,Value类是一种kv式的容器,可以将结构体数据装载起来。装载起来后,使用FastWriter类或者是StyledWriter类创建的对象,使用对象的方法write进行序列化。

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

//序列化
typedef struct request
{
    int x;
    int y;
    char op;//"+-*/%"
}request_t;

int main()
{
    request_t req = {10,20,'*'};//结构体数据
    //创建json对象,这个对象可以承装任何对象
    //kv式的序列化方案
    //这一步:将需要序列化的数据先装载到json的对象中
    Json::Value root;
    root["datax"] = req.x;
    root["datay"] = req.y;
    root["dataop"] = req.op;

    //写入,并将其序列化
    //Wirte有两种:
    //一种是FastWriter,一种是StyledWriter
    Json::StyledWriter writer;
    std::string json_string = writer.write(root);
    std::cout<<json_string<<std::endl;
    return 0;
}

使用 StyledWriter

 使用FastWriter

反序列化代码演示:

代码思路:Json中的Reader类,将字符串装载到Value类的对象中,然后赋值给我们准备好的结构体对象就可以了。

int main()
{
    //反序列化
    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.op<<" "<<req.y<<std::endl;

    return 0;
}

实现网络版计算器

定制协议代码:

#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的时候成功,code为-1,除0了
    int result;//计算结果
}response_t;

//对请求格式request定制序列化和反序列化
//序列化
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;
}

//反序列化
void DeserializeRequest(const std::string &json_string,request_t& out)
{
    Json::Reader reader;
    Json::Value root;
    //将字符串装载到root中
    reader.parse(json_string,root);
    out.x = root["datax"].asInt();
    out.y= root["datay"].asInt();
    out.op = (char)root["operator"].asInt();
}

//对响应格式response定制序列化和反序列化

//序列化
std::string SerializeRespond(const response_t& resp)
{
    //装载
    Json::Value root;
    root["code"] = resp.code;
    root["result"] = resp.result;

    //序列化
    Json::FastWriter writer;
    std::string json_string = writer.write(root);
    return json_string;

}

//反序列化
void DeserializeRespond(const std::string &json_string,response_t& out)
{
    Json::Reader reader;
    Json::Value root;
    //将字符串装载到root中
    reader.parse(json_string,root);
    out.code = root["code"].asInt();
    out.result = root["result"].asInt();
}

套接字的封装

#pragma once

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

using namespace std;

class Sock
{
public:
    static int Socket()
    {
        int sock = socket(AF_INET, SOCK_STREAM, 0);
        if (sock < 0)
        {
            cerr << "socket error" << 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 error!" << endl;
            exit(3);
        }
    }

    static void Listen(int sock)
    {
        if (listen(sock, 5) < 0)
        {
            cerr << "listen error !" << 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 Success!" << endl;
        }
        else
        {
            cout << "Connect Failed!" << endl;
            exit(5);
        }
    }
};

服务端代码思路及代码:

服务端通过套接字将来自客户端传输来的请求信息进行反序列化,然后通过计算得出结果填入响应的结构体对象中,然后对结构体数据进行序列化传输回给客户端,完成通信。

#include <pthread.h>
#include "Protocol.hpp"
#include "Sock.hpp"

static void Usage(string proc)
{
    cout << "Usage: " << proc << " port" << endl;
    exit(1);
}

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

    pthread_detach(pthread_self());
    // 业务逻辑, 做一个短服务
    // request -> 分析处理 -> 构建response -> sent(response)->close(sock)
    // 1. 读取请求
    char buffer[1024];
    request_t req;
    ssize_t s = read(sock,buffer,sizeof(buffer)-1);
    if(s > 0)
    {
        buffer[s] = 0;
        std::cout<<"get a new request: "<<buffer<<std::endl;
        std::string str = buffer;
        //反序列化
        //将读取到的字符串,反序列化为结构体数据
        DeserializeRequest(str,req);
    
        //读取到了完整的请求,待定
        //请求格式为:req.x , req.y, req.op
        //2. 分析请求 && 3. 计算结果
        //4. 构建响应,并进行返回
        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; //代表除0
            else
                resp.result = req.x / req.y;
            break;
        case '%':
            if (req.y == 0)
                resp.code = -2; //代表模0
            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 = SerializeRespond(resp);
        write(sock,send_string.c_str(),send_string.size());
        std::cout<<"服务结束"<<send_string<<std::endl;
    // 5. 关闭链接
        close(sock);
    }
}

// ./CalServer port
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;
}

客户端代码思路及代码:

客户端先将用于请求的数据填入请求结构体对象中,然后将其序列化并通过套接字传输给服务端,然后再通过套接字将服务端返回来的结果接收,反序列化,完成通信。

#include "Protocol.hpp"
#include "Sock.hpp"

void Usage(string proc)
{
    cout << "Usage: " << proc << " server_ip server_port" << endl;
}

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        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 opreator# ";
    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;
        DeserializeRespond(str,resp);
        cout << "code[0:success]: " << resp.code << endl;
        cout << "result: " << resp.result << std::endl;
    }
    return 0;
}

重新看待TCP/IP应用层

上面实现的网络版计算器,本质就是一个应用层的网络服务。它包含了请求和响应的格式,即协议,有业务逻辑,也有网络通信的实现,也有序列化和反序列化。在OSI模型中,应用层作用是针对特定的协议,表示层的作用是格式转化(序列化和反序列化),会话层的作用是管理网络通信!而在TCP/IP模型中,应用层就已经将这三部分包含起来,成为一个整体了!

HTTP协议

在本文中学习HTTP协议的顺序流程:

①首先认识什么是HTTP协议。②了解如何定位到在网络上的唯一的网络资源,从而引入并学习URL,进而引入并简单学习urlencode和urldecode在URL中的作用。③简单认识和学习HTTP协议格式,从而再引入并学习HTTP获取资源的方法(如何将前端的资源输送到后端后台)、HTTP的状态码(也就是我们见得最多的404那种)和HTTP的header(也就是HTTP格式中的报头部分),期间会用代码例子来演示。④最后实现一个超简单的HTTP服务器。

HTTP协议是什么

在上面的网络计算器的例子中,它的应用层协议是我们自己指定的,而在现实中,已经有大佬定义了现成的,非常好用的应用层协议,而HTTP(超文本传输协议)就是其中之一!

因此,http协议,本质上跟我们在网络计算器中的协议没有什么区别,都是应用层协议!

URL

确定唯一的网络资源

我们看到的东西,比如图片、视频、音频、html、js、css、标签、文档等等这些都称之为资源。要确定网络上唯一的资源,我们可以联想到如何确定网络中的唯一一台主机,那就是通过主机的公网IP地址+端口号来确定唯一的一台主机。而在网络中,资源是需要放在某台服务器上的,一般的服务器后台是Linux系统做的,因此这些资源一定存在某台Linux服务器中!而对于Linux系统,是以文件的方式保存资源的,而对于文件来说,是通过路径来标识文件的!因此,我们可以通过IP+路径的方式来确定网络唯一的网络资源!而这里的IP,不是公网IP,是以域名方式呈现的,路径可以通过目录名+/确定。

什么是URL

我们平时说的网址,就是URL。来看一个比较简单的URL。

urlencode和urldecode

当我们在某度的搜索栏上查询“C++”的时候,其URL是这样的:

可以看到,在显示结果的网址URL中,出现了wd=C%2B%2B这样的字符,其实它表示的便是C++的意思。这就涉及到了字符转义的点了。

为什么需要转义?

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

转义的规则:

将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编码成%XY格式。那么"+" 就被转义成了 "%2B"。做这一步转义的就是urlencode做的,而urldecode就是urlencode的逆过程,将C%2B%2B转成C++。

HTTP协议格式

简单认识HTTP格式

我们来简单认识一些HTTP协议格式:

HTTP协议格式一般都是按照行(\n)为单位进行构建请求和响应的,而格式一般有3或4部分(跟我们上课的教材中,把空行也算进去了,因为空行有用):请求行或响应行、报头、空行和有效载荷(有时候没有,就是3行)。

第一部分:请求行或响应行

⭐请求行中保存的是请求方法、url和http的版本,最后带一个\n。

:①请求方法一般是GET方法,也有POST方法,这两种方法是最常用的。②这里保存的url一般是去掉域名之后的内容。③http的版本有http/1.0 和 http/1.1,一般是1.1版本。

⭐响应行中保存的是http的版本、状态码和状态码描述,最后带一个\n。

:①状态码:比如说我们见得最多是就是404,也就是网页访问错误的时候的状态码。②而状态码描述就是对状态码的解释,状态码表示什么意思。

第二部分:报头

报头中是以kv式的方式保存报头信息,并且有很多行,每一行最后都带有\n。

第三部分的空行和第四部分的有效载荷并没有什么可以单独拿出来说明的。接下来我们来看看http是如何进行解包和封装的,这就涉及到了空行了。

http在封装中,将所有的行的字符串看做成一个大的长的整体的字符串装起来,并发送出去。这也是http发送请求响应的方式。在解包中,用空行将长字符串一分为二!

 接下来,我们使用代码,化理论为实践,看看HTTP的请求和响应。

HTTP请求示例代码

recv和send接口

recv方法:从套接字中接收的数据读到buf中。send方法:将buf中的数据写入到套接字中。这两个方法都是TCP使用的。

recv和read方法:recv方法的前三个参数与read方法的三个参数是一样的,是向文件中读取数据到某个空间中。区别是recv的第四个参数,这个参数我们直接设为0就🆗。

send和write方法也是如此,send方法的前三个参数跟write的三个参数是一样的,是将buf中的数据弄到文件中。

示例代码:

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

void Usage(std::string proc)
{
    std::cout << "Usage: " << proc << " port" << std::endl;
}

void *HandlerHttpRequest(void *args)
{
    //Http协议,如果自己写的话,本质是,我们要根据协议内容,来进行文本分析!

    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;
        std::cout  << buffer; //查看http的请求格式! for test
        
        //响应行
        std::string http_response = "http/1.0 200 OK\n";
        http_response += "Content-Type: text/plain\n"; //text/plain,正文是普通的文本
        http_response += "\n"; //传说中的空行
        //有效载荷
        http_response += "hello bit, hello liunx,hello internet!";

        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]);
        exit(1);
    }

    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 *parm = new int(sock);
            pthread_create(&tid, nullptr, HandlerHttpRequest, parm);
        }
    }
}

此时,我们使用我们的公网IP地址和端口号,在网页中打开,就会显示一下请求的HTTP格式:

分析报头信息(一)

①Content-Length

在上述代码中,有一处是不合理的,那就是每次读取HTTP请求格式的时候,我们都是使用代码中的空间大小为1024*10的buffer。其不合理之处便是我们每次读取这个字节大小的信息,不能保证每次读取都是一个完整的格式信息,或许读少了,或许是读多了,把下一个请求格式的一部分也读取了过来。因此,在HTTP中,为了完整地读取格式,并且不会多读,在HTTP的格式中,有一个叫做Content-Length的自描述字段,和发挥空行的作用!

Content-Length字段就显示了当前格式的有效载荷的长度。而读到了空行,就证明已经把报头部分读完。

通过web根目录访问资源 

在请求行中,我们看到反斜杠“/”,这个是web根目录的意思。对于web根目录,我们在打开网页,使用这个根目录的时候,一般会默认打开官网首页。接下来我们使用代码简单实现一下这个操作:

在源代码文件所处的文件夹中,创建一个新的文件夹wwwroot,在wwwroot文件夹中,创建一个html文件,使用html写一个简单的网页index.html,而这个index.html便是这个网站的首页:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
    </head>
    <body>
        <h3>hello linux!</h3>
        <h3>hello linux!</h3>
        <h3>hello linux!</h3>
        <h3>hello linux!</h3>
        <h3>hello linux!</h3>
        <h3>hello linux!</h3>
        <h3>hello linux!</h3>
    </body>
</html>

在写入请求的正文部分中,将网页文件打开,然后将其内容按行读取到字符串中,最后交给响应的正文即可。

#include "Sock.hpp"
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fstream>

//web路径
#define WWWORT "./wwwroot/"
#define HOME_PAGE "index.html"

void Usage(std::string proc)
{
    std::cout << "Usage: " << proc << " port" << std::endl;
}

void *HandlerHttpRequest(void *args)
{
    //Http协议,如果自己写的话,本质是,我们要根据协议内容,来进行文本分析!

    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;
        std::cout  << buffer; //查看http的请求格式! for test
        
        //无论发起什么请求,都把首页返回
        //网页文件的路径
        std::string html_file = WWWORT;
        html_file+=HOME_PAGE;
        struct stat st;
        stat(html_file.c_str(),&st);
        //构建响应
        //返回的时候,不仅仅返回网页的正文信息,还会返回HTTP的请求
        std::string http_response = "http/1.0 200 OK\n";
        //报头信息
        //正文部分的数据类型
        http_response+="Content-Type: text/html; charset=utf8\n";
        //文件的长度,通过stat函数获取文件的属性
        http_response+="Content-Length: ";
        http_response+=std::to_string(st.st_size);
        http_response+="\n";
        //添加空行
        http_response+="\n";
        //正文,即有效载荷
        //先打开这个网页文件
        std::ifstream in(html_file);
        if(!in.is_open())
        {
            std::cerr<<"open html file err"<<std::endl;
        }
        else
        {
            std::cout<<"read html begin"<<std::endl;
            //将文件的内容按行读到line字符串中,然后复制给content字符串
            std::string content;
            std::string line;
            while(std::getline(in,line))
            {
                content+=line;
            }
            //然后将正文交给正文部分
            http_response+=content;
            in.close();
            std::cout<<http_response<<std::endl;
            //最后发出去
            send(sock, http_response.c_str(), http_response.size(), 0);
            std::cout<<"read html end"<<std::endl;
        
        }
    }
    close(sock);
    return nullptr;
}

int main(int argc, char *argv[])
{
    if( argc != 2 )
    {
        Usage(argv[0]);
        exit(1);
    }

    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 *parm = new int(sock);
            pthread_create(&tid, nullptr, HandlerHttpRequest, parm);
        }
    }
    return 0;
}

HTTP方法

GET和POST方法介绍

HTTP方法中,很多方法都不能对外提供的,而且其中对于我们来说,最重要的是GET和POST两种方法,GET和POST的作用其实都是获取资源,但两者也有区别,接下来我们通过代码来验证一下这两种方法。

验证GET方法:

使用上面访问网页首页的代码,然后改写index.html,使其可以带上输入框输入姓名和密码,这些不重要,重要的是,在GET方法中,数据从前端到达后端究竟是怎么样的?

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
    </head>
    <body>
        <h5>hello linux!我是首页哦!</h5>
        <h5>hello 我是表单!</h5>
        
        <!--方法为GET,/a/b/handler_from并不存在,也不处理-->
        <form action="/a/b/handler_from" method="GET">
            姓名:<input type="text" name="name"><br/>
            密码:<input type="password" name="passwd"><br/>
            <input type = "submit" value="登陆">
        </form>
    </body>
</html>

从网页中可以看到,数据显示在了网址的输入框中了。而从发送的请求当中,我们可以看到,数据被拼接到了请求行中,以问号?做分隔符,用&隔开显示。

因此,GET方法结论:GET方法提交参数是通过url的方式进行提交的。

验证POST方法

将方法改为POST方法后,来看结果:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
    </head>
    <body>
        <h5>hello linux!我是首页哦!</h5>
        <h5>hello 我是表单!</h5>
        
        <!--方法为POST,/a/b/handler_from并不存在,也不处理-->
        <form action="/a/b/handler_from" method="POST">
            姓名:<input type="text" name="name"><br/>
            密码:<input type="password" name="passwd"><br/>
            <input type = "submit" value="登陆">
        </form>
    </body>
</html>

 ​

 POST方法结论:POST方法是通过正文提交参数的。

GET和POST方法总结

概念问题:

GET方法叫做获取,是最常用的方法,它是提交参数的方式是通过URL来进行参数拼接从而提交给服务端。

POST方法叫做推送,也是很常用的方法,它提交参数的方式是通过正文提交的,其中Content-Length便是表示参数的长度。

两者区别:

提交参数的位置不同,POST方法比较私密(但不能说安全),不会回显到浏览器的url输入框中。GET方法不私秘,因为它会把参数回显到url中,被盗取的风险比较大。

GET方法是通过URL传参的,而URL是由大小限制的,和具体的浏览器有关。POST是通过正文传参的,一般没有大小限制。

如何选择两者其中之一

如果提交的参数很少,并且不敏感,那么可以选择使用GET方法,否则就使用POST方法。

HTTP状态码

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

3XX重定向状态码

重定向的意思我们可以理解为网页的自动跳转。

3XX重定向状态码有两个重要的状态码:301永久重定向和302或307临时重定向。

301永久重定向:即我们打开了一个网页,但是这个网页已经是上古版本的网页了,而为了用户的方便使用,即使老用户不知道新页面的网址,只要打开了老页面,就会自动跳转到新页面中。这个便是永久重定向。

302或307临时重定向:当我们要访问某种资源的时候,首先会跳转到登录页面,而登录之后,又会跳转回到我们需要的那个页面。这就叫做临时重定向。

对于重定向,我们就需要用到报头信息中的Location搭配着使用。

location: 搭配3xx状态码使用, 告诉客户端接下来要去哪里访问。

301永久重定向:状态码描述为Permanently moved。

void *HandlerHttpRequest(void *args)
{
    //Http协议,如果自己写的话,本质是,我们要根据协议内容,来进行文本分析!

    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;
        std::cout  << buffer; //查看http的请求格式! for test

        //重定向
        std::string response = "http/1.1 301 Permanently moved\n";
        //location
        response+="Location: https://www.qq.com/\n";
        //空行
        response+='\n';
        send(sock, response.c_str(), response.size(), 0);

    }
    close(sock);
    return nullptr;
}

此时打开我们的网址后,就会自动跳转到腾讯的首页。

HTTP常见Header(分析报头信息二)

①Connection

一般而言,一个网页是由许多元素组成的,而http/1.0采用的网络请求方案是短链接

......

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ChatGPT原理剖析

文章目录 ChatGPT常见误解1. 罐头回应2. 网络搜寻重组 ChatGPT真正做的事——文字接龙ChatGPT背后的关键技术——预训练&#xff08;Pre-train&#xff09;一般机器是怎样学习的&#xff1f; ChatGPT带来的研究问题1. 如何精准提出需求2. 如何更改错误3. 侦测AI生成的物件4. 不…

十、v-model的基本使用

一、v-model的基本使用 表单提交是开发中非常常见的功能&#xff0c;也是和用户交互的重要手段&#xff1a; 比如用户在登录、注册时需要提交账号密码&#xff1b;比如用户在检索、创建、更新信息时&#xff0c;需要提交一些数据&#xff1b; 这些都要求我们可以在代码逻辑中…

系统分析师《企业信息化战略与实施》高频知识点

企业信息化战略与实施---企业信息化与电子商务 业务流程重组&#xff08;Business Process Reengineering BPR&#xff09;是针对企业业务流程的基本问题进行反思&#xff0c;并对它进行彻底的重新设计&#xff0c;使业绩取得显著性提高。与目标管理、全面质量管理、战略管理等…

输入捕获实验

实验内容 用TIM5 的通道 1&#xff08;PA0&#xff09;来做输入捕获&#xff0c;捕获 PA0 上高电平的脉宽&#xff08;用 WK_UP 按键输入高电平&#xff09;&#xff0c;通过串口打印高电平脉宽时间。 输入捕获简介 输入捕获模式可以用来测量脉冲宽度或者测量频率。STM32 的…

快速搭建Electron+Vite3+Vue3+TypeScript5脚手架 (无需梯子,快速安装Electron)

一、介绍 &#x1f606; &#x1f601; &#x1f609; Electron是一个使用 JavaScript、HTML 和 CSS 构建桌面应用程序的框架。 嵌入 Chromium 和 Node.js 到 二进制的 Electron 允许您保持一个 JavaScript 代码代码库并创建 在Windows上运行的跨平台应用 macOS和Linux——不需…

宝塔面板搭建自己的网站,并发布公网远程访问

文章目录 1. 环境安装2. 安装cpolar内网穿透3. 内网穿透4.固定http地址5. 配置二级子域名6.创建一个测试页面 宝塔面板简单几步搭建本地web站点&#xff0c;并做内网穿透&#xff0c;实现公网用户也可以正常远程访问&#xff0c;无需公网IP&#xff0c;无需设置路由器。 1. 环…

Volatile系列(一):Volatile测试案例一可见性

系列文章 Volatile测试案例一可见性 目录 前言 测试1 逻辑 代码 结果 测试2 逻辑 代码 结果 结论 原理探讨&#xff08;可见性&#xff09; 前言 多线程是 JAVA 并发编程的主要应用&#xff0c;并发环境能大幅提高应用性能&#xff0c;提高 CPU 使用率&#xff0c…

优雅的接口防刷处理方案

前言 本文为描述通过Interceptor以及Redis实现接口访问防刷Demo 这里会通过逐步找问题&#xff0c;逐步去完善的形式展示 原理 通过ip地址uri拼接用以作为访问者访问接口区分 通过在Interceptor中拦截请求&#xff0c;从Redis中统计用户访问接口次数从而达到接口防刷目的 …

不应使用Excel进行项目资源规划的 7 个原因

项目资源规划早期仅限于基本分配和调度。因此&#xff0c;企业使用自制工具或excel表来执行这一简单功能。然而&#xff0c;随着技术和业务流程的发展&#xff0c;资源规划变得复杂&#xff0c;并包括其他组成部分&#xff0c;如预测和容量规划&#xff0c;优化等。 由于传统…

掌握好这几款TikTok数据分析工具,让你轻松提高曝光率!

为什么别人在TikTok发的普普通通的视频却有那么高的流量、几天内疯狂涨粉&#xff0c;而自己想破脑袋装饰自己的视频&#xff0c;结果却不如人意。 其实原因很简单&#xff0c;TikTok不像国内的抖音只面向中华民族&#xff0c;而是覆盖了150多个国家和75种语言用户&#xff0c…

【通信接口】UART、IIC、SPI

目录 一、预备知识 1、串行与并行 2、单工与双工 3、波特率 二、UART 三、IIC 四、SPI &#xff08;一对一、一对多&#xff09; 五、IIC、SPI异同点 参考文章&#xff1a;这些单片机接口&#xff0c;一定要熟悉&#xff1a;UART、I2C、SPI、TTL、RS232、RS422、RS485…

【Java EE初阶】计算机简介及多线程之创建线程

目录 1.计算机发展史 2.冯诺依曼体系 3.操作系统 操作系统的作用&#xff1a; 4.进程 1.PID&#xff08;进程编号&#xff09; 2.内存指针 应用程序申请到的内存中的首地址 3.文件描述符表 问&#xff1a;什么是并发&#xff1f;什么是并行&#xff1f; 4.进程的优先级&a…

卡尔曼滤波原理及代码

目录 一.简介 二.原理 1.先验估计原理 2.后验估计原理 3.总结 三.示例 一.简介 卡尔曼滤波&#xff08;Kalman filtering&#xff09;是一种利用线性系统状态方程&#xff0c;通过系统输入输出观测数据&#xff0c;对系统状态进行最优估计的算法&#xff0c;它可以在任意…

StarRocks 3.0 集群安装手册

本文介绍如何以二进制安装包方式手动部署最新版 StarRocks 3.0集群。 什么是 StarRocks StarRocks 是新一代极速全场景 MPP (Massively Parallel Processing) 数据库。StarRocks 的愿景是能够让用户的数据分析变得更加简单和敏捷。用户无需经过复杂的预处理&#xff0c;就可以…

同步辐射X射线断层扫描成像在各行业的应用

同步辐射X射线断层扫描成像在各行业的应用 同步辐射X射线断层扫描成像&#xff08;synchrotron radiation X-ray computed tomography&#xff0c;SRCT&#xff09;是一种非侵入式、高分辨率的成像技术&#xff0c;利用同步辐射光束产生的高强度、高亮度、单色性和相干性的X射线…

【面试】MySQL事务的12连问

文章目录 前言1. 什么是数据库事务&#xff1f;2. 事务的四大特性3. 事务的隔离级别有哪些&#xff1f;MySQL的默认隔离级别是什么&#xff1f;4. Mysql为什么选择RR作为默认隔离级别&#xff1f;5. 很多大厂为什么选择RC数据库隔离级别&#xff1f;6. 并发场景&#xff0c;数据…

Qt连接MySql数据库(本地和远程数据库均可)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 三种方法方法一 略方法二 使用ODBC设置mysql为数据源库1. 添加ODBC数据源&#xff0c;在控制面板中找到管理工具&#xff0c;其中有ODBC数据源 64位的&#xff0c;打…

数字孪生与元宇宙:数字化科技的双向融合之路

概念 &#xff08;1&#xff09;元宇宙&#xff08;Metaverse&#xff09;是一个虚拟的三维世界&#xff0c;由数字内容和物理世界中的现实空间相互交织而成&#xff0c;能够提供各种虚拟体验&#xff0c;例如虚拟现实、增强现实、虚拟社交、虚拟经济等。在元宇宙中&#xff0…

8种不同类型的防火墙

什么是防火墙&#xff1f; 防火墙是一种监视网络流量并检测潜在威胁的安全设备或程序&#xff0c;作为一道保护屏障&#xff0c;它只允许非威胁性流量进入&#xff0c;阻止危险流量进入。 防火墙是client-server模型中网络安全的基础之一&#xff0c;但它们容易受到以下方面的…

连ChatGPT都不懂的五一调休,到底怎么来的?

今天是周几&#xff1f; 你上了几天班了&#xff1f; 还要上几天班放假&#xff1f; 五一啥安排&#xff1f; 出行的票抢到了吗&#xff1f; 调休到底是谁发明的&#xff1f;&#xff01; 五一劳动节是要劳动吗&#xff1f; 为什么昨天是周一&#xff0c;今天还是周一&a…