简易Web代理服务器实现

任务解析

任务要求实现一个 Web代理服务器,即浏览器访问网站时不会直接请求网站服务器,而是先请求我们编写的 Web代理服务器,再由代理服务器转发请求,接收响应,再转发给浏览器。

流程如下:

1
浏览器 -> 代理服务器 -> 目标服务器 -> 代理服务器 -> 浏览器

代理服务器:

  • 接收浏览器发来的 HTTP 请求;
  • 解析请求,建立到目标服务器的连接;
  • 转发请求;
  • 收到响应后转发回浏览器;
  • 支持多线程,能同时处理多个浏览器请求。

架构设计

1
2
3
4
5
6
7
8
9
10
11
main()
├─ 创建监听 socket(listenfd)
├─ bind() & listen()
└─ while (true):
├─ accept() 新连接(浏览器)
└─ 创建新线程 handle_client(connfd)
├─ 解析 HTTP 请求
├─ 连接目标服务器
├─ 转发请求
├─ 接收目标响应
└─ 转发响应给浏览器

核心模块划分

模块 功能
handle_client() 新线程,处理一个完整的代理过程
parse_request() 解析浏览器请求,提取 method、host、port、path
connect_remote() 连接目标服务器
relay_data() 数据转发:从浏览器→服务器,再从服务器→浏览器

完整代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>

#include <cstring>
#include <iostream>
#include <sstream>
#include <string>
#include <thread>

#define PORT 8888
#define BUF_SIZE 8192

// 解析HTTP请求中的host,port and path
bool parse_request(const std::string& request, std::string& host, int& port, std::string& path) {
std::istringstream iss(request); // 解析请求
std::string method, url, version; // 定义HTTP请求行
iss >> method >> url >> version; // ...

if (url.substr(0, 7) == "http://") {
url = url.substr(7); // 去除前面部分
}

size_t pos = url.find('/'); // find path
std::string host_port = (pos == std::string::npos) ? url : url.substr(0, pos);
path = (pos == std::string::npos) ? "/" : url.substr(pos);

size_t colon = host_port.find(':'); // find colon;
if (colon != std::string::npos) {
host = host_port.substr(0, colon);
port = std::stoi(host_port.substr(colon + 1));
} else {
host = host_port;
port = 80; // Default
}

return true;
}

// 建立与远程服务器的连接
int connect_remote(const std::string& host, int port) {
struct hostent* he = gethostbyname(host.c_str()); // DNS->IP
if (!he) {
std::cerr << "DNS resole failed for host: " << host << std::endl;
return -1;
}

int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) return -1;

struct sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
memcpy(&addr.sin_addr, he->h_addr, he->h_length);

if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
close(sock);
return -1;
}

return sock;
}

// 转发数据
void relay_data(int src, int dst) {
char buffer[BUF_SIZE];
ssize_t n;
while ((n = recv(src, buffer, BUF_SIZE, 0)) > 0) {
send(dst, buffer, n, 0);
}
}

// 线程回调函数,处理每个client请求
void handle_client(int client_sock) {
char buffer[BUF_SIZE];
ssize_t n = recv(client_sock, buffer, BUF_SIZE - 1, 0);
if (n < 0) {
close(client_sock);
return;
}
buffer[n] = '\0';
std::string request(buffer);

std::string host, path;
int port;
parse_request(request, host, port, path);

std::cout << "[INFO] Client requested " << host << ":" << port << path << std::endl;

int remote_sock = connect_remote(host, port);
if (remote_sock < 0) {
std::cerr << "[ERROR] Cannot connect to remote server" << std::endl;
close(client_sock);
return;
}

// 修改请求行: 把 GET http://host/path -> GET /path
size_t first_space = request.find(' ');
size_t second_space = request.find(' ', first_space + 1);
if (first_space != std::string::npos && second_space != std::string::npos) {
std::string new_req = request.substr(0, first_space + 1) + path + request.substr(second_space);
send(remote_sock, new_req.c_str(), new_req.size(), 0);
} else {
send(remote_sock, request.c_str(), request.size(), 0);
}

// 接收服务器响应并转发给客户端
relay_data(remote_sock, client_sock);

close(remote_sock);
close(client_sock);
}

int main() {
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
if (listenfd < 0) {
perror("socket");
return 1;
}

// 设置忽略 TIME_WAIT状态
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
struct sockaddr_in addr{};
addr.sin_family = AF_INET; // 地址族
addr.sin_port = htons(PORT); // 端口号
addr.sin_addr.s_addr = INADDR_ANY; // IP地址

if (bind(listenfd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
perror("bind");
return 1;
}

if (listen(listenfd, 10) < 0) {
perror("listen");
return 1;
}

std::cout << "[INFO] Proxy server listening on port " << PORT << "..." << std::endl;

while (true) {
struct sockaddr_in client_addr{};
socklen_t len = sizeof(client_addr);
int connfd = accept(listenfd, (struct sockaddr*)&client_addr, &len);
if (connfd < 0) continue;
std::thread(handle_client, connfd).detach();
}

close(listenfd);
return 0;
}

测试:
在终端中输入curl -x http://127.0.0.1:8888 http://www.lorixyu.life/2025/11/12/随地大小记/;