【Linux后端服务器开发】HTTP协议

目录

一、HTTP协议概述

二、HTTP应用层服务器实现

Util.hpp

Protocal.hpp

Http_Server.hpp

http_server.cc

indext.html


一、HTTP协议概述

【Linux后端服务器开发】HTTP协议_第1张图片

请求和响应怎么保证应用层完整读取完毕了?

  1. 读取完整的一行(识别行分隔符),while(完整的一行),直到将所有的请求行+请求报头全部读完,直到空行
  2. 我们能保证把报头读完,报头有一个属性:Content-Length 表明报头长度,解析内容长度,根据内容长度读取全部内容

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方法:

  • GET通过url传递参数,具体:http://ip:port/XXX/YY?name1=value1&name2=value2
  • POST通过http请求正文传递参数
  • POST通过请求正文提交参数,所以用户一般看不到,私密性更好,但是私密性≠安全性,GET和POST方法都不安全
  • URL传参参数不能太大,POST方法通过请求体正文传参,参数可以很大,也可以是其他类型数据

HTTP的状态码

类型

原因短语

1XX

Informational(信息性状态码)

接收的请求正在处理

2XX

Success(成功状态码)

请求正常处理完毕

3XX

Redirection(重定向状态码)

需要进行附加操作以完成请求

4XX

Client Error(客户端错误状态码)

处理器无法处理请求

5XX

Server Error(服务端错误状态码)

服务器处理请求出错

互联网公司并不会严格遵守HTTP协议的状态码规定,即使是服务器出错,绝大多数情况也不会返回5XX状态码,一是返回5XX的错误码会“掉价”,二是避免入侵。

HTTP的请求头Header

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

二、HTTP应用层服务器实现

服务器获取网页连接请求,返回响应体给客户端(html、图片等资源),展现网页信息

服务器获取网页的请求体之后,分析请求体的字符串数据,生成请求体结构体,根据请求体结构体生成响应体结构体

生成请求体req:①从inbuffer缓冲区读取第一行,里面包含method、url、httpversion信息;②根据url设置资源路径path;③根据path获取资源后缀;④获取资源的大小st

生成响应体resp:①根据req.path获取资源位置;②根据资源后缀返回对应的资源类型;③添加响应体的报头和空行信息;④将资源的二进制信息通过响应正文传输

编写对应的html文件,展示网页,html的根目录设置为wwwroot,index.html是默认主页,404.html是访问错误的默认页面,其他的一些网页资源也可以放在wwwroot目录下,一些二级网页可以放入wwwroot目录下的子目录中

Util.hpp

#pragma once

#include 
#include 
#include 
#include 

using namespace std;

class Util
{
public:
    static string Get_Line(string& buffer, const string& sep)
    {
        auto pos = buffer.find(sep);
        if (pos == string::npos)
            return "";
            
        string sub = buffer.substr(0, pos);
        buffer.erase(0, sub.size() + sep.size());
        
        return sub;
    }

    static bool Read_File(const string resource, char* buffer, int size)
    {
        ifstream in(resource, ios_base::binary);
        if (!in.is_open())
            return false;

        in.read(buffer, size);

        in.close();
        return true;
    }
};

Protocal.hpp

#pragma once

#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include "Util.hpp"

using namespace std;

static const string g_sep = "\r\n";
static const string g_default_root = "./wwwroot";
static const string g_homepage = "index.html";
static const string g_html404 = "./wwwroot/404.html";

class HttpRequest
{
public:
    HttpRequest()
    {}

    void Parse()
    {
        // 1. 从inbuffer里拿到第一行,分隔符 "\r\n"
        string line = Util::Get_Line(inbuffer, g_sep); 
        if (line.empty())
            return;

        // 2. 提取三个字段
        cout << line << endl;
        stringstream ss(line);
        ss >> method >> url >> httpversion;

        // 3. 添加web默认路径
        path = g_default_root;
        path += url;    
        if (path[path.size() - 1] == '/')
            path += g_homepage;

        // 4. 获取path对应的资源后缀
        auto pos = path.rfind(".");
        if (pos == string::npos)
            suffix = ".html";
        else
            suffix = path.substr(pos);

        // 5. 获取资源的大小
        struct stat st;
        int n = stat(path.c_str(), &st);
        if (n == 0)
            size = st.st_size;
        else
            size = -1;
    }

public:
    string inbuffer;
    // std::string reqline;
    // std::vector reqheader;
    // std::string body;
    string method;
    string url;
    string httpversion;
    string path;
    string suffix;
    int size;
};

class HttpResponse
{
public:
    string outbuffer;
};

Http_Server.hpp

#pragma once

#include 
#include 
#include 
#include 
#include 
#include 

#include 
#include 
#include 
#include 

// #include 

#include "Protocal.hpp"

static const uint16_t g_port = 8080;
static const int g_backlog = 5;

using namespace std;
using func_t = function;

class HttpServer
{
public:
    HttpServer(func_t func, const uint16_t& port = g_port)
        : _listenfd(-1), _func(func), _port(port)
    {}

    void Init()
    {
        _listenfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_listenfd < 0)
        {
            std::cerr << "socket create error" << std::endl;
            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(_listenfd, (struct sockaddr*)&local, sizeof(local)) < 0)
        {
            cerr << "bind error" << endl;
            exit(1);
        }

        if (listen(_listenfd, g_backlog) < 0)
        {
            cerr << "listen error" << endl;
            exit(1);
        }
    }

    void Start()
    {
        while (true)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int sock = accept(_listenfd, (struct sockaddr*)&peer, &len);
            if (sock < 0)
                continue;

            pid_t id = fork();
            if (id == 0)
            {
                close(_listenfd);
                if (fork() > 0)
                    exit(0);
                Handler_Http(sock);
                close(sock);
                exit(0);
            }
            waitpid(id, nullptr, 0);
        }
    }

    void Handler_Http(int sock)
    {
        // 1. 读取完整http请求
        // 2. 反序列化请求
        // 3. 回调
        // 4. 序列化响应
        // 5. send

        char buffer[1024];
        HttpRequest req;
        HttpResponse resp;
        ssize_t n = recv(sock, buffer, sizeof(buffer) - 1, 0);
        if (n > 0)
        {
            buffer[n] = 0;
            req.inbuffer = buffer;
            req.Parse();
            _func(req, resp);
            // funcs[req.path](req, resp);     
            send(sock, resp.outbuffer.c_str(), resp.outbuffer.size(), 0);
        }
    }

    // void register_cb(std::string serverceName, func_t cb)
    // {
    //     funcs.insert(std::make_pair(serverceName, cb));
    // }

private:
    int _listenfd;
    uint16_t _port;
    func_t _func;
    // std::unordered_map funcs;
};

http_server.cc

#include "Http_Server.hpp"
#include 

using namespace std;

string Suffix_To_Desc(const string suffix)
{
    string ct = "Content-Type: ";
    if (suffix == ".html")
        ct += "text/html";
    else if (suffix == ".jpg")
        ct += "application/x-jpg"; 

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

// 1. 服务器和网页分离
// 2. 根据url获取资源位置
// 3. 根据访问资源的后缀返回对应的资源类型
bool Get_Resp(const HttpRequest& req, HttpResponse& resp)
{
    // if (req.path == "./seach")
    // {
    //     // 根据path提供特定的服务
    //     // 建立进程间通信,进程替换
    //     // 父进程将 req.parm 通过管道
    // }

    cout << "------------   http start ----------------" << endl;
    cout << req.inbuffer << endl;
    cout << "mothod: " << 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 << "-------------  http end   ----------------" << endl << endl;

    string respline = "HTTP/1.1 200 OK\r\n";
    string respheader = Suffix_To_Desc(req.suffix);

    if (req.size > 0)
    {
        respheader += "Content-Length: ";
        respheader += to_string(req.size);
        respheader += "\r\n";
    }

    respheader += "Set-Cookie: name=12345678qwer; Max-Age=120\r\n";      // Max-Age是到期时间,单位为秒
    // 往后每次http请求,都会自动携带cookie,帮浏览器进行鉴权功能

    string respblank = "\r\n";

    string body;
    body.resize(req.size + 1);
    if (!Util::Read_File(req.path, (char*)body.c_str(), req.size))
    {
        Util::Read_File(g_html404, (char*)body.c_str(), req.size);
    }

    resp.outbuffer += respline;
    resp.outbuffer += respheader;
    resp.outbuffer += respblank;

    cout << "------------   resp start ----------------" << endl;
    cout<< resp.outbuffer << endl;
    cout << "------------   resp end   ----------------" << endl;


    resp.outbuffer += body;

    return true;
}

// ./http_server port
int main(int argc, char* argv[])
{
    if (argc != 2)
    {
        cout << "usage error" << endl;
        exit(1);
    }

    uint16_t port = atoi(argv[1]);
    
    unique_ptr httpsvr(new HttpServer(Get_Resp, port));

    // httpsvr->register_cb("/", get_resp);
    // httpsvr->register_cb("/a/b/test.py", py_mothod);

    httpsvr->Init();
    httpsvr->Start();

    return 0;
}

indext.html




    
    
    
    我的首页

你可能感兴趣的:(Linux后端服务器开发,http,网络协议,网络,linux,服务器,tcp/ip)