Linux——网络(http)

目录

一、HTTP协议与URL编码:从入门到实践

[1. 引言](#1. 引言)

[2. HTTP协议:应用层的基石](#2. HTTP协议:应用层的基石)

[3. URL:互联网资源的"门牌号"](#3. URL:互联网资源的“门牌号”)

[4. 为什么需要URL编码?](#4. 为什么需要URL编码?)

[5. URL编码规则:从字符到%XY](#5. URL编码规则:从字符到%XY)

[6. 实战:编码与解码](#6. 实战:编码与解码)

[6.1 手动编码示例](#6.1 手动编码示例)

[6.2 工具与代码](#6.2 工具与代码)

[7. 常见陷阱与最佳实践](#7. 常见陷阱与最佳实践)

[8. 总结](#8. 总结)

二、HTTP协议格式详解:请求与响应

[1. HTTP请求格式](#1. HTTP请求格式)

[1.1 请求行(Request Line)](#1.1 请求行(Request Line))

[1.2 请求头(Headers)](#1.2 请求头(Headers))

[1.3 请求体(Body)](#1.3 请求体(Body))

[2. HTTP响应格式](#2. HTTP响应格式)

[2.1 状态行(Status Line)](#2.1 状态行(Status Line))

[2.2 响应头(Headers)](#2.2 响应头(Headers))

[2.3 响应体(Body)](#2.3 响应体(Body))

[3. 常见HTTP方法对照表](#3. 常见HTTP方法对照表)

[4. 状态码分类](#4. 状态码分类)

[5. 调试工具与技巧](#5. 调试工具与技巧)

[6. 代码示例(C++)](#6. 代码示例(C++))

1、发送GET请求

2、发送POST请求(JSON数据)

3、实例

[7. 总结](#7. 总结)


一、HTTP协议与URL编码:从入门到实践

1. 引言

在互联网的世界中,HTTP协议和URL如同空气般无处不在。无论是浏览网页、调用API,还是提交表单,它们的背后都离不开这些基础技术。然而,许多开发者对URL中的特殊字符处理和编码机制一知半解。本文将从HTTP协议出发,深入解析URL的结构,并揭开urlencodeurldecode的神秘面纱。


2. HTTP协议:应用层的基石

HTTP(HyperText Transfer Protocol)是一种无状态请求-响应协议,属于应用层协议。它的核心特点包括:

  • 无连接性:每次请求完成后关闭连接(HTTP/1.1默认支持长连接)。

  • 无状态:服务器不保留客户端的历史请求信息(依赖Cookie/Session实现状态管理)。

  • 灵活性:可传输文本、图片、视频等任意类型的数据。

bash 复制代码
GET /index.html HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0

3. URL:互联网资源的"门牌号"

URL(Uniform Resource Locator)俗称"网址",用于定位网络资源。其标准格式如下:
协议://域名:端口/路径?查询参数#锚点

示例解析
https://www.example.com:8080/search?q=HTTP&page=1#results

  • 协议https

  • 域名www.example.com

  • 端口8080(默认可省略)

  • 路径/search

  • 查询参数q=HTTP&page=1

  • 锚点results(仅客户端使用)


4. 为什么需要URL编码?

URL中保留字符(如/, ?, :, =等)具有特殊含义。若参数中包含这些字符,需进行转义以避免歧义。

例如,查询参数中的空格、中文或符号必须编码,否则可能导致:

  • 服务器解析错误

  • 安全漏洞(如SQL注入)

  • 跨平台兼容性问题


5. URL编码规则:从字符到%XY

核心步骤

  1. 字符转换:将字符按指定编码(通常为UTF-8)转换为字节序列。

  2. 十六进制转义 :每个字节转为%后跟两位十六进制数(大写字母)。

示例

  • 空格 → %20(ASCII码为32 → 0x20)

  • 中文"码" → UTF-8编码为E7 A0 81%E7%A0%81

  • 符号@%40


6. 实战:编码与解码
6.1 手动编码示例

假设参数为name=Alice&msg=Hello, World!,编码后为:
name=Alice&msg=Hello%2C%20World%21

6.2 工具与代码

7. 常见陷阱与最佳实践
  • 双重编码 :确保只编码一次,避免%2520(原本应为%20)。

  • 保留字符处理 :使用safe参数保留部分字符(如quote("/api", safe="")%2Fapi)。

  • 编码一致性:服务器与客户端需使用相同的字符集(如UTF-8)。


8. 总结
  • HTTP协议是Web通信的基石,理解其无状态特性至关重要。

  • URL通过结构化格式精准定位资源,特殊字符需编码处理。

  • urlencode/urldecode确保数据安全传输,避免解析冲突。


扩展阅读

掌握这些知识,你已迈出成为Web开发高手的第一步!🚀


二、HTTP协议格式详解:请求与响应

1. HTTP请求格式

HTTP请求由请求行(Request Line)请求头(Headers)空行和**请求体(Body)**四部分组成。

格式示例:

bash 复制代码
GET /api/data?page=1 HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0
Accept: application/json
Content-Type: application/x-www-form-urlencoded
Connection: keep-alive

username=alice&password=123456
1.1 请求行(Request Line)
  • 语法<Method> <Request-URI> <HTTP-Version>

  • 关键元素

    • Method :HTTP方法(如GET, POST, PUT, DELETE)。

    • Request-URI :资源路径(如/index.html或带参数的/search?q=hello)。

    • HTTP Version :协议版本(如HTTP/1.1HTTP/2)。

1.2 请求头(Headers)
  • 作用:传递附加信息(如客户端类型、支持的内容格式等)。

  • 常见请求头

    头字段 说明
    Host 目标服务器域名(必填)
    User-Agent 客户端标识(如浏览器或工具类型)
    Accept 客户端可接收的响应格式(如text/html
    Content-Type 请求体的数据类型(如application/json
    Authorization 身份验证凭证(如Bearer token
1.3 请求体(Body)
  • 适用场景POSTPUT等需要传递数据的请求。

  • 格式 :由Content-Type决定,常见类型:

    • application/x-www-form-urlencoded(表单数据)

    • application/json(JSON数据)

    • multipart/form-data(文件上传)


2. HTTP响应格式

HTTP响应由状态行(Status Line)响应头(Headers)空行和**响应体(Body)**四部分组成。

格式示例:

bash 复制代码
HTTP/1.1 200 OK
Server: nginx/1.18.0
Content-Type: application/json
Content-Length: 42
Date: Fri, 15 Sep 2023 12:00:00 GMT

{"status": "success", "data": "Hello, World!"}
2.1 状态行(Status Line)
  • 语法<HTTP-Version> <Status-Code> <Reason-Phrase>

  • 关键元素

    • Status Code :3位数字状态码(如200)。

    • Reason Phrase :状态码的文本描述(如OK)。

2.2 响应头(Headers)
  • 作用:传递服务器信息或控制客户端行为。

  • 常见响应头

    头字段 说明
    Server 服务器软件信息(如Apache/2.4
    Content-Type 响应体的数据类型(如text/html
    Content-Length 响应体字节数
    Set-Cookie 向客户端设置Cookie
    Cache-Control 缓存策略(如max-age=3600
2.3 响应体(Body)
  • 作用:承载服务器返回的实际数据(如HTML、JSON等)。

  • 示例

    • HTML页面:<html>...</html>

    • JSON数据:{"error": "Invalid token"}


3. 常见HTTP方法对照表
方法 用途 是否幂等 是否有Body
GET 获取资源
POST 提交数据
PUT 更新资源
DELETE 删除资源
PATCH 部分更新资源

4. 状态码分类
状态码 类别 说明
1xx 信息性 请求已被接收,继续处理
2xx 成功 请求已被成功处理(如200 OK
3xx 重定向 需进一步操作(如301 Moved Permanently
4xx 客户端错误 请求有误(如404 Not Found
5xx 服务器错误 服务器处理失败(如500 Internal Server Error

5. 调试工具与技巧
  1. 浏览器开发者工具

    • F12打开,在Network标签中查看请求/响应详情。
  2. 命令行工具

    • cURL

      bash 复制代码
      curl -v http://example.com  # 显示详细请求/响应信息
    • HTTPie(更友好的替代工具):

      bash 复制代码
      http POST http://api.example.com/login username=alice
  3. 在线测试工具

    • Postman:可视化构造请求并测试API。

6. 代码示例(C++)
1、发送GET请求
cpp 复制代码
#include <iostream>
#include <cpr/cpr.h>
#include <nlohmann/json.hpp>

using json = nlohmann::json;

int main() {
    // 发送GET请求(带查询参数和请求头)
    cpr::Response response = cpr::Get(
        cpr::Url{"http://api.example.com/data"},
        cpr::Parameters{
  
  {"page", "1"}},
        cpr::Header{
  
  {"Authorization", "Bearer YOUR_TOKEN"}}
    );

    // 检查响应状态码
    if (response.status_code == 200) {
        // 解析JSON响应体
        json data = json::parse(response.text);
        std::cout << "响应数据: " << data.dump(2) << std::endl;
    } else {
        std::cerr << "请求失败,状态码: " << response.status_code << std::endl;
    }

    return 0;
}
2、发送POST请求(JSON数据)
cpp 复制代码
#include <iostream>
#include <cpr/cpr.h>
#include <nlohmann/json.hpp>

using json = nlohmann::json;

int main() {
    // 构造JSON请求体
    json request_body = {
        {"title", "New Post"},
        {"content", "Hello!"}
    };

    // 发送POST请求
    cpr::Response response = cpr::Post(
        cpr::Url{"http://api.example.com/create"},
        cpr::Header{
  
  {"Content-Type", "application/json"}},
        cpr::Body{request_body.dump()}
    );

    // 检查响应状态码
    if (response.status_code >= 200 && response.status_code < 300) {
        std::cout << "请求成功!响应内容: " << response.text << std::endl;
    } else {
        std::cerr << "请求失败,状态码: " << response.status_code << std::endl;
    }

    return 0;
}
3、实例
cpp 复制代码
#include <iostream>
#include <string>
#include <pthread.h>
#include <fstream>
#include <vector>
#include <sstream>
#include <sys/types.h>
#include <sys/socket.h>
#include <unordered_map>
#include <memory>
#include <pthread.h>
#include <time.h>
#include <stdarg.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>

#define SIZE 1024

#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4

#define Screen 1
#define Onefile 2
#define Classfile 3

#define LogFile "log.txt"

class Log
{
public:
    Log()
    {
        printMethod = Screen;
        path = "./log/";
    }
    void Enable(int method)
    {
        printMethod = method;
    }
    std::string levelToString(int level)
    {
        switch (level)
        {
        case Info:
            return "Info";
        case Debug:
            return "Debug";
        case Warning:
            return "Warning";
        case Error:
            return "Error";
        case Fatal:
            return "Fatal";
        default:
            return "None";
        }
    }

    // void logmessage(int level, const char *format, ...)
    // {
    //     time_t t = time(nullptr);
    //     struct tm *ctime = localtime(&t);
    //     char leftbuffer[SIZE];
    //     snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),
    //              ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,
    //              ctime->tm_hour, ctime->tm_min, ctime->tm_sec);

    //     // va_list s;
    //     // va_start(s, format);
    //     char rightbuffer[SIZE];
    //     vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);
    //     // va_end(s);

    //     // 格式:默认部分+自定义部分
    //     char logtxt[SIZE * 2];
    //     snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftbuffer, rightbuffer);

    //     // printf("%s", logtxt); // 暂时打印
    //     printLog(level, logtxt);
    // }
    void printLog(int level, const std::string &logtxt)
    {
        switch (printMethod)
        {
        case Screen:
            std::cout << logtxt << std::endl;
            break;
        case Onefile:
            printOneFile(LogFile, logtxt);
            break;
        case Classfile:
            printClassFile(level, logtxt);
            break;
        default:
            break;
        }
    }
    void printOneFile(const std::string &logname, const std::string &logtxt)
    {
        std::string _logname = path + logname;
        int fd = open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666); // "log.txt"
        if (fd < 0)
            return;
        write(fd, logtxt.c_str(), logtxt.size());
        close(fd);
    }
    void printClassFile(int level, const std::string &logtxt)
    {
        std::string filename = LogFile;
        filename += ".";
        filename += levelToString(level); // "log.txt.Debug/Warning/Fatal"
        printOneFile(filename, logtxt);
    }

    ~Log()
    {
    }
    void operator()(int level, const char *format, ...)
    {
        time_t t = time(nullptr);
        struct tm *ctime = localtime(&t);
        char leftbuffer[SIZE];
        snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),
                 ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,
                 ctime->tm_hour, ctime->tm_min, ctime->tm_sec);

        va_list s;
        va_start(s, format);
        char rightbuffer[SIZE];
        vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);
        va_end(s);

        // 格式:默认部分+自定义部分
        char logtxt[SIZE * 2];
        snprintf(logtxt, sizeof(logtxt), "%s %s", leftbuffer, rightbuffer);

        // printf("%s", logtxt); // 暂时打印
        printLog(level, logtxt);
    }

private:
    int printMethod;
    std::string path;
};

Log lg;

const std::string wwwroot="./wwwroot"; // web 根目录
const std::string sep = "\r\n";
const std::string homepage = "index.html";

static const int defaultport = 8082;

class HttpServer;

class ThreadData
{
public:
    ThreadData(int fd, HttpServer *s) : sockfd(fd), svr(s)
    {
    }

public:
    int sockfd;
    HttpServer *svr;
};

class HttpRequest
{
public:
    void Deserialize(std::string req)
    {
        while(true)
        {
            std::size_t pos = req.find(sep);
            if(pos == std::string::npos) break;
            std::string temp = req.substr(0, pos);
            if(temp.empty()) break;
            req_header.push_back(temp);
            req.erase(0, pos+sep.size());
        }
        text = req;
    }
    // .png:image/png
    void Parse()
    {
        std::stringstream ss(req_header[0]);
        ss >> method >> url >> http_version;
        file_path = wwwroot; // ./wwwroot
        if(url == "/" || url == "/index.html") {
            file_path += "/";
            file_path += homepage; // ./wwwroot/index.html
        }
        else file_path += url; // /a/b/c/d.html->./wwwroot/a/b/c/d.html

        auto pos = file_path.rfind(".");
        if(pos == std::string::npos) suffix = ".html";
        else suffix = file_path.substr(pos);
    }
    void DebugPrint()
    {
        for(auto &line : req_header)
        {
            std::cout << "--------------------------------" << std::endl;
            std::cout << line << "\n\n";
        }

        std::cout << "method: " << method << std::endl;
        std::cout << "url: " << url << std::endl;
        std::cout << "http_version: " << http_version << std::endl;
        std::cout << "file_path: " << file_path << std::endl;
        std::cout << text << std::endl;
    }
public:
    std::vector<std::string> req_header;
    std::string text;

    // 解析之后的结果
    std::string method;
    std::string url;
    std::string http_version;
    std::string file_path; // ./wwwroot/a/b/c.html 2.png

    std::string suffix;
};

class HttpServer
{
public:
    HttpServer(uint16_t port = defaultport) : port_(port)
    {
        content_type.insert({".html", "text/html"});
        content_type.insert({".png", "image/png"});
    }
    bool Start()
    {
        listensock_.Socket();
        listensock_.Bind(port_);
        listensock_.Listen();
        for (;;)
        {
            std::string clientip;
            uint16_t clientport;
            int sockfd = listensock_.Accept(&clientip, &clientport);
            if (sockfd < 0)
                continue;
            lg(Info, "get a new connect, sockfd: %d", sockfd);
            pthread_t tid;
            ThreadData *td = new ThreadData(sockfd, this);
            pthread_create(&tid, nullptr, ThreadRun, td);
        }
    }
    static std::string ReadHtmlContent(const std::string &htmlpath)
    {
        // 坑
        std::ifstream in(htmlpath, std::ios::binary);
        if(!in.is_open()) return "";

        in.seekg(0, std::ios_base::end);
        auto len = in.tellg();
        in.seekg(0, std::ios_base::beg);

        std::string content;
        content.resize(len);

        in.read((char*)content.c_str(), content.size());
        //std::string content;
        //std::string line;
        //while(std::getline(in, line))
        //{
        //    content += line;
        //}

        in.close();

        return content;
    }
    std::string SuffixToDesc(const std::string &suffix)
    {
        auto iter = content_type.find(suffix);
        if(iter == content_type.end()) return content_type[".html"];
        else return content_type[suffix];
    }
    void HandlerHttp(int sockfd)
    {
        char buffer[10240];
        ssize_t n = recv(sockfd, buffer, sizeof(buffer) - 1, 0); // bug
        if (n > 0)
        {
            buffer[n] = 0;
            std::cout << buffer << std::endl; // 假设我们读取到的就是一个完整的,独立的http 请求
            HttpRequest req;
            req.Deserialize(buffer);
            req.Parse();
            //req.DebugPrint();

            //std::string path = wwwroot;
            //path += url; // wwwroot/a/a/b/index.html

            // 返回响应的过程
            std::string text;
            bool ok = true;
            text = ReadHtmlContent(req.file_path); // 失败?
            if(text.empty())
            {
                ok = false;
                std::string err_html = wwwroot;
                err_html += "/";
                err_html += "err.html";
                text = ReadHtmlContent(err_html);
            }

            std::string response_line;
            if(ok)
                response_line = "HTTP/1.0 200 OK\r\n";
            else
                response_line = "HTTP/1.0 404 Not Found\r\n";
            
            //response_line = "HTTP/1.0 302 Found\r\n";
            std::string response_header = "Content-Length: ";
            response_header += std::to_string(text.size()); // Content-Length: 11
            response_header += "\r\n";
            response_header += "Content-Type: ";
            response_header += SuffixToDesc(req.suffix);
            response_header += "\r\n";
            response_header += "Set-Cookie: name=haha&&passwd=12345";
            response_header += "\r\n";

            //response_header += "Location: https://www.qq.com\r\n";
            std::string blank_line = "\r\n"; // \n

            std::string response = response_line;
            response += response_header;
            response += blank_line;
            response += text;

            send(sockfd, response.c_str(), response.size(), 0);
        }
        close(sockfd);
    }
    static void *ThreadRun(void *args)
    {
        pthread_detach(pthread_self());
        ThreadData *td = static_cast<ThreadData *>(args);
        td->svr->HandlerHttp(td->sockfd);
        delete td;
        return nullptr;
    }
    ~HttpServer()
    {
    }

private:
    Sock listensock_;
    uint16_t port_;
    std::unordered_map<std::string, std::string> content_type;
};



using namespace std;

int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        exit(1);
    }
    
    uint16_t port = std::stoi(argv[1]);
    // HttpServer *svr = new HttpServer();
    // std::unique<HttpServer> svr(new HttpServer());
    std::unique_ptr<HttpServer> svr(new HttpServer(port));
    svr->Start();
    return 0;
}

7. 总结
  • HTTP请求由请求行、头、空行、体构成,方法决定操作类型。

  • HTTP响应包含状态行、头、空行、体,状态码反映处理结果。

  • 掌握工具和代码实践,能快速调试和开发Web应用。

进阶学习

相关推荐
时差freebright1 小时前
【Linux系统】线程:线程的优点 / 缺点 / 超线程技术 / 异常 / 用途
linux·运维·服务器
努力的小T1 小时前
在Linux上部署Jenkins的详细指南
linux·运维·服务器·云计算·jenkins
网络安全King1 小时前
网络安全风险量化值 网络安全风险控制
网络·安全·web安全
致奋斗的我们1 小时前
rsync增量同步
linux·运维·服务器·网络·shell·rsync·openeurler
vortex52 小时前
网络安全威胁框架与入侵分析模型概述
网络·安全·web安全·网络安全·渗透测试
烛.照1032 小时前
使用java代码操作rabbitMQ收发消息
java·linux·rabbitmq
m0_739675763 小时前
判断192.168.1.0/24网络中,当前在线的ip有哪些
网络·tcp/ip·php
'tubug'3 小时前
Fiddler Classic(HTTP流量代理+半汉化)
前端·http·网络安全·fiddler