Skip to content
Go back

Asio HTTP-HTTPS 代理实现

Edit page

最近我们游戏需要接谷歌SDK, 比较麻烦的是国内无法直接访问谷歌接口,并且需要用https访问。由于moon只支持http client,所以想到用nginx来正向代理并实现http转https,然后使用本机的https代理请求谷歌。但实践后发现nginx代理后的请求并没有使用本机的https代理。首先想到一种解决方案是,手动配置nginx把请求发送到本机的https代理。http、https代理的机制如下:

代理请求结构

通常情况下我发送的http请求头里面的结构是:

GET /path?params HTTP 1.1\r\n

如过使用http代理,需要发送的结构是:

GET http://host:port/path?params HTTP/1.1\r\n

可以发现比正常请求多了http://host:port

但通过尝试,nginx好像无法做到转发代理请求。所以进一步了解了下HTTP代理处理流程,准备自己尝试开发。

HTTPS代理处理流程

  1. source socket 按\r\n读取一行获取 host,port
  2. 根据 host,port connect destination socket
  3. destination openssl handshake
  4. 把第一步读取到数据中http://host:port去掉,发送给destination
  5. 直接转发source-> destination, destination->source 的数据

HTTPS代理再次转发代理处理流程

  1. source socket 按\r\n读取一行获取 host,port
  2. 根据 host,port connect destination socket
  3. destination发送 CONNECT host:port HTTP/1.1\r\nHost: host:port\r\n\r\n
  4. \r\n\r\n读取destination,获取结果200OK
  5. destination openssl handshake
  6. 直接转发source-> destination, destination->source 的数据

使用asio封装的cpp20协程很容易实现上面流程:

    awaitable<bool> handshake(std::string_view proxy_host, uint16_t proxy_port)
    {
        asio::streambuf streambuf;
        size_t n = co_await asio::async_read_until(source, streambuf, "\r\n", use_awaitable);
        if (0 == n)
            co_return false;

        auto line = std::string_view{ (const char*)streambuf.data().data(), n };
        size_t method_end;
        if ((method_end = line.find(' ')) == std::string_view::npos)
        {
            co_return false;
        }

        auto method = line.substr(0, method_end);
        size_t query_start = std::string_view::npos;
        size_t path_and_query_string_end = std::string_view::npos;
        for (size_t i = method_end + 1; i < line.size(); ++i)
        {
            if (line[i] == '?' && (i + 1) < line.size())
            {
                query_start = i + 1;
            }
            else if (line[i] == ' ')
            {
                path_and_query_string_end = i;
                break;
            }
        }

        std::string_view scheme;
        std::string_view host;
        uint16_t port;
        std::string_view path;
        parse_url(line.substr(method_end + 1, path_and_query_string_end - method_end - 1), scheme, host, port, path);

        asio::ip::tcp::resolver resolver(parent->io_context());
        if (!proxy_host.empty())
        {
            auto endpoints = co_await resolver.async_resolve(proxy_host, std::to_string(proxy_port), use_awaitable);
            co_await asio::async_connect(destination.lowest_layer(), endpoints, use_awaitable);

            std::string host_port_string;
            host_port_string.append(host);
            host_port_string.append(":");
            host_port_string.append(std::to_string(port));

            std::string data;
            data.append("CONNECT ");
            data.append(host_port_string);
            data.append(" HTTP/1.1\r\n");
            data.append("Host: ");
            data.append(host_port_string);
            data.append("\r\n\r\n");

            co_await asio::async_write(destination.next_layer(), asio::buffer(data.data(), data.size()), use_awaitable);

            asio::streambuf readbuf;
            co_await asio::async_read_until(destination.next_layer(), readbuf, "\r\n\r\n", use_awaitable);
            if (0 == n)
                co_return false;

            LOG_INFO("recv proxy response: %s", std::string{ (const char*)readbuf.data().data(), readbuf.size() }.data());
        }
        else
        {
            auto endpoints = co_await resolver.async_resolve(host, std::to_string(port), use_awaitable);
            co_await asio::async_connect(destination.lowest_layer(), endpoints, use_awaitable);
        }

        SSL_set_tlsext_host_name(destination.native_handle(), std::string{host}.data());
        co_await destination.async_handshake(asio::ssl::stream_base::client, use_awaitable);

        if (proxy_host.empty())
        {
            std::string str;
            str.append(method);
            str.append(1, ' ');
            str.append(path);
            str.append(1, ' ');
            str.append(line.substr(path_and_query_string_end + 1));
            size_t num_additional_bytes = streambuf.size() - n;
            streambuf.consume(n);
            str.append((const char*)streambuf.data().data(), num_additional_bytes);
            co_await asio::async_write(destination, asio::buffer(str.data(), str.size()), use_awaitable);
        }
        else
        {
            co_await asio::async_write(destination, streambuf, use_awaitable);
        }
        co_return true;
    }

通过测试,满足了我们的需求


Edit page

Previous Post
Lua to-be-close 协程锁
Next Post
memcpy使用注意事项