网络基础(二)——HTTP协议

目录

1、2个简单的预备知识

2、HTTP请求和响应的格式

3、实现一个最简单的httpserver

4、HTTP的细节字段

4.1、GET和POST

4.2、HTTP的状态码

4.3、HTTP常见Header


1、2个简单的预备知识

首先我们来看一个域名:http://www.baidu.com/,很明显这是百度的域名,但是结合我们之前的知识其实这个域名做过包装,实际上可以解析成ip地址的形式。所以这里我们用ip地址220.181.38.150:80(http绑定端口号80,https绑定端口号443)也可以访问百度。

了解了这些后我们来看看域名的整体结构:

服务器地址后面的一串文件索引就是url(统一资源定位符),所有网络上的资源,都可以用唯一的一个“字符串标识”,并且可以获取到。

我们要知道。网络的行为就是两种:把别人的东西拿下来或者把自己的东西传上去。在少数情况下,提交或者获取的数据本身可能包含和url中特殊的字符冲突的字符,所以就要求CS双方要进行编码(encode)和解码(decode),这些我们之前已经实现过一遍,但http这里编码解码的方式有一些小区别,通过接下来要将的http请求和响应的格式就会观察到。


2、HTTP请求和响应的格式

Http请求

首行 : [ 方法 ] + [url] + [ 版本 ];
Header: 请求的属性 , 冒号分割的键值对 ; 每组属性之间使用\r \n 分隔 ; 遇到空行表示 Header 部分结束;

Body: 空行后面的内容都是 Body. Body 允许为空字符串 . 如果 Body 存在 , 则在 Header 中会有一个 Content-Length属性来标识 Body 的长度 ;

Http响应

首行 : [ 版本号 ] + [ 状态码 ] + [ 状态码解释 ];
Header: 请求的属性 , 冒号分割的键值对 ; 每组属性之间使用\r \n 分隔 ; 遇到空行表示 Header 部分结束;
Body: 空行后面的内容都是 Body. Body 允许为空字符串 . 如果 Body 存在 , 则在 Header 中会有一个 Content-Length属性来标识 Body 的长度 ; 如果服务器返回了一个 html 页面 , 那么 html 页面内容就是在body中。

HTTP请求和响应的结构和过程如图所示:


3、实现一个最简单的httpserver

HttpServer.hpp

#pragma once 

#include <iostream>
#include <string>
#include <pthread.h>
#include <fstream>
#include <vector>
#include <sstream>
#include <sys/types.h>
#include <sys/socket.h>
#include <unordered_map>

#include "Socket.hpp"
#include "Log.hpp"

//设置根目录和主页以及分隔符
const std::string wwwroot = "./wwwroot"; //web 根目录
const std::string sep = "\r\n";
const std::string homepage = "index.html";


static const int defaultport = 8080;

class HttpServer;

//设置多线程
class ThreadData
{
public:
    ThreadData(int fd, HttpServer *s):sockfd(fd), svr(s)
    {}
public:
    int sockfd;
    HttpServer *svr;
};

//服务器请求,实现解码
class HttpRequest
{
public:
    void Deserialize(std::string req)
    {
        while(true)
        {
            std::size_t pos = req.find(sep);
            if(pos == std::string::npos) break;
            std::string temp = req.substr(0, pos);
            if(temp.empty()) break;
            req_header.push_back(temp);
            req.erase(0, pos+sep.size());
        }
        text = req;
    }
    void Parse()
    {
        std::stringstream ss(req_header[0]);
        ss >> method >> url >> http_version;
        file_path = wwwroot; // ./wwwroot
        if(url == "/" || url == "/index.html") 
        {
            file_path += '/';
            file_path += homepage; // ./wwwroot/index.html
        }
        else file_path += url; // /a/b/c/d.html->./wwwroot/a/b/c/d.html

        auto pos = file_path.rfind(".");
        if(pos == std::string::npos) suffix = ".html";
        else suffix = file_path.substr(pos);
    }
    void DebugPrint()
    {
        std::cout << "------------------------------" << std::endl;
        for(auto &line : req_header)
        {
            std::cout << "------------------------------" << std::endl;
            std::cout << line << "\n\n";
        }

        std::cout << "method: " << method << std::endl;
        std::cout << "url: " << url << std::endl;
        std::cout << "http_version: " << http_version << std::endl;
        std::cout << "file_path: " << file_path << std::endl;
        std::cout << text << std::endl;
    }
public:
    std::vector<std::string>req_header;
    std::string text;

    // 解析之后的结果
    std::string method;
    std::string url;
    std::string http_version;
    std::string file_path;

    std::string suffix;
};

//服务器接口
class HttpServer
{
public:
    HttpServer(uint16_t port = defaultport):port_(port)
    {
        content_type.insert({".html", "text/html"});
        content_type.insert({".jpg", "image/jpeg"});
    }
    
    //启动服务器
    bool Start()
    {
        listensock_.Socket();
        listensock_.Bind(port_);
        listensock_.Listen();
        for(;;)
        {
            std::string clientip;
            uint16_t clientport;
            int sockfd = listensock_.Accept(&clientip, &clientport);
            if (sockfd < 0) continue;
            lg(Info, "get a new connet, sockfd: %d", sockfd);
            pthread_t tid;
            ThreadData *td = new ThreadData(sockfd, this);
            td->sockfd = sockfd;
            pthread_create(&tid, nullptr, ThreadRun, td);
        }
    }

    //读取指定路径的文件
    static std::string ReadHtmlContent(const std::string &htmlpath)
    {
        // 坑 传图片,要读二进制数据
        std::ifstream in(htmlpath, std::ios::binary);
        if(!in.is_open()) return "";

        in.seekg(0, std::ios_base::end);
        auto len = in.tellg();
        in.seekg(0, std::ios_base::beg);

        std::string content;
        content.resize(len);

        in.read((char*)content.c_str(), content.size());

        // std::string content;
        // std::string line;
        // while(std::getline(in, line))
        // {
        //     content += line;
        // }

        in.close();
        return content;
    }

    //确定文件后缀
    std::string SuffixToDesc(const std::string &suffix)
    {
        auto iter = content_type.find(suffix);
        if(iter == content_type.end()) return content_type[".html"];
        else return content_type[suffix];
    }

    //处理请求,对响应进行编码并发回
    void HandlerHttp(int sockfd)
    {
        char buffer[10240];
        ssize_t n = recv(sockfd , buffer, sizeof(buffer) - 1, 0);
        if(n > 0)
        {
            buffer[n] = 0;
            std::cout << buffer;
            HttpRequest req;
            req.Deserialize(buffer);    
            req.Parse();

            //req.DebugPrint();

            // 返回响应的过程
            std::string text; 
            bool ok = true;
            text = ReadHtmlContent(req.file_path); // 失败?
            if(text.empty())
            {
                ok = false;
                std::string err_html = wwwroot;
                err_html += "/";
                err_html += "err.html";
                text = ReadHtmlContent("err_html");
            }

            std::string response_line;
            if(ok)
                response_line = "HTTP/1.0 200 OK\r\n";
            else 
                response_line = "HTTP/1.0 404 Not Found\r\n";
            // response_line = "HTTP/1.0 302 Found\r\n";
            std::string response_header = "Content-Length: ";
            response_header += std::to_string(text.size());
            response_header += "\r\n";
            response_header += "Content-Type: ";
            response_header += SuffixToDesc(req.suffix);
            response_header += "\r\n";
            response_header += "Set-Cookie: name=haha&&passwd=12345";
            response_header += "\r\n";
            // response_header += "Location: https://www.qq.com\r\n";
            std::string blank_line = "\r\n";

            std::string response = response_line;
            response += response_header;
            response += blank_line;
            response += text;
            
            send(sockfd, response.c_str(), response.size(), 0);
        }
        close(sockfd);
    }
    static void *ThreadRun(void *args)
    {
        pthread_detach(pthread_self());
        ThreadData *td = static_cast<ThreadData*>(args);
        td->svr->HandlerHttp(td->sockfd);
        delete td;
        return nullptr;
    }
    ~HttpServer()
    {}
private:
    Sock listensock_;
    uint16_t port_;
    std::unordered_map<std::string, std::string> content_type;
};

HttpServer.cc

#include "HttpServer.hpp"

#include <iostream>
#include <memory>

using namespace std;

int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        exit(1);
    }
    uint16_t port = std::stoi(argv[1]);
    // unique<HttpServer> svr(new HttpServer());
    HttpServer *svr = new HttpServer(port);
    svr->Start();

    return 0;

}

用到的Socket接口和Log接口之前博客中已给出,这里不再给出。

实现的网页服务器主页界面如上(网页的美化等工作属于前端的内容,我们研究的是后端服务器的具体细节,所以不对前端做过多描述。)


4、HTTP的细节字段

4.1、GET和POST

HTTP中最常用的方法就是GET方法和POST方法,那么这两种方法有什么区别呢?接下来我们来研究一下。

首先来看get方法:

我们将主页文件里的表单提交改成get方法,输入用户名密码进行提交;

这了可以看到我们输入的用户名和密码提交给服务器时,是通过url提交的,那么我们改成post方法试一下:

这个时候我们发现,post方法的url里并没有用户名密码,那么它们在哪里呢?就在请求的正文部分,还记得我们说的请求和响应的结构吗?正文和报头之间是用一个空行分隔,而报头里都是键值对。

总结一下,如果我们要提交的参数给我们的服务器,我们使用get方法的时候,我们提交的参数是通过url提交的!而post方法也支持参数提交,采用请求的正文提交参数!

4.2、HTTP的状态码

类别原因短语
1XXInformational(信息性状态码)接收的请求正在处理
2XXSuccess(成功状态码)请求正常处理完毕
3XXRedirection(重定向状态码)需要进行附加操作以完成请求
4XXClient Error(客户端错误状态码)服务器无法处理请求
5XXServer Error(服务器错误状态码)服务器处理请求出错
最常见的状态码 , 比如 200(OK), 404(Not Found), 403(Forbidden), 302(Redirect, 重定向 ), 504(Bad Gateway)。

代码中的这一段就是在判断服务器状态码并返回相应的状态页面。

还有一个重定向的状态码我们单独拿出来说一下,重定向就是让服务器指导浏览器,让浏览器访问新的地址;

如果我们将这段注释的代码放开,那么浏览器就会自动的跳转访问qq的主页面。

4.3、HTTP常见Header

Content-Type: 数据类型 (text/html );
Content-Length: Body 的长度;
Host: 客户端告知服务器 , 所请求的资源是在哪个主机的哪个端口上 ;
User-Agent: 声明用户的操作系统和浏览器版本信息 ;
referer: 当前页面是从哪个页面跳转过来的 ;
location: 搭配 3xx 状态码使用 , 告诉客户端接下来要去哪里访问 ;
Cookie: 用于在客户端存储少量信息 . 通常用于实现会话 (session) 的功能 ;
这里除了上面的Header,我们还要单独说一个connection
可以看到,这里的Connection对应的值为keep-alive,是什么意思呢?我们先说结论,这是长连接也就是用的HTTP/1.1版本。
再来解释一下,我们要知道,其实一个浏览器页面是会包含很多的元素的,而每一个元素都是一个资源,而如果一次请求响应一个资源,再关闭连接,就是短连接,也就是对应HTTP/1.0版本;如果是建立一个TCP连接,发送和返回多个http的requset和response,就是长连接,也就是对应的HTTP/1.1版本。
这里的Connection:keep-alive就是长连接的意思。
最后我们再解释一下Cookie,Cookie是http对登录用户的会话保持功能。
举个例子,我们在登录网页版bilibili的时候输入账号密码登录,那么我们在下一次访问这个页面的时候,它就不会再要求我们输入账号密码而能自动登录,这就是Cookie的作用,那么我们再来看一下原理:

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

实验三智能手机互联网程序设计(微信程序方向)实验报告

实验目的和要求 请编写下方商品列表页面&#xff0c;展示商品名称和价格&#xff1b; 二、实验步骤与结果&#xff08;给出对应的代码或运行结果截图&#xff09; Index.WXML <view class"shop" wx:for"{{10}}"> <vie…

40.网络游戏逆向分析与漏洞攻防-角色管理功能通信分析-角色删除功能的数据包失败的分析

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 如果看不懂、不知道现在做的什么&#xff0c;那就跟着做完看效果 内容参考于&#xff1a; 易道云信息技术研究院VIP课 上一个内容&#xff1a;39.角色数据的维…

如何通过cookie来区分这是瑞数反爬的几代

一、以下仅个人观点&#xff0c;可能有误 1、瑞数反爬了解 瑞数反爬&#xff1a;大多数首次不带cookie的请求&#xff0c;响应状态码是202/412瑞数的cookie &#xff1a; 我们看PPT结尾的Cookie的来定位是几代&#xff0c;PT的是js生成的&#xff1b; 不看OS的&#xff0c;OS…

SQLite版本3中的文件锁定和并发(七)

返回&#xff1a;SQLite—系列文章目录 上一篇&#xff1a;自己编译SQLite或将SQLite移植到新的操作系统&#xff08;六&#xff09; 下一篇&#xff1a;SQLite—系列文章目录 正文&#xff1a; 1.0 SQLite 版本 3 中的文件锁定和并发 SQLite 版本 3.0.0 引入了新的锁…

【蓝桥杯嵌入式】六、真题演练(一)-1演练篇:第 届真题

温馨提示&#xff1a; 真题演练分为模拟篇和研究篇。本专栏的主要作用是记录我的备赛过程&#xff0c;我打算先自己做一遍&#xff0c;把遇到的问题和不同之处记录到演练篇&#xff0c;然后再返回来仔细研究一下&#xff0c;找到最佳的解题方法记录到研究篇。 解题记录&#x…

Yarn的安装及使用(1):安装

一、Yarn的安装 在不同操作系统上安装Yarn的步骤和注意事项&#xff1a; 1、Windows 1.1 通过.msi安装程序安装&#xff1a; 步骤&#xff1a; 访问 Yarn官方网站 下载适用于Windows的.msi安装包。 运行下载好的.msi文件&#xff0c;按照向导进行安装。 在安装过程中&#…

Apache Hive的基本使用语法(一)

一、数据库操作 创建数据库 create database if not exists myhive;查看数据库 use myhive; desc database myhive;创建数据库并指定hdfs存储 create database myhive2 location /myhive2;删除空数据库&#xff08;如果有表会报错&#xff09; drop database myhive;…

爱上数据结构:栈和队列的概念及使用

​ ​ &#x1f525;个人主页&#xff1a;guoguoqiang. &#x1f525;专栏&#xff1a;数据结构 ​ 一、栈 1.栈的基本概念 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端 称为栈顶&#xff0c;…

酒店管理系统项目用例图及用例说明

1、系统功能模块图 2、部分系统功能模块说明 &#xff08;1&#xff09;查询房间剩余 模块名称&#xff1a;管理员登录 编号&#xff1a;1-1 主要功能&#xff1a;验证管理员登录用户名及密码 上级调用模块&#xff1a;无 下级调用模块&#xff1a; 约束&#xff1a; &a…

强化基础-Java-泛型基础

什么是泛型&#xff1f; 泛型其实就参数化类型&#xff0c;也就是说这个类型类似一个变量是可变的。 为什么会有泛型&#xff1f; 在没有泛型之前&#xff0c;java中是通过Object来实现泛型的功能。但是这样做有下面两个缺陷&#xff1a; 1 获取值的时候必须进行强转 2 没有…

音视频开发之旅(80)- AI数字人-腾讯开源AniPortrait-音频驱动的肖像动画

目录 1、前言 2、效果展示 3、原理学习 4、遇到的问题与解决方案 5、资料 一、前言 一个月前阿里Emo发布&#xff0c;通过音频驱动的非常自然的肖像视频&#xff0c;引起很大反响。具体看下面的视频&#xff0c;但是并没有开源其代码。 这两天腾讯开源了其音频驱动的肖像…

2024年美团笔试题(1)

一.题目描述 小美拿到了一个排列&#xff0c;其中初始所有元素都是红色&#xff0c;但有些元素被染成了白色。 小美每次操作可以选择交换任意两个红色元素的位置。她希望操作尽可能少的次数使得数组变成非降序&#xff0c;你能帮帮她吗? 排列是指:一个长度为n的数组&#…

Java | Leetcode Java题解之第1题两数之和

题目&#xff1a; 题解&#xff1a; class Solution {public int[] twoSum(int[] nums, int target) {Map<Integer, Integer> map new HashMap<>();for(int i 0; i< nums.length; i) {if(map.containsKey(target - nums[i])) {return new int[] {map.get(tar…

【React】vite + react 项目,进行配置 eslint

安装与配置 eslint 1 安装 eslint babel/eslint-parser2 初始化配置 eslint3 安装 vite-plugin-eslint4 配置 vite.config.js 文件5 修改 eslint 默认配置 1 安装 eslint babel/eslint-parser npm i -D eslint babel/eslint-parser2 初始化配置 eslint npx eslint --init相关…

应急物资管理系统|实现应急物资的全生命周期管理和监控

应急物资管理系统是一种现代化、智能化、可视化的物资管理平台&#xff0c;主要用于实现对应急物资的全生命周期管理和监控&#xff0c;并提供可靠的应急响应支持。 应急物资管理系统功能 准入控制&#xff1a;东识应急物资管理系统可以实现准入控制&#xff0c;确保只有经过授…

C语言----strcmp()函数:比较两个字符串

C语言中strcmp&#xff08;&#xff09;用于对两个字符串进行比较&#xff08;区分大小&#xff09;。 头文件&#xff1a;string.h 语法原型 int strcmp(const char*str1,const char*str2) 参数str1和str2是参与比较的两个字符串。 strcmp()是根据ASCLL编码依次比较str1和str…

MP设置动态表名

Mybatis设置动态表名 Mybatis设置动态表名1.动态表名插件2.传递表名3.注意事项 Mybatis设置动态表名 1.动态表名插件 MybatisPlus中提供了一个动态表名的插件&#xff1a;https://baomidou.com/pages/2a45ff/#dynamictablenameinnerinterceptor 插件的部分源码如下&#xff…

大模型面试准备(十):大模型数据处理方法及优秀的开源数据介绍

节前&#xff0c;我们组织了一场算法岗技术&面试讨论会&#xff0c;邀请了一些互联网大厂朋友、参加社招和校招面试的同学&#xff0c;针对大模型技术趋势、大模型落地项目经验分享、新手如何入门算法岗、该如何备战、面试常考点分享等热门话题进行了深入的讨论。 合集在这…

【Consul】Linux安装Consul保姆级教程

【Consul】Linux安装Consul保姆级教程 大家好 我是寸铁&#x1f44a; 总结了一篇【Consul】Linux安装Consul保姆级教程✨ 喜欢的小伙伴可以点点关注 &#x1f49d; 前言 今天要把编写的go程序放到linux上进行测试Consul服务注册与发现&#xff0c;那怎么样才能实现这一过程&am…

内网渗透之域环境探索和简单提权

参考文章&#xff1a;http://t.csdnimg.cn/AZ2OR 一个简单的域环境可以这样子搭建&#xff1a; 其中边界服务器有两张网卡&#xff0c;一个是对外的公网网卡&#xff0c;另一张是对内的局域网网卡。一般渗透过程中&#xff0c;拿下这个作为跳板机&#xff0c;进而继续渗透。 …