ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

linux网络:Http协议

linux网络:Http协议 一、认识URL解析URI也叫做统一资源定位符http://是协议作用是规定通信的规?开头分割多个参数给资源传入参数筛选、定位资源则http默认端口是80https默认端口是443user:pass是登录认证信息是用户名密码用于登录认证但是可以省略www.example.jp是服务器地址叫做主机域名而浏览器会通过DNS解析将域名解析为ip地址定位到唯一的主机上:80是端口号协议默认端口可以不写作用是定位到主机上的唯一网络服务/dir/index.htm是带层次的文件路径是服务器上资源的文件路径定位主机内部的资源文件/是路径分隔符?uid1是查询字符串URI?开头分割多个参数给资源传入参数筛选、定位资源。#ch1是片段标识符#后面内容定位页面内部某个位置不会发给服务器只在浏览器本地生效。五点核心理解域名→IP人记域名浏览器做 DNS 解析转换成 IP 地址找到网络中的主机。端口号定位服务IP 找到机器端口找到机器上运行的网络服务。浏览器会自动补协议对应的默认端口。文件路径Linux 一切皆文件web 资源本质就是服务器上的文件路径指明访问哪个文件。查询字符串?keyvalkey2val2同一路径下靠不同参数区分不同内容提交参数。#片段锚点页面内跳转不参与网络请求浏览器本地跳转页面位置。总结1.域名 路径定位一台主机上的某个文件资源2.协议 端口确定使用哪种服务来收发数据3.查询字符串进一步筛选资源、传递参数4.所有可以用 URL 访问到的资源属于万维网 WWW 资源。二、urlencode 和 urldecode了解介绍在URL里面的部分字符有特殊含义/ ? : # 这些符号会被浏览器解析为URL结构标记不能够当做普通参数内容举例如果我要传递参数aaa7/9bbb这里的/、在 URL 里有特殊语义如果不编码服务器就分不清哪些是参数分隔符哪些是传入的数据会解析错乱。所以含有特殊字符的参数必须先做urlencode 转义服务器收到后做urldecode 还原。urlencode的编码规则urldecode的解码规则什么时候才会触发编码查询字符串keyvalue的value部分如果包含 / ? # 空格必须编码。总结注意URL 结构符号?本身不要编码只有参数内容编码。坑自己拼接 URL 字符串时如果直接拼接带特殊符号的字符串会产生错误 URL必须手动调用 urlencode。 浏览器会自动对表单、搜索框内容做 urlencode但是我们写代码构造 http 请求需要自己实现或者调用库做编码。三、Http协议的格式在网络中最常见的两个行为(1)从网络中将别人的资源拿下来GET(2)将自己的资源传送到网络上POSThttp请求一共四个部分请求行请求报头空行请求正文http响应一共四个部分状态行响应报头空行响应正文底层本质http1.1是基于TCP的文本协议所有报文本质就是字符串使用了\r\n作为行结束标志HTTP REQUST3.1请求行3.1.1请求方法代表本次客户端想要对资源进行什么操作有GET和POST两种操作GET是获取资源将参数放在URI字符串一般不带请求正文POST是提交资源将数据放在请求正文。3.1.2URL统一资源标识符URL:重点区分URI ≠ 完整 URLURI只写/search?wdC%2B%2B包含资源路径 查询参数不带域名、不带端口。域名 / 端口放在请求头Host字段里面传递给服务器。URL包括协议http主机 (host)job.xjtu.edu.cn资源路径 (_uri)/compony/login.do3.1.3HTTP版本HTTP/1.1最主流支持长连接、Host头部必填。HTTP/1.0短连接请求完成默认断开。HTTP/2、HTTP/3改为二进制帧不再是这种文本报文格式。3.2请求报头每一行的格式必须为key:空格 value\r\n必须严格按照格式来否则服务器就会解析失败每个报头占一行每一行都以\r\n结尾。几个核心报头深度理解Host: job.xjtu.edu.cnHost 告诉服务器我这次访问的是哪一个域名。没有 Host 头部服务器不知道返回哪个网站内容。Connection: keep‑alive开启长连接。TCP 连接这次请求完成之后不关闭可以复用这条 socket 继续发多个 HTTP 请求减少反复建立断开 TCP 的开销。HTTP1.1 默认就是 keep‑alive。 如果值是close代表本次响应完成就要断开 TCP。Content‑Length: 36代表的是请求正文的总长度为36字节服务器在读的时候就知道后面要读取36个字节作为body我们来看一下底部请求正文的长度正好为36个字节。Cache‑Control: max‑age0缓存控制告诉服务器不要用本地缓存而是向服务器获取最新的资源一般登录这种敏感操作才会用这种Origin: http://job.xjtu.edu.cn跨域相关头。标识这个请求来源于哪个源协议 域名 端口浏览器发起跨域请求会带上 Origin服务器用它判断是否允许跨域访问。本次是同域登录Origin 和 Host 一致。Upgrade‑Insecure‑Requests: 1浏览器告诉服务器优先希望升级成 HTTPShttps 加密访问1 代表开启该特性。Content‑Type: application/x‑www‑form‑urlencoded作用是说明请求正文的数据格式application/x‑www‑form‑urlencoded普通表单编码格式格式就是keyvaluekey2value2value 会做 urlencode 编码。这就是我们前面学的 urlencode 对应的 Content‑Type 类型。如果是传 JSON这里是application/json。User‑Agent: Mozilla/5.0 (Windows NT 6.3; Win64; x64) Chrome/61.0.3163.100 Safari/537.36简称 UA。告诉服务器客户端是什么程序Windows 下 Chrome 浏览器版本信息。 服务器可以根据 UA 适配返回不同页面内容。C 写网络爬虫的时候经常伪造 UA否则服务器识别是脚本直接拒绝。Accept:text/html,application/xhtmlxml,application/xml;q0.9,image/webp,image/apng,*/*;q0.8告诉服务器客户端能够接受哪些类型的响应数据text/htmlhtml 网页优先q0.9 q0.8权重系数数值越小优先级越低*/*所有类型都可以接受。Referer: http://job.xjtu.edu.cn/companyLogin.do告诉服务器我是从哪个页面跳转过来发起本次请求。 这里就是登录页面本身。服务器经常用 Referer 做防盗链判断。Accept‑Encoding: gzip, deflate告诉服务器客户端支持接收压缩数据。服务器可以把返回的 html/json 用 gzip 压缩后返回减少网络传输流量客户端收到后解压。 服务器如果支持响应头会多出Content‑Encoding: gzip。Accept‑Language: zh‑CN,zh;q0.8告诉服务器客户端偏好的语言简体中文优先。网站可以根据这个返回对应语言页面。3.3请求正文格式依旧是keyvalue的形式用作为分割参数由于用户名密码都没有特殊字符所以就不需要编码字节长度严格和上文的36对应相等HTTP RESPONSE3.1状态行HTTP/1.1 200 OKHTTP/1.1响应协议版本和请求保持一致2003 位状态码成功服务器正常处理完请求OK人类可读描述程序解析只看数字2003.2响应报头Server: YxlinkWAFServer响应头告诉客户端服务器软件 / 设备名称。 这里YxlinkWAF是Web 应用防火墙不是原始 Web 服务器说明流量经过 WAF 防护设备转发。生产环境常常会伪装 / 删除 Server 头避免泄露服务器版本信息防止针对性攻击。Content‑Type: text/html;charsetUTF‑8告诉客户端响应正文是什么类型什么字符集Content‑Language: zh‑CN说明页面内容语言简体中文。Transfer‑Encoding: chunked分块传输编码当响应体总大小服务器预先不知道的时候动态网页、实时输出内容就不会设置Content‑Length改用chunkedDate: Fri, 29 Sep 2017 05:10:13 GMT服务器的GMT 标准时间格林威治时间不是北京时间。 HTTP 协议所有时间头一律使用 GMT 时区。北京时间 GMT8。3.3响应正文响应正文就是HTML源码了注意因为是chunked模式TCP 流里 HTML 并不是一股脑全部发送而是被切割成若干 chunk 块传输。抓包工具帮我们把 chunk 块重组展示成完整 html 文本。需求1如何保证正文部分能一字不落的全部读到呢报头属性里面有 标注正文部分的长度的属性所以他只要根据换行规则去读取整个报文然后一直读到空行就说明报头读完了然后再根据length字段 直接向后读相应长度的字节 就可以把正文部分也给读完了问题1为什么信息需要有版本号呢版本号是便于我们双方标识对应版本号的功能因为可能有的用户没有更新客户端所以服务端是有可能会接受到各种不同版本的客户端请求的而较新的版本可能会存在一些旧版本没有的功能比如有的版本可以发消息而有的版本还可以发朋友圈问题2状态码有什么含义四、用代码去理解HTTP1.0这是一个基于 C TCP 封装实现的极简静态 HTTP 1.0 Web 服务器客户类#pragma once #include Common.hpp // 网络地址和主机地址之间进行转换的类 class InetAddr { public: InetAddr() {} InetAddr(struct sockaddr_in addr) { SetAddr(addr); } InetAddr(const std::string ip, uint16_t port) : _ip(ip), _port(port) { // 主机转网络 memset(_addr, 0, sizeof(_addr)); _addr.sin_family AF_INET; inet_pton(AF_INET, _ip.c_str(), _addr.sin_addr); _addr.sin_port htons(_port); // local.sin_addr.s_addr inet_addr(_ip.c_str()); // TODO } InetAddr(uint16_t port) : _port(port), _ip() { // 主机转网络 memset(_addr, 0, sizeof(_addr)); _addr.sin_family AF_INET; _addr.sin_addr.s_addr INADDR_ANY; _addr.sin_port htons(_port); } void SetAddr(struct sockaddr_in addr) { _addr addr; // 网络转主机 _port ntohs(_addr.sin_port); // 从网络中拿到的网络序列 // _ip inet_ntoa(_addr.sin_addr); // 4字节网络风格的IP - 点分十进制的字符串风格的IP char ipbuffer[64]; inet_ntop(AF_INET, _addr.sin_addr, ipbuffer, sizeof(_addr)); _ip ipbuffer; } uint16_t Port() { return _port; } std::string Ip() { return _ip; } const struct sockaddr_in NetAddr() { return _addr; } const struct sockaddr *NetAddrPtr() { return CONV(_addr); } socklen_t NetAddrLen() { return sizeof(_addr); } bool operator(const InetAddr addr) { return addr._ip _ip addr._port _port; } std::string StringAddr() { return _ip : std::to_string(_port); } ~InetAddr() { } private: struct sockaddr_in _addr; std::string _ip; uint16_t _port;Socket.hpp和Tcpserver.hppSocket抽象基类模板方法模式定义服务端套接字完整流程骨架把底层系统调用封装成纯虚接口。TcpSocket : public SocketTCP 具体实现子类override 全部纯虚函数内部调用 Linux socket 系统调用。#pragma once #include unistd.h #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; static const int gbacklog 16; // 基类 namespace SocketModule { class Socket { public: virtual ~Socket() {} virtual void Sockfd() 0; // 创建套接字 virtual void Bind(uint16_t port) 0; // 绑定需要端口号 virtual void Listen(int backlog) 0; virtual std::shared_ptrSocket Accept(InetAddr *client) 0; virtual void Close() 0; virtual int Recv(std::string *out)0; virtual int Send(const std::string message)0; virtual int Connect(const std::string server_ip, uint16_t port)0; public: void BuildTcpSocket(uint16_t port) { Sockfd(); Bind(port); Listen(gbacklog); } }; static const int defaultfd -1; class TcpSocket : public Socket { public: TcpSocket() : _sockfd(defaultfd) {} TcpSocket(int fd) : _sockfd(fd) {} ~TcpSocket() {} void Sockfd() override { _sockfd socket(AF_INET, SOCK_DGRAM, 0); if (_sockfd 0) { LOG(LogLevel::FATAL) socket error; exit(SOCK_ERR); } LOG(LogLevel::INFO) socket success; } void Bind(uint16_t port) override { InetAddr peer(port); int n bind(_sockfd, peer.NetAddrPtr(), peer.NetAddrLen()); // 绑定对象 if (n 0) { LOG(LogLevel::FATAL) bind error; exit(BIND_ERR); } LOG(LogLevel::INFO) bind success; } void Listen(int backlog) override { // 监听继续用_sockfd即可 int n listen(_sockfd, backlog); if (n 0) { LOG(LogLevel::FATAL) listen error; exit(LISTEN_ERR); } LOG(LogLevel::INFO) listen success; } // unique_ptr独占所有权只能有一个指针持有资源不能拷贝只能移动。 // shared_ptr共享所有权引用计数可以拷贝、复制多个智能指针同时管理同一个对象计数到 0 自动释放。 std::shared_ptrSocket Accept(InetAddr *client) override { struct sockaddr_in peer; socklen_t len sizeof(peer); int fd accept(_sockfd, CONV(peer), len); // 接收对象 if (fd 0) { LOG(LogLevel::WARNING) accept error; return nullptr; } // 接收成功 LOG(LogLevel::DEBUG) accept successclient-StringAddr(); client-SetAddr(peer); // 用peer把客户端的信息带出来 return std::make_sharedTcpSocket(fd); // 返回一个TcpSocket的对象 } void Close() override { if (_sockfd 0) { ::close(_sockfd); } } int Recv(std::string *out)override { //缓冲区 char buffer[4096]; int nrecv(_sockfd,buffer,sizeof(buffer)-1,0); if(n0) { buffer[n]0; *outbuffer; } return n; } int Send(const std::string message)override { return send(_sockfd,message.c_str(),message.size(),0); } //因为是客户端一定要绑定服务端的ip地址和port告诉内核我要连哪一个机器上的哪个程序 int Connect(const std::string server_ip, uint16_t port) override { InetAddr server(server_ip, port); return ::connect(_sockfd, server.NetAddrPtr(), server.NetAddrLen()); } private: int _sockfd; }; }有人就有疑问了服务器与0.0.0.0有关系啊为啥connect时要传特定的地址呢0.0.0.0是服务端bind()的特殊监听地址不是机器真实 IP而connect是客户端绑定特定的ip地址需要服务器本地的机器ip地址服务器类#include Socket.hpp #include iostream #include memory #include sys/wait.h #include functional using namespace SocketModule; using namespace LogModule; using ioservicestd::functionvoid(std::shared_ptrSocket sock,InetAddrclient); class TcpServer { public: TcpServer(uint16_t port,ioservice service):_port(port),_isrunning(false),_listenptr(std::make_uniqueTcpSocket()),_service(service) { _listenptr-BuildTcpSocket(_port); } void Start() { _isrunningtrue; while(_isrunning) { //开始接听 InetAddr client(_port); auto sock_listenptr-Accept(client);//这种接收方式客户端信息我们也有了 if(socknullptr) { continue; } //创建子进程干活 pid_t idfork(); if(id0) { LOG(LogLevel::ERROR)fork error; exit(FORK_ERR); } else if(id0) { //子进程干的活是服务压根不需要监听所以我们得把监听的_fd给关闭 _listenptr-Close(); if(fork()0) { exit(OK); } //留下孙子进程执行任务 _service(sock,client); exit(OK); } else { //先关闭 sock-Close(); //再等待 pid_t pidwaitpid(id,nullptr,0); (void)pid; } } _isrunningfalse; } ~TcpServer(){} private: uint16_t _port; std::unique_ptrTcpSocket _listenptr; bool _isrunning; ioservice _service; };在讲http和工具类之前我们先解决几个问题问题1我们返回类型是socket基类但是可以返回Tcpsocket类型还有传参时我们的形参是socket类型传的实参为什么可以是Tcpsocket类型呢这其实是c的向上转型只有指针引用智能指针才会完成向上转型转化为了socket基类而值传递只能够完成切片而当调用函数时TcpSocket 对象内存的基类子对象部分就有 vptr智能指针内部保存的是Tcpsocket类型对象的起始地址然后找到堆上 TcpSocket 对象对象内部 vptr 去找 TcpSocket 虚表执行 TcpSocket 重写的 Recv。延伸模版方法你或许会很好奇为什么BuildTcpSocketMethod在基类内部我子类可以直接调用而且里面的函数是经过重写之后的呢因为BuildTcpSocketMethod不是虚函数他是一个普通成员函数当继承时它会被子类调用所以能够直接使用不会走虚表查找对象在堆上创建完整TcpSocket对象对象内存内 vptr 指向 TcpSocket 的虚表虚表里面存放重写后的Sockfd、Bind、Listen的函数地址。进入BuildTcpSocketMethod内部虽然说在基类的函数内部但是调用的是虚函数this指针是Socket*类型但指向真实的 TcpSocket 对象通过对象内部的 vptr → TcpSocket 虚表拿到子类重写的虚函数地址问题2为什么在定义_listenptr时用的是unique_ptr的指针但是套接字对象却是shared_ptr类型unique_ptr独占所有权不允许拷贝只能移动同一时刻只能有一个智能指针管理这块堆对象禁止拷贝构造 / 拷贝赋值监听 TcpSocket 对象只有 TcpServer 的 _listenptr 拥有它没有其他地方拷贝、复制这个_listenptr。没有拷贝就不会违反 unique_ptr 独占规则。shared_ptr是共享所有权引用计数管理可以多个指针同时指向同一个对象每来一个客户端就新建一个通信套接字fork 之后父子进程都拷贝sockshared_ptr引用计数 1父子都可以使用计数归 0 释放。问题3为什么设置回调方法时形参必须是套接字和客户呢回调是事件循环触发的回调函数本身没有捕获外部变量不知道是哪一个监听 socket来了新连接传入它回调内部可以继续执行 accept 接收后续连接同时shared_ptr保证回调执行期间对象不会被销毁。accept()系统调用返回的新文件描述符代表刚接入的客户端业务代码拿到这个 fd才能封装成通信 socket完成读写交互。简单总结sock标记哪个监听 socket 产生事件client_fd拿到新客户端通信句柄。工具类#pragma once #include iostream #include fstream #include string // 工具类 class Util { public: static bool ReadFileContent(const std::string filename , std::string *out) { // version1: 默认是以文本方式读取文件的. 图片是二进制的. // std::ifstream in(filename); // if (!in.is_open()) // { // return false; // } // std::string line; // while(std::getline(in, line)) // { // *out line; // } // in.close(); // version2 : 以二进制方式进行读取 int filesize FileSize(filename); if(filesize 0) { std::ifstream in(filename); if(!in.is_open()) return false; out-resize(filesize); in.read((char*)(out-c_str()), filesize); in.close(); } else { return false; } return true; } static bool ReadOneLine(std::string bigstr, std::string *out, const std::string sep/*\r\n*/) { auto pos bigstr.find(sep); if(pos std::string::npos) return false; *out bigstr.substr(0, pos); bigstr.erase(0, pos sep.size()); return true; } static int FileSize(const std::string filename) { std::ifstream in(filename, std::ios::binary); if(!in.is_open()) return -1; in.seekg(0, in.end); int filesize in.tellg(); in.seekg(0, in.beg); in.close(); return filesize; } };这是个工具类全部都是static类型的静态变量说明这就是单纯的方法没有什么成员变量就避免了每次调用都要创建对象的麻烦有效的节省了空间。我们首先来看看读取文件内容这个工具我们的第一个版本是以文本方式进行读取的先打开文件然后将文件的每一行读到我们的*out里面但是以文本方式读取没法读取类似于图片的二进制文件所以我们又采取了版本2以二进制方式读取文件我们首先先拿到文件的大小如果成功拿到我们就用ifstream来打开文件然后先把out的大小定义为filesize目的是直接给输出 string 预先分配filesize字节内存空间然后再read()一次性读取filesize个字节读到缓冲区里注意c_str()是const char*类型的所以我们得把它强转成为(char*)类型的然后关掉文件流释放操作系统文件句柄。我们再来看看截取每一行的操作我们把大的字符串传进来out是缓冲区sep是分割符\r\n然后没有分割符就返回false如果找到了分隔符就把从开头到pos的内容赋值给我们的*out然后再删除掉我们截取的内容这是filesize的大小我们用了ifstream这里先对ifstream做一个简单的介绍很明显我们这里是以二进制形式打开的文件如果打开失败就返回-1打开成功后我们用了seekg是一个移动读指针我们先把指针移动到文件末尾然后tellg返回当前指针的字节位置也就是文件字节大小然后再把指针返回到开头位置然后关闭文件流返回大小HTTP在此处我们将http请求和http响应单独封装起来作为两个独立的类HTTPREQUST代码#pragma once #include Socket.hpp #include TcpServer.hpp #include Util.hpp #include Log.hpp #include iostream #include string #include memory #include sstream #include unordered_map using namespace SocketModule; using namespace LogModule; const std::string gspace ; const std::string glinespace \r\n; const std::string glinesep : ; const std::string webroot ./wwwroot; // 根目录的路径 const std::string homepage index.html; // 首页 const std::string page_404 /404.html; // 错误页面 class HttpRequest { public: HttpRequest() { } std::string Serialize() { return std::string(); } // 分割请求行 bool Praseline(std::string reqline) { std::stringstream ss(reqline); ss _method _uri _version; } // 反序列化 bool DeSerialize(std::string reqstr) { // 获取请求行 std::string reqline; bool res Util::ReadOneLine(reqstr, reqline, glinespace); if (!res) { return false; } LOG(LogLevel::DEBUG) reqline; // 开始分割 Praseline(reqline); if (_uri /) { _uri webroot _uri homepage; } else { _uri webroot _uri; } LOG(LogLevel::DEBUG) _method: _method; LOG(LogLevel::DEBUG) _uri: _uri; LOG(LogLevel::DEBUG) _version: _version; // ./wwwroot/XXX.YYY return true; } std::string Uri() { return _uri; } ~HttpRequest() { } private: std::string _method; // 请求方法 std::string _uri; // 资源 std::string _version; // 版本 std::unordered_mapstd::string, std::string _headers; std::string blackline; std::string _text; };关于uri的一些冷知识HTTPRESPONSEclass HttpResponse { public: HttpResponse() : blackline(glinespace), _version(HTTP/1.0) { } // 序列化 std::string Serialize() { // 拼接成字符串 std::string repline _version gspace std::to_string(_code) gspace _desc glinespace; // 拼接响应报头 std::string repkv; for (auto header : _headers) { std::string line header.first glinesep header.second glinespace; repkv line; } return repline repkv blackline _text; } // 设置要打开的本地资源文件路径例如 ./wwwroot/index.html。 void SetTargetFile(const std::string target) { _targetfile target; } // 设置退出码 void Setcode(int code) { _code code; switch (_code) { case 200: _desc OK; break; case 404: _desc Not Found; break; default: break; } } // 设置请求报头 void SetHeader(const std::string key, const std::string value) { auto iter _headers.find(key); if (iter ! _headers.end()) return; _headers.insert(std::make_pair(key, value)); } std::string Uri2Suffix(const std::string targetfile) { // 现在开始截取后缀 auto pos targetfile.rfind(.); if (pos std::string::npos) { return text/html; } std::string suffix targetfile.substr(pos); // 从pos接到最后 if (suffix .html || suffix .htm) return text/html; else if (suffix .jpg) return image/jpeg; else if (suffix png) return image/png; else return ; } bool MakeResponse() { if (_targetfile ./wwwroot/favicon.ico) { LOG(LogLevel::DEBUG) 用户请求: _targetfile 忽略它; return false; } int filesize 0; bool res Util::ReadFileContent(_targetfile, _text); // 浏览器请求的资源一定会存在吗出错呢 if(!res) { //走404页面,首先清空文本 _text; LOG(LogLevel::WARNING) client want get : _targetfile but not found; Setcode(404); _targetfilewebrootpage_404;//重新给_targetfile filesizeUtil::FileSize(_targetfile); Util::ReadFileContent(_targetfile,_text); std::string suffixUri2Suffix(_targetfile); //重读 SetHeader(Content-type,suffix); SetHeader(Content-length,std::to_string(filesize)); } else { //读取_text LOG(LogLevel::DEBUG)read success_targetfile; Setcode(200); filesizeUtil::FileSize(_targetfile); std::string suffixUri2Suffix(_targetfile); SetHeader(Content-length,std::to_string(filesize)); SetHeader(Content-type,suffix); } return true; } bool Deserialize(std::string reqstr) { return true; } ~HttpResponse() {} private: std::string _version; // HTTP版本默认 HTTP/1.0 int _code; std::string _desc; std::unordered_mapstd::string, std::string _headers; std::string blackline; std::string _text; // 响应body读取的html/图片文件内容 // 其他属性 std::string _targetfile; // 要读取的本地磁盘文件路径 ./wwwroot/xxx.html };HTTP处理请求class Http { public: Http(uint16_t port) : tsvrp(std::make_uniqueTcpServer(port)) { } void HandlerHttpRquest(std::shared_ptrSocket sock, InetAddr client) { // 收到请求 std::string httpreqstr; // 假设概率大读到了完整的请求 // bug! int n sock-Recv(httpreqstr); // 浏览器给我发过来的是一个大的http字符串, 其实我们的recv也是有问题的。tcp是面向字节流的. if (n 0) { std::cout ########################## std::endl; std::cout httpreqstr; std::cout ########################## std::endl; // 对报文完整性进行审核 -- 缺 // 所以今天我们就不在担心用户访问一个服务器上不存在的资源了. // 我们更加不担心给用户返回任何网页资源(html, css, js, 图片视频)..., 这种资源静态资源 HttpRequest req; req.DeSerialize(httpreqstr); HttpResponse resp; resp.SetTargetFile(req.Uri()); if (resp.MakeResponse()) { std::string response_str resp.Serialize(); sock-Send(response_str); } // HttpResponse resp; // resp._version HTTP/1.1; // resp._code 200; // success // resp._desc OK; // //./wwwroot/a/b/c.html // LOG(LogLevel::DEBUG) 用户请求: filename; // bool res Util::ReadFileContent(filename, (resp._text)); // 浏览器请求的资源一定会存在吗出错呢 // (void)res; } // #ifndef DEBUG // #define DEBUG #ifdef DEBUG // 收到请求 std::string httpreqstr; // 假设概率大读到了完整的请求 sock-Recv(httpreqstr); // 浏览器给我发过来的是一个大的http字符串, 其实我们的recv也是有问题的。tcp是面向字节流的. std::cout httpreqstr; // 直接构建http应答. 内存级别固定 HttpResponse resp; resp._version HTTP/1.1; resp._code 200; // success resp._desc OK; std::string filename webroot homepage; // ./wwwroot/index.html; bool res Util::ReadFileContent(filename, (resp._text)); (void)res; std::string response_str resp.Serialize(); sock-Send(response_str); #endif // 对请求字符串进行反序列化 } void Start() { tsvrp-Start([this](std::shared_ptrSocket sock, InetAddr client) {this-HandlerHttpRquest(sock, client); }); } ~Http() { } private: std::unique_ptrTcpServer tsvrp; };结果
返回列表