@@ -19,6 +19,7 @@ limitations under the License.
1919#include < algorithm>
2020#include < random>
2121#include < photon/common/alog-stdstring.h>
22+ #include < photon/common/estring.h>
2223#include < photon/common/iovector.h>
2324#include < photon/common/string_view.h>
2425#include < photon/net/socket.h>
@@ -37,9 +38,9 @@ class PooledDialer {
3738public:
3839 net::TLSContext* tls_ctx = nullptr ;
3940 std::unique_ptr<ISocketClient> tcpsock;
40- std::unique_ptr<ISocketClient> tlssock;
4141 std::unique_ptr<ISocketClient> udssock;
4242 std::unique_ptr<Resolver> resolver;
43+ std::unique_ptr<ISocketPool> tcp_pool;
4344 photon::mutex init_mtx;
4445 bool initialized = false ;
4546 bool tls_ctx_ownership = false ;
@@ -61,73 +62,174 @@ class PooledDialer {
6162 tls_ctx = new_tls_context (nullptr , nullptr , nullptr );
6263 tls_ctx->set_verify_mode (VerifyMode::PEER ); // act like curl
6364 }
64- auto tcp_cli = new_tcp_socket_client (src_ips.data (), src_ips.size ());
65- auto tls_cli = new_tls_client (tls_ctx, new_tcp_socket_client (src_ips.data (), src_ips.size ()), true );
66- tcpsock.reset (new_tcp_socket_pool (tcp_cli, -1 , true ));
67- tlssock.reset (new_tcp_socket_pool (tls_cli, -1 , true ));
65+ tcpsock.reset (new_tcp_socket_client (src_ips.data (), src_ips.size ()));
6866 udssock.reset (new_uds_client ());
67+ tcp_pool.reset (new_tcp_socket_pool (nullptr , -1ULL , false ));
6968 resolver.reset (new_default_resolver (kDNSCacheLife ));
7069 initialized = true ;
7170 return 0 ;
7271 }
7372
7473 void at_photon_fini () {
74+ tcp_pool.reset ();
7575 resolver.reset ();
7676 udssock.reset ();
77- tlssock.reset ();
7877 tcpsock.reset ();
7978 if (tls_ctx_ownership)
8079 delete tls_ctx;
8180 initialized = false ;
8281 tls_ctx_ownership = false ;
8382 }
8483
85- ISocketStream* dial (std::string_view host, uint16_t port, bool secure,
86- uint64_t timeout = - 1ULL );
84+ // Single entry point: choose dial method based on Operation proxy config.
85+ ISocketStream* dial (Client::Operation* op, Timeout tmo );
8786
88- template <typename T>
89- ISocketStream* dial (const T& x, uint64_t timeout = -1ULL ) {
90- return dial (x.host_no_port (), x.port (), x.secure (), timeout);
87+ ISocketStream* dial (std::string_view host, uint16_t port, bool secure, uint64_t timeout);
88+
89+ private:
90+ // Key format for pool connection grouping:
91+ // direct/proxy: <host>:<port>:<0|1> (0=TCP, 1=TLS)
92+ // tunnel: <proxy_host>:<proxy_port>:<s|p>:<target_host>:<target_port>[:<auth>]
93+ static std::string make_key (std::string_view host, uint16_t port, bool secure) {
94+ return estring ().appends (host, " :" , port, " :" , secure ? " 1" : " 0" );
9195 }
9296
93- ISocketStream* dial (std::string_view uds_path, uint64_t timeout = -1ULL );
94- };
97+ static std::string make_tunnel_key (const StoredURL& proxy, std::string_view target_host,
98+ uint16_t target_port, const Headers* headers) {
99+ auto auth = headers ? headers->get_value (" Proxy-Authorization" ) : std::string_view{};
100+ auto phost = proxy.host_no_port ();
101+ estring key;
102+ key.appends (phost, " :" , proxy.port (), " :" , proxy.secure () ? " s" : " p" ,
103+ " :" , target_host, " :" , target_port,
104+ estring::make_conditional_cat_list (!auth.empty (), " :" , auth));
105+ return std::string (std::move (key));
106+ }
95107
96- ISocketStream* PooledDialer::dial (std::string_view host, uint16_t port, bool secure, uint64_t timeout) {
97- LOG_DEBUG (" Dialing to `:`" , host, port);
98- auto ipaddr = resolver->resolve (host);
99- if (ipaddr.undefined ()) {
100- LOG_ERROR_RETURN (ENOENT , nullptr , " DNS resolve failed, name = `" , host)
108+ // Factory: DNS resolve -> TCP connect -> optional TLS wrap with SNI.
109+ // Discards the resolved IP from cache on connection failure.
110+ ISocketStream* make_stream (std::string_view host, uint16_t port, bool secure, uint64_t timeout) {
111+ auto ip = resolver->resolve (host);
112+ if (ip.undefined ())
113+ LOG_ERROR_RETURN (ENOENT , nullptr , " DNS resolve failed, name=`" , host);
114+ EndPoint ep (ip, port);
115+ tcpsock->timeout (timeout);
116+ auto stream = tcpsock->connect (ep);
117+ if (!stream) {
118+ resolver->discard_cache (host, ip);
119+ LOG_ERROR_RETURN (0 , nullptr , " connection failed, ep=` host=` secure=`" , ep, host, secure);
120+ }
121+ if (secure) {
122+ stream = new_tls_stream (tls_ctx, stream, SecurityRole::Client, true );
123+ if (!stream)
124+ LOG_ERRNO_RETURN (0 , nullptr , " TLS handshake failed, host=`" , host);
125+ tls_stream_set_hostname (stream, std::string (host).c_str ());
126+ }
127+ LOG_DEBUG (" Connected ` ssl:` host:`" , ep, secure, host);
128+ return stream;
101129 }
102130
103- EndPoint ep (ipaddr, port);
104- LOG_DEBUG ( " Connecting ` ssl: ` " , ep, secure);
105- ISocketStream *sock = nullptr ;
106- if (secure ) {
107- tlssock-> timeout (timeout );
108- sock = tlssock-> connect (ep);
109- tls_stream_set_hostname (sock, estring_view (host). extract_c_str ());
110- } else {
131+ // Factory: full CONNECT tunnel handshake sequence.
132+ // DNS resolve proxy -> TCP -> [TLS to proxy] -> CONNECT handshake -> TLS to target.
133+ ISocketStream* make_tunnel_stream ( const StoredURL& proxy_url, std::string_view target_host,
134+ uint16_t target_port, const Headers* headers, uint64_t timeout ) {
135+ auto proxy_ip = resolver-> resolve (proxy_url. host_no_port () );
136+ if (proxy_ip. undefined ())
137+ LOG_ERROR_RETURN ( ENOENT , nullptr , " CONNECT tunnel: DNS resolve failed for proxy ` " , proxy_url. host_no_port ());
138+ EndPoint proxy_ep (proxy_ip, proxy_url. port ());
111139 tcpsock->timeout (timeout);
112- sock = tcpsock->connect (ep);
140+ auto stream = tcpsock->connect (proxy_ep);
141+ if (!stream) {
142+ resolver->discard_cache (proxy_url.host_no_port (), proxy_ip);
143+ LOG_ERRNO_RETURN (0 , nullptr , " CONNECT tunnel: failed to connect to proxy" );
144+ }
145+ stream->timeout (timeout);
146+ if (proxy_url.secure ()) {
147+ auto tls = new_tls_stream (tls_ctx, stream, SecurityRole::Client, true );
148+ if (!tls) {
149+ delete stream;
150+ LOG_ERRNO_RETURN (0 , nullptr , " CONNECT tunnel: TLS to proxy failed" );
151+ }
152+ stream = tls;
153+ tls_stream_set_hostname (stream, std::string (proxy_url.host_no_port ()).c_str ());
154+ }
155+ if (do_connect_handshake (stream, target_host, target_port, headers, timeout) != 0 ) {
156+ delete stream;
157+ return nullptr ;
158+ }
159+ auto tls = new_tls_stream (tls_ctx, stream, SecurityRole::Client, true );
160+ if (!tls) {
161+ delete stream;
162+ LOG_ERRNO_RETURN (0 , nullptr , " CONNECT tunnel: TLS to target failed" );
163+ }
164+ stream = tls;
165+ tls_stream_set_hostname (stream, std::string (target_host).c_str ());
166+ return stream;
167+ }
168+
169+ // Send CONNECT request and verify 2xx response.
170+ int do_connect_handshake (ISocketStream* stream, std::string_view target_host,
171+ uint16_t target_port, const Headers* headers, uint64_t timeout) {
172+ char buf[4096 ];
173+ estring authority;
174+ authority.appends (target_host, " :" , target_port);
175+ Request req (buf, (uint16_t )sizeof (buf), Verb::CONNECT , authority);
176+ if (headers) {
177+ for (auto it = headers->begin (); it != headers->end (); ++it)
178+ req.headers .insert (it.first (), it.second ());
179+ }
180+ if (req.send_header (stream) < 0 )
181+ LOG_ERRNO_RETURN (0 , -1 , " CONNECT tunnel: failed to send CONNECT request" );
182+
183+ // Response needs room for receive_bytes' MAX_TRANSFER_BYTES + RESERVED_INDEX_SIZE.
184+ char rbuf[8192 ];
185+ Response resp (rbuf, (uint16_t )sizeof (rbuf));
186+ resp.reset (stream, false );
187+ if (resp.receive_header (timeout) != 0 )
188+ LOG_ERRNO_RETURN (0 , -1 , " CONNECT tunnel: failed to read proxy response" );
189+ auto sc = resp.status_code ();
190+ if (sc < 200 || sc >= 300 )
191+ LOG_ERROR_RETURN (EINVAL , -1 , " CONNECT tunnel: proxy returned status `" , sc);
192+ return 0 ;
193+ }
194+ };
195+
196+ ISocketStream* PooledDialer::dial (Client::Operation* op, Timeout tmo) {
197+ auto * proxy = op->get_active_proxy ();
198+
199+ // HTTPS target + proxy: CONNECT tunnel.
200+ if (proxy && op->req .secure ()) {
201+ auto key = make_tunnel_key (*proxy, op->req .host_no_port (), op->req .port (), &op->proxy_header );
202+ return tcp_pool->connect (key, [&]() -> ISocketStream* {
203+ return make_tunnel_stream (*proxy, op->req .host_no_port (), op->req .port (), &op->proxy_header , tmo.timeout ());
204+ });
113205 }
114- if (sock) {
115- LOG_DEBUG (" Connected ` " , ep, VALUE (host), VALUE (secure));
116- return sock;
206+
207+ // HTTP proxy: connect to proxy endpoint.
208+ if (proxy) {
209+ auto key = make_key (proxy->host_no_port (), proxy->port (), proxy->secure ());
210+ return tcp_pool->connect (key, [&]() -> ISocketStream* {
211+ return make_stream (proxy->host_no_port (), proxy->port (), proxy->secure (), tmo.timeout ());
212+ });
117213 }
118- LOG_ERROR (" connection failed, ssl : ` ep : ` host : `" , secure, ep, host);
119- // When failed, remove resolved result from dns cache so that following retries can try
120- // different ips.
121- resolver->discard_cache (host, ipaddr);
122- return nullptr ;
214+
215+ // Unix domain socket.
216+ if (!op->uds_path .empty ()) {
217+ udssock->timeout (tmo.timeout ());
218+ return udssock->connect (op->uds_path .data ());
219+ }
220+
221+ // Direct TCP/TLS connection.
222+ auto key = make_key (op->req .host_no_port (), op->req .port (), op->req .secure ());
223+ return tcp_pool->connect (key, [&]() -> ISocketStream* {
224+ return make_stream (op->req .host_no_port (), op->req .port (), op->req .secure (), tmo.timeout ());
225+ });
123226}
124227
125- ISocketStream* PooledDialer::dial (std::string_view uds_path, uint64_t timeout) {
126- udssock->timeout (timeout);
127- auto stream = udssock->connect (uds_path.data ());
128- if (!stream)
129- LOG_ERRNO_RETURN (0 , nullptr , " failed to dial to unix socket `" , uds_path);
130- return stream;
228+ ISocketStream* PooledDialer::dial (std::string_view host, uint16_t port, bool secure, uint64_t timeout) {
229+ auto key = make_key (host, port, secure);
230+ return tcp_pool->connect (key, [&]() -> ISocketStream* {
231+ return make_stream (host, port, secure, timeout);
232+ });
131233}
132234
133235constexpr uint64_t code3xx () { return 0 ; }
@@ -141,19 +243,6 @@ constexpr static std::bitset<10>
141243
142244static constexpr size_t kMinimalHeadersSize = 8 * 1024 - 1 ;
143245
144- void Client::set_proxy (std::string_view proxy) {
145- m_proxy_url.from_string (proxy);
146- m_proxy = true ;
147- auto ui = m_proxy_url.user_passwd ();
148- if (!ui.empty ()) {
149- std::string encoded;
150- Base64Encode (ui, encoded);
151- m_proxy_auth = " Basic " + encoded;
152- } else {
153- m_proxy_auth.clear ();
154- }
155- }
156-
157246enum RoundtripStatus {
158247 ROUNDTRIP_SUCCESS ,
159248 ROUNDTRIP_FAILED ,
@@ -166,6 +255,7 @@ enum RoundtripStatus {
166255class ClientImpl : public Client {
167256public:
168257 CommonHeaders<> m_common_headers;
258+ CommonHeaders<> m_proxy_headers;
169259 TLSContext *m_tls_ctx;
170260 ICookieJar *m_cookie_jar;
171261 ClientImpl (ICookieJar *cookie_jar, TLSContext *tls_ctx) :
@@ -202,7 +292,7 @@ class ClientImpl : public Client {
202292 " invalid 3xx status code: " , op->status_code );
203293 }
204294
205- if (op->req .redirect (v, location, op->enable_proxy ) < 0 ) {
295+ if (op->req .redirect (v, location, op->get_active_proxy () != nullptr ) < 0 ) {
206296 LOG_ERRNO_RETURN (0 , ROUNDTRIP_FAILED , " redirect failed" );
207297 }
208298 return ROUNDTRIP_REDIRECT ;
@@ -213,15 +303,7 @@ class ClientImpl : public Client {
213303 if (tmo.timeout () == 0 )
214304 LOG_ERROR_RETURN (ETIMEDOUT , ROUNDTRIP_FAILED , " connection timedout" );
215305 auto &req = op->req ;
216- ISocketStream* s;
217- if (op->enable_proxy && !op->proxy_url .empty ())
218- s = get_dialer ().dial (op->proxy_url , tmo.timeout ());
219- else if (op->enable_proxy && !m_proxy_url.empty ())
220- s = get_dialer ().dial (m_proxy_url, tmo.timeout ());
221- else if (!op->uds_path .empty ())
222- s = get_dialer ().dial (op->uds_path , tmo.timeout ());
223- else
224- s = get_dialer ().dial (req, tmo.timeout ());
306+ ISocketStream* s = get_dialer ().dial (op, tmo);
225307 if (!s) {
226308 if (errno == ECONNREFUSED || errno == ENOENT ) {
227309 LOG_ERROR_RETURN (0 , ROUNDTRIP_FAST_RETRY , " connection refused" )
@@ -304,10 +386,33 @@ class ClientImpl : public Client {
304386 op->req .headers .insert (" User-Agent" , m_user_agent.empty () ? std::string_view (USERAGENT )
305387 : std::string_view (m_user_agent));
306388 op->req .headers .insert (" Connection" , " keep-alive" );
307- if (op->enable_proxy && !m_proxy_auth.empty ())
308- op->req .headers .insert (" Proxy-Authorization" , m_proxy_auth);
309389 if (m_cookie_jar && m_cookie_jar->set_cookies_to_headers (&op->req ) != 0 )
310390 LOG_ERROR_RETURN (0 , -1 , " set_cookies_to_headers failed" );
391+
392+ // Proxy header assembly: op proxy_header <- client proxy_headers <- URL auth
393+ auto proxy = op->get_active_proxy ();
394+ if (proxy) {
395+ op->proxy_header .merge (m_proxy_headers);
396+ if (!proxy->user_passwd ().empty ()) {
397+ std::string encoded;
398+ Base64Encode (proxy->user_passwd (), encoded);
399+ op->proxy_header .insert (" Proxy-Authorization" , " Basic " + encoded);
400+ }
401+ }
402+
403+ // Rebuild request line if necessary (absolute URI for HTTP proxy)
404+ auto need_abs = proxy && !op->req .secure ();
405+ auto is_abs = what_protocol (estring_view (op->req .target ())) != 0 ;
406+ if (need_abs != is_abs) {
407+ op->req .redirect (op->req .verb (), op->req .target (), need_abs);
408+ }
409+
410+ // For HTTP/1.1 proxy: merge proxy_header into req.headers (sent with the request).
411+ // For HTTPS proxy: proxy_header stays separate (used by CONNECT handshake only).
412+ if (proxy && !op->req .secure ()) {
413+ op->req .headers .merge (op->proxy_header );
414+ }
415+
311416 Timeout tmo (std::min (op->timeout .timeout (), m_timeout));
312417 int retry = 0 , followed = 0 , ret = 0 ;
313418 uint64_t sleep_interval = 0 ;
@@ -346,6 +451,10 @@ class ClientImpl : public Client {
346451 CommonHeaders<>* common_headers () override {
347452 return &m_common_headers;
348453 }
454+
455+ CommonHeaders<>* proxy_headers () override {
456+ return &m_proxy_headers;
457+ }
349458};
350459
351460Client* new_http_client (ICookieJar *cookie_jar, TLSContext *tls_ctx) {
0 commit comments