HTTP协议

HTTP协议

1. HTTP介绍

HTTP协议叫做超文本传输协议, 是一种用作获取诸如HTML文档这类资源的协议。它是 Web 上进行任何数据交换的基础,同时,也是一种客户端—服务器(client-server)协议,也就是说,请求是由接受方——通常是浏览器——发起的。一个完整网页文档是由获取到的不同文档组件像是文本、布局描述、图片、视频、脚本等——重新构建出来的。

在这里插入图片描述

client和server在进行网络通信的时候,client可以把自己的“东西"给别人,client也可以把别人的“东西"拿到自己的本地,站在系统层面这个过程叫做IO,站在网络层面这个过程叫做request/response。在http协议中这些"东西" 可以是:网页文本,图片,视频,音频,我们把它们统称为资源。http协议叫做超文本传输协议,即它可以进行网络资源的互相传递。

2. 认识URL

URL代表着是统一资源定位符(Uniform Resource Locator),平时我们俗称的 “网址” 其实就是说的 URL。

一个URL的构成:

在这里插入图片描述

(1) 协议方案名

http://中的http代表协议名称,它表明了浏览器必须使用何种协议。它通常都是HTTP 协议或是HTTP协议的安全版,即 HTTPS。

(2) 登录信息

user:pass表示登录认证信息,包含了用户的用户名和密码。现在绝大多数的URL这个字段是被省略的

(3) 服务器地址

  • 要访问服务器我们必须要知道服务器的ip地址+端口号。

  • www.xxxx表示域名,域名的本质:就是IP地址,用来表示唯一主机。因为直接使用IP地址不方便,所以它不经常在网络上使用。域名会被解析成IP地址,域名解析服务是由域名解析服务器完成的。平时用的浏览器和APP的服务端其实已经内置域名解析服务器对应的IP地址和端口。

使用ping指令就能查看到域名解析

在这里插入图片描述

(4) 服务器端口号

  • 80表示端口号,http协议默认端口号是80,https默认端口号是443
  • 由于服务端的端口号不能随意指定,必须是众所周知且不能随意更改的,端口号和成数的应用层协议是一一对应的!即协议名称,端口号 = 1对1强相关。发现在URL中服务端的端口号是没有的,因为浏览器会自动识别协议名称,协议名称就直接对应相应的端口号

(5) 带层次的文件路径

  • /dir/index.htm表示要访问的资源所在的路径
  • 第一个/代表web根目录

(6) 查询字符串

?是区分URL左侧和右侧的分隔符,?后面所跟的uid=1代表参数,这些参数是用&符号分隔的键/值对列表

(7) 片段标识符

#后面的ch1表示片段标识符,一般用来定位网页上的内容,比如点击页面上的一个链接,跳到当前页面的某一个位置。

3. urlencode和urldecode

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

转义的规则如下:
将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编成%XY

例如搜索C++,“+” 被转义成了 “%2B”,同时对汉字也会进行编码。这个过程是浏览器或某种客户端自动做的,称为urlencode

在这里插入图片描述

当服务器受到请求时,要对特殊符号和汉字进行解码,这个过程称为urldecode

我们可以使用相关的在线工具验证一下。

在这里插入图片描述

4. HTTP协议格式

4.1 宏观理解

在这里插入图片描述

(1) HTTP请求格式

HTTP请求由4部分构成:

  1. 请求行:[请求方法] + [URL] + [协议版本] + [\r\n]
  2. 请求报头:请求的属性, 冒号分割的键值对(多行KV形式的键值对);每组属性之间使用\r\n分隔
  3. 空行:遇到空行(\r\n),表示请求报头部分结束
  4. 有效载荷:一般是用户可能提交的参数,允许为空字符串。如果有效载荷存在, 则在报头中会有一个Content-Length属性来标识其长度
(2) HTTP响应格式

HTTP响应由4部分构成:

  1. 状态行:[协议版本] + [状态码] + [状态码描述] + [\r\n]
  2. 响应报头:响应的属性, 冒号分割的键值对(多行KV形式的键值对);每组属性之间使用\r\n分隔
  3. 空行:遇到空行(\r\n),表示请求报头部分结束
  4. 有效载荷:可以是html/css/js,图片,视频,音频等资源,允许为空字符串。如果有效载荷存在, 则在报头中会有一个Content-Length属性来标识其长度
(3) 2个核心问题
  1. HTTP是怎么做到报头和有效载荷分离呢?

    根据特殊字符\r\n,按行来读取,一旦读到空行,报头就读取完了。如果有效载荷存在, 则在报头中会有一个Content-Length属性来标识其长度,,Content-Type属性来标识其数据类型(text/html等)

  2. HTTP是怎么做到序列化和反序列化的呢?

    • 序列化:根据特殊字符\r\n,把多行请求累加就变成了一个大字符串
    • 反序列化:提取一个一个字段时,以\r\n作为分隔符,切分成各种结构

4.2 代码测试

浏览器本身就可以构建HTTP请求,所以以浏览器作为HTTP的客户端,自己来实现一个HTTP的服务端

完整代码:lesson37 · 遇健/Linux - 码云 - 开源中国 (gitee.com)

HttpServer.hpp

#pragma once
#include"Sock.hpp"
#include<pthread.h>
#include<functional>

static const uint16_t defaultport = 8888;
class HttpServer;

using func_t =function<string(string&)>;

class ThreadData
{
public:
    ThreadData(int sock, string ip, uint16_t port, HttpServer *tsvrp)
        : _sock(sock), _ip(ip), _port(port), _tsvrp(tsvrp)
    {
    }

    ~ThreadData()
    {
    }

public:
    int _sock;
    HttpServer *_tsvrp;
    string _ip;
    uint16_t _port;
};

class HttpServer
{
public:
    HttpServer(func_t f, uint16_t port = defaultport)
        :func(f)
        ,_port(port)
    {}
    void InitServer()
    {
        _listensock.Socket();
        _listensock.Bind(_port);
        _listensock.Listen();
    }
    void HanlderHttpRequest(int sock)
    {
        char buffer[4096];
        string request;
        ssize_t s=recv(sock,&buffer,sizeof(buffer)-1,0);     // 我们认为我们一次读完了
        if(s>0)
        {
            buffer[s]=0;
            request=buffer;
            string response=func(request);
            send(sock,response.c_str(),response.size(),0);
        }
        else
        {
            logMessage(Info, "client quit...");
        }
    }
    static void*threadRoutine(void*args)
    {
        pthread_detach(pthread_self());
        ThreadData*td=static_cast<ThreadData*>(args);
        td->_tsvrp->HanlderHttpRequest(td->_sock);
        close(td->_sock);
        delete td;
        return nullptr;
    }
    void Start()
    {
        while(true)
        {
            string clientip;
            uint16_t clientport;
            int sock=_listensock.Accept(&clientip,&clientport);
            if(sock<0)
                continue;

            pthread_t tid;
            ThreadData*td=new ThreadData(sock, clientip, clientport, this);
            pthread_create(&tid,nullptr,threadRoutine,td);
        }
    }
    ~HttpServer()
    {

    }
private:
    uint16_t _port;
    Sock _listensock;
    func_t func;
};

Main.cc

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

string HandlerHttp(const string&request)
{
    // 确信, request一定是一个完整的http请求报文
    // 给别人返回的是一个http response
    std::cout<<"-----------------------------"<<endl;
    cout<<request<<endl;
    return"";
}
// ./httpserver 8888
int main()
{
    uint16_t port=8889;
    std::unique_ptr<HttpServer> tsvr(new HttpServer(HandlerHttp,port));

    tsvr->InitServer();
    tsvr->Start();

    return 0;
}
(1) 见一见http请求

启动服务端后,在浏览器上进行访问

由于代码中什么也没有,就会显示下面的信息

在这里插入图片描述

此时浏览器发送请求后,服务端就会收到浏览器发送来的请求

在这里插入图片描述

上面的URL是/,此时浏览器会默认访问Web根目录。我们想要访问特定目录下的资源也是通过URL来体现的

在这里插入图片描述

  • Host:被请求的目标服务器的ip地址和端口号

  • Connection:表示链接模式,长短链接(后面详谈)

  • User-Agent:这次请求的客户端信息

平时我们在下载软件的时候,好像浏览器就自动能够识别并适配我的系统,它是如何做到的呢?

在这里插入图片描述

比如我要下载微信,浏览器就会自动识别出我是Windows系统的。

我在搜索的本质是向百度服务器发送了http请求,我的http请求中携带一个User-Agent字段,表明浏览器相关的详细信息,其中就包含有操作系统的信息,百度识别后就可以自动呈现搜索结果时,自动把电脑平台中符合操作系统的标签选中,自动下载它。

剩下的字段了解即可:

Cache-Insecure-Requests:代表最大缓存机制,双方在通信时要建立缓存,默认为0表示不缓存

Accept-Encoding:代表客户端所接收的编码和压缩类型

Accept-Language:代表客户端可以接收的编码符号

(2) 见一见http响应

这里介绍两种方法:

方法一:

我们用telnet工具来连接百度的服务器,发出http请求,百度就会给我们http响应

在这里插入图片描述

方法二:

使用Postman软件

在这里插入图片描述
在这里插入图片描述

用Postman获取出来的百度首页其实和上面telnet获取出来的相同,只不过Postman做了一些排版便于我们查看,实际在网络传输中还是telnet上面的方式。

(3) 根据协议,模拟一个简单响应

完整代码:lesson37/Http_v2 · 遇健/Linux - 码云 - 开源中国 (gitee.com)

Main.cc

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

const string SEP="\r\n";
 
string HandlerHttp(const string&request)
{
    // 确信, request一定是一个完整的http请求报文
    // 给别人返回的是一个http response
    std::cout<<"-----------------------------"<<endl;
    cout<<request<<endl;

    // 给你一个响应
    string response="HTTP/1.0 200 OK"+SEP;
    response += SEP;
    response += "Hello Http";  // 有效载荷部分(正文)

    return response;
}

// ./httpserver 8888
int main()
{
    uint16_t port=8888;
    std::unique_ptr<HttpServer> tsvr(new HttpServer(HandlerHttp,port));

    tsvr->InitServer();
    tsvr->Start();

    return 0;
}

启动服务端后,在浏览器上进行访问,就会出现Hello Http的响应

在这里插入图片描述

服务端也收到了浏览器发送来的请求

在这里插入图片描述

(4) 响应扩展
<1> 把响应放入文件

可是我们一般的响应是一个网页,图片,视频,音频类的资源,同时它们不能硬编码到代码中。我们必须把它们放到文件里,所以我们要给这些资源维护一个自己的目录通过读取指定的文件来访问它们。

完整代码:lesson37/Http_v4 · 遇健/Linux - 码云 - 开源中国 (gitee.com)

Util.hpp: 主要功能是读取文件。坑:一般对于网页文件都是文本的, 但是如果是图片, 视频, 音频是二进制的,所以我们要以二进制的形式读

#pragma once

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string>
#include <sys/stat.h>
#include <fcntl.h>
#include"log.hpp"

class Util
{
public:
    // 坑: 一般对于网页文件, 都是文本的
    // 但是如果是图片, 视频, 音频 -> 二进制的(中间可能存在0值)

    static bool ReadFile(const std::string&path,std::string*fileContent)
    {
        // 1. 获取文件本身的大小
        struct stat st;
        int n=stat(path.c_str(),&st);
        if(n<0)
            return false;
        int size=st.st_size;

        // 2. 调整string的空间
        fileContent->resize(size);

        // 3. 读取
        int fd=open(path.c_str(), O_RDONLY);
        if(fd<0)
            return false;
        read(fd,(char*)fileContent->c_str(),size);
        close(fd);
        logMessage(Info,"read file %s done",path.c_str());
        
        return true;
    }
};

Main.cc

#include"HttpServer.hpp"
#include"Util.hpp"
#include<memory>

const string SEP="\r\n";

const string path="./wwwroot/index.html";
 
string HandlerHttp(const string&request)
{
    // 确信, request一定是一个完整的http请求报文
    // 给别人返回的是一个http response
    std::cout<<"-----------------------------"<<endl;
    cout<<request<<endl;

    // 资源, 图片(.png, jpg...), 网页(.html, .htm), 视频, 音频 -> 文件! 都要有自己的后缀!
    string body;  
    Util::ReadFile(path,&body);

    // 给你一个响应
    string response="HTTP/1.0 200 OK"+SEP;   //状态行
    response += "Content-Length: "+std::to_string(body.size()) + SEP;    // 表明有效载荷的长度
    response += "Content-Type: text/html" + SEP;   
    response += SEP;
    response += body;  // 有效载荷部分(正文)

    return response;
}

// ./httpserver 8888
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        exit(USAGE_ERR);
    }
    uint16_t port = atoi(argv[1]);
    std::unique_ptr<HttpServer> tsvr(new HttpServer(HandlerHttp,port));

    tsvr->InitServer();
    tsvr->Start();

    return 0;
}

启动服务端后,在浏览器上进行访问,就会出现刚才写的html文件

在这里插入图片描述

下面要做的工作:

  • 一般在请求时可能会请求不同的资源,我们想要看到客户端请求的资源所以我们要对request做处理,把字符串信息变成结构化字段。
  • 请求不同的资源实际是在访问wwwroot(Web根目录)下的不同文件
  • 支持打开网页时可以跳转其他网页,所谓的跳转,本质其实就是让html中特定的标签被浏览器解释,重新发起http请求
  • 给网页添加照片
<2> 对请求反序列化

完整代码:lesson37/Http_v5 · 遇健/Linux - 码云 - 开源中国 (gitee.com)

Util.hpp

#pragma once

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string>
#include <sys/stat.h>
#include <fcntl.h>
#include<sstream>
#include"log.hpp"

class Util
{
public:
    // 坑: 一般对于网页文件, 都是文本的
    // 但是如果是图片, 视频, 音频 -> 二进制的(中间可能存在0值)
    static bool ReadFile(const std::string&path,std::string*fileContent)
    {
        // 1. 获取文件本身的大小
        struct stat st;
        int n=stat(path.c_str(),&st);
        if(n<0)
            return false;
        int size=st.st_size;

        // 2. 调整string的空间
        fileContent->resize(size);

        // 3. 读取
        int fd=open(path.c_str(), O_RDONLY);
        if(fd<0)
            return false;
        read(fd,(char*)fileContent->c_str(),size);
        close(fd);
        logMessage(Info,"read file %s done",path.c_str());

        return true;
    }
    static string ReadOneline(string&message,const string&sep)
    {
        auto pos=message.find(sep);
        if(pos==string::npos)
            return"";
        string s=message.substr(0,pos);
        message.erase(0,pos+sep.size());
        return s;
    }

    // GET /favicon.ico HTTP/1.1
    // GET /a/b/c/d/e/f/g/h/i/g/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z HTTP/1.1
    static bool ParseRequestLine(const string&line, string*method, string*url,                                            string*httpVersion)
    {
        // 1个字符串变3个字符串
        stringstream ss(line);
        ss>>*method>>*url>>*httpVersion;
        return true;
    }
};

Main.cc

#include"HttpServer.hpp"
#include"Util.hpp"
#include<memory>
#include<vector>

const string SEP="\r\n";

// 一般一个webservser, 不做特殊说明, 如果用户直接默认访问'/', 我们绝对不能把整站给对方
// 需要添加默认首页!! 而且, 不能让用户访问wwwroot里面的任何一个目录本身,也可以给每一个目录都带上默认首页
const string defaultHomePage="index.html";   // web根目录
const string webRoot="./wwwroot";   // web根目录

// const string path="./wwwroot/index.html";

class HttpRequest
{
public:
    HttpRequest()
        :path_(webRoot)
    {}

    ~HttpRequest()
    {}

    void Print()
    {
        logMessage(Info, "method: %s, url: %s, version: %s", method_.c_str(), url_.c_str(),
                   httpVersion_.c_str());

        // for(const auto&line: body_)
        //     logMessage(Debug, "-%s",line.c_str());

        logMessage(Debug, "path: %s", path_.c_str());
    }

public:
    std::string method_;
    std::string url_;
    std::string httpVersion_;
    std::vector<std::string> body_;
    std::string path_;

    std::string suffix_;   // 要访问资源的后缀
};

HttpRequest Deserialize(string&message)
{
    HttpRequest req;
    string line=Util::ReadOneline(message,SEP);
    Util::ParseRequestLine(line,&req.method_,&req.url_,&req.httpVersion_);

    while(!message.empty())
    {
        line=Util::ReadOneline(message,SEP);
        req.body_.push_back(line);
    }
    req.path_+=req.url_;  // "wwwroot/a/b/c.html" , "./wwwroot"
    if(req.path_[req.path_.size()-1]=='/')
        req.path_+=defaultHomePage;

    auto pos=req.path_.rfind(".");
    if(pos==string::npos)
        req.suffix_=".html";
    else
        req.suffix_=req.path_.substr(pos);
    return req;
}

string GetContentType(string&suffix)
{
    string content_type="Content-Type: ";
    if(suffix==".html" || suffix==".htm")  
        content_type + "text/html";
    else if (suffix == ".css")
        content_type += "text/css";
    else if (suffix == ".js")
        content_type += "application/x-javascript";
    else if (suffix == ".png")
        content_type += "image/png";
    else if (suffix == ".jpg")
        content_type += "image/jpeg";
    else
    {
    }

    return  content_type + SEP;
}

string HandlerHttp(string&message)
{
    // 1. 读取请求
    // 确信, request一定是一个完整的http请求报文
    // 给别人返回的是一个http response
    std::cout<<"-----------------------------"<<endl;

    // 资源, 图片(.png, jpg...), 网页(.html, .htm), 视频, 音频 -> 文件! 都要有自己的后缀!

    // 2. 反序列化和分析请求
    HttpRequest req= Deserialize(message);
    req.Print();

    // 3. 使用请求
    string body;  
    Util::ReadFile(req.path_,&body);
   
    // 给你一个响应
    string response="HTTP/1.0 200 OK"+SEP;   //状态行
    response += "Content-Length: "+std::to_string(body.size()) + SEP;    // 表明有效载荷的长度
    response += GetContentType(req.suffix_);   
    response += SEP;
    response += body;  // 有效载荷部分(正文)

    return response;
}

// ./httpserver 8888
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        exit(USAGE_ERR);
    }
    uint16_t port = atoi(argv[1]);
    std::unique_ptr<HttpServer> tsvr(new HttpServer(HandlerHttp,port));

    tsvr->InitServer();
    tsvr->Start();

    return 0;
}

启动服务端,观察收到的请求

在这里插入图片描述

支持网页间的跳转

在这里插入图片描述

网页添加图片

在这里插入图片描述

5. HTTP的方法

在这里插入图片描述

(1) GET与POST

其中最常用的就是GET方法和POST方法。

HTTP方法是由浏览器客户端发起的,它会构建一个http request,携带的方法GET/POST。在服务端和浏览器交互时,浏览器是通过html的表单,采用不同的方法进行资源请求提交的。

  • GET方法
    • GET方法能获取一个静态的网页
    • GET方法提交参数时,是通过URL的方式提交的

在这里插入图片描述

  • POST方法
    • POST方法提交参数时,是通过正文部分提交的

在这里插入图片描述

总结:

GET方法,POST方法的差别和它们各自的应用场景

  • GET方法提交参数通过URL,不私密。
  • POST方法提交参数通过正文,比较私密一些。
  • GET方法通过URL传参,URL是http请求行的字符串,一般都会有大小约束。
  • POST方法通过正文传参,理论上正文可以非常大。

注:私密 != 安全,两种方法都不安全,因为没有加密

所以所有的登录注册支付等行为,都要使用POST方法提参(不是只用)

6. HTTP的状态码

在这里插入图片描述

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

(1) 重定向状态码

<1> 感性理解

下面就是一个重定向过程,你去西门吃麻辣烫却看到门口写着:移至东门跑去东门吃
在这里插入图片描述

你后来每次去麻辣烫前先去西门,看西门的店是否开门,没开去东门。这就是一个临时重定向的过程。对应的状态码有:302, 307

在这里插入图片描述

半年后,你先去西门的麻辣烫发现没开门,并且在门外写着往后三个月之内本店将永久搬到东门,你后来就直接东门,不再跑到西门。这就是一个永久重定向的过程。对应的状态码有:301

在这里插入图片描述

发现在上面两次重定向中都携带了新的麻辣烫地址

重定向就是就是通过各种方法将各种网络请求重新定个方向转到其它位置。重定向操作由服务器向请求发送特殊的重定向响应而触发。重定向响应包含以 3 开头的状态码以及 Location标头,其保存着重定向的URL

  • 临时重定向不更改浏览器的任何地址信息(302,307)
  • 永久重定向会更改浏览器的本地书签(301)
<2> 代码实操

下面演示一下临时重定向

将HTTP响应当中的状态码改为302,加上对应的状态码描述,此外还需要在HTTP响应报头当中添加Location字段,这个Location后面跟的就是你需要重定向到的网页,比如我这里将其设置为腾讯网
在这里插入图片描述

此时当浏览器访问我的服务器时,就会立马跳转到腾讯网的首页

在这里插入图片描述

<3> 应用场景

临时重定向:618,双11期间,你打开某些软件就会自动跳转到金主爸爸(淘宝,京东)的网站

永久重定向:迁移到新的域名。例如,公司改名后,你希望用户在搜索旧名称的时候,依然可以访问到应用了新名称的站点。

7. HTTP常见Header

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

Keep-Alive

Keep-Alive:长连接,一张网页中有很多资源,基于一条建立好的连接上的很多http请求,对方收到后对这些请求依次处理,基于同样的连接返回一些响应。即不用每次访问资源都建立连接并断开连接,基于一条连接就能保证我们把资源传输完毕

HTTP/1.0中默认使用短连接。也就是说,客户端和服务器每进行一次HTTP操作,就建立一次连接,任务结束就中断连接。而从HTTP/1.1起,默认使用长连接,用以保持连接特性

favicon.ico

使用chrome测试我们的服务器时, 可以看到服务器打出的请求中还有一个 GET /favicon.ico HTTP/1.1 这样的请求。其中favicon.ico:代表网页上面的图标

在这里插入图片描述

8. HTTP的会话保持功能

http本身是一种无状态协议,在同一个连接中,两个执行成功的请求之间是没有关系的。这就带来了一个问题,用户没有办法在同一个网站中进行连贯的交互,比如在电商网站中使用购物车功能。

可是我们在使用浏览器时发现并不是这样的。
比如,我们打开bilibili网站,登录账号,你登录一次之后,这个登录状态就会维持很久,即使你关闭网站甚至关掉浏览器,再次打开此网站我们还是登录状态的。

上面的工作就是通过Cookie和session实现的,称为会话保持。http并不直接做这个工作,而是浏览器为了满足用户需求,提供了会话保持功能。

(1) Cookie

Cookie是服务器发送到用户浏览器并保存在本地的一小块数据。浏览器会存储 cookie 并在下次向同一服务器再发起请求时携带并发送到服务器上。通常,它用于告知服务端两个请求是否来自同一浏览器。

在这里插入图片描述

  • 用户想要访问某个VIP资源时,网站会要求此用户输入账号密码登录后进行认证。认证通过后,服务器会通过一些http的选项把我的私人信息携带到http响应中,当浏览器收到响应后,浏览器会将响应中的Cookie信息在本地进行保存。

  • Cookie信息分为内存级和文件级。

    • 将浏览器关掉甚至将电脑重启再打开,访问之前登录过的网站,若不需要你重新输入账号和密码,说明你之前登录时浏览器当中保存的Cookie信息是文件级别的
    • 将浏览器关掉后再打开,访问之前登录过的网站,如果需要你重新输入账号和密码,说明你之前登录时浏览器当中保存的Cookie信息是内存级别的
  • 从此往后,继续在访问相同的网站,浏览器构建的http请求中都会携带Cookie属性,会把曾经保存的历史Cookie信息携带上。不用用户手动操作,这个是浏览器自动做的。同时client在访问每一个资源时会自动进行认证,不用用户再输入账号和密码了。

我们登录某网站后,将把该网站的Cookie信息删除,删除之后,用户就不是登录状态了,需要进行重新登录

在这里插入图片描述

Cookie存在的问题:

我们今天在上网,登录了QQ, 微信, 淘宝,bilibili等,于是浏览器客户端就会存在大量的Cookie信息,如果信息被不法分子盗取,他就可以自己的浏览器直接使用用户的Cookie信息访问服务器,服务器就会误认为是用户在访问服务端。

(2) Session

Session是一种可以维持服务器端的数据存储技术,Session对象存储特定用户会话所需的属性及配置信息。

在这里插入图片描述

  • 当用户首次登录认证成功后,服务端会形成一个session对象把当前用户的基本信息保存起来,并且为用户创建唯一的session id,服务器的http 响应中会携带session id,当浏览器收到response后,浏览器会将响应中的session id保存在Cookie文件中。
  • 从此往后继续在访问相同的网站,浏览器构建的http请求中都会携带Cookie文件里的session id,服务端直接使用session id去确认。

session解决的问题:

  • 虽然仍然存在Cookie信息被盗取的问题,但是Cookie中保存的是session id,并没有泄露用户的账号和密码,用户的私人信息已经被服务器维护起来的。这就解决了泄露用户私人信息的问题。

  • 黑客盗取了用户的session id后仍可以非法登录,只能靠服务端的安全策略保障安全,例如账号被异地登录了,服务端察觉后只要让session id失效即可,这样异地登录将会使用户重新验证账号密码,一定程度上保障了信息的安全。

(3) cookie和session的区别及session的生命周期(补充)

<1> cookie与session区别

  1. cookie数据存放在客户端,session数据放在服务器上。
  2. cookie不是很安全,别人可以分析存放在本地的Cookie并进行Cookie欺骗考虑到安全应当使用session。
  3. session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能考虑到减轻服务器性能方面,应当使用Cookie。
  4. 单个Cookie保存的数据长度不能超过4K,很多浏览器都限制一个网址最多保存20个cookie。

<2> session的生命周期

Session保存在服务器端,为了获取更高的存取速度,服务器一般会把Session放在内存里面,每个用户都会有一个独立的Session。如果Session里面的内容太过复杂,当大量的用户访问服务器时,可能会导致内存溢出,所以我们的session内容应当适当的精简。当我们第一次访问服务器时,服务器会给我们自动创建一个Session,生成session后,只要用户继续访问,服务器就会更新session的最后访问时间,并且维护这个session当用户访问服务器一次,无论是否读写了session,服务器都会认定这个session活跃(active)了一次。当越来越多的用户访问我们的服务器时,因此我们的session会越来越多。为了防止内存溢出,服务器会把长时间没有活跃的Session删除。这个时间就是session的超时时间,过了超时时间,我们的session就会自动失效。

(4) cookie && session实验

在服务器的响应报头当中添加上一个Set-Cookie字段,看看浏览器第二次发起HTTP请求时是否会带上这个Set-Cookie字段

在这里插入图片描述

客户端第二次请求就已经携带cookie信息

在这里插入图片描述

往后,每次http请求,都会自动携带曾经设置好的所有cookie,帮助服务器进行鉴权行为,这就是http的会话保持的功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值