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

文章目录

  • 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文件,所以浏览器处理后就显示出了自己的网页

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

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

相关文章

【将回声引入信号中】在语音或音频文件中引入混响或简单回声,以研究回声延迟和回波幅度对生成的回波信号感知的影响(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

【杨辉三角的两种解法——(超级详细)】

杨辉三角 1.杨辉三角简介&#x1f575;️ 杨辉三角&#xff0c;是二项式系数在三角形中的一种几何排列。在欧洲&#xff0c;这个表叫做帕斯卡三角形。帕斯卡&#xff08;1623----1662&#xff09;是在1654年发现这一规律的&#xff0c;比杨辉要迟393年&#xff0c;比贾宪迟600…

分布式 - 消息队列Kafka:Kafka消费者的分区分配策略

文章目录 1. 环境准备2. range 范围分区策略介绍3. round-robin 轮询分区策略4. sticky 粘性分区策略5. 自定义分区分配策略 1. 环境准备 创建主题 test 有5个分区&#xff0c;准备 3 个消费者并进行消费&#xff0c;观察消费分配情况。然后再停止其中一个消费者&#xff0c;再…

fastadmin 自定义搜索分类和时间范围

1.分类搜索&#xff0c;分类信息获取----php 2.对应html页面&#xff0c;页面底部加搜索提交代码&#xff08;这里需要注意&#xff1a;红框内容&#xff09; 图上代码----方便直接复制使用 <script id"countrySearch" type"text/html"><!--form…

python之matplotlib入门初体验:使用Matplotlib进行简单的图形绘制

目录 绘制简单的折线图1.1 修改标签文字和线条粗细1.2 校正图形1.3 使用内置样式1.4 使用scatter()绘制散点图并设置样式1.5 使用scatter()绘制一系列点1.6 python循环自动计算数据1.7 自定义颜色1.8 使用颜色映射1.9 自动保存图表练习题 绘制简单的折线图 绘制一个简单折线图…

GPT-3.5 人工智能还是人工智障?——西红柿炒钢丝球!!

人工智能还是人工智障&#xff1f;——西红柿炒钢丝球 西红柿炒钢丝球的 基本信息西红柿炒钢丝球的 详细制作方法材料步骤 备注幕后花絮。。。。。。。。。关于GPT-3.5&#xff0c;你的看法&#xff1a; 西红柿炒钢丝球的 基本信息 西红柿炒钢丝球是一道具有悠久历史的传统中式…

springboot汽车租赁后台java出租客户管理jsp源代码mysql

本项目为前几天收费帮学妹做的一个项目&#xff0c;Java EE JSP项目&#xff0c;在工作环境中基本使用不到&#xff0c;但是很多学校把这个当作编程入门的项目来做&#xff0c;故分享出本项目供初学者参考。 一、项目描述 springboot汽车租赁后台 系统有1权限&#xff1a;管理…

阿里巴巴面试题---考察对底层源代码的熟悉程度

题目如图所示: 很多人可能会觉得两个输出都会是false,因为我们都会觉得""比较的是引用类型的地址,虽然放入的值都一样但是重新创造了新对象,地址不一样,所以结果都是false. 然而,当我们运行程序会发现结果都是false. 下面,我们来分析为什么是这样的结果. 我们知道…

OneFlow 中的 Softmax

Softmax 是深度学习模型中的常见算子。PyTorch 的 Softmax 算子直接调用 cuDNN 的接口。而 OneFlow 内部针对输入数据的类别数量&#xff0c;采用3个 kernel 来分别处理&#xff0c;在多数情况下都可以获得比 cuDNN 更优的性能表现。测试结果可见 如何实现一个高效的Softmax CU…

未来混合动力汽车的发展:技术探索与前景展望

随着环境保护意识的增强和对能源消耗的关注&#xff0c;混合动力汽车成为了汽车行业的研发热点。混合动力汽车融合了传统燃油动力和电力动力系统&#xff0c;通过优化能源利用效率&#xff0c;既降低了燃油消耗和排放&#xff0c;又提供了更长的续航里程。本文将探讨混合动力汽…

配置docker,案例复现

配置docker(系统为centos) 1.检查操作系统环境: docker要求CentOS系统的内核版本高于 3.10 &#xff0c;通过 uname -r 命令查看你当前的内核版本是否支持安装docker 2.查看你是否拥有旧的版本&#xff0c;有的话卸载&#xff0c;没有的话直接略过该步骤 sudo yum remove d…

【快应用】list组件属性的运用指导

【关键词】 list、瀑布流、刷新、页面布局 【问题背景】 1、 页面部分内容需要瀑布流格式展示&#xff0c;在使用lsit列表组件设置columns进行多列渲染时&#xff0c;此时在里面加入刷新动画时&#xff0c;动画只占了list组件的一列&#xff0c;并没有完全占据一行宽度&…

“解锁IDEA的潜力:高级Java Maven项目配置指南”

目录 前言&#xff1a;流程目录&#xff1a;1.确保Java和Maven已安装检查Java是否已正确安装并配置环境变量 2.创建一个新的Maven项目导航到要创建项目的目录配置Maven运行以下命令创建一个新的Maven项目 3.配置项目的pom.xml文件打开项目根目录下的pom.xml文件配置Web.xml 4.配…

案例13 Spring MVC参数传递案例

基于Spring MVC实现HttpServletRequest、基本数据类型、Java Bean、数组、List、Map、JSON方式的参数传递。 1. 创建项目 选择Maven快速构建web项目&#xff0c;项目名称为case13-springmvc02。 2. 配置Maven依赖 <?xml version"1.0" encoding"UTF-8&quo…

Vue2(生命周期,列表排序,计算属性和监听器)

目录 前言一&#xff0c;生命周期1.1&#xff0c;生命周期函数简介1.2&#xff0c;Vue的初始化流程1.3,Vue的更新流程1.4&#xff0c; Vue的销毁流程1.5&#xff0c; 回顾生命周期1.,6&#xff0c;代码演示1.6-1&#xff0c;beforeCreate1.6-2&#xff0c;created1.6-3&#xf…

9.2.1Socket(UDP)

一.传输层: 1.UDP:无连接,不可靠,面向数据报,全双工. 2.TCP:有连接,可靠,面向字节流,全双工. 注意:这里的可不可靠是相对的,并且和安不安全无关. 二.UDP数据报套接字编程: 1.socket文件:表示网卡的这类文件. 2.DatagramPacket:表示一个UDP数据报. 三.代码实现: 1.回显服务…

Linux系列:从0到1用Docker部署springboot项目

目录 1.前提条件 2.编写DockerFile镜像文件 3.打包SpringBoot项目 4.通过软件Xftp进行传输&#xff08;*&#xff09; 1.点击“文件-新建”​编辑 5.操作远程主机 1.docker构建 2.容器运行 6.容器的关闭和删除 1.前提条件 Linux、docker、xftp的安装、一台可以访问的远…

自动驾驶——驶向未来的革命性技术

自动驾驶——驶向未来的革命性技术 自动驾驶的组件自动驾驶的优势自动驾驶的应用自动驾驶的未来中国的自动驾驶 自动驾驶是一种技术&#xff0c;它允许车辆在没有人类驾驶员的情况下自主地进行行驶。它利用各种传感器、计算机视觉、人工智能和机器学习算法来感知和理解周围环境…

Vim学习(三)—— Git Repo Gerrit

Git、Gerrit、Repo三者的概念及使用 三者各自作用&#xff1a; git&#xff1a;版本管理库&#xff0c;在git库中没有中心服务器的概念&#xff0c;真正的分布式。 repo&#xff1a;repo就是多个git库的管理工具。如果是多个git库同时管理&#xff0c;可以使用repo。当然使用…

Linux常见面试题,应对面试分享

操作系统基础 1.cpu占⽤率太⾼了怎么办? 排查思路是什么&#xff0c;怎么定位这个问题&#xff0c;处理流程 其他程序: 1.通过top命令按照CPU使⽤率排序找出占⽤资源最⾼的进程 2.lsof查看这个进程在使⽤什么⽂件或者有哪些线程 3.询问开发或者⽼⼤,是什么业务在使⽤这个进程…