实验内容

  1. 设计并实现一个基本 HTTP 代理服务器。要求在指定端口(例如 8080)接收来自客户的 HTTP 请求并且根据其中的 URL 地址访问该地址 所指向的 HTTP 服务器(原服务器),接收 HTTP 服 务器的响应报文,并 将响应报文转发给对应的客户进行浏览。
  2. 设计并实现一个支持 Cache 功能的 HTTP 代理服务器。要求能缓 存原服务器响应的对象,并 能够通过修改请求报文(添加 if-modified-since 头行),向原服务器确认缓存对象是否是最新版本。 (选作内容)
  3. 扩展 HTTP 代理服务器,支持如下功能: (选作内容)
    1. 网站过滤:允许/不允许访问某些网站;
    2. 用户过滤:支持/不支持某些用户访问外部网站;
    3. 网站引导:将用户对某个网站的访问引导至一个模拟网站(钓鱼)。

代理服务器的概念

代理服务器,允许一个网络终端(一般为客户端)通过这个服务与另一 个网络终端(一般为服务器)进行非直接的连接。普通 Web 应用通信方式与采用代理服务器的 通信方式的对比如下图所示:

代理服务器在指定端口(本实验中所指定的是666端口)监听浏览器的访问请求(需要在客户端浏览器进行相应的设置),接收到浏览器对远程网站的浏览请求时,代理服务器开始在代理服务器的缓存中检索 URL 对应的对象(网页、 图像等对象),找到对象文件后,提取该对象文件的最新被修改时间;代理服务器程序在客户的请求报文首部插入,并向原 Web 服务器转发修改后的请求报文。如果代理服务器没有该对象的缓存,则会直接向原服务器转发请求报文,并将原服务器返回的响应直接转发给客户端,同时将对象缓存到代理服务器中。代理服务器程序会根据缓存的时间、大小和提取记录等对缓存进行清理。

代码结构

代码中共实现 3个类,分别为WebsiteDetector类、Cache类和HttpProxyServer类。

WebsiteDetector类:

该类实现了网站过滤和网站引导功能。通过构造函数直接静态设置了钓鱼网站和屏蔽的网站:

  1. WebsiteDetector::WebsiteDetector()
  2. {
  3. AddValidURL("http://jwc.hit.edu.cn/","http://jwts.hit.edu.cn/");
  4. AddBlockedURL("http://xltj.hit.edu.cn/");
  5. }

可知,屏蔽了心理网站。将教务处网站引导到本科教学管理与服务平台。

Cache

该类在当前目录下创建文件夹.cache/,在其中存储浏览缓存对象。同时该类中,保存着对象与文件名的映射关系,对象和LastModified字段的映射关系。

  1. class Cache {
  2. public:
  3. std::string GetDate(const std::string& url); // 获取url对应保存的LastModified字段
  4. bool Get(const std::string& url, char* response, size_t& start, size_t& responseSize); // 读取缓存
  5. bool Put(const std::string& url, const char* response, size_t responseSize, size_t& start); // 保存缓存
  6. private:
  7. std::string cacheDirectory_; // 存放缓存的文件目录
  8. std::map<std::string, std::string> cacheMap_; // 对象和LastModified字段的映射关系
  9. std::map<std::string, std::string> fileMap_; / 对象与文件名的映射关系
  10. std::mutex mutex_; // 多线程同时读写文件的互斥锁
  11. };

HttpProxyServer

该类是代理服务器的实现类。是一个多用户代理服务器。首先该类创建HTTP代理服务的TCP主套接字,该套接字监听等待客户端的连接请求。当客户端连接之后,创建一个子线程,由子线程行上述一对一的代理过程,服务结束之后子线程终止。

  1. class HttpProxyServer
  2. {
  3. public:
  4. HttpProxyServer(int port); // 构造函数,参数为端口号
  5. void Start(); // 监听客户端连接请求
  6. private:
  7. int serverSocket_; // 代理服务Socket
  8. int port_; // 端口号
  9. struct sockaddr_in serverAddr_; // 代理服务地址
  10. Cache cache_; // Cache类
  11. WebsiteDetector websiteDetector_; // websiteDetector类
  12. void HandleClient(int clientSocket); // 子线程调用函数
  13. std::string ExtractUrl(const std::string &httpRequest); // 解析URL
  14. int CreateServerSocket(const std::string &host); // 创建与原服务器连接的Socket
  15. bool ServerToClient(const std::string &url, int clientSocket); // 转发数据
  16. void ParseUrl(const std::string &url, std::string &host, std::string &path); // 解析主机名与路径名
  17. };

程序基本流程

(1) 初始化服务器Socket,监听等待客户端的连接请求。

(2) 当客户端连接后,创建子线程处理请求。

(3) 子线程接收请求,解析HTTP请求的首部行和请求头。然后提取Url,Url作为参数通过websiteDetector类判断是否屏蔽或者引导。

(4) 然后进入转发的过程,首先进行域名解析,然后创建Socket先原服务器发送请求,接收响应,将数据转发到客户端。

(5) 在转发的过程中,涉及保存缓存和读取缓存。

网站引导功

利用首部行中的location字段,实现引导。

  1. std::string locationResponse = std::string("HTTP/1.1 302 Found") + MY_CRLF + "Location: " + newUrl + MY_CRLF + MY_CRLF;
  2. send(clientSocket, locationResponse.c_str(), locationResponse.size(), 0);

用户过滤功能

设置服务器地址信息时实现。

  1. serverAddr_.sin_addr.s_addr = INADDR_ANY;
  2. // serverAddr_.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); //只允许本机用户访问服务器

Cache功能

1. 代理服务器处理客户端请求时,对于第一次出现的对象,会保存下。当客户端再次请求时,代理服务器就会在请求中添加If-Modified-Since首部行。

  1. date = cache_.GetDate(url);
  2. std::string cacheRequest = httpRequest + "If-Modified-Since: " + date + MY_CRLF + MY_CRLF;
  1. 发送该请求后,等待原服务器响应,并判断是否回应304状态码。
  1. if (IsResponseNotModified(responseNotModified) ) {
  2. // std::cout << "304 Not Modified" << std::endl;
  3. sel = false;
  4. }else {
  5. cache_.ClearFileContent(url); //清空
  6. sel = true;
  7. }
  1. sel为false时,则读取Cache转发到客户端。若为true,则发送HTTP请求到原服务器,再接收响应,转发到客户端,再保存到Cache。

修改Chrome浏览器代理配置

--proxy-server="http://127.0.0.1:666"

VScode编译运行

该代理服务器成功在666端口启动,并输出了cache目录。

验证

验证基础的代理功能

访问今日哈工大网站:http://today.hit.edu.cn

可以看到,网站资源顺利加载,输出栏中,输出了请求的各个资源对象的url。

验证网站引导功能

输入网址:http://jwc.hit.edu.cn/

最后直接跳转到到了,http://jwts.hit.edu.cn/

验证网站过滤功能

输入网址:http://xltj.hit.edu.cn/

可以看到,无法访问。

验证用户过滤功能

验证Cache功能

将在Cache中的资源 http://jwts.hit.edu.cn/resources/css/common/ydy.css ,修改一下。

把色彩均改为红色,再次访问 http://jwts.hit.edu.cn/

可以看到,字体颜色变为红色。可知,HTTP代理服务器这次使用的是Cache中的资源。

源代码

点击查看代码
  1. //g++ your_code.cpp -o your_executable -lws2_32
  2. #include <fstream>
  3. #include <iostream>
  4. #include <cstring>
  5. #include <cstdlib>
  6. #include <cstdio>
  7. #include <string>
  8. #include <thread>
  9. #include <vector>
  10. #include <mutex>
  11. #include <list>
  12. #include <map>
  13. #include <sstream>
  14. #include <winsock2.h>
  15. #include <WS2tcpip.h>
  16. #define MAX_CLIENTS 6
  17. #define BUFSIZE 655360
  18. #define HEADSIZE 128
  19. #define MY_CRLF "\r\n"
  20. class WebsiteDetector {
  21. public:
  22. WebsiteDetector()
  23. {
  24. AddValidURL("http://jwc.hit.edu.cn/", "http://jwts.hit.edu.cn/");
  25. AddBlockedURL("http://xltj.hit.edu.cn/");
  26. }
  27. // 钓鱼
  28. std::string IsURLPhishing(const std::string& url) {
  29. auto it = validURLs_.find(url);
  30. if (it != validURLs_.end()) {
  31. return it->second;
  32. } else {
  33. return "Phishing";
  34. }
  35. }
  36. // 屏蔽
  37. bool IsURLBlocked(const std::string& url) {
  38. for (const std::string& blockedURL : blockedURLs_) {
  39. if (url.find(blockedURL) != std::string::npos) {
  40. return true;
  41. }
  42. }
  43. return false;
  44. }
  45. private:
  46. std::map<std::string, std::string> validURLs_;
  47. std::vector<std::string> blockedURLs_;
  48. void AddValidURL(const std::string& srcURL, const std::string& dstURL)
  49. {
  50. validURLs_[srcURL] = dstURL;
  51. }
  52. void AddBlockedURL(const std::string& url) {
  53. blockedURLs_.push_back(url);
  54. }
  55. };
  56. class Cache {
  57. public:
  58. Cache() : cacheDirectory_("H:\\cppwork\\CS-networking\\.cache") {
  59. std::cout << cacheDirectory_ << std::endl;
  60. std::system(("mkdir -p " + cacheDirectory_).c_str());
  61. }
  62. bool Check(const std::string& url) {
  63. std::lock_guard<std::mutex> lock(mutex_);
  64. auto it = cacheMap_.find(url);
  65. if (it != cacheMap_.end())
  66. {
  67. return true;
  68. }
  69. return false;
  70. }
  71. // 清空文件内容
  72. bool ClearFileContent(const std::string& url) {
  73. std::lock_guard<std::mutex> lock(mutex_);
  74. // Generate a unique filename based on the URL
  75. std::string fileName = GetFileNameFromUrl(url);
  76. auto it = fileMap_.find(fileName);
  77. std::string fileTag = it->second;
  78. std::string filePath = cacheDirectory_ + "\\" + fileTag;
  79. // 打开文件并使用 std::ios::trunc 模式来清空文件内容
  80. std::ofstream file(filePath, std::ios::out | std::ios::trunc);
  81. if (!file) {
  82. std::cerr << "无法打开文件:" << filePath << std::endl;
  83. return false;
  84. }
  85. // 关闭文件
  86. file.close();
  87. return true;
  88. }
  89. std::string GetDate(const std::string& url)
  90. {
  91. std::lock_guard<std::mutex> lock(mutex_);
  92. auto it = cacheMap_.find(url);
  93. return it->second;
  94. }
  95. bool Get(const std::string& url, char* response, size_t& start, size_t& responseSize) {
  96. std::lock_guard<std::mutex> lock(mutex_);
  97. // Generate a unique filename based on the URL
  98. std::string fileName = GetFileNameFromUrl(url);
  99. std::string fileTag = fileMap_[fileName];
  100. std::cout << "Get() url: " << url << std::endl;
  101. std::cout << "Get() fileTag: " << fileTag << std::endl;
  102. // If found, read the response from the file
  103. std::ifstream file(cacheDirectory_ + "\\" + fileTag, std::ios::binary);
  104. if (file) {
  105. file.seekg(start, std::ios::beg);
  106. file.read(response, BUFSIZE);
  107. // Get the number of bytes read in this chunk
  108. size_t bytesRead = static_cast<size_t>(file.gcount());
  109. start += bytesRead;
  110. responseSize = bytesRead;
  111. response[bytesRead] = '\0';
  112. file.close();
  113. return true;
  114. }
  115. return false; // URL not found in the cache
  116. }
  117. bool Put(const std::string& url, const char* response, size_t responseSize, size_t& start) {
  118. std::lock_guard<std::mutex> lock(mutex_);
  119. // Generate a unique filename based on the URL
  120. std::string fileName = GetFileNameFromUrl(url);
  121. auto it = fileMap_.find(fileName);
  122. std::string fileTag;
  123. if (it == fileMap_.end())
  124. {
  125. fileTag = std::to_string(cnt);
  126. fileMap_[fileName] = fileTag;
  127. cnt ++;
  128. }else
  129. {
  130. fileTag = it->second;
  131. }
  132. // Store the response in a file
  133. std::ofstream file(cacheDirectory_ + "\\" + fileTag, std::ios::binary | std::ios::app );
  134. if (!file) {
  135. fprintf(stderr, "file open error: %s(errno: %d)\n", strerror(errno),errno);
  136. return false; // Unable to open file for writing
  137. }
  138. file.seekp(start);
  139. file.write(response, responseSize);
  140. file.close();
  141. start += responseSize;
  142. return true; // Failed to store response in the cache
  143. }
  144. void PutDate(const std::string& url)
  145. {
  146. std::lock_guard<std::mutex> lock(mutex_);
  147. // Generate a unique filename based on the URL
  148. std::string fileName = GetFileNameFromUrl(url);
  149. std::string fileTag = fileMap_[fileName];
  150. // 拼接完整的文件路径
  151. std::string filePath = cacheDirectory_ + "\\" + fileTag;
  152. // 打开文件并读取 Last-Modified 首部内容
  153. std::ifstream file(filePath);
  154. if (!file) {
  155. fprintf(stderr, "file open error: %s(errno: %d)\n", strerror(errno),errno);
  156. }
  157. std::string line;
  158. while (std::getline(file, line)) {
  159. // 查找包含 Last-Modified 首部的行
  160. if (line.find("Last-Modified:") != std::string::npos) {
  161. // 提取 Last-Modified 的值并存储到 cacheMap_
  162. size_t startPos = line.find(":") + 2;
  163. size_t endPos = line.find(MY_CRLF);
  164. std::string date = line.substr(startPos, endPos);
  165. // std::cout << "line: " << line << std::endl;
  166. // std::cout << "date: " << date << std::endl;
  167. cacheMap_[url] = date;
  168. break; // 找到后可以退出循环
  169. }else
  170. {
  171. if (line == MY_CRLF)
  172. {
  173. break;
  174. }
  175. }
  176. }
  177. file.close();
  178. }
  179. private:
  180. std::string cacheDirectory_;
  181. std::map<std::string, std::string> cacheMap_;
  182. std::map<std::string, std::string> fileMap_;
  183. std::mutex mutex_;
  184. int cnt = 1;
  185. std::string GetFileNameFromUrl(const std::string& url) {
  186. // Replace characters in the URL to create a valid filename
  187. std::string fileName = url;
  188. for (char& c : fileName) {
  189. if (c == '/' || c == '?' || c == '&' || c == '=') {
  190. c = '_';
  191. }
  192. }
  193. return fileName;
  194. }
  195. };
  196. // 定义HTTP代理服务器类
  197. class HttpProxyServer
  198. {
  199. public:
  200. HttpProxyServer(int port) : port_(port)
  201. {
  202. // 初始化服务器
  203. // 创建主套接字并绑定端口
  204. serverSocket_ = socket(AF_INET, SOCK_STREAM, 0);
  205. if (serverSocket_ == -1)
  206. {
  207. fprintf(stderr, "Constructor(): create socket error: %s(errno: %d)\n", strerror(errno),errno);
  208. exit(EXIT_FAILURE);
  209. }
  210. // 设置服务器地址信息
  211. // 初始化 serverAddr_
  212. memset(&serverAddr_, 0, sizeof(serverAddr_));
  213. serverAddr_.sin_family = AF_INET;
  214. serverAddr_.sin_addr.s_addr = INADDR_ANY;
  215. // serverAddr_.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); //只允许本机用户访问服务器
  216. serverAddr_.sin_port = htons(port_);
  217. // 绑定套接字到指定端口
  218. if (bind(serverSocket_, (struct sockaddr *)&serverAddr_, sizeof(serverAddr_)) == -1)
  219. {
  220. fprintf(stderr, "Constructor(): bind socket error: %s(errno: %d)\n",strerror(errno), errno);
  221. closesocket(serverSocket_);
  222. exit(EXIT_FAILURE);
  223. }
  224. // 开始监听客户端连接请求
  225. if (listen(serverSocket_, MAX_CLIENTS) == -1)
  226. {
  227. fprintf(stderr, "Constructor(): listen socket error: %s(errno: %d)\n",strerror(errno),errno);
  228. closesocket(serverSocket_);
  229. exit(EXIT_FAILURE);
  230. }
  231. std::cout << "Proxy server started on port " << port_ << std::endl;
  232. }
  233. void Start()
  234. {
  235. // 启动服务器,监听客户端连接请求
  236. while (true)
  237. {
  238. struct sockaddr_in clientAddr;
  239. int clientAddrLen = sizeof(struct sockaddr);
  240. // 接受客户端连接
  241. int clientSocket = accept(serverSocket_, (struct sockaddr *)&clientAddr, &clientAddrLen);
  242. if (clientSocket == INVALID_SOCKET)
  243. {
  244. fprintf(stderr, "Start(): accept socket error: %s(errno: %d)",strerror(errno),errno);
  245. continue; // 继续等待下一个连接
  246. }
  247. // std::cout << "Start(): Accepted a client connection" << std::endl;
  248. // 创建子线程处理客户端请求
  249. std::thread clientThread(&HttpProxyServer::HandleClient, this, clientSocket);
  250. clientThread.detach(); // 不等待
  251. }
  252. }
  253. private:
  254. int serverSocket_;
  255. int port_;
  256. struct sockaddr_in serverAddr_;
  257. Cache cache_;
  258. WebsiteDetector websiteDetector_;
  259. void HandleClient(int clientSocket)
  260. {
  261. // 读取客户端的HTTP请求
  262. char buffer[BUFSIZE];
  263. memset(buffer, 0, BUFSIZE);
  264. ssize_t bytesRead = recv(clientSocket, buffer, BUFSIZE - 1, 0);
  265. if (bytesRead == -1)
  266. {
  267. perror("HandleClient(): Error reading from client socket");
  268. closesocket(clientSocket);
  269. return;
  270. }
  271. // 解析请求,提取URL
  272. std::string request(buffer);
  273. std::string url = ExtractUrl(request);
  274. std::cout << "<" << url << ">" << std::endl;
  275. // Website Filter; User Filter ; Website phishing
  276. if (websiteDetector_.IsURLBlocked(url))
  277. {
  278. std::cout << "Url Blocked Success: " << url << std::endl;
  279. }else
  280. {
  281. std::string newUrl = websiteDetector_.IsURLPhishing(url);
  282. if (newUrl == "Phishing")
  283. {
  284. // 向服务端请求,向客户端发送
  285. if( ServerToClient(url, clientSocket) )
  286. {
  287. std::cout << "Transmit Success!" << std::endl;
  288. }else
  289. {
  290. std::cout << "Transmit Fail!" << std::endl;
  291. }
  292. }else
  293. {
  294. std::cout << "Phishing" << std::endl;
  295. std::string locationResponse = std::string("HTTP/1.1 302 Found") + MY_CRLF + "Location: " + newUrl + MY_CRLF + MY_CRLF;
  296. std::cout << locationResponse << std::endl;
  297. send(clientSocket, locationResponse.c_str(), locationResponse.size(), 0);
  298. }
  299. }
  300. std::cout << "----------------------" << std::endl;
  301. // 关闭连接
  302. closesocket(clientSocket);
  303. }
  304. // 提取URL
  305. std::string ExtractUrl(const std::string &httpRequest)
  306. {
  307. std::string url;
  308. // Debug
  309. // std::cout << "ExtractUrl(): httpRequest = " << std::endl << httpRequest << std::endl;
  310. // 在HTTP请求中查找"GET ",通常URL紧随其后
  311. size_t getPos = httpRequest.find("GET ");
  312. if (getPos != std::string::npos)
  313. {
  314. // 找到"GET "后,查找下一个空格,该空格之后是URL
  315. size_t spacePos = httpRequest.find(' ', getPos + 4);
  316. if (spacePos != std::string::npos)
  317. {
  318. url = httpRequest.substr(getPos + 4, spacePos - (getPos + 4));
  319. }
  320. }
  321. return url;
  322. }
  323. void ParseUrl(const std::string &url, std::string &host, std::string &path)
  324. {
  325. // 查找 URL 中的 "http://",并获取其后的部分
  326. size_t httpPos = url.find("http://");
  327. if (httpPos != std::string::npos)
  328. {
  329. std::string urlWithoutHttp = url.substr(httpPos + 7); // 7 是 "http://" 的长度
  330. // 查找 "/",分隔主机名和路径
  331. size_t slashPos = urlWithoutHttp.find('/');
  332. if (slashPos != std::string::npos)
  333. {
  334. host = urlWithoutHttp.substr(0, slashPos);
  335. path = urlWithoutHttp.substr(slashPos);
  336. }
  337. else
  338. {
  339. // 如果没有找到 "/",则整个剩余部分都是主机名
  340. host = urlWithoutHttp;
  341. path = "/";
  342. }
  343. }
  344. else
  345. {
  346. // 如果没有 "http://" 前缀,则默认协议为 HTTP,整个 URL 都是主机名
  347. host = url;
  348. path = "/";
  349. }
  350. // Debug
  351. // std::cout << "url: " + url << std::endl;
  352. // std::cout << "host: " + host << std::endl;
  353. // std::cout << "path: " + path << std::endl;
  354. }
  355. int CreateServerSocket(const std::string &host)
  356. {
  357. // 域名解析
  358. addrinfo* result = NULL;
  359. addrinfo hints;
  360. ZeroMemory(&hints, sizeof(hints));
  361. hints.ai_family = AF_INET; // 使用IPv4地址
  362. hints.ai_socktype = SOCK_STREAM;
  363. if (getaddrinfo(host.c_str(), "http", &hints, &result) != 0)
  364. {
  365. fprintf(stderr, "CreateServerSocket(): Failed to resolve the host: %s\n", host.c_str());
  366. return -1; // 返回-1表示连接失败
  367. }
  368. // 创建Socket
  369. int serverSocket = socket(AF_INET, SOCK_STREAM, 0);
  370. if (serverSocket == -1)
  371. {
  372. fprintf(stderr, "CreateServerSocket(): create socket error: %s(errno: %d)\n", strerror(errno), errno);
  373. freeaddrinfo(result); // 释放内存
  374. return -1; // 返回-1表示连接失败
  375. }
  376. // 设置服务器地址信息
  377. struct sockaddr_in serverAddr;
  378. memset(&serverAddr, 0, sizeof(serverAddr));
  379. serverAddr.sin_family = AF_INET;
  380. serverAddr.sin_port = htons(80); // 设置端口号为80,可以根据需要修改
  381. serverAddr.sin_addr.s_addr = ((struct sockaddr_in *)(result->ai_addr))->sin_addr.s_addr;
  382. // 连接到原服务器
  383. if (connect(serverSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) == -1)
  384. {
  385. fprintf(stderr, "CreateServerSocket(): connect error: %s(errno: %d)\n",strerror(errno),errno);
  386. closesocket(serverSocket); // 在Windows中使用closesocket关闭套接字
  387. freeaddrinfo(result); // 释放内存
  388. return -1; // 返回-1表示连接失败
  389. }
  390. freeaddrinfo(result); // 释放内存
  391. return serverSocket; // 返回连接成功的套接字描述符
  392. }
  393. bool ServerToClient(const std::string &url, int clientSocket)
  394. {
  395. // 解析URL,获取主机名和路径
  396. std::string host, path;
  397. ParseUrl(url, host, path);
  398. // 创建Socket连接到原服务器
  399. int serverSocket = CreateServerSocket(host);
  400. if (serverSocket == -1)
  401. {
  402. return FALSE; // 处理连接失败的情况
  403. }
  404. // 构建HTTP请求
  405. std::string httpRequest = "GET " + path + " HTTP/1.1" + MY_CRLF + "Host: " + host + MY_CRLF + "Connection: close" + MY_CRLF;
  406. std::string date;
  407. bool sel;
  408. if (cache_.Check(url))
  409. {
  410. sel = false;
  411. date = cache_.GetDate(url);
  412. std::string cacheRequest = httpRequest + "If-Modified-Since: " + date + MY_CRLF + MY_CRLF;
  413. // 发送HTTP, 带有If-Modified-Since 首部行
  414. if (send(serverSocket, cacheRequest.c_str(), cacheRequest.size(), 0) == -1)
  415. {
  416. perror("Error sending request to server");
  417. closesocket(serverSocket);
  418. return FALSE;
  419. }
  420. std::string cacheResponse;
  421. char cacheBuffer[HEADSIZE];
  422. ssize_t cacheBytesRead;
  423. cacheBytesRead = recv(serverSocket, cacheBuffer, HEADSIZE - 1, 0);
  424. std::string responseNotModified(cacheBuffer);
  425. // std::cout << "responseNotModified: " << responseNotModified << std::endl;
  426. if (IsResponseNotModified(responseNotModified) )
  427. {
  428. // std::cout << "304 Not Modified" << std::endl;
  429. sel = false;
  430. }else
  431. {
  432. cache_.ClearFileContent(url); //清空
  433. sel = true;
  434. }
  435. }else
  436. {
  437. sel = true;
  438. }
  439. if (sel == false)
  440. {
  441. // std::cout << "cache hit!" << std::endl;
  442. // 接收缓存,转发到客户端
  443. char buffer[BUFSIZE];
  444. size_t start = 0;
  445. size_t bytesRead;
  446. while (1)
  447. {
  448. if (cache_.Get(url, buffer, start, bytesRead) == false)
  449. {
  450. perror("Error sending response to client");
  451. }
  452. // std::cout << "bytesRead: " << bytesRead << std::endl;
  453. if (bytesRead == 0) break;
  454. if (send(clientSocket, buffer, bytesRead, 0) == -1)
  455. {
  456. perror("Error sending response to client");
  457. closesocket(serverSocket);
  458. return FALSE;
  459. }
  460. }
  461. }else
  462. {
  463. httpRequest += MY_CRLF;
  464. // 发送HTTP请求到原服务器
  465. if (send(serverSocket, httpRequest.c_str(), httpRequest.size(), 0) == -1)
  466. {
  467. perror("Error sending request to server");
  468. closesocket(serverSocket);
  469. return FALSE;
  470. }
  471. // 接收原服务器的HTTP响应
  472. char buffer[BUFSIZE];
  473. size_t start = 0;
  474. ssize_t bytesRead;
  475. while ((bytesRead = recv(serverSocket, buffer, BUFSIZE - 1, 0)) > 0)
  476. {
  477. buffer[bytesRead] = '\0';
  478. // 发送接收到的数据到客户端
  479. if (send(clientSocket, buffer, bytesRead, 0) == -1)
  480. {
  481. perror("Error sending response to client");
  482. closesocket(serverSocket);
  483. return FALSE;
  484. }
  485. if(cache_.Put(url, buffer, bytesRead, start) == false)
  486. {
  487. std::cerr << "Cache put error" << std::endl;
  488. }
  489. }
  490. cache_.PutDate(url);
  491. if (! cache_.Check(url))
  492. {
  493. cache_.ClearFileContent(url);
  494. }
  495. }
  496. // 关闭原服务器连接
  497. closesocket(serverSocket);
  498. return TRUE;
  499. }
  500. bool IsResponseNotModified(const std::string& response) {
  501. // 查找第一个空格,定位到状态码的开始
  502. size_t spacePos = response.find(' ');
  503. if (spacePos != std::string::npos) {
  504. // 提取状态码部分
  505. std::string statusCode = response.substr(spacePos + 1, 3);
  506. // 检查状态码是否为 "304"
  507. return (statusCode == "304"); // HTTP/1.1 304 Not Modified
  508. }
  509. return false; // 未找到状态码
  510. }
  511. };
  512. bool InitWinsock()
  513. {
  514. // 加载套接字库(必须)
  515. WORD wVersionRequested;
  516. WSADATA wsaData;
  517. // 套接字加载时错误提示
  518. int err;
  519. // 版本 2.2
  520. wVersionRequested = MAKEWORD(2, 2);
  521. // 加载 dll 文件 Scoket 库
  522. err = WSAStartup(wVersionRequested, &wsaData);
  523. if (err != 0)
  524. {
  525. // 找不到 winsock.dll
  526. printf("加载 winsock 失败,错误代码为: %d\n", WSAGetLastError());
  527. return FALSE;
  528. }
  529. if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
  530. {
  531. printf("不能找到正确的 winsock 版本\n");
  532. return FALSE;
  533. }
  534. return TRUE;
  535. }
  536. int main()
  537. {
  538. if (!InitWinsock())
  539. {
  540. WSACleanup();
  541. return -1; // 初始化失败,退出程序
  542. }
  543. int port = 666; // 设置端口
  544. HttpProxyServer proxyServer(port);
  545. proxyServer.Start();
  546. WSACleanup(); // 在程序结束时清理Winsock库
  547. return 0;
  548. }

HTTP 代理服务器的设计与实现(C++)的更多相关文章

  1. 哈工大 计算机网络 实验一 HTTP 代理服务器的设计与实现

    计算机网络实验代码与文件可见github:计算机网络实验整理 实验名称 HTTP 代理服务器的设计与实现 实验目的: 熟悉并掌握 Socket 网络编程的过程与技术:深入理解 HTTP 协议, 掌握 ...

  2. Flask 教程 第二十三章:应用程序编程接口(API)

    本文翻译自The Flask Mega-Tutorial Part XXIII: Application Programming Interfaces (APIs) 我为此应用程序构建的所有功能都只适 ...

  3. 优酷、YouTube、Twitter及JustinTV视频网站架构设计笔记

    本文是整理的关于优酷.YouTube.Twitter及JustinTV几个视频网站的架构或笔记,对于不管是视频网站.门户网站或者其它的网站,在架构上都有一定的参考意义,毕竟成功者的背后总有值得学习的地 ...

  4. 如何设计一个RPC系统

    版权声明:本文由韩伟原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/162 来源:腾云阁 https://www.qclou ...

  5. 代理服务器基本知识普及代理IP使用方法!

    本文并未从专业角度进行详细讲解,而是从应用的角度出发来普及一些代理服务器的基本知识.文章明显是搜集多方资料的拼凑,而且比较老了,但往往越老的东西越接近事物的本质,更容易窥探到原理,对于刚接触的人来说, ...

  6. 【翻译】使用nginx作为反向代理服务器,uWSGI作为应用服务器来部署flask应用

    最近在看关于Docker和Nginx方面的内容,先于在Docker上开发以及部署python应用自然要先能够在本机上部署,其中找到一篇文章写的最为详细并且实验成功,所以在此翻译转载过来以备后需.[原文 ...

  7. 借助nginx搭建反向代理服务器小例

    1 反向代理: 反向代理(Reverse Proxy)方式是指以代理服务器接收internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接 ...

  8. 微服务从设计到部署(二)使用 API 网关

    链接:https://github.com/oopsguy/microservices-from-design-to-deployment-chinese 译者:Oopsguy 本书的七个章节是关于设 ...

  9. 推荐一个比FiddlerCore好用的HTTP(S)代理服务器

    为什么不用FiddlerCore? 说到FiddlerCore大家可能会比较陌生,那么它哥Fiddler就比较熟悉了:抓包.模拟低带宽.修改请求我平时比较常用.Fiddler的本质就是一个HTTP代理 ...

  10. 开发一个http代理服务器

    参考链接: http://www.cnblogs.com/jivi/archive/2013/03/10/2952860.html https://www.2cto.com/kf/201405/297 ...

随机推荐

  1. DotNetGuide新增C#/.NET/.NET Core充电站(让你学习不迷路)

    DotNetGuide简介 记录.收集和总结C#/.NET/.NET Core基础知识.学习路线.开发实战.学习视频.文章.书籍.项目框架.社区组织.开发必备工具.常见面试题.面试须知.简历模板.以及 ...

  2. 整理php防注入和XSS攻击通用过滤

    对网站发动XSS攻击的方式有很多种,仅仅使用php的一些内置过滤函数是对付不了的,即使你将filter_var,mysql_real_escape_string,htmlentities,htmlsp ...

  3. 其它——windows提示缺少VCRUNTIME140.dll错误

    文章目录 一 原因 二 解决方法一 三 解决方法二 缺少了Microsoft.Net.Framework的安装 一 原因 最新在系统安装一些软件发现提示 这是因为缺少了一下windows运行需要的库 ...

  4. QQ机器人整理合集

    QQ机器人有什么用呢? QQ机器人可以实现包括自动回复.定时推送.发送图片QQ机器人,营销圈用的比较多,可以开发各种自动功能等等.用其制作的QQ机器人程序 机器人框架+插件 小栗子机器人 官网:htt ...

  5. 自学一周python做的一个小游戏《大球吃小球》

    需求 1,显示一个窗口. 2,我们要做到的功能有鼠标点击屏幕生成小球. 3,生成的小球大小随机,颜色随机,向随机方向移动,速度也随机. 4,大的球碰到小球时可以吃掉小球,吃掉后会变大. 5,球碰到边界 ...

  6. Lab3 存储过程与触发器

    实验三 存储过程与触发器  实验目的: 学习SQL语言进行编程的基本方法与技术,能够编写存储过程.触发器解决数据库需要处理的复杂问题.  实验内容: 1. 设计一个存储过程或者自定义函数,练习存储 ...

  7. 每天5分钟复习OpenStack(四) virsh 常用命令

    在上一章节中,我们拉起了第一台虚拟机,但是执行virsh shutdown 关机是无法关机的,需要使用virsh destroy 强制断电的命令来关机.为什么会这样了? 这里我们介绍下 QGA的概念 ...

  8. Util应用框架核心(二) - 启动器

    本节介绍 Util 项目启动初始化过程. 文章分为多个小节,如果对设计原理不感兴趣,只需阅读基础用法部分即可. 基础用法 查看 Util 服务配置,范例: var builder = WebAppli ...

  9. Redis 哨兵模式的原理及其搭建

    1.Redis哨兵 Redis提供了哨兵(Sentinel)机制来实现主从集群的自动故障恢复. 1.1.哨兵原理 1.1.1.集群结构和作用 哨兵的结构如图: 哨兵的作用如下: 监控:Sentinel ...

  10. mySql中使用命令行建表基本操作

    一:打开命令行启动mysql服务 注意事项:应该使用管理员身份打开命令行键入命令:net start mysql (鼠标右键使用管理员身份打开),否则会出现拒绝访问报错. 二:登陆数据库 登陆命令为& ...