认识http的方法、Header、状态码以及简单实现一个http的业务逻辑

news2024/12/22 23:45:30

文章目录

  • http的方法
  • http状态码
  • http重定向
  • http常见Header
  • 实现简单业务逻辑
    • Protocol.hpp
    • Util.hpp
    • Server.hpp
    • Server.cc
  • 效果

http的方法

方法说明支持的HTTP版本
GET获取资源1.0/1.1
POST传输实体主体1.0/1.1
PUT传输文件1.0/1.1
HEAD获得报文首部1.0/1.1
DELETE删除文件1.0/1.1
OPTIONS询问支持方法1.1
TRACE追踪路径1.1
CONNECT要求用隧道协议连接代理1.1
LINK建立和资源之间的联系1.0
UNLINE断开连接关系1.0

其中最为常见的请求方法为:GET POST

事实上,浏览器向服务器进行数据提交时,本质是前端通过form表单提交的,浏览器会自动将form表单中的内容转换为GET/POST的方法请求

例如在QQ的网址上会有登陆框,查看登陆框的源代码就会发现有form表单

image-20230808141948402

如果输入了账号密码之后点击了登陆按钮,浏览器就会将账号和密码根据指定的GET或者POST方法发送给服务器。

其中两者的区别有:

  1. GET方法会将获取到的数据作为参数直接通过url 传递,也就是说GET方法会在url 上直接显示出数据,格式为:http://ip:port/XXX/YY?name=value&name2=value2。会直接暴露出数据
  2. POST方法不是通过url 传递数据,而是直接向请求的正文里提交数据。也就是说参数会存在在正文里,服务器再从正文里提取参数
  3. 因为GET方法是再url中直接传递参数,所以参数不能太大
  4. POST在正文传递参数,所以可以参数很大

需要注意的是

虽然POST方法不会暴露数据,但是并不意味着就是安全的。私密 != 安全。

如果要谈到安全,那就必须要加密,加密内容属于https协议

http状态码

类别原因
1XXinformational - 信息性状态码接受的请求正在处理
2XXsuccess - 成功状态码请求正常处理完毕
3XXredirection - 重定向状态码需要进行附加操作以完成请求
4XXclient error - 客户端错误状态码服务器无法处理请求
5XXserver error - 服务端错误状态码服务器处理请求出错

其中最常见的就是 404 网页不存在

200 代表OK,404 Not Found,403 Forbiden,302 Redirect 重定向,504 Bad Gateway

http重定向

要实现重定向其实很简单,将状态码修改为307代表重定向,然后在正文里加入重定向的网址即可,这样向服务器请求后,服务器就会将处理的请求重定向到指定的网址

// 服务端处理的回调函数
bool func(const HttpRequest &req, HttpResponse &res)
{
    // 打印方便调试查看接收到的数据是否正确
    cout << "---------------http--------------" << endl;
    cout << req._inbuffer;
    cout << "_method: " << req._method << endl;
    cout << " _url: " << req._url << endl;
    cout << " _httpversion: " << req._httpversion << endl;
    cout << " _path: " << req._path << endl;
    cout << " _suffix: " << req._suffix << endl;
    cout << " _size: " << req._size << endl;
    cout << "---------------end---------------" << endl;

    // 状态行
    // string resline = "HTTP/1.1 200 OK\r\n";
    string resline = "HTTP/1.1 307 Temporary Redirect\r\n";

    // 响应报头
    // 需要注意正确的给客户端返回资源,图片是图片,网页是网页
    string rescontet = Util::suffixToDesc(req._suffix);

    // 添加资源长度到报头
    rescontet += "Content-Length: ";
    rescontet += to_string(req._size);
    rescontet += "\r\n";

    // 添加重定向
    rescontet += "Location: https://www.qq.com/\r\n";

    // 空行
    string resblank = "\r\n";
    // 响应正文
    string body;

    // 判断资源是否存在,不存在就返回错误状态码 - 404
    if (!Util::FileIsNo(req._path, &body))
    {
        Util::FileIsNo(errorhtml, &body);
    }

    // 写回响应的数据,后续要发送回客户端
    res._outbuffer += resline;
    res._outbuffer += rescontet;
    res._outbuffer += resblank;
    res._outbuffer += body;

    return true;
}

http常见Header

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

实现简单业务逻辑

代码里涉及html代码,不详细讲解

由于服务器较弱,所以图片获取直接从网址获取,不从服务器读取

需要注意,一个网页看到的结果,可能是有多个资源组合而成,例如有网页,图片,视频等。所以要获取一张完整的网页效果需要浏览器发送多次请求,那么服务器就要根据请求的类型不同对响应正文处理的方式要指明Content-Type的类型。例如网页为“text/html”,jpg格式的图片为“image/jpeg”,不同的格式可自行搜索

判断格式的方法可以根据 url 中资源的后缀进行判断

以下代码均有注释:

Protocol.hpp

请求响应类

因为浏览器会自动处理收到的响应报文,所以不需要编写处理方法只需要将响应报文发送回浏览器即可

#pragma once

#include <iostream>
#include <string>
#include <sstream>
#include "Util.hpp"

using namespace std;

// 定义分隔符
#define sep "\r\n"
#define default_root "./wwwroot"
#define home_page "index.html"
#define errorhtml "./wwwroot/404.html"

// 请求
class HttpRequest
{
public:
    string _inbuffer; // 接收请求数据
    string _method; // 处理数据方法的名称
    string _url; // url
    string _httpversion; //  http协议版本
    string _path; // 查找资源的路径
    string _suffix;// 资源后缀
    int _size;//资源长度

    HttpRequest(){}

    // 处理收到的数据
    // 添加默认路径
    void parse()
    {
        // 拿到第一行
        string line = Util::GetOneLine(_inbuffer, sep);
        if(line.empty())
            return;
        cout << "line: " << line << endl;
        // 拿到第一行中的三个字段
        stringstream ss(line);
        ss >> _method >> _url >> _httpversion;

        // 添加默认路径
        _path = default_root;
        _path += _url;
        // 如果url为/ 则添加默认路径
        if(_path[_path.size() - 1] == '/')
            _path += home_page;

        // 获取资源的后缀
        auto pos = _path.rfind(".");
        if(pos == string::npos)
            _suffix = ".html";
        else
            _suffix = _path.substr(pos);

        // 获取到长度
        _size = Util::GetLen(_path);
    }

};

// 响应
class HttpResponse
{
public:
    string _outbuffer;
};

Util.hpp

工具类,将共有的方法定义同个类,方便调用

#pragma once

#include <iostream>
#include <string>
#include <fstream>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

using namespace std;

class Util
{
public:
    // 提取并删除首行
    // 读到的首行并不需要处理
    static string GetOneLine(string &inbuffer, const string &sep)
    {
        auto pos = inbuffer.find(sep);
        if (pos == string::npos)
            return "";

        string sub = inbuffer.substr(0, pos);
        inbuffer.erase(0, sub.size() + sep.size());
        return sub;
    }

    // 判断请求的资源是否存在
    static bool FileIsNo(const string resource, string *out)
    {
        ifstream in(resource);
        // 打开文件失败说明资源不存在
        if (!in.is_open())
            return false;

        string line;
        while (getline(in, line))
            *out += line;

        in.close();
        return true;
    }

    // 根据后缀指明响应报头类型
    static string suffixToDesc(const string &suffix)
    {
        string st = "Content-Type: ";
        if (suffix == ".html")
            st += "text/html";
        else if (suffix == ".jpg")
            st += "image/jpeg";

        st += "\r\n";
        return st;
    }

    // 获取资源的长度
    static int GetLen(const string &path)
    {
        struct stat s;
        int n = stat(path.c_str(), &s);
        if(n == 0)
            return s.st_size;
        
        return -1;
    }
};

Server.hpp

#pragma once

#include "Protocol.hpp"
#include <sys/types.h>
#include <sys/socket.h>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <functional>
#include <sys/wait.h>
#include <unistd.h>

using func_t = function<bool(const HttpRequest &, HttpResponse &)>;

class Server
{
public:
    Server(func_t func, uint16_t &port)
        : _port(port), _func(func)
    {
    }

    void Init()
    {
        // 创建负责监听的套接字 面向字节流
        _listenSock = socket(AF_INET, SOCK_STREAM, 0);
        if (_listenSock < 0)
            exit(1);

        // 绑定网络信息
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = INADDR_ANY;

        if (bind(_listenSock, (struct sockaddr *)&local, sizeof(local)) < 0)
            exit(3);

        // 设置socket为监听状态
        if (listen(_listenSock, 5) < 0)
            exit(4);
    }

    // 服务端读取处理请求方法
    void HttpHandler(int sock)
    {
        // 确保读到完整的http请求
        char buffer[4096];
        size_t n = recv(sock, buffer, sizeof(buffer) - 1, 0);
        HttpRequest req;
        HttpResponse res;
        if (n > 0)
        {
            buffer[n] = 0;
            req._inbuffer = buffer;
            // 处理读到的数据
            req.parse();
            // 调用回调方法反序列化请求并得到响应结果和序列化响应结果
            _func(req, res);
            // 发回客户端
            send(sock, res._outbuffer.c_str(), res._outbuffer.size(), 0);
        }
    }

    void start()
    {
        while (1)
        {
            // server获取建立新连接
            struct sockaddr_in peer;
            memset(&peer, 0, sizeof(peer));
            socklen_t len = sizeof(peer);
            // 创建通信的套接字
            // accept的返回值才是真正用于通信的套接字
            _sock = accept(_listenSock, (struct sockaddr *)&peer, &len);
            if (_sock < 0)
                continue;
            cout << "sock: " << _sock << endl;

            // 利用多进程实现
            pid_t id = fork();
            if (id == 0) // child
            {
                close(_listenSock);
                // 调用方法包括读取、反序列化、计算、序列化、发送
                HttpHandler(_sock);
                close(_sock);
                exit(0);
            }
            close(_sock);

            // father
            pid_t ret = waitpid(id, nullptr, 0);
        }
    }

private:
    int _listenSock; // 负责监听的套接字
    int _sock;       // 通信的套接字
    uint16_t _port;  // 端口号
    func_t _func;
};

Server.cc

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

// 输出命令错误函数
void Usage(string proc)
{
    cout << "Usage:\n\t" << proc << " local_ip local_port\n\n";
}

// 服务端处理的回调函数
bool func(const HttpRequest &req, HttpResponse &res)
{
    // 打印方便调试查看接收到的数据是否正确
    cout << "---------------http--------------" << endl;
    cout << req._inbuffer;
    cout << "_method: " << req._method << endl;
    cout << " _url: " << req._url << endl;
    cout << " _httpversion: " << req._httpversion << endl;
    cout << " _path: " << req._path << endl;
    cout << " _suffix: " << req._suffix << endl;
    cout << " _size: " << req._size << endl;
    cout << "---------------end---------------" << endl;

    // 状态行
    string resline = "HTTP/1.1 200 OK\r\n";
    // string resline = "HTTP/1.1 307 Temporary Redirect\r\n";

    // 响应报头
    // 需要注意正确的给客户端返回资源,图片是图片,网页是网页
    string rescontet = Util::suffixToDesc(req._suffix);

    // 添加资源长度到报头
    rescontet += "Content-Length: ";
    rescontet += to_string(req._size);
    rescontet += "\r\n";
    
    // // 添加重定向
    // rescontet += "Location: https://www.qq.com/\r\n";

    // 空行
    string resblank = "\r\n";
    // 响应正文
    string body;

    // 判断资源是否存在,不存在就返回错误状态码 - 404
    if (!Util::FileIsNo(req._path, &body))
    {
        Util::FileIsNo(errorhtml, &body);
    }

    // 写回响应的数据,后续要发送回客户端
    res._outbuffer += resline;
    res._outbuffer += rescontet;
    res._outbuffer += resblank;
    res._outbuffer += body;

    return true;
}

int main(int argc, char *argv[])
{
    // 启动服务端不需要指定IP
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(1);
    }

    uint16_t port = atoi(argv[1]);

    unique_ptr<Server> server(new Server(func, port));

    // 服务端初始化
    server->Init();
    // 服务端启动
    server->start();

    return 0;
}

效果

在此就不写出html的文件了,看着效果能够实现即可

可以看到浏览器向服务器发送请求,服务器返回响应,响应里就包括了自己编写的html文件,所以浏览器处理后就显示出了自己的网页

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

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

相关文章

Centos7.6安装mysql8.0.20

安装前 1.安装mysql前&#xff0c;需将系统自带的mariadb卸载。 [rootk8s01 ~]#  rpm ‐qa|grep mariadbmariadb‐libs.x86_643[rootk8s01 ~]#  rpm ‐e ‐‐nodeps mariadb‐libs.x86_643[rootk8s01 ~]#  rpm ‐qa|grep mariadb 2. 下载mysql8.0.20 [rootk8s-01…

【王树森】深度强化学习(DRL)课程笔记:P1 基本概念(含gym安装)

课程信息 课程主讲&#xff1a;王树森&#xff08;史蒂文斯理工学院计算机科学系的终身制助理教授&#xff09; 课程内容&#xff1a;基本概念、价值学习、策略学习、Actor-Critic方法、AlphaGo、Monte Carlo (蒙特卡洛) 课程资料&#xff1a;https://github.com/wangshusen/D…

【MATLAB第66期】#源码分享 | 基于MATLAB的PAWN全局敏感性分析模型(有条件参数和无条件参数)

【MATLAB第66期】#源码分享 | 基于MATLAB的PAWN全局敏感性分析模型&#xff08;有条件参数和无条件参数&#xff09; 文献参考 Pianosi, F., Wagener, T., 2015. A simple and efficient method for global sensitivity analysis based on cumulative distribution functions.…

python多线程及协程

目录 进程和线程 串行和并行 多线程编程 Thread类 创建线程参数 具体案例 继承Thread类 具体案例 线程池 具体案例 协程 协程的使用 协程函数写法 调用多个协程函数 main函数的写法 案例 进程和线程 进程&#xff1a;就是一个程序&#xff0c;运行在系统之上…

[每日习题]年终奖(动态规划) 迷宫问题(DFS+回溯)——牛客习题

hello,大家好&#xff0c;这里是bang___bang_&#xff0c;本篇记录2道牛客习题&#xff0c;年终奖&#xff08;简单&#xff09;&#xff0c;迷宫问题&#xff08;中等&#xff09;&#xff0c;如有需要&#xff0c;希望能有所帮助&#xff01; 目录 1️⃣年终奖 2️⃣迷宫问…

Linux学习-1

Linux学习-1 1.文件系统的常识 本文主要引用鸟哥的Linux私房菜 1.1 常见的标识介绍 > [-][rwx][r-x][r--] > 1 234 567 890 1 为&#xff1a;代表这个文件名为目录或文件&#xff0c;本例中为文件&#xff08;-&#xff09;&#xff1b; 234为&#xff1a;拥有者的权限…

MongoDB数据库操作及操作命令

目录 一、基础概念 二、安装mongod 三、命令交互数据库 &#xff08;1&#xff09;数据库命令 &#xff08;2&#xff09;集合命令 &#xff08;3&#xff09;文档命令 四、Mongoose &#xff08;1&#xff09;增加一条数据 &#xff08;2&#xff09;插入多个数据 &am…

React实现关键字高亮

先看效果&#xff1a; 实现很简单通过以下这个函数&#xff1a; highLight (text, keyword ) > {return text.split(keyword).flatMap(str > [<span style{{ color: red, fontWeight: bold }}>{keyword}</span>, str]).slice(1);}展示某段文本时调用该函数…

【从零开始学习JAVA | 第四十二篇】初学网络编程

目录 前言&#xff1a; 什么是网络编程&#xff1a; 网络编程的应用场景&#xff1a; 常见的软件架构&#xff1a; CS架构&#xff1a; BS架构&#xff1a; 网络编程三要素&#xff1a;​ 总结&#xff1a; 前言&#xff1a; 当今互联网已经渗透到我们日常生活的方方面…

【MongoDB】万字长文,命令与代码一一对应SpringBoot整合MongoDB之MongoTemplate

目录 一、导入依赖与配置信息 二、导入测试数据创建实体类 三、插入数据 1、Insert默认集合插入 2、Insert指定集合插入 3、Insert批量插入数据 4、save默认集合插入 5、save指定集合插入 6、insert与save的区别 四、修改数据 1、修改符合条件的第一条数据 2、全…

单例模式-java实现

介绍 单例模式的意图&#xff1a;保证某个类在系统中有且仅有一个实例。 我们可以看到下面的类图&#xff1a;一般的单例的实现&#xff0c;是属性中保持着一个自己的私有静态实例引用&#xff0c;还有一个私有的构造方法&#xff0c;然后再开放一个静态的获取实例的方法给外界…

golang内存对齐

为什么要内存对齐&#xff1f; CPU访问内存时&#xff0c;以CPU的位数为单位进行访问。 如果访问未对齐的内存&#xff0c;处理器需要做两次内存访问&#xff0c;对齐的内存的访问可能仅需要一次&#xff0c;利用内存对齐后提升读取速度。 golang结构体内存对齐规则 在代码编译…

MySql学习3:常用函数

常用字符串函数 CHAR_LENGTH(s)&#xff1a;返回字符串的长度 select *, char_length(name) as nameLength from emp;CONCAT(s1,s2…sn)&#xff1a;字符串拼接 select name,concat(name,入职时间&#xff1a;,entrydata) as 入职时间 from emp;CONCAT_WS(x, s1,s2…sn)&a…

24v转3.3v输出3A用什么芯片

问&#xff1a;客户需要一个能够将24V输入电压转换为3.3V输出电压&#xff0c;并且能够提供1-3A的电流输出的芯片。还希望它能够内置MOS管。有什么推荐的型号吗&#xff1f;&#xff08;vin24v、5v&#xff0c;vout3.3v&#xff0c;Io1-3A&#xff09; 答&#xff1a;推荐使用…

Unity游戏源码分享-塔防游戏保卫兔子的食物CarrotFantasy

Unity游戏源码分享-塔防游戏保卫兔子的食物CarrotFantasy 经典塔防游戏&#xff0c;可发布PC、Andoid、IOS、Web等 下载地址&#xff1a;https://download.csdn.net/download/Highning0007/88189987

【Spring Boot】Thymeleaf模板引擎 — Thymeleaf页面布局

Thymeleaf页面布局 熟悉Thymeleaf的语法和表达式后&#xff0c;后面开发起来会更加得心应手。接下来好好研究一下Thymeleaf如何实现完整的Web系统页面布局。 1.引入代码片段 在模板中经常希望包含来自其他模板页面的内容&#xff0c;如页脚、页眉、菜单等。为了做到这一点&a…

【从零开始学习JAVA | 三十九篇】深入多线程

目录 前言&#xff1a; ​1.线程的寿命周期​ 2.线程的安全问题 3.锁 同步代码块&#xff1a; 同步方法&#xff1a; 死锁&#xff1a; 4.生产者和消费者模式&#xff08;等待唤醒机制&#xff09; 总结&#xff1a; 前言&#xff1a; 当今软件开发领…

构建Docker容器监控系统(2)(Cadvisor +Prometheus+Grafana)

Cadvisor产品简介 Cadvisor是Google开源的一款用于展示和分析容器运行状态的可视化工具。通过在主机上运行Cadvisor用户可以轻松的获取到当前主机上容器的运行统计信息&#xff0c;并以图表的形式向用户展示。 接着上一篇来继续 部署Cadvisor 被监控主机上部署Cadvisor容器…

Echart(v5)实现中国地图区域图

一、需求背景 需要实现一个中国地图的区域图&#xff08;区域级别到市&#xff09;&#xff0c;并且指定区域可以高亮。 二、相关工具 1、中国的GeoJSON数据获取&#xff1a;DataV.GeoAtlas地理小工具系列 2、Echart组件库 Apache ECharts 三、实现 echart配置&#xff1a; …

MySQL查看当前数据库视图-SQL语句

引言 查询语句为&#xff1a; show full tables where table_type 可查询当前数据库表 一&#xff0c;创建一个视图 # 创建视图 create view v_stu as # 视图内容&#xff08;连接的一个表&#xff09; select name from t_stu union all select tname from t_teach; 二&…