【Linux网络】应用层协议HTTP

  🌈个人主页:秦jh__https://blog.csdn.net/qinjh_?spm=1010.2135.3001.5343
🔥 系列专栏:https://blog.csdn.net/qinjh_/category_12891150.html

9efbcbc3d25747719da38c01b3fa9b4f.gif

目录

HTTP 协议

认识 URL

urlencode 和 urldecode

HTTP 协议请求与响应格式 

HTTP 常见 Header

 关于 connection 报头

 HTTP 的状态码

 HTTP 的方法

HTTP 常见方法

部分http代码


前言

    💬 hello! 各位铁子们大家好哇。

             今日更新了Linux网络http的内容
    🎉 欢迎大家关注🔍点赞👍收藏⭐️留言📝

HTTP 协议

虽然我们说, 应用层协议是我们程序猿自己定的. 但实际上, 已经有大佬们定义了一些现成的, 又非常好用的应用层协议, 供我们直接参考使用. HTTP(超文本传输协议)就是其中之一。

在互联网世界中,HTTP(HyperText Transfer Protocol,超文本传输协议)是一个至关重要的协议。它定义了客户端(如浏览器)与服务器之间如何通信,以交换或传输超文本(如 HTML 文档)。

HTTP 协议是客户端与服务器之间通信的基础。客户端通过 HTTP 协议向服务器发送请求,服务器收到请求后处理并返回响应。HTTP 协议是一个无连接、无状态的协议,即每次请求都需要建立新的连接,且服务器不会保存客户端的状态信息。

认识 URL

平时我们俗称的 "网址" 其实就是说的 URL

 

域名会被我们客户端的应用自动转换成ip地址,叫做地址转化,也称DNS。

上面的url中,没有端口号,这里的端口号默认被忽略了,他们会默认绑定某个知名端口,所以显示上会被忽略。当浏览器发起请求时,会自动拼接端口号。

域名后面是路径,可以表示文件的唯一性。

域名表示唯一一台主机,路径表示主机上唯一的文件资源,二者结合就可以表示互联网中唯一的一个文件资源!

所以,URL也称为统一资源定位符。        

urlencode 和 urldecode

像 / ? : 等这样的字符, 已经被 url 当做特殊意义理解了. 因此这些字符不能随意出现. 比如, 某个参数中需要带有这些特殊字符, 就必须先对特殊字符进行转义.

转义的规则如下:

将需要转码的字符转为 16 进制,然后从右到左,取 4 位(不足4 位直接处理),每2位做一位,前面加上%,编码成%XY 格式 

这种对特殊字符进行编码处理的操作叫做urlencode。

urldecode 就是 urlencode 的逆过程;

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 常见 Header

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

 关于 connection 报头

HTTP 中的 Connection 字段是 HTTP 报文头的一部分,它主要用于控制和管理客户端与服务器之间的连接状态

核心作用:

  • 管理持久连接:Connection 字段还用于管理持久连接(也称为长连接)。持久连接允许客户端和服务器在请求/响应完成后不立即关闭 TCP 连接,以便在同一个连接上发送多个请求和接收多个响应。

持久连接(长连接):

  • HTTP/1.1:在 HTTP/1.1 协议中,默认使用持久连接。当客户端和服务器都不明确指定关闭连接时,连接将保持打开状态,以便后续的请求和响应可以复用同一个连接。
  • HTTP/1.0:在 HTTP/1.0 协议中,默认连接是非持久的。如果希望在HTTP/1.0上实现持久连接,需要在请求头中显式设置 Connection: keep-alive

语法格式:

  • Connection: keep-alive:表示希望保持连接以复用 TCP 连接。
  • Connection: close:表示请求/响应完成后,应该关闭TCP 连接 

 HTTP 的状态码

最常见的状态码, 比如 200(OK), 404(Not Found), 403(Forbidden), 302(Redirect, 重定向), 504(Bad Gateway)

以下是仅包含重定向相关状态码的表格:

 HTTP 状态码 301(永久重定向)和 302(临时重定向)都依赖Location 选项。以下是关于两者依赖 Location 选项的详细说明:

HTTP 状态码 301(永久重定向):

  • 当服务器返回 HTTP 301 状态码时,表示请求的资源已经被永久移动到新的位置。
  • 在这种情况下,服务器会在响应中添加一个 Location 头部,用于指定资源的新位置。这个 Location 头部包含了新的 URL 地址,浏览器会自动重定向到该地址

例如,在 HTTP 响应中,可能会看到类似于以下的头部信息:

HTTP/1.1 301 Moved Permanently\r\n

Location: https://www.new-url.com\r\n

HTTP 状态码 302(临时重定向):

  • 当服务器返回 HTTP 302 状态码时,表示请求的资源临时被移动到新的位置。
  • 同样地,服务器也会在响应中添加一个 Location 头部来指定资源的新位置。浏览器会暂时使用新的 URL 进行后续的请求,但不会缓存这个重定向。

总结:无论是 HTTP 301 还是 HTTP 302 重定向,都需要依赖Location 选项来指定资源的新位置。这个 Location 选项是一个标准的 HTTP 响应头部,用于告诉浏览器应该将请求重定向到哪个新的 URL 地址。

 HTTP 的方法

其中最常用的就是 GET 方法和 POST 方法

HTTP 常见方法

GET 方法

  • 用途:用于请求 URL 指定的资源。
  • 示例:GET /index.html HTTP/1.1
  • 特性:指定资源经服务器端解析后返回响应内容 

POST 方法

  • 用途:用于传输实体的主体,通常用于提交表单数据。
  • 示例:POST /submit.cgi HTTP/1.1
  • 特性:可以发送大量的数据给服务器,并且数据包含在请求体中 

总结:

GET一般用来获取静态资源,也可以通过url向服务器传递参数。

POST可以通过 http request 的正文来进行参数传递 

url传递参数,参数的体量一定不大,正文可以很大。

POST方法,比GET方法传参更私密,但都不安全。

HEAD 方法

  • 用途:与 GET 方法类似,但不返回报文主体部分,仅返回响应头。
  • 示例:HEAD /index.html HTTP/1.1
  • 特性:用于确认 URL 的有效性及资源更新的日期时间等。

DELETE 方法

  • 用途:用于删除文件,是 PUT 的相反方法。
  • 示例:DELETE /example.html HTTP/1.1
  • 特性:按请求 URL 删除指定的资源

OPTIONS 方法

  • 用途:用于查询针对请求 URL 指定的资源支持的方法。
  • 示例:OPTIONS * HTTP/1.1
  • 特性:返回允许的方法,如 GET、POST 等。

部分http代码

#pragma once

#include <iostream>
#include <string>
#include <vector>
#include <sstream>
#include<functional>
#include <fstream>
#include <unordered_map>

const static std::string base_sep = "\r\n";
const static std::string line_sep = ": ";
const static std::string prefixpath = "wwwroot"; // web根目录
const static std::string homepage = "index.html";
const static std::string httpversion = "HTTP/1.0";
const static std::string spacesep = " ";
const static std::string suffixsep = ".";
const static std::string html_404 = "404.html";
const static std::string arg_sep = "?";

class HttpRequest
{
private:
    std::string GetLine(std::string &reqstr)
    {
        auto pos = reqstr.find(base_sep);
        if (pos == std::string::npos)
            return std::string();
        std::string line = reqstr.substr(0, pos);
        reqstr.erase(0, line.size() + base_sep.size());
        return line.empty() ? base_sep : line;
    }

    void ParseReqLine()
    {
        std::stringstream ss(_req_line); // cin >>
        ss >> _method >> _url >> _version;//a/b/c.html or /login?user=XXX&passwd=1234 /register
        
        if(strcasecmp(_method.c_str(),"GET")==0)
        {
            auto pos=_url.find(arg_sep);
            if(pos!=std::string::npos)
            {
                _body_text=_url.substr(pos+arg_sep.size());
                _url.resize(pos);
            }

            
        }
        
        _path += _url;

        if (_path[_path.size() - 1] == '/')
        {
            _path += homepage;
        }
        // wwwroot/index.html
        // wwwroot/image/1.png
        auto pos = _path.rfind(suffixsep);
        if (pos != std::string::npos)
        {
            _suffix = _path.substr(pos);
        }
        else
        {
            _suffix = ".default";
        }
    }
    void ParseReqHeader()
    {
        for (auto &header : _req_headers)
        {
            auto pos = header.find(line_sep);
            if (pos == std::string::npos)
                continue;
            std::string k = header.substr(0, pos);
            std::string v = header.substr(pos + line_sep.size());
            if (k.empty() || v.empty())
                continue;
            _headers_kv.insert(std::make_pair(k, v));
        }
    }


public:
    HttpRequest() : _blank_line(base_sep), _path(prefixpath)
    {
    }

    void Deserialize(std::string &reqstr)
    {
        // 基本的反序列化
        _req_line = GetLine(reqstr);
        std::string header;
        do
        {
            header = GetLine(reqstr);
            if (header.empty())
                break;
            else if (header == base_sep)
                break;
            _req_headers.push_back(header);
        } while (true);

        if (!reqstr.empty())
        {
            _body_text = reqstr;
        }

        // 再进一步反序列化
        ParseReqLine();
        ParseReqHeader();
    }

    std::string Url()
    {
        LOG(DEBUG, "Client Want url %s\n", _url.c_str());
        return _url;
    }

    std::string Path()
    {
        LOG(DEBUG, "Client Want path %s\n", _path.c_str());
        return _path;
    }
    std::string Suffix()
    {
        return _suffix;
    }
    std::string Method()
    {
        LOG(DEBUG, "Client request method is %s\n", _method.c_str());
        return _method;
    }
    std::string GetRequestBody()
    {
        LOG(DEBUG, "Client request method is %s, args: %s, request path: %s\n", _method.c_str(),_body_text.c_str(),_path.c_str());
        return _body_text;
    }
    void Print()
    {
        std::cout << "---------------------------" << std::endl;
        std::cout << "###" << _req_line << std::endl;
        for (auto &header : _req_headers)
        {
            std::cout << "@@@" << header << std::endl;
        }
        std::cout << "***" << _blank_line;
        std::cout << ">>>" << _body_text << std::endl;

        std::cout << "Method:" << _method << std::endl;
        std::cout << "Url:" << _url << std::endl;
        std::cout << "Version:" << _version << std::endl;

        for (auto &header_kv : _headers_kv)
        {
            std::cout << ")))" << header_kv.first << "->" << header_kv.second << std::endl;
        }
    }

    ~HttpRequest()
    {
    }

private:
    // 基本的httprequest的格式
    std::string _req_line;
    std::vector<std::string> _req_headers;
    std::string _blank_line;
    std::string _body_text;

    // 更具体的属性字段,需要进一步反序列化
    std::string _method;
    std::string _url;
    std::string _path;
    std::string _suffix; // 资源后缀
    std::string _version;
    std::unordered_map<std::string, std::string> _headers_kv;
};

class HttpResponse
{
public:
    HttpResponse() : _version(httpversion), _blank_line(base_sep)
    {
    }

    void AddCode(int code, const std::string &desc)
    {
        _status_code = code;
        _desc = desc;
    }
    void AddHeader(const std::string &k, const std::string &v)
    {
        _headers_kv[k] = v;
    }
    void AddBodyText(const std::string body_text)
    {
        _resp_body_text = body_text;
    }
    std::string Serialize()
    {
        // 1.构建状态行
        _status_line = _version + spacesep + std::to_string(_status_code) + spacesep + _desc + base_sep;

        // 2.构建应答报头
        for (auto &header : _headers_kv)
        {
            std::string header_line = header.first + line_sep + header.second + base_sep;
            _resp_headers.push_back(header_line);
        }

        // 3.空行和正文

        // 4.正式序列化
        std::string responsestr = _status_line;
        for (auto &line : _resp_headers)
        {
            responsestr += line;
        }
        responsestr += _blank_line;
        responsestr += _resp_body_text;

        return responsestr;
    }
    ~HttpResponse()
    {
    }

private:
    // httpresponse base 属性
    std::string _version;
    int _status_code;
    std::string _desc;
    std::unordered_map<std::string, std::string> _headers_kv;

    // 基本的httprequest的格式
    std::string _status_line;
    std::vector<std::string> _resp_headers;
    std::string _blank_line;
    std::string _resp_body_text;
};

using func_t=std::function<HttpResponse(HttpRequest&)>;

class HttpServer
{
public:
    std::string GetFileContent(const std::string &path)
    {
        std::ifstream in(path, std::ios::binary);
        if (!in.is_open())
            return std::string();
        in.seekg(0, in.end);
        int filesize = in.tellg(); // 告诉我你的rw偏移量是多少
        in.seekg(0, in.beg);

        std::string content;
        content.resize(filesize);
        in.read((char *)content.c_str(), filesize);
        in.close();

        return content;
    }

public:
    HttpServer()
    {
        _mime_type.insert(std::make_pair(".html", "text/html"));
        _mime_type.insert(std::make_pair(".jpg", "image/jpeg"));
        _mime_type.insert(std::make_pair(".png", "image/png"));
        _mime_type.insert(std::make_pair(".default", "text/html"));

        _code_to_desc.insert(std::make_pair(100, "Continue"));
        _code_to_desc.insert(std::make_pair(200, "OK"));
        _code_to_desc.insert(std::make_pair(201, "Created"));
        _code_to_desc.insert(std::make_pair(301, "Moved Permanently"));
        _code_to_desc.insert(std::make_pair(302, "Found"));
        _code_to_desc.insert(std::make_pair(404, "Not Found"));
    }

    // #define TEST
    std::string HandlerHttpRequest(std::string &reqstr) // req曾经被客户端序列化过
    {
#ifdef TEST
        std::cout << "--------------------------------" << std::endl;
        std::cout << reqstr;

        std::string responsestr = "HTTP/1.1 200 OK\r\n";
        responsestr += "Content-Type: text/html\r\n";
        responsestr += "\r\n";
        responsestr += "<html><h1>hello Linux, hello bite!</h1></html>";

        return responsestr;
#else
        std::cout << "--------------------------------" << std::endl;
        std::cout << reqstr;
        std::cout << "--------------------------------" << std::endl;

        HttpRequest req;
        HttpResponse resp;
        req.Deserialize(reqstr);
        // req.Method();
        

        if (req.Path() == "wwwroot/redir")
        {
            //处理重定向
            std::string redir_path="https://www.qq.com";
            resp.AddCode(301,_code_to_desc[301]);
            resp.AddHeader("Location",redir_path);
        }
        else
        {
            // 最基本的上层处理,处理静态资源
            std::string content = GetFileContent(req.Path());
            if (content.empty())
            {

                std::string content = GetFileContent("wwwroot/404.html");
                resp.AddCode(404, _code_to_desc[404]);
                resp.AddHeader("Content-Length", std::to_string(content.size()));
                resp.AddHeader("Content-Type", _mime_type[".html"]);
                resp.AddBodyText(content);
            }
            else if(!req.GetRequestBody().empty())
            {
                if(IsServiceExists(req.Path()))
                {
                    resp=_service_list[req.Path()](req);
                }
            }
            else
            {
                resp.AddCode(200, _code_to_desc[200]);
                resp.AddHeader("Content-Length", std::to_string(content.size()));
                resp.AddHeader("Content-Type", _mime_type[req.Suffix()]);
                resp.AddHeader("Set-Cookie","username=zhangsan");
                // resp.AddHeader("Set-Cookie","passwd=12345");
                resp.AddBodyText(content);
            }
        }

        return resp.Serialize();
#endif
    }

    void InsertService(const std::string &servicename,func_t f)
    {
        std::string s=prefixpath+servicename;
        _service_list[servicename]=f;
    }

    bool IsServiceExists(const std::string &servicename)
    {
        auto iter=_service_list.find(servicename);
        if(iter==_service_list.end()) return false;
        else return true;
    }

    ~HttpServer()
    {
    }

private:
    std::unordered_map<std::string, std::string> _mime_type;
    std::unordered_map<int, std::string> _code_to_desc;
    std::unordered_map<std::string, func_t> _service_list;
};

评论 18
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秦jh_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值