从报文层面分析正向代理和反向代理的区别
概念
首先先简单解释下正向代理和反向代理的概念区别
正向代理
正向代理代表客户端向外部服务器发起请求,主要用于绕过限制或提升访问效率。
链路:客户端->代理服务器->目标
反向代理
反向代理则接收客户端请求并分发给后台服务器处理,旨在优化服务性能和增强安全性。
链路:客户端->代理服务器->目标
可见,从链路上来说,正向代理与反向代理几乎是相同的,但是从功能上来说,正向代理可以代表客户端发往几乎任意的目标服务器,而反向代理却只能将请求发往预先配置的目标。
那么他们实际上到底有什么不同呢?
报文分析
直接请求
直接使用curl发送请求
curl http://www.baidu.com
使用tcpdump抓包并丢到wireshark中分析,可得



正向代理
首先使用tinyproxy搭建一个正向代理,这里使用第三方构建的docker镜像来快速部署
docker run -d -p 6666:8888 dannydirect/tinyproxy:latest ANY
然后通过正向代理向http://www.baidu.com发送请求
curl -x 'http://127.0.0.1:6666' http://www.baidu.com
使用tcpdump进行抓包
sudo tcpdump -i any port 6666 or host www.baidu.com -w output.pcap
抓到的pcap包丢到wireshark中进行分析



可见,虽然发送的目标地址已经是127.0.0.1:6666了,但是实际上的HTTP请求报文依旧与不使用代理的时候一样。唯一的不同是多了一个proxy-connection: keep-alive请求头。
该请求头的作用是告诉代理服务器,尝试维持与代理服务器之间的一个持久连接。
反向代理
使用nginx配置一个简单的代理服务器,端口8888,由于对于客户端来说,不知道反向代理后是什么,因此,此时的报文与直接请求是类似的,只是里面所有与目标baidu.com有关的全都为反向代理服务器了。



正向代理如何转发请求?
由上可知,虽然正向代理与反向代理的链路是相同的。
但报文上存在一些差异,IPV4以及TCP的报文都是大同小异的
区别就在于HTTP报文中的Host请求头以及首行的路径。
查看tinyproxy相关代码,定位到reqs.c中的handle_connection函数中的代码段。
connptr->server_fd = opensock (request->host, request->port,
connptr->server_ip_addr);
可见,正向代理服务器是根据request->host, request->port建立与目标地址的连接的
而request是使用如下代码行获取的
request = process_request (connptr, hashofheaders);
定位到process_request中的
ret = sscanf (connptr->request_line, "%[^ ] %[^ ] %[^ ]",
request->method, url, request->protocol);
该行的作用是将connptr->request_line中的字符串进行解析,空格为分界符。
当正向代理时,connptr->request_line值为GET http://www.baidu.com/ HTTP/1.1,那么经过处理后,得到如下结果
request-method:GET
url:http://www.baidu.com/
request->protocol:HTTP/1.1
获得的url会经过extract_url函数处理
static int extract_url (const char *url, int default_port,
struct request_s *request)
{
char *p;
int port;
/* Split the URL on the slash to separate host from path */
p = strchr (url, '/');
if (p != NULL) {
int len;
len = p - url;
request->host = (char *) safemalloc (len + 1);
memcpy (request->host, url, len);
request->host[len] = '\0';
request->path = safestrdup (p);
} else {
request->host = safestrdup (url);
request->path = safestrdup ("/");
}
if (!request->host || !request->path)
goto ERROR_EXIT;
/* Remove the username/password if they're present */
strip_username_password (request->host);
/* Find a proper port in www.site.com:8001 URLs */
port = strip_return_port (request->host);
request->port = (port != 0) ? port : default_port;
/* Remove any surrounding '[' and ']' from IPv6 literals */
p = strrchr (request->host, ']');
if (p && (*(request->host) == '[')) {
memmove(request->host, request->host + 1,
strlen(request->host) - 2);
*p = '\0';
p--;
*p = '\0';
}
return 0;
ERROR_EXIT:
if (request->host)
safefree (request->host);
if (request->path)
safefree (request->path);
return -1;
}
最终得到
request->host:www.baidu.com
request->port:默认
request->path:/
因此,我们就能够最终确定目标服务器了,并且确定发送过去的报文。
结论
正向代理与反向代理虽然链路相同,但是由于HTTP报文的不同而带来截然不同的行为。
发送给正向代理的报文请求行中的Request-URI(统一资源标识符)带有全路径,以便与代理服务器从中获取到目标地址,从而实现转发。
之前还以为是通过Host请求头获取目标地址的,然后试着Nginx反代配置中直接用proxy-set-header Host xxxx,想着能实现通过正向代理向特定目标转发请求,现在看来这样无法实现。
以上仅为个人的浅薄分析,如有错误请指正。