Open dosmlp opened 1 week ago
https://github.com/qicosmos/cinatra/blob/master/tests/test_coro_http_server.cpp#L1485 看这个例子,代理服务器proxy_rr 代理了后面3个服务器,并通过轮询方式访问三个服务器。
@qicosmos 我怎么感觉这个反代有问题啊,比如下面的代码,127.0.0.1:9580是一个Everything的http服务
#include <iostream>
#include "cinatra.hpp"
using namespace cinatra;
using namespace std;
int main()
{
coro_http_server proxy_rr(4, 8091);
proxy_rr.set_http_proxy_handler<GET, POST>(
"/([^]+)", {"127.0.0.1:9580"},
coro_io::load_blance_algorithm::RR, {});
proxy_rr.async_start();
while (1) {
Sleep(1);
}
return 0;
}
然后我直接访问http://127.0.0.1:8091/C:
并没响应任何内容
用wireshark抓包查看,反代到everything的数据都是正常的,但是反代到浏览器的响应是空的
抓包内容附件
cinatra.zip
反代到everything
GET /C: HTTP/1.1
sec-ch-ua: "Chromium";v="124", "Microsoft Edge";v="124", "Not-A.Brand";v="99"
Host: 127.0.0.1
Sec-Fetch-Mode: navigate
Connection: keep-alive
Upgrade-Insecure-Requests: 1
sec-ch-ua-platform: "Windows"
Cache-Control: max-age=0
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: zh-CN,zh;q=0.9
HTTP/1.0 200 OK
Server: Everything HTTP Server
Content-Type: text/html; charset=UTF-8
Date: Wed, 20 Nov 2024 17:52:54 GMT
Cache-Control: no-cache, no-store, must-revalidate
Pragma: no-cache
Expires: 0
Connection: Close
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"><meta name="viewport" content="width=512"><meta name="robots" content="noindex, nofollow"><title>C: - Everything</title>
<link rel="stylesheet" href="/main.css" type="text/css">
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">
</head>
<body><center>
<br>
<br>
<a href="/"><img class="logo" src="/Everything.gif" alt="Everything"></a>
<br>.......................此处省略
反代到浏览器
GET /C: HTTP/1.1
Host: 127.0.0.1:8091
Connection: keep-alive
Cache-Control: max-age=0
sec-ch-ua: "Chromium";v="124", "Microsoft Edge";v="124", "Not-A.Brand";v="99"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: zh-CN,zh;q=0.9
HTTP/1.1 200 OK
Content-Length: 0
Server: Everything HTTP Server
Content-Type: text/html; charset=UTF-8
Date: Wed, 20 Nov 2024 17:52:54 GMT
Cache-Control: no-cache, no-store, must-revalidate
Pragma: no-cache
Expires: 0
Connection: Close
同样尝试了反代alist,alist的响应为Transfer-Encoding: chunked 但是到了反代-浏览器这边,虽然标头还是Transfer-Encoding: chunked,响应内容已经完全丢掉了chunked格式
不知道你说的代理到浏览器是什么意思,测试代码也不完整。
调试了半天源码发现了两个问题 第一:http client中处理了chunked,也就是说body中已经不是chunked格式了,但是标头中依然存在Transfer-Encoding: chunked, 这样反向代理后,浏览器访问反代服务器,收到这种奇怪的数据导致产生错误 第二:某些http1.0协议中标头并没有content-length字段,我看源码中如果没有该字段是直接把body_len_设为了0,这样就接收不到body数据了
chunked数据不存在body_ 里面,body_里面存的是有length的数据,chunked是流式的存在chunked_buf_里面。不管存在哪里,都会通过resp_data 返回出去。
如果有问题欢迎帮助提个pr修复一下。
1、首先说一下http1.0中content-length的问题,虽然是个古老协议,但万一有人踩坑了好知道怎么回事。 参考连接:https://www.w3.org/Protocols/HTTP/1.0/draft-ietf-http-spec.html#BodyLength 查看7.2.2的描述,服务端的响应可能不存在content-length,这时候客户端需要读取到服务端关闭连接以确定body的长度 cinatra的http client对于服务端响应中不存在content-length的处理是直接认为bodylen=0,这样就读取不到body了 2、chunked问题 假如服务端有这样一个响应
Content-Type: text/html
Date: Fri, 22 Nov 2024 07:40:43 GMT
Transfer-Encoding: chunked
800
{0x800个字节的内容}
741
{0x741个字节的内容}
0
但是cinatra的http client处理后变成了
Content-Type: text/html
Date: Fri, 22 Nov 2024 07:40:43 GMT
Transfer-Encoding: chunked
{0x800个字节的内容}
{0x741个字节的内容}
显然标头中Transfer-Encoding: chunked依然存在,但body已经不是chunked了
谢谢,我晚点构造一下chunked的case看一下什么情况。 1.0那个也可以支持一下。
后面继续在这里讨论。
这个代码应该可以说明问题,http client因为反向代理返回的数据错误一直在试图用chunked解析,然后就卡住了
#include <iostream>
#include "cinatra.hpp"
using namespace cinatra;
using namespace std;
int main()
{
cinatra::coro_http_server server(1, 9001);
server.set_http_handler<cinatra::GET, cinatra::POST>(
"/test_chunked",
[](coro_http_request &req,
coro_http_response &resp) -> async_simple::coro::Lazy<void> {
resp.set_format_type(format_type::chunked);
bool ok;
if (ok = co_await resp.get_conn()->begin_chunked(); !ok) {
co_return;
}
std::vector<std::string> vec{"hello", " world", " ok"};
for (auto &str : vec) {
if (ok = co_await resp.get_conn()->write_chunked(str); !ok) {
co_return;
}
}
ok = co_await resp.get_conn()->end_chunked();
});
server.set_http_handler<cinatra::GET, cinatra::POST>(
"/test",
[](coro_http_request &req,
coro_http_response &resp){
resp.set_status_and_content(status_type::ok, "hello world");
});
server.async_start();
//反向代理
coro_http_server proxy_rr(2, 8001);
proxy_rr.set_http_proxy_handler<GET, POST>(
"/([^]+)", {"127.0.0.1:9001"},
coro_io::load_blance_algorithm::RR);
proxy_rr.async_start();
std::this_thread::sleep_for(200ms);
coro_http_client client{};
auto result = client.get("http://127.0.0.1:8001/test_chunked");
for (const auto& h:result.resp_headers) {
cerr << h.name << ":" << h.value << "\n";
}
cerr << result.resp_body;
while(1) std::this_thread::sleep_for(200ms);
return 0;
}
好的,晚点看一下这个case。
这个问题是因为反代的时候把chunked的数据解析之后再发给client的,同时header里又保持了服务端响应的header,里面包括了chunked,但实际上反代服务器返回的不是chunked格式数据,从而引起了问题。
一种解决方法是如果是chunked multipart 这种格式的数据时把对应的header删掉,按照length 返回给client。 这种方法能解决问题但似乎把chunked协议做了修改,另外一个问题是如果chunked 的数据很大的时候,把结果保存到内存里也不现实。这种解决方法不太好。
另外一种解决犯法是完全透明的反代,即反代服务器完全不解析http 协议,收到什么就转发什么,这种情况下client 端收到的数据和代理的服务器响应的数据格式完全一样。但这种方法有个问题是如果用户希望校验服务端响应的http 头的时候就不行了,这时候不得不延迟到client 端去检查了。
现在的反代是先解析了http 协议再返回给client,只适合有length的场景。
我的想法是改进现在的反代,先解析http 头,允许用户传入检查http头的回调函数,通过http头可以得知是否含有length,如果检查通过则将http 头响应给client,再读body返回给client;如果为chunked,multipart等格式时,则按流的内容实时响应给client。这样就能灵活适配多种格式了,并且还支持用户检查http 头。有必要的话也可以增加body内容检查的回调函数。
透明反代是stream based的反代;现在的反代是http based的反代。
你的想法如何?是透明代理好还是改进现在的反代好? @dosmlp
先简单改一版,反代响应的时候把header里面的chunked multipart删掉,这样就能正常返回给client了。虽然不是一个好方法,先解决问题。后面再持续改进。
如果要做一个功能完善的反代,request和response的标头都要处理,甚至body还会修改,比如增加压缩。 要实现这些,透明代理显然不行,还是改进现有反代吧
这个示例似乎跑不起来啊,代码里似乎也没有反向代理哦 reverse_proxy proxy_rr(10, 8091); proxy_rr.add_dest_host("127.0.0.1:9001"); proxy_rr.add_dest_host("127.0.0.1:9002"); proxy_rr.add_dest_host("127.0.0.1:9003"); proxy_rr.start_reverse_proxy<GET, POST>("/rr", true, coro_io::load_blance_algorithm::RR);