文章目录
- 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表单

如果输入了账号密码之后点击了登陆按钮,浏览器就会将账号和密码根据指定的GET或者POST方法发送给服务器。
其中两者的区别有:
- GET方法会将获取到的数据作为参数直接通过url 传递,也就是说GET方法会在url 上直接显示出数据,格式为:http://ip:port/XXX/YY?name=value&name2=value2。会直接暴露出数据
 - POST方法不是通过url 传递数据,而是直接向请求的正文里提交数据。也就是说参数会存在在正文里,服务器再从正文里提取参数
 - 因为GET方法是再url中直接传递参数,所以参数不能太大
 - POST在正文传递参数,所以可以参数很大
 
需要注意的是:
虽然POST方法不会暴露数据,但是并不意味着就是安全的。私密 != 安全。
如果要谈到安全,那就必须要加密,加密内容属于https协议
http状态码
| 类别 | 原因 | |
|---|---|---|
| 1XX | informational - 信息性状态码 | 接受的请求正在处理 | 
| 2XX | success - 成功状态码 | 请求正常处理完毕 | 
| 3XX | redirection - 重定向状态码 | 需要进行附加操作以完成请求 | 
| 4XX | client error - 客户端错误状态码 | 服务器无法处理请求 | 
| 5XX | server 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-Length | Body的长度 | 
| 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文件,所以浏览器处理后就显示出了自己的网页




![[每日习题]年终奖(动态规划) 迷宫问题(DFS+回溯)——牛客习题](https://img-blog.csdnimg.cn/be63b4a2609d47069ffabb7ec34f0176.png)













