HTTP协议重定向及交互

1.重定向概念

将客服端的请求从一个URL转发到另一URL,就是本来要去超市买东西,结果上面挂了个牌子说在另一半开,就要去另一边买,不再是原来的地方买东西。

302状态码

302状态码表示请求的资源暂时被移动到一个新的URL,客服端应该使用临时的URL获取资源,但是这个重定向是临时的,原来的URL也是有效的。

现象就是浏览器会自动跳到新的URL,使用场景如网战维护期间将用户重定向到一个临时界面。

进行临时重定向需要用到Location字段,Location字段是HTTP报头当中的一个属性信息,表明要重定向到的目标网站。

301状态码

301状态码表示请求的资源已经被永久的移动到了一个新的URL,客服端应该使用新的URL来获取资源,这个重定向是永久的,原来的URL不在有效。

例子

HTTP/1.1 302 Found

Location: http://example.com/newpage

客服端请求一个地址时,服务器返回302状态码,并将客服端重定向到Location对应的值的地址。

2.HTTP的方法

GET方法一般用户获取某种资源,POST方法一般将资源上传给服务器,GET和POST都可以传参,GET方法通过URL传参,POST通过正文传参。GET在URL传参有长度限制,POST正文传参能包含更多的数据。

两种方法都是不安全的,可以被爬取,密码就会被爬取出去,要安全就需要对数据进行加密。

3.重定向与交互实现

HttpRequest类还要加入两个成员变量,一个判断是否要进行交互(_is_interact),一个存储交互后的信息(_args),也在这个类这里提供了接口函数。提取出交互的有效信息先找?,问号前面是方法,后面就是交互信息,通过find函数找?就可以得到两个,一个给uri存储一个给args存储。

cpp 复制代码
#pragma once

#include "Socket.hpp"
#include "TcpServer.hpp"
#include "Util.hpp"
#include "Log.hpp"
#include <iostream>
#include <string>
#include <memory>
#include <sstream>
#include <functional>
#include <unordered_map>

using namespace SocketModule;
using namespace LogModule;

const std::string gspace = " ";
const std::string glinespace = "\r\n";
const std::string glinesep = ": ";

const std::string webroot = "./wwwroot";
const std::string homepage = "index.html";
const std::string page_404 = "/404.html";

class HttpRequest
{
public:
    HttpRequest() : _is_interact(false)
    {
    }
    std::string Serialize()
    {
        return std::string();
    }
    void ParseReqLine(std::string &reqline)
    {
        // GET / HTTP/1.1
        std::stringstream ss(reqline);
        ss >> _method >> _uri >> _version;
    }
    // 实现, 我们今天认为,reqstr是一个完整的http request string
    bool Deserialize(std::string &reqstr)
    {
        // 1. 提取请求行
        std::string reqline;
        bool res = Util::ReadOneLine(reqstr, &reqline, glinespace);
        LOG(LogLevel::DEBUG) << reqline;

        // 2. 对请求行进行反序列化
        ParseReqLine(reqline);

        if (_uri == "/")
            _uri = webroot + _uri + homepage; // ./wwwroot/index.html
        else
            _uri = webroot + _uri; // ./wwwroot/a/b/c.html

        //  _uri: ./wwwroot/login?username=zhangsan&password=123456

        // if(_method == "POST")
        // {
        //     _is_interact = true;
        //     while(true)
        //     {
        //         Util::ReadOneLine(reqstr, &reqline, glinespace);
        //         if(reqline != glinespace)
        //         {
        //             // 获得了request header->key value
        //             // Content-Length;
        //         }
        //         else
        //         {
        //             break;
        //         }
        //     }
            // Util::ReadOneLine(reqstr, &reqline, glinespace); // 正文数据
        //}

        LOG(LogLevel::DEBUG) << "_method: " << _method;
        LOG(LogLevel::DEBUG) << "_uri: " << _uri;
        LOG(LogLevel::DEBUG) << "_version: " << _version;
        const std::string temp = "?";
        auto pos = _uri.find(temp);
        if (pos == std::string::npos)
        {
            return true;
        }
        // _uri: ./wwwroot/login
        // username=zhangsan&password=123456
        _args = _uri.substr(pos + temp.size());
        _uri = _uri.substr(0, pos);
        _is_interact = true;

        // ./wwwroot/XXX.YYY
        return true;
    }
    std::string Uri() { return _uri; }
    bool isInteract() { return _is_interact; }
    std::string Args() {return _args; }
    ~HttpRequest()
    {
    }

private:
    std::string _method;
    std::string _uri;
    std::string _version;

    std::unordered_map<std::string, std::string> _headers;
    std::string _blankline;
    std::string _text;

    std::string _args;
    bool _is_interact;
};

HttpResonse类的MakeResponse写了重定向的操作,如果targetfile的值是redir_test就会跳转到qq页面,这个就是永久重定向,状态码为301,SetHeader建立了报头信息,Location关键字对应qq地址,如果你要读取到文件内容也会也可以设置状态码302进行暂时重定向到404界面。

cpp 复制代码
class HttpResponse
{
public:
    HttpResponse() : _blankline(glinespace), _version("HTTP/1.0")
    {
    }
    // 实现: 成熟的http,应答做序列化,不要依赖任何第三方库!
    std::string Serialize()
    {
        std::string status_line = _version + gspace + std::to_string(_code) + gspace + _desc + glinespace;
        std::string resp_header;
        for (auto &header : _headers)
        {
            std::string line = header.first + glinesep + header.second + glinespace;
            resp_header += line;
        }

        return status_line + resp_header + _blankline + _text;
    }
    void SetTargetFile(const std::string &target)
    {
        _targetfile = target;
    }
    void SetCode(int code)
    {
        _code = code;
        switch (_code)
        {
        case 200:
            _desc = "OK";
            break;
        case 404:
            _desc = "Not Found";
            break;
        case 301:
            _desc = "Moved Permanently";
            break;
        case 302:
            _desc = "See Other";
            break;
        default:
            break;
        }
    }
    void SetHeader(const std::string &key, const std::string &value)
    {
        auto iter = _headers.find(key);
        if (iter != _headers.end())
            return;
        _headers.insert(std::make_pair(key, value));
    }
    std::string Uri2Suffix(const std::string &targetfile)
    {
        // ./wwwroot/a/b/c.html
        auto pos = targetfile.rfind(".");
        if (pos == std::string::npos)
        {
            return "text/html";
        }

        std::string suffix = targetfile.substr(pos);
        if (suffix == ".html" || suffix == ".htm")
            return "text/html";
        else if (suffix == ".jpg")
            return "image/jpeg";
        else if (suffix == "png")
            return "image/png";
        else
            return "";
    }

    bool MakeResponse()
    {
        if (_targetfile == "./wwwroot/favicon.ico")
        {
            LOG(LogLevel::DEBUG) << "用户请求: " << _targetfile << "忽略它";
            return false;
        }
        if (_targetfile == "./wwwroot/redir_test")
        {
            SetCode(301);
            SetHeader("Location", "https://www.qq.com/");
            return true;
        }
        int filesize = 0;
        bool res = Util::ReadFileContent(_targetfile, &_text); // 浏览器请求的资源,一定会存在吗?出错呢?
        if (!res)
        {
            _text = "";
            LOG(LogLevel::WARNING) << "client want get : " << _targetfile << " but not found";
            SetCode(404);
            _targetfile = webroot + page_404;
            filesize = Util::FileSize(_targetfile);
            Util::ReadFileContent(_targetfile, &_text);
            std::string suffix = Uri2Suffix(_targetfile);
            SetHeader("Content-Type", suffix);
            SetHeader("Content-Length", std::to_string(filesize));
            // SetCode(302);
            // SetHeader("Location", "http://8.137.19.140:8080/404.html");
            // return true;
        }
        else
        {
            LOG(LogLevel::DEBUG) << "读取文件: " << _targetfile;
            SetCode(200);
            filesize = Util::FileSize(_targetfile);
            std::string suffix = Uri2Suffix(_targetfile);
            SetHeader("Conent-Type", suffix);
            SetHeader("Content-Length", std::to_string(filesize));
        }
        return true;
    }
    void SetText(const std::string &t)
    {
        _text = t;
    }
    bool Deserialize(std::string &reqstr)
    {
        return true;
    }
    ~HttpResponse() {}

    // private:
public:
    std::string _version;
    int _code;         // 404
    std::string _desc; // "Not Found"

    std::unordered_map<std::string, std::string> _headers;
    std::string _blankline;
    std::string _text;

    // 其他属性
    std::string _targetfile;
};

定义了一个回调函数,参数为HttpRequest和HttpResponse类,也就是请求和响应的类,HandlerHttpRquest函数先在套接字里读取信息,读取成功走if,创建请求对象和响应对象,调用请求对象的类方法对获取的信息反序列化,如果交互布尔值为1走if,类成员有一个route对象类型是map(pair是string和回调函数),如果在route没有找到键就可以暂时重定向,如果有就调用这个键对应的值,也就是执行对应这个键的函数,把执行后的信息进行序列化并发送。RegisterService函数就是要传入一个键和一个回调函数值,然后把webroot与传入的键拼接形成完整的格式(./wwwroot/login),查询如果没有查到就构建pair并插入到route中。

cpp 复制代码
using http_func_t = std::function<void(HttpRequest &req, HttpResponse &resp)>;

// 1. 返回静态资源
// 2. 提供动态交互的能力
class Http
{
public:
    Http(uint16_t port) : tsvrp(std::make_unique<TcpServer>(port))
    {
    }
    void HandlerHttpRquest(std::shared_ptr<Socket> &sock, InetAddr &client)
    {
        // 收到请求
        std::string httpreqstr;
        // 假设:概率大,读到了完整的请求
        // bug!
        int n = sock->Recv(&httpreqstr); // 浏览器给我发过来的是一个大的http字符串, 其实我们的recv也是有问题的。tcp是面向字节流的.
        if (n > 0)
        {
            std::cout << "##########################" << std::endl;
            std::cout << httpreqstr;
            std::cout << "##########################" << std::endl;
            // 对报文完整性进行审核 -- 缺
            // 所以,今天,我们就不在担心,用户访问一个服务器上不存在的资源了.
            // 我们更加不担心,给用户返回任何网页资源(html, css, js, 图片,视频)..., 这种资源,静态资源!!
            HttpRequest req;
            HttpResponse resp;
            req.Deserialize(httpreqstr);
            if (req.isInteract())
            {
                // _uri: ./wwwroot/login
                if (_route.find(req.Uri()) == _route.end())
                {
                    // SetCode(302)
                }
                else
                {
                    _route[req.Uri()](req, resp);
                    std::string response_str = resp.Serialize();
                    sock->Send(response_str);
                }
            }
            else
            {
                resp.SetTargetFile(req.Uri());
                if (resp.MakeResponse())
                {
                    std::string response_str = resp.Serialize();
                    sock->Send(response_str);
                }
            }

            // HttpResponse resp;
            // resp._version = "HTTP/1.1";
            // resp._code = 200; // success
            // resp._desc = "OK";
            // //./wwwroot/a/b/c.html
            // LOG(LogLevel::DEBUG) << "用户请求: " << filename;
            // bool res = Util::ReadFileContent(filename, &(resp._text)); // 浏览器请求的资源,一定会存在吗?出错呢?
            // (void)res;
        }

// #ifndef DEBUG
// #define DEBUG
#ifdef DEBUG
        // 收到请求
        std::string httpreqstr;
        // 假设:概率大,读到了完整的请求
        sock->Recv(&httpreqstr); // 浏览器给我发过来的是一个大的http字符串, 其实我们的recv也是有问题的。tcp是面向字节流的.
        std::cout << httpreqstr;

        // 直接构建http应答. 内存级别+固定
        HttpResponse resp;
        resp._version = "HTTP/1.1";
        resp._code = 200; // success
        resp._desc = "OK";

        std::string filename = webroot + homepage; // "./wwwroot/index.html";
        bool res = Util::ReadFileContent(filename, &(resp._text));
        (void)res;
        std::string response_str = resp.Serialize();
        sock->Send(response_str);
#endif
        // 对请求字符串,进行反序列化
    }
    void Start()
    {
        tsvrp->Start([this](std::shared_ptr<Socket> &sock, InetAddr &client)
                     { this->HandlerHttpRquest(sock, client); });
    }
    void RegisterService(const std::string name, http_func_t h)
    {
        std::string key = webroot + name; // ./wwwroot/login
        auto iter = _route.find(key);
        if (iter == _route.end())
        {
            _route.insert(std::make_pair(key, h));
        }
    }
    ~Http()
    {
    }

private:
    std::unique_ptr<TcpServer> tsvrp;
    std::unordered_map<std::string, http_func_t> _route;
};

Main.cc

主函数创建Http类的智能指针后,先调用RegisterService函数进行插入pair,也实现了回调函数的具体操作,Login就是调用接口函数获取了args的值进行打印,设置状态码200,设置报头信息,内容类型为text/plain型纯文本,会看到输入的信息在这个文本上。

cpp 复制代码
#include "Http.hpp"

void Login(HttpRequest &req, HttpResponse &resp)
{
    LOG(LogLevel::DEBUG) << req.Args() << ", 我们成功进入到了处理数据的逻辑";
    std::string text = "hello: " + req.Args();

    // 登录认证

    resp.SetCode(200);
    resp.SetHeader("Content-Type","text/plain");
    resp.SetHeader("Content-Length", std::to_string(text.size()));
    resp.SetText(text);
}
void Register(HttpRequest &req, HttpResponse &resp)
{
    LOG(LogLevel::DEBUG) << req.Args() << ", 我们成功进入到了处理数据的逻辑";
    std::string text = "hello: " + req.Args();



    resp.SetCode(200);
    resp.SetHeader("Content-Type","text/plain");
    resp.SetHeader("Content-Length", std::to_string(text.size()));
    resp.SetText(text);
}
void VipCheck(HttpRequest &req, HttpResponse &resp)
{
    LOG(LogLevel::DEBUG) << req.Args() << ", 我们成功进入到了处理数据的逻辑";
    std::string text = "hello: " + req.Args();
    resp.SetCode(200);
    resp.SetHeader("Content-Type","text/plain");
    resp.SetHeader("Content-Length", std::to_string(text.size()));
    resp.SetText(text);
}

void Search(HttpRequest &req, HttpResponse &resp)
{

}


// http port
int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        std::cout << "Usage: " << argv[0] << " port" << std::endl;
        exit(USAGE_ERR);
    }
    uint16_t port = std::stoi(argv[1]);

    std::unique_ptr<Http> httpsvr = std::make_unique<Http>(port);
    httpsvr->RegisterService("/login", Login); // 
    httpsvr->RegisterService("/register", Register);
    httpsvr->RegisterService("/vip_check", VipCheck);
    httpsvr->RegisterService("/s", Search);
    //httpsvr->RegisterService("/", Login);

    httpsvr->Start();
    return 0;
}

额外

百度的请求方法是/s,也就说把这个方法进行封装,就可以实现百度的搜索,就是套了一层外壳,本质还是百度。

相关推荐
christine-rr20 分钟前
【25软考网工】第四章无线通信网(1)移动通信与4G/5G技术、CDMA计算
网络·5g·网络工程师·软考·考试
caimouse25 分钟前
C#里创建一个TCP客户端连接类
java·网络·tcp/ip
DanmF--1 小时前
详解UnityWebRequest类
网络·unity·c#·游戏引擎·游戏程序
北海有初拥1 小时前
【Linux网络#1】:网络基础知识
网络·智能路由器
MerlinTheMagic1 小时前
Tshark:强大的命令行网络抓包与分析工具
网络
我的golang之路果然有问题2 小时前
案例速成GO+Socket,个人笔记
开发语言·笔记·后端·websocket·学习·http·golang
凢en2 小时前
NOC科普一
网络·笔记·算法·智能路由器·硬件工程
言之。4 小时前
Go语言Context机制深度解析:从原理到实践
服务器·网络·golang
Antonio9154 小时前
【网络编程】UDP协议 和 Socket编程
网络·udp·网络编程