【网络编程】应用层协议——HTTP协议

news2024/11/29 2:45:35

文章目录

  • 一、HTTP协议基本认识
  • 二、URL的认识
    • 2.1 urlencode和urldecode
  • 三、HTTP协议格式
    • 3.1 HTTP请求与响应格式
    • 3.2 如何保证请求和响应被应用层完整读取?
    • 3.3 请求和响应如何做到序列化和反序列化?
    • 3.4 代码验证请求格式
    • 3.5 代码验证响应格式
      • 3.5.1 telnet命令
    • 3.6 解析状态行信息
    • 3.7 web根目录
    • 3.8 获取服务器资源
  • 四、HTTP的请求方法
    • 4.1 GET方法
    • 4.2 POST方法
  • 五、 HTTP状态码
    • 5.1 重定向状态码(3XX)
  • 六、HTTP常见的报头信息
  • 七、HTTP长连接
  • 八、HTTP会话保持(Cookie和Session)

一、HTTP协议基本认识

HTTP全称超文本传输协议,是一个简单的请求-响应协议,HTTP通常运行在TCP之上。

在上一章【网络编程】自定义协议+Json序列化与反序列化实现的网络计算机中对数据的处理计算就是我们自己手写的应用层协议。

在编写网络通信代码时,我们可以自己进行协议的定制,但实际有很多优秀的工程师早就已经写出了许多非常成熟的应用层协议,其中最典型的就是HTTP协议。

二、URL的认识

其实URL就是平时我们所说的网址。
在这里插入图片描述

使用浏览器访问URL:通过域名找到唯一一台网络主机,而域名后面就是文件路径,通过文件找到我们想要的资源,可能是图片或者文本,把资源返回给浏览器。

在这里插入图片描述

这个/就是web根目录,一般而言,可以是Linux下的任意一个目录。

http的本质就是通过http协议从服务端拿下文件资源,而因为文件资源的种类特别多,http都能搞定,所以叫做超文本传输协议

2.1 urlencode和urldecode

像 / ? : 等这样的字符, 已经被url当做特殊意义理解了. 因此这些字符不能随意出现.
比如, 某个参数中需要带有这些特殊字符, 就必须先对特殊字符进行转义.
转义的规则如下:取出字符的ASCII码,转成16进制,然后前面加上百分号即可。
比如+被转成了%2B,这个过程就叫做encode,decode就是把特殊符号转回去。

这个过程不需要我们自己做,有需要解码的时候在网上查即可。
URL编码/解码

实际当服务器拿到对应的URL后,也需要对编码后的参数进行解码,此时服务器才能拿到你想要传递的参数,解码实际就是编码的逆过程。

三、HTTP协议格式

HTTP是基于请求和响应的应用层服务,作为客户端,你可以向服务器发起request,服务器收到这个request后,会对这个request做数据分析,得出你想要访问什么资源,然后服务器再构建response,完成这一次HTTP的请求,返回响应。

由于HTTP是基于请求和响应的应用层访问,因此我们必须要知道HTTP对应的请求格式和响应格式,这就是学习HTTP的重点。

3.1 HTTP请求与响应格式

HTTP请求与响应协议格式如下:
在这里插入图片描述

3.2 如何保证请求和响应被应用层完整读取?

首先要知道http所有请求字段都是按行为单位的字符串

比如说对于http请求,我们使用while循环按行读取,直到遇到空行为止,这样可以保证把请求行和请求报头读完。而报头的key: val结构就有一个属性是Content-Length: XXX,它表示的是正文的长度,由此,正文的也能完整读取了。

3.3 请求和响应如何做到序列化和反序列化?

如果现在我们想获得name的key值,怎么把数据从字符串中反序列化呢?

1️⃣ 对于报头部分,其实请求/响应报头布置包含name: val信息,后边还有字符串分隔符:name: val\r\n,序列化直接发送就行,想要反序列化就可以按照\r\n来按行提取所以http报头是用特殊字符进行信息分离
2️⃣ 对于正文部分,不用处理,如果需要的话,可以设计自定义序列化与反序列化方案。

3.4 代码验证请求格式

// Protocol.hpp
#pragma once
#include <iostream>
#include <string>
#include <vector>

class HttpRequest
{
public:
    std::string inbuf;// 缓冲区
    std::string req_line;// 请求行
    std::vector<std::string> req_header;// 请求报头
    std::string req_body;// 请求正文

    std::string req_method;// 请求方法
    std::string req_url;// url
    std::string http_version;// http版本
};

class HttpResponse
{
public:
    std::string outbuf;// 缓冲区
};

// HttpServer.hpp
#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <strings.h>
#include <cstdlib>
#include <sys/wait.h>
#include <pthread.h>
#include "Protocol.hpp"

static const uint16_t gport = 8080;
static const int gbacklog = 10;

typedef std::function<void(const HttpRequest&, HttpResponse&)> func_t;

class HttpServer
{
public:
    HttpServer(func_t func, const uint16_t& port = gport)
        : _func(func)
        , _listensock(-1)
        , _port(port)
    {}

    void InitServer()
    {
        _listensock = socket(AF_INET, SOCK_STREAM, 0);
        if(_listensock == -1)
        {
            exit(1);
        }
        struct sockaddr_in si;
        // 初始化结构体
        bzero(&si, sizeof si);
        si.sin_family = AF_INET;
        si.sin_port = htons(_port);// 主机转网络序列
        si.sin_addr.s_addr = INADDR_ANY;
        if(bind(_listensock, (struct sockaddr*)&si, sizeof si) < 0)
        {
            exit(1);
        }
        // 设置监听状态
        if(listen(_listensock, gbacklog) < 0)
        {
            exit(1);
        }
    }

    void HandlerHttp(int sock)
    {
        char buf[4096];
        HttpRequest req;
        HttpResponse resp;
        ssize_t n = recv(sock, buf, sizeof buf - 1, 0);
        if(n > 0)
        {
            buf[n] = '\0';
            req.inbuf = buf;
            // 通过请求获得响应
            _func(req, resp);
            send(sock, resp.outbuf.c_str(), resp.outbuf.size(), 0);
        }
        
    }

    void start()
    {
        while(1)
        {
            // 获取新链接
            struct sockaddr_in si;
            socklen_t len = sizeof si;
            int sock = accept(_listensock, (struct sockaddr*)&si, &len);
            if(sock < 0)
            {
                // 获取链接失败无影响,继续获取即可
                continue;
            }
            
            // 多进程
            pid_t id = fork();
            if(id == 0)// child
            {
                close(_listensock);
                if(fork() > 0) exit(1);
                HandlerHttp(sock);
                close(sock);
                exit(1);
            }
            close(sock);
            // father
            waitpid(id, nullptr, 0);
        }
    }

private:
    int _listensock;
    uint16_t _port;
    func_t _func;
};

// HttpServer.cc
#include <memory>
#include "HttpServer.hpp"

using namespace std;

bool Get(const HttpRequest& req, HttpResponse& resp)
{
    cout << "-------------http start-------------" << endl;
    cout << req.inbuf;
    cout << "-------------http end---------------" << endl;
    return true;
}

// ./HttpServer 8080
int main(int argc, char* argv[])
{
    if(argc != 2)
    {
        cerr << "Too few passed parameters\r\n\r\n";
        exit(0);
    }
    uint16_t port = atoi(argv[1]);
    unique_ptr<HttpServer> p(new HttpServer(Get, port));
    p->InitServer();
    p->start();
    return 0;
}

这里实现的就是一个简单的TCP服务器,而处理的任务就是把接收到的HTTP请求进行打印即可,服务器会把收到的数据全部放入请求缓冲区,然后直接打印出来。
客户端并不用我们自己实现,有一个现成的客户端就是浏览器。

在这里插入图片描述

  • 说明以下收到的请求:

对于请求行GET / HTTP/1.1
Get表示请求方法。
/ 表示url:url当中的/不能称之为我们云服务器上根目录,这个 /表示的是web根目录,这个web根目录可以是你的机器上的任何一个目录,这个是可以自己指定的,不一定就是Linux的根目录。
在这里插入图片描述
HTTP/1.1表示协议版本

  • 为什么请求要包含版本?

因为客户端的会存在更新的情况,但是有的客户端并没有更新,所以服务端要根据版本来提供不同的服务。

在这里插入图片描述

而请求报头进过验证也是name: val的格式。里面都是属性字段。

3.5 代码验证响应格式

bool Get(const HttpRequest& req, HttpResponse& resp)
{
    cout << "-------------http start-------------" << endl;
    cout << req.inbuf;
    cout << "-------------http end---------------" << endl;
    // 状态行
    std::string resp_line = "HTTP/1.1 200 OK\r\n";
    // 报头
    std::string resp_header = "Content-Type: text/html\r\n";
    // 空行
    std::string resp_blank = "\r\n";
    // 响应正文
    std::string resp_body = "<html><head></head><body><h1>Hello HTTP</h1></body></html>";
    resp.outbuf += resp_line;
    resp.outbuf += resp_header;
    resp.outbuf += resp_blank;
    resp.outbuf += resp_body;
    return true;
}

3.5.1 telnet命令

telnet 是一种用于远程访问和管理计算机网络设备、服务器和服务的协议和命令行工具。它可以用于连接到运行 Telnet 服务器软件的任何计算机,并在远程计算机上执行命令和操作。

通常我们会使用该命令传参测试你的服务器与其他的服务器是不是能正常访问

  • 使用示例
telnet ip地址  端口

telnet 127.0.0.1 8080

当使用 Telnet 命令连接到远程 IP 地址和端口时,如果连接成功,则会返回类似以下的响应:

Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.

其中,“Trying 127.0.0.1…” 表示正在尝试连接指定的 IP 地址,“Connected to 127.0.0.1.” 表示连接已经建立,Escape character is ‘^]’.” 是提示信息,表示可以使用 Ctrl + ] 来退出 Telnet 命令。


在这里插入图片描述

3.6 解析状态行信息

// Util.hpp
#pragma once

#include <iostream>

class Util
{
public:
    static std::string getOneLine(std::string& buf, const std::string& sep)
    {
        auto pos = buf.find(sep);
        if(pos == std::string::npos) return "";
        std::string sub = buf.substr(0, pos);
        // 读取完就删掉
        buf.erase(0, sub.size() + sep.size());
        return sub;
    }
};

// Protocol.hpp
const std::string sep = "\r\n";

class HttpRequest
{
public:
    void parse()
    {
        //1、从请求结构体中的inbuf中拿到请求行(第一行),分隔符\r\n
        std::string line = Util::getOneLine(inbuf, sep);
        if(line.empty()) return;
        //2、从请求行中获取三个字段:请求方法、url、请求版本
        std::cout << "line:" << line << std::endl;
        std::stringstream ss(line);
        ss >> req_method >> req_url >> http_version;//自动以空格为分割读取其中的字段
    }
public:
    std::string inbuf;// 缓冲区
    // std::string req_line;// 请求行
    // std::vector<std::string> req_header;// 请求报头
    // std::string req_body;// 请求正文

    std::string req_method;// 请求方法
    std::string req_url;// url
    std::string http_version;// http版本
};

这些代码的目的是把请求状态行的信息解析出来:
在这里插入图片描述

3.7 web根目录

上图的url 的/是web根目录。
这个根目录可以我们自己设置,比如果我们就设置在当前路径下:
在这里插入图片描述

// 默认路径
const std::string default_root = "./wwwroot";
// 添加web默认路径
std::string path = default_root;
path += req_url;

以后我们想要访问的资源就从wwwroot目录下开始,未来的所有资源放在这个目录里,可以通过url请求,例如:./wwwroot/a/b/c

那么如果直接是./wwwroot/呢?此时就可以获得主页资源

// 网站首页
const std::string home_page = "index.html";
// 添加web默认路径
path = default_root;
path += req_url;
if(path[path.size() - 1] == '/') path += home_page;

为了看到路径多打印一行信息:
在这里插入图片描述
在这里插入图片描述
当我们配置了主页文件就可以在浏览器中看到我们的主页了,在下面介绍。

3.8 获取服务器资源

读取资源其实就是读取文件。
在这里插入图片描述
通过请求构建响应:读取请求发送过来的路径,看是否存在
如果不存在,就给一个资源不存在的信息:

const std::string html_404 = "wwwroot/404.html";

在这里插入图片描述
目前我们只有网站首页存在:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
资源不存在的原因是在web根目录里无./wwwroot/a/b/c文件。


我们也可以多添加几个资源文件来获取看看:

在这里插入图片描述

然后在首页让我们能跳转a和b
**加粗样式**
在这里插入图片描述


我们发送给客户端资源,那么首先我们自己就需要知道这是什么类型的资源,那么怎么才能知道呢?

通过后缀,比如说.html、.jpg

所以我们可以在请求结构体添加一个成员变量表示path的后缀资源。

std::string suffix;// 后缀

此时我们就可以在报头中添加对应信息了:

std::string SuffixToDesc(const std::string suffix)
{
    std::string ct = "Content-Type: ";
    if(suffix == ".html")
    {
        // 通过网上查后缀对应的类型
        ct += "text/html";
    }
    else if(suffix == ".jpg")
    {
        ct += "application/x-jpg";
    }
    ct += "\r\n";
    return ct;
}
// 报头
std::string resp_header = SuffixToDesc(req.suffix);// 通过后缀转成描述

为了让网页更美观,可以添加图片信息,其实图片也是文件。
一个用户看到的网页结果可能是多个资源组合而成的,所以要获取一张完整的网页效果,浏览器会发送多次http请求。

为了方便观察,可以在请求的结构体中加入正文长度size后缀信息suffix
在这里插入图片描述

// prtocol.hpp
class HttpRequest
{
public:
    void parse()
    {
        //从请求结构体中的inbuf中拿到请求行(第一行),分隔符\r\n
        std::string line = Util::getOneLine(inbuf, sep);
        if(line.empty()) return;
        //从请求行中获取三个字段:请求方法、url、请求版本
        std::cout << "line:" << line << std::endl;
        std::stringstream ss(line);
        ss >> req_method >> req_url >> http_version;//自动以空格为分割读取其中的字段
        // 添加web默认路径
        path = default_root;
        path += req_url;
        if(path[path.size() - 1] == '/') path += home_page;
        // 获取后缀资源 .jpg
        auto pos = path.rfind(".");
        if(pos == std::string::npos)
        {
            // 默认
            suffix = ".html";
        }
        else
        {
            suffix = path.substr(pos);
        }
        // 获得资源的大小
        struct stat st;
        int n = stat(path.c_str(), &st);
        if(n == 0)
        {
            size = st.st_size;
        }
        else size = -1;
    }
public:
    std::string inbuf;// 缓冲区
    // std::string req_line;// 请求行
    // std::vector<std::string> req_header;// 请求报头
    // std::string req_body;// 请求正文
    
    std::string path;// 路径
    std::string req_method;// 请求方法
    std::string req_url;// url
    std::string http_version;// http版本
    std::string suffix;// 后缀
    int size;// 正文资源的大小
};
// HttpServer.cc
bool Get(const HttpRequest& req, HttpResponse& resp)
{
    cout << "-------------request start-------------" << endl;
    cout << req.inbuf << endl;
    cout << "method: " << req.req_method << endl;
    cout << "url: " << req.req_url << endl;
    cout << "version: " << req.http_version << endl;
    cout << "path: " << req.path << endl;
    cout << "suffix: " << req.suffix << endl;
    cout << "size: " << req.size << "byte" << endl;
    cout << "-------------request end---------------" << endl;
    // 状态行
    std::string resp_line = "HTTP/1.1 200 OK\r\n";
    // 报头
    std::string resp_header = SuffixToDesc(req.suffix);// 通过后缀转成描述
    // 正文长度
    if(req.size > 0)
    {
        resp_header += "Content-Length: ";
        resp_header += std::to_string(req.size);
        resp_header += "\r\n";
    }
    // 空行
    std::string resp_blank = "\r\n";
    // 响应正文
    // std::string resp_body = "<html><head></head><body><h1>Hello HTTP</h1></body></html>";
    //std::string resp_body;
    // 从文件中读取
    std::string resp_body;
    resp_body.resize(req.size+1);
    if(!Util::readFile(req.path, (char*)resp_body.c_str(), req.size))
    {
        // 资源不存在
        Util::readFile(html_404, (char*)resp_body.c_str(), req.size);
    }
    resp.outbuf += resp_line;
    resp.outbuf += resp_header;
    resp.outbuf += resp_blank;
    cout << "-------------response start-------------" << endl;
    cout << resp.outbuf << endl;
    cout << "-------------response end---------------" << endl;
    resp.outbuf += resp_body;
    return true;
}

// ./HttpServer 8080
int main(int argc, char* argv[])
{
    if(argc != 2)
    {
        cerr << "Too few passed parameters\r\n\r\n";
        exit(0);
    }
    uint16_t port = atoi(argv[1]);
    unique_ptr<HttpServer> p(new HttpServer(Get, port));
    p->InitServer();
    p->start();
    return 0;
}

因为图片是二进制文件,所以要用二进制读取:

static bool readFile(const std::string path, char* buf, int size)
{
    std::ifstream in(path);
    // 打开文件
    if(!in.is_open())
    {
        // 资源不存在
        return false;
    }
    else
    {
        std::ifstream in(path, std::ios::binary);
        if(!in.is_open()) return false;
        in.read(buf, size);
    }
    in.close();
    return true;
}

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

在这里插入图片描述

四、HTTP的请求方法

比方说我们在百度里搜索东西,要把数据提交到对应的框框里:
在这里插入图片描述
其实本质是前端通过form表单进行提交的,浏览器会自动将form表单里的内容转成GET/POST方法请求。

请求方法说明支持的http协议版本
GET获取资源1.0/1.1
POST传输实体主体1.0/1.1

4.1 GET方法

在这里插入图片描述

这一块整体就是个form表单,我们可以通过GET方法提交

在这里插入图片描述

所以可以看到GET方法可以把要提交的参数拼接到到url的后边。

在这里插入图片描述

4.2 POST方法

再来看看POST方法:
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
总结一下:

GET方法通过url传递参数
POST方法通过请求正文提交参数

因为POST通过请求正文传参,所以一般用户看不到,私密性(不等于安全性)更好,GET方法不私秘。
但是无论是GET或者是POST方法都不安全。 (http请求都是可以被抓到的,想要安全必须加密,使用https协议)

一般我们传递大字段或者较为私密的数据的时候使用POST方法,其他的使用GET方法


服务器收到参数怎么进行数据解析呢?

如果是POST方法,本身就是分离的,不用处理。
如果用的是GET方法,需要对url进行额外的处理,例如/a/b/c.py?xname=yyh&ypwd=123456,需要拆解出其中的路径(path),即"/a/b/c.py"。问号右侧则是参数(parm)。

然后我们path匹配到服务,再通过传递的参数parm提供服务。

if(req._path == "/a/b/c.py")
{
	// 利用parm参数
    // 使用我们自己写的C++方法提供服务,不走下面的网页显示
    //....
    return true;
}

比方说path路径下是我们写的python程序,我们就可以把参数传递过去,利用python语言来提供服务。

五、 HTTP状态码

// 状态行
std::string resp_line = "HTTP/1.1 200 OK\r\n";

前面我们自己定义的状态行中的200就是状态码。

状态码有五种类型,分别以1 ~ 5开头:

状态码类别原因短语
1XXInformational(信息性状态码)接收的请求正在处理
2XXSuccess(成功状态码)请求正常处理完毕
3XXRedirection(重定向状态码)需要进行附加操作以完成请求
4XXClient Error(客户端错误状态码)服务器无法处理请求
5XXServer Error(服务器错误状态码)服务器处理请求出错

最常见的状态码,比如200(OK),404(Not Found),403(Forbidden请求权限不够),302(Redirect),504(Bad Gateway)。

5.1 重定向状态码(3XX)

重定向就是通过各种方法将各种网络请求重新定个方向转到其它位置(跳转网站),此时这个服务器相当于提供了一个引路的服务。

我们发送请求给服务端,服务端返回一个新的url,状态码是3,浏览器自动用这个新的url继续发送请求给新的地址。

所以重定向是由客户端完成的。

而重定向又分为临时重定向永久重定向。其中状态码301表示的就是永久重定向,而状态码302和307表示的是临时重定向。

临时重定向和永久重定向本质是影响客户端的标签,决定客户端是否需要更新目标地址。如果某个网站是永久重定向,那么第一次访问该网站时由浏览器帮你进行重定向,但后续再访问该网站时就不需要浏览器再进行重定向了,此时你访问的直接就是重定向后的网站。而如果某个网站是临时重定向,那么每次访问该网站时如果需要进行重定向,都需要浏览器来帮我们完成重定向跳转到目标网站。

  • 临时重定向演示
    在这里插入图片描述
    现在当我们访问浏览器的时候自动会跳转到CSDN网站:
    在这里插入图片描述
    在这里插入图片描述

六、HTTP常见的报头信息

HTTP常见的Header如下:

  • Content-Type:数据类型(text/html等)。
  • Content-Length:正文的长度。
  • Host:客户端告知服务器,所请求的资源是在哪个主机的哪个端口上。
  • User-Agent:声明用户的操作系统和浏览器的版本信息。
  • Referer:当前页面是哪个页面跳转过来的。
  • Location:搭配3XX状态码使用,告诉客户端接下来要去哪里访问。
  • Cookie:用于在客户端存储少量信息,通常用于实现会话(session)的功能。

七、HTTP长连接

http请求是基于tcp协议的,而tcp是需要进行连接的。对于一个网页,可能包含多种元素,则需要发起多次connect。为了减少连接次数,需要客户端和服务器均支持长链接,建立一条连接,传输完后不断开连接,一直传递资源,不用频繁创建连接。如果是短连接请求了一份资源后就会自动关闭连接。

那么客户端和服务端怎么知道是否是长连接呢?

在报头信息中会有Connection字段。
在这里插入图片描述

Connection: keep-alive// 支持长连接
Connection: close

八、HTTP会话保持(Cookie和Session)

HTTP实际上是一种无状态协议每次请求并不会记录它曾经请求了什么。HTTP的每次请求/响应之间是没有任何关系的,但你在使用浏览器的时候发现并不是这样的。

比如当你登录一次CSDN后,就算你把CSDN网站关了甚至是重启电脑,当你再次打开CSDN网站时,CSDN并没有要求你再次输入账号和密码,这实际上是通过cookie技术实现的,点击浏览器当中锁的标志就可以看到对应网站的各种cookie数据。

在这里插入图片描述
在这里插入图片描述
这些cookie数据实际都是对应的服务器方写的,如果你将对应的某些cookie删除,那么此时可能就需要你重新进行登录认证了,因为你删除的可能正好就是你登录时所设置的cookie信息。

得出结论:会话保持不是http协议天然具备的特点,而是浏览器为了满足用户的使用需求,做了相应的工作。

  • 如何做到的呢?

用户在第一次输入账号和密码时,浏览器会进行保存(Cookie),近期再次访问同一个网站(发送http请求),浏览器会自动将用户信息添加到报头中推送给服务器。这样只要用户首次输入密码,一段时间内将不用再做登录操作了。

这种把用户名和密码保存起来的技术叫做Cookie技术

而Cookie又分为Cookie内存Cookie文件

  • 内存级别与文件级别

cookie就是在浏览器当中的一个小文件,文件里记录的就是用户的私有信息。cookie文件可以分为两种,一种是内存级别的cookie文件,另一种是文件级别的cookie文件。
1️⃣ 将浏览器关掉后再打开,访问之前登录过的网站,如果需要你重新输入账号和密码,说明你之前登录时浏览器当中保存的cookie信息是内存级别的。
2️⃣ 将浏览器关掉甚至将电脑重启再打开,访问之前登录过的网站,如果不需要你重新输入账户和密码,说明你之前登录时浏览器当中保存的cookie信息是文件级别的(真实的文件,保存在磁盘,进程退出也不影响)。

  • Cookie安全问题

我们本地的Cookie如果被不法分子拿到了,那么此时这个非法用户就可以用你的cookie信息,以你的身份去访问你曾经访问过的网站,我们将这种现象称为cookie被盗取了。

为了保证安全,我们可以把信息保存在服务端,在服务端形成一个文件:session文件,而因为有很多session文件,所以给每个文件一个名字:session id。并将其返回给浏览器,浏览器存到Cookie的其实是session id。接下来我们把session id放到请求中,然后发送到服务端,在服务端获取登录信息(鉴权)。目前只保证了用户信息的泄漏,接下来只能靠服务端的安全策略保障安全,例如账号被异地登录了,服务端察觉后只要让session id失效即可,这样异地登录将会使用户重新验证账号密码或手机或人脸信息(尽可能确保是本人),一定程度上保障了信息的安全。

在这里插入图片描述

  • 写入Cookie信息

其实就是向发送给浏览器的响应中写入报头中。

//写入Cookie
resp_header += "Set-Cookie: name=12345abc; Max-Age=180\r\n";//设置Cookie响应报头,有效期3分钟

在这里插入图片描述

在这里插入图片描述



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

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

相关文章

小程序开发及生态丰富,还需要App吗?

微信小程序自2017年推出以来&#xff0c;其生态系统得到了迅速的发展和壮大。作为中国最大的社交平台之一&#xff0c;微信拥有庞大的用户基础。微信小程序作为微信生态系统的一部分&#xff0c;自然而然地吸引了大量用户。据对公开资料进行统计&#xff0c;2021年全网小程序数…

校园跑腿小程序:为学生提供便捷的服务

随着社会的发展和人们生活水平的提高&#xff0c;高等教育越来越受到重视。大学校园不仅是学习的地方&#xff0c;也是学生们日常生活的场所。然而&#xff0c;在繁忙的学业和生活压力下&#xff0c;学生可能经常面临诸如代购、快递、取餐等各种琐碎但繁琐的任务。基于这个需求…

ModaHub魔搭社区:AI原生云向量数据库Zilliz Cloud与 Cohere 集成搭建智能问答系统

目录 准备工作 主要参数 准备数据 创建 Collection 插入数据 测试问答 本文将演示如何使用 Zilliz Cloud 和 Cohere 搭建基于 SQuAD 数据集 的问答系统。其中,Zilliz Cloud 负责提供向量数据库,Cohere 负责提供获取指定文字向量表示的接口。 准备工作 本示例中的脚…

黑马前端三大件

文章目录 1.html1.1 标题标签1.2 段落标签1.3换行标签1.4 水平分割线标签1.5 文本格式化标签1.6 图片标签1.7 音频标签1.8 视频标签1.9 链接标签的介绍1.10列表标签1.10.1 无序列表1.10.2 无序列表1.10.3 自定义列表 1.11表格属性1.11.1表格的基本标签1.11.2 表格相关属性1.11.…

使用layui滑块slider遇到问题解决:1、加标尺2、兼容移动端拖拽1、多个滑块使用赋值4、切换箭头赋值问题

使用layui框架 滑块组件 slider 时遇到的问题&#xff0c;以下图为例&#xff0c;我创建了一个总滑块和3个滑块&#xff0c;改变总滑块可以控制滑块123&#xff1a; 1、我的需求是加上标尺&#xff0c;layui没有该功能&#xff0c;自己写了一个简单的,代码在下面。 2、移动端…

Linux:输入输出流、重定向、管道命令

相关文章 Linux&#xff1a;chgrp、chown、chmod权限属性更改指令 Linux&#xff1a; 磁盘状态观察命令lsblk、blkid Linux&#xff1a;df、du容量查询指令 1、标准流文件 数据流重定定向由字面上的意思来看&#xff0c;就是将数据定向到其他地方去&#xff0c;事实也是如此…

ModaHub魔搭社区:非结构化数据范式转变和示例

目录 范式转变——非结构化数据 非结构化数据示例 范式转变——非结构化数据 既然我们对结构化数据和半结构化数据有了清晰的理解,现在让我们开始谈谈非结构化数据。与结构化数据和半结构化数据不同,非结构化数据可以采取任何形式,可以有任意大小或尺寸,并需要大量的运行…

ModaHub魔搭社区:AI原生云向量数据库Zilliz Cloud与 HuggingFace 集成搭建问答系统

目录 准备工作 主要参数 创建 Collection 插入数据 测试问答 本文将演示如何使用 Zilliz Cloud 和 HuggingFace 搭建问答系统。其中,Zilliz Cloud 负责提供向量数据库,HuggingFace 负责提供获取指定文字向量表示的接口。 准备工作 本示例中的脚本需要安装 pymilvus,…

寡头竞争和混合策略均衡

纯寡头的产量竞争 两家企业生产相同的产品,产量是决策变量,市场上只有一种价格。市场需求/Demand:P = 100 - (Q1+Q2) Q 是企业生产的产量 假设生产成本为0 这个案例可能存在的结果 (1)合谋 (2)古诺-纳什均衡 每个企业将它竞争对手的产量水平视为固定的,然后决定自己…

【C++学习笔记】C++如何规范C语言中的类型转换

C的类型转换 1 C语言中类型转换的缺陷2 为什么C要规范C的类型转换3 C强制类型转换3.1 static_cast3.2 reinterpret_cast3.3 const_cast3.4 dynamic_cast 1 C语言中类型转换的缺陷 在C语言中&#xff0c;如果赋值运算符左右两侧类型不同&#xff0c;或者形参与实参类型不匹配&a…

【kubernetes系列】Kubernetes之RBAC

概述 k8s的权限控制在实际工作中不那么经常使用&#xff0c;但是却是很重要的&#xff0c;我们需要深入理解才能很好的解决某些问题。在我们现目前的了解中&#xff0c;常用的授权插件有以下几种&#xff1a; Node&#xff08;节点认证&#xff09; ABAC(基于属性的访问控制) …

【UE4 C++】03-新建UE C++工程,新建C++类

UE版本&#xff1a;4.26 步骤 新建一个空白模板工程&#xff0c;选择C项目&#xff0c;选择项目名和项目存储位置&#xff0c;然后点击创建项目。 新建C类 选择父类为Character 命名&#xff0c;选择公有&#xff0c;然后点击创建类 等待编译完成 此时在Visual Studio中可以看…

源启:云原生计算架构的行业实现

7月5日&#xff0c;由工业和信息化部网络安全产业发展中心、中国软件行业协会等单位指导&#xff0c;中国电子主办的“麒麟傲天聚创未来2023操作系统产业大会”在京举行。中电金信研究院副院长陈书华在大会上发表主旨演讲。 陈书华认为数字经济已成为社会发展的重要引擎&#…

【论文解读】基于分层判断的 x264 快速模式选择算法

简介 题目&#xff1a; 基于分层判断的 x264 快速模式选择算法 原文&#xff1a; https://www.nstl.gov.cn/paper_detail.html?id77880ade9a55b3cc527192010242d90e 级别&#xff1a; EI 年份&#xff1a; 2010 年 机构&#xff1a; 上海交通大学 结论&#xff1a; 相比较 HE…

汽车+ChatGPT 车内生活体验再升级

这两年&#xff0c;人工智能工具ChatGPT爆火&#xff0c;在全球掀起了大模型之战。如今&#xff0c;最前沿的自然语言处理大模型应用到了人类的出行工具上&#xff0c;梅赛德斯-奔驰和微软官宣正在合作测试车载ChatGPT人工智能&#xff0c;并将面向约90万车主开启测试&#xff…

对象存储、数据库、NAS、标注数据集,均支持元信息配置|ModelWhale 版本更新

七月中旬&#xff0c;暑气渐浓&#xff0c;ModelWhale 新一轮的版本更新&#xff0c;期待为每个领域的你带来更好的使用体验。 本次更新中&#xff0c;ModelWhale 主要进行了以下功能迭代&#xff1a; • 新增 对象存储、数据库、NAS、标注数据的元信息展示&#xff08;专业版…

flutter开发实战-Theme主题切换

flutter开发实战-Theme主题切换 之前做的应用中有用到Theme主题切换&#xff0c;一直没有整理&#xff0c;这里整理一下。 使用的是Android studio 一、效果图 二、创建ThemeModel // 提供五套可选主题色 const _themes <MaterialColor>[Colors.blue,Colors.cyan,Co…

缓存 - Caffeine 不完全指北

文章目录 官网概述设计CodePOMPopulationEviction PolicyRefreshStatistics 官网 https://github.com/ben-manes/caffeine wiki: https://github.com/ben-manes/caffeine/wiki 概述 Caffeine是一个用于Java应用程序的高性能缓存框架。它提供了一个强大且易于使用的缓存库&a…

如何判定是否一份适合工作呢

核心指标&#xff1a;喜欢 春节仿佛还在昨天&#xff0c;转眼间2023年已经过半。分享和总结一下自己过去的这6个月吧&#xff01;你可以从以下几个方面展开谈谈。 23年上半年已经过去啦。结合工作多年经历简单写一写。 主要写自己&#xff1a; 工作非常努力&#xff0c;但是…

Java 一文掌握全部阻塞队列的使用

1、简介 本文主要对Java常用阻塞队列进行介绍和提供相关使用案例 2、 阻塞队列作用 阻塞队列提供了一种线程安全、高效的数据传递和同步机制 &#xff0c; 主要用于缓冲数据、限流、削峰填谷&#xff0c;生产者-消费者模型&#xff0c;线程间的协作等等。 3、 各阻塞队列区…