1
- //! http-client implementation for async-h1.
1
+ //! http-client implementation for async-h1, with connecton pooling ("Keep-Alive") .
2
2
3
- use super :: { async_trait, Error , HttpClient , Request , Response } ;
3
+ use std:: fmt:: Debug ;
4
+ use std:: net:: SocketAddr ;
4
5
5
6
use async_h1:: client;
7
+ use async_std:: net:: TcpStream ;
8
+ use dashmap:: DashMap ;
9
+ use deadpool:: managed:: Pool ;
6
10
use http_types:: StatusCode ;
7
11
8
- /// Async-h1 based HTTP Client.
9
- #[ derive( Debug ) ]
12
+ #[ cfg( not( feature = "h1_client_rustls" ) ) ]
13
+ use async_native_tls:: TlsStream ;
14
+ #[ cfg( feature = "h1_client_rustls" ) ]
15
+ use async_tls:: client:: TlsStream ;
16
+
17
+ use super :: { async_trait, Error , HttpClient , Request , Response } ;
18
+
19
+ mod tcp;
20
+ mod tls;
21
+
22
+ use tcp:: { TcpConnWrapper , TcpConnection } ;
23
+ use tls:: { TlsConnWrapper , TlsConnection } ;
24
+
25
+ // This number is based on a few random benchmarks and see whatever gave decent perf vs resource use.
26
+ const DEFAULT_MAX_CONCURRENT_CONNECTIONS : usize = 50 ;
27
+
28
+ type HttpPool = DashMap < SocketAddr , Pool < TcpStream , std:: io:: Error > > ;
29
+ type HttpsPool = DashMap < SocketAddr , Pool < TlsStream < TcpStream > , Error > > ;
30
+
31
+ /// Async-h1 based HTTP Client, with connecton pooling ("Keep-Alive").
10
32
pub struct H1Client {
11
- _priv : ( ) ,
33
+ http_pools : HttpPool ,
34
+ https_pools : HttpsPool ,
35
+ max_concurrent_connections : usize ,
36
+ }
37
+
38
+ impl Debug for H1Client {
39
+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
40
+ f. write_str ( "H1Client" )
41
+ }
12
42
}
13
43
14
44
impl Default for H1Client {
@@ -20,13 +50,28 @@ impl Default for H1Client {
20
50
impl H1Client {
21
51
/// Create a new instance.
22
52
pub fn new ( ) -> Self {
23
- Self { _priv : ( ) }
53
+ Self {
54
+ http_pools : DashMap :: new ( ) ,
55
+ https_pools : DashMap :: new ( ) ,
56
+ max_concurrent_connections : DEFAULT_MAX_CONCURRENT_CONNECTIONS ,
57
+ }
58
+ }
59
+
60
+ /// Create a new instance.
61
+ pub fn with_max_connections ( max : usize ) -> Self {
62
+ Self {
63
+ http_pools : DashMap :: new ( ) ,
64
+ https_pools : DashMap :: new ( ) ,
65
+ max_concurrent_connections : max,
66
+ }
24
67
}
25
68
}
26
69
27
70
#[ async_trait]
28
71
impl HttpClient for H1Client {
29
72
async fn send ( & self , mut req : Request ) -> Result < Response , Error > {
73
+ req. insert_header ( "Connection" , "keep-alive" ) ;
74
+
30
75
// Insert host
31
76
let host = req
32
77
. url ( )
@@ -57,40 +102,58 @@ impl HttpClient for H1Client {
57
102
58
103
match scheme {
59
104
"http" => {
60
- let stream = async_std:: net:: TcpStream :: connect ( addr) . await ?;
105
+ let pool_ref = if let Some ( pool_ref) = self . http_pools . get ( & addr) {
106
+ pool_ref
107
+ } else {
108
+ let manager = TcpConnection :: new ( addr) ;
109
+ let pool = Pool :: < TcpStream , std:: io:: Error > :: new (
110
+ manager,
111
+ self . max_concurrent_connections ,
112
+ ) ;
113
+ self . http_pools . insert ( addr, pool) ;
114
+ self . http_pools . get ( & addr) . unwrap ( )
115
+ } ;
116
+
117
+ // Deadlocks are prevented by cloning an inner pool Arc and dropping the original locking reference before we await.
118
+ let pool = pool_ref. clone ( ) ;
119
+ std:: mem:: drop ( pool_ref) ;
120
+
121
+ let stream = pool. get ( ) . await ?;
61
122
req. set_peer_addr ( stream. peer_addr ( ) . ok ( ) ) ;
62
123
req. set_local_addr ( stream. local_addr ( ) . ok ( ) ) ;
63
- client:: connect ( stream, req) . await
124
+ client:: connect ( TcpConnWrapper :: new ( stream) , req) . await
64
125
}
65
126
"https" => {
66
- let raw_stream = async_std:: net:: TcpStream :: connect ( addr) . await ?;
67
- req. set_peer_addr ( raw_stream. peer_addr ( ) . ok ( ) ) ;
68
- req. set_local_addr ( raw_stream. local_addr ( ) . ok ( ) ) ;
69
- let tls_stream = add_tls ( host, raw_stream) . await ?;
70
- client:: connect ( tls_stream, req) . await
127
+ let pool_ref = if let Some ( pool_ref) = self . https_pools . get ( & addr) {
128
+ pool_ref
129
+ } else {
130
+ let manager = TlsConnection :: new ( host. clone ( ) , addr) ;
131
+ let pool = Pool :: < TlsStream < TcpStream > , Error > :: new (
132
+ manager,
133
+ self . max_concurrent_connections ,
134
+ ) ;
135
+ self . https_pools . insert ( addr, pool) ;
136
+ self . https_pools . get ( & addr) . unwrap ( )
137
+ } ;
138
+
139
+ // Deadlocks are prevented by cloning an inner pool Arc and dropping the original locking reference before we await.
140
+ let pool = pool_ref. clone ( ) ;
141
+ std:: mem:: drop ( pool_ref) ;
142
+
143
+ let stream = pool
144
+ . get ( )
145
+ . await
146
+ . map_err ( |e| Error :: from_str ( 400 , e. to_string ( ) ) ) ?;
147
+ req. set_peer_addr ( stream. get_ref ( ) . peer_addr ( ) . ok ( ) ) ;
148
+ req. set_local_addr ( stream. get_ref ( ) . local_addr ( ) . ok ( ) ) ;
149
+
150
+ client:: connect ( TlsConnWrapper :: new ( stream) , req) . await
71
151
}
72
152
_ => unreachable ! ( ) ,
73
153
}
74
154
}
75
155
}
76
156
77
- #[ cfg( not( feature = "h1_client_rustls" ) ) ]
78
- async fn add_tls (
79
- host : String ,
80
- stream : async_std:: net:: TcpStream ,
81
- ) -> Result < async_native_tls:: TlsStream < async_std:: net:: TcpStream > , async_native_tls:: Error > {
82
- async_native_tls:: connect ( host, stream) . await
83
- }
84
-
85
- #[ cfg( feature = "h1_client_rustls" ) ]
86
- async fn add_tls (
87
- host : String ,
88
- stream : async_std:: net:: TcpStream ,
89
- ) -> std:: io:: Result < async_tls:: client:: TlsStream < async_std:: net:: TcpStream > > {
90
- let connector = async_tls:: TlsConnector :: default ( ) ;
91
- connector. connect ( host, stream) . await
92
- }
93
-
94
157
#[ cfg( test) ]
95
158
mod tests {
96
159
use super :: * ;
0 commit comments