http协议(一)/应用层

news2024/11/29 13:37:51

学习目标:⭐理解应用层的作用,理解协议,理解序列化和反序列化,并且实现网络版计算器⭐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采用的网络请求方案是短链接

......

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

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

相关文章

第三节:支持向量机分类预测

0、介绍 监督学习&#xff08;英语&#xff1a;Supervised learning&#xff09;是机器学习中最为常见、应用最为广泛的分支之一。本次实验将带你了解监督学习中运用十分广泛的支持向量机&#xff0c;并学会使用 scikit-learn 来构建预测模型&#xff0c;用于解决实际问题。 知…

都什么年代了,还在用Excel和ACCESS做应用系统?快来学Eversheet

表格用的越久&#xff0c;就越头疼 稍微有规模的企业&#xff0c;各种表格都会多如牛毛&#xff0c;一堆堆的&#xff0c;有时候这里一张&#xff0c;那里一张&#xff0c;容易整乱&#xff0c;更容易丢失。不管你是用WPS还是用Excel&#xff0c;有些问题你还是依旧解决不了。…

【VM服务管家】VM4.x算法模块开发_4.1 开发配置类

目录 4.1.1 算法开发&#xff1a;算法模块的开发流程4.1.2 参数操作&#xff1a;获取与设置模块参数的方法4.1.3 文件交互&#xff1a;文件交互操作的配置方法4.1.4 输出显示&#xff1a;设置输出并显示在VM界面的方法4.1.5 模板配置&#xff1a;模板配置界面的实现方法4.1.6 命…

中文译英文 模型

Helsinki-NLP/opus-mt-zh-en Hugging FaceWe’re on a journey to advance and democratize artificial intelligence through open source and open science.https://huggingface.co/Helsinki-NLP/opus-mt-zh-en?text%E6%88%91%E5%8F%AB%E6%B2%83%E5%B0%94%E5%A4%AB%E5%86%8…

【Java入门合集】第一章Java概述

【Java入门合集】第一章Java概述 博主&#xff1a;命运之光 专栏&#xff1a;JAVA入门 学习目标 1.理解JVM、JRE、JDK的概念&#xff1b; 2.掌握Java开发环境的搭建,环境变量的配置&#xff1b; 3.掌握Java程序的编写、编译和运行&#xff1b; 4.学会编写第一个Java程序&#x…

Python 科研绘图可视化(后处理)Matplotlib - RGBAxes

Introduction 科研可视化是将数据和信息转化为可视化形式的过程&#xff0c;旨在通过图形化展示数据和信息&#xff0c;使得科研工作者能够更好地理解和分析数据&#xff0c;并从中发现新的知识和洞见。科研可视化可以应用于各种领域&#xff0c;如生物学、物理学、计算机科学…

一文带你入门C++类和对象【十万字详解,一篇足够了】

本文字数较多&#xff0c;建议电脑端访问。不多废话&#xff0c;正文开始 文章目录 ———————————————【类和对象 筑基篇】 ———————————————一、前言二、面向过程与面向对象三、结构体与类1、C中结构体的变化2、C中结构体的具体使用3、结构体 --&…

3.6 Linux shell脚本编程(概念、变量、语句)

目录 shell脚本概述 shell脚本编写步骤 第一个shell脚本文件 shell脚本变量 变量的介绍 变量的作用 变量的命名要求 变量的分类 用户自定义变量 取值 用户自定义变量-数组 只读变量 位置变量与预定义变量 环境变量 shell语句 shell程序 说明性语句&#xff08…

MATLAB连续时间信号的实现和时域基本运算(八)

1、实验目的&#xff1a; 1&#xff09;熟悉常用连续时间信号的实现方法&#xff1b; 2&#xff09;掌握连续时间信号的时域基本运算&#xff1b; 3&#xff09;掌握实现基本函数及其运算的函数的使用方法&#xff1b; 4&#xff09;加深对信号基本运算的理解。 2、实验内容&am…

【VM服务管家】VM4.0平台SDK_2.4 结果获取类

目录 2.4.1 数据结果&#xff1a;通过流程输出或模块输出获取数据结果的方法2.4.2 流程运行&#xff1a;所有流程运行结束的回调方法2.4.3 模块回调&#xff1a;所有模块运行结束的回调方法2.4.4 加密狗回调&#xff1a;获取加密狗状态的回调方法2.4.5 方案加载&#xff1a;方案…

STM32物联网实战开发(4)——基本定时器

我使用的是正点原子的阿波罗F429开发板&#xff0c;他有14个定时器&#xff0c;本次实验使用STM32F429的基本定时器6作定时&#xff0c;在中断中每隔1秒翻转LED电平状态。 1.CubeMX初始化定时器 先开启定时器6 再对定时器6的参数进行配置&#xff0c;将定时器6定时时间配置为…

记一次SSRF漏洞的学习和利用

导语&#xff1a;本文主要记录一次我们在复盘嘶吼网站渗透报告时遇到的一个SSRF漏洞。 1.前言 本文主要记录一次我们在复盘嘶吼网站渗透报告时遇到的一个SSRF漏洞。此漏洞并结合腾讯云的API接口&#xff0c;可以获取大量嘶吼服务器的敏感信息。利用这些敏感信息&#xff0c;又…

阿里测试8年,肝到P8只剩他了····

在阿里工作了8年&#xff0c;工作压力大&#xff0c;节奏快&#xff0c;但是从技术上确实得到了成长&#xff0c;尤其是当你维护与大促相关的系统的时候&#xff0c;熬到P7也费了不少心思&#xff0c;小编也是个爱学习的人&#xff0c;把这几年的工作经验整理成了一份完整的笔记…

玩转ChatGPT提示词 持续更新·······

导语&#xff1a; 众所周知&#xff0c;在AI的世界里&#xff0c;提示词就是和AI沟通语言的桥梁&#xff0c;提示关键词常用于AI对话及AI绘画等相关场景&#xff0c;通过准确的使用关键词&#xff0c;你就能更好的让AI辅助自己的工作&#xff0c;其中的成分重要性不言而喻&…

黑客教程,从零基础入门到精通

学前感言: 1.这是一条坚持的道路,三分钟的热情可以放弃往下看了. 2.多练多想,不要离开了教程什么都不会了.最好看完教程自己独立完成技术方面的开发. 3.有时多google,baidu,我们往往都遇不到好心的大神,谁会无聊天天给你做解答. 4.遇到实在搞不懂的,可以先放放,以后再来解决. …

c++标准模板(STL)(std::array)(三)

定义于头文件 <array> template< class T, std::size_t N > struct array;(C11 起 std::array 是封装固定大小数组的容器。 此容器是一个聚合类型&#xff0c;其语义等同于保有一个 C 风格数组 T[N] 作为其唯一非静态数据成员的结构体。不同于 C 风格数组…

C#非常实用的技巧

1、解压和压缩 .NET Framework 4.5以上版本&#xff1a; string zipFilePath "C:\path\to\file.zip";string destFolder "C:\path\to\destination\folder";using (var archive ZipFile.OpenRead(zipFilePath)){foreach (var entry in archive.Entries…

【Python】【进阶篇】14、Django创建第一个项目

目录 Django创建第一个项目1. 第一个项目BookStore1) BookStore项目创建 2. Django项目配置文件1) manage.py文件2) __init__.py文件3) settings.py文件4) urls.py文件5) wsgi.py文件 Django创建第一个项目 在上一章中&#xff0c;我们完成了开发环境的搭建工作。 本章我们将学…

网络安全基础入门学习路线

在大多数的思维里总觉得学习网络安全得先收集资料、学习编程、学习计算机基础&#xff0c;这样不是不可以&#xff0c;但是这样学效率太低了&#xff01; 你要知道网络安全是一门技术&#xff0c;任何技术的学习一定是以实践为主的。也就是说很多的理论知识其实是可以在实践中…

【一起撸个DL框架】4 反向传播求梯度

CSDN个人主页&#xff1a;清风莫追 欢迎关注本专栏&#xff1a;《一起撸个DL框架》 文章目录 4 反向传播求梯度&#x1f965;4.1 简介4.2 导数与梯度4.3 链式法则4.4 示例&#xff1a;y2x1的梯度 4 反向传播求梯度&#x1f965; 4.1 简介 上一篇&#xff1a;【一起撸个DL框架】…