政府网站建设情况调研报告/网店推广培训

政府网站建设情况调研报告,网店推广培训,wordpress图文列表插件,济南做外贸的网站公司吗应用层协议 HTTP 一. HTTP 协议1. URL 地址2. urlencode 和 urldecode3. 请求与响应格式 二. HTTP 请求方法1. GET 和 POST (重点) 三. HTTP 状态码四. HTTP 常见报头五. 手写 HTTP 服务器 HTTP(超文本传输协议)是一种应用层协议,用于在万维网…

应用层协议 HTTP

  • 一. HTTP 协议
    • 1. URL 地址
    • 2. urlencode 和 urldecode
    • 3. 请求与响应格式
  • 二. HTTP 请求方法
    • 1. GET 和 POST (重点)
  • 三. HTTP 状态码
  • 四. HTTP 常见报头
  • 五. 手写 HTTP 服务器

HTTP(超文本传输协议)是一种应用层协议,用于在万维网上进行超文本传输。它是现代互联网的基础协议之一,主要用于浏览器和服务器之间的通信,用于请求和响应网页内容。HTTP协议是无连接的、无状态的,基于请求-响应模型。

  • 无连接:客户端和服务器之间不需要建立长期的连接,每个请求/响应对完成后,连接即被关闭。
  • 无状态:请求/响应对都是独立的,服务器不会保存客户端请求之间的任何状态信息。

一. HTTP 协议

1. URL 地址

平时我们俗称的 “网址” 其实就是说的 URL(Uniform Resource Locator),“统一资源定位符”

例如:https://news.qq.com/rain/a/20250326A01C0V00

  • news.qq.com:域名,公网 IP 地址。
  • rain/a/20250326A01C0V00:服务器路径下的文件(html、css、js)

前置知识:

  1. 我的数据给别人,别人的数据给我,就是 IO 操作,也就是说:上网的行为就是 IO
  2. 请求的资源:图片,视频,音频,文本,本质就是文件。
  3. 先要确认我要的资源在那一台服务器上(IP 地址),在什么路径下(文件路径)
  4. URL 中的 “/” 不一定是根目录,它是 Web 根目录,二者不一样。
  5. 为什么没有端口号?在成熟的应用层协议中,默认存在固定的端口号,HTTP 的默认端口号是80

2. urlencode 和 urldecode

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

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

在这里插入图片描述

3. 请求与响应格式

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 请求方法

方法说明支持的 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
UNLINK断开链接关系1.0

GET 和 POST 是 HTTP 协议中最常用的两种请求方法,用于客户端与服务器之间的数据交互。

1. GET 和 POST (重点)

特性GETPOST
用途用于请求 URL 指定的资源提交数据到服务器
数据位置参数附加在 URL 中参数放在请求体(Body)中
数据可见性URL 中明文显示,不安全数据不可见,相对安全
数据长度限制受限于 URL 长度(通常 ≤ 2048 字节)无限制(理论上)
常见场景搜索、浏览页面、获取 API 数据表单提交、上传文件、用户登录

在这里插入图片描述

  • GET 的参数:通过 ? 附加在 URL 后,多个参数用 & 分隔!
  • 浏览器默认使用 GET 发起请求(例如:直接输入 URL 或点击链接)
  • HTTP 协议本身是明文传输的,无论是 GET 还是 POST 方法,数据在网络中传输时都可能被抓包,需要 HTTPS 协议对数据进行加密!

三. HTTP 状态码

状态码类别说明
1XXInformational(信息性状态码)接收的请求正在处理
2XXSuccess(成功状态码)请求正常处理方式
3XXRedirection(重定向状态码)需要进行附加操作以完成请求
4XXClient Error(客户端错误状态码)服务器无法处理请求
5XXServer Error(服务器错误状态码)服务器处理错误请求

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

状态码状态码描述应用样例
100Continue上传大文件时,服务器告诉客户端可以继续上传
200OK访问网站首页,服务器返回网页内容
201Created发布新文章,服务器返回文章创建成功的信息
204No Content删除文章后,服务器返回“无内容”表示操作成功
301Moved Permanently网站换域名后,自动跳转到新域名;搜索引擎更新网站链接时使用
302Found 或 See Other用户登录成功后,重定向到用户首页
304Not Modified浏览器缓存机制,对未修改的资源返回 304 状态码
400Bad Request填写表单时,格式不正确导致提交失败
401Unauthorized访问需要登录的页面时,未登录或认证失败
403Forbidden尝试访问你没有权限查看的页面
404Not Found访问不存在的网页链接
500Internal Server Error服务器崩溃或数据库错误导致页面无法加载
502Bad Gateway使用代理服务器时,代理服务器无法从上游服务器获取有效响应
503Service Unavailable服务器维护或过载,暂时无法处理请求

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

状态码状态码描述重定向类型应用样例
301Moved Permanently永久重定向网站换域名后,自动跳转到新域名;搜索引擎更新网站链接时使用
302Found 或 See Other临时重定向用户登录成功后,重定向到用户首页
307Temporary Redirect临时重定向临时重定向资源到新的位置(较少使用)
308Permanent Redirect永久重定向永久重定向资源到新的位置(较少使用)
  • 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 响应中,可能会看到类似于以下的头部信息:
HTTP/1.1 302 Found\r\n
Location: https://www.new-url.com\r\n

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

  • 爬虫原理:模拟浏览器向目标网站发送 HTTP/HTTPS 请求,获取服务器返回的 HTML/XML 页面内容,从当前页面提取所有 URL,加入待爬队列(避免重复抓取,通过 URL 去重),将提取的数据存入数据库/文件/内存中。
  • 搜索引擎:核心功能是从互联网上获取信息并为用户提供精准的搜索结果,而这一过程的基础正是爬虫能力

四. HTTP 常见报头

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

五. 手写 HTTP 服务器

  1. Makefile
httpserver:HttpServer.ccg++ -o $@ $^ -std=c++17.PHONY:clean
clean:rm -rf httpserver
  1. Mutex.hpp
#pragma once#include <pthread.h>namespace MutexModule
{class Mutex{Mutex(const Mutex &m) = delete;const Mutex &operator=(const Mutex &m) = delete;public:Mutex(){::pthread_mutex_init(&_mutex, nullptr);}~Mutex(){::pthread_mutex_destroy(&_mutex);}void Lock(){::pthread_mutex_lock(&_mutex);}void Unlock(){::pthread_mutex_unlock(&_mutex);}pthread_mutex_t *LockAddr() { return &_mutex; }private:pthread_mutex_t _mutex;};class LockGuard{public:LockGuard(Mutex &mutex): _mutex(mutex){_mutex.Lock();}~LockGuard(){_mutex.Unlock();}private:Mutex &_mutex; // 使用引用: 互斥锁不支持拷贝};
}
  1. Socket.hpp
#pragma once#include <iostream>
#include <string>
#include <unistd.h>
#include <cstring>
#include <cstdlib>#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>#include "Log.hpp"
#include "Common.hpp"
#include "InetAddr.hpp"using namespace LogModule;const int gdefaultsockfd = -1;
const int gbacklog = 8;namespace SocketModule
{class Socket;using SockPtr = std::shared_ptr<Socket>;// 模版方法模式// 基类: 规定创建Socket方法class Socket{public:virtual ~Socket() = default;virtual void SocketOrDie() = 0;virtual void SetSocketOpt() = 0;virtual bool BindOrDie(int port) = 0;virtual bool ListenOrDie() = 0;virtual SockPtr AcceptOrDie(InetAddr *client) = 0;virtual void Close() = 0;virtual int Recv(std::string *out) = 0;virtual int Send(const std::string &in) = 0;virtual int Fd() = 0;// 提供创建TCP套接字的固定格式void BuildTcpSocketMethod(int port){SocketOrDie();SetSocketOpt();BindOrDie(port);ListenOrDie();}};class TcpSocket : public Socket{public:TcpSocket(int sockfd = gdefaultsockfd): _sockfd(sockfd){}virtual ~TcpSocket() {}virtual void SocketOrDie() override{_sockfd = ::socket(AF_INET, SOCK_STREAM, 0);if (_sockfd < 0){LOG(LogLevel::DEBUG) << "socket error";exit(SOCKET_ERR);}LOG(LogLevel::DEBUG) << "socket success, sockfd: " << _sockfd;}virtual void SetSocketOpt() override{// 保证服务器在异常断开之后可以立即重启, 不会存在bind error问题!int opt = 1;::setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));}virtual bool BindOrDie(int port) override{if (_sockfd == gdefaultsockfd)return false;InetAddr addr(port);int n = ::bind(_sockfd, addr.NetAddr(), addr.NetAddrLen());if (n < 0){LOG(LogLevel::DEBUG) << "bind error";exit(BIND_ERR);}LOG(LogLevel::DEBUG) << "bind success, sockfd: " << _sockfd;return true;}virtual bool ListenOrDie() override{if (_sockfd == gdefaultsockfd)return false;int n = ::listen(_sockfd, gbacklog);if (n < 0){LOG(LogLevel::DEBUG) << "listen error";exit(LISTEN_ERR);}LOG(LogLevel::DEBUG) << "listen success, sockfd: " << _sockfd;return true;}// 返回: 文件描述符 && 客户端信息virtual SockPtr AcceptOrDie(InetAddr *client) override{struct sockaddr_in peer;socklen_t len = sizeof(peer);int newsockfd = ::accept(_sockfd, CONV(&peer), &len);if (newsockfd < 0){LOG(LogLevel::DEBUG) << "accept error";return nullptr;}client->SetAddr(peer);return std::make_shared<TcpSocket>(newsockfd);}virtual void Close() override{if (_sockfd == gdefaultsockfd)return;::close(_sockfd);}virtual int Recv(std::string *out) override{char buffer[1024 * 8];int n = ::recv(_sockfd, buffer, sizeof(buffer) - 1, 0);if(n > 0){buffer[n] = 0;*out = buffer;}return n;}virtual int Send(const std::string &in) override{int n = ::send(_sockfd, in.c_str(), in.size(), 0);return n;}virtual int Fd() override{return _sockfd;}private:int _sockfd;};
}
  1. Log.hpp
#pragma once#include <iostream>
#include <cstdio>
#include <string>
#include <filesystem>
#include <fstream>
#include <sstream>
#include <memory>
#include <unistd.h>
#include <time.h>
#include "Mutex.hpp"namespace LogModule
{using namespace MutexModule;// 获取系统时间std::string CurrentTime(){time_t time_stamp = ::time(nullptr); // 获取时间戳struct tm curr;localtime_r(&time_stamp, &curr); // 将时间戳转化为可读性强的信息char buffer[1024];snprintf(buffer, sizeof(buffer), "%4d-%02d-%02d %02d:%02d:%02d",curr.tm_year + 1900,curr.tm_mon + 1,curr.tm_mday,curr.tm_hour,curr.tm_min,curr.tm_sec);return buffer;}// 日志文件: 默认路径和默认文件名const std::string defaultlogpath = "./log/";const std::string defaultlogname = "log.txt";// 日志等级enum class LogLevel{DEBUG = 1,INFO,WARNING,ERROR,FATAL};std::string Level2String(LogLevel level){switch (level){case LogLevel::DEBUG:return "DEBUG";case LogLevel::INFO:return "INFO";case LogLevel::WARNING:return "WARNING";case LogLevel::ERROR:return "ERROR";case LogLevel::FATAL:return "FATAL";default:return "NONE";}}// 3. 策略模式: 刷新策略class LogStrategy{public:virtual ~LogStrategy() = default;// 纯虚函数: 无法实例化对象, 派生类可以重载该函数, 实现不同的刷新方式virtual void SyncLog(const std::string &message) = 0;};// 3.1 控制台策略class ConsoleLogStrategy : public LogStrategy{public:ConsoleLogStrategy() {}~ConsoleLogStrategy() {}void SyncLog(const std::string &message) override{LockGuard lockguard(_mutex);std::cout << message << std::endl;}private:Mutex _mutex;};// 3.2 文件级(磁盘)策略class FileLogStrategy : public LogStrategy{public:FileLogStrategy(const std::string &logpath = defaultlogpath, const std::string &logname = defaultlogname): _logpath(logpath), _logname(logname){// 判断_logpath目录是否存在if (std::filesystem::exists(_logpath)){return;}try{std::filesystem::create_directories(_logpath);}catch (std::filesystem::filesystem_error &e){std::cerr << e.what() << std::endl;}}~FileLogStrategy() {}void SyncLog(const std::string &message) override{LockGuard lockguard(_mutex);std::string log = _logpath + _logname;std::ofstream out(log, std::ios::app); // 以追加的方式打开文件if (!out.is_open()){return;}out << message << "\n"; // 将信息刷新到out流中out.close();}private:std::string _logpath;std::string _logname;Mutex _mutex;};// 4. 日志类: 构建日志字符串, 根据策略进行刷新class Logger{public:Logger(){// 默认往控制台上刷新_strategy = std::make_shared<ConsoleLogStrategy>();}~Logger() {}void EnableConsoleLog(){_strategy = std::make_shared<ConsoleLogStrategy>();}void EnableFileLog(){_strategy = std::make_shared<FileLogStrategy>();}// 内部类: 记录完整的日志信息class LogMessage{public:LogMessage(LogLevel level, const std::string &filename, int line, Logger &logger): _currtime(CurrentTime()), _level(level), _pid(::getpid()), _filename(filename), _line(line), _logger(logger){std::stringstream ssbuffer;ssbuffer << "[" << _currtime << "] "<< "[" << Level2String(_level) << "] "<< "[" << _pid << "] "<< "[" << _filename << "] "<< "[" << _line << "] - ";_loginfo = ssbuffer.str();}~LogMessage(){if(_logger._strategy){_logger._strategy->SyncLog(_loginfo);}}template <class T>LogMessage &operator<<(const T &info){std::stringstream ssbuffer;ssbuffer << info;_loginfo += ssbuffer.str();return *this;}private:std::string _currtime;  // 当前日志时间LogLevel _level;       // 日志水平pid_t _pid;            // 进程pidstd::string _filename; // 文件名uint32_t _line;        // 日志行号Logger &_logger;       // 负责根据不同的策略进行刷新std::string _loginfo;  // 日志信息};// 故意拷贝, 形成LogMessage临时对象, 后续在被<<时,会被持续引用,// 直到完成输入,才会自动析构临时LogMessage, 至此完成了日志的刷新,// 同时形成的临时对象内包含独立日志数据, 未来采用宏替换, 获取文件名和代码行数LogMessage operator()(LogLevel level, const std::string &filename, int line){return LogMessage(level, filename, line, *this);}private:// 纯虚类不能实例化对象, 但是可以定义指针std::shared_ptr<LogStrategy> _strategy; // 日志刷新策略方案};// 定义全局logger对象Logger logger;// 编译时进行宏替换: 方便随时获取行号和文件名
#define LOG(level) logger(level, __FILE__, __LINE__)// 提供选择使用何种日志策略的方法
#define ENABLE_CONSOLE_LOG() logger.EnableConsoleLog()
#define ENABLE_FILE_LOG() logger.EnableFileLog()
}
  1. Common.hpp
#pragma once#include <iostream>
#include <string>#define Die(code)   \do              \{               \exit(code); \} while (0)#define CONV(v) (struct sockaddr *)(v)enum
{USAGE_ERR = 1,SOCKET_ERR,BIND_ERR,LISTEN_ERR
};bool ParseOneLine(std::string &str, std::string *out, const std::string &sep)
{auto pos = str.find(sep);if (pos == std::string::npos)return false;*out = str.substr(0, pos);str.erase(0, pos + sep.size());return true;
}// Connection: keep-alive
// 解析后: key = Connection; value = keep-alive
bool SplitString(const std::string &header, const std::string sep, std::string *key, std::string *value)
{auto pos = header.find(sep);if (pos == std::string::npos)return false;*key = header.substr(0, pos);*value = header.substr(pos + sep.size());return true;
}
  1. Deamon.hpp
#pragma once#include <iostream>
#include <cstdlib>
#include <signal.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>#define ROOT "/"
#define devnull "/dev/null"void Deamon(bool ischdir, bool isclose)
{// 1. 守护进程一般要屏蔽一些特定的信号signal(SIGCHLD, SIG_IGN);signal(SIGPIPE, SIG_IGN);// 2. 成为非组长进程: 创建子进程if (fork())exit(0);// 3. 建立新会话setsid();// 4. 每一个进程都有自己的CWD, 是否将其修改为根目录if (ischdir)chdir(ROOT);// 5. 脱离终端: 将标准输入、输出重定向到字符文件"/dev/null"中if (isclose){::close(0);::close(1);::close(2);}else{// 建议这样!int fd = ::open(devnull, O_WRONLY);if (fd > 0){::dup2(fd, 0);::dup2(fd, 1);::dup2(fd, 2);::close(fd);}}
}
  1. InetAddr.hpp
#pragma once#include <iostream>
#include <string>#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>#include "Common.hpp"class InetAddr
{
private:// 端口号: 网络序列->主机序列void PortNetToHost(){_port = ::ntohs(_net_addr.sin_port);}// IP: 网络序列->主机序列void IpNetToHost(){char ipbuffer[64];::inet_ntop(AF_INET, &_net_addr.sin_addr, ipbuffer, sizeof(ipbuffer));_ip = ipbuffer;}public:InetAddr() {}InetAddr(const struct sockaddr_in &addr): _net_addr(addr){PortNetToHost();IpNetToHost();}InetAddr(uint16_t port): _port(port), _ip(""){_net_addr.sin_family = AF_INET;_net_addr.sin_port = ::htons(_port);_net_addr.sin_addr.s_addr = INADDR_ANY;}~InetAddr() {}bool operator==(const InetAddr &addr) { return _ip == addr._ip && _port == addr._port; }struct sockaddr *NetAddr() { return CONV(&_net_addr); }socklen_t NetAddrLen() { return sizeof(_net_addr); }std::string Ip() { return _ip; }uint16_t Port() { return _port; }std::string Addr() { return Ip() + ":" + std::to_string(Port()); }void SetAddr(sockaddr_in &client){_net_addr = client;PortNetToHost();IpNetToHost();}private:struct sockaddr_in _net_addr;std::string _ip; // 主机序列: IPuint16_t _port;  // 主机序列: 端口号
};
  1. TcpServer.hpp
#pragma once#include <iostream>
#include <string>
#include <memory>
#include <functional>
#include <sys/wait.h>#include "Socket.hpp"
#include "InetAddr.hpp"using namespace SocketModule;
using namespace LogModule;using tcphandler_t = std::function<bool(SockPtr, InetAddr)>;namespace TcpServerModule
{class TcpServer{public:TcpServer(int port): _listensockp(std::make_unique<TcpSocket>()), _isrunning(false), _port(port){}~TcpServer(){_listensockp->Close();}void InitServer(tcphandler_t handler){_listensockp->BuildTcpSocketMethod(_port);_handler = handler;}void Loop(){_isrunning = true;while (_isrunning){// 1. 获取连接: 获取网络通信sockfd && 客户端的InetAddr clientaddr;auto sockfd = _listensockp->AcceptOrDie(&clientaddr);if (sockfd == nullptr)continue;LOG(LogLevel::DEBUG) << "get a new client info is: " << clientaddr.Addr();// 2. IO处理pid_t id = fork();if (id == 0){// 子进程关闭listensockfd_listensockp->Close();if (fork() > 0)exit(0); // 子进程直接退出// 孙子进程进行IO处理_handler(sockfd, clientaddr);exit(0);}// 父进程关闭sockfdsockfd->Close();waitpid(id, nullptr, 0); // 子进程直接退出, 父进程无需阻塞等待}_isrunning = false;}private:std::unique_ptr<Socket> _listensockp;bool _isrunning;tcphandler_t _handler;int _port;};
}
  1. HttpProtocol.hpp
#pragma once#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
#include <sstream>
#include <fstream>#include "Common.hpp"const std::string Sep = "\r\n";
const std::string LineSep = " ";
const std::string HeaderLineSep = ": ";
const std::string BlankLine = "\r\n";const std::string default_home_path = "wwwroot"; // 浏览器的请求的默认服务器路径
const std::string http_version = "HTTP/1.0";     // http的版本
const std::string page_404 = "wwwroot/404.html"; // 404页面
const std::string first_page = "index.html";     // 首页// 浏览器/服务器模式(B/S): 浏览器充当客户端, 发送请求; 输入: 123.60.170.90:8080
class HttpRequset
{
public:HttpRequset() {}~HttpRequset() {}// 浏览器具有自动识别http请求的能力, 可以充当客户端// 浏览器发送的http请求(序列化数据)如下:// GET /favicon.ico HTTP/1.1// Host: 123.60.170.90:8080// Connection: keep-alive// User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36 Edg/134.0.0.0// Accept: image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8// Referer: http://123.60.170.90:8080/// Accept-Encoding: gzip, deflate// Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6void ParseReqHeaderKV(){std::string key, value;for (auto &header : _req_header){if (SplitString(header, HeaderLineSep, &key, &value)){_header_kv.insert(std::make_pair(key, value));}}}void ParseReqHeader(std::string &requset){std::string line;while (true){bool ret = ParseOneLine(requset, &line, Sep);if (ret && !line.empty()){_req_header.push_back(line);}else{break;}}// 提取请求报头每一行ParseReqHeaderKV();}// 解析请求行中详细的字段// GET /index.html HTTP/1.1void ParseReqLine(std::string &_req_line, const std::string &sep){std::stringstream ss(_req_line);ss >> _req_method >> _uri >> _http_version;}// 对http请求进行反序列化void Deserialize(std::string &requset){// 提取请求行if (ParseOneLine(requset, &_req_line, Sep)){// 提取请求行中的详细字段ParseReqLine(_req_line, LineSep);// 提取请求报文ParseReqHeader(requset);_blank_line = Sep;_req_body = requset;// 分析请求中是否含有参数if (_req_method == "POST") // 默认POST带参数{// 参数在正文_req_body部分: name=zhangsan&password=123456_isexec = true;_args = _req_body;_path = _uri;}else if (_req_method == "GET"){// 参数在URI中: login?name=zhangsan&password=123456auto pos = _uri.find("?"); if (pos != std::string::npos) // 存在?带参数{_isexec = true;_path = _uri.substr(0, pos);_args = _uri.substr(pos + 1);}else // 不存在?不带参数{_isexec = false;}}}}// 返回请求的资源: uristd::string GetContent(const std::string &path){// 既支持文本文件, 又支持二进制图片std::string content;std::ifstream in(path, std::ios::binary);if (!in.is_open())return std::string();in.seekg(0, in.end);int filesize = in.tellg();in.seekg(0, in.beg);content.resize(filesize);in.read((char *)content.c_str(), filesize);in.close();return content;// 只支持读取文本文件, 不支持二进制图片// std::string content;// std::ifstream in(path);// if (!in.is_open())//     return std::string();// std::string line;// while (std::getline(in, line))// {//     content += line;// }// return content;}// 获取资源的文件后缀std::string Suffix(){// _uri -> wwwroot/index.html wwwroot/image/1.jpgauto pos = _uri.rfind(".");if (pos == std::string::npos)return std::string(".html");elsereturn _uri.substr(pos);}std::string Uri() { return _uri; }void SetUri(const std::string newuri) { _uri = newuri; }std::string Path() { return _path; }std::string Args() { return _args; }bool IsHasArgs() { return _isexec; }void Print(){std::cout << "请求行详细字段: " << std::endl;std::cout << "_req_method: " << _req_method << std::endl;std::cout << "_uri: " << _uri << std::endl;std::cout << "_http_version: " << _http_version << std::endl;std::cout << "请求报头: " << std::endl;for (auto &kv : _header_kv){std::cout << kv.first << " # " << kv.second << std::endl;}std::cout << "空行: " << std::endl;std::cout << "_blank_line: " << _blank_line << std::endl;std::cout << "请求正文: " << std::endl;std::cout << "_body: " << _req_body << std::endl;}private:std::string _req_line;                                   // 请求行std::vector<std::string> _req_header;                    // 请求报头std::unordered_map<std::string, std::string> _header_kv; // 请求报头的KV结构std::string _blank_line;                                 // 空行std::string _req_body;                                   // 请求正文: 内部可能会包含参数(POST请求)// 请求行中详细的字段std::string _req_method;   // 请求方法std::string _uri;          // 用户想要的资源路径: 内部可能会包含参数(GET请求) /login.hmtl  |  /login?xxx&yyystd::string _http_version; // http版本// 关于请求传参GET/POST相关的结构std::string _path;    // 路径std::string _args;    // 参数bool _isexec = false; // 执行动态方法
};// 对于http, 任何请求都要有应答
class HttpResponse
{
public:HttpResponse() {}~HttpResponse() {}// 通过requset结构体, 构建response结构体void Build(HttpRequset &req){// 当用户输入:// 123.60.170.90:8080/      -> 默认访问 wwwroot/index.html// 123.60.170.90:8080/a/b/  -> 默认访问 wwwroot/a/b/index.htmlstd::string uri = default_home_path + req.Uri(); // wwwroot/if (uri.back() == '/'){uri += first_page; // wwwroot/index.htmlreq.SetUri(uri);}// 获取用户请求的资源_content = req.GetContent(uri);if (_content.empty()){_status_code = 404; // 用户请求的资源不存在!req.SetUri(page_404);_content = req.GetContent(page_404); // 注意: 需要读取404页面}else{_status_code = 200; // 用户请求的资源存在!}_status_code_desc = CodeToDesc(_status_code);_resp_body = _content;// 设置响应报头SetHeader("Content-Length", std::to_string(_content.size()));std::string mime_type = SuffixToDesc(req.Suffix());SetHeader("Content-Type", mime_type);}// 设置响应报头的KV结构void SetHeader(const std::string &k, const std::string &v){_header_kv[k] = v;}void SetCode(int code){_status_code = code;_status_code_desc = CodeToDesc(_status_code);}   void SetBody(const std::string &body){_resp_body = body;}// 对http响应序列化void Serialize(std::string *response){// 1. 求各个字段for (auto &header : _header_kv){_resp_header.push_back(header.first + HeaderLineSep + header.second);}_http_version = http_version;_resp_line = _http_version + LineSep + std::to_string(_status_code) + LineSep + _status_code_desc + Sep;_blank_line = BlankLine;// 2. 开始序列化: 各个字段相加*response = _resp_line;for (auto &line : _resp_header){*response += (line + Sep);}*response += _blank_line;*response += _resp_body;}private:// 将 状态码 转化为 状态码描述std::string CodeToDesc(int code){switch (code){case 200:return "OK";case 404:return "Not Found";default:return std::string();}}// 将 文件后缀 转化为 文件类型std::string SuffixToDesc(const std::string &suffix){if (suffix == ".html")return "text/html";else if (suffix == ".jpg")return "application/x-jpg";elsereturn "text/html";}private:std::string _resp_line;                                  // 响应行std::vector<std::string> _resp_header;                   // 响应报头std::unordered_map<std::string, std::string> _header_kv; // 响应报头的KV结构std::string _blank_line;                                 // 空行std::string _resp_body;                                  // 响应正文// 响应行中详细的字段std::string _http_version;     // http版本int _status_code;              // 状态码std::string _status_code_desc; // 状态码描述std::string _content;          // 返回给用户的内容: 响应正文
};
  1. HttpServer.hpp
#pragma once#include <iostream>
#include <string>
#include <memory>
#include <functional>
#include <unordered_map>#include "TcpServer.hpp"
#include "HttpProtocol.hpp"using namespace TcpServerModule;using http_handler_t = std::function<void(HttpRequset &, HttpResponse &)>;class HttpServer
{
public:HttpServer(int port): _tsvr(std::make_unique<TcpServer>(port)){}~HttpServer() {}void Register(std::string funcname, http_handler_t func){_route[funcname] = func;}void Start(){_tsvr->InitServer([this](SockPtr sockfd, InetAddr client){ return this->HanlerRequset(sockfd, client); });_tsvr->Loop();}bool SafeCheck(const std::string &service){auto iter = _route.find(service);return iter != _route.end();}bool HanlerRequset(SockPtr sockfd, InetAddr client){LOG(LogLevel::DEBUG) << "HttpServer: get a new client: " << sockfd->Fd() << " addr info: " << client.Addr();// 1. 读取浏览器发送的http请求std::string http_requset;sockfd->Recv(&http_requset);// 2. 请求反序列化HttpRequset req;req.Deserialize(http_requset);// 3. 根据请求构建响应HttpResponse resp;if (req.IsHasArgs()) // 动态交互请求(含有参数): 登入, 注册... {// GET 请求的参数在 URL 中// POST请求的参数在 body中std::string service = req.Path();if(SafeCheck(service)){_route[service](req, resp); // login}else{resp.Build(req);}}else // 请求一般的静态资源(不含参数): 网页, 图片, 视频...{resp.Build(req);}// 4. 响应序列化std::string http_response;resp.Serialize(&http_response);// 5. 发送响应给用户sockfd->Send(http_response);return true;}private:std::unique_ptr<TcpServer> _tsvr;std::unordered_map<std::string, http_handler_t> _route; // 功能路由
};
  1. HttpServer.cc
#include "HttpServer.hpp"
#include "Deamon.hpp"using namespace LogModule;// 登入功能
void Login(HttpRequset &req, HttpResponse &resp)
{// 根据 req 动态构建 resp: // Path: /login// Args: name=zhangsan&password=123456LOG(LogLevel::DEBUG) << "进入登入模块: " << req.Path() << ", " << req.Args();// 1. 解析参数格式, 得到想要的参数std::string req_args = req.Args();// 2. 访问数据库, 验证是否是合法用户// 3. 登入成功// resp.SetCode(302);// resp.SetHeader("Location", "/"); // 登入成功后跳转到首页std::string body = req.GetContent("wwwroot/success.html");resp.SetCode(200);resp.SetHeader("Content-Length", std::to_string(body.size()));resp.SetHeader("Content-Type", "text/html");resp.SetHeader("Set-Cookie", "username=xzy&password=123456");resp.SetBody(body);
}// 注册功能
void Register(HttpRequset &req, HttpResponse &resp)
{LOG(LogLevel::DEBUG) << "进入注册模块: " << req.Path() << ", " << req.Args();
}// 搜索引擎功能
void Search(HttpRequset &req, HttpResponse &resp)
{LOG(LogLevel::DEBUG) << "进入注册模块: " << req.Path() << ", " << req.Args();
}int main(int argc, char *argv[])
{// Deamon(false, false); // 守护进程if (argc != 2){std::cout << "Usage: " << argv[0] << " port" << std::endl;return 1;}int port = std::stoi(argv[1]);std::unique_ptr<HttpServer> httpserver = std::make_unique<HttpServer>(port);// 服务器具有登入成功功能httpserver->Register("/login", Login);httpserver->Register("/register", Register);httpserver->Start();return 0;
}
  1. 前端代码
    点击跳转

  2. 运行操作

# 启动http服务器
xzy@hcss-ecs-b3aa:~$ ./httpserver 8888

浏览器输入:云服务器IP地址:端口号(例如:http://123.60.170.90:8888/)

效果如下:

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【活动回顾】StarRocks Singapore Meetup #2 @Shopee

3 月 13 日&#xff0c;StarRocks 社区在新加坡成功举办了第二场 Meetup 活动&#xff0c;主题为“Empowering Customer-Facing Analytics”。本次活动在 Shopee 新加坡办公室举行&#xff0c;吸引了来自 Shopee、Grab 和 Pinterest 的专家讲师以及 50 多位参会者。大家围绕电商…

Retinexformer:基于 Retinex 的单阶段 Transformer 低光照图像增强方法

开头发点牢骚&#xff1a;本来做的好好都都要中期了&#xff0c;导师怎么突然给我换题目啊。真是绷不住了......又要从头开始学了&#xff0c;唉&#xff01; 原论文链接&#xff1a;Retinexformer: One-stage Retinex-based Transformer for Low-light Image Enhancement 低光…

后端——AOP异步日志

需求分析 在SpringBoot系统中&#xff0c;一般会对访问系统的请求做日志记录的需求&#xff0c;确保系统的安全维护以及查看接口的调用情况&#xff0c;可以使用AOP对controller层的接口进行增强&#xff0c;作日志记录。日志保存在数据库当中&#xff0c;为了避免影响接口的响…

Redis 和 MySQL双写一致性的更新策略有哪些?常见面试题深度解答。

目录 一. 业务数据查询&#xff0c;更新顺序简要分析 二. 更新数据库、查询数据库、更新缓存、查询缓存耗时对比 2.1 更新数据库&#xff08;最慢&#xff09; 2.2 查询数据库&#xff08;较慢&#xff09; 2.3 更新缓存&#xff08;次快&#xff09; 2.4 查询缓存&#…

“征服HTML引号恶魔:“完全解析手册”!!!(quot;表示双引号)

&#x1f6a8;&#x1f4e2; "征服HTML引号恶魔&#xff1a;“完全解析手册” &#x1f4e2;&#x1f6a8; &#x1f3af; 博客引言&#xff1a;当引号变成"恶魔" &#x1f631; 是否遇到过这种情况&#xff1a; 写HTML时满心欢喜输入<div title"他…

npm install 卡在创建项目:sill idealTree buildDeps

参考&#xff1a; https://blog.csdn.net/PengXing_Huang/article/details/136460133 或者再执行 npm install -g cnpm --registryhttps://registry.npm.taobao.org 或者换梯子

Pytorch学习笔记(十二)Learning PyTorch - NLP from Scratch

这篇博客瞄准的是 pytorch 官方教程中 Learning PyTorch 章节的 NLP from Scratch 部分。 官网链接&#xff1a;https://pytorch.org/tutorials/intermediate/nlp_from_scratch_index.html 完整网盘链接: https://pan.baidu.com/s/1L9PVZ-KRDGVER-AJnXOvlQ?pwdaa2m 提取码: …

mysql--socket报错

错误原因分析 MySQL 服务未运行&#xff08;最常见原因&#xff09; 错误中的 (2) 表示 “No such file or directory”&#xff0c;即 /tmp/mysql.sock 不存在这通常意味着 MySQL 服务器根本没有启动 socket 文件路径不匹配 客户端尝试连接 /tmp/mysql.sock但 MySQL 服务器可…

labview加载matlab数据时报错提示:对象引用句柄无效。

1. labview报错提示 labview加载mat数据时报错提示&#xff1a;对象引用句柄无效。返回该引用句柄的节点可能遇到错误&#xff0c;并没有返回有效的引用句柄。该引用句柄所指的存储可能在执行调用之前已关闭。报错提示如下&#xff1a; 这是由于labview缺少matlab MathWorks导…

20250330 Pyflink with Paimon

1. 数据湖 2. 本地安装Pyflink和Paimon 必须安装Python 3.11 Pip install python -m pip install apache-flink1.20.1 需要手动加入这两个jar 测试代码&#xff1a; import argparse import logging import sys import timefrom pyflink.common import Row from pyflink.tab…

-PHP 应用SQL 盲注布尔回显延时判断报错处理增删改查方式

#PHP-MYSQL-SQL 操作 - 增删改查 1 、功能&#xff1a;数据查询(对数据感兴趣&#xff09; 查询&#xff1a; SELECT * FROM news where id$id 2 、功能&#xff1a;新增用户&#xff0c;添加新闻等&#xff08;对操作的结果感兴趣&#xff09; 增加&#xff1a; INSERT INT…

【学习记录】大模型微调之使用 LLaMA-Factory 微调 Qwen系列大模型,可以用自己的数据训练

一、LoRA微调的基本原理 1、基本概念 LoRA&#xff08;Low-Rank Adaptation&#xff09;是一种用于大模型微调的技术&#xff0c;通过引入低秩矩阵来减少微调时的参数量。在预训练的模型中&#xff0c;LoRA通过添加两个小矩阵B和A来近似原始的大矩阵ΔW&#xff0c;从而减少需…

vulntarget_a 训练笔记

win 7 权限 利用任意文件上传 getshell POST /module/ueditor/php/action_upload.php?actionuploadfile HTTP/1.1 User-Agent: Mozilla/5.0 (compatible; Baiduspider/2.0; http://www.baidu.com/search/spider.html) Accept: */* Accept-Language: zh-CN,zh;q0.9 Connectio…

无人机螺旋桨平衡标准

螺旋桨平衡是确保无人机(UAV)平稳运行、可靠性和使用寿命的关键过程。螺旋桨的不平衡会导致振动、噪音&#xff0c;并加速关键部件的磨损&#xff0c;从而对飞行性能产生负面影响。 ISO 21940-11:2016标准为旋翼平衡提供了一个广泛引用的框架&#xff0c;定义了可接受的不平衡…

既生瑜何生亮?Nginx RTMP 模块与 SRS RTMP服务器技术对比

在实时视频流的场景中&#xff0c;RTMP 协议作为一种传统且高效的流媒体传输协议&#xff0c;广泛应用于各类直播和点播系统。两款流行的开源 RTMP 服务器分别是基于 Nginx 的 Nginx RTMP 模块 和 SRS&#xff08;Simple Real-Time Server&#xff09;。这两者都在流媒体行业有…

MATLAB 批量移动 TIF 文件至分类文件夹

文章目录 前言一、步骤二、代码 前言 本代码用于从指定的源文件夹 (sourceFolder) 中筛选所有 .tif 文件&#xff0c;并根据文件名的特定关键词&#xff08;Daynight 和 FDI&#xff09;将其分类移动到相应的目标文件夹 (targetDaynightFolder 和 targetFDIFolder)。 一、步骤…

基于Kubernetes部署Prometheus监控平台

#作者&#xff1a;stackofumbrella 文章目录 prometheus和k8s集群版本对照表架构Prometheus Operator简介kube-prometheus下载地址 安装修改镜像地址修改Prometheus的service修改Grafana的service修改Alertmanager的service数据持久化执行安装 Prometheus验证Grafana验证解决C…

STM32基础教程——输入捕获模式测量PWM频率

目录 前言 技术实现 原理图 连线图 代码实现 内容要点 PWM基本结构 开启外设时钟 配置GPIO端口 配置时基单元 初始化输出比较单元 输出比较通道重映射 输入捕获功能初始化 计算捕获PWM的频率 实验结果 问题记录 前言 IC&#xff08;Input Capture&#xff09;输…

基于网启PXE服务器的批量定制系统平台(详细版)

项目说明 该项目共分为2个子项目&#xff0c;由iventoy和定制安装两部分组成 该项目旨在复习巩固系统服务部署使用、shell编程等知识&#xff0c;旨在让学生增加知识面&#xff0c;提高项目实习经历&#xff0c;充实简历 项目背景&#xff1a; 公司新购了一批服务器和台式机…

旅游CMS选型:WordPress、Joomla与Drupal对比

内容概要 在旅游行业数字化转型进程中&#xff0c;内容管理系统&#xff08;CMS&#xff09;的选择直接影响网站运营效率与用户体验。WordPress、Joomla和Drupal作为全球主流的开源CMS平台&#xff0c;其功能特性与行业适配性存在显著差异。本文将从旅游企业核心需求出发&…