Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

反向代理的问题 #649

Open
dosmlp opened this issue Nov 19, 2024 · 13 comments
Open

反向代理的问题 #649

dosmlp opened this issue Nov 19, 2024 · 13 comments

Comments

@dosmlp
Copy link

dosmlp commented Nov 19, 2024

这个示例似乎跑不起来啊,代码里似乎也没有反向代理哦
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);

@qicosmos
Copy link
Owner

https://github.com/qicosmos/cinatra/blob/master/tests/test_coro_http_server.cpp#L1485
看这个例子,代理服务器proxy_rr 代理了后面3个服务器,并通过轮询方式访问三个服务器。

@dosmlp dosmlp changed the title 反向代理在哪里 反向代理的问题 Nov 20, 2024
@dosmlp
Copy link
Author

dosmlp commented Nov 20, 2024

@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格式

@qicosmos
Copy link
Owner

不知道你说的代理到浏览器是什么意思,测试代码也不完整。

@dosmlp
Copy link
Author

dosmlp commented Nov 21, 2024

调试了半天源码发现了两个问题
第一:http client中处理了chunked,也就是说body中已经不是chunked格式了,但是标头中依然存在Transfer-Encoding: chunked,
这样反向代理后,浏览器访问反代服务器,收到这种奇怪的数据导致产生错误
第二:某些http1.0协议中标头并没有content-length字段,我看源码中如果没有该字段是直接把body_len_设为了0,这样就接收不到body数据了
image

@qicosmos
Copy link
Owner

chunked数据不存在body_ 里面,body_里面存的是有length的数据,chunked是流式的存在chunked_buf_里面。不管存在哪里,都会通过resp_data 返回出去。

如果有问题欢迎帮助提个pr修复一下。

@dosmlp
Copy link
Author

dosmlp commented Nov 22, 2024

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的处理是直接认为body_len_=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了

@qicosmos
Copy link
Owner

谢谢,我晚点构造一下chunked的case看一下什么情况。
1.0那个也可以支持一下。

后面继续在这里讨论。

@dosmlp
Copy link
Author

dosmlp commented Nov 23, 2024

这个代码应该可以说明问题,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;
}

@qicosmos
Copy link
Owner

好的,晚点看一下这个case。

@qicosmos
Copy link
Owner

qicosmos commented Nov 25, 2024

这个问题是因为反代的时候把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

@qicosmos
Copy link
Owner

先简单改一版,反代响应的时候把header里面的chunked multipart删掉,这样就能正常返回给client了。虽然不是一个好方法,先解决问题。后面再持续改进。

@qicosmos
Copy link
Owner

#653

@dosmlp
Copy link
Author

dosmlp commented Nov 27, 2024

如果要做一个功能完善的反代,request和response的标头都要处理,甚至body还会修改,比如增加压缩。
要实现这些,透明代理显然不行,还是改进现有反代吧

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants