Linux 重定向与Cookie

一.重定向

1.重谈状态码

由状态码表我们知道,3开头的状态码代表着重定向。

临时重定向------不改变任何信息,常用来做登录跳转

永久重定向------网站更换域名,旧网站不删除,做一个永久重定向的操作,会改变用户的信息

简单来说,重定向的功能就是:例如我们访问老网站s1 ,此时s1会返回给我们一个response报文 ,其中Location报头显示新的s2地址,并且没有正文。

做一个简单的测试:比如当客户端访问该网址时,MakeResponse需要SetCode为30X,并且做重定向工作。

cpp 复制代码
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));
            SetHeader("Set-Cookie", "username=zhangsan;");
            // SetHeader("Set-Cookie", "passwd=123456;");
        }
        return true;
    }

通过这个简单的例子,我们就可以调整对404错误的处理:重定向到我们的内部资源。

我们上面实现的,是短链接。在上网都用电脑的时代,最重要的软件就是浏览器,所有人想上网,都会打开浏览器。微软为了占据一部分市场,直接在windows系统中预装IE浏览器,在IE中内置自己的搜索引擎。对应的,谷歌直接开源了chrome浏览器。越来越多的浏览器以及浏览器技术,需要一个标准来统一。

2.HTTP的请求方法

1.GET方法

获取资源(静态资源)

在request报文的请求方法中,包含这些字段。实际上也可以进行资源上传

2.POST方法

上传对应的数据。问题:怎么把数据上传?

最常见的就是上传登录数据------表单。这里用Login页面的表单为例:

html 复制代码
<body>
    <div class="login-container">
        <h2>Login</h2>
        <form action="/login" method="GET">
            <div class="form-group">
                <label for="username">Username</label>
                <input type="text" id="username" name="username" required>
            </div>
            <div class="form-group">
                <label for="password">Password</label>
                <input type="password" id="password" name="password" required>
            </div>
            <div class="form-group">
                <input type="submit" value="Login">
            </div>
            <a href="Register.html">Login</a> <!-- 跳转到登录页面 -->
            <a href="index.html">Register</a> <!-- 跳转到注册页面 -->
        </form>
    </div>
</body>

input标签和password标签会被解释成输入框,submit会被解释称提交按钮。

关键:action中显式/login,他表示将表单提交给后端的那个服务,而method为POST------数据提交

表单的信息会被拼接,比如用户名和密码

101.34.228.219:8080/login?username=zs&password=123456

login及之前的字段代表目标地址和目标服务,问好之后的字段就代表form表单提交的信息。

对于GET方法:也可以进行数据提交,不过需要通过URI提交。

我们在Main.cc代码中添加服务Login

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

    // 登录认证

    resp.SetCode(200);
    resp.SetHeader("Content-Type","text/plain");
    resp.SetHeader("Content-Length", std::to_string(text.size()));
    resp.SetText(text);
}

std::unique_ptr<Http> httpsvr = std::make_unique<Http>(port);
    httpsvr->RegisterService("/login", Login);

此时request报文中的uri,会有请求的资源。我们需要在request中修改

是普通的请求资源,还是处理上传数据?我们只要从请求报文中根据"?"提取URI进行分析即可。如果报文中不含"?",说明只是简单的资源请求,而不是上传数据。

cpp 复制代码
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;

注册处理函数

cpp 复制代码
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));
        }
    }

//而这个_route,是一个string,func类型的map,存储名字为string的函数处理方法

std::unordered_map<std::string, http_func_t> _route;

因此在处理request,我们来判断是否需要交互 :如果需要交互,就根据登录请求调用响应方法(方法存在),如果不需要交互,就是普通的资源请求。

cpp 复制代码
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);
                }
            }

同样地,我们可以注册更多处理方法,来受理客户端发来的请求。

以上的操作,就类似用http为用户提供了微服务接口。

POST提交和GET提交有什么区别?

POST提交的报文,参数在正文中。

而不是GET的URI中。

注意:GET会在地址栏回显,也就意味着POST会更加私密(注意,并不代表着安全!)。POST提交的参数在正文中,相较于GET提交在URI中,更适合传入长参数。

为什么不安全?例如一个抓包工具fiddler:

所以我们要对报文进行加密:https协议。

3.长连接connection

常见其他选项:put,delete不常用,head主要用于测试,不包含正文

option方法:可以用nginx测试,如果当前服务器支持,在响应报头中会有allow字段。

HTTP/1.1 200 OK

Allow: GET, HEAD, POST, OPTIONS Content-Type: text/plain Content-Length: 0

Server: nginx/1.18.0 (Ubuntu) Date: Sun, 16 Jun 2024 09:04:44 GMT Access-Control-Allow-Origin: *

Access-Control-Allow-Methods: GET, POST, OPTIONS Access-Control-Allow-Headers: Content-Type, Authorization

connection报头:我们当前的服务器模式,只受理一个请求一个应答就关闭。这种特点是http 1.0的主要工作方式,因为在这个标准所处的时代,网络资源规模比较小。

在现实中,当我们创立一个网页,其中有各种文本,尤其会有大量的音视频,每一种资源若都通过一个http请求和响应获取,是十分浪费服务器性能的。于是有了长连接------基于一个连接发起多个HTTP request请求,服务器就可以根据请求顺序构建对应的多个response应答。

response与request中的Connection报头:如果当前的主机支持长连接,Connection报头会显示keep-alive

如果Connection均为keep-alive,就会默认采用长连接方式进行通信。

而我们在自己实现的TCP中,默认是只读一个请求的。

cpp 复制代码
//执行一次callback就close
else if (id == 0)
            {
                // 子进程 -> listensock
                _listensockptr->Close();
                if (fork() > 0)
                    exit(OK);
                // 孙子进程在执行任务,已经是孤儿了
                callback(sock, client);
                sock->Close();
                exit(OK);
            }

我们要连续读到多个完整的request。我们写的网络计算器,就是长连接的,当时是如何做到的?TCP的接收缓冲区中,while循环逐次解析每个完整的请求报文!

二.cookie与session

1.回顾HTTP

HTTP,即超文本传输协议,因为它请求访问的是web根目录下的资源,其中包括音视频等等,不再局限于文本

HTTP是一个无连接无状态的协议。

问题:刚刚不是说,HTTP有长连接吗?

指的是让TCP保持长连接,而HTTP其实无关链接概念,只跟request和response强相关。链接的长短,都是TCP底层去进行维护的。

那么无状态 ,是什么意思?指的是对于服务器,它不关心你历史上 是否请求过这一资源,只要你请求就会返回,服务器不会保存客户端的状态信息

这种无状态,实际上会给用户造成困扰:每访问一次资源,都是一次新的http请求 ;如果要求我们以**登录状态(客户端状态)**访问服务器,登录之后返回给用户网页页面,用户挑选要访问的资源(例如一个电影),就需要再一次进行登录认证。HTTP为了解决这个问题,引入了cookie。

2.cookie与session

cookie的工作流程:其实麻烦的不是认证本身,而是这个工作需要人手动完成。而cookie就解决了这个问题。

我们可以做一个简单的例子:bilibili的登录,就会使用cookie。

于是我们根据之前写的HTTP,为MakeResponse的同时构建cookie信息。

cookie有其特定的数据格式:

Set-Cookie: username=peter; expires=Thu, 18 Dec 2024 12:00:00 UTC; path=/; domain=.example.com; secure; HttpOnly

◦ Tue: 星期⼆(星期⼏的缩写)

◦ ,: 逗号分隔符

◦ 01: ⽇期(两位数表⽰)

◦ Jan: ⼀⽉(⽉份的缩写)

◦ 2030: 年份(四位数)

◦ 12:34:56: 时间(⼩时、分钟、秒)

◦ GMT: 格林威治标准时间(时区缩写)

我们先进行最简单的测试:(后续,我们可能需要用一个额外的容器存储cookie对象)

cpp 复制代码
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));
            SetHeader("Set-Cookie", "username=zhangsan;");
            // SetHeader("Set-Cookie", "passwd=123456;");
        }

然后客户端再发起请求时,就应该携带这个Set-Cookie。

当前这个cookie的问题:

内存级cookie,会随着浏览器关闭而失效

当用户请求服务器资源时,有黑客拦截请求,获取其中的cookie字段,并且拷贝到自己浏览器的某个位置中,这样黑客也向浏览器发起请求,就获取了你本该获取的资源!

用户名的密码,用户名,浏览痕迹等等若都再cookie中,私密信息和账号密码就全部泄漏了。

为了 一定成都上解决安全问题,就引入了session。

每一个session都有标识其唯一性的sessionID ,用户的私密信息就在其中。set-Cookie依然存在,只不过其中的字段变成了当前用户的sessionID

session实际上是一种类。问题是,session是如何真正做到安全性的?虽然黑客以用户身份进行访问,但是用户的私密信息已经再服务端保存了。一个方案是很难做到面面俱到的防护的,所以往往还会搭配其他的辅助手段,例如ip溯源,地址变更等等free掉session,强行让用户重新登录达到防护。

因此会话管理与会话保持的技术就通过:cookie+session实现。

相关推荐
ytttr8732 小时前
Rocky Linux 8.9配置Kubernetes集群详解,适用于CentOS环境
linux·kubernetes·centos
HLJ洛神千羽2 小时前
Linux下程序设计综合实验报告——图书管理系统(黑龙江大学)
linux·运维·服务器
观北海2 小时前
网络安全等保测评实践指南:从理论到技术实现
网络·安全·web安全
Mr_汪2 小时前
离线工程集成其他推送
前端
惜茶2 小时前
使用前端框架vue做一个小游戏
前端·vue.js·前端框架
云盾安全防护2 小时前
DNS防护:企业网络稳定性的第一道隐形防线
网络
学渣676562 小时前
个人笔记|单臂路由,子接口,VLAN标签
网络·笔记·智能路由器
普通码农2 小时前
Vue 3 接入谷歌登录 (小白版)
前端·vue.js